JAL-3063 Save/Load user colour scheme using JAXB
[jalview.git] / src / jalview / gui / FeatureSettings.java
index 63f1b13..90d7c35 100644 (file)
@@ -22,20 +22,22 @@ package jalview.gui;
 
 import jalview.api.FeatureColourI;
 import jalview.api.FeatureSettingsControllerI;
-import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SequenceI;
+import jalview.datamodel.features.FeatureMatcherI;
+import jalview.datamodel.features.FeatureMatcherSet;
+import jalview.datamodel.features.FeatureMatcherSetI;
 import jalview.gui.Help.HelpId;
 import jalview.io.JalviewFileChooser;
 import jalview.io.JalviewFileView;
-import jalview.schemabinding.version2.JalviewUserColours;
 import jalview.schemes.FeatureColour;
-import jalview.util.Format;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
-import jalview.util.QuickSort;
-import jalview.viewmodel.AlignmentViewport;
-import jalview.ws.dbsources.das.api.jalviewSourceI;
+import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
+import jalview.xml.binding.jalview.JalviewUserColours;
+import jalview.xml.binding.jalview.JalviewUserColours.Colour;
+import jalview.xml.binding.jalview.JalviewUserColours.Filter;
+import jalview.xml.binding.jalview.ObjectFactory;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
@@ -44,6 +46,7 @@ import java.awt.Dimension;
 import java.awt.Font;
 import java.awt.Graphics;
 import java.awt.GridLayout;
+import java.awt.Point;
 import java.awt.Rectangle;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
@@ -61,13 +64,14 @@ import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.Vector;
 
 import javax.help.HelpSetException;
 import javax.swing.AbstractCellEditor;
@@ -86,36 +90,57 @@ import javax.swing.JPanel;
 import javax.swing.JPopupMenu;
 import javax.swing.JScrollPane;
 import javax.swing.JSlider;
-import javax.swing.JTabbedPane;
 import javax.swing.JTable;
 import javax.swing.ListSelectionModel;
 import javax.swing.SwingConstants;
-import javax.swing.SwingUtilities;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
 import javax.swing.table.AbstractTableModel;
 import javax.swing.table.TableCellEditor;
 import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.Marshaller;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamReader;
 
 public class FeatureSettings extends JPanel
         implements FeatureSettingsControllerI
 {
-  DasSourceBrowser dassourceBrowser;
+  private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
+          .getString("label.sequence_feature_colours");
 
-  jalview.ws.DasSequenceFeatureFetcher dasFeatureFetcher;
+  /*
+   * column indices of fields in Feature Settings table
+   */
+  static final int TYPE_COLUMN = 0;
+
+  static final int COLOUR_COLUMN = 1;
+
+  static final int FILTER_COLUMN = 2;
+
+  static final int SHOW_COLUMN = 3;
 
-  JPanel settingsPane = new JPanel();
+  private static final int COLUMN_COUNT = 4;
 
-  JPanel dasSettingsPane = new JPanel();
+  private static final int MIN_WIDTH = 400;
+
+  private static final int MIN_HEIGHT = 400;
 
   final FeatureRenderer fr;
 
   public final AlignFrame af;
 
+  /*
+   * 'original' fields hold settings to restore on Cancel
+   */
   Object[][] originalData;
 
   private float originalTransparency;
 
+  private Map<String, FeatureMatcherSetI> originalFilters;
+
   final JInternalFrame frame;
 
   JScrollPane scrollPane = new JScrollPane();
@@ -126,29 +151,47 @@ public class FeatureSettings extends JPanel
 
   JSlider transparency = new JSlider();
 
-  JPanel transPanel = new JPanel(new GridLayout(1, 2));
-
-  private static final int MIN_WIDTH = 400;
-
-  private static final int MIN_HEIGHT = 400;
-  
-  /**
+  /*
    * when true, constructor is still executing - so ignore UI events
    */
   protected volatile boolean inConstruction = true;
 
+  int selectedRow = -1;
+
+  JButton fetchDAS = new JButton();
+
+  JButton saveDAS = new JButton();
+
+  JButton cancelDAS = new JButton();
+
+  boolean resettingTable = false;
+
+  /*
+   * true when Feature Settings are updating from feature renderer
+   */
+  private boolean handlingUpdate = false;
+
+  /*
+   * holds {featureCount, totalExtent} for each feature type
+   */
+  Map<String, float[]> typeWidth = null;
+
   /**
    * Constructor
    * 
    * @param af
    */
-  public FeatureSettings(AlignFrame af)
+  public FeatureSettings(AlignFrame alignFrame)
   {
-    this.af = af;
+    this.af = alignFrame;
     fr = af.getFeatureRenderer();
-    // allow transparency to be recovered
-    transparency.setMaximum(100
-            - (int) ((originalTransparency = fr.getTransparency()) * 100));
+
+    // save transparency for restore on Cancel
+    originalTransparency = fr.getTransparency();
+    int originalTransparencyAsPercent = (int) (originalTransparency * 100);
+    transparency.setMaximum(100 - originalTransparencyAsPercent);
+
+    originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
 
     try
     {
@@ -163,25 +206,48 @@ public class FeatureSettings extends JPanel
       @Override
       public String getToolTipText(MouseEvent e)
       {
-        if (table.columnAtPoint(e.getPoint()) == 0)
+        String tip = null;
+        int column = table.columnAtPoint(e.getPoint());
+        switch (column)
         {
-          /*
-           * Tooltip for feature name only
-           */
-          return JvSwingUtils.wrapTooltip(true, MessageManager
+        case TYPE_COLUMN:
+          tip = JvSwingUtils.wrapTooltip(true, MessageManager
                   .getString("label.feature_settings_click_drag"));
+          break;
+        case FILTER_COLUMN:
+          int row = table.rowAtPoint(e.getPoint());
+          FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
+                  column);
+          tip = o.isEmpty()
+                  ? MessageManager.getString("label.filters_tooltip")
+                  : o.toString();
+          break;
+        default:
+          break;
         }
-        return null;
+        return tip;
       }
     };
     table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
     table.setFont(new Font("Verdana", Font.PLAIN, 12));
-    table.setDefaultRenderer(Color.class, new ColorRenderer());
-
-    table.setDefaultEditor(Color.class, new ColorEditor(this));
 
+    // table.setDefaultRenderer(Color.class, new ColorRenderer());
+    // table.setDefaultEditor(Color.class, new ColorEditor(this));
+    //
     table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
     table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
+
+    table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
+    table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
+
+    TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
+            new ColorRenderer(), new ColorEditor(this));
+    table.addColumn(colourColumn);
+
+    TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
+            new FilterRenderer(), new FilterEditor(this));
+    table.addColumn(filterColumn);
+
     table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
 
     table.addMouseListener(new MouseAdapter()
@@ -190,11 +256,12 @@ public class FeatureSettings extends JPanel
       public void mousePressed(MouseEvent evt)
       {
         selectedRow = table.rowAtPoint(evt.getPoint());
+        String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
         if (evt.isPopupTrigger())
         {
-          popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0),
-                  table.getValueAt(selectedRow, 1), fr.getMinMax(),
-                  evt.getX(), evt.getY());
+          Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
+          popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
+                  evt.getY());
         }
         else if (evt.getClickCount() == 2)
         {
@@ -202,8 +269,7 @@ public class FeatureSettings extends JPanel
           boolean toggleSelection = Platform.isControlDown(evt);
           boolean extendSelection = evt.isShiftDown();
           fr.ap.alignFrame.avc.markColumnsContainingFeatures(
-                  invertSelection, extendSelection, toggleSelection,
-                  (String) table.getValueAt(selectedRow, 0));
+                  invertSelection, extendSelection, toggleSelection, type);
         }
       }
 
