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