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