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