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