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