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