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