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