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