Merge branch 'develop' into features/JAL-3010ontologyFeatureSettings
[jalview.git] / src / jalview / gui / FeatureSettings.java
index 4526517..cdca96a 100644 (file)
@@ -28,9 +28,11 @@ import jalview.datamodel.features.FeatureMatcher;
 import jalview.datamodel.features.FeatureMatcherI;
 import jalview.datamodel.features.FeatureMatcherSet;
 import jalview.datamodel.features.FeatureMatcherSetI;
+import jalview.datamodel.ontology.OntologyI;
 import jalview.gui.Help.HelpId;
 import jalview.io.JalviewFileChooser;
 import jalview.io.JalviewFileView;
+import jalview.io.gff.SequenceOntologyFactory;
 import jalview.schemes.FeatureColour;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
@@ -64,6 +66,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;
@@ -92,6 +95,7 @@ import javax.swing.JScrollPane;
 import javax.swing.JSlider;
 import javax.swing.JTable;
 import javax.swing.ListSelectionModel;
+import javax.swing.RowFilter;
 import javax.swing.SwingConstants;
 import javax.swing.border.Border;
 import javax.swing.event.ChangeEvent;
@@ -101,6 +105,7 @@ import javax.swing.table.JTableHeader;
 import javax.swing.table.TableCellEditor;
 import javax.swing.table.TableCellRenderer;
 import javax.swing.table.TableColumn;
+import javax.swing.table.TableRowSorter;
 import javax.xml.bind.JAXBContext;
 import javax.xml.bind.JAXBElement;
 import javax.xml.bind.Marshaller;
@@ -110,6 +115,8 @@ import javax.xml.stream.XMLStreamReader;
 public class FeatureSettings extends JPanel
         implements FeatureSettingsControllerI
 {
+  private static final Font VERDANA_12 = new Font("Verdana", Font.PLAIN, 12);
+
   private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
           .getString("label.sequence_feature_colours");
 
@@ -174,6 +181,17 @@ public class FeatureSettings extends JPanel
    */
   Map<String, float[]> typeWidth = null;
 
+  /*
+   * if true, 'child' feature types are not displayed
+   */
+  JCheckBox summaryView;
+
+  /*
+   * those feature types that do not have a parent feature type present
+   * (as determined by an Ontology relationship)
+   */
+  List<String> topLevelTypes;
+
   /**
    * Constructor
    * 
@@ -191,6 +209,8 @@ public class FeatureSettings extends JPanel
 
     originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
 
+    topLevelTypes = new ArrayList<>();
+
     try
     {
       jbInit();
@@ -199,6 +219,69 @@ public class FeatureSettings extends JPanel
       ex.printStackTrace();
     }
 
+    initTable();
+
+    scrollPane.setViewportView(table);
+
+    if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
+    {
+      fr.findAllFeatures(true); // display everything!
+    }
+
+    discoverAllFeatureData();
+    final PropertyChangeListener change;
+    final FeatureSettings fs = this;
+    fr.addPropertyChangeListener(change = new PropertyChangeListener()
+    {
+      @Override
+      public void propertyChange(PropertyChangeEvent evt)
+      {
+        if (!fs.resettingTable && !fs.handlingUpdate)
+        {
+          fs.handlingUpdate = true;
+          fs.resetTable(null);
+          // new groups may be added with new sequence feature types only
+          fs.handlingUpdate = false;
+        }
+      }
+    });
+
+    frame = new JInternalFrame();
+    frame.setContentPane(this);
+    if (Platform.isAMac())
+    {
+      Desktop.addInternalFrame(frame,
+              MessageManager.getString("label.sequence_feature_settings"),
+              600, 480);
+    }
+    else
+    {
+      Desktop.addInternalFrame(frame,
+              MessageManager.getString("label.sequence_feature_settings"),
+              600, 450);
+    }
+    frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
+
+    frame.addInternalFrameListener(
+            new javax.swing.event.InternalFrameAdapter()
+            {
+              @Override
+              public void internalFrameClosed(
+                      javax.swing.event.InternalFrameEvent evt)
+              {
+                fr.removePropertyChangeListener(change);
+              };
+            });
+    frame.setLayer(JLayeredPane.PALETTE_LAYER);
+    inConstruction = false;
+  }
+
+  /**
+   * Constructs and configures the JTable which displays columns of data for
+   * each feature type
+   */
+  protected void initTable()
+  {
     table = new JTable()
     {
       @Override
@@ -207,11 +290,16 @@ public class FeatureSettings extends JPanel
         String tip = null;
         int column = table.columnAtPoint(e.getPoint());
         int row = table.rowAtPoint(e.getPoint());
-
         switch (column)
         {
         case TYPE_COLUMN:
-          tip = JvSwingUtils.wrapTooltip(true, MessageManager
+          /*
+           * drag to reorder not enabled in Summary View
+           */
+          tip = summaryView.isSelected()
+                  ? MessageManager.getString(
+                          "label.feature_settings_select_columns")
+                  : JvSwingUtils.wrapTooltip(true, MessageManager
                   .getString("label.feature_settings_click_drag"));
           break;
         case COLOUR_COLUMN:
@@ -230,7 +318,6 @@ public class FeatureSettings extends JPanel
         default:
           break;
         }
-        
         return tip;
       }
 
@@ -249,10 +336,11 @@ public class FeatureSettings extends JPanel
         return loc;
       }
     };
+
     JTableHeader tableHeader = table.getTableHeader();
-    tableHeader.setFont(new Font("Verdana", Font.PLAIN, 12));
+    tableHeader.setFont(VERDANA_12);
     tableHeader.setReorderingAllowed(false);
-    table.setFont(new Font("Verdana", Font.PLAIN, 12));
+    table.setFont(VERDANA_12);
 
     table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
     table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
@@ -279,17 +367,16 @@ public class FeatureSettings extends JPanel
         String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
         if (evt.isPopupTrigger())
         {
-          Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
-          popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
-                  evt.getY());
+          popupMenu(selectedRow, type, evt.getX(), evt.getY());
         }
         else if (evt.getClickCount() == 2)
         {
           boolean invertSelection = evt.isAltDown();
           boolean toggleSelection = Platform.isControlDown(evt);
           boolean extendSelection = evt.isShiftDown();
+          String[] terms = getTermsInScope(type);
           fr.ap.alignFrame.avc.markColumnsContainingFeatures(
-                  invertSelection, extendSelection, toggleSelection, type);
+                  invertSelection, extendSelection, toggleSelection, terms);
         }
       }
 
