X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fgui%2FFeatureEditor.java;h=880a1b22d9aaac0734cfb6c8aff84840445b522d;hb=afe4b88500a5d61bc8d312909cfdde2b7cdd53df;hp=b34e104b9a4bf121552357687fbdad3017f47afe;hpb=6f0b432ac19ed1d0d5e01a834586dc81b8a6a72d;p=jalview.git
diff --git a/src/jalview/gui/FeatureEditor.java b/src/jalview/gui/FeatureEditor.java
index b34e104..880a1b2 100644
--- a/src/jalview/gui/FeatureEditor.java
+++ b/src/jalview/gui/FeatureEditor.java
@@ -1,17 +1,25 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ *
+ * This file is part of Jalview.
+ *
+ * Jalview 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.
+ *
+ * Jalview 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 Jalview. If not, see .
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
package jalview.gui;
-import jalview.api.FeatureColourI;
-import jalview.datamodel.SearchResults;
-import jalview.datamodel.SearchResultsI;
-import jalview.datamodel.SequenceFeature;
-import jalview.datamodel.SequenceI;
-import jalview.gui.JalviewColourChooser.ColourChooserListener;
-import jalview.io.FeaturesFile;
-import jalview.schemes.FeatureColour;
-import jalview.util.ColorUtils;
-import jalview.util.MessageManager;
-import jalview.util.dialogrunner.RunResponse;
-
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
@@ -33,10 +41,24 @@ import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTextArea;
import javax.swing.JTextField;
+import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
+import jalview.api.FeatureColourI;
+import jalview.datamodel.SearchResults;
+import jalview.datamodel.SearchResultsI;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
+import jalview.gui.JalviewColourChooser.ColourChooserListener;
+import jalview.io.FeaturesFile;
+import jalview.schemes.FeatureColour;
+import jalview.util.ColorUtils;
+import jalview.util.MessageManager;
+
/**
* Provides a dialog allowing the user to add new features, or amend or delete
* existing features
@@ -52,7 +74,23 @@ public class FeatureEditor
static String lastFeatureGroupAdded = "Jalview";
/*
- * index into a list of features if more than one selected for editing
+ * the sequence(s) with features to be created / amended
+ */
+ final List sequences;
+
+ /*
+ * the features (or template features) to be created / amended
+ */
+ final List features;
+
+ /*
+ * true if the dialog is to create a new feature, false if
+ * for amend or delete of existing feature(s)
+ */
+ final boolean forCreate;
+
+ /*
+ * index into the list of features
*/
int featureIndex;
@@ -64,64 +102,50 @@ public class FeatureEditor
AlignmentPanel ap;
+ JTextField name;
+
+ JTextField group;
+
+ JTextArea description;
+
+ JSpinner start;
+
+ JSpinner end;
+
+ JPanel mainPanel;
+
/**
* Constructor
*
* @param alignPanel
+ * @param seqs
+ * @param feats
+ * @param create
+ * if true create a new feature, else amend or delete an existing
+ * feature
*/
- public FeatureEditor(AlignmentPanel alignPanel)
+ public FeatureEditor(AlignmentPanel alignPanel, List seqs,
+ List feats, boolean create)
{
ap = alignPanel;
fr = alignPanel.getSeqPanel().seqCanvas.fr;
- }
-
- public void amendFeatures(final List sequences,
- final List features,
- final Runnable responseHandler)
- {
- amendFeatures(sequences, features, false, responseHandler);
- }
+ sequences = seqs;
+ features = feats;
+ this.forCreate = create;
- public void createFeatures(final List sequences,
- final List features,
- final Runnable responseHandler)
- {
- amendFeatures(sequences, features, true, responseHandler);
+ init();
}
/**
- * Presents a dialog allowing the user to add new features, or amend or delete
- * existing features. Currently this can be on
- *
- * - double-click on a sequence - Amend/Delete features at position
- * - Create sequence feature from pop-up menu on selected region
- * - Create features for pattern matches from Find
- *
- * If the supplied feature type is null, show (and update on confirm) the type
- * and group of the last new feature created (with initial defaults of
- * "feature_1" and "Jalview").
- *
- * @param sequences
- * the sequences features are to be created on (if creating
- * features), or a single sequence (if amending features)
- * @param features
- * the current features at the position (if amending), or template
- * new feature(s) with start/end position set (if creating)
- * @param create
- * true to create features, false to amend or delete
- * @param alignPanel
- * @param responseHandler
- * boolean true RunResponse is run if features are created
+ * Initialise the layout and controls
*/
- private void amendFeatures(final List sequences,
- final List features, boolean create,
- final Runnable responseHandler)
+ protected void init()
{
featureIndex = 0;
- final JPanel mainPanel = new JPanel(new BorderLayout());
+ mainPanel = new JPanel(new BorderLayout());
- final JTextField name = new JTextField(25);
+ name = new JTextField(25);
name.getDocument().addDocumentListener(new DocumentListener()
{
@Override
@@ -143,7 +167,7 @@ public class FeatureEditor
}
});
- final JTextField group = new JTextField(25);
+ group = new JTextField(25);
group.getDocument().addDocumentListener(new DocumentListener()
{
@Override
@@ -165,25 +189,48 @@ public class FeatureEditor
}
});
- final JTextArea description = new JTextArea(3, 25);
- final JSpinner start = new JSpinner();
- final JSpinner end = new JSpinner();
+ description = new JTextArea(3, 25);
+
+ start = new JSpinner();
+ end = new JSpinner();
start.setPreferredSize(new Dimension(80, 20));
end.setPreferredSize(new Dimension(80, 20));
+
+ /*
+ * ensure that start can never be more than end
+ */
+ start.addChangeListener(new ChangeListener()
+ {
+ @Override
+ public void stateChanged(ChangeEvent e)
+ {
+ Integer startVal = (Integer) start.getValue();
+ ((SpinnerNumberModel) end.getModel()).setMinimum(startVal);
+ }
+ });
+ end.addChangeListener(new ChangeListener()
+ {
+ @Override
+ public void stateChanged(ChangeEvent e)
+ {
+ Integer endVal = (Integer) end.getValue();
+ ((SpinnerNumberModel) start.getModel()).setMaximum(endVal);
+ }
+ });
+
final JLabel colour = new JLabel();
colour.setOpaque(true);
- // colour.setBorder(BorderFactory.createEtchedBorder());
colour.setMaximumSize(new Dimension(30, 16));
colour.addMouseListener(new MouseAdapter()
{
- /*
- * open colour chooser on click in colour panel
- */
@Override
public void mousePressed(MouseEvent evt)
{
if (featureColour.isSimpleColour())
{
+ /*
+ * open colour chooser on click in colour panel
+ */
String title = MessageManager
.getString("label.select_feature_colour");
ColourChooserListener listener = new ColourChooserListener()
@@ -206,7 +253,7 @@ public class FeatureEditor
*/
final String ft = features.get(featureIndex).getType();
final String type = ft == null ? lastFeatureAdded : ft;
- FeatureTypeSettings fcc = new FeatureTypeSettings(fr, type);
+ FeatureTypeSettings fcc = new FeatureTypeSettings(fr, type, true);
fcc.setRequestFocusEnabled(true);
fcc.requestFocus();
fcc.addActionListener(new ActionListener()
@@ -224,7 +271,7 @@ public class FeatureEditor
});
JPanel gridPanel = new JPanel(new GridLayout(3, 1));
- if (!create && features.size() > 1)
+ if (!forCreate && features.size() > 1)
{
/*
* more than one feature at selected position -
@@ -265,6 +312,8 @@ public class FeatureEditor
group.setText(sf.getFeatureGroup());
start.setValue(new Integer(sf.getBegin()));
end.setValue(new Integer(sf.getEnd()));
+ ((SpinnerNumberModel) start.getModel()).setMaximum(sf.getEnd());
+ ((SpinnerNumberModel) end.getModel()).setMinimum(sf.getBegin());
SearchResultsI highlight = new SearchResults();
highlight.addResult(sequences.get(0), sf.getBegin(),
@@ -319,7 +368,7 @@ public class FeatureEditor
description.setLineWrap(true);
descriptionPanel.add(new JScrollPane(description));
- if (!create)
+ if (!forCreate)
{
mainPanel.add(descriptionPanel, BorderLayout.SOUTH);
@@ -352,158 +401,167 @@ public class FeatureEditor
start.setValue(new Integer(firstFeature.getBegin()));
end.setValue(new Integer(firstFeature.getEnd()));
+ ((SpinnerNumberModel) start.getModel())
+ .setMaximum(firstFeature.getEnd());
+ ((SpinnerNumberModel) end.getModel())
+ .setMinimum(firstFeature.getBegin());
+
description.setText(firstFeature.getDescription());
featureColour = fr.getFeatureStyle(featureType);
oldColour = featureColour;
updateColourButton(mainPanel, colour, oldColour);
- Object[] options;
- if (!create)
+ }
+
+ /**
+ * Presents a dialog allowing the user to add new features, or amend or delete
+ * an existing feature. Currently this can be on
+ *
+ * - double-click on a sequence - Amend/Delete a selected feature at the
+ * position
+ * - Create sequence feature(s) from pop-up menu on selected region
+ * - Create features for pattern matches from Find
+ *
+ * If the supplied feature type is null, show (and update on confirm) the type
+ * and group of the last new feature created (with initial defaults of
+ * "feature_1" and "Jalview").
+ */
+ public void showDialog()
+ {
+ Runnable okAction = forCreate ? getCreateAction() : getAmendAction();
+ Runnable cancelAction = getCancelAction();
+
+ /*
+ * set dialog action handlers for OK (create/Amend) and Cancel options
+ * also for Delete if applicable (when amending features)
+ */
+ JvOptionPane dialog = JvOptionPane.newOptionDialog(ap.alignFrame)
+ .setResponseHandler(0, okAction)
+ .setResponseHandler(2, cancelAction);
+ if (!forCreate)
{
- options = new Object[] { MessageManager.getString("label.amend"),
- MessageManager.getString("action.delete"),
+ dialog.setResponseHandler(1, getDeleteAction());
+ }
+
+ String title = null;
+ Object[] options = null;
+ if (forCreate)
+ {
+ title = MessageManager
+ .getString("label.create_new_sequence_features");
+ options = new Object[] { MessageManager.getString("action.ok"),
MessageManager.getString("action.cancel") };
}
else
{
- options = new Object[] { MessageManager.getString("action.ok"),
+ title = MessageManager.formatMessage("label.amend_delete_features",
+ new String[]
+ { sequences.get(0).getName() });
+ options = new Object[] { MessageManager.getString("label.amend"),
+ MessageManager.getString("action.delete"),
MessageManager.getString("action.cancel") };
}
- String title = create
- ? MessageManager.getString("label.create_new_sequence_features")
- : MessageManager.formatMessage("label.amend_delete_features",
- new String[]
- { sequences.get(0).getName() });
+ dialog.showInternalDialog(mainPanel, title,
+ JvOptionPane.YES_NO_CANCEL_OPTION, JvOptionPane.PLAIN_MESSAGE,
+ null, options, MessageManager.getString("action.ok"));
+ }
- /*
- * register responses and show the dialog
- */
- JvOptionPane.newOptionDialog(Desktop.desktop).response(
+ /**
+ * Answers an action to run on Cancel in the dialog. This is just to remove
+ * any feature highlighting from the display. Changes in the dialog are not
+ * applied until it is dismissed with OK, Amend or Delete, so there are no
+ * updates to reset on Cancel.
+ *
+ * @return
+ */
+ protected Runnable getCancelAction()
+ {
+ Runnable okAction = () -> {
+ ap.highlightSearchResults(null);
+ ap.paintAlignment(false, false);
+ };
+ return okAction;
+ }
- new RunResponse(JvOptionPane.OK_OPTION)
- {
- public void run()
- {
- final String enteredType = name.getText().trim();
- final String enteredGroup = group.getText().trim();
- final String enteredDescription = description.getText()
- .replaceAll("\n", " ");
- if (enteredType.length() > 0)
-
- {
- /*
- * update default values only if creating using default values
- */
- if (useLastDefaults)
- {
- lastFeatureAdded = enteredType;
- lastFeatureGroupAdded = enteredGroup;
- // TODO: determine if the null feature group is valid
- if (lastFeatureGroupAdded.length() < 1)
- {
- lastFeatureGroupAdded = null;
- }
- }
- }
-
- if (create)
- {
- // NEW FEATURES ADDED
- if (enteredType.length() > 0)
- {
- for (int i = 0; i < sequences.size(); i++)
- {
- SequenceFeature sf = features.get(i);
- SequenceFeature sf2 = new SequenceFeature(enteredType,
- enteredDescription, sf.getBegin(),
- sf.getEnd(), enteredGroup);
- new FeaturesFile().parseDescriptionHTML(sf2, false);
- sequences.get(i).addSequenceFeature(sf2);
- }
-
- fr.setColour(enteredType, featureColour);
-
- fr.featuresAdded();
-
- responseHandler.run();
- }
- }
- else
- {
- SequenceFeature sf = features.get(featureIndex);
- /*
- * Feature amended - YES_OPTION corresponds to the Amend button
- * need to refresh Feature Settings if type, group or colour changed;
- * note we don't force the feature to be visible - the user has been
- * warned if a hidden feature type or group was entered
- */
- boolean refreshSettings = (!featureType
- .equals(enteredType)
- || !featureGroup.equals(enteredGroup));
- refreshSettings |= (featureColour != oldColour);
- fr.setColour(enteredType, featureColour);
- int newBegin = sf.begin;
- int newEnd = sf.end;
- try
- {
- newBegin = ((Integer) start.getValue()).intValue();
- newEnd = ((Integer) end.getValue()).intValue();
- } catch (NumberFormatException ex)
- {
- // JSpinner doesn't accept invalid format data :-)
- }
-
- /*
- * replace the feature by deleting it and adding a new one
- * (to ensure integrity of SequenceFeatures data store)
- */
- sequences.get(0).deleteFeature(sf);
- SequenceFeature newSf = new SequenceFeature(sf,
- enteredType, newBegin, newEnd, enteredGroup,
- sf.getScore());
- newSf.setDescription(enteredDescription);
- new FeaturesFile().parseDescriptionHTML(newSf, false);
- // amend features dialog only updates one sequence at a time
- sequences.get(0).addSequenceFeature(newSf);
-
- if (refreshSettings)
- {
- fr.featuresAdded();
- }
- }
-
- /*
- * suppress fastPaint here - if feature colour changed,
- * we need to repaint the whole alignment
- */
- ap.getSeqPanel().seqCanvas.highlightSearchResults(null);
- ap.paintAlignment(true, true);
- }
- }).response(new RunResponse(JvOptionPane.NO_OPTION)
- {
- public void run()
- {
- SequenceFeature sf = features.get(featureIndex);
- /*
- * NO_OPTION corresponds to the Delete button
- */
- sequences.get(0).getDatasetSequence().deleteFeature(sf);
- // update Feature Settings for removal of feature / group
- fr.featuresAdded();
- ap.getSeqPanel().seqCanvas.highlightSearchResults(null);
- ap.paintAlignment(true, true);
- }
- }).defaultResponse(new Runnable()
+ /**
+ * Returns the action to be run on OK in the dialog when creating one or more
+ * sequence features. Note these may have a pre-supplied feature type (such as
+ * a Find pattern), or none, in which case the feature type and group default
+ * to those last added through this dialog. The action includes refreshing the
+ * Feature Settings panel (if it is open), to show any new feature type, or
+ * amended colour for an existing type.
+ *
+ * @return
+ */
+ protected Runnable getCreateAction()
+ {
+ Runnable okAction = new Runnable()
+ {
+ boolean useLastDefaults = features.get(0).getType() == null;
+
+ @Override
+ public void run()
+ {
+ final String enteredType = name.getText().trim();
+ final String enteredGroup = group.getText().trim();
+ final String enteredDescription = description.getText()
+ .replaceAll("\n", " ");
+ if (enteredType.length() > 0)
+ {
+ /*
+ * update default values only if creating using default values
+ */
+ if (useLastDefaults)
+ {
+ lastFeatureAdded = enteredType;
+ lastFeatureGroupAdded = enteredGroup;
+ // TODO: determine if the null feature group is valid
+ if (lastFeatureGroupAdded.length() < 1)
{
- public void run()
- {
- ap.getSeqPanel().seqCanvas.highlightSearchResults(null);
- ap.paintAlignment(true, true);
- }
- }).showInternalDialog(mainPanel, title,
- JvOptionPane.YES_NO_CANCEL_OPTION,
- JvOptionPane.QUESTION_MESSAGE, null, options,
- MessageManager.getString("action.ok"));
+ lastFeatureGroupAdded = null;
+ }
+ }
+ }
+
+ if (enteredType.length() > 0)
+ {
+ for (int i = 0; i < sequences.size(); i++)
+ {
+ SequenceFeature sf = features.get(i);
+ SequenceFeature sf2 = new SequenceFeature(enteredType,
+ enteredDescription, sf.getBegin(), sf.getEnd(),
+ enteredGroup);
+ new FeaturesFile().parseDescriptionHTML(sf2, false);
+ sequences.get(i).addSequenceFeature(sf2);
+ }
+
+ fr.setColour(enteredType, featureColour);
+ fr.featuresAdded();
+
+ repaintPanel();
+ }
+ }
+ };
+ return okAction;
+ }
+
+ /**
+ * Answers the action to run on Delete in the dialog. Note this includes
+ * refreshing the Feature Settings (if open) in case the only instance of a
+ * feature type or group has been deleted.
+ *
+ * @return
+ */
+ protected Runnable getDeleteAction()
+ {
+ Runnable deleteAction = () -> {
+ SequenceFeature sf = features.get(featureIndex);
+ sequences.get(0).getDatasetSequence().deleteFeature(sf);
+ fr.featuresAdded();
+ ap.getSeqPanel().seqCanvas.highlightSearchResults(null);
+ ap.paintAlignment(true, true);
+ };
+ return deleteAction;
}
/**
@@ -518,17 +576,18 @@ public class FeatureEditor
{
colour.removeAll();
colour.setIcon(null);
- colour.setToolTipText(null);
colour.setText("");
if (col.isSimpleColour())
{
+ colour.setToolTipText(null);
colour.setBackground(col.getColour());
}
else
{
colour.setBackground(bigPanel.getBackground());
colour.setForeground(Color.black);
+ colour.setToolTipText(FeatureSettings.getColorTooltip(col, false));
FeatureSettings.renderGraduatedColor(colour, col);
}
}
@@ -570,4 +629,104 @@ public class FeatureEditor
}
}
+ /**
+ * On closing the dialog - ensure feature display is turned on, to show any
+ * new features - remove highlighting of the last selected feature - repaint
+ * the panel to show any changes
+ */
+ protected void repaintPanel()
+ {
+ ap.alignFrame.showSeqFeatures.setSelected(true);
+ ap.av.setShowSequenceFeatures(true);
+ ap.av.setSearchResults(null);
+ ap.paintAlignment(true, true);
+ }
+
+ /**
+ * Returns the action to be run on OK in the dialog when amending a feature.
+ * Note this may include refreshing the Feature Settings panel (if it is
+ * open), if feature type, group or colour has changed (but not for
+ * description or extent).
+ *
+ * @return
+ */
+ protected Runnable getAmendAction()
+ {
+ Runnable okAction = new Runnable()
+ {
+ boolean useLastDefaults = features.get(0).getType() == null;
+
+ String featureType = name.getText();
+
+ String featureGroup = group.getText();
+
+ @Override
+ public void run()
+ {
+ final String enteredType = name.getText().trim();
+ final String enteredGroup = group.getText().trim();
+ final String enteredDescription = description.getText()
+ .replaceAll("\n", " ");
+ if (enteredType.length() > 0)
+
+ {
+ /*
+ * update default values only if creating using default values
+ */
+ if (useLastDefaults)
+ {
+ lastFeatureAdded = enteredType;
+ lastFeatureGroupAdded = enteredGroup;
+ // TODO: determine if the null feature group is valid
+ if (lastFeatureGroupAdded.length() < 1)
+ {
+ lastFeatureGroupAdded = null;
+ }
+ }
+ }
+
+ SequenceFeature sf = features.get(featureIndex);
+
+ /*
+ * Need to refresh Feature Settings if type, group or colour changed;
+ * note we don't force the feature to be visible - the user has been
+ * warned if a hidden feature type or group was entered
+ */
+ boolean refreshSettings = (!featureType.equals(enteredType)
+ || !featureGroup.equals(enteredGroup));
+ refreshSettings |= (featureColour != oldColour);
+ fr.setColour(enteredType, featureColour);
+ int newBegin = sf.begin;
+ int newEnd = sf.end;
+ try
+ {
+ newBegin = ((Integer) start.getValue()).intValue();
+ newEnd = ((Integer) end.getValue()).intValue();
+ } catch (NumberFormatException ex)
+ {
+ // JSpinner doesn't accept invalid format data :-)
+ }
+
+ /*
+ * 'amend' the feature by deleting it and adding a new one
+ * (to ensure integrity of SequenceFeatures data store)
+ * note this dialog only updates one sequence at a time
+ */
+ sequences.get(0).deleteFeature(sf);
+ SequenceFeature newSf = new SequenceFeature(sf, enteredType,
+ newBegin, newEnd, enteredGroup, sf.getScore());
+ newSf.setDescription(enteredDescription);
+ new FeaturesFile().parseDescriptionHTML(newSf, false);
+ sequences.get(0).addSequenceFeature(newSf);
+
+ if (refreshSettings)
+ {
+ fr.featuresAdded();
+ }
+ repaintPanel();
+ }
+ };
+ return okAction;
+ }
+
}