JAL-1690 'scale protein as cDNA' options in Preferences, FontChooser
[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   /**
225    * Apply any settings saved in user preferences
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     viewStyle.setScaleProteinAsCdna(Cache.getDefault(
253             Preferences.SCALE_PROTEIN_TO_CDNA, false));
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)), true);
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     String colourProperty = alignment.isNucleotide() ? Preferences.DEFAULT_COLOUR_NUC
305             : Preferences.DEFAULT_COLOUR_PROT;
306     String propertyValue = Cache.getProperty(colourProperty);
307     if (propertyValue == null)
308     {
309       // fall back on this property for backwards compatibility
310       propertyValue = Cache.getProperty(Preferences.DEFAULT_COLOUR);
311     }
312     if (propertyValue != null)
313     {
314       globalColourScheme = ColourSchemeProperty.getColour(alignment,
315               propertyValue);
316
317       if (globalColourScheme instanceof UserColourScheme)
318       {
319         globalColourScheme = UserDefinedColours.loadDefaultColours();
320         ((UserColourScheme) globalColourScheme).setThreshold(0,
321                 isIgnoreGapsConsensus());
322       }
323
324       if (globalColourScheme != null)
325       {
326         globalColourScheme.setConsensus(hconsensus);
327       }
328     }
329   }
330
331   /**
332    * get the consensus sequence as displayed under the PID consensus annotation
333    * row.
334    * 
335    * @return consensus sequence as a new sequence object
336    */
337   public SequenceI getConsensusSeq()
338   {
339     if (consensus == null)
340     {
341       updateConsensus(null);
342     }
343     if (consensus == null)
344     {
345       return null;
346     }
347     StringBuffer seqs = new StringBuffer();
348     for (int i = 0; i < consensus.annotations.length; i++)
349     {
350       if (consensus.annotations[i] != null)
351       {
352         if (consensus.annotations[i].description.charAt(0) == '[')
353         {
354           seqs.append(consensus.annotations[i].description.charAt(1));
355         }
356         else
357         {
358           seqs.append(consensus.annotations[i].displayCharacter);
359         }
360       }
361     }
362
363     SequenceI sq = new Sequence("Consensus", seqs.toString());
364     sq.setDescription("Percentage Identity Consensus "
365             + ((ignoreGapsInConsensusCalculation) ? " without gaps" : ""));
366     return sq;
367   }
368
369   /**
370    * DOCUMENT ME!
371    * 
372    * @return DOCUMENT ME!
373    */
374   public int getStartRes()
375   {
376     return startRes;
377   }
378
379   /**
380    * DOCUMENT ME!
381    * 
382    * @return DOCUMENT ME!
383    */
384   public int getEndRes()
385   {
386     return endRes;
387   }
388
389   /**
390    * DOCUMENT ME!
391    * 
392    * @return DOCUMENT ME!
393    */
394   public int getStartSeq()
395   {
396     return startSeq;
397   }
398
399   /**
400    * DOCUMENT ME!
401    * 
402    * @param res
403    *          DOCUMENT ME!
404    */
405   public void setStartRes(int res)
406   {
407     this.startRes = res;
408   }
409
410   /**
411    * DOCUMENT ME!
412    * 
413    * @param seq
414    *          DOCUMENT ME!
415    */
416   public void setStartSeq(int seq)
417   {
418     this.startSeq = seq;
419   }
420
421   /**
422    * DOCUMENT ME!
423    * 
424    * @param res
425    *          DOCUMENT ME!
426    */
427   public void setEndRes(int res)
428   {
429     if (res > (alignment.getWidth() - 1))
430     {
431       // log.System.out.println(" Corrected res from " + res + " to maximum " +
432       // (alignment.getWidth()-1));
433       res = alignment.getWidth() - 1;
434     }
435
436     if (res < 0)
437     {
438       res = 0;
439     }
440
441     this.endRes = res;
442   }
443
444   /**
445    * DOCUMENT ME!
446    * 
447    * @param seq
448    *          DOCUMENT ME!
449    */
450   public void setEndSeq(int seq)
451   {
452     if (seq > alignment.getHeight())
453     {
454       seq = alignment.getHeight();
455     }
456
457     if (seq < 0)
458     {
459       seq = 0;
460     }
461
462     this.endSeq = seq;
463   }
464
465   /**
466    * DOCUMENT ME!
467    * 
468    * @return DOCUMENT ME!
469    */
470   public int getEndSeq()
471   {
472     return endSeq;
473   }
474
475   boolean validCharWidth;
476
477   /**
478    * update view settings with the given font. You may need to call
479    * alignPanel.fontChanged to update the layout geometry
480    * 
481    * @param setGrid
482    *          when true, charWidth/height is set according to font mentrics
483    */
484   public void setFont(Font f, boolean setGrid)
485   {
486     font = f;
487
488     Container c = new Container();
489
490     java.awt.FontMetrics fm = c.getFontMetrics(font);
491     int w = viewStyle.getCharWidth(), ww = fm.charWidth('M'), h = viewStyle
492             .getCharHeight();
493     if (setGrid)
494     {
495       setCharHeight(fm.getHeight());
496       setCharWidth(ww);
497     }
498     viewStyle.setFontName(font.getName());
499     viewStyle.setFontStyle(font.getStyle());
500     viewStyle.setFontSize(font.getSize());
501
502     validCharWidth = true;
503   }
504
505   @Override
506   public void setViewStyle(ViewStyleI settingsForView)
507   {
508     super.setViewStyle(settingsForView);
509     setFont(new Font(viewStyle.getFontName(), viewStyle.getFontStyle(),
510             viewStyle.getFontSize()), false);
511
512   }
513   /**
514    * DOCUMENT ME!
515    * 
516    * @return DOCUMENT ME!
517    */
518   public Font getFont()
519   {
520     return font;
521   }
522
523   /**
524    * DOCUMENT ME!
525    * 
526    * @param align
527    *          DOCUMENT ME!
528    */
529   public void setAlignment(AlignmentI align)
530   {
531     if (alignment != null && alignment.getCodonFrames() != null)
532     {
533       StructureSelectionManager.getStructureSelectionManager(
534               Desktop.instance).removeMappings(alignment.getCodonFrames());
535     }
536     this.alignment = align;
537     if (alignment != null && alignment.getCodonFrames() != null)
538     {
539       StructureSelectionManager.getStructureSelectionManager(
540               Desktop.instance).addMappings(alignment.getCodonFrames());
541     }
542   }
543
544   /**
545    * DOCUMENT ME!
546    * 
547    * @return DOCUMENT ME!
548    */
549   public char getGapCharacter()
550   {
551     return getAlignment().getGapCharacter();
552   }
553
554   /**
555    * DOCUMENT ME!
556    * 
557    * @param gap
558    *          DOCUMENT ME!
559    */
560   public void setGapCharacter(char gap)
561   {
562     if (getAlignment() != null)
563     {
564       getAlignment().setGapCharacter(gap);
565     }
566   }
567
568   /**
569    * DOCUMENT ME!
570    * 
571    * @return DOCUMENT ME!
572    */
573   public ColumnSelection getColumnSelection()
574   {
575     return colSel;
576   }
577
578   /**
579    * DOCUMENT ME!
580    * 
581    * @param tree
582    *          DOCUMENT ME!
583    */
584   public void setCurrentTree(NJTree tree)
585   {
586     currentTree = tree;
587   }
588
589   /**
590    * DOCUMENT ME!
591    * 
592    * @return DOCUMENT ME!
593    */
594   public NJTree getCurrentTree()
595   {
596     return currentTree;
597   }
598
599   /**
600    * returns the visible column regions of the alignment
601    * 
602    * @param selectedRegionOnly
603    *          true to just return the contigs intersecting with the selected
604    *          area
605    * @return
606    */
607   public int[] getViewAsVisibleContigs(boolean selectedRegionOnly)
608   {
609     int[] viscontigs = null;
610     int start = 0, end = 0;
611     if (selectedRegionOnly && selectionGroup != null)
612     {
613       start = selectionGroup.getStartRes();
614       end = selectionGroup.getEndRes() + 1;
615     }
616     else
617     {
618       end = alignment.getWidth();
619     }
620     viscontigs = colSel.getVisibleContigs(start, end);
621     return viscontigs;
622   }
623
624   /**
625    * get hash of undo and redo list for the alignment
626    * 
627    * @return long[] { historyList.hashCode, redoList.hashCode };
628    */
629   public long[] getUndoRedoHash()
630   {
631     // TODO: JAL-1126
632     if (historyList == null || redoList == null)
633     {
634       return new long[]
635       { -1, -1 };
636     }
637     return new long[]
638     { historyList.hashCode(), this.redoList.hashCode() };
639   }
640
641   /**
642    * test if a particular set of hashcodes are different to the hashcodes for
643    * the undo and redo list.
644    * 
645    * @param undoredo
646    *          the stored set of hashcodes as returned by getUndoRedoHash
647    * @return true if the hashcodes differ (ie the alignment has been edited) or
648    *         the stored hashcode array differs in size
649    */
650   public boolean isUndoRedoHashModified(long[] undoredo)
651   {
652     if (undoredo == null)
653     {
654       return true;
655     }
656     long[] cstate = getUndoRedoHash();
657     if (cstate.length != undoredo.length)
658     {
659       return true;
660     }
661
662     for (int i = 0; i < cstate.length; i++)
663     {
664       if (cstate[i] != undoredo[i])
665       {
666         return true;
667       }
668     }
669     return false;
670   }
671
672   /**
673    * when set, view will scroll to show the highlighted position
674    */
675   public boolean followHighlight = true;
676
677   /**
678    * @return true if view should scroll to show the highlighted region of a
679    *         sequence
680    * @return
681    */
682   public boolean getFollowHighlight()
683   {
684     return followHighlight;
685   }
686
687   public boolean followSelection = true;
688
689   /**
690    * @return true if view selection should always follow the selections
691    *         broadcast by other selection sources
692    */
693   public boolean getFollowSelection()
694   {
695     return followSelection;
696   }
697
698   /**
699    * Send the current selection to be broadcast to any selection listeners.
700    */
701   public void sendSelection()
702   {
703     jalview.structure.StructureSelectionManager
704             .getStructureSelectionManager(Desktop.instance).sendSelection(
705                     new SequenceGroup(getSelectionGroup()),
706                     new ColumnSelection(getColumnSelection()), this);
707   }
708
709   /**
710    * return the alignPanel containing the given viewport. Use this to get the
711    * components currently handling the given viewport.
712    * 
713    * @param av
714    * @return null or an alignPanel guaranteed to have non-null alignFrame
715    *         reference
716    */
717   public AlignmentPanel getAlignPanel()
718   {
719     AlignmentPanel[] aps = PaintRefresher.getAssociatedPanels(this
720             .getSequenceSetId());
721     for (int p = 0; aps != null && p < aps.length; p++)
722     {
723       if (aps[p].av == this)
724       {
725         return aps[p];
726       }
727     }
728     return null;
729   }
730
731   public boolean getSortByTree()
732   {
733     return sortByTree;
734   }
735
736   public void setSortByTree(boolean sort)
737   {
738     sortByTree = sort;
739   }
740
741   /**
742    * synthesize a column selection if none exists so it covers the given
743    * selection group. if wholewidth is false, no column selection is made if the
744    * selection group covers the whole alignment width.
745    * 
746    * @param sg
747    * @param wholewidth
748    */
749   public void expandColSelection(SequenceGroup sg, boolean wholewidth)
750   {
751     int sgs, sge;
752     if (sg != null
753             && (sgs = sg.getStartRes()) >= 0
754             && sg.getStartRes() <= (sge = sg.getEndRes())
755             && (colSel == null || colSel.getSelected() == null || colSel
756                     .getSelected().size() == 0))
757     {
758       if (!wholewidth && alignment.getWidth() == (1 + sge - sgs))
759       {
760         // do nothing
761         return;
762       }
763       if (colSel == null)
764       {
765         colSel = new ColumnSelection();
766       }
767       for (int cspos = sg.getStartRes(); cspos <= sg.getEndRes(); cspos++)
768       {
769         colSel.addElement(cspos);
770       }
771     }
772   }
773
774   /**
775    * Returns the (Desktop) instance of the StructureSelectionManager
776    */
777   @Override
778   public StructureSelectionManager getStructureSelectionManager()
779   {
780     return StructureSelectionManager
781             .getStructureSelectionManager(Desktop.instance);
782   }
783
784   /**
785    * 
786    * @param pdbEntries
787    * @return a series of SequenceI arrays, one for each PDBEntry, listing which
788    *         sequence in the alignment holds a reference to it
789    */
790   public SequenceI[][] collateForPDB(PDBEntry[] pdbEntries)
791   {
792     ArrayList<SequenceI[]> seqvectors = new ArrayList<SequenceI[]>();
793     for (PDBEntry pdb : pdbEntries)
794     {
795       ArrayList<SequenceI> seqs = new ArrayList<SequenceI>();
796       for (int i = 0; i < alignment.getHeight(); i++)
797       {
798         Vector pdbs = alignment.getSequenceAt(i).getDatasetSequence()
799                 .getPDBId();
800         if (pdbs == null)
801         {
802           continue;
803         }
804         SequenceI sq;
805         for (int p = 0; p < pdbs.size(); p++)
806         {
807           PDBEntry p1 = (PDBEntry) pdbs.elementAt(p);
808           if (p1.getId().equals(pdb.getId()))
809           {
810             if (!seqs.contains(sq = alignment.getSequenceAt(i)))
811             {
812               seqs.add(sq);
813             }
814
815             continue;
816           }
817         }
818       }
819       seqvectors.add(seqs.toArray(new SequenceI[seqs.size()]));
820     }
821     return seqvectors.toArray(new SequenceI[seqvectors.size()][]);
822   }
823
824   public boolean isNormaliseSequenceLogo()
825   {
826     return normaliseSequenceLogo;
827   }
828
829   public void setNormaliseSequenceLogo(boolean state)
830   {
831     normaliseSequenceLogo = state;
832   }
833
834   /**
835    * 
836    * @return true if alignment characters should be displayed
837    */
838   public boolean isValidCharWidth()
839   {
840     return validCharWidth;
841   }
842
843   private Hashtable<String, AutoCalcSetting> calcIdParams = new Hashtable<String, AutoCalcSetting>();
844
845   public AutoCalcSetting getCalcIdSettingsFor(String calcId)
846   {
847     return calcIdParams.get(calcId);
848   }
849
850   public void setCalcIdSettingsFor(String calcId, AutoCalcSetting settings,
851           boolean needsUpdate)
852   {
853     calcIdParams.put(calcId, settings);
854     // TODO: create a restart list to trigger any calculations that need to be
855     // restarted after load
856     // calculator.getRegisteredWorkersOfClass(settings.getWorkerClass())
857     if (needsUpdate)
858     {
859       Cache.log.debug("trigger update for " + calcId);
860     }
861   }
862
863   /**
864    * Method called when another alignment's edit (or possibly other) command is
865    * broadcast to here.
866    *
867    * To allow for sequence mappings (e.g. protein to cDNA), we have to first
868    * 'unwind' the command on the source sequences (in simulation, not in fact),
869    * and then for each edit in turn:
870    * <ul>
871    * <li>compute the equivalent edit on the mapped sequences</li>
872    * <li>apply the mapped edit</li>
873    * <li>'apply' the source edit to the working copy of the source sequences</li>
874    * </ul>
875    * 
876    * @param command
877    * @param undo
878    * @param ssm
879    */
880   @Override
881   public void mirrorCommand(CommandI command, boolean undo,
882           StructureSelectionManager ssm, VamsasSource source)
883   {
884     /*
885      * Do nothing unless we are a 'complement' of the source. May replace this
886      * with direct calls not via SSM.
887      */
888     if (source instanceof AlignViewportI
889             && ((AlignViewportI) source).getCodingComplement() == this)
890     {
891       // ok to continue;
892     }
893     else
894     {
895       return;
896     }
897
898     CommandI mappedCommand = ssm.mapCommand(command, undo, getAlignment(),
899             getGapCharacter());
900     if (mappedCommand != null)
901     {
902       AlignmentI[] views = getAlignPanel().alignFrame.getViewAlignments();
903       mappedCommand.doCommand(views);
904       getAlignPanel().alignmentChanged();
905     }
906   }
907
908   /**
909    * Add the sequences from the given alignment to this viewport. Optionally,
910    * may give the user the option to open a new frame, or split panel, with cDNA
911    * and protein linked.
912    * 
913    * @param al
914    * @param title
915    */
916   public void addAlignment(AlignmentI al, String title)
917   {
918     // TODO: promote to AlignViewportI? applet CutAndPasteTransfer is different
919
920     // JBPComment: title is a largely redundant parameter at the moment
921     // JBPComment: this really should be an 'insert/pre/append' controller
922     // JBPComment: but the DNA/Protein check makes it a bit more complex
923
924     // refactored from FileLoader / CutAndPasteTransfer / SequenceFetcher with
925     // this comment:
926     // TODO: create undo object for this JAL-1101
927
928     /*
929      * If one alignment is protein and one nucleotide, with at least one
930      * sequence name in common, offer to open a linked alignment.
931      */
932     if (AlignmentUtils.isMappable(al, getAlignment()))
933     {
934       if (openLinkedAlignment(al, title))
935       {
936         return;
937       }
938     }
939     // TODO: JAL-407 regardless of above - identical sequences (based on ID and
940     // provenance) should share the same dataset sequence
941
942     for (int i = 0; i < al.getHeight(); i++)
943     {
944       getAlignment().addSequence(al.getSequenceAt(i));
945     }
946     // TODO this call was done by SequenceFetcher but not FileLoader or
947     // CutAndPasteTransfer. Is it needed?
948     // JBPComment: this repositions the view to show the new sequences
949     // JBPComment: so it is needed for UX
950     setEndSeq(getAlignment().getHeight());
951     firePropertyChange("alignment", null, getAlignment().getSequences());
952   }
953
954   /**
955    * Show a dialog with the option to open and link (cDNA <-> protein) as a new
956    * alignment. Returns true if the new alignment was opened, false if not,
957    * because the user declined the offer.
958    * 
959    * @param title
960    */
961   protected boolean openLinkedAlignment(AlignmentI al, String title)
962   {
963     String[] options = new String[]
964     { MessageManager.getString("action.no"),
965         MessageManager.getString("label.split_window"),
966         MessageManager.getString("label.new_window"), };
967     final String question = JvSwingUtils.wrapTooltip(true,
968             MessageManager.getString("label.open_split_window?"));
969     int response = JOptionPane.showOptionDialog(Desktop.desktop, question,
970             MessageManager.getString("label.open_split_window"),
971             JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null,
972             options, options[0]);
973
974     if (response != 1 && response != 2)
975     {
976       return false;
977     }
978     final boolean openSplitPane = (response == 1);
979     final boolean openInNewWindow = (response == 2);
980
981     /*
982      * Create the AlignFrame first (which creates the new alignment's datasets),
983      * before attempting sequence mapping.
984      */
985     AlignFrame newAlignFrame = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH,
986             AlignFrame.DEFAULT_HEIGHT);
987     newAlignFrame.setTitle(title);
988
989     /*
990      * Identify protein and dna alignments. Make a copy of this one if opening
991      * in a new split pane.
992      */
993     AlignmentI thisAlignment = openSplitPane ? new Alignment(getAlignment())
994             : getAlignment();
995     AlignmentI protein = al.isNucleotide() ? thisAlignment : al;
996     final AlignmentI cdna = al.isNucleotide() ? al : thisAlignment;
997
998     newAlignFrame.statusBar.setText(MessageManager.formatMessage(
999             "label.successfully_loaded_file", new Object[]
1000             { title }));
1001
1002     // TODO if we want this (e.g. to enable reload of the alignment from file),
1003     // we will need to add parameters to the stack.
1004     // if (!protocol.equals(AppletFormatAdapter.PASTE))
1005     // {
1006     // alignFrame.setFileName(file, format);
1007     // }
1008
1009     if (openInNewWindow)
1010     {
1011       Desktop.addInternalFrame(newAlignFrame, title,
1012               AlignFrame.DEFAULT_WIDTH,
1013               AlignFrame.DEFAULT_HEIGHT);
1014     }
1015
1016     /*
1017      * Map sequences. At least one should get mapped as we have already passed
1018      * the test for 'mappability'. Any mappings made will be added to the
1019      * protein alignment.
1020      */
1021     AlignmentUtils.mapProteinToCdna(protein, cdna);
1022
1023     try
1024     {
1025       newAlignFrame.setMaximum(jalview.bin.Cache.getDefault(
1026               "SHOW_FULLSCREEN",
1027               false));
1028     } catch (java.beans.PropertyVetoException ex)
1029     {
1030     }
1031
1032     if (openSplitPane)
1033     {
1034       protein = openSplitFrame(newAlignFrame, thisAlignment,
1035               protein.getCodonFrames());
1036     }
1037
1038     /*
1039      * Register the mappings (held on the protein alignment) with the
1040      * StructureSelectionManager (for mouseover linking).
1041      */
1042     final StructureSelectionManager ssm = StructureSelectionManager
1043             .getStructureSelectionManager(Desktop.instance);
1044     ssm.addMappings(protein.getCodonFrames());
1045
1046     return true;
1047   }
1048
1049   /**
1050    * Helper method to open a new SplitFrame holding linked dna and protein
1051    * alignments.
1052    * 
1053    * @param newAlignFrame
1054    *          containing a new alignment to be shown
1055    * @param complement
1056    *          cdna/protein complement alignment to show in the other split half
1057    * @param mappings
1058    * @return the protein alignment in the split frame
1059    */
1060   protected AlignmentI openSplitFrame(AlignFrame newAlignFrame,
1061           AlignmentI complement, Set<AlignedCodonFrame> mappings)
1062   {
1063     /*
1064      * Open in split pane. DNA sequence above, protein below.
1065      */
1066     AlignFrame copyMe = new AlignFrame(complement,
1067             AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
1068     copyMe.setTitle(getAlignPanel().alignFrame.getTitle());
1069
1070     AlignmentI al = newAlignFrame.viewport.getAlignment();
1071     final AlignFrame proteinFrame = al.isNucleotide() ? copyMe
1072             : newAlignFrame;
1073     final AlignFrame cdnaFrame = al.isNucleotide() ? newAlignFrame
1074             : copyMe;
1075     AlignmentI protein = proteinFrame.viewport.getAlignment();
1076     protein.setCodonFrames(mappings);
1077
1078     cdnaFrame.setVisible(true);
1079     proteinFrame.setVisible(true);
1080     String linkedTitle = MessageManager
1081             .getString("label.linked_view_title");
1082     JInternalFrame splitFrame = new SplitFrame(cdnaFrame, proteinFrame);
1083     Desktop.addInternalFrame(splitFrame, linkedTitle, -1, -1);
1084
1085     return protein;
1086   }
1087
1088   public AnnotationColumnChooser getAnnotationColumnSelectionState()
1089   {
1090     return annotationColumnSelectionState;
1091   }
1092
1093   public void setAnnotationColumnSelectionState(
1094           AnnotationColumnChooser currentAnnotationColumnSelectionState)
1095   {
1096     this.annotationColumnSelectionState = currentAnnotationColumnSelectionState;
1097   }
1098
1099   @Override
1100   public void setIdWidth(int i)
1101   {
1102     super.setIdWidth(i);
1103     AlignmentPanel ap = getAlignPanel();
1104     if (ap != null)
1105     {
1106       // modify GUI elements to reflect geometry change
1107       Dimension idw = getAlignPanel().getIdPanel().getIdCanvas()
1108               .getPreferredSize();
1109       idw.width = i;
1110       getAlignPanel().getIdPanel().getIdCanvas().setPreferredSize(idw);
1111     }
1112   }
1113
1114   public Rectangle getExplodedGeometry()
1115   {
1116     return explodedGeometry;
1117   }
1118
1119   public void setExplodedGeometry(Rectangle explodedPosition)
1120   {
1121     this.explodedGeometry = explodedPosition;
1122   }
1123
1124   public boolean isGatherViewsHere()
1125   {
1126     return gatherViewsHere;
1127   }
1128
1129   public void setGatherViewsHere(boolean gatherViewsHere)
1130   {
1131     this.gatherViewsHere = gatherViewsHere;
1132   }
1133 }