9af5ac2d3f89c01d24bc18ed236b4d7d16fdfcfa
[jalview.git] / src / jalview / appletgui / AlignViewport.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
3  * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.appletgui;
19
20 import java.util.*;
21
22 import java.awt.*;
23
24 import jalview.analysis.*;
25 import jalview.api.AlignViewportI;
26 import jalview.bin.*;
27 import jalview.datamodel.*;
28 import jalview.schemes.*;
29 import jalview.structure.SelectionSource;
30 import jalview.structure.VamsasSource;
31
32 public class AlignViewport implements AlignViewportI, SelectionSource, VamsasSource
33 {
34   int startRes;
35
36   int endRes;
37
38   int startSeq;
39
40   int endSeq;
41
42   boolean cursorMode = false;
43
44   boolean showJVSuffix = true;
45
46   boolean showText = true;
47
48   boolean showColourText = false;
49
50   boolean showBoxes = true;
51
52   boolean wrapAlignment = false;
53
54   boolean renderGaps = true;
55
56   boolean showSequenceFeatures = false;
57
58   boolean showAnnotation = true;
59
60   boolean showConservation = true;
61
62   boolean showQuality = true;
63
64   boolean showConsensus = true;
65
66   boolean upperCasebold = false;
67
68   boolean colourAppliesToAllGroups = true;
69
70   ColourSchemeI globalColourScheme = null;
71
72   boolean conservationColourSelected = false;
73
74   boolean abovePIDThreshold = false;
75
76   SequenceGroup selectionGroup;
77
78   int charHeight;
79
80   int charWidth;
81
82   int wrappedWidth;
83
84   Font font = new Font("SansSerif", Font.PLAIN, 10);
85
86   boolean validCharWidth = true;
87
88   AlignmentI alignment;
89
90   ColumnSelection colSel = new ColumnSelection();
91
92   int threshold;
93
94   int increment;
95
96   NJTree currentTree = null;
97
98   boolean scaleAboveWrapped = true;
99
100   boolean scaleLeftWrapped = true;
101
102   boolean scaleRightWrapped = true;
103
104   // The following vector holds the features which are
105   // currently visible, in the correct order or rendering
106   public Hashtable featuresDisplayed;
107
108   boolean hasHiddenColumns = false;
109
110   boolean hasHiddenRows = false;
111
112   boolean showHiddenMarkers = true;
113
114   public Hashtable[] hconsensus;
115
116   AlignmentAnnotation consensus;
117
118   AlignmentAnnotation conservation;
119
120   AlignmentAnnotation quality;
121
122   AlignmentAnnotation[] groupConsensus;
123
124   AlignmentAnnotation[] groupConservation;
125
126   boolean autocalculateConsensus = true;
127
128   public int ConsPercGaps = 25; // JBPNote : This should be a scalable property!
129
130   private java.beans.PropertyChangeSupport changeSupport = new java.beans.PropertyChangeSupport(
131           this);
132
133   boolean ignoreGapsInConsensusCalculation = false;
134
135   public jalview.bin.JalviewLite applet;
136
137   Hashtable sequenceColours;
138
139   boolean MAC = false;
140
141   Stack historyList = new Stack();
142
143   Stack redoList = new Stack();
144
145   String sequenceSetID;
146
147   Hashtable hiddenRepSequences;
148   
149   public void finalize() {
150     applet=null;
151     quality=null;
152     alignment=null;
153     colSel=null;
154   }
155
156   public AlignViewport(AlignmentI al, JalviewLite applet)
157   {
158     this.applet = applet;
159     setAlignment(al);
160     this.startRes = 0;
161     this.endRes = al.getWidth() - 1;
162     this.startSeq = 0;
163     this.endSeq = al.getHeight() - 1;
164     if (applet != null)
165     {
166       // get the width and height scaling factors if they were specified
167       String param = applet.getParameter("widthScale");
168       if (param != null)
169       {
170         try
171         {
172           widthScale = new Float(param).floatValue();
173         } catch (Exception e)
174         {
175         }
176         if (widthScale <= 1.0)
177         {
178           System.err
179                   .println("Invalid alignment character width scaling factor ("
180                           + widthScale + "). Ignoring.");
181           widthScale = 1;
182         }
183         if (applet.debug)
184         {
185           System.err
186                   .println("Alignment character width scaling factor is now "
187                           + widthScale);
188         }
189       }
190       param = applet.getParameter("heightScale");
191       if (param != null)
192       {
193         try
194         {
195           heightScale = new Float(param).floatValue();
196         } catch (Exception e)
197         {
198         }
199         if (heightScale <= 1.0)
200         {
201           System.err
202                   .println("Invalid alignment character height scaling factor ("
203                           + heightScale + "). Ignoring.");
204           heightScale = 1;
205         }
206         if (applet.debug)
207         {
208           System.err
209                   .println("Alignment character height scaling factor is now "
210                           + heightScale);
211         }
212       }
213     }
214     setFont(font);
215
216     MAC = new jalview.util.Platform().isAMac();
217
218     if (applet != null)
219     {
220       showJVSuffix = applet.getDefaultParameter("showFullId", showJVSuffix);
221
222       showAnnotation = applet.getDefaultParameter("showAnnotation", showAnnotation);
223       
224       showConservation = applet.getDefaultParameter("showConservation", showConservation);
225       
226       showQuality = applet.getDefaultParameter("showQuality", showQuality);
227
228       showConsensus = applet.getDefaultParameter("showConsensus", showConsensus);
229
230       showUnconserved = applet.getDefaultParameter("showUnconserved", showUnconserved);
231
232       String param = applet.getParameter("upperCase");
233       if (param != null)
234       {
235         if (param.equalsIgnoreCase("bold"))
236         {
237           upperCasebold = true;
238         }
239       }
240       sortByTree = applet.getDefaultParameter("sortByTree", sortByTree);
241
242       followHighlight = applet.getDefaultParameter("automaticScrolling",followHighlight);
243       followSelection = followHighlight;
244
245       showSequenceLogo = applet.getDefaultParameter("showSequenceLogo", showSequenceLogo);
246
247       normaliseSequenceLogo = applet.getDefaultParameter("normaliseSequenceLogo", normaliseSequenceLogo);
248
249       showGroupConsensus = applet.getDefaultParameter("showGroupConsensus", showGroupConsensus);
250       
251       showGroupConservation = applet.getDefaultParameter("showGroupConservation", showGroupConservation);
252         
253       showConsensusHistogram = applet.getDefaultParameter("showConsensusHistogram", showConsensusHistogram);
254       
255     }
256
257     if (applet != null)
258     {
259       String colour = applet.getParameter("defaultColour");
260
261       if (colour == null)
262       {
263         colour = applet.getParameter("userDefinedColour");
264         if (colour != null)
265         {
266           colour = "User Defined";
267         }
268       }
269
270       if (colour != null)
271       {
272         globalColourScheme = ColourSchemeProperty.getColour(alignment,
273                 colour);
274         if (globalColourScheme != null)
275         {
276           globalColourScheme.setConsensus(hconsensus);
277         }
278       }
279
280       if (applet.getParameter("userDefinedColour") != null)
281       {
282         ((UserColourScheme) globalColourScheme).parseAppletParameter(applet
283                 .getParameter("userDefinedColour"));
284       }
285     }
286     if (hconsensus == null)
287     {
288       if (!alignment.isNucleotide())
289       {
290         conservation = new AlignmentAnnotation("Conservation",
291                 "Conservation of total alignment less than " + ConsPercGaps
292                         + "% gaps", new Annotation[1], 0f, 11f,
293                 AlignmentAnnotation.BAR_GRAPH);
294         conservation.hasText = true;
295         conservation.autoCalculated = true;
296
297         if (showConservation)
298         {
299           alignment.addAnnotation(conservation);
300         }
301
302         if (showQuality)
303         {
304           quality = new AlignmentAnnotation("Quality",
305                   "Alignment Quality based on Blosum62 scores",
306                   new Annotation[1], 0f, 11f, AlignmentAnnotation.BAR_GRAPH);
307           quality.hasText = true;
308           quality.autoCalculated = true;
309
310           alignment.addAnnotation(quality);
311         }
312       }
313
314       consensus = new AlignmentAnnotation("Consensus", "PID",
315               new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
316       consensus.hasText = true;
317       consensus.autoCalculated = true;
318
319       if (showConsensus)
320       {
321         alignment.addAnnotation(consensus);
322       }
323     }
324
325   }
326
327   public void showSequenceFeatures(boolean b)
328   {
329     showSequenceFeatures = b;
330   }
331
332   public boolean getShowSequenceFeatures()
333   {
334     return showSequenceFeatures;
335   }
336
337   class ConservationThread extends Thread
338   {
339     AlignmentPanel ap;
340
341     public ConservationThread(AlignmentPanel ap)
342     {
343       this.ap = ap;
344     }
345
346     public void run()
347     {
348       try
349       {
350         updatingConservation = true;
351
352         while (UPDATING_CONSERVATION)
353         {
354           try
355           {
356             if (ap != null)
357             {
358               ap.paintAlignment(false);
359             }
360             Thread.sleep(200);
361           } catch (Exception ex)
362           {
363             ex.printStackTrace();
364           }
365         }
366
367         UPDATING_CONSERVATION = true;
368
369         int alWidth = (alignment==null) ? -1 : alignment.getWidth();
370         if (alWidth < 0)
371         {
372           updatingConservation = false;
373           UPDATING_CONSERVATION = false;
374           return;
375         }
376
377         Conservation cons = new jalview.analysis.Conservation("All",
378                 jalview.schemes.ResidueProperties.propHash, 3,
379                 alignment.getSequences(), 0, alWidth - 1);
380
381         cons.calculate();
382         cons.verdict(false, ConsPercGaps);
383
384         if (quality != null)
385         {
386           cons.findQuality();
387         }
388
389         char[] sequence = cons.getConsSequence().getSequence();
390         float minR;
391         float minG;
392         float minB;
393         float maxR;
394         float maxG;
395         float maxB;
396         minR = 0.3f;
397         minG = 0.0f;
398         minB = 0f;
399         maxR = 1.0f - minR;
400         maxG = 0.9f - minG;
401         maxB = 0f - minB; // scalable range for colouring both Conservation and
402         // Quality
403
404         float min = 0f;
405         float max = 11f;
406         float qmin = 0f;
407         float qmax = 0f;
408
409         char c;
410
411         conservation.annotations = new Annotation[alWidth];
412
413         if (quality != null)
414         {
415           quality.graphMax = cons.qualityRange[1].floatValue();
416           quality.annotations = new Annotation[alWidth];
417           qmin = cons.qualityRange[0].floatValue();
418           qmax = cons.qualityRange[1].floatValue();
419         }
420
421         for (int i = 0; i < alWidth; i++)
422         {
423           float value = 0;
424
425           c = sequence[i];
426
427           if (Character.isDigit(c))
428           {
429             value = (int) (c - '0');
430           }
431           else if (c == '*')
432           {
433             value = 11;
434           }
435           else if (c == '+')
436           {
437             value = 10;
438           }
439           // TODO - refactor to use a graduatedColorScheme to calculate the
440           // histogram colors.
441           float vprop = value - min;
442           vprop /= max;
443           conservation.annotations[i] = new Annotation(String.valueOf(c),
444                   String.valueOf(value), ' ', value, new Color(minR
445                           + (maxR * vprop), minG + (maxG * vprop), minB
446                           + (maxB * vprop)));
447
448           // Quality calc
449           if (quality != null)
450           {
451             value = ((Double) cons.quality.elementAt(i)).floatValue();
452             vprop = value - qmin;
453             vprop /= qmax;
454             quality.annotations[i] = new Annotation(" ",
455                     String.valueOf(value), ' ', value, new Color(minR
456                             + (maxR * vprop), minG + (maxG * vprop), minB
457                             + (maxB * vprop)));
458           }
459         }
460       } catch (OutOfMemoryError error)
461       {
462         System.out.println("Out of memory calculating conservation!!");
463         conservation = null;
464         quality = null;
465         System.gc();
466       }
467
468       UPDATING_CONSERVATION = false;
469       updatingConservation = false;
470
471       if (ap != null)
472       {
473         ap.paintAlignment(true);
474       }
475
476     }
477   }
478
479   ConservationThread conservationThread;
480
481   ConsensusThread consensusThread;
482   
483 //  StrucConsensusThread strucConsensusThread;
484
485
486   boolean consUpdateNeeded = false;
487
488   static boolean UPDATING_CONSENSUS = false;
489
490   static boolean UPDATING_STRUC_CONSENSUS = false;
491
492   static boolean UPDATING_CONSERVATION = false;
493
494   boolean updatingConsensus = false;
495
496   boolean updatingStrucConsensus = false;
497
498   boolean updatingConservation = false;
499
500   /**
501    * DOCUMENT ME!
502    */
503   public void updateConservation(final AlignmentPanel ap)
504   {
505     if (alignment.isNucleotide() || conservation == null)
506     {
507       return;
508     }
509
510     conservationThread = new ConservationThread(ap);
511     conservationThread.start();
512   }
513
514   /**
515    * DOCUMENT ME!
516    */
517   public void updateConsensus(final AlignmentPanel ap)
518   {
519     consensusThread = new ConsensusThread(ap);
520     consensusThread.start();
521   }
522
523   class ConsensusThread extends Thread
524   {
525     AlignmentPanel ap;
526
527     public ConsensusThread(AlignmentPanel ap)
528     {
529       this.ap = ap;
530     }
531
532     public void run()
533     {
534       updatingConsensus = true;
535       while (UPDATING_CONSENSUS)
536       {
537         try
538         {
539           if (ap != null)
540           {
541             ap.paintAlignment(false);
542           }
543
544           Thread.sleep(200);
545         } catch (Exception ex)
546         {
547           ex.printStackTrace();
548         }
549       }
550
551       UPDATING_CONSENSUS = true;
552
553       try
554       {
555         int aWidth = alignment==null ? -1 : alignment.getWidth();
556         if (aWidth < 0)
557         {
558           UPDATING_CONSENSUS = false;
559           updatingConsensus = false;
560           return;
561         }
562
563         consensus.annotations = null;
564         consensus.annotations = new Annotation[aWidth];
565
566         hconsensus = new Hashtable[aWidth];
567         AAFrequency.calculate(alignment.getSequencesArray(), 0,
568                 alignment.getWidth(), hconsensus, true); // always calculate the
569                                                          // full profile
570         updateAnnotation(true);
571         //AAFrequency.completeConsensus(consensus, hconsensus, 0, aWidth,
572         //        ignoreGapsInConsensusCalculation,
573         //        true);
574         
575         if (globalColourScheme != null)
576         {
577           globalColourScheme.setConsensus(hconsensus);
578         }
579
580       } catch (OutOfMemoryError error)
581       {
582         alignment.deleteAnnotation(consensus);
583
584         consensus = null;
585         hconsensus = null;
586         System.out.println("Out of memory calculating consensus!!");
587         System.gc();
588       }
589       UPDATING_CONSENSUS = false;
590       updatingConsensus = false;
591
592       if (ap != null)
593       {
594         ap.paintAlignment(true);
595       }
596     }
597
598     /**
599      * update the consensus annotation from the sequence profile data using
600      * current visualization settings.
601      */
602     public void updateAnnotation()
603     {
604       updateAnnotation(false);
605     }
606
607     protected void updateAnnotation(boolean immediate)
608     {
609       // TODO: make calls thread-safe, so if another thread calls this method,
610       // it will either return or wait until one calculation is finished.
611       if (immediate
612               || (!updatingConsensus && consensus != null && hconsensus != null))
613       {
614         AAFrequency.completeConsensus(consensus, hconsensus, 0,
615                 hconsensus.length, ignoreGapsInConsensusCalculation,
616                 showSequenceLogo);
617       }
618     }
619   }
620
621   /**
622    * get the consensus sequence as displayed under the PID consensus annotation
623    * row.
624    * 
625    * @return consensus sequence as a new sequence object
626    */
627   public SequenceI getConsensusSeq()
628   {
629     if (consensus == null)
630     {
631       updateConsensus(null);
632     }
633     if (consensus == null)
634     {
635       return null;
636     }
637     StringBuffer seqs = new StringBuffer();
638     for (int i = 0; i < consensus.annotations.length; i++)
639     {
640       if (consensus.annotations[i] != null)
641       {
642         if (consensus.annotations[i].description.charAt(0) == '[')
643         {
644           seqs.append(consensus.annotations[i].description.charAt(1));
645         }
646         else
647         {
648           seqs.append(consensus.annotations[i].displayCharacter);
649         }
650       }
651     }
652     SequenceI sq = new Sequence("Consensus", seqs.toString());
653     sq.setDescription("Percentage Identity Consensus "
654             + ((ignoreGapsInConsensusCalculation) ? " without gaps" : ""));
655     return sq;
656   }
657
658   public SequenceGroup getSelectionGroup()
659   {
660     return selectionGroup;
661   }
662
663   public void setSelectionGroup(SequenceGroup sg)
664   {
665     selectionGroup = sg;
666   }
667
668   public boolean getConservationSelected()
669   {
670     return conservationColourSelected;
671   }
672
673   public void setConservationSelected(boolean b)
674   {
675     conservationColourSelected = b;
676   }
677
678   public boolean getAbovePIDThreshold()
679   {
680     return abovePIDThreshold;
681   }
682
683   public void setAbovePIDThreshold(boolean b)
684   {
685     abovePIDThreshold = b;
686   }
687
688   public int getStartRes()
689   {
690     return startRes;
691   }
692
693   public int getEndRes()
694   {
695     return endRes;
696   }
697
698   public int getStartSeq()
699   {
700     return startSeq;
701   }
702
703   public void setGlobalColourScheme(ColourSchemeI cs)
704   {
705     globalColourScheme = cs;
706   }
707
708   public ColourSchemeI getGlobalColourScheme()
709   {
710     return globalColourScheme;
711   }
712
713   public void setStartRes(int res)
714   {
715     this.startRes = res;
716   }
717
718   public void setStartSeq(int seq)
719   {
720     this.startSeq = seq;
721   }
722
723   public void setEndRes(int res)
724   {
725     if (res > alignment.getWidth() - 1)
726     {
727       // log.System.out.println(" Corrected res from " + res + " to maximum " +
728       // (alignment.getWidth()-1));
729       res = alignment.getWidth() - 1;
730     }
731     if (res < 0)
732     {
733       res = 0;
734     }
735     this.endRes = res;
736   }
737
738   public void setEndSeq(int seq)
739   {
740     if (seq > alignment.getHeight())
741     {
742       seq = alignment.getHeight();
743     }
744     if (seq < 0)
745     {
746       seq = 0;
747     }
748     this.endSeq = seq;
749   }
750
751   public int getEndSeq()
752   {
753     return endSeq;
754   }
755
756   java.awt.Frame nullFrame;
757
758   protected FeatureSettings featureSettings = null;
759
760   private float heightScale = 1, widthScale = 1;
761
762   public void setFont(Font f)
763   {
764     font = f;
765     if (nullFrame == null)
766     {
767       nullFrame = new java.awt.Frame();
768       nullFrame.addNotify();
769     }
770
771     java.awt.FontMetrics fm = nullFrame.getGraphics().getFontMetrics(font);
772     setCharHeight((int) (heightScale * fm.getHeight()));
773     charWidth = (int) (widthScale * fm.charWidth('M'));
774
775     if (upperCasebold)
776     {
777       Font f2 = new Font(f.getName(), Font.BOLD, f.getSize());
778       fm = nullFrame.getGraphics().getFontMetrics(f2);
779       charWidth = (int) (widthScale * (fm.stringWidth("MMMMMMMMMMM") / 10));
780     }
781   }
782
783   public Font getFont()
784   {
785     return font;
786   }
787
788   public int getCharWidth()
789   {
790     return charWidth;
791   }
792
793   public void setCharHeight(int h)
794   {
795     this.charHeight = h;
796   }
797
798   public int getCharHeight()
799   {
800     return charHeight;
801   }
802
803   public void setWrappedWidth(int w)
804   {
805     this.wrappedWidth = w;
806   }
807
808   public int getwrappedWidth()
809   {
810     return wrappedWidth;
811   }
812
813   public AlignmentI getAlignment()
814   {
815     return alignment;
816   }
817
818   public void setAlignment(AlignmentI align)
819   {
820     this.alignment = align;
821   }
822
823   public void setWrapAlignment(boolean state)
824   {
825     wrapAlignment = state;
826   }
827
828   public void setShowText(boolean state)
829   {
830     showText = state;
831   }
832
833   public void setRenderGaps(boolean state)
834   {
835     renderGaps = state;
836   }
837
838   public boolean getColourText()
839   {
840     return showColourText;
841   }
842
843   public void setColourText(boolean state)
844   {
845     showColourText = state;
846   }
847
848   public void setShowBoxes(boolean state)
849   {
850     showBoxes = state;
851   }
852
853   public boolean getWrapAlignment()
854   {
855     return wrapAlignment;
856   }
857
858   public boolean getShowText()
859   {
860     return showText;
861   }
862
863   public boolean getShowBoxes()
864   {
865     return showBoxes;
866   }
867
868   public char getGapCharacter()
869   {
870     return getAlignment().getGapCharacter();
871   }
872
873   public void setGapCharacter(char gap)
874   {
875     if (getAlignment() != null)
876     {
877       getAlignment().setGapCharacter(gap);
878     }
879   }
880
881   public void setThreshold(int thresh)
882   {
883     threshold = thresh;
884   }
885
886   public int getThreshold()
887   {
888     return threshold;
889   }
890
891   public void setIncrement(int inc)
892   {
893     increment = inc;
894   }
895
896   public int getIncrement()
897   {
898     return increment;
899   }
900
901   public void setHiddenColumns(ColumnSelection colsel)
902   {
903     this.colSel = colsel;
904     if (colSel.getHiddenColumns() != null)
905     {
906       hasHiddenColumns = true;
907     }
908   }
909
910   public ColumnSelection getColumnSelection()
911   {
912     return colSel;
913   }
914
915   public void resetSeqLimits(int height)
916   {
917     setEndSeq(height / getCharHeight());
918   }
919
920   public void setCurrentTree(NJTree tree)
921   {
922     currentTree = tree;
923   }
924
925   public NJTree getCurrentTree()
926   {
927     return currentTree;
928   }
929
930   public void setColourAppliesToAllGroups(boolean b)
931   {
932     colourAppliesToAllGroups = b;
933   }
934
935   public boolean getColourAppliesToAllGroups()
936   {
937     return colourAppliesToAllGroups;
938   }
939
940   public boolean getShowJVSuffix()
941   {
942     return showJVSuffix;
943   }
944
945   public void setShowJVSuffix(boolean b)
946   {
947     showJVSuffix = b;
948   }
949
950   public boolean getShowAnnotation()
951   {
952     return showAnnotation;
953   }
954
955   public void setShowAnnotation(boolean b)
956   {
957     showAnnotation = b;
958   }
959
960   public boolean getScaleAboveWrapped()
961   {
962     return scaleAboveWrapped;
963   }
964
965   public boolean getScaleLeftWrapped()
966   {
967     return scaleLeftWrapped;
968   }
969
970   public boolean getScaleRightWrapped()
971   {
972     return scaleRightWrapped;
973   }
974
975   public void setScaleAboveWrapped(boolean b)
976   {
977     scaleAboveWrapped = b;
978   }
979
980   public void setScaleLeftWrapped(boolean b)
981   {
982     scaleLeftWrapped = b;
983   }
984
985   public void setScaleRightWrapped(boolean b)
986   {
987     scaleRightWrapped = b;
988   }
989
990   public void setIgnoreGapsConsensus(boolean b)
991   {
992     ignoreGapsInConsensusCalculation = b;
993     updateConsensus(null);
994     if (globalColourScheme != null)
995     {
996       globalColourScheme.setThreshold(globalColourScheme.getThreshold(),
997               ignoreGapsInConsensusCalculation);
998
999     }
1000   }
1001
1002   /**
1003    * Property change listener for changes in alignment
1004    * 
1005    * @param listener
1006    *          DOCUMENT ME!
1007    */
1008   public void addPropertyChangeListener(
1009           java.beans.PropertyChangeListener listener)
1010   {
1011     changeSupport.addPropertyChangeListener(listener);
1012   }
1013
1014   /**
1015    * DOCUMENT ME!
1016    * 
1017    * @param listener
1018    *          DOCUMENT ME!
1019    */
1020   public void removePropertyChangeListener(
1021           java.beans.PropertyChangeListener listener)
1022   {
1023     changeSupport.removePropertyChangeListener(listener);
1024   }
1025
1026   /**
1027    * Property change listener for changes in alignment
1028    * 
1029    * @param prop
1030    *          DOCUMENT ME!
1031    * @param oldvalue
1032    *          DOCUMENT ME!
1033    * @param newvalue
1034    *          DOCUMENT ME!
1035    */
1036   public void firePropertyChange(String prop, Object oldvalue,
1037           Object newvalue)
1038   {
1039     changeSupport.firePropertyChange(prop, oldvalue, newvalue);
1040   }
1041
1042   public boolean getIgnoreGapsConsensus()
1043   {
1044     return ignoreGapsInConsensusCalculation;
1045   }
1046
1047   public void hideSelectedColumns()
1048   {
1049     if (colSel.size() < 1)
1050     {
1051       return;
1052     }
1053
1054     colSel.hideSelectedColumns();
1055     setSelectionGroup(null);
1056
1057     hasHiddenColumns = true;
1058   }
1059
1060   public void invertColumnSelection()
1061   {
1062     for (int i = 0; i < alignment.getWidth(); i++)
1063     {
1064       if (colSel.contains(i))
1065       {
1066         colSel.removeElement(i);
1067       }
1068       else
1069       {
1070         if (!hasHiddenColumns || colSel.isVisible(i))
1071         {
1072           colSel.addElement(i);
1073         }
1074       }
1075     }
1076   }
1077
1078   public void hideColumns(int start, int end)
1079   {
1080     if (start == end)
1081     {
1082       colSel.hideColumns(start);
1083     }
1084     else
1085     {
1086       colSel.hideColumns(start, end);
1087     }
1088
1089     hasHiddenColumns = true;
1090   }
1091
1092   public void hideRepSequences(SequenceI repSequence, SequenceGroup sg)
1093   {
1094     int sSize = sg.getSize();
1095     if (sSize < 2)
1096     {
1097       return;
1098     }
1099
1100     if (hiddenRepSequences == null)
1101     {
1102       hiddenRepSequences = new Hashtable();
1103     }
1104
1105     hiddenRepSequences.put(repSequence, sg);
1106
1107     // Hide all sequences except the repSequence
1108     SequenceI[] seqs = new SequenceI[sSize - 1];
1109     int index = 0;
1110     for (int i = 0; i < sSize; i++)
1111     {
1112       if (sg.getSequenceAt(i) != repSequence)
1113       {
1114         if (index == sSize - 1)
1115         {
1116           return;
1117         }
1118
1119         seqs[index++] = sg.getSequenceAt(i);
1120       }
1121     }
1122
1123     hideSequence(seqs);
1124
1125   }
1126
1127   public void hideAllSelectedSeqs()
1128   {
1129     if (selectionGroup == null || selectionGroup.getSize() < 1)
1130     {
1131       return;
1132     }
1133
1134     SequenceI[] seqs = selectionGroup.getSequencesInOrder(alignment);
1135
1136     hideSequence(seqs);
1137
1138     setSelectionGroup(null);
1139   }
1140
1141   public void hideSequence(SequenceI[] seq)
1142   {
1143     if (seq != null)
1144     {
1145       for (int i = 0; i < seq.length; i++)
1146       {
1147         alignment.getHiddenSequences().hideSequence(seq[i]);
1148       }
1149
1150       hasHiddenRows = true;
1151       firePropertyChange("alignment", null, alignment.getSequences());
1152     }
1153   }
1154   public void showSequence(int index)
1155   {
1156     Vector tmp = alignment.getHiddenSequences().showSequence(index,
1157             hiddenRepSequences);
1158     if (tmp.size() > 0)
1159     {
1160       if (selectionGroup == null)
1161       {
1162         selectionGroup = new SequenceGroup();
1163         selectionGroup.setEndRes(alignment.getWidth() - 1);
1164       }
1165
1166       for (int t = 0; t < tmp.size(); t++)
1167       {
1168         selectionGroup.addSequence((SequenceI) tmp.elementAt(t), false);
1169       }
1170       firePropertyChange("alignment", null, alignment.getSequences());
1171       sendSelection();
1172     }
1173
1174     if (alignment.getHiddenSequences().getSize() < 1)
1175     {
1176       hasHiddenRows = false;
1177     }
1178   }
1179   public void showColumn(int col)
1180   {
1181     colSel.revealHiddenColumns(col);
1182     if (colSel.getHiddenColumns() == null)
1183     {
1184       hasHiddenColumns = false;
1185     }
1186   }
1187
1188   public void showAllHiddenColumns()
1189   {
1190     colSel.revealAllHiddenColumns();
1191     hasHiddenColumns = false;
1192   }
1193
1194   public void showAllHiddenSeqs()
1195   {
1196     if (alignment.getHiddenSequences().getSize() > 0)
1197     {
1198       if (selectionGroup == null)
1199       {
1200         selectionGroup = new SequenceGroup();
1201         selectionGroup.setEndRes(alignment.getWidth() - 1);
1202       }
1203       Vector tmp = alignment.getHiddenSequences().showAll(
1204               hiddenRepSequences);
1205       for (int t = 0; t < tmp.size(); t++)
1206       {
1207         selectionGroup.addSequence((SequenceI) tmp.elementAt(t), false);
1208       }
1209       firePropertyChange("alignment", null, alignment.getSequences());
1210       hasHiddenRows = false;
1211       hiddenRepSequences = null;
1212       sendSelection();
1213     }
1214   }
1215
1216   public int adjustForHiddenSeqs(int alignmentIndex)
1217   {
1218     return alignment.getHiddenSequences().adjustForHiddenSeqs(
1219             alignmentIndex);
1220   }
1221
1222   /**
1223    * This method returns the a new SequenceI [] with the selection sequence and
1224    * start and end points adjusted
1225    * 
1226    * @return String[]
1227    */
1228   public SequenceI[] getSelectionAsNewSequence()
1229   {
1230     SequenceI[] sequences;
1231
1232     if (selectionGroup == null)
1233     {
1234       sequences = alignment.getSequencesArray();
1235     }
1236     else
1237     {
1238       sequences = selectionGroup.getSelectionAsNewSequences(alignment);
1239     }
1240
1241     return sequences;
1242   }
1243
1244   /**
1245    * get the currently selected sequence objects or all the sequences in the
1246    * alignment.
1247    * 
1248    * @return array of references to sequence objects
1249    */
1250   public SequenceI[] getSequenceSelection()
1251   {
1252     SequenceI[] sequences = null;
1253     if (selectionGroup != null)
1254     {
1255       sequences = selectionGroup.getSequencesInOrder(alignment);
1256     }
1257     if (sequences == null)
1258     {
1259       sequences = alignment.getSequencesArray();
1260     }
1261     return sequences;
1262   }
1263
1264   /**
1265    * This method returns the visible alignment as text, as seen on the GUI, ie
1266    * if columns are hidden they will not be returned in the result. Use this for
1267    * calculating trees, PCA, redundancy etc on views which contain hidden
1268    * columns.
1269    * 
1270    * @return String[]
1271    */
1272   public jalview.datamodel.CigarArray getViewAsCigars(
1273           boolean selectedRegionOnly)
1274   {
1275     return new jalview.datamodel.CigarArray(alignment, (hasHiddenColumns ? colSel : null), (selectedRegionOnly ? selectionGroup : null));
1276   }
1277
1278   /**
1279    * return a compact representation of the current alignment selection to pass
1280    * to an analysis function
1281    * 
1282    * @param selectedOnly
1283    *          boolean true to just return the selected view
1284    * @return AlignmentView
1285    */
1286   jalview.datamodel.AlignmentView getAlignmentView(boolean selectedOnly)
1287   {    
1288     return getAlignmentView(selectedOnly, false);
1289   }
1290   
1291   /**
1292    * return a compact representation of the current alignment selection to pass
1293    * to an analysis function
1294    * 
1295    * @param selectedOnly
1296    *          boolean true to just return the selected view
1297    * @param markGroups
1298    *          boolean true to annotate the alignment view with groups on the alignment (and intersecting with selected region if selectedOnly is true) 
1299    * @return AlignmentView
1300    */
1301   public jalview.datamodel.AlignmentView getAlignmentView(boolean selectedOnly, boolean markGroups)
1302   {
1303     return new AlignmentView(alignment, colSel, selectionGroup, hasHiddenColumns, selectedOnly, markGroups);
1304   }
1305   /**
1306    * This method returns the visible alignment as text, as seen on the GUI, ie
1307    * if columns are hidden they will not be returned in the result. Use this for
1308    * calculating trees, PCA, redundancy etc on views which contain hidden
1309    * columns.
1310    * 
1311    * @return String[]
1312    */
1313   public String[] getViewAsString(boolean selectedRegionOnly)
1314   {
1315     String[] selection = null;
1316     SequenceI[] seqs = null;
1317     int i, iSize;
1318     int start = 0, end = 0;
1319     if (selectedRegionOnly && selectionGroup != null)
1320     {
1321       iSize = selectionGroup.getSize();
1322       seqs = selectionGroup.getSequencesInOrder(alignment);
1323       start = selectionGroup.getStartRes();
1324       end = selectionGroup.getEndRes() + 1;
1325     }
1326     else
1327     {
1328       iSize = alignment.getHeight();
1329       seqs = alignment.getSequencesArray();
1330       end = alignment.getWidth();
1331     }
1332
1333     selection = new String[iSize];
1334
1335     for (i = 0; i < iSize; i++)
1336     {
1337       if (hasHiddenColumns)
1338       {
1339         StringBuffer visibleSeq = new StringBuffer();
1340         Vector regions = colSel.getHiddenColumns();
1341
1342         int blockStart = start, blockEnd = end;
1343         int[] region;
1344         int hideStart, hideEnd;
1345
1346         for (int j = 0; j < regions.size(); j++)
1347         {
1348           region = (int[]) regions.elementAt(j);
1349           hideStart = region[0];
1350           hideEnd = region[1];
1351
1352           if (hideStart < start)
1353           {
1354             continue;
1355           }
1356
1357           blockStart = Math.min(blockStart, hideEnd + 1);
1358           blockEnd = Math.min(blockEnd, hideStart);
1359
1360           if (blockStart > blockEnd)
1361           {
1362             break;
1363           }
1364
1365           visibleSeq.append(seqs[i].getSequence(blockStart, blockEnd));
1366
1367           blockStart = hideEnd + 1;
1368           blockEnd = end;
1369         }
1370
1371         if (end > blockStart)
1372         {
1373           visibleSeq.append(seqs[i].getSequence(blockStart, end));
1374         }
1375
1376         selection[i] = visibleSeq.toString();
1377       }
1378       else
1379       {
1380         selection[i] = seqs[i].getSequenceAsString(start, end);
1381       }
1382     }
1383
1384     return selection;
1385   }
1386
1387   public boolean getShowHiddenMarkers()
1388   {
1389     return showHiddenMarkers;
1390   }
1391
1392   public void setShowHiddenMarkers(boolean show)
1393   {
1394     showHiddenMarkers = show;
1395   }
1396
1397   public Color getSequenceColour(SequenceI seq)
1398   {
1399     if (sequenceColours == null || !sequenceColours.containsKey(seq))
1400     {
1401       return Color.white;
1402     }
1403     else
1404     {
1405       return (Color) sequenceColours.get(seq);
1406     }
1407   }
1408
1409   public void setSequenceColour(SequenceI seq, Color col)
1410   {
1411     if (sequenceColours == null)
1412     {
1413       sequenceColours = new Hashtable();
1414     }
1415
1416     if (col == null)
1417     {
1418       sequenceColours.remove(seq);
1419     }
1420     else
1421     {
1422       sequenceColours.put(seq, col);
1423     }
1424   }
1425
1426   public String getSequenceSetId()
1427   {
1428     if (sequenceSetID == null)
1429     {
1430       sequenceSetID = alignment.hashCode() + "";
1431     }
1432
1433     return sequenceSetID;
1434   }
1435   /**
1436    * unique viewId for synchronizing state (e.g. with stored Jalview Project)
1437    * 
1438    */
1439   private String viewId = null;
1440
1441   public String getViewId()
1442   {
1443     if (viewId == null)
1444     {
1445       viewId = this.getSequenceSetId() + "." + this.hashCode() + "";
1446     }
1447     return viewId;
1448   }
1449
1450   public void alignmentChanged(AlignmentPanel ap)
1451   {
1452     alignment.padGaps();
1453
1454     if (hconsensus != null && autocalculateConsensus)
1455     {
1456       updateConsensus(ap);
1457       updateConservation(ap);
1458     }
1459
1460     // Reset endRes of groups if beyond alignment width
1461     int alWidth = alignment.getWidth();
1462     Vector groups = alignment.getGroups();
1463     if (groups != null)
1464     {
1465       for (int i = 0; i < groups.size(); i++)
1466       {
1467         SequenceGroup sg = (SequenceGroup) groups.elementAt(i);
1468         if (sg.getEndRes() > alWidth)
1469         {
1470           sg.setEndRes(alWidth - 1);
1471         }
1472       }
1473     }
1474
1475     if (selectionGroup != null && selectionGroup.getEndRes() > alWidth)
1476     {
1477       selectionGroup.setEndRes(alWidth - 1);
1478     }
1479
1480     resetAllColourSchemes();
1481
1482     // AW alignment.adjustSequenceAnnotations();
1483   }
1484
1485   void resetAllColourSchemes()
1486   {
1487     ColourSchemeI cs = globalColourScheme;
1488     if (cs != null)
1489     {
1490       if (cs instanceof ClustalxColourScheme)
1491       {
1492         ((ClustalxColourScheme) cs).resetClustalX(alignment.getSequences(),
1493                 alignment.getWidth());
1494       }
1495
1496       cs.setConsensus(hconsensus);
1497       if (cs.conservationApplied())
1498       {
1499         Alignment al = (Alignment) alignment;
1500         Conservation c = new Conservation("All",
1501                 ResidueProperties.propHash, 3, al.getSequences(), 0,
1502                 al.getWidth() - 1);
1503         c.calculate();
1504         c.verdict(false, ConsPercGaps);
1505
1506         cs.setConservation(c);
1507       }
1508     }
1509
1510     int s, sSize = alignment.getGroups().size();
1511     for (s = 0; s < sSize; s++)
1512     {
1513       SequenceGroup sg = (SequenceGroup) alignment.getGroups().elementAt(s);
1514       if (sg.cs != null && sg.cs instanceof ClustalxColourScheme)
1515       {
1516         ((ClustalxColourScheme) sg.cs).resetClustalX(
1517                 sg.getSequences(hiddenRepSequences), sg.getWidth());
1518       }
1519       sg.recalcConservation();
1520     }
1521   }
1522
1523   boolean centreColumnLabels;
1524
1525   public boolean getCentreColumnLabels()
1526   {
1527     return centreColumnLabels;
1528   }
1529
1530   public void updateSequenceIdColours()
1531   {
1532     Vector groups = alignment.getGroups();
1533     for (int ig = 0, igSize = groups.size(); ig < igSize; ig++)
1534     {
1535       SequenceGroup sg = (SequenceGroup) groups.elementAt(ig);
1536       if (sg.idColour != null)
1537       {
1538         Vector sqs = sg.getSequences(hiddenRepSequences);
1539         for (int s = 0, sSize = sqs.size(); s < sSize; s++)
1540         {
1541           this.setSequenceColour((SequenceI) sqs.elementAt(s), sg.idColour);
1542         }
1543       }
1544     }
1545   }
1546
1547   public boolean followHighlight = true;
1548
1549   public boolean getFollowHighlight()
1550   {
1551     return followHighlight;
1552   }
1553
1554   public boolean followSelection = true;
1555
1556   /**
1557    * @return true if view selection should always follow the selections
1558    *         broadcast by other selection sources
1559    */
1560   public boolean getFollowSelection()
1561   {
1562     return followSelection;
1563   }
1564
1565   private long sgrouphash = -1, colselhash = -1;
1566
1567   /**
1568    * checks current SelectionGroup against record of last hash value, and
1569    * updates record.
1570    * 
1571    * @return true if SelectionGroup changed since last call
1572    */
1573   boolean isSelectionGroupChanged()
1574   {
1575     int hc = (selectionGroup == null) ? -1 : selectionGroup.hashCode();
1576     if (hc != sgrouphash)
1577     {
1578       sgrouphash = hc;
1579       return true;
1580     }
1581     return false;
1582   }
1583
1584   /**
1585    * checks current colsel against record of last hash value, and updates
1586    * record.
1587    * 
1588    * @return true if colsel changed since last call
1589    */
1590   boolean isColSelChanged()
1591   {
1592     int hc = (colSel == null) ? -1 : colSel.hashCode();
1593     if (hc != colselhash)
1594     {
1595       colselhash = hc;
1596       return true;
1597     }
1598     return false;
1599   }
1600   public void sendSelection()
1601   {
1602     jalview.structure.StructureSelectionManager
1603             .getStructureSelectionManager(applet).sendSelection(
1604                     new SequenceGroup(getSelectionGroup()),
1605                     new ColumnSelection(getColumnSelection()), this);
1606   }
1607
1608
1609
1610
1611   /**
1612    * show non-conserved residues only
1613    */
1614   public boolean showUnconserved = false;
1615
1616   /**
1617    * when set, alignment should be reordered according to a newly opened tree
1618    */
1619   public boolean sortByTree = false;
1620
1621   /**
1622    * @return the showUnconserved
1623    */
1624   public boolean getShowunconserved()
1625   {
1626     return showUnconserved;
1627   }
1628
1629   /**
1630    * @param showNonconserved
1631    *          the showUnconserved to set
1632    */
1633   public void setShowunconserved(boolean displayNonconserved)
1634   {
1635     this.showUnconserved = displayNonconserved;
1636   }
1637
1638   /**
1639    * should conservation rows be shown for groups
1640    */
1641   boolean showGroupConservation = false;
1642
1643   /**
1644    * should consensus rows be shown for groups
1645    */
1646   boolean showGroupConsensus = false;
1647
1648   /**
1649    * should consensus profile be rendered by default
1650    */
1651   public boolean showSequenceLogo = false;
1652   /**
1653    * should consensus profile be rendered normalised to row height
1654    */
1655   public  boolean normaliseSequenceLogo = false;
1656
1657   /**
1658    * should consensus histograms be rendered by default
1659    */
1660   public boolean showConsensusHistogram = true;
1661
1662   /**
1663    * @return the showConsensusProfile
1664    */
1665   public boolean isShowSequenceLogo()
1666   {
1667     return showSequenceLogo;
1668   }
1669
1670   /**
1671    * @param showSequenceLogo
1672    *          the new value
1673    */
1674   public void setShowSequenceLogo(boolean showSequenceLogo)
1675   {
1676     if (showSequenceLogo != this.showSequenceLogo)
1677     {
1678       // TODO: decouple settings setting from calculation when refactoring
1679       // annotation update method from alignframe to viewport
1680       this.showSequenceLogo = showSequenceLogo;
1681       if (consensusThread != null)
1682       {
1683         consensusThread.updateAnnotation();
1684       }
1685     }
1686     this.showSequenceLogo = showSequenceLogo;
1687   }
1688
1689   /**
1690    * @param showConsensusHistogram
1691    *          the showConsensusHistogram to set
1692    */
1693   public void setShowConsensusHistogram(boolean showConsensusHistogram)
1694   {
1695     this.showConsensusHistogram = showConsensusHistogram;
1696   }
1697
1698   /**
1699    * @return the showGroupConservation
1700    */
1701   public boolean isShowGroupConservation()
1702   {
1703     return showGroupConservation;
1704   }
1705
1706   /**
1707    * @param showGroupConservation
1708    *          the showGroupConservation to set
1709    */
1710   public void setShowGroupConservation(boolean showGroupConservation)
1711   {
1712     this.showGroupConservation = showGroupConservation;
1713   }
1714
1715   /**
1716    * @return the showGroupConsensus
1717    */
1718   public boolean isShowGroupConsensus()
1719   {
1720     return showGroupConsensus;
1721   }
1722
1723   /**
1724    * @param showGroupConsensus
1725    *          the showGroupConsensus to set
1726    */
1727   public void setShowGroupConsensus(boolean showGroupConsensus)
1728   {
1729     this.showGroupConsensus = showGroupConsensus;
1730   }
1731
1732   /**
1733    * 
1734    * @return flag to indicate if the consensus histogram should be rendered by
1735    *         default
1736    */
1737   public boolean isShowConsensusHistogram()
1738   {
1739     return this.showConsensusHistogram;
1740   }
1741
1742   /**
1743    * synthesize a column selection if none exists so it covers the given
1744    * selection group. if wholewidth is false, no column selection is made if the
1745    * selection group covers the whole alignment width.
1746    * 
1747    * @param sg
1748    * @param wholewidth
1749    */
1750   public void expandColSelection(SequenceGroup sg, boolean wholewidth)
1751   {
1752     int sgs, sge;
1753     if (sg != null
1754             && (sgs = sg.getStartRes()) >= 0
1755             && sg.getStartRes() <= (sge = sg.getEndRes())
1756             && (colSel == null || colSel.getSelected() == null || colSel
1757                     .getSelected().size() == 0))
1758     {
1759       if (!wholewidth && alignment.getWidth() == (1 + sge - sgs))
1760       {
1761         // do nothing
1762         return;
1763       }
1764       if (colSel == null)
1765       {
1766         colSel = new ColumnSelection();
1767       }
1768       for (int cspos = sg.getStartRes(); cspos <= sg.getEndRes(); cspos++)
1769       {
1770         colSel.addElement(cspos);
1771       }
1772     }
1773   }
1774
1775   @Override
1776   public boolean hasHiddenColumns()
1777   {
1778     return hasHiddenColumns;
1779   }
1780   
1781   public boolean isNormaliseSequenceLogo()
1782   {
1783     return normaliseSequenceLogo;
1784   }
1785
1786   public void setNormaliseSequenceLogo(boolean state)
1787   {
1788     normaliseSequenceLogo = state;
1789   }
1790
1791   public boolean isCalcInProgress()
1792   {
1793     // TODO generalise to iterate over all calculators associated with av
1794     return updatingConsensus || updatingConservation || updatingStrucConsensus;
1795   }
1796
1797   public boolean isCalculationInProgress(
1798           AlignmentAnnotation alignmentAnnotation)
1799   {
1800     if (!alignmentAnnotation.autoCalculated)
1801       return false;
1802     if ((updatingConsensus && consensus==alignmentAnnotation)
1803             || (updatingConservation && (conservation==alignmentAnnotation || quality==alignmentAnnotation))
1804             // || (updatingStrucConsensus && strucConsensus==alignmentAnnotation)
1805             )
1806     {
1807       return true;
1808     }
1809     return false;
1810   }
1811
1812   /**
1813    * 
1814    * @return true if alignment characters should be displayed 
1815    */
1816   public boolean isValidCharWidth()
1817   {
1818     return validCharWidth;
1819   }
1820
1821   @Override
1822   public Hashtable[] getSequenceConsensusHash()
1823   {
1824     return hconsensus;
1825   }
1826
1827   @Override
1828   public Hashtable[] getRnaStructureConsensusHash()
1829   {
1830     return null; // TODO: JAL-891 port to jvlite : refactor and introduce hStrucConsensus;
1831   }
1832 }