@@ -301,9 +388,7 @@ public class FeatureSettings extends JPanel
         if (evt.isPopupTrigger())
         {
           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());
+          popupMenu(selectedRow, type, evt.getX(), evt.getY());
         }
       }
     });
@@ -314,92 +399,46 @@ public class FeatureSettings extends JPanel
       public void mouseDragged(MouseEvent evt)
       {
         int newRow = table.rowAtPoint(evt.getPoint());
-        if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
-        {
-          /*
-           * reposition 'selectedRow' to 'newRow' (the dragged to location)
-           * this could be more than one row away for a very fast drag action
-           * so just swap it with adjacent rows until we get it there
-           */
-          Object[][] data = ((FeatureTableModel) table.getModel())
-                  .getData();
-          int direction = newRow < selectedRow ? -1 : 1;
-          for (int i = selectedRow; i != newRow; i += direction)
-          {
-            Object[] temp = data[i];
-            data[i] = data[i + direction];
-            data[i + direction] = temp;
-          }
-          updateFeatureRenderer(data);
-          table.repaint();
-          selectedRow = newRow;
-        }
+        dragRow(newRow);
       }
     });
-    // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
-    // MessageManager.getString("label.feature_settings_click_drag")));
-    scrollPane.setViewportView(table);
+  }
 
