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