Merge branch 'develop' into bug/JAL-3184restoreGroupVisibility bug/JAL-3184restoreGroupVisibility
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 14 Jan 2019 14:05:22 +0000 (14:05 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 14 Jan 2019 14:05:22 +0000 (14:05 +0000)
src/jalview/gui/FeatureSettings.java
src/jalview/gui/Jalview2XML.java
src/jalview/gui/Jalview2XML_V1.java
src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
src/jalview/viewmodel/seqfeatures/FeatureRendererSettings.java
test/jalview/gui/FeatureSettingsTest.java

index 1358c8f..c310c47 100644 (file)
@@ -62,6 +62,7 @@ import java.io.FileOutputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -135,6 +136,10 @@ public class FeatureSettings extends JPanel
 
   private Map<String, FeatureMatcherSetI> originalFilters;
 
+  private List<String> originalVisibleGroups;
+
+  private List<String> originalHiddenGroups;
+
   final JInternalFrame frame;
 
   JScrollPane scrollPane = new JScrollPane();
@@ -187,6 +192,10 @@ public class FeatureSettings extends JPanel
 
     originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
 
+    originalVisibleGroups = new ArrayList<>(fr.getGroups(true));
+
+    originalHiddenGroups = new ArrayList<>(fr.getGroups(false));
+
     try
     {
       jbInit();
@@ -1236,10 +1245,7 @@ public class FeatureSettings extends JPanel
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        fr.setTransparency(originalTransparency);
-        fr.setFeatureFilters(originalFilters);
-        updateFeatureRenderer(originalData);
-        close();
+        cancel();
       }
     });
 
@@ -1321,6 +1327,22 @@ public class FeatureSettings extends JPanel
     this.add(settingsPane);
   }
 
+  /**
+   * Restores feature type and group visibility, and any filters and
+   * transparency setting, to the values when this dialog was opened. Note this
+   * won't affect any feature types or groups which were added while the dialog
+   * was open.
+   */
+  void cancel()
+  {
+    fr.setTransparency(originalTransparency);
+    fr.setFeatureFilters(originalFilters);
+    fr.setGroupVisibility(originalVisibleGroups, true);
+    fr.setGroupVisibility(originalHiddenGroups, false);
+    updateFeatureRenderer(originalData);
+    close();
+  }
+
   // ///////////////////////////////////////////////////////////////////////
   // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
   // ///////////////////////////////////////////////////////////////////////
index 9285754..c8e887b 100644 (file)
@@ -52,7 +52,6 @@ import jalview.schemabinding.version2.Annotation;
 import jalview.schemabinding.version2.AnnotationColours;
 import jalview.schemabinding.version2.AnnotationElement;
 import jalview.schemabinding.version2.CalcIdParam;
-import jalview.schemabinding.version2.Colour;
 import jalview.schemabinding.version2.CompoundMatcher;
 import jalview.schemabinding.version2.DBRef;
 import jalview.schemabinding.version2.Features;
@@ -4639,125 +4638,9 @@ public class Jalview2XML
       af.viewport.setShowGroupConservation(false);
     }
 
