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