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