-    // recover feature settings
     if (jms.getFeatureSettings() != null)
     {
-      FeatureRenderer fr = af.alignPanel.getSeqPanel().seqCanvas
-              .getFeatureRenderer();
-      FeaturesDisplayed fdi;
-      af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
-      String[] renderOrder = new String[jms.getFeatureSettings()
-              .getSettingCount()];
-      Map<String, FeatureColourI> featureColours = new Hashtable<>();
-      Map<String, Float> featureOrder = new Hashtable<>();
-
-      for (int fs = 0; fs < jms.getFeatureSettings()
-              .getSettingCount(); fs++)
-      {
-        Setting setting = jms.getFeatureSettings().getSetting(fs);
-        String featureType = setting.getType();
-
-        /*
-         * restore feature filters (if any)
-         */
-        MatcherSet filters = setting.getMatcherSet();
-        if (filters != null)
-        {
-          FeatureMatcherSetI filter = Jalview2XML
-                  .unmarshalFilter(featureType, filters);
-          if (!filter.isEmpty())
-          {
-            fr.setFeatureFilter(featureType, filter);
-          }
-        }
-
-        /*
-         * restore feature colour scheme
-         */
-        Color maxColour = new Color(setting.getColour());
-        if (setting.hasMincolour())
-        {
-          /*
-           * minColour is always set unless a simple colour
-           * (including for colour by label though it doesn't use it)
-           */
-          Color minColour = new Color(setting.getMincolour());
-          Color noValueColour = minColour;
-          NoValueColour noColour = setting.getNoValueColour();
-          if (noColour == NoValueColour.NONE)
-          {
-            noValueColour = null;
-          }
-          else if (noColour == NoValueColour.MAX)
-          {
-            noValueColour = maxColour;
-          }
-          float min = setting.hasMin() ? setting.getMin() : 0f;
-          float max = setting.hasMin() ? setting.getMax() : 1f;
-          FeatureColourI gc = new FeatureColour(minColour, maxColour,
-                  noValueColour, min, max);
-          if (setting.getAttributeNameCount() > 0)
-          {
-            gc.setAttributeName(setting.getAttributeName());
-          }
-          if (setting.hasThreshold())
-          {
-            gc.setThreshold(setting.getThreshold());
-            int threshstate = setting.getThreshstate();
-            // -1 = None, 0 = Below, 1 = Above threshold
-            if (threshstate == 0)
-            {
-              gc.setBelowThreshold(true);
-            }
-            else if (threshstate == 1)
-            {
-              gc.setAboveThreshold(true);
-            }
-          }
-          gc.setAutoScaled(true); // default
-          if (setting.hasAutoScale())
-          {
-            gc.setAutoScaled(setting.getAutoScale());
-          }
-          if (setting.hasColourByLabel())
-          {
-            gc.setColourByLabel(setting.getColourByLabel());
-          }
-          // and put in the feature colour table.
-          featureColours.put(featureType, gc);
-        }
-        else
-        {
-          featureColours.put(featureType,
-                  new FeatureColour(maxColour));
-        }
-        renderOrder[fs] = featureType;
-        if (setting.hasOrder())
-        {
-          featureOrder.put(featureType, setting.getOrder());
-        }
-        else
-        {
-          featureOrder.put(featureType, new Float(
-                  fs / jms.getFeatureSettings().getSettingCount()));
-        }
-        if (setting.getDisplay())
-        {
-          fdi.setVisible(featureType);
-        }
-      }
-      Map<String, Boolean> fgtable = new Hashtable<>();
-      for (int gs = 0; gs < jms.getFeatureSettings().getGroupCount(); gs++)
-      {
-        Group grp = jms.getFeatureSettings().getGroup(gs);
-        fgtable.put(grp.getName(), new Boolean(grp.getDisplay()));
-      }
-      // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
-      // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
-      // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
-      FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
-              fgtable, featureColours, 1.0f, featureOrder);
-      fr.transferSettings(frs);
+      loadFeatureSettings(jms, af);
     }
 
     if (view.getHiddenColumnsCount() > 0)
