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