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