/*
* 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, Joël Kuiper, Wouter
* Reckman.
* Copyright © 2013 Gert van Valkenhoef, Joël 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));
}
}