Merge branch 'develop' into spike/JAL-4047/JAL-4048_columns_in_sequenceID
[jalview.git] / src / jalview / gui / PopupMenu.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.event.ActionEvent;
26 import java.awt.event.ActionListener;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.BitSet;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.Hashtable;
33 import java.util.LinkedHashMap;
34 import java.util.List;
35 import java.util.Locale;
36 import java.util.Map;
37 import java.util.Objects;
38 import java.util.SortedMap;
39 import java.util.TreeMap;
40 import java.util.Vector;
41
42 import javax.swing.ButtonGroup;
43 import javax.swing.JCheckBoxMenuItem;
44 import javax.swing.JInternalFrame;
45 import javax.swing.JLabel;
46 import javax.swing.JMenu;
47 import javax.swing.JMenuItem;
48 import javax.swing.JPanel;
49 import javax.swing.JPopupMenu;
50 import javax.swing.JRadioButtonMenuItem;
51 import javax.swing.JScrollPane;
52
53 import jalview.analysis.AAFrequency;
54 import jalview.analysis.AlignmentAnnotationUtils;
55 import jalview.analysis.AlignmentUtils;
56 import jalview.analysis.Conservation;
57 import jalview.api.AlignViewportI;
58 import jalview.bin.Console;
59 import jalview.commands.ChangeCaseCommand;
60 import jalview.commands.EditCommand;
61 import jalview.commands.EditCommand.Action;
62 import jalview.datamodel.AlignmentAnnotation;
63 import jalview.datamodel.AlignmentI;
64 import jalview.datamodel.DBRefEntry;
65 import jalview.datamodel.HiddenColumns;
66 import jalview.datamodel.MappedFeatures;
67 import jalview.datamodel.PDBEntry;
68 import jalview.datamodel.SequenceFeature;
69 import jalview.datamodel.SequenceGroup;
70 import jalview.datamodel.SequenceI;
71 import jalview.gui.ColourMenuHelper.ColourChangeListener;
72 import jalview.gui.JalviewColourChooser.ColourChooserListener;
73 import jalview.io.FileFormatI;
74 import jalview.io.FileFormats;
75 import jalview.io.FormatAdapter;
76 import jalview.io.SequenceAnnotationReport;
77 import jalview.schemes.Blosum62ColourScheme;
78 import jalview.schemes.ColourSchemeI;
79 import jalview.schemes.ColourSchemes;
80 import jalview.schemes.PIDColourScheme;
81 import jalview.schemes.ResidueColourScheme;
82 import jalview.util.Comparison;
83 import jalview.util.GroupUrlLink;
84 import jalview.util.GroupUrlLink.UrlStringTooLongException;
85 import jalview.util.MessageManager;
86 import jalview.util.Platform;
87 import jalview.util.StringUtils;
88 import jalview.util.UrlLink;
89 import jalview.viewmodel.seqfeatures.FeatureRendererModel;
90 import jalview.viewmodel.seqfeatures.IdColumn;
91 import jalview.viewmodel.seqfeatures.IdColumns;
92
93 /**
94  * The popup menu that is displayed on right-click on a sequence id, or in the
95  * sequence alignment.
96  */
97 public class PopupMenu extends JPopupMenu implements ColourChangeListener
98 {
99   /*
100    * maximum length of feature description to include in popup menu item text
101    */
102   private static final int FEATURE_DESC_MAX = 40;
103
104   /*
105    * true for ID Panel menu, false for alignment panel menu
106    */
107   private final boolean forIdPanel;
108
109   private final AlignmentPanel ap;
110
111   /*
112    * the sequence under the cursor when clicked
113    * (additional sequences may be selected)
114    */
115   private final SequenceI sequence;
116
117   JMenu groupMenu = new JMenu();
118
119   JMenuItem groupName = new JMenuItem();
120
121   protected JCheckBoxMenuItem abovePIDColour = new JCheckBoxMenuItem();
122
123   protected JMenuItem modifyPID = new JMenuItem();
124
125   protected JCheckBoxMenuItem conservationMenuItem = new JCheckBoxMenuItem();
126
127   protected JRadioButtonMenuItem annotationColour;
128
129   protected JMenuItem modifyConservation = new JMenuItem();
130
131   JMenu sequenceMenu = new JMenu();
132
133   JMenuItem makeReferenceSeq = new JMenuItem();
134
135   JMenuItem createGroupMenuItem = new JMenuItem();
136
137   JMenuItem unGroupMenuItem = new JMenuItem();
138
139   JMenu colourMenu = new JMenu();
140
141   JCheckBoxMenuItem showBoxes = new JCheckBoxMenuItem();
142
143   JCheckBoxMenuItem showText = new JCheckBoxMenuItem();
144
145   JCheckBoxMenuItem showColourText = new JCheckBoxMenuItem();
146
147   JCheckBoxMenuItem displayNonconserved = new JCheckBoxMenuItem();
148
149   JMenu editMenu = new JMenu();
150
151   JMenuItem upperCase = new JMenuItem();
152
153   JMenuItem lowerCase = new JMenuItem();
154
155   JMenuItem toggle = new JMenuItem();
156
157   JMenu outputMenu = new JMenu();
158
159   JMenu seqShowAnnotationsMenu = new JMenu();
160
161   JMenu seqHideAnnotationsMenu = new JMenu();
162
163   JMenuItem seqAddReferenceAnnotations = new JMenuItem(
164           MessageManager.getString("label.add_reference_annotations"));
165
166   JMenu groupShowAnnotationsMenu = new JMenu();
167
168   JMenu groupHideAnnotationsMenu = new JMenu();
169
170   JMenuItem groupAddReferenceAnnotations = new JMenuItem(
171           MessageManager.getString("label.add_reference_annotations"));
172
173   JMenuItem textColour = new JMenuItem();
174
175   JMenu editGroupMenu = new JMenu();
176
177   JMenuItem chooseStructure = new JMenuItem();
178
179   JMenu rnaStructureMenu = new JMenu();
180
181   /**
182    * Constructs a menu with sub-menu items for any hyperlinks for the sequence
183    * and/or features provided. Hyperlinks may include a lookup by sequence id,
184    * or database cross-references, depending on which links are enabled in user
185    * preferences.
186    * 
187    * @param seq
188    * @param features
189    * @return
190    */
191   protected static JMenu buildLinkMenu(final SequenceI seq,
192           List<SequenceFeature> features)
193   {
194     JMenu linkMenu = new JMenu(MessageManager.getString("action.link"));
195
196     List<String> nlinks = null;
197     if (seq != null)
198     {
199       nlinks = Preferences.sequenceUrlLinks.getLinksForMenu();
200       UrlLink.sort(nlinks);
201     }
202     else
203     {
204       nlinks = new ArrayList<>();
205     }
206
207     if (features != null)
208     {
209       for (SequenceFeature sf : features)
210       {
211         if (sf.links != null)
212         {
213           for (String link : sf.links)
214           {
215             nlinks.add(link);
216           }
217         }
218       }
219     }
220
221     /*
222      * instantiate the hyperlinklink templates from sequence data;
223      * note the order of the templates is preserved in the map
224      */
225     Map<String, List<String>> linkset = new LinkedHashMap<>();
226     for (String link : nlinks)
227     {
228       UrlLink urlLink = null;
229       try
230       {
231         urlLink = new UrlLink(link);
232       } catch (Exception foo)
233       {
234         Console.error("Exception for URLLink '" + link + "'", foo);
235         continue;
236       }
237
238       if (!urlLink.isValid())
239       {
240         Console.error(urlLink.getInvalidMessage());
241         continue;
242       }
243
244       urlLink.createLinksFromSeq(seq, linkset);
245     }
246
247     /*
248      * construct menu items for the hyperlinks (still preserving
249      * the order of the sorted templates)
250      */
251     addUrlLinks(linkMenu, linkset.values());
252
253     return linkMenu;
254   }
255
256   /**
257    * A helper method that builds menu items from the given links, with action
258    * handlers to open the link URL, and adds them to the linkMenu. Each provided
259    * link should be a list whose second item is the menu text, and whose fourth
260    * item is the URL to open when the menu item is selected.
261    * 
262    * @param linkMenu
263    * @param linkset
264    */
265   static private void addUrlLinks(JMenu linkMenu,
266           Collection<List<String>> linkset)
267   {
268     for (List<String> linkstrset : linkset)
269     {
270       final String url = linkstrset.get(3);
271       JMenuItem item = new JMenuItem(linkstrset.get(1));
272       item.setToolTipText(MessageManager
273               .formatMessage("label.open_url_param", new Object[]
274               { url }));
275       item.addActionListener(new ActionListener()
276       {
277         @Override
278         public void actionPerformed(ActionEvent e)
279         {
280           new Thread(new Runnable()
281           {
282             @Override
283             public void run()
284             {
285               showLink(url);
286             }
287           }).start();
288         }
289       });
290       linkMenu.add(item);
291     }
292   }
293
294   /**
295    * Opens the provided url in the default web browser, or shows an error
296    * message if this fails
297    * 
298    * @param url
299    */
300   static void showLink(String url)
301   {
302     try
303     {
304       jalview.util.BrowserLauncher.openURL(url);
305     } catch (Exception ex)
306     {
307       JvOptionPane.showInternalMessageDialog(Desktop.desktop,
308               MessageManager.getString("label.web_browser_not_found_unix"),
309               MessageManager.getString("label.web_browser_not_found"),
310               JvOptionPane.WARNING_MESSAGE);
311
312       ex.printStackTrace();
313     }
314   }
315
316   /**
317    * add a late bound groupURL item to the given linkMenu
318    * 
319    * @param linkMenu
320    * @param label
321    *          - menu label string
322    * @param urlgenerator
323    *          GroupURLLink used to generate URL
324    * @param urlstub
325    *          Object array returned from the makeUrlStubs function.
326    */
327   static void addshowLink(JMenu linkMenu, String label,
328           final GroupUrlLink urlgenerator, final Object[] urlstub)
329   {
330     JMenuItem item = new JMenuItem(label);
331     item.setToolTipText(MessageManager
332             .formatMessage("label.open_url_seqs_param", new Object[]
333             { urlgenerator.getUrl_prefix(),
334                 urlgenerator.getNumberInvolved(urlstub) }));
335     // TODO: put in info about what is being sent.
336     item.addActionListener(new ActionListener()
337     {
338       @Override
339       public void actionPerformed(ActionEvent e)
340       {
341         new Thread(new Runnable()
342         {
343
344           @Override
345           public void run()
346           {
347             try
348             {
349               showLink(urlgenerator.constructFrom(urlstub));
350             } catch (UrlStringTooLongException e2)
351             {
352             }
353           }
354
355         }).start();
356       }
357     });
358
359     linkMenu.add(item);
360   }
361
362   /**
363    * Constructor for a PopupMenu for a click in the alignment panel (on a
364    * residue)
365    * 
366    * @param ap
367    *          the panel in which the mouse is clicked
368    * @param seq
369    *          the sequence under the mouse
370    * @throws NullPointerException
371    *           if seq is null
372    */
373   public PopupMenu(final AlignmentPanel ap, SequenceI seq, int column)
374   {
375     this(false, ap, seq, column, null);
376   }
377
378   /**
379    * Constructor for a PopupMenu for a click in the sequence id panel
380    * 
381    * @param alignPanel
382    *          the panel in which the mouse is clicked
383    * @param seq
384    *          the sequence under the mouse click
385    * @param groupLinks
386    *          templates for sequence external links
387    * @throws NullPointerException
388    *           if seq is null
389    */
390   public PopupMenu(final AlignmentPanel alignPanel, final SequenceI seq,
391           List<String> groupLinks)
392   {
393     this(true, alignPanel, seq, -1, groupLinks);
394   }
395
396   /**
397    * Private constructor that constructs a popup menu for either sequence ID
398    * Panel, or alignment context
399    * 
400    * @param fromIdPanel
401    * @param alignPanel
402    * @param seq
403    * @param column
404    *          aligned column position (0...)
405    * @param groupLinks
406    */
407   private PopupMenu(boolean fromIdPanel, final AlignmentPanel alignPanel,
408           final SequenceI seq, final int column, List<String> groupLinks)
409   {
410     Objects.requireNonNull(seq);
411     this.forIdPanel = fromIdPanel;
412     this.ap = alignPanel;
413     sequence = seq;
414
415     for (String ff : FileFormats.getInstance().getWritableFormats(true))
416     {
417       JMenuItem item = new JMenuItem(ff);
418
419       item.addActionListener(new ActionListener()
420       {
421         @Override
422         public void actionPerformed(ActionEvent e)
423         {
424           outputText_actionPerformed(e);
425         }
426       });
427
428       outputMenu.add(item);
429     }
430
431     /*
432      * Build menus for annotation types that may be shown or hidden, and for
433      * 'reference annotations' that may be added to the alignment. First for the
434      * currently selected sequence (if there is one):
435      */
436     final List<SequenceI> selectedSequence = (forIdPanel && seq != null
437             ? Arrays.asList(seq)
438             : Collections.<SequenceI> emptyList());
439     buildAnnotationTypesMenus(seqShowAnnotationsMenu,
440             seqHideAnnotationsMenu, selectedSequence);
441     configureReferenceAnnotationsMenu(seqAddReferenceAnnotations,
442             selectedSequence);
443
444     /*
445      * And repeat for the current selection group (if there is one):
446      */
447     final List<SequenceI> selectedGroup = (alignPanel.av
448             .getSelectionGroup() == null
449                     ? Collections.<SequenceI> emptyList()
450                     : alignPanel.av.getSelectionGroup().getSequences());
451     buildAnnotationTypesMenus(groupShowAnnotationsMenu,
452             groupHideAnnotationsMenu, selectedGroup);
453     configureReferenceAnnotationsMenu(groupAddReferenceAnnotations,
454             selectedGroup);
455
456     try
457     {
458       jbInit();
459     } catch (Exception e)
460     {
461       e.printStackTrace();
462     }
463
464     if (forIdPanel)
465     {
466       JMenuItem menuItem;
467       sequenceMenu.setText(sequence.getName());
468       if (seq == alignPanel.av.getAlignment().getSeqrep())
469       {
470         makeReferenceSeq.setText(
471                 MessageManager.getString("action.unmark_as_reference"));
472       }
473       else
474       {
475         makeReferenceSeq.setText(
476                 MessageManager.getString("action.set_as_reference"));
477       }
478
479       if (!alignPanel.av.getAlignment().isNucleotide())
480       {
481         remove(rnaStructureMenu);
482       }
483       else
484       {
485         int origCount = rnaStructureMenu.getItemCount();
486         /*
487          * add menu items to 2D-render any alignment or sequence secondary
488          * structure annotation
489          */
490         AlignmentAnnotation[] aas = alignPanel.av.getAlignment()
491                 .getAlignmentAnnotation();
492         if (aas != null)
493         {
494           for (final AlignmentAnnotation aa : aas)
495           {
496             if (aa.isValidStruc() && aa.sequenceRef == null)
497             {
498               /*
499                * valid alignment RNA secondary structure annotation
500                */
501               menuItem = new JMenuItem();
502               menuItem.setText(MessageManager.formatMessage(
503                       "label.2d_rna_structure_line", new Object[]
504                       { aa.label }));
505               menuItem.addActionListener(new ActionListener()
506               {
507                 @Override
508                 public void actionPerformed(ActionEvent e)
509                 {
510                   new AppVarna(seq, aa, alignPanel);
511                 }
512               });
513               rnaStructureMenu.add(menuItem);
514             }
515           }
516         }
517
518         if (seq.getAnnotation() != null)
519         {
520           AlignmentAnnotation seqAnns[] = seq.getAnnotation();
521           for (final AlignmentAnnotation aa : seqAnns)
522           {
523             if (aa.isValidStruc())
524             {
525               /*
526                * valid sequence RNA secondary structure annotation
527                */
528               // TODO: make rnastrucF a bit more nice
529               menuItem = new JMenuItem();
530               menuItem.setText(MessageManager.formatMessage(
531                       "label.2d_rna_sequence_name", new Object[]
532                       { seq.getName() }));
533               menuItem.addActionListener(new ActionListener()
534               {
535                 @Override
536                 public void actionPerformed(ActionEvent e)
537                 {
538                   // TODO: VARNA does'nt print gaps in the sequence
539                   new AppVarna(seq, aa, alignPanel);
540                 }
541               });
542               rnaStructureMenu.add(menuItem);
543             }
544           }
545         }
546         if (rnaStructureMenu.getItemCount() == origCount)
547         {
548           remove(rnaStructureMenu);
549         }
550       }
551
552       menuItem = new JMenuItem(
553               MessageManager.getString("action.hide_sequences"));
554       menuItem.addActionListener(new ActionListener()
555       {
556         @Override
557         public void actionPerformed(ActionEvent e)
558         {
559           hideSequences(false);
560         }
561       });
562       add(menuItem);
563
564       if (alignPanel.av.getSelectionGroup() != null
565               && alignPanel.av.getSelectionGroup().getSize() > 1)
566       {
567         menuItem = new JMenuItem(MessageManager
568                 .formatMessage("label.represent_group_with", new Object[]
569                 { seq.getName() }));
570         menuItem.addActionListener(new ActionListener()
571         {
572           @Override
573           public void actionPerformed(ActionEvent e)
574           {
575             hideSequences(true);
576           }
577         });
578         sequenceMenu.add(menuItem);
579       }
580
581       if (alignPanel.av.hasHiddenRows())
582       {
583         final int index = alignPanel.av.getAlignment().findIndex(seq);
584
585         if (alignPanel.av.adjustForHiddenSeqs(index)
586                 - alignPanel.av.adjustForHiddenSeqs(index - 1) > 1)
587         {
588           menuItem = new JMenuItem(
589                   MessageManager.getString("action.reveal_sequences"));
590           menuItem.addActionListener(new ActionListener()
591           {
592             @Override
593             public void actionPerformed(ActionEvent e)
594             {
595               alignPanel.av.showSequence(index);
596               if (alignPanel.overviewPanel != null)
597               {
598                 alignPanel.overviewPanel.updateOverviewImage();
599               }
600             }
601           });
602           add(menuItem);
603         }
604       }
605     }
606
607     /*
608      * offer 'Reveal All'
609      * - in the IdPanel (seq not null) if any sequence is hidden
610      * - in the IdPanel or SeqPanel if all sequences are hidden (seq is null)
611      */
612     if (alignPanel.av.hasHiddenRows())
613     {
614       boolean addOption = seq != null;
615       if (!addOption && alignPanel.av.getAlignment().getHeight() == 0)
616       {
617         addOption = true;
618       }
619       if (addOption)
620       {
621         JMenuItem menuItem = new JMenuItem(
622                 MessageManager.getString("action.reveal_all"));
623         menuItem.addActionListener(new ActionListener()
624         {
625           @Override
626           public void actionPerformed(ActionEvent e)
627           {
628             alignPanel.av.showAllHiddenSeqs();
629             if (alignPanel.overviewPanel != null)
630             {
631               alignPanel.overviewPanel.updateOverviewImage();
632             }
633           }
634         });
635         add(menuItem);
636       }
637     }
638
639     SequenceGroup sg = alignPanel.av.getSelectionGroup();
640     boolean isDefinedGroup = (sg != null)
641             ? alignPanel.av.getAlignment().getGroups().contains(sg)
642             : false;
643
644     if (sg != null && sg.getSize() > 0)
645     {
646       groupName.setText(MessageManager
647               .getString("label.edit_name_and_description_current_group"));
648
649       ColourMenuHelper.setColourSelected(colourMenu, sg.getColourScheme());
650
651       conservationMenuItem.setEnabled(!sg.isNucleotide());
652
653       if (sg.cs != null)
654       {
655         if (sg.cs.conservationApplied())
656         {
657           conservationMenuItem.setSelected(true);
658         }
659         if (sg.cs.getThreshold() > 0)
660         {
661           abovePIDColour.setSelected(true);
662         }
663       }
664       modifyConservation.setEnabled(conservationMenuItem.isSelected());
665       modifyPID.setEnabled(abovePIDColour.isSelected());
666       displayNonconserved.setSelected(sg.getShowNonconserved());
667       showText.setSelected(sg.getDisplayText());
668       showColourText.setSelected(sg.getColourText());
669       showBoxes.setSelected(sg.getDisplayBoxes());
670       // add any groupURLs to the groupURL submenu and make it visible
671       if (groupLinks != null && groupLinks.size() > 0)
672       {
673         buildGroupURLMenu(sg, groupLinks);
674       }
675       // Add a 'show all structures' for the current selection
676       Hashtable<String, PDBEntry> pdbe = new Hashtable<>(),
677               reppdb = new Hashtable<>();
678
679       SequenceI sqass = null;
680       for (SequenceI sq : alignPanel.av.getSequenceSelection())
681       {
682         Vector<PDBEntry> pes = sq.getDatasetSequence().getAllPDBEntries();
683         if (pes != null && pes.size() > 0)
684         {
685           reppdb.put(pes.get(0).getId(), pes.get(0));
686           for (PDBEntry pe : pes)
687           {
688             pdbe.put(pe.getId(), pe);
689             if (sqass == null)
690             {
691               sqass = sq;
692             }
693           }
694         }
695       }
696       if (pdbe.size() > 0)
697       {
698         final PDBEntry[] pe = pdbe.values()
699                 .toArray(new PDBEntry[pdbe.size()]),
700                 pr = reppdb.values().toArray(new PDBEntry[reppdb.size()]);
701         final JMenuItem gpdbview, rpdbview;
702       }
703     }
704     else
705     {
706       groupMenu.setVisible(false);
707       editMenu.setVisible(false);
708     }
709
710     if (!isDefinedGroup)
711     {
712       createGroupMenuItem.setVisible(true);
713       unGroupMenuItem.setVisible(false);
714       editGroupMenu
715               .setText(MessageManager.getString("action.edit_new_group"));
716     }
717     else
718     {
719       createGroupMenuItem.setVisible(false);
720       unGroupMenuItem.setVisible(true);
721       editGroupMenu.setText(MessageManager.getString("action.edit_group"));
722     }
723
724     if (!forIdPanel)
725     {
726       sequenceMenu.setVisible(false);
727       chooseStructure.setVisible(false);
728       rnaStructureMenu.setVisible(false);
729     }
730
731     if (forIdPanel)
732     {
733       addDisplayColumnsMenu();
734     }
735
736     addLinksAndFeatures(seq, column);
737   }
738
739   void addDisplayColumnsMenu()
740   {
741     JMenu dis_cols = new JMenu(
742             MessageManager.getString("action.displayed_columns"));
743     final IdColumns id_cols = ap.av.getIdColumns();
744     id_cols.updateTypeList();
745     for (final IdColumn col : id_cols.getIdColumns())
746     {
747       JMenuItem col_entry = new JCheckBoxMenuItem(col.getLabel(),
748               col.isVisible());
749       col_entry.addActionListener(new ActionListener()
750       {
751
752         @Override
753         public void actionPerformed(ActionEvent e)
754         {
755           id_cols.toggleVisible(col.getLabel());
756         }
757       });
758       dis_cols.add(col_entry);
759     }
760     add(dis_cols);
761   }
762
763   /**
764    * Adds
765    * <ul>
766    * <li>configured sequence database links (ID panel popup menu)</li>
767    * <li>non-positional feature links (ID panel popup menu)</li>
768    * <li>positional feature links (alignment panel popup menu)</li>
769    * <li>feature details links (alignment panel popup menu)</li>
770    * </ul>
771    * If this panel is also showed complementary (CDS/protein) features, then
772    * links to their feature details are also added.
773    * 
774    * @param seq
775    * @param column
776    */
777   void addLinksAndFeatures(final SequenceI seq, final int column)
778   {
779     List<SequenceFeature> features = null;
780     if (forIdPanel)
781     {
782       features = sequence.getFeatures().getNonPositionalFeatures();
783     }
784     else
785     {
786       features = ap.getFeatureRenderer().findFeaturesAtColumn(sequence,
787               column + 1);
788     }
789
790     addLinks(seq, features);
791
792     if (!forIdPanel)
793     {
794       addFeatureDetails(features, seq, column);
795     }
796   }
797
798   /**
799    * Add a menu item to show feature details for each sequence feature. Any
800    * linked 'virtual' features (CDS/protein) are also optionally found and
801    * included.
802    * 
803    * @param features
804    * @param seq
805    * @param column
806    */
807   protected void addFeatureDetails(List<SequenceFeature> features,
808           final SequenceI seq, final int column)
809   {
810     /*
811      * add features in CDS/protein complement at the corresponding
812      * position if configured to do so
813      */
814     MappedFeatures mf = null;
815     if (ap.av.isShowComplementFeatures())
816     {
817       if (!Comparison.isGap(sequence.getCharAt(column)))
818       {
819         AlignViewportI complement = ap.getAlignViewport()
820                 .getCodingComplement();
821         AlignFrame af = Desktop.getAlignFrameFor(complement);
822         FeatureRendererModel fr2 = af.getFeatureRenderer();
823         int seqPos = sequence.findPosition(column);
824         mf = fr2.findComplementFeaturesAtResidue(sequence, seqPos);
825       }
826     }
827
828     if (features.isEmpty() && mf == null)
829     {
830       /*
831        * no features to show at this position
832        */
833       return;
834     }
835
836     JMenu details = new JMenu(
837             MessageManager.getString("label.feature_details"));
838     add(details);
839
840     String name = seq.getName();
841     for (final SequenceFeature sf : features)
842     {
843       addFeatureDetailsMenuItem(details, name, sf, null);
844     }
845
846     if (mf != null)
847     {
848       for (final SequenceFeature sf : mf.features)
849       {
850         addFeatureDetailsMenuItem(details, name, sf, mf);
851       }
852     }
853   }
854
855   /**
856    * A helper method to add one menu item whose action is to show details for
857    * one feature. The menu text includes feature description, but this may be
858    * truncated.
859    * 
860    * @param details
861    * @param seqName
862    * @param sf
863    * @param mf
864    */
865   void addFeatureDetailsMenuItem(JMenu details, final String seqName,
866           final SequenceFeature sf, MappedFeatures mf)
867   {
868     int start = sf.getBegin();
869     int end = sf.getEnd();
870     if (mf != null)
871     {
872       /*
873        * show local rather than linked feature coordinates
874        */
875       int[] localRange = mf.getMappedPositions(start, end);
876       if (localRange == null)
877       {
878         // e.g. variant extending to stop codon so not mappable
879         return;
880       }
881       start = localRange[0];
882       end = localRange[localRange.length - 1];
883     }
884     StringBuilder desc = new StringBuilder();
885     desc.append(sf.getType()).append(" ").append(String.valueOf(start));
886     if (start != end)
887     {
888       desc.append(sf.isContactFeature() ? ":" : "-");
889       desc.append(String.valueOf(end));
890     }
891     String description = sf.getDescription();
892     if (description != null)
893     {
894       desc.append(" ");
895       description = StringUtils.stripHtmlTags(description);
896
897       /*
898        * truncate overlong descriptions unless they contain an href
899        * (as truncation could leave corrupted html)
900        */
901       boolean hasLink = description.indexOf("a href") > -1;
902       if (description.length() > FEATURE_DESC_MAX && !hasLink)
903       {
904         description = description.substring(0, FEATURE_DESC_MAX) + "...";
905       }
906       desc.append(description);
907     }
908     String featureGroup = sf.getFeatureGroup();
909     if (featureGroup != null)
910     {
911       desc.append(" (").append(featureGroup).append(")");
912     }
913     String htmlText = JvSwingUtils.wrapTooltip(true, desc.toString());
914     JMenuItem item = new JMenuItem(htmlText);
915     item.addActionListener(new ActionListener()
916     {
917       @Override
918       public void actionPerformed(ActionEvent e)
919       {
920         showFeatureDetails(sf, seqName, mf);
921       }
922     });
923     details.add(item);
924   }
925
926   /**
927    * Opens a panel showing a text report of feature details
928    * 
929    * @param sf
930    * @param seqName
931    * @param mf
932    */
933   protected void showFeatureDetails(SequenceFeature sf, String seqName,
934           MappedFeatures mf)
935   {
936     JInternalFrame details;
937     if (Platform.isJS())
938     {
939       details = new JInternalFrame();
940       details.setFrameIcon(null);
941       JPanel panel = new JPanel(new BorderLayout());
942       panel.setOpaque(true);
943       panel.setBackground(Color.white);
944       // TODO JAL-3026 set style of table correctly for feature details
945       JLabel reprt = new JLabel(MessageManager
946               .formatMessage("label.html_content", new Object[]
947               { sf.getDetailsReport(seqName, mf) }));
948       reprt.setBackground(Color.WHITE);
949       reprt.setOpaque(true);
950       panel.add(reprt, BorderLayout.CENTER);
951       details.setContentPane(panel);
952       details.pack();
953     }
954     else
955     /**
956      * Java only
957      * 
958      * @j2sIgnore
959      */
960     {
961       CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
962       // it appears Java's CSS does not support border-collapse :-(
963       cap.addStylesheetRule("table { border-collapse: collapse;}");
964       cap.addStylesheetRule("table, td, th {border: 1px solid black;}");
965       cap.setText(sf.getDetailsReport(seqName, mf));
966       details = cap;
967     }
968     Desktop.addInternalFrame(details,
969             MessageManager.getString("label.feature_details"), 500, 500);
970   }
971
972   /**
973    * Adds a 'Link' menu item with a sub-menu item for each hyperlink provided.
974    * When seq is not null, these are links for the sequence id, which may be to
975    * external web sites for the sequence accession, and/or links embedded in
976    * non-positional features. When seq is null, only links embedded in the
977    * provided features are added. If no links are found, the menu is not added.
978    * 
979    * @param seq
980    * @param features
981    */
982   void addLinks(final SequenceI seq, List<SequenceFeature> features)
983   {
984     JMenu linkMenu = buildLinkMenu(forIdPanel ? seq : null, features);
985
986     // only add link menu if it has entries
987     if (linkMenu.getItemCount() > 0)
988     {
989       if (forIdPanel)
990       {
991         sequenceMenu.add(linkMenu);
992       }
993       else
994       {
995         add(linkMenu);
996       }
997     }
998   }
999
1000   /**
1001    * Add annotation types to 'Show annotations' and/or 'Hide annotations' menus.
1002    * "All" is added first, followed by a separator. Then add any annotation
1003    * types associated with the current selection. Separate menus are built for
1004    * the selected sequence group (if any), and the selected sequence.
1005    * <p>
1006    * Some annotation rows are always rendered together - these can be identified
1007    * by a common graphGroup property > -1. Only one of each group will be marked
1008    * as visible (to avoid duplication of the display). For such groups we add a
1009    * composite type name, e.g.
1010    * <p>
1011    * IUPredWS (Long), IUPredWS (Short)
1012    * 
1013    * @param seq
1014    */
1015   protected void buildAnnotationTypesMenus(JMenu showMenu, JMenu hideMenu,
1016           List<SequenceI> forSequences)
1017   {
1018     showMenu.removeAll();
1019     hideMenu.removeAll();
1020
1021     final List<String> all = Arrays
1022             .asList(new String[]
1023             { MessageManager.getString("label.all") });
1024     addAnnotationTypeToShowHide(showMenu, forSequences, "", all, true,
1025             true);
1026     addAnnotationTypeToShowHide(hideMenu, forSequences, "", all, true,
1027             false);
1028     showMenu.addSeparator();
1029     hideMenu.addSeparator();
1030
1031     final AlignmentAnnotation[] annotations = ap.getAlignment()
1032             .getAlignmentAnnotation();
1033
1034     /*
1035      * Find shown/hidden annotations types, distinguished by source (calcId),
1036      * and grouped by graphGroup. Using LinkedHashMap means we will retrieve in
1037      * the insertion order, which is the order of the annotations on the
1038      * alignment.
1039      */
1040     Map<String, List<List<String>>> shownTypes = new LinkedHashMap<>();
1041     Map<String, List<List<String>>> hiddenTypes = new LinkedHashMap<>();
1042     AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes, hiddenTypes,
1043             AlignmentAnnotationUtils.asList(annotations), forSequences);
1044
1045     for (String calcId : hiddenTypes.keySet())
1046     {
1047       for (List<String> type : hiddenTypes.get(calcId))
1048       {
1049         addAnnotationTypeToShowHide(showMenu, forSequences, calcId, type,
1050                 false, true);
1051       }
1052     }
1053     // grey out 'show annotations' if none are hidden
1054     showMenu.setEnabled(!hiddenTypes.isEmpty());
1055
1056     for (String calcId : shownTypes.keySet())
1057     {
1058       for (List<String> type : shownTypes.get(calcId))
1059       {
1060         addAnnotationTypeToShowHide(hideMenu, forSequences, calcId, type,
1061                 false, false);
1062       }
1063     }
1064     // grey out 'hide annotations' if none are shown
1065     hideMenu.setEnabled(!shownTypes.isEmpty());
1066   }
1067
1068   /**
1069    * Returns a list of sequences - either the current selection group (if there
1070    * is one), else the specified single sequence.
1071    * 
1072    * @param seq
1073    * @return
1074    */
1075   protected List<SequenceI> getSequenceScope(SequenceI seq)
1076   {
1077     List<SequenceI> forSequences = null;
1078     final SequenceGroup selectionGroup = ap.av.getSelectionGroup();
1079     if (selectionGroup != null && selectionGroup.getSize() > 0)
1080     {
1081       forSequences = selectionGroup.getSequences();
1082     }
1083     else
1084     {
1085       forSequences = seq == null ? Collections.<SequenceI> emptyList()
1086               : Arrays.asList(seq);
1087     }
1088     return forSequences;
1089   }
1090
1091   /**
1092    * Add one annotation type to the 'Show Annotations' or 'Hide Annotations'
1093    * menus.
1094    * 
1095    * @param showOrHideMenu
1096    *          the menu to add to
1097    * @param forSequences
1098    *          the sequences whose annotations may be shown or hidden
1099    * @param calcId
1100    * @param types
1101    *          the label to add
1102    * @param allTypes
1103    *          if true this is a special label meaning 'All'
1104    * @param actionIsShow
1105    *          if true, the select menu item action is to show the annotation
1106    *          type, else hide
1107    */
1108   protected void addAnnotationTypeToShowHide(JMenu showOrHideMenu,
1109           final List<SequenceI> forSequences, String calcId,
1110           final List<String> types, final boolean allTypes,
1111           final boolean actionIsShow)
1112   {
1113     String label = types.toString(); // [a, b, c]
1114     label = label.substring(1, label.length() - 1); // a, b, c
1115     final JMenuItem item = new JMenuItem(label);
1116     item.setToolTipText(calcId);
1117     item.addActionListener(new ActionListener()
1118     {
1119       @Override
1120       public void actionPerformed(ActionEvent e)
1121       {
1122         AlignmentUtils.showOrHideSequenceAnnotations(ap.getAlignment(),
1123                 types, forSequences, allTypes, actionIsShow);
1124         refresh();
1125       }
1126     });
1127     showOrHideMenu.add(item);
1128   }
1129
1130   private void buildGroupURLMenu(SequenceGroup sg, List<String> groupLinks)
1131   {
1132
1133     // TODO: usability: thread off the generation of group url content so root
1134     // menu appears asap
1135     // sequence only URLs
1136     // ID/regex match URLs
1137     JMenu groupLinksMenu = new JMenu(
1138             MessageManager.getString("action.group_link"));
1139     // three types of url that might be created.
1140     JMenu[] linkMenus = new JMenu[] { null,
1141         new JMenu(MessageManager.getString("action.ids")),
1142         new JMenu(MessageManager.getString("action.sequences")),
1143         new JMenu(MessageManager.getString("action.ids_sequences")) };
1144
1145     SequenceI[] seqs = ap.av.getSelectionAsNewSequence();
1146     String[][] idandseqs = GroupUrlLink.formStrings(seqs);
1147     Hashtable<String, Object[]> commonDbrefs = new Hashtable<>();
1148     for (int sq = 0; sq < seqs.length; sq++)
1149     {
1150
1151       int start = seqs[sq].findPosition(sg.getStartRes()),
1152               end = seqs[sq].findPosition(sg.getEndRes());
1153       // just collect ids from dataset sequence
1154       // TODO: check if IDs collected from selecton group intersects with the
1155       // current selection, too
1156       SequenceI sqi = seqs[sq];
1157       while (sqi.getDatasetSequence() != null)
1158       {
1159         sqi = sqi.getDatasetSequence();
1160       }
1161       List<DBRefEntry> dbr = sqi.getDBRefs();
1162       int nd;
1163       if (dbr != null && (nd = dbr.size()) > 0)
1164       {
1165         for (int d = 0; d < nd; d++)
1166         {
1167           DBRefEntry e = dbr.get(d);
1168           String src = e.getSource(); // jalview.util.DBRefUtils.getCanonicalName(dbr[d].getSource()).toUpperCase(Locale.ROOT);
1169           Object[] sarray = commonDbrefs.get(src);
1170           if (sarray == null)
1171           {
1172             sarray = new Object[2];
1173             sarray[0] = new int[] { 0 };
1174             sarray[1] = new String[seqs.length];
1175
1176             commonDbrefs.put(src, sarray);
1177           }
1178
1179           if (((String[]) sarray[1])[sq] == null)
1180           {
1181             if (!e.hasMap()
1182                     || (e.getMap().locateMappedRange(start, end) != null))
1183             {
1184               ((String[]) sarray[1])[sq] = e.getAccessionId();
1185               ((int[]) sarray[0])[0]++;
1186             }
1187           }
1188         }
1189       }
1190     }
1191     // now create group links for all distinct ID/sequence sets.
1192     boolean addMenu = false; // indicates if there are any group links to give
1193                              // to user
1194     for (String link : groupLinks)
1195     {
1196       GroupUrlLink urlLink = null;
1197       try
1198       {
1199         urlLink = new GroupUrlLink(link);
1200       } catch (Exception foo)
1201       {
1202         Console.error("Exception for GroupURLLink '" + link + "'", foo);
1203         continue;
1204       }
1205       if (!urlLink.isValid())
1206       {
1207         Console.error(urlLink.getInvalidMessage());
1208         continue;
1209       }
1210       final String label = urlLink.getLabel();
1211       boolean usingNames = false;
1212       // Now see which parts of the group apply for this URL
1213       String ltarget = urlLink.getTarget(); // jalview.util.DBRefUtils.getCanonicalName(urlLink.getTarget());
1214       Object[] idset = commonDbrefs.get(ltarget.toUpperCase(Locale.ROOT));
1215       String[] seqstr, ids; // input to makeUrl
1216       if (idset != null)
1217       {
1218         int numinput = ((int[]) idset[0])[0];
1219         String[] allids = ((String[]) idset[1]);
1220         seqstr = new String[numinput];
1221         ids = new String[numinput];
1222         for (int sq = 0, idcount = 0; sq < seqs.length; sq++)
1223         {
1224           if (allids[sq] != null)
1225           {
1226             ids[idcount] = allids[sq];
1227             seqstr[idcount++] = idandseqs[1][sq];
1228           }
1229         }
1230       }
1231       else
1232       {
1233         // just use the id/seq set
1234         seqstr = idandseqs[1];
1235         ids = idandseqs[0];
1236         usingNames = true;
1237       }
1238       // and try and make the groupURL!
1239
1240       Object[] urlset = null;
1241       try
1242       {
1243         urlset = urlLink.makeUrlStubs(ids, seqstr,
1244                 "FromJalview" + System.currentTimeMillis(), false);
1245       } catch (UrlStringTooLongException e)
1246       {
1247       }
1248       if (urlset != null)
1249       {
1250         int type = urlLink.getGroupURLType() & 3;
1251         // first two bits ofurlLink type bitfield are sequenceids and sequences
1252         // TODO: FUTURE: ensure the groupURL menu structure can be generalised
1253         addshowLink(linkMenus[type],
1254                 label + (((type & 1) == 1)
1255                         ? ("(" + (usingNames ? "Names" : ltarget) + ")")
1256                         : ""),
1257                 urlLink, urlset);
1258         addMenu = true;
1259       }
1260     }
1261     if (addMenu)
1262     {
1263       groupLinksMenu = new JMenu(
1264               MessageManager.getString("action.group_link"));
1265       for (int m = 0; m < linkMenus.length; m++)
1266       {
1267         if (linkMenus[m] != null
1268                 && linkMenus[m].getMenuComponentCount() > 0)
1269         {
1270           groupLinksMenu.add(linkMenus[m]);
1271         }
1272       }
1273
1274       groupMenu.add(groupLinksMenu);
1275     }
1276   }
1277
1278   /**
1279    * DOCUMENT ME!
1280    * 
1281    * @throws Exception
1282    *           DOCUMENT ME!
1283    */
1284   private void jbInit() throws Exception
1285   {
1286     groupMenu.setText(MessageManager.getString("label.selection"));
1287     groupName.setText(MessageManager.getString("label.name"));
1288     groupName.addActionListener(new ActionListener()
1289     {
1290       @Override
1291       public void actionPerformed(ActionEvent e)
1292       {
1293         groupName_actionPerformed();
1294       }
1295     });
1296     sequenceMenu.setText(MessageManager.getString("label.sequence"));
1297
1298     JMenuItem sequenceName = new JMenuItem(
1299             MessageManager.getString("label.edit_name_description"));
1300     sequenceName.addActionListener(new ActionListener()
1301     {
1302       @Override
1303       public void actionPerformed(ActionEvent e)
1304       {
1305         sequenceName_actionPerformed();
1306       }
1307     });
1308     JMenuItem chooseAnnotations = new JMenuItem(
1309             MessageManager.getString("action.choose_annotations"));
1310     chooseAnnotations.addActionListener(new ActionListener()
1311     {
1312       @Override
1313       public void actionPerformed(ActionEvent e)
1314       {
1315         chooseAnnotations_actionPerformed(e);
1316       }
1317     });
1318     JMenuItem sequenceDetails = new JMenuItem(
1319             MessageManager.getString("label.sequence_details"));
1320     sequenceDetails.addActionListener(new ActionListener()
1321     {
1322       @Override
1323       public void actionPerformed(ActionEvent e)
1324       {
1325         createSequenceDetailsReport(new SequenceI[] { sequence });
1326       }
1327     });
1328     JMenuItem sequenceSelDetails = new JMenuItem(
1329             MessageManager.getString("label.sequence_details"));
1330     sequenceSelDetails.addActionListener(new ActionListener()
1331     {
1332       @Override
1333       public void actionPerformed(ActionEvent e)
1334       {
1335         createSequenceDetailsReport(ap.av.getSequenceSelection());
1336       }
1337     });
1338
1339     unGroupMenuItem
1340             .setText(MessageManager.getString("action.remove_group"));
1341     unGroupMenuItem.addActionListener(new ActionListener()
1342     {
1343       @Override
1344       public void actionPerformed(ActionEvent e)
1345       {
1346         unGroupMenuItem_actionPerformed();
1347       }
1348     });
1349     createGroupMenuItem
1350             .setText(MessageManager.getString("action.create_group"));
1351     createGroupMenuItem.addActionListener(new ActionListener()
1352     {
1353       @Override
1354       public void actionPerformed(ActionEvent e)
1355       {
1356         createGroupMenuItem_actionPerformed();
1357       }
1358     });
1359
1360     JMenuItem outline = new JMenuItem(
1361             MessageManager.getString("action.border_colour"));
1362     outline.addActionListener(new ActionListener()
1363     {
1364       @Override
1365       public void actionPerformed(ActionEvent e)
1366       {
1367         outline_actionPerformed();
1368       }
1369     });
1370     showBoxes.setText(MessageManager.getString("action.boxes"));
1371     showBoxes.setState(true);
1372     showBoxes.addActionListener(new ActionListener()
1373     {
1374       @Override
1375       public void actionPerformed(ActionEvent e)
1376       {
1377         showBoxes_actionPerformed();
1378       }
1379     });
1380     showText.setText(MessageManager.getString("action.text"));
1381     showText.setState(true);
1382     showText.addActionListener(new ActionListener()
1383     {
1384       @Override
1385       public void actionPerformed(ActionEvent e)
1386       {
1387         showText_actionPerformed();
1388       }
1389     });
1390     showColourText.setText(MessageManager.getString("label.colour_text"));
1391     showColourText.addActionListener(new ActionListener()
1392     {
1393       @Override
1394       public void actionPerformed(ActionEvent e)
1395       {
1396         showColourText_actionPerformed();
1397       }
1398     });
1399     displayNonconserved
1400             .setText(MessageManager.getString("label.show_non_conserved"));
1401     displayNonconserved.setState(true);
1402     displayNonconserved.addActionListener(new ActionListener()
1403     {
1404       @Override
1405       public void actionPerformed(ActionEvent e)
1406       {
1407         showNonconserved_actionPerformed();
1408       }
1409     });
1410     editMenu.setText(MessageManager.getString("action.edit"));
1411     JMenuItem cut = new JMenuItem(MessageManager.getString("action.cut"));
1412     cut.addActionListener(new ActionListener()
1413     {
1414       @Override
1415       public void actionPerformed(ActionEvent e)
1416       {
1417         cut_actionPerformed();
1418       }
1419     });
1420     upperCase.setText(MessageManager.getString("label.to_upper_case"));
1421     upperCase.addActionListener(new ActionListener()
1422     {
1423       @Override
1424       public void actionPerformed(ActionEvent e)
1425       {
1426         changeCase(e);
1427       }
1428     });
1429     JMenuItem copy = new JMenuItem(MessageManager.getString("action.copy"));
1430     copy.addActionListener(new ActionListener()
1431     {
1432       @Override
1433       public void actionPerformed(ActionEvent e)
1434       {
1435         copy_actionPerformed();
1436       }
1437     });
1438     lowerCase.setText(MessageManager.getString("label.to_lower_case"));
1439     lowerCase.addActionListener(new ActionListener()
1440     {
1441       @Override
1442       public void actionPerformed(ActionEvent e)
1443       {
1444         changeCase(e);
1445       }
1446     });
1447     toggle.setText(MessageManager.getString("label.toggle_case"));
1448     toggle.addActionListener(new ActionListener()
1449     {
1450       @Override
1451       public void actionPerformed(ActionEvent e)
1452       {
1453         changeCase(e);
1454       }
1455     });
1456     outputMenu.setText(
1457             MessageManager.getString("label.out_to_textbox") + "...");
1458     seqShowAnnotationsMenu
1459             .setText(MessageManager.getString("label.show_annotations"));
1460     seqHideAnnotationsMenu
1461             .setText(MessageManager.getString("label.hide_annotations"));
1462     groupShowAnnotationsMenu
1463             .setText(MessageManager.getString("label.show_annotations"));
1464     groupHideAnnotationsMenu
1465             .setText(MessageManager.getString("label.hide_annotations"));
1466     JMenuItem sequenceFeature = new JMenuItem(
1467             MessageManager.getString("label.create_sequence_feature"));
1468     sequenceFeature.addActionListener(new ActionListener()
1469     {
1470       @Override
1471       public void actionPerformed(ActionEvent e)
1472       {
1473         sequenceFeature_actionPerformed();
1474       }
1475     });
1476     editGroupMenu.setText(MessageManager.getString("label.group"));
1477     chooseStructure.setText(
1478             MessageManager.getString("label.show_pdbstruct_dialog"));
1479     chooseStructure.addActionListener(new ActionListener()
1480     {
1481       @Override
1482       public void actionPerformed(ActionEvent actionEvent)
1483       {
1484         SequenceI[] selectedSeqs = new SequenceI[] { sequence };
1485         if (ap.av.getSelectionGroup() != null)
1486         {
1487           selectedSeqs = ap.av.getSequenceSelection();
1488         }
1489         new StructureChooser(selectedSeqs, sequence, ap);
1490       }
1491     });
1492
1493     rnaStructureMenu
1494             .setText(MessageManager.getString("label.view_rna_structure"));
1495
1496     // colStructureMenu.setText("Colour By Structure");
1497     JMenuItem editSequence = new JMenuItem(
1498             MessageManager.getString("label.edit_sequence") + "...");
1499     editSequence.addActionListener(new ActionListener()
1500     {
1501       @Override
1502       public void actionPerformed(ActionEvent actionEvent)
1503       {
1504         editSequence_actionPerformed();
1505       }
1506     });
1507     makeReferenceSeq.setText(
1508             MessageManager.getString("label.mark_as_representative"));
1509     makeReferenceSeq.addActionListener(new ActionListener()
1510     {
1511
1512       @Override
1513       public void actionPerformed(ActionEvent actionEvent)
1514       {
1515         makeReferenceSeq_actionPerformed(actionEvent);
1516
1517       }
1518     });
1519
1520     groupMenu.add(sequenceSelDetails);
1521     add(groupMenu);
1522     add(sequenceMenu);
1523     add(rnaStructureMenu);
1524     add(chooseStructure);
1525     if (forIdPanel)
1526     {
1527       JMenuItem hideInsertions = new JMenuItem(
1528               MessageManager.getString("label.hide_insertions"));
1529       hideInsertions.addActionListener(new ActionListener()
1530       {
1531
1532         @Override
1533         public void actionPerformed(ActionEvent e)
1534         {
1535           hideInsertions_actionPerformed(e);
1536         }
1537       });
1538       add(hideInsertions);
1539     }
1540     // annotations configuration panel suppressed for now
1541     // groupMenu.add(chooseAnnotations);
1542
1543     /*
1544      * Add show/hide annotations to the Sequence menu, and to the Selection menu
1545      * (if a selection group is in force).
1546      */
1547     sequenceMenu.add(seqShowAnnotationsMenu);
1548     sequenceMenu.add(seqHideAnnotationsMenu);
1549     sequenceMenu.add(seqAddReferenceAnnotations);
1550     groupMenu.add(groupShowAnnotationsMenu);
1551     groupMenu.add(groupHideAnnotationsMenu);
1552     groupMenu.add(groupAddReferenceAnnotations);
1553     groupMenu.add(editMenu);
1554     groupMenu.add(outputMenu);
1555     groupMenu.add(sequenceFeature);
1556     groupMenu.add(createGroupMenuItem);
1557     groupMenu.add(unGroupMenuItem);
1558     groupMenu.add(editGroupMenu);
1559     sequenceMenu.add(sequenceName);
1560     sequenceMenu.add(sequenceDetails);
1561     sequenceMenu.add(makeReferenceSeq);
1562
1563     initColourMenu();
1564     buildColourMenu();
1565
1566     editMenu.add(copy);
1567     editMenu.add(cut);
1568     editMenu.add(editSequence);
1569     editMenu.add(upperCase);
1570     editMenu.add(lowerCase);
1571     editMenu.add(toggle);
1572     editGroupMenu.add(groupName);
1573     editGroupMenu.add(colourMenu);
1574     editGroupMenu.add(showBoxes);
1575     editGroupMenu.add(showText);
1576     editGroupMenu.add(showColourText);
1577     editGroupMenu.add(outline);
1578     editGroupMenu.add(displayNonconserved);
1579   }
1580
1581   /**
1582    * Constructs the entries for the colour menu
1583    */
1584   protected void initColourMenu()
1585   {
1586     colourMenu.setText(MessageManager.getString("label.group_colour"));
1587     textColour.setText(MessageManager.getString("label.text_colour"));
1588     textColour.addActionListener(new ActionListener()
1589     {
1590       @Override
1591       public void actionPerformed(ActionEvent e)
1592       {
1593         textColour_actionPerformed();
1594       }
1595     });
1596
1597     abovePIDColour.setText(
1598             MessageManager.getString("label.above_identity_threshold"));
1599     abovePIDColour.addActionListener(new ActionListener()
1600     {
1601       @Override
1602       public void actionPerformed(ActionEvent e)
1603       {
1604         abovePIDColour_actionPerformed(abovePIDColour.isSelected());
1605       }
1606     });
1607
1608     modifyPID.setText(
1609             MessageManager.getString("label.modify_identity_threshold"));
1610     modifyPID.addActionListener(new ActionListener()
1611     {
1612       @Override
1613       public void actionPerformed(ActionEvent e)
1614       {
1615         modifyPID_actionPerformed();
1616       }
1617     });
1618
1619     conservationMenuItem
1620             .setText(MessageManager.getString("action.by_conservation"));
1621     conservationMenuItem.addActionListener(new ActionListener()
1622     {
1623       @Override
1624       public void actionPerformed(ActionEvent e)
1625       {
1626         conservationMenuItem_actionPerformed(
1627                 conservationMenuItem.isSelected());
1628       }
1629     });
1630
1631     annotationColour = new JRadioButtonMenuItem(
1632             MessageManager.getString("action.by_annotation"));
1633     annotationColour.setName(ResidueColourScheme.ANNOTATION_COLOUR);
1634     annotationColour.setEnabled(false);
1635     annotationColour.setToolTipText(
1636             MessageManager.getString("label.by_annotation_tooltip"));
1637
1638     modifyConservation.setText(MessageManager
1639             .getString("label.modify_conservation_threshold"));
1640     modifyConservation.addActionListener(new ActionListener()
1641     {
1642       @Override
1643       public void actionPerformed(ActionEvent e)
1644       {
1645         modifyConservation_actionPerformed();
1646       }
1647     });
1648   }
1649
1650   /**
1651    * Builds the group colour sub-menu, including any user-defined colours which
1652    * were loaded at startup or during the Jalview session
1653    */
1654   protected void buildColourMenu()
1655   {
1656     SequenceGroup sg = ap.av.getSelectionGroup();
1657     if (sg == null)
1658     {
1659       /*
1660        * popup menu with no sequence group scope
1661        */
1662       return;
1663     }
1664     colourMenu.removeAll();
1665     colourMenu.add(textColour);
1666     colourMenu.addSeparator();
1667
1668     ButtonGroup bg = ColourMenuHelper.addMenuItems(colourMenu, this, sg,
1669             false);
1670     bg.add(annotationColour);
1671     colourMenu.add(annotationColour);
1672
1673     colourMenu.addSeparator();
1674     colourMenu.add(conservationMenuItem);
1675     colourMenu.add(modifyConservation);
1676     colourMenu.add(abovePIDColour);
1677     colourMenu.add(modifyPID);
1678   }
1679
1680   protected void modifyConservation_actionPerformed()
1681   {
1682     SequenceGroup sg = getGroup();
1683     if (sg.cs != null)
1684     {
1685       SliderPanel.setConservationSlider(ap, sg.cs, sg.getName());
1686       SliderPanel.showConservationSlider();
1687     }
1688   }
1689
1690   protected void modifyPID_actionPerformed()
1691   {
1692     SequenceGroup sg = getGroup();
1693     if (sg.cs != null)
1694     {
1695       // int threshold = SliderPanel.setPIDSliderSource(ap, sg.cs, getGroup()
1696       // .getName());
1697       // sg.cs.setThreshold(threshold, ap.av.isIgnoreGapsConsensus());
1698       SliderPanel.setPIDSliderSource(ap, sg.cs, getGroup().getName());
1699       SliderPanel.showPIDSlider();
1700     }
1701   }
1702
1703   /**
1704    * Check for any annotations on the underlying dataset sequences (for the
1705    * current selection group) which are not 'on the alignment'.If any are found,
1706    * enable the option to add them to the alignment. The criteria for 'on the
1707    * alignment' is finding an alignment annotation on the alignment, matched on
1708    * calcId, label and sequenceRef.
1709    * 
1710    * A tooltip is also constructed that displays the source (calcId) and type
1711    * (label) of the annotations that can be added.
1712    * 
1713    * @param menuItem
1714    * @param forSequences
1715    */
1716   protected void configureReferenceAnnotationsMenu(JMenuItem menuItem,
1717           List<SequenceI> forSequences)
1718   {
1719     menuItem.setEnabled(false);
1720
1721     /*
1722      * Temporary store to hold distinct calcId / type pairs for the tooltip.
1723      * Using TreeMap means calcIds are shown in alphabetical order.
1724      */
1725     SortedMap<String, String> tipEntries = new TreeMap<>();
1726     final Map<SequenceI, List<AlignmentAnnotation>> candidates = new LinkedHashMap<>();
1727     AlignmentI al = this.ap.av.getAlignment();
1728     AlignmentUtils.findAddableReferenceAnnotations(forSequences, tipEntries,
1729             candidates, al);
1730     if (!candidates.isEmpty())
1731     {
1732       StringBuilder tooltip = new StringBuilder(64);
1733       tooltip.append(MessageManager.getString("label.add_annotations_for"));
1734
1735       /*
1736        * Found annotations that could be added. Enable the menu item, and
1737        * configure its tooltip and action.
1738        */
1739       menuItem.setEnabled(true);
1740       for (String calcId : tipEntries.keySet())
1741       {
1742         tooltip.append("<br/>" + calcId + "/" + tipEntries.get(calcId));
1743       }
1744       String tooltipText = JvSwingUtils.wrapTooltip(true,
1745               tooltip.toString());
1746       menuItem.setToolTipText(tooltipText);
1747
1748       menuItem.addActionListener(new ActionListener()
1749       {
1750         @Override
1751         public void actionPerformed(ActionEvent e)
1752         {
1753           addReferenceAnnotations_actionPerformed(candidates);
1754         }
1755       });
1756     }
1757   }
1758
1759   /**
1760    * Add annotations to the sequences and to the alignment.
1761    * 
1762    * @param candidates
1763    *          a map whose keys are sequences on the alignment, and values a list
1764    *          of annotations to add to each sequence
1765    */
1766   protected void addReferenceAnnotations_actionPerformed(
1767           Map<SequenceI, List<AlignmentAnnotation>> candidates)
1768   {
1769     final AlignmentI alignment = this.ap.getAlignment();
1770     AlignmentUtils.addReferenceAnnotations(candidates, alignment,
1771             null);
1772     refresh();
1773   }
1774
1775   protected void makeReferenceSeq_actionPerformed(ActionEvent actionEvent)
1776   {
1777     if (!ap.av.getAlignment().hasSeqrep())
1778     {
1779       // initialise the display flags so the user sees something happen
1780       ap.av.setDisplayReferenceSeq(true);
1781       ap.av.setColourByReferenceSeq(true);
1782       ap.av.getAlignment().setSeqrep(sequence);
1783     }
1784     else
1785     {
1786       if (ap.av.getAlignment().getSeqrep() == sequence)
1787       {
1788         ap.av.getAlignment().setSeqrep(null);
1789       }
1790       else
1791       {
1792         ap.av.getAlignment().setSeqrep(sequence);
1793       }
1794     }
1795     refresh();
1796   }
1797
1798   protected void hideInsertions_actionPerformed(ActionEvent actionEvent)
1799   {
1800     HiddenColumns hidden = ap.av.getAlignment().getHiddenColumns();
1801     BitSet inserts = new BitSet();
1802
1803     boolean markedPopup = false;
1804     // mark inserts in current selection
1805     if (ap.av.getSelectionGroup() != null)
1806     {
1807       // mark just the columns in the selection group to be hidden
1808       inserts.set(ap.av.getSelectionGroup().getStartRes(),
1809               ap.av.getSelectionGroup().getEndRes() + 1); // TODO why +1?
1810
1811       // now clear columns without gaps
1812       for (SequenceI sq : ap.av.getSelectionGroup().getSequences())
1813       {
1814         if (sq == sequence)
1815         {
1816           markedPopup = true;
1817         }
1818         inserts.and(sq.getInsertionsAsBits());
1819       }
1820       hidden.clearAndHideColumns(inserts,
1821               ap.av.getSelectionGroup().getStartRes(),
1822               ap.av.getSelectionGroup().getEndRes());
1823     }
1824
1825     // now mark for sequence under popup if we haven't already done it
1826     else if (!markedPopup && sequence != null)
1827     {
1828       inserts.or(sequence.getInsertionsAsBits());
1829
1830       // and set hidden columns accordingly
1831       hidden.hideColumns(inserts);
1832     }
1833     refresh();
1834   }
1835
1836   protected void sequenceSelectionDetails_actionPerformed()
1837   {
1838     createSequenceDetailsReport(ap.av.getSequenceSelection());
1839   }
1840
1841   public void createSequenceDetailsReport(SequenceI[] sequences)
1842   {
1843     StringBuilder contents = new StringBuilder(128);
1844     contents.append("<html><body>");
1845     for (SequenceI seq : sequences)
1846     {
1847       contents.append("<p><h2>" + MessageManager.formatMessage(
1848               "label.create_sequence_details_report_annotation_for",
1849               new Object[]
1850               { seq.getDisplayId(true) }) + "</h2></p>\n<p>");
1851       new SequenceAnnotationReport(false).createSequenceAnnotationReport(
1852               contents, seq, true, true, ap.getSeqPanel().seqCanvas.fr);
1853       contents.append("</p>");
1854     }
1855     contents.append("</body></html>");
1856     String report = contents.toString();
1857
1858     JInternalFrame frame;
1859     if (Platform.isJS())
1860     {
1861       JLabel textLabel = new JLabel();
1862       textLabel.setText(report);
1863       textLabel.setBackground(Color.WHITE);
1864       JPanel pane = new JPanel(new BorderLayout());
1865       pane.setOpaque(true);
1866       pane.setBackground(Color.WHITE);
1867       pane.add(textLabel, BorderLayout.NORTH);
1868       frame = new JInternalFrame();
1869       frame.setFrameIcon(null);
1870       frame.getContentPane().add(new JScrollPane(pane));
1871     }
1872     else
1873     /**
1874      * Java only
1875      * 
1876      * @j2sIgnore
1877      */
1878     {
1879       CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
1880       cap.setText(report);
1881       frame = cap;
1882     }
1883
1884     Desktop.addInternalFrame(frame,
1885             MessageManager.formatMessage("label.sequence_details_for",
1886                     (sequences.length == 1 ? new Object[]
1887                     { sequences[0].getDisplayId(true) }
1888                             : new Object[]
1889                             { MessageManager
1890                                     .getString("label.selection") })),
1891             500, 400);
1892   }
1893
1894   protected void showNonconserved_actionPerformed()
1895   {
1896     getGroup().setShowNonconserved(displayNonconserved.isSelected());
1897     refresh();
1898   }
1899
1900   /**
1901    * call to refresh view after settings change
1902    */
1903   void refresh()
1904   {
1905     ap.updateAnnotation();
1906     // removed paintAlignment(true) here:
1907     // updateAnnotation calls paintAlignment already, so don't need to call
1908     // again
1909
1910     PaintRefresher.Refresh(this, ap.av.getSequenceSetId());
1911   }
1912
1913   /*
1914    * protected void covariationColour_actionPerformed() { getGroup().cs = new
1915    * CovariationColourScheme(sequence.getAnnotation()[0]); refresh(); }
1916    */
1917   /**
1918    * DOCUMENT ME!
1919    * 
1920    * @param selected
1921    * 
1922    * @param e
1923    *          DOCUMENT ME!
1924    */
1925   public void abovePIDColour_actionPerformed(boolean selected)
1926   {
1927     SequenceGroup sg = getGroup();
1928     if (sg.cs == null)
1929     {
1930       return;
1931     }
1932
1933     if (selected)
1934     {
1935       sg.cs.setConsensus(AAFrequency.calculate(
1936               sg.getSequences(ap.av.getHiddenRepSequences()),
1937               sg.getStartRes(), sg.getEndRes() + 1));
1938
1939       int threshold = SliderPanel.setPIDSliderSource(ap,
1940               sg.getGroupColourScheme(), getGroup().getName());
1941
1942       sg.cs.setThreshold(threshold, ap.av.isIgnoreGapsConsensus());
1943
1944       SliderPanel.showPIDSlider();
1945     }
1946     else
1947     // remove PIDColouring
1948     {
1949       sg.cs.setThreshold(0, ap.av.isIgnoreGapsConsensus());
1950       SliderPanel.hidePIDSlider();
1951     }
1952     modifyPID.setEnabled(selected);
1953
1954     refresh();
1955   }
1956
1957   /**
1958    * Open a panel where the user can choose which types of sequence annotation
1959    * to show or hide.
1960    * 
1961    * @param e
1962    */
1963   protected void chooseAnnotations_actionPerformed(ActionEvent e)
1964   {
1965     // todo correct way to guard against opening a duplicate panel?
1966     new AnnotationChooser(ap);
1967   }
1968
1969   /**
1970    * DOCUMENT ME!
1971    * 
1972    * @param e
1973    *          DOCUMENT ME!
1974    */
1975   public void conservationMenuItem_actionPerformed(boolean selected)
1976   {
1977     SequenceGroup sg = getGroup();
1978     if (sg.cs == null)
1979     {
1980       return;
1981     }
1982
1983     if (selected)
1984     {
1985       // JBPNote: Conservation name shouldn't be i18n translated
1986       Conservation c = new Conservation("Group",
1987               sg.getSequences(ap.av.getHiddenRepSequences()),
1988               sg.getStartRes(), sg.getEndRes() + 1);
1989
1990       c.calculate();
1991       c.verdict(false, ap.av.getConsPercGaps());
1992       sg.cs.setConservation(c);
1993
1994       SliderPanel.setConservationSlider(ap, sg.getGroupColourScheme(),
1995               sg.getName());
1996       SliderPanel.showConservationSlider();
1997     }
1998     else
1999     // remove ConservationColouring
2000     {
2001       sg.cs.setConservation(null);
2002       SliderPanel.hideConservationSlider();
2003     }
2004     modifyConservation.setEnabled(selected);
2005
2006     refresh();
2007   }
2008
2009   /**
2010    * Shows a dialog where group name and description may be edited
2011    */
2012   protected void groupName_actionPerformed()
2013   {
2014     SequenceGroup sg = getGroup();
2015     EditNameDialog dialog = new EditNameDialog(sg.getName(),
2016             sg.getDescription(),
2017             MessageManager.getString("label.group_name"),
2018             MessageManager.getString("label.group_description"));
2019     dialog.showDialog(ap.alignFrame,
2020             MessageManager.getString("label.edit_group_name_description"),
2021             () -> {
2022               sg.setName(dialog.getName());
2023               sg.setDescription(dialog.getDescription());
2024               refresh();
2025             });
2026   }
2027
2028   /**
2029    * Get selection group - adding it to the alignment if necessary.
2030    * 
2031    * @return sequence group to operate on
2032    */
2033   SequenceGroup getGroup()
2034   {
2035     SequenceGroup sg = ap.av.getSelectionGroup();
2036     // this method won't add a new group if it already exists
2037     if (sg != null)
2038     {
2039       ap.av.getAlignment().addGroup(sg);
2040     }
2041
2042     return sg;
2043   }
2044
2045   /**
2046    * Shows a dialog where the sequence name and description may be edited. If a
2047    * name containing spaces is entered, these are converted to underscores, with
2048    * a warning message.
2049    */
2050   void sequenceName_actionPerformed()
2051   {
2052     EditNameDialog dialog = new EditNameDialog(sequence.getName(),
2053             sequence.getDescription(),
2054             MessageManager.getString("label.sequence_name"),
2055             MessageManager.getString("label.sequence_description"));
2056     dialog.showDialog(ap.alignFrame, MessageManager
2057             .getString("label.edit_sequence_name_description"), () -> {
2058               if (dialog.getName() != null)
2059               {
2060                 if (dialog.getName().indexOf(" ") > -1)
2061                 {
2062                   String ok = MessageManager.getString("action.ok");
2063                   String cancel = MessageManager.getString("action.cancel");
2064                   String message = MessageManager.getString(
2065                           "label.spaces_converted_to_underscores");
2066                   String title = MessageManager.getString(
2067                           "label.no_spaces_allowed_sequence_name");
2068                   Object[] options = new Object[] { ok, cancel };
2069
2070                   JvOptionPane.frameDialog(message, title,
2071                           JvOptionPane.WARNING_MESSAGE, null, null, null,
2072                           false);
2073                 }
2074                 sequence.setName(dialog.getName().replace(' ', '_'));
2075                 ap.paintAlignment(false, false);
2076               }
2077               sequence.setDescription(dialog.getDescription());
2078               ap.av.firePropertyChange("alignment", null,
2079                       ap.av.getAlignment().getSequences());
2080             });
2081   }
2082
2083   /**
2084    * DOCUMENT ME!
2085    * 
2086    * @param e
2087    *          DOCUMENT ME!
2088    */
2089   void unGroupMenuItem_actionPerformed()
2090   {
2091     SequenceGroup sg = ap.av.getSelectionGroup();
2092     ap.av.getAlignment().deleteGroup(sg);
2093     ap.av.setSelectionGroup(null);
2094     refresh();
2095   }
2096
2097   void createGroupMenuItem_actionPerformed()
2098   {
2099     getGroup(); // implicitly creates group - note - should apply defaults / use
2100                 // standard alignment window logic for this
2101     refresh();
2102   }
2103
2104   /**
2105    * Offers a colour chooser and sets the selected colour as the group outline
2106    */
2107   protected void outline_actionPerformed()
2108   {
2109     String title = MessageManager.getString("label.select_outline_colour");
2110     ColourChooserListener listener = new ColourChooserListener()
2111     {
2112       @Override
2113       public void colourSelected(Color c)
2114       {
2115         getGroup().setOutlineColour(c);
2116         refresh();
2117       }
2118     };
2119     JalviewColourChooser.showColourChooser(Desktop.getDesktop(), title,
2120             Color.BLUE, listener);
2121   }
2122
2123   /**
2124    * DOCUMENT ME!
2125    * 
2126    * @param e
2127    *          DOCUMENT ME!
2128    */
2129   public void showBoxes_actionPerformed()
2130   {
2131     getGroup().setDisplayBoxes(showBoxes.isSelected());
2132     refresh();
2133   }
2134
2135   /**
2136    * DOCUMENT ME!
2137    * 
2138    * @param e
2139    *          DOCUMENT ME!
2140    */
2141   public void showText_actionPerformed()
2142   {
2143     getGroup().setDisplayText(showText.isSelected());
2144     refresh();
2145   }
2146
2147   /**
2148    * DOCUMENT ME!
2149    * 
2150    * @param e
2151    *          DOCUMENT ME!
2152    */
2153   public void showColourText_actionPerformed()
2154   {
2155     getGroup().setColourText(showColourText.isSelected());
2156     refresh();
2157   }
2158
2159   void hideSequences(boolean representGroup)
2160   {
2161     ap.av.hideSequences(sequence, representGroup);
2162   }
2163
2164   public void copy_actionPerformed()
2165   {
2166     ap.alignFrame.copy_actionPerformed();
2167   }
2168
2169   public void cut_actionPerformed()
2170   {
2171     ap.alignFrame.cut_actionPerformed();
2172   }
2173
2174   void changeCase(ActionEvent e)
2175   {
2176     Object source = e.getSource();
2177     SequenceGroup sg = ap.av.getSelectionGroup();
2178
2179     if (sg != null)
2180     {
2181       List<int[]> startEnd = ap.av.getVisibleRegionBoundaries(
2182               sg.getStartRes(), sg.getEndRes() + 1);
2183
2184       String description;
2185       int caseChange;
2186
2187       if (source == toggle)
2188       {
2189         description = MessageManager.getString("label.toggle_case");
2190         caseChange = ChangeCaseCommand.TOGGLE_CASE;
2191       }
2192       else if (source == upperCase)
2193       {
2194         description = MessageManager.getString("label.to_upper_case");
2195         caseChange = ChangeCaseCommand.TO_UPPER;
2196       }
2197       else
2198       {
2199         description = MessageManager.getString("label.to_lower_case");
2200         caseChange = ChangeCaseCommand.TO_LOWER;
2201       }
2202
2203       ChangeCaseCommand caseCommand = new ChangeCaseCommand(description,
2204               sg.getSequencesAsArray(ap.av.getHiddenRepSequences()),
2205               startEnd, caseChange);
2206
2207       ap.alignFrame.addHistoryItem(caseCommand);
2208
2209       ap.av.firePropertyChange("alignment", null,
2210               ap.av.getAlignment().getSequences());
2211
2212     }
2213   }
2214
2215   public void outputText_actionPerformed(ActionEvent e)
2216   {
2217     CutAndPasteTransfer cap = new CutAndPasteTransfer();
2218     cap.setForInput(null);
2219     Desktop.addInternalFrame(cap, MessageManager
2220             .formatMessage("label.alignment_output_command", new Object[]
2221             { e.getActionCommand() }), 600, 500);
2222
2223     String[] omitHidden = null;
2224
2225     jalview.bin.Console.outPrintln("PROMPT USER HERE"); // TODO: decide if a prompt happens
2226     // or we simply trust the user wants
2227     // wysiwig behaviour
2228
2229     FileFormatI fileFormat = FileFormats.getInstance()
2230             .forName(e.getActionCommand());
2231     cap.setText(
2232             new FormatAdapter(ap).formatSequences(fileFormat, ap, true));
2233   }
2234
2235   public void sequenceFeature_actionPerformed()
2236   {
2237     SequenceGroup sg = ap.av.getSelectionGroup();
2238     if (sg == null)
2239     {
2240       return;
2241     }
2242
2243     List<SequenceI> seqs = new ArrayList<>();
2244     List<SequenceFeature> features = new ArrayList<>();
2245
2246     /*
2247      * assemble dataset sequences, and template new sequence features,
2248      * for the amend features dialog
2249      */
2250     int gSize = sg.getSize();
2251     for (int i = 0; i < gSize; i++)
2252     {
2253       int start = sg.getSequenceAt(i).findPosition(sg.getStartRes());
2254       int end = sg.findEndRes(sg.getSequenceAt(i));
2255       if (start <= end)
2256       {
2257         seqs.add(sg.getSequenceAt(i).getDatasetSequence());
2258         features.add(new SequenceFeature(null, null, start, end, null));
2259       }
2260     }
2261
2262     /*
2263      * an entirely gapped region will generate empty lists of sequence / features
2264      */
2265     if (!seqs.isEmpty())
2266     {
2267       new FeatureEditor(ap, seqs, features, true).showDialog();
2268     }
2269   }
2270
2271   public void textColour_actionPerformed()
2272   {
2273     SequenceGroup sg = getGroup();
2274     if (sg != null)
2275     {
2276       new TextColourChooser().chooseColour(ap, sg);
2277     }
2278   }
2279
2280   /**
2281    * Shows a dialog where sequence characters may be edited. Any changes are
2282    * applied, and added as an available 'Undo' item in the edit commands
2283    * history.
2284    */
2285   public void editSequence_actionPerformed()
2286   {
2287     SequenceGroup sg = ap.av.getSelectionGroup();
2288
2289     SequenceI seq = sequence;
2290     if (sg != null)
2291     {
2292       if (seq == null)
2293       {
2294         seq = sg.getSequenceAt(0);
2295       }
2296
2297       EditNameDialog dialog = new EditNameDialog(
2298               seq.getSequenceAsString(sg.getStartRes(), sg.getEndRes() + 1),
2299               null, MessageManager.getString("label.edit_sequence"), null);
2300       dialog.showDialog(ap.alignFrame,
2301               MessageManager.getString("label.edit_sequence"), () -> {
2302                 EditCommand editCommand = new EditCommand(
2303                         MessageManager.getString("label.edit_sequences"),
2304                         Action.REPLACE,
2305                         dialog.getName().replace(' ',
2306                                 ap.av.getGapCharacter()),
2307                         sg.getSequencesAsArray(
2308                                 ap.av.getHiddenRepSequences()),
2309                         sg.getStartRes(), sg.getEndRes() + 1,
2310                         ap.av.getAlignment());
2311                 ap.alignFrame.addHistoryItem(editCommand);
2312                 ap.av.firePropertyChange("alignment", null,
2313                         ap.av.getAlignment().getSequences());
2314               });
2315     }
2316   }
2317
2318   /**
2319    * Action on user selecting an item from the colour menu (that does not have
2320    * its bespoke action handler)
2321    * 
2322    * @return
2323    */
2324   @Override
2325   public void changeColour_actionPerformed(String colourSchemeName)
2326   {
2327     SequenceGroup sg = getGroup();
2328     /*
2329      * switch to the chosen colour scheme (or null for None)
2330      */
2331     ColourSchemeI colourScheme = ColourSchemes.getInstance()
2332             .getColourScheme(colourSchemeName, ap.av, sg,
2333                     ap.av.getHiddenRepSequences());
2334     sg.setColourScheme(colourScheme);
2335     if (colourScheme instanceof Blosum62ColourScheme
2336             || colourScheme instanceof PIDColourScheme)
2337     {
2338       sg.cs.setConsensus(AAFrequency.calculate(
2339               sg.getSequences(ap.av.getHiddenRepSequences()),
2340               sg.getStartRes(), sg.getEndRes() + 1));
2341     }
2342
2343     refresh();
2344   }
2345
2346 }