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