JAL-3048 EditNameDialog modified for JalviewJS (layout, i18n)
[jalview.git] / src / jalview / gui / PopupMenu.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import jalview.analysis.AAFrequency;
24 import jalview.analysis.AlignmentAnnotationUtils;
25 import jalview.analysis.AlignmentUtils;
26 import jalview.analysis.Conservation;
27 import jalview.bin.Cache;
28 import jalview.commands.ChangeCaseCommand;
29 import jalview.commands.EditCommand;
30 import jalview.commands.EditCommand.Action;
31 import jalview.datamodel.AlignmentAnnotation;
32 import jalview.datamodel.AlignmentI;
33 import jalview.datamodel.Annotation;
34 import jalview.datamodel.DBRefEntry;
35 import jalview.datamodel.HiddenColumns;
36 import jalview.datamodel.PDBEntry;
37 import jalview.datamodel.SequenceFeature;
38 import jalview.datamodel.SequenceGroup;
39 import jalview.datamodel.SequenceI;
40 import jalview.gui.ColourMenuHelper.ColourChangeListener;
41 import jalview.gui.JalviewColourChooser.ColourChooserListener;
42 import jalview.io.FileFormatI;
43 import jalview.io.FileFormats;
44 import jalview.io.FormatAdapter;
45 import jalview.io.SequenceAnnotationReport;
46 import jalview.schemes.Blosum62ColourScheme;
47 import jalview.schemes.ColourSchemeI;
48 import jalview.schemes.ColourSchemes;
49 import jalview.schemes.PIDColourScheme;
50 import jalview.util.GroupUrlLink;
51 import jalview.util.GroupUrlLink.UrlStringTooLongException;
52 import jalview.util.MessageManager;
53 import jalview.util.StringUtils;
54 import jalview.util.UrlLink;
55 import jalview.util.dialogrunner.RunResponse;
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.JMenu;
75 import javax.swing.JMenuItem;
76 import javax.swing.JPopupMenu;
77
78 /**
79  * DOCUMENT ME!
80  * 
81  * @author $author$
82  * @version $Revision: 1.118 $
83  */
84 public class PopupMenu extends JPopupMenu implements ColourChangeListener
85 {
86   JMenu groupMenu = new JMenu();
87
88   JMenuItem groupName = new JMenuItem();
89
90   protected JCheckBoxMenuItem abovePIDColour = new JCheckBoxMenuItem();
91
92   protected JMenuItem modifyPID = new JMenuItem();
93
94   protected JCheckBoxMenuItem conservationMenuItem = new JCheckBoxMenuItem();
95
96   protected JMenuItem modifyConservation = new JMenuItem();
97
98   AlignmentPanel ap;
99
100   JMenu sequenceMenu = new JMenu();
101
102   JMenuItem sequenceName = new JMenuItem();
103
104   JMenuItem sequenceDetails = new JMenuItem();
105
106   JMenuItem sequenceSelDetails = new JMenuItem();
107
108   JMenuItem makeReferenceSeq = new JMenuItem();
109
110   JMenuItem chooseAnnotations = new JMenuItem();
111
112   SequenceI sequence;
113
114   JMenuItem createGroupMenuItem = new JMenuItem();
115
116   JMenuItem unGroupMenuItem = new JMenuItem();
117
118   JMenuItem outline = new JMenuItem();
119
120   JMenu colourMenu = new JMenu();
121
122   JCheckBoxMenuItem showBoxes = new JCheckBoxMenuItem();
123
124   JCheckBoxMenuItem showText = new JCheckBoxMenuItem();
125
126   JCheckBoxMenuItem showColourText = new JCheckBoxMenuItem();
127
128   JCheckBoxMenuItem displayNonconserved = new JCheckBoxMenuItem();
129
130   JMenu editMenu = new JMenu();
131
132   JMenuItem cut = new JMenuItem();
133
134   JMenuItem copy = new JMenuItem();
135
136   JMenuItem upperCase = new JMenuItem();
137
138   JMenuItem lowerCase = new JMenuItem();
139
140   JMenuItem toggle = new JMenuItem();
141
142   JMenu pdbMenu = new JMenu();
143
144   JMenu outputMenu = new JMenu();
145
146   JMenu seqShowAnnotationsMenu = new JMenu();
147
148   JMenu seqHideAnnotationsMenu = new JMenu();
149
150   JMenuItem seqAddReferenceAnnotations = new JMenuItem(
151           MessageManager.getString("label.add_reference_annotations"));
152
153   JMenu groupShowAnnotationsMenu = new JMenu();
154
155   JMenu groupHideAnnotationsMenu = new JMenu();
156
157   JMenuItem groupAddReferenceAnnotations = new JMenuItem(
158           MessageManager.getString("label.add_reference_annotations"));
159
160   JMenuItem sequenceFeature = new JMenuItem();
161
162   JMenuItem textColour = new JMenuItem();
163
164   JMenu jMenu1 = new JMenu();
165
166   JMenuItem pdbStructureDialog = new JMenuItem();
167
168   JMenu rnaStructureMenu = new JMenu();
169
170   JMenuItem editSequence = new JMenuItem();
171
172   JMenu groupLinksMenu;
173
174   JMenuItem hideInsertions = new JMenuItem();
175
176   /**
177    * Creates a new PopupMenu object.
178    * 
179    * @param ap
180    * @param seq
181    * @param features
182    *          non-positional features (for seq not null), or positional features
183    *          at residue (for seq equal to null)
184    */
185   public PopupMenu(final AlignmentPanel ap, SequenceI seq,
186           List<SequenceFeature> features)
187   {
188     this(ap, seq, features, null);
189   }
190
191   /**
192    * Constructor
193    * 
194    * @param alignPanel
195    * @param seq
196    *          the sequence under the cursor if in the Id panel, null if in the
197    *          sequence panel
198    * @param features
199    *          non-positional features if in the Id panel, features at the
200    *          clicked residue if in the sequence panel
201    * @param groupLinks
202    */
203   public PopupMenu(final AlignmentPanel alignPanel, final SequenceI seq,
204           List<SequenceFeature> features, List<String> groupLinks)
205   {
206     // /////////////////////////////////////////////////////////
207     // If this is activated from the sequence panel, the user may want to
208     // edit or annotate a particular residue. Therefore display the residue menu
209     //
210     // If from the IDPanel, we must display the sequence menu
211     // ////////////////////////////////////////////////////////
212     this.ap = alignPanel;
213     sequence = seq;
214
215     for (String ff : FileFormats.getInstance().getWritableFormats(true))
216     {
217       JMenuItem item = new JMenuItem(ff);
218
219       item.addActionListener(new ActionListener()
220       {
221         @Override
222         public void actionPerformed(ActionEvent e)
223         {
224           outputText_actionPerformed(e);
225         }
226       });
227
228       outputMenu.add(item);
229     }
230
231     /*
232      * Build menus for annotation types that may be shown or hidden, and for
233      * 'reference annotations' that may be added to the alignment. First for the
234      * currently selected sequence (if there is one):
235      */
236     final List<SequenceI> selectedSequence = (seq == null
237             ? Collections.<SequenceI> emptyList()
238             : Arrays.asList(seq));
239     buildAnnotationTypesMenus(seqShowAnnotationsMenu,
240             seqHideAnnotationsMenu, selectedSequence);
241     configureReferenceAnnotationsMenu(seqAddReferenceAnnotations,
242             selectedSequence);
243
244     /*
245      * And repeat for the current selection group (if there is one):
246      */
247     final List<SequenceI> selectedGroup = (alignPanel.av.getSelectionGroup() == null
248             ? Collections.<SequenceI> emptyList()
249             : alignPanel.av.getSelectionGroup().getSequences());
250     buildAnnotationTypesMenus(groupShowAnnotationsMenu,
251             groupHideAnnotationsMenu, selectedGroup);
252     configureReferenceAnnotationsMenu(groupAddReferenceAnnotations,
253             selectedGroup);
254
255     try
256     {
257       jbInit();
258     } catch (Exception e)
259     {
260       e.printStackTrace();
261     }
262
263     JMenuItem menuItem;
264     if (seq != null)
265     {
266       sequenceMenu.setText(sequence.getName());
267       if (seq == alignPanel.av.getAlignment().getSeqrep())
268       {
269         makeReferenceSeq.setText(
270                 MessageManager.getString("action.unmark_as_reference"));
271       }
272       else
273       {
274         makeReferenceSeq.setText(
275                 MessageManager.getString("action.set_as_reference"));
276       }
277
278       if (!alignPanel.av.getAlignment().isNucleotide())
279       {
280         remove(rnaStructureMenu);
281       }
282       else
283       {
284         int origCount = rnaStructureMenu.getItemCount();
285         /*
286          * add menu items to 2D-render any alignment or sequence secondary
287          * structure annotation
288          */
289         AlignmentAnnotation[] aas = alignPanel.av.getAlignment()
290                 .getAlignmentAnnotation();
291         if (aas != null)
292         {
293           for (final AlignmentAnnotation aa : aas)
294           {
295             if (aa.isValidStruc() && aa.sequenceRef == null)
296             {
297               /*
298                * valid alignment RNA secondary structure annotation
299                */
300               menuItem = new JMenuItem();
301               menuItem.setText(MessageManager.formatMessage(
302                       "label.2d_rna_structure_line", new Object[]
303                       { aa.label }));
304               menuItem.addActionListener(new ActionListener()
305               {
306                 @Override
307                 public void actionPerformed(ActionEvent e)
308                 {
309                   new AppVarna(seq, aa, alignPanel);
310                 }
311               });
312               rnaStructureMenu.add(menuItem);
313             }
314           }
315         }
316
317         if (seq.getAnnotation() != null)
318         {
319           AlignmentAnnotation seqAnns[] = seq.getAnnotation();
320           for (final AlignmentAnnotation aa : seqAnns)
321           {
322             if (aa.isValidStruc())
323             {
324               /*
325                * valid sequence RNA secondary structure annotation
326                */
327               // TODO: make rnastrucF a bit more nice
328               menuItem = new JMenuItem();
329               menuItem.setText(MessageManager.formatMessage(
330                       "label.2d_rna_sequence_name", new Object[]
331                       { seq.getName() }));
332               menuItem.addActionListener(new ActionListener()
333               {
334                 @Override
335                 public void actionPerformed(ActionEvent e)
336                 {
337                   // TODO: VARNA does'nt print gaps in the sequence
338                   new AppVarna(seq, aa, alignPanel);
339                 }
340               });
341               rnaStructureMenu.add(menuItem);
342             }
343           }
344         }
345         if (rnaStructureMenu.getItemCount() == origCount)
346         {
347           remove(rnaStructureMenu);
348         }
349       }
350
351       menuItem = new JMenuItem(
352               MessageManager.getString("action.hide_sequences"));
353       menuItem.addActionListener(new ActionListener()
354       {
355         @Override
356         public void actionPerformed(ActionEvent e)
357         {
358           hideSequences(false);
359         }
360       });
361       add(menuItem);
362
363       if (alignPanel.av.getSelectionGroup() != null
364               && alignPanel.av.getSelectionGroup().getSize() > 1)
365       {
366         menuItem = new JMenuItem(MessageManager
367                 .formatMessage("label.represent_group_with", new Object[]
368                 { seq.getName() }));
369         menuItem.addActionListener(new ActionListener()
370         {
371           @Override
372           public void actionPerformed(ActionEvent e)
373           {
374             hideSequences(true);
375           }
376         });
377         sequenceMenu.add(menuItem);
378       }
379
380       if (alignPanel.av.hasHiddenRows())
381       {
382         final int index = alignPanel.av.getAlignment().findIndex(seq);
383
384         if (alignPanel.av.adjustForHiddenSeqs(index)
385                 - alignPanel.av.adjustForHiddenSeqs(index - 1) > 1)
386         {
387           menuItem = new JMenuItem(
388                   MessageManager.getString("action.reveal_sequences"));
389           menuItem.addActionListener(new ActionListener()
390           {
391             @Override
392             public void actionPerformed(ActionEvent e)
393             {
394               alignPanel.av.showSequence(index);
395               if (alignPanel.overviewPanel != null)
396               {
397                 alignPanel.overviewPanel.updateOverviewImage();
398               }
399             }
400           });
401           add(menuItem);
402         }
403       }
404     }
405     // for the case when no sequences are even visible
406     if (alignPanel.av.hasHiddenRows())
407     {
408       {
409         menuItem = new JMenuItem(
410                 MessageManager.getString("action.reveal_all"));
411         menuItem.addActionListener(new ActionListener()
412         {
413           @Override
414           public void actionPerformed(ActionEvent e)
415           {
416             alignPanel.av.showAllHiddenSeqs();
417             if (alignPanel.overviewPanel != null)
418             {
419               alignPanel.overviewPanel.updateOverviewImage();
420             }
421           }
422         });
423
424         add(menuItem);
425       }
426     }
427
428     SequenceGroup sg = alignPanel.av.getSelectionGroup();
429     boolean isDefinedGroup = (sg != null)
430             ? alignPanel.av.getAlignment().getGroups().contains(sg)
431             : false;
432
433     if (sg != null && sg.getSize() > 0)
434     {
435       groupName.setText(MessageManager
436               .getString("label.edit_name_and_description_current_group"));
437
438       ColourMenuHelper.setColourSelected(colourMenu, sg.getColourScheme());
439
440       conservationMenuItem.setEnabled(!sg.isNucleotide());
441
442       if (sg.cs != null)
443       {
444         if (sg.cs.conservationApplied())
445         {
446           conservationMenuItem.setSelected(true);
447         }
448         if (sg.cs.getThreshold() > 0)
449         {
450           abovePIDColour.setSelected(true);
451         }
452       }
453       modifyConservation.setEnabled(conservationMenuItem.isSelected());
454       modifyPID.setEnabled(abovePIDColour.isSelected());
455       displayNonconserved.setSelected(sg.getShowNonconserved());
456       showText.setSelected(sg.getDisplayText());
457       showColourText.setSelected(sg.getColourText());
458       showBoxes.setSelected(sg.getDisplayBoxes());
459       // add any groupURLs to the groupURL submenu and make it visible
460       if (groupLinks != null && groupLinks.size() > 0)
461       {
462         buildGroupURLMenu(sg, groupLinks);
463       }
464       // Add a 'show all structures' for the current selection
465       Hashtable<String, PDBEntry> pdbe = new Hashtable<>(), reppdb = new Hashtable<>();
466
467       SequenceI sqass = null;
468       for (SequenceI sq : alignPanel.av.getSequenceSelection())
469       {
470         Vector<PDBEntry> pes = sq.getDatasetSequence().getAllPDBEntries();
471         if (pes != null && pes.size() > 0)
472         {
473           reppdb.put(pes.get(0).getId(), pes.get(0));
474           for (PDBEntry pe : pes)
475           {
476             pdbe.put(pe.getId(), pe);
477             if (sqass == null)
478             {
479               sqass = sq;
480             }
481           }
482         }
483       }
484       if (pdbe.size() > 0)
485       {
486         final PDBEntry[] pe = pdbe.values()
487                 .toArray(new PDBEntry[pdbe.size()]),
488                 pr = reppdb.values().toArray(new PDBEntry[reppdb.size()]);
489         final JMenuItem gpdbview, rpdbview;
490       }
491     }
492     else
493     {
494       groupMenu.setVisible(false);
495       editMenu.setVisible(false);
496     }
497
498     if (!isDefinedGroup)
499     {
500       createGroupMenuItem.setVisible(true);
501       unGroupMenuItem.setVisible(false);
502       jMenu1.setText(MessageManager.getString("action.edit_new_group"));
503     }
504     else
505     {
506       createGroupMenuItem.setVisible(false);
507       unGroupMenuItem.setVisible(true);
508       jMenu1.setText(MessageManager.getString("action.edit_group"));
509     }
510
511     if (seq == null)
512     {
513       sequenceMenu.setVisible(false);
514       pdbStructureDialog.setVisible(false);
515       rnaStructureMenu.setVisible(false);
516     }
517
518     addLinks(seq, features);
519
520     if (seq == null)
521     {
522       addFeatureDetails(features);
523     }
524   }
525
526   /**
527    * Add a link to show feature details for each sequence feature
528    * 
529    * @param features
530    */
531   protected void addFeatureDetails(List<SequenceFeature> features)
532   {
533     if (features == null || features.isEmpty())
534     {
535       return;
536     }
537     JMenu details = new JMenu(
538             MessageManager.getString("label.feature_details"));
539     add(details);
540
541     for (final SequenceFeature sf : features)
542     {
543       int start = sf.getBegin();
544       int end = sf.getEnd();
545       String desc = null;
546       if (start == end)
547       {
548         desc = String.format("%s %d", sf.getType(), start);
549       }
550       else
551       {
552         desc = String.format("%s %d-%d", sf.getType(), start, end);
553       }
554       String tooltip = desc;
555       String description = sf.getDescription();
556       if (description != null)
557       {
558         description = StringUtils.stripHtmlTags(description);
559         if (description.length() > 12)
560         {
561           desc = desc + " " + description.substring(0, 12) + "..";
562         }
563         else
564         {
565           desc = desc + " " + description;
566         }
567         tooltip = tooltip + " " + description;
568       }
569       if (sf.getFeatureGroup() != null)
570       {
571         tooltip = tooltip + (" (" + sf.getFeatureGroup() + ")");
572       }
573       JMenuItem item = new JMenuItem(desc);
574       item.setToolTipText(tooltip);
575       item.addActionListener(new ActionListener()
576       {
577         @Override
578         public void actionPerformed(ActionEvent e)
579         {
580           showFeatureDetails(sf);
581         }
582       });
583       details.add(item);
584     }
585   }
586
587   /**
588    * Opens a panel showing a text report of feature dteails
589    * 
590    * @param sf
591    */
592   protected void showFeatureDetails(SequenceFeature sf)
593   {
594     CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
595     // it appears Java's CSS does not support border-collaps :-(
596     cap.addStylesheetRule("table { border-collapse: collapse;}");
597     cap.addStylesheetRule("table, td, th {border: 1px solid black;}");
598     cap.setText(sf.getDetailsReport());
599
600     Desktop.addInternalFrame(cap,
601             MessageManager.getString("label.feature_details"), 500, 500);
602   }
603
604   /**
605    * Adds a 'Link' menu item with a sub-menu item for each hyperlink provided.
606    * When seq is not null, these are links for the sequence id, which may be to
607    * external web sites for the sequence accession, and/or links embedded in
608    * non-positional features. When seq is null, only links embedded in the
609    * provided features are added.
610    * 
611    * @param seq
612    * @param features
613    */
614   void addLinks(final SequenceI seq, List<SequenceFeature> features)
615   {
616     JMenu linkMenu = new JMenu(MessageManager.getString("action.link"));
617
618     List<String> nlinks = null;
619     if (seq != null)
620     {
621       nlinks = Preferences.sequenceUrlLinks.getLinksForMenu();
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();
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    * Shows a dialog where group name and description may be edited
1754    */
1755   protected void groupName_actionPerformed()
1756   {
1757     SequenceGroup sg = getGroup();
1758     EditNameDialog dialog = new EditNameDialog(sg.getName(),
1759             sg.getDescription(),
1760             MessageManager.getString("label.group_name"),
1761             MessageManager.getString("label.group_description"));
1762     dialog.showDialog(ap.alignFrame,
1763             MessageManager.getString("label.edit_group_name_description"),
1764             new RunResponse(JvOptionPane.OK_OPTION)
1765             {
1766               @Override
1767               public void run()
1768               {
1769                 sg.setName(dialog.getName());
1770                 sg.setDescription(dialog.getDescription());
1771                 refresh();
1772               }
1773             });
1774   }
1775
1776   /**
1777    * Get selection group - adding it to the alignment if necessary.
1778    * 
1779    * @return sequence group to operate on
1780    */
1781   SequenceGroup getGroup()
1782   {
1783     SequenceGroup sg = ap.av.getSelectionGroup();
1784     // this method won't add a new group if it already exists
1785     if (sg != null)
1786     {
1787       ap.av.getAlignment().addGroup(sg);
1788     }
1789
1790     return sg;
1791   }
1792
1793   /**
1794    * Shows a dialog where sequence name and description may be edited
1795    */
1796   void sequenceName_actionPerformed()
1797   {
1798     EditNameDialog dialog = new EditNameDialog(sequence.getName(),
1799             sequence.getDescription(),
1800             MessageManager.getString("label.sequence_name"),
1801             MessageManager.getString("label.sequence_description"));
1802     dialog.showDialog(ap.alignFrame,
1803             MessageManager.getString(
1804                     "label.edit_sequence_name_description"),
1805             new RunResponse(JvOptionPane.OK_OPTION)
1806             {
1807               @Override
1808               public void run()
1809               {
1810                 if (dialog.getName() != null)
1811                 {
1812                   if (dialog.getName().indexOf(" ") > -1)
1813                   {
1814                     JvOptionPane.showMessageDialog(ap,
1815                             MessageManager.getString(
1816                                     "label.spaces_converted_to_underscores"),
1817                             MessageManager.getString(
1818                                     "label.no_spaces_allowed_sequence_name"),
1819                             JvOptionPane.WARNING_MESSAGE);
1820                   }
1821                   sequence.setName(dialog.getName().replace(' ', '_'));
1822                   ap.paintAlignment(false, false);
1823                 }
1824                 sequence.setDescription(dialog.getDescription());
1825                 ap.av.firePropertyChange("alignment", null,
1826                         ap.av.getAlignment().getSequences());
1827               }
1828             });
1829   }
1830
1831   /**
1832    * DOCUMENT ME!
1833    * 
1834    * @param e
1835    *          DOCUMENT ME!
1836    */
1837   void unGroupMenuItem_actionPerformed()
1838   {
1839     SequenceGroup sg = ap.av.getSelectionGroup();
1840     ap.av.getAlignment().deleteGroup(sg);
1841     ap.av.setSelectionGroup(null);
1842     refresh();
1843   }
1844
1845   void createGroupMenuItem_actionPerformed()
1846   {
1847     getGroup(); // implicitly creates group - note - should apply defaults / use
1848                 // standard alignment window logic for this
1849     refresh();
1850   }
1851
1852   /**
1853    * Offers a colour chooser and sets the selected colour as the group outline
1854    */
1855   protected void outline_actionPerformed()
1856   {
1857     String title = MessageManager
1858             .getString("label.select_outline_colour");
1859     ColourChooserListener listener = new ColourChooserListener()
1860     {
1861       @Override
1862       public void colourSelected(Color c)
1863       {
1864         getGroup().setOutlineColour(c);
1865         refresh();
1866       };
1867     };
1868     JalviewColourChooser.showColourChooser(Desktop.getDesktop(),
1869             title, Color.BLUE, listener);
1870   }
1871
1872   /**
1873    * DOCUMENT ME!
1874    * 
1875    * @param e
1876    *          DOCUMENT ME!
1877    */
1878   public void showBoxes_actionPerformed()
1879   {
1880     getGroup().setDisplayBoxes(showBoxes.isSelected());
1881     refresh();
1882   }
1883
1884   /**
1885    * DOCUMENT ME!
1886    * 
1887    * @param e
1888    *          DOCUMENT ME!
1889    */
1890   public void showText_actionPerformed()
1891   {
1892     getGroup().setDisplayText(showText.isSelected());
1893     refresh();
1894   }
1895
1896   /**
1897    * DOCUMENT ME!
1898    * 
1899    * @param e
1900    *          DOCUMENT ME!
1901    */
1902   public void showColourText_actionPerformed()
1903   {
1904     getGroup().setColourText(showColourText.isSelected());
1905     refresh();
1906   }
1907
1908   public void showLink(String url)
1909   {
1910     try
1911     {
1912       jalview.util.BrowserLauncher.openURL(url);
1913     } catch (Exception ex)
1914     {
1915       JvOptionPane.showInternalMessageDialog(Desktop.desktop,
1916               MessageManager.getString("label.web_browser_not_found_unix"),
1917               MessageManager.getString("label.web_browser_not_found"),
1918               JvOptionPane.WARNING_MESSAGE);
1919
1920       ex.printStackTrace();
1921     }
1922   }
1923
1924   void hideSequences(boolean representGroup)
1925   {
1926     ap.av.hideSequences(sequence, representGroup);
1927   }
1928
1929   public void copy_actionPerformed()
1930   {
1931     ap.alignFrame.copy_actionPerformed(null);
1932   }
1933
1934   public void cut_actionPerformed()
1935   {
1936     ap.alignFrame.cut_actionPerformed(null);
1937   }
1938
1939   void changeCase(ActionEvent e)
1940   {
1941     Object source = e.getSource();
1942     SequenceGroup sg = ap.av.getSelectionGroup();
1943
1944     if (sg != null)
1945     {
1946       List<int[]> startEnd = ap.av.getVisibleRegionBoundaries(
1947               sg.getStartRes(), sg.getEndRes() + 1);
1948
1949       String description;
1950       int caseChange;
1951
1952       if (source == toggle)
1953       {
1954         description = MessageManager.getString("label.toggle_case");
1955         caseChange = ChangeCaseCommand.TOGGLE_CASE;
1956       }
1957       else if (source == upperCase)
1958       {
1959         description = MessageManager.getString("label.to_upper_case");
1960         caseChange = ChangeCaseCommand.TO_UPPER;
1961       }
1962       else
1963       {
1964         description = MessageManager.getString("label.to_lower_case");
1965         caseChange = ChangeCaseCommand.TO_LOWER;
1966       }
1967
1968       ChangeCaseCommand caseCommand = new ChangeCaseCommand(description,
1969               sg.getSequencesAsArray(ap.av.getHiddenRepSequences()),
1970               startEnd, caseChange);
1971
1972       ap.alignFrame.addHistoryItem(caseCommand);
1973
1974       ap.av.firePropertyChange("alignment", null,
1975               ap.av.getAlignment().getSequences());
1976
1977     }
1978   }
1979
1980   public void outputText_actionPerformed(ActionEvent e)
1981   {
1982     CutAndPasteTransfer cap = new CutAndPasteTransfer();
1983     cap.setForInput(null);
1984     Desktop.addInternalFrame(cap, MessageManager
1985             .formatMessage("label.alignment_output_command", new Object[]
1986             { e.getActionCommand() }), 600, 500);
1987
1988     String[] omitHidden = null;
1989
1990     System.out.println("PROMPT USER HERE"); // TODO: decide if a prompt happens
1991     // or we simply trust the user wants
1992     // wysiwig behaviour
1993
1994     FileFormatI fileFormat = FileFormats.getInstance()
1995             .forName(e.getActionCommand());
1996     cap.setText(
1997             new FormatAdapter(ap).formatSequences(fileFormat, ap, true));
1998   }
1999
2000   public void sequenceFeature_actionPerformed()
2001   {
2002     SequenceGroup sg = ap.av.getSelectionGroup();
2003     if (sg == null)
2004     {
2005       return;
2006     }
2007
2008     List<SequenceI> seqs = new ArrayList<>();
2009     List<SequenceFeature> features = new ArrayList<>();
2010
2011     /*
2012      * assemble dataset sequences, and template new sequence features,
2013      * for the amend features dialog
2014      */
2015     int gSize = sg.getSize();
2016     for (int i = 0; i < gSize; i++)
2017     {
2018       int start = sg.getSequenceAt(i).findPosition(sg.getStartRes());
2019       int end = sg.findEndRes(sg.getSequenceAt(i));
2020       if (start <= end)
2021       {
2022         seqs.add(sg.getSequenceAt(i).getDatasetSequence());
2023         features.add(new SequenceFeature(null, null, start, end, null));
2024       }
2025     }
2026
2027     /*
2028      * an entirely gapped region will generate empty lists of sequence / features
2029      */
2030     if (!seqs.isEmpty())
2031     {
2032       new FeatureEditor(ap).createFeatures(seqs, features, new Runnable()
2033       {
2034         public void run()
2035         {
2036           ap.alignFrame.setShowSeqFeatures(true);
2037           ap.av.setSearchResults(null); // clear highlighting
2038           ap.repaint(); // draw new/amended features
2039         }
2040       });
2041     }
2042   }
2043
2044   public void textColour_actionPerformed()
2045   {
2046     SequenceGroup sg = getGroup();
2047     if (sg != null)
2048     {
2049       new TextColourChooser().chooseColour(ap, sg);
2050     }
2051   }
2052
2053   public void colourByStructure(String pdbid)
2054   {
2055     Annotation[] anots = ap.av.getStructureSelectionManager()
2056             .colourSequenceFromStructure(sequence, pdbid);
2057
2058     AlignmentAnnotation an = new AlignmentAnnotation("Structure",
2059             "Coloured by " + pdbid, anots);
2060
2061     ap.av.getAlignment().addAnnotation(an);
2062     an.createSequenceMapping(sequence, 0, true);
2063     // an.adjustForAlignment();
2064     ap.av.getAlignment().setAnnotationIndex(an, 0);
2065
2066     ap.adjustAnnotationHeight();
2067
2068     sequence.addAlignmentAnnotation(an);
2069
2070   }
2071
2072   /**
2073    * Shows a dialog where sequence characters may be edited. Any changes are
2074    * applied, and added as an available 'Undo' item in the edit commands
2075    * history.
2076    */
2077   public void editSequence_actionPerformed()
2078   {
2079     SequenceGroup sg = ap.av.getSelectionGroup();
2080
2081     if (sg != null)
2082     {
2083       if (sequence == null)
2084       {
2085         sequence = sg.getSequenceAt(0);
2086       }
2087
2088       EditNameDialog dialog = new EditNameDialog(
2089               sequence.getSequenceAsString(sg.getStartRes(),
2090                       sg.getEndRes() + 1),
2091               null, MessageManager.getString("label.edit_sequence"), null);
2092       dialog.showDialog(ap.alignFrame,
2093               MessageManager.getString("label.edit_sequence"),
2094               new RunResponse(JvOptionPane.OK_OPTION)
2095               {
2096                 @Override
2097                 public void run()
2098                 {
2099                   EditCommand editCommand = new EditCommand(
2100                           MessageManager.getString("label.edit_sequences"),
2101                           Action.REPLACE,
2102                           dialog.getName().replace(' ',
2103                                   ap.av.getGapCharacter()),
2104                           sg.getSequencesAsArray(
2105                                   ap.av.getHiddenRepSequences()),
2106                           sg.getStartRes(), sg.getEndRes() + 1,
2107                           ap.av.getAlignment());
2108                   ap.alignFrame.addHistoryItem(editCommand);
2109                   ap.av.firePropertyChange("alignment", null,
2110                           ap.av.getAlignment().getSequences());
2111                 }
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 }