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