Merge branch 'features/JAL-845splitPaneMergeDevelop' into merge_JAL-845_JAL-1640
[jalview.git] / src / jalview / gui / AlignViewport.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 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 /*
22  * Jalview - A Sequence Alignment Editor and Viewer
23  * Copyright (C) 2007 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
24  *
25  * This program is free software; you can redistribute it and/or
26  * modify it under the terms of the GNU General Public License
27  * as published by the Free Software Foundation; either version 2
28  * of the License, or (at your option) any later version.
29  *
30  * This program is distributed in the hope that it will be useful,
31  * but WITHOUT ANY WARRANTY; without even the implied warranty of
32  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
33  * GNU General Public License for more details.
34  *
35  * You should have received a copy of the GNU General Public License
36  * along with this program; if not, write to the Free Software
37  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
38  */
39 package jalview.gui;
40
41 import jalview.analysis.AlignmentUtils;
42 import jalview.analysis.AlignmentUtils.MappingResult;
43 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
44 import jalview.analysis.NJTree;
45 import jalview.api.AlignViewportI;
46 import jalview.api.ViewStyleI;
47 import jalview.bin.Cache;
48 import jalview.commands.CommandI;
49 import jalview.datamodel.Alignment;
50 import jalview.datamodel.AlignmentI;
51 import jalview.datamodel.ColumnSelection;
52 import jalview.datamodel.PDBEntry;
53 import jalview.datamodel.Sequence;
54 import jalview.datamodel.SequenceGroup;
55 import jalview.datamodel.SequenceI;
56 import jalview.schemes.ColourSchemeProperty;
57 import jalview.schemes.UserColourScheme;
58 import jalview.structure.CommandListener;
59 import jalview.structure.SelectionSource;
60 import jalview.structure.StructureSelectionManager;
61 import jalview.structure.VamsasSource;
62 import jalview.util.MessageManager;
63 import jalview.util.StringUtils;
64 import jalview.viewmodel.AlignmentViewport;
65 import jalview.ws.params.AutoCalcSetting;
66
67 import java.awt.Container;
68 import java.awt.Dimension;
69 import java.awt.Font;
70 import java.awt.Rectangle;
71 import java.io.File;
72 import java.util.ArrayDeque;
73 import java.util.ArrayList;
74 import java.util.Deque;
75 import java.util.Hashtable;
76 import java.util.Set;
77 import java.util.Vector;
78
79 import javax.swing.JInternalFrame;
80 import javax.swing.JOptionPane;
81
82 /**
83  * DOCUMENT ME!
84  * 
85  * @author $author$
86  * @version $Revision: 1.141 $
87  */
88 public class AlignViewport extends AlignmentViewport implements
89         SelectionSource, VamsasSource, AlignViewportI, CommandListener
90 {
91   int startRes;
92
93   int endRes;
94
95   int startSeq;
96
97   int endSeq;
98
99
100   SequenceAnnotationOrder sortAnnotationsBy = null;
101
102   Font font;
103
104   NJTree currentTree = null;
105
106   boolean cursorMode = false;
107
108   boolean antiAlias = false;
109
110   Rectangle explodedPosition;
111
112   String viewName;
113
114   boolean gatherViewsHere = false;
115
116   private Deque<CommandI> historyList = new ArrayDeque<CommandI>();
117
118   private Deque<CommandI> redoList = new ArrayDeque<CommandI>();
119
120   private AnnotationColumnChooser annotationColumnSelectionState;
121   /**
122    * Creates a new AlignViewport object.
123    * 
124    * @param al
125    *          alignment to view
126    */
127   public AlignViewport(AlignmentI al)
128   {
129     setAlignment(al);
130     init();
131   }
132
133   /**
134    * Create a new AlignViewport object with a specific sequence set ID
135    * 
136    * @param al
137    * @param seqsetid
138    *          (may be null - but potential for ambiguous constructor exception)
139    */
140   public AlignViewport(AlignmentI al, String seqsetid)
141   {
142     this(al, seqsetid, null);
143   }
144
145   public AlignViewport(AlignmentI al, String seqsetid, String viewid)
146   {
147     sequenceSetID = seqsetid;
148     viewId = viewid;
149     // TODO remove these once 2.4.VAMSAS release finished
150     if (Cache.log != null && Cache.log.isDebugEnabled() && seqsetid != null)
151     {
152       Cache.log.debug("Setting viewport's sequence set id : "
153               + sequenceSetID);
154     }
155     if (Cache.log != null && Cache.log.isDebugEnabled() && viewId != null)
156     {
157       Cache.log.debug("Setting viewport's view id : " + viewId);
158     }
159     setAlignment(al);
160     init();
161   }
162
163   /**
164    * Create a new AlignViewport with hidden regions
165    * 
166    * @param al
167    *          AlignmentI
168    * @param hiddenColumns
169    *          ColumnSelection
170    */
171   public AlignViewport(AlignmentI al, ColumnSelection hiddenColumns)
172   {
173     setAlignment(al);
174     if (hiddenColumns != null)
175     {
176       colSel = hiddenColumns;
177     }
178     init();
179   }
180
181   /**
182    * New viewport with hidden columns and an existing sequence set id
183    * 
184    * @param al
185    * @param hiddenColumns
186    * @param seqsetid
187    *          (may be null)
188    */
189   public AlignViewport(AlignmentI al, ColumnSelection hiddenColumns,
190           String seqsetid)
191   {
192     this(al, hiddenColumns, seqsetid, null);
193   }
194
195   /**
196    * New viewport with hidden columns and an existing sequence set id and viewid
197    * 
198    * @param al
199    * @param hiddenColumns
200    * @param seqsetid
201    *          (may be null)
202    * @param viewid
203    *          (may be null)
204    */
205   public AlignViewport(AlignmentI al, ColumnSelection hiddenColumns,
206           String seqsetid, String viewid)
207   {
208     sequenceSetID = seqsetid;
209     viewId = viewid;
210     // TODO remove these once 2.4.VAMSAS release finished
211     if (Cache.log != null && Cache.log.isDebugEnabled() && seqsetid != null)
212     {
213       Cache.log.debug("Setting viewport's sequence set id : "
214               + sequenceSetID);
215     }
216     if (Cache.log != null && Cache.log.isDebugEnabled() && viewId != null)
217     {
218       Cache.log.debug("Setting viewport's view id : " + viewId);
219     }
220     setAlignment(al);
221     if (hiddenColumns != null)
222     {
223       colSel = hiddenColumns;
224     }
225     init();
226   }
227
228   private void applyViewProperties()
229   {
230     antiAlias = Cache.getDefault("ANTI_ALIAS", false);
231
232     viewStyle.setShowJVSuffix(Cache.getDefault("SHOW_JVSUFFIX", true));
233     setShowAnnotation(Cache.getDefault("SHOW_ANNOTATIONS", true));
234
235     setRightAlignIds(Cache.getDefault("RIGHT_ALIGN_IDS", false));
236     setCentreColumnLabels(Cache.getDefault("CENTRE_COLUMN_LABELS", false));
237     autoCalculateConsensus = Cache.getDefault("AUTO_CALC_CONSENSUS", true);
238
239     setPadGaps(Cache.getDefault("PAD_GAPS", true));
240     setShowNPFeats(Cache.getDefault("SHOW_NPFEATS_TOOLTIP", true));
241     setShowDBRefs(Cache.getDefault("SHOW_DBREFS_TOOLTIP", true));
242     viewStyle.setSeqNameItalics(Cache.getDefault("ID_ITALICS", true));
243     viewStyle.setWrapAlignment(Cache.getDefault("WRAP_ALIGNMENT", false));
244     viewStyle.setShowUnconserved(Cache
245             .getDefault("SHOW_UNCONSERVED", false));
246     sortByTree = Cache.getDefault("SORT_BY_TREE", false);
247     followSelection = Cache.getDefault("FOLLOW_SELECTIONS", true);
248     sortAnnotationsBy = SequenceAnnotationOrder.valueOf(Cache.getDefault(
249             Preferences.SORT_ANNOTATIONS,
250             SequenceAnnotationOrder.NONE.name()));
251     showAutocalculatedAbove = Cache.getDefault(
252             Preferences.SHOW_AUTOCALC_ABOVE, false);
253
254   }
255
256   void init()
257   {
258     this.startRes = 0;
259     this.endRes = alignment.getWidth() - 1;
260     this.startSeq = 0;
261     this.endSeq = alignment.getHeight() - 1;
262     applyViewProperties();
263
264     String fontName = Cache.getDefault("FONT_NAME", "SansSerif");
265     String fontStyle = Cache.getDefault("FONT_STYLE", Font.PLAIN + "");
266     String fontSize = Cache.getDefault("FONT_SIZE", "10");
267
268     int style = 0;
269
270     if (fontStyle.equals("bold"))
271     {
272       style = 1;
273     }
274     else if (fontStyle.equals("italic"))
275     {
276       style = 2;
277     }
278
279     setFont(new Font(fontName, style, Integer.parseInt(fontSize)));
280
281     alignment
282             .setGapCharacter(Cache.getDefault("GAP_SYMBOL", "-").charAt(0));
283
284     // We must set conservation and consensus before setting colour,
285     // as Blosum and Clustal require this to be done
286     if (hconsensus == null && !isDataset)
287     {
288       if (!alignment.isNucleotide())
289       {
290         showConservation = Cache.getDefault("SHOW_CONSERVATION", true);
291         showQuality = Cache.getDefault("SHOW_QUALITY", true);
292         showGroupConservation = Cache.getDefault("SHOW_GROUP_CONSERVATION",
293                 false);
294       }
295       showConsensusHistogram = Cache.getDefault("SHOW_CONSENSUS_HISTOGRAM",
296               true);
297       showSequenceLogo = Cache.getDefault("SHOW_CONSENSUS_LOGO", false);
298       normaliseSequenceLogo = Cache.getDefault("NORMALISE_CONSENSUS_LOGO",
299               false);
300       showGroupConsensus = Cache.getDefault("SHOW_GROUP_CONSENSUS", false);
301       showConsensus = Cache.getDefault("SHOW_IDENTITY", true);
302     }
303     initAutoAnnotation();
304     if (jalview.bin.Cache.getProperty("DEFAULT_COLOUR") != null)
305     {
306       globalColourScheme = ColourSchemeProperty.getColour(alignment,
307               jalview.bin.Cache.getProperty("DEFAULT_COLOUR"));
308
309       if (globalColourScheme instanceof UserColourScheme)
310       {
311         globalColourScheme = UserDefinedColours.loadDefaultColours();
312         ((UserColourScheme) globalColourScheme).setThreshold(0,
313                 isIgnoreGapsConsensus());
314       }
315
316       if (globalColourScheme != null)
317       {
318         globalColourScheme.setConsensus(hconsensus);
319       }
320     }
321   }
322
323   /**
324    * get the consensus sequence as displayed under the PID consensus annotation
325    * row.
326    * 
327    * @return consensus sequence as a new sequence object
328    */
329   public SequenceI getConsensusSeq()
330   {
331     if (consensus == null)
332     {
333       updateConsensus(null);
334     }
335     if (consensus == null)
336     {
337       return null;
338     }
339     StringBuffer seqs = new StringBuffer();
340     for (int i = 0; i < consensus.annotations.length; i++)
341     {
342       if (consensus.annotations[i] != null)
343       {
344         if (consensus.annotations[i].description.charAt(0) == '[')
345         {
346           seqs.append(consensus.annotations[i].description.charAt(1));
347         }
348         else
349         {
350           seqs.append(consensus.annotations[i].displayCharacter);
351         }
352       }
353     }
354
355     SequenceI sq = new Sequence("Consensus", seqs.toString());
356     sq.setDescription("Percentage Identity Consensus "
357             + ((ignoreGapsInConsensusCalculation) ? " without gaps" : ""));
358     return sq;
359   }
360
361   /**
362    * DOCUMENT ME!
363    * 
364    * @return DOCUMENT ME!
365    */
366   public int getStartRes()
367   {
368     return startRes;
369   }
370
371   /**
372    * DOCUMENT ME!
373    * 
374    * @return DOCUMENT ME!
375    */
376   public int getEndRes()
377   {
378     return endRes;
379   }
380
381   /**
382    * DOCUMENT ME!
383    * 
384    * @return DOCUMENT ME!
385    */
386   public int getStartSeq()
387   {
388     return startSeq;
389   }
390
391   /**
392    * DOCUMENT ME!
393    * 
394    * @param res
395    *          DOCUMENT ME!
396    */
397   public void setStartRes(int res)
398   {
399     this.startRes = res;
400   }
401
402   /**
403    * DOCUMENT ME!
404    * 
405    * @param seq
406    *          DOCUMENT ME!
407    */
408   public void setStartSeq(int seq)
409   {
410     this.startSeq = seq;
411   }
412
413   /**
414    * DOCUMENT ME!
415    * 
416    * @param res
417    *          DOCUMENT ME!
418    */
419   public void setEndRes(int res)
420   {
421     if (res > (alignment.getWidth() - 1))
422     {
423       // log.System.out.println(" Corrected res from " + res + " to maximum " +
424       // (alignment.getWidth()-1));
425       res = alignment.getWidth() - 1;
426     }
427
428     if (res < 0)
429     {
430       res = 0;
431     }
432
433     this.endRes = res;
434   }
435
436   /**
437    * DOCUMENT ME!
438    * 
439    * @param seq
440    *          DOCUMENT ME!
441    */
442   public void setEndSeq(int seq)
443   {
444     if (seq > alignment.getHeight())
445     {
446       seq = alignment.getHeight();
447     }
448
449     if (seq < 0)
450     {
451       seq = 0;
452     }
453
454     this.endSeq = seq;
455   }
456
457   /**
458    * DOCUMENT ME!
459    * 
460    * @return DOCUMENT ME!
461    */
462   public int getEndSeq()
463   {
464     return endSeq;
465   }
466
467   boolean validCharWidth;
468
469   /**
470    * DOCUMENT ME!
471    * 
472    * @param f
473    *          DOCUMENT ME!
474    */
475   public void setFont(Font f)
476   {
477     font = f;
478
479     Container c = new Container();
480
481     java.awt.FontMetrics fm = c.getFontMetrics(font);
482     int w = viewStyle.getCharWidth(), ww = fm.charWidth('M'), h = viewStyle
483             .getCharHeight();
484     // only update width/height if the new font won't fit
485     if (h < fm.getHeight())
486     {
487       setCharHeight(fm.getHeight());
488     }
489     if (w < ww)
490     {
491       setCharWidth(ww);
492     }
493     viewStyle.setFontName(font.getName());
494     viewStyle.setFontStyle(font.getStyle());
495     viewStyle.setFontSize(font.getSize());
496
497     validCharWidth = true;
498   }
499
500   @Override
501   public void setViewStyle(ViewStyleI settingsForView)
502   {
503     super.setViewStyle(settingsForView);
504     setFont(new Font(viewStyle.getFontName(), viewStyle.getFontStyle(),
505             viewStyle.getFontSize()));
506
507   }
508   /**
509    * DOCUMENT ME!
510    * 
511    * @return DOCUMENT ME!
512    */
513   public Font getFont()
514   {
515     return font;
516   }
517
518   /**
519    * DOCUMENT ME!
520    * 
521    * @param align
522    *          DOCUMENT ME!
523    */
524   public void setAlignment(AlignmentI align)
525   {
526     if (alignment != null && alignment.getCodonFrames() != null)
527     {
528       StructureSelectionManager.getStructureSelectionManager(
529               Desktop.instance).removeMappings(alignment.getCodonFrames());
530     }
531     this.alignment = align;
532     if (alignment != null && alignment.getCodonFrames() != null)
533     {
534       StructureSelectionManager.getStructureSelectionManager(
535               Desktop.instance).addMappings(alignment.getCodonFrames());
536     }
537   }
538
539   /**
540    * DOCUMENT ME!
541    * 
542    * @return DOCUMENT ME!
543    */
544   public char getGapCharacter()
545   {
546     return getAlignment().getGapCharacter();
547   }
548
549   /**
550    * DOCUMENT ME!
551    * 
552    * @param gap
553    *          DOCUMENT ME!
554    */
555   public void setGapCharacter(char gap)
556   {
557     if (getAlignment() != null)
558     {
559       getAlignment().setGapCharacter(gap);
560     }
561   }
562
563   /**
564    * DOCUMENT ME!
565    * 
566    * @return DOCUMENT ME!
567    */
568   public ColumnSelection getColumnSelection()
569   {
570     return colSel;
571   }
572
573   /**
574    * DOCUMENT ME!
575    * 
576    * @param tree
577    *          DOCUMENT ME!
578    */
579   public void setCurrentTree(NJTree tree)
580   {
581     currentTree = tree;
582   }
583
584   /**
585    * DOCUMENT ME!
586    * 
587    * @return DOCUMENT ME!
588    */
589   public NJTree getCurrentTree()
590   {
591     return currentTree;
592   }
593
594   /**
595    * returns the visible column regions of the alignment
596    * 
597    * @param selectedRegionOnly
598    *          true to just return the contigs intersecting with the selected
599    *          area
600    * @return
601    */
602   public int[] getViewAsVisibleContigs(boolean selectedRegionOnly)
603   {
604     int[] viscontigs = null;
605     int start = 0, end = 0;
606     if (selectedRegionOnly && selectionGroup != null)
607     {
608       start = selectionGroup.getStartRes();
609       end = selectionGroup.getEndRes() + 1;
610     }
611     else
612     {
613       end = alignment.getWidth();
614     }
615     viscontigs = colSel.getVisibleContigs(start, end);
616     return viscontigs;
617   }
618
619   /**
620    * get hash of undo and redo list for the alignment
621    * 
622    * @return long[] { historyList.hashCode, redoList.hashCode };
623    */
624   public long[] getUndoRedoHash()
625   {
626     // TODO: JAL-1126
627     if (historyList == null || redoList == null)
628     {
629       return new long[]
630       { -1, -1 };
631     }
632     return new long[]
633     { historyList.hashCode(), this.redoList.hashCode() };
634   }
635
636   /**
637    * test if a particular set of hashcodes are different to the hashcodes for
638    * the undo and redo list.
639    * 
640    * @param undoredo
641    *          the stored set of hashcodes as returned by getUndoRedoHash
642    * @return true if the hashcodes differ (ie the alignment has been edited) or
643    *         the stored hashcode array differs in size
644    */
645   public boolean isUndoRedoHashModified(long[] undoredo)
646   {
647     if (undoredo == null)
648     {
649       return true;
650     }
651     long[] cstate = getUndoRedoHash();
652     if (cstate.length != undoredo.length)
653     {
654       return true;
655     }
656
657     for (int i = 0; i < cstate.length; i++)
658     {
659       if (cstate[i] != undoredo[i])
660       {
661         return true;
662       }
663     }
664     return false;
665   }
666
667   /**
668    * when set, view will scroll to show the highlighted position
669    */
670   public boolean followHighlight = true;
671
672   /**
673    * @return true if view should scroll to show the highlighted region of a
674    *         sequence
675    * @return
676    */
677   public boolean getFollowHighlight()
678   {
679     return followHighlight;
680   }
681
682   public boolean followSelection = true;
683
684   /**
685    * @return true if view selection should always follow the selections
686    *         broadcast by other selection sources
687    */
688   public boolean getFollowSelection()
689   {
690     return followSelection;
691   }
692
693   /**
694    * Send the current selection to be broadcast to any selection listeners.
695    */
696   public void sendSelection()
697   {
698     jalview.structure.StructureSelectionManager
699             .getStructureSelectionManager(Desktop.instance).sendSelection(
700                     new SequenceGroup(getSelectionGroup()),
701                     new ColumnSelection(getColumnSelection()), this);
702   }
703
704   /**
705    * return the alignPanel containing the given viewport. Use this to get the
706    * components currently handling the given viewport.
707    * 
708    * @param av
709    * @return null or an alignPanel guaranteed to have non-null alignFrame
710    *         reference
711    */
712   public AlignmentPanel getAlignPanel()
713   {
714     AlignmentPanel[] aps = PaintRefresher.getAssociatedPanels(this
715             .getSequenceSetId());
716     for (int p = 0; aps != null && p < aps.length; p++)
717     {
718       if (aps[p].av == this)
719       {
720         return aps[p];
721       }
722     }
723     return null;
724   }
725
726   public boolean getSortByTree()
727   {
728     return sortByTree;
729   }
730
731   public void setSortByTree(boolean sort)
732   {
733     sortByTree = sort;
734   }
735
736   /**
737    * synthesize a column selection if none exists so it covers the given
738    * selection group. if wholewidth is false, no column selection is made if the
739    * selection group covers the whole alignment width.
740    * 
741    * @param sg
742    * @param wholewidth
743    */
744   public void expandColSelection(SequenceGroup sg, boolean wholewidth)
745   {
746     int sgs, sge;
747     if (sg != null
748             && (sgs = sg.getStartRes()) >= 0
749             && sg.getStartRes() <= (sge = sg.getEndRes())
750             && (colSel == null || colSel.getSelected() == null || colSel
751                     .getSelected().size() == 0))
752     {
753       if (!wholewidth && alignment.getWidth() == (1 + sge - sgs))
754       {
755         // do nothing
756         return;
757       }
758       if (colSel == null)
759       {
760         colSel = new ColumnSelection();
761       }
762       for (int cspos = sg.getStartRes(); cspos <= sg.getEndRes(); cspos++)
763       {
764         colSel.addElement(cspos);
765       }
766     }
767   }
768
769   public StructureSelectionManager getStructureSelectionManager()
770   {
771     return StructureSelectionManager
772             .getStructureSelectionManager(Desktop.instance);
773   }
774
775   /**
776    * 
777    * @param pdbEntries
778    * @return a series of SequenceI arrays, one for each PDBEntry, listing which
779    *         sequence in the alignment holds a reference to it
780    */
781   public SequenceI[][] collateForPDB(PDBEntry[] pdbEntries)
782   {
783     ArrayList<SequenceI[]> seqvectors = new ArrayList<SequenceI[]>();
784     for (PDBEntry pdb : pdbEntries)
785     {
786       ArrayList<SequenceI> seqs = new ArrayList<SequenceI>();
787       for (int i = 0; i < alignment.getHeight(); i++)
788       {
789         Vector pdbs = alignment.getSequenceAt(i).getDatasetSequence()
790                 .getPDBId();
791         if (pdbs == null)
792         {
793           continue;
794         }
795         SequenceI sq;
796         for (int p = 0; p < pdbs.size(); p++)
797         {
798           PDBEntry p1 = (PDBEntry) pdbs.elementAt(p);
799           if (p1.getId().equals(pdb.getId()))
800           {
801             if (!seqs.contains(sq = alignment.getSequenceAt(i)))
802             {
803               seqs.add(sq);
804             }
805
806             continue;
807           }
808         }
809       }
810       seqvectors.add(seqs.toArray(new SequenceI[seqs.size()]));
811     }
812     return seqvectors.toArray(new SequenceI[seqvectors.size()][]);
813   }
814
815   public boolean isNormaliseSequenceLogo()
816   {
817     return normaliseSequenceLogo;
818   }
819
820   public void setNormaliseSequenceLogo(boolean state)
821   {
822     normaliseSequenceLogo = state;
823   }
824
825   /**
826    * 
827    * @return true if alignment characters should be displayed
828    */
829   public boolean isValidCharWidth()
830   {
831     return validCharWidth;
832   }
833
834   private Hashtable<String, AutoCalcSetting> calcIdParams = new Hashtable<String, AutoCalcSetting>();
835
836   private boolean showAutocalculatedAbove;
837
838   public AutoCalcSetting getCalcIdSettingsFor(String calcId)
839   {
840     return calcIdParams.get(calcId);
841   }
842
843   public void setCalcIdSettingsFor(String calcId, AutoCalcSetting settings,
844           boolean needsUpdate)
845   {
846     calcIdParams.put(calcId, settings);
847     // TODO: create a restart list to trigger any calculations that need to be
848     // restarted after load
849     // calculator.getRegisteredWorkersOfClass(settings.getWorkerClass())
850     if (needsUpdate)
851     {
852       Cache.log.debug("trigger update for " + calcId);
853     }
854   }
855
856   protected SequenceAnnotationOrder getSortAnnotationsBy()
857   {
858     return sortAnnotationsBy;
859   }
860
861   protected void setSortAnnotationsBy(SequenceAnnotationOrder sortAnnotationsBy)
862   {
863     this.sortAnnotationsBy = sortAnnotationsBy;
864   }
865
866   protected boolean isShowAutocalculatedAbove()
867   {
868     return showAutocalculatedAbove;
869   }
870
871   protected void setShowAutocalculatedAbove(boolean showAutocalculatedAbove)
872   {
873     this.showAutocalculatedAbove = showAutocalculatedAbove;
874   }
875
876   /**
877    * Method called when another alignment's edit (or possibly other) command is
878    * broadcast to here.
879    *
880    * To allow for sequence mappings (e.g. protein to cDNA), we have to first
881    * 'unwind' the command on the source sequences (in simulation, not in fact),
882    * and then for each edit in turn:
883    * <ul>
884    * <li>compute the equivalent edit on the mapped sequences</li>
885    * <li>apply the mapped edit</li>
886    * <li>'apply' the source edit to the working copy of the source sequences</li>
887    * </ul>
888    * 
889    * @param command
890    * @param undo
891    * @param ssm
892    */
893   @Override
894   public void mirrorCommand(CommandI command, boolean undo,
895           StructureSelectionManager ssm, VamsasSource source)
896   {
897     /*
898      * ...work in progress... do nothing unless we are a 'complement' of the
899      * source May replace this with direct calls not via SSM.
900      */
901     if (source instanceof AlignViewportI
902             && ((AlignViewportI) source).getCodingComplement() == this)
903     {
904       // ok to continue;
905     }
906     else
907     {
908       return;
909     }
910
911     CommandI mappedCommand = ssm.mapCommand(command, undo, getAlignment(),
912             getGapCharacter());
913     if (mappedCommand != null)
914     {
915       AlignmentI[] views = getAlignPanel().alignFrame.getViewAlignments();
916       mappedCommand.doCommand(views);
917       getAlignPanel().alignmentChanged();
918     }
919   }
920
921   @Override
922   public VamsasSource getVamsasSource()
923   {
924     return this;
925   }
926
927   /**
928    * Add one command to the command history list.
929    * 
930    * @param command
931    */
932   public void addToHistoryList(CommandI command)
933   {
934     if (this.historyList != null)
935     {
936       this.historyList.push(command);
937       broadcastCommand(command, false);
938     }
939   }
940
941   protected void broadcastCommand(CommandI command, boolean undo)
942   {
943     getStructureSelectionManager().commandPerformed(command, undo, getVamsasSource());
944   }
945
946   /**
947    * Add one command to the command redo list.
948    * 
949    * @param command
950    */
951   public void addToRedoList(CommandI command)
952   {
953     if (this.redoList != null)
954     {
955       this.redoList.push(command);
956     }
957     broadcastCommand(command, true);
958   }
959
960   /**
961    * Clear the command redo list.
962    */
963   public void clearRedoList()
964   {
965     if (this.redoList != null)
966     {
967       this.redoList.clear();
968     }
969   }
970
971   public void setHistoryList(Deque<CommandI> list)
972   {
973     this.historyList = list;
974   }
975
976   public Deque<CommandI> getHistoryList()
977   {
978     return this.historyList;
979   }
980
981   public void setRedoList(Deque<CommandI> list)
982   {
983     this.redoList = list;
984   }
985
986   public Deque<CommandI> getRedoList()
987   {
988     return this.redoList;
989   }
990
991   /**
992    * Add the sequences from the given alignment to this viewport. Optionally,
993    * may give the user the option to open a new frame, or split panel, with cDNA
994    * and protein linked.
995    * 
996    * @param al
997    * @param title
998    */
999   public void addAlignment(AlignmentI al, String title)
1000   {
1001     // TODO: promote to AlignViewportI? applet CutAndPasteTransfer is different
1002
1003     // refactored from FileLoader / CutAndPasteTransfer / SequenceFetcher with
1004     // this comment:
1005     // TODO: create undo object for this JAL-1101
1006
1007     /*
1008      * If one alignment is protein and one nucleotide, with at least one
1009      * sequence name in common, offer to open a linked alignment.
1010      */
1011     if (getAlignment().isNucleotide() != al.isNucleotide())
1012     {
1013       final Set<String> sequenceNames = getAlignment().getSequenceNames();
1014       sequenceNames.retainAll(al.getSequenceNames());
1015       if (!sequenceNames.isEmpty()) // at least one sequence name in both
1016       {
1017         if (openLinkedAlignment(al, title))
1018         {
1019           return;
1020         }
1021       }
1022     }
1023
1024     for (int i = 0; i < al.getHeight(); i++)
1025     {
1026       getAlignment().addSequence(al.getSequenceAt(i));
1027     }
1028     // TODO this call was done by SequenceFetcher but not FileLoader or
1029     // CutAndPasteTransfer. Is it needed?
1030     setEndSeq(getAlignment().getHeight());
1031     firePropertyChange("alignment", null, getAlignment().getSequences());
1032   }
1033
1034   /**
1035    * Show a dialog with the option to open and link (cDNA <-> protein) as a new
1036    * alignment. Returns true if the new alignment was opened, false if not,
1037    * because the user declined the offer.
1038    * 
1039    * @param title
1040    */
1041   protected boolean openLinkedAlignment(AlignmentI al, String title)
1042   {
1043     String[] options = new String[]
1044     { MessageManager.getString("action.no"),
1045         MessageManager.getString("label.split_window"),
1046         MessageManager.getString("label.new_window"), };
1047     final String question = JvSwingUtils.wrapTooltip(true,
1048             MessageManager.getString("label.open_linked_alignment?"));
1049     int response = JOptionPane.showOptionDialog(Desktop.desktop, question,
1050             MessageManager.getString("label.open_linked_alignment"),
1051             JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null,
1052             options, options[0]);
1053
1054     if (response != 1 && response != 2)
1055     {
1056       return false;
1057     }
1058     final boolean openSplitPane = (response == 1);
1059     final boolean openInNewWindow = (response == 2);
1060
1061     /*
1062      * Create the AlignFrame first (which creates the new alignment's datasets),
1063      * before attempting sequence mapping.
1064      */
1065     AlignFrame newAlignFrame = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH,
1066             AlignFrame.DEFAULT_HEIGHT);
1067     newAlignFrame.setTitle(title);
1068
1069     /*
1070      * Identify protein and dna alignments. Make a copy of this one if opening
1071      * in a new split pane.
1072      */
1073     AlignmentI thisAlignment = openSplitPane ? new Alignment(getAlignment())
1074             : getAlignment();
1075     AlignmentI protein = al.isNucleotide() ? thisAlignment : al;
1076     final AlignmentI cdna = al.isNucleotide() ? al : thisAlignment;
1077
1078     newAlignFrame.statusBar.setText(MessageManager.formatMessage(
1079             "label.successfully_loaded_file", new Object[]
1080             { title }));
1081
1082     // TODO if we want this (e.g. to enable reload of the alignment from file),
1083     // we will need to add parameters to the stack.
1084     // if (!protocol.equals(AppletFormatAdapter.PASTE))
1085     // {
1086     // alignFrame.setFileName(file, format);
1087     // }
1088
1089     if (openInNewWindow)
1090     {
1091       Desktop.addInternalFrame(newAlignFrame, title,
1092               AlignFrame.DEFAULT_WIDTH,
1093               AlignFrame.DEFAULT_HEIGHT);
1094     }
1095
1096     /*
1097      * Try to find mappings for at least one sequence. Any mappings made will be
1098      * added to the protein alignment.
1099      */
1100     MappingResult mapped = AlignmentUtils.mapProteinToCdna(protein, cdna);
1101     final StructureSelectionManager ssm = StructureSelectionManager
1102             .getStructureSelectionManager(Desktop.instance);
1103     if (mapped != MappingResult.Mapped)
1104     {
1105       /*
1106        * No mapping possible - warn the user, but leave window open.
1107        */
1108       final String msg = JvSwingUtils.wrapTooltip(true,
1109               MessageManager.getString("label.mapping_failed"));
1110       JOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1111               MessageManager.getString("label.no_mappings"),
1112               JOptionPane.WARNING_MESSAGE);
1113     }
1114
1115     try
1116     {
1117       newAlignFrame.setMaximum(jalview.bin.Cache.getDefault(
1118               "SHOW_FULLSCREEN",
1119               false));
1120     } catch (java.beans.PropertyVetoException ex)
1121     {
1122     }
1123
1124     if (openSplitPane)
1125     {
1126       /*
1127        * Open in split pane. DNA sequence above, protein below.
1128        */
1129       AlignFrame copyMe = new AlignFrame(thisAlignment,
1130               AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
1131       copyMe.setTitle(getAlignPanel().alignFrame.getTitle());
1132       final AlignFrame proteinFrame = al.isNucleotide() ? copyMe
1133               : newAlignFrame;
1134       final AlignFrame cdnaFrame = al.isNucleotide() ? newAlignFrame
1135               : copyMe;
1136       protein = proteinFrame.viewport.getAlignment();
1137
1138       cdnaFrame.setVisible(true);
1139       proteinFrame.setVisible(true);
1140       String sep = String.valueOf(File.separatorChar);
1141       String proteinShortName = StringUtils.getLastToken(
1142               proteinFrame.getTitle(), sep);
1143       String dnaShortName = StringUtils.getLastToken(cdnaFrame.getTitle(),
1144               sep);
1145       String linkedTitle = MessageManager.formatMessage(
1146               "label.linked_view_title", dnaShortName, proteinShortName);
1147       JInternalFrame splitFrame = new SplitFrame(cdnaFrame, proteinFrame);
1148       Desktop.addInternalFrame(splitFrame, linkedTitle,
1149               AlignFrame.DEFAULT_WIDTH,
1150               AlignFrame.DEFAULT_HEIGHT);
1151
1152       /*
1153        * Set the frames to listen for each other's edit and sort commands.
1154        */
1155       ssm.addCommandListener(cdnaFrame.getViewport());
1156       ssm.addCommandListener(proteinFrame.getViewport());
1157
1158       /*
1159        * 'Coding complement' (dna/protein) views will mirror each others' edits,
1160        * selections, sorting etc as decided from time to time by the relevant
1161        * authorities.
1162        */
1163       proteinFrame.getViewport().setCodingComplement(cdnaFrame.getViewport());
1164     }
1165
1166     /*
1167      * Register the mappings (held on the protein alignment) with the
1168      * StructureSelectionManager (for mouseover linking).
1169      */
1170     ssm.addMappings(protein.getCodonFrames());
1171
1172     return true;
1173   }
1174
1175   public AnnotationColumnChooser getAnnotationColumnSelectionState()
1176   {
1177     return annotationColumnSelectionState;
1178   }
1179
1180   public void setAnnotationColumnSelectionState(
1181           AnnotationColumnChooser currentAnnotationColumnSelectionState)
1182   {
1183     this.annotationColumnSelectionState = currentAnnotationColumnSelectionState;
1184   }
1185
1186   @Override
1187   public void setIdWidth(int i)
1188   {
1189     super.setIdWidth(i);
1190     AlignmentPanel ap = getAlignPanel();
1191     if (ap != null)
1192     {
1193       // modify GUI elements to reflect geometry change
1194       Dimension idw = getAlignPanel().getIdPanel().getIdCanvas()
1195               .getPreferredSize();
1196       idw.width = i;
1197       getAlignPanel().getIdPanel().getIdCanvas().setPreferredSize(idw);
1198     }
1199   }
1200 }