label.reset_to_defaults = Reset to defaults
label.oview_calc = Recalculating overview...
label.feature_details = Feature details
+label.matchCondition_contains = Contains
+label.matchCondition_notcontains = Does not contain
+label.matchCondition_matches = Matches
+label.matchCondition_notmatches = Does not match
+label.matchCondition_eq = =
+label.matchCondition_ne = not =
+label.matchCondition_lt = <
+label.matchCondition_le = <=
+label.matchCondition_gt = >
+label.matchCondition_ge = >=
+label.numeric_required = The value should be numeric
+label.no_attributes_known = No attributes known
+label.filters = Filters
+label.match_condition = Match condition
+label.join_conditions = Join conditions with
+label.feature_to_filter = Feature to filter
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
+import jalview.util.matcher.KeyedMatcherSetI;
import java.awt.Color;
import java.awt.Graphics;
*/
float getTransparency();
+ /**
+ * Answers the filters applied to the given feature type, or null if none is
+ * set
+ *
+ * @param featureType
+ * @return
+ */
+ KeyedMatcherSetI getFeatureFilter(String featureType);
+
+ /**
+ * Answers a shallow copy of the feature filters map
+ *
+ * @return
+ */
+ public Map<String, KeyedMatcherSetI> getFeatureFilters();
+
+ /**
+ * Sets the filters for the feature type, or removes them if a null or empty
+ * filter is passed
+ *
+ * @param featureType
+ * @param filter
+ */
+ void setFeatureFilter(String featureType, KeyedMatcherSetI filter);
+
+ /**
+ * Replaces all feature filters with the given map
+ *
+ * @param filters
+ */
+ void setFeatureFilters(Map<String, KeyedMatcherSetI> filters);
+
+ /**
+ * Returns the colour for a particular feature instance. This includes
+ * calculation of 'colour by label', or of a graduated score colour, if
+ * applicable.
+ * <p>
+ * Returns null if
+ * <ul>
+ * <li>feature type is not visible, or</li>
+ * <li>feature group is not visible, or</li>
+ * <li>feature values lie outside any colour threshold, or</li>
+ * <li>feature is excluded by filter conditions</li>
+ * </ul>
+ *
+ * @param feature
+ * @return
+ */
+ Color getColour(SequenceFeature feature);
}
* alignment column
* @param seq
* index of sequence in alignment
- * @return position of column in sequence or -1 if at gap
*/
void setStatusMessage(SequenceI sequence, int column, int seq)
{
private AlignViewControllerGuiI avcg;
public AlignViewController(AlignViewControllerGuiI alignFrame,
- AlignViewportI viewport, AlignmentViewPanel alignPanel)
+ AlignViewportI vp, AlignmentViewPanel ap)
{
this.avcg = alignFrame;
- this.viewport = viewport;
- this.alignPanel = alignPanel;
+ this.viewport = vp;
+ this.alignPanel = ap;
}
@Override
- public void setViewportAndAlignmentPanel(AlignViewportI viewport,
- AlignmentViewPanel alignPanel)
+ public void setViewportAndAlignmentPanel(AlignViewportI vp,
+ AlignmentViewPanel ap)
{
- this.alignPanel = alignPanel;
- this.viewport = viewport;
-
+ this.alignPanel = ap;
+ this.viewport = vp;
}
@Override
/**
* Sets a bit in the BitSet for each column (base 0) in the sequence
- * collection which includes the specified feature type. Returns the number of
- * sequences which have the feature in the selected range.
+ * 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.
*
* @param featureType
* @param sqcol
* @param bs
* @return
*/
- static int findColumnsWithFeature(String featureType,
+ int findColumnsWithFeature(String featureType,
SequenceCollectionI sqcol, BitSet bs)
{
+ FeatureRenderer fr = alignPanel == null ? null : alignPanel
+ .getFeatureRenderer();
+
final int startColumn = sqcol.getStartRes() + 1; // converted to base 1
final int endColumn = sqcol.getEndRes() + 1;
List<SequenceI> seqs = sqcol.getSequences();
List<SequenceFeature> sfs = sq.findFeatures(startColumn,
endColumn, featureType);
- if (!sfs.isEmpty())
- {
- nseq++;
- }
-
+ boolean found = false;
for (SequenceFeature sf : sfs)
{
+ if (fr.getColour(sf) == null)
+ {
+ continue;
+ }
+ if (!found)
+ {
+ nseq++;
+ }
+ found = true;
+
int sfStartCol = sq.findIndex(sf.getBegin());
int sfEndCol = sq.findIndex(sf.getEnd());
package jalview.datamodel;
import jalview.datamodel.features.FeatureAttributeType;
+import jalview.datamodel.features.FeatureAttributes;
import jalview.datamodel.features.FeatureLocationI;
import jalview.datamodel.features.FeatureSourceI;
import jalview.datamodel.features.FeatureSources;
}
/**
+ * Answers the value of the specified attribute as string, or null if no such
+ * value
+ *
+ * @param key
+ * @return
+ */
+ public String getValueAsString(String key)
+ {
+ if (otherDetails == null)
+ {
+ return null;
+ }
+ Object value = otherDetails.get(key);
+ return value == null ? null : value.toString();
+ }
+
+ /**
* Returns a property value for the given key if known, else the specified
* default value
*
}
otherDetails.put(key, value);
+ FeatureAttributes.getInstance().addAttribute(this.type, key);
}
}
--- /dev/null
+package jalview.datamodel.features;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * A singleton class to hold the set of attributes known for each feature type
+ */
+public class FeatureAttributes
+{
+ private static FeatureAttributes instance = new FeatureAttributes();
+
+ private Map<String, Set<String>> attributes;
+
+ /**
+ * Answers the singleton instance of this class
+ *
+ * @return
+ */
+ public static FeatureAttributes getInstance()
+ {
+ return instance;
+ }
+
+ private FeatureAttributes()
+ {
+ attributes = new HashMap<>();
+ }
+
+ /**
+ * Answers the attributes known for the given feature type, in alphabetical
+ * order (not case sensitive), or an empty set if no attributes are known
+ *
+ * @param featureType
+ * @return
+ */
+ public List<String> getAttributes(String featureType)
+ {
+ if (!attributes.containsKey(featureType))
+ {
+ return Collections.<String> emptyList();
+ }
+
+ return new ArrayList<>(attributes.get(featureType));
+ }
+
+ /**
+ * Answers true if at least one attribute is known for the given feature type,
+ * else false
+ *
+ * @param featureType
+ * @return
+ */
+ public boolean hasAttributes(String featureType)
+ {
+
+ if (attributes.containsKey(featureType))
+ {
+ if (!attributes.get(featureType).isEmpty())
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Records the given attribute name for the given feature type
+ *
+ * @param featureType
+ * @param attName
+ */
+ public void addAttribute(String featureType, String attName)
+ {
+ if (featureType == null || attName == null)
+ {
+ return;
+ }
+
+ if (!attributes.containsKey(featureType))
+ {
+ attributes.put(featureType, new TreeSet<String>(
+ String.CASE_INSENSITIVE_ORDER));
+ }
+
+ attributes.get(featureType).add(attName);
+ }
+}
import java.util.HashMap;
import java.util.Map;
+/**
+ * A singleton to hold metadata about feature attributes, keyed by a unique
+ * feature source identifier
+ *
+ * @author gmcarstairs
+ *
+ */
public class FeatureSources
{
private static FeatureSources instance = new FeatureSources();
private Map<String, FeatureSourceI> sources;
/**
- * Answers the singelton instance of this class
+ * Answers the singleton instance of this class
*
* @return
*/
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
+import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
private void jbInit() throws Exception
{
+ this.setLayout(new GridLayout(4, 1));
- minColour.setFont(JvSwingUtils.getLabelFont());
- minColour.setBorder(BorderFactory.createLineBorder(Color.black));
- minColour.setPreferredSize(new Dimension(40, 20));
- minColour.setToolTipText(MessageManager.getString("label.min_colour"));
- minColour.addMouseListener(new MouseAdapter()
- {
- @Override
- public void mousePressed(MouseEvent e)
- {
- if (minColour.isEnabled())
- {
- minColour_actionPerformed();
- }
- }
- });
- maxColour.setFont(JvSwingUtils.getLabelFont());
- maxColour.setBorder(BorderFactory.createLineBorder(Color.black));
- maxColour.setPreferredSize(new Dimension(40, 20));
- maxColour.setToolTipText(MessageManager.getString("label.max_colour"));
- maxColour.addMouseListener(new MouseAdapter()
- {
- @Override
- public void mousePressed(MouseEvent e)
- {
- if (maxColour.isEnabled())
- {
- maxColour_actionPerformed();
- }
- }
- });
- maxColour.setBorder(new LineBorder(Color.black));
- JLabel minText = new JLabel(MessageManager.getString("label.min"));
- minText.setFont(JvSwingUtils.getLabelFont());
- JLabel maxText = new JLabel(MessageManager.getString("label.max"));
- maxText.setFont(JvSwingUtils.getLabelFont());
- this.setLayout(new BorderLayout());
- JPanel jPanel1 = new JPanel();
- jPanel1.setBackground(Color.white);
- JPanel jPanel2 = new JPanel();
- jPanel2.setLayout(new FlowLayout());
- jPanel2.setBackground(Color.white);
+ JPanel colourByPanel = initColoursPanel();
+
+ JPanel thresholdPanel = initThresholdPanel();
+
+ JPanel okCancelPanel = initOkCancelPanel();
+
+ this.add(colourByPanel);
+ this.add(thresholdPanel);
+
+ this.add(okCancelPanel);
+ }
+
+ /**
+ * Lay out fields for threshold options
+ *
+ * @return
+ */
+ protected JPanel initThresholdPanel()
+ {
+ JPanel thresholdPanel = new JPanel();
+ thresholdPanel.setLayout(new FlowLayout());
threshold.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
- threshold_actionPerformed();
+ changeColour(true);
}
});
threshold.setToolTipText(MessageManager
threshold.addItem(MessageManager
.getString("label.threshold_feature_below_threshold")); // index 2
- JPanel jPanel3 = new JPanel();
- jPanel3.setLayout(new FlowLayout());
thresholdValue.addActionListener(new ActionListener()
{
@Override
MessageManager.getString("label.adjust_threshold"));
thresholdValue.setEnabled(false);
thresholdValue.setColumns(7);
- jPanel3.setBackground(Color.white);
+ thresholdPanel.setBackground(Color.white);
thresholdIsMin.setBackground(Color.white);
thresholdIsMin
.setText(MessageManager.getString("label.threshold_minmax"));
@Override
public void actionPerformed(ActionEvent actionEvent)
{
- thresholdIsMin_actionPerformed();
+ changeColour(true);
}
});
- colourByLabel.setBackground(Color.white);
- colourByLabel
- .setText(MessageManager.getString("label.colour_by_label"));
- colourByLabel.setToolTipText(MessageManager.getString(
- "label.display_features_same_type_different_label_using_different_colour"));
- colourByLabel.addActionListener(new ActionListener()
+ thresholdPanel.add(threshold);
+ thresholdPanel.add(slider);
+ thresholdPanel.add(thresholdValue);
+ thresholdPanel.add(thresholdIsMin);
+ return thresholdPanel;
+ }
+
+ /**
+ * Lay out OK and Cancel buttons
+ *
+ * @return
+ */
+ protected JPanel initOkCancelPanel()
+ {
+ JPanel okCancelPanel = new JPanel();
+ okCancelPanel.setBackground(Color.white);
+ okCancelPanel.add(ok);
+ okCancelPanel.add(cancel);
+ return okCancelPanel;
+ }
+
+ /**
+ * Lay out Colour by Label and min/max colour widgets
+ *
+ * @return
+ */
+ protected JPanel initColoursPanel()
+ {
+ JPanel colourByPanel = new JPanel();
+ colourByPanel.setLayout(new FlowLayout());
+ colourByPanel.setBackground(Color.white);
+ minColour.setFont(JvSwingUtils.getLabelFont());
+ minColour.setBorder(BorderFactory.createLineBorder(Color.black));
+ minColour.setPreferredSize(new Dimension(40, 20));
+ minColour.setToolTipText(MessageManager.getString("label.min_colour"));
+ minColour.addMouseListener(new MouseAdapter()
{
@Override
- public void actionPerformed(ActionEvent actionEvent)
+ public void mousePressed(MouseEvent e)
{
- colourByLabel_actionPerformed();
+ if (minColour.isEnabled())
+ {
+ minColour_actionPerformed();
+ }
}
});
+ maxColour.setFont(JvSwingUtils.getLabelFont());
+ maxColour.setBorder(BorderFactory.createLineBorder(Color.black));
+ maxColour.setPreferredSize(new Dimension(40, 20));
+ maxColour.setToolTipText(MessageManager.getString("label.max_colour"));
+ maxColour.addMouseListener(new MouseAdapter()
+ {
+ @Override
+ public void mousePressed(MouseEvent e)
+ {
+ if (maxColour.isEnabled())
+ {
+ maxColour_actionPerformed();
+ }
+ }
+ });
+ maxColour.setBorder(new LineBorder(Color.black));
+ JLabel minText = new JLabel(MessageManager.getString("label.min"));
+ minText.setFont(JvSwingUtils.getLabelFont());
+ JLabel maxText = new JLabel(MessageManager.getString("label.max"));
+ maxText.setFont(JvSwingUtils.getLabelFont());
JPanel colourPanel = new JPanel();
colourPanel.setBackground(Color.white);
- jPanel1.add(ok);
- jPanel1.add(cancel);
- jPanel2.add(colourByLabel, BorderLayout.WEST);
- jPanel2.add(colourPanel, BorderLayout.EAST);
colourPanel.add(minText);
colourPanel.add(minColour);
colourPanel.add(maxText);
colourPanel.add(maxColour);
- this.add(jPanel3, BorderLayout.CENTER);
- jPanel3.add(threshold);
- jPanel3.add(slider);
- jPanel3.add(thresholdValue);
- jPanel3.add(thresholdIsMin);
- this.add(jPanel1, BorderLayout.SOUTH);
- this.add(jPanel2, BorderLayout.NORTH);
+ colourByPanel.add(colourByLabel, BorderLayout.WEST);
+ colourByPanel.add(colourPanel, BorderLayout.EAST);
+
+ colourByLabel.setBackground(Color.white);
+ colourByLabel
+ .setText(MessageManager.getString("label.colour_by_label"));
+ colourByLabel
+ .setToolTipText(MessageManager
+ .getString("label.display_features_same_type_different_label_using_different_colour"));
+ colourByLabel.addActionListener(new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent actionEvent)
+ {
+ changeColour(true);
+ }
+ });
+
+ return colourByPanel;
}
/**
maxColour.setForeground(oldmaxColour);
minColour.setForeground(oldminColour);
}
+
fr.setColour(type, acg);
cs = acg;
ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview);
}
/**
- * Action on change of choice of No / Above / Below Threshold
- */
- protected void threshold_actionPerformed()
- {
- changeColour(true);
- }
-
- /**
* Action on text entry of a threshold value
*/
protected void thresholdValue_actionPerformed()
changeColour(false);
}
- protected void thresholdIsMin_actionPerformed()
- {
- changeColour(true);
- }
-
- protected void colourByLabel_actionPerformed()
- {
- changeColour(true);
- }
-
void addActionListener(ActionListener graduatedColorEditor)
{
if (colourEditor != null)
import jalview.bin.Cache;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.SequenceI;
+import jalview.datamodel.features.FeatureAttributes;
import jalview.gui.Help.HelpId;
import jalview.io.JalviewFileChooser;
import jalview.io.JalviewFileView;
import jalview.util.MessageManager;
import jalview.util.Platform;
import jalview.util.QuickSort;
+import jalview.util.ReverseListIterator;
+import jalview.util.matcher.Condition;
+import jalview.util.matcher.KeyedMatcher;
+import jalview.util.matcher.KeyedMatcherI;
+import jalview.util.matcher.KeyedMatcherSet;
+import jalview.util.matcher.KeyedMatcherSetI;
import jalview.viewmodel.AlignmentViewport;
+import jalview.ws.DasSequenceFeatureFetcher;
import jalview.ws.dbsources.das.api.jalviewSourceI;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
+import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Hashtable;
import javax.help.HelpSetException;
import javax.swing.AbstractCellEditor;
import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JColorChooser;
+import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
+import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
+import javax.swing.plaf.basic.BasicArrowButton;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
public class FeatureSettings extends JPanel
implements FeatureSettingsControllerI
{
- DasSourceBrowser dassourceBrowser;
+ private static final int MIN_WIDTH = 400;
- jalview.ws.DasSequenceFeatureFetcher dasFeatureFetcher;
+ private static final int MIN_HEIGHT = 400;
+
+ DasSourceBrowser dassourceBrowser;
- JPanel settingsPane = new JPanel();
+ DasSequenceFeatureFetcher dasFeatureFetcher;
JPanel dasSettingsPane = new JPanel();
public final AlignFrame af;
+ /*
+ * 'original' fields hold settings to restore on Cancel
+ */
Object[][] originalData;
private float originalTransparency;
+ private Map<String, KeyedMatcherSetI> originalFilters;
+
final JInternalFrame frame;
JScrollPane scrollPane = new JScrollPane();
JPanel groupPanel;
JSlider transparency = new JSlider();
-
- JPanel transPanel = new JPanel(new GridLayout(1, 2));
-
- private static final int MIN_WIDTH = 400;
-
- private static final int MIN_HEIGHT = 400;
- /**
+ /*
* when true, constructor is still executing - so ignore UI events
*/
protected volatile boolean inConstruction = true;
+ int selectedRow = -1;
+
+ JButton fetchDAS = new JButton();
+
+ JButton saveDAS = new JButton();
+
+ JButton cancelDAS = new JButton();
+
+ boolean resettingTable = false;
+
+ /*
+ * true when Feature Settings are updating from feature renderer
+ */
+ private boolean handlingUpdate = false;
+
+ /*
+ * holds {featureCount, totalExtent} for each feature type
+ */
+ Map<String, float[]> typeWidth = null;
+
+ /*
+ * fields of the feature filters tab
+ */
+ private JPanel filtersPane;
+
+ private JPanel chooseFiltersPanel;
+
+ private JComboBox<String> filteredFeatureChoice;
+
+ private JRadioButton andFilters;
+
+ private JRadioButton orFilters;
+
+ /*
+ * filters for the currently selected feature type
+ */
+ private List<KeyedMatcherI> filters;
+
+ private JTextArea filtersAsText;
+
/**
* Constructor
*
* @param af
*/
- public FeatureSettings(AlignFrame af)
+ public FeatureSettings(AlignFrame alignFrame)
{
- this.af = af;
+ this.af = alignFrame;
fr = af.getFeatureRenderer();
- // allow transparency to be recovered
- transparency.setMaximum(100
- - (int) ((originalTransparency = fr.getTransparency()) * 100));
+
+ // save transparency for restore on Cancel
+ originalTransparency = fr.getTransparency();
+ int originalTransparencyAsPercent = (int) (originalTransparency * 100);
+ transparency.setMaximum(100 - originalTransparencyAsPercent);
+
+ originalFilters = fr.getFeatureFilters();
try
{
{
Desktop.addInternalFrame(frame,
MessageManager.getString("label.sequence_feature_settings"),
- 475, 480);
+ 600, 480);
}
else
{
Desktop.addInternalFrame(frame,
MessageManager.getString("label.sequence_feature_settings"),
- 400, 450);
+ 600, 450);
}
frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
men.show(table, x, y);
}
- /**
- * true when Feature Settings are updating from feature renderer
- */
- private boolean handlingUpdate = false;
-
- /**
- * holds {featureCount, totalExtent} for each feature type
- */
- Map<String, float[]> typeWidth = null;
-
@Override
synchronized public void discoverAllFeatureData()
{
}
}
+ populateFilterableFeatures();
+
resetTable(null);
validate();
return visible;
}
- boolean resettingTable = false;
-
synchronized void resetTable(String[] groupChanged)
{
if (resettingTable)
}
}
- int selectedRow = -1;
-
- JTabbedPane tabbedPane = new JTabbedPane();
-
- BorderLayout borderLayout1 = new BorderLayout();
-
- BorderLayout borderLayout2 = new BorderLayout();
-
- BorderLayout borderLayout3 = new BorderLayout();
-
- JPanel bigPanel = new JPanel();
-
- BorderLayout borderLayout4 = new BorderLayout();
-
- JButton invert = new JButton();
-
- JPanel buttonPanel = new JPanel();
-
- JButton cancel = new JButton();
-
- JButton ok = new JButton();
-
- JButton loadColours = new JButton();
-
- JButton saveColours = new JButton();
-
- JPanel dasButtonPanel = new JPanel();
-
- JButton fetchDAS = new JButton();
-
- JButton saveDAS = new JButton();
-
- JButton cancelDAS = new JButton();
-
- JButton optimizeOrder = new JButton();
-
- JButton sortByScore = new JButton();
+ private void jbInit() throws Exception
+ {
+ this.setLayout(new BorderLayout());
- JButton sortByDens = new JButton();
+ JPanel settingsPane = new JPanel();
+ settingsPane.setLayout(new BorderLayout());
- JButton help = new JButton();
+ filtersPane = new JPanel();
- JPanel transbuttons = new JPanel(new GridLayout(5, 1));
+ dasSettingsPane.setLayout(new BorderLayout());
- private void jbInit() throws Exception
- {
- this.setLayout(borderLayout1);
- settingsPane.setLayout(borderLayout2);
- dasSettingsPane.setLayout(borderLayout3);
- bigPanel.setLayout(borderLayout4);
+ JPanel bigPanel = new JPanel();
+ bigPanel.setLayout(new BorderLayout());
groupPanel = new JPanel();
bigPanel.add(groupPanel, BorderLayout.NORTH);
+ JButton invert = new JButton(
+ MessageManager.getString("label.invert_selection"));
invert.setFont(JvSwingUtils.getLabelFont());
- invert.setText(MessageManager.getString("label.invert_selection"));
invert.addActionListener(new ActionListener()
{
@Override
invertSelection();
}
});
+
+ JButton optimizeOrder = new JButton(
+ MessageManager.getString("label.optimise_order"));
optimizeOrder.setFont(JvSwingUtils.getLabelFont());
- optimizeOrder.setText(MessageManager.getString("label.optimise_order"));
optimizeOrder.addActionListener(new ActionListener()
{
@Override
orderByAvWidth();
}
});
+
+ JButton sortByScore = new JButton(
+ MessageManager.getString("label.seq_sort_by_score"));
sortByScore.setFont(JvSwingUtils.getLabelFont());
- sortByScore
- .setText(MessageManager.getString("label.seq_sort_by_score"));
sortByScore.addActionListener(new ActionListener()
{
@Override
af.avc.sortAlignmentByFeatureScore(null);
}
});
- sortByDens.setFont(JvSwingUtils.getLabelFont());
- sortByDens.setText(
+ JButton sortByDens = new JButton(
MessageManager.getString("label.sequence_sort_by_density"));
+ sortByDens.setFont(JvSwingUtils.getLabelFont());
sortByDens.addActionListener(new ActionListener()
{
@Override
af.avc.sortAlignmentByFeatureDensity(null);
}
});
+
+ JButton help = new JButton(MessageManager.getString("action.help"));
help.setFont(JvSwingUtils.getLabelFont());
- help.setText(MessageManager.getString("action.help"));
help.addActionListener(new ActionListener()
{
@Override
}
}
});
+
+ JButton cancel = new JButton(MessageManager.getString("action.cancel"));
cancel.setFont(JvSwingUtils.getLabelFont());
- cancel.setText(MessageManager.getString("action.cancel"));
cancel.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
fr.setTransparency(originalTransparency);
+ fr.setFeatureFilters(originalFilters);
updateFeatureRenderer(originalData);
close();
}
});
+
+ JButton ok = new JButton(MessageManager.getString("action.ok"));
ok.setFont(JvSwingUtils.getLabelFont());
- ok.setText(MessageManager.getString("action.ok"));
ok.addActionListener(new ActionListener()
{
@Override
close();
}
});
+
+ JButton loadColours = new JButton(
+ MessageManager.getString("label.load_colours"));
loadColours.setFont(JvSwingUtils.getLabelFont());
- loadColours.setText(MessageManager.getString("label.load_colours"));
loadColours.addActionListener(new ActionListener()
{
@Override
load();
}
});
+
+ JButton saveColours = new JButton(
+ MessageManager.getString("label.save_colours"));
saveColours.setFont(JvSwingUtils.getLabelFont());
- saveColours.setText(MessageManager.getString("label.save_colours"));
saveColours.addActionListener(new ActionListener()
{
@Override
saveDAS_actionPerformed(e);
}
});
+
+ JPanel dasButtonPanel = new JPanel();
dasButtonPanel.setBorder(BorderFactory.createEtchedBorder());
dasSettingsPane.setBorder(null);
cancelDAS.setEnabled(false);
cancelDAS_actionPerformed(e);
}
});
- this.add(tabbedPane, java.awt.BorderLayout.CENTER);
+
+ JTabbedPane tabbedPane = new JTabbedPane();
+ this.add(tabbedPane, BorderLayout.CENTER);
tabbedPane.addTab(MessageManager.getString("label.feature_settings"),
settingsPane);
- tabbedPane.addTab(MessageManager.getString("label.das_settings"),
- dasSettingsPane);
- bigPanel.add(transPanel, java.awt.BorderLayout.SOUTH);
+ tabbedPane.addTab(MessageManager.getString("label.filters"),
+ filtersPane);
+ // tabbedPane.addTab(MessageManager.getString("label.das_settings"),
+ // dasSettingsPane);
+
+ JPanel transPanel = new JPanel(new GridLayout(1, 2));
+ bigPanel.add(transPanel, BorderLayout.SOUTH);
+
+ JPanel transbuttons = new JPanel(new GridLayout(5, 1));
transbuttons.add(optimizeOrder);
transbuttons.add(invert);
transbuttons.add(sortByScore);
transbuttons.add(sortByDens);
transbuttons.add(help);
- JPanel sliderPanel = new JPanel();
- sliderPanel.add(transparency);
transPanel.add(transparency);
transPanel.add(transbuttons);
+
+ JPanel buttonPanel = new JPanel();
buttonPanel.add(ok);
buttonPanel.add(cancel);
buttonPanel.add(loadColours);
buttonPanel.add(saveColours);
- bigPanel.add(scrollPane, java.awt.BorderLayout.CENTER);
- dasSettingsPane.add(dasButtonPanel, java.awt.BorderLayout.SOUTH);
+ bigPanel.add(scrollPane, BorderLayout.CENTER);
+ dasSettingsPane.add(dasButtonPanel, BorderLayout.SOUTH);
dasButtonPanel.add(fetchDAS);
dasButtonPanel.add(cancelDAS);
dasButtonPanel.add(saveDAS);
- settingsPane.add(bigPanel, java.awt.BorderLayout.CENTER);
- settingsPane.add(buttonPanel, java.awt.BorderLayout.SOUTH);
+ settingsPane.add(bigPanel, BorderLayout.CENTER);
+ settingsPane.add(buttonPanel, BorderLayout.SOUTH);
+
+ initFiltersTab();
+ }
+
+ /**
+ * Populates initial layout of the feature attribute filters panel
+ */
+ protected void initFiltersTab()
+ {
+ filters = new ArrayList<>();
+
+ /*
+ * choose feature type
+ */
+ JPanel chooseTypePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ chooseTypePanel.setBackground(Color.white);
+ chooseTypePanel.setBorder(BorderFactory
+ .createTitledBorder(MessageManager
+ .getString("label.feature_type")));
+ filteredFeatureChoice = new JComboBox<>();
+ filteredFeatureChoice.addItemListener(new ItemListener()
+ {
+ @Override
+ public void itemStateChanged(ItemEvent e)
+ {
+ refreshFiltersDisplay();
+ }
+ });
+ chooseTypePanel.add(new JLabel(MessageManager
+ .getString("label.feature_to_filter")));
+ chooseTypePanel.add(filteredFeatureChoice);
+ populateFilterableFeatures();
+
+ /*
+ * the panel with the filters for the selected feature type
+ */
+ JPanel filtersPanel = new JPanel(new GridLayout(0, 1));
+ filtersPanel.setBackground(Color.white);
+ filtersPanel.setBorder(BorderFactory
+ .createTitledBorder(MessageManager.getString("label.filters")));
+
+ /*
+ * add AND or OR radio buttons
+ */
+ JPanel andOrPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ andOrPanel.setBackground(Color.white);
+ andFilters = new JRadioButton("And");
+ orFilters = new JRadioButton("Or");
+ ActionListener actionListener = new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ filtersChanged();
+ }
+ };
+ andFilters.addActionListener(actionListener);
+ orFilters.addActionListener(actionListener);
+ ButtonGroup andOr = new ButtonGroup();
+ andOr.add(andFilters);
+ andOr.add(orFilters);
+ andFilters.setSelected(true);
+ andOrPanel.add(new JLabel(MessageManager
+ .getString("label.join_conditions")));
+ andOrPanel.add(andFilters);
+ andOrPanel.add(orFilters);
+ filtersPanel.add(andOrPanel);
+
+ /*
+ * panel with filters - populated by refreshFiltersDisplay
+ */
+ chooseFiltersPanel = new JPanel(new GridLayout(0, 1));
+ filtersPanel.add(chooseFiltersPanel);
+
+ /*
+ * a read-only text view of the current filters
+ */
+ JPanel showFiltersPanel = new JPanel(new BorderLayout(5, 5));
+ showFiltersPanel.setBackground(Color.white);
+ showFiltersPanel.setBorder(BorderFactory
+ .createTitledBorder(MessageManager
+ .getString("label.match_condition")));
+ filtersAsText = new JTextArea();
+ filtersAsText.setLineWrap(true);
+ filtersAsText.setWrapStyleWord(true);
+ showFiltersPanel.add(filtersAsText);
+
+ filtersPane.setLayout(new BorderLayout());
+ filtersPane.add(chooseTypePanel, BorderLayout.NORTH);
+ filtersPane.add(filtersPanel, BorderLayout.CENTER);
+ filtersPane.add(showFiltersPanel, BorderLayout.SOUTH);
+
+ /*
+ * update display for initial feature type selection
+ */
+ refreshFiltersDisplay();
+ }
+
+ /**
+ * Adds entries to the 'choose feature to filter' drop-down choice. Only
+ * feature types which have known attributes (so can be filtered) are
+ * included, so recall this method to update the list (check for newly added
+ * attributes).
+ */
+ protected void populateFilterableFeatures()
+ {
+ /*
+ * suppress action handler while updating the list
+ */
+ ItemListener listener = filteredFeatureChoice.getItemListeners()[0];
+ filteredFeatureChoice.removeItemListener(listener);
+
+ filteredFeatureChoice.removeAllItems();
+ ReverseListIterator<String> types = new ReverseListIterator<>(
+ fr.getRenderOrder());
+
+ boolean found = false;
+ while (types.hasNext())
+ {
+ String type = types.next();
+ if (FeatureAttributes.getInstance().hasAttributes(type))
+ {
+ filteredFeatureChoice.addItem(type);
+ found = true;
+ }
+ }
+ if (!found)
+ {
+ filteredFeatureChoice
+ .addItem("No filterable feature attributes known");
+ }
+
+ filteredFeatureChoice.addItemListener(listener);
+
+ }
+
+ /**
+ * 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 on change of selected feature
+ * type, or after a filter has been removed, added or amended.
+ */
+ protected void refreshFiltersDisplay()
+ {
+ /*
+ * clear the panel and list of filter conditions
+ */
+ chooseFiltersPanel.removeAll();
+
+ String selectedType = (String) filteredFeatureChoice.getSelectedItem();
+
+ filters.clear();
+
+ /*
+ * look up attributes known for feature type
+ */
+ List<String> attNames = FeatureAttributes.getInstance().getAttributes(
+ selectedType);
+
+ /*
+ * if this feature type has filters set, load them first
+ */
+ KeyedMatcherSetI featureFilters = fr.getFeatureFilter(selectedType);
+ filtersAsText.setText("");
+ if (featureFilters != null)
+ {
+ filtersAsText.setText(featureFilters.toString());
+ if (!featureFilters.isAnded())
+ {
+ orFilters.setSelected(true);
+ }
+ Iterator<KeyedMatcherI> matchers = featureFilters.getMatchers();
+ while (matchers.hasNext())
+ {
+ filters.add(matchers.next());
+ }
+ }
+
+ /*
+ * and an empty filter for the user to populate (add)
+ */
+ KeyedMatcherI noFilter = new KeyedMatcher("", Condition.values()[0], "");
+ filters.add(noFilter);
+
+ /*
+ * render the conditions in rows, each in its own JPanel
+ */
+ int i = 0;
+ for (KeyedMatcherI filter : filters)
+ {
+ String key = filter.getKey();
+ Condition condition = filter.getMatcher()
+ .getCondition();
+ String pattern = filter.getMatcher().getPattern();
+ JPanel row = addFilter(key, attNames, condition, pattern, i);
+ chooseFiltersPanel.add(row);
+ i++;
+ }
+
+ filtersPane.validate();
+ filtersPane.repaint();
+ }
+
+ /**
+ * A helper method that constructs a panel with one filter condition:
+ * <ul>
+ * <li>a drop-down list of attribute names to choose from</li>
+ * <li>a drop-down list of conditions to choose from</li>
+ * <li>a text field for input of a match pattern</li>
+ * <li>optionally, a 'remove' button</li>
+ * </ul>
+ * If attribute, condition or pattern are not null, they are set as defaults
+ * for the input fields. The 'remove' button is added unless the pattern is
+ * null or empty (incomplete filter condition).
+ *
+ * @param attribute
+ * @param attNames
+ * @param cond
+ * @param pattern
+ * @param i
+ * @return
+ */
+ protected JPanel addFilter(String attribute, List<String> attNames,
+ Condition cond, String pattern, int i)
+ {
+ JPanel filterRow = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ filterRow.setBackground(Color.white);
+
+ /*
+ * inputs for attribute, condition, pattern
+ */
+ JComboBox<String> attCombo = new JComboBox<>();
+ JComboBox<Condition> condCombo = new JComboBox<>();
+ JTextField patternField = new JTextField(8);
+
+ /*
+ * action handlers that validate and (if valid) apply changes
+ */
+ ActionListener actionListener = new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ if (attCombo.getSelectedItem() != null)
+ {
+ if (validateFilter(patternField, condCombo))
+ {
+ updateFilter(attCombo, condCombo, patternField, i);
+ filtersChanged();
+ }
+ }
+ }
+ };
+ ItemListener itemListener = new ItemListener()
+ {
+ @Override
+ public void itemStateChanged(ItemEvent e)
+ {
+ actionListener.actionPerformed(null);
+ }
+ };
+
+ /*
+ * drop-down choice of attribute
+ */
+ if (attNames.isEmpty())
+ {
+ attCombo.addItem("---");
+ attCombo.setToolTipText(MessageManager
+ .getString("label.no_attributes_known"));
+ }
+ else
+ {
+ attCombo.setToolTipText("");
+ for (String attName : attNames)
+ {
+ attCombo.addItem(attName);
+ }
+ if ("".equals(attribute))
+ {
+ attCombo.setSelectedItem(null);
+ }
+ else
+ {
+ attCombo.setSelectedItem(attribute);
+ }
+ attCombo.addItemListener(itemListener);
+ }
+ filterRow.add(attCombo);
+
+ /*
+ * drop-down choice of test condition
+ */
+ for (Condition c : Condition.values())
+ {
+ condCombo.addItem(c);
+ }
+ if (cond != null)
+ {
+ condCombo.setSelectedItem(cond);
+ }
+ condCombo.addItemListener(itemListener);
+ filterRow.add(condCombo);
+
+ /*
+ * pattern to match against
+ */
+ patternField.setText(pattern);
+ patternField.addActionListener(actionListener);
+ patternField.addFocusListener(new FocusAdapter()
+ {
+ @Override
+ public void focusLost(FocusEvent e)
+ {
+ actionListener.actionPerformed(null);
+ }
+ });
+ filterRow.add(patternField);
+
+ /*
+ * add remove button if filter is populated (non-empty pattern)
+ */
+ if (pattern != null && pattern.trim().length() > 0)
+ {
+ // todo: gif for - button
+ JButton removeCondition = new BasicArrowButton(SwingConstants.WEST);
+ removeCondition.setToolTipText(MessageManager
+ .getString("label.delete_row"));
+ removeCondition.addActionListener(new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ filters.remove(i);
+ filtersChanged();
+ }
+ });
+ filterRow.add(removeCondition);
+ }
+
+ return filterRow;
+ }
+
+ /**
+ * Action on any change to feature filtering, namely
+ * <ul>
+ * <li>change of selected attribute</li>
+ * <li>change of selected condition</li>
+ * <li>change of match pattern</li>
+ * <li>removal of a condition</li>
+ * </ul>
+ * The action should be to
+ * <ul>
+ * <li>parse and validate the filters</li>
+ * <li>if valid, update the filter text box</li>
+ * <li>and apply the filters to the viewport</li>
+ * </ul>
+ */
+ protected void filtersChanged()
+ {
+ /*
+ * update the filter conditions for the feature type
+ */
+ String featureType = (String) filteredFeatureChoice.getSelectedItem();
+ boolean anded = andFilters.isSelected();
+ KeyedMatcherSetI combined = new KeyedMatcherSet();
+
+ for (KeyedMatcherI filter : filters)
+ {
+ String pattern = filter.getMatcher().getPattern();
+ if (pattern.trim().length() > 0)
+ {
+ if (anded)
+ {
+ combined.and(filter);
+ }
+ else
+ {
+ combined.or(filter);
+ }
+ }
+ }
+
+ /*
+ * save the filter conditions in the FeatureRenderer
+ * (note this might now be an empty filter with no conditions)
+ */
+ fr.setFeatureFilter(featureType, combined);
+
+ filtersAsText.setText(combined.toString());
+
+ refreshFiltersDisplay();
+
+ af.alignPanel.paintAlignment(true, true);
+ }
+
+ /**
+ * Constructs a filter condition from the given input fields, and replaces the
+ * condition at filterIndex with the new one
+ *
+ * @param attCombo
+ * @param condCombo
+ * @param valueField
+ * @param filterIndex
+ */
+ protected void updateFilter(JComboBox<String> attCombo,
+ JComboBox<Condition> condCombo, JTextField valueField,
+ int filterIndex)
+ {
+ String attName = (String) attCombo.getSelectedItem();
+ Condition cond = (Condition) condCombo.getSelectedItem();
+ String pattern = valueField.getText();
+ KeyedMatcherI km = new KeyedMatcher(attName, cond, pattern);
+
+ filters.set(filterIndex, km);
}
public void fetchDAS_actionPerformed(ActionEvent e)
JvOptionPane.DEFAULT_OPTION, JvOptionPane.INFORMATION_MESSAGE);
}
+ /**
+ * Answers true unless a numeric condition has been selected with a
+ * non-numeric value. Sets the value field to RED with a tooltip if in error.
+ * <p>
+ * If the pattern entered is empty, this method returns false, but does not
+ * mark the field as invalid. This supports selecting an attribute for a new
+ * condition before a match pattern has been entered.
+ *
+ * @param value
+ * @param condCombo
+ */
+ protected boolean validateFilter(JTextField value,
+ JComboBox<Condition> condCombo)
+ {
+ if (value == null || condCombo == null)
+ {
+ return true; // fields not populated
+ }
+
+ Condition cond = (Condition) condCombo.getSelectedItem();
+ value.setBackground(Color.white);
+ value.setToolTipText("");
+ String v1 = value.getText().trim();
+ if (v1.length() == 0)
+ {
+ return false;
+ }
+
+ if (cond.isNumeric())
+ {
+ try
+ {
+ Float.valueOf(v1);
+ } catch (NumberFormatException e)
+ {
+ value.setBackground(Color.red);
+ value.setToolTipText(MessageManager
+ .getString("label.numeric_required"));
+ return false;
+ }
+ }
+
+ return true;
+ }
+
// ///////////////////////////////////////////////////////////////////////
// http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
// ///////////////////////////////////////////////////////////////////////
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
-import java.awt.event.WindowListener;
import javax.swing.JButton;
import javax.swing.JDialog;
closeDialog();
}
});
- frame.addWindowListener(new WindowListener()
+ frame.addWindowListener(new WindowAdapter()
{
-
- @Override
- public void windowOpened(WindowEvent e)
- {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void windowIconified(WindowEvent e)
- {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void windowDeiconified(WindowEvent e)
- {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void windowDeactivated(WindowEvent e)
- {
- // TODO Auto-generated method stub
-
- }
-
@Override
public void windowClosing(WindowEvent e)
{
// user has cancelled the dialog
closeDialog();
}
-
- @Override
- public void windowClosed(WindowEvent e)
- {
- }
-
- @Override
- public void windowActivated(WindowEvent e)
- {
- // TODO Auto-generated method stub
-
- }
});
}
List<SequenceFeature> overlaps = seq.getFeatures().findFeatures(
visiblePositions.getBegin(), visiblePositions.getEnd(), type);
- filterFeaturesForDisplay(overlaps, fc);
+ // filterFeaturesForDisplay(overlaps, fc);
for (SequenceFeature sf : overlaps)
{
- Color featureColour = fc.getColor(sf);
+ Color featureColour = getColor(sf, fc);
if (featureColour == null)
{
- // score feature outwith threshold for colouring
+ /*
+ * feature excluded by visibility settings, filters, or colour threshold
+ */
continue;
}
--- /dev/null
+package jalview.util.matcher;
+
+import jalview.util.MessageManager;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * An enumeration for binary conditions that a user might choose from when
+ * setting filter or match conditions for values
+ */
+public enum Condition
+{
+ Contains(false), NotContains(false), Matches(false), NotMatches(false),
+ EQ(true), NE(true), LT(true), LE(true), GT(true), GE(true);
+
+ private static Map<Condition, String> displayNames = new HashMap<>();
+
+ private boolean numeric;
+
+ Condition(boolean isNumeric)
+ {
+ numeric = isNumeric;
+ }
+
+ /**
+ * Answers true if the condition does a numerical comparison, else false
+ * (string comparison)
+ *
+ * @return
+ */
+ public boolean isNumeric()
+ {
+ return numeric;
+ }
+
+ /**
+ * Answers a display name for the match condition, suitable for showing in
+ * drop-down menus. The value may be internationalized using the resource key
+ * "label.matchCondition_" with the enum name appended.
+ *
+ * @return
+ */
+ @Override
+ public String toString()
+ {
+ String name = displayNames.get(this);
+ if (name != null)
+ {
+ return name;
+ }
+ name = MessageManager
+ .getStringOrReturn("label.matchCondition_", name());
+ displayNames.put(this, name);
+ return name;
+ }
+}
--- /dev/null
+package jalview.util.matcher;
+
+import java.util.function.Function;
+
+/**
+ * An immutable class that models one or more match conditions, each of which is
+ * applied to the value obtained by lookup given the match key.
+ * <p>
+ * For example, the value provider could be a SequenceFeature's attributes map,
+ * and the conditions might be
+ * <ul>
+ * <li>CSQ contains "pathological"</li>
+ * <li>AND</li>
+ * <li>AF <= 1.0e-5</li>
+ * </ul>
+ *
+ * @author gmcarstairs
+ *
+ */
+public class KeyedMatcher implements KeyedMatcherI
+{
+ final private String key;
+
+ final private MatcherI matcher;
+
+ /**
+ * Constructor given a key, a test condition and a match pattern
+ *
+ * @param theKey
+ * @param cond
+ * @param pattern
+ */
+ public KeyedMatcher(String theKey, Condition cond, String pattern)
+ {
+ key = theKey;
+ matcher = new Matcher(cond, pattern);
+ }
+
+ /**
+ * Constructor given a key, a test condition and a numerical value to compare
+ * to. Note that if a non-numerical condition is specified, the float will be
+ * converted to a string.
+ *
+ * @param theKey
+ * @param cond
+ * @param value
+ */
+ public KeyedMatcher(String theKey, Condition cond, float value)
+ {
+ key = theKey;
+ matcher = new Matcher(cond, value);
+ }
+
+ @Override
+ public boolean matches(Function<String, String> valueProvider)
+ {
+ String value = valueProvider.apply(key);
+ return matcher.matches(value);
+ }
+
+ @Override
+ public String getKey()
+ {
+ return key;
+ }
+
+ @Override
+ public MatcherI getMatcher()
+ {
+ return matcher;
+ }
+
+ /**
+ * Answers a string description of this matcher, suitable for debugging or
+ * logging. The format may change in future.
+ */
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append(key).append(" ").append(matcher.getCondition().toString())
+ .append(" ").append(matcher.getPattern());
+
+ return sb.toString();
+ }
+}
--- /dev/null
+package jalview.util.matcher;
+
+import java.util.function.Function;
+
+/**
+ * An interface for an object that can apply one or more match conditions, given
+ * a key-value provider. The match conditions are stored against key values, and
+ * applied to the value obtained by a key-value lookup.
+ *
+ * @author gmcarstairs
+ */
+public interface KeyedMatcherI
+{
+ /**
+ * Answers true if the value provided for this matcher's key passes this
+ * matcher's match condition
+ *
+ * @param valueProvider
+ * @return
+ */
+ boolean matches(Function<String, String> valueProvider);
+
+ /**
+ * Answers the value key this matcher operates on
+ *
+ * @return
+ */
+ String getKey();
+
+ /**
+ * Answers the match condition that is applied
+ *
+ * @return
+ */
+ MatcherI getMatcher();
+}
--- /dev/null
+package jalview.util.matcher;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Function;
+
+public class KeyedMatcherSet implements KeyedMatcherSetI
+{
+ List<KeyedMatcherI> matchConditions;
+
+ boolean andConditions;
+
+ /**
+ * Constructor
+ */
+ public KeyedMatcherSet()
+ {
+ matchConditions = new ArrayList<>();
+ }
+
+ @Override
+ public boolean matches(Function<String, String> valueProvider)
+ {
+ /*
+ * no conditions matches anything
+ */
+ if (matchConditions.isEmpty())
+ {
+ return true;
+ }
+
+ /*
+ * AND until failure
+ */
+ if (andConditions)
+ {
+ for (KeyedMatcherI m : matchConditions)
+ {
+ if (!m.matches(valueProvider))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /*
+ * OR until match
+ */
+ for (KeyedMatcherI m : matchConditions)
+ {
+ if (m.matches(valueProvider))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public KeyedMatcherSetI and(KeyedMatcherI m)
+ {
+ if (!andConditions && matchConditions.size() > 1)
+ {
+ throw new IllegalStateException("Can't add an AND to OR conditions");
+ }
+ matchConditions.add(m);
+ andConditions = true;
+
+ return this;
+ }
+
+ @Override
+ public KeyedMatcherSetI or(KeyedMatcherI m)
+ {
+ if (andConditions && matchConditions.size() > 1)
+ {
+ throw new IllegalStateException("Can't add an OR to AND conditions");
+ }
+ matchConditions.add(m);
+ andConditions = false;
+
+ return this;
+ }
+
+ @Override
+ public boolean isAnded()
+ {
+ return andConditions;
+ }
+
+ @Override
+ public Iterator<KeyedMatcherI> getMatchers()
+ {
+ return matchConditions.iterator();
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for (KeyedMatcherI matcher : matchConditions)
+ {
+ if (!first)
+ {
+ sb.append(andConditions ? " AND " : " OR ");
+ }
+ first = false;
+ sb.append("(").append(matcher.toString()).append(")");
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public boolean isEmpty()
+ {
+ return matchConditions == null || matchConditions.isEmpty();
+ }
+
+}
--- /dev/null
+package jalview.util.matcher;
+
+import java.util.Iterator;
+import java.util.function.Function;
+
+/**
+ * An interface to describe a set of one or more key-value match conditions,
+ * where all conditions are combined with either AND or OR
+ *
+ * @author gmcarstairs
+ *
+ */
+public interface KeyedMatcherSetI
+{
+ /**
+ * Answers true if the value provided for this matcher's key passes this
+ * matcher's match condition
+ *
+ * @param valueProvider
+ * @return
+ */
+ boolean matches(Function<String, String> valueProvider);
+
+ /**
+ * Answers a new object that matches the logical AND of this and m
+ *
+ * @param m
+ * @return
+ * @throws IllegalStateException
+ * if an attempt is made to AND to existing OR-ed conditions
+ */
+ KeyedMatcherSetI and(KeyedMatcherI m);
+
+ /**
+ * Answers true if any second condition is AND-ed with this one, false if it
+ * is OR-ed
+ *
+ * @return
+ */
+ boolean isAnded();
+
+ /**
+ * Answers a new object that matches the logical OR of this and m
+ *
+ * @param m
+ * @return
+ * @throws IllegalStateException
+ * if an attempt is made to OR to existing AND-ed conditions
+ */
+ KeyedMatcherSetI or(KeyedMatcherI m);
+
+ /**
+ * Answers an iterator over the combined match conditions
+ *
+ * @return
+ */
+ Iterator<KeyedMatcherI> getMatchers();
+
+ /**
+ * Answers true if this object contains no conditions
+ *
+ * @return
+ */
+ boolean isEmpty();
+}
--- /dev/null
+package jalview.util.matcher;
+
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+/**
+ * A bean to describe one attribute-based filter
+ */
+public class Matcher implements MatcherI
+{
+ /*
+ * the comparison condition
+ */
+ Condition condition;
+
+ /*
+ * the string value (upper-cased), or the regex, to compare to
+ * also holds the string form of float value if a numeric condition
+ */
+ String pattern;
+
+ /*
+ * the compiled regex if using a pattern match condition
+ * (reserved for possible future enhancement)
+ */
+ Pattern regexPattern;
+
+ /*
+ * the value to compare to for a numerical condition
+ */
+ float value;
+
+ /**
+ * Constructor
+ *
+ * @param cond
+ * @param compareTo
+ * @return
+ * @throws NumberFormatException
+ * if a numerical condition is specified with a non-numeric
+ * comparision value
+ * @throws NullPointerException
+ * if a null condition or comparison string is specified
+ */
+ public Matcher(Condition cond, String compareTo)
+ {
+ condition = cond;
+ if (cond.isNumeric())
+ {
+ value = Float.valueOf(compareTo);
+ pattern = String.valueOf(value);
+ }
+ else
+ {
+ // pattern matches will be non-case-sensitive
+ pattern = compareTo.toUpperCase();
+ }
+
+ // if we add regex conditions (e.g. matchesPattern), then
+ // pattern should hold the raw regex, and
+ // regexPattern = Pattern.compile(compareTo);
+ }
+
+ /**
+ * Constructor for a numerical match condition. Note that if a string
+ * comparison condition is specified, this will be converted to a comparison
+ * with the float value as string
+ *
+ * @param cond
+ * @param compareTo
+ */
+ public Matcher(Condition cond, float compareTo)
+ {
+ Objects.requireNonNull(cond);
+ condition = cond;
+ value = compareTo;
+ pattern = String.valueOf(compareTo).toUpperCase();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @SuppressWarnings("incomplete-switch")
+ @Override
+ public boolean matches(String val)
+ {
+ if (condition.isNumeric())
+ {
+ try
+ {
+ /*
+ * treat a null value (no such attribute) as
+ * failing any numerical filter condition
+ */
+ return val == null ? false : matches(Float.valueOf(val));
+ } catch (NumberFormatException e)
+ {
+ return false;
+ }
+ }
+
+ /*
+ * a null value matches a negative condition, fails a positive test
+ */
+ if (val == null)
+ {
+ return condition == Condition.NotContains
+ || condition == Condition.NotMatches;
+ }
+
+ String upper = val.toUpperCase().trim();
+ boolean matched = false;
+ switch(condition) {
+ case Matches:
+ matched = upper.equals(pattern);
+ break;
+ case NotMatches:
+ matched = !upper.equals(pattern);
+ break;
+ case Contains:
+ matched = upper.indexOf(pattern) > -1;
+ break;
+ case NotContains:
+ matched = upper.indexOf(pattern) == -1;
+ break;
+ }
+ return matched;
+ }
+
+ /**
+ * Applies a numerical comparison match condition
+ *
+ * @param f
+ * @return
+ */
+ @SuppressWarnings("incomplete-switch")
+ boolean matches(float f)
+ {
+ if (!condition.isNumeric())
+ {
+ return matches(String.valueOf(f));
+ }
+
+ boolean matched = false;
+ switch (condition) {
+ case LT:
+ matched = f < value;
+ break;
+ case LE:
+ matched = f <= value;
+ break;
+ case EQ:
+ matched = f == value;
+ break;
+ case NE:
+ matched = f != value;
+ break;
+ case GT:
+ matched = f > value;
+ break;
+ case GE:
+ matched = f >= value;
+ break;
+ }
+
+ return matched;
+ }
+
+ /**
+ * A simple hash function that guarantees that when two objects are equal,
+ * they have the same hashcode
+ */
+ @Override
+ public int hashCode()
+ {
+ return pattern.hashCode() + condition.hashCode() + (int) value;
+ }
+
+ /**
+ * equals is overridden so that we can safely remove Matcher objects from
+ * collections (e.g. delete an attribut match condition for a feature colour)
+ */
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (obj == null || !(obj instanceof Matcher))
+ {
+ return false;
+ }
+ Matcher m = (Matcher) obj;
+ return condition == m.condition && value == m.value
+ && pattern.equals(m.pattern);
+ }
+
+ @Override
+ public Condition getCondition()
+ {
+ return condition;
+ }
+
+ @Override
+ public String getPattern()
+ {
+ return pattern;
+ }
+
+ @Override
+ public float getFloatValue()
+ {
+ return value;
+ }
+
+ @Override
+ public String toString()
+ {
+ return condition.name() + " " + pattern;
+ }
+}
--- /dev/null
+package jalview.util.matcher;
+
+public interface MatcherI
+{
+ /**
+ * Answers true if the given value is matched, else false
+ *
+ * @param s
+ * @return
+ */
+ boolean matches(String s);
+
+ Condition getCondition();
+
+ String getPattern();
+
+ float getFloatValue();
+}
import jalview.renderer.seqfeatures.FeatureRenderer;
import jalview.schemes.FeatureColour;
import jalview.util.ColorUtils;
+import jalview.util.matcher.KeyedMatcherSetI;
import java.awt.Color;
import java.beans.PropertyChangeListener;
implements jalview.api.FeatureRenderer
{
- /**
+ /*
* global transparency for feature
*/
protected float transparency = 1.0f;
- protected Map<String, FeatureColourI> featureColours = new ConcurrentHashMap<String, FeatureColourI>();
+ /*
+ * colour scheme for each feature type
+ */
+ protected Map<String, FeatureColourI> featureColours = new ConcurrentHashMap<>();
+
+ /*
+ * visibility flag for each feature group
+ */
+ protected Map<String, Boolean> featureGroups = new ConcurrentHashMap<>();
- protected Map<String, Boolean> featureGroups = new ConcurrentHashMap<String, Boolean>();
+ /*
+ * filters for each feature type
+ */
+ protected Map<String, KeyedMatcherSetI> featureFilters = new HashMap<>();
protected String[] renderOrder;
this.renderOrder = frs.renderOrder;
this.featureGroups = frs.featureGroups;
this.featureColours = frs.featureColours;
+ this.featureFilters = frs.featureFilters;
this.transparency = frs.transparency;
this.featureOrder = frs.featureOrder;
if (av != null && av != fr.getViewport())
return fc;
}
- /**
- * Returns the configured colour for a particular feature instance. This
- * includes calculation of 'colour by label', or of a graduated score colour,
- * if applicable. It does not take into account feature visibility or colour
- * transparency. Returns null for a score feature whose score value lies
- * outside any colour threshold.
- *
- * @param feature
- * @return
- */
+ @Override
public Color getColour(SequenceFeature feature)
{
FeatureColourI fc = getFeatureStyle(feature.getType());
- return fc.getColor(feature);
+ return getColor(feature, fc);
}
/**
}
/**
- * Removes from the list of features any that have a feature group that is not
- * displayed, or duplicate the location of a feature of the same type (unless
- * a graduated colour scheme or colour by label is applied). Should be used
- * only for features of the same feature colour (which normally implies the
- * same feature type).
+ * Removes from the list of features any that duplicate the location of a
+ * feature of the same type (unless feature is filtered out, or a graduated
+ * colour scheme or colour by label is applied). Should be used only for
+ * features of the same feature colour (which normally implies the same
+ * feature type).
*
* @param features
* @param fc
while (it.hasNext())
{
SequenceFeature sf = it.next();
- if (featureGroupNotShown(sf))
- {
- it.remove();
- continue;
- }
/*
* a feature is redundant for rendering purposes if it has the
}
}
+ @Override
+ public Map<String, KeyedMatcherSetI> getFeatureFilters()
+ {
+ return new HashMap<>(featureFilters);
+ }
+
+ @Override
+ public void setFeatureFilters(Map<String, KeyedMatcherSetI> filters)
+ {
+ featureFilters = filters;
+ }
+
+ @Override
+ public KeyedMatcherSetI getFeatureFilter(String featureType)
+ {
+ return featureFilters.get(featureType);
+ }
+
+ @Override
+ public void setFeatureFilter(String featureType, KeyedMatcherSetI filter)
+ {
+ if (filter == null || filter.isEmpty())
+ {
+ featureFilters.remove(featureType);
+ }
+ else
+ {
+ featureFilters.put(featureType, filter);
+ }
+ }
+
+ /**
+ * Answers the colour for the feature, or null if the feature is excluded by
+ * feature type or group visibility, by filters, or by colour threshold
+ * settings
+ *
+ * @param sf
+ * @param fc
+ * @return
+ */
+ public Color getColor(SequenceFeature sf, FeatureColourI fc)
+ {
+ /*
+ * is the feature type displayed?
+ */
+ if (!showFeatureOfType(sf.getType()))
+ {
+ return null;
+ }
+
+ /*
+ * is the feature group displayed?
+ */
+ if (featureGroupNotShown(sf))
+ {
+ return null;
+ }
+
+ /*
+ * does the feature pass filters?
+ */
+ if (!featureMatchesFilters(sf))
+ {
+ return null;
+ }
+
+ return fc.getColor(sf);
+ }
+
+ /**
+ * Answers true if there no are filters defined for the feature type, or this
+ * feature matches the filters. Answers false if the feature fails to match
+ * filters.
+ *
+ * @param sf
+ * @return
+ */
+ protected boolean featureMatchesFilters(SequenceFeature sf)
+ {
+ KeyedMatcherSetI filter = featureFilters.get(sf.getType());
+ return filter == null ? true : filter.matches(key -> sf
+ .getValueAsString(key));
+ }
+
}
import jalview.api.FeatureColourI;
import jalview.schemes.FeatureColour;
+import jalview.util.matcher.KeyedMatcherSetI;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
*/
Map<String, FeatureColourI> featureColours;
+ /*
+ * map of {featureType, filters}
+ */
+ Map<String, KeyedMatcherSetI> featureFilters;
+
float transparency;
Map<String, Float> featureOrder;
renderOrder = null;
featureGroups = new ConcurrentHashMap<String, Boolean>();
featureColours = new ConcurrentHashMap<String, FeatureColourI>();
+ featureFilters = new HashMap<>();
featureOrder = new ConcurrentHashMap<String, Float>();
+
if (fr.renderOrder != null)
{
this.renderOrder = new String[fr.renderOrder.length];
featureColours.put(next, new FeatureColour((FeatureColour) val));
}
}
+
+ if (fr.featureFilters != null)
+ {
+ this.featureFilters.putAll(fr.featureFilters);
+ }
+
this.transparency = fr.transparency;
if (fr.featureOrder != null)
{
import jalview.analysis.Finder;
import jalview.api.AlignViewControllerI;
+import jalview.api.FeatureColourI;
+import jalview.datamodel.Alignment;
import jalview.datamodel.SearchResults;
import jalview.datamodel.SearchResultsI;
import jalview.datamodel.Sequence;
import jalview.gui.JvOptionPane;
import jalview.io.DataSourceType;
import jalview.io.FileLoader;
+import jalview.schemes.FeatureColour;
+import java.awt.Color;
import java.util.Arrays;
import java.util.BitSet;
null));
seq1.addSequenceFeature(new SequenceFeature("Helix", "desc", 1, 15, 0f,
null));
- seq2.addSequenceFeature(new SequenceFeature("Metal", "desc", 4, 10, 0f,
+ seq2.addSequenceFeature(new SequenceFeature("Metal", "desc", 4, 10,
+ 10f,
null));
seq3.addSequenceFeature(new SequenceFeature("Metal", "desc", 11, 15,
- 0f, null));
+ 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
sg.addSequence(seq3, false);
sg.addSequence(seq4, false);
+ /*
+ * 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);
+ af.getFeatureRenderer().findAllFeatures(true);
+
+ AlignViewController avc = new AlignViewController(af, af.getViewport(),
+ af.alignPanel);
+
BitSet bs = new BitSet();
- int seqCount = AlignViewController.findColumnsWithFeature("Metal", sg,
- bs);
+ int seqCount = avc.findColumnsWithFeature("Metal", sg, bs);
assertEquals(1, seqCount);
assertEquals(2, bs.cardinality());
assertTrue(bs.get(3)); // base 0
*/
sg.setEndRes(6);
bs.clear();
- seqCount = AlignViewController.findColumnsWithFeature("Metal", sg, bs);
+ seqCount = avc.findColumnsWithFeature("Metal", sg, bs);
assertEquals(2, seqCount);
assertEquals(4, bs.cardinality());
assertTrue(bs.get(3));
sg.setStartRes(13);
sg.setEndRes(13);
bs.clear();
- seqCount = AlignViewController.findColumnsWithFeature("Metal", sg, bs);
+ seqCount = avc.findColumnsWithFeature("Metal", sg, bs);
assertEquals(1, seqCount);
assertEquals(1, bs.cardinality());
assertTrue(bs.get(13));
sg.setStartRes(17);
sg.setEndRes(19);
bs.clear();
- seqCount = AlignViewController.findColumnsWithFeature("Metal", sg, bs);
+ seqCount = avc.findColumnsWithFeature("Metal", sg, bs);
assertEquals(0, seqCount);
assertEquals(0, bs.cardinality());
/*
+ * threshold Metal to hide where score < 5
+ * seq1 feature in columns 4-6 is hidden
+ * seq2 feature in columns 6-7 is shown
+ */
+ FeatureColourI fc = new FeatureColour(Color.red, Color.blue, 0f, 10f);
+ fc.setAboveThreshold(true);
+ fc.setThreshold(5f);
+ af.getFeatureRenderer().setColour("Metal", fc);
+ sg.setStartRes(0);
+ sg.setEndRes(6);
+ bs.clear();
+ seqCount = avc.findColumnsWithFeature("Metal", sg, bs);
+ assertEquals(1, seqCount);
+ assertEquals(2, bs.cardinality());
+ assertTrue(bs.get(5));
+ assertTrue(bs.get(6));
+
+ /*
* columns 11-13 should not match disulfide bond at 8/12
*/
sg.setStartRes(10);
sg.setEndRes(12);
bs.clear();
- seqCount = AlignViewController.findColumnsWithFeature("disulfide bond",
- sg, bs);
+ seqCount = avc.findColumnsWithFeature("disulfide bond", sg, bs);
assertEquals(0, seqCount);
assertEquals(0, bs.cardinality());
sg.setStartRes(5);
sg.setEndRes(17);
bs.clear();
- seqCount = AlignViewController.findColumnsWithFeature("disulfide bond",
- sg, bs);
+ seqCount = avc.findColumnsWithFeature("disulfide bond", sg, bs);
assertEquals(1, seqCount);
assertEquals(2, bs.cardinality());
assertTrue(bs.get(8));
sg.setStartRes(0);
sg.setEndRes(19);
bs.clear();
- seqCount = AlignViewController.findColumnsWithFeature("Pfam", sg, bs);
+ seqCount = avc.findColumnsWithFeature("Pfam", sg, bs);
assertEquals(0, seqCount);
assertEquals(0, bs.cardinality());
}
{
// single locus, no group, no score
SequenceFeature sf = new SequenceFeature("variant", "G,C", 22, 22, null);
- String expected = "<br><table><tr><td>Type</td><td>variant</td></tr>"
- + "<tr><td>Start/end</td><td>22</td></tr>"
- + "<tr><td>Description</td><td>G,C</td></tr></table>";
+ String expected = "<br><table><tr><td>Type</td><td>variant</td><td></td></tr>"
+ + "<tr><td>Start/end</td><td>22</td><td></td></tr>"
+ + "<tr><td>Description</td><td>G,C</td><td></td></tr></table>";
assertEquals(expected, sf.getDetailsReport());
// contact feature
sf = new SequenceFeature("Disulphide Bond", "a description", 28, 31,
null);
- expected = "<br><table><tr><td>Type</td><td>Disulphide Bond</td></tr>"
- + "<tr><td>Start/end</td><td>28:31</td></tr>"
- + "<tr><td>Description</td><td>a description</td></tr></table>";
+ expected = "<br><table><tr><td>Type</td><td>Disulphide Bond</td><td></td></tr>"
+ + "<tr><td>Start/end</td><td>28:31</td><td></td></tr>"
+ + "<tr><td>Description</td><td>a description</td><td></td></tr></table>";
assertEquals(expected, sf.getDetailsReport());
sf = new SequenceFeature("variant", "G,C", 22, 33,
12.5f, "group");
sf.setValue("Parent", "ENSG001");
sf.setValue("Child", "ENSP002");
- expected = "<br><table><tr><td>Type</td><td>variant</td></tr>"
- + "<tr><td>Start/end</td><td>22-33</td></tr>"
- + "<tr><td>Description</td><td>G,C</td></tr>"
- + "<tr><td>Score</td><td>12.5</td></tr>"
- + "<tr><td>Group</td><td>group</td></tr>"
- + "<tr><td>Child</td><td>ENSP002</td></tr>"
- + "<tr><td>Parent</td><td>ENSG001</td></tr></table>";
+ expected = "<br><table><tr><td>Type</td><td>variant</td><td></td></tr>"
+ + "<tr><td>Start/end</td><td>22-33</td><td></td></tr>"
+ + "<tr><td>Description</td><td>G,C</td><td></td></tr>"
+ + "<tr><td>Score</td><td>12.5</td><td></td></tr>"
+ + "<tr><td>Group</td><td>group</td><td></td></tr>"
+ + "<tr><td>Child</td><td></td><td>ENSP002</td></tr>"
+ + "<tr><td>Parent</td><td></td><td>ENSG001</td></tr></table>";
assertEquals(expected, sf.getDetailsReport());
/*
*/
String desc = "<html>Fer2 Status: True Positive <a href=\"http://pfam.xfam.org/family/PF00111\">Pfam 8_8</a></html>";
sf = new SequenceFeature("Pfam", desc, 8, 83, "Uniprot");
- expected = "<br><table><tr><td>Type</td><td>Pfam</td></tr>"
- + "<tr><td>Start/end</td><td>8-83</td></tr>"
- + "<tr><td>Description</td><td>Fer2 Status: True Positive <a href=\"http://pfam.xfam.org/family/PF00111\">Pfam 8_8</a></td></tr>"
- + "<tr><td>Group</td><td>Uniprot</td></tr></table>";
+ expected = "<br><table><tr><td>Type</td><td>Pfam</td><td></td></tr>"
+ + "<tr><td>Start/end</td><td>8-83</td><td></td></tr>"
+ + "<tr><td>Description</td><td>Fer2 Status: True Positive <a href=\"http://pfam.xfam.org/family/PF00111\">Pfam 8_8</a></td><td></td></tr>"
+ + "<tr><td>Group</td><td>Uniprot</td><td></td></tr></table>";
assertEquals(expected, sf.getDetailsReport());
}
}
import static org.testng.Assert.assertSame;
import static org.testng.Assert.assertTrue;
+import jalview.api.FeatureColourI;
import jalview.bin.Cache;
import jalview.bin.Jalview;
import jalview.datamodel.Alignment;
import jalview.io.Jalview2xmlTests;
import jalview.renderer.ResidueShaderI;
import jalview.schemes.BuriedColourScheme;
+import jalview.schemes.FeatureColour;
import jalview.schemes.HelixColourScheme;
import jalview.schemes.JalviewColourScheme;
import jalview.schemes.StrandColourScheme;
{
SequenceI seq1 = new Sequence("Seq1", "ABCDEFGHIJ");
SequenceI seq2 = new Sequence("Seq2", "ABCDEFGHIJ");
- seq1.addSequenceFeature(new SequenceFeature("Metal", "", 1, 5,
- Float.NaN, null));
- seq2.addSequenceFeature(new SequenceFeature("Metal", "", 6, 10,
- Float.NaN, null));
+ 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));
AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
- AlignFrame alignFrame = new AlignFrame(al, al.getWidth(), al.getHeight());
+ AlignFrame alignFrame = new AlignFrame(al, al.getWidth(),
+ al.getHeight());
+
+ /*
+ * make all features visible (select feature columns checks visibility)
+ */
+ alignFrame.getFeatureRenderer().findAllFeatures(true);
/*
* hiding a feature not present does nothing
assertFalse(alignFrame.hideFeatureColumns("exon", true));
assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty());
assertTrue(alignFrame.getViewport().getAlignment().getHiddenColumns()
- .getHiddenColumnsCopy()
- .isEmpty());
+ .getHiddenColumnsCopy().isEmpty());
assertFalse(alignFrame.hideFeatureColumns("exon", false));
assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty());
assertTrue(alignFrame.getViewport().getAlignment().getHiddenColumns()
- .getHiddenColumnsCopy()
- .isEmpty());
+ .getHiddenColumnsCopy().isEmpty());
/*
* hiding a feature in all columns does nothing
assertFalse(alignFrame.hideFeatureColumns("Metal", true));
assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty());
List<int[]> hidden = alignFrame.getViewport().getAlignment()
- .getHiddenColumns()
- .getHiddenColumnsCopy();
+ .getHiddenColumns().getHiddenColumnsCopy();
assertTrue(hidden.isEmpty());
/*
+ * threshold Metal to hide features where score < 5
+ * seq1 feature in columns 1-5 is hidden
+ * seq2 feature in columns 6-10 is shown
+ */
+ 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));
+ hidden = alignFrame.getViewport().getAlignment().getHiddenColumns()
+ .getHiddenColumnsCopy();
+ assertEquals(hidden.size(), 1);
+ assertEquals(hidden.get(0)[0], 5);
+ assertEquals(hidden.get(0)[1], 9);
+
+ /*
* hide a feature present in some columns
* sequence positions [2-4], [7-9] are column positions
* [1-3], [6-8] base zero
*/
+ alignFrame.getViewport().showAllHiddenColumns();
assertTrue(alignFrame.hideFeatureColumns("Turn", true));
hidden = alignFrame.getViewport().getAlignment().getHiddenColumns()
.getHiddenColumnsCopy();
--- /dev/null
+package jalview.util.matcher;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Locale;
+
+import org.testng.annotations.Test;
+
+public class ConditionTest
+{
+ @Test
+ public void testToString()
+ {
+ Locale.setDefault(Locale.UK);
+ assertEquals(Condition.Contains.toString(), "Contains");
+ assertEquals(Condition.NotContains.toString(), "Does not contain");
+ assertEquals(Condition.Matches.toString(), "Matches");
+ assertEquals(Condition.NotMatches.toString(), "Does not match");
+ assertEquals(Condition.LT.toString(), "<");
+ assertEquals(Condition.LE.toString(), "<=");
+ assertEquals(Condition.GT.toString(), ">");
+ assertEquals(Condition.GE.toString(), ">=");
+ assertEquals(Condition.EQ.toString(), "=");
+ assertEquals(Condition.NE.toString(), "not =");
+
+ /*
+ * repeat call to get coverage of value caching
+ */
+ assertEquals(Condition.NE.toString(), "not =");
+ }
+}
--- /dev/null
+package jalview.util.matcher;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.function.Function;
+
+import org.testng.annotations.Test;
+
+public class KeyedMatcherSetTest
+{
+ @Test
+ public void testMatches()
+ {
+ /*
+ * a numeric matcher - MatcherTest covers more conditions
+ */
+ KeyedMatcherI km = new KeyedMatcher("AF", Condition.GE, -2F);
+ KeyedMatcherSetI kms = new KeyedMatcherSet();
+ kms.and(km);
+ assertTrue(kms.matches(key -> "-2"));
+ assertTrue(kms.matches(key -> "-1"));
+ assertFalse(kms.matches(key -> "-3"));
+ assertFalse(kms.matches(key -> ""));
+ assertFalse(kms.matches(key -> "junk"));
+ assertFalse(kms.matches(key -> null));
+
+ /*
+ * a string pattern matcher
+ */
+ km = new KeyedMatcher("AF", Condition.Contains, "Cat");
+ kms = new KeyedMatcherSet();
+ kms.and(km);
+ assertTrue(kms
+ .matches(key -> "AF".equals(key) ? "raining cats and dogs"
+ : "showers"));
+ }
+
+ @Test
+ public void testAnd()
+ {
+ // condition1: AF value contains "dog" (matches)
+ KeyedMatcherI km1 = new KeyedMatcher("AF", Condition.Contains, "dog");
+ // condition 2: CSQ value does not contain "how" (does not match)
+ KeyedMatcherI km2 = new KeyedMatcher("CSQ", Condition.NotContains,
+ "how");
+
+ Function<String, String> vp = key -> "AF".equals(key) ? "raining cats and dogs"
+ : "showers";
+ assertTrue(km1.matches(vp));
+ assertFalse(km2.matches(vp));
+
+ KeyedMatcherSetI kms = new KeyedMatcherSet();
+ assertTrue(kms.matches(vp)); // if no conditions, then 'all' pass
+ kms.and(km1);
+ assertTrue(kms.matches(vp));
+ kms.and(km2);
+ assertFalse(kms.matches(vp));
+ }
+
+ @Test
+ public void testToString()
+ {
+ KeyedMatcherI km1 = new KeyedMatcher("AF", Condition.LT, 1.2f);
+ assertEquals(km1.toString(), "AF < 1.2");
+
+ KeyedMatcher km2 = new KeyedMatcher("CLIN_SIG", Condition.NotContains, "path");
+ assertEquals(km2.toString(), "CLIN_SIG Does not contain PATH");
+
+ /*
+ * AND them
+ */
+ KeyedMatcherSetI kms = new KeyedMatcherSet();
+ assertEquals(kms.toString(), "");
+ kms.and(km1);
+ assertEquals(kms.toString(), "(AF < 1.2)");
+ kms.and(km2);
+ assertEquals(kms.toString(),
+ "(AF < 1.2) AND (CLIN_SIG Does not contain PATH)");
+
+ /*
+ * OR them
+ */
+ kms = new KeyedMatcherSet();
+ assertEquals(kms.toString(), "");
+ kms.or(km1);
+ assertEquals(kms.toString(), "(AF < 1.2)");
+ kms.or(km2);
+ assertEquals(kms.toString(),
+ "(AF < 1.2) OR (CLIN_SIG Does not contain PATH)");
+ }
+
+ @Test
+ public void testOr()
+ {
+ // condition1: AF value contains "dog" (matches)
+ KeyedMatcherI km1 = new KeyedMatcher("AF", Condition.Contains, "dog");
+ // condition 2: CSQ value does not contain "how" (does not match)
+ KeyedMatcherI km2 = new KeyedMatcher("CSQ", Condition.NotContains,
+ "how");
+
+ Function<String, String> vp = key -> "AF".equals(key) ? "raining cats and dogs"
+ : "showers";
+ assertTrue(km1.matches(vp));
+ assertFalse(km2.matches(vp));
+
+ KeyedMatcherSetI kms = new KeyedMatcherSet();
+ kms.or(km2);
+ assertFalse(kms.matches(vp));
+ kms.or(km1);
+ assertTrue(kms.matches(vp));
+ }
+
+ @Test
+ public void testIsEmpty()
+ {
+ KeyedMatcherI km = new KeyedMatcher("AF", Condition.GE, -2F);
+ KeyedMatcherSetI kms = new KeyedMatcherSet();
+ assertTrue(kms.isEmpty());
+ kms.and(km);
+ assertFalse(kms.isEmpty());
+ }
+}
--- /dev/null
+package jalview.util.matcher;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import org.testng.annotations.Test;
+
+public class KeyedMatcherTest
+{
+ @Test
+ public void testMatches()
+ {
+ /*
+ * a numeric matcher - MatcherTest covers more conditions
+ */
+ KeyedMatcherI km = new KeyedMatcher("AF", Condition.GE, -2F);
+ assertTrue(km.matches(key -> "-2"));
+ assertTrue(km.matches(key -> "-1"));
+ assertFalse(km.matches(key -> "-3"));
+ assertFalse(km.matches(key -> ""));
+ assertFalse(km.matches(key -> "junk"));
+ assertFalse(km.matches(key -> null));
+
+ /*
+ * a string pattern matcher
+ */
+ km = new KeyedMatcher("AF", Condition.Contains, "Cat");
+ assertTrue(km.matches(key -> "AF".equals(key) ? "raining cats and dogs"
+ : "showers"));
+ }
+
+ @Test
+ public void testToString()
+ {
+ /*
+ * toString uses the i18n translation of the enum conditions
+ */
+ KeyedMatcherI km = new KeyedMatcher("AF", Condition.LT, 1.2f);
+ assertEquals(km.toString(), "AF < 1.2");
+ }
+
+ @Test
+ public void testGetKey()
+ {
+ KeyedMatcherI km = new KeyedMatcher("AF", Condition.GE, -2F);
+ assertEquals(km.getKey(), "AF");
+ }
+
+ @Test
+ public void testGetMatcher()
+ {
+ KeyedMatcherI km = new KeyedMatcher("AF", Condition.GE, -2F);
+ assertEquals(km.getMatcher().getCondition(), Condition.GE);
+ assertEquals(km.getMatcher().getFloatValue(), -2F);
+ assertEquals(km.getMatcher().getPattern(), "-2.0");
+ }
+}
--- /dev/null
+package jalview.util.matcher;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import org.testng.annotations.Test;
+
+public class MatcherTest
+{
+ @Test
+ public void testConstructor()
+ {
+ MatcherI m = new Matcher(Condition.Contains, "foo");
+ assertEquals(m.getCondition(), Condition.Contains);
+ assertEquals(m.getPattern(), "FOO"); // all comparisons upper-cased
+ assertEquals(m.getFloatValue(), 0f);
+
+ m = new Matcher(Condition.GT, -2.1f);
+ assertEquals(m.getCondition(), Condition.GT);
+ assertEquals(m.getPattern(), "-2.1");
+ assertEquals(m.getFloatValue(), -2.1f);
+
+ m = new Matcher(Condition.NotContains, "-1.2f");
+ assertEquals(m.getCondition(), Condition.NotContains);
+ assertEquals(m.getPattern(), "-1.2F");
+ assertEquals(m.getFloatValue(), 0f);
+
+ m = new Matcher(Condition.GE, "-1.2f");
+ assertEquals(m.getCondition(), Condition.GE);
+ assertEquals(m.getPattern(), "-1.2");
+ assertEquals(m.getFloatValue(), -1.2f);
+
+ try
+ {
+ new Matcher(null, 0f);
+ fail("Expected exception");
+ } catch (NullPointerException e)
+ {
+ // expected
+ }
+
+ try
+ {
+ new Matcher(Condition.LT, "123,456");
+ fail("Expected exception");
+ } catch (NumberFormatException e)
+ {
+ // expected
+ }
+ }
+
+ /**
+ * Tests for float comparison conditions
+ */
+ @Test
+ public void testMatches_float()
+ {
+ /*
+ * EQUALS test
+ */
+ MatcherI m = new Matcher(Condition.EQ, 2f);
+ assertTrue(m.matches("2"));
+ assertTrue(m.matches("2.0"));
+ assertFalse(m.matches("2.01"));
+
+ /*
+ * NOT EQUALS test
+ */
+ m = new Matcher(Condition.NE, 2f);
+ assertFalse(m.matches("2"));
+ assertFalse(m.matches("2.0"));
+ assertTrue(m.matches("2.01"));
+
+ /*
+ * >= test
+ */
+ m = new Matcher(Condition.GE, 2f);
+ assertTrue(m.matches("2"));
+ assertTrue(m.matches("2.1"));
+ assertFalse(m.matches("1.9"));
+
+ /*
+ * > test
+ */
+ m = new Matcher(Condition.GT, 2f);
+ assertFalse(m.matches("2"));
+ assertTrue(m.matches("2.1"));
+ assertFalse(m.matches("1.9"));
+
+ /*
+ * <= test
+ */
+ m = new Matcher(Condition.LE, 2f);
+ assertTrue(m.matches("2"));
+ assertFalse(m.matches("2.1"));
+ assertTrue(m.matches("1.9"));
+
+ /*
+ * < test
+ */
+ m = new Matcher(Condition.LT, 2f);
+ assertFalse(m.matches("2"));
+ assertFalse(m.matches("2.1"));
+ assertTrue(m.matches("1.9"));
+ }
+
+ @Test
+ public void testMatches_floatNullOrInvalid()
+ {
+ for (Condition cond : Condition.values())
+ {
+ if (cond.isNumeric())
+ {
+ MatcherI m = new Matcher(cond, 2f);
+ assertFalse(m.matches(null));
+ assertFalse(m.matches(""));
+ assertFalse(m.matches("two"));
+ }
+ }
+ }
+
+ /**
+ * Tests for string comparison conditions
+ */
+ @Test
+ public void testMatches_pattern()
+ {
+ /*
+ * Contains
+ */
+ MatcherI m = new Matcher(Condition.Contains, "benign");
+ assertTrue(m.matches("benign"));
+ assertTrue(m.matches("MOSTLY BENIGN OBSERVED")); // not case-sensitive
+ assertFalse(m.matches("pathogenic"));
+ assertFalse(m.matches(null));
+
+ /*
+ * does not contain
+ */
+ m = new Matcher(Condition.NotContains, "benign");
+ assertFalse(m.matches("benign"));
+ assertFalse(m.matches("MOSTLY BENIGN OBSERVED")); // not case-sensitive
+ assertTrue(m.matches("pathogenic"));
+ assertTrue(m.matches(null)); // null value passes this condition
+
+ /*
+ * matches
+ */
+ m = new Matcher(Condition.Matches, "benign");
+ assertTrue(m.matches("benign"));
+ assertTrue(m.matches(" Benign ")); // trim before testing
+ assertFalse(m.matches("MOSTLY BENIGN"));
+ assertFalse(m.matches("pathogenic"));
+ assertFalse(m.matches(null));
+
+ /*
+ * does not match
+ */
+ m = new Matcher(Condition.NotMatches, "benign");
+ assertFalse(m.matches("benign"));
+ assertFalse(m.matches(" Benign ")); // trim before testing
+ assertTrue(m.matches("MOSTLY BENIGN"));
+ assertTrue(m.matches("pathogenic"));
+ assertTrue(m.matches(null));
+
+ /*
+ * a float with a string match condition will be treated as string
+ */
+ Matcher m1 = new Matcher(Condition.Contains, "32");
+ assertFalse(m1.matches(-203f));
+ assertTrue(m1.matches(-4321.0f));
+ }
+
+ /**
+ * If a float is passed with a string condition it gets converted to a string
+ */
+ @Test
+ public void testMatches_floatWithStringCondition()
+ {
+ MatcherI m = new Matcher(Condition.Contains, 1.2e-6f);
+ assertTrue(m.matches("1.2e-6"));
+
+ m = new Matcher(Condition.Contains, 0.0000001f);
+ assertTrue(m.matches("1.0e-7"));
+ assertTrue(m.matches("1.0E-7"));
+ assertFalse(m.matches("0.0000001f"));
+ }
+
+ @Test
+ public void testToString()
+ {
+ MatcherI m = new Matcher(Condition.LT, 1.2e-6f);
+ assertEquals(m.toString(), "LT 1.2E-6");
+
+ m = new Matcher(Condition.NotMatches, "ABC");
+ assertEquals(m.toString(), "NotMatches ABC");
+
+ m = new Matcher(Condition.Contains, -1.2f);
+ assertEquals(m.toString(), "Contains -1.2");
+ }
+
+ @Test
+ public void testEquals()
+ {
+ /*
+ * string condition
+ */
+ MatcherI m = new Matcher(Condition.NotMatches, "ABC");
+ assertFalse(m.equals(null));
+ assertFalse(m.equals("foo"));
+ assertTrue(m.equals(m));
+ assertTrue(m.equals(new Matcher(Condition.NotMatches, "ABC")));
+ // not case-sensitive:
+ assertTrue(m.equals(new Matcher(Condition.NotMatches, "abc")));
+ assertFalse(m.equals(new Matcher(Condition.Matches, "ABC")));
+ assertFalse(m.equals(new Matcher(Condition.NotMatches, "def")));
+
+ /*
+ * numeric conditions
+ */
+ m = new Matcher(Condition.LT, -1f);
+ assertFalse(m.equals(null));
+ assertFalse(m.equals("foo"));
+ assertTrue(m.equals(m));
+ assertTrue(m.equals(new Matcher(Condition.LT, -1f)));
+ assertTrue(m.equals(new Matcher(Condition.LT, "-1f")));
+ assertTrue(m.equals(new Matcher(Condition.LT, "-1.00f")));
+ assertFalse(m.equals(new Matcher(Condition.LE, -1f)));
+ assertFalse(m.equals(new Matcher(Condition.GE, -1f)));
+ assertFalse(m.equals(new Matcher(Condition.NE, -1f)));
+ assertFalse(m.equals(new Matcher(Condition.LT, 1f)));
+ assertFalse(m.equals(new Matcher(Condition.LT, -1.1f)));
+ }
+
+ @Test
+ public void testHashCode()
+ {
+ MatcherI m1 = new Matcher(Condition.NotMatches, "ABC");
+ MatcherI m2 = new Matcher(Condition.NotMatches, "ABC");
+ MatcherI m3 = new Matcher(Condition.NotMatches, "AB");
+ MatcherI m4 = new Matcher(Condition.Matches, "ABC");
+ assertEquals(m1.hashCode(), m2.hashCode());
+ assertNotEquals(m1.hashCode(), m3.hashCode());
+ assertNotEquals(m1.hashCode(), m4.hashCode());
+ assertNotEquals(m3.hashCode(), m4.hashCode());
+ }
+}