Merge branch 'develop' into features/JAL-3010ontologyFeatureSettings
authorJim Procter <jprocter@issues.jalview.org>
Wed, 19 Sep 2018 14:56:26 +0000 (15:56 +0100)
committerJim Procter <jprocter@issues.jalview.org>
Wed, 19 Sep 2018 14:56:26 +0000 (15:56 +0100)
20 files changed:
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/api/AlignViewControllerI.java
src/jalview/bin/Jalview.java
src/jalview/controller/AlignViewController.java
src/jalview/datamodel/ontology/OntologyBase.java [new file with mode: 0644]
src/jalview/datamodel/ontology/OntologyI.java [new file with mode: 0644]
src/jalview/ext/so/SequenceOntology.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/CrossRefAction.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/FeatureTypeSettings.java
src/jalview/gui/SequenceFetcher.java
src/jalview/io/gff/SequenceOntologyI.java
src/jalview/io/gff/SequenceOntologyLite.java
src/jalview/workers/ConsensusThread.java
test/jalview/controller/AlignViewControllerTest.java
test/jalview/ext/so/SequenceOntologyTest.java
test/jalview/gui/AlignFrameTest.java
test/jalview/io/gff/SequenceOntologyLiteTest.java

index ae5b0e7..dd66340 100644 (file)
@@ -814,6 +814,7 @@ label.rest_client_submit = {0} using {1}
 label.fetch_retrieve_from =Retrieve from {0}</html>
 label.fetch_retrieve_from_all_sources = Retrieve from all {0} sources in {1}<br>First is :{2}<html> 
 label.feature_settings_click_drag = Drag up or down to change render order.<br/>Double click to select columns containing feature.
+label.feature_settings_select_columns = Double click to select columns containing feature
 label.transparency_tip = Adjust transparency to 'see through' feature colours.
 label.opt_and_params_further_details = see further details by right-clicking
 label.opt_and_params_show_brief_desc_image_link = <html>Click to show brief description<br><img src="{0}"/> Right click for further information.</html> 
@@ -1363,3 +1364,7 @@ label.most_bound_molecules = Most Bound Molecules
 label.most_polymer_residues = Most Polymer Residues
 label.cached_structures = Cached Structures
 label.free_text_search = Free Text Search
+label.summary_view = Summary View
+label.summary_view_tip = Show only top level ontology terms
+label.apply_to_subtypes = Apply changes also to sub-types of ''{0}''
+label.apply_also_to = Apply also to:
\ No newline at end of file
index 555977d..58c0d88 100644 (file)
@@ -739,7 +739,8 @@ label.services_at = Servicios en {0}
 label.rest_client_submit = {0} utilizando {1}
 label.fetch_retrieve_from =Recuperar de {0}
 label.fetch_retrieve_from_all_sources = Recuperar de todas las fuentes {0} en {1}<br>La primera es :{2}
-label.feature_settings_click_drag = Haga clic o arrastre los tipos de las características hacia arriba o hacia abajo para cambiar el orden de visualización.<br/>Haga doble clic para seleccionar las columnas que contienen las características del alineamiento/selección actual.<br/>
+label.feature_settings_click_drag = Haga clic o arrastre los tipos de las características hacia arriba o hacia abajo para cambiar el orden de visualización.<br/>Haga doble clic para seleccionar las columnas que contienen las características del alineamiento/selección actual.
+label.feature_settings_select_columns =Haga doble clic para seleccionar las columnas que contienen las características del alineamiento/selección actual
 label.opt_and_params_further_details = ver los detalles adicionales haciendo clic en el botón derecho
 label.opt_and_params_show_brief_desc_image_link = Haga clic para ver una descripción breve<br><img src="{0}"/>Haga clic en el botón derecho para obtener información adicional.
 label.opt_and_params_show_brief_desc = Haga clic para ver una descripción breve<br>
@@ -1364,3 +1365,7 @@ label.most_bound_molecules = M
 label.most_polymer_residues = Más Residuos de Polímeros
 label.cached_structures = Estructuras en Caché
 label.free_text_search = Búsqueda de texto libre
+label.summary_view = Vista Resumida
+label.summary_view_tip = Mostrar solo términos de ontología de nivel mayor
+label.apply_to_subtypes = Aplicar cambios también a subtipos de ''{0}''
+label.apply_also_to = Aplicar también a:
\ No newline at end of file
index a7ec69e..f7575c3 100644 (file)
@@ -62,11 +62,11 @@ public interface AlignViewControllerI
    * @param toggle
    *          - rather than explicitly set, toggle selection state
    * @param featureType
-   *          - feature type string
+   *          - one or more feature types to match
    * @return true if operation affected state
    */
   boolean markColumnsContainingFeatures(boolean invert,
-          boolean extendCurrent, boolean toggle, String featureType);
+          boolean extendCurrent, boolean toggle, String... featureType);
 
   /**
    * sort the alignment or current selection by average score over the given set
index 3270144..c41f291 100755 (executable)
@@ -326,7 +326,7 @@ public class Jalview
      * configure 'full' SO model if preferences say to, 
      * else use the default (SO Lite)
      */
-    if (Cache.getDefault("USE_FULL_SO", false))
+    if (Cache.getDefault("USE_FULL_SO", true))
     {
       SequenceOntologyFactory.setInstance(new SequenceOntology());
     }
index d992e4e..cacde84 100644 (file)
@@ -157,7 +157,7 @@ public class AlignViewController implements AlignViewControllerI
 
   @Override
   public boolean markColumnsContainingFeatures(boolean invert,
-          boolean extendCurrent, boolean toggle, String featureType)
+          boolean extendCurrent, boolean toggle, String... featureType)
   {
     // JBPNote this routine could also mark rows, not just columns.
     // need a decent query structure to allow all types of feature searches
@@ -166,7 +166,7 @@ public class AlignViewController implements AlignViewControllerI
             || extendCurrent) ? viewport.getAlignment()
                     : viewport.getSelectionGroup();
 
-    int nseq = findColumnsWithFeature(featureType, sqcol, bs);
+    int nseq = findColumnsWithFeature(sqcol, bs, featureType);
 
     ColumnSelection cs = viewport.getColumnSelection();
     if (cs == null)
@@ -174,6 +174,9 @@ public class AlignViewController implements AlignViewControllerI
       cs = new ColumnSelection();
     }
 
