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