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