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