JAL-1452 nucleotide/protein default colour; JAL-845 SplitFrame refactor
[jalview.git] / src / jalview / gui / AlignViewport.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 /*
22  * Jalview - A Sequence Alignment Editor and Viewer
23  * Copyright (C) 2007 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
24  *
25  * This program is free software; you can redistribute it and/or
26  * modify it under the terms of the GNU General Public License
27  * as published by the Free Software Foundation; either version 2
28  * of the License, or (at your option) any later version.
29  *
30  * This program is distributed in the hope that it will be useful,
31  * but WITHOUT ANY WARRANTY; without even the implied warranty of
32  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
33  * GNU General Public License for more details.
34  *
35  * You should have received a copy of the GNU General Public License
36  * along with this program; if not, write to the Free Software
37  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
38  */
39 package jalview.gui;
40
41 import jalview.analysis.AlignmentUtils;
42 import jalview.analysis.AlignmentUtils.MappingResult;
43 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
44 import jalview.analysis.NJTree;
45 import jalview.api.AlignViewportI;
46 import jalview.api.ViewStyleI;
47 import jalview.bin.Cache;
48 import jalview.commands.CommandI;
49 import jalview.datamodel.AlignedCodonFrame;
50 import jalview.datamodel.Alignment;
51 import jalview.datamodel.AlignmentI;
52 import jalview.datamodel.ColumnSelection;
53 import jalview.datamodel.PDBEntry;
54 import jalview.datamodel.Sequence;
55 import jalview.datamodel.SequenceGroup;
56 import jalview.datamodel.SequenceI;
57 import jalview.schemes.ColourSchemeProperty;
58 import jalview.schemes.UserColourScheme;
59 import jalview.structure.CommandListener;
60 import jalview.structure.SelectionSource;
61 import jalview.structure.StructureSelectionManager;
62 import jalview.structure.VamsasSource;
63 import jalview.util.MessageManager;
64 import jalview.viewmodel.AlignmentViewport;
65 import jalview.ws.params.AutoCalcSetting;
66
67 import java.awt.Container;
68 import java.awt.Dimension;
69 import java.awt.Font;
70 import java.awt.Rectangle;
71 import java.util.ArrayList;
72 import java.util.Hashtable;
73 import java.util.Set;
74 import java.util.Vector;
75
76 import javax.swing.JInternalFrame;
77 import javax.swing.JOptionPane;
78
79 /**
80  * DOCUMENT ME!
81  * 
82  * @author $author$
83  * @version $Revision: 1.141 $
84  */
85 public class AlignViewport extends AlignmentViewport implements
86         SelectionSource, AlignViewportI, CommandListener
87 {
88   int startRes;
89
90   int endRes;
91
92   int startSeq;
93
94   int endSeq;
95
96
97   SequenceAnnotationOrder sortAnnotationsBy = null;
98
99   Font font;
100
101   NJTree currentTree = null;
102
103   boolean cursorMode = false;
104
105   boolean antiAlias = false;
106
107   private Rectangle explodedGeometry;
108
109   String viewName;
110
111   /*
112    * Flag set true on the view that should 'gather' multiple views of the same
113    * sequence set id when a project is reloaded. Set false on all views when
114    * they are 'exploded' into separate windows. Set true on the current view
115    * when 'Gather' is performed, and also on the first tab when the first new
116    * view is created.
117    */
118   private boolean gatherViewsHere = false;
119
120   private AnnotationColumnChooser annotationColumnSelectionState;
121   /**
122    * Creates a new AlignViewport object.
123    * 
124    * @param al
125    *          alignment to view
126    */
127   public AlignViewport(AlignmentI al)
128   {
129     setAlignment(al);
130     init();
131   }
132
133   /**
134    * Create a new AlignViewport object with a specific sequence set ID
135    * 
136    * @param al
137    * @param seqsetid
138    *          (may be null - but potential for ambiguous constructor exception)
139    */
140   public AlignViewport(AlignmentI al, String seqsetid)
141   {
142     this(al, seqsetid, null);
143   }
144
145   public AlignViewport(AlignmentI al, String seqsetid, String viewid)
146   {
147     sequenceSetID = seqsetid;
148     viewId = viewid;
149     // TODO remove these once 2.4.VAMSAS release finished
150     if (Cache.log != null && Cache.log.isDebugEnabled() && seqsetid != null)
151     {
152       Cache.log.debug("Setting viewport's sequence set id : "
153               + sequenceSetID);
154     }
155     if (Cache.log != null && Cache.log.isDebugEnabled() && viewId != null)
156     {
157       Cache.log.debug("Setting viewport's view id : " + viewId);
158     }
159     setAlignment(al);
160     init();
161   }
162
163   /**
164    * Create a new AlignViewport with hidden regions
165    * 
166    * @param al
167    *          AlignmentI
168    * @param hiddenColumns
169    *          ColumnSelection
170    */
171   public AlignViewport(AlignmentI al, ColumnSelection hiddenColumns)
172   {
173     setAlignment(al);
174     if (hiddenColumns != null)
175     {
176       colSel = hiddenColumns;
177     }
178     init();
179   }
180
181   /**
182    * New viewport with hidden columns and an existing sequence set id
183    * 
184    * @param al
185    * @param hiddenColumns
186    * @param seqsetid
187    *          (may be null)
188    */
189   public AlignViewport(AlignmentI al, ColumnSelection hiddenColumns,
190           String seqsetid)
191   {
192     this(al, hiddenColumns, seqsetid, null);
193   }
194
195   /**
196    * New viewport with hidden columns and an existing sequence set id and viewid
197    * 
198    * @param al
199    * @param hiddenColumns
200    * @param seqsetid
201    *          (may be null)
202    * @param viewid
203    *          (may be null)
204    */
205   public AlignViewport(AlignmentI al, ColumnSelection hiddenColumns,
206           String seqsetid, String viewid)
207   {
208     sequenceSetID = seqsetid;
209     viewId = viewid;
210     // TODO remove these once 2.4.VAMSAS release finished
211     if (Cache.log != null && Cache.log.isDebugEnabled() && seqsetid != null)
212     {
213       Cache.log.debug("Setting viewport's sequence set id : "
214               + sequenceSetID);
215     }
216     if (Cache.log != null && Cache.log.isDebugEnabled() && viewId != null)
217     {
218       Cache.log.debug("Setting viewport's view id : " + viewId);
219     }
220     setAlignment(al);
221     if (hiddenColumns != null)
222     {
223       colSel = hiddenColumns;
224     }
225     init();
226   }
227
228   private void applyViewProperties()
229   {
230     antiAlias = Cache.getDefault("ANTI_ALIAS", false);
231
232     viewStyle.setShowJVSuffix(Cache.getDefault("SHOW_JVSUFFIX", true));
233     setShowAnnotation(Cache.getDefault("SHOW_ANNOTATIONS", true));
234
235     setRightAlignIds(Cache.getDefault("RIGHT_ALIGN_IDS", false));
236     setCentreColumnLabels(Cache.getDefault("CENTRE_COLUMN_LABELS", false));
237     autoCalculateConsensus = Cache.getDefault("AUTO_CALC_CONSENSUS", true);
238
239     setPadGaps(Cache.getDefault("PAD_GAPS", true));
240     setShowNPFeats(Cache.getDefault("SHOW_NPFEATS_TOOLTIP", true));
241     setShowDBRefs(Cache.getDefault("SHOW_DBREFS_TOOLTIP", true));
242     viewStyle.setSeqNameItalics(Cache.getDefault("ID_ITALICS", true));
243     viewStyle.setWrapAlignment(Cache.getDefault("WRAP_ALIGNMENT", false));
244     viewStyle.setShowUnconserved(Cache
245             .getDefault("SHOW_UNCONSERVED", false));
246     sortByTree = Cache.getDefault("SORT_BY_TREE", false);
247     followSelection = Cache.getDefault("FOLLOW_SELECTIONS", true);
248     sortAnnotationsBy = SequenceAnnotationOrder.valueOf(Cache.getDefault(
249             Preferences.SORT_ANNOTATIONS,
250             SequenceAnnotationOrder.NONE.name()));
251     showAutocalculatedAbove = Cache.getDefault(
252             Preferences.SHOW_AUTOCALC_ABOVE, false);
253
254   }
255
256   void init()
257   {
258     this.startRes = 0;
259     this.endRes = alignment.getWidth() - 1;
260     this.startSeq = 0;
261     this.endSeq = alignment.getHeight() - 1;
262     applyViewProperties();
263
264     String fontName = Cache.getDefault("FONT_NAME", "SansSerif");
265     String fontStyle = Cache.getDefault("FONT_STYLE", Font.PLAIN + "");
266     String fontSize = Cache.getDefault("FONT_SIZE", "10");
267
268     int style = 0;
269
270     if (fontStyle.equals("bold"))
271     {
272       style = 1;
273     }
274     else if (fontStyle.equals("italic"))
275     {
276       style = 2;
277     }
278
279     setFont(new Font(fontName, style, Integer.parseInt(fontSize)), 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   private boolean showAutocalculatedAbove;
846
847   public AutoCalcSetting getCalcIdSettingsFor(String calcId)
848   {
849     return calcIdParams.get(calcId);
850   }
851
852   public void setCalcIdSettingsFor(String calcId, AutoCalcSetting settings,
853           boolean needsUpdate)
854   {
855     calcIdParams.put(calcId, settings);
856     // TODO: create a restart list to trigger any calculations that need to be
857     // restarted after load
858     // calculator.getRegisteredWorkersOfClass(settings.getWorkerClass())
859     if (needsUpdate)
860     {
861       Cache.log.debug("trigger update for " + calcId);
862     }
863   }
864
865   protected SequenceAnnotationOrder getSortAnnotationsBy()
866   {
867     return sortAnnotationsBy;
868   }
869
870   protected void setSortAnnotationsBy(SequenceAnnotationOrder sortAnnotationsBy)
871   {
872     this.sortAnnotationsBy = sortAnnotationsBy;
873   }
874
875   protected boolean isShowAutocalculatedAbove()
876   {
877     return showAutocalculatedAbove;
878   }
879
880   protected void setShowAutocalculatedAbove(boolean showAutocalculatedAbove)
881   {
882     this.showAutocalculatedAbove = showAutocalculatedAbove;
883   }
884
885   /**
886    * Method called when another alignment's edit (or possibly other) command is
887    * broadcast to here.
888    *
889    * To allow for sequence mappings (e.g. protein to cDNA), we have to first
890    * 'unwind' the command on the source sequences (in simulation, not in fact),
891    * and then for each edit in turn:
892    * <ul>
893    * <li>compute the equivalent edit on the mapped sequences</li>
894    * <li>apply the mapped edit</li>
895    * <li>'apply' the source edit to the working copy of the source sequences</li>
896    * </ul>
897    * 
898    * @param command
899    * @param undo
900    * @param ssm
901    */
902   @Override
903   public void mirrorCommand(CommandI command, boolean undo,
904           StructureSelectionManager ssm, VamsasSource source)
905   {
906     /*
907      * Do nothing unless we are a 'complement' of the source. May replace this
908      * with direct calls not via SSM.
909      */
910     if (source instanceof AlignViewportI
911             && ((AlignViewportI) source).getCodingComplement() == this)
912     {
913       // ok to continue;
914     }
915     else
916     {
917       return;
918     }
919
920     CommandI mappedCommand = ssm.mapCommand(command, undo, getAlignment(),
921             getGapCharacter());
922     if (mappedCommand != null)
923     {
924       AlignmentI[] views = getAlignPanel().alignFrame.getViewAlignments();
925       mappedCommand.doCommand(views);
926       getAlignPanel().alignmentChanged();
927     }
928   }
929
930   /**
931    * Add the sequences from the given alignment to this viewport. Optionally,
932    * may give the user the option to open a new frame, or split panel, with cDNA
933    * and protein linked.
934    * 
935    * @param al
936    * @param title
937    */
938   public void addAlignment(AlignmentI al, String title)
939   {
940     // TODO: promote to AlignViewportI? applet CutAndPasteTransfer is different
941
942     // JBPComment: title is a largely redundant parameter at the moment
943     // JBPComment: this really should be an 'insert/pre/append' controller
944     // JBPComment: but the DNA/Protein check makes it a bit more complex
945
946     // refactored from FileLoader / CutAndPasteTransfer / SequenceFetcher with
947     // this comment:
948     // TODO: create undo object for this JAL-1101
949
950     /*
951      * If one alignment is protein and one nucleotide, with at least one
952      * sequence name in common, offer to open a linked alignment.
953      */
954     if (getAlignment().isNucleotide() != al.isNucleotide())
955     {
956       // TODO: JAL-845 try a bit harder to link up imported sequences
957       final Set<String> sequenceNames = getAlignment().getSequenceNames();
958       sequenceNames.retainAll(al.getSequenceNames());
959       if (!sequenceNames.isEmpty()) // at least one sequence name in both
960       {
961         if (openLinkedAlignment(al, title))
962         {
963           return;
964         }
965       }
966     }
967     // TODO: JAL-407 regardless of above - identical sequences (based on ID and
968     // provenance) should share the same dataset sequence
969
970     for (int i = 0; i < al.getHeight(); i++)
971     {
972       getAlignment().addSequence(al.getSequenceAt(i));
973     }
974     // TODO this call was done by SequenceFetcher but not FileLoader or
975     // CutAndPasteTransfer. Is it needed?
976     // JBPComment: this repositions the view to show the new sequences
977     // JBPComment: so it is needed for UX
978     setEndSeq(getAlignment().getHeight());
979     firePropertyChange("alignment", null, getAlignment().getSequences());
980   }
981
982   /**
983    * Show a dialog with the option to open and link (cDNA <-> protein) as a new
984    * alignment. Returns true if the new alignment was opened, false if not,
985    * because the user declined the offer.
986    * 
987    * @param title
988    */
989   protected boolean openLinkedAlignment(AlignmentI al, String title)
990   {
991     String[] options = new String[]
992     { MessageManager.getString("action.no"),
993         MessageManager.getString("label.split_window"),
994         MessageManager.getString("label.new_window"), };
995     final String question = JvSwingUtils.wrapTooltip(true,
996             MessageManager.getString("label.open_split_window?"));
997     int response = JOptionPane.showOptionDialog(Desktop.desktop, question,
998             MessageManager.getString("label.open_split_window"),
999             JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null,
1000             options, options[0]);
1001
1002     if (response != 1 && response != 2)
1003     {
1004       return false;
1005     }
1006     final boolean openSplitPane = (response == 1);
1007     final boolean openInNewWindow = (response == 2);
1008
1009     /*
1010      * Create the AlignFrame first (which creates the new alignment's datasets),
1011      * before attempting sequence mapping.
1012      */
1013     AlignFrame newAlignFrame = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH,
1014             AlignFrame.DEFAULT_HEIGHT);
1015     newAlignFrame.setTitle(title);
1016
1017     /*
1018      * Identify protein and dna alignments. Make a copy of this one if opening
1019      * in a new split pane.
1020      */
1021     AlignmentI thisAlignment = openSplitPane ? new Alignment(getAlignment())
1022             : getAlignment();
1023     AlignmentI protein = al.isNucleotide() ? thisAlignment : al;
1024     final AlignmentI cdna = al.isNucleotide() ? al : thisAlignment;
1025
1026     newAlignFrame.statusBar.setText(MessageManager.formatMessage(
1027             "label.successfully_loaded_file", new Object[]
1028             { title }));
1029
1030     // TODO if we want this (e.g. to enable reload of the alignment from file),
1031     // we will need to add parameters to the stack.
1032     // if (!protocol.equals(AppletFormatAdapter.PASTE))
1033     // {
1034     // alignFrame.setFileName(file, format);
1035     // }
1036
1037     if (openInNewWindow)
1038     {
1039       Desktop.addInternalFrame(newAlignFrame, title,
1040               AlignFrame.DEFAULT_WIDTH,
1041               AlignFrame.DEFAULT_HEIGHT);
1042     }
1043
1044     /*
1045      * Try to find mappings for at least one sequence. Any mappings made will be
1046      * added to the protein alignment.
1047      */
1048     MappingResult mapped = AlignmentUtils.mapProteinToCdna(protein, cdna);
1049     if (mapped != MappingResult.Mapped)
1050     {
1051       /*
1052        * No mapping possible - warn the user, but leave window open.
1053        */
1054       final String msg = JvSwingUtils.wrapTooltip(true,
1055               MessageManager.getString("label.mapping_failed"));
1056       JOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1057               MessageManager.getString("label.no_mappings"),
1058               JOptionPane.WARNING_MESSAGE);
1059     }
1060
1061     try
1062     {
1063       newAlignFrame.setMaximum(jalview.bin.Cache.getDefault(
1064               "SHOW_FULLSCREEN",
1065               false));
1066     } catch (java.beans.PropertyVetoException ex)
1067     {
1068     }
1069
1070     if (openSplitPane)
1071     {
1072       protein = openSplitFrame(newAlignFrame,
1073               thisAlignment.getSequencesArray(), protein.getCodonFrames());
1074     }
1075
1076     /*
1077      * Register the mappings (held on the protein alignment) with the
1078      * StructureSelectionManager (for mouseover linking).
1079      */
1080     final StructureSelectionManager ssm = StructureSelectionManager
1081             .getStructureSelectionManager(Desktop.instance);
1082     ssm.addMappings(protein.getCodonFrames());
1083
1084     return true;
1085   }
1086
1087   /**
1088    * Helper method to open a new SplitFrame holding linked dna and protein
1089    * alignments.
1090    * 
1091    * @param newAlignFrame
1092    *          containing a new alignment to be shown
1093    * @param seqs
1094    *          'complementary' sequences to show in the other split half
1095    * @param mappings
1096    * @return the protein alignment in the split frame
1097    */
1098   protected AlignmentI openSplitFrame(AlignFrame newAlignFrame,
1099           SequenceI[] seqs, Set<AlignedCodonFrame> mappings)
1100   {
1101     AlignmentI complementAlignment = new Alignment(seqs);
1102     // TODO: move this to a factory/controller method ?
1103     /*
1104      * Open in split pane. DNA sequence above, protein below.
1105      */
1106     AlignFrame copyMe = new AlignFrame(complementAlignment,
1107             AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
1108     copyMe.setTitle(getAlignPanel().alignFrame.getTitle());
1109
1110     AlignmentI al = newAlignFrame.viewport.getAlignment();
1111     final AlignFrame proteinFrame = al.isNucleotide() ? copyMe
1112             : newAlignFrame;
1113     final AlignFrame cdnaFrame = al.isNucleotide() ? newAlignFrame
1114             : copyMe;
1115     AlignmentI protein = proteinFrame.viewport.getAlignment();
1116     protein.setCodonFrames(mappings);
1117
1118     cdnaFrame.setVisible(true);
1119     proteinFrame.setVisible(true);
1120     String linkedTitle = MessageManager
1121             .getString("label.linked_view_title");
1122     JInternalFrame splitFrame = new SplitFrame(cdnaFrame, proteinFrame);
1123     Desktop.addInternalFrame(splitFrame, linkedTitle, -1, -1);
1124
1125     return protein;
1126   }
1127
1128   public AnnotationColumnChooser getAnnotationColumnSelectionState()
1129   {
1130     return annotationColumnSelectionState;
1131   }
1132
1133   public void setAnnotationColumnSelectionState(
1134           AnnotationColumnChooser currentAnnotationColumnSelectionState)
1135   {
1136     this.annotationColumnSelectionState = currentAnnotationColumnSelectionState;
1137   }
1138
1139   @Override
1140   public void setIdWidth(int i)
1141   {
1142     super.setIdWidth(i);
1143     AlignmentPanel ap = getAlignPanel();
1144     if (ap != null)
1145     {
1146       // modify GUI elements to reflect geometry change
1147       Dimension idw = getAlignPanel().getIdPanel().getIdCanvas()
1148               .getPreferredSize();
1149       idw.width = i;
1150       getAlignPanel().getIdPanel().getIdCanvas().setPreferredSize(idw);
1151     }
1152   }
1153
1154   public Rectangle getExplodedGeometry()
1155   {
1156     return explodedGeometry;
1157   }
1158
1159   public void setExplodedGeometry(Rectangle explodedPosition)
1160   {
1161     this.explodedGeometry = explodedPosition;
1162   }
1163
1164   public boolean isGatherViewsHere()
1165   {
1166     return gatherViewsHere;
1167   }
1168
1169   public void setGatherViewsHere(boolean gatherViewsHere)
1170   {
1171     this.gatherViewsHere = gatherViewsHere;
1172   }
1173 }