3 import jalview.api.FeatureColourI;
4 import jalview.datamodel.SearchResults;
5 import jalview.datamodel.SearchResultsI;
6 import jalview.datamodel.SequenceFeature;
7 import jalview.datamodel.SequenceI;
8 import jalview.gui.JalviewColourChooser.ColourChooserListener;
9 import jalview.io.FeaturesFile;
10 import jalview.schemes.FeatureColour;
11 import jalview.util.ColorUtils;
12 import jalview.util.MessageManager;
14 import java.awt.BorderLayout;
15 import java.awt.Color;
16 import java.awt.Dimension;
18 import java.awt.GridLayout;
19 import java.awt.event.ActionEvent;
20 import java.awt.event.ActionListener;
21 import java.awt.event.ItemEvent;
22 import java.awt.event.ItemListener;
23 import java.awt.event.MouseAdapter;
24 import java.awt.event.MouseEvent;
25 import java.util.ArrayList;
26 import java.util.List;
28 import javax.swing.JComboBox;
29 import javax.swing.JLabel;
30 import javax.swing.JPanel;
31 import javax.swing.JScrollPane;
32 import javax.swing.JSpinner;
33 import javax.swing.JTextArea;
34 import javax.swing.JTextField;
35 import javax.swing.SpinnerNumberModel;
36 import javax.swing.SwingConstants;
37 import javax.swing.event.ChangeEvent;
38 import javax.swing.event.ChangeListener;
39 import javax.swing.event.DocumentEvent;
40 import javax.swing.event.DocumentListener;
43 * Provides a dialog allowing the user to add new features, or amend or delete
46 public class FeatureEditor
49 * defaults for creating a new feature are the last created
50 * feature type and group
52 static String lastFeatureAdded = "feature_1";
54 static String lastFeatureGroupAdded = "Jalview";
57 * the sequence(s) with features to be created / amended
59 final List<SequenceI> sequences;
62 * the features (or template features) to be created / amended
64 final List<SequenceFeature> features;
67 * true if the dialog is to create a new feature, false if
68 * for amend or delete of existing feature(s)
70 final boolean forCreate;
73 * index into the list of features
77 FeatureColourI oldColour;
79 FeatureColourI featureColour;
89 JTextArea description;
102 * one or more Sequence if create is true; a singleton if create is
105 * a list of new SequenceFeature instances if create is true,
106 * otherwise a list of known features for this sequence at this
109 * if true create a new feature, else amend or delete an existing
112 public FeatureEditor(AlignmentPanel alignPanel, List<SequenceI> seqs,
113 List<SequenceFeature> feats, boolean create)
116 fr = alignPanel.getSeqPanel().seqCanvas.fr;
119 this.forCreate = create;
125 * Initialise the layout and controls
127 protected void init()
131 mainPanel = new JPanel(new BorderLayout());
133 name = new JTextField(25);
134 name.getDocument().addDocumentListener(new DocumentListener()
137 public void insertUpdate(DocumentEvent e)
139 warnIfTypeHidden(mainPanel, name.getText());
143 public void removeUpdate(DocumentEvent e)
145 warnIfTypeHidden(mainPanel, name.getText());
149 public void changedUpdate(DocumentEvent e)
151 warnIfTypeHidden(mainPanel, name.getText());
155 group = new JTextField(25);
156 group.getDocument().addDocumentListener(new DocumentListener()
159 public void insertUpdate(DocumentEvent e)
161 warnIfGroupHidden(mainPanel, group.getText());
165 public void removeUpdate(DocumentEvent e)
167 warnIfGroupHidden(mainPanel, group.getText());
171 public void changedUpdate(DocumentEvent e)
173 warnIfGroupHidden(mainPanel, group.getText());
177 description = new JTextArea(3, 25);
179 start = new JSpinner();
180 end = new JSpinner();
181 start.setPreferredSize(new Dimension(80, 20));
182 end.setPreferredSize(new Dimension(80, 20));
185 * ensure that start can never be more than end
187 start.addChangeListener(new ChangeListener()
190 public void stateChanged(ChangeEvent e)
192 Integer startVal = (Integer) start.getValue();
193 ((SpinnerNumberModel) end.getModel()).setMinimum(startVal);
196 end.addChangeListener(new ChangeListener()
199 public void stateChanged(ChangeEvent e)
201 Integer endVal = (Integer) end.getValue();
202 ((SpinnerNumberModel) start.getModel()).setMaximum(endVal);
206 final JLabel colour = new JLabel();
207 colour.setOpaque(true);
208 colour.setMaximumSize(new Dimension(30, 16));
209 colour.addMouseListener(new MouseAdapter()
212 public void mousePressed(MouseEvent evt)
214 if (featureColour.isSimpleColour())
217 * open colour chooser on click in colour panel
219 String title = MessageManager
220 .getString("label.select_feature_colour");
221 ColourChooserListener listener = new ColourChooserListener()
224 public void colourSelected(Color c)
226 featureColour = new FeatureColour(c);
227 updateColourButton(mainPanel, colour, featureColour);
230 JalviewColourChooser.showColourChooser(Desktop.getDesktopPane(),
231 title, featureColour.getColour(), listener);
236 * variable colour dialog - on OK, refetch the updated
237 * feature colour and update this display
239 final String ft = features.get(featureIndex).getType();
240 final String type = ft == null ? lastFeatureAdded : ft;
241 FeatureTypeSettings fcc = new FeatureTypeSettings(fr, type);
242 fcc.setRequestFocusEnabled(true);
244 fcc.addActionListener(new ActionListener()
247 public void actionPerformed(ActionEvent e)
249 featureColour = fr.getFeatureStyle(ft);
250 fr.setColour(type, featureColour);
251 updateColourButton(mainPanel, colour, featureColour);
257 JPanel gridPanel = new JPanel(new GridLayout(3, 1));
259 if (!forCreate && features.size() > 1)
262 * more than one feature at selected position -
263 * add a drop-down to choose the feature to amend
264 * space pad text if necessary to make entries distinct
266 gridPanel = new JPanel(new GridLayout(4, 1));
267 JPanel choosePanel = new JPanel();
268 choosePanel.add(new JLabel(
269 MessageManager.getString("label.select_feature") + ":"));
270 final JComboBox<String> overlaps = new JComboBox<>();
271 List<String> added = new ArrayList<>();
272 for (SequenceFeature sf : features)
274 String text = String.format("%s/%d-%d (%s)", sf.getType(),
275 sf.getBegin(), sf.getEnd(), sf.getFeatureGroup());
276 while (added.contains(text))
280 overlaps.addItem(text);
283 choosePanel.add(overlaps);
285 overlaps.addItemListener(new ItemListener()
288 public void itemStateChanged(ItemEvent e)
290 int index = overlaps.getSelectedIndex();
293 featureIndex = index;
294 SequenceFeature sf = features.get(index);
295 name.setText(sf.getType());
296 description.setText(sf.getDescription());
297 group.setText(sf.getFeatureGroup());
298 start.setValue(new Integer(sf.getBegin()));
299 end.setValue(new Integer(sf.getEnd()));
300 ((SpinnerNumberModel) start.getModel()).setMaximum(sf.getEnd());
301 ((SpinnerNumberModel) end.getModel()).setMinimum(sf.getBegin());
303 SearchResultsI highlight = new SearchResults();
304 highlight.addResult(sequences.get(0), sf.getBegin(),
307 ap.getSeqPanel().seqCanvas.highlightSearchResults(highlight);
309 FeatureColourI col = fr.getFeatureStyle(name.getText());
312 col = new FeatureColour(
313 ColorUtils.createColourFromName(name.getText()));
315 oldColour = featureColour = col;
316 updateColourButton(mainPanel, colour, col);
320 gridPanel.add(choosePanel);
323 JPanel namePanel = new JPanel();
324 gridPanel.add(namePanel);
325 namePanel.add(new JLabel(MessageManager.getString("label.name:"),
329 JPanel groupPanel = new JPanel();
330 gridPanel.add(groupPanel);
331 groupPanel.add(new JLabel(MessageManager.getString("label.group:"),
333 groupPanel.add(group);
335 JPanel colourPanel = new JPanel();
336 gridPanel.add(colourPanel);
337 colourPanel.add(new JLabel(MessageManager.getString("label.colour"),
339 colourPanel.add(colour);
340 colour.setPreferredSize(new Dimension(150, 15));
341 colour.setFont(new java.awt.Font("Verdana", Font.PLAIN, 9));
342 colour.setForeground(Color.black);
343 colour.setHorizontalAlignment(SwingConstants.CENTER);
344 colour.setVerticalAlignment(SwingConstants.CENTER);
345 colour.setHorizontalTextPosition(SwingConstants.CENTER);
346 colour.setVerticalTextPosition(SwingConstants.CENTER);
347 mainPanel.add(gridPanel, BorderLayout.NORTH);
349 JPanel descriptionPanel = new JPanel();
350 descriptionPanel.add(new JLabel(
351 MessageManager.getString("label.description:"), JLabel.RIGHT));
352 description.setFont(JvSwingUtils.getTextAreaFont());
353 description.setLineWrap(true);
354 descriptionPanel.add(new JScrollPane(description));
358 mainPanel.add(descriptionPanel, BorderLayout.SOUTH);
360 JPanel startEndPanel = new JPanel();
361 startEndPanel.add(new JLabel(MessageManager.getString("label.start"),
363 startEndPanel.add(start);
364 startEndPanel.add(new JLabel(MessageManager.getString("label.end"),
366 startEndPanel.add(end);
367 mainPanel.add(startEndPanel, BorderLayout.CENTER);
371 mainPanel.add(descriptionPanel, BorderLayout.CENTER);
375 * default feature type and group to that of the first feature supplied,
376 * or to the last feature created if not supplied (null value)
378 SequenceFeature firstFeature = features.get(0);
379 boolean useLastDefaults = firstFeature.getType() == null;
380 final String featureType = useLastDefaults ? lastFeatureAdded
381 : firstFeature.getType();
382 final String featureGroup = useLastDefaults ? lastFeatureGroupAdded
383 : firstFeature.getFeatureGroup();
384 name.setText(featureType);
385 group.setText(featureGroup);
387 start.setValue(new Integer(firstFeature.getBegin()));
388 end.setValue(new Integer(firstFeature.getEnd()));
389 ((SpinnerNumberModel) start.getModel()).setMaximum(firstFeature.getEnd());
390 ((SpinnerNumberModel) end.getModel()).setMinimum(firstFeature.getBegin());
392 description.setText(firstFeature.getDescription());
393 featureColour = fr.getFeatureStyle(featureType);
394 oldColour = featureColour;
395 updateColourButton(mainPanel, colour, oldColour);
399 * Presents a dialog allowing the user to add new features, or amend or delete
400 * an existing feature. Currently this can be on
402 * <li>double-click on a sequence - Amend/Delete a selected feature at the
404 * <li>Create sequence feature(s) from pop-up menu on selected region</li>
405 * <li>Create features for pattern matches from Find</li>
407 * If the supplied feature type is null, show (and update on confirm) the type
408 * and group of the last new feature created (with initial defaults of
409 * "feature_1" and "Jalview").
411 public void showDialog()
413 Runnable okAction = forCreate ? getCreateAction() : getAmendAction();
414 Runnable cancelAction = getCancelAction();
417 * set dialog action handlers for OK (create/Amend) and Cancel options
418 * also for Delete if applicable (when amending features)
420 JvOptionPane dialog = JvOptionPane.newOptionDialog(Desktop.getDesktopPane())
421 .setResponseHandler(0, okAction).setResponseHandler(2, cancelAction);
424 dialog.setResponseHandler(1, getDeleteAction());
428 Object[] options = null;
431 title = MessageManager
432 .getString("label.create_new_sequence_features");
433 options = new Object[] { MessageManager.getString("action.ok"),
434 MessageManager.getString("action.cancel") };
438 title = MessageManager.formatMessage("label.amend_delete_features",
440 { sequences.get(0).getName() });
441 options = new Object[] { MessageManager.getString("label.amend"),
442 MessageManager.getString("action.delete"),
443 MessageManager.getString("action.cancel") };
446 dialog.showInternalDialog(mainPanel, title,
447 JvOptionPane.YES_NO_CANCEL_OPTION,
448 JvOptionPane.PLAIN_MESSAGE, null, options,
449 MessageManager.getString("action.ok"));
453 * Answers an action to run on Cancel in the dialog. This is just to remove
454 * any feature highlighting from the display. Changes in the dialog are not
455 * applied until it is dismissed with OK, Amend or Delete, so there are no
456 * updates to reset on Cancel.
460 protected Runnable getCancelAction()
462 Runnable okAction = new Runnable()
467 ap.highlightSearchResults(null);
468 ap.paintAlignment(false, false);
475 * Returns the action to be run on OK in the dialog when creating one or more
476 * sequence features. Note these may have a pre-supplied feature type (such as
477 * a Find pattern), or none, in which case the feature type and group default
478 * to those last added through this dialog. The action includes refreshing the
479 * Feature Settings panel (if it is open), to show any new feature type, or
480 * amended colour for an existing type.
484 protected Runnable getCreateAction()
486 Runnable okAction = new Runnable()
488 boolean useLastDefaults = features.get(0).getType() == null;
493 final String enteredType = name.getText().trim();
494 final String enteredGroup = group.getText().trim();
495 final String enteredDescription = description.getText()
496 .replaceAll("\n", " ");
497 if (enteredType.length() > 0)
500 * update default values only if creating using default values
504 lastFeatureAdded = enteredType;
505 lastFeatureGroupAdded = enteredGroup;
506 // TODO: determine if the null feature group is valid
507 if (lastFeatureGroupAdded.length() < 1)
509 lastFeatureGroupAdded = null;
514 if (enteredType.length() > 0)
516 for (int i = 0; i < sequences.size(); i++)
518 SequenceFeature sf = features.get(i);
519 SequenceFeature sf2 = new SequenceFeature(enteredType,
520 enteredDescription, sf.getBegin(), sf.getEnd(),
522 new FeaturesFile().parseDescriptionHTML(sf2, false);
523 sequences.get(i).addSequenceFeature(sf2);
526 fr.setColour(enteredType, featureColour);
537 * Answers the action to run on Delete in the dialog. Note this includes
538 * refreshing the Feature Settings (if open) in case the only instance of a
539 * feature type or group has been deleted.
543 protected Runnable getDeleteAction()
545 Runnable deleteAction = new Runnable()
550 SequenceFeature sf = features.get(featureIndex);
551 sequences.get(0).getDatasetSequence().deleteFeature(sf);
553 ap.getSeqPanel().seqCanvas.highlightSearchResults(null);
554 ap.paintAlignment(true, true);
561 * update the amend feature button dependent on the given style
567 protected void updateColourButton(JPanel bigPanel, JLabel colour,
571 colour.setIcon(null);
574 if (col.isSimpleColour())
576 colour.setToolTipText(null);
577 colour.setBackground(col.getColour());
581 colour.setBackground(bigPanel.getBackground());
582 colour.setForeground(Color.black);
583 colour.setToolTipText(FeatureSettings.getColorTooltip(col, false));
584 FeatureSettings.renderGraduatedColor(colour, col);
589 * Show a warning message if the entered group is one that is currently hidden
594 protected void warnIfGroupHidden(JPanel panel, String group)
596 if (!fr.isGroupVisible(group))
598 String msg = MessageManager.formatMessage("label.warning_hidden",
599 MessageManager.getString("label.group"), group);
600 JvOptionPane.showMessageDialog(panel, msg, "",
601 JvOptionPane.OK_OPTION);
606 * Show a warning message if the entered type is one that is currently hidden
611 protected void warnIfTypeHidden(JPanel panel, String type)
613 if (fr.getRenderOrder().contains(type))
615 if (!fr.showFeatureOfType(type))
617 String msg = MessageManager.formatMessage("label.warning_hidden",
618 MessageManager.getString("label.feature_type"), type);
619 JvOptionPane.showMessageDialog(panel, msg, "",
620 JvOptionPane.OK_OPTION);
626 * On closing the dialog - ensure feature display is turned on, to show any
627 * new features - remove highlighting of the last selected feature - repaint
628 * the panel to show any changes
630 protected void repaintPanel()
632 ap.alignFrame.showSeqFeatures.setSelected(true);
633 ap.av.setShowSequenceFeatures(true);
634 ap.av.setSearchResults(null);
635 ap.paintAlignment(true, true);
639 * Returns the action to be run on OK in the dialog when amending a feature.
640 * Note this may include refreshing the Feature Settings panel (if it is
641 * open), if feature type, group or colour has changed (but not for
642 * description or extent).
646 protected Runnable getAmendAction()
648 Runnable okAction = new Runnable()
650 boolean useLastDefaults = features.get(0).getType() == null;
652 String featureType = name.getText();
654 String featureGroup = group.getText();
659 final String enteredType = name.getText().trim();
660 final String enteredGroup = group.getText().trim();
661 final String enteredDescription = description.getText()
662 .replaceAll("\n", " ");
663 if (enteredType.length() > 0)
667 * update default values only if creating using default values
671 lastFeatureAdded = enteredType;
672 lastFeatureGroupAdded = enteredGroup;
673 // TODO: determine if the null feature group is valid
674 if (lastFeatureGroupAdded.length() < 1)
676 lastFeatureGroupAdded = null;
681 SequenceFeature sf = features.get(featureIndex);
684 * Need to refresh Feature Settings if type, group or colour changed;
685 * note we don't force the feature to be visible - the user has been
686 * warned if a hidden feature type or group was entered
688 boolean refreshSettings = (!featureType.equals(enteredType)
689 || !featureGroup.equals(enteredGroup));
690 refreshSettings |= (featureColour != oldColour);
691 fr.setColour(enteredType, featureColour);
692 int newBegin = sf.begin;
696 newBegin = ((Integer) start.getValue()).intValue();
697 newEnd = ((Integer) end.getValue()).intValue();
698 } catch (NumberFormatException ex)
700 // JSpinner doesn't accept invalid format data :-)
704 * 'amend' the feature by deleting it and adding a new one
705 * (to ensure integrity of SequenceFeatures data store)
706 * note this dialog only updates one sequence at a time
708 sequences.get(0).deleteFeature(sf);
709 SequenceFeature newSf = new SequenceFeature(sf, enteredType,
710 newBegin, newEnd, enteredGroup, sf.getScore());
711 newSf.setDescription(enteredDescription);
712 new FeaturesFile().parseDescriptionHTML(newSf, false);
713 sequences.get(0).addSequenceFeature(newSf);