@@ -4812,6 +4695,160 @@ public class Jalview2XML
   }
 
   /**
+   * Loads feature settings data from project XML and stores
+   * <ul>
+   * <li>feature type visibility in the Viewport</li>
+   * <li>other data in the FeatureRenderer:
+   * <ul>
+   * <li>feature type render order</li>
+   * <li>feature type colours</li>
+   * <li>feature type filters</li>
+   * <li>feature groups and their visibility</li>
+   * </ul>
+   * </li>
+   * </ul>
+   * 
+   * @param jms
+   * @param af
+   */
+  static void loadFeatureSettings(JalviewModelSequence jms, AlignFrame af)
+  {
+    FeatureRenderer fr = af.getFeatureRenderer();
+    FeaturesDisplayed fdi = new FeaturesDisplayed();
+    af.viewport.setFeaturesDisplayed(fdi);
+    String[] renderOrder = new String[jms.getFeatureSettings()
+            .getSettingCount()];
+    Map<String, FeatureColourI> featureColours = new Hashtable<>();
+    Map<String, Float> featureOrder = new Hashtable<>();
+    Map<String, FeatureMatcherSetI> filters = new HashMap<>();
+
+    for (int fs = 0; fs < jms.getFeatureSettings()
+            .getSettingCount(); fs++)
+    {
+      Setting setting = jms.getFeatureSettings().getSetting(fs);
+      String featureType = setting.getType();
+
+      /*
+       * restore feature filters (if any)
+       */
+      MatcherSet matchers = setting.getMatcherSet();
+      if (matchers != null)
+      {
+        FeatureMatcherSetI filter = Jalview2XML
+                .unmarshalFilter(featureType, matchers);
+        if (!filter.isEmpty())
+        {
+          filters.put(featureType, filter);
+        }
+      }
+
+      /*
+       * restore feature colour scheme
+       */
+      FeatureColourI featureColour = loadFeatureColour(setting);
+      featureColours.put(featureType, featureColour);
+
+      renderOrder[fs] = featureType;
+      if (setting.hasOrder())
+      {
+        featureOrder.put(featureType, setting.getOrder());
+      }
+      else
+      {
+        featureOrder.put(featureType, new Float(
+                fs / jms.getFeatureSettings().getSettingCount()));
+      }
+      if (setting.getDisplay())
+      {
+        fdi.setVisible(featureType);
+      }
+    }
+    Map<String, Boolean> fgtable = new Hashtable<>();
+    for (int gs = 0; gs < jms.getFeatureSettings().getGroupCount(); gs++)
+    {
+      Group grp = jms.getFeatureSettings().getGroup(gs);
+      fgtable.put(grp.getName(), new Boolean(grp.getDisplay()));
+    }
+
+    /*
+     * todo: save transparency in project (JAL-1147)
+     */
+    float transparency = 1.0f;
+    FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
+            fgtable, featureColours, transparency, featureOrder, filters);
+    fr.transferSettings(frs);
+  }
+
+  /**
+   * Constructs a FeatureColour from the data saved for one FeatureSetting
+   * <code>setting</code> element
+   * 
+   * @param setting
+   * @return
+   */
+  static FeatureColourI loadFeatureColour(Setting setting)
+  {
+    FeatureColourI featureColour;
+    Color maxColour = new Color(setting.getColour());
+    if (setting.hasMincolour())
+    {
+      /*
+       * minColour is always set unless a simple colour
+       * (including for colour by label though it doesn't use it)
+       */
+      Color minColour = new Color(setting.getMincolour());
+      Color noValueColour = minColour;
+      NoValueColour noColour = setting.getNoValueColour();
+      if (noColour == NoValueColour.NONE)
+      {
+        noValueColour = null;
+      }
+      else if (noColour == NoValueColour.MAX)
+      {
+        noValueColour = maxColour;
+      }
+      float min = setting.hasMin() ? setting.getMin() : 0f;
+      float max = setting.hasMin() ? setting.getMax() : 1f;
+      FeatureColourI gc = new FeatureColour(minColour, maxColour,
+              noValueColour, min, max);
+      if (setting.getAttributeNameCount() > 0)
+      {
+        gc.setAttributeName(setting.getAttributeName());
+      }
+      if (setting.hasThreshold())
+      {
+        gc.setThreshold(setting.getThreshold());
+        int threshstate = setting.getThreshstate();
+        // -1 = None, 0 = Below, 1 = Above threshold
+        if (threshstate == 0)
+        {
+          gc.setBelowThreshold(true);
+        }
+        else if (threshstate == 1)
+        {
+          gc.setAboveThreshold(true);
+        }
+      }
+      gc.setAutoScaled(true); // default
+      if (setting.hasAutoScale())
+      {
+        gc.setAutoScaled(setting.getAutoScale());
+      }
+      if (setting.hasColourByLabel())
+      {
+        gc.setColourByLabel(setting.getColourByLabel());
+      }
+      featureColour = gc;
+    }
+    else
+    {
+      featureColour = new FeatureColour(maxColour);
+    }
+
+    return featureColour;
+  }
+
+  /**
    * Reads saved data to restore Colour by Annotation settings
    * 
    * @param viewAnnColour
index 331e738..d2ba95b 100755 (executable)
@@ -21,6 +21,7 @@
 package jalview.gui;
 
 import jalview.analysis.Conservation;
+import jalview.api.FeatureColourI;
 import jalview.binding.Annotation;
 import jalview.binding.AnnotationElement;
 import jalview.binding.Features;
@@ -40,6 +41,7 @@ import jalview.datamodel.SequenceFeature;
 import jalview.io.FileFormat;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ColourSchemeProperty;
+import jalview.schemes.FeatureColour;
 import jalview.structure.StructureSelectionManager;
 import jalview.util.MessageManager;
 import jalview.util.jarInputStreamProvider;
@@ -428,8 +430,8 @@ public class Jalview2XML_V1
 
     if (jms.getFeatureSettings() != null)
     {
-      Hashtable featuresDisplayed = new Hashtable();
-      Hashtable featureColours = new Hashtable();
+      Hashtable<String, Integer> featuresDisplayed = new Hashtable<>();
+      Hashtable<String, FeatureColourI> featureColours = new Hashtable<>();
       String[] renderOrder = new String[jms.getFeatureSettings()
               .getSettingCount()];
       for (int fs = 0; fs < jms.getFeatureSettings()
@@ -438,7 +440,7 @@ public class Jalview2XML_V1
         Setting setting = jms.getFeatureSettings().getSetting(fs);
 
         featureColours.put(setting.getType(),
-                new java.awt.Color(setting.getColour()));
+                new FeatureColour(new java.awt.Color(setting.getColour())));
 
         renderOrder[fs] = setting.getType();
 
@@ -449,7 +451,8 @@ public class Jalview2XML_V1
         }
       }
       FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
-              new Hashtable(), featureColours, 1.0f, null);
+              new Hashtable<String, Boolean>(), featureColours, 1.0f, null,
+              null);
       af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
               .transferSettings(frs);
     }
index 553f813..ce65f9f 100644 (file)
@@ -43,6 +43,7 @@ import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -119,6 +120,7 @@ public abstract class FeatureRendererModel
     this.featureColours = fr.featureColours;
     this.transparency = fr.transparency;
     this.featureOrder = fr.featureOrder;
+    this.featureFilters = fr.featureFilters;
   }
 
   /**
@@ -859,30 +861,27 @@ public abstract class FeatureRendererModel
   }
 
   /**
-   * get visible or invisible groups
+   * Answers a (possibly empty) list of visible or invisible feature groups
    * 
    * @param visible
-   *          true to return visible groups, false to return hidden ones.
-   * @return list of groups
+   *          true to return visible groups, false to return hidden ones
+   * @return
    */
   @Override
   public List<String> getGroups(boolean visible)
   {
+    List<String> groups = new ArrayList<>();
     if (featureGroups != null)
     {
-      List<String> gp = new ArrayList<>();
-
-      for (String grp : featureGroups.keySet())
+      for (Entry<String, Boolean> grp : featureGroups.entrySet())
       {
-        Boolean state = featureGroups.get(grp);
-        if (state.booleanValue() == visible)
+        if (grp.getValue() == visible)
         {
-          gp.add(grp);
+          groups.add(grp.getKey());
         }
       }
-      return gp;
     }
-    return null;
+    return groups;
   }
 
   @Override