@@ -214,9 +280,10 @@ public class FeatureSettings extends JPanel
         selectedRow = table.rowAtPoint(evt.getPoint());
         if (evt.isPopupTrigger())
         {
-          popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0),
-                  table.getValueAt(selectedRow, 1), fr.getMinMax(),
-                  evt.getX(), evt.getY());
+          String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
+          Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
+          popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
+                  evt.getY());
         }
       }
     });
@@ -253,9 +320,6 @@ public class FeatureSettings extends JPanel
     // MessageManager.getString("label.feature_settings_click_drag")));
     scrollPane.setViewportView(table);
 
-    dassourceBrowser = new DasSourceBrowser(this);
-    dasSettingsPane.add(dassourceBrowser, BorderLayout.CENTER);
-
     if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
     {
       fr.findAllFeatures(true); // display everything!
@@ -272,8 +336,8 @@ public class FeatureSettings extends JPanel
         if (!fs.resettingTable && !fs.handlingUpdate)
         {
           fs.handlingUpdate = true;
-          fs.resetTable(null); // new groups may be added with new seuqence
-          // feature types only
+          fs.resetTable(null);
+          // new groups may be added with new sequence feature types only
           fs.handlingUpdate = false;
         }
       }
@@ -286,13 +350,13 @@ public class FeatureSettings extends JPanel
     {
       Desktop.addInternalFrame(frame,
               MessageManager.getString("label.sequence_feature_settings"),
-              475, 480);
+              600, 480);
     }
     else
     {
       Desktop.addInternalFrame(frame,
               MessageManager.getString("label.sequence_feature_settings"),
-              400, 450);
+              600, 450);
     }
     frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
 
@@ -304,14 +368,13 @@ public class FeatureSettings extends JPanel
                       javax.swing.event.InternalFrameEvent evt)
               {
                 fr.removePropertyChangeListener(change);
-                dassourceBrowser.fs = null;
               };
             });
     frame.setLayer(JLayeredPane.PALETTE_LAYER);
     inConstruction = false;
   }
 
-  protected void popupSort(final int selectedRow, final String type,
+  protected void popupSort(final int rowSelected, final String type,
           final Object typeCol, final Map<String, float[][]> minmax, int x,
           int y)
   {
@@ -351,84 +414,70 @@ public class FeatureSettings extends JPanel
 
     });
     men.add(dens);
-    if (minmax != null)
+
+    /*
+     * variable colour options include colour by label, by score,
+     * by selected attribute text, or attribute value
+     */
+    final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
+            MessageManager.getString("label.variable_colour"));
+    mxcol.setSelected(!featureColour.isSimpleColour());
+    men.add(mxcol);
+    mxcol.addActionListener(new ActionListener()
     {
-      final float[][] typeMinMax = minmax.get(type);
-      /*
-       * final JCheckBoxMenuItem chb = new JCheckBoxMenuItem("Vary Height"); //
-       * this is broken at the moment and isn't that useful anyway!
-       * chb.setSelected(minmax.get(type) != null); chb.addActionListener(new
-       * ActionListener() {
-       * 
-       * public void actionPerformed(ActionEvent e) {
-       * chb.setState(chb.getState()); if (chb.getState()) { minmax.put(type,
-       * null); } else { minmax.put(type, typeMinMax); } }
-       * 
-       * });
-       * 
-       * men.add(chb);
-       */
-      if (typeMinMax != null && typeMinMax[0] != null)
-      {
-        // if (table.getValueAt(row, column));
-        // graduated colourschemes for those where minmax exists for the
-        // positional features
-        final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
-                "Graduated Colour");
-        mxcol.setSelected(!featureColour.isSimpleColour());
-        men.add(mxcol);
-        mxcol.addActionListener(new ActionListener()
-        {
-          JColorChooser colorChooser;
+      JColorChooser colorChooser;
 
-          @Override
-          public void actionPerformed(ActionEvent e)
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        if (e.getSource() == mxcol)
+        {
+          if (featureColour.isSimpleColour())
           {
-            if (e.getSource() == mxcol)
-            {
-              if (featureColour.isSimpleColour())
-              {
-                FeatureColourChooser fc = new FeatureColourChooser(me.fr,
-                        type);
-                fc.addActionListener(this);
-              }
-              else
-              {
-                // bring up simple color chooser
-                colorChooser = new JColorChooser();
-                JDialog dialog = JColorChooser.createDialog(me,
-                        "Select new Colour", true, // modal
-                        colorChooser, this, // OK button handler
-                        null); // no CANCEL button handler
-                colorChooser.setColor(featureColour.getMaxColour());
-                dialog.setVisible(true);
-              }
-            }
-            else
-            {
-              if (e.getSource() instanceof FeatureColourChooser)
-              {
-                FeatureColourChooser fc = (FeatureColourChooser) e
-                        .getSource();
-                table.setValueAt(fc.getLastColour(), selectedRow, 1);
-                table.validate();
-              }
-              else
-              {
-                // probably the color chooser!
-                table.setValueAt(new FeatureColour(colorChooser.getColor()),
-                        selectedRow, 1);
-                table.validate();
-                me.updateFeatureRenderer(
-                        ((FeatureTableModel) table.getModel()).getData(),
-                        false);
-              }
-            }
+            FeatureTypeSettings fc = new FeatureTypeSettings(me.fr, type);
+            fc.addActionListener(this);
           }
-
-        });
+          else
+          {
+            // bring up simple color chooser
+            colorChooser = new JColorChooser();
+            String title = MessageManager
+                    .getString("label.select_colour");
+            JDialog dialog = JColorChooser.createDialog(me,
+                    title, true, // modal
+                    colorChooser, this, // OK button handler
+                    null); // no CANCEL button handler
+            colorChooser.setColor(featureColour.getMaxColour());
+            dialog.setVisible(true);
+          }
+        }
+        else
+        {
+          if (e.getSource() instanceof FeatureTypeSettings)
+          {
+            /*
+             * update after OK in feature colour dialog; the updated
+             * colour will have already been set in the FeatureRenderer
+             */
+            FeatureColourI fci = fr.getFeatureColours().get(type);
+            table.setValueAt(fci, rowSelected, 1);
+            table.validate();
+          }
+          else
+          {
+            // probably the color chooser!
+            table.setValueAt(new FeatureColour(colorChooser.getColor()),
+                    rowSelected, 1);
+            table.validate();
+            me.updateFeatureRenderer(
+                    ((FeatureTableModel) table.getModel()).getData(),
+                    false);
+          }
+        }
       }
-    }
+
+    });
+
     JMenuItem selCols = new JMenuItem(
             MessageManager.getString("label.select_columns_containing"));
     selCols.addActionListener(new ActionListener()
@@ -478,16 +527,6 @@ public class FeatureSettings extends JPanel
     men.show(table, x, y);
   }
 
-  /**
-   * true when Feature Settings are updating from feature renderer
-   */
-  private boolean handlingUpdate = false;
-
-  /**
-   * holds {featureCount, totalExtent} for each feature type
-   */
-  Map<String, float[]> typeWidth = null;
-
   @Override
   synchronized public void discoverAllFeatureData()
   {
@@ -541,21 +580,14 @@ public class FeatureSettings extends JPanel
       public void itemStateChanged(ItemEvent evt)
       {
         fr.setGroupVisibility(check.getText(), check.isSelected());
-        af.alignPanel.getSeqPanel().seqCanvas.repaint();
-        if (af.alignPanel.overviewPanel != null)
-        {
-          af.alignPanel.overviewPanel.updateOverviewImage();
-        }
-
         resetTable(new String[] { grp });
+        af.alignPanel.paintAlignment(true, true);
       }
     });
     groupPanel.add(check);
     return visible;
   }
 
-  boolean resettingTable = false;
-
   synchronized void resetTable(String[] groupChanged)
   {
     if (resettingTable)
@@ -618,7 +650,7 @@ public class FeatureSettings extends JPanel
       }
     }
 
