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