From f13c8f13408308b89e271ee9f4f11bd0493c1973 Mon Sep 17 00:00:00 2001 From: gmungoc Date: Mon, 16 Jul 2018 12:23:14 +0100 Subject: [PATCH] JAL-3060 more extensive extract method refactoring of FeatureEditor --- src/jalview/gui/FeatureEditor.java | 473 +++++++++++++++++++++------------- src/jalview/gui/FeatureSettings.java | 16 +- src/jalview/gui/Finder.java | 22 +- src/jalview/gui/PopupMenu.java | 11 +- src/jalview/gui/SeqPanel.java | 15 +- 5 files changed, 308 insertions(+), 229 deletions(-) diff --git a/src/jalview/gui/FeatureEditor.java b/src/jalview/gui/FeatureEditor.java index b34e104..00ea668 100644 --- a/src/jalview/gui/FeatureEditor.java +++ b/src/jalview/gui/FeatureEditor.java @@ -52,7 +52,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 +80,52 @@ public class FeatureEditor AlignmentPanel ap; + Object[] options; + + 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; - } + sequences = seqs; + features = feats; + this.forCreate = create; - public void amendFeatures(final List sequences, - final List features, - final Runnable responseHandler) - { - amendFeatures(sequences, features, false, responseHandler); - } - - 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 +147,7 @@ public class FeatureEditor } }); - final JTextField group = new JTextField(25); + group = new JTextField(25); group.getDocument().addDocumentListener(new DocumentListener() { @Override @@ -165,25 +169,24 @@ 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)); 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() @@ -224,7 +227,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 - @@ -319,7 +322,7 @@ public class FeatureEditor description.setLineWrap(true); descriptionPanel.add(new JScrollPane(description)); - if (!create) + if (!forCreate) { mainPanel.add(descriptionPanel, BorderLayout.SOUTH); @@ -356,157 +359,157 @@ public class FeatureEditor featureColour = fr.getFeatureStyle(featureType); oldColour = featureColour; updateColourButton(mainPanel, colour, oldColour); - Object[] options; - if (!create) + if (forCreate) { - options = new Object[] { MessageManager.getString("label.amend"), - MessageManager.getString("action.delete"), + options = new Object[] { MessageManager.getString("action.ok"), MessageManager.getString("action.cancel") }; } else { - options = new Object[] { MessageManager.getString("action.ok"), + options = new Object[] { MessageManager.getString("label.amend"), + MessageManager.getString("action.delete"), MessageManager.getString("action.cancel") }; } + } + + /** + * 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 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"). + */ + public void showDialog() + { + RunResponse okAction = forCreate ? getCreateAction() : getAmendAction(); + RunResponse deleteAction = getDeleteAction(); + Runnable cancelAction = getCancelAction(); - String title = create + String title = forCreate ? MessageManager.getString("label.create_new_sequence_features") : MessageManager.formatMessage("label.amend_delete_features", new String[] { sequences.get(0).getName() }); - /* - * register responses and show the dialog - */ - JvOptionPane.newOptionDialog(Desktop.desktop).response( - - 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() - { - public void run() - { - ap.getSeqPanel().seqCanvas.highlightSearchResults(null); - ap.paintAlignment(true, true); - } - }).showInternalDialog(mainPanel, title, + JvOptionPane.newOptionDialog(Desktop.desktop).response(okAction) + .response(deleteAction).defaultResponse(cancelAction) + .showInternalDialog(mainPanel, title, JvOptionPane.YES_NO_CANCEL_OPTION, JvOptionPane.QUESTION_MESSAGE, null, options, MessageManager.getString("action.ok")); } /** + * 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 defaultResponse = new Runnable() + { + public void run() + { + ap.highlightSearchResults(null); + ap.paintAlignment(false, false); + } + }; + return defaultResponse; + } + + /** + * 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 RunResponse getCreateAction() + { + RunResponse okAction = new RunResponse(JvOptionPane.OK_OPTION) + { + boolean useLastDefaults = features.get(0).getType() == null; + + 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 (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 RunResponse getDeleteAction() + { + RunResponse deleteAction = new RunResponse(JvOptionPane.NO_OPTION) + { + public void run() + { + SequenceFeature sf = features.get(featureIndex); + sequences.get(0).getDatasetSequence().deleteFeature(sf); + fr.featuresAdded(); + ap.getSeqPanel().seqCanvas.highlightSearchResults(null); + ap.paintAlignment(true, true); + } + }; + return deleteAction; + } + + /** * update the amend feature button dependent on the given style * * @param bigPanel @@ -518,17 +521,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 +574,103 @@ 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 RunResponse getAmendAction() + { + RunResponse okAction = new RunResponse(JvOptionPane.OK_OPTION) + { + boolean useLastDefaults = features.get(0).getType() == null; + + String featureType = name.getText(); + + String featureGroup = group.getText(); + + 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; + } + } diff --git a/src/jalview/gui/FeatureSettings.java b/src/jalview/gui/FeatureSettings.java index 062eda1..0e18d91 100644 --- a/src/jalview/gui/FeatureSettings.java +++ b/src/jalview/gui/FeatureSettings.java @@ -81,8 +81,6 @@ import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JCheckBoxMenuItem; -import javax.swing.JColorChooser; -import javax.swing.JDialog; import javax.swing.JInternalFrame; import javax.swing.JLabel; import javax.swing.JLayeredPane; @@ -221,7 +219,7 @@ public class FeatureSettings extends JPanel case COLOUR_COLUMN: FeatureColourI colour = (FeatureColourI) table.getValueAt(row, column); - tip = getColorTooltip(colour); + tip = getColorTooltip(colour, true); break; case FILTER_COLUMN: FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row, @@ -1362,15 +1360,18 @@ public class FeatureSettings extends JPanel * Answers a suitable tooltip to show on the colour cell of the table * * @param fcol + * @param withHint + * if true include 'click to edit' and similar text * @return */ - public static String getColorTooltip(FeatureColourI fcol) + public static String getColorTooltip(FeatureColourI fcol, + boolean withHint) { if (fcol == null) { return null; } - if (fcol.isSimpleColour()) + if (fcol.isSimpleColour() && withHint) { return BASE_TOOLTIP; } @@ -1378,7 +1379,10 @@ public class FeatureSettings extends JPanel description = description.replaceAll("<", "<"); description = description.replaceAll(">", ">"); StringBuilder tt = new StringBuilder(description); - tt.append("
").append(BASE_TOOLTIP).append("
"); + if (withHint) + { + tt.append("
").append(BASE_TOOLTIP).append("
"); + } return JvSwingUtils.wrapTooltip(true, tt.toString()); } diff --git a/src/jalview/gui/Finder.java b/src/jalview/gui/Finder.java index fd8eb0c..6361a86 100755 --- a/src/jalview/gui/Finder.java +++ b/src/jalview/gui/Finder.java @@ -205,11 +205,15 @@ public class Finder extends GFinder /** * Opens a dialog that allows the user to create sequence features for the - * find match results. + * find match results */ @Override public void createFeatures_actionPerformed() { + if (searchResults.isEmpty()) + { + return; // shouldn't happen + } List seqs = new ArrayList(); List features = new ArrayList(); @@ -227,21 +231,7 @@ public class Finder extends GFinder match.getEnd(), desc)); } - new FeatureEditor(ap).createFeatures(seqs, features, new Runnable() - { - @Override - public void run() - { - - /* - * ensure feature display is turned on to show the new features, - * and remove them as highlighted regions - */ - ap.alignFrame.showSeqFeatures.setSelected(true); - av.setShowSequenceFeatures(true); - ap.highlightSearchResults(null); - } - }); + new FeatureEditor(ap, seqs, features, true).showDialog(); } /** diff --git a/src/jalview/gui/PopupMenu.java b/src/jalview/gui/PopupMenu.java index 2bcd229..8e2cda4 100644 --- a/src/jalview/gui/PopupMenu.java +++ b/src/jalview/gui/PopupMenu.java @@ -78,7 +78,6 @@ import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; -import javax.swing.JTextArea; /** * DOCUMENT ME! @@ -2054,15 +2053,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener */ if (!seqs.isEmpty()) { - new FeatureEditor(ap).createFeatures(seqs, features, new Runnable() - { - public void run() - { - ap.alignFrame.setShowSeqFeatures(true); - ap.av.setSearchResults(null); // clear highlighting - ap.repaint(); // draw new/amended features - } - }); + new FeatureEditor(ap, seqs, features, true).showDialog(); } } diff --git a/src/jalview/gui/SeqPanel.java b/src/jalview/gui/SeqPanel.java index eff4985..875743b 100644 --- a/src/jalview/gui/SeqPanel.java +++ b/src/jalview/gui/SeqPanel.java @@ -1676,19 +1676,10 @@ public class SeqPanel extends JPanel seqCanvas.highlightSearchResults(highlight, true); /* - * open the Amend Features dialog; clear highlighting afterwards, - * whether changes were made or not + * open the Amend Features dialog */ - List seqs = Collections.singletonList(sequence); - new FeatureEditor(ap).amendFeatures(seqs, features, new Runnable() - { - @Override - public void run() - { - av.setSearchResults(null); // clear highlighting - seqCanvas.repaint(); // draw new/amended features - } - }); + new FeatureEditor(ap, Collections.singletonList(sequence), features, + false).showDialog(); } } } -- 1.7.10.2