-    Object[][] data = new Object[displayableTypes.size()][3];
+    Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
     int dataIndex = 0;
 
     if (fr.hasRenderOrder())
@@ -641,9 +673,13 @@ public class FeatureSettings extends JPanel
           continue;
         }
 
-        data[dataIndex][0] = type;
-        data[dataIndex][1] = fr.getFeatureStyle(type);
-        data[dataIndex][2] = new Boolean(
+        data[dataIndex][TYPE_COLUMN] = type;
+        data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
+        FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
+        data[dataIndex][FILTER_COLUMN] = featureFilter == null
+                ? new FeatureMatcherSet()
+                : featureFilter;
+        data[dataIndex][SHOW_COLUMN] = new Boolean(
                 af.getViewport().getFeaturesDisplayed().isVisible(type));
         dataIndex++;
         displayableTypes.remove(type);
@@ -657,27 +693,30 @@ public class FeatureSettings extends JPanel
     while (!displayableTypes.isEmpty())
     {
       String type = displayableTypes.iterator().next();
-      data[dataIndex][0] = type;
+      data[dataIndex][TYPE_COLUMN] = type;
 
-      data[dataIndex][1] = fr.getFeatureStyle(type);
-      if (data[dataIndex][1] == null)
+      data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
+      if (data[dataIndex][COLOUR_COLUMN] == null)
       {
         // "Colour has been updated in another view!!"
         fr.clearRenderOrder();
         return;
       }
-
-      data[dataIndex][2] = new Boolean(true);
+      FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
+      data[dataIndex][FILTER_COLUMN] = featureFilter == null
+              ? new FeatureMatcherSet()
+              : featureFilter;
+      data[dataIndex][SHOW_COLUMN] = new Boolean(true);
       dataIndex++;
       displayableTypes.remove(type);
     }
 
     if (originalData == null)
     {
-      originalData = new Object[data.length][3];
+      originalData = new Object[data.length][COLUMN_COUNT];
       for (int i = 0; i < data.length; i++)
       {
-        System.arraycopy(data[i], 0, originalData[i], 0, 3);
+        System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
       }
     }
     else
@@ -698,8 +737,8 @@ public class FeatureSettings extends JPanel
   }
 
   /**
-   * Updates 'originalData' (used for restore on Cancel) if we detect that
-   * changes have been made outwith this dialog
+   * Updates 'originalData' (used for restore on Cancel) if we detect that changes
+   * have been made outwith this dialog
    * <ul>
    * <li>a new feature type added (and made visible)</li>
    * <li>a feature colour changed (in the Amend Features dialog)</li>
@@ -715,27 +754,27 @@ public class FeatureSettings extends JPanel
             .getData();
     for (Object[] row : foundData)
     {
-      String type = (String) row[0];
+      String type = (String) row[TYPE_COLUMN];
       boolean found = false;
       for (Object[] current : currentData)
       {
-        if (type.equals(current[0]))
+        if (type.equals(current[TYPE_COLUMN]))
         {
           found = true;
           /*
            * currently dependent on object equality here;
            * really need an equals method on FeatureColour
            */
-          if (!row[1].equals(current[1]))
+          if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
           {
             /*
              * feature colour has changed externally - update originalData
              */
             for (Object[] original : originalData)
             {
-              if (type.equals(original[0]))
+              if (type.equals(original[TYPE_COLUMN]))
               {
-                original[1] = row[1];
+                original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
                 break;
               }
             }
@@ -748,10 +787,12 @@ public class FeatureSettings extends JPanel
         /*
          * new feature detected - add to original data (on top)
          */
-        Object[][] newData = new Object[originalData.length + 1][3];
+        Object[][] newData = new Object[originalData.length
+                + 1][COLUMN_COUNT];
         for (int i = 0; i < originalData.length; i++)
         {
-          System.arraycopy(originalData[i], 0, newData[i + 1], 0, 3);
+          System.arraycopy(originalData[i], 0, newData[i + 1], 0,
+                  COLUMN_COUNT);
         }
         newData[0] = row;
         originalData = newData;
@@ -761,8 +802,8 @@ public class FeatureSettings extends JPanel
 
   /**
    * Remove from the groups panel any checkboxes for groups that are not in the
-   * foundGroups set. This enables removing a group from the display when the
-   * last feature in that group is deleted.
+   * foundGroups set. This enables removing a group from the display when the last
+   * feature in that group is deleted.
    * 
    * @param foundGroups
    */
@@ -805,10 +846,14 @@ public class FeatureSettings extends JPanel
     }
   }
 
+  /**
+   * Offers a file chooser dialog, and then loads the feature colours and
+   * filters from file in XML format and unmarshals to Jalview feature settings
+   */
   void load()
   {
     JalviewFileChooser chooser = new JalviewFileChooser("fc",
-            "Sequence Feature Colours");
+            SEQUENCE_FEATURE_COLOURS);
     chooser.setFileView(new JalviewFileView());
     chooser.setDialogTitle(
             MessageManager.getString("label.load_feature_colours"));
@@ -819,88 +864,87 @@ public class FeatureSettings extends JPanel
     if (value == JalviewFileChooser.APPROVE_OPTION)
     {
       File file = chooser.getSelectedFile();
+      load(file);
+    }
+  }
 
-      try
-      {
-        InputStreamReader in = new InputStreamReader(
-                new FileInputStream(file), "UTF-8");
+  /**
+   * Loads feature colours and filters from XML stored in the given file
+   * 
+   * @param file
+   */
+  void load(File file)
+  {
+    try
+    {
+      InputStreamReader in = new InputStreamReader(
+              new FileInputStream(file), "UTF-8");
 
-        JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
+      JAXBContext jc = JAXBContext
+              .newInstance("jalview.xml.binding.jalview");
+      javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
+      XMLStreamReader streamReader = XMLInputFactory.newInstance()
+              .createXMLStreamReader(in);
+      JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
+              JalviewUserColours.class);
+      JalviewUserColours jucs = jbe.getValue();
 
-        for (int i = jucs.getColourCount() - 1; i >= 0; i--)
-        {
-          String name;
-          jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
-          if (newcol.hasMax())
-          {
-            Color mincol = null, maxcol = null;
-            try
-            {
-              mincol = new Color(Integer.parseInt(newcol.getMinRGB(), 16));
-              maxcol = new Color(Integer.parseInt(newcol.getRGB(), 16));
+      // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
 
-            } catch (Exception e)
-            {
-              Cache.log.warn("Couldn't parse out graduated feature color.",
-                      e);
-            }
-            FeatureColourI gcol = new FeatureColour(mincol, maxcol,
-                    newcol.getMin(), newcol.getMax());
-            if (newcol.hasAutoScale())
-            {
-              gcol.setAutoScaled(newcol.getAutoScale());
-            }
-            if (newcol.hasColourByLabel())
-            {
-              gcol.setColourByLabel(newcol.getColourByLabel());
-            }
-            if (newcol.hasThreshold())
-            {
-              gcol.setThreshold(newcol.getThreshold());
-            }
-            if (newcol.getThreshType().length() > 0)
-            {
-              String ttyp = newcol.getThreshType();
-              if (ttyp.equalsIgnoreCase("ABOVE"))
-              {
-                gcol.setAboveThreshold(true);
-              }
-              if (ttyp.equalsIgnoreCase("BELOW"))
-              {
-                gcol.setBelowThreshold(true);
-              }
-            }
-            fr.setColour(name = newcol.getName(), gcol);
-          }
-          else
-          {
-            Color color = new Color(
-                    Integer.parseInt(jucs.getColour(i).getRGB(), 16));
-            fr.setColour(name = jucs.getColour(i).getName(),
-                    new FeatureColour(color));
-          }
-          fr.setOrder(name, (i == 0) ? 0 : i / jucs.getColourCount());
-        }
-        if (table != null)
+      /*
+       * load feature colours
+       */
+      for (int i = jucs.getColour().size() - 1; i >= 0; i--)
+      {
+        Colour newcol = jucs.getColour().get(i);
+        FeatureColourI colour = jalview.project.Jalview2XML
+                .parseColour(newcol);
+        fr.setColour(newcol.getName(), colour);
+        fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
+      }
+
+      /*
+       * load feature filters; loaded filters will replace any that are
+       * currently defined, other defined filters are left unchanged 
+       */
+      for (int i = 0; i < jucs.getFilter().size(); i++)
+      {
+        Filter filterModel = jucs.getFilter().get(i);
+        String featureType = filterModel.getFeatureType();
+        FeatureMatcherSetI filter = jalview.project.Jalview2XML
+                .parseFilter(featureType, filterModel.getMatcherSet());
+        if (!filter.isEmpty())
         {
-          resetTable(null);
-          Object[][] data = ((FeatureTableModel) table.getModel())
-                  .getData();
-          ensureOrder(data);
-          updateFeatureRenderer(data, false);
-          table.repaint();
+          fr.setFeatureFilter(featureType, filter);
         }
-      } catch (Exception ex)
-      {
-        System.out.println("Error loading User Colour File\n" + ex);
       }
+
+      /*
+       * update feature settings table
+       */
+      if (table != null)
+      {
+        resetTable(null);
+        Object[][] data = ((FeatureTableModel) table.getModel())
+                .getData();
+        ensureOrder(data);
+        updateFeatureRenderer(data, false);
+        table.repaint();
+      }
+    } catch (Exception ex)
+    {
+      System.out.println("Error loading User Colour File\n" + ex);
     }
   }
 
