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