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