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