JAL-2094 first pass with jalview.api.ColorI interface
[jalview.git] / src / jalview / datamodel / SequenceGroup.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.datamodel;
22
23 import jalview.analysis.AAFrequency;
24 import jalview.analysis.Conservation;
25 import jalview.api.ColorI;
26 import jalview.schemes.Colour;
27 import jalview.schemes.ColourSchemeI;
28 import jalview.schemes.ResidueProperties;
29
30 import java.awt.Color;
31 import java.util.ArrayList;
32 import java.util.Hashtable;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Vector;
36
37 /**
38  * Collects a set contiguous ranges on a set of sequences
39  * 
40  * @author $author$
41  * @version $Revision$
42  */
43 public class SequenceGroup implements AnnotatedCollectionI
44 {
45   String groupName;
46
47   String description;
48
49   Conservation conserve;
50
51   Vector aaFrequency;
52
53   boolean displayBoxes = true;
54
55   boolean displayText = true;
56
57   boolean colourText = false;
58
59   /**
60    * after Olivier's non-conserved only character display
61    */
62   boolean showNonconserved = false;
63
64   /**
65    * group members
66    */
67   private List<SequenceI> sequences = new ArrayList<SequenceI>();
68
69   /**
70    * representative sequence for this group (if any)
71    */
72   private SequenceI seqrep = null;
73
74   int width = -1;
75
76   /**
77    * Colourscheme applied to group if any
78    */
79   public ColourSchemeI cs;
80
81   // start column (base 0)
82   int startRes = 0;
83
84   // end column (base 0)
85   int endRes = 0;
86
87   public ColorI outlineColour = Colour.black;
88
89   public ColorI idColour = null;
90
91   public int thresholdTextColour = 0;
92
93   public ColorI textColour = Colour.black;
94
95   public ColorI textColour2 = Colour.white;
96
97   /**
98    * consensus calculation property
99    */
100   private boolean ignoreGapsInConsensus = true;
101
102   /**
103    * consensus calculation property
104    */
105   private boolean showSequenceLogo = false;
106
107   /**
108    * flag indicating if logo should be rendered normalised
109    */
110   private boolean normaliseSequenceLogo;
111
112   /**
113    * @return the includeAllConsSymbols
114    */
115   public boolean isShowSequenceLogo()
116   {
117     return showSequenceLogo;
118   }
119
120   /**
121    * Creates a new SequenceGroup object.
122    */
123   public SequenceGroup()
124   {
125     groupName = "JGroup:" + this.hashCode();
126   }
127
128   /**
129    * Creates a new SequenceGroup object.
130    * 
131    * @param sequences
132    * @param groupName
133    * @param scheme
134    * @param displayBoxes
135    * @param displayText
136    * @param colourText
137    * @param start
138    *          first column of group
139    * @param end
140    *          last column of group
141    */
142   public SequenceGroup(List<SequenceI> sequences, String groupName,
143           ColourSchemeI scheme, boolean displayBoxes, boolean displayText,
144           boolean colourText, int start, int end)
145   {
146     this.sequences = sequences;
147     this.groupName = groupName;
148     this.displayBoxes = displayBoxes;
149     this.displayText = displayText;
150     this.colourText = colourText;
151     this.cs = scheme;
152     startRes = start;
153     endRes = end;
154     recalcConservation();
155   }
156
157   /**
158    * copy constructor
159    * 
160    * @param seqsel
161    */
162   public SequenceGroup(SequenceGroup seqsel)
163   {
164     if (seqsel != null)
165     {
166       sequences = new ArrayList<SequenceI>();
167       sequences.addAll(seqsel.sequences);
168       if (seqsel.groupName != null)
169       {
170         groupName = new String(seqsel.groupName);
171       }
172       displayBoxes = seqsel.displayBoxes;
173       displayText = seqsel.displayText;
174       colourText = seqsel.colourText;
175       startRes = seqsel.startRes;
176       endRes = seqsel.endRes;
177       cs = seqsel.cs;
178       if (seqsel.description != null)
179       {
180         description = new String(seqsel.description);
181       }
182       hidecols = seqsel.hidecols;
183       hidereps = seqsel.hidereps;
184       idColour = seqsel.idColour;
185       outlineColour = seqsel.outlineColour;
186       seqrep = seqsel.seqrep;
187       textColour = seqsel.textColour;
188       textColour2 = seqsel.textColour2;
189       thresholdTextColour = seqsel.thresholdTextColour;
190       width = seqsel.width;
191       ignoreGapsInConsensus = seqsel.ignoreGapsInConsensus;
192       if (seqsel.conserve != null)
193       {
194         recalcConservation(); // safer than
195         // aaFrequency = (Vector) seqsel.aaFrequency.clone(); // ??
196       }
197     }
198   }
199
200   public SequenceI[] getSelectionAsNewSequences(AlignmentI align)
201   {
202     int iSize = sequences.size();
203     SequenceI[] seqs = new SequenceI[iSize];
204     SequenceI[] inorder = getSequencesInOrder(align);
205
206     for (int i = 0, ipos = 0; i < inorder.length; i++)
207     {
208       SequenceI seq = inorder[i];
209
210       seqs[ipos] = seq.getSubSequence(startRes, endRes + 1);
211       if (seqs[ipos] != null)
212       {
213         seqs[ipos].setDescription(seq.getDescription());
214         seqs[ipos].setDBRefs(seq.getDBRefs());
215         seqs[ipos].setSequenceFeatures(seq.getSequenceFeatures());
216         if (seq.getDatasetSequence() != null)
217         {
218           seqs[ipos].setDatasetSequence(seq.getDatasetSequence());
219         }
220
221         if (seq.getAnnotation() != null)
222         {
223           AlignmentAnnotation[] alann = align.getAlignmentAnnotation();
224           // Only copy annotation that is either a score or referenced by the
225           // alignment's annotation vector
226           for (int a = 0; a < seq.getAnnotation().length; a++)
227           {
228             AlignmentAnnotation tocopy = seq.getAnnotation()[a];
229             if (alann != null)
230             {
231               boolean found = false;
232               for (int pos = 0; pos < alann.length; pos++)
233               {
234                 if (alann[pos] == tocopy)
235                 {
236                   found = true;
237                   break;
238                 }
239               }
240               if (!found)
241               {
242                 continue;
243               }
244             }
245             AlignmentAnnotation newannot = new AlignmentAnnotation(
246                     seq.getAnnotation()[a]);
247             newannot.restrict(startRes, endRes);
248             newannot.setSequenceRef(seqs[ipos]);
249             newannot.adjustForAlignment();
250             seqs[ipos].addAlignmentAnnotation(newannot);
251           }
252         }
253         ipos++;
254       }
255       else
256       {
257         iSize--;
258       }
259     }
260     if (iSize != inorder.length)
261     {
262       SequenceI[] nseqs = new SequenceI[iSize];
263       System.arraycopy(seqs, 0, nseqs, 0, iSize);
264       seqs = nseqs;
265     }
266     return seqs;
267
268   }
269
270   /**
271    * If sequence ends in gaps, the end residue can be correctly calculated here
272    * 
273    * @param seq
274    *          SequenceI
275    * @return int
276    */
277   public int findEndRes(SequenceI seq)
278   {
279     int eres = 0;
280     char ch;
281
282     for (int j = 0; j < endRes + 1 && j < seq.getLength(); j++)
283     {
284       ch = seq.getCharAt(j);
285       if (!jalview.util.Comparison.isGap((ch)))
286       {
287         eres++;
288       }
289     }
290
291     if (eres > 0)
292     {
293       eres += seq.getStart() - 1;
294     }
295
296     return eres;
297   }
298
299   @Override
300   public List<SequenceI> getSequences()
301   {
302     return sequences;
303   }
304
305   @Override
306   public List<SequenceI> getSequences(
307           Map<SequenceI, SequenceCollectionI> hiddenReps)
308   {
309     if (hiddenReps == null)
310     {
311       // TODO: need a synchronizedCollection here ?
312       return sequences;
313     }
314     else
315     {
316       List<SequenceI> allSequences = new ArrayList<SequenceI>();
317       for (SequenceI seq : sequences)
318       {
319         allSequences.add(seq);
320         if (hiddenReps.containsKey(seq))
321         {
322           SequenceCollectionI hsg = hiddenReps.get(seq);
323           for (SequenceI seq2 : hsg.getSequences())
324           {
325             if (seq2 != seq && !allSequences.contains(seq2))
326             {
327               allSequences.add(seq2);
328             }
329           }
330         }
331       }
332
333       return allSequences;
334     }
335   }
336
337   public SequenceI[] getSequencesAsArray(
338           Map<SequenceI, SequenceCollectionI> map)
339   {
340     List<SequenceI> tmp = getSequences(map);
341     if (tmp == null)
342     {
343       return null;
344     }
345     return tmp.toArray(new SequenceI[tmp.size()]);
346   }
347
348   /**
349    * DOCUMENT ME!
350    * 
351    * @param col
352    *          DOCUMENT ME!
353    * 
354    * @return DOCUMENT ME!
355    */
356   public boolean adjustForRemoveLeft(int col)
357   {
358     // return value is true if the group still exists
359     if (startRes >= col)
360     {
361       startRes = startRes - col;
362     }
363
364     if (endRes >= col)
365     {
366       endRes = endRes - col;
367
368       if (startRes > endRes)
369       {
370         startRes = 0;
371       }
372     }
373     else
374     {
375       // must delete this group!!
376       return false;
377     }
378
379     return true;
380   }
381
382   /**
383    * DOCUMENT ME!
384    * 
385    * @param col
386    *          DOCUMENT ME!
387    * 
388    * @return DOCUMENT ME!
389    */
390   public boolean adjustForRemoveRight(int col)
391   {
392     if (startRes > col)
393     {
394       // delete this group
395       return false;
396     }
397
398     if (endRes >= col)
399     {
400       endRes = col;
401     }
402
403     return true;
404   }
405
406   /**
407    * DOCUMENT ME!
408    * 
409    * @return DOCUMENT ME!
410    */
411   public String getName()
412   {
413     return groupName;
414   }
415
416   public String getDescription()
417   {
418     return description;
419   }
420
421   /**
422    * DOCUMENT ME!
423    * 
424    * @param name
425    *          DOCUMENT ME!
426    */
427   public void setName(String name)
428   {
429     groupName = name;
430     // TODO: URGENT: update dependent objects (annotation row)
431   }
432
433   public void setDescription(String desc)
434   {
435     description = desc;
436   }
437
438   /**
439    * DOCUMENT ME!
440    * 
441    * @return DOCUMENT ME!
442    */
443   public Conservation getConservation()
444   {
445     return conserve;
446   }
447
448   /**
449    * DOCUMENT ME!
450    * 
451    * @param c
452    *          DOCUMENT ME!
453    */
454   public void setConservation(Conservation c)
455   {
456     conserve = c;
457   }
458
459   /**
460    * Add s to this sequence group. If aligment sequence is already contained in
461    * group, it will not be added again, but recalculation may happen if the flag
462    * is set.
463    * 
464    * @param s
465    *          alignment sequence to be added
466    * @param recalc
467    *          true means Group's conservation should be recalculated
468    */
469   public void addSequence(SequenceI s, boolean recalc)
470   {
471     synchronized (sequences)
472     {
473       if (s != null && !sequences.contains(s))
474       {
475         sequences.add(s);
476       }
477
478       if (recalc)
479       {
480         recalcConservation();
481       }
482     }
483   }
484
485   /**
486    * Max Gaps Threshold (percent) for performing a conservation calculation
487    */
488   private int consPercGaps = 25;
489
490   /**
491    * @return Max Gaps Threshold for performing a conservation calculation
492    */
493   public int getConsPercGaps()
494   {
495     return consPercGaps;
496   }
497
498   /**
499    * set Max Gaps Threshold (percent) for performing a conservation calculation
500    * 
501    * @param consPercGaps
502    */
503   public void setConsPercGaps(int consPercGaps)
504   {
505     this.consPercGaps = consPercGaps;
506   }
507
508   /**
509    * calculate residue conservation for group - but only if necessary.
510    */
511   public void recalcConservation()
512   {
513     if (cs == null && consensus == null && conservation == null)
514     {
515       return;
516     }
517     try
518     {
519       Hashtable cnsns[] = AAFrequency.calculate(sequences, startRes,
520               endRes + 1, showSequenceLogo);
521       if (consensus != null)
522       {
523         _updateConsensusRow(cnsns, sequences.size());
524       }
525       if (cs != null)
526       {
527         cs.setConsensus(cnsns);
528       }
529
530       if ((conservation != null)
531               || (cs != null && cs.conservationApplied()))
532       {
533         Conservation c = new Conservation(groupName,
534                 ResidueProperties.propHash, 3, sequences, startRes,
535                 endRes + 1);
536         c.calculate();
537         c.verdict(false, consPercGaps);
538         if (conservation != null)
539         {
540           _updateConservationRow(c);
541         }
542         if (cs != null)
543         {
544           if (cs.conservationApplied())
545           {
546             cs.setConservation(c);
547           }
548         }
549       }
550       if (cs != null)
551       {
552         cs.alignmentChanged(context != null ? context : this, null);
553       }
554     } catch (java.lang.OutOfMemoryError err)
555     {
556       // TODO: catch OOM
557       System.out.println("Out of memory loading groups: " + err);
558     }
559
560   }
561
562   private void _updateConservationRow(Conservation c)
563   {
564     if (conservation == null)
565     {
566       getConservation();
567     }
568     // update Labels
569     conservation.label = "Conservation for " + getName();
570     conservation.description = "Conservation for group " + getName()
571             + " less than " + consPercGaps + "% gaps";
572     // preserve width if already set
573     int aWidth = (conservation.annotations != null) ? (endRes < conservation.annotations.length ? conservation.annotations.length
574             : endRes + 1)
575             : endRes + 1;
576     conservation.annotations = null;
577     conservation.annotations = new Annotation[aWidth]; // should be alignment
578                                                        // width
579     c.completeAnnotations(conservation, null, startRes, endRes + 1);
580   }
581
582   public Hashtable[] consensusData = null;
583
584   private void _updateConsensusRow(Hashtable[] cnsns, long nseq)
585   {
586     if (consensus == null)
587     {
588       getConsensus();
589     }
590     consensus.label = "Consensus for " + getName();
591     consensus.description = "Percent Identity";
592     consensusData = cnsns;
593     // preserve width if already set
594     int aWidth = (consensus.annotations != null) ? (endRes < consensus.annotations.length ? consensus.annotations.length
595             : endRes + 1)
596             : endRes + 1;
597     consensus.annotations = null;
598     consensus.annotations = new Annotation[aWidth]; // should be alignment width
599
600     AAFrequency.completeConsensus(consensus, cnsns, startRes, endRes + 1,
601             ignoreGapsInConsensus, showSequenceLogo, nseq); // TODO: setting
602                                                             // container
603     // for
604     // ignoreGapsInConsensusCalculation);
605   }
606
607   /**
608    * @param s
609    *          sequence to either add or remove from group
610    * @param recalc
611    *          flag passed to delete/addSequence to indicate if group properties
612    *          should be recalculated
613    */
614   public void addOrRemove(SequenceI s, boolean recalc)
615   {
616     synchronized (sequences)
617     {
618       if (sequences.contains(s))
619       {
620         deleteSequence(s, recalc);
621       }
622       else
623       {
624         addSequence(s, recalc);
625       }
626     }
627   }
628
629   /**
630    * remove
631    * 
632    * @param s
633    *          to be removed
634    * @param recalc
635    *          true means recalculate conservation
636    */
637   public void deleteSequence(SequenceI s, boolean recalc)
638   {
639     synchronized (sequences)
640     {
641       sequences.remove(s);
642
643       if (recalc)
644       {
645         recalcConservation();
646       }
647     }
648   }
649
650   /**
651    * 
652    * 
653    * @return the first column selected by this group. Runs from 0<=i<N_cols
654    */
655   @Override
656   public int getStartRes()
657   {
658     return startRes;
659   }
660
661   /**
662    * 
663    * @return the groups last selected column. Runs from 0<=i<N_cols
664    */
665   @Override
666   public int getEndRes()
667   {
668     return endRes;
669   }
670
671   /**
672    * Set the first column selected by this group. Runs from 0<=i<N_cols
673    * 
674    * @param i
675    */
676   public void setStartRes(int i)
677   {
678     startRes = i;
679   }
680
681   /**
682    * Set the groups last selected column. Runs from 0<=i<N_cols
683    * 
684    * @param i
685    */
686   public void setEndRes(int i)
687   {
688     endRes = i;
689   }
690
691   /**
692    * @return number of sequences in group
693    */
694   public int getSize()
695   {
696     return sequences.size();
697   }
698
699   /**
700    * @param i
701    * @return the ith sequence
702    */
703   public SequenceI getSequenceAt(int i)
704   {
705     return sequences.get(i);
706   }
707
708   /**
709    * @param state
710    *          colourText
711    */
712   public void setColourText(boolean state)
713   {
714     colourText = state;
715   }
716
717   /**
718    * DOCUMENT ME!
719    * 
720    * @return DOCUMENT ME!
721    */
722   public boolean getColourText()
723   {
724     return colourText;
725   }
726
727   /**
728    * DOCUMENT ME!
729    * 
730    * @param state
731    *          DOCUMENT ME!
732    */
733   public void setDisplayText(boolean state)
734   {
735     displayText = state;
736   }
737
738   /**
739    * DOCUMENT ME!
740    * 
741    * @return DOCUMENT ME!
742    */
743   public boolean getDisplayText()
744   {
745     return displayText;
746   }
747
748   /**
749    * DOCUMENT ME!
750    * 
751    * @param state
752    *          DOCUMENT ME!
753    */
754   public void setDisplayBoxes(boolean state)
755   {
756     displayBoxes = state;
757   }
758
759   /**
760    * DOCUMENT ME!
761    * 
762    * @return DOCUMENT ME!
763    */
764   public boolean getDisplayBoxes()
765   {
766     return displayBoxes;
767   }
768
769   /**
770    * computes the width of current set of sequences and returns it
771    * 
772    * @return DOCUMENT ME!
773    */
774   @Override
775   public int getWidth()
776   {
777     synchronized (sequences)
778     {
779       // MC This needs to get reset when characters are inserted and deleted
780       boolean first = true;
781       for (SequenceI seq : sequences)
782       {
783         if (first || seq.getLength() > width)
784         {
785           width = seq.getLength();
786           first = false;
787         }
788       }
789       return width;
790     }
791   }
792
793   /**
794    * DOCUMENT ME!
795    * 
796    * @param c
797    *          DOCUMENT ME!
798    */
799   public void setOutlineColour(Color c)
800   {
801     outlineColour = new Colour(c);
802   }
803
804   /**
805    * DOCUMENT ME!
806    * 
807    * @return DOCUMENT ME!
808    */
809   public ColorI getOutlineColour()
810   {
811     return outlineColour;
812   }
813
814   /**
815    * 
816    * returns the sequences in the group ordered by the ordering given by al.
817    * this used to return an array with null entries regardless, new behaviour is
818    * below. TODO: verify that this does not affect use in applet or application
819    * 
820    * @param al
821    *          Alignment
822    * @return SequenceI[] intersection of sequences in group with al, ordered by
823    *         al, or null if group does not intersect with al
824    */
825   public SequenceI[] getSequencesInOrder(AlignmentI al)
826   {
827     return getSequencesInOrder(al, true);
828   }
829
830   /**
831    * return an array representing the intersection of the group with al,
832    * optionally returning an array the size of al.getHeight() where nulls mark
833    * the non-intersected sequences
834    * 
835    * @param al
836    * @param trim
837    * @return null or array
838    */
839   public SequenceI[] getSequencesInOrder(AlignmentI al, boolean trim)
840   {
841     synchronized (sequences)
842     {
843       int sSize = sequences.size();
844       int alHeight = al.getHeight();
845
846       SequenceI[] seqs = new SequenceI[(trim) ? sSize : alHeight];
847
848       int index = 0;
849       for (int i = 0; i < alHeight && index < sSize; i++)
850       {
851         if (sequences.contains(al.getSequenceAt(i)))
852         {
853           seqs[(trim) ? index : i] = al.getSequenceAt(i);
854           index++;
855         }
856       }
857       if (index == 0)
858       {
859         return null;
860       }
861       if (!trim)
862       {
863         return seqs;
864       }
865       if (index < seqs.length)
866       {
867         SequenceI[] dummy = seqs;
868         seqs = new SequenceI[index];
869         while (--index >= 0)
870         {
871           seqs[index] = dummy[index];
872           dummy[index] = null;
873         }
874       }
875       return seqs;
876     }
877   }
878
879   /**
880    * @return the idColour
881    */
882   public ColorI getIdColour()
883   {
884     return idColour;
885   }
886
887   /**
888    * @param idColour
889    *          the idColour to set
890    */
891   public void setIdColour(Color idColour)
892   {
893     this.idColour = new Colour(idColour);
894   }
895
896   /**
897    * @return the representative sequence for this group
898    */
899   @Override
900   public SequenceI getSeqrep()
901   {
902     return seqrep;
903   }
904
905   /**
906    * set the representative sequence for this group. Note - this affects the
907    * interpretation of the Hidereps attribute.
908    * 
909    * @param seqrep
910    *          the seqrep to set (null means no sequence representative)
911    */
912   @Override
913   public void setSeqrep(SequenceI seqrep)
914   {
915     this.seqrep = seqrep;
916   }
917
918   /**
919    * 
920    * @return true if group has a sequence representative
921    */
922   @Override
923   public boolean hasSeqrep()
924   {
925     return seqrep != null;
926   }
927
928   /**
929    * visibility of rows or represented rows covered by group
930    */
931   private boolean hidereps = false;
932
933   /**
934    * set visibility of sequences covered by (if no sequence representative is
935    * defined) or represented by this group.
936    * 
937    * @param visibility
938    */
939   public void setHidereps(boolean visibility)
940   {
941     hidereps = visibility;
942   }
943
944   /**
945    * 
946    * @return true if sequences represented (or covered) by this group should be
947    *         hidden
948    */
949   public boolean isHidereps()
950   {
951     return hidereps;
952   }
953
954   /**
955    * visibility of columns intersecting this group
956    */
957   private boolean hidecols = false;
958
959   /**
960    * set intended visibility of columns covered by this group
961    * 
962    * @param visibility
963    */
964   public void setHideCols(boolean visibility)
965   {
966     hidecols = visibility;
967   }
968
969   /**
970    * 
971    * @return true if columns covered by group should be hidden
972    */
973   public boolean isHideCols()
974   {
975     return hidecols;
976   }
977
978   /**
979    * create a new sequence group from the intersection of this group with an
980    * alignment Hashtable of hidden representatives
981    * 
982    * @param alignment
983    *          (may not be null)
984    * @param map
985    *          (may be null)
986    * @return new group containing sequences common to this group and alignment
987    */
988   public SequenceGroup intersect(AlignmentI alignment,
989           Map<SequenceI, SequenceCollectionI> map)
990   {
991     SequenceGroup sgroup = new SequenceGroup(this);
992     SequenceI[] insect = getSequencesInOrder(alignment);
993     sgroup.sequences = new ArrayList<SequenceI>();
994     for (int s = 0; insect != null && s < insect.length; s++)
995     {
996       if (map == null || map.containsKey(insect[s]))
997       {
998         sgroup.sequences.add(insect[s]);
999       }
1000     }
1001     return sgroup;
1002   }
1003
1004   /**
1005    * @return the showUnconserved
1006    */
1007   public boolean getShowNonconserved()
1008   {
1009     return showNonconserved;
1010   }
1011
1012   /**
1013    * @param showNonconserved
1014    *          the showUnconserved to set
1015    */
1016   public void setShowNonconserved(boolean displayNonconserved)
1017   {
1018     this.showNonconserved = displayNonconserved;
1019   }
1020
1021   AlignmentAnnotation consensus = null, conservation = null;
1022
1023   /**
1024    * flag indicating if consensus histogram should be rendered
1025    */
1026   private boolean showConsensusHistogram;
1027
1028   /**
1029    * set this alignmentAnnotation object as the one used to render consensus
1030    * annotation
1031    * 
1032    * @param aan
1033    */
1034   public void setConsensus(AlignmentAnnotation aan)
1035   {
1036     if (consensus == null)
1037     {
1038       consensus = aan;
1039     }
1040   }
1041
1042   /**
1043    * 
1044    * @return automatically calculated consensus row
1045    */
1046   public AlignmentAnnotation getConsensus()
1047   {
1048     // TODO get or calculate and get consensus annotation row for this group
1049     int aWidth = this.getWidth();
1050     // pointer
1051     // possibility
1052     // here.
1053     if (aWidth < 0)
1054     {
1055       return null;
1056     }
1057     if (consensus == null)
1058     {
1059       consensus = new AlignmentAnnotation("", "", new Annotation[1], 0f,
1060               100f, AlignmentAnnotation.BAR_GRAPH);
1061       consensus.hasText = true;
1062       consensus.autoCalculated = true;
1063       consensus.groupRef = this;
1064       consensus.label = "Consensus for " + getName();
1065       consensus.description = "Percent Identity";
1066     }
1067     return consensus;
1068   }
1069
1070   /**
1071    * set this alignmentAnnotation object as the one used to render consensus
1072    * annotation
1073    * 
1074    * @param aan
1075    */
1076   public void setConservationRow(AlignmentAnnotation aan)
1077   {
1078     if (conservation == null)
1079     {
1080       conservation = aan;
1081     }
1082   }
1083
1084   /**
1085    * get the conservation annotation row for this group
1086    * 
1087    * @return autoCalculated annotation row
1088    */
1089   public AlignmentAnnotation getConservationRow()
1090   {
1091     if (conservation == null)
1092     {
1093       conservation = new AlignmentAnnotation("", "", new Annotation[1], 0f,
1094               11f, AlignmentAnnotation.BAR_GRAPH);
1095     }
1096
1097     conservation.hasText = true;
1098     conservation.autoCalculated = true;
1099     conservation.groupRef = this;
1100     conservation.label = "Conservation for " + getName();
1101     conservation.description = "Conservation for group " + getName()
1102             + " less than " + consPercGaps + "% gaps";
1103     return conservation;
1104   }
1105
1106   /**
1107    * 
1108    * @return true if annotation rows have been instantiated for this group
1109    */
1110   public boolean hasAnnotationRows()
1111   {
1112     return consensus != null || conservation != null;
1113   }
1114
1115   public SequenceI getConsensusSeq()
1116   {
1117     getConsensus();
1118     StringBuffer seqs = new StringBuffer();
1119     for (int i = 0; i < consensus.annotations.length; i++)
1120     {
1121       if (consensus.annotations[i] != null)
1122       {
1123         if (consensus.annotations[i].description.charAt(0) == '[')
1124         {
1125           seqs.append(consensus.annotations[i].description.charAt(1));
1126         }
1127         else
1128         {
1129           seqs.append(consensus.annotations[i].displayCharacter);
1130         }
1131       }
1132     }
1133
1134     SequenceI sq = new Sequence("Group" + getName() + " Consensus",
1135             seqs.toString());
1136     sq.setDescription("Percentage Identity Consensus "
1137             + ((ignoreGapsInConsensus) ? " without gaps" : ""));
1138     return sq;
1139   }
1140
1141   public void setIgnoreGapsConsensus(boolean state)
1142   {
1143     if (this.ignoreGapsInConsensus != state && consensus != null)
1144     {
1145       ignoreGapsInConsensus = state;
1146       recalcConservation();
1147     }
1148     ignoreGapsInConsensus = state;
1149   }
1150
1151   public boolean getIgnoreGapsConsensus()
1152   {
1153     return ignoreGapsInConsensus;
1154   }
1155
1156   /**
1157    * @param showSequenceLogo
1158    *          indicates if a sequence logo is shown for consensus annotation
1159    */
1160   public void setshowSequenceLogo(boolean showSequenceLogo)
1161   {
1162     // TODO: decouple calculation from settings update
1163     if (this.showSequenceLogo != showSequenceLogo && consensus != null)
1164     {
1165       this.showSequenceLogo = showSequenceLogo;
1166       recalcConservation();
1167     }
1168     this.showSequenceLogo = showSequenceLogo;
1169   }
1170
1171   /**
1172    * 
1173    * @param showConsHist
1174    *          flag indicating if the consensus histogram for this group should
1175    *          be rendered
1176    */
1177   public void setShowConsensusHistogram(boolean showConsHist)
1178   {
1179
1180     if (showConsensusHistogram != showConsHist && consensus != null)
1181     {
1182       this.showConsensusHistogram = showConsHist;
1183       recalcConservation();
1184     }
1185     this.showConsensusHistogram = showConsHist;
1186   }
1187
1188   /**
1189    * @return the showConsensusHistogram
1190    */
1191   public boolean isShowConsensusHistogram()
1192   {
1193     return showConsensusHistogram;
1194   }
1195
1196   /**
1197    * set flag indicating if logo should be normalised when rendered
1198    * 
1199    * @param norm
1200    */
1201   public void setNormaliseSequenceLogo(boolean norm)
1202   {
1203     normaliseSequenceLogo = norm;
1204   }
1205
1206   public boolean isNormaliseSequenceLogo()
1207   {
1208     return normaliseSequenceLogo;
1209   }
1210
1211   @Override
1212   /**
1213    * returns a new array with all annotation involving this group
1214    */
1215   public AlignmentAnnotation[] getAlignmentAnnotation()
1216   {
1217     // TODO add in other methods like 'getAlignmentAnnotation(String label),
1218     // etc'
1219     ArrayList<AlignmentAnnotation> annot = new ArrayList<AlignmentAnnotation>();
1220     synchronized (sequences)
1221     {
1222       for (SequenceI seq : sequences)
1223       {
1224         AlignmentAnnotation[] aa = seq.getAnnotation();
1225         if (aa != null)
1226         {
1227           for (AlignmentAnnotation al : aa)
1228           {
1229             if (al.groupRef == this)
1230             {
1231               annot.add(al);
1232             }
1233           }
1234         }
1235       }
1236       if (consensus != null)
1237       {
1238         annot.add(consensus);
1239       }
1240       if (conservation != null)
1241       {
1242         annot.add(conservation);
1243       }
1244     }
1245     return annot.toArray(new AlignmentAnnotation[0]);
1246   }
1247
1248   @Override
1249   public Iterable<AlignmentAnnotation> findAnnotation(String calcId)
1250   {
1251     ArrayList<AlignmentAnnotation> aa = new ArrayList<AlignmentAnnotation>();
1252     for (AlignmentAnnotation a : getAlignmentAnnotation())
1253     {
1254       if (a.getCalcId() == calcId)
1255       {
1256         aa.add(a);
1257       }
1258     }
1259     return aa;
1260   }
1261
1262   /**
1263    * Returns a list of annotations that match the specified sequenceRef, calcId
1264    * and label, ignoring null values.
1265    * 
1266    * @return list of AlignmentAnnotation objects
1267    */
1268   @Override
1269   public Iterable<AlignmentAnnotation> findAnnotations(SequenceI seq,
1270           String calcId, String label)
1271   {
1272     ArrayList<AlignmentAnnotation> aa = new ArrayList<AlignmentAnnotation>();
1273     for (AlignmentAnnotation ann : getAlignmentAnnotation())
1274     {
1275       if (ann.getCalcId() != null && ann.getCalcId().equals(calcId)
1276               && ann.sequenceRef != null && ann.sequenceRef == seq
1277               && ann.label != null && ann.label.equals(label))
1278       {
1279         aa.add(ann);
1280       }
1281     }
1282     return aa;
1283   }
1284
1285   /**
1286    * Answer true if any annotation matches the calcId passed in (if not null).
1287    * 
1288    * @param calcId
1289    * @return
1290    */
1291   public boolean hasAnnotation(String calcId)
1292   {
1293     if (calcId != null && !"".equals(calcId))
1294     {
1295       for (AlignmentAnnotation a : getAlignmentAnnotation())
1296       {
1297         if (a.getCalcId() == calcId)
1298         {
1299           return true;
1300         }
1301       }
1302     }
1303     return false;
1304   }
1305
1306   /**
1307    * Remove all sequences from the group (leaving other properties unchanged).
1308    */
1309   public void clear()
1310   {
1311     synchronized (sequences)
1312     {
1313       sequences.clear();
1314     }
1315   }
1316
1317   private AnnotatedCollectionI context;
1318
1319   /**
1320    * set the alignment or group context for this group
1321    * 
1322    * @param context
1323    */
1324   public void setContext(AnnotatedCollectionI context)
1325   {
1326     this.context = context;
1327   }
1328
1329   /*
1330    * (non-Javadoc)
1331    * 
1332    * @see jalview.datamodel.AnnotatedCollectionI#getContext()
1333    */
1334   @Override
1335   public AnnotatedCollectionI getContext()
1336   {
1337     return context;
1338   }
1339 }