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