-    if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
+  /**
+   * Answers an array consisting of the given type, and also (if 'Summary View'
+   * is selected), any feature types which are child terms of it in the Sequence
+   * Ontology
+   * 
+   * @param type
+   * @return
+   */
+  protected String[] getTermsInScope(String type)
+  {
+    if (!summaryView.isSelected())
     {
-      fr.findAllFeatures(true); // display everything!
+      return new String[] { type };
     }
 
-    discoverAllFeatureData();
-    final PropertyChangeListener change;
-    final FeatureSettings fs = this;
-    fr.addPropertyChangeListener(change = new PropertyChangeListener()
-    {
-      @Override
-      public void propertyChange(PropertyChangeEvent evt)
-      {
-        if (!fs.resettingTable && !fs.handlingUpdate)
-        {
-          fs.handlingUpdate = true;
-          fs.resetTable(null);
-          // new groups may be added with new sequence feature types only
-          fs.handlingUpdate = false;
-        }
-      }
+    List<String> terms = new ArrayList<>();
+    terms.add(type);
 
-    });
+    OntologyI so = SequenceOntologyFactory.getInstance();
 
-    frame = new JInternalFrame();
-    frame.setContentPane(this);
-    if (Platform.isAMac())
-    {
-      Desktop.addInternalFrame(frame,
-              MessageManager.getString("label.sequence_feature_settings"),
-              600, 480);
-    }
-    else
+    Object[][] data = ((FeatureTableModel) table.getModel()).getData();
+    for (Object[] row : data)
     {
-      Desktop.addInternalFrame(frame,
-              MessageManager.getString("label.sequence_feature_settings"),
-              600, 450);
+      String type2 = (String) row[TYPE_COLUMN];
+      if (!type2.equals(type) && so.isA(type2, type))
+      {
+        terms.add(type2);
+      }
     }
-    frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
-
-    frame.addInternalFrameListener(
-            new javax.swing.event.InternalFrameAdapter()
-            {
-              @Override
-              public void internalFrameClosed(
-                      javax.swing.event.InternalFrameEvent evt)
-              {
-                fr.removePropertyChangeListener(change);
-              };
-            });
-    frame.setLayer(JLayeredPane.PALETTE_LAYER);
-    inConstruction = false;
+    return terms.toArray(new String[terms.size()]);
   }
 
-  protected void popupSort(final int rowSelected, final String type,
-          final Object typeCol, final Map<String, float[][]> minmax, int x,
+  protected void popupMenu(final int rowSelected, final String type, int x,
           int y)
   {
-    final FeatureColourI featureColour = (FeatureColourI) typeCol;
-
     JPopupMenu men = new JPopupMenu(MessageManager
             .formatMessage("label.settings_for_param", new String[]
             { type }));
@@ -409,29 +448,23 @@ public class FeatureSettings extends JPanel
     final FeatureSettings me = this;
     scr.addActionListener(new ActionListener()
     {
-
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        me.af.avc
-                .sortAlignmentByFeatureScore(Arrays.asList(new String[]
-                { type }));
+        String[] types = getTermsInScope(type);
+        me.af.avc.sortAlignmentByFeatureScore(Arrays.asList(types));
       }
-
     });
     JMenuItem dens = new JMenuItem(
             MessageManager.getString("label.sort_by_density"));
     dens.addActionListener(new ActionListener()
     {
-
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        me.af.avc
-                .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
-                { type }));
+        String[] types = getTermsInScope(type);
+        me.af.avc.sortAlignmentByFeatureDensity(Arrays.asList(types));
       }
-
     });
     men.add(dens);
 
@@ -442,8 +475,9 @@ public class FeatureSettings extends JPanel
       @Override
       public void actionPerformed(ActionEvent arg0)
       {
+        String[] types = getTermsInScope(type);
         fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
-                false, type);
+                false, types);
       }
     });
     JMenuItem clearCols = new JMenuItem(MessageManager
@@ -453,8 +487,9 @@ public class FeatureSettings extends JPanel
       @Override
       public void actionPerformed(ActionEvent arg0)
       {
+        String[] types = getTermsInScope(type);
         fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
-                false, type);
+                false, types);
       }
     });
     JMenuItem hideCols = new JMenuItem(
@@ -464,7 +499,8 @@ public class FeatureSettings extends JPanel
       @Override
       public void actionPerformed(ActionEvent arg0)
       {
-        fr.ap.alignFrame.hideFeatureColumns(type, true);
+        String[] types = getTermsInScope(type);
+        fr.ap.alignFrame.hideFeatureColumns(true, types);
       }
     });
     JMenuItem hideOtherCols = new JMenuItem(
@@ -474,7 +510,8 @@ public class FeatureSettings extends JPanel
       @Override
       public void actionPerformed(ActionEvent arg0)
       {
-        fr.ap.alignFrame.hideFeatureColumns(type, false);
+        String[] types = getTermsInScope(type);
+        fr.ap.alignFrame.hideFeatureColumns(false, types);
       }
     });
     men.add(selCols);
@@ -589,6 +626,7 @@ public class FeatureSettings extends JPanel
        */
       Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
               visibleGroups.toArray(new String[visibleGroups.size()]));
