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