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;
13 import jalview.util.dialogrunner.RunResponse;
15 import java.awt.BorderLayout;
16 import java.awt.Color;
17 import java.awt.Dimension;
19 import java.awt.GridLayout;
20 import java.awt.event.ActionEvent;
21 import java.awt.event.ActionListener;
22 import java.awt.event.ItemEvent;
23 import java.awt.event.ItemListener;
24 import java.awt.event.MouseAdapter;
25 import java.awt.event.MouseEvent;
26 import java.util.ArrayList;
27 import java.util.List;
29 import javax.swing.JComboBox;
30 import javax.swing.JLabel;
31 import javax.swing.JPanel;
32 import javax.swing.JScrollPane;
33 import javax.swing.JSpinner;
34 import javax.swing.JTextArea;
35 import javax.swing.JTextField;
36 import javax.swing.SpinnerNumberModel;
37 import javax.swing.SwingConstants;
38 import javax.swing.event.ChangeEvent;
39 import javax.swing.event.ChangeListener;
40 import javax.swing.event.DocumentEvent;
41 import javax.swing.event.DocumentListener;
44 * Provides a dialog allowing the user to add new features, or amend or delete
47 public class FeatureEditor
50 * defaults for creating a new feature are the last created
51 * feature type and group
53 static String lastFeatureAdded = "feature_1";
55 static String lastFeatureGroupAdded = "Jalview";
58 * the sequence(s) with features to be created / amended
60 final List<SequenceI> sequences;
63 * the features (or template features) to be created / amended
65 final List<SequenceFeature> features;
68 * true if the dialog is to create a new feature, false if
69 * for amend or delete of existing feature(s)
71 final boolean forCreate;
74 * index into the list of features
78 FeatureColourI oldColour;
80 FeatureColourI featureColour;
90 JTextArea description;
105 * if true create a new feature, else amend or delete an existing
108 public FeatureEditor(AlignmentPanel alignPanel, List<SequenceI> seqs,
109 List<SequenceFeature> feats, boolean create)
112 fr = alignPanel.getSeqPanel().seqCanvas.fr;
115 this.forCreate = create;
121 * Initialise the layout and controls
123 protected void init()
127 mainPanel = new JPanel(new BorderLayout());
129 name = new JTextField(25);
130 name.getDocument().addDocumentListener(new DocumentListener()
133 public void insertUpdate(DocumentEvent e)
135 warnIfTypeHidden(mainPanel, name.getText());
139 public void removeUpdate(DocumentEvent e)
141 warnIfTypeHidden(mainPanel, name.getText());
145 public void changedUpdate(DocumentEvent e)
147 warnIfTypeHidden(mainPanel, name.getText());
151 group = new JTextField(25);
152 group.getDocument().addDocumentListener(new DocumentListener()
155 public void insertUpdate(DocumentEvent e)
157 warnIfGroupHidden(mainPanel, group.getText());
161 public void removeUpdate(DocumentEvent e)
163 warnIfGroupHidden(mainPanel, group.getText());
167 public void changedUpdate(DocumentEvent e)
169 warnIfGroupHidden(mainPanel, group.getText());
173 description = new JTextArea(3, 25);
175 start = new JSpinner();
176 end = new JSpinner();
177 start.setPreferredSize(new Dimension(80, 20));
178 end.setPreferredSize(new Dimension(80, 20));
181 * ensure that start can never be more than end
183 start.addChangeListener(new ChangeListener()
186 public void stateChanged(ChangeEvent e)
188 Integer startVal = (Integer) start.getValue();
189 ((SpinnerNumberModel) end.getModel()).setMinimum(startVal);
192 end.addChangeListener(new ChangeListener()
195 public void stateChanged(ChangeEvent e)
197 Integer endVal = (Integer) end.getValue();
198 ((SpinnerNumberModel) start.getModel()).setMaximum(endVal);
202 final JLabel colour = new JLabel();
203 colour.setOpaque(true);
204 colour.setMaximumSize(new Dimension(30, 16));
205 colour.addMouseListener(new MouseAdapter()
208 public void mousePressed(MouseEvent evt)
210 if (featureColour.isSimpleColour())
213 * open colour chooser on click in colour panel
215 String title = MessageManager
216 .getString("label.select_feature_colour");
217 ColourChooserListener listener = new ColourChooserListener()
220 public void colourSelected(Color c)
222 featureColour = new FeatureColour(c);
223 updateColourButton(mainPanel, colour, featureColour);
226 JalviewColourChooser.showColourChooser(Desktop.getDesktop(),
227 title, featureColour.getColour(), listener);
232 * variable colour dialog - on OK, refetch the updated
233 * feature colour and update this display
235 final String ft = features.get(featureIndex).getType();
236 final String type = ft == null ? lastFeatureAdded : ft;
237 FeatureTypeSettings fcc = new FeatureTypeSettings(fr, type);
238 fcc.setRequestFocusEnabled(true);
240 fcc.addActionListener(new ActionListener()
243 public void actionPerformed(ActionEvent e)
245 featureColour = fr.getFeatureStyle(ft);
246 fr.setColour(type, featureColour);
247 updateColourButton(mainPanel, colour, featureColour);
253 JPanel gridPanel = new JPanel(new GridLayout(3, 1));
255 if (!forCreate && features.size() > 1)
258 * more than one feature at selected position -
259 * add a drop-down to choose the feature to amend
260 * space pad text if necessary to make entries distinct
262 gridPanel = new JPanel(new GridLayout(4, 1));
263 JPanel choosePanel = new JPanel();
264 choosePanel.add(new JLabel(
265 MessageManager.getString("label.select_feature") + ":"));
266 final JComboBox<String> overlaps = new JComboBox<>();
267 List<String> added = new ArrayList<>();
268 for (SequenceFeature sf : features)
270 String text = String.format("%s/%d-%d (%s)", sf.getType(),
271 sf.getBegin(), sf.getEnd(), sf.getFeatureGroup());
272 while (added.contains(text))
276 overlaps.addItem(text);
279 choosePanel.add(overlaps);
281 overlaps.addItemListener(new ItemListener()
284 public void itemStateChanged(ItemEvent e)
286 int index = overlaps.getSelectedIndex();
289 featureIndex = index;
290 SequenceFeature sf = features.get(index);
291 name.setText(sf.getType());
292 description.setText(sf.getDescription());
293 group.setText(sf.getFeatureGroup());
294 start.setValue(new Integer(sf.getBegin()));
295 end.setValue(new Integer(sf.getEnd()));
296 ((SpinnerNumberModel) start.getModel()).setMaximum(sf.getEnd());
297 ((SpinnerNumberModel) end.getModel()).setMinimum(sf.getBegin());
299 SearchResultsI highlight = new SearchResults();
300 highlight.addResult(sequences.get(0), sf.getBegin(),
303 ap.getSeqPanel().seqCanvas.highlightSearchResults(highlight);
305 FeatureColourI col = fr.getFeatureStyle(name.getText());
308 col = new FeatureColour(
309 ColorUtils.createColourFromName(name.getText()));
311 oldColour = featureColour = col;
312 updateColourButton(mainPanel, colour, col);
316 gridPanel.add(choosePanel);
319 JPanel namePanel = new JPanel();
320 gridPanel.add(namePanel);
321 namePanel.add(new JLabel(MessageManager.getString("label.name:"),
325 JPanel groupPanel = new JPanel();
326 gridPanel.add(groupPanel);
327 groupPanel.add(new JLabel(MessageManager.getString("label.group:"),
329 groupPanel.add(group);
331 JPanel colourPanel = new JPanel();
332 gridPanel.add(colourPanel);
333 colourPanel.add(new JLabel(MessageManager.getString("label.colour"),
335 colourPanel.add(colour);
336 colour.setPreferredSize(new Dimension(150, 15));
337 colour.setFont(new java.awt.Font("Verdana", Font.PLAIN, 9));
338 colour.setForeground(Color.black);
339 colour.setHorizontalAlignment(SwingConstants.CENTER);
340 colour.setVerticalAlignment(SwingConstants.CENTER);
341 colour.setHorizontalTextPosition(SwingConstants.CENTER);
342 colour.setVerticalTextPosition(SwingConstants.CENTER);
343 mainPanel.add(gridPanel, BorderLayout.NORTH);
345 JPanel descriptionPanel = new JPanel();
346 descriptionPanel.add(new JLabel(
347 MessageManager.getString("label.description:"), JLabel.RIGHT));
348 description.setFont(JvSwingUtils.getTextAreaFont());
349 description.setLineWrap(true);
350 descriptionPanel.add(new JScrollPane(description));
354 mainPanel.add(descriptionPanel, BorderLayout.SOUTH);
356 JPanel startEndPanel = new JPanel();
357 startEndPanel.add(new JLabel(MessageManager.getString("label.start"),
359 startEndPanel.add(start);
360 startEndPanel.add(new JLabel(MessageManager.getString("label.end"),
362 startEndPanel.add(end);
363 mainPanel.add(startEndPanel, BorderLayout.CENTER);
367 mainPanel.add(descriptionPanel, BorderLayout.CENTER);
371 * default feature type and group to that of the first feature supplied,
372 * or to the last feature created if not supplied (null value)
374 SequenceFeature firstFeature = features.get(0);
375 boolean useLastDefaults = firstFeature.getType() == null;
376 final String featureType = useLastDefaults ? lastFeatureAdded
377 : firstFeature.getType();
378 final String featureGroup = useLastDefaults ? lastFeatureGroupAdded
379 : firstFeature.getFeatureGroup();
380 name.setText(featureType);
381 group.setText(featureGroup);
383 start.setValue(new Integer(firstFeature.getBegin()));
384 end.setValue(new Integer(firstFeature.getEnd()));
385 ((SpinnerNumberModel) start.getModel()).setMaximum(firstFeature.getEnd());
386 ((SpinnerNumberModel) end.getModel()).setMinimum(firstFeature.getBegin());
388 description.setText(firstFeature.getDescription());
389 featureColour = fr.getFeatureStyle(featureType);
390 oldColour = featureColour;
391 updateColourButton(mainPanel, colour, oldColour);
395 * Presents a dialog allowing the user to add new features, or amend or delete
396 * an existing feature. Currently this can be on
398 * <li>double-click on a sequence - Amend/Delete a selected feature at the
400 * <li>Create sequence feature(s) from pop-up menu on selected region</li>
401 * <li>Create features for pattern matches from Find</li>
403 * If the supplied feature type is null, show (and update on confirm) the type
404 * and group of the last new feature created (with initial defaults of
405 * "feature_1" and "Jalview").
407 public void showDialog()
409 RunResponse okAction = forCreate ? getCreateAction() : getAmendAction();
410 RunResponse cancelAction = getCancelAction();
413 * set dialog action handlers for OK (create/Amend) and Cancel options
414 * also for Delete if applicable (when amending features)
416 JvOptionPane dialog = JvOptionPane.newOptionDialog(Desktop.desktop)
417 .addResponse(okAction).addResponse(cancelAction);
420 dialog.addResponse(getDeleteAction());
424 Object[] options = null;
427 title = MessageManager
428 .getString("label.create_new_sequence_features");
429 options = new Object[] { MessageManager.getString("action.ok"),
430 MessageManager.getString("action.cancel") };
434 title = MessageManager.formatMessage("label.amend_delete_features",
436 { sequences.get(0).getName() });
437 options = new Object[] { MessageManager.getString("label.amend"),
438 MessageManager.getString("action.delete"),
439 MessageManager.getString("action.cancel") };
442 dialog.showInternalDialog(mainPanel, title,
443 JvOptionPane.YES_NO_CANCEL_OPTION,
444 JvOptionPane.PLAIN_MESSAGE, null, options,
445 MessageManager.getString("action.ok"));
449 * Answers an action to run on Cancel in the dialog. This is just to remove
450 * any feature highlighting from the display. Changes in the dialog are not
451 * applied until it is dismissed with OK, Amend or Delete, so there are no
452 * updates to reset on Cancel.
456 protected RunResponse getCancelAction()
458 RunResponse okAction = new RunResponse(JvOptionPane.CANCEL_OPTION)
463 ap.highlightSearchResults(null);
464 ap.paintAlignment(false, false);
471 * Returns the action to be run on OK in the dialog when creating one or more
472 * sequence features. Note these may have a pre-supplied feature type (such as
473 * a Find pattern), or none, in which case the feature type and group default
474 * to those last added through this dialog. The action includes refreshing the
475 * Feature Settings panel (if it is open), to show any new feature type, or
476 * amended colour for an existing type.
480 protected RunResponse getCreateAction()
482 RunResponse okAction = new RunResponse(JvOptionPane.OK_OPTION)
484 boolean useLastDefaults = features.get(0).getType() == null;
488 final String enteredType = name.getText().trim();
489 final String enteredGroup = group.getText().trim();
490 final String enteredDescription = description.getText()
491 .replaceAll("\n", " ");
492 if (enteredType.length() > 0)
495 * update default values only if creating using default values
499 lastFeatureAdded = enteredType;
500 lastFeatureGroupAdded = enteredGroup;
501 // TODO: determine if the null feature group is valid
502 if (lastFeatureGroupAdded.length() < 1)
504 lastFeatureGroupAdded = null;
509 if (enteredType.length() > 0)
511 for (int i = 0; i < sequences.size(); i++)
513 SequenceFeature sf = features.get(i);
514 SequenceFeature sf2 = new SequenceFeature(enteredType,
515 enteredDescription, sf.getBegin(), sf.getEnd(),
517 new FeaturesFile().parseDescriptionHTML(sf2, false);
518 sequences.get(i).addSequenceFeature(sf2);
521 fr.setColour(enteredType, featureColour);
532 * Answers the action to run on Delete in the dialog. Note this includes
533 * refreshing the Feature Settings (if open) in case the only instance of a
534 * feature type or group has been deleted.
538 protected RunResponse getDeleteAction()
540 RunResponse deleteAction = new RunResponse(JvOptionPane.NO_OPTION)
544 SequenceFeature sf = features.get(featureIndex);
545 sequences.get(0).getDatasetSequence().deleteFeature(sf);
547 ap.getSeqPanel().seqCanvas.highlightSearchResults(null);
548 ap.paintAlignment(true, true);
555 * update the amend feature button dependent on the given style
561 protected void updateColourButton(JPanel bigPanel, JLabel colour,
565 colour.setIcon(null);
568 if (col.isSimpleColour())
570 colour.setToolTipText(null);
571 colour.setBackground(col.getColour());
575 colour.setBackground(bigPanel.getBackground());
576 colour.setForeground(Color.black);
577 colour.setToolTipText(FeatureSettings.getColorTooltip(col, false));
578 FeatureSettings.renderGraduatedColor(colour, col);
583 * Show a warning message if the entered group is one that is currently hidden
588 protected void warnIfGroupHidden(JPanel panel, String group)
590 if (!fr.isGroupVisible(group))
592 String msg = MessageManager.formatMessage("label.warning_hidden",
593 MessageManager.getString("label.group"), group);
594 JvOptionPane.showMessageDialog(panel, msg, "",
595 JvOptionPane.OK_OPTION);
600 * Show a warning message if the entered type is one that is currently hidden
605 protected void warnIfTypeHidden(JPanel panel, String type)
607 if (fr.getRenderOrder().contains(type))
609 if (!fr.showFeatureOfType(type))
611 String msg = MessageManager.formatMessage("label.warning_hidden",
612 MessageManager.getString("label.feature_type"), type);
613 JvOptionPane.showMessageDialog(panel, msg, "",
614 JvOptionPane.OK_OPTION);
620 * On closing the dialog - ensure feature display is turned on, to show any
621 * new features - remove highlighting of the last selected feature - repaint
622 * the panel to show any changes
624 protected void repaintPanel()
626 ap.alignFrame.showSeqFeatures.setSelected(true);
627 ap.av.setShowSequenceFeatures(true);
628 ap.av.setSearchResults(null);
629 ap.paintAlignment(true, true);
633 * Returns the action to be run on OK in the dialog when amending a feature.
634 * Note this may include refreshing the Feature Settings panel (if it is
635 * open), if feature type, group or colour has changed (but not for
636 * description or extent).
640 protected RunResponse getAmendAction()
642 RunResponse okAction = new RunResponse(JvOptionPane.OK_OPTION)
644 boolean useLastDefaults = features.get(0).getType() == null;
646 String featureType = name.getText();
648 String featureGroup = group.getText();
652 final String enteredType = name.getText().trim();
653 final String enteredGroup = group.getText().trim();
654 final String enteredDescription = description.getText()
655 .replaceAll("\n", " ");
656 if (enteredType.length() > 0)
660 * update default values only if creating using default values
664 lastFeatureAdded = enteredType;
665 lastFeatureGroupAdded = enteredGroup;
666 // TODO: determine if the null feature group is valid
667 if (lastFeatureGroupAdded.length() < 1)
669 lastFeatureGroupAdded = null;
674 SequenceFeature sf = features.get(featureIndex);
677 * Need to refresh Feature Settings if type, group or colour changed;
678 * note we don't force the feature to be visible - the user has been
679 * warned if a hidden feature type or group was entered
681 boolean refreshSettings = (!featureType.equals(enteredType)
682 || !featureGroup.equals(enteredGroup));
683 refreshSettings |= (featureColour != oldColour);
684 fr.setColour(enteredType, featureColour);
685 int newBegin = sf.begin;
689 newBegin = ((Integer) start.getValue()).intValue();
690 newEnd = ((Integer) end.getValue()).intValue();
691 } catch (NumberFormatException ex)
693 // JSpinner doesn't accept invalid format data :-)
697 * 'amend' the feature by deleting it and adding a new one
698 * (to ensure integrity of SequenceFeatures data store)
699 * note this dialog only updates one sequence at a time
701 sequences.get(0).deleteFeature(sf);
702 SequenceFeature newSf = new SequenceFeature(sf, enteredType,
703 newBegin, newEnd, enteredGroup, sf.getScore());
704 newSf.setDescription(enteredDescription);
705 new FeaturesFile().parseDescriptionHTML(newSf, false);
706 sequences.get(0).addSequenceFeature(newSf);