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