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