+
       for (String type : types)
       {
         displayableTypes.add(type);
@@ -607,6 +645,13 @@ public class FeatureSettings extends JPanel
       }
     }
 
+    /*
+     * enable 'Summary View' if some types are sub-types of others
+     */
+    Set<String> parents = SequenceOntologyFactory.getInstance()
+            .getParentTerms(displayableTypes);
+    summaryView.setEnabled(parents.size() < displayableTypes.size());
+
     Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
     int dataIndex = 0;
 
@@ -629,7 +674,6 @@ public class FeatureSettings extends JPanel
         {
           continue;
         }
-
         data[dataIndex][TYPE_COLUMN] = type;
         data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
         FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
@@ -681,7 +725,43 @@ public class FeatureSettings extends JPanel
       updateOriginalData(data);
     }
 
-    table.setModel(new FeatureTableModel(data));
+    /*
+     * recreate the table model
+     */
+    FeatureTableModel dataModel = new FeatureTableModel(data);
+    table.setModel(dataModel);
+
+    /*
+     * we want to be able to filter out rows for sub-types, but not to sort 
+     * rows, so have to add a RowFilter to a disabled TableRowSorter (!)
+     */
+    final TableRowSorter<FeatureTableModel> sorter = new TableRowSorter<>(
+            dataModel);
+    for (int i = 0; i < table.getColumnCount(); i++)
+    {
+      sorter.setSortable(i, false);
+    }
+
+    /*
+     * filter rows to only top-level Ontology types if requested
+     */
+    sorter.setRowFilter(new RowFilter<FeatureTableModel, Integer>()
+    {
+      @Override
+      public boolean include(
+              Entry<? extends FeatureTableModel, ? extends Integer> entry)
+      {
+        if (!summaryView.isSelected())
+        {
+          return true;
+        }
+        int row = entry.getIdentifier(); // this is model, not view, row number
+        String featureType = (String) entry.getModel().getData()[row][TYPE_COLUMN];
+        return parents.contains(featureType);
+      }
+    });
+    table.setRowSorter(sorter);
+
     table.getColumnModel().getColumn(0).setPreferredWidth(200);
 
     groupPanel.setLayout(
@@ -1260,12 +1340,26 @@ public class FeatureSettings extends JPanel
       }
     });
 
+    summaryView = new JCheckBox(
+            MessageManager.getString("label.summary_view"));
+    summaryView
+            .setToolTipText(
+                    MessageManager.getString("label.summary_view_tip"));
+    summaryView.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        resetTable(null);
+      }
+    });
+
     transparency.setMaximum(70);
     transparency.setToolTipText(
             MessageManager.getString("label.transparency_tip"));
 
-    JPanel transPanel = new JPanel(new GridLayout(1, 2));
-    bigPanel.add(transPanel, BorderLayout.SOUTH);
+    JPanel lowerPanel = new JPanel(new GridLayout(1, 2));
+    bigPanel.add(lowerPanel, BorderLayout.SOUTH);
 
     JPanel transbuttons = new JPanel(new GridLayout(5, 1));
     transbuttons.add(optimizeOrder);
@@ -1273,8 +1367,12 @@ public class FeatureSettings extends JPanel
     transbuttons.add(sortByScore);
     transbuttons.add(sortByDens);
     transbuttons.add(help);
+    JPanel transPanel = new JPanel(new GridLayout(3, 1));
+    transPanel.add(summaryView);
+    transPanel.add(new JLabel(" Colour transparency" + ":"));
     transPanel.add(transparency);
-    transPanel.add(transbuttons);
+    lowerPanel.add(transPanel);
+    lowerPanel.add(transbuttons);
 
     JPanel buttonPanel = new JPanel();
     buttonPanel.add(ok);
