org.drugis.addis.gui.wizard.AddStudyWizard.java Source code

Java tutorial

Introduction

Here is the source code for org.drugis.addis.gui.wizard.AddStudyWizard.java

Source

/*
 * This file is part of ADDIS (Aggregate Data Drug Information System).
 * ADDIS is distributed from http://drugis.org/.
 * Copyright  2009 Gert van Valkenhoef, Tommi Tervonen.
 * Copyright  2010 Gert van Valkenhoef, Tommi Tervonen, Tijs Zwinkels,
 * Maarten Jacobs, Hanno Koeslag, Florin Schimbinschi, Ahmad Kamal, Daniel
 * Reid.
 * Copyright  2011 Gert van Valkenhoef, Ahmad Kamal, Daniel Reid, Florin
 * Schimbinschi.
 * Copyright  2012 Gert van Valkenhoef, Daniel Reid, Jol Kuiper, Wouter
 * Reckman.
 * Copyright  2013 Gert van Valkenhoef, Jol Kuiper.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.drugis.addis.gui.wizard;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DropMode;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.border.Border;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableModel;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultFormatter;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.StyledDocument;

import org.apache.commons.math3.util.Pair;
import org.drugis.addis.FileNames;
import org.drugis.addis.entities.AdverseEvent;
import org.drugis.addis.entities.Arm;
import org.drugis.addis.entities.BasicStudyCharacteristic;
import org.drugis.addis.entities.Endpoint;
import org.drugis.addis.entities.Epoch;
import org.drugis.addis.entities.Indication;
import org.drugis.addis.entities.Note;
import org.drugis.addis.entities.ObjectWithNotes;
import org.drugis.addis.entities.PopulationCharacteristic;
import org.drugis.addis.entities.PubMedIdList;
import org.drugis.addis.entities.Source;
import org.drugis.addis.entities.Study;
import org.drugis.addis.entities.StudyActivity;
import org.drugis.addis.entities.StudyOutcomeMeasure;
import org.drugis.addis.entities.TypeWithNotes;
import org.drugis.addis.gui.AddisWindow;
import org.drugis.addis.gui.AuxComponentFactory;
import org.drugis.addis.gui.CategoryKnowledgeFactory;
import org.drugis.addis.gui.GUIFactory;
import org.drugis.addis.gui.Main;
import org.drugis.addis.gui.builder.StudyView;
import org.drugis.addis.gui.components.ComboBoxPopupOnFocusListener;
import org.drugis.addis.gui.components.MeasurementTable;
import org.drugis.addis.gui.components.NotesView;
import org.drugis.addis.gui.util.ComboBoxSelectionModel;
import org.drugis.addis.gui.util.NonEmptyValueModel;
import org.drugis.addis.imports.PubMedIDRetriever;
import org.drugis.addis.presentation.AbstractListValidator;
import org.drugis.addis.presentation.DurationPresentation;
import org.drugis.addis.presentation.ModifiableHolder;
import org.drugis.addis.presentation.StudyActivitiesTableModel;
import org.drugis.addis.presentation.ValueHolder;
import org.drugis.addis.presentation.wizard.AddArmsPresentation;
import org.drugis.addis.presentation.wizard.AddEpochsPresentation;
import org.drugis.addis.presentation.wizard.AddStudyWizardPresentation;
import org.drugis.addis.presentation.wizard.AddStudyWizardPresentation.OutcomeMeasurementsModel;
import org.drugis.addis.presentation.wizard.StudyActivityPresentation;
import org.drugis.addis.util.PubMedListFormat;
import org.drugis.addis.util.RunnableReadyModel;
import org.drugis.common.beans.ContentAwareListModel;
import org.drugis.common.gui.ErrorDialog;
import org.drugis.common.gui.LayoutUtil;
import org.drugis.common.gui.TextComponentFactory;
import org.drugis.common.validation.BooleanAndModel;
import org.drugis.common.validation.BooleanNotModel;
import org.pietschy.wizard.AbstractWizardModel;
import org.pietschy.wizard.PanelWizardStep;
import org.pietschy.wizard.Wizard;
import org.pietschy.wizard.WizardEvent;
import org.pietschy.wizard.WizardListener;
import org.pietschy.wizard.models.StaticModel;

import com.jgoodies.binding.PresentationModel;
import com.jgoodies.binding.adapter.BasicComponentFactory;
import com.jgoodies.binding.adapter.Bindings;
import com.jgoodies.binding.beans.PropertyAdapter;
import com.jgoodies.binding.beans.PropertyConnector;
import com.jgoodies.binding.list.ObservableList;
import com.jgoodies.binding.value.AbstractValueModel;
import com.jgoodies.binding.value.ValueModel;
import com.jgoodies.forms.builder.ButtonBarBuilder2;
import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import com.toedter.calendar.JDateChooser;

@SuppressWarnings("serial")
public class AddStudyWizard extends Wizard {
    private static final String EXAMPLE_NCT_ID = "NCT00296517";
    public static final String INSERT_EXAMPLE_ID = "control I";

    public AddStudyWizard(final AddStudyWizardPresentation pm, final AddisWindow mainWindow, JDialog dialog) {
        super(buildModel(pm, mainWindow, dialog));

        setDefaultExitMode(Wizard.EXIT_ON_FINISH);
        addWizardListener(new WizardListener() {
            public void wizardClosed(WizardEvent e) {
                mainWindow.leftTreeFocus(pm.saveStudy());
            }

            public void wizardCancelled(WizardEvent e) {
                mainWindow.leftTreeFocus(pm.getOldStudy());
            }
        });
        setOverviewVisible(false);
    }

    private static AbstractWizardModel buildModel(final AddStudyWizardPresentation pm, AddisWindow mainWindow,
            JDialog dialog) {
        StaticModel wizardModel = new StaticModel();
        wizardModel.add(new EnterIdTitleWizardStep(dialog, pm));
        wizardModel.add(new SelectIndicationWizardStep(pm, mainWindow));
        wizardModel.add(new EnterCharacteristicsWizardStep(pm));

        wizardModel.add(new AddArmsWizardStep(dialog, pm.getAddArmsModel()));
        wizardModel.add(new AddEpochsWizardStep(dialog, pm.getAddEpochsModel()));
        wizardModel.add(new AssignActivitiesWizardStep(dialog, pm, mainWindow));

        wizardModel.add(new SelectEndpointWizardStep(dialog, pm));
        wizardModel.add(new SetEndpointMeasurementsWizardStep(dialog, pm));
        wizardModel.add(new SelectAdverseEventWizardStep(dialog, pm));
        wizardModel.add(new SetAdverseEventMeasurementsWizardStep(dialog, pm));
        wizardModel.add(new SelectPopulationCharsWizardStep(dialog, pm));
        wizardModel.add(new SetPopulationCharMeasurementsWizardStep(dialog, pm));
        wizardModel.add(new ReviewStudyStep(dialog, pm, mainWindow));

        wizardModel.setLastVisible(false);
        // The measurements + variable lists are saved on viewing the measurement tables
        // unless this is changed, skipping steps should be disabled.

        return wizardModel;
    }

    // FIXME: should probably be in presentation
    private static ObjectWithNotes<?> getCharWithNotes(Study newStudy, BasicStudyCharacteristic schar) {
        ObjectWithNotes<?> charWithNotes = newStudy.getCharacteristicWithNotes(schar);
        if (charWithNotes == null) {
            newStudy.setCharacteristic(schar, null);
            charWithNotes = newStudy.getCharacteristicWithNotes(schar);
        }
        return charWithNotes;
    }

    static NotesView buildNotesEditor(TypeWithNotes obj) {
        return buildNotesEditor(obj.getNotes());
    }

    static NotesView buildNotesEditor(ObservableList<Note> notes) {
        return new NotesView(notes, true);
    }

    // -- Wizard Steps

    public static class AddArmsWizardStep extends AddListItemsWizardStep<Arm> {
        public AddArmsWizardStep(JDialog dialog, AddArmsPresentation pm) {
            super("Add arms", "Enter the arms for this study.", pm, dialog);
        }

        @Override
        protected void addAdditionalFields(PanelBuilder builder, CellConstraints cc, int rows, int idx) {
            builder.addLabel("Size: ", cc.xy(7, rows));
            JTextField sizeField = BasicComponentFactory.createFormattedTextField(
                    new PresentationModel<Arm>(d_pm.getList().get(idx)).getModel(Arm.PROPERTY_SIZE),
                    new DefaultFormatter());
            sizeField.setColumns(4);
            builder.add(sizeField, cc.xy(9, rows));
        }

    }

    public static class AddEpochsWizardStep extends AddListItemsWizardStep<Epoch> {
        public AddEpochsWizardStep(JDialog dialog, AddEpochsPresentation pm) {
            super("Add epochs", "Enter the epochs for this study.", pm, dialog);
            rebuild();
        }

        @Override
        protected void addAdditionalFields(PanelBuilder builder, CellConstraints cc, int rows, int idx) {
            DurationPresentation<Epoch> durationModel = ((AddEpochsPresentation) d_pm).getDurationModel(idx);
            ValueModel definedModel = new PropertyAdapter<DurationPresentation<Epoch>>(durationModel,
                    DurationPresentation.PROPERTY_DEFINED, true);

            JPanel panel = new JPanel();
            panel.add(new JLabel("Duration: "));

            // defined/undefined radio buttons
            panel.add(BasicComponentFactory.createRadioButton(definedModel, true, "Known"));
            panel.add(BasicComponentFactory.createRadioButton(definedModel, false, "Unknown"));

            // duration quantity input
            final JTextField quantityField = BasicComponentFactory
                    .createFormattedTextField(new PropertyAdapter<DurationPresentation<Epoch>>(durationModel,
                            DurationPresentation.PROPERTY_DURATION_QUANTITY, true), new DefaultFormatter());
            quantityField.setColumns(4);
            Bindings.bind(quantityField, "enabled", definedModel);
            panel.add(quantityField);

            // duration units input
            final JComboBox unitsField = AuxComponentFactory.createBoundComboBox(
                    DurationPresentation.DateUnits.values(), new PropertyAdapter<DurationPresentation<Epoch>>(
                            durationModel, DurationPresentation.PROPERTY_DURATION_UNITS, true));
            Bindings.bind(unitsField, "enabled", definedModel);
            panel.add(unitsField);

            builder.add(panel, cc.xy(7, rows));
        }
    }

    public static class AssignActivitiesWizardStep extends PanelWizardStep {

        public final static class ActivitiesCompleteValidator extends AbstractListValidator<StudyActivity> {
            public ActivitiesCompleteValidator(ObservableList<StudyActivity> list) {
                super(new ContentAwareListModel<StudyActivity>(list));
            }

            @Override
            public boolean validate() {
                for (StudyActivity act : d_list) {
                    if (!act.isComplete()) {
                        return false;
                    }
                }
                return true;
            }
        }

        public final static class AllActivitiesUsedValidator extends AbstractListValidator<StudyActivity> {
            public AllActivitiesUsedValidator(ObservableList<StudyActivity> list) {
                super(new ContentAwareListModel<StudyActivity>(list));
            }

            @Override
            public boolean validate() {
                for (StudyActivity act : d_list) {
                    if (act.getUsedBy().isEmpty()) {
                        return false;
                    }
                }
                return true;
            }
        }

        public final static class TableFilledValidator extends AbstractValueModel implements ValueHolder<Boolean> {
            private final StudyActivitiesTableModel d_tableModel;
            private boolean d_value;

            public TableFilledValidator(StudyActivitiesTableModel table) {
                d_tableModel = table;
                d_tableModel.addTableModelListener(new TableModelListener() {

                    @Override
                    public void tableChanged(TableModelEvent e) {
                        update();
                    }
                });
                update();
            }

            private void update() {
                boolean oldValue = d_value;
                d_value = validate();
                fireValueChange(oldValue, d_value);
            }

            private boolean validate() {
                for (int row = 0; row < d_tableModel.getRowCount(); ++row) {
                    for (int col = 0; col < d_tableModel.getColumnCount(); ++col) {
                        if (d_tableModel.getValueAt(row, col) == null) {
                            return false;
                        }
                    }
                }
                return true;
            }

            @Override
            public Boolean getValue() {
                return d_value;
            }

            @Override
            public void setValue(Object newValue) {
                throw new IllegalAccessError("TableFilledFilledValidator is read-only");
            }

        }

        private PanelBuilder d_builder;
        private JScrollPane d_scrollPane;

        private AddStudyWizardPresentation d_pm;
        private List<Pair<String, ValueModel>> d_validators = new ArrayList<Pair<String, ValueModel>>();
        public JTable armsEpochsTable;
        private final JDialog d_parent;
        private AddisWindow d_mainWindow;

        private static DataFlavor s_studyActivityFlavor = createFlavor();
        private JList d_activityList;
        private StudyActivitiesTableModel d_tableModel;
        private BooleanAndModel d_readyValidator;

        private static DataFlavor createFlavor() {
            try {
                return new DataFlavor(
                        DataFlavor.javaJVMLocalObjectMimeType + ";class=" + StudyActivity.class.getCanonicalName());
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }

        public AssignActivitiesWizardStep(JDialog parent, AddStudyWizardPresentation pm, AddisWindow mainWindow) {
            super("Assign activities",
                    "Drag activities to their proper combination of (arm, epoch). Incomplete activities are shown in red.");
            d_parent = parent;
            d_mainWindow = mainWindow;
            d_pm = pm;
        }

        private void addValidator(String warning, ValueModel validator) {
            d_validators.add(new Pair<String, ValueModel>(warning, validator));
        }

        @Override
        public void prepare() {
            this.setVisible(false);

            d_tableModel = new StudyActivitiesTableModel(d_pm.getNewStudyPM().getBean());
            d_validators.clear();
            addValidator("Some activities have missing data",
                    new ActivitiesCompleteValidator(d_pm.getNewStudyPM().getBean().getStudyActivities()));
            addValidator("Not all cells in the table are filled in", new TableFilledValidator(d_tableModel));
            addValidator("Not all the activities are used",
                    new AllActivitiesUsedValidator(d_pm.getNewStudyPM().getBean().getStudyActivities()));

            List<ValueModel> validators = new ArrayList<ValueModel>();
            for (Pair<String, ValueModel> validator : d_validators) {
                validators.add(validator.getValue());
            }
            d_readyValidator = new BooleanAndModel(validators);
            PropertyConnector.connectAndUpdate(d_readyValidator, this, "complete");

            if (d_scrollPane != null)
                remove(d_scrollPane);

            buildWizardStep();
            this.setVisible(true);
            repaint();
        }

        private void buildWizardStep() {
            FormLayout layout = new FormLayout("fill:pref, 7dlu, fill:pref:grow",
                    "p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu, p");
            d_builder = new PanelBuilder(layout);
            d_builder.setDefaultDialogBorder();
            CellConstraints cc = new CellConstraints();

            // add labels
            d_builder.addLabel("Activities: ", cc.xy(1, 1));
            d_builder.addLabel("Arms and Epochs: ", cc.xy(3, 1));

            Study study = d_pm.getNewStudyPM().getBean();

            ContentAwareListModel<StudyActivity> dataModel = new ContentAwareListModel<StudyActivity>(
                    study.getStudyActivities());
            dataModel.addListDataListener(new ListDataListener() {
                public void intervalRemoved(ListDataEvent e) {
                    d_tableModel.fireTableDataChanged();
                    revalidate();
                }

                public void intervalAdded(ListDataEvent e) {
                    d_tableModel.fireTableDataChanged();
                    revalidate();
                }

                public void contentsChanged(ListDataEvent e) {
                    d_tableModel.fireTableDataChanged();
                    revalidate();
                }
            });

            d_activityList = new JList(dataModel);
            d_activityList.setDragEnabled(true);
            d_activityList.setTransferHandler(new TransferHandler() {
                @Override
                public int getSourceActions(JComponent c) {
                    return COPY;
                }

                @Override
                protected Transferable createTransferable(final JComponent c) {
                    return new Transferable() {
                        private Object d_value = ((JList) c).getSelectedValue();

                        public boolean isDataFlavorSupported(DataFlavor flavor) {
                            return flavor.equals(s_studyActivityFlavor);
                        }

                        public DataFlavor[] getTransferDataFlavors() {
                            return new DataFlavor[] { s_studyActivityFlavor };
                        }

                        public Object getTransferData(DataFlavor flavor)
                                throws UnsupportedFlavorException, IOException {
                            if (!isDataFlavorSupported(flavor)) {
                                throw new UnsupportedFlavorException(flavor);
                            }
                            return d_value;
                        }
                    };
                }
            });
            d_activityList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
            d_activityList.setLayoutOrientation(JList.VERTICAL);
            d_activityList.setCellRenderer(new DefaultListCellRenderer() {
                public Component getListCellRendererComponent(JList list, Object value, int index,
                        boolean isSelected, boolean cellHasFocus) {
                    StudyActivity sa = (StudyActivity) value;
                    JComponent listCellRendererComponent = (JComponent) super.getListCellRendererComponent(list,
                            sa.getName(), index, isSelected, cellHasFocus);
                    if (!sa.isComplete()) {
                        listCellRendererComponent.setBorder(BorderFactory.createLineBorder(Color.RED));
                        listCellRendererComponent.setForeground(Color.RED);
                    }
                    return listCellRendererComponent;
                }
            });

            JScrollPane activityScrollPane = new JScrollPane(d_activityList);
            activityScrollPane.setPreferredSize(new Dimension(200, 300));
            d_builder.add(activityScrollPane, cc.xy(1, 3));

            createArmsAndEpochsTable(cc);

            createButtons(cc, d_activityList);

            this.setLayout(new BorderLayout());
            d_scrollPane = new JScrollPane(d_builder.getPanel());
            d_scrollPane.getVerticalScrollBar().setUnitIncrement(16);

            add(d_scrollPane, BorderLayout.CENTER);

            JPanel validationPanel = new JPanel();
            validationPanel.setLayout(new BoxLayout(validationPanel, BoxLayout.Y_AXIS));
            for (Pair<String, ValueModel> x : d_validators) {
                validationPanel.add(createValidationLabel(x.getKey(), x.getValue()));
            }

            d_builder.add(validationPanel, cc.xy(3, 11));

            JComponent note = buildTip(
                    "The study activities encode precisely what happened in each arm (patient group), during each epoch (phase of the study). "
                            + "To create a valid study design, please add an activity to each cell of the arms and epochs table above."
                            + "You should also make sure that each activity is completely specified and used in the study design.");
            d_builder.add(note, cc.xyw(1, 13, 3));
        }

        private JComponent createValidationLabel(String message, ValueModel validModel) {
            JLabel label = new JLabel(message);
            label.setForeground(Color.RED);
            label.setFont(label.getFont().deriveFont(Font.BOLD));
            Bindings.bind(label, "visible", new BooleanNotModel(validModel));
            return label;
        }

        private void createArmsAndEpochsTable(CellConstraints cc) {

            final JTable armsEpochsTable = new JTable(d_tableModel);
            armsEpochsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
            for (int i = 0; i < armsEpochsTable.getColumnCount(); i++) {
                armsEpochsTable.getColumnModel().getColumn(i).setMinWidth(100);
            }
            armsEpochsTable.getTableHeader().setReorderingAllowed(false);
            armsEpochsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
            armsEpochsTable.setDropMode(DropMode.ON_OR_INSERT_COLS);

            TransferHandler transferHandler = new TransferHandler() {
                public boolean canImport(TransferSupport support) {
                    if (!support.isDrop()) {
                        return false;
                    }
                    if (!support.isDataFlavorSupported(s_studyActivityFlavor)) {
                        return false;
                    }
                    if (support.getDropLocation() instanceof JTable.DropLocation) { // drop to table cell
                        return ((JTable.DropLocation) support.getDropLocation()).getColumn() > 0;
                    }
                    if (support.getDropLocation() instanceof TransferHandler.DropLocation) { // drop to table header
                        return getHeaderColumnIndex(support.getDropLocation()) > 0;
                    }
                    return false;
                }

                private int getHeaderColumnIndex(TransferHandler.DropLocation dl) {
                    return armsEpochsTable.getTableHeader().getColumnModel().getColumnIndexAtX(dl.getDropPoint().x);
                }

                public boolean importData(TransferSupport support) {
                    if (!canImport(support)) {
                        return false;
                    }

                    StudyActivity data;
                    try {
                        data = (StudyActivity) support.getTransferable().getTransferData(s_studyActivityFlavor);
                    } catch (UnsupportedFlavorException e) {
                        return false;
                    } catch (IOException e) {
                        return false;
                    }

                    if (support.getDropLocation() instanceof JTable.DropLocation) { // drop to table cell
                        JTable.DropLocation dl = (JTable.DropLocation) support.getDropLocation();
                        d_tableModel.setValueAt(data, dl.getRow(), dl.getColumn());
                        return true;
                    } else { // drop to table header
                        int columnIndex = getHeaderColumnIndex(support.getDropLocation());
                        for (int i = 0; i < armsEpochsTable.getRowCount(); i++) {
                            d_tableModel.setValueAt(data, i, columnIndex);
                        }
                        return true;
                    }
                }
            };

            armsEpochsTable.setTransferHandler(transferHandler);
            armsEpochsTable.getTableHeader().setTransferHandler(transferHandler);
            armsEpochsTable.setDefaultRenderer(StudyActivity.class, new DefaultTableCellRenderer() {
                @Override
                public Component getTableCellRendererComponent(JTable table, Object value, boolean arg2,
                        boolean arg3, int arg4, int arg5) {
                    String strValue = value == null ? "" : ((StudyActivity) value).getName();
                    return super.getTableCellRendererComponent(table, strValue, arg2, arg3, arg4, arg5);
                }
            });
            JScrollPane tableScrollPane = new JScrollPane(armsEpochsTable);
            d_builder.add(tableScrollPane, cc.xywh(3, 3, 1, 5));
        }

        private void createButtons(CellConstraints cc, JList activities) {
            final JButton newButton = new JButton("New Activity");
            final JButton editButton = new JButton("Edit Activity");
            final JButton removeButton = new JButton("Remove Activity");

            // make sure edit and remove are only enabled when anything is selected.
            editButton.setEnabled(activities.getSelectedIndex() >= 0);
            removeButton.setEnabled(activities.getSelectedIndex() >= 0);

            activities.addListSelectionListener(new ListSelectionListener() {
                public void valueChanged(ListSelectionEvent e) {
                    boolean anySelected = ((JList) (e.getSource())).getSelectedIndex() >= 0;
                    editButton.setEnabled(anySelected);
                    removeButton.setEnabled(anySelected);
                }
            });

            d_builder.add(newButton, cc.xy(1, 5));
            newButton.addActionListener(new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    AddStudyActivityDialog addStudyActivityDialog = new AddStudyActivityDialog(d_parent,
                            d_mainWindow,
                            new StudyActivityPresentation(getStudyActivities(), d_pm.getDrugsModel()));
                    addStudyActivityDialog.setLocationRelativeTo(d_parent);
                    addStudyActivityDialog.setVisible(true);
                }
            });

            d_builder.add(editButton, cc.xy(1, 7));
            editButton.addActionListener(new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    AddStudyActivityDialog addStudyActivityDialog = new AddStudyActivityDialog(d_parent,
                            d_mainWindow, new StudyActivityPresentation(getStudyActivities(), d_pm.getDrugsModel(),
                                    getStudyActivities().get(d_activityList.getSelectedIndex())));
                    addStudyActivityDialog.setLocationRelativeTo(d_parent);
                    addStudyActivityDialog.setVisible(true);
                }
            });

            d_builder.add(removeButton, cc.xy(1, 9));
            removeButton.addActionListener(new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    Study study = d_pm.getNewStudyPM().getBean();
                    StudyActivity studyActivity = d_pm.getNewStudyPM().getBean().getStudyActivities()
                            .get(d_activityList.getSelectedIndex());
                    StudyActivity activity = study.findStudyActivity(studyActivity.getName());
                    study.getStudyActivities().remove(activity);
                }
            });
        }

        private ObservableList<StudyActivity> getStudyActivities() {
            return d_pm.getNewStudyPM().getBean().getStudyActivities();
        }
    }

    public static class ReviewStudyStep extends PanelWizardStep {
        private final AddStudyWizardPresentation d_pm;
        private final AddisWindow d_mainwindow;
        private final JDialog d_dialog;

        public ReviewStudyStep(JDialog dialog, AddStudyWizardPresentation pm, AddisWindow mainWindow) {
            super("Review study",
                    "Please review the study to be created. "
                            + "You can go back through the wizard to correct any mistakes, "
                            + "but after the study has been added it cannot be changed.");
            d_pm = pm;
            d_mainwindow = mainWindow;
            d_dialog = dialog;
            setLayout(new BorderLayout());
            setComplete(true);
        }

        @Override
        public void prepare() {
            StudyView view = new StudyView(d_pm.getNewStudyPM(), d_pm.getDomain(), d_dialog,
                    d_mainwindow.getPresentationModelFactory());
            removeAll();
            add(view.buildPanel(), BorderLayout.CENTER);
            this.setVisible(true);
        }
    }

    public static class SetMeasurementsWizardStep extends PanelWizardStep {
        private JScrollPane d_scrollPane;
        private OutcomeMeasurementsModel d_model;
        private JDialog d_dialog;
        private MeasurementTable d_table;

        public SetMeasurementsWizardStep(AddStudyWizardPresentation pm, String title, String description,
                OutcomeMeasurementsModel model, JDialog dialog) {
            super(title, description);
            d_model = model;
            d_dialog = dialog;
            setComplete(true);
        }

        @Override
        public void prepare() {
            this.setVisible(false);
            if (d_scrollPane != null) {
                remove(d_scrollPane);
            }

            buildWizardStep();
            this.setVisible(true);
            repaint();
            setComplete(true);
        }

        private void buildWizardStep() {
            this.setLayout(new BorderLayout());
            TableModel tableModel = d_model.getMeasurementTableModel();
            d_table = new MeasurementTable(tableModel, d_dialog);
            d_scrollPane = new JScrollPane(d_table);
            d_scrollPane.getVerticalScrollBar().setUnitIncrement(16);

            add(d_scrollPane, BorderLayout.CENTER);
        }
    }

    public static class SetEndpointMeasurementsWizardStep extends SetMeasurementsWizardStep {
        public SetEndpointMeasurementsWizardStep(JDialog dialog, AddStudyWizardPresentation pm) {
            super(pm, "Set Measurements", "Please enter the measurements for all arm-endpoint combinations.",
                    pm.getEndpointsModel(), dialog);
        }
    }

    public static class SetAdverseEventMeasurementsWizardStep extends SetMeasurementsWizardStep {
        public SetAdverseEventMeasurementsWizardStep(JDialog dialog, AddStudyWizardPresentation pm) {
            super(pm, "Input adverse event data", "Please enter the measurements for all arm-event combinations.",
                    pm.getAdverseEventsModel(), dialog);
        }

    }

    public static class SetPopulationCharMeasurementsWizardStep extends SetMeasurementsWizardStep {
        public SetPopulationCharMeasurementsWizardStep(JDialog dialog, AddStudyWizardPresentation pm) {
            super(pm, "Input population data",
                    "Please enter the measurements for all population baseline characteristics.",
                    pm.getPopulationCharsModel(), dialog);
        }

    }

    public static class SelectEndpointWizardStep extends SelectFromOutcomeMeasureListWizardStep<Endpoint> {
        public SelectEndpointWizardStep(JDialog parent, AddStudyWizardPresentation pm) {
            super(parent, pm.getEndpointSelectModel(), pm.getAddEpochsModel());
        }

        @Override
        protected int createAdditionalComponents(ModifiableHolder<Endpoint> slot, PanelBuilder builder,
                FormLayout layout, int row) {
            row = LayoutUtil.addRow(layout, row);
            StudyOutcomeMeasure<Endpoint> som = (StudyOutcomeMeasure<Endpoint>) slot;
            PropertyAdapter<StudyOutcomeMeasure<Endpoint>> primaryModel = new PropertyAdapter<StudyOutcomeMeasure<Endpoint>>(
                    som, StudyOutcomeMeasure.PROPERTY_IS_PRIMARY, true);
            JCheckBox primaryCB = BasicComponentFactory.createCheckBox(primaryModel, "Primary endpoint");
            builder.add(primaryCB, (new CellConstraints()).xy(5, row));
            return row;
        }
    }

    public static class SelectPopulationCharsWizardStep
            extends SelectFromOutcomeMeasureListWizardStep<PopulationCharacteristic> {
        public SelectPopulationCharsWizardStep(JDialog parent, AddStudyWizardPresentation pm) {
            super(parent, pm.getPopulationCharSelectModel(), pm.getAddEpochsModel());
        }
    }

    public static class SelectAdverseEventWizardStep extends SelectFromOutcomeMeasureListWizardStep<AdverseEvent> {
        protected SelectAdverseEventWizardStep(JDialog parent, AddStudyWizardPresentation pm) {
            super(parent, pm.getAdverseEventSelectModel(), pm.getAddEpochsModel());
        }
    }

    public static class EnterCharacteristicsWizardStep extends PanelWizardStep {
        JPanel d_me = this;
        private PanelBuilder d_builder;
        private JScrollPane d_scrollPane;

        private Set<BasicStudyCharacteristic> excludedChars = new HashSet<BasicStudyCharacteristic>();
        private AddStudyWizardPresentation d_pm;

        public EnterCharacteristicsWizardStep(AddStudyWizardPresentation pm) {
            super("Enter additional information",
                    "Enter additional information for this study. " + "Fields may be left empty if unknown.");

            excludedChars.add(BasicStudyCharacteristic.TITLE);
            excludedChars.add(BasicStudyCharacteristic.CREATION_DATE);
            excludedChars.add(BasicStudyCharacteristic.SOURCE);
            d_pm = pm;
            setComplete(true);
        }

        @Override
        public void prepare() {
            if (d_scrollPane != null)
                remove(d_scrollPane);

            buildWizardStep();
            repaint();
        }

        private void buildWizardStep() {
            FormLayout layout = new FormLayout("right:pref, 3dlu, fill:pref:grow, 3dlu, pref", "p, 3dlu, p");
            d_builder = new PanelBuilder(layout);
            d_builder.setDefaultDialogBorder();
            CellConstraints cc = new CellConstraints();

            buildCharacteristicsPart(3, d_builder, cc, 1, layout);

            JPanel panel = d_builder.getPanel();
            this.setLayout(new BorderLayout());
            d_scrollPane = new JScrollPane(panel);
            d_scrollPane.getVerticalScrollBar().setUnitIncrement(16);

            add(d_scrollPane, BorderLayout.CENTER);
        }

        private int buildCharacteristicsPart(int fullWidth, PanelBuilder builder, CellConstraints cc, int row,
                FormLayout layout) {
            for (BasicStudyCharacteristic c : BasicStudyCharacteristic.values()) {
                if (!excludedChars.contains(c)) {
                    // add characteristic field
                    builder.addLabel(c.getDescription() + ":", cc.xy(1, row));
                    builder.add(createCharacteristicComponent(c), cc.xyw(3, row, fullWidth));

                    // add note field
                    LayoutUtil.addRow(layout);
                    row += 2;
                    builder.add(buildNotesEditor(getCharWithNotes(d_pm.getNewStudyPM().getBean(), c)),
                            cc.xy(3, row));

                    LayoutUtil.addRow(layout);
                    row += 2;
                }
            }
            return row;
        }

        private JComponent createCharacteristicComponent(BasicStudyCharacteristic c) {
            JComponent component = null;
            if (c.getValueType() != null) {
                if (c.getValueType().equals(String.class)) {
                    ValueModel model = d_pm.getCharacteristicModel(c);
                    component = TextComponentFactory.createTextArea(model, true);
                } else if (c.getValueType().equals(Integer.class)) {
                    component = AuxComponentFactory
                            .createNonNegativeIntegerTextField(d_pm.getCharacteristicModel(c));
                } else if (c.getValueType().equals(Date.class)) {
                    ValueModel mvmodel = d_pm.getCharacteristicModel(c);
                    JDateChooser chooser = new JDateChooser();
                    PropertyConnector.connectAndUpdate(mvmodel, chooser, "date");
                    component = chooser;
                } else if (PubMedIdList.class.isAssignableFrom(c.getValueType())) {
                    ValueModel model = d_pm.getCharacteristicModel(c);
                    component = createPubMedIDComponent(model);
                } else {
                    if (c.getValueType().isEnum()) {
                        try {
                            component = createOptionsComboBox(c, c.getValueType().getEnumConstants());
                        } catch (Exception e) {
                            component = new JLabel("ILLEGAL CHARACTERISTIC ENUM TYPE");
                        }
                    } else {
                        throw new RuntimeException("unknown characteristic type");
                    }
                }
            }

            return component;
        }

        private class PubMedIdsRetriever implements Runnable {
            private final JButton d_importButton;

            public PubMedIdsRetriever(JButton importButton) {
                d_importButton = importButton;
            }

            public void run() {
                String studyID = d_pm.getIdModel().getValue().toString().trim();
                try {
                    d_importButton.setEnabled(false);

                    PubMedIdList importPubMedID = new PubMedIDRetriever()
                            .importPubMedID(studyID.replace(" ", "%20"));

                    if (!importPubMedID.isEmpty()) {
                        d_pm.getCharacteristicModel(BasicStudyCharacteristic.PUBMED).setValue(importPubMedID);
                    } else {
                        JOptionPane.showMessageDialog(d_me,
                                "The Study ID (" + studyID + ")\nhas no PubMed ID associated", "Warning",
                                JOptionPane.WARNING_MESSAGE);
                    }
                    d_importButton.setEnabled(true);
                } catch (IOException e) {
                    ErrorDialog.showDialog(e, "Couldn't retrieve PubMed ID",
                            "Error reading from PubMed: " + e.toString(), false);
                }
            }
        }

        private JComponent createPubMedIDComponent(ValueModel model) {
            JTextField inputField = BasicComponentFactory.createFormattedTextField(model, new PubMedListFormat());
            inputField.setColumns(30);
            inputField.setToolTipText("You can enter multiple PubMed IDs delimited by comma");

            final JButton d_importButton = GUIFactory.createIconButton(FileNames.ICON_SEARCH,
                    "Search PubMed ID based on the trial ID");
            d_importButton.setDisabledIcon(Main.IMAGELOADER.getIcon(FileNames.ICON_LOADING));
            d_importButton.addActionListener(new AbstractAction() {
                public void actionPerformed(ActionEvent arg0) {
                    PubMedIdsRetriever pubMedRetriever = new PubMedIdsRetriever(d_importButton);
                    RunnableReadyModel readyModel = new RunnableReadyModel(pubMedRetriever);
                    new Thread(readyModel).start();
                }
            });

            PanelBuilder builder = new PanelBuilder(new FormLayout("pref:grow, 3dlu, pref", "p"));
            CellConstraints cc = new CellConstraints();

            builder.add(inputField, cc.xy(1, 1));
            builder.add(d_importButton, cc.xy(3, 1));

            return builder.getPanel();
        }

        private <E> JComponent createOptionsComboBox(BasicStudyCharacteristic c, E[] options) {
            ValueModel selectionHolder = d_pm.getCharacteristicModel(c);
            JComboBox component = AuxComponentFactory.createBoundComboBox(options, selectionHolder);
            ComboBoxPopupOnFocusListener.add(component);
            return component;
        }
    }

    public static class SelectIndicationWizardStep extends PanelWizardStep {
        private PanelBuilder d_builder;
        private BooleanAndModel d_validator = new BooleanAndModel();
        private JScrollPane d_scrollPane;
        private AddStudyWizardPresentation d_pm;
        private AddisWindow d_mainWindow;

        public SelectIndicationWizardStep(AddStudyWizardPresentation pm, AddisWindow mainWindow) {
            super("Select Indication",
                    "Select the indication for this study. " + "An indication must be selected to continue.");
            d_pm = pm;
            d_mainWindow = mainWindow;
            if (d_pm.isEditing())
                setComplete(true);
        }

        @Override
        public void prepare() {
            this.setVisible(false);
            PropertyConnector.connectAndUpdate(d_validator, this, "complete");

            if (d_scrollPane != null)
                remove(d_scrollPane);
            buildWizardStep();
            this.setVisible(true);
            repaint();
        }

        public void buildWizardStep() {
            FormLayout layout = new FormLayout("right:pref, 3dlu, pref:grow:fill, 3dlu, left:pref",
                    "p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu, p");
            d_builder = new PanelBuilder(layout);
            d_builder.setDefaultDialogBorder();
            CellConstraints cc = new CellConstraints();

            d_builder.addLabel("Indication", cc.xy(1, 3));

            JComboBox indBox = AuxComponentFactory.createBoundComboBox(d_pm.getIndicationsModel(),
                    d_pm.getIndicationModel(), true);
            if (d_pm.getIndicationsModel().size() == 1 && indBox.getSelectedIndex() < 0) {
                indBox.setSelectedIndex(0);
            }
            d_builder.add(indBox, cc.xyw(3, 3, 2));
            d_validator.add(new NonEmptyValueModel(new ComboBoxSelectionModel(indBox)));

            // (new) '+' button
            JButton btn = GUIFactory.createPlusButton("Create Indication");
            d_builder.add(btn, cc.xy(5, 3));
            btn.addActionListener(new AbstractAction() {
                public void actionPerformed(ActionEvent arg0) {
                    d_mainWindow.showAddDialog(CategoryKnowledgeFactory.getCategoryKnowledge(Indication.class),
                            d_pm.getIndicationModel());
                    prepare();
                }
            });

            // add note
            d_builder.add(buildNotesEditor(d_pm.getNewStudyPM().getBean().getIndicationWithNotes()), cc.xy(3, 5));

            this.setLayout(new BorderLayout());
            d_scrollPane = new JScrollPane(d_builder.getPanel());
            d_scrollPane.getVerticalScrollBar().setUnitIncrement(16);

            add(d_scrollPane, BorderLayout.CENTER);
        }
    }

    public static class EnterIdTitleWizardStep extends PanelWizardStep {
        JPanel d_me = this;
        private JTextField d_idField;
        private JComponent d_titleField;
        private PanelBuilder d_builder;
        private JButton d_importButton;
        private List<ValueModel> d_validators = new LinkedList<ValueModel>();
        private JScrollPane d_scrollPane;
        private AddStudyWizardPresentation d_pm;
        private JCheckBox d_withResultsCheckBox;

        private class IdStepValidator extends NonEmptyValueModel {
            public IdStepValidator(ValueModel idModel) {
                super(idModel);
            }

            @Override
            public Boolean getValue() {
                return super.getValue() && d_pm.isIdAvailable();
            }
        }

        public EnterIdTitleWizardStep(JDialog dialog, AddStudyWizardPresentation pm) {
            super("Select ID and Title",
                    "Set the ID and title of the study. Studies can also be extracted from Clinicaltrials.gov using the NCT-id.");
            d_pm = pm;
        }

        @Override
        public void prepare() {
            this.setVisible(false);
            d_validators.clear();
            d_validators.add(new IdStepValidator(d_pm.getIdModel()));
            d_validators.add(new NonEmptyValueModel(d_pm.getTitleModel()));

            if (d_scrollPane != null)
                remove(d_scrollPane);

            buildWizardStep();
            final BooleanAndModel validator = new BooleanAndModel(d_validators);
            PropertyConnector.connectAndUpdate(validator, this, "complete");

            this.setVisible(true);
            repaint();
        }

        private void buildWizardStep() {
            FormLayout layout = new FormLayout("right:pref, 3dlu, pref:grow:fill, 3dlu, left:pref, 3dlu, left:pref",
                    "p");
            d_builder = new PanelBuilder(layout);
            d_builder.setDefaultDialogBorder();
            CellConstraints cc = new CellConstraints();

            // add source fields
            int row = 1;

            d_builder.addLabel("Source:", cc.xy(1, row));
            JComponent sourceSelecter = AuxComponentFactory.createBoundComboBox(Source.values(),
                    d_pm.getSourceModel());
            sourceSelecter.setEnabled(false);
            d_builder.add(sourceSelecter, cc.xyw(3, row, 2));

            // add ID fields
            row = LayoutUtil.addRow(layout, row);

            d_builder.addLabel("ID:", cc.xy(1, row));
            d_idField = BasicComponentFactory.createTextField(d_pm.getIdModel(), false);
            d_idField.setColumns(30);
            bindDefaultId(d_idField);
            d_builder.add(d_idField, cc.xy(3, row));
            final Border border = d_idField.getBorder();
            final IdValidModel idValidModel = new IdValidModel();
            d_idField.addCaretListener(new CaretListener() {
                public void caretUpdate(CaretEvent e) {
                    if (!d_pm.isIdAvailable()) {
                        d_idField.setBorder(BorderFactory.createLineBorder(Color.RED, 2));
                        d_idField.setToolTipText("Study ID already exists, please change it.");
                        d_idField.dispatchEvent(new KeyEvent(d_idField, KeyEvent.KEY_PRESSED, 0, KeyEvent.CTRL_MASK,
                                KeyEvent.VK_F1, KeyEvent.CHAR_UNDEFINED));
                    } else {
                        d_idField.setToolTipText("");
                        d_idField.setBorder(border);
                    }
                    idValidModel.update();
                }
            });

            // add import button
            row = LayoutUtil.addRow(layout, row);
            d_importButton = GUIFactory.createLabeledIconButton("Import from ClinicalTrials.gov",
                    FileNames.ICON_IMPORT);
            d_importButton.setToolTipText("Enter NCT id above to retrieve study data from ClinicalTrials.gov");
            final ModifiableHolder<Boolean> retrieverReady = new ModifiableHolder<Boolean>(true);

            d_importButton.addActionListener(new AbstractAction() {
                public void actionPerformed(ActionEvent arg0) {
                    final RunnableReadyModel retriever = new RunnableReadyModel(new CTRetriever());
                    retrieverReady.setValue(false);
                    retriever.addPropertyChangeListener(new PropertyChangeListener() {
                        public void propertyChange(PropertyChangeEvent evt) {
                            retrieverReady.setValue(retriever.getValue());
                        }
                    });
                    new Thread(retriever).start();
                }
            });

            BooleanAndModel importAvailable = new BooleanAndModel(idValidModel, retrieverReady);
            d_withResultsCheckBox = BasicComponentFactory.createCheckBox(d_pm.shouldImportCTWithResults(),
                    "import results (experimental)");
            Bindings.bind(d_importButton, "enabled", importAvailable);
            Bindings.bind(d_withResultsCheckBox, "enabled", importAvailable);

            ButtonBarBuilder2 bb = ButtonBarBuilder2.createLeftToRightBuilder();
            bb.addButton(d_importButton);
            bb.addRelatedGap();
            bb.addButton(d_withResultsCheckBox);

            d_builder.add(bb.getPanel(), cc.xy(3, row));

            // add note to ID field
            row = LayoutUtil.addRow(layout, row);
            Study newStudy = d_pm.getNewStudyPM().getBean();
            d_builder.add(buildNotesEditor(newStudy), cc.xy(3, row));

            // add title label
            row = LayoutUtil.addRow(layout, row);

            d_builder.addLabel("Title:", cc.xy(1, row));
            d_titleField = TextComponentFactory.createTextArea(d_pm.getTitleModel(), true, false);
            d_builder.add(d_titleField, cc.xy(3, row));

            row = LayoutUtil.addRow(layout, row);
            d_builder.add(
                    buildNotesEditor(
                            (ObjectWithNotes<?>) getCharWithNotes(newStudy, BasicStudyCharacteristic.TITLE)),
                    cc.xy(3, row));

            // add clear button
            row = LayoutUtil.addRow(layout, row);
            JButton clearButton = new JButton("Clear input");
            clearButton.addActionListener(new AbstractAction() {
                public void actionPerformed(ActionEvent arg0) {
                    d_pm.resetStudy();
                    prepare();
                }
            });
            d_builder.add(clearButton, cc.xy(3, row));
            String tip = "You can import studies from ClinicalTrials.gov by entering their NCT-ID, "
                    + "and then pressing the import button next to the ID field. " + "For example, try "
                    + EXAMPLE_NCT_ID + ".\n\n"
                    + "Unfortunately, due to limitations of ClinicalTrials.gov, it is currently not possible to import adverse events or study results.";

            row = LayoutUtil.addRow(layout, row);
            d_builder.add(buildTip(tip), cc.xy(3, row));

            this.setLayout(new BorderLayout());
            d_scrollPane = new JScrollPane(d_builder.getPanel());
            d_scrollPane.getVerticalScrollBar().setUnitIncrement(16);

            add(d_scrollPane, BorderLayout.CENTER);
        }

        public class CTRetriever implements Runnable {
            public void run() {
                try {
                    setImportDisabledIcon(Main.IMAGELOADER.getIcon(FileNames.ICON_LOADING));

                    d_pm.importCT();

                    setImportDisabledIcon(Main.IMAGELOADER.getIcon(FileNames.ICON_IMPORT));

                } catch (FileNotFoundException e) { // file not found is expected when user enters "strange" IDs
                    JOptionPane.showMessageDialog(d_me, "Couldn't find NCT ID: " + d_pm.getIdModel().getValue(),
                            "Not Found", JOptionPane.WARNING_MESSAGE);
                } catch (IOException e) { // IOExceptions are expected when there is a network error -- so report them
                    ErrorDialog.showDialog(e, "Couldn't retrieve study",
                            "Error reading from ClinicalTrials.gov: " + e.toString(), false);
                } catch (Exception e) { // otherwise throw onwards.
                    throw new RuntimeException("Unexpected error trying to import study from ClinicalTrials.gov",
                            e);
                }
                prepare();
            }

            private void setImportDisabledIcon(final ImageIcon icon)
                    throws InterruptedException, InvocationTargetException {
                SwingUtilities.invokeAndWait(new Runnable() {
                    public void run() {
                        d_importButton.setDisabledIcon(icon);
                    }
                });
            }
        }

        private class IdValidModel extends AbstractValueModel implements ValueHolder<Boolean> {
            Boolean d_valid = false;

            @Override
            public Boolean getValue() {
                return d_valid;
            }

            @Override
            public void setValue(Object newValue) {
                throw new RuntimeException("Unexpected modification");
            }

            private Boolean isIdValid() {
                return d_idField.getText().toUpperCase().trim().matches("^NCT[0-9]+$");
            }

            public void update() {
                Boolean oldVal = d_valid;
                d_valid = isIdValid();
                fireValueChange(oldVal, d_valid);
            }

        }

        public static void bindDefaultId(final JTextField idField) {
            idField.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(INSERT_EXAMPLE_ID),
                    "insertSample");
            idField.getActionMap().put("insertSample", new AbstractAction("insertSample") {
                public void actionPerformed(ActionEvent evt) {
                    idField.setText(EXAMPLE_NCT_ID);
                }
            });
        }
    }

    private static JComponent buildTip(String tip) {
        JTextPane area = new JTextPane();
        StyledDocument doc = area.getStyledDocument();
        addStylesToDoc(doc);

        area.setBackground(new Color(255, 180, 180));

        try {
            doc.insertString(0, "x", doc.getStyle("tip"));
            doc.insertString(doc.getLength(), " Tip: \n", doc.getStyle("bold"));
            doc.insertString(doc.getLength(), tip, doc.getStyle("regular"));
        } catch (BadLocationException e) {
            e.printStackTrace();
        }

        area.setEditable(false);

        JScrollPane pane = new JScrollPane(area);
        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        pane.setPreferredSize(TextComponentFactory.textPaneDimension(area, 270, 70));

        pane.setWheelScrollingEnabled(true);
        pane.getVerticalScrollBar().setValue(0);

        return pane;
    }

    public static void addStylesToDoc(StyledDocument doc) {
        //Initialize some styles.
        Style def = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);

        Style regular = doc.addStyle("regular", def);

        Style bold = doc.addStyle("bold", regular);
        StyleConstants.setBold(bold, true);

        // The image must first be wrapped in a style
        Style style = doc.addStyle("tip", null);
        StyleConstants.setIcon(style, Main.IMAGELOADER.getIcon(FileNames.ICON_TIP));
    }

}