+  /**
+   * Offers a file chooser dialog, and then saves the current feature colours
+   * and any filters to the selected file in XML format
+   */
   void save()
   {
     JalviewFileChooser chooser = new JalviewFileChooser("fc",
-            "Sequence Feature Colours");
+            SEQUENCE_FEATURE_COLOURS);
     chooser.setFileView(new JalviewFileView());
     chooser.setDialogTitle(
             MessageManager.getString("label.save_feature_colours"));
@@ -910,68 +954,97 @@ public class FeatureSettings extends JPanel
 
     if (value == JalviewFileChooser.APPROVE_OPTION)
     {
-      String choice = chooser.getSelectedFile().getPath();
-      jalview.schemabinding.version2.JalviewUserColours ucs = new jalview.schemabinding.version2.JalviewUserColours();
-      ucs.setSchemeName("Sequence Features");
-      try
-      {
-        PrintWriter out = new PrintWriter(new OutputStreamWriter(
-                new FileOutputStream(choice), "UTF-8"));
+      save(chooser.getSelectedFile());
+    }
+  }
+
+  /**
+   * Saves feature colours and filters to the given file
+   * 
+   * @param file
+   */
+  void save(File file)
+  {
+    JalviewUserColours ucs = new JalviewUserColours();
+    ucs.setSchemeName("Sequence Features");
+    try
+    {
+      PrintWriter out = new PrintWriter(new OutputStreamWriter(
+              new FileOutputStream(file), "UTF-8"));
 
-        Set<String> fr_colours = fr.getAllFeatureColours();
-        Iterator<String> e = fr_colours.iterator();
-        float[] sortOrder = new float[fr_colours.size()];
-        String[] sortTypes = new String[fr_colours.size()];
-        int i = 0;
-        while (e.hasNext())
+      /*
+       * sort feature types by colour order, from 0 (highest)
+       * to 1 (lowest)
+       */
+      Set<String> fr_colours = fr.getAllFeatureColours();
+      String[] sortedTypes = fr_colours
+              .toArray(new String[fr_colours.size()]);
+      Arrays.sort(sortedTypes, new Comparator<String>()
+      {
+        @Override
+        public int compare(String type1, String type2)
         {
-          sortTypes[i] = e.next();
-          sortOrder[i] = fr.getOrder(sortTypes[i]);
-          i++;
+          return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
         }
-        QuickSort.sort(sortOrder, sortTypes);
-        sortOrder = null;
-        for (i = 0; i < sortTypes.length; i++)
+      });
+
+      /*
+       * save feature colours
+       */
+      for (String featureType : sortedTypes)
+      {
+        FeatureColourI fcol = fr.getFeatureStyle(featureType);
+        Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
+                fcol);
+        ucs.getColour().add(col);
+      }
+
+      /*
+       * save any feature filters
+       */
+      for (String featureType : sortedTypes)
+      {
+        FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
+        if (filter != null && !filter.isEmpty())
         {
-          jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
-          col.setName(sortTypes[i]);
-          FeatureColourI fcol = fr.getFeatureStyle(sortTypes[i]);
-          if (fcol.isSimpleColour())
-          {
-            col.setRGB(Format.getHexString(fcol.getColour()));
-          }
-          else
-          {
-            col.setRGB(Format.getHexString(fcol.getMaxColour()));
-            col.setMin(fcol.getMin());
-            col.setMax(fcol.getMax());
-            col.setMinRGB(
-                    jalview.util.Format.getHexString(fcol.getMinColour()));
-            col.setAutoScale(fcol.isAutoScaled());
-            col.setThreshold(fcol.getThreshold());
-            col.setColourByLabel(fcol.isColourByLabel());
-            col.setThreshType(fcol.isAboveThreshold() ? "ABOVE"
-                    : (fcol.isBelowThreshold() ? "BELOW" : "NONE"));
-          }
-          ucs.addColour(col);
+          Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
+          FeatureMatcherI firstMatcher = iterator.next();
+          jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
+                  .marshalFilter(firstMatcher, iterator,
+                  filter.isAnded());
+          Filter filterModel = new Filter();
+          filterModel.setFeatureType(featureType);
+          filterModel.setMatcherSet(ms);
+          ucs.getFilter().add(filterModel);
         }
-        ucs.marshal(out);
-        out.close();
-      } catch (Exception ex)
-      {
-        ex.printStackTrace();
       }
+      JAXBContext jaxbContext = JAXBContext
+              .newInstance(JalviewUserColours.class);
+      Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
+      jaxbMarshaller.marshal(
+              new ObjectFactory().createJalviewUserColours(ucs), out);
+
+      // jaxbMarshaller.marshal(object, pout);
+      // marshaller.marshal(object);
+      out.flush();
+
+      // ucs.marshal(out);
+      out.close();
+    } catch (Exception ex)
+    {
+      ex.printStackTrace();
     }
   }
 
   public void invertSelection()
   {
-    for (int i = 0; i < table.getRowCount(); i++)
+    Object[][] data = ((FeatureTableModel) table.getModel()).getData();
+    for (int i = 0; i < data.length; i++)
     {
-      Boolean value = (Boolean) table.getValueAt(i, 2);
-
-      table.setValueAt(new Boolean(!value.booleanValue()), i, 2);
+      data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
     }
+    updateFeatureRenderer(data, true);
+    table.repaint();
   }
 
   public void orderByAvWidth()
@@ -984,17 +1057,16 @@ public class FeatureSettings extends JPanel
     float[] width = new float[data.length];
     float[] awidth;
     float max = 0;