index f594453..cd6f51b 100644 (file)
@@ -30,7 +30,18 @@ import java.util.Iterator;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
-public class FeatureRendererSettings implements Cloneable
+/**
+ * A data bean that holds
+ * <ul>
+ * <li>feature types and their render order</li>
+ * <li>feature groups and their visibility</li>
+ * <li>colour for each feature type</li>
+ * <li>filters (if any) for each feature type</li>
+ * <li>feature colour transparency</li>
+ * </ul>
+ * Note that feature type visibility settings are not held here.
+ */
+public class FeatureRendererSettings
 {
   String[] renderOrder;
 
@@ -53,19 +64,31 @@ public class FeatureRendererSettings implements Cloneable
 
   Map<String, Float> featureOrder;
 
+  /**
+   * Constructor
+   * 
+   * @param renderOrder
+   * @param featureGroups
+   * @param featureColours
+   * @param transparency
+   * @param featureOrder
+   * @param filters
+   */
   public FeatureRendererSettings(String[] renderOrder,
           Map<String, Boolean> featureGroups,
           Map<String, FeatureColourI> featureColours, float transparency,
-          Map<String, Float> featureOrder)
+          Map<String, Float> featureOrder,
+          Map<String, FeatureMatcherSetI> filters)
   {
     super();
     this.renderOrder = Arrays.copyOf(renderOrder, renderOrder.length);
-    this.featureGroups = new ConcurrentHashMap<String, Boolean>(
+    this.featureGroups = new ConcurrentHashMap<>(
             featureGroups);
-    this.featureColours = new ConcurrentHashMap<String, FeatureColourI>(
+    this.featureColours = new ConcurrentHashMap<>(
             featureColours);
     this.transparency = transparency;
-    this.featureOrder = new ConcurrentHashMap<String, Float>(featureOrder);
+    this.featureOrder = new ConcurrentHashMap<>(featureOrder);
+    featureFilters = filters;
   }
 
   /**
@@ -77,10 +100,10 @@ public class FeatureRendererSettings implements Cloneable
           jalview.viewmodel.seqfeatures.FeatureRendererModel fr)
   {
     renderOrder = null;
-    featureGroups = new ConcurrentHashMap<String, Boolean>();
-    featureColours = new ConcurrentHashMap<String, FeatureColourI>();
+    featureGroups = new ConcurrentHashMap<>();
+    featureColours = new ConcurrentHashMap<>();
     featureFilters = new HashMap<>();
-    featureOrder = new ConcurrentHashMap<String, Float>();
+    featureOrder = new ConcurrentHashMap<>();
 
     if (fr.renderOrder != null)
     {
@@ -90,12 +113,12 @@ public class FeatureRendererSettings implements Cloneable
     }
     if (fr.featureGroups != null)
     {
-      this.featureGroups = new ConcurrentHashMap<String, Boolean>(
+      this.featureGroups = new ConcurrentHashMap<>(
               fr.featureGroups);
     }
     if (fr.featureColours != null)
     {
-      this.featureColours = new ConcurrentHashMap<String, FeatureColourI>(
+      this.featureColours = new ConcurrentHashMap<>(
               fr.featureColours);
     }
     Iterator<String> en = fr.featureColours.keySet().iterator();
@@ -118,7 +141,7 @@ public class FeatureRendererSettings implements Cloneable
     this.transparency = fr.transparency;
     if (fr.featureOrder != null)
     {
-      this.featureOrder = new ConcurrentHashMap<String, Float>(
+      this.featureOrder = new ConcurrentHashMap<>(
               fr.featureOrder);
     }
   }
index 6ddebf8..228d924 100644 (file)
@@ -1,6 +1,11 @@
 package jalview.gui;
 
+import static jalview.gui.FeatureSettings.COLOUR_COLUMN;
+import static jalview.gui.FeatureSettings.FILTER_COLUMN;
+import static jalview.gui.FeatureSettings.SHOW_COLUMN;
+import static jalview.gui.FeatureSettings.TYPE_COLUMN;
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 
@@ -11,6 +16,7 @@ import jalview.datamodel.features.FeatureMatcher;
 import jalview.datamodel.features.FeatureMatcherSet;
 import jalview.datamodel.features.FeatureMatcherSetI;
 import jalview.io.DataSourceType;
+import jalview.io.FileFormat;
 import jalview.io.FileLoader;
 import jalview.schemes.FeatureColour;
 import jalview.util.matcher.Condition;
@@ -33,7 +39,7 @@ public class FeatureSettingsTest
   public void testSaveLoad() throws IOException
   {
     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
-            ">Seq1\nACDEFGHIKLM", DataSourceType.PASTE);
+            ">Seq1\nACDEFGHIKLM", DataSourceType.PASTE, FileFormat.Fasta);
     SequenceI seq1 = af.getViewport().getAlignment().getSequenceAt(0);
 
     /*
@@ -188,4 +194,186 @@ public class FeatureSettingsTest
     });
     seq.addSequenceFeature(sf);
   }
+
+  /**
+   * Test of the behaviour of the Show / Hide checkbox
+   */
+  @Test(groups = "Functional")
+  public void testHideShow()
+  {
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
+            ">Seq1\nACDEFGHIKLM", DataSourceType.PASTE, FileFormat.Fasta);
+    SequenceI seq1 = af.getViewport().getAlignment().getSequenceAt(0);
+    seq1.addSequenceFeature(
+            new SequenceFeature("type1", "", 1, 4, "group1"));
+
+    FeatureRenderer fr = af.getFeatureRenderer();
+    fr.setColour("type1", new FeatureColour(Color.red));
+
+    af.showSeqFeatures_actionPerformed(null);
+    FeatureSettings dialog = new FeatureSettings(af);
+
+    assertTrue(fr.getDisplayedFeatureTypes().contains("type1"));
+
+    /*
+     * check the table has one row, for type1, visible, no filter, red
+     */
+    assertEquals(dialog.table.getRowCount(), 1);
+    assertEquals(dialog.table.getColumnCount(), 4);
+    assertEquals(dialog.table.getModel().getValueAt(0, TYPE_COLUMN),
+            "type1");
+    FeatureColourI colour = (FeatureColourI) dialog.table.getModel()
+            .getValueAt(0, COLOUR_COLUMN);
+    assertTrue(colour.isSimpleColour());
+    assertEquals(colour.getColour(), Color.red);
+    FeatureMatcherSetI filter = (FeatureMatcherSetI) dialog.table.getModel()
+            .getValueAt(0, FILTER_COLUMN);
+    assertTrue(filter.isEmpty());
+    assertEquals(dialog.table.getModel().getValueAt(0, SHOW_COLUMN),
+            Boolean.TRUE);
+
+    /*
+     * set feature type to hidden by clicking the checkbox in column 4,
+     * and verify that now no feature types are displayed
+     */
+    dialog.table.setValueAt(Boolean.FALSE, 0, SHOW_COLUMN);
+    assertTrue(fr.getDisplayedFeatureTypes().isEmpty());
+
+    /*
+     * set feature type back to visible by clicking the checkbox in column 4,
+     * and verify that now the feature type is displayed
+     */
+    dialog.table.setValueAt(Boolean.TRUE, 0, SHOW_COLUMN);
+    assertTrue(fr.getDisplayedFeatureTypes().contains("type1"));
+  }
+
+  /**
+   * Test Cancel resets any changes made
+   */
+  @Test(groups = "Functional")
+  public void testCancel()
+  {
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
+            ">Seq1\nACDEFGHIKLM", DataSourceType.PASTE, FileFormat.Fasta);
+    SequenceI seq1 = af.getViewport().getAlignment().getSequenceAt(0);
+    seq1.addSequenceFeature(
+            new SequenceFeature("type1", "", 1, 4, "group1"));
+    seq1.addSequenceFeature(
+            new SequenceFeature("type2", "", 1, 4, "group2"));
+  
+    /*
+     * set type1: red, filter 'Label Contains metal'
+     *     type2: variable colour red:blue, no filter
+     */
+    FeatureRenderer fr = af.getFeatureRenderer();
+    fr.setColour("type1", new FeatureColour(Color.red));
+    fr.setColour("type2", new FeatureColour(Color.red, Color.blue, 0f, 1f));
+    FeatureMatcherSetI f = new FeatureMatcherSet();
+    f.and(FeatureMatcher.byLabel(Condition.Contains, "metal"));
+    fr.setFeatureFilter("type1", f);
+
+    af.showSeqFeatures_actionPerformed(null);
+    FeatureSettings dialog = new FeatureSettings(af);
+  
+    assertTrue(fr.getDisplayedFeatureTypes().contains("type1"));
+    assertTrue(fr.getDisplayedFeatureTypes().contains("type2"));
+  
+    /*
+     * check the table has two rows, for type1 and type2
+     * note type2 is shown first; the initial ordering is 'random' as driven by
+     * the Set of feature groups for the sequence
+     */
+    assertEquals(dialog.table.getRowCount(), 2);
+    assertEquals(dialog.table.getColumnCount(), 4);
+
+    assertEquals(dialog.table.getModel().getValueAt(0, TYPE_COLUMN),
+            "type2");
+    FeatureColourI colour = (FeatureColourI) dialog.table.getModel()
+            .getValueAt(0, COLOUR_COLUMN);
+    assertFalse(colour.isSimpleColour());
+    assertEquals(colour.getMinColour(), Color.red);
+    assertEquals(colour.getMaxColour(), Color.blue);
+    FeatureMatcherSetI filter = (FeatureMatcherSetI) dialog.table.getModel()
+            .getValueAt(0, FILTER_COLUMN);
+    assertTrue(filter.isEmpty());
+    assertEquals(dialog.table.getModel().getValueAt(0, SHOW_COLUMN),
+            Boolean.TRUE);
+
+    assertEquals(dialog.table.getModel().getValueAt(1, TYPE_COLUMN),
+            "type1");
+    colour = (FeatureColourI) dialog.table.getModel().getValueAt(1,
+            COLOUR_COLUMN);
+    assertTrue(colour.isSimpleColour());
+    assertEquals(colour.getColour(), Color.red);
+    filter = (FeatureMatcherSetI) dialog.table.getModel().getValueAt(1,
+            FILTER_COLUMN);
+    assertFalse(filter.isEmpty());
+    assertEquals(dialog.table.getModel().getValueAt(1, SHOW_COLUMN),
+            Boolean.TRUE);
+  
+    /*
+     * Make some changes:
+     * - set type1 to hidden by clicking the checkbox in column 4
+     * - change type2 to plain colour green
+     * We can do these by setting values in the table model -
+     * updateFeatureRenderer then updates visibility and colour
+     */
+    dialog.table.setValueAt(Boolean.FALSE, 1, SHOW_COLUMN);
+    dialog.table.setValueAt(new FeatureColour(Color.green), 0,
+            COLOUR_COLUMN);
+
+    /*
+     * - remove the filter on type1
+     * - set a filter on type2
+     * We have to do this directly on FeatureRendererModel (as done in the
+     * application from FeatureTypeSettings)
+     */
+    fr.setFeatureFilter("type1", null);
+    fr.setFeatureFilter("type2",
+            FeatureMatcherSet.fromString("Label matches Metal"));
+
+    /*
+     * verify the changes reached the FeatureRender
+     */
+    assertFalse(fr.getDisplayedFeatureTypes().contains("type1"));
+    assertNull(fr.getFeatureFilter("type1"));
+    colour = fr.getFeatureColours().get("type2");
+    assertTrue(colour.isSimpleColour());
+    assertEquals(colour.getColour(), Color.green);
+    filter = fr.getFeatureFilter("type2");
+    assertFalse(filter.isEmpty());
+    assertEquals(filter.toStableString(), "Label Matches Metal");
+
+    /*
+     * add a new feature type and 'notify' FeatureRenderer
+     * it should appear in the first row of FeatureSettings table
+     */
+    seq1.addSequenceFeature(
+            new SequenceFeature("type3", "desc", 4, 5, null));
+    fr.featuresAdded();
+    assertEquals(dialog.table.getRowCount(), 3);
+    assertEquals(dialog.table.getModel().getValueAt(0, TYPE_COLUMN),
+            "type3");
+    assertEquals(dialog.table.getModel().getValueAt(0, SHOW_COLUMN),
+            Boolean.TRUE);
+    assertTrue(fr.getDisplayedFeatureTypes().contains("type3"));
+
+    /*
+     * cancel the dialog, and verify FeatureRenderer data has been reset
+     */
+    dialog.cancel();
+    // type1 has been reset to visible:
+    assertTrue(fr.getDisplayedFeatureTypes().contains("type1"));
+    // type3 has been reset to not visible:
+    assertTrue(fr.getDisplayedFeatureTypes().contains("type3"));
+    // type2 colour has been reset to graduated:
+    colour = fr.getFeatureColours().get("type2");
+    assertFalse(colour.isSimpleColour());
+    assertEquals(colour.getMaxColour(), Color.blue);
+    // type2 filter has been reset to none (held as null):
+    assertNull(fr.getFeatureFilter("type2"));
+    // type1 filter has been reset:
+    filter = fr.getFeatureFilter("type1");
+    assertEquals(filter.toStableString(), "Label Contains metal");
+  }
 }