Merge remote-tracking branch 'origin/develop' into
[jalview.git] / src / jalview / datamodel / SequenceGroup.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 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 Vector<SequenceI> sequences = new Vector<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(Vector 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       for (SequenceI seq : seqsel.sequences)
163       {
164         sequences.addElement(seq);
165       }
166       if (seqsel.groupName != null)
167       {
168         groupName = new String(seqsel.groupName);
169       }
170       displayBoxes = seqsel.displayBoxes;
171       displayText = seqsel.displayText;
172       colourText = seqsel.colourText;
173       startRes = seqsel.startRes;
174       endRes = seqsel.endRes;
175       cs = seqsel.cs;
176       if (seqsel.description != null)
177       {
178         description = new String(seqsel.description);
179       }
180       hidecols = seqsel.hidecols;
181       hidereps = seqsel.hidereps;
182       idColour = seqsel.idColour;
183       outlineColour = seqsel.outlineColour;
184       seqrep = seqsel.seqrep;
185       textColour = seqsel.textColour;
186       textColour2 = seqsel.textColour2;
187       thresholdTextColour = seqsel.thresholdTextColour;
188       width = seqsel.width;
189       ignoreGapsInConsensus = seqsel.ignoreGapsInConsensus;
190       if (seqsel.conserve != null)
191       {
192         recalcConservation(); // safer than
193         // aaFrequency = (Vector) seqsel.aaFrequency.clone(); // ??
194       }
195     }
196   }
197
198   public SequenceI[] getSelectionAsNewSequences(AlignmentI align)
199   {
200     int iSize = sequences.size();
201     SequenceI[] seqs = new SequenceI[iSize];
202     SequenceI[] inorder = getSequencesInOrder(align);
203
204     for (int i = 0, ipos = 0; i < inorder.length; i++)
205     {
206       SequenceI seq = inorder[i];
207
208       seqs[ipos] = seq.getSubSequence(startRes, endRes + 1);
209       if (seqs[ipos] != null)
210       {
211         seqs[ipos].setDescription(seq.getDescription());
212         seqs[ipos].setDBRef(seq.getDBRef());
213         seqs[ipos].setSequenceFeatures(seq.getSequenceFeatures());
214         if (seq.getDatasetSequence() != null)
215         {
216           seqs[ipos].setDatasetSequence(seq.getDatasetSequence());
217         }
218
219         if (seq.getAnnotation() != null)
220         {
221           AlignmentAnnotation[] alann = align.getAlignmentAnnotation();
222           // Only copy annotation that is either a score or referenced by the
223           // alignment's annotation vector
224           for (int a = 0; a < seq.getAnnotation().length; a++)
225           {
226             AlignmentAnnotation tocopy = seq.getAnnotation()[a];
227             if (alann != null)
228             {
229               boolean found = false;
230               for (int pos = 0; pos < alann.length; pos++)
231               {
232                 if (alann[pos] == tocopy)
233                 {
234                   found = true;
235                   break;
236                 }
237               }
238               if (!found)
239               {
240                 continue;
241               }
242             }
243             AlignmentAnnotation newannot = new AlignmentAnnotation(
244                     seq.getAnnotation()[a]);
245             newannot.restrict(startRes, endRes);
246             newannot.setSequenceRef(seqs[ipos]);
247             newannot.adjustForAlignment();
248             seqs[ipos].addAlignmentAnnotation(newannot);
249           }
250         }
251         ipos++;
252       }
253       else
254       {
255         iSize--;
256       }
257     }
258     if (iSize != inorder.length)
259     {
260       SequenceI[] nseqs = new SequenceI[iSize];
261       System.arraycopy(seqs, 0, nseqs, 0, iSize);
262       seqs = nseqs;
263     }
264     return seqs;
265
266   }
267
268   /**
269    * If sequence ends in gaps, the end residue can be correctly calculated here
270    * 
271    * @param seq
272    *          SequenceI
273    * @return int
274    */
275   public int findEndRes(SequenceI seq)
276   {
277     int eres = 0;
278     char ch;
279
280     for (int j = 0; j < endRes + 1 && j < seq.getLength(); j++)
281     {
282       ch = seq.getCharAt(j);
283       if (!jalview.util.Comparison.isGap((ch)))
284       {
285         eres++;
286       }
287     }
288
289     if (eres > 0)
290     {
291       eres += seq.getStart() - 1;
292     }
293
294     return eres;
295   }
296
297   @Override
298   public List<SequenceI> getSequences()
299   {
300     return sequences;
301   }
302
303   @Override
304   public List<SequenceI> getSequences(
305           Map<SequenceI, SequenceCollectionI> hiddenReps)
306   {
307     if (hiddenReps == null)
308     {
309       return sequences;
310     }
311     else
312     {
313       Vector allSequences = new Vector();
314       SequenceI seq;
315       for (int i = 0; i < sequences.size(); i++)
316       {
317         seq = sequences.elementAt(i);
318         allSequences.addElement(seq);
319         if (hiddenReps.containsKey(seq))
320         {
321           SequenceCollectionI hsg = hiddenReps.get(seq);
322           for (SequenceI seq2 : hsg.getSequences())
323           {
324             if (seq2 != seq && !allSequences.contains(seq2))
325             {
326               allSequences.addElement(seq2);
327             }
328           }
329         }
330       }
331
332       return allSequences;
333     }
334   }
335
336   public SequenceI[] getSequencesAsArray(
337           Map<SequenceI, SequenceCollectionI> map)
338   {
339     List<SequenceI> tmp = getSequences(map);
340     if (tmp == null)
341     {
342       return null;
343     }
344     return tmp.toArray(new SequenceI[tmp.size()]);
345   }
346
347   /**
348    * DOCUMENT ME!
349    * 
350    * @param col
351    *          DOCUMENT ME!
352    * 
353    * @return DOCUMENT ME!
354    */
355   public boolean adjustForRemoveLeft(int col)
356   {
357     // return value is true if the group still exists
358     if (startRes >= col)
359     {
360       startRes = startRes - col;
361     }
362
363     if (endRes >= col)
364     {
365       endRes = endRes - col;
366
367       if (startRes > endRes)
368       {
369         startRes = 0;
370       }
371     }
372     else
373     {
374       // must delete this group!!
375       return false;
376     }
377
378     return true;
379   }
380
381   /**
382    * DOCUMENT ME!
383    * 
384    * @param col
385    *          DOCUMENT ME!
386    * 
387    * @return DOCUMENT ME!
388    */
389   public boolean adjustForRemoveRight(int col)
390   {
391     if (startRes > col)
392     {
393       // delete this group
394       return false;
395     }
396
397     if (endRes >= col)
398     {
399       endRes = col;
400     }
401
402     return true;
403   }
404
405   /**
406    * DOCUMENT ME!
407    * 
408    * @return DOCUMENT ME!
409    */
410   public String getName()
411   {
412     return groupName;
413   }
414
415   public String getDescription()
416   {
417     return description;
418   }
419
420   /**
421    * DOCUMENT ME!
422    * 
423    * @param name
424    *          DOCUMENT ME!
425    */
426   public void setName(String name)
427   {
428     groupName = name;
429     // TODO: URGENT: update dependent objects (annotation row)
430   }
431
432   public void setDescription(String desc)
433   {
434     description = desc;
435   }
436
437   /**
438    * DOCUMENT ME!
439    * 
440    * @return DOCUMENT ME!
441    */
442   public Conservation getConservation()
443   {
444     return conserve;
445   }
446
447   /**
448    * DOCUMENT ME!
449    * 
450    * @param c
451    *          DOCUMENT ME!
452    */
453   public void setConservation(Conservation c)
454   {
455     conserve = c;
456   }
457
458   /**
459    * Add s to this sequence group. If aligment sequence is already contained in
460    * group, it will not be added again, but recalculation may happen if the flag
461    * is set.
462    * 
463    * @param s
464    *          alignment sequence to be added
465    * @param recalc
466    *          true means Group's conservation should be recalculated
467    */
468   public void addSequence(SequenceI s, boolean recalc)
469   {
470     if (s != null && !sequences.contains(s))
471     {
472       sequences.addElement(s);
473     }
474
475     if (recalc)
476     {
477       recalcConservation();
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     if (sequences.contains(s))
613     {
614       deleteSequence(s, recalc);
615     }
616     else
617     {
618       addSequence(s, recalc);
619     }
620   }
621
622   /**
623    * DOCUMENT ME!
624    * 
625    * @param s
626    *          DOCUMENT ME!
627    * @param recalc
628    *          DOCUMENT ME!
629    */
630   public void deleteSequence(SequenceI s, boolean recalc)
631   {
632     sequences.removeElement(s);
633
634     if (recalc)
635     {
636       recalcConservation();
637     }
638   }
639
640   /**
641    * 
642    * 
643    * @return the first column selected by this group. Runs from 0<=i<N_cols
644    */
645   @Override
646   public int getStartRes()
647   {
648     return startRes;
649   }
650
651   /**
652    * 
653    * @return the groups last selected column. Runs from 0<=i<N_cols
654    */
655   @Override
656   public int getEndRes()
657   {
658     return endRes;
659   }
660
661   /**
662    * Set the first column selected by this group. Runs from 0<=i<N_cols
663    * 
664    * @param i
665    */
666   public void setStartRes(int i)
667   {
668     startRes = i;
669   }
670
671   /**
672    * Set the groups last selected column. Runs from 0<=i<N_cols
673    * 
674    * @param i
675    */
676   public void setEndRes(int i)
677   {
678     endRes = i;
679   }
680
681   /**
682    * DOCUMENT ME!
683    * 
684    * @return DOCUMENT ME!
685    */
686   public int getSize()
687   {
688     return sequences.size();
689   }
690
691   /**
692    * DOCUMENT ME!
693    * 
694    * @param i
695    *          DOCUMENT ME!
696    * 
697    * @return DOCUMENT ME!
698    */
699   public SequenceI getSequenceAt(int i)
700   {
701     return sequences.elementAt(i);
702   }
703
704   /**
705    * DOCUMENT ME!
706    * 
707    * @param state
708    *          DOCUMENT ME!
709    */
710   public void setColourText(boolean state)
711   {
712     colourText = state;
713   }
714
715   /**
716    * DOCUMENT ME!
717    * 
718    * @return DOCUMENT ME!
719    */
720   public boolean getColourText()
721   {
722     return colourText;
723   }
724
725   /**
726    * DOCUMENT ME!
727    * 
728    * @param state
729    *          DOCUMENT ME!
730    */
731   public void setDisplayText(boolean state)
732   {
733     displayText = state;
734   }
735
736   /**
737    * DOCUMENT ME!
738    * 
739    * @return DOCUMENT ME!
740    */
741   public boolean getDisplayText()
742   {
743     return displayText;
744   }
745
746   /**
747    * DOCUMENT ME!
748    * 
749    * @param state
750    *          DOCUMENT ME!
751    */
752   public void setDisplayBoxes(boolean state)
753   {
754     displayBoxes = state;
755   }
756
757   /**
758    * DOCUMENT ME!
759    * 
760    * @return DOCUMENT ME!
761    */
762   public boolean getDisplayBoxes()
763   {
764     return displayBoxes;
765   }
766
767   /**
768    * DOCUMENT ME!
769    * 
770    * @return DOCUMENT ME!
771    */
772   @Override
773   public int getWidth()
774   {
775     // MC This needs to get reset when characters are inserted and deleted
776     if (sequences.size() > 0)
777     {
778       width = sequences.elementAt(0).getLength();
779     }
780
781     for (int i = 1; i < sequences.size(); i++)
782     {
783       SequenceI seq = sequences.elementAt(i);
784
785       if (seq.getLength() > width)
786       {
787         width = seq.getLength();
788       }
789     }
790
791     return width;
792   }
793
794   /**
795    * DOCUMENT ME!
796    * 
797    * @param c
798    *          DOCUMENT ME!
799    */
800   public void setOutlineColour(Color c)
801   {
802     outlineColour = c;
803   }
804
805   /**
806    * DOCUMENT ME!
807    * 
808    * @return DOCUMENT ME!
809    */
810   public Color getOutlineColour()
811   {
812     return outlineColour;
813   }
814
815   /**
816    * 
817    * returns the sequences in the group ordered by the ordering given by al.
818    * this used to return an array with null entries regardless, new behaviour is
819    * below. TODO: verify that this does not affect use in applet or application
820    * 
821    * @param al
822    *          Alignment
823    * @return SequenceI[] intersection of sequences in group with al, ordered by
824    *         al, or null if group does not intersect with al
825    */
826   public SequenceI[] getSequencesInOrder(AlignmentI al)
827   {
828     return getSequencesInOrder(al, true);
829   }
830
831   /**
832    * return an array representing the intersection of the group with al,
833    * optionally returning an array the size of al.getHeight() where nulls mark
834    * the non-intersected sequences
835    * 
836    * @param al
837    * @param trim
838    * @return null or array
839    */
840   public SequenceI[] getSequencesInOrder(AlignmentI al, boolean trim)
841   {
842     int sSize = sequences.size();
843     int alHeight = al.getHeight();
844
845     SequenceI[] seqs = new SequenceI[(trim) ? sSize : alHeight];
846
847     int index = 0;
848     for (int i = 0; i < alHeight && index < sSize; i++)
849     {
850       if (sequences.contains(al.getSequenceAt(i)))
851       {
852         seqs[(trim) ? index : i] = al.getSequenceAt(i);
853         index++;
854       }
855     }
856     if (index == 0)
857     {
858       return null;
859     }
860     if (!trim)
861     {
862       return seqs;
863     }
864     if (index < seqs.length)
865     {
866       SequenceI[] dummy = seqs;
867       seqs = new SequenceI[index];
868       while (--index >= 0)
869       {
870         seqs[index] = dummy[index];
871         dummy[index] = null;
872       }
873     }
874     return seqs;
875   }
876
877   /**
878    * @return the idColour
879    */
880   public Color getIdColour()
881   {
882     return idColour;
883   }
884
885   /**
886    * @param idColour
887    *          the idColour to set
888    */
889   public void setIdColour(Color idColour)
890   {
891     this.idColour = idColour;
892   }
893
894   /**
895    * @return the representative sequence for this group
896    */
897   public SequenceI getSeqrep()
898   {
899     return seqrep;
900   }
901
902   /**
903    * set the representative sequence for this group. Note - this affects the
904    * interpretation of the Hidereps attribute.
905    * 
906    * @param seqrep
907    *          the seqrep to set (null means no sequence representative)
908    */
909   public void setSeqrep(SequenceI seqrep)
910   {
911     this.seqrep = seqrep;
912   }
913
914   /**
915    * 
916    * @return true if group has a sequence representative
917    */
918   public boolean hasSeqrep()
919   {
920     return seqrep != null;
921   }
922
923   /**
924    * visibility of rows or represented rows covered by group
925    */
926   private boolean hidereps = false;
927
928   /**
929    * set visibility of sequences covered by (if no sequence representative is
930    * defined) or represented by this group.
931    * 
932    * @param visibility
933    */
934   public void setHidereps(boolean visibility)
935   {
936     hidereps = visibility;
937   }
938
939   /**
940    * 
941    * @return true if sequences represented (or covered) by this group should be
942    *         hidden
943    */
944   public boolean isHidereps()
945   {
946     return hidereps;
947   }
948
949   /**
950    * visibility of columns intersecting this group
951    */
952   private boolean hidecols = false;
953
954   /**
955    * set intended visibility of columns covered by this group
956    * 
957    * @param visibility
958    */
959   public void setHideCols(boolean visibility)
960   {
961     hidecols = visibility;
962   }
963
964   /**
965    * 
966    * @return true if columns covered by group should be hidden
967    */
968   public boolean isHideCols()
969   {
970     return hidecols;
971   }
972
973   /**
974    * create a new sequence group from the intersection of this group with an
975    * alignment Hashtable of hidden representatives
976    * 
977    * @param alignment
978    *          (may not be null)
979    * @param map
980    *          (may be null)
981    * @return new group containing sequences common to this group and alignment
982    */
983   public SequenceGroup intersect(AlignmentI alignment,
984           Map<SequenceI, SequenceCollectionI> map)
985   {
986     SequenceGroup sgroup = new SequenceGroup(this);
987     SequenceI[] insect = getSequencesInOrder(alignment);
988     sgroup.sequences = new Vector<SequenceI>();
989     if (insect != null)
990     {
991       for (SequenceI seq : insect)
992       {
993         if (map == null || map.containsKey(seq))
994         {
995           sgroup.sequences.addElement(seq);
996         }
997       }
998     }
999     // Enumeration en =getSequences(hashtable).elements();
1000     // while (en.hasMoreElements())
1001     // {
1002     // SequenceI elem = (SequenceI) en.nextElement();
1003     // if (alignment.getSequences().contains(elem))
1004     // {
1005     // sgroup.addSequence(elem, false);
1006     // }
1007     // }
1008     return sgroup;
1009   }
1010
1011   /**
1012    * @return the showUnconserved
1013    */
1014   public boolean getShowNonconserved()
1015   {
1016     return showNonconserved;
1017   }
1018
1019   /**
1020    * @param showNonconserved
1021    *          the showUnconserved to set
1022    */
1023   public void setShowNonconserved(boolean displayNonconserved)
1024   {
1025     this.showNonconserved = displayNonconserved;
1026   }
1027
1028   AlignmentAnnotation consensus = null, conservation = null;
1029
1030   /**
1031    * flag indicating if consensus histogram should be rendered
1032    */
1033   private boolean showConsensusHistogram;
1034
1035   /**
1036    * set this alignmentAnnotation object as the one used to render consensus
1037    * annotation
1038    * 
1039    * @param aan
1040    */
1041   public void setConsensus(AlignmentAnnotation aan)
1042   {
1043     if (consensus == null)
1044     {
1045       consensus = aan;
1046     }
1047   }
1048
1049   /**
1050    * 
1051    * @return automatically calculated consensus row
1052    */
1053   public AlignmentAnnotation getConsensus()
1054   {
1055     // TODO get or calculate and get consensus annotation row for this group
1056     int aWidth = this.getWidth();
1057     // pointer
1058     // possibility
1059     // here.
1060     if (aWidth < 0)
1061     {
1062       return null;
1063     }
1064     if (consensus == null)
1065     {
1066       consensus = new AlignmentAnnotation("", "", new Annotation[1], 0f,
1067               100f, AlignmentAnnotation.BAR_GRAPH);
1068       consensus.hasText = true;
1069       consensus.autoCalculated = true;
1070       consensus.groupRef = this;
1071       consensus.label = "Consensus for " + getName();
1072       consensus.description = "Percent Identity";
1073     }
1074     return consensus;
1075   }
1076
1077   /**
1078    * set this alignmentAnnotation object as the one used to render consensus
1079    * annotation
1080    * 
1081    * @param aan
1082    */
1083   public void setConservationRow(AlignmentAnnotation aan)
1084   {
1085     if (conservation == null)
1086     {
1087       conservation = aan;
1088     }
1089   }
1090
1091   /**
1092    * get the conservation annotation row for this group
1093    * 
1094    * @return autoCalculated annotation row
1095    */
1096   public AlignmentAnnotation getConservationRow()
1097   {
1098     if (conservation == null)
1099     {
1100       conservation = new AlignmentAnnotation("", "", new Annotation[1], 0f,
1101               11f, AlignmentAnnotation.BAR_GRAPH);
1102     }
1103
1104     conservation.hasText = true;
1105     conservation.autoCalculated = true;
1106     conservation.groupRef = this;
1107     conservation.label = "Conservation for " + getName();
1108     conservation.description = "Conservation for group " + getName()
1109             + " less than " + consPercGaps + "% gaps";
1110     return conservation;
1111   }
1112
1113   /**
1114    * 
1115    * @return true if annotation rows have been instantiated for this group
1116    */
1117   public boolean hasAnnotationRows()
1118   {
1119     return consensus != null || conservation != null;
1120   }
1121
1122   public SequenceI getConsensusSeq()
1123   {
1124     getConsensus();
1125     StringBuffer seqs = new StringBuffer();
1126     for (int i = 0; i < consensus.annotations.length; i++)
1127     {
1128       if (consensus.annotations[i] != null)
1129       {
1130         if (consensus.annotations[i].description.charAt(0) == '[')
1131         {
1132           seqs.append(consensus.annotations[i].description.charAt(1));
1133         }
1134         else
1135         {
1136           seqs.append(consensus.annotations[i].displayCharacter);
1137         }
1138       }
1139     }
1140
1141     SequenceI sq = new Sequence("Group" + getName() + " Consensus",
1142             seqs.toString());
1143     sq.setDescription("Percentage Identity Consensus "
1144             + ((ignoreGapsInConsensus) ? " without gaps" : ""));
1145     return sq;
1146   }
1147
1148   public void setIgnoreGapsConsensus(boolean state)
1149   {
1150     if (this.ignoreGapsInConsensus != state && consensus != null)
1151     {
1152       ignoreGapsInConsensus = state;
1153       recalcConservation();
1154     }
1155     ignoreGapsInConsensus = state;
1156   }
1157
1158   public boolean getIgnoreGapsConsensus()
1159   {
1160     return ignoreGapsInConsensus;
1161   }
1162
1163   /**
1164    * @param showSequenceLogo
1165    *          indicates if a sequence logo is shown for consensus annotation
1166    */
1167   public void setshowSequenceLogo(boolean showSequenceLogo)
1168   {
1169     // TODO: decouple calculation from settings update
1170     if (this.showSequenceLogo != showSequenceLogo && consensus != null)
1171     {
1172       this.showSequenceLogo = showSequenceLogo;
1173       recalcConservation();
1174     }
1175     this.showSequenceLogo = showSequenceLogo;
1176   }
1177
1178   /**
1179    * 
1180    * @param showConsHist
1181    *          flag indicating if the consensus histogram for this group should
1182    *          be rendered
1183    */
1184   public void setShowConsensusHistogram(boolean showConsHist)
1185   {
1186
1187     if (showConsensusHistogram != showConsHist && consensus != null)
1188     {
1189       this.showConsensusHistogram = showConsHist;
1190       recalcConservation();
1191     }
1192     this.showConsensusHistogram = showConsHist;
1193   }
1194
1195   /**
1196    * @return the showConsensusHistogram
1197    */
1198   public boolean isShowConsensusHistogram()
1199   {
1200     return showConsensusHistogram;
1201   }
1202
1203   /**
1204    * set flag indicating if logo should be normalised when rendered
1205    * 
1206    * @param norm
1207    */
1208   public void setNormaliseSequenceLogo(boolean norm)
1209   {
1210     normaliseSequenceLogo = norm;
1211   }
1212
1213   public boolean isNormaliseSequenceLogo()
1214   {
1215     return normaliseSequenceLogo;
1216   }
1217
1218   @Override
1219   /**
1220    * returns a new array with all annotation involving this group
1221    */
1222   public AlignmentAnnotation[] getAlignmentAnnotation()
1223   {
1224     // TODO add in other methods like 'getAlignmentAnnotation(String label),
1225     // etc'
1226     ArrayList<AlignmentAnnotation> annot = new ArrayList<AlignmentAnnotation>();
1227     for (SequenceI seq : sequences)
1228     {
1229       AlignmentAnnotation[] aa = seq.getAnnotation();
1230       if (aa != null)
1231       {
1232         for (AlignmentAnnotation al : aa)
1233         {
1234           if (al.groupRef == this)
1235           {
1236             annot.add(al);
1237           }
1238         }
1239       }
1240     }
1241     if (consensus != null)
1242     {
1243       annot.add(consensus);
1244     }
1245     if (conservation != null)
1246     {
1247       annot.add(conservation);
1248     }
1249     return annot.toArray(new AlignmentAnnotation[0]);
1250   }
1251
1252   @Override
1253   public Iterable<AlignmentAnnotation> findAnnotation(String calcId)
1254   {
1255     ArrayList<AlignmentAnnotation> aa = new ArrayList<AlignmentAnnotation>();
1256     for (AlignmentAnnotation a : getAlignmentAnnotation())
1257     {
1258       if (a.getCalcId() == calcId)
1259       {
1260         aa.add(a);
1261       }
1262     }
1263     return aa;
1264   }
1265
1266   /**
1267    * Returns a list of annotations that match the specified sequenceRef, calcId
1268    * and label, ignoring null values.
1269    * 
1270    * @return list of AlignmentAnnotation objects
1271    */
1272   @Override
1273   public Iterable<AlignmentAnnotation> findAnnotations(SequenceI seq,
1274           String calcId, String label)
1275   {
1276     ArrayList<AlignmentAnnotation> aa = new ArrayList<AlignmentAnnotation>();
1277     for (AlignmentAnnotation ann : getAlignmentAnnotation())
1278     {
1279       if (ann.getCalcId() != null && ann.getCalcId().equals(calcId)
1280               && ann.sequenceRef != null && ann.sequenceRef == seq
1281               && ann.label != null && ann.label.equals(label))
1282       {
1283         aa.add(ann);
1284       }
1285     }
1286     return aa;
1287   }
1288
1289   /**
1290    * Answer true if any annotation matches the calcId passed in (if not null).
1291    * 
1292    * @param calcId
1293    * @return
1294    */
1295   public boolean hasAnnotation(String calcId)
1296   {
1297     if (calcId != null && !"".equals(calcId))
1298     {
1299       for (AlignmentAnnotation a : getAlignmentAnnotation())
1300       {
1301         if (a.getCalcId() == calcId)
1302         {
1303           return true;
1304         }
1305       }
1306     }
1307     return false;
1308   }
1309
1310   public void clear()
1311   {
1312     sequences.clear();
1313   }
1314
1315   private AnnotatedCollectionI context;
1316
1317   /**
1318    * set the alignment or group context for this group
1319    * 
1320    * @param context
1321    */
1322   public void setContext(AnnotatedCollectionI context)
1323   {
1324     this.context = context;
1325   }
1326
1327   /*
1328    * (non-Javadoc)
1329    * 
1330    * @see jalview.datamodel.AnnotatedCollectionI#getContext()
1331    */
1332   @Override
1333   public AnnotatedCollectionI getContext()
1334   {
1335     return context;
1336   }
1337 }