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