Merge branch 'Jalview-JS/develop_j2s_v3_2_9_j11' into Jalview-JS/develop
[jalview.git] / src / jalview / gui / AlignFrame.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.AlignmentSorter;
24 import jalview.analysis.AlignmentUtils;
25 import jalview.analysis.CrossRef;
26 import jalview.analysis.Dna;
27 import jalview.analysis.GeneticCodeI;
28 import jalview.analysis.ParseProperties;
29 import jalview.analysis.SequenceIdMatcher;
30 import jalview.api.AlignExportSettingsI;
31 import jalview.api.AlignViewControllerGuiI;
32 import jalview.api.AlignViewControllerI;
33 import jalview.api.AlignViewportI;
34 import jalview.api.AlignmentViewPanel;
35 import jalview.api.FeatureSettingsControllerI;
36 import jalview.api.FeatureSettingsModelI;
37 import jalview.api.SplitContainerI;
38 import jalview.api.ViewStyleI;
39 import jalview.api.analysis.SimilarityParamsI;
40 import jalview.bin.Cache;
41 import jalview.bin.Jalview;
42 import jalview.commands.CommandI;
43 import jalview.commands.EditCommand;
44 import jalview.commands.EditCommand.Action;
45 import jalview.commands.OrderCommand;
46 import jalview.commands.RemoveGapColCommand;
47 import jalview.commands.RemoveGapsCommand;
48 import jalview.commands.SlideSequencesCommand;
49 import jalview.commands.TrimRegionCommand;
50 import jalview.datamodel.AlignExportSettingsAdapter;
51 import jalview.datamodel.AlignedCodonFrame;
52 import jalview.datamodel.Alignment;
53 import jalview.datamodel.AlignmentAnnotation;
54 import jalview.datamodel.AlignmentExportData;
55 import jalview.datamodel.AlignmentI;
56 import jalview.datamodel.AlignmentOrder;
57 import jalview.datamodel.AlignmentView;
58 import jalview.datamodel.ColumnSelection;
59 import jalview.datamodel.HiddenColumns;
60 import jalview.datamodel.PDBEntry;
61 import jalview.datamodel.SeqCigar;
62 import jalview.datamodel.Sequence;
63 import jalview.datamodel.SequenceGroup;
64 import jalview.datamodel.SequenceI;
65 import jalview.gui.ColourMenuHelper.ColourChangeListener;
66 import jalview.gui.ViewSelectionMenu.ViewSetProvider;
67 import jalview.io.AlignmentProperties;
68 import jalview.io.AnnotationFile;
69 import jalview.io.BackupFiles;
70 import jalview.io.BioJsHTMLOutput;
71 import jalview.io.DataSourceType;
72 import jalview.io.FileFormat;
73 import jalview.io.FileFormatI;
74 import jalview.io.FileFormats;
75 import jalview.io.FileLoader;
76 import jalview.io.FileParse;
77 import jalview.io.FormatAdapter;
78 import jalview.io.HtmlSvgOutput;
79 import jalview.io.IdentifyFile;
80 import jalview.io.JPredFile;
81 import jalview.io.JalviewFileChooser;
82 import jalview.io.JalviewFileView;
83 import jalview.io.JnetAnnotationMaker;
84 import jalview.io.NewickFile;
85 import jalview.io.ScoreMatrixFile;
86 import jalview.io.TCoffeeScoreFile;
87 import jalview.io.vcf.VCFLoader;
88 import jalview.jbgui.GAlignFrame;
89 import jalview.project.Jalview2XML;
90 import jalview.schemes.ColourSchemeI;
91 import jalview.schemes.ColourSchemes;
92 import jalview.schemes.ResidueColourScheme;
93 import jalview.schemes.TCoffeeColourScheme;
94 import jalview.util.ImageMaker.TYPE;
95 import jalview.util.MessageManager;
96 import jalview.util.Platform;
97 import jalview.viewmodel.AlignmentViewport;
98 import jalview.viewmodel.ViewportRanges;
99 import jalview.ws.DBRefFetcher;
100 import jalview.ws.DBRefFetcher.FetchFinishedListenerI;
101 import jalview.ws.jws1.Discoverer;
102 import jalview.ws.jws2.Jws2Discoverer;
103 import jalview.ws.jws2.jabaws2.Jws2Instance;
104 import jalview.ws.seqfetcher.DbSourceProxy;
105
106 import java.awt.BorderLayout;
107 import java.awt.Color;
108 import java.awt.Component;
109 import java.awt.Dimension;
110 import java.awt.Rectangle;
111 import java.awt.Toolkit;
112 import java.awt.datatransfer.Clipboard;
113 import java.awt.datatransfer.DataFlavor;
114 import java.awt.datatransfer.StringSelection;
115 import java.awt.datatransfer.Transferable;
116 import java.awt.dnd.DnDConstants;
117 import java.awt.dnd.DropTargetDragEvent;
118 import java.awt.dnd.DropTargetDropEvent;
119 import java.awt.dnd.DropTargetEvent;
120 import java.awt.dnd.DropTargetListener;
121 import java.awt.event.ActionEvent;
122 import java.awt.event.ActionListener;
123 import java.awt.event.FocusAdapter;
124 import java.awt.event.FocusEvent;
125 import java.awt.event.ItemEvent;
126 import java.awt.event.ItemListener;
127 import java.awt.event.KeyAdapter;
128 import java.awt.event.KeyEvent;
129 import java.awt.event.MouseEvent;
130 import java.awt.print.PageFormat;
131 import java.awt.print.PrinterJob;
132 import java.beans.PropertyChangeEvent;
133 import java.io.File;
134 import java.io.FileWriter;
135 import java.io.PrintWriter;
136 import java.net.URL;
137 import java.util.ArrayList;
138 import java.util.Arrays;
139 import java.util.Deque;
140 import java.util.Enumeration;
141 import java.util.Hashtable;
142 import java.util.List;
143 import java.util.Vector;
144
145 import javax.swing.ButtonGroup;
146 import javax.swing.JCheckBoxMenuItem;
147 import javax.swing.JComponent;
148 import javax.swing.JEditorPane;
149 import javax.swing.JInternalFrame;
150 import javax.swing.JLabel;
151 import javax.swing.JLayeredPane;
152 import javax.swing.JMenu;
153 import javax.swing.JMenuItem;
154 import javax.swing.JPanel;
155 import javax.swing.JScrollPane;
156 import javax.swing.SwingUtilities;
157
158 import ext.vamsas.ServiceHandle;
159
160 /**
161  * DOCUMENT ME!
162  * 
163  * @author $author$
164  * @version $Revision$
165  */
166 @SuppressWarnings("serial")
167 public class AlignFrame extends GAlignFrame implements DropTargetListener,
168         IProgressIndicator, AlignViewControllerGuiI, ColourChangeListener
169 {
170
171   public static int frameCount;
172
173   public static final int DEFAULT_WIDTH = 700;
174
175   public static final int DEFAULT_HEIGHT = 500;
176
177   /*
178    * The currently displayed panel (selected tabbed view if more than one)
179    */
180   public AlignmentPanel alignPanel;
181
182   AlignViewport viewport;
183
184   public AlignViewControllerI avc;
185
186   List<AlignmentPanel> alignPanels = new ArrayList<>();
187
188   /**
189    * Last format used to load or save alignments in this window
190    */
191   FileFormatI currentFileFormat = null;
192
193   /**
194    * Current filename for this alignment
195    */
196   String fileName = null;
197
198   File fileObject;
199
200   private int id;
201
202   private DataSourceType protocol ;
203
204   /**
205    * Creates a new AlignFrame object with specific width and height.
206    * 
207    * @param al
208    * @param width
209    * @param height
210    */
211   public AlignFrame(AlignmentI al, int width, int height)
212   {
213     this(al, null, width, height);
214   }
215
216   /**
217    * Creates a new AlignFrame object with specific width, height and
218    * sequenceSetId
219    * 
220    * @param al
221    * @param width
222    * @param height
223    * @param sequenceSetId
224    */
225   public AlignFrame(AlignmentI al, int width, int height,
226           String sequenceSetId)
227   {
228     this(al, null, width, height, sequenceSetId);
229   }
230
231   /**
232    * Creates a new AlignFrame object with specific width, height and
233    * sequenceSetId
234    * 
235    * @param al
236    * @param width
237    * @param height
238    * @param sequenceSetId
239    * @param viewId
240    */
241   public AlignFrame(AlignmentI al, int width, int height,
242           String sequenceSetId, String viewId)
243   {
244     this(al, null, width, height, sequenceSetId, viewId);
245   }
246
247   /**
248    * new alignment window with hidden columns
249    * 
250    * @param al
251    *          AlignmentI
252    * @param hiddenColumns
253    *          ColumnSelection or null
254    * @param width
255    *          Width of alignment frame
256    * @param height
257    *          height of frame.
258    */
259   public AlignFrame(AlignmentI al, HiddenColumns hiddenColumns, int width,
260           int height)
261   {
262     this(al, hiddenColumns, width, height, null);
263   }
264
265   /**
266    * Create alignment frame for al with hiddenColumns, a specific width and
267    * height, and specific sequenceId
268    * 
269    * @param al
270    * @param hiddenColumns
271    * @param width
272    * @param height
273    * @param sequenceSetId
274    *          (may be null)
275    */
276   public AlignFrame(AlignmentI al, HiddenColumns hiddenColumns, int width,
277           int height, String sequenceSetId)
278   {
279     this(al, hiddenColumns, width, height, sequenceSetId, null);
280   }
281
282   /**
283    * Create alignment frame for al with hiddenColumns, a specific width and
284    * height, and specific sequenceId
285    * 
286    * @param al
287    * @param hiddenColumns
288    * @param width
289    * @param height
290    * @param sequenceSetId
291    *          (may be null)
292    * @param viewId
293    *          (may be null)
294    */
295   public AlignFrame(AlignmentI al, HiddenColumns hiddenColumns, int width,
296           int height, String sequenceSetId, String viewId)
297   {
298
299     id = (++frameCount);
300
301     setSize(width, height);
302
303     if (al.getDataset() == null)
304     {
305       al.setDataset(null);
306     }
307
308     viewport = new AlignViewport(al, hiddenColumns, sequenceSetId, viewId);
309
310     // JalviewJS needs to distinguish a new panel from an old one in init()
311     // alignPanel = new AlignmentPanel(this, viewport);
312     // addAlignmentPanel(alignPanel, true);
313     init();
314   }
315
316   public AlignFrame(AlignmentI al, SequenceI[] hiddenSeqs,
317           HiddenColumns hiddenColumns, int width, int height)
318   {
319     setSize(width, height);
320
321     if (al.getDataset() == null)
322     {
323       al.setDataset(null);
324     }
325
326     viewport = new AlignViewport(al, hiddenColumns);
327
328     if (hiddenSeqs != null && hiddenSeqs.length > 0)
329     {
330       viewport.hideSequence(hiddenSeqs);
331     }
332     // alignPanel = new AlignmentPanel(this, viewport);
333     // addAlignmentPanel(alignPanel, true);
334     init();
335   }
336
337   /**
338    * Make a new AlignFrame from existing alignmentPanels
339    * 
340    * @param ap
341    *          AlignmentPanel
342    * @param av
343    *          AlignViewport
344    */
345   public AlignFrame(AlignmentPanel ap)
346   {
347     viewport = ap.av;
348     alignPanel = ap;
349     // addAlignmentPanel(ap, false);
350     init();
351   }
352
353   /**
354    * initalise the alignframe from the underlying viewport data and the
355    * configurations
356    */
357
358   void init()
359   {
360
361     boolean newPanel = (alignPanel == null);
362     viewport.setShowAutocalculatedAbove(isShowAutoCalculatedAbove());
363     if (newPanel)
364     {
365       if (Platform.isJS())
366       {
367         // need to set this up front if NOANNOTATION is
368         // used in conjunction with SHOWOVERVIEW.
369
370         // I have not determined if this is appropriate for
371         // Jalview/Java, as it means we are setting this flag
372         // for all subsequent AlignFrames. For now, at least,
373         // I am setting it to be JalviewJS-only.
374
375         boolean showAnnotation = Jalview.getInstance().getShowAnnotation();
376         viewport.setShowAnnotation(showAnnotation);
377       }
378       alignPanel = new AlignmentPanel(this, viewport);
379     }
380     addAlignmentPanel(alignPanel, newPanel);
381
382     // setBackground(Color.white); // BH 2019
383
384     if (!Jalview.isHeadlessMode())
385     {
386       progressBar = new ProgressBar(this.statusPanel, this.statusBar);
387       // JalviewJS options
388       statusPanel.setVisible(Jalview.getInstance().getShowStatus());
389       alignFrameMenuBar.setVisible(Jalview.getInstance().getAllowMenuBar());
390     }
391
392     avc = new jalview.controller.AlignViewController(this, viewport,
393             alignPanel);
394     if (viewport.getAlignmentConservationAnnotation() == null)
395     {
396       // BLOSUM62Colour.setEnabled(false);
397       conservationMenuItem.setEnabled(false);
398       modifyConservation.setEnabled(false);
399       // PIDColour.setEnabled(false);
400       // abovePIDThreshold.setEnabled(false);
401       // modifyPID.setEnabled(false);
402     }
403
404     String sortby = jalview.bin.Cache.getDefault(Preferences.SORT_ALIGNMENT,
405             "No sort");
406
407     if (sortby.equals("Id"))
408     {
409       sortIDMenuItem_actionPerformed(null);
410     }
411     else if (sortby.equals("Pairwise Identity"))
412     {
413       sortPairwiseMenuItem_actionPerformed(null);
414     }
415
416     // BH see above
417     //
418     // this.alignPanel.av
419     // .setShowAutocalculatedAbove(isShowAutoCalculatedAbove());
420
421     setMenusFromViewport(viewport);
422     buildSortByAnnotationScoresMenu();
423     calculateTree.addActionListener(new ActionListener()
424     {
425
426       @Override
427       public void actionPerformed(ActionEvent e)
428       {
429         openTreePcaDialog();
430       }
431     });
432     buildColourMenu();
433
434     if (Desktop.getDesktopPane() != null)
435     {
436       this.setDropTarget(new java.awt.dnd.DropTarget(this, this));
437       if (!Platform.isJS())
438       {
439         addServiceListeners();
440       }
441       setGUINucleotide();
442     }
443
444     if (viewport.getWrapAlignment())
445     {
446       wrapMenuItem_actionPerformed(null);
447     }
448
449     if (jalview.bin.Cache.getDefault(Preferences.SHOW_OVERVIEW, false))
450     {
451       this.overviewMenuItem_actionPerformed(null);
452     }
453
454     addKeyListener();
455
456     final List<AlignmentPanel> selviews = new ArrayList<>();
457     final List<AlignmentPanel> origview = new ArrayList<>();
458     final String menuLabel = MessageManager
459             .getString("label.copy_format_from");
460     ViewSelectionMenu vsel = new ViewSelectionMenu(menuLabel,
461             new ViewSetProvider()
462             {
463
464               @Override
465               public AlignmentPanel[] getAllAlignmentPanels()
466               {
467                 origview.clear();
468                 origview.add(alignPanel);
469                 // make an array of all alignment panels except for this one
470                 List<AlignmentPanel> aps = new ArrayList<>(
471                         Arrays.asList(Desktop.getAlignmentPanels(null)));
472                 aps.remove(AlignFrame.this.alignPanel);
473                 return aps.toArray(new AlignmentPanel[aps.size()]);
474               }
475             }, selviews, new ItemListener()
476             {
477
478               @Override
479               public void itemStateChanged(ItemEvent e)
480               {
481                 if (origview.size() > 0)
482                 {
483                   final AlignmentPanel ap = origview.get(0);
484
485                   /*
486                    * Copy the ViewStyle of the selected panel to 'this one'.
487                    * Don't change value of 'scaleProteinAsCdna' unless copying
488                    * from a SplitFrame.
489                    */
490                   ViewStyleI vs = selviews.get(0).getAlignViewport()
491                           .getViewStyle();
492                   boolean fromSplitFrame = selviews.get(0)
493                           .getAlignViewport().getCodingComplement() != null;
494                   if (!fromSplitFrame)
495                   {
496                     vs.setScaleProteinAsCdna(ap.getAlignViewport()
497                             .getViewStyle().isScaleProteinAsCdna());
498                   }
499                   ap.getAlignViewport().setViewStyle(vs);
500
501                   /*
502                    * Also rescale ViewStyle of SplitFrame complement if there is
503                    * one _and_ it is set to 'scaledProteinAsCdna'; we don't copy
504                    * the whole ViewStyle (allow cDNA protein to have different
505                    * fonts)
506                    */
507                   AlignViewportI complement = ap.getAlignViewport()
508                           .getCodingComplement();
509                   if (complement != null && vs.isScaleProteinAsCdna())
510                   {
511                     AlignFrame af = Desktop.getAlignFrameFor(complement);
512                     ((SplitFrame) af.getSplitViewContainer())
513                             .adjustLayout();
514                     af.setMenusForViewport();
515                   }
516
517                   ap.updateLayout();
518                   ap.setSelected(true);
519                   ap.alignFrame.setMenusForViewport();
520
521                 }
522               }
523             });
524     if (Cache.getDefault("VERSION", "DEVELOPMENT").toLowerCase()
525             .indexOf("devel") > -1
526             || Cache.getDefault("VERSION", "DEVELOPMENT").toLowerCase()
527                     .indexOf("test") > -1)
528     {
529       formatMenu.add(vsel);
530     }
531     addFocusListener(new FocusAdapter()
532     {
533
534       @Override
535       public void focusGained(FocusEvent e)
536       {
537         Jalview.setCurrentAlignFrame(AlignFrame.this);
538       }
539     });
540
541   }
542
543   /**
544    * Change the filename and format for the alignment, and enable the 'reload'
545    * button functionality.
546    * 
547    * @param file
548    *          valid filename
549    * @param format
550    *          format of file
551    */
552
553   @Deprecated
554   public void setFileName(String file, FileFormatI format)
555   {
556     fileName = file;
557     setFileFormat(format);
558     reload.setEnabled(true);
559   }
560
561   /**
562    * 
563    * @param fileName
564    * @param file  from SwingJS; may contain bytes -- for reload
565    * @param protocol from SwingJS; may be RELATIVE_URL
566    * @param format
567    */
568   public void setFile(String fileName, File file, DataSourceType protocol, FileFormatI format)
569   {
570     this.fileName = fileName;
571     this.fileObject = file;
572     this.protocol = protocol;
573     setFileFormat(format);
574     reload.setEnabled(true);
575   }
576
577   /**
578    * JavaScript will have this, maybe others. More dependable than a file name
579    * and maintains a reference to the actual bytes loaded.
580    * 
581    * @param file
582    */
583
584   public void setFileObject(File file)
585   {
586     this.fileObject = file;
587   }
588
589   /**
590    * Add a KeyListener with handlers for various KeyPressed and KeyReleased
591    * events
592    */
593
594   void addKeyListener()
595   {
596     addKeyListener(new KeyAdapter()
597     {
598
599       @Override
600       public void keyPressed(KeyEvent evt)
601       {
602         if (viewport.cursorMode
603                 && ((evt.getKeyCode() >= KeyEvent.VK_0
604                         && evt.getKeyCode() <= KeyEvent.VK_9)
605                         || (evt.getKeyCode() >= KeyEvent.VK_NUMPAD0
606                                 && evt.getKeyCode() <= KeyEvent.VK_NUMPAD9))
607                 && Character.isDigit(evt.getKeyChar()))
608         {
609           alignPanel.getSeqPanel().numberPressed(evt.getKeyChar());
610         }
611
612         switch (evt.getKeyCode())
613         {
614
615         case KeyEvent.VK_ESCAPE: // escape key
616                                  // alignPanel.deselectAllSequences();
617           alignPanel.deselectAllSequences();
618
619           break;
620
621         case KeyEvent.VK_DOWN:
622           if (evt.isAltDown() || !viewport.cursorMode)
623           {
624             moveSelectedSequences(false);
625           }
626           if (viewport.cursorMode)
627           {
628             alignPanel.getSeqPanel().moveCursor(0, 1);
629           }
630           break;
631
632         case KeyEvent.VK_UP:
633           if (evt.isAltDown() || !viewport.cursorMode)
634           {
635             moveSelectedSequences(true);
636           }
637           if (viewport.cursorMode)
638           {
639             alignPanel.getSeqPanel().moveCursor(0, -1);
640           }
641
642           break;
643
644         case KeyEvent.VK_LEFT:
645           if (evt.isAltDown() || !viewport.cursorMode)
646           {
647             slideSequences(false,
648                     alignPanel.getSeqPanel().getKeyboardNo1());
649           }
650           else
651           {
652             alignPanel.getSeqPanel().moveCursor(-1, 0);
653           }
654
655           break;
656
657         case KeyEvent.VK_RIGHT:
658           if (evt.isAltDown() || !viewport.cursorMode)
659           {
660             slideSequences(true, alignPanel.getSeqPanel().getKeyboardNo1());
661           }
662           else
663           {
664             alignPanel.getSeqPanel().moveCursor(1, 0);
665           }
666           break;
667
668         case KeyEvent.VK_SPACE:
669           if (viewport.cursorMode)
670           {
671             alignPanel.getSeqPanel().insertGapAtCursor(evt.isControlDown()
672                     || evt.isShiftDown() || evt.isAltDown());
673           }
674           break;
675
676         // case KeyEvent.VK_A:
677         // if (viewport.cursorMode)
678         // {
679         // alignPanel.seqPanel.insertNucAtCursor(false,"A");
680         // //System.out.println("A");
681         // }
682         // break;
683         /*
684          * case KeyEvent.VK_CLOSE_BRACKET: if (viewport.cursorMode) {
685          * System.out.println("closing bracket"); } break;
686          */
687         case KeyEvent.VK_DELETE:
688         case KeyEvent.VK_BACK_SPACE:
689           if (!viewport.cursorMode)
690           {
691             cut_actionPerformed();
692           }
693           else
694           {
695             alignPanel.getSeqPanel().deleteGapAtCursor(evt.isControlDown()
696                     || evt.isShiftDown() || evt.isAltDown());
697           }
698
699           break;
700
701         case KeyEvent.VK_S:
702           if (viewport.cursorMode)
703           {
704             alignPanel.getSeqPanel().setCursorRow();
705           }
706           break;
707         case KeyEvent.VK_C:
708           if (viewport.cursorMode && !evt.isControlDown())
709           {
710             alignPanel.getSeqPanel().setCursorColumn();
711           }
712           break;
713         case KeyEvent.VK_P:
714           if (viewport.cursorMode)
715           {
716             alignPanel.getSeqPanel().setCursorPosition();
717           }
718           break;
719
720         case KeyEvent.VK_ENTER:
721         case KeyEvent.VK_COMMA:
722           if (viewport.cursorMode)
723           {
724             alignPanel.getSeqPanel().setCursorRowAndColumn();
725           }
726           break;
727
728         case KeyEvent.VK_Q:
729           if (viewport.cursorMode)
730           {
731             alignPanel.getSeqPanel().setSelectionAreaAtCursor(true);
732           }
733           break;
734         case KeyEvent.VK_M:
735           if (viewport.cursorMode)
736           {
737             alignPanel.getSeqPanel().setSelectionAreaAtCursor(false);
738           }
739           break;
740
741         case KeyEvent.VK_F2:
742           viewport.cursorMode = !viewport.cursorMode;
743           setStatus(MessageManager
744                   .formatMessage("label.keyboard_editing_mode", new String[]
745                   { (viewport.cursorMode ? "on" : "off") }));
746           if (viewport.cursorMode)
747           {
748             ViewportRanges ranges = viewport.getRanges();
749             alignPanel.getSeqPanel().seqCanvas.cursorX = ranges
750                     .getStartRes();
751             alignPanel.getSeqPanel().seqCanvas.cursorY = ranges
752                     .getStartSeq();
753           }
754           alignPanel.getSeqPanel().seqCanvas.repaint();
755           break;
756
757         case KeyEvent.VK_F1:
758           try
759           {
760             Help.showHelpWindow();
761           } catch (Exception ex)
762           {
763             ex.printStackTrace();
764           }
765           break;
766         case KeyEvent.VK_H:
767         {
768           boolean toggleSeqs = !evt.isControlDown();
769           boolean toggleCols = !evt.isShiftDown();
770           toggleHiddenRegions(toggleSeqs, toggleCols);
771           break;
772         }
773         case KeyEvent.VK_B:
774         {
775           boolean toggleSel = evt.isControlDown() || evt.isMetaDown();
776           boolean modifyExisting = true; // always modify, don't clear
777                                          // evt.isShiftDown();
778           boolean invertHighlighted = evt.isAltDown();
779           avc.markHighlightedColumns(invertHighlighted, modifyExisting,
780                   toggleSel);
781           break;
782         }
783         case KeyEvent.VK_PAGE_UP:
784           viewport.getRanges().pageUp();
785           break;
786         case KeyEvent.VK_PAGE_DOWN:
787           viewport.getRanges().pageDown();
788           break;
789         }
790       }
791
792       @Override
793       public void keyReleased(KeyEvent evt)
794       {
795         switch (evt.getKeyCode())
796         {
797         case KeyEvent.VK_LEFT:
798           if (evt.isAltDown() || !viewport.cursorMode)
799           {
800             viewport.notifyAlignment();
801           }
802           break;
803
804         case KeyEvent.VK_RIGHT:
805           if (evt.isAltDown() || !viewport.cursorMode)
806           {
807             viewport.notifyAlignment();
808           }
809           break;
810         }
811       }
812     });
813   }
814
815   public void addAlignmentPanel(final AlignmentPanel ap, boolean newPanel)
816   {
817     ap.alignFrame = this;
818     avc = new jalview.controller.AlignViewController(this, viewport,
819             alignPanel);
820
821     alignPanels.add(ap);
822
823     PaintRefresher.Register(ap, ap.av.getSequenceSetId());
824
825     int aSize = alignPanels.size();
826
827     tabbedPane.setVisible(aSize > 1 || ap.av.getViewName() != null);
828
829     if (aSize == 1 && ap.av.getViewName() == null)
830     {
831       this.getContentPane().add(ap, BorderLayout.CENTER);
832     }
833     else
834     {
835       if (aSize == 2)
836       {
837         setInitialTabVisible();
838       }
839
840       expandViews.setEnabled(true);
841       gatherViews.setEnabled(true);
842       tabbedPane.addTab(ap.av.getViewName(), ap);
843
844       ap.setVisible(false);
845     }
846
847     if (newPanel)
848     {
849       if (ap.av.isPadGaps())
850       {
851         ap.av.getAlignment().padGaps();
852       }
853       if (Jalview.getInstance().getStartCalculations())
854       {
855         ap.av.updateConservation(ap);
856         ap.av.updateConsensus(ap);
857         ap.av.updateStrucConsensus(ap);
858       }
859     }
860   }
861
862   public void setInitialTabVisible()
863   {
864     expandViews.setEnabled(true);
865     gatherViews.setEnabled(true);
866     tabbedPane.setVisible(true);
867     AlignmentPanel first = alignPanels.get(0);
868     tabbedPane.addTab(first.av.getViewName(), first);
869     this.getContentPane().add(tabbedPane, BorderLayout.CENTER);
870   }
871
872   public AlignViewport getViewport()
873   {
874     return viewport;
875   }
876
877   /* Set up intrinsic listeners for dynamically generated GUI bits. */
878   private void addServiceListeners()
879   {
880     final java.beans.PropertyChangeListener thisListener;
881     Desktop.getInstance().addJalviewPropertyChangeListener("services",
882             thisListener = new java.beans.PropertyChangeListener()
883             {
884
885               @Override
886               public void propertyChange(PropertyChangeEvent evt)
887               {
888                 // // System.out.println("Discoverer property change.");
889                 // if (evt.getPropertyName().equals("services"))
890                 {
891                   SwingUtilities.invokeLater(new Runnable()
892                   {
893
894                     @Override
895                     public void run()
896                     {
897                       System.err.println(
898                               "Rebuild WS Menu for service change");
899                       BuildWebServiceMenu();
900                     }
901
902                   });
903                 }
904               }
905             });
906     addInternalFrameListener(new javax.swing.event.InternalFrameAdapter()
907     {
908
909       @Override
910       public void internalFrameClosed(
911               javax.swing.event.InternalFrameEvent evt)
912       {
913         // System.out.println("deregistering discoverer listener");
914         Desktop.getInstance().removeJalviewPropertyChangeListener(
915                 "services", thisListener);
916         closeMenuItem_actionPerformed(true);
917       }
918     });
919     // Finally, build the menu once to get current service state
920     new Thread(new Runnable()
921     {
922
923       @Override
924       public void run()
925       {
926         BuildWebServiceMenu();
927       }
928     }).start();
929   }
930
931   /**
932    * Configure menu items that vary according to whether the alignment is
933    * nucleotide or protein
934    */
935
936   public void setGUINucleotide()
937   {
938     AlignmentI al = getViewport().getAlignment();
939     boolean nucleotide = al.isNucleotide();
940
941     loadVcf.setVisible(nucleotide);
942     showTranslation.setVisible(nucleotide);
943     showReverse.setVisible(nucleotide);
944     showReverseComplement.setVisible(nucleotide);
945     conservationMenuItem.setEnabled(!nucleotide);
946     modifyConservation
947             .setEnabled(!nucleotide && conservationMenuItem.isSelected());
948     showGroupConservation.setEnabled(!nucleotide);
949
950     showComplementMenuItem
951             .setText(nucleotide ? MessageManager.getString("label.protein")
952                     : MessageManager.getString("label.nucleotide"));
953   }
954
955   /**
956    * set up menus for the current viewport. This may be called after any
957    * operation that affects the data in the current view (selection changed,
958    * etc) to update the menus to reflect the new state.
959    */
960
961   @Override
962   public void setMenusForViewport()
963   {
964     setMenusFromViewport(viewport);
965   }
966
967   /**
968    * Need to call this method when tabs are selected for multiple views, or when
969    * loading from Jalview2XML.java
970    * 
971    * @param av
972    *          AlignViewport
973    */
974
975   public void setMenusFromViewport(AlignViewport av)
976   {
977     padGapsMenuitem.setSelected(av.isPadGaps());
978     colourTextMenuItem.setSelected(av.isShowColourText());
979     abovePIDThreshold.setSelected(av.getAbovePIDThreshold());
980     modifyPID.setEnabled(abovePIDThreshold.isSelected());
981     conservationMenuItem.setSelected(av.getConservationSelected());
982     modifyConservation.setEnabled(conservationMenuItem.isSelected());
983     seqLimits.setSelected(av.getShowJVSuffix());
984     idRightAlign.setSelected(av.isRightAlignIds());
985     centreColumnLabelsMenuItem.setState(av.isCentreColumnLabels());
986     renderGapsMenuItem.setSelected(av.isRenderGaps());
987     wrapMenuItem.setSelected(av.getWrapAlignment());
988     scaleAbove.setVisible(av.getWrapAlignment());
989     scaleLeft.setVisible(av.getWrapAlignment());
990     scaleRight.setVisible(av.getWrapAlignment());
991     annotationPanelMenuItem.setState(av.isShowAnnotation());
992     // Show/hide annotations only enabled if annotation panel is shown
993     syncAnnotationMenuItems(av.isShowAnnotation());
994     viewBoxesMenuItem.setSelected(av.getShowBoxes());
995     viewTextMenuItem.setSelected(av.getShowText());
996     showNonconservedMenuItem.setSelected(av.getShowUnconserved());
997     showGroupConsensus.setSelected(av.isShowGroupConsensus());
998     showGroupConservation.setSelected(av.isShowGroupConservation());
999     showConsensusHistogram.setSelected(av.isShowConsensusHistogram());
1000     showSequenceLogo.setSelected(av.isShowSequenceLogo());
1001     normaliseSequenceLogo.setSelected(av.isNormaliseSequenceLogo());
1002
1003     ColourMenuHelper.setColourSelected(colourMenu,
1004             av.getGlobalColourScheme());
1005
1006     showSeqFeatures.setSelected(av.isShowSequenceFeatures());
1007     hiddenMarkers.setState(av.getShowHiddenMarkers());
1008     applyToAllGroups.setState(av.getColourAppliesToAllGroups());
1009     showNpFeatsMenuitem.setSelected(av.isShowNPFeats());
1010     showDbRefsMenuitem.setSelected(av.isShowDBRefs());
1011     autoCalculate
1012             .setSelected(av.getAutoCalculateConsensusAndConservation());
1013     sortByTree.setSelected(av.sortByTree);
1014     listenToViewSelections.setSelected(av.followSelection);
1015
1016     showProducts.setEnabled(canShowProducts());
1017     setGroovyEnabled(Desktop.getGroovyConsole() != null);
1018
1019     updateEditMenuBar();
1020   }
1021
1022   /**
1023    * Set the enabled state of the 'Run Groovy' option in the Calculate menu
1024    * 
1025    * @param b
1026    */
1027
1028   public void setGroovyEnabled(boolean b)
1029   {
1030     runGroovy.setEnabled(b);
1031   }
1032
1033   private IProgressIndicator progressBar;
1034
1035   /*
1036    * (non-Javadoc)
1037    * 
1038    * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
1039    */
1040
1041   @Override
1042   public void setProgressBar(String message, long id)
1043   {
1044     progressBar.setProgressBar(message, id);
1045   }
1046
1047   @Override
1048   public void registerHandler(final long id,
1049           final IProgressIndicatorHandler handler)
1050   {
1051     progressBar.registerHandler(id, handler);
1052   }
1053
1054   /**
1055    * 
1056    * @return true if any progress bars are still active
1057    */
1058
1059   @Override
1060   public boolean operationInProgress()
1061   {
1062     return progressBar.operationInProgress();
1063   }
1064
1065   /**
1066    * Sets the text of the status bar. Note that setting a null or empty value
1067    * will cause the status bar to be hidden, with possibly undesirable flicker
1068    * of the screen layout.
1069    */
1070
1071   @Override
1072   public void setStatus(String text)
1073   {
1074     statusBar.setText(text == null || text.isEmpty() ? " " : text);
1075   }
1076
1077   /*
1078    * Added so Castor Mapping file can obtain Jalview Version
1079    */
1080
1081   public String getVersion()
1082   {
1083     return jalview.bin.Cache.getProperty("VERSION");
1084   }
1085
1086   public FeatureRenderer getFeatureRenderer()
1087   {
1088     return alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer();
1089   }
1090
1091   @Override
1092   public void fetchSequence_actionPerformed()
1093   {
1094     new SequenceFetcher(this);
1095   }
1096
1097   @Override
1098   public void addFromFile_actionPerformed(ActionEvent e)
1099   {
1100     Desktop.getInstance().inputLocalFileMenuItem_actionPerformed(viewport);
1101   }
1102
1103   @Override
1104   public void reload_actionPerformed(ActionEvent e)
1105   {
1106     if (fileName == null && fileObject == null)
1107     {
1108       return;
1109     }
1110     // TODO: JAL-1108 - ensure all associated frames are closed regardless of
1111     // originating file's format
1112     // TODO: work out how to recover feature settings for correct view(s) when
1113     // file is reloaded.
1114     if (FileFormat.Jalview.equals(currentFileFormat))
1115     {
1116       JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
1117       for (int i = 0; i < frames.length; i++)
1118       {
1119         if (frames[i] instanceof AlignFrame && frames[i] != this
1120                 && ((AlignFrame) frames[i]).fileName != null
1121                 && ((AlignFrame) frames[i]).fileName.equals(fileName))
1122         {
1123           try
1124           {
1125             frames[i].setSelected(true);
1126             Desktop.getInstance().closeAssociatedWindows();
1127           } catch (java.beans.PropertyVetoException ex)
1128           {
1129           }
1130         }
1131
1132       }
1133       Desktop.getInstance().closeAssociatedWindows();
1134
1135       FileLoader loader = new FileLoader();
1136 //      DataSourceType protocol = fileName.startsWith("http:")
1137 //              ? DataSourceType.URL
1138 //              : DataSourceType.FILE;
1139         loader.LoadFile(viewport, (fileObject == null ? fileName : fileObject), protocol, currentFileFormat);
1140     }
1141     else
1142     {
1143       Rectangle bounds = this.getBounds();
1144
1145       FileLoader loader = new FileLoader();
1146
1147       AlignFrame newframe = null;
1148
1149       if (fileObject == null)
1150       {
1151         newframe = loader.LoadFileWaitTillLoaded(fileName, protocol,
1152                 currentFileFormat);
1153       }
1154       else
1155       {
1156         newframe = loader.LoadFileWaitTillLoaded(fileObject,
1157                 DataSourceType.FILE, currentFileFormat);
1158       }
1159
1160       newframe.setBounds(bounds);
1161       if (featureSettings != null && featureSettings.isShowing())
1162       {
1163         final Rectangle fspos = featureSettings.frame.getBounds();
1164         // TODO: need a 'show feature settings' function that takes bounds -
1165         // need to refactor Desktop.addFrame
1166         newframe.featureSettings_actionPerformed(null);
1167         final FeatureSettings nfs = newframe.featureSettings;
1168         SwingUtilities.invokeLater(new Runnable()
1169         {
1170
1171           @Override
1172           public void run()
1173           {
1174             nfs.frame.setBounds(fspos);
1175           }
1176         });
1177         this.featureSettings.close();
1178         this.featureSettings = null;
1179       }
1180       this.closeMenuItem_actionPerformed(true);
1181     }
1182
1183   }
1184
1185   @Override
1186   public void addFromText_actionPerformed(ActionEvent e)
1187   {
1188     Desktop.getInstance()
1189             .inputTextboxMenuItem_actionPerformed(viewport.getAlignPanel());
1190   }
1191
1192   @Override
1193   public void addFromURL_actionPerformed(ActionEvent e)
1194   {
1195     Desktop.getInstance().inputURLMenuItem_actionPerformed(viewport);
1196   }
1197
1198   @Override
1199   public void save_actionPerformed(ActionEvent e)
1200   {
1201     if (fileName == null || (currentFileFormat == null)
1202             || fileName.startsWith("http"))
1203     {
1204       saveAs_actionPerformed();
1205     }
1206     else
1207     {
1208       saveAlignment(fileName, currentFileFormat);
1209     }
1210   }
1211
1212   /**
1213    * Saves the alignment to a file with a name chosen by the user, if necessary
1214    * warning if a file would be overwritten
1215    */
1216
1217   @Override
1218   public void saveAs_actionPerformed()
1219   {
1220     String format = currentFileFormat == null ? null
1221             : currentFileFormat.getName();
1222     JalviewFileChooser chooser = JalviewFileChooser
1223             .forWrite(Cache.getProperty("LAST_DIRECTORY"), format);
1224
1225     chooser.setFileView(new JalviewFileView());
1226     chooser.setDialogTitle(
1227             MessageManager.getString("label.save_alignment_to_file"));
1228     chooser.setToolTipText(MessageManager.getString("action.save"));
1229
1230     int value = chooser.showSaveDialog(this);
1231
1232     if (value != JalviewFileChooser.APPROVE_OPTION)
1233     {
1234       return;
1235     }
1236     currentFileFormat = chooser.getSelectedFormat();
1237     // todo is this (2005) test now obsolete - value is never null?
1238     while (currentFileFormat == null)
1239     {
1240       JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
1241               MessageManager
1242                       .getString("label.select_file_format_before_saving"),
1243               MessageManager.getString("label.file_format_not_specified"),
1244               JvOptionPane.WARNING_MESSAGE);
1245       currentFileFormat = chooser.getSelectedFormat();
1246       value = chooser.showSaveDialog(this);
1247       if (value != JalviewFileChooser.APPROVE_OPTION)
1248       {
1249         return;
1250       }
1251     }
1252
1253     fileName = chooser.getSelectedFile().getPath();
1254
1255     Cache.setProperty("DEFAULT_FILE_FORMAT", currentFileFormat.getName());
1256     Cache.setProperty("LAST_DIRECTORY", fileName);
1257     saveAlignment(fileName, currentFileFormat);
1258   }
1259
1260   boolean lastSaveSuccessful = false;
1261
1262   FileFormatI lastFormatSaved;
1263
1264   String lastFilenameSaved;
1265
1266   /**
1267    * Raise a dialog or status message for the last call to saveAlignment.
1268    *
1269    * @return true if last call to saveAlignment(file, format) was successful.
1270    */
1271
1272   public boolean isSaveAlignmentSuccessful()
1273   {
1274
1275     if (!lastSaveSuccessful)
1276     {
1277       JvOptionPane.showInternalMessageDialog(this, MessageManager
1278               .formatMessage("label.couldnt_save_file", new Object[]
1279               { lastFilenameSaved }),
1280               MessageManager.getString("label.error_saving_file"),
1281               JvOptionPane.WARNING_MESSAGE);
1282     }
1283     else
1284     {
1285
1286       setStatus(MessageManager.formatMessage(
1287               "label.successfully_saved_to_file_in_format", new Object[]
1288               { lastFilenameSaved, lastFormatSaved }));
1289
1290     }
1291     return lastSaveSuccessful;
1292   }
1293
1294   /**
1295    * Saves the alignment to the specified file path, in the specified format,
1296    * which may be an alignment format, or Jalview project format. If the
1297    * alignment has hidden regions, or the format is one capable of including
1298    * non-sequence data (features, annotations, groups), then the user may be
1299    * prompted to specify what to include in the output.
1300    * 
1301    * @param file
1302    * @param format
1303    */
1304
1305   public void saveAlignment(String file, FileFormatI format)
1306   {
1307     lastSaveSuccessful = true;
1308     lastFilenameSaved = file;
1309     lastFormatSaved = format;
1310
1311     if (FileFormat.Jalview.equals(format))
1312     {
1313       String shortName = title;
1314       if (shortName.indexOf(File.separatorChar) > -1)
1315       {
1316         shortName = shortName
1317                 .substring(shortName.lastIndexOf(File.separatorChar) + 1);
1318       }
1319       lastSaveSuccessful = new Jalview2XML().saveAlignment(this, file,
1320               shortName);
1321
1322       statusBar.setText(MessageManager.formatMessage(
1323               "label.successfully_saved_to_file_in_format", new Object[]
1324               { fileName, format }));
1325
1326       return;
1327     }
1328
1329     AlignExportSettingsI options = new AlignExportSettingsAdapter(false);
1330     Runnable cancelAction = new Runnable()
1331     {
1332
1333       @Override
1334       public void run()
1335       {
1336         lastSaveSuccessful = false;
1337       }
1338     };
1339     Runnable outputAction = new Runnable()
1340     {
1341
1342       @Override
1343       public void run()
1344       {
1345         // todo defer this to inside formatSequences (or later)
1346         AlignmentExportData exportData = viewport
1347                 .getAlignExportData(options);
1348         String output = new FormatAdapter(alignPanel, options)
1349                 .formatSequences(format, exportData.getAlignment(),
1350                         exportData.getOmitHidden(),
1351                         exportData.getStartEndPostions(),
1352                         viewport.getAlignment().getHiddenColumns());
1353         if (output == null)
1354         {
1355           lastSaveSuccessful = false;
1356         }
1357         else
1358         {
1359           // create backupfiles object and get new temp filename destination
1360           boolean doBackup = BackupFiles.getEnabled();
1361           BackupFiles backupfiles = doBackup ? new BackupFiles(file) : null;
1362           try
1363           {
1364             String tempFilePath = doBackup ? backupfiles.getTempFilePath()
1365                     : file;
1366             PrintWriter out = new PrintWriter(new FileWriter(tempFilePath));
1367
1368             out.print(output);
1369             out.close();
1370             AlignFrame.this.setTitle(file);
1371             statusBar.setText(MessageManager.formatMessage(
1372                     "label.successfully_saved_to_file_in_format",
1373                     new Object[]
1374                     { fileName, format.getName() }));
1375             lastSaveSuccessful = true;
1376           } catch (Exception ex)
1377           {
1378             lastSaveSuccessful = false;
1379             ex.printStackTrace();
1380           }
1381
1382           if (doBackup)
1383           {
1384             backupfiles.setWriteSuccess(lastSaveSuccessful);
1385             // do the backup file roll and rename the temp file to actual file
1386             lastSaveSuccessful = backupfiles.rollBackupsAndRenameTempFile();
1387           }
1388         }
1389       }
1390     };
1391
1392     /*
1393      * show dialog with export options if applicable; else just do it
1394      */
1395     if (AlignExportOptions.isNeeded(viewport, format))
1396     {
1397       AlignExportOptions choices = new AlignExportOptions(
1398               alignPanel.getAlignViewport(), format, options);
1399       choices.setResponseAction(0, outputAction);
1400       choices.setResponseAction(1, cancelAction);
1401       choices.showDialog();
1402     }
1403     else
1404     {
1405       outputAction.run();
1406     }
1407   }
1408
1409   /**
1410    * Outputs the alignment to textbox in the requested format, if necessary
1411    * first prompting the user for whether to include hidden regions or
1412    * non-sequence data
1413    * 
1414    * @param fileFormatName
1415    */
1416
1417   @Override
1418   protected void outputText_actionPerformed(String fileFormatName)
1419   {
1420     FileFormatI fileFormat = FileFormats.getInstance()
1421             .forName(fileFormatName);
1422     AlignExportSettingsI options = new AlignExportSettingsAdapter(false);
1423     Runnable outputAction = new Runnable()
1424     {
1425
1426       @Override
1427       public void run()
1428       {
1429         // todo defer this to inside formatSequences (or later)
1430         AlignmentExportData exportData = viewport
1431                 .getAlignExportData(options);
1432         CutAndPasteTransfer cap = new CutAndPasteTransfer();
1433         cap.setForInput(null);
1434         try
1435         {
1436           FileFormatI format = fileFormat;
1437           cap.setText(new FormatAdapter(alignPanel, options)
1438                   .formatSequences(format, exportData.getAlignment(),
1439                           exportData.getOmitHidden(),
1440                           exportData.getStartEndPostions(),
1441                           viewport.getAlignment().getHiddenColumns()));
1442           Desktop.addInternalFrame(cap, MessageManager.formatMessage(
1443                   "label.alignment_output_command", new Object[]
1444                   { fileFormat.getName() }), 600, 500);
1445         } catch (OutOfMemoryError oom)
1446         {
1447           new OOMWarning("Outputting alignment as " + fileFormat.getName(),
1448                   oom);
1449           cap.dispose();
1450         }
1451       }
1452     };
1453
1454     /*
1455      * show dialog with export options if applicable; else just do it
1456      */
1457     if (AlignExportOptions.isNeeded(viewport, fileFormat))
1458     {
1459       AlignExportOptions choices = new AlignExportOptions(
1460               alignPanel.getAlignViewport(), fileFormat, options);
1461       choices.setResponseAction(0, outputAction);
1462       choices.showDialog();
1463     }
1464     else
1465     {
1466       outputAction.run();
1467     }
1468   }
1469
1470   /**
1471    * DOCUMENT ME!
1472    * 
1473    * @param e
1474    *          DOCUMENT ME!
1475    */
1476
1477   @Override
1478   protected void htmlMenuItem_actionPerformed(ActionEvent e)
1479   {
1480     HtmlSvgOutput htmlSVG = new HtmlSvgOutput(alignPanel);
1481     htmlSVG.exportHTML(null);
1482   }
1483
1484   @Override
1485   public void bioJSMenuItem_actionPerformed(ActionEvent e)
1486   {
1487     BioJsHTMLOutput bjs = new BioJsHTMLOutput(alignPanel);
1488     bjs.exportHTML(null);
1489   }
1490
1491   // ??
1492
1493   public void createImageMap(File file, String image)
1494   {
1495     alignPanel.makePNGImageMap(file, image);
1496   }
1497
1498   /**
1499    * Creates a PNG image of the alignment and writes it to the given file. If
1500    * the file is null, the user is prompted to choose a file.
1501    * 
1502    * @param f
1503    */
1504
1505   @Override
1506   public void createPNG(File f)
1507   {
1508     alignPanel.makeAlignmentImage(TYPE.PNG, f);
1509   }
1510
1511   /**
1512    * Creates an EPS image of the alignment and writes it to the given file. If
1513    * the file is null, the user is prompted to choose a file.
1514    * 
1515    * @param f
1516    */
1517
1518   @Override
1519   public void createEPS(File f)
1520   {
1521     alignPanel.makeAlignmentImage(TYPE.EPS, f);
1522   }
1523
1524   /**
1525    * Creates an SVG image of the alignment and writes it to the given file. If
1526    * the file is null, the user is prompted to choose a file.
1527    * 
1528    * @param f
1529    */
1530
1531   @Override
1532   public void createSVG(File f)
1533   {
1534     alignPanel.makeAlignmentImage(TYPE.SVG, f);
1535   }
1536
1537   @Override
1538   public void pageSetup_actionPerformed(ActionEvent e)
1539   {
1540     PrinterJob printJob = PrinterJob.getPrinterJob();
1541     PrintThread.pf = printJob.pageDialog(printJob.defaultPage());
1542   }
1543
1544   /**
1545    * DOCUMENT ME!
1546    * 
1547    * @param e
1548    *          DOCUMENT ME!
1549    */
1550
1551   @Override
1552   public void printMenuItem_actionPerformed(ActionEvent e)
1553   {
1554     // Putting in a thread avoids Swing painting problems
1555     PrintThread thread = new PrintThread(alignPanel);
1556     thread.start();
1557   }
1558
1559   @Override
1560   public void exportFeatures_actionPerformed(ActionEvent e)
1561   {
1562     new AnnotationExporter(alignPanel).exportFeatures();
1563   }
1564
1565   @Override
1566   public void exportAnnotations_actionPerformed(ActionEvent e)
1567   {
1568     new AnnotationExporter(alignPanel).exportAnnotations();
1569   }
1570
1571   @Override
1572   public void associatedData_actionPerformed(ActionEvent e)
1573   {
1574     final JalviewFileChooser chooser = new JalviewFileChooser(
1575             jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
1576     chooser.setFileView(new JalviewFileView());
1577     String tooltip = MessageManager
1578             .getString("label.load_jalview_annotations");
1579     chooser.setDialogTitle(tooltip);
1580     chooser.setToolTipText(tooltip);
1581     chooser.setResponseHandler(0, new Runnable()
1582     {
1583
1584       @Override
1585       public void run()
1586       {
1587         String choice = chooser.getSelectedFile().getPath();
1588         jalview.bin.Cache.setProperty("LAST_DIRECTORY", choice);
1589         loadJalviewDataFile(chooser.getSelectedFile(), null, null, null);
1590       }
1591     });
1592
1593     chooser.showOpenDialog(this);
1594   }
1595
1596   /**
1597    * Close the current view or all views in the alignment frame. If the frame
1598    * only contains one view then the alignment will be removed from memory.
1599    * 
1600    * @param closeAllTabs
1601    */
1602
1603   @Override
1604   public void closeMenuItem_actionPerformed(boolean closeAllTabs)
1605   {
1606     if (alignPanels != null && alignPanels.size() < 2)
1607     {
1608       closeAllTabs = true;
1609     }
1610
1611     try
1612     {
1613       if (alignPanels != null)
1614       {
1615         if (closeAllTabs)
1616         {
1617           if (this.isClosed())
1618           {
1619             // really close all the windows - otherwise wait till
1620             // setClosed(true) is called
1621             for (int i = 0; i < alignPanels.size(); i++)
1622             {
1623               AlignmentPanel ap = alignPanels.get(i);
1624               ap.closePanel();
1625             }
1626           }
1627         }
1628         else
1629         {
1630           closeView(alignPanel);
1631         }
1632       }
1633       if (closeAllTabs)
1634       {
1635         if (featureSettings != null && featureSettings.isOpen())
1636         {
1637           featureSettings.close();
1638           featureSettings = null;
1639         }
1640         /*
1641          * this will raise an INTERNAL_FRAME_CLOSED event and this method will
1642          * be called recursively, with the frame now in 'closed' state
1643          */
1644         this.setClosed(true);
1645       }
1646     } catch (Exception ex)
1647     {
1648       ex.printStackTrace();
1649     }
1650   }
1651
1652   /**
1653    * Close the specified panel and close up tabs appropriately.
1654    * 
1655    * @param panelToClose
1656    */
1657
1658   public void closeView(AlignmentPanel panelToClose)
1659   {
1660     int index = tabbedPane.getSelectedIndex();
1661     int closedindex = tabbedPane.indexOfComponent(panelToClose);
1662     alignPanels.remove(panelToClose);
1663     panelToClose.closePanel();
1664     panelToClose = null;
1665
1666     tabbedPane.removeTabAt(closedindex);
1667     tabbedPane.validate();
1668
1669     if (index > closedindex || index == tabbedPane.getTabCount())
1670     {
1671       // modify currently selected tab index if necessary.
1672       index--;
1673     }
1674
1675     this.tabSelectionChanged(index);
1676   }
1677
1678   /**
1679    * DOCUMENT ME!
1680    */
1681
1682   void updateEditMenuBar()
1683   {
1684
1685     if (viewport.getHistoryList().size() > 0)
1686     {
1687       undoMenuItem.setEnabled(true);
1688       CommandI command = viewport.getHistoryList().peek();
1689       undoMenuItem.setText(MessageManager
1690               .formatMessage("label.undo_command", new Object[]
1691               { command.getDescription() }));
1692     }
1693     else
1694     {
1695       undoMenuItem.setEnabled(false);
1696       undoMenuItem.setText(MessageManager.getString("action.undo"));
1697     }
1698
1699     if (viewport.getRedoList().size() > 0)
1700     {
1701       redoMenuItem.setEnabled(true);
1702
1703       CommandI command = viewport.getRedoList().peek();
1704       redoMenuItem.setText(MessageManager
1705               .formatMessage("label.redo_command", new Object[]
1706               { command.getDescription() }));
1707     }
1708     else
1709     {
1710       redoMenuItem.setEnabled(false);
1711       redoMenuItem.setText(MessageManager.getString("action.redo"));
1712     }
1713   }
1714
1715   @Override
1716   public void addHistoryItem(CommandI command)
1717   {
1718     if (command.getSize() > 0)
1719     {
1720       viewport.addToHistoryList(command);
1721       viewport.clearRedoList();
1722       updateEditMenuBar();
1723       viewport.updateHiddenColumns();
1724       // viewport.hasHiddenColumns = (viewport.getColumnSelection() != null
1725       // && viewport.getColumnSelection().getHiddenColumns() != null &&
1726       // viewport.getColumnSelection()
1727       // .getHiddenColumns().size() > 0);
1728     }
1729   }
1730
1731   /**
1732    * 
1733    * @return alignment objects for all views
1734    */
1735
1736   AlignmentI[] getViewAlignments()
1737   {
1738     if (alignPanels != null)
1739     {
1740       AlignmentI[] als = new AlignmentI[alignPanels.size()];
1741       int i = 0;
1742       for (AlignmentPanel ap : alignPanels)
1743       {
1744         als[i++] = ap.av.getAlignment();
1745       }
1746       return als;
1747     }
1748     if (viewport != null)
1749     {
1750       return new AlignmentI[] { viewport.getAlignment() };
1751     }
1752     return null;
1753   }
1754
1755   /**
1756    * DOCUMENT ME!
1757    * 
1758    * @param e
1759    *          DOCUMENT ME!
1760    */
1761
1762   @Override
1763   protected void undoMenuItem_actionPerformed(ActionEvent e)
1764   {
1765     if (viewport.getHistoryList().isEmpty())
1766     {
1767       return;
1768     }
1769     CommandI command = viewport.getHistoryList().pop();
1770     viewport.addToRedoList(command);
1771     command.undoCommand(getViewAlignments());
1772
1773     AlignmentViewport originalSource = getOriginatingSource(command);
1774     updateEditMenuBar();
1775
1776     if (originalSource != null)
1777     {
1778       if (originalSource != viewport)
1779       {
1780         Cache.log.warn(
1781                 "Implementation worry: mismatch of viewport origin for undo");
1782       }
1783       originalSource.updateHiddenColumns();
1784       // originalSource.hasHiddenColumns = (viewport.getColumnSelection() !=
1785       // null
1786       // && viewport.getColumnSelection().getHiddenColumns() != null &&
1787       // viewport.getColumnSelection()
1788       // .getHiddenColumns().size() > 0);
1789       originalSource.notifyAlignment();
1790
1791     }
1792   }
1793
1794   /**
1795    * DOCUMENT ME!
1796    * 
1797    * @param e
1798    *          DOCUMENT ME!
1799    */
1800
1801   @Override
1802   protected void redoMenuItem_actionPerformed(ActionEvent e)
1803   {
1804     if (viewport.getRedoList().size() < 1)
1805     {
1806       return;
1807     }
1808
1809     CommandI command = viewport.getRedoList().pop();
1810     viewport.addToHistoryList(command);
1811     command.doCommand(getViewAlignments());
1812
1813     AlignmentViewport originalSource = getOriginatingSource(command);
1814     updateEditMenuBar();
1815
1816     if (originalSource != null)
1817     {
1818
1819       if (originalSource != viewport)
1820       {
1821         Cache.log.warn(
1822                 "Implementation worry: mismatch of viewport origin for redo");
1823       }
1824       originalSource.updateHiddenColumns();
1825       // originalSource.hasHiddenColumns = (viewport.getColumnSelection() !=
1826       // null
1827       // && viewport.getColumnSelection().getHiddenColumns() != null &&
1828       // viewport.getColumnSelection()
1829       // .getHiddenColumns().size() > 0);
1830       originalSource.notifyAlignment();
1831
1832     }
1833   }
1834
1835   AlignmentViewport getOriginatingSource(CommandI command)
1836   {
1837     AlignmentViewport originalSource = null;
1838     // For sequence removal and addition, we need to fire
1839     // the property change event FROM the viewport where the
1840     // original alignment was altered
1841     AlignmentI al = null;
1842     if (command instanceof EditCommand)
1843     {
1844       EditCommand editCommand = (EditCommand) command;
1845       al = editCommand.getAlignment();
1846       List<Component> comps = PaintRefresher.components
1847               .get(viewport.getSequenceSetId());
1848
1849       for (Component comp : comps)
1850       {
1851         if (comp instanceof AlignmentPanel)
1852         {
1853           if (al == ((AlignmentPanel) comp).av.getAlignment())
1854           {
1855             originalSource = ((AlignmentPanel) comp).av;
1856             break;
1857           }
1858         }
1859       }
1860     }
1861
1862     if (originalSource == null)
1863     {
1864       // The original view is closed, we must validate
1865       // the current view against the closed view first
1866       if (al != null)
1867       {
1868         PaintRefresher.validateSequences(al, viewport.getAlignment());
1869       }
1870
1871       originalSource = viewport;
1872     }
1873
1874     return originalSource;
1875   }
1876
1877   /**
1878    * DOCUMENT ME!
1879    * 
1880    * @param up
1881    *          DOCUMENT ME!
1882    */
1883
1884   public void moveSelectedSequences(boolean up)
1885   {
1886     SequenceGroup sg = viewport.getSelectionGroup();
1887
1888     if (sg == null)
1889     {
1890       return;
1891     }
1892     viewport.getAlignment().moveSelectedSequencesByOne(sg,
1893             viewport.getHiddenRepSequences(), up);
1894     alignPanel.paintAlignment(true, false);
1895   }
1896
1897   synchronized void slideSequences(boolean right, int size)
1898   {
1899     List<SequenceI> sg = new ArrayList<>();
1900     if (viewport.cursorMode)
1901     {
1902       sg.add(viewport.getAlignment()
1903               .getSequenceAt(alignPanel.getSeqPanel().seqCanvas.cursorY));
1904     }
1905     else if (viewport.getSelectionGroup() != null
1906             && viewport.getSelectionGroup().getSize() != viewport
1907                     .getAlignment().getHeight())
1908     {
1909       sg = viewport.getSelectionGroup()
1910               .getSequences(viewport.getHiddenRepSequences());
1911     }
1912
1913     if (sg.size() < 1)
1914     {
1915       return;
1916     }
1917
1918     List<SequenceI> invertGroup = new ArrayList<>();
1919
1920     for (SequenceI seq : viewport.getAlignment().getSequences())
1921     {
1922       if (!sg.contains(seq))
1923       {
1924         invertGroup.add(seq);
1925       }
1926     }
1927
1928     SequenceI[] seqs1 = sg.toArray(new SequenceI[0]);
1929
1930     SequenceI[] seqs2 = new SequenceI[invertGroup.size()];
1931     for (int i = 0; i < invertGroup.size(); i++)
1932     {
1933       seqs2[i] = invertGroup.get(i);
1934     }
1935
1936     SlideSequencesCommand ssc;
1937     if (right)
1938     {
1939       ssc = new SlideSequencesCommand("Slide Sequences", seqs2, seqs1, size,
1940               viewport.getGapCharacter());
1941     }
1942     else
1943     {
1944       ssc = new SlideSequencesCommand("Slide Sequences", seqs1, seqs2, size,
1945               viewport.getGapCharacter());
1946     }
1947
1948     int groupAdjustment = 0;
1949     if (ssc.getGapsInsertedBegin() && right)
1950     {
1951       if (viewport.cursorMode)
1952       {
1953         alignPanel.getSeqPanel().moveCursor(size, 0);
1954       }
1955       else
1956       {
1957         groupAdjustment = size;
1958       }
1959     }
1960     else if (!ssc.getGapsInsertedBegin() && !right)
1961     {
1962       if (viewport.cursorMode)
1963       {
1964         alignPanel.getSeqPanel().moveCursor(-size, 0);
1965       }
1966       else
1967       {
1968         groupAdjustment = -size;
1969       }
1970     }
1971
1972     if (groupAdjustment != 0)
1973     {
1974       viewport.getSelectionGroup().setStartRes(
1975               viewport.getSelectionGroup().getStartRes() + groupAdjustment);
1976       viewport.getSelectionGroup().setEndRes(
1977               viewport.getSelectionGroup().getEndRes() + groupAdjustment);
1978     }
1979
1980     /*
1981      * just extend the last slide command if compatible; but not if in
1982      * SplitFrame mode (to ensure all edits are broadcast - JAL-1802)
1983      */
1984     boolean appendHistoryItem = false;
1985     Deque<CommandI> historyList = viewport.getHistoryList();
1986     boolean inSplitFrame = getSplitViewContainer() != null;
1987     if (!inSplitFrame && historyList != null && historyList.size() > 0
1988             && historyList.peek() instanceof SlideSequencesCommand)
1989     {
1990       appendHistoryItem = ssc.appendSlideCommand(
1991               (SlideSequencesCommand) historyList.peek());
1992     }
1993
1994     if (!appendHistoryItem)
1995     {
1996       addHistoryItem(ssc);
1997     }
1998
1999     repaint();
2000   }
2001
2002   /**
2003    * DOCUMENT ME!
2004    * 
2005    * @param e
2006    *          DOCUMENT ME!
2007    */
2008
2009   @Override
2010   protected void copy_actionPerformed()
2011   {
2012     if (viewport.getSelectionGroup() == null)
2013     {
2014       return;
2015     }
2016     // TODO: preserve the ordering of displayed alignment annotation in any
2017     // internal paste (particularly sequence associated annotation)
2018     SequenceI[] seqs = viewport.getSelectionAsNewSequence();
2019     String[] omitHidden = null;
2020
2021     if (viewport.hasHiddenColumns())
2022     {
2023       omitHidden = viewport.getViewAsString(true);
2024     }
2025
2026     String output = new FormatAdapter().formatSequences(FileFormat.Fasta,
2027             seqs, omitHidden, null);
2028
2029     StringSelection ss = new StringSelection(output);
2030
2031     Desktop d = Desktop.getInstance();
2032     try
2033     {
2034       d.internalCopy = true;
2035       // Its really worth setting the clipboard contents
2036       // to empty before setting the large StringSelection!!
2037       Toolkit.getDefaultToolkit().getSystemClipboard()
2038               .setContents(new StringSelection(""), null);
2039
2040       Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss,
2041               Desktop.getInstance());
2042     } catch (OutOfMemoryError er)
2043     {
2044       new OOMWarning("copying region", er);
2045       return;
2046     }
2047
2048     HiddenColumns hiddenColumns = null;
2049     if (viewport.hasHiddenColumns())
2050     {
2051       int hiddenOffset = viewport.getSelectionGroup().getStartRes();
2052       int hiddenCutoff = viewport.getSelectionGroup().getEndRes();
2053
2054       // create new HiddenColumns object with copy of hidden regions
2055       // between startRes and endRes, offset by startRes
2056       hiddenColumns = new HiddenColumns(
2057               viewport.getAlignment().getHiddenColumns(), hiddenOffset,
2058               hiddenCutoff, hiddenOffset);
2059     }
2060
2061     d.jalviewClipboard = new Object[] { seqs,
2062         viewport.getAlignment().getDataset(), hiddenColumns };
2063     setStatus(MessageManager.formatMessage(
2064             "label.copied_sequences_to_clipboard", new Object[]
2065             { Integer.valueOf(seqs.length).toString() }));
2066   }
2067
2068   /**
2069    * DOCUMENT ME!
2070    * 
2071    * @param e
2072    *          DOCUMENT ME!
2073    */
2074
2075   @Override
2076   protected void pasteNew_actionPerformed(ActionEvent e)
2077   {
2078     paste(true);
2079   }
2080
2081   /**
2082    * DOCUMENT ME!
2083    * 
2084    * @param e
2085    *          DOCUMENT ME!
2086    */
2087
2088   @Override
2089   protected void pasteThis_actionPerformed(ActionEvent e)
2090   {
2091     paste(false);
2092   }
2093
2094   /**
2095    * Paste contents of Jalview clipboard
2096    * 
2097    * @param newAlignment
2098    *          true to paste to a new alignment, otherwise add to this.
2099    */
2100
2101   void paste(boolean newAlignment)
2102   {
2103     boolean externalPaste = true;
2104     try
2105     {
2106       Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
2107       Transferable contents = c.getContents(this);
2108
2109       if (contents == null)
2110       {
2111         return;
2112       }
2113
2114       String str;
2115       FileFormatI format;
2116       try
2117       {
2118         str = (String) contents.getTransferData(DataFlavor.stringFlavor);
2119         if (str.length() < 1)
2120         {
2121           return;
2122         }
2123
2124         format = new IdentifyFile().identify(str, DataSourceType.PASTE);
2125
2126       } catch (OutOfMemoryError er)
2127       {
2128         new OOMWarning("Out of memory pasting sequences!!", er);
2129         return;
2130       }
2131
2132       SequenceI[] sequences;
2133       boolean annotationAdded = false;
2134       AlignmentI alignment = null;
2135
2136       Desktop d = Desktop.getInstance();
2137
2138       if (d.jalviewClipboard != null)
2139       {
2140         // The clipboard was filled from within Jalview, we must use the
2141         // sequences
2142         // And dataset from the copied alignment
2143         SequenceI[] newseq = (SequenceI[]) d.jalviewClipboard[0];
2144         // be doubly sure that we create *new* sequence objects.
2145         sequences = new SequenceI[newseq.length];
2146         for (int i = 0; i < newseq.length; i++)
2147         {
2148           sequences[i] = new Sequence(newseq[i]);
2149         }
2150         alignment = new Alignment(sequences);
2151         externalPaste = false;
2152       }
2153       else
2154       {
2155         // parse the clipboard as an alignment.
2156         alignment = new FormatAdapter().readFile(str, DataSourceType.PASTE,
2157                 format);
2158         sequences = alignment.getSequencesArray();
2159       }
2160
2161       int alwidth = 0;
2162       ArrayList<Integer> newGraphGroups = new ArrayList<>();
2163       int fgroup = -1;
2164
2165       if (newAlignment)
2166       {
2167
2168         if (d.jalviewClipboard != null)
2169         {
2170           // dataset is inherited
2171           alignment.setDataset((Alignment) d.jalviewClipboard[1]);
2172         }
2173         else
2174         {
2175           // new dataset is constructed
2176           alignment.setDataset(null);
2177         }
2178         alwidth = alignment.getWidth() + 1;
2179       }
2180       else
2181       {
2182         AlignmentI pastedal = alignment; // preserve pasted alignment object
2183         // Add pasted sequences and dataset into existing alignment.
2184         alignment = viewport.getAlignment();
2185         alwidth = alignment.getWidth() + 1;
2186         // decide if we need to import sequences from an existing dataset
2187         boolean importDs = d.jalviewClipboard != null
2188                 && d.jalviewClipboard[1] != alignment.getDataset();
2189         // importDs==true instructs us to copy over new dataset sequences from
2190         // an existing alignment
2191         Vector<SequenceI> newDs = (importDs) ? new Vector<>() : null; // used to
2192                                                                       // create
2193         // minimum dataset set
2194
2195         for (int i = 0; i < sequences.length; i++)
2196         {
2197           if (importDs)
2198           {
2199             newDs.addElement(null);
2200           }
2201           SequenceI ds = sequences[i].getDatasetSequence(); // null for a simple
2202           // paste
2203           if (importDs && ds != null)
2204           {
2205             if (!newDs.contains(ds))
2206             {
2207               newDs.setElementAt(ds, i);
2208               ds = new Sequence(ds);
2209               // update with new dataset sequence
2210               sequences[i].setDatasetSequence(ds);
2211             }
2212             else
2213             {
2214               ds = sequences[newDs.indexOf(ds)].getDatasetSequence();
2215             }
2216           }
2217           else
2218           {
2219             // copy and derive new dataset sequence
2220             sequences[i] = sequences[i].deriveSequence();
2221             alignment.getDataset()
2222                     .addSequence(sequences[i].getDatasetSequence());
2223             // TODO: avoid creation of duplicate dataset sequences with a
2224             // 'contains' method using SequenceI.equals()/SequenceI.contains()
2225           }
2226           alignment.addSequence(sequences[i]); // merges dataset
2227         }
2228         if (newDs != null)
2229         {
2230           newDs.clear(); // tidy up
2231         }
2232         if (alignment.getAlignmentAnnotation() != null)
2233         {
2234           for (AlignmentAnnotation alan : alignment
2235                   .getAlignmentAnnotation())
2236           {
2237             if (alan.graphGroup > fgroup)
2238             {
2239               fgroup = alan.graphGroup;
2240             }
2241           }
2242         }
2243         if (pastedal.getAlignmentAnnotation() != null)
2244         {
2245           // Add any annotation attached to alignment.
2246           AlignmentAnnotation[] alann = pastedal.getAlignmentAnnotation();
2247           for (int i = 0; i < alann.length; i++)
2248           {
2249             annotationAdded = true;
2250             if (alann[i].sequenceRef == null && !alann[i].autoCalculated)
2251             {
2252               AlignmentAnnotation newann = new AlignmentAnnotation(
2253                       alann[i]);
2254               if (newann.graphGroup > -1)
2255               {
2256                 if (newGraphGroups.size() <= newann.graphGroup
2257                         || newGraphGroups.get(newann.graphGroup) == null)
2258                 {
2259                   for (int q = newGraphGroups
2260                           .size(); q <= newann.graphGroup; q++)
2261                   {
2262                     newGraphGroups.add(q, null);
2263                   }
2264                   newGraphGroups.set(newann.graphGroup,
2265                           Integer.valueOf(++fgroup));
2266                 }
2267                 newann.graphGroup = newGraphGroups.get(newann.graphGroup)
2268                         .intValue();
2269               }
2270
2271               newann.padAnnotation(alwidth);
2272               alignment.addAnnotation(newann);
2273             }
2274           }
2275         }
2276       }
2277       if (!newAlignment)
2278       {
2279         // /////
2280         // ADD HISTORY ITEM
2281         //
2282         addHistoryItem(new EditCommand(
2283                 MessageManager.getString("label.add_sequences"),
2284                 Action.PASTE, sequences, 0, alignment.getWidth(),
2285                 alignment));
2286       }
2287       // Add any annotations attached to sequences
2288       for (int i = 0; i < sequences.length; i++)
2289       {
2290         if (sequences[i].getAnnotation() != null)
2291         {
2292           AlignmentAnnotation newann;
2293           for (int a = 0; a < sequences[i].getAnnotation().length; a++)
2294           {
2295             annotationAdded = true;
2296             newann = sequences[i].getAnnotation()[a];
2297             newann.adjustForAlignment();
2298             newann.padAnnotation(alwidth);
2299             if (newann.graphGroup > -1)
2300             {
2301               if (newann.graphGroup > -1)
2302               {
2303                 if (newGraphGroups.size() <= newann.graphGroup
2304                         || newGraphGroups.get(newann.graphGroup) == null)
2305                 {
2306                   for (int q = newGraphGroups
2307                           .size(); q <= newann.graphGroup; q++)
2308                   {
2309                     newGraphGroups.add(q, null);
2310                   }
2311                   newGraphGroups.set(newann.graphGroup,
2312                           Integer.valueOf(++fgroup));
2313                 }
2314                 newann.graphGroup = newGraphGroups.get(newann.graphGroup)
2315                         .intValue();
2316               }
2317             }
2318             alignment.addAnnotation(sequences[i].getAnnotation()[a]); // annotation
2319             // was
2320             // duplicated
2321             // earlier
2322             alignment.setAnnotationIndex(sequences[i].getAnnotation()[a],
2323                     a);
2324           }
2325         }
2326       }
2327       if (!newAlignment)
2328       {
2329
2330         // propagate alignment changed.
2331         viewport.getRanges().setEndSeq(alignment.getHeight() - 1);
2332         if (annotationAdded)
2333         {
2334           // Duplicate sequence annotation in all views.
2335           AlignmentI[] alview = this.getViewAlignments();
2336           for (int i = 0; i < sequences.length; i++)
2337           {
2338             AlignmentAnnotation sann[] = sequences[i].getAnnotation();
2339             if (sann == null)
2340             {
2341               continue;
2342             }
2343             for (int avnum = 0; avnum < alview.length; avnum++)
2344             {
2345               if (alview[avnum] != alignment)
2346               {
2347                 // duplicate in a view other than the one with input focus
2348                 int avwidth = alview[avnum].getWidth() + 1;
2349                 // this relies on sann being preserved after we
2350                 // modify the sequence's annotation array for each duplication
2351                 for (int a = 0; a < sann.length; a++)
2352                 {
2353                   AlignmentAnnotation newann = new AlignmentAnnotation(
2354                           sann[a]);
2355                   sequences[i].addAlignmentAnnotation(newann);
2356                   newann.padAnnotation(avwidth);
2357                   alview[avnum].addAnnotation(newann); // annotation was
2358                   // duplicated earlier
2359                   // TODO JAL-1145 graphGroups are not updated for sequence
2360                   // annotation added to several views. This may cause
2361                   // strangeness
2362                   alview[avnum].setAnnotationIndex(newann, a);
2363                 }
2364               }
2365             }
2366           }
2367           buildSortByAnnotationScoresMenu();
2368         }
2369         viewport.notifyAlignment();
2370         if (alignPanels != null)
2371         {
2372           for (AlignmentPanel ap : alignPanels)
2373           {
2374             ap.validateAnnotationDimensions(false);
2375           }
2376         }
2377         else
2378         {
2379           alignPanel.validateAnnotationDimensions(false);
2380         }
2381
2382       }
2383       else
2384       {
2385         AlignFrame af = new AlignFrame(alignment, DEFAULT_WIDTH,
2386                 DEFAULT_HEIGHT);
2387         String newtitle = new String("Copied sequences");
2388
2389         if (d.jalviewClipboard != null && d.jalviewClipboard[2] != null)
2390         {
2391           HiddenColumns hc = (HiddenColumns) d.jalviewClipboard[2];
2392           af.viewport.setHiddenColumns(hc);
2393         }
2394
2395         // >>>This is a fix for the moment, until a better solution is
2396         // found!!<<<
2397         af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
2398                 .transferSettings(alignPanel.getSeqPanel().seqCanvas
2399                         .getFeatureRenderer());
2400
2401         // TODO: maintain provenance of an alignment, rather than just make the
2402         // title a concatenation of operations.
2403         if (!externalPaste)
2404         {
2405           if (title.startsWith("Copied sequences"))
2406           {
2407             newtitle = title;
2408           }
2409           else
2410           {
2411             newtitle = newtitle.concat("- from " + title);
2412           }
2413         }
2414         else
2415         {
2416           newtitle = new String("Pasted sequences");
2417         }
2418
2419         Desktop.addInternalFrame(af, newtitle, DEFAULT_WIDTH,
2420                 DEFAULT_HEIGHT);
2421
2422       }
2423
2424     } catch (Exception ex)
2425     {
2426       ex.printStackTrace();
2427       System.out.println("Exception whilst pasting: " + ex);
2428       // could be anything being pasted in here
2429     }
2430
2431   }
2432
2433   @Override
2434   protected void expand_newalign(ActionEvent e)
2435   {
2436     try
2437     {
2438       AlignmentI alignment = AlignmentUtils
2439               .expandContext(getViewport().getAlignment(), -1);
2440       AlignFrame af = new AlignFrame(alignment, DEFAULT_WIDTH,
2441               DEFAULT_HEIGHT);
2442       String newtitle = new String("Flanking alignment");
2443       Desktop d = Desktop.getInstance();
2444       if (d.jalviewClipboard != null && d.jalviewClipboard[2] != null)
2445       {
2446         HiddenColumns hc = (HiddenColumns) d.jalviewClipboard[2];
2447         af.viewport.setHiddenColumns(hc);
2448       }
2449
2450       // >>>This is a fix for the moment, until a better solution is
2451       // found!!<<<
2452       af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
2453               .transferSettings(alignPanel.getSeqPanel().seqCanvas
2454                       .getFeatureRenderer());
2455
2456       // TODO: maintain provenance of an alignment, rather than just make the
2457       // title a concatenation of operations.
2458       {
2459         if (title.startsWith("Copied sequences"))
2460         {
2461           newtitle = title;
2462         }
2463         else
2464         {
2465           newtitle = newtitle.concat("- from " + title);
2466         }
2467       }
2468
2469       Desktop.addInternalFrame(af, newtitle, DEFAULT_WIDTH, DEFAULT_HEIGHT);
2470
2471     } catch (Exception ex)
2472     {
2473       ex.printStackTrace();
2474       System.out.println("Exception whilst pasting: " + ex);
2475       // could be anything being pasted in here
2476     } catch (OutOfMemoryError oom)
2477     {
2478       new OOMWarning("Viewing flanking region of alignment", oom);
2479     }
2480   }
2481
2482   /**
2483    * Action Cut (delete and copy) the selected region
2484    */
2485
2486   @Override
2487   protected void cut_actionPerformed()
2488   {
2489     copy_actionPerformed();
2490     delete_actionPerformed();
2491   }
2492
2493   /**
2494    * Performs menu option to Delete the currently selected region
2495    */
2496
2497   @Override
2498   protected void delete_actionPerformed()
2499   {
2500
2501     SequenceGroup sg = viewport.getSelectionGroup();
2502     if (sg == null)
2503     {
2504       return;
2505     }
2506
2507     Runnable okAction = new Runnable()
2508     {
2509
2510       @Override
2511       public void run()
2512       {
2513         SequenceI[] cut = sg.getSequences()
2514                 .toArray(new SequenceI[sg.getSize()]);
2515
2516         addHistoryItem(new EditCommand(
2517                 MessageManager.getString("label.cut_sequences"), Action.CUT,
2518                 cut, sg.getStartRes(),
2519                 sg.getEndRes() - sg.getStartRes() + 1,
2520                 viewport.getAlignment()));
2521
2522         viewport.setSelectionGroup(null);
2523         viewport.sendSelection();
2524         viewport.getAlignment().deleteGroup(sg);
2525
2526         viewport.notifyAlignment();
2527
2528         if (viewport.getAlignment().getHeight() < 1)
2529         {
2530           try
2531           {
2532             AlignFrame.this.setClosed(true);
2533           } catch (Exception ex)
2534           {
2535           }
2536         } else {
2537           updateAll(null);
2538         }
2539       }
2540     };
2541
2542     /*
2543      * If the cut affects all sequences, prompt for confirmation
2544      */
2545     boolean wholeHeight = sg.getSize() == viewport.getAlignment()
2546             .getHeight();
2547     boolean wholeWidth = (((sg.getEndRes() - sg.getStartRes())
2548             + 1) == viewport.getAlignment().getWidth()) ? true : false;
2549     if (wholeHeight && wholeWidth)
2550     {
2551       JvOptionPane dialog = JvOptionPane
2552               .newOptionDialog(Desktop.getDesktopPane());
2553       dialog.setResponseHandler(0, okAction); // 0 = OK_OPTION
2554       Object[] options = new Object[] {
2555           MessageManager.getString("action.ok"),
2556           MessageManager.getString("action.cancel") };
2557       dialog.showDialog(MessageManager.getString("warn.delete_all"),
2558               MessageManager.getString("label.delete_all"),
2559               JvOptionPane.DEFAULT_OPTION, JvOptionPane.PLAIN_MESSAGE, null,
2560               options, options[0]);
2561     }
2562     else
2563     {
2564       okAction.run();
2565     }
2566   }
2567
2568   /**
2569    * DOCUMENT ME!
2570    * 
2571    * @param e
2572    *          DOCUMENT ME!
2573    */
2574
2575   @Override
2576   protected void deleteGroups_actionPerformed(ActionEvent e)
2577   {
2578     if (avc.deleteGroups())
2579     {
2580       updateAll(viewport.getSequenceSetId());
2581     }
2582   }
2583
2584   private void updateAll(String id)
2585   {
2586     if (id == null)
2587     {
2588       // this will force a non-fast repaint of both the IdPanel and SeqPanel
2589       alignPanel.getIdPanel().getIdCanvas().setNoFastPaint();
2590       alignPanel.getSeqPanel().seqCanvas.setNoFastPaint();
2591       alignPanel.repaint();
2592     }
2593     else
2594     {
2595       // original version
2596       PaintRefresher.Refresh(this, id);
2597       alignPanel.paintAlignment(true, true);
2598     }
2599     alignPanel.updateAnnotation();
2600   }
2601
2602   /**
2603    * DOCUMENT ME!
2604    * 
2605    * @param e
2606    *          DOCUMENT ME!
2607    */
2608
2609   @Override
2610   public void selectAllSequenceMenuItem_actionPerformed(ActionEvent e)
2611   {
2612     alignPanel.selectAllSequences();
2613   }
2614
2615   /**
2616    * DOCUMENT ME!
2617    * 
2618    * @param e
2619    *          DOCUMENT ME!
2620    */
2621
2622   @Override
2623   public void deselectAllSequenceMenuItem_actionPerformed(ActionEvent e)
2624   {
2625     alignPanel.deselectAllSequences();
2626   }
2627
2628   /**
2629    * DOCUMENT ME!
2630    * 
2631    * @param e
2632    *          DOCUMENT ME!
2633    */
2634
2635   @Override
2636   public void invertSequenceMenuItem_actionPerformed(ActionEvent e)
2637   {
2638     SequenceGroup sg = viewport.getSelectionGroup();
2639
2640     if (sg == null)
2641     {
2642       alignPanel.selectAllSequences();
2643
2644       return;
2645     }
2646
2647     for (int i = 0; i < viewport.getAlignment().getSequences().size(); i++)
2648     {
2649       sg.addOrRemove(viewport.getAlignment().getSequenceAt(i), false);
2650     }
2651     // JAL-2034 - should delegate to
2652     // alignPanel to decide if overview needs
2653     // updating.
2654
2655     alignPanel.paintAlignment(true, false);
2656     PaintRefresher.Refresh(alignPanel, viewport.getSequenceSetId());
2657     viewport.sendSelection();
2658   }
2659
2660   @Override
2661   public void invertColSel_actionPerformed(ActionEvent e)
2662   {
2663     viewport.invertColumnSelection();
2664     alignPanel.paintAlignment(true, false);
2665     viewport.sendSelection();
2666   }
2667
2668   /**
2669    * DOCUMENT ME!
2670    * 
2671    * @param e
2672    *          DOCUMENT ME!
2673    */
2674
2675   @Override
2676   public void remove2LeftMenuItem_actionPerformed(ActionEvent e)
2677   {
2678     trimAlignment(true);
2679   }
2680
2681   /**
2682    * DOCUMENT ME!
2683    * 
2684    * @param e
2685    *          DOCUMENT ME!
2686    */
2687
2688   @Override
2689   public void remove2RightMenuItem_actionPerformed(ActionEvent e)
2690   {
2691     trimAlignment(false);
2692   }
2693
2694   void trimAlignment(boolean trimLeft)
2695   {
2696     ColumnSelection colSel = viewport.getColumnSelection();
2697     int column;
2698
2699     if (!colSel.isEmpty())
2700     {
2701       if (trimLeft)
2702       {
2703         column = colSel.getMin();
2704       }
2705       else
2706       {
2707         column = colSel.getMax();
2708       }
2709
2710       SequenceI[] seqs;
2711       if (viewport.getSelectionGroup() != null)
2712       {
2713         seqs = viewport.getSelectionGroup()
2714                 .getSequencesAsArray(viewport.getHiddenRepSequences());
2715       }
2716       else
2717       {
2718         seqs = viewport.getAlignment().getSequencesArray();
2719       }
2720
2721       TrimRegionCommand trimRegion;
2722       if (trimLeft)
2723       {
2724         trimRegion = new TrimRegionCommand("Remove Left", true, seqs,
2725                 column, viewport.getAlignment());
2726         viewport.getRanges().setStartRes(0);
2727       }
2728       else
2729       {
2730         trimRegion = new TrimRegionCommand("Remove Right", false, seqs,
2731                 column, viewport.getAlignment());
2732       }
2733
2734       setStatus(MessageManager.formatMessage("label.removed_columns",
2735               new String[]
2736               { Integer.valueOf(trimRegion.getSize()).toString() }));
2737
2738       addHistoryItem(trimRegion);
2739
2740       for (SequenceGroup sg : viewport.getAlignment().getGroups())
2741       {
2742         if ((trimLeft && !sg.adjustForRemoveLeft(column))
2743                 || (!trimLeft && !sg.adjustForRemoveRight(column)))
2744         {
2745           viewport.getAlignment().deleteGroup(sg);
2746         }
2747       }
2748
2749       viewport.notifyAlignment();
2750
2751     }
2752   }
2753
2754   /**
2755    * DOCUMENT ME!
2756    * 
2757    * @param e
2758    *          DOCUMENT ME!
2759    */
2760
2761   @Override
2762   public void removeGappedColumnMenuItem_actionPerformed(ActionEvent e)
2763   {
2764     int start = 0, end = viewport.getAlignment().getWidth() - 1;
2765
2766     SequenceI[] seqs;
2767     if (viewport.getSelectionGroup() != null)
2768     {
2769       seqs = viewport.getSelectionGroup()
2770               .getSequencesAsArray(viewport.getHiddenRepSequences());
2771       start = viewport.getSelectionGroup().getStartRes();
2772       end = viewport.getSelectionGroup().getEndRes();
2773     }
2774     else
2775     {
2776       seqs = viewport.getAlignment().getSequencesArray();
2777     }
2778
2779     RemoveGapColCommand removeGapCols = new RemoveGapColCommand(
2780             "Remove Gapped Columns", seqs, start, end,
2781             viewport.getAlignment());
2782
2783     addHistoryItem(removeGapCols);
2784
2785     setStatus(MessageManager.formatMessage("label.removed_empty_columns",
2786             new Object[]
2787             { Integer.valueOf(removeGapCols.getSize()).toString() }));
2788
2789     // This is to maintain viewport position on first residue
2790     // of first sequence
2791     SequenceI seq = viewport.getAlignment().getSequenceAt(0);
2792     ViewportRanges ranges = viewport.getRanges();
2793     int startRes = seq.findPosition(ranges.getStartRes());
2794     // ShiftList shifts;
2795     // viewport.getAlignment().removeGaps(shifts=new ShiftList());
2796     // edit.alColumnChanges=shifts.getInverse();
2797     // if (viewport.hasHiddenColumns)
2798     // viewport.getColumnSelection().compensateForEdits(shifts);
2799     ranges.setStartRes(seq.findIndex(startRes) - 1);
2800     viewport.notifyAlignment();
2801
2802
2803   }
2804
2805   /**
2806    * DOCUMENT ME!
2807    * 
2808    * @param e
2809    *          DOCUMENT ME!
2810    */
2811
2812   @Override
2813   public void removeAllGapsMenuItem_actionPerformed(ActionEvent e)
2814   {
2815     int start = 0, end = viewport.getAlignment().getWidth() - 1;
2816
2817     SequenceI[] seqs;
2818     if (viewport.getSelectionGroup() != null)
2819     {
2820       seqs = viewport.getSelectionGroup()
2821               .getSequencesAsArray(viewport.getHiddenRepSequences());
2822       start = viewport.getSelectionGroup().getStartRes();
2823       end = viewport.getSelectionGroup().getEndRes();
2824     }
2825     else
2826     {
2827       seqs = viewport.getAlignment().getSequencesArray();
2828     }
2829
2830     // This is to maintain viewport position on first residue
2831     // of first sequence
2832     SequenceI seq = viewport.getAlignment().getSequenceAt(0);
2833     int startRes = seq.findPosition(viewport.getRanges().getStartRes());
2834
2835     addHistoryItem(new RemoveGapsCommand("Remove Gaps", seqs, start, end,
2836             viewport.getAlignment()));
2837
2838     viewport.getRanges().setStartRes(seq.findIndex(startRes) - 1);
2839     viewport.notifyAlignment();
2840
2841   }
2842
2843   /**
2844    * DOCUMENT ME!
2845    * 
2846    * @param e
2847    *          DOCUMENT ME!
2848    */
2849
2850   @Override
2851   public void padGapsMenuitem_actionPerformed(ActionEvent e)
2852   {
2853     viewport.setPadGaps(padGapsMenuitem.isSelected());
2854     viewport.notifyAlignment();
2855
2856   }
2857
2858   /**
2859    * DOCUMENT ME!
2860    * 
2861    * @param e
2862    *          DOCUMENT ME!
2863    */
2864
2865   @Override
2866   public void findMenuItem_actionPerformed(ActionEvent e)
2867   {
2868     new Finder();
2869   }
2870
2871   /**
2872    * Create a new view of the current alignment.
2873    */
2874
2875   @Override
2876   public void newView_actionPerformed(ActionEvent e)
2877   {
2878     newView(null, true);
2879   }
2880
2881   /**
2882    * Creates and shows a new view of the current alignment.
2883    * 
2884    * @param viewTitle
2885    *          title of newly created view; if null, one will be generated
2886    * @param copyAnnotation
2887    *          if true then duplicate all annnotation, groups and settings
2888    * @return new alignment panel, already displayed.
2889    */
2890
2891   public AlignmentPanel newView(String viewTitle, boolean copyAnnotation)
2892   {
2893     /*
2894      * Create a new AlignmentPanel (with its own, new Viewport)
2895      */
2896     AlignmentPanel newap = new jalview.project.Jalview2XML()
2897             .copyAlignPanel(alignPanel);
2898     if (!copyAnnotation)
2899     {
2900       /*
2901        * remove all groups and annotation except for the automatic stuff
2902        */
2903       newap.av.getAlignment().deleteAllGroups();
2904       newap.av.getAlignment().deleteAllAnnotations(false);
2905     }
2906
2907     newap.av.setGatherViewsHere(false);
2908
2909     if (viewport.getViewName() == null)
2910     {
2911       viewport.setViewName(
2912               MessageManager.getString("label.view_name_original"));
2913     }
2914
2915     /*
2916      * Views share the same edits undo and redo stacks
2917      */
2918     newap.av.setHistoryList(viewport.getHistoryList());
2919     newap.av.setRedoList(viewport.getRedoList());
2920
2921     /*
2922      * copy any visualisation settings that are not saved in the project
2923      */
2924     newap.av.setColourAppliesToAllGroups(
2925             viewport.getColourAppliesToAllGroups());
2926
2927     /*
2928      * Views share the same mappings; need to deregister any new mappings
2929      * created by copyAlignPanel, and register the new reference to the shared
2930      * mappings
2931      */
2932     newap.av.replaceMappings(viewport.getAlignment());
2933
2934     /*
2935      * start up cDNA consensus (if applicable) now mappings are in place
2936      */
2937     if (newap.av.initComplementConsensus())
2938     {
2939       newap.refresh(true); // adjust layout of annotations
2940     }
2941
2942     newap.av.setViewName(getNewViewName(viewTitle));
2943
2944     addAlignmentPanel(newap, true);
2945     newap.alignmentChanged();
2946
2947     if (alignPanels.size() == 2)
2948     {
2949       viewport.setGatherViewsHere(true);
2950     }
2951     tabbedPane.setSelectedIndex(tabbedPane.getTabCount() - 1);
2952     return newap;
2953   }
2954
2955   /**
2956    * Make a new name for the view, ensuring it is unique within the current
2957    * sequenceSetId. (This used to be essential for Jalview Project archives, but
2958    * these now use viewId. Unique view names are still desirable for usability.)
2959    * 
2960    * @param viewTitle
2961    * @return
2962    */
2963
2964   protected String getNewViewName(String viewTitle)
2965   {
2966     int index = Desktop.getViewCount(viewport.getSequenceSetId());
2967     boolean addFirstIndex = false;
2968     if (viewTitle == null || viewTitle.trim().length() == 0)
2969     {
2970       viewTitle = MessageManager.getString("action.view");
2971       addFirstIndex = true;
2972     }
2973     else
2974     {
2975       index = 1;// we count from 1 if given a specific name
2976     }
2977     String newViewName = viewTitle + ((addFirstIndex) ? " " + index : "");
2978
2979     List<Component> comps = PaintRefresher.components
2980             .get(viewport.getSequenceSetId());
2981
2982     List<String> existingNames = getExistingViewNames(comps);
2983
2984     while (existingNames.contains(newViewName))
2985     {
2986       newViewName = viewTitle + " " + (++index);
2987     }
2988     return newViewName;
2989   }
2990
2991   /**
2992    * Returns a list of distinct view names found in the given list of
2993    * components. View names are held on the viewport of an AlignmentPanel.
2994    * 
2995    * @param comps
2996    * @return
2997    */
2998
2999   protected List<String> getExistingViewNames(List<Component> comps)
3000   {
3001     List<String> existingNames = new ArrayList<>();
3002     for (Component comp : comps)
3003     {
3004       if (comp instanceof AlignmentPanel)
3005       {
3006         AlignmentPanel ap = (AlignmentPanel) comp;
3007         if (!existingNames.contains(ap.av.getViewName()))
3008         {
3009           existingNames.add(ap.av.getViewName());
3010         }
3011       }
3012     }
3013     return existingNames;
3014   }
3015
3016   /**
3017    * Explode tabbed views into separate windows.
3018    */
3019
3020   @Override
3021   public void expandViews_actionPerformed(ActionEvent e)
3022   {
3023     Desktop.explodeViews(this);
3024   }
3025
3026   /**
3027    * Gather views in separate windows back into a tabbed presentation.
3028    */
3029
3030   @Override
3031   public void gatherViews_actionPerformed(ActionEvent e)
3032   {
3033     Desktop.getInstance().gatherViews(this);
3034   }
3035
3036   /**
3037    * DOCUMENT ME!
3038    * 
3039    * @param e
3040    *          DOCUMENT ME!
3041    */
3042
3043   @Override
3044   public void font_actionPerformed(ActionEvent e)
3045   {
3046     new FontChooser(alignPanel);
3047   }
3048
3049   /**
3050    * DOCUMENT ME!
3051    * 
3052    * @param e
3053    *          DOCUMENT ME!
3054    */
3055
3056   @Override
3057   protected void seqLimit_actionPerformed(ActionEvent e)
3058   {
3059     viewport.setShowJVSuffix(seqLimits.isSelected());
3060
3061     alignPanel.getIdPanel().getIdCanvas()
3062             .setPreferredSize(alignPanel.calculateIdWidth());
3063     alignPanel.paintAlignment(true, false);
3064   }
3065
3066   @Override
3067   public void idRightAlign_actionPerformed(ActionEvent e)
3068   {
3069     viewport.setRightAlignIds(idRightAlign.isSelected());
3070     alignPanel.paintAlignment(false, false);
3071   }
3072
3073   @Override
3074   public void centreColumnLabels_actionPerformed(ActionEvent e)
3075   {
3076     viewport.setCentreColumnLabels(centreColumnLabelsMenuItem.getState());
3077     alignPanel.paintAlignment(false, false);
3078   }
3079
3080   /*
3081    * (non-Javadoc)
3082    * 
3083    * @see jalview.jbgui.GAlignFrame#followHighlight_actionPerformed()
3084    */
3085
3086   @Override
3087   protected void followHighlight_actionPerformed()
3088   {
3089     /*
3090      * Set the 'follow' flag on the Viewport (and scroll to position if now
3091      * true).
3092      */
3093     final boolean state = this.followHighlightMenuItem.getState();
3094     viewport.setFollowHighlight(state);
3095     if (state)
3096     {
3097       alignPanel.scrollToPosition(viewport.getSearchResults());
3098     }
3099   }
3100
3101   /**
3102    * DOCUMENT ME!
3103    * 
3104    * @param e
3105    *          DOCUMENT ME!
3106    */
3107
3108   @Override
3109   protected void colourTextMenuItem_actionPerformed(ActionEvent e)
3110   {
3111     viewport.setColourText(colourTextMenuItem.isSelected());
3112     alignPanel.paintAlignment(false, false);
3113   }
3114
3115   /**
3116    * DOCUMENT ME!
3117    * 
3118    * @param e
3119    *          DOCUMENT ME!
3120    */
3121
3122   @Override
3123   public void wrapMenuItem_actionPerformed(ActionEvent e)
3124   {
3125     scaleAbove.setVisible(wrapMenuItem.isSelected());
3126     scaleLeft.setVisible(wrapMenuItem.isSelected());
3127     scaleRight.setVisible(wrapMenuItem.isSelected());
3128     viewport.setWrapAlignment(wrapMenuItem.isSelected());
3129     alignPanel.updateLayout();
3130   }
3131
3132   @Override
3133   public void showAllSeqs_actionPerformed(ActionEvent e)
3134   {
3135     viewport.showAllHiddenSeqs();
3136   }
3137
3138   @Override
3139   public void showAllColumns_actionPerformed(ActionEvent e)
3140   {
3141     viewport.showAllHiddenColumns();
3142     alignPanel.paintAlignment(true, true);
3143     viewport.sendSelection();
3144   }
3145
3146   @Override
3147   public void hideSelSequences_actionPerformed(ActionEvent e)
3148   {
3149     viewport.hideAllSelectedSeqs();
3150   }
3151
3152   /**
3153    * called by key handler and the hide all/show all menu items
3154    * 
3155    * @param toggleSeqs
3156    * @param toggleCols
3157    */
3158
3159   protected void toggleHiddenRegions(boolean toggleSeqs, boolean toggleCols)
3160   {
3161
3162     boolean hide = false;
3163     SequenceGroup sg = viewport.getSelectionGroup();
3164     if (!toggleSeqs && !toggleCols)
3165     {
3166       // Hide everything by the current selection - this is a hack - we do the
3167       // invert and then hide
3168       // first check that there will be visible columns after the invert.
3169       if (viewport.hasSelectedColumns() || (sg != null && sg.getSize() > 0
3170               && sg.getStartRes() <= sg.getEndRes()))
3171       {
3172         // now invert the sequence set, if required - empty selection implies
3173         // that no hiding is required.
3174         if (sg != null)
3175         {
3176           invertSequenceMenuItem_actionPerformed(null);
3177           sg = viewport.getSelectionGroup();
3178           toggleSeqs = true;
3179
3180         }
3181         viewport.expandColSelection(sg, true);
3182         // finally invert the column selection and get the new sequence
3183         // selection.
3184         invertColSel_actionPerformed(null);
3185         toggleCols = true;
3186       }
3187     }
3188
3189     if (toggleSeqs)
3190     {
3191       if (sg != null && sg.getSize() != viewport.getAlignment().getHeight())
3192       {
3193         hideSelSequences_actionPerformed(null);
3194         hide = true;
3195       }
3196       else if (!(toggleCols && viewport.hasSelectedColumns()))
3197       {
3198         showAllSeqs_actionPerformed(null);
3199       }
3200     }
3201
3202     if (toggleCols)
3203     {
3204       if (viewport.hasSelectedColumns())
3205       {
3206         hideSelColumns_actionPerformed(null);
3207         if (!toggleSeqs)
3208         {
3209           viewport.setSelectionGroup(sg);
3210         }
3211       }
3212       else if (!hide)
3213       {
3214         showAllColumns_actionPerformed(null);
3215       }
3216     }
3217   }
3218
3219   /*
3220    * (non-Javadoc)
3221    * 
3222    * @see
3223    * jalview.jbgui.GAlignFrame#hideAllButSelection_actionPerformed(java.awt.
3224    * event.ActionEvent)
3225    */
3226
3227   @Override
3228   public void hideAllButSelection_actionPerformed(ActionEvent e)
3229   {
3230     toggleHiddenRegions(false, false);
3231     viewport.sendSelection();
3232   }
3233
3234   /*
3235    * (non-Javadoc)
3236    * 
3237    * @see
3238    * jalview.jbgui.GAlignFrame#hideAllSelection_actionPerformed(java.awt.event
3239    * .ActionEvent)
3240    */
3241
3242   @Override
3243   public void hideAllSelection_actionPerformed(ActionEvent e)
3244   {
3245     SequenceGroup sg = viewport.getSelectionGroup();
3246     viewport.expandColSelection(sg, false);
3247     viewport.hideAllSelectedSeqs();
3248     viewport.hideSelectedColumns();
3249     alignPanel.updateLayout();
3250     alignPanel.paintAlignment(true, true);
3251     viewport.sendSelection();
3252   }
3253
3254   /*
3255    * (non-Javadoc)
3256    * 
3257    * @see
3258    * jalview.jbgui.GAlignFrame#showAllhidden_actionPerformed(java.awt.event.
3259    * ActionEvent)
3260    */
3261
3262   @Override
3263   public void showAllhidden_actionPerformed(ActionEvent e)
3264   {
3265     viewport.showAllHiddenColumns();
3266     viewport.showAllHiddenSeqs();
3267     alignPanel.paintAlignment(true, true);
3268     viewport.sendSelection();
3269   }
3270
3271   @Override
3272   public void hideSelColumns_actionPerformed(ActionEvent e)
3273   {
3274     viewport.hideSelectedColumns();
3275     alignPanel.updateLayout();
3276     alignPanel.paintAlignment(true, true);
3277     viewport.sendSelection();
3278   }
3279
3280   @Override
3281   public void hiddenMarkers_actionPerformed(ActionEvent e)
3282   {
3283     viewport.setShowHiddenMarkers(hiddenMarkers.isSelected());
3284     repaint();
3285   }
3286
3287   /**
3288    * DOCUMENT ME!
3289    * 
3290    * @param e
3291    *          DOCUMENT ME!
3292    */
3293
3294   @Override
3295   protected void scaleAbove_actionPerformed(ActionEvent e)
3296   {
3297     viewport.setScaleAboveWrapped(scaleAbove.isSelected());
3298     alignPanel.updateLayout();
3299     alignPanel.paintAlignment(true, false);
3300   }
3301
3302   /**
3303    * DOCUMENT ME!
3304    * 
3305    * @param e
3306    *          DOCUMENT ME!
3307    */
3308
3309   @Override
3310   protected void scaleLeft_actionPerformed(ActionEvent e)
3311   {
3312     viewport.setScaleLeftWrapped(scaleLeft.isSelected());
3313     alignPanel.updateLayout();
3314     alignPanel.paintAlignment(true, false);
3315   }
3316
3317   /**
3318    * DOCUMENT ME!
3319    * 
3320    * @param e
3321    *          DOCUMENT ME!
3322    */
3323
3324   @Override
3325   protected void scaleRight_actionPerformed(ActionEvent e)
3326   {
3327     viewport.setScaleRightWrapped(scaleRight.isSelected());
3328     alignPanel.updateLayout();
3329     alignPanel.paintAlignment(true, false);
3330   }
3331
3332   /**
3333    * DOCUMENT ME!
3334    * 
3335    * @param e
3336    *          DOCUMENT ME!
3337    */
3338
3339   @Override
3340   public void viewBoxesMenuItem_actionPerformed(ActionEvent e)
3341   {
3342     viewport.setShowBoxes(viewBoxesMenuItem.isSelected());
3343     alignPanel.paintAlignment(false, false);
3344   }
3345
3346   /**
3347    * DOCUMENT ME!
3348    * 
3349    * @param e
3350    *          DOCUMENT ME!
3351    */
3352
3353   @Override
3354   public void viewTextMenuItem_actionPerformed(ActionEvent e)
3355   {
3356     viewport.setShowText(viewTextMenuItem.isSelected());
3357     alignPanel.paintAlignment(false, false);
3358   }
3359
3360   /**
3361    * DOCUMENT ME!
3362    * 
3363    * @param e
3364    *          DOCUMENT ME!
3365    */
3366
3367   @Override
3368   protected void renderGapsMenuItem_actionPerformed(ActionEvent e)
3369   {
3370     viewport.setRenderGaps(renderGapsMenuItem.isSelected());
3371     alignPanel.paintAlignment(false, false);
3372   }
3373
3374   public FeatureSettings featureSettings;
3375
3376   @Override
3377   public FeatureSettingsControllerI getFeatureSettingsUI()
3378   {
3379     return featureSettings;
3380   }
3381
3382   @Override
3383   public void featureSettings_actionPerformed(ActionEvent e)
3384   {
3385     showFeatureSettingsUI();
3386   }
3387
3388   @Override
3389   public FeatureSettingsControllerI showFeatureSettingsUI()
3390   {
3391     if (featureSettings != null)
3392     {
3393       featureSettings.closeOldSettings();
3394       featureSettings = null;
3395     }
3396     if (!showSeqFeatures.isSelected())
3397     {
3398       // make sure features are actually displayed
3399       showSeqFeatures.setSelected(true);
3400       showSeqFeatures_actionPerformed(null);
3401     }
3402     featureSettings = new FeatureSettings(this);
3403     return featureSettings;
3404   }
3405
3406   /**
3407    * Set or clear 'Show Sequence Features'
3408    * 
3409    * @param evt
3410    *          DOCUMENT ME!
3411    */
3412
3413   @Override
3414   public void showSeqFeatures_actionPerformed(ActionEvent evt)
3415   {
3416     viewport.setShowSequenceFeatures(showSeqFeatures.isSelected());
3417     alignPanel.paintAlignment(true, true);
3418   }
3419
3420   /**
3421    * Action on toggle of the 'Show annotations' menu item. This shows or hides
3422    * the annotations panel as a whole.
3423    * 
3424    * The options to show/hide all annotations should be enabled when the panel
3425    * is shown, and disabled when the panel is hidden.
3426    * 
3427    * @param e
3428    */
3429
3430   @Override
3431   public void annotationPanelMenuItem_actionPerformed(ActionEvent e)
3432   {
3433     final boolean setVisible = annotationPanelMenuItem.isSelected();
3434     viewport.setShowAnnotation(setVisible);
3435     syncAnnotationMenuItems(setVisible);
3436     alignPanel.updateLayout();
3437     repaint();
3438     SwingUtilities.invokeLater(new Runnable() {
3439
3440       @Override
3441       public void run()
3442       {
3443         alignPanel.updateScrollBarsFromRanges();
3444       }
3445       
3446     });
3447   }
3448
3449   private void syncAnnotationMenuItems(boolean setVisible)
3450   {
3451     showAllSeqAnnotations.setEnabled(setVisible);
3452     hideAllSeqAnnotations.setEnabled(setVisible);
3453     showAllAlAnnotations.setEnabled(setVisible);
3454     hideAllAlAnnotations.setEnabled(setVisible);
3455   }
3456
3457   @Override
3458   public void alignmentProperties()
3459   {
3460     JComponent pane;
3461     StringBuffer contents = new AlignmentProperties(viewport.getAlignment())
3462
3463             .formatAsHtml();
3464     String content = MessageManager.formatMessage("label.html_content",
3465             new Object[]
3466             { contents.toString() });
3467     contents = null;
3468
3469     if (Platform.isJS())
3470     {
3471       JLabel textLabel = new JLabel();
3472       textLabel.setText(content);
3473       textLabel.setBackground(Color.WHITE);
3474
3475       pane = new JPanel(new BorderLayout());
3476       ((JPanel) pane).setOpaque(true);
3477       pane.setBackground(Color.WHITE);
3478       ((JPanel) pane).add(textLabel, BorderLayout.NORTH);
3479     }
3480     else
3481     /**
3482      * Java only
3483      * 
3484      * @j2sIgnore
3485      */
3486     {
3487       JEditorPane editPane = new JEditorPane("text/html", "");
3488       editPane.setEditable(false);
3489       editPane.setText(content);
3490       pane = editPane;
3491     }
3492
3493     JInternalFrame frame = new JInternalFrame();
3494
3495     frame.getContentPane().add(new JScrollPane(pane));
3496
3497     Desktop.addInternalFrame(frame, MessageManager
3498             .formatMessage("label.alignment_properties", new Object[]
3499             { getTitle() }), 500, 400);
3500   }
3501
3502   /**
3503    * DOCUMENT ME!
3504    * 
3505    * @param e
3506    *          DOCUMENT ME!
3507    */
3508
3509   @Override
3510   public void overviewMenuItem_actionPerformed(ActionEvent e)
3511   {
3512     if (alignPanel.overviewPanel != null)
3513     {
3514       return;
3515     }
3516
3517     JInternalFrame frame = new JInternalFrame();
3518
3519     // BH 2019.07.26 we allow for an embedded
3520     // undecorated overview with defined size
3521     frame.setName(Platform.getAppID("overview"));
3522     //
3523     Dimension dim = Platform.getDimIfEmbedded(frame, -1, -1);
3524     if (dim != null && dim.width == 0)
3525     {
3526       dim = null; // hidden, not embedded
3527     }
3528     OverviewPanel overview = new OverviewPanel(alignPanel, dim);
3529
3530     frame.setContentPane(overview);
3531     if (dim == null)
3532     {
3533       dim = new Dimension();
3534       // was frame.getSize(), but that is 0,0 at this point;
3535     }
3536     else
3537     {
3538       // we are imbedding, and so we have an undecorated frame
3539       // and we can set the the frame dimensions accordingly.
3540     }
3541     // allowing for unresizable option using, style="resize:none"
3542     boolean resizable = (Platform.getEmbeddedAttribute(frame,
3543             "resize") != "none");
3544     Desktop.addInternalFrame(frame, MessageManager
3545             .formatMessage("label.overview_params", new Object[]
3546             { this.getTitle() }), Desktop.FRAME_MAKE_VISIBLE, dim.width,
3547             dim.height, resizable, Desktop.FRAME_ALLOW_ANY_SIZE);
3548     frame.pack();
3549     frame.setLayer(JLayeredPane.PALETTE_LAYER);
3550     frame.addInternalFrameListener(
3551             new javax.swing.event.InternalFrameAdapter()
3552             {
3553
3554               @Override
3555               public void internalFrameClosed(
3556                       javax.swing.event.InternalFrameEvent evt)
3557               {
3558                 overview.dispose();
3559                 alignPanel.setOverviewPanel(null);
3560               }
3561             });
3562     if (getKeyListeners().length > 0)
3563     {
3564       frame.addKeyListener(getKeyListeners()[0]);
3565     }
3566
3567     alignPanel.setOverviewPanel(overview);
3568   }
3569
3570   @Override
3571   public void textColour_actionPerformed()
3572   {
3573     new TextColourChooser().chooseColour(alignPanel, null);
3574   }
3575
3576   /*
3577    * public void covariationColour_actionPerformed() {
3578    * changeColour(new
3579    * CovariationColourScheme(viewport.getAlignment().getAlignmentAnnotation
3580    * ()[0])); }
3581    */
3582
3583   @Override
3584   public void annotationColour_actionPerformed()
3585   {
3586     new AnnotationColourChooser(viewport, alignPanel);
3587   }
3588
3589   @Override
3590   public void annotationColumn_actionPerformed(ActionEvent e)
3591   {
3592     new AnnotationColumnChooser(viewport, alignPanel);
3593   }
3594
3595   /**
3596    * Action on the user checking or unchecking the option to apply the selected
3597    * colour scheme to all groups. If unchecked, groups may have their own
3598    * independent colour schemes.
3599    * 
3600    * @param selected
3601    */
3602
3603   @Override
3604   public void applyToAllGroups_actionPerformed(boolean selected)
3605   {
3606     viewport.setColourAppliesToAllGroups(selected);
3607   }
3608
3609   /**
3610    * Action on user selecting a colour from the colour menu
3611    * 
3612    * @param name
3613    *          the name (not the menu item label!) of the colour scheme
3614    */
3615
3616   @Override
3617   public void changeColour_actionPerformed(String name)
3618   {
3619     /*
3620      * 'User Defined' opens a panel to configure or load a
3621      * user-defined colour scheme
3622      */
3623     if (ResidueColourScheme.USER_DEFINED_MENU.equals(name))
3624     {
3625       new UserDefinedColours(alignPanel);
3626       return;
3627     }
3628
3629     /*
3630      * otherwise set the chosen colour scheme (or null for 'None')
3631      */
3632     ColourSchemeI cs = ColourSchemes.getInstance().getColourScheme(name,
3633             viewport, viewport.getAlignment(),
3634             viewport.getHiddenRepSequences());
3635     changeColour(cs);
3636   }
3637
3638   /**
3639    * Actions on setting or changing the alignment colour scheme
3640    * 
3641    * @param cs
3642    */
3643
3644   @Override
3645   public void changeColour(ColourSchemeI cs)
3646   {
3647     // TODO: pull up to controller method
3648     ColourMenuHelper.setColourSelected(colourMenu, cs);
3649
3650     viewport.setGlobalColourScheme(cs);
3651
3652     alignPanel.paintAlignment(true, true);
3653   }
3654
3655   /**
3656    * Show the PID threshold slider panel
3657    */
3658
3659   @Override
3660   protected void modifyPID_actionPerformed()
3661   {
3662     SliderPanel.setPIDSliderSource(alignPanel, viewport.getResidueShading(),
3663             alignPanel.getViewName());
3664     SliderPanel.showPIDSlider();
3665   }
3666
3667   /**
3668    * Show the Conservation slider panel
3669    */
3670
3671   @Override
3672   protected void modifyConservation_actionPerformed()
3673   {
3674     SliderPanel.setConservationSlider(alignPanel,
3675             viewport.getResidueShading(), alignPanel.getViewName());
3676     SliderPanel.showConservationSlider();
3677   }
3678
3679   /**
3680    * Action on selecting or deselecting (Colour) By Conservation
3681    */
3682
3683   @Override
3684   public void conservationMenuItem_actionPerformed(boolean selected)
3685   {
3686     modifyConservation.setEnabled(selected);
3687     viewport.setConservationSelected(selected);
3688     viewport.getResidueShading().setConservationApplied(selected);
3689
3690     changeColour(viewport.getGlobalColourScheme());
3691     if (selected)
3692     {
3693       modifyConservation_actionPerformed();
3694     }
3695     else
3696     {
3697       SliderPanel.hideConservationSlider();
3698     }
3699   }
3700
3701   /**
3702    * Action on selecting or deselecting (Colour) Above PID Threshold
3703    */
3704
3705   @Override
3706   public void abovePIDThreshold_actionPerformed(boolean selected)
3707   {
3708     modifyPID.setEnabled(selected);
3709     viewport.setAbovePIDThreshold(selected);
3710     if (!selected)
3711     {
3712       viewport.getResidueShading().setThreshold(0,
3713               viewport.isIgnoreGapsConsensus());
3714     }
3715
3716     changeColour(viewport.getGlobalColourScheme());
3717     if (selected)
3718     {
3719       modifyPID_actionPerformed();
3720     }
3721     else
3722     {
3723       SliderPanel.hidePIDSlider();
3724     }
3725   }
3726
3727   /**
3728    * DOCUMENT ME!
3729    * 
3730    * @param e
3731    *          DOCUMENT ME!
3732    */
3733
3734   @Override
3735   public void sortPairwiseMenuItem_actionPerformed(ActionEvent e)
3736   {
3737     SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
3738     AlignmentSorter.sortByPID(viewport.getAlignment(),
3739             viewport.getAlignment().getSequenceAt(0));
3740     addHistoryItem(new OrderCommand("Pairwise Sort", oldOrder,
3741             viewport.getAlignment()));
3742     alignPanel.paintAlignment(true, false);
3743   }
3744
3745   /**
3746    * DOCUMENT ME!
3747    * 
3748    * @param e
3749    *          DOCUMENT ME!
3750    */
3751
3752   @Override
3753   public void sortIDMenuItem_actionPerformed(ActionEvent e)
3754   {
3755     SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
3756     AlignmentSorter.sortByID(viewport.getAlignment());
3757     addHistoryItem(
3758             new OrderCommand("ID Sort", oldOrder, viewport.getAlignment()));
3759     alignPanel.paintAlignment(true, false);
3760   }
3761
3762   /**
3763    * DOCUMENT ME!
3764    * 
3765    * @param e
3766    *          DOCUMENT ME!
3767    */
3768
3769   @Override
3770   public void sortLengthMenuItem_actionPerformed(ActionEvent e)
3771   {
3772     SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
3773     AlignmentSorter.sortByLength(viewport.getAlignment());
3774     addHistoryItem(new OrderCommand("Length Sort", oldOrder,
3775             viewport.getAlignment()));
3776     alignPanel.paintAlignment(true, false);
3777   }
3778
3779   /**
3780    * DOCUMENT ME!
3781    * 
3782    * @param e
3783    *          DOCUMENT ME!
3784    */
3785
3786   @Override
3787   public void sortGroupMenuItem_actionPerformed(ActionEvent e)
3788   {
3789     SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
3790     AlignmentSorter.sortByGroup(viewport.getAlignment());
3791     addHistoryItem(new OrderCommand("Group Sort", oldOrder,
3792             viewport.getAlignment()));
3793
3794     alignPanel.paintAlignment(true, false);
3795   }
3796
3797   /**
3798    * DOCUMENT ME!
3799    * 
3800    * @param e
3801    *          DOCUMENT ME!
3802    */
3803
3804   @Override
3805   public void removeRedundancyMenuItem_actionPerformed(ActionEvent e)
3806   {
3807     new RedundancyPanel(alignPanel, this);
3808   }
3809
3810   /**
3811    * DOCUMENT ME!
3812    * 
3813    * @param e
3814    *          DOCUMENT ME!
3815    */
3816
3817   @Override
3818   public void pairwiseAlignmentMenuItem_actionPerformed(ActionEvent e)
3819   {
3820     if ((viewport.getSelectionGroup() == null)
3821             || (viewport.getSelectionGroup().getSize() < 2))
3822     {
3823       JvOptionPane.showInternalMessageDialog(this,
3824               MessageManager.getString(
3825                       "label.you_must_select_least_two_sequences"),
3826               MessageManager.getString("label.invalid_selection"),
3827               JvOptionPane.WARNING_MESSAGE);
3828     }
3829     else
3830     {
3831       JInternalFrame frame = new JInternalFrame();
3832       frame.setContentPane(new PairwiseAlignPanel(viewport));
3833       Desktop.addInternalFrame(frame,
3834               MessageManager.getString("action.pairwise_alignment"), 600,
3835               500);
3836     }
3837   }
3838
3839   @Override
3840   public void autoCalculate_actionPerformed(ActionEvent e)
3841   {
3842     viewport.setAutoCalculateConsensusAndConservation(
3843             autoCalculate.isSelected());
3844     if (viewport.getAutoCalculateConsensusAndConservation())
3845     // ??
3846     // viewport.autoCalculateConsensus = autoCalculate.isSelected();
3847     // if (viewport.autoCalculateConsensus)
3848     {
3849       viewport.notifyAlignment();
3850     }
3851   }
3852
3853   @Override
3854   public void sortByTreeOption_actionPerformed(ActionEvent e)
3855   {
3856     viewport.sortByTree = sortByTree.isSelected();
3857   }
3858
3859   @Override
3860   protected void listenToViewSelections_actionPerformed(ActionEvent e)
3861   {
3862     viewport.followSelection = listenToViewSelections.isSelected();
3863   }
3864
3865   /**
3866    * Constructs a tree panel and adds it to the desktop
3867    * 
3868    * @param type
3869    *          tree type (NJ or AV)
3870    * @param modelName
3871    *          name of score model used to compute the tree
3872    * @param options
3873    *          parameters for the distance or similarity calculation
3874    */
3875
3876   void newTreePanel(String type, String modelName,
3877           SimilarityParamsI options)
3878   {
3879     String frameTitle = "";
3880     TreePanel tp;
3881
3882     boolean onSelection = false;
3883     if (viewport.getSelectionGroup() != null
3884             && viewport.getSelectionGroup().getSize() > 0)
3885     {
3886       SequenceGroup sg = viewport.getSelectionGroup();
3887
3888       /* Decide if the selection is a column region */
3889       for (SequenceI _s : sg.getSequences())
3890       {
3891         if (_s.getLength() < sg.getEndRes())
3892         {
3893           JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
3894                   MessageManager.getString(
3895                           "label.selected_region_to_tree_may_only_contain_residues_or_gaps"),
3896                   MessageManager.getString(
3897                           "label.sequences_selection_not_aligned"),
3898                   JvOptionPane.WARNING_MESSAGE);
3899
3900           return;
3901         }
3902       }
3903       onSelection = true;
3904     }
3905     else
3906     {
3907       if (viewport.getAlignment().getHeight() < 2)
3908       {
3909         return;
3910       }
3911     }
3912
3913     tp = new TreePanel(alignPanel, type, modelName, options);
3914     frameTitle = tp.getPanelTitle() + (onSelection ? " on region" : "");
3915
3916     frameTitle += " from ";
3917
3918     if (viewport.getViewName() != null)
3919     {
3920       frameTitle += viewport.getViewName() + " of ";
3921     }
3922
3923     frameTitle += this.title;
3924
3925     Dimension dim = Platform.getDimIfEmbedded(tp, 600, 500);
3926     Desktop.addInternalFrame(tp, frameTitle, dim.width, dim.height);
3927   }
3928
3929   /**
3930    * DOCUMENT ME!
3931    * 
3932    * @param title
3933    *          DOCUMENT ME!
3934    * @param order
3935    *          DOCUMENT ME!
3936    */
3937
3938   public void addSortByOrderMenuItem(String title,
3939           final AlignmentOrder order)
3940   {
3941     final JMenuItem item = new JMenuItem(MessageManager
3942             .formatMessage("action.by_title_param", new Object[]
3943             { title }));
3944     sort.add(item);
3945     item.addActionListener(new java.awt.event.ActionListener()
3946     {
3947
3948       @Override
3949       public void actionPerformed(ActionEvent e)
3950       {
3951         SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
3952
3953         // TODO: JBPNote - have to map order entries to curent SequenceI
3954         // pointers
3955         AlignmentSorter.sortBy(viewport.getAlignment(), order);
3956
3957         addHistoryItem(new OrderCommand(order.getName(), oldOrder,
3958                 viewport.getAlignment()));
3959
3960         alignPanel.paintAlignment(true, false);
3961       }
3962     });
3963   }
3964
3965   /**
3966    * Add a new sort by annotation score menu item
3967    * 
3968    * @param sort
3969    *          the menu to add the option to
3970    * @param scoreLabel
3971    *          the label used to retrieve scores for each sequence on the
3972    *          alignment
3973    */
3974
3975   public void addSortByAnnotScoreMenuItem(JMenu sort,
3976           final String scoreLabel)
3977   {
3978     final JMenuItem item = new JMenuItem(scoreLabel);
3979     sort.add(item);
3980     item.addActionListener(new java.awt.event.ActionListener()
3981     {
3982
3983       @Override
3984       public void actionPerformed(ActionEvent e)
3985       {
3986         SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
3987         AlignmentSorter.sortByAnnotationScore(scoreLabel,
3988                 viewport.getAlignment());// ,viewport.getSelectionGroup());
3989         addHistoryItem(new OrderCommand("Sort by " + scoreLabel, oldOrder,
3990                 viewport.getAlignment()));
3991         alignPanel.paintAlignment(true, false);
3992       }
3993     });
3994   }
3995
3996   /**
3997    * last hash for alignment's annotation array - used to minimise cost of
3998    * rebuild.
3999    */
4000   protected int _annotationScoreVectorHash;
4001
4002   /**
4003    * search the alignment and rebuild the sort by annotation score submenu the
4004    * last alignment annotation vector hash is stored to minimize cost of
4005    * rebuilding in subsequence calls.
4006    * 
4007    */
4008
4009   @Override
4010   public void buildSortByAnnotationScoresMenu()
4011   {
4012     if (viewport.getAlignment().getAlignmentAnnotation() == null)
4013     {
4014       return;
4015     }
4016
4017     if (viewport.getAlignment().getAlignmentAnnotation()
4018             .hashCode() != _annotationScoreVectorHash)
4019     {
4020       sortByAnnotScore.removeAll();
4021       // almost certainly a quicker way to do this - but we keep it simple
4022       Hashtable<String, String> scoreSorts = new Hashtable<>();
4023       AlignmentAnnotation aann[];
4024       for (SequenceI sqa : viewport.getAlignment().getSequences())
4025       {
4026         aann = sqa.getAnnotation();
4027         for (int i = 0; aann != null && i < aann.length; i++)
4028         {
4029           if (aann[i].hasScore() && aann[i].sequenceRef != null)
4030           {
4031             scoreSorts.put(aann[i].label, aann[i].label);
4032           }
4033         }
4034       }
4035       Enumeration<String> labels = scoreSorts.keys();
4036       while (labels.hasMoreElements())
4037       {
4038         addSortByAnnotScoreMenuItem(sortByAnnotScore, labels.nextElement());
4039       }
4040       sortByAnnotScore.setVisible(scoreSorts.size() > 0);
4041       scoreSorts.clear();
4042
4043       _annotationScoreVectorHash = viewport.getAlignment()
4044               .getAlignmentAnnotation().hashCode();
4045     }
4046   }
4047
4048   /**
4049    * Enable (or, if desired, make visible) the By Tree 
4050    * submenu only if it has at least one element (or will have).
4051    * 
4052    */
4053   @Override
4054   protected void enableSortMenuOptions()
4055   {
4056     List<TreePanel> treePanels = getTreePanels();
4057     sortByTreeMenu.setEnabled(!treePanels.isEmpty());
4058   }
4059   
4060   /**
4061    * Maintain the Order by->Displayed Tree menu. Creates a new menu item for a
4062    * TreePanel with an appropriate <code>jalview.analysis.AlignmentSorter</code>
4063    * call. Listeners are added to remove the menu item when the treePanel is
4064    * closed, and adjust the tree leaf to sequence mapping when the alignment is
4065    * modified.
4066    */
4067
4068   @Override
4069   public void buildTreeSortMenu()
4070   {
4071     sortByTreeMenu.removeAll();
4072
4073     List<TreePanel> treePanels = getTreePanels();
4074
4075     for (final TreePanel tp : treePanels)
4076     {
4077       final JMenuItem item = new JMenuItem(tp.getTitle());
4078       item.addActionListener(new java.awt.event.ActionListener()
4079       {
4080
4081         @Override
4082         public void actionPerformed(ActionEvent e)
4083         {
4084           tp.sortByTree_actionPerformed();
4085           addHistoryItem(tp.sortAlignmentIn(alignPanel));
4086
4087         }
4088       });
4089
4090       sortByTreeMenu.add(item);
4091     }
4092   }
4093
4094   private List<TreePanel> getTreePanels()
4095   {
4096     List<Component> comps = PaintRefresher.components
4097             .get(viewport.getSequenceSetId());
4098     List<TreePanel> treePanels = new ArrayList<>();
4099     for (Component comp : comps)
4100     {
4101       if (comp instanceof TreePanel)
4102       {
4103         treePanels.add((TreePanel) comp);
4104       }
4105     }
4106     return treePanels;
4107   }
4108
4109   public boolean sortBy(AlignmentOrder alorder, String undoname)
4110   {
4111     SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
4112     AlignmentSorter.sortBy(viewport.getAlignment(), alorder);
4113     if (undoname != null)
4114     {
4115       addHistoryItem(new OrderCommand(undoname, oldOrder,
4116               viewport.getAlignment()));
4117     }
4118     alignPanel.paintAlignment(true, false);
4119     return true;
4120   }
4121
4122   /**
4123    * Work out whether the whole set of sequences or just the selected set will
4124    * be submitted for multiple alignment.
4125    * 
4126    */
4127
4128   public jalview.datamodel.AlignmentView gatherSequencesForAlignment()
4129   {
4130     // Now, check we have enough sequences
4131     AlignmentView msa = null;
4132
4133     if ((viewport.getSelectionGroup() != null)
4134             && (viewport.getSelectionGroup().getSize() > 1))
4135     {
4136       // JBPNote UGLY! To prettify, make SequenceGroup and Alignment conform to
4137       // some common interface!
4138       /*
4139        * SequenceGroup seqs = viewport.getSelectionGroup(); int sz; msa = new
4140        * SequenceI[sz = seqs.getSize(false)];
4141        * 
4142        * for (int i = 0; i < sz; i++) { msa[i] = (SequenceI)
4143        * seqs.getSequenceAt(i); }
4144        */
4145       msa = viewport.getAlignmentView(true);
4146     }
4147     else if (viewport.getSelectionGroup() != null
4148             && viewport.getSelectionGroup().getSize() == 1)
4149     {
4150       int option = JvOptionPane.showConfirmDialog(this,
4151               MessageManager.getString("warn.oneseq_msainput_selection"),
4152               MessageManager.getString("label.invalid_selection"),
4153               JvOptionPane.OK_CANCEL_OPTION);
4154       if (option == JvOptionPane.OK_OPTION)
4155       {
4156         msa = viewport.getAlignmentView(false);
4157       }
4158     }
4159     else
4160     {
4161       msa = viewport.getAlignmentView(false);
4162     }
4163     return msa;
4164   }
4165
4166   /**
4167    * Decides what is submitted to a secondary structure prediction service: the
4168    * first sequence in the alignment, or in the current selection, or, if the
4169    * alignment is 'aligned' (ie padded with gaps), then the currently selected
4170    * region or the whole alignment. (where the first sequence in the set is the
4171    * one that the prediction will be for).
4172    */
4173
4174   public AlignmentView gatherSeqOrMsaForSecStrPrediction()
4175   {
4176     AlignmentView seqs = null;
4177
4178     if ((viewport.getSelectionGroup() != null)
4179             && (viewport.getSelectionGroup().getSize() > 0))
4180     {
4181       seqs = viewport.getAlignmentView(true);
4182     }
4183     else
4184     {
4185       seqs = viewport.getAlignmentView(false);
4186     }
4187     // limit sequences - JBPNote in future - could spawn multiple prediction
4188     // jobs
4189     // TODO: viewport.getAlignment().isAligned is a global state - the local
4190     // selection may well be aligned - we preserve 2.0.8 behaviour for moment.
4191     if (!viewport.getAlignment().isAligned(false))
4192     {
4193       seqs.setSequences(new SeqCigar[] { seqs.getSequences()[0] });
4194       // TODO: if seqs.getSequences().length>1 then should really have warned
4195       // user!
4196
4197     }
4198     return seqs;
4199   }
4200
4201   /**
4202    * DOCUMENT ME!
4203    * 
4204    * @param e
4205    *          DOCUMENT ME!
4206    */
4207
4208   @Override
4209   protected void loadTreeMenuItem_actionPerformed(ActionEvent e)
4210   {
4211     // Pick the tree file
4212     JalviewFileChooser chooser = new JalviewFileChooser(
4213             jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
4214     chooser.setFileView(new JalviewFileView());
4215     chooser.setDialogTitle(
4216             MessageManager.getString("label.select_newick_like_tree_file"));
4217     chooser.setToolTipText(
4218             MessageManager.getString("label.load_tree_file"));
4219
4220     chooser.setResponseHandler(0, new Runnable()
4221     {
4222
4223       @Override
4224       public void run()
4225       {
4226         String filePath = chooser.getSelectedFile().getPath();
4227         Cache.setProperty("LAST_DIRECTORY", filePath);
4228         NewickFile fin = null;
4229         try
4230         {
4231           fin = new NewickFile(new FileParse(chooser.getSelectedFile(),
4232                   DataSourceType.FILE));
4233           viewport.setCurrentTree(showNewickTree(fin, filePath).getTree());
4234         } catch (Exception ex)
4235         {
4236           JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
4237                   ex.getMessage(),
4238                   MessageManager
4239                           .getString("label.problem_reading_tree_file"),
4240                   JvOptionPane.WARNING_MESSAGE);
4241           ex.printStackTrace();
4242         }
4243         if (fin != null && fin.hasWarningMessage())
4244         {
4245           JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
4246                   fin.getWarningMessage(),
4247                   MessageManager.getString(
4248                           "label.possible_problem_with_tree_file"),
4249                   JvOptionPane.WARNING_MESSAGE);
4250         }
4251       }
4252     });
4253     chooser.showOpenDialog(this);
4254   }
4255
4256   public TreePanel showNewickTree(NewickFile nf, String treeTitle)
4257   {
4258     return showNewickTree(nf, treeTitle, 600, 500, 4, 5);
4259   }
4260
4261   public TreePanel showNewickTree(NewickFile nf, String treeTitle, int w,
4262           int h, int x, int y)
4263   {
4264     return showNewickTree(nf, treeTitle, null, w, h, x, y);
4265   }
4266
4267   /**
4268    * Add a treeviewer for the tree extracted from a Newick file object to the
4269    * current alignment view
4270    * 
4271    * @param nf
4272    *          the tree
4273    * @param title
4274    *          tree viewer title
4275    * @param input
4276    *          Associated alignment input data (or null)
4277    * @param w
4278    *          width
4279    * @param h
4280    *          height
4281    * @param x
4282    *          position
4283    * @param y
4284    *          position
4285    * @return TreePanel handle
4286    */
4287
4288   public TreePanel showNewickTree(NewickFile nf, String treeTitle,
4289           AlignmentView input, int w, int h, int x, int y)
4290   {
4291     TreePanel tp = null;
4292
4293     try
4294     {
4295       nf.parse();
4296
4297       if (nf.getTree() != null)
4298       {
4299         tp = new TreePanel(alignPanel, nf, treeTitle, input);
4300         Dimension dim = Platform.getDimIfEmbedded(tp, -1, -1);
4301         if (dim == null)
4302         {
4303           dim = new Dimension(w, h);
4304         }
4305         else
4306         {
4307           // no offset, either
4308           x = 0;
4309         }
4310         tp.setSize(dim.width, dim.height);
4311
4312         if (x > 0 && y > 0)
4313         {
4314           tp.setLocation(x, y);
4315         }
4316
4317         Desktop.addInternalFrame(tp, treeTitle, dim.width, dim.height);
4318       }
4319     } catch (Exception ex)
4320     {
4321       ex.printStackTrace();
4322     }
4323
4324     return tp;
4325   }
4326
4327   private boolean buildingMenu = false;
4328
4329   /**
4330    * Generates menu items and listener event actions for web service clients
4331    * 
4332    */
4333
4334   public void BuildWebServiceMenu()
4335   {
4336     while (buildingMenu)
4337     {
4338       try
4339       {
4340         System.err.println("Waiting for building menu to finish.");
4341         Thread.sleep(10);
4342       } catch (Exception e)
4343       {
4344       }
4345     }
4346     final AlignFrame me = this;
4347     buildingMenu = true;
4348     new Thread(new Runnable()
4349     {
4350
4351       @Override
4352       public void run()
4353       {
4354         final List<JMenuItem> legacyItems = new ArrayList<>();
4355         try
4356         {
4357           // System.err.println("Building ws menu again "
4358           // + Thread.currentThread());
4359           // TODO: add support for context dependent disabling of services based
4360           // on
4361           // alignment and current selection
4362           // TODO: add additional serviceHandle parameter to specify abstract
4363           // handler
4364           // class independently of AbstractName
4365           // TODO: add in rediscovery GUI function to restart discoverer
4366           // TODO: group services by location as well as function and/or
4367           // introduce
4368           // object broker mechanism.
4369           final Vector<JMenu> wsmenu = new Vector<>();
4370           final IProgressIndicator af = me;
4371
4372           /*
4373            * do not i18n these strings - they are hard-coded in class
4374            * compbio.data.msa.Category, Jws2Discoverer.isRecalculable() and
4375            * SequenceAnnotationWSClient.initSequenceAnnotationWSClient()
4376            */
4377           final JMenu msawsmenu = new JMenu("Alignment");
4378           final JMenu secstrmenu = new JMenu(
4379                   "Secondary Structure Prediction");
4380           final JMenu seqsrchmenu = new JMenu("Sequence Database Search");
4381           final JMenu analymenu = new JMenu("Analysis");
4382           final JMenu dismenu = new JMenu("Protein Disorder");
4383           // JAL-940 - only show secondary structure prediction services from
4384           // the legacy server
4385           Hashtable<String, Vector<ServiceHandle>> ds = Discoverer
4386                   .getInstance().getServices();
4387           if (// Cache.getDefault("SHOW_JWS1_SERVICES", true)
4388               // &&
4389           ds != null && (ds.size() > 0))
4390           {
4391             // TODO: refactor to allow list of AbstractName/Handler bindings to
4392             // be
4393             // stored or retrieved from elsewhere
4394             // No MSAWS used any more:
4395             // Vector msaws = null; // (Vector)
4396             // Discoverer.services.get("MsaWS");
4397             Vector<ServiceHandle> secstrpr = ds.get("SecStrPred");
4398             if (secstrpr != null)
4399             {
4400               // Add any secondary structure prediction services
4401               for (int i = 0, j = secstrpr.size(); i < j; i++)
4402               {
4403                 final ext.vamsas.ServiceHandle sh = secstrpr.get(i);
4404                 jalview.ws.WSMenuEntryProviderI impl = jalview.ws.jws1.Discoverer
4405                         .getServiceClient(sh);
4406                 int p = secstrmenu.getItemCount();
4407                 impl.attachWSMenuEntry(secstrmenu, me);
4408                 int q = secstrmenu.getItemCount();
4409                 for (int litm = p; litm < q; litm++)
4410                 {
4411                   legacyItems.add(secstrmenu.getItem(litm));
4412                 }
4413               }
4414             }
4415           }
4416
4417           // Add all submenus in the order they should appear on the web
4418           // services menu
4419           wsmenu.add(msawsmenu);
4420           wsmenu.add(secstrmenu);
4421           wsmenu.add(dismenu);
4422           wsmenu.add(analymenu);
4423           // No search services yet
4424           // wsmenu.add(seqsrchmenu);
4425
4426           javax.swing.SwingUtilities.invokeLater(new Runnable()
4427           {
4428
4429             @Override
4430             public void run()
4431             {
4432               try
4433               {
4434                 webService.removeAll();
4435                 // first, add discovered services onto the webservices menu
4436                 if (wsmenu.size() > 0)
4437                 {
4438                   for (int i = 0, j = wsmenu.size(); i < j; i++)
4439                   {
4440                     webService.add(wsmenu.get(i));
4441                   }
4442                 }
4443                 else
4444                 {
4445                   webService.add(me.webServiceNoServices);
4446                 }
4447                 // TODO: move into separate menu builder class.
4448                 // boolean new_sspred = false;
4449                 if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
4450                 {
4451                   Jws2Discoverer jws2servs = Jws2Discoverer.getInstance();
4452                   if (jws2servs != null)
4453                   {
4454                     if (jws2servs.hasServices())
4455                     {
4456                       jws2servs.attachWSMenuEntry(webService, me);
4457                       for (Jws2Instance sv : jws2servs.getServices())
4458                       {
4459                         if (sv.description.toLowerCase().contains("jpred"))
4460                         {
4461                           for (JMenuItem jmi : legacyItems)
4462                           {
4463                             jmi.setVisible(false);
4464                           }
4465                         }
4466                       }
4467
4468                     }
4469                     if (jws2servs.isRunning())
4470                     {
4471                       JMenuItem tm = new JMenuItem(
4472                               "Still discovering JABA Services");
4473                       tm.setEnabled(false);
4474                       webService.add(tm);
4475                     }
4476                   }
4477                 }
4478                 build_urlServiceMenu(me.webService);
4479                 build_fetchdbmenu(webService);
4480                 for (JMenu item : wsmenu)
4481                 {
4482                   if (item.getItemCount() == 0)
4483                   {
4484                     item.setEnabled(false);
4485                   }
4486                   else
4487                   {
4488                     item.setEnabled(true);
4489                   }
4490                 }
4491               } catch (Exception e)
4492               {
4493                 Cache.log.debug(
4494                         "Exception during web service menu building process.",
4495                         e);
4496               }
4497             }
4498           });
4499         } catch (Exception e)
4500         {
4501         }
4502         buildingMenu = false;
4503       }
4504     }).start();
4505
4506   }
4507
4508   /**
4509    * construct any groupURL type service menu entries.
4510    * 
4511    * @param webService
4512    */
4513
4514   protected void build_urlServiceMenu(JMenu webService)
4515   {
4516     // TODO: remove this code when 2.7 is released
4517     // DEBUG - alignmentView
4518     /*
4519      * JMenuItem testAlView = new JMenuItem("Test AlignmentView"); final
4520      * AlignFrame af = this; testAlView.addActionListener(new ActionListener() {
4521      * 
4522      *  public void actionPerformed(ActionEvent e) {
4523      * jalview.datamodel.AlignmentView
4524      * .testSelectionViews(af.viewport.getAlignment(),
4525      * af.viewport.getColumnSelection(), af.viewport.selectionGroup); }
4526      * 
4527      * }); webService.add(testAlView);
4528      */
4529     // TODO: refactor to RestClient discoverer and merge menu entries for
4530     // rest-style services with other types of analysis/calculation service
4531     // SHmmr test client - still being implemented.
4532     // DEBUG - alignmentView
4533
4534     for (jalview.ws.rest.RestClient client : jalview.ws.rest.RestClient
4535             .getRestClients())
4536     {
4537       client.attachWSMenuEntry(
4538               JvSwingUtils.findOrCreateMenu(webService, client.getAction()),
4539               this);
4540     }
4541   }
4542
4543   /**
4544    * Searches the alignment sequences for xRefs and builds the Show
4545    * Cross-References menu (formerly called Show Products), with database
4546    * sources for which cross-references are found (protein sources for a
4547    * nucleotide alignment and vice versa)
4548    * 
4549    * @return true if Show Cross-references menu should be enabled
4550    */
4551
4552   public boolean canShowProducts()
4553   {
4554     SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
4555     AlignmentI dataset = viewport.getAlignment().getDataset();
4556
4557     showProducts.removeAll();
4558     final boolean dna = viewport.getAlignment().isNucleotide();
4559
4560     if (seqs == null || seqs.length == 0)
4561     {
4562       // nothing to see here.
4563       return false;
4564     }
4565
4566     boolean showp = false;
4567     try
4568     {
4569       List<String> ptypes = new CrossRef(seqs, dataset)
4570               .findXrefSourcesForSequences(dna);
4571
4572       for (final String source : ptypes)
4573       {
4574         showp = true;
4575         final AlignFrame af = this;
4576         JMenuItem xtype = new JMenuItem(source);
4577         xtype.addActionListener(new ActionListener()
4578         {
4579
4580           @Override
4581           public void actionPerformed(ActionEvent e)
4582           {
4583             showProductsFor(af.viewport.getSequenceSelection(), dna,
4584                     source);
4585           }
4586         });
4587         showProducts.add(xtype);
4588       }
4589       showProducts.setVisible(showp);
4590       showProducts.setEnabled(showp);
4591     } catch (Exception e)
4592     {
4593       Cache.log.warn(
4594               "canShowProducts threw an exception - please report to help@jalview.org",
4595               e);
4596       return false;
4597     }
4598     return showp;
4599   }
4600
4601   /**
4602    * Finds and displays cross-references for the selected sequences (protein
4603    * products for nucleotide sequences, dna coding sequences for peptides).
4604    * 
4605    * @param sel
4606    *          the sequences to show cross-references for
4607    * @param dna
4608    *          true if from a nucleotide alignment (so showing proteins)
4609    * @param source
4610    *          the database to show cross-references for
4611    */
4612
4613   protected void showProductsFor(final SequenceI[] sel, final boolean _odna,
4614           final String source)
4615   {
4616     new Thread(CrossRefAction.getHandlerFor(sel, _odna, source, this))
4617             .start();
4618   }
4619
4620   /**
4621    * Construct and display a new frame containing the translation of this
4622    * frame's DNA sequences to their aligned protein (amino acid) equivalents.
4623    */
4624
4625   @Override
4626   public void showTranslation_actionPerformed(GeneticCodeI codeTable)
4627   {
4628     AlignmentI al = null;
4629     try
4630     {
4631       Dna dna = new Dna(viewport, viewport.getViewAsVisibleContigs(true));
4632
4633       al = dna.translateCdna(codeTable);
4634     } catch (Exception ex)
4635     {
4636       jalview.bin.Cache.log.error(
4637               "Exception during translation. Please report this !", ex);
4638       final String msg = MessageManager.getString(
4639               "label.error_when_translating_sequences_submit_bug_report");
4640       final String errorTitle = MessageManager
4641               .getString("label.implementation_error")
4642               + MessageManager.getString("label.translation_failed");
4643       JvOptionPane.showMessageDialog(Desktop.getDesktopPane(), msg,
4644               errorTitle, JvOptionPane.ERROR_MESSAGE);
4645       return;
4646     }
4647     if (al == null || al.getHeight() == 0)
4648     {
4649       final String msg = MessageManager.getString(
4650               "label.select_at_least_three_bases_in_at_least_one_sequence_to_cDNA_translation");
4651       final String errorTitle = MessageManager
4652               .getString("label.translation_failed");
4653       JvOptionPane.showMessageDialog(Desktop.getDesktopPane(), msg,
4654               errorTitle, JvOptionPane.WARNING_MESSAGE);
4655     }
4656     else
4657     {
4658       AlignFrame af = new AlignFrame(al, DEFAULT_WIDTH, DEFAULT_HEIGHT);
4659       af.setFileFormat(this.currentFileFormat);
4660       final String newTitle = MessageManager
4661               .formatMessage("label.translation_of_params", new Object[]
4662               { this.getTitle(), codeTable.getId() });
4663       af.setTitle(newTitle);
4664       if (Cache.getDefault(Preferences.ENABLE_SPLIT_FRAME, true))
4665       {
4666         final SequenceI[] seqs = viewport.getSelectionAsNewSequence();
4667         AlignViewport.openSplitFrame(this, af, new Alignment(seqs));
4668       }
4669       else
4670       {
4671         Desktop.addInternalFrame(af, newTitle, DEFAULT_WIDTH,
4672                 DEFAULT_HEIGHT);
4673       }
4674     }
4675   }
4676
4677   /**
4678    * Set the file format
4679    * 
4680    * @param format
4681    */
4682
4683   public void setFileFormat(FileFormatI format)
4684   {
4685     this.currentFileFormat = format;
4686   }
4687
4688   /**
4689    * Try to load a features file onto the alignment.
4690    * 
4691    * @param file
4692    *          contents or path to retrieve file or a File object
4693    * @param sourceType
4694    *          access mode of file (see jalview.io.AlignFile)
4695    * @return true if features file was parsed correctly.
4696    */
4697
4698   public boolean parseFeaturesFile(Object file, DataSourceType sourceType)
4699   {
4700     // BH 2018
4701     return avc.parseFeaturesFile(file, sourceType,
4702             Cache.getDefault(Preferences.RELAXEDSEQIDMATCHING, false));
4703
4704   }
4705
4706   @Override
4707   public void refreshFeatureUI(boolean enableIfNecessary)
4708   {
4709     // note - currently this is only still here rather than in the controller
4710     // because of the featureSettings hard reference that is yet to be
4711     // abstracted
4712     if (enableIfNecessary)
4713     {
4714       viewport.setShowSequenceFeatures(true);
4715       showSeqFeatures.setSelected(true);
4716     }
4717
4718   }
4719
4720   @Override
4721   public void dragEnter(DropTargetDragEvent evt)
4722   {
4723   }
4724
4725   @Override
4726   public void dragExit(DropTargetEvent evt)
4727   {
4728   }
4729
4730   @Override
4731   public void dragOver(DropTargetDragEvent evt)
4732   {
4733   }
4734
4735   @Override
4736   public void dropActionChanged(DropTargetDragEvent evt)
4737   {
4738   }
4739
4740   @Override
4741   public void drop(DropTargetDropEvent evt)
4742   {
4743     // JAL-1552 - acceptDrop required before getTransferable call for
4744     // Java's Transferable for native dnd
4745     evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
4746     Transferable t = evt.getTransferable();
4747
4748     final List<Object> files = new ArrayList<>();
4749     List<DataSourceType> protocols = new ArrayList<>();
4750
4751     try
4752     {
4753       Desktop.transferFromDropTarget(files, protocols, evt, t);
4754       if (files.size() > 0)
4755       {
4756         new Thread(new Runnable()
4757         {
4758
4759           @Override
4760           public void run()
4761           {
4762             loadDroppedFiles(files, protocols, evt, t);
4763           }
4764         }).start();
4765       }
4766     } catch (Exception e)
4767     {
4768       e.printStackTrace();
4769     }
4770   }
4771
4772   protected void loadDroppedFiles(List<Object> files,
4773           List<DataSourceType> protocols, DropTargetDropEvent evt,
4774           Transferable t)
4775   {
4776     try
4777     {
4778       // check to see if any of these files have names matching sequences
4779       // in
4780       // the alignment
4781       SequenceIdMatcher idm = new SequenceIdMatcher(
4782               viewport.getAlignment().getSequencesArray());
4783       /**
4784        * Object[] { String,SequenceI}
4785        */
4786       ArrayList<Object[]> filesmatched = new ArrayList<>();
4787       ArrayList<Object> filesnotmatched = new ArrayList<>();
4788       for (int i = 0; i < files.size(); i++)
4789       {
4790         // BH 2018
4791         Object fileObj = files.get(i);
4792         String fileName = fileObj.toString();
4793         String pdbfn = "";
4794         DataSourceType protocol = (fileObj instanceof File
4795                 ? DataSourceType.FILE
4796                 : FormatAdapter.checkProtocol(fileName));
4797         if (protocol == DataSourceType.FILE)
4798         {
4799           File file;
4800           if (fileObj instanceof File)
4801           {
4802             file = (File) fileObj;
4803             Platform.cacheFileData(file);
4804           }
4805           else
4806           {
4807             file = new File(fileName);
4808           }
4809           pdbfn = file.getName();
4810         }
4811         else if (protocol == DataSourceType.URL)
4812         {
4813           URL url = new URL(fileName);
4814           pdbfn = url.getFile();
4815         }
4816         if (pdbfn.length() > 0)
4817         {
4818           // attempt to find a match in the alignment
4819           SequenceI[] mtch = idm.findAllIdMatches(pdbfn);
4820           int l = 0, c = pdbfn.indexOf(".");
4821           while (mtch == null && c != -1)
4822           {
4823             do
4824             {
4825               l = c;
4826             } while ((c = pdbfn.indexOf(".", l)) > l);
4827             if (l > -1)
4828             {
4829               pdbfn = pdbfn.substring(0, l);
4830             }
4831             mtch = idm.findAllIdMatches(pdbfn);
4832           }
4833           if (mtch != null)
4834           {
4835             FileFormatI type;
4836             try
4837             {
4838               type = new IdentifyFile().identify(fileObj, protocol);
4839             } catch (Exception ex)
4840             {
4841               type = null;
4842             }
4843             if (type != null && type.isStructureFile())
4844             {
4845               filesmatched.add(new Object[] { fileObj, protocol, mtch });
4846               continue;
4847             }
4848           }
4849           // File wasn't named like one of the sequences or wasn't a PDB
4850           // file.
4851           filesnotmatched.add(fileObj);
4852         }
4853       }
4854       int assocfiles = 0;
4855       if (filesmatched.size() > 0)
4856       {
4857         boolean autoAssociate = Cache
4858                 .getDefault(Preferences.AUTOASSOCIATE_PDBANDSEQS, false);
4859         if (!autoAssociate)
4860         {
4861           String msg = MessageManager.formatMessage(
4862                   "label.automatically_associate_structure_files_with_sequences_same_name",
4863                   new Object[]
4864                   { Integer.valueOf(filesmatched.size()).toString() });
4865           String ttl = MessageManager.getString(
4866                   "label.automatically_associate_structure_files_by_name");
4867           int choice = JvOptionPane.showConfirmDialog(this, msg, ttl,
4868                   JvOptionPane.YES_NO_OPTION);
4869           autoAssociate = choice == JvOptionPane.YES_OPTION;
4870         }
4871         if (autoAssociate)
4872         {
4873           for (Object[] fm : filesmatched)
4874           {
4875             // try and associate
4876             // TODO: may want to set a standard ID naming formalism for
4877             // associating PDB files which have no IDs.
4878             for (SequenceI toassoc : (SequenceI[]) fm[2])
4879             {
4880               PDBEntry pe = AssociatePdbFileWithSeq.associatePdbWithSeq(
4881                       fm[0].toString(), (DataSourceType) fm[1], toassoc,
4882                       false);
4883               if (pe != null)
4884               {
4885                 System.err.println("Associated file : " + (fm[0].toString())
4886                         + " with " + toassoc.getDisplayId(true));
4887                 assocfiles++;
4888               }
4889             }
4890             // TODO: do we need to update overview ? only if features are
4891             // shown I guess
4892             alignPanel.paintAlignment(true, false);
4893           }
4894         }
4895         else
4896         {
4897           /*
4898            * add declined structures as sequences
4899            */
4900           for (Object[] o : filesmatched)
4901           {
4902             filesnotmatched.add(o[0]);
4903           }
4904         }
4905       }
4906       if (filesnotmatched.size() > 0)
4907       {
4908         if (assocfiles > 0 && (Cache
4909                 .getDefault("AUTOASSOCIATE_PDBANDSEQS_IGNOREOTHERS", false)
4910                 || JvOptionPane.showConfirmDialog(this,
4911                         "<html>" + MessageManager.formatMessage(
4912                                 "label.ignore_unmatched_dropped_files_info",
4913                                 new Object[]
4914                                 { Integer.valueOf(filesnotmatched.size())
4915                                         .toString() })
4916                                 + "</html>",
4917                         MessageManager.getString(
4918                                 "label.ignore_unmatched_dropped_files"),
4919                         JvOptionPane.YES_NO_OPTION) == JvOptionPane.YES_OPTION))
4920         {
4921           return;
4922         }
4923         for (Object fn : filesnotmatched)
4924         {
4925           loadJalviewDataFile(fn, null, null, null);
4926         }
4927
4928       }
4929     } catch (Exception ex)
4930     {
4931       ex.printStackTrace();
4932     }
4933   }
4934
4935   /**
4936    * Attempt to load a "dropped" file or URL string, by testing in turn for
4937    * <ul>
4938    * <li>an Annotation file</li>
4939    * <li>a JNet file</li>
4940    * <li>a features file</li>
4941    * <li>else try to interpret as an alignment file</li>
4942    * </ul>
4943    * 
4944    * @param file
4945    *          either a filename or a URL string.
4946    */
4947
4948   public void loadJalviewDataFile(Object file, DataSourceType sourceType,
4949           FileFormatI format, SequenceI assocSeq)
4950   {
4951     // BH 2018 was String file
4952     try
4953     {
4954       if (sourceType == null)
4955       {
4956         sourceType = FormatAdapter.checkProtocol(file);
4957       }
4958       // if the file isn't identified, or not positively identified as some
4959       // other filetype (PFAM is default unidentified alignment file type) then
4960       // try to parse as annotation.
4961       boolean isAnnotation = (format == null
4962               || FileFormat.Pfam.equals(format))
4963                       ? new AnnotationFile().annotateAlignmentView(viewport,
4964                               file, sourceType)
4965                       : false;
4966
4967       if (!isAnnotation)
4968       {
4969         // first see if its a T-COFFEE score file
4970         TCoffeeScoreFile tcf = null;
4971         try
4972         {
4973           tcf = new TCoffeeScoreFile(file, sourceType);
4974           if (tcf.isValid())
4975           {
4976             if (tcf.annotateAlignment(viewport.getAlignment(), true))
4977             {
4978               buildColourMenu();
4979               changeColour(
4980                       new TCoffeeColourScheme(viewport.getAlignment()));
4981               isAnnotation = true;
4982               setStatus(MessageManager.getString(
4983                       "label.successfully_pasted_tcoffee_scores_to_alignment"));
4984             }
4985             else
4986             {
4987               // some problem - if no warning its probable that the ID matching
4988               // process didn't work
4989               JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
4990                       tcf.getWarningMessage() == null
4991                               ? MessageManager.getString(
4992                                       "label.check_file_matches_sequence_ids_alignment")
4993                               : tcf.getWarningMessage(),
4994                       MessageManager.getString(
4995                               "label.problem_reading_tcoffee_score_file"),
4996                       JvOptionPane.WARNING_MESSAGE);
4997             }
4998           }
4999           else
5000           {
5001             tcf = null;
5002           }
5003         } catch (Exception x)
5004         {
5005           Cache.log.debug(
5006                   "Exception when processing data source as T-COFFEE score file",
5007                   x);
5008           tcf = null;
5009         }
5010         if (tcf == null)
5011         {
5012           // try to see if its a JNet 'concise' style annotation file *before*
5013           // we
5014           // try to parse it as a features file
5015           if (format == null)
5016           {
5017             format = new IdentifyFile().identify(file, sourceType);
5018           }
5019           if (FileFormat.ScoreMatrix == format)
5020           {
5021             ScoreMatrixFile sm = new ScoreMatrixFile(
5022                     new FileParse(file, sourceType));
5023             sm.parse();
5024             // todo: i18n this message
5025             setStatus(MessageManager.formatMessage(
5026                     "label.successfully_loaded_matrix",
5027                     sm.getMatrixName()));
5028           }
5029           else if (FileFormat.Jnet.equals(format))
5030           {
5031             JPredFile predictions = new JPredFile(file, sourceType);
5032             new JnetAnnotationMaker();
5033             JnetAnnotationMaker.add_annotation(predictions,
5034                     viewport.getAlignment(), 0, false);
5035             viewport.getAlignment().setupJPredAlignment();
5036             isAnnotation = true;
5037           }
5038           // else if (IdentifyFile.FeaturesFile.equals(format))
5039           else if (FileFormat.Features.equals(format))
5040           {
5041             if (parseFeaturesFile(file, sourceType))
5042             {
5043               SplitFrame splitFrame = (SplitFrame) getSplitViewContainer();
5044               if (splitFrame != null)
5045               {
5046                 splitFrame.repaint();
5047               }
5048               else
5049               {
5050                 alignPanel.paintAlignment(true, true);
5051               }
5052             }
5053           }
5054           else
5055           {
5056             new FileLoader().LoadFile(viewport, file, sourceType, format);
5057           }
5058         }
5059       }
5060       if (isAnnotation)
5061       {
5062         updateForAnnotations();
5063       }
5064     } catch (Exception ex)
5065     {
5066       ex.printStackTrace();
5067     } catch (OutOfMemoryError oom)
5068     {
5069       try
5070       {
5071         System.gc();
5072       } catch (Exception x)
5073       {
5074       }
5075       new OOMWarning(
5076               "loading data "
5077                       + (sourceType != null
5078                               ? (sourceType == DataSourceType.PASTE
5079                                       ? "from clipboard."
5080                                       : "using " + sourceType + " from "
5081                                               + file)
5082                               : ".")
5083                       + (format != null
5084                               ? "(parsing as '" + format + "' file)"
5085                               : ""),
5086               oom, Desktop.getDesktopPane());
5087     }
5088   }
5089
5090   /**
5091    * Do all updates necessary after an annotation file such as jnet. Also called
5092    * from Jalview.loadAppletParams for "annotations", "jnetFile"
5093    */
5094
5095   public void updateForAnnotations()
5096   {
5097     alignPanel.adjustAnnotationHeight();
5098     viewport.updateSequenceIdColours();
5099     buildSortByAnnotationScoresMenu();
5100     alignPanel.paintAlignment(true, true);
5101   }
5102
5103   /**
5104    * Change the display state for the given feature groups -- Added by BH from
5105    * JalviewLite
5106    * 
5107    * @param groups
5108    *          list of group strings
5109    * @param state
5110    *          visible or invisible
5111    */
5112
5113   public void setFeatureGroupState(String[] groups, boolean state)
5114   {
5115     jalview.api.FeatureRenderer fr = null;
5116     viewport.setShowSequenceFeatures(true);
5117     if (alignPanel != null
5118             && (fr = alignPanel.getFeatureRenderer()) != null)
5119     {
5120
5121       fr.setGroupVisibility(Arrays.asList(groups), state);
5122       alignPanel.getSeqPanel().seqCanvas.repaint();
5123       if (alignPanel.overviewPanel != null)
5124       {
5125         alignPanel.overviewPanel.updateOverviewImage();
5126       }
5127     }
5128   }
5129
5130   /**
5131    * Method invoked by the ChangeListener on the tabbed pane, in other words
5132    * when a different tabbed pane is selected by the user or programmatically.
5133    */
5134
5135   @Override
5136   public void tabSelectionChanged(int index)
5137   {
5138     if (index > -1)
5139     {
5140       alignPanel = alignPanels.get(index);
5141       viewport = alignPanel.av;
5142       avc.setViewportAndAlignmentPanel(viewport, alignPanel);
5143       setMenusFromViewport(viewport);
5144       if (featureSettings != null && featureSettings.isOpen()
5145               && featureSettings.fr.getViewport() != viewport)
5146       {
5147         if (viewport.isShowSequenceFeatures())
5148         {
5149           // refresh the featureSettings to reflect UI change
5150           showFeatureSettingsUI();
5151         }
5152         else
5153         {
5154           // close feature settings for this view.
5155           featureSettings.close();
5156         }
5157       }
5158
5159     }
5160
5161     /*
5162      * 'focus' any colour slider that is open to the selected viewport
5163      */
5164     if (viewport.getConservationSelected())
5165     {
5166       SliderPanel.setConservationSlider(alignPanel,
5167               viewport.getResidueShading(), alignPanel.getViewName());
5168     }
5169     else
5170     {
5171       SliderPanel.hideConservationSlider();
5172     }
5173     if (viewport.getAbovePIDThreshold())
5174     {
5175       SliderPanel.setPIDSliderSource(alignPanel,
5176               viewport.getResidueShading(), alignPanel.getViewName());
5177     }
5178     else
5179     {
5180       SliderPanel.hidePIDSlider();
5181     }
5182
5183     /*
5184      * If there is a frame linked to this one in a SplitPane, switch it to the
5185      * same view tab index. No infinite recursion of calls should happen, since
5186      * tabSelectionChanged() should not get invoked on setting the selected
5187      * index to an unchanged value. Guard against setting an invalid index
5188      * before the new view peer tab has been created.
5189      */
5190     final AlignViewportI peer = viewport.getCodingComplement();
5191     if (peer != null)
5192     {
5193       AlignFrame linkedAlignFrame = ((AlignViewport) peer)
5194               .getAlignPanel().alignFrame;
5195       if (linkedAlignFrame.tabbedPane.getTabCount() > index)
5196       {
5197         linkedAlignFrame.tabbedPane.setSelectedIndex(index);
5198       }
5199     }
5200   }
5201
5202   /**
5203    * On right mouse click on view tab, prompt for and set new view name.
5204    */
5205
5206   @Override
5207   public void tabbedPane_mousePressed(MouseEvent e)
5208   {
5209     if (e.isPopupTrigger())
5210     {
5211       String msg = MessageManager.getString("label.enter_view_name");
5212       String ttl = tabbedPane.getTitleAt(tabbedPane.getSelectedIndex());
5213       String reply = JvOptionPane.showInputDialog(msg, ttl);
5214
5215       if (reply != null)
5216       {
5217         viewport.setViewName(reply);
5218         // TODO warn if reply is in getExistingViewNames()?
5219         tabbedPane.setTitleAt(tabbedPane.getSelectedIndex(), reply);
5220       }
5221     }
5222   }
5223
5224   public AlignViewport getCurrentView()
5225   {
5226     return viewport;
5227   }
5228
5229   /**
5230    * Open the dialog for regex description parsing.
5231    */
5232
5233   @Override
5234   protected void extractScores_actionPerformed(ActionEvent e)
5235   {
5236     ParseProperties pp = new jalview.analysis.ParseProperties(
5237             viewport.getAlignment());
5238     // TODO: verify regex and introduce GUI dialog for version 2.5
5239     // if (pp.getScoresFromDescription("col", "score column ",
5240     // "\\W*([-+]?\\d*\\.?\\d*e?-?\\d*)\\W+([-+]?\\d*\\.?\\d*e?-?\\d*)",
5241     // true)>0)
5242     if (pp.getScoresFromDescription("description column",
5243             "score in description column ", "\\W*([-+eE0-9.]+)", true) > 0)
5244     {
5245       buildSortByAnnotationScoresMenu();
5246     }
5247   }
5248
5249   /*
5250    * (non-Javadoc)
5251    * 
5252    * @see
5253    * jalview.jbgui.GAlignFrame#showDbRefs_actionPerformed(java.awt.event.ActionEvent
5254    * )
5255    */
5256
5257   @Override
5258   protected void showDbRefs_actionPerformed(ActionEvent e)
5259   {
5260     viewport.setShowDBRefs(showDbRefsMenuitem.isSelected());
5261   }
5262
5263   /*
5264    * (non-Javadoc)
5265    * 
5266    * @seejalview.jbgui.GAlignFrame#showNpFeats_actionPerformed(java.awt.event.
5267    * ActionEvent)
5268    */
5269
5270   @Override
5271   protected void showNpFeats_actionPerformed(ActionEvent e)
5272   {
5273     viewport.setShowNPFeats(showNpFeatsMenuitem.isSelected());
5274   }
5275
5276   /**
5277    * find the viewport amongst the tabs in this alignment frame and close that
5278    * tab
5279    * 
5280    * @param av
5281    */
5282
5283   public boolean closeView(AlignViewportI av)
5284   {
5285     if (viewport == av)
5286     {
5287       this.closeMenuItem_actionPerformed(false);
5288       return true;
5289     }
5290     Component[] comp = tabbedPane.getComponents();
5291     for (int i = 0; comp != null && i < comp.length; i++)
5292     {
5293       if (comp[i] instanceof AlignmentPanel)
5294       {
5295         if (((AlignmentPanel) comp[i]).av == av)
5296         {
5297           // close the view.
5298           closeView((AlignmentPanel) comp[i]);
5299           return true;
5300         }
5301       }
5302     }
5303     return false;
5304   }
5305
5306   protected void build_fetchdbmenu(JMenu webService)
5307   {
5308     // Temporary hack - DBRef Fetcher always top level ws entry.
5309     // TODO We probably want to store a sequence database checklist in
5310     // preferences and have checkboxes.. rather than individual sources selected
5311     // here
5312     final JMenu rfetch = new JMenu(
5313             MessageManager.getString("action.fetch_db_references"));
5314     rfetch.setToolTipText(MessageManager.getString(
5315             "label.retrieve_parse_sequence_database_records_alignment_or_selected_sequences"));
5316     webService.add(rfetch);
5317
5318     final JCheckBoxMenuItem trimrs = new JCheckBoxMenuItem(
5319             MessageManager.getString("option.trim_retrieved_seqs"));
5320     trimrs.setToolTipText(
5321             MessageManager.getString("label.trim_retrieved_sequences"));
5322     trimrs.setSelected(
5323             Cache.getDefault(DBRefFetcher.TRIM_RETRIEVED_SEQUENCES, true));
5324     trimrs.addActionListener(new ActionListener()
5325     {
5326
5327       @Override
5328       public void actionPerformed(ActionEvent e)
5329       {
5330         trimrs.setSelected(trimrs.isSelected());
5331         Cache.setProperty(DBRefFetcher.TRIM_RETRIEVED_SEQUENCES,
5332                 Boolean.valueOf(trimrs.isSelected()).toString());
5333       }
5334     });
5335     rfetch.add(trimrs);
5336     JMenuItem fetchr = new JMenuItem(
5337             MessageManager.getString("label.standard_databases"));
5338     fetchr.setToolTipText(
5339             MessageManager.getString("label.fetch_embl_uniprot"));
5340     fetchr.addActionListener(new ActionListener()
5341     {
5342
5343       @Override
5344       public void actionPerformed(ActionEvent e)
5345       {
5346         new Thread(new Runnable()
5347         {
5348
5349           @Override
5350           public void run()
5351           {
5352             boolean isNucleotide = alignPanel.alignFrame.getViewport()
5353                     .getAlignment().isNucleotide();
5354             DBRefFetcher dbRefFetcher = new DBRefFetcher(
5355                     alignPanel.av.getSequenceSelection(),
5356                     alignPanel.alignFrame, null,
5357                     alignPanel.alignFrame.featureSettings, isNucleotide);
5358             dbRefFetcher.addListener(new FetchFinishedListenerI()
5359             {
5360
5361               @Override
5362               public void finished()
5363               {
5364
5365                 for (FeatureSettingsModelI srcSettings : dbRefFetcher
5366                         .getFeatureSettingsModels())
5367                 {
5368
5369                   alignPanel.av.mergeFeaturesStyle(srcSettings);
5370                 }
5371                 AlignFrame.this.setMenusForViewport();
5372               }
5373             });
5374             dbRefFetcher.fetchDBRefs(false);
5375           }
5376         }).start();
5377
5378       }
5379
5380     });
5381     rfetch.add(fetchr);
5382     new Thread(new Runnable()
5383     {
5384
5385       @Override
5386       public void run()
5387       {
5388         // ??
5389         // final jalview.ws.SequenceFetcher sf = jalview.gui.SequenceFetcher
5390         // .getSequenceFetcherSingleton();
5391         javax.swing.SwingUtilities.invokeLater(new Runnable()
5392         {
5393
5394           @Override
5395           public void run()
5396           {
5397             jalview.ws.SequenceFetcher sf = jalview.ws.SequenceFetcher
5398                     .getInstance();
5399             String[] dbclasses = sf.getNonAlignmentSources();
5400             List<DbSourceProxy> otherdb;
5401             JMenu dfetch = new JMenu();
5402             JMenu ifetch = new JMenu();
5403             JMenuItem fetchr = null;
5404             int comp = 0, icomp = 0, mcomp = 15;
5405             String mname = null;
5406             int dbi = 0;
5407             for (String dbclass : dbclasses)
5408             {
5409               otherdb = sf.getSourceProxy(dbclass);
5410               // add a single entry for this class, or submenu allowing 'fetch
5411               // all' or pick one
5412               if (otherdb == null || otherdb.size() < 1)
5413               {
5414                 continue;
5415               }
5416               if (mname == null)
5417               {
5418                 mname = "From " + dbclass;
5419               }
5420               if (otherdb.size() == 1)
5421               {
5422                 DbSourceProxy src = otherdb.get(0);
5423                 DbSourceProxy[] dassource = new DbSourceProxy[] { src };
5424                 fetchr = new JMenuItem(src.getDbSource());
5425                 fetchr.addActionListener(new ActionListener()
5426                 {
5427
5428                   @Override
5429                   public void actionPerformed(ActionEvent e)
5430                   {
5431                     new Thread(new Runnable()
5432                     {
5433
5434                       @Override
5435                       public void run()
5436                       {
5437                         boolean isNucleotide = alignPanel.alignFrame
5438                                 .getViewport().getAlignment()
5439                                 .isNucleotide();
5440                         DBRefFetcher dbRefFetcher = new DBRefFetcher(
5441                                 alignPanel.av.getSequenceSelection(),
5442                                 alignPanel.alignFrame, dassource,
5443                                 alignPanel.alignFrame.featureSettings,
5444                                 isNucleotide);
5445                         dbRefFetcher
5446                                 .addListener(new FetchFinishedListenerI()
5447                                 {
5448
5449                                   @Override
5450                                   public void finished()
5451                                   {
5452                                     FeatureSettingsModelI srcSettings = dassource[0]
5453                                             .getFeatureColourScheme();
5454                                     alignPanel.av.mergeFeaturesStyle(
5455                                             srcSettings);
5456                                     AlignFrame.this.setMenusForViewport();
5457                                   }
5458                                 });
5459                         dbRefFetcher.fetchDBRefs(false);
5460                       }
5461                     }).start();
5462                   }
5463
5464                 });
5465                 fetchr.setToolTipText(JvSwingUtils.wrapTooltip(true,
5466                         MessageManager.formatMessage(
5467                                 "label.fetch_retrieve_from", new Object[]
5468                                 { src.getDbName() })));
5469                 dfetch.add(fetchr);
5470                 comp++;
5471               }
5472               else
5473               {
5474                 final DbSourceProxy[] dassource = otherdb
5475                         .toArray(new DbSourceProxy[0]);
5476                 // fetch all entry
5477                 DbSourceProxy src = otherdb.get(0);
5478                 fetchr = new JMenuItem(MessageManager
5479                         .formatMessage("label.fetch_all_param", new Object[]
5480                         { src.getDbSource() }));
5481                 fetchr.addActionListener(new ActionListener()
5482                 {
5483
5484                   @Override
5485                   public void actionPerformed(ActionEvent e)
5486                   {
5487                     new Thread(new Runnable()
5488                     {
5489
5490                       @Override
5491                       public void run()
5492                       {
5493                         boolean isNucleotide = alignPanel.alignFrame
5494                                 .getViewport().getAlignment()
5495                                 .isNucleotide();
5496                         DBRefFetcher dbRefFetcher = new DBRefFetcher(
5497                                 alignPanel.av.getSequenceSelection(),
5498                                 alignPanel.alignFrame, dassource,
5499                                 alignPanel.alignFrame.featureSettings,
5500                                 isNucleotide);
5501                         dbRefFetcher
5502                                 .addListener(new FetchFinishedListenerI()
5503                                 {
5504
5505                                   @Override
5506                                   public void finished()
5507                                   {
5508                                     AlignFrame.this.setMenusForViewport();
5509                                   }
5510                                 });
5511                         dbRefFetcher.fetchDBRefs(false);
5512                       }
5513                     }).start();
5514                   }
5515                 });
5516
5517                 fetchr.setToolTipText(JvSwingUtils.wrapTooltip(true,
5518                         MessageManager.formatMessage(
5519                                 "label.fetch_retrieve_from_all_sources",
5520                                 new Object[]
5521                                 { Integer.valueOf(otherdb.size())
5522                                         .toString(),
5523                                     src.getDbSource(), src.getDbName() })));
5524                 dfetch.add(fetchr);
5525                 comp++;
5526                 // and then build the rest of the individual menus
5527                 ifetch = new JMenu(MessageManager.formatMessage(
5528                         "label.source_from_db_source", new Object[]
5529                         { src.getDbSource() }));
5530                 icomp = 0;
5531                 String imname = null;
5532                 int i = 0;
5533                 for (DbSourceProxy sproxy : otherdb)
5534                 {
5535                   String dbname = sproxy.getDbName();
5536                   String sname = dbname.length() > 5
5537                           ? dbname.substring(0, 5) + "..."
5538                           : dbname;
5539                   String msname = dbname.length() > 10
5540                           ? dbname.substring(0, 10) + "..."
5541                           : dbname;
5542                   if (imname == null)
5543                   {
5544                     imname = MessageManager
5545                             .formatMessage("label.from_msname", new Object[]
5546                             { sname });
5547                   }
5548                   fetchr = new JMenuItem(msname);
5549                   final DbSourceProxy[] dassrc = { sproxy };
5550                   fetchr.addActionListener(new ActionListener()
5551                   {
5552
5553                     @Override
5554                     public void actionPerformed(ActionEvent e)
5555                     {
5556                       new Thread(new Runnable()
5557                       {
5558
5559                         @Override
5560                         public void run()
5561                         {
5562                           boolean isNucleotide = alignPanel.alignFrame
5563                                   .getViewport().getAlignment()
5564                                   .isNucleotide();
5565                           DBRefFetcher dbRefFetcher = new DBRefFetcher(
5566                                   alignPanel.av.getSequenceSelection(),
5567                                   alignPanel.alignFrame, dassrc,
5568                                   alignPanel.alignFrame.featureSettings,
5569                                   isNucleotide);
5570                           dbRefFetcher
5571                                   .addListener(new FetchFinishedListenerI()
5572                                   {
5573
5574                                     @Override
5575                                     public void finished()
5576                                     {
5577                                       AlignFrame.this.setMenusForViewport();
5578                                     }
5579                                   });
5580                           dbRefFetcher.fetchDBRefs(false);
5581                         }
5582                       }).start();
5583                     }
5584
5585                   });
5586                   fetchr.setToolTipText(
5587                           "<html>" + MessageManager.formatMessage(
5588                                   "label.fetch_retrieve_from", new Object[]
5589                                   { dbname }));
5590                   ifetch.add(fetchr);
5591                   ++i;
5592                   if (++icomp >= mcomp || i == (otherdb.size()))
5593                   {
5594                     ifetch.setText(MessageManager.formatMessage(
5595                             "label.source_to_target", imname, sname));
5596                     dfetch.add(ifetch);
5597                     ifetch = new JMenu();
5598                     imname = null;
5599                     icomp = 0;
5600                     comp++;
5601                   }
5602                 }
5603               }
5604               ++dbi;
5605               if (comp >= mcomp || dbi >= (dbclasses.length))
5606               {
5607                 dfetch.setText(MessageManager.formatMessage(
5608                         "label.source_to_target", mname, dbclass));
5609                 rfetch.add(dfetch);
5610                 dfetch = new JMenu();
5611                 mname = null;
5612                 comp = 0;
5613               }
5614             }
5615           }
5616         });
5617       }
5618     }).start();
5619
5620   }
5621
5622   /**
5623    * Left justify the whole alignment.
5624    */
5625
5626   @Override
5627   protected void justifyLeftMenuItem_actionPerformed(ActionEvent e)
5628   {
5629     viewport.getAlignment().justify(false);
5630     viewport.notifyAlignment();
5631   }
5632
5633   /**
5634    * Right justify the whole alignment.
5635    */
5636
5637   @Override
5638   protected void justifyRightMenuItem_actionPerformed(ActionEvent e)
5639   {
5640     viewport.getAlignment().justify(true);
5641     viewport.notifyAlignment();
5642   }
5643
5644   @Override
5645   public void setShowSeqFeatures(boolean b)
5646   {
5647     showSeqFeatures.setSelected(b);
5648     viewport.setShowSequenceFeatures(b);
5649   }
5650
5651   /*
5652    * (non-Javadoc)
5653    * 
5654    * @see
5655    * jalview.jbgui.GAlignFrame#showUnconservedMenuItem_actionPerformed(java.
5656    * awt.event.ActionEvent)
5657    */
5658
5659   @Override
5660   protected void showUnconservedMenuItem_actionPerformed(ActionEvent e)
5661   {
5662     viewport.setShowUnconserved(showNonconservedMenuItem.getState());
5663     alignPanel.paintAlignment(false, false);
5664   }
5665
5666   /*
5667    * (non-Javadoc)
5668    * 
5669    * @see
5670    * jalview.jbgui.GAlignFrame#showGroupConsensus_actionPerformed(java.awt.event
5671    * .ActionEvent)
5672    */
5673
5674   @Override
5675   protected void showGroupConsensus_actionPerformed(ActionEvent e)
5676   {
5677     viewport.setShowGroupConsensus(showGroupConsensus.getState());
5678     alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
5679
5680   }
5681
5682   /*
5683    * (non-Javadoc)
5684    * 
5685    * @see
5686    * jalview.jbgui.GAlignFrame#showGroupConservation_actionPerformed(java.awt
5687    * .event.ActionEvent)
5688    */
5689
5690   @Override
5691   protected void showGroupConservation_actionPerformed(ActionEvent e)
5692   {
5693     viewport.setShowGroupConservation(showGroupConservation.getState());
5694     alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
5695   }
5696
5697   /*
5698    * (non-Javadoc)
5699    * 
5700    * @see
5701    * jalview.jbgui.GAlignFrame#showConsensusHistogram_actionPerformed(java.awt
5702    * .event.ActionEvent)
5703    */
5704
5705   @Override
5706   protected void showConsensusHistogram_actionPerformed(ActionEvent e)
5707   {
5708     viewport.setShowConsensusHistogram(showConsensusHistogram.getState());
5709     alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
5710   }
5711
5712   /*
5713    * (non-Javadoc)
5714    * 
5715    * @see
5716    * jalview.jbgui.GAlignFrame#showConsensusProfile_actionPerformed(java.awt
5717    * .event.ActionEvent)
5718    */
5719
5720   @Override
5721   protected void showSequenceLogo_actionPerformed(ActionEvent e)
5722   {
5723     viewport.setShowSequenceLogo(showSequenceLogo.getState());
5724     alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
5725   }
5726
5727   @Override
5728   protected void normaliseSequenceLogo_actionPerformed(ActionEvent e)
5729   {
5730     showSequenceLogo.setState(true);
5731     viewport.setShowSequenceLogo(true);
5732     viewport.setNormaliseSequenceLogo(normaliseSequenceLogo.getState());
5733     alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
5734   }
5735
5736   @Override
5737   protected void applyAutoAnnotationSettings_actionPerformed(ActionEvent e)
5738   {
5739     alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
5740   }
5741
5742   /*
5743    * (non-Javadoc)
5744    * 
5745    * @see
5746    * jalview.jbgui.GAlignFrame#makeGrpsFromSelection_actionPerformed(java.awt
5747    * .event.ActionEvent)
5748    */
5749
5750   @Override
5751   protected void makeGrpsFromSelection_actionPerformed(ActionEvent e)
5752   {
5753     if (avc.makeGroupsFromSelection())
5754     {
5755       PaintRefresher.Refresh(this, viewport.getSequenceSetId());
5756       alignPanel.updateAnnotation();
5757       alignPanel.paintAlignment(true,
5758               viewport.needToUpdateStructureViews());
5759     }
5760   }
5761
5762   public void clearAlignmentSeqRep()
5763   {
5764     // TODO refactor alignmentseqrep to controller
5765     if (viewport.getAlignment().hasSeqrep())
5766     {
5767       viewport.getAlignment().setSeqrep(null);
5768       PaintRefresher.Refresh(this, viewport.getSequenceSetId());
5769       alignPanel.updateAnnotation();
5770       alignPanel.paintAlignment(true, true);
5771     }
5772   }
5773
5774   @Override
5775   protected void createGroup_actionPerformed(ActionEvent e)
5776   {
5777     if (avc.createGroup())
5778     {
5779       if (applyAutoAnnotationSettings.isSelected())
5780       {
5781         alignPanel.updateAnnotation(true, false);
5782       }
5783       alignPanel.alignmentChanged();
5784     }
5785   }
5786
5787   @Override
5788   protected void unGroup_actionPerformed(ActionEvent e)
5789   {
5790     if (avc.unGroup())
5791     {
5792       alignPanel.alignmentChanged();
5793     }
5794   }
5795
5796   /**
5797    * make the given alignmentPanel the currently selected tab
5798    * 
5799    * @param alignmentPanel
5800    */
5801
5802   public void setDisplayedView(AlignmentPanel alignmentPanel)
5803   {
5804     if (!viewport.getSequenceSetId()
5805             .equals(alignmentPanel.av.getSequenceSetId()))
5806     {
5807       throw new Error(MessageManager.getString(
5808               "error.implementation_error_cannot_show_view_alignment_frame"));
5809     }
5810     if (tabbedPane != null && tabbedPane.getTabCount() > 0 && alignPanels
5811             .indexOf(alignmentPanel) != tabbedPane.getSelectedIndex())
5812     {
5813       tabbedPane.setSelectedIndex(alignPanels.indexOf(alignmentPanel));
5814     }
5815   }
5816
5817   /**
5818    * Action on selection of menu options to Show or Hide annotations.
5819    * 
5820    * @param visible
5821    * @param forSequences
5822    *          update sequence-related annotations
5823    * @param forAlignment
5824    *          update non-sequence-related annotations
5825    */
5826
5827   @Override
5828   protected void setAnnotationsVisibility(boolean visible,
5829           boolean forSequences, boolean forAlignment)
5830   {
5831     AlignmentAnnotation[] anns = alignPanel.getAlignment()
5832             .getAlignmentAnnotation();
5833     if (anns == null)
5834     {
5835       return;
5836     }
5837     for (AlignmentAnnotation aa : anns)
5838     {
5839       /*
5840        * don't display non-positional annotations on an alignment
5841        */
5842       if (aa.annotations == null)
5843       {
5844         continue;
5845       }
5846       boolean apply = (aa.sequenceRef == null && forAlignment)
5847               || (aa.sequenceRef != null && forSequences);
5848       if (apply)
5849       {
5850         aa.visible = visible;
5851       }
5852     }
5853     alignPanel.validateAnnotationDimensions(true);
5854     alignPanel.alignmentChanged();
5855   }
5856
5857   /**
5858    * Store selected annotation sort order for the view and repaint.
5859    */
5860
5861   @Override
5862   protected void sortAnnotations_actionPerformed()
5863   {
5864     this.alignPanel.av.setSortAnnotationsBy(getAnnotationSortOrder());
5865     this.alignPanel.av
5866             .setShowAutocalculatedAbove(isShowAutoCalculatedAbove());
5867     alignPanel.paintAlignment(false, false);
5868   }
5869
5870   /**
5871    * 
5872    * @return alignment panels in this alignment frame
5873    */
5874
5875   public List<? extends AlignmentViewPanel> getAlignPanels()
5876   {
5877     // alignPanels is never null
5878     // return alignPanels == null ? Arrays.asList(alignPanel) : alignPanels;
5879     return alignPanels;
5880   }
5881
5882   /**
5883    * Open a new alignment window, with the cDNA associated with this (protein)
5884    * alignment, aligned as is the protein.
5885    */
5886
5887   protected void viewAsCdna_actionPerformed()
5888   {
5889     // TODO no longer a menu action - refactor as required
5890     final AlignmentI alignment = getViewport().getAlignment();
5891     List<AlignedCodonFrame> mappings = alignment.getCodonFrames();
5892     if (mappings == null)
5893     {
5894       return;
5895     }
5896     List<SequenceI> cdnaSeqs = new ArrayList<>();
5897     for (SequenceI aaSeq : alignment.getSequences())
5898     {
5899       for (AlignedCodonFrame acf : mappings)
5900       {
5901         SequenceI dnaSeq = acf.getDnaForAaSeq(aaSeq.getDatasetSequence());
5902         if (dnaSeq != null)
5903         {
5904           /*
5905            * There is a cDNA mapping for this protein sequence - add to new
5906            * alignment. It will share the same dataset sequence as other mapped
5907            * cDNA (no new mappings need to be created).
5908            */
5909           final Sequence newSeq = new Sequence(dnaSeq);
5910           newSeq.setDatasetSequence(dnaSeq);
5911           cdnaSeqs.add(newSeq);
5912         }
5913       }
5914     }
5915     if (cdnaSeqs.size() == 0)
5916     {
5917       // show a warning dialog no mapped cDNA
5918       return;
5919     }
5920     AlignmentI cdna = new Alignment(
5921             cdnaSeqs.toArray(new SequenceI[cdnaSeqs.size()]));
5922     GAlignFrame alignFrame = new AlignFrame(cdna, AlignFrame.DEFAULT_WIDTH,
5923             AlignFrame.DEFAULT_HEIGHT);
5924     cdna.alignAs(alignment);
5925     String newtitle = "cDNA " + MessageManager.getString("label.for") + " "
5926             + this.title;
5927     Desktop.addInternalFrame(alignFrame, newtitle, AlignFrame.DEFAULT_WIDTH,
5928             AlignFrame.DEFAULT_HEIGHT);
5929   }
5930
5931   /**
5932    * Set visibility of dna/protein complement view (available when shown in a
5933    * split frame).
5934    * 
5935    * @param show
5936    */
5937
5938   @Override
5939   protected void showComplement_actionPerformed(boolean show)
5940   {
5941     SplitContainerI sf = getSplitViewContainer();
5942     if (sf != null)
5943     {
5944       sf.setComplementVisible(this, show);
5945     }
5946   }
5947
5948   /**
5949    * Generate the reverse (optionally complemented) of the selected sequences,
5950    * and add them to the alignment
5951    */
5952
5953   @Override
5954   protected void showReverse_actionPerformed(boolean complement)
5955   {
5956     AlignmentI al = null;
5957     try
5958     {
5959       Dna dna = new Dna(viewport, viewport.getViewAsVisibleContigs(true));
5960       al = dna.reverseCdna(complement);
5961       viewport.addAlignment(al, "");
5962       addHistoryItem(new EditCommand(
5963               MessageManager.getString("label.add_sequences"), Action.PASTE,
5964               al.getSequencesArray(), 0, al.getWidth(),
5965               viewport.getAlignment()));
5966     } catch (Exception ex)
5967     {
5968       System.err.println(ex.getMessage());
5969       return;
5970     }
5971   }
5972
5973   /**
5974    * Try to run a script in the Groovy console, having first ensured that this
5975    * AlignFrame is set as currentAlignFrame in Desktop, to allow the script to
5976    * be targeted at this alignment.
5977    */
5978
5979   @Override
5980   protected void runGroovy_actionPerformed()
5981   {
5982     Jalview.setCurrentAlignFrame(this);
5983     groovy.ui.Console console = Desktop.getGroovyConsole();
5984     if (console != null)
5985     {
5986       try
5987       {
5988         console.runScript();
5989       } catch (Exception ex)
5990       {
5991         System.err.println((ex.toString()));
5992         JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
5993                 MessageManager.getString("label.couldnt_run_groovy_script"),
5994                 MessageManager.getString("label.groovy_support_failed"),
5995                 JvOptionPane.ERROR_MESSAGE);
5996       }
5997     }
5998     else
5999     {
6000       System.err.println("Can't run Groovy script as console not found");
6001     }
6002   }
6003
6004   /**
6005    * Hides columns containing (or not containing) a specified feature, provided
6006    * that would not leave all columns hidden
6007    * 
6008    * @param featureType
6009    * @param columnsContaining
6010    * @return
6011    */
6012
6013   public boolean hideFeatureColumns(String featureType,
6014           boolean columnsContaining)
6015   {
6016     boolean notForHiding = avc.markColumnsContainingFeatures(
6017             columnsContaining, false, false, featureType);
6018     if (notForHiding)
6019     {
6020       if (avc.markColumnsContainingFeatures(!columnsContaining, false,
6021               false, featureType))
6022       {
6023         getViewport().hideSelectedColumns();
6024         return true;
6025       }
6026     }
6027     return false;
6028   }
6029
6030   @Override
6031   protected void selectHighlightedColumns_actionPerformed(
6032           ActionEvent actionEvent)
6033   {
6034     // include key modifier check in case user selects from menu
6035     avc.markHighlightedColumns(
6036             (actionEvent.getModifiers() & ActionEvent.ALT_MASK) != 0, true,
6037             (actionEvent.getModifiers() & (ActionEvent.META_MASK
6038                     | ActionEvent.CTRL_MASK)) != 0);
6039   }
6040
6041   /**
6042    * Rebuilds the Colour menu, including any user-defined colours which have
6043    * been loaded either on startup or during the session
6044    */
6045
6046   public void buildColourMenu()
6047   {
6048     colourMenu.removeAll();
6049
6050     colourMenu.add(applyToAllGroups);
6051     colourMenu.add(textColour);
6052     colourMenu.addSeparator();
6053
6054     ButtonGroup bg = ColourMenuHelper.addMenuItems(colourMenu, this,
6055             viewport.getAlignment(), false);
6056
6057     colourMenu.add(annotationColour);
6058     bg.add(annotationColour);
6059     colourMenu.addSeparator();
6060     colourMenu.add(conservationMenuItem);
6061     colourMenu.add(modifyConservation);
6062     colourMenu.add(abovePIDThreshold);
6063     colourMenu.add(modifyPID);
6064
6065     ColourSchemeI colourScheme = viewport.getGlobalColourScheme();
6066     ColourMenuHelper.setColourSelected(colourMenu, colourScheme);
6067   }
6068
6069   /**
6070    * Open a dialog (if not already open) that allows the user to select and
6071    * calculate PCA or Tree analysis
6072    */
6073
6074   protected void openTreePcaDialog()
6075   {
6076     if (alignPanel.getCalculationDialog() == null)
6077     {
6078       new CalculationChooser(AlignFrame.this);
6079     }
6080   }
6081
6082   @Override
6083   protected void loadVcf_actionPerformed()
6084   {
6085     JalviewFileChooser chooser = new JalviewFileChooser(
6086             Cache.getProperty("LAST_DIRECTORY"));
6087     chooser.setFileView(new JalviewFileView());
6088     chooser.setDialogTitle(MessageManager.getString("label.load_vcf_file"));
6089     chooser.setToolTipText(MessageManager.getString("label.load_vcf_file"));
6090     final AlignFrame us = this;
6091     chooser.setResponseHandler(0, new Runnable()
6092     {
6093
6094       @Override
6095       public void run()
6096       {
6097         String choice = chooser.getSelectedFile().getPath();
6098         Cache.setProperty("LAST_DIRECTORY", choice);
6099         SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
6100         new VCFLoader(choice).loadVCF(seqs, us);
6101       }
6102     });
6103     chooser.showOpenDialog(null);
6104
6105   }
6106
6107   private Rectangle lastFeatureSettingsBounds = null;
6108
6109   @Override
6110   public void setFeatureSettingsGeometry(Rectangle bounds)
6111   {
6112     lastFeatureSettingsBounds = bounds;
6113   }
6114
6115   @Override
6116   public Rectangle getFeatureSettingsGeometry()
6117   {
6118     return lastFeatureSettingsBounds;
6119   }
6120
6121   public void scrollTo(int row, int column)
6122   {
6123     alignPanel.getSeqPanel().scrollTo(row, column);
6124   }
6125
6126   public void scrollToRow(int row)
6127   {
6128     alignPanel.getSeqPanel().scrollToRow(row);
6129   }
6130
6131   public void scrollToColumn(int column)
6132   {
6133     alignPanel.getSeqPanel().scrollToColumn(column);
6134   }
6135
6136   /**
6137    * BH 2019 from JalviewLite
6138    * 
6139    * get sequence feature groups that are hidden or shown
6140    * 
6141    * @param visible
6142    *          true is visible
6143    * @return list
6144    */
6145
6146   public String[] getFeatureGroupsOfState(boolean visible)
6147   {
6148     jalview.api.FeatureRenderer fr = null;
6149     if (alignPanel != null
6150             && (fr = alignPanel.getFeatureRenderer()) != null)
6151     {
6152       List<String> gps = fr.getGroups(visible);
6153       String[] _gps = gps.toArray(new String[gps.size()]);
6154       return _gps;
6155     }
6156     return null;
6157   }
6158
6159   /**
6160    * 
6161    * @return list of feature groups on the view
6162    */
6163
6164   public String[] getFeatureGroups()
6165   {
6166     jalview.api.FeatureRenderer fr = null;
6167     if (alignPanel != null
6168             && (fr = alignPanel.getFeatureRenderer()) != null)
6169     {
6170       List<String> gps = fr.getFeatureGroups();
6171       String[] _gps = gps.toArray(new String[gps.size()]);
6172       return _gps;
6173     }
6174     return null;
6175   }
6176
6177   public void select(SequenceGroup sel, ColumnSelection csel,
6178           HiddenColumns hidden)
6179   {
6180     alignPanel.getSeqPanel().selection(sel, csel, hidden, null);
6181   }
6182
6183   public int getID()
6184   {
6185     return id;
6186   }
6187
6188   static class PrintThread extends Thread
6189   {
6190     AlignmentPanel ap;
6191
6192     public PrintThread(AlignmentPanel ap)
6193     {
6194       this.ap = ap;
6195     }
6196
6197     static PageFormat pf;
6198
6199     @Override
6200     public void run()
6201     {
6202       PrinterJob printJob = PrinterJob.getPrinterJob();
6203
6204       if (pf != null)
6205       {
6206         printJob.setPrintable(ap, pf);
6207       }
6208       else
6209       {
6210         printJob.setPrintable(ap);
6211       }
6212
6213       if (printJob.printDialog())
6214       {
6215         try
6216         {
6217           printJob.print();
6218         } catch (Exception PrintException)
6219         {
6220           PrintException.printStackTrace();
6221         }
6222       }
6223     }
6224   }
6225 }
6226