-    int num = 0;
+
     for (int i = 0; i < data.length; i++)
     {
-      awidth = typeWidth.get(data[i][0]);
+      awidth = typeWidth.get(data[i][TYPE_COLUMN]);
       if (awidth[0] > 0)
       {
         width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
         // weight - but have to make per
         // sequence, too (awidth[2])
         // if (width[i]==1) // hack to distinguish single width sequences.
-        num++;
       }
       else
       {
@@ -1011,16 +1083,17 @@ public class FeatureSettings extends JPanel
       // awidth = (float[]) typeWidth.get(data[i][0]);
       if (width[i] == 0)
       {
-        width[i] = fr.getOrder(data[i][0].toString());
+        width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
         if (width[i] < 0)
         {
-          width[i] = fr.setOrder(data[i][0].toString(), i / data.length);
+          width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
+                  i / data.length);
         }
       }
       else
       {
         width[i] /= max; // normalize
-        fr.setOrder(data[i][0].toString(), width[i]); // store for later
+        fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
       }
       if (i > 0)
       {
@@ -1054,76 +1127,56 @@ public class FeatureSettings extends JPanel
   }
 
   /**
-   * Update the priority order of features; only repaint if this changed the
-   * order of visible features
+   * Update the priority order of features; only repaint if this changed the order
+   * of visible features
    * 
    * @param data
    * @param visibleNew
    */
   private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
   {
-    if (fr.setFeaturePriority(data, visibleNew))
+    FeatureSettingsBean[] rowData = getTableAsBeans(data);
+
+    if (fr.setFeaturePriority(rowData, visibleNew))
     {
-      af.alignPanel.paintAlignment(true);
+      af.alignPanel.paintAlignment(true, true);
     }
   }
 
-  int selectedRow = -1;
-
-  JTabbedPane tabbedPane = new JTabbedPane();
-
-  BorderLayout borderLayout1 = new BorderLayout();
-
-  BorderLayout borderLayout2 = new BorderLayout();
-
-  BorderLayout borderLayout3 = new BorderLayout();
-
-  JPanel bigPanel = new JPanel();
-
-  BorderLayout borderLayout4 = new BorderLayout();
-
-  JButton invert = new JButton();
-
-  JPanel buttonPanel = new JPanel();
-
-  JButton cancel = new JButton();
-
-  JButton ok = new JButton();
-
-  JButton loadColours = new JButton();
-
-  JButton saveColours = new JButton();
-
-  JPanel dasButtonPanel = new JPanel();
-
-  JButton fetchDAS = new JButton();
-
-  JButton saveDAS = new JButton();
-
-  JButton cancelDAS = new JButton();
-
-  JButton optimizeOrder = new JButton();
-
-  JButton sortByScore = new JButton();
-
-  JButton sortByDens = new JButton();
-
-  JButton help = new JButton();
-
-  JPanel transbuttons = new JPanel(new GridLayout(5, 1));
+  /**
+   * Converts table data into an array of data beans
+   */
+  private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
+  {
+    FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
+    for (int i = 0; i < data.length; i++)
+    {
+      String type = (String) data[i][TYPE_COLUMN];
+      FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
+      FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
+      Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
+      rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
+              isShown);
+    }
+    return rowData;
+  }
 
   private void jbInit() throws Exception
   {
-    this.setLayout(borderLayout1);
-    settingsPane.setLayout(borderLayout2);
-    dasSettingsPane.setLayout(borderLayout3);
-    bigPanel.setLayout(borderLayout4);
+    this.setLayout(new BorderLayout());
+
+    JPanel settingsPane = new JPanel();
+    settingsPane.setLayout(new BorderLayout());
+
+    JPanel bigPanel = new JPanel();
+    bigPanel.setLayout(new BorderLayout());
 
     groupPanel = new JPanel();
     bigPanel.add(groupPanel, BorderLayout.NORTH);
 
+    JButton invert = new JButton(
+            MessageManager.getString("label.invert_selection"));
     invert.setFont(JvSwingUtils.getLabelFont());
-    invert.setText(MessageManager.getString("label.invert_selection"));
     invert.addActionListener(new ActionListener()
     {
       @Override
@@ -1132,8 +1185,10 @@ public class FeatureSettings extends JPanel
         invertSelection();
       }
     });
+
+    JButton optimizeOrder = new JButton(
+            MessageManager.getString("label.optimise_order"));
     optimizeOrder.setFont(JvSwingUtils.getLabelFont());
-    optimizeOrder.setText(MessageManager.getString("label.optimise_order"));
     optimizeOrder.addActionListener(new ActionListener()
     {
       @Override
@@ -1142,9 +1197,10 @@ public class FeatureSettings extends JPanel
         orderByAvWidth();
       }
     });
+
+    JButton sortByScore = new JButton(
+            MessageManager.getString("label.seq_sort_by_score"));
     sortByScore.setFont(JvSwingUtils.getLabelFont());
-    sortByScore
-            .setText(MessageManager.getString("label.seq_sort_by_score"));
     sortByScore.addActionListener(new ActionListener()
     {
       @Override
@@ -1153,9 +1209,9 @@ public class FeatureSettings extends JPanel
         af.avc.sortAlignmentByFeatureScore(null);
       }
     });
-    sortByDens.setFont(JvSwingUtils.getLabelFont());
-    sortByDens.setText(
+    JButton sortByDens = new JButton(
             MessageManager.getString("label.sequence_sort_by_density"));
+    sortByDens.setFont(JvSwingUtils.getLabelFont());
     sortByDens.addActionListener(new ActionListener()
     {
       @Override
@@ -1164,8 +1220,9 @@ public class FeatureSettings extends JPanel
         af.avc.sortAlignmentByFeatureDensity(null);
       }
     });
+
+    JButton help = new JButton(MessageManager.getString("action.help"));
     help.setFont(JvSwingUtils.getLabelFont());
-    help.setText(MessageManager.getString("action.help"));
     help.addActionListener(new ActionListener()
     {
       @Override
@@ -1196,20 +1253,23 @@ public class FeatureSettings extends JPanel
         }
       }
     });
+
+    JButton cancel = new JButton(MessageManager.getString("action.cancel"));
     cancel.setFont(JvSwingUtils.getLabelFont());
-    cancel.setText(MessageManager.getString("action.cancel"));
     cancel.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
         fr.setTransparency(originalTransparency);
+        fr.setFeatureFilters(originalFilters);
         updateFeatureRenderer(originalData);
         close();
       }
     });
+
+    JButton ok = new JButton(MessageManager.getString("action.ok"));
     ok.setFont(JvSwingUtils.getLabelFont());
-    ok.setText(MessageManager.getString("action.ok"));
     ok.addActionListener(new ActionListener()
     {
       @Override
@@ -1218,8 +1278,12 @@ public class FeatureSettings extends JPanel
         close();
       }
     });
+
+    JButton loadColours = new JButton(
+            MessageManager.getString("label.load_colours"));
     loadColours.setFont(JvSwingUtils.getLabelFont());
-    loadColours.setText(MessageManager.getString("label.load_colours"));
+    loadColours.setToolTipText(
+            MessageManager.getString("label.load_colours_tooltip"));
     loadColours.addActionListener(new ActionListener()
     {
       @Override
@@ -1228,8 +1292,12 @@ public class FeatureSettings extends JPanel
         load();
       }
     });
+
+    JButton saveColours = new JButton(
+            MessageManager.getString("label.save_colours"));
     saveColours.setFont(JvSwingUtils.getLabelFont());
-    saveColours.setText(MessageManager.getString("label.save_colours"));
+    saveColours.setToolTipText(
+            MessageManager.getString("label.save_colours_tooltip"));
     saveColours.addActionListener(new ActionListener()
     {
       @Override
@@ -1246,7 +1314,7 @@ public class FeatureSettings extends JPanel
         if (!inConstruction)
         {
           fr.setTransparency((100 - transparency.getValue()) / 100f);
-          af.alignPanel.paintAlignment(true);
+          af.alignPanel.paintAlignment(true, true);
         }
       }
     });
@@ -1254,219 +1322,28 @@ public class FeatureSettings extends JPanel
     transparency.setMaximum(70);
     transparency.setToolTipText(
             MessageManager.getString("label.transparency_tip"));
