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