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