JAL-2069 spike updated with latest (FeatureTypeSettings)
[jalview.git] / src / jalview / datamodel / SequenceGroup.java
index 17348c2..6b797d7 100755 (executable)
@@ -1,6 +1,6 @@
 /*
- * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
- * Copyright (C) 2014 The Jalview Authors
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
  * 
  * This file is part of Jalview.
  * 
@@ -22,16 +22,17 @@ package jalview.datamodel;
 
 import jalview.analysis.AAFrequency;
 import jalview.analysis.Conservation;
+import jalview.renderer.ResidueShader;
+import jalview.renderer.ResidueShaderI;
 import jalview.schemes.ColourSchemeI;
-import jalview.schemes.ResidueProperties;
 
 import java.awt.Color;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
 import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.Hashtable;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
-import java.util.Vector;
 
 /**
  * Collects a set contiguous ranges on a set of sequences
@@ -41,14 +42,32 @@ import java.util.Vector;
  */
 public class SequenceGroup implements AnnotatedCollectionI
 {
+  // TODO ideally this event notification functionality should be separated into
+  // a
+  // subclass of ViewportProperties similarly to ViewportRanges. Done here as
+  // quick fix for JAL-2665
+  public static final String SEQ_GROUP_CHANGED = "Sequence group changed";
+
+  protected PropertyChangeSupport changeSupport = new PropertyChangeSupport(
+          this);
+
+  public void addPropertyChangeListener(PropertyChangeListener listener)
+  {
+    changeSupport.addPropertyChangeListener(listener);
+  }
+
+  public void removePropertyChangeListener(PropertyChangeListener listener)
+  {
+    changeSupport.removePropertyChangeListener(listener);
+  }
+  // end of event notification functionality initialisation
+
   String groupName;
 
   String description;
 
   Conservation conserve;
 
-  Vector aaFrequency;
-
   boolean displayBoxes = true;
 
   boolean displayText = true;
@@ -56,6 +75,12 @@ public class SequenceGroup implements AnnotatedCollectionI
   boolean colourText = false;
 
   /**
+   * True if the group is defined as a group on the alignment, false if it is
+   * just a selection.
+   */
+  boolean isDefined = false;
+
+  /**
    * after Olivier's non-conserved only character display
    */
   boolean showNonconserved = false;
@@ -63,7 +88,7 @@ public class SequenceGroup implements AnnotatedCollectionI
   /**
    * group members
    */
-  private Vector<SequenceI> sequences = new Vector<SequenceI>();
+  private List<SequenceI> sequences = new ArrayList<>();
 
   /**
    * representative sequence for this group (if any)
@@ -75,10 +100,12 @@ public class SequenceGroup implements AnnotatedCollectionI
   /**
    * Colourscheme applied to group if any
    */
-  public ColourSchemeI cs;
+  public ResidueShaderI cs;
 
+  // start column (base 0)
   int startRes = 0;
 
+  // end column (base 0)
   int endRes = 0;
 
   public Color outlineColour = Color.black;
@@ -106,13 +133,23 @@ public class SequenceGroup implements AnnotatedCollectionI
    */
   private boolean normaliseSequenceLogo;
 
-  /**
-   * @return the includeAllConsSymbols
+  /*
+   * visibility of rows or represented rows covered by group
    */
-  public boolean isShowSequenceLogo()
-  {
-    return showSequenceLogo;
-  }
+  private boolean hidereps = false;
+
+  /*
+   * visibility of columns intersecting this group
+   */
+  private boolean hidecols = false;
+
+  AlignmentAnnotation consensus = null;
+
+  AlignmentAnnotation conservation = null;
+
+  private boolean showConsensusHistogram;
+
+  private AnnotatedCollectionI context;
 
   /**
    * Creates a new SequenceGroup object.
@@ -120,6 +157,7 @@ public class SequenceGroup implements AnnotatedCollectionI
   public SequenceGroup()
   {
     groupName = "JGroup:" + this.hashCode();
+    cs = new ResidueShader();
   }
 
   /**
@@ -136,16 +174,17 @@ public class SequenceGroup implements AnnotatedCollectionI
    * @param end
    *          last column of group
    */
-  public SequenceGroup(Vector sequences, String groupName,
+  public SequenceGroup(List<SequenceI> sequences, String groupName,
           ColourSchemeI scheme, boolean displayBoxes, boolean displayText,
           boolean colourText, int start, int end)
   {
+    this();
     this.sequences = sequences;
     this.groupName = groupName;
     this.displayBoxes = displayBoxes;
     this.displayText = displayText;
     this.colourText = colourText;
-    this.cs = scheme;
+    this.cs = new ResidueShader(scheme);
     startRes = start;
     endRes = end;
     recalcConservation();
@@ -158,15 +197,11 @@ public class SequenceGroup implements AnnotatedCollectionI
    */
   public SequenceGroup(SequenceGroup seqsel)
   {
+    this();
     if (seqsel != null)
     {
-      sequences = new Vector();
-      Enumeration<SequenceI> sq = seqsel.sequences.elements();
-      while (sq.hasMoreElements())
-      {
-        sequences.addElement(sq.nextElement());
-      }
-      ;
+      sequences = new ArrayList<>();
+      sequences.addAll(seqsel.sequences);
       if (seqsel.groupName != null)
       {
         groupName = new String(seqsel.groupName);
@@ -176,11 +211,17 @@ public class SequenceGroup implements AnnotatedCollectionI
       colourText = seqsel.colourText;
       startRes = seqsel.startRes;
       endRes = seqsel.endRes;
-      cs = seqsel.cs;
+      cs = new ResidueShader((ResidueShader) seqsel.cs);
       if (seqsel.description != null)
+      {
         description = new String(seqsel.description);
+      }
       hidecols = seqsel.hidecols;
       hidereps = seqsel.hidereps;
+      showNonconserved = seqsel.showNonconserved;
+      showSequenceLogo = seqsel.showSequenceLogo;
+      normaliseSequenceLogo = seqsel.normaliseSequenceLogo;
+      showConsensusHistogram = seqsel.showConsensusHistogram;
       idColour = seqsel.idColour;
       outlineColour = seqsel.outlineColour;
       seqrep = seqsel.seqrep;
@@ -197,6 +238,11 @@ public class SequenceGroup implements AnnotatedCollectionI
     }
   }
 
+  public boolean isShowSequenceLogo()
+  {
+    return showSequenceLogo;
+  }
+
   public SequenceI[] getSelectionAsNewSequences(AlignmentI align)
   {
     int iSize = sequences.size();
@@ -211,7 +257,7 @@ public class SequenceGroup implements AnnotatedCollectionI
       if (seqs[ipos] != null)
       {
         seqs[ipos].setDescription(seq.getDescription());
-        seqs[ipos].setDBRef(seq.getDBRef());
+        seqs[ipos].setDBRefs(seq.getDBRefs());
         seqs[ipos].setSequenceFeatures(seq.getSequenceFeatures());
         if (seq.getDatasetSequence() != null)
         {
@@ -238,7 +284,9 @@ public class SequenceGroup implements AnnotatedCollectionI
                 }
               }
               if (!found)
+              {
                 continue;
+              }
             }
             AlignmentAnnotation newannot = new AlignmentAnnotation(
                     seq.getAnnotation()[a]);
@@ -306,16 +354,15 @@ public class SequenceGroup implements AnnotatedCollectionI
   {
     if (hiddenReps == null)
     {
+      // TODO: need a synchronizedCollection here ?
       return sequences;
     }
     else
     {
-      Vector allSequences = new Vector();
-      SequenceI seq;
-      for (int i = 0; i < sequences.size(); i++)
+      List<SequenceI> allSequences = new ArrayList<>();
+      for (SequenceI seq : sequences)
       {
-        seq = sequences.elementAt(i);
-        allSequences.addElement(seq);
+        allSequences.add(seq);
         if (hiddenReps.containsKey(seq))
         {
           SequenceCollectionI hsg = hiddenReps.get(seq);
@@ -323,7 +370,7 @@ public class SequenceGroup implements AnnotatedCollectionI
           {
             if (seq2 != seq && !allSequences.contains(seq2))
             {
-              allSequences.addElement(seq2);
+              allSequences.add(seq2);
             }
           }
         }
@@ -467,14 +514,19 @@ public class SequenceGroup implements AnnotatedCollectionI
    */
   public void addSequence(SequenceI s, boolean recalc)
   {
-    if (s != null && !sequences.contains(s))
+    synchronized (sequences)
     {
-      sequences.addElement(s);
-    }
+      if (s != null && !sequences.contains(s))
+      {
+        sequences.add(s);
+        changeSupport.firePropertyChange(SEQ_GROUP_CHANGED,
+                sequences.size() - 1, sequences.size());
+      }
 
-    if (recalc)
-    {
-      recalcConservation();
+      if (recalc)
+      {
+        recalcConservation();
+      }
     }
   }
 
@@ -502,32 +554,51 @@ public class SequenceGroup implements AnnotatedCollectionI
   }
 
   /**
-   * calculate residue conservation for group - but only if necessary.
+   * calculate residue conservation and colourschemes for group - but only if
+   * necessary. returns true if the calculation resulted in a visible change to
+   * group
+   */
+  public boolean recalcConservation()
+  {
+    return recalcConservation(false);
+  }
+
+  /**
+   * calculate residue conservation for group - but only if necessary. returns
+   * true if the calculation resulted in a visible change to group
+   * 
+   * @param defer
+   *          when set, colourschemes for this group are not refreshed after
+   *          recalculation
    */
-  public void recalcConservation()
+  public boolean recalcConservation(boolean defer)
   {
     if (cs == null && consensus == null && conservation == null)
     {
-      return;
+      return false;
     }
+    // TODO: try harder to detect changes in state in order to minimise
+    // recalculation effort
+    boolean upd = false;
     try
     {
-      Hashtable cnsns[] = AAFrequency.calculate(sequences, startRes,
+      ProfilesI cnsns = AAFrequency.calculate(sequences, startRes,
               endRes + 1, showSequenceLogo);
       if (consensus != null)
       {
         _updateConsensusRow(cnsns, sequences.size());
+        upd = true;
       }
       if (cs != null)
       {
         cs.setConsensus(cnsns);
+        upd = true;
       }
 
       if ((conservation != null)
               || (cs != null && cs.conservationApplied()))
       {
-        Conservation c = new Conservation(groupName,
-                ResidueProperties.propHash, 3, sequences, startRes,
+        Conservation c = new Conservation(groupName, sequences, startRes,
                 endRes + 1);
         c.calculate();
         c.verdict(false, consPercGaps);
@@ -542,17 +613,25 @@ public class SequenceGroup implements AnnotatedCollectionI
             cs.setConservation(c);
           }
         }
+        // eager update - will cause a refresh of overview regardless
+        upd = true;
       }
-      if (cs != null)
+      if (cs != null && !defer)
       {
+        // TODO: JAL-2034 should cs.alignmentChanged modify return state
         cs.alignmentChanged(context != null ? context : this, null);
+        return true;
+      }
+      else
+      {
+        return upd;
       }
     } catch (java.lang.OutOfMemoryError err)
     {
       // TODO: catch OOM
       System.out.println("Out of memory loading groups: " + err);
     }
-
+    return upd;
   }
 
   private void _updateConservationRow(Conservation c)
@@ -566,8 +645,10 @@ public class SequenceGroup implements AnnotatedCollectionI
     conservation.description = "Conservation for group " + getName()
             + " less than " + consPercGaps + "% gaps";
     // preserve width if already set
-    int aWidth = (conservation.annotations != null) ? (endRes < conservation.annotations.length ? conservation.annotations.length
-            : endRes + 1)
+    int aWidth = (conservation.annotations != null)
+            ? (endRes < conservation.annotations.length
+                    ? conservation.annotations.length
+                    : endRes + 1)
             : endRes + 1;
     conservation.annotations = null;
     conservation.annotations = new Annotation[aWidth]; // should be alignment
@@ -575,9 +656,9 @@ public class SequenceGroup implements AnnotatedCollectionI
     c.completeAnnotations(conservation, null, startRes, endRes + 1);
   }
 
-  public Hashtable[] consensusData = null;
+  public ProfilesI consensusData = null;
 
-  private void _updateConsensusRow(Hashtable[] cnsns, long nseq)
+  private void _updateConsensusRow(ProfilesI cnsns, long nseq)
   {
     if (consensus == null)
     {
@@ -587,8 +668,10 @@ public class SequenceGroup implements AnnotatedCollectionI
     consensus.description = "Percent Identity";
     consensusData = cnsns;
     // preserve width if already set
-    int aWidth = (consensus.annotations != null) ? (endRes < consensus.annotations.length ? consensus.annotations.length
-            : endRes + 1)
+    int aWidth = (consensus.annotations != null)
+            ? (endRes < consensus.annotations.length
+                    ? consensus.annotations.length
+                    : endRes + 1)
             : endRes + 1;
     consensus.annotations = null;
     consensus.annotations = new Annotation[aWidth]; // should be alignment width
@@ -609,31 +692,39 @@ public class SequenceGroup implements AnnotatedCollectionI
    */
   public void addOrRemove(SequenceI s, boolean recalc)
   {
-    if (sequences.contains(s))
-    {
-      deleteSequence(s, recalc);
-    }
-    else
+    synchronized (sequences)
     {
-      addSequence(s, recalc);
+      if (sequences.contains(s))
+      {
+        deleteSequence(s, recalc);
+      }
+      else
+      {
+        addSequence(s, recalc);
+      }
     }
   }
 
   /**
-   * DOCUMENT ME!
+   * remove
    * 
    * @param s
-   *          DOCUMENT ME!
+   *          to be removed
    * @param recalc
-   *          DOCUMENT ME!
+   *          true means recalculate conservation
    */
   public void deleteSequence(SequenceI s, boolean recalc)
   {
-    sequences.removeElement(s);
-
-    if (recalc)
+    synchronized (sequences)
     {
-      recalcConservation();
+      sequences.remove(s);
+      changeSupport.firePropertyChange(SEQ_GROUP_CHANGED,
+              sequences.size() + 1, sequences.size());
+
+      if (recalc)
+      {
+        recalcConservation();
+      }
     }
   }
 
@@ -665,7 +756,9 @@ public class SequenceGroup implements AnnotatedCollectionI
    */
   public void setStartRes(int i)
   {
+    int before = startRes;
     startRes = i;
+    changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before, startRes);
   }
 
   /**
@@ -675,13 +768,13 @@ public class SequenceGroup implements AnnotatedCollectionI
    */
   public void setEndRes(int i)
   {
+    int before = endRes;
     endRes = i;
+    changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before, endRes);
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
+   * @return number of sequences in group
    */
   public int getSize()
   {
@@ -689,23 +782,17 @@ public class SequenceGroup implements AnnotatedCollectionI
   }
 
   /**
-   * DOCUMENT ME!
-   * 
    * @param i
-   *          DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
+   * @return the ith sequence
    */
   public SequenceI getSequenceAt(int i)
   {
-    return sequences.elementAt(i);
+    return sequences.get(i);
   }
 
   /**
-   * DOCUMENT ME!
-   * 
    * @param state
-   *          DOCUMENT ME!
+   *          colourText
    */
   public void setColourText(boolean state)
   {
@@ -765,30 +852,27 @@ public class SequenceGroup implements AnnotatedCollectionI
   }
 
   /**
-   * DOCUMENT ME!
+   * computes the width of current set of sequences and returns it
    * 
    * @return DOCUMENT ME!
    */
   @Override
   public int getWidth()
   {
-    // MC This needs to get reset when characters are inserted and deleted
-    if (sequences.size() > 0)
-    {
-      width = sequences.elementAt(0).getLength();
-    }
-
-    for (int i = 1; i < sequences.size(); i++)
+    synchronized (sequences)
     {
-      SequenceI seq = sequences.elementAt(i);
-
-      if (seq.getLength() > width)
+      // MC This needs to get reset when characters are inserted and deleted
+      boolean first = true;
+      for (SequenceI seq : sequences)
       {
-        width = seq.getLength();
+        if (first || seq.getLength() > width)
+        {
+          width = seq.getLength();
+          first = false;
+        }
       }
+      return width;
     }
-
-    return width;
   }
 
   /**
@@ -839,39 +923,42 @@ public class SequenceGroup implements AnnotatedCollectionI
    */
   public SequenceI[] getSequencesInOrder(AlignmentI al, boolean trim)
   {
-    int sSize = sequences.size();
-    int alHeight = al.getHeight();
+    synchronized (sequences)
+    {
+      int sSize = sequences.size();
+      int alHeight = al.getHeight();
 
-    SequenceI[] seqs = new SequenceI[(trim) ? sSize : alHeight];
+      SequenceI[] seqs = new SequenceI[(trim) ? sSize : alHeight];
 
-    int index = 0;
-    for (int i = 0; i < alHeight && index < sSize; i++)
-    {
-      if (sequences.contains(al.getSequenceAt(i)))
+      int index = 0;
+      for (int i = 0; i < alHeight && index < sSize; i++)
       {
-        seqs[(trim) ? index : i] = al.getSequenceAt(i);
-        index++;
+        if (sequences.contains(al.getSequenceAt(i)))
+        {
+          seqs[(trim) ? index : i] = al.getSequenceAt(i);
+          index++;
+        }
       }
-    }
-    if (index == 0)
-    {
-      return null;
-    }
-    if (!trim)
-    {
-      return seqs;
-    }
-    if (index < seqs.length)
-    {
-      SequenceI[] dummy = seqs;
-      seqs = new SequenceI[index];
-      while (--index >= 0)
+      if (index == 0)
+      {
+        return null;
+      }
+      if (!trim)
       {
-        seqs[index] = dummy[index];
-        dummy[index] = null;
+        return seqs;
+      }
+      if (index < seqs.length)
+      {
+        SequenceI[] dummy = seqs;
+        seqs = new SequenceI[index];
+        while (--index >= 0)
+        {
+          seqs[index] = dummy[index];
+          dummy[index] = null;
+        }
       }
+      return seqs;
     }
-    return seqs;
   }
 
   /**
@@ -894,6 +981,7 @@ public class SequenceGroup implements AnnotatedCollectionI
   /**
    * @return the representative sequence for this group
    */
+  @Override
   public SequenceI getSeqrep()
   {
     return seqrep;
@@ -906,6 +994,7 @@ public class SequenceGroup implements AnnotatedCollectionI
    * @param seqrep
    *          the seqrep to set (null means no sequence representative)
    */
+  @Override
   public void setSeqrep(SequenceI seqrep)
   {
     this.seqrep = seqrep;
@@ -915,17 +1004,13 @@ public class SequenceGroup implements AnnotatedCollectionI
    * 
    * @return true if group has a sequence representative
    */
+  @Override
   public boolean hasSeqrep()
   {
     return seqrep != null;
   }
 
   /**
-   * visibility of rows or represented rows covered by group
-   */
-  private boolean hidereps = false;
-
-  /**
    * set visibility of sequences covered by (if no sequence representative is
    * defined) or represented by this group.
    * 
@@ -947,11 +1032,6 @@ public class SequenceGroup implements AnnotatedCollectionI
   }
 
   /**
-   * visibility of columns intersecting this group
-   */
-  private boolean hidecols = false;
-
-  /**
    * set intended visibility of columns covered by this group
    * 
    * @param visibility
@@ -985,23 +1065,14 @@ public class SequenceGroup implements AnnotatedCollectionI
   {
     SequenceGroup sgroup = new SequenceGroup(this);
     SequenceI[] insect = getSequencesInOrder(alignment);
-    sgroup.sequences = new Vector();
+    sgroup.sequences = new ArrayList<>();
     for (int s = 0; insect != null && s < insect.length; s++)
     {
       if (map == null || map.containsKey(insect[s]))
       {
-        sgroup.sequences.addElement(insect[s]);
+        sgroup.sequences.add(insect[s]);
       }
     }
-    // Enumeration en =getSequences(hashtable).elements();
-    // while (en.hasMoreElements())
-    // {
-    // SequenceI elem = (SequenceI) en.nextElement();
-    // if (alignment.getSequences().contains(elem))
-    // {
-    // sgroup.addSequence(elem, false);
-    // }
-    // }
     return sgroup;
   }
 
@@ -1022,13 +1093,6 @@ public class SequenceGroup implements AnnotatedCollectionI
     this.showNonconserved = displayNonconserved;
   }
 
-  AlignmentAnnotation consensus = null, conservation = null;
-
-  /**
-   * flag indicating if consensus histogram should be rendered
-   */
-  private boolean showConsensusHistogram;
-
   /**
    * set this alignmentAnnotation object as the one used to render consensus
    * annotation
@@ -1045,7 +1109,8 @@ public class SequenceGroup implements AnnotatedCollectionI
 
   /**
    * 
-   * @return automatically calculated consensus row
+   * @return automatically calculated consensus row note: the row is a stub if a
+   *         consensus calculation has not yet been performed on the group
    */
   public AlignmentAnnotation getConsensus()
   {
@@ -1220,28 +1285,31 @@ public class SequenceGroup implements AnnotatedCollectionI
   {
     // TODO add in other methods like 'getAlignmentAnnotation(String label),
     // etc'
-    ArrayList<AlignmentAnnotation> annot = new ArrayList<AlignmentAnnotation>();
-    for (SequenceI seq : sequences)
+    ArrayList<AlignmentAnnotation> annot = new ArrayList<>();
+    synchronized (sequences)
     {
-      AlignmentAnnotation[] aa = seq.getAnnotation();
-      if (aa != null)
+      for (SequenceI seq : sequences)
       {
-        for (AlignmentAnnotation al : aa)
+        AlignmentAnnotation[] aa = seq.getAnnotation();
+        if (aa != null)
         {
-          if (al.groupRef == this)
+          for (AlignmentAnnotation al : aa)
           {
-            annot.add(al);
+            if (al.groupRef == this)
+            {
+              annot.add(al);
+            }
           }
         }
       }
-    }
-    if (consensus != null)
-    {
-      annot.add(consensus);
-    }
-    if (conservation != null)
-    {
-      annot.add(conservation);
+      if (consensus != null)
+      {
+        annot.add(consensus);
+      }
+      if (conservation != null)
+      {
+        annot.add(conservation);
+      }
     }
     return annot.toArray(new AlignmentAnnotation[0]);
   }
@@ -1249,15 +1317,16 @@ public class SequenceGroup implements AnnotatedCollectionI
   @Override
   public Iterable<AlignmentAnnotation> findAnnotation(String calcId)
   {
-    ArrayList<AlignmentAnnotation> aa = new ArrayList<AlignmentAnnotation>();
-    for (AlignmentAnnotation a : getAlignmentAnnotation())
-    {
-      if (a.getCalcId() == calcId)
-      {
-        aa.add(a);
-      }
-    }
-    return aa;
+    return AlignmentAnnotation.findAnnotation(
+            Arrays.asList(getAlignmentAnnotation()), calcId);
+  }
+
+  @Override
+  public Iterable<AlignmentAnnotation> findAnnotations(SequenceI seq,
+          String calcId, String label)
+  {
+    return AlignmentAnnotation.findAnnotations(
+            Arrays.asList(getAlignmentAnnotation()), seq, calcId, label);
   }
 
   /**
@@ -1268,34 +1337,63 @@ public class SequenceGroup implements AnnotatedCollectionI
    */
   public boolean hasAnnotation(String calcId)
   {
-    if (calcId != null && !"".equals(calcId))
-    {
-      for (AlignmentAnnotation a : getAlignmentAnnotation())
-      {
-        if (a.getCalcId() == calcId)
-        {
-          return true;
-        }
-      }
-    }
-    return false;
+    return AlignmentAnnotation
+            .hasAnnotation(Arrays.asList(getAlignmentAnnotation()), calcId);
   }
 
+  /**
+   * Remove all sequences from the group (leaving other properties unchanged).
+   */
   public void clear()
   {
-    sequences.clear();
+    synchronized (sequences)
+    {
+      int before = sequences.size();
+      sequences.clear();
+      changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before,
+              sequences.size());
+    }
   }
 
-  private AnnotatedCollectionI context;
+  /**
+   * Sets the alignment or group context for this group, and whether it is
+   * defined as a group
+   * 
+   * @param ctx
+   *          the context for the group
+   * @param defined
+   *          whether the group is defined on the alignment or is just a
+   *          selection
+   * @throws IllegalArgumentException
+   *           if setting the context would result in a circular reference chain
+   */
+  public void setContext(AnnotatedCollectionI ctx, boolean defined)
+  {
+    setContext(ctx);
+    this.isDefined = defined;
+  }
 
   /**
-   * set the alignment or group context for this group
+   * Sets the alignment or group context for this group
    * 
-   * @param context
+   * @param ctx
+   *          the context for the group
+   * @throws IllegalArgumentException
+   *           if setting the context would result in a circular reference chain
    */
-  public void setContext(AnnotatedCollectionI context)
+  public void setContext(AnnotatedCollectionI ctx)
   {
-    this.context = context;
+    AnnotatedCollectionI ref = ctx;
+    while (ref != null)
+    {
+      if (ref == this || ref.getContext() == ctx)
+      {
+        throw new IllegalArgumentException(
+                "Circular reference in SequenceGroup.context");
+      }
+      ref = ref.getContext();
+    }
+    this.context = ctx;
   }
 
   /*
@@ -1308,4 +1406,63 @@ public class SequenceGroup implements AnnotatedCollectionI
   {
     return context;
   }
+
+  public boolean isDefined()
+  {
+    return isDefined;
+  }
+
+  public void setColourScheme(ColourSchemeI scheme)
+  {
+    if (cs == null)
+    {
+      cs = new ResidueShader();
+    }
+    cs.setColourScheme(scheme);
+  }
+
+  public void setGroupColourScheme(ResidueShaderI scheme)
+  {
+    cs = scheme;
+  }
+
+  public ColourSchemeI getColourScheme()
+  {
+    return cs == null ? null : cs.getColourScheme();
+  }
+
+  public ResidueShaderI getGroupColourScheme()
+  {
+    return cs;
+  }
+
+  @Override
+  public boolean isNucleotide()
+  {
+    if (context != null)
+    {
+      return context.isNucleotide();
+    }
+    return false;
+  }
+
+  /**
+   * @param seq
+   * @return true if seq is a member of the group
+   */
+
+  public boolean contains(SequenceI seq1)
+  {
+    return sequences.contains(seq1);
+  }
+
+  /**
+   * @param seq
+   * @param apos
+   * @return true if startRes<=apos and endRes>=apos and seq is in the group
+   */
+  public boolean contains(SequenceI seq, int apos)
+  {
+    return (startRes <= apos && endRes >= apos) && sequences.contains(seq);
+  }
 }