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