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