JAL-3010 options to hide / apply changes to ontology sub-types
[jalview.git] / src / jalview / gui / FeatureSettings.java
index 821454f..cb571d8 100644 (file)
@@ -30,6 +30,7 @@ import jalview.datamodel.features.FeatureMatcherSetI;
 import jalview.gui.Help.HelpId;
 import jalview.io.JalviewFileChooser;
 import jalview.io.JalviewFileView;
+import jalview.io.gff.SequenceOntologyFactory;
 import jalview.schemabinding.version2.Filter;
 import jalview.schemabinding.version2.JalviewUserColours;
 import jalview.schemabinding.version2.MatcherSet;
@@ -65,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;
@@ -95,6 +97,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.SwingUtilities;
 import javax.swing.event.ChangeEvent;
@@ -103,10 +106,13 @@ import javax.swing.table.AbstractTableModel;
 import javax.swing.table.TableCellEditor;
 import javax.swing.table.TableCellRenderer;
 import javax.swing.table.TableColumn;
+import javax.swing.table.TableRowSorter;
 
 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");
 
@@ -181,6 +187,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
    * 
@@ -198,6 +215,8 @@ public class FeatureSettings extends JPanel
 
     originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
 
+    topLevelTypes = new ArrayList<>();
+
     try
     {
       jbInit();
@@ -206,6 +225,73 @@ public class FeatureSettings extends JPanel
       ex.printStackTrace();
     }
 
+    initTable();
+
+    scrollPane.setViewportView(table);
+
+    dassourceBrowser = new DasSourceBrowser(this);
+    dasSettingsPane.add(dassourceBrowser, BorderLayout.CENTER);
+
+    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);
+                dassourceBrowser.fs = null;
+              };
+            });
+    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
@@ -216,7 +302,10 @@ public class FeatureSettings extends JPanel
         switch (column)
         {
         case TYPE_COLUMN:
-          tip = JvSwingUtils.wrapTooltip(true, MessageManager
+          tip = summaryView.isSelected()
+                  ? MessageManager.getString(
+                          "label.feature_settings_select_columns")
+                  : JvSwingUtils.wrapTooltip(true, MessageManager
                   .getString("label.feature_settings_click_drag"));
           break;
         case FILTER_COLUMN:
@@ -233,12 +322,9 @@ public class FeatureSettings extends JPanel
         return tip;
       }
     };
-    table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
-    table.setFont(new Font("Verdana", Font.PLAIN, 12));
+    table.getTableHeader().setFont(VERDANA_12);
+    table.setFont(VERDANA_12);
 
-    // 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());
 
@@ -265,8 +351,7 @@ public class FeatureSettings extends JPanel
         if (evt.isPopupTrigger())
         {
           Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
-          popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
-                  evt.getY());
+          popupMenu(selectedRow, type, colour, evt.getX(), evt.getY());
         }
         else if (evt.getClickCount() == 2)
         {
@@ -287,8 +372,7 @@ public class FeatureSettings extends JPanel
         {
           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, colour, evt.getX(), evt.getY());
         }
       }
     });
@@ -299,93 +383,13 @@ 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;
-        }
-      }
-    });
-    // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
-    // 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!
-    }
-
-    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;
-        }
+        dragRow(newRow);
       }
-
     });
-
-    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);
-                dassourceBrowser.fs = null;
-              };
-            });
-    frame.setLayer(JLayeredPane.PALETTE_LAYER);
-    inConstruction = false;
   }
 
-  protected void popupSort(final int rowSelected, final String type,
-          final Object typeCol, final Map<String, float[][]> minmax, int x,
-          int y)
+  protected void popupMenu(final int rowSelected, final String type,
+          final Object typeCol, int x, int y)
   {
     final FeatureColourI featureColour = (FeatureColourI) typeCol;
 
@@ -484,7 +488,6 @@ public class FeatureSettings extends JPanel
           }
         }
       }
-
     });
 
     JMenuItem selCols = new JMenuItem(
@@ -641,6 +644,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);
@@ -659,6 +663,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;
 
@@ -681,7 +692,6 @@ public class FeatureSettings extends JPanel
         {
           continue;
         }
-
         data[dataIndex][TYPE_COLUMN] = type;
         data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
         FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
@@ -733,7 +743,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(
@@ -1311,6 +1357,17 @@ public class FeatureSettings extends JPanel
       }
     });
 
+    summaryView = new JCheckBox("Summary view");
+    summaryView.setToolTipText("Show only top level ontology terms");
+    summaryView.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        resetTable(null);
+      }
+    });
+
     transparency.setMaximum(70);
     transparency.setToolTipText(
             MessageManager.getString("label.transparency_tip"));
@@ -1347,8 +1404,8 @@ public class FeatureSettings extends JPanel
       }
     });
 
-    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);
@@ -1356,8 +1413,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);
@@ -1531,6 +1592,59 @@ public class FeatureSettings extends JPanel
             JvOptionPane.DEFAULT_OPTION, JvOptionPane.INFORMATION_MESSAGE);
   }
 
+  /**
+   * 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();
+  }
+
   // ///////////////////////////////////////////////////////////////////////
   // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
   // ///////////////////////////////////////////////////////////////////////
@@ -1824,7 +1938,7 @@ public class FeatureSettings extends JPanel
 
     String type;
 
-    JButton button;
+    JButton colourButton;
 
     JColorChooser colorChooser;
 
@@ -1841,13 +1955,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
@@ -1867,7 +1981,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);
         }
@@ -1882,7 +1996,6 @@ public class FeatureSettings extends JPanel
         }
         // Make the renderer reappear.
         fireEditingStopped();
-
       }
       else
       {
@@ -1901,16 +2014,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();
@@ -1932,24 +2036,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;
     }
   }
 
@@ -1970,7 +2074,7 @@ public class FeatureSettings extends JPanel
 
     String type;
 
-    JButton button;
+    JButton filterButton;
 
     protected static final String EDIT = "edit";
 
@@ -1979,10 +2083,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);
     }
 
     /**
@@ -1991,7 +2095,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);
@@ -2009,22 +2113,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();
       }
@@ -2043,18 +2137,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.setToolTipText(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;
@@ -2109,7 +2205,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(