From: Jim Procter Date: Wed, 19 Sep 2018 14:56:26 +0000 (+0100) Subject: Merge branch 'develop' into features/JAL-3010ontologyFeatureSettings X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=345e36bca9ccb985a560aeaade58a41b09b69bac;hp=0846d5b45274088d3fbec45f9af23eaab8f2f4de;p=jalview.git Merge branch 'develop' into features/JAL-3010ontologyFeatureSettings --- diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index ae5b0e7..dd66340 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -814,6 +814,7 @@ label.rest_client_submit = {0} using {1} label.fetch_retrieve_from =Retrieve from {0} label.fetch_retrieve_from_all_sources = Retrieve from all {0} sources in {1}
First is :{2} label.feature_settings_click_drag = Drag up or down to change render order.
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 = Click to show brief description
Right click for further information. @@ -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 diff --git a/resources/lang/Messages_es.properties b/resources/lang/Messages_es.properties index 555977d..58c0d88 100644 --- a/resources/lang/Messages_es.properties +++ b/resources/lang/Messages_es.properties @@ -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}
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.
Haga doble clic para seleccionar las columnas que contienen las características del alineamiento/selección actual.
+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.
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
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
@@ -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 diff --git a/src/jalview/api/AlignViewControllerI.java b/src/jalview/api/AlignViewControllerI.java index a7ec69e..f7575c3 100644 --- a/src/jalview/api/AlignViewControllerI.java +++ b/src/jalview/api/AlignViewControllerI.java @@ -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 diff --git a/src/jalview/bin/Jalview.java b/src/jalview/bin/Jalview.java index 3270144..c41f291 100755 --- a/src/jalview/bin/Jalview.java +++ b/src/jalview/bin/Jalview.java @@ -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()); } diff --git a/src/jalview/controller/AlignViewController.java b/src/jalview/controller/AlignViewController.java index d992e4e..cacde84 100644 --- a/src/jalview/controller/AlignViewController.java +++ b/src/jalview/controller/AlignViewController.java @@ -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 index 0000000..25dae22 --- /dev/null +++ b/src/jalview/datamodel/ontology/OntologyBase.java @@ -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 getParentTerms(Set terms) + { + Set 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 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 getChildTerms(String parent, List terms) + { + List 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 index 0000000..545a3c7 --- /dev/null +++ b/src/jalview/datamodel/ontology/OntologyI.java @@ -0,0 +1,61 @@ +package jalview.datamodel.ontology; + +import java.util.List; +import java.util.Set; + +public interface OntologyI +{ + + /** + * Answers true if childTerm is the same as, or a sub-type + * (specialisation of) parentTerm, 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 getParentTerms(Set terms); + + /** + * Answers a (possibly empty) list of those terms in the supplied list which + * are a child (directly or indirectly) of parent. The parent + * term itself is not included (even if in the input list) + * + * @param parent + * @param terms + * @return + */ + List getChildTerms(String parent, List 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 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 termsNotFound(); +} \ No newline at end of file diff --git a/src/jalview/ext/so/SequenceOntology.java b/src/jalview/ext/so/SequenceOntology.java index 0d631e6..7842294 100644 --- a/src/jalview/ext/so/SequenceOntology.java +++ b/src/jalview/ext/so/SequenceOntology.java @@ -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(); - termsNotFound = new ArrayList(); - termsByDescription = new HashMap(); - termIsA = new HashMap>(); + 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 result = new ArrayList(); + List result = new ArrayList<>(); for (Triple triple : ontology.getTriples(childTerm, null, isA)) { Term parent = triple.getObject(); diff --git a/src/jalview/gui/AlignFrame.java b/src/jalview/gui/AlignFrame.java index 94b38ed..62f8891 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -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; diff --git a/src/jalview/gui/CrossRefAction.java b/src/jalview/gui/CrossRefAction.java index 85f2498..7045481 100644 --- a/src/jalview/gui/CrossRefAction.java +++ b/src/jalview/gui/CrossRefAction.java @@ -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") diff --git a/src/jalview/gui/FeatureSettings.java b/src/jalview/gui/FeatureSettings.java index 1358c8f..a53d730 100644 --- a/src/jalview/gui/FeatureSettings.java +++ b/src/jalview/gui/FeatureSettings.java @@ -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 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 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 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 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 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 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 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() + { + @Override + public boolean include( + Entry 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( diff --git a/src/jalview/gui/FeatureTypeSettings.java b/src/jalview/gui/FeatureTypeSettings.java index 55bc519..9609996 100644 --- a/src/jalview/gui/FeatureTypeSettings.java +++ b/src/jalview/gui/FeatureTypeSettings.java @@ -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 originalColours; - private final FeatureMatcherSetI originalFilter; + private Map 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 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 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 entry : originalFilters + .entrySet()) + { + fr.setFeatureFilter(entry.getKey(), entry.getValue()); + } + } + + protected void restoreOriginalColours() + { + for (Entry 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("
").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(); diff --git a/src/jalview/gui/SequenceFetcher.java b/src/jalview/gui/SequenceFetcher.java index 8754fbb..0982dcc 100755 --- a/src/jalview/gui/SequenceFetcher.java +++ b/src/jalview/gui/SequenceFetcher.java @@ -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) { diff --git a/src/jalview/io/gff/SequenceOntologyI.java b/src/jalview/io/gff/SequenceOntologyI.java index 307e1d1..e9b9923 100644 --- a/src/jalview/io/gff/SequenceOntologyI.java +++ b/src/jalview/io/gff/SequenceOntologyI.java @@ -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 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 termsNotFound(); } diff --git a/src/jalview/io/gff/SequenceOntologyLite.java b/src/jalview/io/gff/SequenceOntologyLite.java index 72e906c..670d887 100644 --- a/src/jalview/io/gff/SequenceOntologyLite.java +++ b/src/jalview/io/gff/SequenceOntologyLite.java @@ -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" }, diff --git a/src/jalview/workers/ConsensusThread.java b/src/jalview/workers/ConsensusThread.java index 335529c..78c6da2 100644 --- a/src/jalview/workers/ConsensusThread.java +++ b/src/jalview/workers/ConsensusThread.java @@ -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) { diff --git a/test/jalview/controller/AlignViewControllerTest.java b/test/jalview/controller/AlignViewControllerTest.java index efee93b..ab32616 100644 --- a/test/jalview/controller/AlignViewControllerTest.java +++ b/test/jalview/controller/AlignViewControllerTest.java @@ -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()); } diff --git a/test/jalview/ext/so/SequenceOntologyTest.java b/test/jalview/ext/so/SequenceOntologyTest.java index 31e1887..c7776a3 100644 --- a/test/jalview/ext/so/SequenceOntologyTest.java +++ b/test/jalview/ext/so/SequenceOntologyTest.java @@ -20,11 +20,18 @@ */ 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 terms = Collections. emptyList(); + List 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 terms = new HashSet<>(); + terms.add("sequence_variant"); + terms.add("NMD_transcript_variant"); + terms.add("stop_lost"); + terms.add("chain"); // not an SO term + + Set parents = so.getParentTerms(terms); + assertEquals(parents.size(), 2); + assertTrue(parents.contains("sequence_variant")); + assertTrue(parents.contains("chain")); + } } diff --git a/test/jalview/gui/AlignFrameTest.java b/test/jalview/gui/AlignFrameTest.java index b0aaab9..5018e4b 100644 --- a/test/jalview/gui/AlignFrameTest.java +++ b/test/jalview/gui/AlignFrameTest.java @@ -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 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) diff --git a/test/jalview/io/gff/SequenceOntologyLiteTest.java b/test/jalview/io/gff/SequenceOntologyLiteTest.java index 0766666..3076f96 100644 --- a/test/jalview/io/gff/SequenceOntologyLiteTest.java +++ b/test/jalview/io/gff/SequenceOntologyLiteTest.java @@ -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 terms = new HashSet<>(); + terms.add("sequence_variant"); + terms.add("NMD_transcript_variant"); + terms.add("stop_lost"); + terms.add("chain"); // not an SO term + + Set parents = so.getParentTerms(terms); + assertEquals(parents.size(), 2); + assertTrue(parents.contains("sequence_variant")); + assertTrue(parents.contains("chain")); + } + + @Test(groups = "Functional") + public void testGetChildTerms() + { + List terms = Collections. emptyList(); + List 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")); + } }