-    fetchDAS.setText(MessageManager.getString("label.fetch_das_features"));
-    fetchDAS.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        fetchDAS_actionPerformed(e);
-      }
-    });
-    saveDAS.setText(MessageManager.getString("action.save_as_default"));
-    saveDAS.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        saveDAS_actionPerformed(e);
-      }
-    });
-    dasButtonPanel.setBorder(BorderFactory.createEtchedBorder());
-    dasSettingsPane.setBorder(null);
-    cancelDAS.setEnabled(false);
-    cancelDAS.setText(MessageManager.getString("action.cancel_fetch"));
-    cancelDAS.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        cancelDAS_actionPerformed(e);
-      }
-    });
-    this.add(tabbedPane, java.awt.BorderLayout.CENTER);
-    tabbedPane.addTab(MessageManager.getString("label.feature_settings"),
-            settingsPane);
-    tabbedPane.addTab(MessageManager.getString("label.das_settings"),
-            dasSettingsPane);
-    bigPanel.add(transPanel, java.awt.BorderLayout.SOUTH);
-    transbuttons.add(optimizeOrder);
+
+    JPanel transPanel = new JPanel(new GridLayout(1, 2));
+    bigPanel.add(transPanel, BorderLayout.SOUTH);
+
+    JPanel transbuttons = new JPanel(new GridLayout(5, 1));
+    transbuttons.add(optimizeOrder);
     transbuttons.add(invert);
     transbuttons.add(sortByScore);
     transbuttons.add(sortByDens);
     transbuttons.add(help);
-    JPanel sliderPanel = new JPanel();
-    sliderPanel.add(transparency);
     transPanel.add(transparency);
     transPanel.add(transbuttons);
+
+    JPanel buttonPanel = new JPanel();
     buttonPanel.add(ok);
     buttonPanel.add(cancel);
     buttonPanel.add(loadColours);
     buttonPanel.add(saveColours);
-    bigPanel.add(scrollPane, java.awt.BorderLayout.CENTER);
-    dasSettingsPane.add(dasButtonPanel, java.awt.BorderLayout.SOUTH);
-    dasButtonPanel.add(fetchDAS);
-    dasButtonPanel.add(cancelDAS);
-    dasButtonPanel.add(saveDAS);
-    settingsPane.add(bigPanel, java.awt.BorderLayout.CENTER);
-    settingsPane.add(buttonPanel, java.awt.BorderLayout.SOUTH);
-  }
-
-  public void fetchDAS_actionPerformed(ActionEvent e)
-  {
-    fetchDAS.setEnabled(false);
-    cancelDAS.setEnabled(true);
-    dassourceBrowser.setGuiEnabled(false);
-    Vector<jalviewSourceI> selectedSources = dassourceBrowser
-            .getSelectedSources();
-    doDasFeatureFetch(selectedSources, true, true);
-  }
-
-  /**
-   * get the features from selectedSources for all or the current selection
-   * 
-   * @param selectedSources
-   * @param checkDbRefs
-   * @param promptFetchDbRefs
-   */
-  private void doDasFeatureFetch(List<jalviewSourceI> selectedSources,
-          boolean checkDbRefs, boolean promptFetchDbRefs)
-  {
-    SequenceI[] dataset, seqs;
-    int iSize;
-    AlignmentViewport vp = af.getViewport();
-    if (vp.getSelectionGroup() != null
-            && vp.getSelectionGroup().getSize() > 0)
-    {
-      iSize = vp.getSelectionGroup().getSize();
-      dataset = new SequenceI[iSize];
-      seqs = vp.getSelectionGroup().getSequencesInOrder(vp.getAlignment());
-    }
-    else
-    {
-      iSize = vp.getAlignment().getHeight();
-      seqs = vp.getAlignment().getSequencesArray();
-    }
-
-    dataset = new SequenceI[iSize];
-    for (int i = 0; i < iSize; i++)
-    {
-      dataset[i] = seqs[i].getDatasetSequence();
-    }
-
-    cancelDAS.setEnabled(true);
-    dasFeatureFetcher = new jalview.ws.DasSequenceFeatureFetcher(dataset,
-            this, selectedSources, checkDbRefs, promptFetchDbRefs);
-    af.getViewport().setShowSequenceFeatures(true);
-    af.showSeqFeatures.setSelected(true);
-  }
-
-  /**
-   * blocking call to initialise the das source browser
-   */
-  public void initDasSources()
-  {
-    dassourceBrowser.initDasSources();
-  }
-
-  /**
-   * examine the current list of das sources and return any matching the given
-   * nicknames in sources
-   * 
-   * @param sources
-   *          Vector of Strings to resolve to DAS source nicknames.
-   * @return sources that are present in source list.
-   */
-  public List<jalviewSourceI> resolveSourceNicknames(Vector<String> sources)
-  {
-    return dassourceBrowser.sourceRegistry.resolveSourceNicknames(sources);
-  }
-
-  /**
-   * get currently selected das sources. ensure you have called initDasSources
-   * before calling this.
-   * 
-   * @return vector of selected das source nicknames
-   */
-  public Vector<jalviewSourceI> getSelectedSources()
-  {
-    return dassourceBrowser.getSelectedSources();
-  }
-
-  /**
-   * properly initialise DAS fetcher and then initiate a new thread to fetch
-   * features from the named sources (rather than any turned on by default)
-   * 
-   * @param sources
-   * @param block
-   *          if true then runs in same thread, otherwise passes to the Swing
-   *          executor
-   */
-  public void fetchDasFeatures(Vector<String> sources, boolean block)
-  {
-    initDasSources();
-    List<jalviewSourceI> resolved = dassourceBrowser.sourceRegistry
-            .resolveSourceNicknames(sources);
-    if (resolved.size() == 0)
-    {
-      resolved = dassourceBrowser.getSelectedSources();
-    }
-    if (resolved.size() > 0)
-    {
-      final List<jalviewSourceI> dassources = resolved;
-      fetchDAS.setEnabled(false);
-      // cancelDAS.setEnabled(true); doDasFetch does this.
-      Runnable fetcher = new Runnable()
-      {
-
-        @Override
-        public void run()
-        {
-          doDasFeatureFetch(dassources, true, false);
-
-        }
-      };
-      if (block)
-      {
-        fetcher.run();
-      }
-      else
-      {
-        SwingUtilities.invokeLater(fetcher);
-      }
-    }
-  }
-
-  public void saveDAS_actionPerformed(ActionEvent e)
-  {
-    dassourceBrowser
-            .saveProperties(jalview.bin.Cache.applicationProperties);
-  }
-
-  public void complete()
-  {
-    fetchDAS.setEnabled(true);
-    cancelDAS.setEnabled(false);
-    dassourceBrowser.setGuiEnabled(true);
-
-  }
-
-  public void cancelDAS_actionPerformed(ActionEvent e)
-  {
-    if (dasFeatureFetcher != null)
-    {
-      dasFeatureFetcher.cancel();
-    }
-    complete();
-  }
-
-  public void noDasSourceActive()
-  {
-    complete();
-    JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
-            MessageManager.getString("label.no_das_sources_selected_warn"),
-            MessageManager.getString("label.no_das_sources_selected_title"),
-            JvOptionPane.DEFAULT_OPTION, JvOptionPane.INFORMATION_MESSAGE);
+    bigPanel.add(scrollPane, BorderLayout.CENTER);
+    settingsPane.add(bigPanel, BorderLayout.CENTER);
+    settingsPane.add(buttonPanel, BorderLayout.SOUTH);
+    this.add(settingsPane);
   }
 
   // ///////////////////////////////////////////////////////////////////////
@@ -1474,18 +1351,19 @@ public class FeatureSettings extends JPanel
   // ///////////////////////////////////////////////////////////////////////
   class FeatureTableModel extends AbstractTableModel
   {
-    FeatureTableModel(Object[][] data)
-    {
-      this.data = data;
-    }
-
     private String[] columnNames = {
         MessageManager.getString("label.feature_type"),
         MessageManager.getString("action.colour"),
-        MessageManager.getString("label.display") };
+        MessageManager.getString("label.filter"),
+        MessageManager.getString("label.show") };
 
     private Object[][] data;
 
