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