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