+    FeatureTableModel(Object[][] data)
+    {
+      this.data = data;
+    }
+
     public Object[][] getData()
     {
       return data;
@@ -1525,10 +1403,14 @@ public class FeatureSettings extends JPanel
       return data[row][col];
     }
 
+    /**
+     * Answers the class of the object in column c of the first row of the table
+     */
     @Override
-    public Class getColumnClass(int c)
+    public Class<?> getColumnClass(int c)
     {
-      return getValueAt(0, c).getClass();
+      Object v = getValueAt(0, c);
+      return v == null ? null : v.getClass();
     }
 
     @Override
@@ -1567,12 +1449,7 @@ public class FeatureSettings extends JPanel
             boolean isSelected, boolean hasFocus, int row, int column)
     {
       FeatureColourI cellColour = (FeatureColourI) color;
-      // JLabel comp = new JLabel();
-      // comp.
       setOpaque(true);
-      // comp.
-      // setBounds(getBounds());
-      Color newColor;
       setToolTipText(baseTT);
       setBackground(tbl.getBackground());
       if (!cellColour.isSimpleColour())
@@ -1580,14 +1457,12 @@ public class FeatureSettings extends JPanel
         Rectangle cr = tbl.getCellRect(row, column, false);
         FeatureSettings.renderGraduatedColor(this, cellColour,
                 (int) cr.getWidth(), (int) cr.getHeight());
-
       }
       else
       {
         this.setText("");
         this.setIcon(null);
-        newColor = cellColour.getColour();
-        setBackground(newColor);
+        setBackground(cellColour.getColour());
       }
       if (isSelected)
       {
@@ -1612,6 +1487,54 @@ public class FeatureSettings extends JPanel
     }
   }
 
+  class FilterRenderer extends JLabel implements TableCellRenderer
+  {
+    javax.swing.border.Border unselectedBorder = null;
+
+    javax.swing.border.Border selectedBorder = null;
+
+    public FilterRenderer()
+    {
+      setOpaque(true); // MUST do this for background to show up.
+      setHorizontalTextPosition(SwingConstants.CENTER);
+      setVerticalTextPosition(SwingConstants.CENTER);
+    }
+
+    @Override
+    public Component getTableCellRendererComponent(JTable tbl,
+            Object filter, boolean isSelected, boolean hasFocus, int row,
+            int column)
+    {
+      FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
+      setOpaque(true);
+      String asText = theFilter.toString();
+      setBackground(tbl.getBackground());
+      this.setText(asText);
+      this.setIcon(null);
+
+      if (isSelected)
+      {
+        if (selectedBorder == null)
+        {
+          selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
+                  tbl.getSelectionBackground());
+        }
+        setBorder(selectedBorder);
+      }
+      else
+      {
+        if (unselectedBorder == null)
+        {
+          unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
+                  tbl.getBackground());
+        }
+        setBorder(unselectedBorder);
+      }
+
+      return this;
+    }
+  }
+
   /**
    * update comp using rendering settings from gcol
    * 
@@ -1638,28 +1561,43 @@ public class FeatureSettings extends JPanel
           int w, int h)
   {
     boolean thr = false;
-    String tt = "";
-    String tx = "";
+    StringBuilder tt = new StringBuilder();
+    StringBuilder tx = new StringBuilder();
+
+    if (gcol.isColourByAttribute())
+    {
+      tx.append(String.join(":", gcol.getAttributeName()));
+    }
+    else if (!gcol.isColourByLabel())
+    {
+      tx.append(MessageManager.getString("label.score"));
+    }
+    tx.append(" ");
     if (gcol.isAboveThreshold())
     {
       thr = true;
-      tx += ">";
-      tt += "Thresholded (Above " + gcol.getThreshold() + ") ";
+      tx.append(">");
+      tt.append("Thresholded (Above ").append(gcol.getThreshold())
+              .append(") ");
     }
     if (gcol.isBelowThreshold())
     {
       thr = true;
-      tx += "<";
-      tt += "Thresholded (Below " + gcol.getThreshold() + ") ";
+      tx.append("<");
+      tt.append("Thresholded (Below ").append(gcol.getThreshold())
+              .append(") ");
     }
     if (gcol.isColourByLabel())
     {
-      tt = "Coloured by label text. " + tt;
+      tt.append("Coloured by label text. ").append(tt);
       if (thr)
       {
-        tx += " ";
+        tx.append(" ");
+      }
+      if (!gcol.isColourByAttribute())
+      {
+        tx.append("Label");
       }
-      tx += "Label";
       comp.setIcon(null);
     }
     else
@@ -1675,19 +1613,259 @@ public class FeatureSettings extends JPanel
       // + ", " + minCol.getBlue() + ")");
     }
     comp.setHorizontalAlignment(SwingConstants.CENTER);
-    comp.setText(tx);
+    comp.setText(tx.toString());
     if (tt.length() > 0)
     {
       if (comp.getToolTipText() == null)
       {
-        comp.setToolTipText(tt);
+        comp.setToolTipText(tt.toString());
       }
       else
       {
-        comp.setToolTipText(tt + " " + comp.getToolTipText());
+        comp.setToolTipText(
+                tt.append(" ").append(comp.getToolTipText()).toString());
       }
     }
   }
+
+  class ColorEditor extends AbstractCellEditor
+          implements TableCellEditor, ActionListener
+  {
+    FeatureSettings me;
+
+    FeatureColourI currentColor;
+
+    FeatureTypeSettings chooser;
+
+    String type;
+
+    JButton button;
+
+    JColorChooser colorChooser;
+
+    JDialog dialog;
+
+    protected static final String EDIT = "edit";
+
+    int rowSelected = 0;
+
+    public ColorEditor(FeatureSettings me)
+    {
+      this.me = me;
+      // Set up the editor (from the table's point of view),
+      // which is a button.
+      // This button brings up the color chooser dialog,
+      // which is the editor from the user's point of view.
+      button = new JButton();
+      button.setActionCommand(EDIT);
+      button.addActionListener(this);
+      button.setBorderPainted(false);
+      // Set up the dialog that the button brings up.
+      colorChooser = new JColorChooser();
+      dialog = JColorChooser.createDialog(button,
+              MessageManager.getString("label.select_colour"), true, // modal
+              colorChooser, this, // OK button handler
+              null); // no CANCEL button handler
+    }
+
+    /**
+     * Handles events from the editor button and from the dialog's OK button.
+     */
+    @Override
+    public void actionPerformed(ActionEvent e)
+    {
+      // todo test e.getSource() instead here
+      if (EDIT.equals(e.getActionCommand()))
+      {
+        // The user has clicked the cell, so
+        // bring up the dialog.
+        if (currentColor.isSimpleColour())
+        {
+          // bring up simple color chooser
+          button.setBackground(currentColor.getColour());
+          colorChooser.setColor(currentColor.getColour());
+          dialog.setVisible(true);
+        }
+        else
+        {
+          // bring up graduated chooser.
+          chooser = new FeatureTypeSettings(me.fr, type);
+          chooser.setRequestFocusEnabled(true);
+          chooser.requestFocus();
+          chooser.addActionListener(this);
+          chooser.showTab(true);
+        }
+        // Make the renderer reappear.
+        fireEditingStopped();
+
+      }
+      else
+      {
+        if (currentColor.isSimpleColour())
+        {
+          /*
+           * read off colour picked in colour chooser after OK pressed
+           */
+          currentColor = new FeatureColour(colorChooser.getColor());
+          me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
+        }
+        else
+        {
+          /*
+           * after OK in variable colour dialog, any changes to colour 
+           * (or filters!) are already set in FeatureRenderer, so just
+           * update table data without triggering updateFeatureRenderer
+           */
+          currentColor = fr.getFeatureColours().get(type);
+          FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
+          if (currentFilter == null)
+          {
+            currentFilter = new FeatureMatcherSet();
+          }
+          Object[] data = ((FeatureTableModel) table.getModel())
+                  .getData()[rowSelected];
+          data[COLOUR_COLUMN] = currentColor;
+          data[FILTER_COLUMN] = currentFilter;
+        }
+        fireEditingStopped();
+        me.table.validate();
+      }
+    }
+
+    // Implement the one CellEditor method that AbstractCellEditor doesn't.
+    @Override
+    public Object getCellEditorValue()
+    {
+      return currentColor;
+    }
+
+    // Implement the one method defined by TableCellEditor.
+    @Override
+    public Component getTableCellEditorComponent(JTable theTable, Object value,
+            boolean isSelected, int row, int column)
+    {
+      currentColor = (FeatureColourI) value;
+      this.rowSelected = row;
+      type = me.table.getValueAt(row, TYPE_COLUMN).toString();
+      button.setOpaque(true);
+      button.setBackground(me.getBackground());
+      if (!currentColor.isSimpleColour())
+      {
+        JLabel btn = new JLabel();
+        btn.setSize(button.getSize());
+        FeatureSettings.renderGraduatedColor(btn, currentColor);
+        button.setBackground(btn.getBackground());
+        button.setIcon(btn.getIcon());
+        button.setText(btn.getText());
+      }
+      else
+      {
+        button.setText("");
+        button.setIcon(null);
+        button.setBackground(currentColor.getColour());
+      }
+      return button;
+    }
+  }
+
+  /**
+   * The cell editor for the Filter column. It displays the text of any filters
+   * for the feature type in that row (in full as a tooltip, possible abbreviated
+   * as display text). On click in the cell, opens the Feature Display Settings
+   * dialog at the Filters tab.
+   */
+  class FilterEditor extends AbstractCellEditor
+          implements TableCellEditor, ActionListener
+  {
+    FeatureSettings me;
+
+    FeatureMatcherSetI currentFilter;
+
+    Point lastLocation;
+
+    String type;
+
+    JButton button;
+
+    protected static final String EDIT = "edit";
+
+    int rowSelected = 0;
+
+    public FilterEditor(FeatureSettings me)
+    {
+      this.me = me;
+      button = new JButton();
+      button.setActionCommand(EDIT);
+      button.addActionListener(this);
+      button.setBorderPainted(false);
+    }
+
+    /**
+     * Handles events from the editor button
+     */
+    @Override
+    public void actionPerformed(ActionEvent e)
+    {
+      if (button == e.getSource())
+      {
+        FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
+        chooser.addActionListener(this);
+        chooser.setRequestFocusEnabled(true);
+        chooser.requestFocus();
+        if (lastLocation != null)
+        {
+          // todo open at its last position on screen
+          chooser.setBounds(lastLocation.x, lastLocation.y,
+                  chooser.getWidth(), chooser.getHeight());
+          chooser.validate();
+        }
+        chooser.showTab(false);
+        fireEditingStopped();
+      }
+      else if (e.getSource() instanceof Component)
+      {
+
+        /*
+         * after OK in variable colour dialog, any changes to filter
+         * (or colours!) are already set in FeatureRenderer, so just
+         * update table data without triggering updateFeatureRenderer
+         */
+        FeatureColourI currentColor = fr.getFeatureColours().get(type);
+        currentFilter = me.fr.getFeatureFilter(type);
+        if (currentFilter == null)
+        {
+          currentFilter = new FeatureMatcherSet();
+        }
+        Object[] data = ((FeatureTableModel) table.getModel())
+                .getData()[rowSelected];
+        data[COLOUR_COLUMN] = currentColor;
+        data[FILTER_COLUMN] = currentFilter;
+        fireEditingStopped();
+        me.table.validate();
+      }
+    }
+
+    @Override
+    public Object getCellEditorValue()
+    {
+      return currentFilter;
+    }
+
+    @Override
+    public Component getTableCellEditorComponent(JTable theTable, Object value,
+            boolean isSelected, int row, int column)
+    {
+      currentFilter = (FeatureMatcherSetI) value;
+      this.rowSelected = row;
+      type = me.table.getValueAt(row, TYPE_COLUMN).toString();
+      button.setOpaque(true);
+      button.setBackground(me.getBackground());
+      button.setText(currentFilter.toString());
+      button.setToolTipText(currentFilter.toString());
+      button.setIcon(null);
+      return button;
+    }
+  }
 }
 
 class FeatureIcon implements Icon
