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