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