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