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