+    String featureTypeString = featureType.length == 1 ? featureType[0]
+            : featureType.toString();
+
     if (bs.cardinality() > 0 || invert)
     {
       boolean changed = cs.markColumns(bs, sqcol.getStartRes(),
@@ -194,7 +197,7 @@ public class AlignViewController implements AlignViewControllerI
                     invert ? MessageManager
                             .getString("label.not_containing")
                             : MessageManager.getString("label.containing"),
-                    featureType, Integer.valueOf(nseq).toString() }));
+                    featureTypeString, Integer.valueOf(nseq).toString() }));
         return true;
       }
     }
@@ -202,7 +205,7 @@ public class AlignViewController implements AlignViewControllerI
     {
       avcg.setStatus(MessageManager
               .formatMessage("label.no_feature_of_type_found", new String[]
-              { featureType }));
+              { featureTypeString }));
       if (!extendCurrent)
       {
         cs.clear();
@@ -214,17 +217,18 @@ public class AlignViewController implements AlignViewControllerI
 
   /**
    * Sets a bit in the BitSet for each column (base 0) in the sequence
-   * collection which includes a visible feature of the specified feature type.
-   * Returns the number of sequences which have the feature visible in the
-   * selected range.
+   * collection which includes a visible feature of the specified feature
+   * type(s). Returns the number of sequences which have the feature(s) visible
+   * in the selected range.
    * 
-   * @param featureType
    * @param sqcol
    * @param bs
+   * @param featureType
+   * 
    * @return
    */
-  int findColumnsWithFeature(String featureType,
-          SequenceCollectionI sqcol, BitSet bs)
+  int findColumnsWithFeature(SequenceCollectionI sqcol,
+          BitSet bs, String... featureType)
   {
     FeatureRenderer fr = alignPanel == null ? null : alignPanel
             .getFeatureRenderer();
diff --git a/src/jalview/datamodel/ontology/OntologyBase.java b/src/jalview/datamodel/ontology/OntologyBase.java
new file mode 100644 (file)
index 0000000..25dae22
--- /dev/null
@@ -0,0 +1,73 @@
+package jalview.datamodel.ontology;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A base class for models of Sequence Ontology and others
+ * 
+ * @author gmcarstairs
+ *
+ */
+public abstract class OntologyBase implements OntologyI
+{
+  @Override
+  public Set<String> getParentTerms(Set<String> terms)
+  {
+    Set<String> parents = new HashSet<>(terms);
+
+    boolean childRemoved = true;
+    while (childRemoved)
+    {
+      childRemoved = removeChild(parents);
+    }
+    return parents;
+  }
+
+  /**
+   * Removes the first term in the given set found which is a child of another
+   * term in the set. Answers true if a child was found and removed, else false.
+   * 
+   * @param terms
+   * @return
+   */
+  boolean removeChild(Set<String> terms)
+  {
+    for (String t1 : terms)
+    {
+      for (String t2 : terms)
+      {
+        if (t1 != t2)
+        {
+          if (isA(t1, t2))
+          {
+            terms.remove(t1);
+            return true;
+          }
+          if (isA(t2, t1))
+          {
+            terms.remove(t2);
+            return true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public List<String> getChildTerms(String parent, List<String> terms)
+  {
+    List<String> children = new ArrayList<>();
+    for (String term : terms)
+    {
+      if (!term.equals(parent) && isA(term, parent))
+      {
+        children.add(term);
+      }
+    }
+    return children;
+  }
+}
diff --git a/src/jalview/datamodel/ontology/OntologyI.java b/src/jalview/datamodel/ontology/OntologyI.java
new file mode 100644 (file)
index 0000000..545a3c7
--- /dev/null
@@ -0,0 +1,61 @@
+package jalview.datamodel.ontology;
+
+import java.util.List;
+import java.util.Set;
+
+public interface OntologyI
+{
+
+  /**
+   * Answers true if <code>childTerm</code> is the same as, or a sub-type
+   * (specialisation of) <code>parentTerm</code>, else false
+   * 
+   * @param childTerm
+   * @param parentTerm
+   * @return
+   */
+  boolean isA(String childTerm, String parentTerm);
+
+  /**
+   * Answers those terms in the given set which are not child terms of some
+   * other term in the set. That is, returns a set of parent terms. The input
+   * set is not modified.
+   * 
+   * @param terms
+   * @return
+   */
+  Set<String> getParentTerms(Set<String> terms);
+
+  /**
+   * Answers a (possibly empty) list of those terms in the supplied list which
+   * are a child (directly or indirectly) of <code>parent</code>. The parent
+   * term itself is not included (even if in the input list)
+   * 
+   * @param parent
+   * @param terms
+   * @return
+   */
+  List<String> getChildTerms(String parent, List<String> terms);
+
+  /**
+   * Returns a sorted list of all valid terms queried for (i.e. terms processed
+   * which were valid in the SO), using the friendly description.
+   * 
+   * This can be used to check that any hard-coded stand-in for the full SO
+   * includes all the terms needed for correct processing.
+   * 
+   * @return
+   */
+  List<String> termsFound();
+
+  /**
+   * Returns a sorted list of all invalid terms queried for (i.e. terms
+   * processed which were not found in the SO), using the friendly description.
+   * 
+   * This can be used to report any 'non-compliance' in data, and/or to report
+   * valid terms missing from any hard-coded stand-in for the full SO.
+   * 
+   * @return
+   */
+  List<String> termsNotFound();
+}
\ No newline at end of file
index 0d631e6..7842294 100644 (file)
@@ -20,6 +20,7 @@
  */
 package jalview.ext.so;
 
+import jalview.datamodel.ontology.OntologyBase;
 import jalview.io.gff.SequenceOntologyI;
 
 import java.io.BufferedInputStream;
@@ -48,7 +49,8 @@ import org.biojava.nbio.ontology.utils.Annotation;
  * A wrapper class that parses the Sequence Ontology and exposes useful access
  * methods. This version uses the BioJava parser.
  */
-public class SequenceOntology implements SequenceOntologyI
+public class SequenceOntology extends OntologyBase
+        implements SequenceOntologyI
 {
   /*
    * the parsed Ontology data as modelled by BioJava
@@ -82,10 +84,10 @@ public class SequenceOntology implements SequenceOntologyI
    */
   public SequenceOntology()
   {
-    termsFound = new ArrayList<String>();
-    termsNotFound = new ArrayList<String>();
-    termsByDescription = new HashMap<String, Term>();
-    termIsA = new HashMap<Term, List<Term>>();
+    termsFound = new ArrayList<>();
+    termsNotFound = new ArrayList<>();
+    termsByDescription = new HashMap<>();
+    termIsA = new HashMap<>();
 
     loadOntologyZipFile("so-xp-simple.obo");
   }
@@ -404,7 +406,7 @@ public class SequenceOntology implements SequenceOntologyI
    */
   protected synchronized void findParents(Term childTerm)
   {
-    List<Term> result = new ArrayList<Term>();
+    List<Term> result = new ArrayList<>();
     for (Triple triple : ontology.getTriples(childTerm, null, isA))
     {
       Term parent = triple.getObject();
index 94b38ed..62f8891 100644 (file)
@@ -5521,22 +5521,23 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   }
 
   /**
-   * Hides columns containing (or not containing) a specified feature, provided
-   * that would not leave all columns hidden
+   * Hides columns containing (or not containing) the specified feature(s),
+   * provided that would not leave all columns hidden
    * 
-   * @param featureType
    * @param columnsContaining
+   * @param featureTypes
+   * 
    * @return
    */
-  public boolean hideFeatureColumns(String featureType,
-          boolean columnsContaining)
+  public boolean hideFeatureColumns(boolean columnsContaining,
+          String... featureTypes)
   {
     boolean notForHiding = avc.markColumnsContainingFeatures(
-            columnsContaining, false, false, featureType);
+            columnsContaining, false, false, featureTypes);
     if (notForHiding)
     {
       if (avc.markColumnsContainingFeatures(!columnsContaining, false,
-              false, featureType))
+              false, featureTypes))
       {
         getViewport().hideSelectedColumns();
         return true;
index 85f2498..7045481 100644 (file)
@@ -143,7 +143,7 @@ public class CrossRefAction implements Runnable
               AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
       if (Cache.getDefault("HIDE_INTRONS", true))
       {
-        newFrame.hideFeatureColumns(SequenceOntologyI.EXON, false);
+        newFrame.hideFeatureColumns(false, SequenceOntologyI.EXON);
       }
       String newtitle = String.format("%s %s %s",
               dna ? MessageManager.getString("label.proteins")
index 1358c8f..a53d730 100644 (file)
@@ -27,9 +27,11 @@ import jalview.datamodel.SequenceI;
 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.schemabinding.version2.Filter;
 import jalview.schemabinding.version2.JalviewUserColours;
 import jalview.schemabinding.version2.MatcherSet;
@@ -62,6 +64,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;
@@ -91,6 +94,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.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
@@ -98,10 +102,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");
 
@@ -152,12 +159,6 @@ public class FeatureSettings extends JPanel
 
   int selectedRow = -1;
 
-  JButton fetchDAS = new JButton();
-
-  JButton saveDAS = new JButton();
-
-  JButton cancelDAS = new JButton();
-
   boolean resettingTable = false;
 
   /*
@@ -170,6 +171,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
    * 
@@ -187,6 +199,8 @@ public class FeatureSettings extends JPanel
 
     originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
 
+    topLevelTypes = new ArrayList<>();
+
     try
     {
       jbInit();
@@ -195,6 +209,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
@@ -205,7 +282,13 @@ public class FeatureSettings extends JPanel
         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 FILTER_COLUMN:
@@ -222,12 +305,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());
 
@@ -254,16 +334,16 @@ 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)
         {
           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);
         }
       }
 
@@ -276,8 +356,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());
         }
       }
     });
@@ -288,89 +367,44 @@ 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 child terms 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,
-          int y)
+  protected void popupMenu(final int rowSelected, final String type,
+          final Object typeCol, int x, int y)
   {
     final FeatureColourI featureColour = (FeatureColourI) typeCol;
 
@@ -383,29 +417,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);
 
@@ -469,7 +497,6 @@ public class FeatureSettings extends JPanel
           }
         }
       }
-
     });
 
     JMenuItem selCols = new JMenuItem(
@@ -479,8 +506,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
@@ -490,8 +518,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(
@@ -501,7 +530,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(
@@ -511,7 +541,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);
@@ -626,6 +657,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);
@@ -644,6 +676,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;
 
@@ -666,7 +705,6 @@ public class FeatureSettings extends JPanel
         {
           continue;
         }
-
         data[dataIndex][TYPE_COLUMN] = type;
         data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
         FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
@@ -718,7 +756,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(
@@ -1294,12 +1368,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);
@@ -1307,8 +1395,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);
@@ -1321,6 +1413,59 @@ public class FeatureSettings extends JPanel
     this.add(settingsPane);
   }
 
+  /**
+   * 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
   // ///////////////////////////////////////////////////////////////////////
@@ -1388,20 +1533,57 @@ public class FeatureSettings extends JPanel
       return v == null ? null : v.getClass();
     }
 
+    /**
+     * 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
@@ -1614,7 +1796,7 @@ public class FeatureSettings extends JPanel
 
     String type;
 
-    JButton button;
+    JButton colourButton;
 
     JColorChooser colorChooser;
 
@@ -1631,13 +1813,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
@@ -1657,7 +1839,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);
         }
@@ -1665,13 +1847,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();
@@ -1694,16 +1871,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();
@@ -1725,24 +1893,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;
     }
   }
 
@@ -1763,7 +1931,7 @@ public class FeatureSettings extends JPanel
 
     String type;
 
-    JButton button;
+    JButton filterButton;
 
     protected static final String EDIT = "edit";
 
@@ -1772,10 +1940,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);
     }
 
     /**
@@ -1784,7 +1952,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);
@@ -1801,22 +1969,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();
       }
@@ -1835,18 +1993,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;
@@ -1901,7 +2061,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(
index 55bc519..9609996 100644 (file)
@@ -29,6 +29,7 @@ import jalview.datamodel.features.FeatureMatcher;
 import jalview.datamodel.features.FeatureMatcherI;
 import jalview.datamodel.features.FeatureMatcherSet;
 import jalview.datamodel.features.FeatureMatcherSetI;
+import jalview.io.gff.SequenceOntologyFactory;
 import jalview.schemes.FeatureColour;
 import jalview.util.ColorUtils;
 import jalview.util.MessageManager;
@@ -49,7 +50,11 @@ import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.text.DecimalFormat;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 
 import javax.swing.BorderFactory;
 import javax.swing.BoxLayout;
@@ -118,10 +123,11 @@ public class FeatureTypeSettings extends JalviewDialog
 
   /*
    * the colour and filters to reset to on Cancel
+   * (including feature sub-types if modified)
    */
-  private final FeatureColourI originalColour;
+  private Map<String, FeatureColourI> originalColours;
 
-  private final FeatureMatcherSetI originalFilter;
+  private Map<String, FeatureMatcherSetI> originalFilters;
 
   /*
    * set flag to true when setting values programmatically,
@@ -206,6 +212,20 @@ public class FeatureTypeSettings extends JalviewDialog
 
   private JPanel chooseFiltersPanel;
 
+  /*
+   * feature types present in Feature Renderer which are
+   * sub-types of the one this editor is acting on
+   */
+  private final List<String> subTypes;
+
+  /*
+   * if true, filter or colour settings are also applied to 
+   * any feature sub-types in the Sequence Ontology
+   */
+  private boolean applyFiltersToSubtypes;
+
+  private boolean applyColourToSubtypes;
+
   /**
    * Constructor
    * 
@@ -217,9 +237,29 @@ public class FeatureTypeSettings extends JalviewDialog
     this.fr = frender;
     this.featureType = theType;
     ap = fr.ap;
-    originalFilter = fr.getFeatureFilter(theType);
-    originalColour = fr.getFeatureColours().get(theType);
-    
+
+    /*
+     * determine sub-types (if any) of this feature type
+     */
+    List<String> types = fr.getRenderOrder();
+    subTypes = SequenceOntologyFactory.getInstance()
+            .getChildTerms(this.featureType, types);
+    Collections.sort(subTypes); // sort for ease of reading in tooltip
+
+    /*
+     * save original colours and filters for this feature type
+     * and any sub-types, to restore on Cancel
+     */
+    originalFilters = new HashMap<>();
+    originalFilters.put(theType, fr.getFeatureFilter(theType));
+    originalColours = new HashMap<>();
+    originalColours.put(theType, fr.getFeatureColours().get(theType));
+    for (String child : subTypes)
+    {
+      originalFilters.put(child, fr.getFeatureFilter(child));
+      originalColours.put(child, fr.getFeatureColours().get(child));
+    }
+
     adjusting = true;
     
     try
@@ -723,6 +763,15 @@ public class FeatureTypeSettings extends JalviewDialog
             MessageManager.getString("action.colour"), true);
 
     /*
+     * option to apply colour to sub-types as well (if there are any)
+     */
+    if (!subTypes.isEmpty())
+    {
+      applyColourToSubtypes = false;
+      colourByPanel.add(initSubtypesPanel(false));
+    }
+
+    /*
      * simple colour radio button and colour picker
      */
     JPanel simpleColourPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
@@ -826,6 +875,46 @@ public class FeatureTypeSettings extends JalviewDialog
     return colourByPanel;
   }
 
+  /**
+   * Constructs and returns a panel with a checkbox for the option to apply any
+   * changes also to sub-types of the feature type
+   * 
+   * @return
+   */
+  protected JPanel initSubtypesPanel(final boolean forFilters)
+  {
+    JPanel toSubtypes = new JPanel(new FlowLayout(FlowLayout.LEFT));
+    toSubtypes.setBackground(Color.WHITE);
+    JCheckBox applyToSubtypesCB = new JCheckBox(MessageManager
+            .formatMessage("label.apply_to_subtypes", featureType));
+    applyToSubtypesCB.setToolTipText(getSubtypesTooltip());
+    applyToSubtypesCB.addActionListener(new ActionListener()
+    {
+      /*
+       * reset and reapply settings on toggle of checkbox
+       */
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        if (forFilters)
+        {
+          applyFiltersToSubtypes = applyToSubtypesCB.isSelected();
+          restoreOriginalFilters();
+          filtersChanged();
+        }
+        else
+        {
+          applyColourToSubtypes = applyToSubtypesCB.isSelected();
+          restoreOriginalColours();
+          colourChanged(true);
+        }
+      }
+    });
+    toSubtypes.add(applyToSubtypesCB);
+
+    return toSubtypes;
+  }
+
   private void showColourChooser(JPanel colourPanel, String key)
   {
     Color col = JColorChooser.showDialog(this,
@@ -865,9 +954,17 @@ public class FeatureTypeSettings extends JalviewDialog
     FeatureColourI acg = makeColourFromInputs();
 
     /*
-     * save the colour, and repaint stuff
+     * save the colour, and set on subtypes if selected
      */
     fr.setColour(featureType, acg);
+    if (applyColourToSubtypes)
+    {
+      for (String child : subTypes)
+      {
+        fr.setColour(child, acg);
+      }
+    }
+    refreshFeatureSettings();
     ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview);
 
     updateColoursTab();
@@ -985,9 +1082,14 @@ public class FeatureTypeSettings extends JalviewDialog
   @Override
   protected void raiseClosed()
   {
+    refreshFeatureSettings();
+  }
+
+  protected void refreshFeatureSettings()
+  {
     if (this.featureSettings != null)
     {
-      featureSettings.actionPerformed(new ActionEvent(this, 0, "CLOSED"));
+      featureSettings.actionPerformed(new ActionEvent(this, 0, "REFRESH"));
     }
   }
 
@@ -1002,16 +1104,34 @@ public class FeatureTypeSettings extends JalviewDialog
 
   /**
    * Action on Cancel is to restore colour scheme and filters as they were when
-   * the dialog was opened
+   * the dialog was opened (including any feature sub-types that may have been
+   * changed)
    */
   @Override
   public void cancelPressed()
   {
-    fr.setColour(featureType, originalColour);
-    fr.setFeatureFilter(featureType, originalFilter);
+    restoreOriginalColours();
+    restoreOriginalFilters();
     ap.paintAlignment(true, true);
   }
 
+  protected void restoreOriginalFilters()
+  {
+    for (Entry<String, FeatureMatcherSetI> entry : originalFilters
+            .entrySet())
+    {
+      fr.setFeatureFilter(entry.getKey(), entry.getValue());
+    }
+  }
+
+  protected void restoreOriginalColours()
+  {
+    for (Entry<String, FeatureColourI> entry : originalColours.entrySet())
+    {
+      fr.setColour(entry.getKey(), entry.getValue());
+    }
+  }
+
   /**
    * Action on text entry of a threshold value
    */
@@ -1150,11 +1270,25 @@ public class FeatureTypeSettings extends JalviewDialog
   {
     filters = new ArrayList<>();
 
+    JPanel outerPanel = new JPanel();
+    outerPanel.setLayout(new BoxLayout(outerPanel, BoxLayout.Y_AXIS));
+    outerPanel.setBackground(Color.white);
+
+    /*
+     * option to apply colour to sub-types as well (if there are any)
+     */
+    if (!subTypes.isEmpty())
+    {
+      applyFiltersToSubtypes = false;
+      outerPanel.add(initSubtypesPanel(true));
+    }
+
     JPanel filtersPanel = new JPanel();
     filtersPanel.setLayout(new BoxLayout(filtersPanel, BoxLayout.Y_AXIS));
     filtersPanel.setBackground(Color.white);
     JvSwingUtils.createTitledBorder(filtersPanel,
             MessageManager.getString("label.filters"), true);
+    outerPanel.add(filtersPanel);
 
     JPanel andOrPanel = initialiseAndOrPanel();
     filtersPanel.add(andOrPanel);
@@ -1167,7 +1301,7 @@ public class FeatureTypeSettings extends JalviewDialog
     chooseFiltersPanel.setBackground(Color.white);
     filtersPanel.add(chooseFiltersPanel);
 
-    return filtersPanel;
+    return outerPanel;
   }
 
   /**
@@ -1177,8 +1311,13 @@ public class FeatureTypeSettings extends JalviewDialog
    */
   private JPanel initialiseAndOrPanel()
   {
-    JPanel andOrPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+    JPanel andOrPanel = new JPanel(new BorderLayout());
     andOrPanel.setBackground(Color.white);
+
+//    JPanel panel1 = new JPanel(new FlowLayout(FlowLayout.LEFT));
+//    andOrPanel.add(panel1, BorderLayout.WEST);
+//    panel1.setBackground(Color.white);
+//    panel1.setBorder(BorderFactory.createLineBorder(debugBorderColour));
     andFilters = new JRadioButton(MessageManager.getString("label.and"));
     orFilters = new JRadioButton(MessageManager.getString("label.or"));
     ActionListener actionListener = new ActionListener()
@@ -1199,10 +1338,29 @@ public class FeatureTypeSettings extends JalviewDialog
             new JLabel(MessageManager.getString("label.join_conditions")));
     andOrPanel.add(andFilters);
     andOrPanel.add(orFilters);
+
     return andOrPanel;
   }
 
   /**
+   * Builds a tooltip for the 'Apply to subtypes' checkbox with a list of
+   * subtypes of this feature type
+   * 
+   * @return
+   */
+  protected String getSubtypesTooltip()
+  {
+    StringBuilder sb = new StringBuilder(20 * subTypes.size());
+    sb.append(MessageManager.getString("label.apply_also_to"));
+    for (String child : subTypes)
+    {
+      sb.append("<br>").append(child);
+    }
+    String tooltip = JvSwingUtils.wrapTooltip(true, sb.toString());
+    return tooltip;
+  }
+
+  /**
    * Refreshes the display to show any filters currently configured for the
    * selected feature type (editable, with 'remove' option), plus one extra row
    * for adding a condition. This should be called after a filter has been
@@ -1736,6 +1894,15 @@ public class FeatureTypeSettings extends JalviewDialog
      * (note this might now be an empty filter with no conditions)
      */
     fr.setFeatureFilter(featureType, combined.isEmpty() ? null : combined);
+    if (applyFiltersToSubtypes)
+    {
+      for (String child : subTypes)
+      {
+        fr.setFeatureFilter(child, combined.isEmpty() ? null : combined);
+      }
+    }
+
+    refreshFeatureSettings();
     ap.paintAlignment(true, true);
 
     updateFiltersTab();
index 8754fbb..0982dcc 100755 (executable)
@@ -1029,7 +1029,7 @@ public class SequenceFetcher extends JPanel implements Runnable
         }
         if (Cache.getDefault("HIDE_INTRONS", true))
         {
-          af.hideFeatureColumns(SequenceOntologyI.EXON, false);
+          af.hideFeatureColumns(false, SequenceOntologyI.EXON);
         }
         if (newAlframes != null)
         {
index 307e1d1..e9b9923 100644 (file)
@@ -20,9 +20,9 @@
  */
 package jalview.io.gff;
 
-import java.util.List;
+import jalview.datamodel.ontology.OntologyI;
 
-public interface SequenceOntologyI
+public interface SequenceOntologyI extends OntologyI
 {
   /*
    * selected commonly used values for quick reference
@@ -62,28 +62,4 @@ public interface SequenceOntologyI
 
   // SO:0000704
   public static final String GENE = "gene";
-
-  public boolean isA(String childTerm, String parentTerm);
-
-  /**
-   * Returns a sorted list of all valid terms queried for (i.e. terms processed
-   * which were valid in the SO), using the friendly description.
-   * 
-   * This can be used to check that any hard-coded stand-in for the full SO
-   * includes all the terms needed for correct processing.
-   * 
-   * @return
-   */
-  public List<String> termsFound();
-
-  /**
-   * Returns a sorted list of all invalid terms queried for (i.e. terms
-   * processed which were not found in the SO), using the friendly description.
-   * 
-   * This can be used to report any 'non-compliance' in data, and/or to report
-   * valid terms missing from any hard-coded stand-in for the full SO.
-   * 
-   * @return
-   */
-  public List<String> termsNotFound();
 }
index 72e906c..670d887 100644 (file)
@@ -20,6 +20,8 @@
  */
 package jalview.io.gff;
 
+import jalview.datamodel.ontology.OntologyBase;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -38,7 +40,8 @@ import java.util.Map;
  * @author gmcarstairs
  *
  */
-public class SequenceOntologyLite implements SequenceOntologyI
+public class SequenceOntologyLite extends OntologyBase
+        implements SequenceOntologyI
 {
   /*
    * initial selection of types of interest when processing Ensembl features
@@ -80,8 +83,11 @@ public class SequenceOntologyLite implements SequenceOntologyI
     { "sequence_variant", "sequence_variant" },
     { "structural_variant", "sequence_variant" },
     { "feature_variant", "sequence_variant" },
+    { "upstream_gene_variant", "sequence_variant" },
     { "gene_variant", "sequence_variant" },
     { "transcript_variant", "sequence_variant" },
+    { "non_coding_transcript_variant", "sequence_variant" },
+    { "non_coding_transcript_exon_variant", "sequence_variant" },
     // NB Ensembl uses NMD_transcript_variant as if a 'transcript'
     // but we model it here correctly as per the SO
     { "NMD_transcript_variant", "sequence_variant" },
index 335529c..78c6da2 100644 (file)
@@ -118,7 +118,10 @@ public class ConsensusThread extends AlignCalcWorker
   protected void eraseConsensus(int aWidth)
   {
     AlignmentAnnotation consensus = getConsensusAnnotation();
-    consensus.annotations = new Annotation[aWidth];
+    if (consensus != null)
+    {
+      consensus.annotations = new Annotation[aWidth];
+    }
     AlignmentAnnotation gap = getGapAnnotation();
     if (gap != null)
     {
index efee93b..ab32616 100644 (file)
@@ -60,25 +60,24 @@ public class AlignViewControllerTest
   public void testFindColumnsWithFeature()
   {
     SequenceI seq1 = new Sequence("seq1", "-a-MMMaaaaaaaaaaaaaaaa");
-    SequenceI seq2 = new Sequence("seq2", "aa--aMM-MMMMMaaaaaaaaaa");
+    SequenceI seq2 = new Sequence("seq2/11-30", "aa--aMM-MMMMMaaaaaaaaaa");
     SequenceI seq3 = new Sequence("seq3", "abcab-caD-aaMMMMMaaaaa");
     SequenceI seq4 = new Sequence("seq4", "abc--abcaaaaaaaaaaaaaa");
 
     /*
-     * features start/end are base 1
+     * features
      */
-    seq1.addSequenceFeature(new SequenceFeature("Metal", "desc", 2, 4, 0f,
-            null));
-    seq1.addSequenceFeature(new SequenceFeature("Helix", "desc", 1, 15, 0f,
-            null));
-    seq2.addSequenceFeature(new SequenceFeature("Metal", "desc", 4, 10,
-            10f,
-            null));
-    seq3.addSequenceFeature(new SequenceFeature("Metal", "desc", 11, 15,
-            10f, null));
+    seq1.addSequenceFeature(
+            new SequenceFeature("Metal", "desc", 2, 4, 0f, null));
+    seq1.addSequenceFeature(
+            new SequenceFeature("Helix", "desc", 1, 15, 0f, null));
+    seq2.addSequenceFeature(
+            new SequenceFeature("Metal", "desc", 14, 20, 10f, null));
+    seq3.addSequenceFeature(
+            new SequenceFeature("Metal", "desc", 11, 15, 10f, null));
     // disulfide bond is a 'contact feature' - only select its 'start' and 'end'
-    seq3.addSequenceFeature(new SequenceFeature("disulfide bond", "desc",
-            8, 12, 0f, null));
+    seq3.addSequenceFeature(
+            new SequenceFeature("disulfide bond", "desc", 8, 12, 0f, null));
 
     /*
      * select the first five columns --> Metal in seq1 cols 4-5
@@ -94,15 +93,17 @@ public class AlignViewControllerTest
     /*
      * set features visible on a viewport as only visible features are selected
      */
-    AlignFrame af = new AlignFrame(new Alignment(new SequenceI[] { seq1,
-        seq2, seq3, seq4 }), 100, 100);
+    Alignment al = new Alignment(
+            new SequenceI[]
+            { seq1, seq2, seq3, seq4 });
+    AlignFrame af = new AlignFrame(al, 100, 100);
     af.getFeatureRenderer().findAllFeatures(true);
 
     AlignViewController avc = new AlignViewController(af, af.getViewport(),
             af.alignPanel);
 
     BitSet bs = new BitSet();
-    int seqCount = avc.findColumnsWithFeature("Metal", sg, bs);
+    int seqCount = avc.findColumnsWithFeature(sg, bs, "Metal");
     assertEquals(1, seqCount);
     assertEquals(2, bs.cardinality());
     assertTrue(bs.get(3)); // base 0
@@ -113,7 +114,7 @@ public class AlignViewControllerTest
      */
     sg.setEndRes(6);
     bs.clear();
-    seqCount = avc.findColumnsWithFeature("Metal", sg, bs);
+    seqCount = avc.findColumnsWithFeature(sg, bs, "Metal");
     assertEquals(2, seqCount);
     assertEquals(4, bs.cardinality());
     assertTrue(bs.get(3));
@@ -127,7 +128,7 @@ public class AlignViewControllerTest
     sg.setStartRes(13);
     sg.setEndRes(13);
     bs.clear();
-    seqCount = avc.findColumnsWithFeature("Metal", sg, bs);
+    seqCount = avc.findColumnsWithFeature(sg, bs, "Metal");
     assertEquals(1, seqCount);
     assertEquals(1, bs.cardinality());
     assertTrue(bs.get(13));
@@ -138,7 +139,7 @@ public class AlignViewControllerTest
     sg.setStartRes(17);
     sg.setEndRes(19);
     bs.clear();
-    seqCount = avc.findColumnsWithFeature("Metal", sg, bs);
+    seqCount = avc.findColumnsWithFeature(sg, bs, "Metal");
     assertEquals(0, seqCount);
     assertEquals(0, bs.cardinality());
 
@@ -154,7 +155,7 @@ public class AlignViewControllerTest
     sg.setStartRes(0);
     sg.setEndRes(6);
     bs.clear();
-    seqCount = avc.findColumnsWithFeature("Metal", sg, bs);
+    seqCount = avc.findColumnsWithFeature(sg, bs, "Metal");
     assertEquals(1, seqCount);
     assertEquals(2, bs.cardinality());
     assertTrue(bs.get(5));
@@ -166,7 +167,7 @@ public class AlignViewControllerTest
     sg.setStartRes(10);
     sg.setEndRes(12);
     bs.clear();
-    seqCount = avc.findColumnsWithFeature("disulfide bond", sg, bs);
+    seqCount = avc.findColumnsWithFeature(sg, bs, "disulfide bond");
     assertEquals(0, seqCount);
     assertEquals(0, bs.cardinality());
 
@@ -176,19 +177,42 @@ public class AlignViewControllerTest
     sg.setStartRes(5);
     sg.setEndRes(17);
     bs.clear();
-    seqCount = avc.findColumnsWithFeature("disulfide bond", sg, bs);
+    seqCount = avc.findColumnsWithFeature(sg, bs, "disulfide bond");
     assertEquals(1, seqCount);
     assertEquals(2, bs.cardinality());
     assertTrue(bs.get(8));
     assertTrue(bs.get(13));
 
     /*
+     * look for multiple features; should match 
+     * transcript_variant in seq3 positions 3-6, columns 3-7
+     * sequence_variant in seq2 positions 15-18, columns 7-11
+     * transcript_variant in seq3 positions 8 and 12, columns 9 and 14 
+     */
+    seq3.addSequenceFeature(new SequenceFeature("transcript_variant",
+            "desc", 3, 6, 0f, null));
+    seq2.addSequenceFeature(new SequenceFeature("sequence_variant", "desc",
+            15, 18, 0f, null));
+    sg.setStartRes(0);
+    sg.setEndRes(20);
+    bs.clear();
+    seqCount = avc.findColumnsWithFeature(sg, bs, "transcript_variant",
+            "sequence_variant", "disulfide bond", "junk");
+    assertEquals(2, seqCount);
+    assertEquals(10, bs.cardinality()); // 2-10 and 13, base 0
+    for (int i = 2; i <= 10; i++)
+    {
+      assertTrue(bs.get(i));
+    }
+    assertTrue(bs.get(13));
+
+    /*
      * look for a feature that isn't there
      */
     sg.setStartRes(0);
     sg.setEndRes(19);
     bs.clear();
-    seqCount = avc.findColumnsWithFeature("Pfam", sg, bs);
+    seqCount = avc.findColumnsWithFeature(sg, bs, "Pfam");
     assertEquals(0, seqCount);
     assertEquals(0, bs.cardinality());
   }
index 31e1887..c7776a3 100644 (file)
  */
 package jalview.ext.so;
 
-import static org.testng.AssertJUnit.assertFalse;
-import static org.testng.AssertJUnit.assertTrue;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
 
+import jalview.datamodel.ontology.OntologyI;
 import jalview.gui.JvOptionPane;
-import jalview.io.gff.SequenceOntologyI;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -39,7 +46,7 @@ public class SequenceOntologyTest
     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
   }
 
-  private SequenceOntologyI so;
+  private OntologyI so;
 
   @BeforeClass(alwaysRun = true)
   public void setUp()
@@ -132,4 +139,42 @@ public class SequenceOntologyTest
     assertTrue(so.isA("inframe_deletion", "sequence_variant"));
     assertTrue(so.isA("inframe_insertion", "sequence_variant"));
   }
+
+  @Test(groups = "Functional")
+  public void testGetChildTerms()
+  {
+    List<String> terms = Collections.<String> emptyList();
+    List<String> children = so.getChildTerms("exon", terms);
+    assertTrue(children.isEmpty());
+  
+    terms = Arrays.asList("gene", "transcript", "snRNA", "junk", "mRNA");
+    children = so.getChildTerms("exon", terms);
+    assertTrue(children.isEmpty());
+    children = so.getChildTerms("transcript", terms);
+    assertEquals(children.size(), 2);
+    assertTrue(children.contains("snRNA"));
+    assertTrue(children.contains("mRNA"));
+  
+    terms = Arrays.asList("gene", "transcript", "synonymous_variant",
+            "stop_lost", "chain");
+    children = so.getChildTerms("sequence_variant", terms);
+    assertEquals(children.size(), 2);
+    assertTrue(children.contains("synonymous_variant"));
+    assertTrue(children.contains("stop_lost"));
+  }
+
+  @Test(groups = "Functional")
+  public void testGetParentTerms()
+  {
+    Set<String> terms = new HashSet<>();
+    terms.add("sequence_variant");
+    terms.add("NMD_transcript_variant");
+    terms.add("stop_lost");
+    terms.add("chain"); // not an SO term
+  
+    Set<String> parents = so.getParentTerms(terms);
+    assertEquals(parents.size(), 2);
+    assertTrue(parents.contains("sequence_variant"));
+    assertTrue(parents.contains("chain"));
+  }
 }
index b0aaab9..5018e4b 100644 (file)
@@ -70,15 +70,18 @@ public class AlignFrameTest
   @Test(groups = "Functional")
   public void testHideFeatureColumns()
   {
-    SequenceI seq1 = new Sequence("Seq1", "ABCDEFGHIJ");
-    SequenceI seq2 = new Sequence("Seq2", "ABCDEFGHIJ");
-    seq1.addSequenceFeature(new SequenceFeature("Metal", "", 1, 5, 0f, null));
-    seq2.addSequenceFeature(new SequenceFeature("Metal", "", 6, 10, 10f,
-            null));
-    seq1.addSequenceFeature(new SequenceFeature("Turn", "", 2, 4,
-            Float.NaN, null));
-    seq2.addSequenceFeature(new SequenceFeature("Turn", "", 7, 9,
-            Float.NaN, null));
+    SequenceI seq1 = new Sequence("Seq1/01-10", "A---BCDEFG-HIJ");
+    SequenceI seq2 = new Sequence("Seq2/11-20", "-AB-CDEF--GHIJ");
+    String METAL = "Metal";
+    String TURN = "Turn";
+    seq1.addSequenceFeature(
+            new SequenceFeature(METAL, "", 1, 5, 0f, null));
+    seq2.addSequenceFeature(
+            new SequenceFeature(METAL, "", 16, 20, 10f, null));
+    seq1.addSequenceFeature(
+            new SequenceFeature(TURN, "", 2, 4, Float.NaN, null));
+    seq2.addSequenceFeature(
+            new SequenceFeature(TURN, "", 17, 19, Float.NaN, null));
     AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
     AlignFrame alignFrame = new AlignFrame(al, al.getWidth(),
             al.getHeight());
@@ -91,62 +94,91 @@ public class AlignFrameTest
     /*
      * hiding a feature not present does nothing
      */
-    assertFalse(alignFrame.hideFeatureColumns("exon", true));
+    assertFalse(alignFrame.hideFeatureColumns(true, "exon"));
     assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty());
-
     assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns()
             .getNumberOfRegions(), 0);
 
-    assertFalse(alignFrame.hideFeatureColumns("exon", false));
+    assertFalse(alignFrame.hideFeatureColumns(false, "exon"));
     assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty());
-
     assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns()
             .getNumberOfRegions(), 0);
 
     /*
      * hiding a feature in all columns does nothing
      */
-    assertFalse(alignFrame.hideFeatureColumns("Metal", true));
+    assertFalse(alignFrame.hideFeatureColumns(true, METAL));
     assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty());
