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