f50b72b206c9da5fbe942c7e3698910a93d34dcc
[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       JPanel panel = new JPanel(new BorderLayout());
941       panel.setOpaque(true);
942       panel.setBackground(Color.white);
943       // TODO JAL-3026 set style of table correctly for feature details
944       JLabel reprt = new JLabel(MessageManager
945               .formatMessage("label.html_content", new Object[]
946               { sf.getDetailsReport(seqName, mf) }));
947       reprt.setBackground(Color.WHITE);
948       reprt.setOpaque(true);
949       panel.add(reprt, BorderLayout.CENTER);
950       details.setContentPane(panel);
951       details.pack();
952     }
953     else
954     /**
955      * Java only
956      * 
957      * @j2sIgnore
958      */
959     {
960       CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
961       // it appears Java's CSS does not support border-collapse :-(
962       cap.addStylesheetRule("table { border-collapse: collapse;}");
963       cap.addStylesheetRule("table, td, th {border: 1px solid black;}");
964       cap.setText(sf.getDetailsReport(seqName, mf));
965       details = cap;
966     }
967     Desktop.addInternalFrame(details,
968             MessageManager.getString("label.feature_details"), 500, 500);
969   }
970
971   /**
972    * Adds a 'Link' menu item with a sub-menu item for each hyperlink provided.
973    * When seq is not null, these are links for the sequence id, which may be to
974    * external web sites for the sequence accession, and/or links embedded in
975    * non-positional features. When seq is null, only links embedded in the
976    * provided features are added. If no links are found, the menu is not added.
977    * 
978    * @param seq
979    * @param features
980    */
981   void addLinks(final SequenceI seq, List<SequenceFeature> features)
982   {
983     JMenu linkMenu = buildLinkMenu(forIdPanel ? seq : null, features);
984
985     // only add link menu if it has entries
986     if (linkMenu.getItemCount() > 0)
987     {
988       if (forIdPanel)
989       {
990         sequenceMenu.add(linkMenu);
991       }
992       else
993       {
994         add(linkMenu);
995       }
996     }
997   }
998
999   /**
1000    * Add annotation types to 'Show annotations' and/or 'Hide annotations' menus.
1001    * "All" is added first, followed by a separator. Then add any annotation
1002    * types associated with the current selection. Separate menus are built for
1003    * the selected sequence group (if any), and the selected sequence.
1004    * <p>
1005    * Some annotation rows are always rendered together - these can be identified
1006    * by a common graphGroup property > -1. Only one of each group will be marked
1007    * as visible (to avoid duplication of the display). For such groups we add a
1008    * composite type name, e.g.
1009    * <p>
1010    * IUPredWS (Long), IUPredWS (Short)
1011    * 
1012    * @param seq
1013    */
1014   protected void buildAnnotationTypesMenus(JMenu showMenu, JMenu hideMenu,
1015           List<SequenceI> forSequences)
1016   {
1017     showMenu.removeAll();
1018     hideMenu.removeAll();
1019
1020     final List<String> all = Arrays
1021             .asList(new String[]
1022             { MessageManager.getString("label.all") });
1023     addAnnotationTypeToShowHide(showMenu, forSequences, "", all, true,
1024             true);
1025     addAnnotationTypeToShowHide(hideMenu, forSequences, "", all, true,
1026             false);
1027     showMenu.addSeparator();
1028     hideMenu.addSeparator();
1029
1030     final AlignmentAnnotation[] annotations = ap.getAlignment()
1031             .getAlignmentAnnotation();
1032
1033     /*
1034      * Find shown/hidden annotations types, distinguished by source (calcId),
1035      * and grouped by graphGroup. Using LinkedHashMap means we will retrieve in
1036      * the insertion order, which is the order of the annotations on the
1037      * alignment.
1038      */
1039     Map<String, List<List<String>>> shownTypes = new LinkedHashMap<>();
1040     Map<String, List<List<String>>> hiddenTypes = new LinkedHashMap<>();
1041     AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes, hiddenTypes,
1042             AlignmentAnnotationUtils.asList(annotations), forSequences);
1043
1044     for (String calcId : hiddenTypes.keySet())
1045     {
1046       for (List<String> type : hiddenTypes.get(calcId))
1047       {
1048         addAnnotationTypeToShowHide(showMenu, forSequences, calcId, type,
1049                 false, true);
1050       }
1051     }
1052     // grey out 'show annotations' if none are hidden
1053     showMenu.setEnabled(!hiddenTypes.isEmpty());
1054
1055     for (String calcId : shownTypes.keySet())
1056     {
1057       for (List<String> type : shownTypes.get(calcId))
1058       {
1059         addAnnotationTypeToShowHide(hideMenu, forSequences, calcId, type,
1060                 false, false);
1061       }
1062     }
1063     // grey out 'hide annotations' if none are shown
1064     hideMenu.setEnabled(!shownTypes.isEmpty());
1065   }
1066
1067   /**
1068    * Returns a list of sequences - either the current selection group (if there
1069    * is one), else the specified single sequence.
1070    * 
1071    * @param seq
1072    * @return
1073    */
1074   protected List<SequenceI> getSequenceScope(SequenceI seq)
1075   {
1076     List<SequenceI> forSequences = null;
1077     final SequenceGroup selectionGroup = ap.av.getSelectionGroup();
1078     if (selectionGroup != null && selectionGroup.getSize() > 0)
1079     {
1080       forSequences = selectionGroup.getSequences();
1081     }
1082     else
1083     {
1084       forSequences = seq == null ? Collections.<SequenceI> emptyList()
1085               : Arrays.asList(seq);
1086     }
1087     return forSequences;
1088   }
1089
1090   /**
1091    * Add one annotation type to the 'Show Annotations' or 'Hide Annotations'
1092    * menus.
1093    * 
1094    * @param showOrHideMenu
1095    *          the menu to add to
1096    * @param forSequences
1097    *          the sequences whose annotations may be shown or hidden
1098    * @param calcId
1099    * @param types
1100    *          the label to add
1101    * @param allTypes
1102    *          if true this is a special label meaning 'All'
1103    * @param actionIsShow
1104    *          if true, the select menu item action is to show the annotation
1105    *          type, else hide
1106    */
1107   protected void addAnnotationTypeToShowHide(JMenu showOrHideMenu,
1108           final List<SequenceI> forSequences, String calcId,
1109           final List<String> types, final boolean allTypes,
1110           final boolean actionIsShow)
1111   {
1112     String label = types.toString(); // [a, b, c]
1113     label = label.substring(1, label.length() - 1); // a, b, c
1114     final JMenuItem item = new JMenuItem(label);
1115     item.setToolTipText(calcId);
1116     item.addActionListener(new ActionListener()
1117     {
1118       @Override
1119       public void actionPerformed(ActionEvent e)
1120       {
1121         AlignmentUtils.showOrHideSequenceAnnotations(ap.getAlignment(),
1122                 types, forSequences, allTypes, actionIsShow);
1123         refresh();
1124       }
1125     });
1126     showOrHideMenu.add(item);
1127   }
1128
1129   private void buildGroupURLMenu(SequenceGroup sg, List<String> groupLinks)
1130   {
1131
1132     // TODO: usability: thread off the generation of group url content so root
1133     // menu appears asap
1134     // sequence only URLs
1135     // ID/regex match URLs
1136     JMenu groupLinksMenu = new JMenu(
1137             MessageManager.getString("action.group_link"));
1138     // three types of url that might be created.
1139     JMenu[] linkMenus = new JMenu[] { null,
1140         new JMenu(MessageManager.getString("action.ids")),
1141         new JMenu(MessageManager.getString("action.sequences")),
1142         new JMenu(MessageManager.getString("action.ids_sequences")) };
1143
1144     SequenceI[] seqs = ap.av.getSelectionAsNewSequence();
1145     String[][] idandseqs = GroupUrlLink.formStrings(seqs);
1146     Hashtable<String, Object[]> commonDbrefs = new Hashtable<>();
1147     for (int sq = 0; sq < seqs.length; sq++)
1148     {
1149
1150       int start = seqs[sq].findPosition(sg.getStartRes()),
1151               end = seqs[sq].findPosition(sg.getEndRes());
1152       // just collect ids from dataset sequence
1153       // TODO: check if IDs collected from selecton group intersects with the
1154       // current selection, too
1155       SequenceI sqi = seqs[sq];
1156       while (sqi.getDatasetSequence() != null)
1157       {
1158         sqi = sqi.getDatasetSequence();
1159       }
1160       List<DBRefEntry> dbr = sqi.getDBRefs();
1161       int nd;
1162       if (dbr != null && (nd = dbr.size()) > 0)
1163       {
1164         for (int d = 0; d < nd; d++)
1165         {
1166           DBRefEntry e = dbr.get(d);
1167           String src = e.getSource(); // jalview.util.DBRefUtils.getCanonicalName(dbr[d].getSource()).toUpperCase(Locale.ROOT);
1168           Object[] sarray = commonDbrefs.get(src);
1169           if (sarray == null)
1170           {
1171             sarray = new Object[2];
1172             sarray[0] = new int[] { 0 };
1173             sarray[1] = new String[seqs.length];
1174
1175             commonDbrefs.put(src, sarray);
1176           }
1177
1178           if (((String[]) sarray[1])[sq] == null)
1179           {
1180             if (!e.hasMap()
1181                     || (e.getMap().locateMappedRange(start, end) != null))
1182             {
1183               ((String[]) sarray[1])[sq] = e.getAccessionId();
1184               ((int[]) sarray[0])[0]++;
1185             }
1186           }
1187         }
1188       }
1189     }
1190     // now create group links for all distinct ID/sequence sets.
1191     boolean addMenu = false; // indicates if there are any group links to give
1192                              // to user
1193     for (String link : groupLinks)
1194     {
1195       GroupUrlLink urlLink = null;
1196       try
1197       {
1198         urlLink = new GroupUrlLink(link);
1199       } catch (Exception foo)
1200       {
1201         Console.error("Exception for GroupURLLink '" + link + "'", foo);
1202         continue;
1203       }
1204       if (!urlLink.isValid())
1205       {
1206         Console.error(urlLink.getInvalidMessage());
1207         continue;
1208       }
1209       final String label = urlLink.getLabel();
1210       boolean usingNames = false;
1211       // Now see which parts of the group apply for this URL
1212       String ltarget = urlLink.getTarget(); // jalview.util.DBRefUtils.getCanonicalName(urlLink.getTarget());
1213       Object[] idset = commonDbrefs.get(ltarget.toUpperCase(Locale.ROOT));
1214       String[] seqstr, ids; // input to makeUrl
1215       if (idset != null)
1216       {
1217         int numinput = ((int[]) idset[0])[0];
1218         String[] allids = ((String[]) idset[1]);
1219         seqstr = new String[numinput];
1220         ids = new String[numinput];
1221         for (int sq = 0, idcount = 0; sq < seqs.length; sq++)
1222         {
1223           if (allids[sq] != null)
1224           {
1225             ids[idcount] = allids[sq];
1226             seqstr[idcount++] = idandseqs[1][sq];
1227           }
1228         }
1229       }
1230       else
1231       {
1232         // just use the id/seq set
1233         seqstr = idandseqs[1];
1234         ids = idandseqs[0];
1235         usingNames = true;
1236       }
1237       // and try and make the groupURL!
1238
1239       Object[] urlset = null;
1240       try
1241       {
1242         urlset = urlLink.makeUrlStubs(ids, seqstr,
1243                 "FromJalview" + System.currentTimeMillis(), false);
1244       } catch (UrlStringTooLongException e)
1245       {
1246       }
1247       if (urlset != null)
1248       {
1249         int type = urlLink.getGroupURLType() & 3;
1250         // first two bits ofurlLink type bitfield are sequenceids and sequences
1251         // TODO: FUTURE: ensure the groupURL menu structure can be generalised
1252         addshowLink(linkMenus[type],
1253                 label + (((type & 1) == 1)
1254                         ? ("(" + (usingNames ? "Names" : ltarget) + ")")
1255                         : ""),
1256                 urlLink, urlset);
1257         addMenu = true;
1258       }
1259     }
1260     if (addMenu)
1261     {
1262       groupLinksMenu = new JMenu(
1263               MessageManager.getString("action.group_link"));
1264       for (int m = 0; m < linkMenus.length; m++)
1265       {
1266         if (linkMenus[m] != null
1267                 && linkMenus[m].getMenuComponentCount() > 0)
1268         {
1269           groupLinksMenu.add(linkMenus[m]);
1270         }
1271       }
1272
1273       groupMenu.add(groupLinksMenu);
1274     }
1275   }
1276
1277   /**
1278    * DOCUMENT ME!
1279    * 
1280    * @throws Exception
1281    *           DOCUMENT ME!
1282    */
1283   private void jbInit() throws Exception
1284   {
1285     groupMenu.setText(MessageManager.getString("label.selection"));
1286     groupName.setText(MessageManager.getString("label.name"));
1287     groupName.addActionListener(new ActionListener()
1288     {
1289       @Override
1290       public void actionPerformed(ActionEvent e)
1291       {
1292         groupName_actionPerformed();
1293       }
1294     });
1295     sequenceMenu.setText(MessageManager.getString("label.sequence"));
1296
1297     JMenuItem sequenceName = new JMenuItem(
1298             MessageManager.getString("label.edit_name_description"));
1299     sequenceName.addActionListener(new ActionListener()
1300     {
1301       @Override
1302       public void actionPerformed(ActionEvent e)
1303       {
1304         sequenceName_actionPerformed();
1305       }
1306     });
1307     JMenuItem chooseAnnotations = new JMenuItem(
1308             MessageManager.getString("action.choose_annotations"));
1309     chooseAnnotations.addActionListener(new ActionListener()
1310     {
1311       @Override
1312       public void actionPerformed(ActionEvent e)
1313       {
1314         chooseAnnotations_actionPerformed(e);
1315       }
1316     });
1317     JMenuItem sequenceDetails = new JMenuItem(
1318             MessageManager.getString("label.sequence_details"));
1319     sequenceDetails.addActionListener(new ActionListener()
1320     {
1321       @Override
1322       public void actionPerformed(ActionEvent e)
1323       {
1324         createSequenceDetailsReport(new SequenceI[] { sequence });
1325       }
1326     });
1327     JMenuItem sequenceSelDetails = new JMenuItem(
1328             MessageManager.getString("label.sequence_details"));
1329     sequenceSelDetails.addActionListener(new ActionListener()
1330     {
1331       @Override
1332       public void actionPerformed(ActionEvent e)
1333       {
1334         createSequenceDetailsReport(ap.av.getSequenceSelection());
1335       }
1336     });
1337
1338     unGroupMenuItem
1339             .setText(MessageManager.getString("action.remove_group"));
1340     unGroupMenuItem.addActionListener(new ActionListener()
1341     {
1342       @Override
1343       public void actionPerformed(ActionEvent e)
1344       {
1345         unGroupMenuItem_actionPerformed();
1346       }
1347     });
1348     createGroupMenuItem
1349             .setText(MessageManager.getString("action.create_group"));
1350     createGroupMenuItem.addActionListener(new ActionListener()
1351     {
1352       @Override
1353       public void actionPerformed(ActionEvent e)
1354       {
1355         createGroupMenuItem_actionPerformed();
1356       }
1357     });
1358
1359     JMenuItem outline = new JMenuItem(
1360             MessageManager.getString("action.border_colour"));
1361     outline.addActionListener(new ActionListener()
1362     {
1363       @Override
1364       public void actionPerformed(ActionEvent e)
1365       {
1366         outline_actionPerformed();
1367       }
1368     });
1369     showBoxes.setText(MessageManager.getString("action.boxes"));
1370     showBoxes.setState(true);
1371     showBoxes.addActionListener(new ActionListener()
1372     {
1373       @Override
1374       public void actionPerformed(ActionEvent e)
1375       {
1376         showBoxes_actionPerformed();
1377       }
1378     });
1379     showText.setText(MessageManager.getString("action.text"));
1380     showText.setState(true);
1381     showText.addActionListener(new ActionListener()
1382     {
1383       @Override
1384       public void actionPerformed(ActionEvent e)
1385       {
1386         showText_actionPerformed();
1387       }
1388     });
1389     showColourText.setText(MessageManager.getString("label.colour_text"));
1390     showColourText.addActionListener(new ActionListener()
1391     {
1392       @Override
1393       public void actionPerformed(ActionEvent e)
1394       {
1395         showColourText_actionPerformed();
1396       }
1397     });
1398     displayNonconserved
1399             .setText(MessageManager.getString("label.show_non_conserved"));
1400     displayNonconserved.setState(true);
1401     displayNonconserved.addActionListener(new ActionListener()
1402     {
1403       @Override
1404       public void actionPerformed(ActionEvent e)
1405       {
1406         showNonconserved_actionPerformed();
1407       }
1408     });
1409     editMenu.setText(MessageManager.getString("action.edit"));
1410     JMenuItem cut = new JMenuItem(MessageManager.getString("action.cut"));
1411     cut.addActionListener(new ActionListener()
1412     {
1413       @Override
1414       public void actionPerformed(ActionEvent e)
1415       {
1416         cut_actionPerformed();
1417       }
1418     });
1419     upperCase.setText(MessageManager.getString("label.to_upper_case"));
1420     upperCase.addActionListener(new ActionListener()
1421     {
1422       @Override
1423       public void actionPerformed(ActionEvent e)
1424       {
1425         changeCase(e);
1426       }
1427     });
1428     JMenuItem copy = new JMenuItem(MessageManager.getString("action.copy"));
1429     copy.addActionListener(new ActionListener()
1430     {
1431       @Override
1432       public void actionPerformed(ActionEvent e)
1433       {
1434         copy_actionPerformed();
1435       }
1436     });
1437     lowerCase.setText(MessageManager.getString("label.to_lower_case"));
1438     lowerCase.addActionListener(new ActionListener()
1439     {
1440       @Override
1441       public void actionPerformed(ActionEvent e)
1442       {
1443         changeCase(e);
1444       }
1445     });
1446     toggle.setText(MessageManager.getString("label.toggle_case"));
1447     toggle.addActionListener(new ActionListener()
1448     {
1449       @Override
1450       public void actionPerformed(ActionEvent e)
1451       {
1452         changeCase(e);
1453       }
1454     });
1455     outputMenu.setText(
1456             MessageManager.getString("label.out_to_textbox") + "...");
1457     seqShowAnnotationsMenu
1458             .setText(MessageManager.getString("label.show_annotations"));
1459     seqHideAnnotationsMenu
1460             .setText(MessageManager.getString("label.hide_annotations"));
1461     groupShowAnnotationsMenu
1462             .setText(MessageManager.getString("label.show_annotations"));
1463     groupHideAnnotationsMenu
1464             .setText(MessageManager.getString("label.hide_annotations"));
1465     JMenuItem sequenceFeature = new JMenuItem(
1466             MessageManager.getString("label.create_sequence_feature"));
1467     sequenceFeature.addActionListener(new ActionListener()
1468     {
1469       @Override
1470       public void actionPerformed(ActionEvent e)
1471       {
1472         sequenceFeature_actionPerformed();
1473       }
1474     });
1475     editGroupMenu.setText(MessageManager.getString("label.group"));
1476     chooseStructure.setText(
1477             MessageManager.getString("label.show_pdbstruct_dialog"));
1478     chooseStructure.addActionListener(new ActionListener()
1479     {
1480       @Override
1481       public void actionPerformed(ActionEvent actionEvent)
1482       {
1483         SequenceI[] selectedSeqs = new SequenceI[] { sequence };
1484         if (ap.av.getSelectionGroup() != null)
1485         {
1486           selectedSeqs = ap.av.getSequenceSelection();
1487         }
1488         new StructureChooser(selectedSeqs, sequence, ap);
1489       }
1490     });
1491
1492     rnaStructureMenu
1493             .setText(MessageManager.getString("label.view_rna_structure"));
1494
1495     // colStructureMenu.setText("Colour By Structure");
1496     JMenuItem editSequence = new JMenuItem(
1497             MessageManager.getString("label.edit_sequence") + "...");
1498     editSequence.addActionListener(new ActionListener()
1499     {
1500       @Override
1501       public void actionPerformed(ActionEvent actionEvent)
1502       {
1503         editSequence_actionPerformed();
1504       }
1505     });
1506     makeReferenceSeq.setText(
1507             MessageManager.getString("label.mark_as_representative"));
1508     makeReferenceSeq.addActionListener(new ActionListener()
1509     {
1510
1511       @Override
1512       public void actionPerformed(ActionEvent actionEvent)
1513       {
1514         makeReferenceSeq_actionPerformed(actionEvent);
1515
1516       }
1517     });
1518
1519     groupMenu.add(sequenceSelDetails);
1520     add(groupMenu);
1521     add(sequenceMenu);
1522     add(rnaStructureMenu);
1523     add(chooseStructure);
1524     if (forIdPanel)
1525     {
1526       JMenuItem hideInsertions = new JMenuItem(
1527               MessageManager.getString("label.hide_insertions"));
1528       hideInsertions.addActionListener(new ActionListener()
1529       {
1530
1531         @Override
1532         public void actionPerformed(ActionEvent e)
1533         {
1534           hideInsertions_actionPerformed(e);
1535         }
1536       });
1537       add(hideInsertions);
1538     }
1539     // annotations configuration panel suppressed for now
1540     // groupMenu.add(chooseAnnotations);
1541
1542     /*
1543      * Add show/hide annotations to the Sequence menu, and to the Selection menu
1544      * (if a selection group is in force).
1545      */
1546     sequenceMenu.add(seqShowAnnotationsMenu);
1547     sequenceMenu.add(seqHideAnnotationsMenu);
1548     sequenceMenu.add(seqAddReferenceAnnotations);
1549     groupMenu.add(groupShowAnnotationsMenu);
1550     groupMenu.add(groupHideAnnotationsMenu);
1551     groupMenu.add(groupAddReferenceAnnotations);
1552     groupMenu.add(editMenu);
1553     groupMenu.add(outputMenu);
1554     groupMenu.add(sequenceFeature);
1555     groupMenu.add(createGroupMenuItem);
1556     groupMenu.add(unGroupMenuItem);
1557     groupMenu.add(editGroupMenu);
1558     sequenceMenu.add(sequenceName);
1559     sequenceMenu.add(sequenceDetails);
1560     sequenceMenu.add(makeReferenceSeq);
1561
1562     initColourMenu();
1563     buildColourMenu();
1564
1565     editMenu.add(copy);
1566     editMenu.add(cut);
1567     editMenu.add(editSequence);
1568     editMenu.add(upperCase);
1569     editMenu.add(lowerCase);
1570     editMenu.add(toggle);
1571     editGroupMenu.add(groupName);
1572     editGroupMenu.add(colourMenu);
1573     editGroupMenu.add(showBoxes);
1574     editGroupMenu.add(showText);
1575     editGroupMenu.add(showColourText);
1576     editGroupMenu.add(outline);
1577     editGroupMenu.add(displayNonconserved);
1578   }
1579
1580   /**
1581    * Constructs the entries for the colour menu
1582    */
1583   protected void initColourMenu()
1584   {
1585     colourMenu.setText(MessageManager.getString("label.group_colour"));
1586     textColour.setText(MessageManager.getString("label.text_colour"));
1587     textColour.addActionListener(new ActionListener()
1588     {
1589       @Override
1590       public void actionPerformed(ActionEvent e)
1591       {
1592         textColour_actionPerformed();
1593       }
1594     });
1595
1596     abovePIDColour.setText(
1597             MessageManager.getString("label.above_identity_threshold"));
1598     abovePIDColour.addActionListener(new ActionListener()
1599     {
1600       @Override
1601       public void actionPerformed(ActionEvent e)
1602       {
1603         abovePIDColour_actionPerformed(abovePIDColour.isSelected());
1604       }
1605     });
1606
1607     modifyPID.setText(
1608             MessageManager.getString("label.modify_identity_threshold"));
1609     modifyPID.addActionListener(new ActionListener()
1610     {
1611       @Override
1612       public void actionPerformed(ActionEvent e)
1613       {
1614         modifyPID_actionPerformed();
1615       }
1616     });
1617
1618     conservationMenuItem
1619             .setText(MessageManager.getString("action.by_conservation"));
1620     conservationMenuItem.addActionListener(new ActionListener()
1621     {
1622       @Override
1623       public void actionPerformed(ActionEvent e)
1624       {
1625         conservationMenuItem_actionPerformed(
1626                 conservationMenuItem.isSelected());
1627       }
1628     });
1629
1630     annotationColour = new JRadioButtonMenuItem(
1631             MessageManager.getString("action.by_annotation"));
1632     annotationColour.setName(ResidueColourScheme.ANNOTATION_COLOUR);
1633     annotationColour.setEnabled(false);
1634     annotationColour.setToolTipText(
1635             MessageManager.getString("label.by_annotation_tooltip"));
1636
1637     modifyConservation.setText(MessageManager
1638             .getString("label.modify_conservation_threshold"));
1639     modifyConservation.addActionListener(new ActionListener()
1640     {
1641       @Override
1642       public void actionPerformed(ActionEvent e)
1643       {
1644         modifyConservation_actionPerformed();
1645       }
1646     });
1647   }
1648
1649   /**
1650    * Builds the group colour sub-menu, including any user-defined colours which
1651    * were loaded at startup or during the Jalview session
1652    */
1653   protected void buildColourMenu()
1654   {
1655     SequenceGroup sg = ap.av.getSelectionGroup();
1656     if (sg == null)
1657     {
1658       /*
1659        * popup menu with no sequence group scope
1660        */
1661       return;
1662     }
1663     colourMenu.removeAll();
1664     colourMenu.add(textColour);
1665     colourMenu.addSeparator();
1666
1667     ButtonGroup bg = ColourMenuHelper.addMenuItems(colourMenu, this, sg,
1668             false);
1669     bg.add(annotationColour);
1670     colourMenu.add(annotationColour);
1671
1672     colourMenu.addSeparator();
1673     colourMenu.add(conservationMenuItem);
1674     colourMenu.add(modifyConservation);
1675     colourMenu.add(abovePIDColour);
1676     colourMenu.add(modifyPID);
1677   }
1678
1679   protected void modifyConservation_actionPerformed()
1680   {
1681     SequenceGroup sg = getGroup();
1682     if (sg.cs != null)
1683     {
1684       SliderPanel.setConservationSlider(ap, sg.cs, sg.getName());
1685       SliderPanel.showConservationSlider();
1686     }
1687   }
1688
1689   protected void modifyPID_actionPerformed()
1690   {
1691     SequenceGroup sg = getGroup();
1692     if (sg.cs != null)
1693     {
1694       // int threshold = SliderPanel.setPIDSliderSource(ap, sg.cs, getGroup()
1695       // .getName());
1696       // sg.cs.setThreshold(threshold, ap.av.isIgnoreGapsConsensus());
1697       SliderPanel.setPIDSliderSource(ap, sg.cs, getGroup().getName());
1698       SliderPanel.showPIDSlider();
1699     }
1700   }
1701
1702   /**
1703    * Check for any annotations on the underlying dataset sequences (for the
1704    * current selection group) which are not 'on the alignment'.If any are found,
1705    * enable the option to add them to the alignment. The criteria for 'on the
1706    * alignment' is finding an alignment annotation on the alignment, matched on
1707    * calcId, label and sequenceRef.
1708    * 
1709    * A tooltip is also constructed that displays the source (calcId) and type
1710    * (label) of the annotations that can be added.
1711    * 
1712    * @param menuItem
1713    * @param forSequences
1714    */
1715   protected void configureReferenceAnnotationsMenu(JMenuItem menuItem,
1716           List<SequenceI> forSequences)
1717   {
1718     menuItem.setEnabled(false);
1719
1720     /*
1721      * Temporary store to hold distinct calcId / type pairs for the tooltip.
1722      * Using TreeMap means calcIds are shown in alphabetical order.
1723      */
1724     SortedMap<String, String> tipEntries = new TreeMap<>();
1725     final Map<SequenceI, List<AlignmentAnnotation>> candidates = new LinkedHashMap<>();
1726     AlignmentI al = this.ap.av.getAlignment();
1727     AlignmentUtils.findAddableReferenceAnnotations(forSequences, tipEntries,
1728             candidates, al);
1729     if (!candidates.isEmpty())
1730     {
1731       StringBuilder tooltip = new StringBuilder(64);
1732       tooltip.append(MessageManager.getString("label.add_annotations_for"));
1733
1734       /*
1735        * Found annotations that could be added. Enable the menu item, and
1736        * configure its tooltip and action.
1737        */
1738       menuItem.setEnabled(true);
1739       for (String calcId : tipEntries.keySet())
1740       {
1741         tooltip.append("<br/>" + calcId + "/" + tipEntries.get(calcId));
1742       }
1743       String tooltipText = JvSwingUtils.wrapTooltip(true,
1744               tooltip.toString());
1745       menuItem.setToolTipText(tooltipText);
1746
1747       menuItem.addActionListener(new ActionListener()
1748       {
1749         @Override
1750         public void actionPerformed(ActionEvent e)
1751         {
1752           addReferenceAnnotations_actionPerformed(candidates);
1753         }
1754       });
1755     }
1756   }
1757
1758   /**
1759    * Add annotations to the sequences and to the alignment.
1760    * 
1761    * @param candidates
1762    *          a map whose keys are sequences on the alignment, and values a list
1763    *          of annotations to add to each sequence
1764    */
1765   protected void addReferenceAnnotations_actionPerformed(
1766           Map<SequenceI, List<AlignmentAnnotation>> candidates)
1767   {
1768     final SequenceGroup selectionGroup = this.ap.av.getSelectionGroup();
1769     final AlignmentI alignment = this.ap.getAlignment();
1770     AlignmentUtils.addReferenceAnnotations(candidates, alignment,
1771             selectionGroup);
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.getContentPane().add(new JScrollPane(pane));
1870     }
1871     else
1872     /**
1873      * Java only
1874      * 
1875      * @j2sIgnore
1876      */
1877     {
1878       CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
1879       cap.setText(report);
1880       frame = cap;
1881     }
1882
1883     Desktop.addInternalFrame(frame,
1884             MessageManager.formatMessage("label.sequence_details_for",
1885                     (sequences.length == 1 ? new Object[]
1886                     { sequences[0].getDisplayId(true) }
1887                             : new Object[]
1888                             { MessageManager
1889                                     .getString("label.selection") })),
1890             500, 400);
1891   }
1892
1893   protected void showNonconserved_actionPerformed()
1894   {
1895     getGroup().setShowNonconserved(displayNonconserved.isSelected());
1896     refresh();
1897   }
1898
1899   /**
1900    * call to refresh view after settings change
1901    */
1902   void refresh()
1903   {
1904     ap.updateAnnotation();
1905     // removed paintAlignment(true) here:
1906     // updateAnnotation calls paintAlignment already, so don't need to call
1907     // again
1908
1909     PaintRefresher.Refresh(this, ap.av.getSequenceSetId());
1910   }
1911
1912   /*
1913    * protected void covariationColour_actionPerformed() { getGroup().cs = new
1914    * CovariationColourScheme(sequence.getAnnotation()[0]); refresh(); }
1915    */
1916   /**
1917    * DOCUMENT ME!
1918    * 
1919    * @param selected
1920    * 
1921    * @param e
1922    *          DOCUMENT ME!
1923    */
1924   public void abovePIDColour_actionPerformed(boolean selected)
1925   {
1926     SequenceGroup sg = getGroup();
1927     if (sg.cs == null)
1928     {
1929       return;
1930     }
1931
1932     if (selected)
1933     {
1934       sg.cs.setConsensus(AAFrequency.calculate(
1935               sg.getSequences(ap.av.getHiddenRepSequences()),
1936               sg.getStartRes(), sg.getEndRes() + 1));
1937
1938       int threshold = SliderPanel.setPIDSliderSource(ap,
1939               sg.getGroupColourScheme(), getGroup().getName());
1940
1941       sg.cs.setThreshold(threshold, ap.av.isIgnoreGapsConsensus());
1942
1943       SliderPanel.showPIDSlider();
1944     }
1945     else
1946     // remove PIDColouring
1947     {
1948       sg.cs.setThreshold(0, ap.av.isIgnoreGapsConsensus());
1949       SliderPanel.hidePIDSlider();
1950     }
1951     modifyPID.setEnabled(selected);
1952
1953     refresh();
1954   }
1955
1956   /**
1957    * Open a panel where the user can choose which types of sequence annotation
1958    * to show or hide.
1959    * 
1960    * @param e
1961    */
1962   protected void chooseAnnotations_actionPerformed(ActionEvent e)
1963   {
1964     // todo correct way to guard against opening a duplicate panel?
1965     new AnnotationChooser(ap);
1966   }
1967
1968   /**
1969    * DOCUMENT ME!
1970    * 
1971    * @param e
1972    *          DOCUMENT ME!
1973    */
1974   public void conservationMenuItem_actionPerformed(boolean selected)
1975   {
1976     SequenceGroup sg = getGroup();
1977     if (sg.cs == null)
1978     {
1979       return;
1980     }
1981
1982     if (selected)
1983     {
1984       // JBPNote: Conservation name shouldn't be i18n translated
1985       Conservation c = new Conservation("Group",
1986               sg.getSequences(ap.av.getHiddenRepSequences()),
1987               sg.getStartRes(), sg.getEndRes() + 1);
1988
1989       c.calculate();
1990       c.verdict(false, ap.av.getConsPercGaps());
1991       sg.cs.setConservation(c);
1992
1993       SliderPanel.setConservationSlider(ap, sg.getGroupColourScheme(),
1994               sg.getName());
1995       SliderPanel.showConservationSlider();
1996     }
1997     else
1998     // remove ConservationColouring
1999     {
2000       sg.cs.setConservation(null);
2001       SliderPanel.hideConservationSlider();
2002     }
2003     modifyConservation.setEnabled(selected);
2004
2005     refresh();
2006   }
2007
2008   /**
2009    * Shows a dialog where group name and description may be edited
2010    */
2011   protected void groupName_actionPerformed()
2012   {
2013     SequenceGroup sg = getGroup();
2014     EditNameDialog dialog = new EditNameDialog(sg.getName(),
2015             sg.getDescription(),
2016             MessageManager.getString("label.group_name"),
2017             MessageManager.getString("label.group_description"));
2018     dialog.showDialog(ap.alignFrame,
2019             MessageManager.getString("label.edit_group_name_description"),
2020             () -> {
2021               sg.setName(dialog.getName());
2022               sg.setDescription(dialog.getDescription());
2023               refresh();
2024               return null;
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                   JvOptionPane.showMessageDialog(ap,
2063                           MessageManager.getString(
2064                                   "label.spaces_converted_to_underscores"),
2065                           MessageManager.getString(
2066                                   "label.no_spaces_allowed_sequence_name"),
2067                           JvOptionPane.WARNING_MESSAGE);
2068                 }
2069                 sequence.setName(dialog.getName().replace(' ', '_'));
2070                 ap.paintAlignment(false, false);
2071               }
2072               sequence.setDescription(dialog.getDescription());
2073               ap.av.firePropertyChange("alignment", null,
2074                       ap.av.getAlignment().getSequences());
2075               return null;
2076             });
2077   }
2078
2079   /**
2080    * DOCUMENT ME!
2081    * 
2082    * @param e
2083    *          DOCUMENT ME!
2084    */
2085   void unGroupMenuItem_actionPerformed()
2086   {
2087     SequenceGroup sg = ap.av.getSelectionGroup();
2088     ap.av.getAlignment().deleteGroup(sg);
2089     ap.av.setSelectionGroup(null);
2090     refresh();
2091   }
2092
2093   void createGroupMenuItem_actionPerformed()
2094   {
2095     getGroup(); // implicitly creates group - note - should apply defaults / use
2096                 // standard alignment window logic for this
2097     refresh();
2098   }
2099
2100   /**
2101    * Offers a colour chooser and sets the selected colour as the group outline
2102    */
2103   protected void outline_actionPerformed()
2104   {
2105     String title = MessageManager.getString("label.select_outline_colour");
2106     ColourChooserListener listener = new ColourChooserListener()
2107     {
2108       @Override
2109       public void colourSelected(Color c)
2110       {
2111         getGroup().setOutlineColour(c);
2112         refresh();
2113       }
2114     };
2115     JalviewColourChooser.showColourChooser(Desktop.getDesktop(), title,
2116             Color.BLUE, listener);
2117   }
2118
2119   /**
2120    * DOCUMENT ME!
2121    * 
2122    * @param e
2123    *          DOCUMENT ME!
2124    */
2125   public void showBoxes_actionPerformed()
2126   {
2127     getGroup().setDisplayBoxes(showBoxes.isSelected());
2128     refresh();
2129   }
2130
2131   /**
2132    * DOCUMENT ME!
2133    * 
2134    * @param e
2135    *          DOCUMENT ME!
2136    */
2137   public void showText_actionPerformed()
2138   {
2139     getGroup().setDisplayText(showText.isSelected());
2140     refresh();
2141   }
2142
2143   /**
2144    * DOCUMENT ME!
2145    * 
2146    * @param e
2147    *          DOCUMENT ME!
2148    */
2149   public void showColourText_actionPerformed()
2150   {
2151     getGroup().setColourText(showColourText.isSelected());
2152     refresh();
2153   }
2154
2155   void hideSequences(boolean representGroup)
2156   {
2157     ap.av.hideSequences(sequence, representGroup);
2158   }
2159
2160   public void copy_actionPerformed()
2161   {
2162     ap.alignFrame.copy_actionPerformed();
2163   }
2164
2165   public void cut_actionPerformed()
2166   {
2167     ap.alignFrame.cut_actionPerformed();
2168   }
2169
2170   void changeCase(ActionEvent e)
2171   {
2172     Object source = e.getSource();
2173     SequenceGroup sg = ap.av.getSelectionGroup();
2174
2175     if (sg != null)
2176     {
2177       List<int[]> startEnd = ap.av.getVisibleRegionBoundaries(
2178               sg.getStartRes(), sg.getEndRes() + 1);
2179
2180       String description;
2181       int caseChange;
2182
2183       if (source == toggle)
2184       {
2185         description = MessageManager.getString("label.toggle_case");
2186         caseChange = ChangeCaseCommand.TOGGLE_CASE;
2187       }
2188       else if (source == upperCase)
2189       {
2190         description = MessageManager.getString("label.to_upper_case");
2191         caseChange = ChangeCaseCommand.TO_UPPER;
2192       }
2193       else
2194       {
2195         description = MessageManager.getString("label.to_lower_case");
2196         caseChange = ChangeCaseCommand.TO_LOWER;
2197       }
2198
2199       ChangeCaseCommand caseCommand = new ChangeCaseCommand(description,
2200               sg.getSequencesAsArray(ap.av.getHiddenRepSequences()),
2201               startEnd, caseChange);
2202
2203       ap.alignFrame.addHistoryItem(caseCommand);
2204
2205       ap.av.firePropertyChange("alignment", null,
2206               ap.av.getAlignment().getSequences());
2207
2208     }
2209   }
2210
2211   public void outputText_actionPerformed(ActionEvent e)
2212   {
2213     CutAndPasteTransfer cap = new CutAndPasteTransfer();
2214     cap.setForInput(null);
2215     Desktop.addInternalFrame(cap, MessageManager
2216             .formatMessage("label.alignment_output_command", new Object[]
2217             { e.getActionCommand() }), 600, 500);
2218
2219     String[] omitHidden = null;
2220
2221     System.out.println("PROMPT USER HERE"); // TODO: decide if a prompt happens
2222     // or we simply trust the user wants
2223     // wysiwig behaviour
2224
2225     FileFormatI fileFormat = FileFormats.getInstance()
2226             .forName(e.getActionCommand());
2227     cap.setText(
2228             new FormatAdapter(ap).formatSequences(fileFormat, ap, true));
2229   }
2230
2231   public void sequenceFeature_actionPerformed()
2232   {
2233     SequenceGroup sg = ap.av.getSelectionGroup();
2234     if (sg == null)
2235     {
2236       return;
2237     }
2238
2239     List<SequenceI> seqs = new ArrayList<>();
2240     List<SequenceFeature> features = new ArrayList<>();
2241
2242     /*
2243      * assemble dataset sequences, and template new sequence features,
2244      * for the amend features dialog
2245      */
2246     int gSize = sg.getSize();
2247     for (int i = 0; i < gSize; i++)
2248     {
2249       int start = sg.getSequenceAt(i).findPosition(sg.getStartRes());
2250       int end = sg.findEndRes(sg.getSequenceAt(i));
2251       if (start <= end)
2252       {
2253         seqs.add(sg.getSequenceAt(i).getDatasetSequence());
2254         features.add(new SequenceFeature(null, null, start, end, null));
2255       }
2256     }
2257
2258     /*
2259      * an entirely gapped region will generate empty lists of sequence / features
2260      */
2261     if (!seqs.isEmpty())
2262     {
2263       new FeatureEditor(ap, seqs, features, true).showDialog();
2264     }
2265   }
2266
2267   public void textColour_actionPerformed()
2268   {
2269     SequenceGroup sg = getGroup();
2270     if (sg != null)
2271     {
2272       new TextColourChooser().chooseColour(ap, sg);
2273     }
2274   }
2275
2276   /**
2277    * Shows a dialog where sequence characters may be edited. Any changes are
2278    * applied, and added as an available 'Undo' item in the edit commands
2279    * history.
2280    */
2281   public void editSequence_actionPerformed()
2282   {
2283     SequenceGroup sg = ap.av.getSelectionGroup();
2284
2285     SequenceI seq = sequence;
2286     if (sg != null)
2287     {
2288       if (seq == null)
2289       {
2290         seq = sg.getSequenceAt(0);
2291       }
2292
2293       EditNameDialog dialog = new EditNameDialog(
2294               seq.getSequenceAsString(sg.getStartRes(), sg.getEndRes() + 1),
2295               null, MessageManager.getString("label.edit_sequence"), null);
2296       dialog.showDialog(ap.alignFrame,
2297               MessageManager.getString("label.edit_sequence"), () -> {
2298                 EditCommand editCommand = new EditCommand(
2299                         MessageManager.getString("label.edit_sequences"),
2300                         Action.REPLACE,
2301                         dialog.getName().replace(' ',
2302                                 ap.av.getGapCharacter()),
2303                         sg.getSequencesAsArray(
2304                                 ap.av.getHiddenRepSequences()),
2305                         sg.getStartRes(), sg.getEndRes() + 1,
2306                         ap.av.getAlignment());
2307                 ap.alignFrame.addHistoryItem(editCommand);
2308                 ap.av.firePropertyChange("alignment", null,
2309                         ap.av.getAlignment().getSequences());
2310                 return null;
2311               });
2312     }
2313   }
2314
2315   /**
2316    * Action on user selecting an item from the colour menu (that does not have
2317    * its bespoke action handler)
2318    * 
2319    * @return
2320    */
2321   @Override
2322   public void changeColour_actionPerformed(String colourSchemeName)
2323   {
2324     SequenceGroup sg = getGroup();
2325     /*
2326      * switch to the chosen colour scheme (or null for None)
2327      */
2328     ColourSchemeI colourScheme = ColourSchemes.getInstance()
2329             .getColourScheme(colourSchemeName, ap.av, sg,
2330                     ap.av.getHiddenRepSequences());
2331     sg.setColourScheme(colourScheme);
2332     if (colourScheme instanceof Blosum62ColourScheme
2333             || colourScheme instanceof PIDColourScheme)
2334     {
2335       sg.cs.setConsensus(AAFrequency.calculate(
2336               sg.getSequences(ap.av.getHiddenRepSequences()),
2337               sg.getStartRes(), sg.getEndRes() + 1));
2338     }
2339
2340     refresh();
2341   }
2342
2343 }