@@ -1771,124 +1949,3 @@ class FeatureIcon implements Icon
     }
   }
 }
-
-class ColorEditor extends AbstractCellEditor
-        implements TableCellEditor, ActionListener
-{
-  FeatureSettings me;
-
-  FeatureColourI currentColor;
-
-  FeatureColourChooser chooser;
-
-  String type;
-
-  JButton button;
-
-  JColorChooser colorChooser;
-
-  JDialog dialog;
-
-  protected static final String EDIT = "edit";
-
-  int selectedRow = 0;
-
-  public ColorEditor(FeatureSettings me)
-  {
-    this.me = me;
-    // Set up the editor (from the table's point of view),
-    // which is a button.
-    // This button brings up the color chooser dialog,
-    // which is the editor from the user's point of view.
-    button = new JButton();
-    button.setActionCommand(EDIT);
-    button.addActionListener(this);
-    button.setBorderPainted(false);
-    // Set up the dialog that the button brings up.
-    colorChooser = new JColorChooser();
-    dialog = JColorChooser.createDialog(button, "Select new Colour", true, // modal
-            colorChooser, this, // OK button handler
-            null); // no CANCEL button handler
-  }
-
-  /**
-   * Handles events from the editor button and from the dialog's OK button.
-   */
-  @Override
-  public void actionPerformed(ActionEvent e)
-  {
-
-    if (EDIT.equals(e.getActionCommand()))
-    {
-      // The user has clicked the cell, so
-      // bring up the dialog.
-      if (currentColor.isSimpleColour())
-      {
-        // bring up simple color chooser
-        button.setBackground(currentColor.getColour());
-        colorChooser.setColor(currentColor.getColour());
-        dialog.setVisible(true);
-      }
-      else
-      {
-        // bring up graduated chooser.
-        chooser = new FeatureColourChooser(me.fr, type);
-        chooser.setRequestFocusEnabled(true);
-        chooser.requestFocus();
-        chooser.addActionListener(this);
-      }
-      // Make the renderer reappear.
-      fireEditingStopped();
-
-    }
-    else
-    { // User pressed dialog's "OK" button.
-      if (currentColor.isSimpleColour())
-      {
-        currentColor = new FeatureColour(colorChooser.getColor());
-      }
-      else
-      {
-        currentColor = chooser.getLastColour();
-      }
-      me.table.setValueAt(getCellEditorValue(), selectedRow, 1);
-      fireEditingStopped();
-      me.table.validate();
-    }
-  }
-
-  // Implement the one CellEditor method that AbstractCellEditor doesn't.
-  @Override
-  public Object getCellEditorValue()
-  {
-    return currentColor;
-  }
-
-  // Implement the one method defined by TableCellEditor.
-  @Override
-  public Component getTableCellEditorComponent(JTable table, Object value,
-          boolean isSelected, int row, int column)
-  {
-    currentColor = (FeatureColourI) value;
-    this.selectedRow = row;
-    type = me.table.getValueAt(row, 0).toString();
-    button.setOpaque(true);
-    button.setBackground(me.getBackground());
-    if (!currentColor.isSimpleColour())
-    {
-      JLabel btn = new JLabel();
-      btn.setSize(button.getSize());
-      FeatureSettings.renderGraduatedColor(btn, currentColor);
-      button.setBackground(btn.getBackground());
-      button.setIcon(btn.getIcon());
-      button.setText(btn.getText());
-    }
-    else
-    {
-      button.setText("");
-      button.setIcon(null);
-      button.setBackground(currentColor.getColour());
-    }
-    return button;
-  }
-}