JAL-3690 Let's enable web services (seriously this time)
[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       addServiceListeners();
422       if (!Platform.isJS())
423       {
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         Cache.log.info("Building web service menu for slivka");
4424         SlivkaWSDiscoverer discoverer = SlivkaWSDiscoverer.getInstance();
4425         JMenu submenu = new JMenu("Slivka");
4426         buildWebServicesMenu(discoverer, submenu);
4427         webService.add(submenu);
4428       }
4429       if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
4430       {
4431         WSDiscovererI jws2servs = Jws2Discoverer.getDiscoverer();
4432         JMenu submenu = new JMenu("JABAWS");
4433         buildLegacyWebServicesMenu(submenu);
4434         buildWebServicesMenu(jws2servs, submenu);
4435         webService.add(submenu);
4436       }
4437     });
4438   }
4439
4440   private void buildLegacyWebServicesMenu(JMenu menu)
4441   {
4442     JMenu secstrmenu = new JMenu("Secondary Structure Prediction");
4443     if (Discoverer.services != null && Discoverer.services.size() > 0) 
4444     {
4445       var secstrpred = Discoverer.services.get("SecStrPred");
4446       if (secstrpred != null) 
4447       {
4448         for (ext.vamsas.ServiceHandle sh : secstrpred) 
4449         {
4450           var menuProvider = Discoverer.getServiceClient(sh);
4451           menuProvider.attachWSMenuEntry(secstrmenu, this);
4452         }
4453       }
4454     }
4455     menu.add(secstrmenu);
4456   }
4457
4458   /**
4459    * Constructs the web services menu for the given discoverer under the
4460    * specified menu. This method must be called on the EDT
4461    * 
4462    * @param discoverer
4463    *          the discoverer used to build the menu
4464    * @param menu
4465    *          parent component which the elements will be attached to
4466    */
4467   private void buildWebServicesMenu(WSDiscovererI discoverer, JMenu menu)
4468   {
4469     if (discoverer.hasServices())
4470     {
4471       PreferredServiceRegistry.getRegistry().populateWSMenuEntry(
4472               discoverer.getServices(), sv -> buildWebServicesMenu(), menu,
4473               this, null);
4474     }
4475     if (discoverer.isRunning())
4476     {
4477       JMenuItem item = new JMenuItem("Service discovery in progress.");
4478       item.setEnabled(false);
4479       menu.add(item);
4480     }
4481     else if (!discoverer.hasServices())
4482     {
4483       JMenuItem item = new JMenuItem("No services available.");
4484       item.setEnabled(false);
4485       menu.add(item);
4486     }
4487   }
4488
4489   /**
4490    * construct any groupURL type service menu entries.
4491    * 
4492    * @param webService
4493    */
4494   protected void build_urlServiceMenu(JMenu webService)
4495   {
4496     // TODO: remove this code when 2.7 is released
4497     // DEBUG - alignmentView
4498     /*
4499      * JMenuItem testAlView = new JMenuItem("Test AlignmentView"); final
4500      * AlignFrame af = this; testAlView.addActionListener(new ActionListener() {
4501      * 
4502      * @Override public void actionPerformed(ActionEvent e) {
4503      * jalview.datamodel.AlignmentView
4504      * .testSelectionViews(af.viewport.getAlignment(),
4505      * af.viewport.getColumnSelection(), af.viewport.selectionGroup); }
4506      * 
4507      * }); webService.add(testAlView);
4508      */
4509     // TODO: refactor to RestClient discoverer and merge menu entries for
4510     // rest-style services with other types of analysis/calculation service
4511     // SHmmr test client - still being implemented.
4512     // DEBUG - alignmentView
4513
4514     for (jalview.ws.rest.RestClient client : jalview.ws.rest.RestClient
4515             .getRestClients())
4516     {
4517       client.attachWSMenuEntry(
4518               JvSwingUtils.findOrCreateMenu(webService, client.getAction()),
4519               this);
4520     }
4521   }
4522
4523   /**
4524    * Searches the alignment sequences for xRefs and builds the Show
4525    * Cross-References menu (formerly called Show Products), with database
4526    * sources for which cross-references are found (protein sources for a
4527    * nucleotide alignment and vice versa)
4528    * 
4529    * @return true if Show Cross-references menu should be enabled
4530    */
4531   public boolean canShowProducts()
4532   {
4533     SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
4534     AlignmentI dataset = viewport.getAlignment().getDataset();
4535
4536     showProducts.removeAll();
4537     final boolean dna = viewport.getAlignment().isNucleotide();
4538
4539     if (seqs == null || seqs.length == 0)
4540     {
4541       // nothing to see here.
4542       return false;
4543     }
4544
4545     boolean showp = false;
4546     try
4547     {
4548       List<String> ptypes = new CrossRef(seqs, dataset)
4549               .findXrefSourcesForSequences(dna);
4550
4551       for (final String source : ptypes)
4552       {
4553         showp = true;
4554         final AlignFrame af = this;
4555         JMenuItem xtype = new JMenuItem(source);
4556         xtype.addActionListener(new ActionListener()
4557         {
4558           @Override
4559           public void actionPerformed(ActionEvent e)
4560           {
4561             showProductsFor(af.viewport.getSequenceSelection(), dna,
4562                     source);
4563           }
4564         });
4565         showProducts.add(xtype);
4566       }
4567       showProducts.setVisible(showp);
4568       showProducts.setEnabled(showp);
4569     } catch (Exception e)
4570     {
4571       Cache.log.warn(
4572               "canShowProducts threw an exception - please report to help@jalview.org",
4573               e);
4574       return false;
4575     }
4576     return showp;
4577   }
4578
4579   /**
4580    * Finds and displays cross-references for the selected sequences (protein
4581    * products for nucleotide sequences, dna coding sequences for peptides).
4582    * 
4583    * @param sel
4584    *          the sequences to show cross-references for
4585    * @param dna
4586    *          true if from a nucleotide alignment (so showing proteins)
4587    * @param source
4588    *          the database to show cross-references for
4589    */
4590   protected void showProductsFor(final SequenceI[] sel, final boolean _odna,
4591           final String source)
4592   {
4593     new Thread(CrossRefAction.getHandlerFor(sel, _odna, source, this))
4594             .start();
4595   }
4596
4597   /**
4598    * Construct and display a new frame containing the translation of this
4599    * frame's DNA sequences to their aligned protein (amino acid) equivalents.
4600    */
4601   @Override
4602   public void showTranslation_actionPerformed(GeneticCodeI codeTable)
4603   {
4604     AlignmentI al = null;
4605     try
4606     {
4607       Dna dna = new Dna(viewport, viewport.getViewAsVisibleContigs(true));
4608
4609       al = dna.translateCdna(codeTable);
4610     } catch (Exception ex)
4611     {
4612       jalview.bin.Cache.log.error(
4613               "Exception during translation. Please report this !", ex);
4614       final String msg = MessageManager.getString(
4615               "label.error_when_translating_sequences_submit_bug_report");
4616       final String errorTitle = MessageManager
4617               .getString("label.implementation_error")
4618               + MessageManager.getString("label.translation_failed");
4619       JvOptionPane.showMessageDialog(Desktop.desktop, msg, errorTitle,
4620               JvOptionPane.ERROR_MESSAGE);
4621       return;
4622     }
4623     if (al == null || al.getHeight() == 0)
4624     {
4625       final String msg = MessageManager.getString(
4626               "label.select_at_least_three_bases_in_at_least_one_sequence_to_cDNA_translation");
4627       final String errorTitle = MessageManager
4628               .getString("label.translation_failed");
4629       JvOptionPane.showMessageDialog(Desktop.desktop, msg, errorTitle,
4630               JvOptionPane.WARNING_MESSAGE);
4631     }
4632     else
4633     {
4634       AlignFrame af = new AlignFrame(al, DEFAULT_WIDTH, DEFAULT_HEIGHT);
4635       af.setFileFormat(this.currentFileFormat);
4636       final String newTitle = MessageManager
4637               .formatMessage("label.translation_of_params", new Object[]
4638               { this.getTitle(), codeTable.getId() });
4639       af.setTitle(newTitle);
4640       if (Cache.getDefault(Preferences.ENABLE_SPLIT_FRAME, true))
4641       {
4642         final SequenceI[] seqs = viewport.getSelectionAsNewSequence();
4643         viewport.openSplitFrame(af, new Alignment(seqs));
4644       }
4645       else
4646       {
4647         Desktop.addInternalFrame(af, newTitle, DEFAULT_WIDTH,
4648                 DEFAULT_HEIGHT);
4649       }
4650     }
4651   }
4652
4653   /**
4654    * Set the file format
4655    * 
4656    * @param format
4657    */
4658   public void setFileFormat(FileFormatI format)
4659   {
4660     this.currentFileFormat = format;
4661   }
4662
4663   /**
4664    * Try to load a features file onto the alignment.
4665    * 
4666    * @param file
4667    *          contents or path to retrieve file or a File object
4668    * @param sourceType
4669    *          access mode of file (see jalview.io.AlignFile)
4670    * @return true if features file was parsed correctly.
4671    */
4672   public boolean parseFeaturesFile(Object file, DataSourceType sourceType)
4673   {
4674     // BH 2018
4675     return avc.parseFeaturesFile(file, sourceType,
4676             Cache.getDefault("RELAXEDSEQIDMATCHING", false));
4677
4678   }
4679
4680   @Override
4681   public void refreshFeatureUI(boolean enableIfNecessary)
4682   {
4683     // note - currently this is only still here rather than in the controller
4684     // because of the featureSettings hard reference that is yet to be
4685     // abstracted
4686     if (enableIfNecessary)
4687     {
4688       viewport.setShowSequenceFeatures(true);
4689       showSeqFeatures.setSelected(true);
4690     }
4691
4692   }
4693
4694   @Override
4695   public void dragEnter(DropTargetDragEvent evt)
4696   {
4697   }
4698
4699   @Override
4700   public void dragExit(DropTargetEvent evt)
4701   {
4702   }
4703
4704   @Override
4705   public void dragOver(DropTargetDragEvent evt)
4706   {
4707   }
4708
4709   @Override
4710   public void dropActionChanged(DropTargetDragEvent evt)
4711   {
4712   }
4713
4714   @Override
4715   public void drop(DropTargetDropEvent evt)
4716   {
4717     // JAL-1552 - acceptDrop required before getTransferable call for
4718     // Java's Transferable for native dnd
4719     evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
4720     Transferable t = evt.getTransferable();
4721
4722     final AlignFrame thisaf = this;
4723     final List<Object> files = new ArrayList<>();
4724     List<DataSourceType> protocols = new ArrayList<>();
4725
4726     try
4727     {
4728       Desktop.transferFromDropTarget(files, protocols, evt, t);
4729     } catch (Exception e)
4730     {
4731       e.printStackTrace();
4732     }
4733     if (files != null)
4734     {
4735       new Thread(new Runnable()
4736       {
4737         @Override
4738         public void run()
4739         {
4740           try
4741           {
4742             // check to see if any of these files have names matching sequences
4743             // in
4744             // the alignment
4745             SequenceIdMatcher idm = new SequenceIdMatcher(
4746                     viewport.getAlignment().getSequencesArray());
4747             /**
4748              * Object[] { String,SequenceI}
4749              */
4750             ArrayList<Object[]> filesmatched = new ArrayList<>();
4751             ArrayList<Object> filesnotmatched = new ArrayList<>();
4752             for (int i = 0; i < files.size(); i++)
4753             {
4754               // BH 2018
4755               Object file = files.get(i);
4756               String fileName = file.toString();
4757               String pdbfn = "";
4758               DataSourceType protocol = (file instanceof File
4759                       ? DataSourceType.FILE
4760                       : FormatAdapter.checkProtocol(fileName));
4761               if (protocol == DataSourceType.FILE)
4762               {
4763                 File fl;
4764                 if (file instanceof File)
4765                 {
4766                   fl = (File) file;
4767                   Platform.cacheFileData(fl);
4768                 }
4769                 else
4770                 {
4771                   fl = new File(fileName);
4772                 }
4773                 pdbfn = fl.getName();
4774               }
4775               else if (protocol == DataSourceType.URL)
4776               {
4777                 URL url = new URL(fileName);
4778                 pdbfn = url.getFile();
4779               }
4780               if (pdbfn.length() > 0)
4781               {
4782                 // attempt to find a match in the alignment
4783                 SequenceI[] mtch = idm.findAllIdMatches(pdbfn);
4784                 int l = 0, c = pdbfn.indexOf(".");
4785                 while (mtch == null && c != -1)
4786                 {
4787                   do
4788                   {
4789                     l = c;
4790                   } while ((c = pdbfn.indexOf(".", l)) > l);
4791                   if (l > -1)
4792                   {
4793                     pdbfn = pdbfn.substring(0, l);
4794                   }
4795                   mtch = idm.findAllIdMatches(pdbfn);
4796                 }
4797                 if (mtch != null)
4798                 {
4799                   FileFormatI type;
4800                   try
4801                   {
4802                     type = new IdentifyFile().identify(file, protocol);
4803                   } catch (Exception ex)
4804                   {
4805                     type = null;
4806                   }
4807                   if (type != null && type.isStructureFile())
4808                   {
4809                     filesmatched.add(new Object[] { file, protocol, mtch });
4810                     continue;
4811                   }
4812                 }
4813                 // File wasn't named like one of the sequences or wasn't a PDB
4814                 // file.
4815                 filesnotmatched.add(file);
4816               }
4817             }
4818             int assocfiles = 0;
4819             if (filesmatched.size() > 0)
4820             {
4821               boolean autoAssociate = Cache
4822                       .getDefault("AUTOASSOCIATE_PDBANDSEQS", false);
4823               if (!autoAssociate)
4824               {
4825                 String msg = MessageManager.formatMessage(
4826                         "label.automatically_associate_structure_files_with_sequences_same_name",
4827                         new Object[]
4828                         { Integer.valueOf(filesmatched.size())
4829                                 .toString() });
4830                 String ttl = MessageManager.getString(
4831                         "label.automatically_associate_structure_files_by_name");
4832                 int choice = JvOptionPane.showConfirmDialog(thisaf, msg,
4833                         ttl, JvOptionPane.YES_NO_OPTION);
4834                 autoAssociate = choice == JvOptionPane.YES_OPTION;
4835               }
4836               if (autoAssociate)
4837               {
4838                 for (Object[] fm : filesmatched)
4839                 {
4840                   // try and associate
4841                   // TODO: may want to set a standard ID naming formalism for
4842                   // associating PDB files which have no IDs.
4843                   for (SequenceI toassoc : (SequenceI[]) fm[2])
4844                   {
4845                     PDBEntry pe = new AssociatePdbFileWithSeq()
4846                             .associatePdbWithSeq(fm[0].toString(),
4847                                     (DataSourceType) fm[1], toassoc, false,
4848                                     Desktop.instance);
4849                     if (pe != null)
4850                     {
4851                       System.err.println("Associated file : "
4852                               + (fm[0].toString()) + " with "
4853                               + toassoc.getDisplayId(true));
4854                       assocfiles++;
4855                     }
4856                   }
4857                   // TODO: do we need to update overview ? only if features are
4858                   // shown I guess
4859                   alignPanel.paintAlignment(true, false);
4860                 }
4861               }
4862               else
4863               {
4864                 /*
4865                  * add declined structures as sequences
4866                  */
4867                 for (Object[] o : filesmatched)
4868                 {
4869                   filesnotmatched.add(o[0]);
4870                 }
4871               }
4872             }
4873             if (filesnotmatched.size() > 0)
4874             {
4875               if (assocfiles > 0 && (Cache.getDefault(
4876                       "AUTOASSOCIATE_PDBANDSEQS_IGNOREOTHERS", false)
4877                       || JvOptionPane.showConfirmDialog(thisaf,
4878                               "<html>" + MessageManager.formatMessage(
4879                                       "label.ignore_unmatched_dropped_files_info",
4880                                       new Object[]
4881                                       { Integer.valueOf(
4882                                               filesnotmatched.size())
4883                                               .toString() })
4884                                       + "</html>",
4885                               MessageManager.getString(
4886                                       "label.ignore_unmatched_dropped_files"),
4887                               JvOptionPane.YES_NO_OPTION) == JvOptionPane.YES_OPTION))
4888               {
4889                 return;
4890               }
4891               for (Object fn : filesnotmatched)
4892               {
4893                 loadJalviewDataFile(fn, null, null, null);
4894               }
4895
4896             }
4897           } catch (Exception ex)
4898           {
4899             ex.printStackTrace();
4900           }
4901         }
4902       }).start();
4903     }
4904   }
4905
4906   /**
4907    * Attempt to load a "dropped" file or URL string, by testing in turn for
4908    * <ul>
4909    * <li>an Annotation file</li>
4910    * <li>a JNet file</li>
4911    * <li>a features file</li>
4912    * <li>else try to interpret as an alignment file</li>
4913    * </ul>
4914    * 
4915    * @param file
4916    *          either a filename or a URL string.
4917    * @throws InterruptedException
4918    * @throws IOException
4919    */
4920   public void loadJalviewDataFile(Object file, DataSourceType sourceType,
4921           FileFormatI format, SequenceI assocSeq)
4922   {
4923     // BH 2018 was String file
4924     try
4925     {
4926       if (sourceType == null)
4927       {
4928         sourceType = FormatAdapter.checkProtocol(file);
4929       }
4930       // if the file isn't identified, or not positively identified as some
4931       // other filetype (PFAM is default unidentified alignment file type) then
4932       // try to parse as annotation.
4933       boolean isAnnotation = (format == null
4934               || FileFormat.Pfam.equals(format))
4935                       ? new AnnotationFile().annotateAlignmentView(viewport,
4936                               file, sourceType)
4937                       : false;
4938
4939       if (!isAnnotation)
4940       {
4941         // first see if its a T-COFFEE score file
4942         TCoffeeScoreFile tcf = null;
4943         try
4944         {
4945           tcf = new TCoffeeScoreFile(file, sourceType);
4946           if (tcf.isValid())
4947           {
4948             if (tcf.annotateAlignment(viewport.getAlignment(), true))
4949             {
4950               buildColourMenu();
4951               changeColour(
4952                       new TCoffeeColourScheme(viewport.getAlignment()));
4953               isAnnotation = true;
4954               setStatus(MessageManager.getString(
4955                       "label.successfully_pasted_tcoffee_scores_to_alignment"));
4956             }
4957             else
4958             {
4959               // some problem - if no warning its probable that the ID matching
4960               // process didn't work
4961               JvOptionPane.showMessageDialog(Desktop.desktop,
4962                       tcf.getWarningMessage() == null
4963                               ? MessageManager.getString(
4964                                       "label.check_file_matches_sequence_ids_alignment")
4965                               : tcf.getWarningMessage(),
4966                       MessageManager.getString(
4967                               "label.problem_reading_tcoffee_score_file"),
4968                       JvOptionPane.WARNING_MESSAGE);
4969             }
4970           }
4971           else
4972           {
4973             tcf = null;
4974           }
4975         } catch (Exception x)
4976         {
4977           Cache.log.debug(
4978                   "Exception when processing data source as T-COFFEE score file",
4979                   x);
4980           tcf = null;
4981         }
4982         if (tcf == null)
4983         {
4984           // try to see if its a JNet 'concise' style annotation file *before*
4985           // we
4986           // try to parse it as a features file
4987           if (format == null)
4988           {
4989             format = new IdentifyFile().identify(file, sourceType);
4990           }
4991           if (FileFormat.ScoreMatrix == format)
4992           {
4993             ScoreMatrixFile sm = new ScoreMatrixFile(
4994                     new FileParse(file, sourceType));
4995             sm.parse();
4996             // todo: i18n this message
4997             setStatus(MessageManager.formatMessage(
4998                     "label.successfully_loaded_matrix",
4999                     sm.getMatrixName()));
5000           }
5001           else if (FileFormat.Jnet.equals(format))
5002           {
5003             JPredFile predictions = new JPredFile(file, sourceType);
5004             new JnetAnnotationMaker();
5005             JnetAnnotationMaker.add_annotation(predictions,
5006                     viewport.getAlignment(), 0, false);
5007             viewport.getAlignment().setupJPredAlignment();
5008             isAnnotation = true;
5009           }
5010           // else if (IdentifyFile.FeaturesFile.equals(format))
5011           else if (FileFormat.Features.equals(format))
5012           {
5013             if (parseFeaturesFile(file, sourceType))
5014             {
5015               SplitFrame splitFrame = (SplitFrame) getSplitViewContainer();
5016               if (splitFrame != null)
5017               {
5018                 splitFrame.repaint();
5019               }
5020               else
5021               {
5022                 alignPanel.paintAlignment(true, true);
5023               }
5024             }
5025           }
5026           else
5027           {
5028             new FileLoader().LoadFile(viewport, file, sourceType, format);
5029           }
5030         }
5031       }
5032       if (isAnnotation)
5033       {
5034         alignPanel.adjustAnnotationHeight();
5035         viewport.updateSequenceIdColours();
5036         buildSortByAnnotationScoresMenu();
5037         alignPanel.paintAlignment(true, true);
5038       }
5039     } catch (Exception ex)
5040     {
5041       ex.printStackTrace();
5042     } catch (OutOfMemoryError oom)
5043     {
5044       try
5045       {
5046         System.gc();
5047       } catch (Exception x)
5048       {
5049       }
5050       new OOMWarning(
5051               "loading data "
5052                       + (sourceType != null
5053                               ? (sourceType == DataSourceType.PASTE
5054                                       ? "from clipboard."
5055                                       : "using " + sourceType + " from "
5056                                               + file)
5057                               : ".")
5058                       + (format != null
5059                               ? "(parsing as '" + format + "' file)"
5060                               : ""),
5061               oom, Desktop.desktop);
5062     }
5063   }
5064
5065   /**
5066    * Method invoked by the ChangeListener on the tabbed pane, in other words
5067    * when a different tabbed pane is selected by the user or programmatically.
5068    */
5069   @Override
5070   public void tabSelectionChanged(int index)
5071   {
5072     if (index > -1)
5073     {
5074       alignPanel = alignPanels.get(index);
5075       viewport = alignPanel.av;
5076       avc.setViewportAndAlignmentPanel(viewport, alignPanel);
5077       setMenusFromViewport(viewport);
5078       if (featureSettings != null && featureSettings.isOpen()
5079               && featureSettings.fr.getViewport() != viewport)
5080       {
5081         if (viewport.isShowSequenceFeatures())
5082         {
5083           // refresh the featureSettings to reflect UI change
5084           showFeatureSettingsUI();
5085         }
5086         else
5087         {
5088           // close feature settings for this view.
5089           featureSettings.close();
5090         }
5091       }
5092
5093     }
5094
5095     /*
5096      * 'focus' any colour slider that is open to the selected viewport
5097      */
5098     if (viewport.getConservationSelected())
5099     {
5100       SliderPanel.setConservationSlider(alignPanel,
5101               viewport.getResidueShading(), alignPanel.getViewName());
5102     }
5103     else
5104     {
5105       SliderPanel.hideConservationSlider();
5106     }
5107     if (viewport.getAbovePIDThreshold())
5108     {
5109       SliderPanel.setPIDSliderSource(alignPanel,
5110               viewport.getResidueShading(), alignPanel.getViewName());
5111     }
5112     else
5113     {
5114       SliderPanel.hidePIDSlider();
5115     }
5116
5117     /*
5118      * If there is a frame linked to this one in a SplitPane, switch it to the
5119      * same view tab index. No infinite recursion of calls should happen, since
5120      * tabSelectionChanged() should not get invoked on setting the selected
5121      * index to an unchanged value. Guard against setting an invalid index
5122      * before the new view peer tab has been created.
5123      */
5124     final AlignViewportI peer = viewport.getCodingComplement();
5125     if (peer != null)
5126     {
5127       AlignFrame linkedAlignFrame = ((AlignViewport) peer)
5128               .getAlignPanel().alignFrame;
5129       if (linkedAlignFrame.tabbedPane.getTabCount() > index)
5130       {
5131         linkedAlignFrame.tabbedPane.setSelectedIndex(index);
5132       }
5133     }
5134   }
5135
5136   /**
5137    * On right mouse click on view tab, prompt for and set new view name.
5138    */
5139   @Override
5140   public void tabbedPane_mousePressed(MouseEvent e)
5141   {
5142     if (e.isPopupTrigger())
5143     {
5144       String msg = MessageManager.getString("label.enter_view_name");
5145       String ttl = tabbedPane.getTitleAt(tabbedPane.getSelectedIndex());
5146       String reply = JvOptionPane.showInputDialog(msg, ttl);
5147
5148       if (reply != null)
5149       {
5150         viewport.setViewName(reply);
5151         // TODO warn if reply is in getExistingViewNames()?
5152         tabbedPane.setTitleAt(tabbedPane.getSelectedIndex(), reply);
5153       }
5154     }
5155   }
5156
5157   public AlignViewport getCurrentView()
5158   {
5159     return viewport;
5160   }
5161
5162   /**
5163    * Open the dialog for regex description parsing.
5164    */
5165   @Override
5166   protected void extractScores_actionPerformed(ActionEvent e)
5167   {
5168     ParseProperties pp = new jalview.analysis.ParseProperties(
5169             viewport.getAlignment());
5170     // TODO: verify regex and introduce GUI dialog for version 2.5
5171     // if (pp.getScoresFromDescription("col", "score column ",
5172     // "\\W*([-+]?\\d*\\.?\\d*e?-?\\d*)\\W+([-+]?\\d*\\.?\\d*e?-?\\d*)",
5173     // true)>0)
5174     if (pp.getScoresFromDescription("description column",
5175             "score in description column ", "\\W*([-+eE0-9.]+)", true) > 0)
5176     {
5177       buildSortByAnnotationScoresMenu();
5178     }
5179   }
5180
5181   /*
5182    * (non-Javadoc)
5183    * 
5184    * @see
5185    * jalview.jbgui.GAlignFrame#showDbRefs_actionPerformed(java.awt.event.ActionEvent
5186    * )
5187    */
5188   @Override
5189   protected void showDbRefs_actionPerformed(ActionEvent e)
5190   {
5191     viewport.setShowDBRefs(showDbRefsMenuitem.isSelected());
5192   }
5193
5194   /*
5195    * (non-Javadoc)
5196    * 
5197    * @seejalview.jbgui.GAlignFrame#showNpFeats_actionPerformed(java.awt.event.
5198    * ActionEvent)
5199    */
5200   @Override
5201   protected void showNpFeats_actionPerformed(ActionEvent e)
5202   {
5203     viewport.setShowNPFeats(showNpFeatsMenuitem.isSelected());
5204   }
5205
5206   /**
5207    * find the viewport amongst the tabs in this alignment frame and close that
5208    * tab
5209    * 
5210    * @param av
5211    */
5212   public boolean closeView(AlignViewportI av)
5213   {
5214     if (viewport == av)
5215     {
5216       this.closeMenuItem_actionPerformed(false);
5217       return true;
5218     }
5219     Component[] comp = tabbedPane.getComponents();
5220     for (int i = 0; comp != null && i < comp.length; i++)
5221     {
5222       if (comp[i] instanceof AlignmentPanel)
5223       {
5224         if (((AlignmentPanel) comp[i]).av == av)
5225         {
5226           // close the view.
5227           closeView((AlignmentPanel) comp[i]);
5228           return true;
5229         }
5230       }
5231     }
5232     return false;
5233   }
5234
5235   protected void build_fetchdbmenu(JMenu webService)
5236   {
5237     // Temporary hack - DBRef Fetcher always top level ws entry.
5238     // TODO We probably want to store a sequence database checklist in
5239     // preferences and have checkboxes.. rather than individual sources selected
5240     // here
5241     final JMenu rfetch = new JMenu(
5242             MessageManager.getString("action.fetch_db_references"));
5243     rfetch.setToolTipText(MessageManager.getString(
5244             "label.retrieve_parse_sequence_database_records_alignment_or_selected_sequences"));
5245     webService.add(rfetch);
5246
5247     final JCheckBoxMenuItem trimrs = new JCheckBoxMenuItem(
5248             MessageManager.getString("option.trim_retrieved_seqs"));
5249     trimrs.setToolTipText(
5250             MessageManager.getString("label.trim_retrieved_sequences"));
5251     trimrs.setSelected(
5252             Cache.getDefault(DBRefFetcher.TRIM_RETRIEVED_SEQUENCES, true));
5253     trimrs.addActionListener(new ActionListener()
5254     {
5255       @Override
5256       public void actionPerformed(ActionEvent e)
5257       {
5258         trimrs.setSelected(trimrs.isSelected());
5259         Cache.setProperty(DBRefFetcher.TRIM_RETRIEVED_SEQUENCES,
5260                 Boolean.valueOf(trimrs.isSelected()).toString());
5261       }
5262     });
5263     rfetch.add(trimrs);
5264     JMenuItem fetchr = new JMenuItem(
5265             MessageManager.getString("label.standard_databases"));
5266     fetchr.setToolTipText(
5267             MessageManager.getString("label.fetch_embl_uniprot"));
5268     fetchr.addActionListener(new ActionListener()
5269     {
5270
5271       @Override
5272       public void actionPerformed(ActionEvent e)
5273       {
5274         new Thread(new Runnable()
5275         {
5276           @Override
5277           public void run()
5278           {
5279             boolean isNucleotide = alignPanel.alignFrame.getViewport()
5280                     .getAlignment().isNucleotide();
5281             DBRefFetcher dbRefFetcher = new DBRefFetcher(
5282                     alignPanel.av.getSequenceSelection(),
5283                     alignPanel.alignFrame, null,
5284                     alignPanel.alignFrame.featureSettings, isNucleotide);
5285             dbRefFetcher.addListener(new FetchFinishedListenerI()
5286             {
5287               @Override
5288               public void finished()
5289               {
5290
5291                 for (FeatureSettingsModelI srcSettings : dbRefFetcher
5292                         .getFeatureSettingsModels())
5293                 {
5294
5295                   alignPanel.av.mergeFeaturesStyle(srcSettings);
5296                 }
5297                 AlignFrame.this.setMenusForViewport();
5298               }
5299             });
5300             dbRefFetcher.fetchDBRefs(false);
5301           }
5302         }).start();
5303
5304       }
5305
5306     });
5307     rfetch.add(fetchr);
5308     new Thread(new Runnable()
5309     {
5310       @Override
5311       public void run()
5312       {
5313         final jalview.ws.SequenceFetcher sf = jalview.gui.SequenceFetcher
5314                 .getSequenceFetcherSingleton();
5315         javax.swing.SwingUtilities.invokeLater(new Runnable()
5316         {
5317           @Override
5318           public void run()
5319           {
5320             String[] dbclasses = sf.getNonAlignmentSources();
5321             List<DbSourceProxy> otherdb;
5322             JMenu dfetch = new JMenu();
5323             JMenu ifetch = new JMenu();
5324             JMenuItem fetchr = null;
5325             int comp = 0, icomp = 0, mcomp = 15;
5326             String mname = null;
5327             int dbi = 0;
5328             for (String dbclass : dbclasses)
5329             {
5330               otherdb = sf.getSourceProxy(dbclass);
5331               // add a single entry for this class, or submenu allowing 'fetch
5332               // all' or pick one
5333               if (otherdb == null || otherdb.size() < 1)
5334               {
5335                 continue;
5336               }
5337               if (mname == null)
5338               {
5339                 mname = "From " + dbclass;
5340               }
5341               if (otherdb.size() == 1)
5342               {
5343                 final DbSourceProxy[] dassource = otherdb
5344                         .toArray(new DbSourceProxy[0]);
5345                 DbSourceProxy src = otherdb.get(0);
5346                 fetchr = new JMenuItem(src.getDbSource());
5347                 fetchr.addActionListener(new ActionListener()
5348                 {
5349
5350                   @Override
5351                   public void actionPerformed(ActionEvent e)
5352                   {
5353                     new Thread(new Runnable()
5354                     {
5355
5356                       @Override
5357                       public void run()
5358                       {
5359                         boolean isNucleotide = alignPanel.alignFrame
5360                                 .getViewport().getAlignment()
5361                                 .isNucleotide();
5362                         DBRefFetcher dbRefFetcher = new DBRefFetcher(
5363                                 alignPanel.av.getSequenceSelection(),
5364                                 alignPanel.alignFrame, dassource,
5365                                 alignPanel.alignFrame.featureSettings,
5366                                 isNucleotide);
5367                         dbRefFetcher
5368                                 .addListener(new FetchFinishedListenerI()
5369                                 {
5370                                   @Override
5371                                   public void finished()
5372                                   {
5373                                     FeatureSettingsModelI srcSettings = dassource[0]
5374                                             .getFeatureColourScheme();
5375                                     alignPanel.av.mergeFeaturesStyle(
5376                                             srcSettings);
5377                                     AlignFrame.this.setMenusForViewport();
5378                                   }
5379                                 });
5380                         dbRefFetcher.fetchDBRefs(false);
5381                       }
5382                     }).start();
5383                   }
5384
5385                 });
5386                 fetchr.setToolTipText(JvSwingUtils.wrapTooltip(true,
5387                         MessageManager.formatMessage(
5388                                 "label.fetch_retrieve_from", new Object[]
5389                                 { src.getDbName() })));
5390                 dfetch.add(fetchr);
5391                 comp++;
5392               }
5393               else
5394               {
5395                 final DbSourceProxy[] dassource = otherdb
5396                         .toArray(new DbSourceProxy[0]);
5397                 // fetch all entry
5398                 DbSourceProxy src = otherdb.get(0);
5399                 fetchr = new JMenuItem(MessageManager
5400                         .formatMessage("label.fetch_all_param", new Object[]
5401                         { src.getDbSource() }));
5402                 fetchr.addActionListener(new ActionListener()
5403                 {
5404                   @Override
5405                   public void actionPerformed(ActionEvent e)
5406                   {
5407                     new Thread(new Runnable()
5408                     {
5409
5410                       @Override
5411                       public void run()
5412                       {
5413                         boolean isNucleotide = alignPanel.alignFrame
5414                                 .getViewport().getAlignment()
5415                                 .isNucleotide();
5416                         DBRefFetcher dbRefFetcher = new DBRefFetcher(
5417                                 alignPanel.av.getSequenceSelection(),
5418                                 alignPanel.alignFrame, dassource,
5419                                 alignPanel.alignFrame.featureSettings,
5420                                 isNucleotide);
5421                         dbRefFetcher
5422                                 .addListener(new FetchFinishedListenerI()
5423                                 {
5424                                   @Override
5425                                   public void finished()
5426                                   {
5427                                     AlignFrame.this.setMenusForViewport();
5428                                   }
5429                                 });
5430                         dbRefFetcher.fetchDBRefs(false);
5431                       }
5432                     }).start();
5433                   }
5434                 });
5435
5436                 fetchr.setToolTipText(JvSwingUtils.wrapTooltip(true,
5437                         MessageManager.formatMessage(
5438                                 "label.fetch_retrieve_from_all_sources",
5439                                 new Object[]
5440                                 { Integer.valueOf(otherdb.size())
5441                                         .toString(),
5442                                     src.getDbSource(), src.getDbName() })));
5443                 dfetch.add(fetchr);
5444                 comp++;
5445                 // and then build the rest of the individual menus
5446                 ifetch = new JMenu(MessageManager.formatMessage(
5447                         "label.source_from_db_source", new Object[]
5448                         { src.getDbSource() }));
5449                 icomp = 0;
5450                 String imname = null;
5451                 int i = 0;
5452                 for (DbSourceProxy sproxy : otherdb)
5453                 {
5454                   String dbname = sproxy.getDbName();
5455                   String sname = dbname.length() > 5
5456                           ? dbname.substring(0, 5) + "..."
5457                           : dbname;
5458                   String msname = dbname.length() > 10
5459                           ? dbname.substring(0, 10) + "..."
5460                           : dbname;
5461                   if (imname == null)
5462                   {
5463                     imname = MessageManager
5464                             .formatMessage("label.from_msname", new Object[]
5465                             { sname });
5466                   }
5467                   fetchr = new JMenuItem(msname);
5468                   final DbSourceProxy[] dassrc = { sproxy };
5469                   fetchr.addActionListener(new ActionListener()
5470                   {
5471
5472                     @Override
5473                     public void actionPerformed(ActionEvent e)
5474                     {
5475                       new Thread(new Runnable()
5476                       {
5477
5478                         @Override
5479                         public void run()
5480                         {
5481                           boolean isNucleotide = alignPanel.alignFrame
5482                                   .getViewport().getAlignment()
5483                                   .isNucleotide();
5484                           DBRefFetcher dbRefFetcher = new DBRefFetcher(
5485                                   alignPanel.av.getSequenceSelection(),
5486                                   alignPanel.alignFrame, dassrc,
5487                                   alignPanel.alignFrame.featureSettings,
5488                                   isNucleotide);
5489                           dbRefFetcher
5490                                   .addListener(new FetchFinishedListenerI()
5491                                   {
5492                                     @Override
5493                                     public void finished()
5494                                     {
5495                                       AlignFrame.this.setMenusForViewport();
5496                                     }
5497                                   });
5498                           dbRefFetcher.fetchDBRefs(false);
5499                         }
5500                       }).start();
5501                     }
5502
5503                   });
5504                   fetchr.setToolTipText(
5505                           "<html>" + MessageManager.formatMessage(
5506                                   "label.fetch_retrieve_from", new Object[]
5507                                   { dbname }));
5508                   ifetch.add(fetchr);
5509                   ++i;
5510                   if (++icomp >= mcomp || i == (otherdb.size()))
5511                   {
5512                     ifetch.setText(MessageManager.formatMessage(
5513                             "label.source_to_target", imname, sname));
5514                     dfetch.add(ifetch);
5515                     ifetch = new JMenu();
5516                     imname = null;
5517                     icomp = 0;
5518                     comp++;
5519                   }
5520                 }
5521               }
5522               ++dbi;
5523               if (comp >= mcomp || dbi >= (dbclasses.length))
5524               {
5525                 dfetch.setText(MessageManager.formatMessage(
5526                         "label.source_to_target", mname, dbclass));
5527                 rfetch.add(dfetch);
5528                 dfetch = new JMenu();
5529                 mname = null;
5530                 comp = 0;
5531               }
5532             }
5533           }
5534         });
5535       }
5536     }).start();
5537
5538   }
5539
5540   /**
5541    * Left justify the whole alignment.
5542    */
5543   @Override
5544   protected void justifyLeftMenuItem_actionPerformed(ActionEvent e)
5545   {
5546     AlignmentI al = viewport.getAlignment();
5547     al.justify(false);
5548     viewport.firePropertyChange("alignment", null, al);
5549   }
5550
5551   /**
5552    * Right justify the whole alignment.
5553    */
5554   @Override
5555   protected void justifyRightMenuItem_actionPerformed(ActionEvent e)
5556   {
5557     AlignmentI al = viewport.getAlignment();
5558     al.justify(true);
5559     viewport.firePropertyChange("alignment", null, al);
5560   }
5561
5562   @Override
5563   public void setShowSeqFeatures(boolean b)
5564   {
5565     showSeqFeatures.setSelected(b);
5566     viewport.setShowSequenceFeatures(b);
5567   }
5568
5569   /*
5570    * (non-Javadoc)
5571    * 
5572    * @see
5573    * jalview.jbgui.GAlignFrame#showUnconservedMenuItem_actionPerformed(java.
5574    * awt.event.ActionEvent)
5575    */
5576   @Override
5577   protected void showUnconservedMenuItem_actionPerformed(ActionEvent e)
5578   {
5579     viewport.setShowUnconserved(showNonconservedMenuItem.getState());
5580     alignPanel.paintAlignment(false, false);
5581   }
5582
5583   /*
5584    * (non-Javadoc)
5585    * 
5586    * @see
5587    * jalview.jbgui.GAlignFrame#showGroupConsensus_actionPerformed(java.awt.event
5588    * .ActionEvent)
5589    */
5590   @Override
5591   protected void showGroupConsensus_actionPerformed(ActionEvent e)
5592   {
5593     viewport.setShowGroupConsensus(showGroupConsensus.getState());
5594     alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
5595
5596   }
5597
5598   /*
5599    * (non-Javadoc)
5600    * 
5601    * @see
5602    * jalview.jbgui.GAlignFrame#showGroupConservation_actionPerformed(java.awt
5603    * .event.ActionEvent)
5604    */
5605   @Override
5606   protected void showGroupConservation_actionPerformed(ActionEvent e)
5607   {
5608     viewport.setShowGroupConservation(showGroupConservation.getState());
5609     alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
5610   }
5611
5612   /*
5613    * (non-Javadoc)
5614    * 
5615    * @see
5616    * jalview.jbgui.GAlignFrame#showConsensusHistogram_actionPerformed(java.awt
5617    * .event.ActionEvent)
5618    */
5619   @Override
5620   protected void showConsensusHistogram_actionPerformed(ActionEvent e)
5621   {
5622     viewport.setShowConsensusHistogram(showConsensusHistogram.getState());
5623     alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
5624   }
5625
5626   /*
5627    * (non-Javadoc)
5628    * 
5629    * @see
5630    * jalview.jbgui.GAlignFrame#showConsensusProfile_actionPerformed(java.awt
5631    * .event.ActionEvent)
5632    */
5633   @Override
5634   protected void showSequenceLogo_actionPerformed(ActionEvent e)
5635   {
5636     viewport.setShowSequenceLogo(showSequenceLogo.getState());
5637     alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
5638   }
5639
5640   @Override
5641   protected void normaliseSequenceLogo_actionPerformed(ActionEvent e)
5642   {
5643     showSequenceLogo.setState(true);
5644     viewport.setShowSequenceLogo(true);
5645     viewport.setNormaliseSequenceLogo(normaliseSequenceLogo.getState());
5646     alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
5647   }
5648
5649   @Override
5650   protected void applyAutoAnnotationSettings_actionPerformed(ActionEvent e)
5651   {
5652     alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
5653   }
5654
5655   /*
5656    * (non-Javadoc)
5657    * 
5658    * @see
5659    * jalview.jbgui.GAlignFrame#makeGrpsFromSelection_actionPerformed(java.awt
5660    * .event.ActionEvent)
5661    */
5662   @Override
5663   protected void makeGrpsFromSelection_actionPerformed(ActionEvent e)
5664   {
5665     if (avc.makeGroupsFromSelection())
5666     {
5667       PaintRefresher.Refresh(this, viewport.getSequenceSetId());
5668       alignPanel.updateAnnotation();
5669       alignPanel.paintAlignment(true,
5670               viewport.needToUpdateStructureViews());
5671     }
5672   }
5673
5674   public void clearAlignmentSeqRep()
5675   {
5676     // TODO refactor alignmentseqrep to controller
5677     if (viewport.getAlignment().hasSeqrep())
5678     {
5679       viewport.getAlignment().setSeqrep(null);
5680       PaintRefresher.Refresh(this, viewport.getSequenceSetId());
5681       alignPanel.updateAnnotation();
5682       alignPanel.paintAlignment(true, true);
5683     }
5684   }
5685
5686   @Override
5687   protected void createGroup_actionPerformed(ActionEvent e)
5688   {
5689     if (avc.createGroup())
5690     {
5691       if (applyAutoAnnotationSettings.isSelected())
5692       {
5693         alignPanel.updateAnnotation(true, false);
5694       }
5695       alignPanel.alignmentChanged();
5696     }
5697   }
5698
5699   @Override
5700   protected void unGroup_actionPerformed(ActionEvent e)
5701   {
5702     if (avc.unGroup())
5703     {
5704       alignPanel.alignmentChanged();
5705     }
5706   }
5707
5708   /**
5709    * make the given alignmentPanel the currently selected tab
5710    * 
5711    * @param alignmentPanel
5712    */
5713   public void setDisplayedView(AlignmentPanel alignmentPanel)
5714   {
5715     if (!viewport.getSequenceSetId()
5716             .equals(alignmentPanel.av.getSequenceSetId()))
5717     {
5718       throw new Error(MessageManager.getString(
5719               "error.implementation_error_cannot_show_view_alignment_frame"));
5720     }
5721     if (tabbedPane != null && tabbedPane.getTabCount() > 0 && alignPanels
5722             .indexOf(alignmentPanel) != tabbedPane.getSelectedIndex())
5723     {
5724       tabbedPane.setSelectedIndex(alignPanels.indexOf(alignmentPanel));
5725     }
5726   }
5727
5728   /**
5729    * Action on selection of menu options to Show or Hide annotations.
5730    * 
5731    * @param visible
5732    * @param forSequences
5733    *          update sequence-related annotations
5734    * @param forAlignment
5735    *          update non-sequence-related annotations
5736    */
5737   @Override
5738   protected void setAnnotationsVisibility(boolean visible,
5739           boolean forSequences, boolean forAlignment)
5740   {
5741     AlignmentAnnotation[] anns = alignPanel.getAlignment()
5742             .getAlignmentAnnotation();
5743     if (anns == null)
5744     {
5745       return;
5746     }
5747     for (AlignmentAnnotation aa : anns)
5748     {
5749       /*
5750        * don't display non-positional annotations on an alignment
5751        */
5752       if (aa.annotations == null)
5753       {
5754         continue;
5755       }
5756       boolean apply = (aa.sequenceRef == null && forAlignment)
5757               || (aa.sequenceRef != null && forSequences);
5758       if (apply)
5759       {
5760         aa.visible = visible;
5761       }
5762     }
5763     alignPanel.validateAnnotationDimensions(true);
5764     alignPanel.alignmentChanged();
5765   }
5766
5767   /**
5768    * Store selected annotation sort order for the view and repaint.
5769    */
5770   @Override
5771   protected void sortAnnotations_actionPerformed()
5772   {
5773     this.alignPanel.av.setSortAnnotationsBy(getAnnotationSortOrder());
5774     this.alignPanel.av
5775             .setShowAutocalculatedAbove(isShowAutoCalculatedAbove());
5776     alignPanel.paintAlignment(false, false);
5777   }
5778
5779   /**
5780    * 
5781    * @return alignment panels in this alignment frame
5782    */
5783   public List<? extends AlignmentViewPanel> getAlignPanels()
5784   {
5785     // alignPanels is never null
5786     // return alignPanels == null ? Arrays.asList(alignPanel) : alignPanels;
5787     return alignPanels;
5788   }
5789
5790   /**
5791    * Open a new alignment window, with the cDNA associated with this (protein)
5792    * alignment, aligned as is the protein.
5793    */
5794   protected void viewAsCdna_actionPerformed()
5795   {
5796     // TODO no longer a menu action - refactor as required
5797     final AlignmentI alignment = getViewport().getAlignment();
5798     List<AlignedCodonFrame> mappings = alignment.getCodonFrames();
5799     if (mappings == null)
5800     {
5801       return;
5802     }
5803     List<SequenceI> cdnaSeqs = new ArrayList<>();
5804     for (SequenceI aaSeq : alignment.getSequences())
5805     {
5806       for (AlignedCodonFrame acf : mappings)
5807       {
5808         SequenceI dnaSeq = acf.getDnaForAaSeq(aaSeq.getDatasetSequence());
5809         if (dnaSeq != null)
5810         {
5811           /*
5812            * There is a cDNA mapping for this protein sequence - add to new
5813            * alignment. It will share the same dataset sequence as other mapped
5814            * cDNA (no new mappings need to be created).
5815            */
5816           final Sequence newSeq = new Sequence(dnaSeq);
5817           newSeq.setDatasetSequence(dnaSeq);
5818           cdnaSeqs.add(newSeq);
5819         }
5820       }
5821     }
5822     if (cdnaSeqs.size() == 0)
5823     {
5824       // show a warning dialog no mapped cDNA
5825       return;
5826     }
5827     AlignmentI cdna = new Alignment(
5828             cdnaSeqs.toArray(new SequenceI[cdnaSeqs.size()]));
5829     GAlignFrame alignFrame = new AlignFrame(cdna, AlignFrame.DEFAULT_WIDTH,
5830             AlignFrame.DEFAULT_HEIGHT);
5831     cdna.alignAs(alignment);
5832     String newtitle = "cDNA " + MessageManager.getString("label.for") + " "
5833             + this.title;
5834     Desktop.addInternalFrame(alignFrame, newtitle, AlignFrame.DEFAULT_WIDTH,
5835             AlignFrame.DEFAULT_HEIGHT);
5836   }
5837
5838   /**
5839    * Set visibility of dna/protein complement view (available when shown in a
5840    * split frame).
5841    * 
5842    * @param show
5843    */
5844   @Override
5845   protected void showComplement_actionPerformed(boolean show)
5846   {
5847     SplitContainerI sf = getSplitViewContainer();
5848     if (sf != null)
5849     {
5850       sf.setComplementVisible(this, show);
5851     }
5852   }
5853
5854   /**
5855    * Generate the reverse (optionally complemented) of the selected sequences,
5856    * and add them to the alignment
5857    */
5858   @Override
5859   protected void showReverse_actionPerformed(boolean complement)
5860   {
5861     AlignmentI al = null;
5862     try
5863     {
5864       Dna dna = new Dna(viewport, viewport.getViewAsVisibleContigs(true));
5865       al = dna.reverseCdna(complement);
5866       viewport.addAlignment(al, "");
5867       addHistoryItem(new EditCommand(
5868               MessageManager.getString("label.add_sequences"), Action.PASTE,
5869               al.getSequencesArray(), 0, al.getWidth(),
5870               viewport.getAlignment()));
5871     } catch (Exception ex)
5872     {
5873       System.err.println(ex.getMessage());
5874       return;
5875     }
5876   }
5877
5878   /**
5879    * Try to run a script in the Groovy console, having first ensured that this
5880    * AlignFrame is set as currentAlignFrame in Desktop, to allow the script to
5881    * be targeted at this alignment.
5882    */
5883   @Override
5884   protected void runGroovy_actionPerformed()
5885   {
5886     Jalview.setCurrentAlignFrame(this);
5887     groovy.ui.Console console = Desktop.getGroovyConsole();
5888     if (console != null)
5889     {
5890       try
5891       {
5892         console.runScript();
5893       } catch (Exception ex)
5894       {
5895         System.err.println((ex.toString()));
5896         JvOptionPane.showInternalMessageDialog(Desktop.desktop,
5897                 MessageManager.getString("label.couldnt_run_groovy_script"),
5898                 MessageManager.getString("label.groovy_support_failed"),
5899                 JvOptionPane.ERROR_MESSAGE);
5900       }
5901     }
5902     else
5903     {
5904       System.err.println("Can't run Groovy script as console not found");
5905     }
5906   }
5907
5908   /**
5909    * Hides columns containing (or not containing) a specified feature, provided
5910    * that would not leave all columns hidden
5911    * 
5912    * @param featureType
5913    * @param columnsContaining
5914    * @return
5915    */
5916   public boolean hideFeatureColumns(String featureType,
5917           boolean columnsContaining)
5918   {
5919     boolean notForHiding = avc.markColumnsContainingFeatures(
5920             columnsContaining, false, false, featureType);
5921     if (notForHiding)
5922     {
5923       if (avc.markColumnsContainingFeatures(!columnsContaining, false,
5924               false, featureType))
5925       {
5926         getViewport().hideSelectedColumns();
5927         return true;
5928       }
5929     }
5930     return false;
5931   }
5932
5933   @Override
5934   protected void selectHighlightedColumns_actionPerformed(
5935           ActionEvent actionEvent)
5936   {
5937     // include key modifier check in case user selects from menu
5938     avc.markHighlightedColumns(
5939             (actionEvent.getModifiers() & ActionEvent.ALT_MASK) != 0, true,
5940             (actionEvent.getModifiers() & (ActionEvent.META_MASK
5941                     | ActionEvent.CTRL_MASK)) != 0);
5942   }
5943
5944   /**
5945    * Rebuilds the Colour menu, including any user-defined colours which have
5946    * been loaded either on startup or during the session
5947    */
5948   public void buildColourMenu()
5949   {
5950     colourMenu.removeAll();
5951
5952     colourMenu.add(applyToAllGroups);
5953     colourMenu.add(textColour);
5954     colourMenu.addSeparator();
5955
5956     ButtonGroup bg = ColourMenuHelper.addMenuItems(colourMenu, this,
5957             viewport.getAlignment(), false);
5958
5959     colourMenu.add(annotationColour);
5960     bg.add(annotationColour);
5961     colourMenu.addSeparator();
5962     colourMenu.add(conservationMenuItem);
5963     colourMenu.add(modifyConservation);
5964     colourMenu.add(abovePIDThreshold);
5965     colourMenu.add(modifyPID);
5966
5967     ColourSchemeI colourScheme = viewport.getGlobalColourScheme();
5968     ColourMenuHelper.setColourSelected(colourMenu, colourScheme);
5969   }
5970
5971   /**
5972    * Open a dialog (if not already open) that allows the user to select and
5973    * calculate PCA or Tree analysis
5974    */
5975   protected void openTreePcaDialog()
5976   {
5977     if (alignPanel.getCalculationDialog() == null)
5978     {
5979       new CalculationChooser(AlignFrame.this);
5980     }
5981   }
5982
5983   /**
5984    * Sets the status of the HMMER menu
5985    */
5986   public void updateHMMERStatus()
5987   {
5988     hmmerMenu.setEnabled(HmmerCommand.isHmmerAvailable());
5989   }
5990
5991   @Override
5992   protected void loadVcf_actionPerformed()
5993   {
5994     JalviewFileChooser chooser = new JalviewFileChooser(
5995             Cache.getProperty("LAST_DIRECTORY"));
5996     chooser.setFileView(new JalviewFileView());
5997     chooser.setDialogTitle(MessageManager.getString("label.load_vcf_file"));
5998     chooser.setToolTipText(MessageManager.getString("label.load_vcf_file"));
5999     final AlignFrame us = this;
6000     chooser.setResponseHandler(0, new Runnable()
6001     {
6002       @Override
6003       public void run()
6004       {
6005         String choice = chooser.getSelectedFile().getPath();
6006         Cache.setProperty("LAST_DIRECTORY", choice);
6007         SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
6008         new VCFLoader(choice).loadVCF(seqs, us);
6009       }
6010     });
6011     chooser.showOpenDialog(null);
6012
6013   }
6014
6015   private Rectangle lastFeatureSettingsBounds = null;
6016
6017   @Override
6018   public void setFeatureSettingsGeometry(Rectangle bounds)
6019   {
6020     lastFeatureSettingsBounds = bounds;
6021   }
6022
6023   @Override
6024   public Rectangle getFeatureSettingsGeometry()
6025   {
6026     return lastFeatureSettingsBounds;
6027   }
6028 }
6029
6030 class PrintThread extends Thread
6031 {
6032   AlignmentPanel ap;
6033
6034   public PrintThread(AlignmentPanel ap)
6035   {
6036     this.ap = ap;
6037   }
6038
6039   static PageFormat pf;
6040
6041   @Override
6042   public void run()
6043   {
6044     PrinterJob printJob = PrinterJob.getPrinterJob();
6045
6046     if (pf != null)
6047     {
6048       printJob.setPrintable(ap, pf);
6049     }
6050     else
6051     {
6052       printJob.setPrintable(ap);
6053     }
6054
6055     if (printJob.printDialog())
6056     {
6057       try
6058       {
6059         printJob.print();
6060       } catch (Exception PrintException)
6061       {
6062         PrintException.printStackTrace();
6063       }
6064     }
6065   }
6066 }