JAL-1619 refactoring / tests to support 'align linked dna as protein'
[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.bin.Cache;
47 import jalview.commands.CommandI;
48 import jalview.commands.EditCommand;
49 import jalview.datamodel.AlignmentI;
50 import jalview.datamodel.ColumnSelection;
51 import jalview.datamodel.PDBEntry;
52 import jalview.datamodel.Sequence;
53 import jalview.datamodel.SequenceGroup;
54 import jalview.datamodel.SequenceI;
55 import jalview.schemes.ColourSchemeProperty;
56 import jalview.schemes.UserColourScheme;
57 import jalview.structure.CommandListener;
58 import jalview.structure.SelectionSource;
59 import jalview.structure.StructureSelectionManager;
60 import jalview.structure.VamsasSource;
61 import jalview.util.MessageManager;
62 import jalview.viewmodel.AlignmentViewport;
63 import jalview.ws.params.AutoCalcSetting;
64
65 import java.awt.Color;
66 import java.awt.Container;
67 import java.awt.Font;
68 import java.awt.Rectangle;
69 import java.util.ArrayDeque;
70 import java.util.ArrayList;
71 import java.util.Deque;
72 import java.util.Hashtable;
73 import java.util.List;
74 import java.util.Set;
75 import java.util.Vector;
76
77 import javax.swing.JInternalFrame;
78 import javax.swing.JOptionPane;
79 import javax.swing.JSplitPane;
80
81 /**
82  * DOCUMENT ME!
83  * 
84  * @author $author$
85  * @version $Revision: 1.141 $
86  */
87 public class AlignViewport extends AlignmentViewport implements
88         SelectionSource, VamsasSource, AlignViewportI, CommandListener
89 {
90   int startRes;
91
92   int endRes;
93
94   int startSeq;
95
96   int endSeq;
97
98   boolean showJVSuffix = true;
99
100   boolean showText = true;
101
102   boolean showColourText = false;
103
104   boolean showBoxes = true;
105
106   boolean wrapAlignment = false;
107
108   boolean renderGaps = true;
109
110   boolean showSequenceFeatures = false;
111
112   boolean showAnnotation = true;
113
114   SequenceAnnotationOrder sortAnnotationsBy = null;
115
116   int charHeight;
117
118   int charWidth;
119
120   boolean validCharWidth;
121
122   int wrappedWidth;
123
124   Font font;
125
126   boolean seqNameItalics;
127
128   NJTree currentTree = null;
129
130   boolean scaleAboveWrapped = false;
131
132   boolean scaleLeftWrapped = true;
133
134   boolean scaleRightWrapped = true;
135
136   boolean showHiddenMarkers = true;
137
138   boolean cursorMode = false;
139
140   /**
141    * Keys are the feature types which are currently visible. Note: Values are
142    * not used!
143    */
144   Hashtable featuresDisplayed = null;
145
146   boolean antiAlias = false;
147
148   Rectangle explodedPosition;
149
150   String viewName;
151
152   boolean gatherViewsHere = false;
153
154   private Deque<CommandI> historyList = new ArrayDeque<CommandI>();
155
156   private Deque<CommandI> redoList = new ArrayDeque<CommandI>();
157
158   int thresholdTextColour = 0;
159
160   Color textColour = Color.black;
161
162   Color textColour2 = Color.white;
163
164   boolean rightAlignIds = false;
165
166   /**
167    * Creates a new AlignViewport object.
168    * 
169    * @param al
170    *          alignment to view
171    */
172   public AlignViewport(AlignmentI al)
173   {
174     setAlignment(al);
175     init();
176   }
177
178   /**
179    * Create a new AlignViewport object with a specific sequence set ID
180    * 
181    * @param al
182    * @param seqsetid
183    *          (may be null - but potential for ambiguous constructor exception)
184    */
185   public AlignViewport(AlignmentI al, String seqsetid)
186   {
187     this(al, seqsetid, null);
188   }
189
190   public AlignViewport(AlignmentI al, String seqsetid, String viewid)
191   {
192     sequenceSetID = seqsetid;
193     viewId = viewid;
194     // TODO remove these once 2.4.VAMSAS release finished
195     if (Cache.log != null && Cache.log.isDebugEnabled() && seqsetid != null)
196     {
197       Cache.log.debug("Setting viewport's sequence set id : "
198               + sequenceSetID);
199     }
200     if (Cache.log != null && Cache.log.isDebugEnabled() && viewId != null)
201     {
202       Cache.log.debug("Setting viewport's view id : " + viewId);
203     }
204     setAlignment(al);
205     init();
206   }
207
208   /**
209    * Create a new AlignViewport with hidden regions
210    * 
211    * @param al
212    *          AlignmentI
213    * @param hiddenColumns
214    *          ColumnSelection
215    */
216   public AlignViewport(AlignmentI al, ColumnSelection hiddenColumns)
217   {
218     setAlignment(al);
219     if (hiddenColumns != null)
220     {
221       this.colSel = hiddenColumns;
222       if (hiddenColumns.getHiddenColumns() != null
223               && hiddenColumns.getHiddenColumns().size() > 0)
224       {
225         hasHiddenColumns = true;
226       }
227       else
228       {
229         hasHiddenColumns = false;
230       }
231     }
232     init();
233   }
234
235   /**
236    * New viewport with hidden columns and an existing sequence set id
237    * 
238    * @param al
239    * @param hiddenColumns
240    * @param seqsetid
241    *          (may be null)
242    */
243   public AlignViewport(AlignmentI al, ColumnSelection hiddenColumns,
244           String seqsetid)
245   {
246     this(al, hiddenColumns, seqsetid, null);
247   }
248
249   /**
250    * New viewport with hidden columns and an existing sequence set id and viewid
251    * 
252    * @param al
253    * @param hiddenColumns
254    * @param seqsetid
255    *          (may be null)
256    * @param viewid
257    *          (may be null)
258    */
259   public AlignViewport(AlignmentI al, ColumnSelection hiddenColumns,
260           String seqsetid, String viewid)
261   {
262     sequenceSetID = seqsetid;
263     viewId = viewid;
264     // TODO remove these once 2.4.VAMSAS release finished
265     if (Cache.log != null && Cache.log.isDebugEnabled() && seqsetid != null)
266     {
267       Cache.log.debug("Setting viewport's sequence set id : "
268               + sequenceSetID);
269     }
270     if (Cache.log != null && Cache.log.isDebugEnabled() && viewId != null)
271     {
272       Cache.log.debug("Setting viewport's view id : " + viewId);
273     }
274     setAlignment(al);
275     if (hiddenColumns != null)
276     {
277       this.colSel = hiddenColumns;
278       if (hiddenColumns.getHiddenColumns() != null
279               && hiddenColumns.getHiddenColumns().size() > 0)
280       {
281         hasHiddenColumns = true;
282       }
283       else
284       {
285         hasHiddenColumns = false;
286       }
287     }
288     init();
289   }
290
291   void init()
292   {
293     this.startRes = 0;
294     this.endRes = alignment.getWidth() - 1;
295     this.startSeq = 0;
296     this.endSeq = alignment.getHeight() - 1;
297
298     antiAlias = Cache.getDefault("ANTI_ALIAS", false);
299
300     showJVSuffix = Cache.getDefault("SHOW_JVSUFFIX", true);
301     showAnnotation = Cache.getDefault("SHOW_ANNOTATIONS", true);
302
303     rightAlignIds = Cache.getDefault("RIGHT_ALIGN_IDS", false);
304     centreColumnLabels = Cache.getDefault("CENTRE_COLUMN_LABELS", false);
305     autoCalculateConsensus = Cache.getDefault("AUTO_CALC_CONSENSUS", true);
306
307     setPadGaps(Cache.getDefault("PAD_GAPS", true));
308     shownpfeats = Cache.getDefault("SHOW_NPFEATS_TOOLTIP", true);
309     showdbrefs = Cache.getDefault("SHOW_DBREFS_TOOLTIP", true);
310
311     String fontName = Cache.getDefault("FONT_NAME", "SansSerif");
312     String fontStyle = Cache.getDefault("FONT_STYLE", Font.PLAIN + "");
313     String fontSize = Cache.getDefault("FONT_SIZE", "10");
314
315     seqNameItalics = Cache.getDefault("ID_ITALICS", true);
316
317     int style = 0;
318
319     if (fontStyle.equals("bold"))
320     {
321       style = 1;
322     }
323     else if (fontStyle.equals("italic"))
324     {
325       style = 2;
326     }
327
328     setFont(new Font(fontName, style, Integer.parseInt(fontSize)));
329
330     alignment
331             .setGapCharacter(Cache.getDefault("GAP_SYMBOL", "-").charAt(0));
332
333     // We must set conservation and consensus before setting colour,
334     // as Blosum and Clustal require this to be done
335     if (hconsensus == null && !isDataset)
336     {
337       if (!alignment.isNucleotide())
338       {
339         showConservation = Cache.getDefault("SHOW_CONSERVATION", true);
340         showQuality = Cache.getDefault("SHOW_QUALITY", true);
341         showGroupConservation = Cache.getDefault("SHOW_GROUP_CONSERVATION",
342                 false);
343       }
344       showConsensusHistogram = Cache.getDefault("SHOW_CONSENSUS_HISTOGRAM",
345               true);
346       showSequenceLogo = Cache.getDefault("SHOW_CONSENSUS_LOGO", false);
347       normaliseSequenceLogo = Cache.getDefault("NORMALISE_CONSENSUS_LOGO",
348               false);
349       showGroupConsensus = Cache.getDefault("SHOW_GROUP_CONSENSUS", false);
350       showConsensus = Cache.getDefault("SHOW_IDENTITY", true);
351     }
352     initAutoAnnotation();
353     if (jalview.bin.Cache.getProperty("DEFAULT_COLOUR") != null)
354     {
355       globalColourScheme = ColourSchemeProperty.getColour(alignment,
356               jalview.bin.Cache.getProperty("DEFAULT_COLOUR"));
357
358       if (globalColourScheme instanceof UserColourScheme)
359       {
360         globalColourScheme = UserDefinedColours.loadDefaultColours();
361         ((UserColourScheme) globalColourScheme).setThreshold(0,
362                 getIgnoreGapsConsensus());
363       }
364
365       if (globalColourScheme != null)
366       {
367         globalColourScheme.setConsensus(hconsensus);
368       }
369     }
370
371     wrapAlignment = Cache.getDefault("WRAP_ALIGNMENT", false);
372     showUnconserved = Cache.getDefault("SHOW_UNCONSERVED", false);
373     sortByTree = Cache.getDefault("SORT_BY_TREE", false);
374     followSelection = Cache.getDefault("FOLLOW_SELECTIONS", true);
375     sortAnnotationsBy = SequenceAnnotationOrder.valueOf(Cache.getDefault(
376             Preferences.SORT_ANNOTATIONS,
377             SequenceAnnotationOrder.NONE.name()));
378     showAutocalculatedAbove = Cache.getDefault(
379             Preferences.SHOW_AUTOCALC_ABOVE, false);
380   }
381
382   /**
383    * set the flag
384    * 
385    * @param b
386    *          features are displayed if true
387    */
388   public void setShowSequenceFeatures(boolean b)
389   {
390     showSequenceFeatures = b;
391   }
392
393   public boolean getShowSequenceFeatures()
394   {
395     return showSequenceFeatures;
396   }
397
398   /**
399    * centre columnar annotation labels in displayed alignment annotation TODO:
400    * add to jalviewXML and annotation display settings
401    */
402   boolean centreColumnLabels = false;
403
404   private boolean showdbrefs;
405
406   private boolean shownpfeats;
407
408   // --------END Structure Conservation
409
410   /**
411    * get the consensus sequence as displayed under the PID consensus annotation
412    * row.
413    * 
414    * @return consensus sequence as a new sequence object
415    */
416   public SequenceI getConsensusSeq()
417   {
418     if (consensus == null)
419     {
420       updateConsensus(null);
421     }
422     if (consensus == null)
423     {
424       return null;
425     }
426     StringBuffer seqs = new StringBuffer();
427     for (int i = 0; i < consensus.annotations.length; i++)
428     {
429       if (consensus.annotations[i] != null)
430       {
431         if (consensus.annotations[i].description.charAt(0) == '[')
432         {
433           seqs.append(consensus.annotations[i].description.charAt(1));
434         }
435         else
436         {
437           seqs.append(consensus.annotations[i].displayCharacter);
438         }
439       }
440     }
441
442     SequenceI sq = new Sequence("Consensus", seqs.toString());
443     sq.setDescription("Percentage Identity Consensus "
444             + ((ignoreGapsInConsensusCalculation) ? " without gaps" : ""));
445     return sq;
446   }
447
448   /**
449    * DOCUMENT ME!
450    * 
451    * @return DOCUMENT ME!
452    */
453   public int getStartRes()
454   {
455     return startRes;
456   }
457
458   /**
459    * DOCUMENT ME!
460    * 
461    * @return DOCUMENT ME!
462    */
463   public int getEndRes()
464   {
465     return endRes;
466   }
467
468   /**
469    * DOCUMENT ME!
470    * 
471    * @return DOCUMENT ME!
472    */
473   public int getStartSeq()
474   {
475     return startSeq;
476   }
477
478   /**
479    * DOCUMENT ME!
480    * 
481    * @param res
482    *          DOCUMENT ME!
483    */
484   public void setStartRes(int res)
485   {
486     this.startRes = res;
487   }
488
489   /**
490    * DOCUMENT ME!
491    * 
492    * @param seq
493    *          DOCUMENT ME!
494    */
495   public void setStartSeq(int seq)
496   {
497     this.startSeq = seq;
498   }
499
500   /**
501    * DOCUMENT ME!
502    * 
503    * @param res
504    *          DOCUMENT ME!
505    */
506   public void setEndRes(int res)
507   {
508     if (res > (alignment.getWidth() - 1))
509     {
510       // log.System.out.println(" Corrected res from " + res + " to maximum " +
511       // (alignment.getWidth()-1));
512       res = alignment.getWidth() - 1;
513     }
514
515     if (res < 0)
516     {
517       res = 0;
518     }
519
520     this.endRes = res;
521   }
522
523   /**
524    * DOCUMENT ME!
525    * 
526    * @param seq
527    *          DOCUMENT ME!
528    */
529   public void setEndSeq(int seq)
530   {
531     if (seq > alignment.getHeight())
532     {
533       seq = alignment.getHeight();
534     }
535
536     if (seq < 0)
537     {
538       seq = 0;
539     }
540
541     this.endSeq = seq;
542   }
543
544   /**
545    * DOCUMENT ME!
546    * 
547    * @return DOCUMENT ME!
548    */
549   public int getEndSeq()
550   {
551     return endSeq;
552   }
553
554   /**
555    * DOCUMENT ME!
556    * 
557    * @param f
558    *          DOCUMENT ME!
559    */
560   public void setFont(Font f)
561   {
562     font = f;
563
564     Container c = new Container();
565
566     java.awt.FontMetrics fm = c.getFontMetrics(font);
567     setCharHeight(fm.getHeight());
568     setCharWidth(fm.charWidth('M'));
569     validCharWidth = true;
570   }
571
572   /**
573    * DOCUMENT ME!
574    * 
575    * @return DOCUMENT ME!
576    */
577   public Font getFont()
578   {
579     return font;
580   }
581
582   /**
583    * DOCUMENT ME!
584    * 
585    * @param w
586    *          DOCUMENT ME!
587    */
588   public void setCharWidth(int w)
589   {
590     this.charWidth = w;
591   }
592
593   /**
594    * DOCUMENT ME!
595    * 
596    * @return DOCUMENT ME!
597    */
598   public int getCharWidth()
599   {
600     return charWidth;
601   }
602
603   /**
604    * DOCUMENT ME!
605    * 
606    * @param h
607    *          DOCUMENT ME!
608    */
609   public void setCharHeight(int h)
610   {
611     this.charHeight = h;
612   }
613
614   /**
615    * DOCUMENT ME!
616    * 
617    * @return DOCUMENT ME!
618    */
619   public int getCharHeight()
620   {
621     return charHeight;
622   }
623
624   /**
625    * DOCUMENT ME!
626    * 
627    * @param w
628    *          DOCUMENT ME!
629    */
630   public void setWrappedWidth(int w)
631   {
632     this.wrappedWidth = w;
633   }
634
635   /**
636    * DOCUMENT ME!
637    * 
638    * @return DOCUMENT ME!
639    */
640   public int getWrappedWidth()
641   {
642     return wrappedWidth;
643   }
644
645   /**
646    * DOCUMENT ME!
647    * 
648    * @return DOCUMENT ME!
649    */
650   public AlignmentI getAlignment()
651   {
652     return alignment;
653   }
654
655   /**
656    * DOCUMENT ME!
657    * 
658    * @param align
659    *          DOCUMENT ME!
660    */
661   public void setAlignment(AlignmentI align)
662   {
663     if (alignment != null && alignment.getCodonFrames() != null)
664     {
665       StructureSelectionManager.getStructureSelectionManager(
666               Desktop.instance).removeMappings(alignment.getCodonFrames());
667     }
668     this.alignment = align;
669     if (alignment != null && alignment.getCodonFrames() != null)
670     {
671       StructureSelectionManager.getStructureSelectionManager(
672               Desktop.instance).addMappings(alignment.getCodonFrames());
673     }
674   }
675
676   /**
677    * DOCUMENT ME!
678    * 
679    * @param state
680    *          DOCUMENT ME!
681    */
682   public void setWrapAlignment(boolean state)
683   {
684     wrapAlignment = state;
685   }
686
687   /**
688    * DOCUMENT ME!
689    * 
690    * @param state
691    *          DOCUMENT ME!
692    */
693   public void setShowText(boolean state)
694   {
695     showText = state;
696   }
697
698   /**
699    * DOCUMENT ME!
700    * 
701    * @param state
702    *          DOCUMENT ME!
703    */
704   public void setRenderGaps(boolean state)
705   {
706     renderGaps = state;
707   }
708
709   /**
710    * DOCUMENT ME!
711    * 
712    * @return DOCUMENT ME!
713    */
714   public boolean getColourText()
715   {
716     return showColourText;
717   }
718
719   /**
720    * DOCUMENT ME!
721    * 
722    * @param state
723    *          DOCUMENT ME!
724    */
725   public void setColourText(boolean state)
726   {
727     showColourText = state;
728   }
729
730   /**
731    * DOCUMENT ME!
732    * 
733    * @param state
734    *          DOCUMENT ME!
735    */
736   public void setShowBoxes(boolean state)
737   {
738     showBoxes = state;
739   }
740
741   /**
742    * DOCUMENT ME!
743    * 
744    * @return DOCUMENT ME!
745    */
746   public boolean getWrapAlignment()
747   {
748     return wrapAlignment;
749   }
750
751   /**
752    * DOCUMENT ME!
753    * 
754    * @return DOCUMENT ME!
755    */
756   public boolean getShowText()
757   {
758     return showText;
759   }
760
761   /**
762    * DOCUMENT ME!
763    * 
764    * @return DOCUMENT ME!
765    */
766   public boolean getShowBoxes()
767   {
768     return showBoxes;
769   }
770
771   /**
772    * DOCUMENT ME!
773    * 
774    * @return DOCUMENT ME!
775    */
776   public char getGapCharacter()
777   {
778     return getAlignment().getGapCharacter();
779   }
780
781   /**
782    * DOCUMENT ME!
783    * 
784    * @param gap
785    *          DOCUMENT ME!
786    */
787   public void setGapCharacter(char gap)
788   {
789     if (getAlignment() != null)
790     {
791       getAlignment().setGapCharacter(gap);
792     }
793   }
794
795   /**
796    * DOCUMENT ME!
797    * 
798    * @return DOCUMENT ME!
799    */
800   public ColumnSelection getColumnSelection()
801   {
802     return colSel;
803   }
804
805   /**
806    * DOCUMENT ME!
807    * 
808    * @param tree
809    *          DOCUMENT ME!
810    */
811   public void setCurrentTree(NJTree tree)
812   {
813     currentTree = tree;
814   }
815
816   /**
817    * DOCUMENT ME!
818    * 
819    * @return DOCUMENT ME!
820    */
821   public NJTree getCurrentTree()
822   {
823     return currentTree;
824   }
825
826   /**
827    * DOCUMENT ME!
828    * 
829    * @return DOCUMENT ME!
830    */
831   public boolean getShowJVSuffix()
832   {
833     return showJVSuffix;
834   }
835
836   /**
837    * DOCUMENT ME!
838    * 
839    * @param b
840    *          DOCUMENT ME!
841    */
842   public void setShowJVSuffix(boolean b)
843   {
844     showJVSuffix = b;
845   }
846
847   /**
848    * DOCUMENT ME!
849    * 
850    * @return DOCUMENT ME!
851    */
852   public boolean getShowAnnotation()
853   {
854     return showAnnotation;
855   }
856
857   /**
858    * DOCUMENT ME!
859    * 
860    * @param b
861    *          DOCUMENT ME!
862    */
863   public void setShowAnnotation(boolean b)
864   {
865     showAnnotation = b;
866   }
867
868   /**
869    * DOCUMENT ME!
870    * 
871    * @return DOCUMENT ME!
872    */
873   public boolean getScaleAboveWrapped()
874   {
875     return scaleAboveWrapped;
876   }
877
878   /**
879    * DOCUMENT ME!
880    * 
881    * @return DOCUMENT ME!
882    */
883   public boolean getScaleLeftWrapped()
884   {
885     return scaleLeftWrapped;
886   }
887
888   /**
889    * DOCUMENT ME!
890    * 
891    * @return DOCUMENT ME!
892    */
893   public boolean getScaleRightWrapped()
894   {
895     return scaleRightWrapped;
896   }
897
898   /**
899    * DOCUMENT ME!
900    * 
901    * @param b
902    *          DOCUMENT ME!
903    */
904   public void setScaleAboveWrapped(boolean b)
905   {
906     scaleAboveWrapped = b;
907   }
908
909   /**
910    * DOCUMENT ME!
911    * 
912    * @param b
913    *          DOCUMENT ME!
914    */
915   public void setScaleLeftWrapped(boolean b)
916   {
917     scaleLeftWrapped = b;
918   }
919
920   /**
921    * DOCUMENT ME!
922    * 
923    * @param b
924    *          DOCUMENT ME!
925    */
926   public void setScaleRightWrapped(boolean b)
927   {
928     scaleRightWrapped = b;
929   }
930
931   public void setDataset(boolean b)
932   {
933     isDataset = b;
934   }
935
936   public boolean isDataset()
937   {
938     return isDataset;
939   }
940
941   public boolean getShowHiddenMarkers()
942   {
943     return showHiddenMarkers;
944   }
945
946   public void setShowHiddenMarkers(boolean show)
947   {
948     showHiddenMarkers = show;
949   }
950
951   /**
952    * returns the visible column regions of the alignment
953    * 
954    * @param selectedRegionOnly
955    *          true to just return the contigs intersecting with the selected
956    *          area
957    * @return
958    */
959   public int[] getViewAsVisibleContigs(boolean selectedRegionOnly)
960   {
961     int[] viscontigs = null;
962     int start = 0, end = 0;
963     if (selectedRegionOnly && selectionGroup != null)
964     {
965       start = selectionGroup.getStartRes();
966       end = selectionGroup.getEndRes() + 1;
967     }
968     else
969     {
970       end = alignment.getWidth();
971     }
972     viscontigs = colSel.getVisibleContigs(start, end);
973     return viscontigs;
974   }
975
976   /**
977    * get hash of undo and redo list for the alignment
978    * 
979    * @return long[] { historyList.hashCode, redoList.hashCode };
980    */
981   public long[] getUndoRedoHash()
982   {
983     // TODO: JAL-1126
984     if (historyList == null || redoList == null)
985     {
986       return new long[]
987       { -1, -1 };
988     }
989     return new long[]
990     { historyList.hashCode(), this.redoList.hashCode() };
991   }
992
993   /**
994    * test if a particular set of hashcodes are different to the hashcodes for
995    * the undo and redo list.
996    * 
997    * @param undoredo
998    *          the stored set of hashcodes as returned by getUndoRedoHash
999    * @return true if the hashcodes differ (ie the alignment has been edited) or
1000    *         the stored hashcode array differs in size
1001    */
1002   public boolean isUndoRedoHashModified(long[] undoredo)
1003   {
1004     if (undoredo == null)
1005     {
1006       return true;
1007     }
1008     long[] cstate = getUndoRedoHash();
1009     if (cstate.length != undoredo.length)
1010     {
1011       return true;
1012     }
1013
1014     for (int i = 0; i < cstate.length; i++)
1015     {
1016       if (cstate[i] != undoredo[i])
1017       {
1018         return true;
1019       }
1020     }
1021     return false;
1022   }
1023
1024   public boolean getCentreColumnLabels()
1025   {
1026     return centreColumnLabels;
1027   }
1028
1029   public void setCentreColumnLabels(boolean centrecolumnlabels)
1030   {
1031     centreColumnLabels = centrecolumnlabels;
1032   }
1033
1034   /**
1035    * enable or disable the display of Database Cross References in the sequence
1036    * ID tooltip
1037    */
1038   public void setShowDbRefs(boolean show)
1039   {
1040     showdbrefs = show;
1041   }
1042
1043   /**
1044    * 
1045    * @return true if Database References are to be displayed on tooltips.
1046    */
1047   public boolean isShowDbRefs()
1048   {
1049     return showdbrefs;
1050   }
1051
1052   /**
1053    * 
1054    * @return true if Non-positional features are to be displayed on tooltips.
1055    */
1056   public boolean isShowNpFeats()
1057   {
1058     return shownpfeats;
1059   }
1060
1061   /**
1062    * enable or disable the display of Non-Positional sequence features in the
1063    * sequence ID tooltip
1064    * 
1065    * @param show
1066    */
1067   public void setShowNpFeats(boolean show)
1068   {
1069     shownpfeats = show;
1070   }
1071
1072   /**
1073    * 
1074    * @return true if view has hidden rows
1075    */
1076   public boolean hasHiddenRows()
1077   {
1078     return hasHiddenRows;
1079   }
1080
1081   /**
1082    * 
1083    * @return true if view has hidden columns
1084    */
1085   public boolean hasHiddenColumns()
1086   {
1087     return hasHiddenColumns;
1088   }
1089
1090   /**
1091    * when set, view will scroll to show the highlighted position
1092    */
1093   public boolean followHighlight = true;
1094
1095   /**
1096    * @return true if view should scroll to show the highlighted region of a
1097    *         sequence
1098    * @return
1099    */
1100   public boolean getFollowHighlight()
1101   {
1102     return followHighlight;
1103   }
1104
1105   public boolean followSelection = true;
1106
1107   /**
1108    * @return true if view selection should always follow the selections
1109    *         broadcast by other selection sources
1110    */
1111   public boolean getFollowSelection()
1112   {
1113     return followSelection;
1114   }
1115
1116   boolean showSeqFeaturesHeight;
1117
1118   public void sendSelection()
1119   {
1120     jalview.structure.StructureSelectionManager
1121             .getStructureSelectionManager(Desktop.instance).sendSelection(
1122                     new SequenceGroup(getSelectionGroup()),
1123                     new ColumnSelection(getColumnSelection()), this);
1124   }
1125
1126   public void setShowSequenceFeaturesHeight(boolean selected)
1127   {
1128     showSeqFeaturesHeight = selected;
1129   }
1130
1131   public boolean getShowSequenceFeaturesHeight()
1132   {
1133     return showSeqFeaturesHeight;
1134   }
1135
1136   /**
1137    * return the alignPanel containing the given viewport. Use this to get the
1138    * components currently handling the given viewport.
1139    * 
1140    * @param av
1141    * @return null or an alignPanel guaranteed to have non-null alignFrame
1142    *         reference
1143    */
1144   public AlignmentPanel getAlignPanel()
1145   {
1146     AlignmentPanel[] aps = PaintRefresher.getAssociatedPanels(this
1147             .getSequenceSetId());
1148     AlignmentPanel ap = null;
1149     for (int p = 0; aps != null && p < aps.length; p++)
1150     {
1151       if (aps[p].av == this)
1152       {
1153         return aps[p];
1154       }
1155     }
1156     return null;
1157   }
1158
1159   public boolean getSortByTree()
1160   {
1161     return sortByTree;
1162   }
1163
1164   public void setSortByTree(boolean sort)
1165   {
1166     sortByTree = sort;
1167   }
1168
1169   /**
1170    * synthesize a column selection if none exists so it covers the given
1171    * selection group. if wholewidth is false, no column selection is made if the
1172    * selection group covers the whole alignment width.
1173    * 
1174    * @param sg
1175    * @param wholewidth
1176    */
1177   public void expandColSelection(SequenceGroup sg, boolean wholewidth)
1178   {
1179     int sgs, sge;
1180     if (sg != null
1181             && (sgs = sg.getStartRes()) >= 0
1182             && sg.getStartRes() <= (sge = sg.getEndRes())
1183             && (colSel == null || colSel.getSelected() == null || colSel
1184                     .getSelected().size() == 0))
1185     {
1186       if (!wholewidth && alignment.getWidth() == (1 + sge - sgs))
1187       {
1188         // do nothing
1189         return;
1190       }
1191       if (colSel == null)
1192       {
1193         colSel = new ColumnSelection();
1194       }
1195       for (int cspos = sg.getStartRes(); cspos <= sg.getEndRes(); cspos++)
1196       {
1197         colSel.addElement(cspos);
1198       }
1199     }
1200   }
1201
1202   public StructureSelectionManager getStructureSelectionManager()
1203   {
1204     return StructureSelectionManager
1205             .getStructureSelectionManager(Desktop.instance);
1206   }
1207
1208   /**
1209    * 
1210    * @param pdbEntries
1211    * @return a series of SequenceI arrays, one for each PDBEntry, listing which
1212    *         sequence in the alignment holds a reference to it
1213    */
1214   public SequenceI[][] collateForPDB(PDBEntry[] pdbEntries)
1215   {
1216     ArrayList<SequenceI[]> seqvectors = new ArrayList<SequenceI[]>();
1217     for (PDBEntry pdb : pdbEntries)
1218     {
1219       ArrayList<SequenceI> seqs = new ArrayList<SequenceI>();
1220       for (int i = 0; i < alignment.getHeight(); i++)
1221       {
1222         Vector pdbs = alignment.getSequenceAt(i).getDatasetSequence()
1223                 .getPDBId();
1224         if (pdbs == null)
1225         {
1226           continue;
1227         }
1228         SequenceI sq;
1229         for (int p = 0; p < pdbs.size(); p++)
1230         {
1231           PDBEntry p1 = (PDBEntry) pdbs.elementAt(p);
1232           if (p1.getId().equals(pdb.getId()))
1233           {
1234             if (!seqs.contains(sq = alignment.getSequenceAt(i)))
1235             {
1236               seqs.add(sq);
1237             }
1238
1239             continue;
1240           }
1241         }
1242       }
1243       seqvectors.add(seqs.toArray(new SequenceI[seqs.size()]));
1244     }
1245     return seqvectors.toArray(new SequenceI[seqvectors.size()][]);
1246   }
1247
1248   public boolean isNormaliseSequenceLogo()
1249   {
1250     return normaliseSequenceLogo;
1251   }
1252
1253   public void setNormaliseSequenceLogo(boolean state)
1254   {
1255     normaliseSequenceLogo = state;
1256   }
1257
1258   /**
1259    * 
1260    * @return true if alignment characters should be displayed
1261    */
1262   public boolean isValidCharWidth()
1263   {
1264     return validCharWidth;
1265   }
1266
1267   private Hashtable<String, AutoCalcSetting> calcIdParams = new Hashtable<String, AutoCalcSetting>();
1268
1269   private boolean showAutocalculatedAbove;
1270
1271   public AutoCalcSetting getCalcIdSettingsFor(String calcId)
1272   {
1273     return calcIdParams.get(calcId);
1274   }
1275
1276   public void setCalcIdSettingsFor(String calcId, AutoCalcSetting settings,
1277           boolean needsUpdate)
1278   {
1279     calcIdParams.put(calcId, settings);
1280     // TODO: create a restart list to trigger any calculations that need to be
1281     // restarted after load
1282     // calculator.getRegisteredWorkersOfClass(settings.getWorkerClass())
1283     if (needsUpdate)
1284     {
1285       Cache.log.debug("trigger update for " + calcId);
1286     }
1287   }
1288
1289   protected SequenceAnnotationOrder getSortAnnotationsBy()
1290   {
1291     return sortAnnotationsBy;
1292   }
1293
1294   protected void setSortAnnotationsBy(SequenceAnnotationOrder sortAnnotationsBy)
1295   {
1296     this.sortAnnotationsBy = sortAnnotationsBy;
1297   }
1298
1299   protected boolean isShowAutocalculatedAbove()
1300   {
1301     return showAutocalculatedAbove;
1302   }
1303
1304   protected void setShowAutocalculatedAbove(boolean showAutocalculatedAbove)
1305   {
1306     this.showAutocalculatedAbove = showAutocalculatedAbove;
1307   }
1308
1309   /**
1310    * Method called when another alignment's edit (or possibly other) command is
1311    * broadcast to here.
1312    *
1313    * To allow for sequence mappings (e.g. protein to cDNA), we have to first
1314    * 'unwind' the command on the source sequences (in simulation, not in fact),
1315    * and then for each edit in turn:
1316    * <ul>
1317    * <li>compute the equivalent edit on the mapped sequences</li>
1318    * <li>apply the mapped edit</li>
1319    * <li>'apply' the source edit to the working copy of the source sequences</li>
1320    * </ul>
1321    * 
1322    * @param command
1323    * @param undo
1324    * @param ssm
1325    */
1326   @Override
1327   public void mirrorCommand(CommandI command, boolean undo,
1328           StructureSelectionManager ssm)
1329   {
1330     /*
1331      * Only EditCommand is currently handled by listeners.
1332      */
1333     if (!(command instanceof EditCommand))
1334     {
1335       return;
1336     }
1337     EditCommand edit = (EditCommand) command;
1338
1339     List<SequenceI> seqs = getAlignment().getSequences();
1340     EditCommand mappedCommand = ssm.mapEditCommand(edit, undo, seqs,
1341             getGapCharacter());
1342     AlignmentI[] views = getAlignPanel().alignFrame.getViewAlignments();
1343     mappedCommand.performEdit(0, views);
1344     getAlignPanel().alignmentChanged();
1345   }
1346
1347   @Override
1348   public VamsasSource getVamsasSource()
1349   {
1350     return this;
1351   }
1352
1353   /**
1354    * Add one command to the command history list.
1355    * 
1356    * @param command
1357    */
1358   public void addToHistoryList(CommandI command)
1359   {
1360     if (this.historyList != null)
1361     {
1362       this.historyList.push(command);
1363       broadcastCommand(command, false);
1364     }
1365   }
1366
1367   protected void broadcastCommand(CommandI command, boolean undo)
1368   {
1369     getStructureSelectionManager().commandPerformed(command, undo, getVamsasSource());
1370   }
1371
1372   /**
1373    * Add one command to the command redo list.
1374    * 
1375    * @param command
1376    */
1377   public void addToRedoList(CommandI command)
1378   {
1379     if (this.redoList != null)
1380     {
1381       this.redoList.push(command);
1382     }
1383     broadcastCommand(command, true);
1384   }
1385
1386   /**
1387    * Clear the command redo list.
1388    */
1389   public void clearRedoList()
1390   {
1391     if (this.redoList != null)
1392     {
1393       this.redoList.clear();
1394     }
1395   }
1396
1397   public void setHistoryList(Deque<CommandI> list)
1398   {
1399     this.historyList = list;
1400   }
1401
1402   public Deque<CommandI> getHistoryList()
1403   {
1404     return this.historyList;
1405   }
1406
1407   public void setRedoList(Deque<CommandI> list)
1408   {
1409     this.redoList = list;
1410   }
1411
1412   public Deque<CommandI> getRedoList()
1413   {
1414     return this.redoList;
1415   }
1416
1417   /**
1418    * Add the sequences from the given alignment to this viewport. Optionally,
1419    * may give the user the option to open a new frame or panel linking cDNA and
1420    * protein.
1421    * 
1422    * @param al
1423    * @param title
1424    */
1425   public void addAlignment(AlignmentI al, String title)
1426   {
1427     // TODO: promote to AlignViewportI? applet CutAndPasteTransfer is different
1428
1429     // refactored from FileLoader / CutAndPasteTransfer / SequenceFetcher with
1430     // this comment:
1431     // TODO: create undo object for this JAL-1101
1432
1433     /*
1434      * If one alignment is protein and one nucleotide, with at least one
1435      * sequence name in common, offer to open a linked alignment.
1436      */
1437     if (getAlignment().isNucleotide() != al.isNucleotide())
1438     {
1439       final Set<String> sequenceNames = getAlignment().getSequenceNames();
1440       sequenceNames.retainAll(al.getSequenceNames());
1441       if (!sequenceNames.isEmpty()) // at least one sequence name in both
1442       {
1443         if (openLinkedAlignment(al, title))
1444         {
1445           return;
1446         }
1447       }
1448     }
1449
1450     for (int i = 0; i < al.getHeight(); i++)
1451     {
1452       getAlignment().addSequence(al.getSequenceAt(i));
1453     }
1454     // TODO this call was done by SequenceFetcher but not FileLoader or
1455     // CutAndPasteTransfer. Is it needed?
1456     setEndSeq(getAlignment().getHeight());
1457     firePropertyChange("alignment", null, getAlignment().getSequences());
1458   }
1459
1460   /**
1461    * Show a dialog with the option to open and link (cDNA <-> protein) as a new
1462    * alignment. Returns true if the new alignment was opened, false if not -
1463    * either because the user declined the offer, or because no mapping could be
1464    * made.
1465    * 
1466    * @param title
1467    */
1468   protected boolean openLinkedAlignment(AlignmentI al, String title)
1469   {
1470     String[] options = new String[]
1471     { MessageManager.getString("action.no"),
1472         MessageManager.getString("label.split_window"),
1473         MessageManager.getString("label.new_window"), };
1474     final String question = JvSwingUtils.wrapTooltip(true,
1475             MessageManager.getString("label.open_linked_alignment?"));
1476     int response = JOptionPane.showOptionDialog(Desktop.desktop, question,
1477             MessageManager.getString("label.open_linked_alignment"),
1478             JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null,
1479             options, options[0]);
1480     // int reply = JOptionPane.showInternalConfirmDialog(Desktop.desktop,
1481     // question,
1482     // MessageManager.getString("label.open_linked_alignment"),
1483     // JOptionPane.YES_NO_OPTION,
1484     // JOptionPane.QUESTION_MESSAGE);
1485
1486     if (response != 1 && response != 2)
1487     {
1488       return false;
1489     }
1490     final boolean openSplitPane = (response == 1);
1491     final boolean openInNewWindow = (response == 2);
1492
1493     /*
1494      * Create the AlignFrame first (which creates the new alignment's datasets),
1495      * before attempting sequence mapping.
1496      */
1497     AlignFrame alignFrame = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH,
1498             AlignFrame.DEFAULT_HEIGHT);
1499
1500     final AlignmentI protein = al.isNucleotide() ? getAlignment() : al;
1501     final AlignmentI cdna = al.isNucleotide() ? al : getAlignment();
1502
1503     alignFrame.statusBar.setText(MessageManager.formatMessage(
1504             "label.successfully_loaded_file", new Object[]
1505             { title }));
1506
1507     // TODO if we want this (e.g. to enable reload of the alignment from file),
1508     // we will need to add parameters to the stack.
1509     // if (!protocol.equals(AppletFormatAdapter.PASTE))
1510     // {
1511     // alignFrame.setFileName(file, format);
1512     // }
1513     if (openInNewWindow)
1514     {
1515       /*
1516        * open in new window
1517        */
1518       Desktop.addInternalFrame(alignFrame, title, AlignFrame.DEFAULT_WIDTH,
1519               AlignFrame.DEFAULT_HEIGHT);
1520     }
1521
1522     /*
1523      * Try to find mappings for at least one sequence.
1524      */
1525     MappingResult mapped = AlignmentUtils.mapProteinToCdna(protein, cdna);
1526     if (mapped == MappingResult.Mapped)
1527     {
1528
1529       /*
1530        * Register the mappings (held on the protein alignment) with the
1531        * StructureSelectionManager (for mouseover linking).
1532        */
1533       final StructureSelectionManager ssm = StructureSelectionManager
1534               .getStructureSelectionManager(Desktop.instance);
1535       ssm.addMappings(protein.getCodonFrames());
1536
1537       /*
1538        * Set the cDNA to listen for edits on the protein.
1539        */
1540       ssm.addCommandListener(al.isNucleotide() ? alignFrame.getViewport()
1541               : this);
1542     }
1543     else
1544     {
1545
1546       /*
1547        * No mapping possible - warn the user, but leave window open.
1548        */
1549       final String msg = JvSwingUtils.wrapTooltip(true,
1550               MessageManager.getString("label.mapping_failed"));
1551       JOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1552               MessageManager.getString("label.no_mappings"),
1553               JOptionPane.WARNING_MESSAGE);
1554     }
1555
1556     try
1557     {
1558       alignFrame.setMaximum(jalview.bin.Cache.getDefault("SHOW_FULLSCREEN",
1559               false));
1560     } catch (java.beans.PropertyVetoException ex)
1561     {
1562     }
1563
1564     if (openSplitPane)
1565     {
1566       /*
1567        * Open in split pane. Original sequence above, new one below.
1568        */
1569       JInternalFrame splitFrame = new JInternalFrame();
1570       splitFrame.setSize(AlignFrame.DEFAULT_WIDTH,
1571               AlignFrame.DEFAULT_HEIGHT);
1572       // TODO not quite right to 'move' AlignPanel from 'this' to the split
1573       // pane
1574       // TODO probably want linked editing set up here
1575       JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
1576               getAlignPanel(), alignFrame.alignPanel);
1577       splitPane.setDividerLocation(0.5d);
1578       splitFrame.setSize(AlignFrame.DEFAULT_WIDTH,
1579               AlignFrame.DEFAULT_HEIGHT);
1580       splitFrame.add(splitPane);
1581       Desktop.addInternalFrame(splitFrame, title, AlignFrame.DEFAULT_WIDTH,
1582               AlignFrame.DEFAULT_HEIGHT);
1583     }
1584
1585     return true;
1586   }
1587 }