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