import jalview.datamodel.features.FeatureMatcherSet;
import jalview.datamodel.features.FeatureMatcherSetI;
import jalview.io.gff.SequenceOntologyFactory;
+import jalview.io.gff.SequenceOntologyI;
import jalview.schemes.FeatureColour;
import jalview.util.ColorUtils;
import jalview.util.MessageManager;
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;
/*
* 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 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();
*/
private List<FeatureMatcherI> filters;
- // set white normally, black to debug layout
- private Color debugBorderColour = Color.white;
-
private JPanel chooseFiltersPanel;
- private JTabbedPane tabbedPane;
-
/*
- * feature types present in Feature Renderer which are
- * sub-types of the one this editor is acting on
+ * the root Sequence Ontology terms (if any) that is a parent of
+ * the current feature type
*/
- private final List<String> subTypes;
+ private String rootSOTerm;
/*
- * if selected, filter settings are also applied to any
- * feature sub-types in the Sequence Ontology
+ * feature types present in Feature Renderer which have the same Sequence
+ * Ontology root parent as the one this editor is acting on
*/
- private JCheckBox applyFiltersToSubtypes;
+ private final List<String> peerSoTerms;
/*
- * if selected, colour settings are also applied to any
- * feature sub-types in the Sequence Ontology
+ * if true, filter or colour settings are also applied to
+ * any sub-types of rootSOTerm in the Sequence Ontology
*/
- private JCheckBox applyColourToChildren;
+ private boolean applyFiltersToSubtypes;
+
+ private boolean applyColourToSubtypes;
/**
* 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;
- /*
- * determine sub-types (if any) of this feature type
- */
- List<String> types = fr.getRenderOrder();
- subTypes = Collections.unmodifiableList(SequenceOntologyFactory
- .getInstance().getChildTerms(this.featureType, types));
+ peerSoTerms = findSequenceOntologyPeers(this.featureType);
/*
* save original colours and filters for this feature type
originalFilters.put(theType, fr.getFeatureFilter(theType));
originalColours = new HashMap<>();
originalColours.put(theType, fr.getFeatureColours().get(theType));
- for (String child : subTypes)
+ for (String child : peerSoTerms)
{
originalFilters.put(child, fr.getFeatureFilter(child));
originalColours.put(child, fr.getFeatureColours().get(child));
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();
}
/**
+ * Answers a (possibly empty) list of feature types known to the Feature
+ * Renderer which share a top level Sequence Ontology parent with the current
+ * feature type. The current type is not included.
+ *
+ * @return
+ */
+ protected List<String> findSequenceOntologyPeers(String featureType)
+ {
+ List<String> peers = new ArrayList<>();
+
+ /*
+ * first find the SO term (if any) that is the root
+ * parent of the current type
+ */
+ SequenceOntologyI so = SequenceOntologyFactory.getInstance();
+ List<String> roots = so.getRootParents(featureType);
+ if (roots == null || roots.size() > 1)
+ {
+ /*
+ * feature type is not an SO term, or has ambiguous root
+ */
+ return peers;
+ }
+ rootSOTerm = roots.get(0);
+
+ List<String> types = fr.getRenderOrder();
+ for (String type : types)
+ {
+ if (!type.equals(featureType) && so.isA(type, rootSOTerm))
+ {
+ peers.add(type);
+ }
+
+ }
+ Collections.sort(peers); // sort for ease of reading in tooltip
+ return peers;
+ }
+
+ /**
* Configures the widgets on the Colours tab according to the current feature
* colour scheme
*/
*/
if (fc.isSimpleColour())
{
- simpleColour.setSelected(true);
singleColour.setBackground(fc.getColour());
singleColour.setForeground(fc.getColour());
+ simpleColour.setSelected(true);
}
/*
: 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();
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));
+ FeatureColourI originalColour = originalColours.get(featureType);
+ 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);
+
+ /*
+ * option to apply colour to peer types as well (if there are any)
+ */
+ if (!peerSoTerms.isEmpty())
+ {
+ applyColourToSubtypes = false;
+ colourByPanel.add(initSubtypesPanel(false));
+ }
/*
* 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));
+ FeatureColourI originalColour = originalColours.get(featureType);
+ singleColour.setBackground(originalColour.getColour());
+ singleColour.setForeground(originalColour.getColour());
+
singleColour.addMouseListener(new MouseAdapter()
{
@Override
return colourByPanel;
}
+ /**
+ * Constructs and returns a panel with a checkbox for the option to apply any
+ * changes also to sub-types of the feature type
+ *
+ * @return
+ */
+ protected JPanel initSubtypesPanel(final boolean forFilters)
+ {
+ JPanel toSubtypes = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ toSubtypes.setBackground(Color.WHITE);
+ JCheckBox applyToSubtypesCB = new JCheckBox(MessageManager
+ .formatMessage("label.apply_to_subtypes", rootSOTerm));
+ applyToSubtypesCB.setToolTipText(getSubtypesTooltip());
+ applyToSubtypesCB.addActionListener(new ActionListener()
+ {
+ /*
+ * reset and reapply settings on toggle of checkbox
+ */
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ if (forFilters)
+ {
+ applyFiltersToSubtypes = applyToSubtypesCB.isSelected();
+ restoreOriginalFilters();
+ filtersChanged();
+ }
+ else
+ {
+ applyColourToSubtypes = applyToSubtypesCB.isSelected();
+ restoreOriginalColours();
+ colourChanged(true);
+ }
+ }
+ });
+ toSubtypes.add(applyToSubtypesCB);
+
+ return toSubtypes;
+ }
+
private void showColourChooser(JPanel colourPanel, String key)
{
Color col = JColorChooser.showDialog(this,
}
/**
- * 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
*/
FeatureColourI acg = makeColourFromInputs();
/*
- * save the colour, and repaint stuff
+ * save the colour, and set on subtypes if selected
*/
fr.setColour(featureType, acg);
+ if (applyColourToSubtypes)
+ {
+ for (String child : peerSoTerms)
+ {
+ fr.setColour(child, acg);
+ }
+ }
refreshFeatureSettings();
ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview);
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 (!LABEL_18N.equals(byWhat))
- {
- fc.setAttributeName(
- FeatureMatcher.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();
@Override
public void cancelPressed()
{
- for (Entry<String, FeatureColourI> entry : originalColours.entrySet())
- {
- fr.setColour(entry.getKey(), entry.getValue());
- }
+ restoreOriginalColours();
+ restoreOriginalFilters();
+ ap.paintAlignment(true, true);
+ }
+
+ protected void restoreOriginalFilters()
+ {
for (Entry<String, FeatureMatcherSetI> entry : originalFilters
.entrySet())
{
fr.setFeatureFilter(entry.getKey(), entry.getValue());
}
+ }
- ap.paintAlignment(true, true);
+ protected void restoreOriginalColours()
+ {
+ for (Entry<String, FeatureColourI> entry : originalColours.entrySet())
+ {
+ fr.setColour(entry.getKey(), entry.getValue());
+ }
}
/**
{
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.
{
filters = new ArrayList<>();
+ JPanel outerPanel = new JPanel();
+ outerPanel.setLayout(new BoxLayout(outerPanel, BoxLayout.Y_AXIS));
+ outerPanel.setBackground(Color.white);
+
+ /*
+ * option to apply colour to peer types as well (if there are any)
+ */
+ if (!peerSoTerms.isEmpty())
+ {
+ applyFiltersToSubtypes = false;
+ outerPanel.add(initSubtypesPanel(true));
+ }
+
JPanel filtersPanel = new JPanel();
filtersPanel.setLayout(new BoxLayout(filtersPanel, BoxLayout.Y_AXIS));
filtersPanel.setBackground(Color.white);
JvSwingUtils.createTitledBorder(filtersPanel,
MessageManager.getString("label.filters"), true);
+ outerPanel.add(filtersPanel);
JPanel andOrPanel = initialiseAndOrPanel();
filtersPanel.add(andOrPanel);
chooseFiltersPanel.setBackground(Color.white);
filtersPanel.add(chooseFiltersPanel);
- return filtersPanel;
+ return outerPanel;
}
/**
{
JPanel andOrPanel = new JPanel(new BorderLayout());
andOrPanel.setBackground(Color.white);
- JPanel panel1 = new JPanel(new FlowLayout(FlowLayout.LEFT));
- andOrPanel.add(panel1, BorderLayout.WEST);
- panel1.setBackground(Color.white);
- panel1.setBorder(BorderFactory.createLineBorder(debugBorderColour));
+
andFilters = new JRadioButton(MessageManager.getString("label.and"));
orFilters = new JRadioButton(MessageManager.getString("label.or"));
ActionListener actionListener = new ActionListener()
andOr.add(andFilters);
andOr.add(orFilters);
andFilters.setSelected(true);
- panel1.add(
+ andOrPanel.add(
new JLabel(MessageManager.getString("label.join_conditions")));
- panel1.add(andFilters);
- panel1.add(orFilters);
-
- /*
- * add an 'Apply to child terms' checkbox if there are any child terms
- */
- applyFiltersToSubtypes = new JCheckBox("Apply also to child terms");
- if (!subTypes.isEmpty())
- {
- JPanel panel2 = new JPanel(new FlowLayout(FlowLayout.RIGHT));
- panel2.setBackground(Color.white);
- andOrPanel.add(panel2, BorderLayout.EAST);
- StringBuilder sb = new StringBuilder(20 * subTypes.size());
- sb.append("Apply filters also to child terms" + ":"); // todo i18n
- Collections.sort(subTypes);
- for (String child : subTypes)
- {
- sb.append("<br>").append(child);
- }
- String tooltip = JvSwingUtils.wrapTooltip(true, sb.toString());
- applyFiltersToSubtypes.setToolTipText(tooltip);
- panel2.add(applyFiltersToSubtypes);
- }
+ andOrPanel.add(andFilters);
+ andOrPanel.add(orFilters);
return andOrPanel;
}
/**
+ * Builds a tooltip for the 'Apply to subtypes' checkbox with a list of
+ * subtypes of this feature type
+ *
+ * @return
+ */
+ protected String getSubtypesTooltip()
+ {
+ StringBuilder sb = new StringBuilder(20 * peerSoTerms.size());
+ sb.append(MessageManager.getString("label.apply_also_to"));
+ for (String child : peerSoTerms)
+ {
+ sb.append("<br>").append(child);
+ }
+ String tooltip = JvSwingUtils.wrapTooltip(true, sb.toString());
+ return tooltip;
+ }
+
+ /**
* Refreshes the display to show any filters currently configured for the
* selected feature type (editable, with 'remove' option), plus one extra row
* for adding a condition. This should be called after a filter has been
for (FeatureMatcherI filter : filters)
{
JPanel row = addFilter(filter, attNames, filterIndex);
- row.setBorder(BorderFactory.createLineBorder(debugBorderColour));
chooseFiltersPanel.add(row);
filterIndex++;
}
/**
* A helper method that constructs a row (panel) with one filter condition:
* <ul>
- * <li>a drop-down list of Label, Score and 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>
* @param selectedCondition
* @param patternField
*/
- private void setNumericHints(String attName,
- Condition selectedCondition, JTextField patternField)
+ private void setNumericHints(String attName, Condition selectedCondition,
+ JTextField patternField)
{
patternField.setToolTipText("");
}
/**
- * 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.
+ * 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
}
/**
- * 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 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.
+ * 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
/**
* Constructs a filter condition from the given input fields, and replaces the
- * 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.
+ * 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.
+ * 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
}
/**
- * 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);
- }
-
- /**
* Action on any change to feature filtering, namely
* <ul>
* <li>change of selected attribute</li>
* <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()
{
* (note this might now be an empty filter with no conditions)
*/
fr.setFeatureFilter(featureType, combined.isEmpty() ? null : combined);
- if (applyFiltersToSubtypes.isSelected())
+ if (applyFiltersToSubtypes)
{
- for (String child : subTypes)
+ for (String child : peerSoTerms)
{
fr.setFeatureFilter(child, combined.isEmpty() ? null : combined);
}