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