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