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