-
     assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns()
             .getNumberOfRegions(), 0);
 
 
     /*
      * threshold Metal to hide features where score < 5
-     * seq1 feature in columns 1-5 is hidden
-     * seq2 feature in columns 6-10 is shown
+     * seq1 feature in columns 1-8 is hidden
+     * seq2 feature in columns 8-14 is shown
+     * result: columns 8-14 are hidden
+     * note this includes gapped columns spanned by the feature
      */
     FeatureColourI fc = new FeatureColour(Color.red, Color.blue, 0f, 10f);
     fc.setAboveThreshold(true);
     fc.setThreshold(5f);
-    alignFrame.getFeatureRenderer().setColour("Metal", fc);
-    assertTrue(alignFrame.hideFeatureColumns("Metal", true));
+    alignFrame.getFeatureRenderer().setColour(METAL, fc);
+    assertTrue(alignFrame.hideFeatureColumns(true, METAL));
     HiddenColumns hidden = alignFrame.getViewport().getAlignment().getHiddenColumns();
     assertEquals(hidden.getNumberOfRegions(), 1);
     Iterator<int[]> regions = hidden.iterator();
-    int[] next = regions.next();
-    assertEquals(next[0], 5);
-    assertEquals(next[1], 9);
+    assertEquals(regions.next(), new int[] { 7, 13 }); // base 0
+    assertFalse(regions.hasNext());
 
     /*
      * hide a feature present in some columns
-     * sequence positions [2-4], [7-9] are column positions
-     * [1-3], [6-8] base zero
+     * seq1 positions [2-4] are column positions [4-6] base zero
+     * seq2 positions [17-19] are column positions [10-12] base zero
      */
     alignFrame.getViewport().showAllHiddenColumns();