@@ -1288,6 +1386,59 @@ public class FeatureSettings extends JPanel
   }
 
   /**
+   * Reorders features by 'dragging' selectedRow to 'newRow'
+   * 
+   * @param newRow
+   */
+  protected void dragRow(int newRow)
+  {
+    if (summaryView.isSelected())
+    {
+      // no drag while in summary view
+      return;
+    }
+
+    if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
+    {
+      /*
+       * reposition 'selectedRow' to 'newRow' (the dragged to location)
+       * this could be more than one row away for a very fast drag action
+       * so just swap it with adjacent rows until we get it there
+       */
+      Object[][] data = ((FeatureTableModel) table.getModel())
+              .getData();
+      int direction = newRow < selectedRow ? -1 : 1;
+      for (int i = selectedRow; i != newRow; i += direction)
+      {
+        Object[] temp = data[i];
+        data[i] = data[i + direction];
+        data[i + direction] = temp;
+      }
+      updateFeatureRenderer(data);
+      table.repaint();
+      selectedRow = newRow;
+    }
+  }
+
+  protected void refreshTable()
+  {
+    Object[][] data = ((FeatureTableModel) table.getModel()).getData();
+    for (Object[] row : data)
+    {
+      String type = (String) row[TYPE_COLUMN];
+      FeatureColourI colour = fr.getFeatureColours().get(type);
+      FeatureMatcherSetI filter = fr.getFeatureFilter(type);
+      if (filter == null)
+      {
+        filter = new FeatureMatcherSet();
+      }
+      row[COLOUR_COLUMN] = colour;
+      row[FILTER_COLUMN] = filter;
+    }
+    repaint();
+  }
+
+  /*
    * Answers a suitable tooltip to show on the colour cell of the table
    * 
    * @param fcol
@@ -1429,29 +1580,75 @@ public class FeatureSettings extends JPanel
     }
 
     /**
-     * Answers the class of the object in column c of the first row of the table
+     * Answers the class of column c of the table
      */
     @Override
     public Class<?> getColumnClass(int c)
     {
-      Object v = getValueAt(0, c);
-      return v == null ? null : v.getClass();
+      switch (c)
+      {
+      case TYPE_COLUMN:
+        return String.class;
+      case COLOUR_COLUMN:
+        return FeatureColour.class;
+      case FILTER_COLUMN:
+        return FeatureMatcherSet.class;
+      default:
+        return Boolean.class;
+      }
     }
 
+    /**
+     * Answers true for all columns except Feature Type
+     */
     @Override
     public boolean isCellEditable(int row, int col)
     {
-      return col == 0 ? false : true;
+      return col != TYPE_COLUMN;
     }
 
+    /**
+     * Sets the value in the model for a given row and column. If Visibility
+     * (Show/Hide) is being set, and the table is in Summary View, then it is
+     * set also on any sub-types of the row's feature type.
+     */
     @Override
     public void setValueAt(Object value, int row, int col)
     {
       data[row][col] = value;
       fireTableCellUpdated(row, col);
+      if (summaryView.isSelected() && col == SHOW_COLUMN)
+      {
+        setSubtypesVisibility(row, (Boolean) value);
+      }
       updateFeatureRenderer(data);
     }
 
+    /**
+     * Sets the visibility of any feature types which are sub-types of the type
+     * in the given row of the table
+     * 
+     * @param row
+     * @param value
+     */
+    protected void setSubtypesVisibility(int row, Boolean value)
+    {
+      String type = (String) data[row][TYPE_COLUMN];
+      OntologyI so = SequenceOntologyFactory.getInstance();
+
+      for (int r = 0; r < data.length; r++)
+      {
+        if (r != row)
+        {
+          String type2 = (String) data[r][TYPE_COLUMN];
+          if (so.isA(type2, type))
+          {
+            data[r][SHOW_COLUMN] = value;
+            fireTableCellUpdated(r, SHOW_COLUMN);
+          }
+        }
+      }
+    }
   }
 
   class ColorRenderer extends JLabel implements TableCellRenderer
@@ -1590,7 +1787,7 @@ public class FeatureSettings extends JPanel
 
     String type;
 
-    JButton button;
+    JButton colourButton;
 
     JColorChooser colorChooser;
 
@@ -1607,13 +1804,13 @@ public class FeatureSettings extends JPanel
       // 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);
+      colourButton = new JButton();
+      colourButton.setActionCommand(EDIT);
+      colourButton.addActionListener(this);
+      colourButton.setBorderPainted(false);
       // Set up the dialog that the button brings up.
       colorChooser = new JColorChooser();
