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