-    assertTrue(alignFrame.hideFeatureColumns("Turn", true));
+    assertTrue(alignFrame.hideFeatureColumns(true, TURN));
     regions = alignFrame.getViewport().getAlignment()
             .getHiddenColumns().iterator();
     assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns()
             .getNumberOfRegions(), 2);
-    next = regions.next();
-    assertEquals(next[0], 1);
-    assertEquals(next[1], 3);
-    next = regions.next();
-    assertEquals(next[0], 6);
-    assertEquals(next[1], 8);
+    assertEquals(regions.next(), new int[] { 4, 6 });
+    assertEquals(regions.next(), new int[] { 10, 12 });
+    assertFalse(regions.hasNext());
+    
+    /*
+     * hiding a contact feature should only hide start and end positions,
+     * not the intermediate columns
+     */
+    String DISULFIDE = "Disulfide Bond";
+    seq1.addSequenceFeature(
+            new SequenceFeature(DISULFIDE, "", 1, 5, 0f, null));
+    alignFrame.getViewport().showAllHiddenColumns();
+    assertTrue(alignFrame.hideFeatureColumns(true, DISULFIDE));
+    regions = alignFrame.getViewport().getAlignment().getHiddenColumns()
+            .iterator();
+    assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns()
+            .getNumberOfRegions(), 2);
+    assertEquals(regions.next(), new int[] { 0, 0 });
+    assertEquals(regions.next(), new int[] { 7, 7 });
+    assertFalse(regions.hasNext());
+
+    /*
+     * hide multiple feature types:
+     * TURN columns hides 4-6, 10-12
+     * DISULFIDE columns hides 0, 7
+     * combined is { 0-0, 4-7, 10-12 }
+     */
+    alignFrame.getViewport().showAllHiddenColumns();
+    assertTrue(alignFrame.hideFeatureColumns(true, TURN, DISULFIDE));
+    regions = alignFrame.getViewport().getAlignment().getHiddenColumns()
+            .iterator();
+    assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns()
+            .getNumberOfRegions(), 3);
+    assertEquals(regions.next(), new int[] { 0, 0 });
+    assertEquals(regions.next(), new int[] { 4, 7 });
+    assertEquals(regions.next(), new int[] { 10, 12 });
+    assertFalse(regions.hasNext());
   }
 
   @BeforeClass(alwaysRun = true)