-      dialog = JColorChooser.createDialog(button,
+      dialog = JColorChooser.createDialog(colourButton,
               MessageManager.getString("label.select_colour"), true, // modal
               colorChooser, this, // OK button handler
               null); // no CANCEL button handler
@@ -1633,7 +1830,7 @@ public class FeatureSettings extends JPanel
         if (currentColor.isSimpleColour())
         {
           // bring up simple color chooser
-          button.setBackground(currentColor.getColour());
+          colourButton.setBackground(currentColor.getColour());
           colorChooser.setColor(currentColor.getColour());
           dialog.setVisible(true);
         }
@@ -1641,13 +1838,8 @@ public class FeatureSettings extends JPanel
         {
           // bring up graduated chooser.
           chooser = new FeatureTypeSettings(me.fr, type);
-          /**
-           * @j2sNative
-           */
-          {
-            chooser.setRequestFocusEnabled(true);
-            chooser.requestFocus();
-          }
+          chooser.setRequestFocusEnabled(true);
+          chooser.requestFocus();
           chooser.addActionListener(this);
           // Make the renderer reappear.
           fireEditingStopped();
@@ -1670,16 +1862,7 @@ public class FeatureSettings extends JPanel
            * (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;
+          refreshTable();
         }
         fireEditingStopped();
         me.table.validate();
@@ -1701,24 +1884,24 @@ public class FeatureSettings extends JPanel
       currentColor = (FeatureColourI) value;
       this.rowSelected = row;
       type = me.table.getValueAt(row, TYPE_COLUMN).toString();
-      button.setOpaque(true);
-      button.setBackground(me.getBackground());
+      colourButton.setOpaque(true);
+      colourButton.setBackground(me.getBackground());
       if (!currentColor.isSimpleColour())
       {
         JLabel btn = new JLabel();
-        btn.setSize(button.getSize());
+        btn.setSize(colourButton.getSize());
         FeatureSettings.renderGraduatedColor(btn, currentColor);
-        button.setBackground(btn.getBackground());
-        button.setIcon(btn.getIcon());
-        button.setText(btn.getText());
+        colourButton.setBackground(btn.getBackground());
+        colourButton.setIcon(btn.getIcon());
+        colourButton.setText(btn.getText());
       }
       else
       {
-        button.setText("");
-        button.setIcon(null);
-        button.setBackground(currentColor.getColour());
+        colourButton.setText("");
+        colourButton.setIcon(null);
+        colourButton.setBackground(currentColor.getColour());
       }
-      return button;
+      return colourButton;
     }
   }
 
@@ -1739,7 +1922,7 @@ public class FeatureSettings extends JPanel
 
     String type;
 
-    JButton button;
+    JButton filterButton;
 
     protected static final String EDIT = "edit";
 
@@ -1748,10 +1931,10 @@ public class FeatureSettings extends JPanel
     public FilterEditor(FeatureSettings me)
     {
       this.me = me;
-      button = new JButton();
-      button.setActionCommand(EDIT);
-      button.addActionListener(this);
-      button.setBorderPainted(false);
+      filterButton = new JButton();
+      filterButton.setActionCommand(EDIT);
+      filterButton.addActionListener(this);
+      filterButton.setBorderPainted(false);
     }
 
     /**
@@ -1760,7 +1943,7 @@ public class FeatureSettings extends JPanel
     @Override
     public void actionPerformed(ActionEvent e)
     {
-      if (button == e.getSource())
+      if (filterButton == e.getSource())
       {
         FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
         chooser.addActionListener(this);
@@ -1777,22 +1960,12 @@ public class FeatureSettings extends JPanel
       }
       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;
+        refreshTable();
         fireEditingStopped();
         me.table.validate();
       }
@@ -1811,17 +1984,20 @@ public class FeatureSettings extends JPanel
       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.setIcon(null);
-      return button;
+      filterButton.setOpaque(true);
+      filterButton.setBackground(me.getBackground());
+      filterButton.setText(currentFilter.toString());
+      filterButton.setToolTipText(currentFilter.toString());
+      filterButton.setIcon(null);
+      return filterButton;
     }
   }
 }
 
 class FeatureIcon implements Icon
 {
+  private static final Font VERDANA_9 = new Font("Verdana", Font.PLAIN, 9);
+
   FeatureColourI gcol;
 
   Color backg;
@@ -1876,7 +2052,7 @@ class FeatureIcon implements Icon
       // need an icon here.
       g.setColor(gcol.getMaxColour());
 
-      g.setFont(new Font("Verdana", Font.PLAIN, 9));
+      g.setFont(VERDANA_9);
 
       // g.setFont(g.getFont().deriveFont(
       // AffineTransform.getScaleInstance(