import jalview.datamodel.GraphLine;
import jalview.datamodel.features.FeatureAttributes;
import jalview.datamodel.features.FeatureAttributes.Datatype;
+import jalview.datamodel.features.FeatureMatcher;
+import jalview.datamodel.features.FeatureMatcherI;
+import jalview.datamodel.features.FeatureMatcherSet;
+import jalview.datamodel.features.FeatureMatcherSetI;
import jalview.schemes.FeatureColour;
import jalview.util.ColorUtils;
import jalview.util.MessageManager;
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 java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
-import java.awt.LayoutManager;
+import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
+import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSlider;
-import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.border.LineBorder;
*/
public class FeatureTypeSettings extends JalviewDialog
{
+ private final static String LABEL_18N = MessageManager
+ .getString("label.label");
+
+ private final static String SCORE_18N = MessageManager
+ .getString("label.score");
+
private static final int RADIO_WIDTH = 130;
private static final String COLON = ":";
private static final int BELOW_THRESHOLD_OPTION = 2;
+ private static final DecimalFormat DECFMT_2_2 = new DecimalFormat(
+ "##.##");
+
/*
* FeatureRenderer holds colour scheme and filters for feature types
*/
- private final FeatureRenderer fr; // todo refactor to allow interface type here
+ private final FeatureRenderer fr; // todo refactor to allow interface type
+ // here
/*
* the view panel to update when settings change
*/
private final FeatureColourI originalColour;
- private final KeyedMatcherSetI originalFilter;
+ private final FeatureMatcherSetI originalFilter;
/*
* set flag to true when setting values programmatically,
*/
private boolean adjusting = false;
+ /*
+ * minimum of the value range for graduated colour
+ * (may be for feature score or for a numeric attribute)
+ */
private float min;
+ /*
+ * maximum of the value range for graduated colour
+ */
private float max;
+ /*
+ * scale factor for conversion between absolute min-max and slider
+ */
private float scaleFactor;
/*
private JRadioButton graduatedColour = new JRadioButton();
- private JPanel singleColour = new JPanel();
+ /**
+ * colours and filters are shown in tabbed view or single content pane
+ */
+ JPanel coloursPanel, filtersPanel;
+
+ JPanel singleColour = new JPanel();
private JPanel minColour = new JPanel();
/*
* filters for the currently selected feature type
*/
- private List<KeyedMatcherI> filters;
-
- // set white normally, black to debug layout
- private Color debugBorderColour = Color.white;
+ private List<FeatureMatcherI> filters;
private JPanel chooseFiltersPanel;
- private JTabbedPane tabbedPane;
-
/**
* Constructor
*
*/
public FeatureTypeSettings(FeatureRenderer frender, String theType)
{
- this(frender, false, theType);
- }
-
- /**
- * Constructor, with option to make a blocking dialog (has to complete in the
- * AWT event queue thread). Currently this option is always set to false.
- *
- * @param frender
- * @param blocking
- * @param theType
- */
- FeatureTypeSettings(FeatureRenderer frender, boolean blocking,
- String theType)
- {
this.fr = frender;
this.featureType = theType;
ap = fr.ap;
originalFilter = fr.getFeatureFilter(theType);
originalColour = fr.getFeatureColours().get(theType);
-
+
adjusting = true;
-
+
try
{
initialise();
ex.printStackTrace();
return;
}
-
+
updateColoursTab();
-
+
updateFiltersTab();
-
+
adjusting = false;
-
+
colourChanged(false);
-
+
String title = MessageManager
.formatMessage("label.display_settings_for", new String[]
{ theType });
- initDialogFrame(this, true, blocking, title, 600, 360);
-
+ initDialogFrame(this, true, false, title, 580, 500);
waitForInput();
}
*/
if (fc.isSimpleColour())
{
- simpleColour.setSelected(true);
singleColour.setBackground(fc.getColour());
singleColour.setForeground(fc.getColour());
+ simpleColour.setSelected(true);
}
/*
if (fc.isColourByAttribute())
{
String[] attributeName = fc.getAttributeName();
- colourByTextCombo
- .setSelectedItem(toAttributeDisplayName(attributeName));
+ colourByTextCombo.setSelectedItem(
+ FeatureMatcher.toAttributeDisplayName(attributeName));
}
else
{
- colourByTextCombo
- .setSelectedItem(MessageManager.getString("label.label"));
+ colourByTextCombo.setSelectedItem(LABEL_18N);
}
}
else
* Graduated colour, by score or attribute value range
*/
graduatedColour.setSelected(true);
+ updateColourMinMax(); // ensure min, max are set
colourByRangeCombo.setEnabled(colourByRangeCombo.getItemCount() > 1);
minColour.setEnabled(true);
maxColour.setEnabled(true);
if (fc.isColourByAttribute())
{
String[] attributeName = fc.getAttributeName();
- colourByRangeCombo
- .setSelectedItem(toAttributeDisplayName(attributeName));
+ colourByRangeCombo.setSelectedItem(
+ FeatureMatcher.toAttributeDisplayName(attributeName));
}
else
{
- colourByRangeCombo
- .setSelectedItem(MessageManager.getString("label.score"));
+ colourByRangeCombo.setSelectedItem(SCORE_18N);
}
Color noColour = fc.getNoColour();
if (noColour == null)
: BELOW_THRESHOLD_OPTION);
slider.setEnabled(true);
slider.setValue((int) (fc.getThreshold() * scaleFactor));
- thresholdValue.setText(String.valueOf(getRoundedSliderValue()));
+ thresholdValue.setText(String.valueOf(fc.getThreshold()));
thresholdValue.setEnabled(true);
thresholdIsMin.setEnabled(true);
}
private void initialise()
{
this.setLayout(new BorderLayout());
- tabbedPane = new JTabbedPane();
- this.add(tabbedPane, BorderLayout.CENTER);
/*
* an ActionListener that applies colour changes
};
/*
- * first tab: colour options
+ * first panel/tab: colour options
*/
JPanel coloursPanel = initialiseColoursPanel();
- tabbedPane.addTab(MessageManager.getString("action.colour"),
- coloursPanel);
+ this.add(coloursPanel, BorderLayout.NORTH);
/*
- * second tab: filter options
+ * second panel/tab: filter options
*/
JPanel filtersPanel = initialiseFiltersPanel();
- tabbedPane.addTab(MessageManager.getString("label.filters"),
- filtersPanel);
+ this.add(filtersPanel, BorderLayout.CENTER);
JPanel okCancelPanel = initialiseOkCancelPanel();
* Updates the min-max range if Colour By selected item is Score, or an
* attribute, with a min-max range
*/
- protected void updateMinMax()
+ protected void updateColourMinMax()
{
if (!graduatedColour.isSelected())
{
return;
}
- float[] minMax = null;
String colourBy = (String) colourByRangeCombo.getSelectedItem();
- if (MessageManager.getString("label.score").equals(colourBy))
+ float[] minMax = getMinMax(colourBy);
+
+ if (minMax != null)
+ {
+ min = minMax[0];
+ max = minMax[1];
+ }
+ }
+
+ /**
+ * Retrieves the min-max range:
+ * <ul>
+ * <li>of feature score, if colour or filter is by Score</li>
+ * <li>else of the selected attribute</li>
+ * </ul>
+ *
+ * @param attName
+ * @return
+ */
+ private float[] getMinMax(String attName)
+ {
+ float[] minMax = null;
+ if (SCORE_18N.equals(attName))
{
minMax = fr.getMinMax().get(featureType)[0];
}
else
{
// colour by attribute range
- String[] attNames = fromAttributeDisplayName(colourBy);
minMax = FeatureAttributes.getInstance().getMinMax(featureType,
- attNames);
- }
-
- if (minMax != null)
- {
- min = minMax[0];
- max = minMax[1];
+ FeatureMatcher.fromAttributeDisplayName(attName));
}
+ return minMax;
}
/**
maxColour.setBorder(new LineBorder(Color.black));
/*
- * default max colour to current colour (if a plain colour),
- * or to Black if colour by label; make min colour a pale
- * version of max colour
+ * if not set, default max colour to last plain colour,
+ * and make min colour a pale version of max colour
*/
- FeatureColourI fc = fr.getFeatureColours().get(featureType);
- Color bg = fc.isSimpleColour() ? fc.getColour() : Color.BLACK;
- maxColour.setBackground(bg);
- minColour.setBackground(ColorUtils.bleachColour(bg, 0.9f));
+ Color max = originalColour.getMaxColour();
+ if (max == null)
+ {
+ max = originalColour.getColour();
+ minColour.setBackground(ColorUtils.bleachColour(max, 0.9f));
+ }
+ else
+ {
+ maxColour.setBackground(max);
+ minColour.setBackground(originalColour.getMinColour());
+ }
noValueCombo = new JComboBox<>();
noValueCombo.addItem(MessageManager.getString("label.no_colour"));
{
thresholdValue
.setText(String.valueOf(slider.getValue() / scaleFactor));
+ thresholdValue.setBackground(Color.white); // to reset red for invalid
sliderValueChanged();
}
}
private JPanel initialiseColoursPanel()
{
JPanel colourByPanel = new JPanel();
+ colourByPanel.setBackground(Color.white);
colourByPanel.setLayout(new BoxLayout(colourByPanel, BoxLayout.Y_AXIS));
+ JvSwingUtils.createTitledBorder(colourByPanel,
+ MessageManager.getString("action.colour"), true);
/*
* simple colour radio button and colour picker
*/
JPanel simpleColourPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
simpleColourPanel.setBackground(Color.white);
- JvSwingUtils.createTitledBorder(simpleColourPanel,
- MessageManager.getString("label.simple"), true);
colourByPanel.add(simpleColourPanel);
simpleColour = new JRadioButton(
{
if (simpleColour.isSelected() && !adjusting)
{
- showColourChooser(singleColour, "label.select_colour");
+ colourChanged(true);
}
}
-
});
-
+
singleColour.setFont(JvSwingUtils.getLabelFont());
singleColour.setBorder(BorderFactory.createLineBorder(Color.black));
singleColour.setPreferredSize(new Dimension(40, 20));
+ // if (originalColour.isGraduatedColour())
+ // {
+ // singleColour.setBackground(originalColour.getMaxColour());
+ // singleColour.setForeground(originalColour.getMaxColour());
+ // }
+ // else
+ // {
+ singleColour.setBackground(originalColour.getColour());
+ singleColour.setForeground(originalColour.getColour());
+ // }
singleColour.addMouseListener(new MouseAdapter()
{
@Override
}
/**
- * Constructs and sets the selected colour options as the colour for the feature
- * type, and repaints the alignment, and optionally the Overview and/or
- * structure viewer if open
+ * Constructs and sets the selected colour options as the colour for the
+ * feature type, and repaints the alignment, and optionally the Overview
+ * and/or structure viewer if open
*
* @param updateStructsAndOverview
*/
* ensure min-max range is for the latest choice of
* 'graduated colour by'
*/
- updateMinMax();
+ updateColourMinMax();
FeatureColourI acg = makeColourFromInputs();
private FeatureColourI makeColourFromInputs()
{
/*
- * easiest case - a single colour
- */
- if (simpleColour.isSelected())
- {
- return new FeatureColour(singleColour.getBackground());
- }
-
- /*
- * next easiest case - colour by Label, or attribute text
- */
- if (byCategory.isSelected())
- {
- Color c = this.getBackground();
- FeatureColourI fc = new FeatureColour(c, c, null, 0f, 0f);
- fc.setColourByLabel(true);
- String byWhat = (String) colourByTextCombo.getSelectedItem();
- if (!MessageManager.getString("label.label").equals(byWhat))
- {
- fc.setAttributeName(fromAttributeDisplayName(byWhat));
- }
- return fc;
- }
-
- /*
- * remaining case - graduated colour by score, or attribute value
+ * min-max range is to (or from) threshold value if
+ * 'threshold is min/max' is selected
*/
- Color noColour = null;
- if (noValueCombo.getSelectedIndex() == MIN_COLOUR_OPTION)
- {
- noColour = minColour.getBackground();
- }
- else if (noValueCombo.getSelectedIndex() == MAX_COLOUR_OPTION)
- {
- noColour = maxColour.getBackground();
- }
float thresh = 0f;
try
{
// invalid inputs are already handled on entry
}
-
- /*
- * min-max range is to (or from) threshold value if
- * 'threshold is min/max' is selected
- */
float minValue = min;
float maxValue = max;
final int thresholdOption = threshold.getSelectedIndex();
{
maxValue = thresh;
}
+ Color noColour = null;
+ if (noValueCombo.getSelectedIndex() == MIN_COLOUR_OPTION)
+ {
+ noColour = minColour.getBackground();
+ }
+ else if (noValueCombo.getSelectedIndex() == MAX_COLOUR_OPTION)
+ {
+ noColour = maxColour.getBackground();
+ }
/*
- * make the graduated colour
+ * construct a colour that 'remembers' all the options, including
+ * those not currently selected
*/
- FeatureColourI fc = new FeatureColour(minColour.getBackground(),
- maxColour.getBackground(), noColour, minValue, maxValue);
+ FeatureColourI fc = new FeatureColour(singleColour.getBackground(),
+ minColour.getBackground(), maxColour.getBackground(), noColour,
+ minValue, maxValue);
/*
+ * easiest case - a single colour
+ */
+ if (simpleColour.isSelected())
+ {
+ ((FeatureColour) fc).setGraduatedColour(false);
+ return fc;
+ }
+
+ /*
+ * next easiest case - colour by Label, or attribute text
+ */
+ if (byCategory.isSelected())
+ {
+ fc.setColourByLabel(true);
+ String byWhat = (String) colourByTextCombo.getSelectedItem();
+ if (!LABEL_18N.equals(byWhat))
+ {
+ fc.setAttributeName(
+ FeatureMatcher.fromAttributeDisplayName(byWhat));
+ }
+ return fc;
+ }
+
+ /*
+ * remaining case - graduated colour by score, or attribute value;
* set attribute to colour by if selected
*/
String byWhat = (String) colourByRangeCombo.getSelectedItem();
- if (!MessageManager.getString("label.score").equals(byWhat))
+ if (!SCORE_18N.equals(byWhat))
{
- fc.setAttributeName(fromAttributeDisplayName(byWhat));
+ fc.setAttributeName(FeatureMatcher.fromAttributeDisplayName(byWhat));
}
/*
return fc;
}
- /**
- * A helper method that converts a 'compound' attribute name from its display
- * form, e.g. CSQ:PolyPhen to array form, e.g. { "CSQ", "PolyPhen" }
- *
- * @param attribute
- * @return
- */
- private String[] fromAttributeDisplayName(String attribute)
- {
- return attribute == null ? null : attribute.split(COLON);
- }
-
- /**
- * A helper method that converts a 'compound' attribute name to its display
- * form, e.g. CSQ:PolyPhen from its array form, e.g. { "CSQ", "PolyPhen" }
- *
- * @param attName
- * @return
- */
- private String toAttributeDisplayName(String[] attName)
- {
- return attName == null ? "" : String.join(COLON, attName);
- }
-
@Override
protected void raiseClosed()
{
{
try
{
+ /*
+ * set 'adjusting' flag while moving the slider, so it
+ * doesn't then in turn change the value (with rounding)
+ */
adjusting = true;
float f = Float.parseFloat(thresholdValue.getText());
+ f = Float.max(f, this.min);
+ f = Float.min(f, this.max);
+ thresholdValue.setText(String.valueOf(f));
slider.setValue((int) (f * scaleFactor));
threshline.value = f;
thresholdValue.setBackground(Color.white); // ok
-
- /*
- * force repaint of any Overview window or structure
- */
- ap.paintAlignment(true, true);
+ adjusting = false;
+ colourChanged(true);
} catch (NumberFormatException ex)
{
thresholdValue.setBackground(Color.red); // not ok
- } finally
- {
adjusting = false;
}
}
/**
* Converts the slider value to its absolute value by dividing by the
- * scaleFactor. Rounding errors are squashed by forcing min/max of slider range
- * to the actual min/max of feature score range
+ * scaleFactor. Rounding errors are squashed by forcing min/max of slider
+ * range to the actual min/max of feature score range
*
* @return
*/
}
/**
- * A helper method to build the drop-down choice of attributes for a feature. If
- * 'withRange' is true, then Score, and any attributes with a min-max range, are
- * added. If 'withText' is true, Label and any known attributes are added. This
- * allows 'categorical numerical' attributes e.g. codon position to be coloured
- * by text.
+ * A helper method to build the drop-down choice of attributes for a feature.
+ * If 'withRange' is true, then Score, and any attributes with a min-max
+ * range, are added. If 'withText' is true, Label and any known attributes are
+ * added. This allows 'categorical numerical' attributes e.g. codon position
+ * to be coloured by text.
* <p>
* Where metadata is available with a description for an attribute, that is
* added as a tooltip.
if (withText)
{
- displayAtts.add(MessageManager.getString("label.label"));
+ displayAtts.add(LABEL_18N);
tooltips.add(MessageManager.getString("label.description"));
}
if (withRange)
float[][] minMax = fr.getMinMax().get(featureType);
if (minMax != null && minMax[0][0] != minMax[0][1])
{
- displayAtts.add(MessageManager.getString("label.score"));
- tooltips.add(MessageManager.getString("label.score"));
+ displayAtts.add(SCORE_18N);
+ tooltips.add(SCORE_18N);
}
}
{
continue;
}
- displayAtts.add(toAttributeDisplayName(attName));
+ displayAtts.add(FeatureMatcher.toAttributeDisplayName(attName));
String desc = fa.getDescription(featureType, attName);
if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH)
{
filtersPanel.add(andOrPanel);
/*
- * panel with filters - populated by refreshFiltersDisplay
+ * panel with filters - populated by refreshFiltersDisplay,
+ * which also sets the layout manager
*/
chooseFiltersPanel = new JPanel();
- LayoutManager box = new BoxLayout(chooseFiltersPanel, BoxLayout.Y_AXIS);
- chooseFiltersPanel.setLayout(box);
+ chooseFiltersPanel.setBackground(Color.white);
filtersPanel.add(chooseFiltersPanel);
return filtersPanel;
{
JPanel andOrPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
andOrPanel.setBackground(Color.white);
- andOrPanel.setBorder(BorderFactory.createLineBorder(debugBorderColour));
andFilters = new JRadioButton(MessageManager.getString("label.and"));
orFilters = new JRadioButton(MessageManager.getString("label.or"));
ActionListener actionListener = new ActionListener()
/*
* if this feature type has filters set, load them first
*/
- KeyedMatcherSetI featureFilters = fr.getFeatureFilter(featureType);
+ FeatureMatcherSetI featureFilters = fr.getFeatureFilter(featureType);
if (featureFilters != null)
{
if (!featureFilters.isAnded())
/*
* and an empty filter for the user to populate (add)
*/
- KeyedMatcherI noFilter = new KeyedMatcher(Condition.values()[0], "",
- (String) null);
- filters.add(noFilter);
+ filters.add(FeatureMatcher.NULL_MATCHER);
+
+ /*
+ * use GridLayout to 'justify' rows to the top of the panel, until
+ * there are too many to fit in, then fall back on BoxLayout
+ */
+ if (filters.size() <= 5)
+ {
+ chooseFiltersPanel.setLayout(new GridLayout(5, 1));
+ }
+ else
+ {
+ chooseFiltersPanel.setLayout(
+ new BoxLayout(chooseFiltersPanel, BoxLayout.Y_AXIS));
+ }
/*
* render the conditions in rows, each in its own JPanel
*/
int filterIndex = 0;
- for (KeyedMatcherI filter : filters)
+ for (FeatureMatcherI filter : filters)
{
- String[] attName = filter.getKey();
- Condition condition = filter.getMatcher().getCondition();
- String pattern = filter.getMatcher().getPattern();
- JPanel row = addFilter(attName, attNames, condition, pattern,
- filterIndex);
- row.setBorder(BorderFactory.createLineBorder(debugBorderColour));
+ JPanel row = addFilter(filter, attNames, filterIndex);
chooseFiltersPanel.add(row);
filterIndex++;
}
/**
* A helper method that constructs a row (panel) with one filter condition:
* <ul>
- * <li>a drop-down list of attribute names to choose from</li>
+ * <li>a drop-down list of Label, Score and 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).
+ * The filter values are set as defaults for the input fields. The 'remove'
+ * button is added unless the pattern is empty (incomplete filter condition).
+ * <p>
+ * Action handlers on these fields provide for
+ * <ul>
+ * <li>validate pattern field - should be numeric if condition is numeric</li>
+ * <li>save filters and refresh display on any (valid) change</li>
+ * <li>remove filter and refresh on 'Remove'</li>
+ * <li>update conditions list on change of Label/Score/Attribute</li>
+ * <li>refresh value field tooltip with min-max range on change of
+ * attribute</li>
+ * </ul>
*
- * @param attName
+ * @param filter
* @param attNames
- * @param cond
- * @param pattern
* @param filterIndex
* @return
*/
- protected JPanel addFilter(String[] attName, List<String[]> attNames,
- Condition cond, String pattern, int filterIndex)
+ protected JPanel addFilter(FeatureMatcherI filter,
+ List<String[]> attNames, int filterIndex)
{
+ String[] attName = filter.getAttribute();
+ Condition cond = filter.getMatcher().getCondition();
+ String pattern = filter.getMatcher().getPattern();
+
JPanel filterRow = new JPanel(new FlowLayout(FlowLayout.LEFT));
filterRow.setBackground(Color.white);
*/
final JComboBox<String> attCombo = populateAttributesDropdown(attNames,
true, true);
+ String filterBy = setSelectedAttribute(attCombo, filter);
+
JComboBox<Condition> condCombo = new JComboBox<>();
+
JTextField patternField = new JTextField(8);
+ patternField.setText(pattern);
/*
* action handlers that validate and (if valid) apply changes
@Override
public void actionPerformed(ActionEvent e)
{
- if (attCombo.getSelectedItem() != null)
+ if (validateFilter(patternField, condCombo))
{
- if (validateFilter(patternField, condCombo))
+ if (updateFilter(attCombo, condCombo, patternField, filterIndex))
{
- updateFilter(attCombo, condCombo, patternField, filterIndex);
filtersChanged();
}
}
}
};
- if (attName == null) // the 'add a condition' row
+ if (filter == FeatureMatcher.NULL_MATCHER) // the 'add a condition' row
{
attCombo.setSelectedIndex(0);
}
else
{
- attCombo.setSelectedItem(toAttributeDisplayName(attName));
+ attCombo.setSelectedItem(
+ FeatureMatcher.toAttributeDisplayName(attName));
}
- attCombo.addItemListener(itemListener);
+ attCombo.addItemListener(new ItemListener()
+ {
+ @Override
+ public void itemStateChanged(ItemEvent e)
+ {
+ /*
+ * on change of attribute, refresh the conditions list to
+ * ensure it is appropriate for the attribute datatype
+ */
+ populateConditions((String) attCombo.getSelectedItem(),
+ (Condition) condCombo.getSelectedItem(), condCombo,
+ patternField);
+ actionListener.actionPerformed(null);
+ }
+ });
filterRow.add(attCombo);
/*
* drop-down choice of test condition
*/
- populateConditions((String) attCombo.getSelectedItem(), cond,
- condCombo);
+ populateConditions(filterBy, cond, condCombo, patternField);
+ condCombo.setPreferredSize(new Dimension(150, 20));
condCombo.addItemListener(itemListener);
filterRow.add(condCombo);
/*
* pattern to match against
*/
- patternField.setText(pattern);
patternField.addActionListener(actionListener);
patternField.addFocusListener(new FocusAdapter()
{
filterRow.add(patternField);
/*
+ * disable pattern field for condition 'Present / NotPresent'
+ */
+ Condition selectedCondition = (Condition) condCombo.getSelectedItem();
+ patternField.setEnabled(selectedCondition.needsAPattern());
+
+ /*
+ * if a numeric condition is selected, show the value range
+ * as a tooltip on the value input field
+ */
+ setNumericHints(filterBy, selectedCondition, patternField);
+
+ /*
* add remove button if filter is populated (non-empty pattern)
*/
- if (pattern != null && pattern.trim().length() > 0)
+ if (!patternField.isEnabled()
+ || (pattern != null && pattern.trim().length() > 0))
{
// todo: gif for button drawing '-' or 'x'
JButton removeCondition = new BasicArrowButton(SwingConstants.WEST);
}
/**
- * Populates the drop-down list of comparison conditions for the given attribute
- * name. The conditions added depend on the datatype of the attribute values.
- * The supplied condition is set as the selected item in the list, provided it
- * is in the list.
+ * Sets the selected item in the Label/Score/Attribute drop-down to match the
+ * filter
+ *
+ * @param attCombo
+ * @param filter
+ */
+ private String setSelectedAttribute(JComboBox<String> attCombo,
+ FeatureMatcherI filter)
+ {
+ String item = null;
+ if (filter.isByScore())
+ {
+ item = SCORE_18N;
+ }
+ else if (filter.isByLabel())
+ {
+ item = LABEL_18N;
+ }
+ else
+ {
+ item = FeatureMatcher.toAttributeDisplayName(filter.getAttribute());
+ }
+ attCombo.setSelectedItem(item);
+ return item;
+ }
+
+ /**
+ * If a numeric comparison condition is selected, retrieves the min-max range
+ * for the value (score or attribute), and sets it as a tooltip on the value
+ * field. If the field is currently empty, then pre-populates it with
+ * <ul>
+ * <li>the minimum value, if condition is > or >=</li>
+ * <li>the maximum value, if condition is < or <=</li>
+ * </ul>
+ *
+ * @param attName
+ * @param selectedCondition
+ * @param patternField
+ */
+ private void setNumericHints(String attName, Condition selectedCondition,
+ JTextField patternField)
+ {
+ patternField.setToolTipText("");
+
+ if (selectedCondition.isNumeric())
+ {
+ float[] minMax = getMinMax(attName);
+ if (minMax != null)
+ {
+ String minFormatted = DECFMT_2_2.format(minMax[0]);
+ String maxFormatted = DECFMT_2_2.format(minMax[1]);
+ String tip = String.format("(%s - %s)", minFormatted, maxFormatted);
+ patternField.setToolTipText(tip);
+ if (patternField.getText().isEmpty())
+ {
+ if (selectedCondition == Condition.GE
+ || selectedCondition == Condition.GT)
+ {
+ patternField.setText(minFormatted);
+ }
+ else
+ {
+ if (selectedCondition == Condition.LE
+ || selectedCondition == Condition.LT)
+ {
+ patternField.setText(maxFormatted);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Populates the drop-down list of comparison conditions for the given
+ * attribute name. The conditions added depend on the datatype of the
+ * attribute values. The supplied condition is set as the selected item in the
+ * list, provided it is in the list. If the pattern is now invalid
+ * (non-numeric pattern for a numeric condition), it is cleared.
*
* @param attName
* @param cond
* @param condCombo
+ * @param patternField
*/
private void populateConditions(String attName, Condition cond,
- JComboBox<Condition> condCombo)
+ JComboBox<Condition> condCombo, JTextField patternField)
{
Datatype type = FeatureAttributes.getInstance().getDatatype(featureType,
- attName);
- if (MessageManager.getString("label.label").equals(attName))
+ FeatureMatcher.fromAttributeDisplayName(attName));
+ if (LABEL_18N.equals(attName))
{
type = Datatype.Character;
}
- else if (MessageManager.getString("label.score").equals(attName))
+ else if (SCORE_18N.equals(attName))
{
type = Datatype.Number;
}
+ /*
+ * remove itemListener before starting
+ */
+ ItemListener listener = condCombo.getItemListeners()[0];
+ condCombo.removeItemListener(listener);
+ boolean condIsValid = false;
+
+ condCombo.removeAllItems();
for (Condition c : Condition.values())
{
- if ((c.isNumeric() && type != Datatype.Character)
+ if ((c.isNumeric() && type == Datatype.Number)
|| (!c.isNumeric() && type != Datatype.Number))
{
condCombo.addItem(c);
+ if (c == cond)
+ {
+ condIsValid = true;
+ }
}
}
/*
* set the selected condition (does nothing if not in the list)
*/
- if (cond != null)
+ if (condIsValid)
{
condCombo.setSelectedItem(cond);
}
+ else
+ {
+ condCombo.setSelectedIndex(0);
+ }
+
+ /*
+ * clear pattern if it is now invalid for condition
+ */
+ if (((Condition) condCombo.getSelectedItem()).isNumeric())
+ {
+ try
+ {
+ String pattern = patternField.getText().trim();
+ if (pattern.length() > 0)
+ {
+ Float.valueOf(pattern);
+ }
+ } catch (NumberFormatException e)
+ {
+ patternField.setText("");
+ }
+ }
+
+ /*
+ * restore the listener
+ */
+ condCombo.addItemListener(listener);
}
/**
- * 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.
+ * 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.
+ * If the pattern is expected but 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
}
Condition cond = (Condition) condCombo.getSelectedItem();
+ if (!cond.needsAPattern())
+ {
+ return true;
+ }
+
value.setBackground(Color.white);
value.setToolTipText("");
String v1 = value.getText().trim();
if (v1.length() == 0)
{
- return false;
+ // return false;
}
- if (cond.isNumeric())
+ if (cond.isNumeric() && v1.length() > 0)
{
try
{
/**
* Constructs a filter condition from the given input fields, and replaces the
- * condition at filterIndex with the new one
+ * condition at filterIndex with the new one. Does nothing if the pattern
+ * field is blank (unless the match condition is one that doesn't require a
+ * pattern, e.g. 'Is present'). Answers true if the filter was updated, else
+ * false.
+ * <p>
+ * This method may update the tooltip on the filter value field to show the
+ * value range, if a numeric condition is selected. This ensures the tooltip
+ * is updated when a numeric valued attribute is chosen on the last 'add a
+ * filter' row.
*
* @param attCombo
* @param condCombo
* @param valueField
* @param filterIndex
*/
- protected void updateFilter(JComboBox<String> attCombo,
+ protected boolean 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(cond, pattern,
- fromAttributeDisplayName(attName));
+ String pattern = valueField.getText().trim();
+
+ setNumericHints(attName, cond, valueField);
+
+ if (pattern.length() == 0 && cond.needsAPattern())
+ {
+ valueField.setEnabled(true); // ensure pattern field is enabled!
+ return false;
+ }
+
+ /*
+ * Construct a matcher that operates on Label, Score,
+ * or named attribute
+ */
+ FeatureMatcherI km = null;
+ if (LABEL_18N.equals(attName))
+ {
+ km = FeatureMatcher.byLabel(cond, pattern);
+ }
+ else if (SCORE_18N.equals(attName))
+ {
+ km = FeatureMatcher.byScore(cond, pattern);
+ }
+ else
+ {
+ km = FeatureMatcher.byAttribute(cond, pattern,
+ FeatureMatcher.fromAttributeDisplayName(attName));
+ }
filters.set(filterIndex, km);
- }
- /**
- * Makes the dialog visible, at the Feature Colour tab or at the Filters tab
- *
- * @param coloursTab
- */
- public void showTab(boolean coloursTab)
- {
- setVisible(true);
- tabbedPane.setSelectedIndex(coloursTab ? 0 : 1);
+ return true;
}
/**
* <li>change of match pattern</li>
* <li>removal of a condition</li>
* </ul>
- * The inputs are parsed into a combined filter and this is set for the feature
- * type, and the alignment redrawn.
+ * The inputs are parsed into a combined filter and this is set for the
+ * feature type, and the alignment redrawn.
*/
protected void filtersChanged()
{
* update the filter conditions for the feature type
*/
boolean anded = andFilters.isSelected();
- KeyedMatcherSetI combined = new KeyedMatcherSet();
+ FeatureMatcherSetI combined = new FeatureMatcherSet();
- for (KeyedMatcherI filter : filters)
+ for (FeatureMatcherI filter : filters)
{
String pattern = filter.getMatcher().getPattern();
- if (pattern.trim().length() > 0)
+ Condition condition = filter.getMatcher().getCondition();
+ if (pattern.trim().length() > 0 || !condition.needsAPattern())
{
if (anded)
{