@@ -155,8 +187,9 @@ public class AlignFrameTest
     /*
      * use read-only test properties file
      */
-    Cache.loadProperties("test/jalview/io/testProps.jvprops");
-    Jalview.main(new String[] { "-nonews" });
+    Jalview.main(
+            new String[]
+            { "-nonews", "-props", "test/jalview/io/testProps.jvprops" });
   }
 
   @AfterMethod(alwaysRun = true)
index 0766666..3076f96 100644 (file)
@@ -1,17 +1,33 @@
 package jalview.io.gff;
 
-import static org.testng.AssertJUnit.assertFalse;
-import static org.testng.AssertJUnit.assertTrue;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
 
+import jalview.datamodel.ontology.OntologyI;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 public class SequenceOntologyLiteTest
 {
+  private OntologyI so;
+
+  @BeforeClass(alwaysRun = true)
+  public void setUp()
+  {
+    so = new SequenceOntologyLite();
+  }
+
   @Test(groups = "Functional")
   public void testIsA_sequenceVariant()
   {
-    SequenceOntologyI so = new SequenceOntologyLite();
-
     assertFalse(so.isA("CDS", "sequence_variant"));
     assertTrue(so.isA("sequence_variant", "sequence_variant"));
 
@@ -34,4 +50,42 @@ public class SequenceOntologyLiteTest
     assertTrue(so.isA("inframe_insertion", "sequence_variant"));
     assertTrue(so.isA("splice_region_variant", "sequence_variant"));
   }
+
+  @Test(groups = "Functional")
+  public void testGetParentTerms()
+  {
+    Set<String> terms = new HashSet<>();
+    terms.add("sequence_variant");
+    terms.add("NMD_transcript_variant");
+    terms.add("stop_lost");
+    terms.add("chain"); // not an SO term
+  
+    Set<String> parents = so.getParentTerms(terms);
+    assertEquals(parents.size(), 2);
+    assertTrue(parents.contains("sequence_variant"));
+    assertTrue(parents.contains("chain"));
+  }
+
+  @Test(groups = "Functional")
+  public void testGetChildTerms()
+  {
+    List<String> terms = Collections.<String> emptyList();
+    List<String> children = so.getChildTerms("exon", terms);
+    assertTrue(children.isEmpty());
+
+    terms = Arrays.asList("gene", "transcript", "snRNA", "junk", "mRNA");
+    children = so.getChildTerms("exon", terms);
+    assertTrue(children.isEmpty());
+    children = so.getChildTerms("transcript", terms);
+    assertEquals(children.size(), 2);
+    assertTrue(children.contains("snRNA"));
+    assertTrue(children.contains("mRNA"));
+
+    terms = Arrays.asList("gene", "transcript", "synonymous_variant",
+            "stop_lost", "chain");
+    children = so.getChildTerms("sequence_variant", terms);
+    assertEquals(children.size(), 2);
+    assertTrue(children.contains("synonymous_variant"));
+    assertTrue(children.contains("stop_lost"));
+  }
 }