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