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