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