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