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