X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fgui%2FFeatureSettings.java;h=3be597cf13fd4be1c0ea0c71c3a38c3ff88a72ed;hb=14307f5cfcbc90c419c892434613a500ca550ecc;hp=0963b310575470461cb778dcc12cc43978f3cb03;hpb=f4766a7bbcfae845fc95923b01fa14ff83d589ff;p=jalview.git diff --git a/src/jalview/gui/FeatureSettings.java b/src/jalview/gui/FeatureSettings.java index 0963b31..3be597c 100644 --- a/src/jalview/gui/FeatureSettings.java +++ b/src/jalview/gui/FeatureSettings.java @@ -25,6 +25,7 @@ import jalview.api.FeatureSettingsControllerI; 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; @@ -34,19 +35,29 @@ import jalview.util.Format; 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; @@ -60,6 +71,7 @@ import java.io.FileOutputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Hashtable; @@ -72,11 +84,14 @@ import java.util.Vector; import javax.help.HelpSetException; import javax.swing.AbstractCellEditor; import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +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; @@ -84,15 +99,19 @@ import javax.swing.JLayeredPane; 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; @@ -100,11 +119,15 @@ import javax.swing.table.TableCellRenderer; public class FeatureSettings extends JPanel implements FeatureSettingsControllerI { - DasSourceBrowser dassourceBrowser; + private static final int MIN_WIDTH = 400; + + private static final int MIN_HEIGHT = 400; + + private static final int MAX_TOOLTIP_LENGTH = 50; - jalview.ws.DasSequenceFeatureFetcher dasFeatureFetcher; + DasSourceBrowser dassourceBrowser; - JPanel settingsPane = new JPanel(); + DasSequenceFeatureFetcher dasFeatureFetcher; JPanel dasSettingsPane = new JPanel(); @@ -112,10 +135,15 @@ public class FeatureSettings extends JPanel public final AlignFrame af; + /* + * 'original' fields hold settings to restore on Cancel + */ Object[][] originalData; private float originalTransparency; + private Map originalFilters; + final JInternalFrame frame; JScrollPane scrollPane = new JScrollPane(); @@ -125,25 +153,68 @@ public class FeatureSettings extends JPanel JPanel groupPanel; JSlider transparency = new JSlider(); + + /* + * when true, constructor is still executing - so ignore UI events + */ + protected volatile boolean inConstruction = true; - JPanel transPanel = new JPanel(new GridLayout(1, 2)); + int selectedRow = -1; - private static final int MIN_WIDTH = 400; + JButton fetchDAS = new JButton(); - private static final int MIN_HEIGHT = 400; + 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 typeWidth = null; + + /* + * fields of the feature filters tab + */ + private JPanel filtersPane; + + private JPanel chooseFiltersPanel; + + private JComboBox filteredFeatureChoice; + + private JRadioButton andFilters; + + private JRadioButton orFilters; + + /* + * filters for the currently selected feature type + */ + private List 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 { @@ -281,13 +352,13 @@ public class FeatureSettings extends JPanel { 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)); @@ -303,6 +374,7 @@ public class FeatureSettings extends JPanel }; }); frame.setLayer(JLayeredPane.PALETTE_LAYER); + inConstruction = false; } protected void popupSort(final int selectedRow, final String type, @@ -345,84 +417,63 @@ public class FeatureSettings extends JPanel }); men.add(dens); - if (minmax != null) + + /* + * variable colour options include colour by label, by score, + * by selected attribute text, or attribute value + */ + final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem( + MessageManager.getString("label.variable_colour")); + mxcol.setSelected(!featureColour.isSimpleColour()); + men.add(mxcol); + mxcol.addActionListener(new ActionListener() { - final float[][] typeMinMax = minmax.get(type); - /* - * final JCheckBoxMenuItem chb = new JCheckBoxMenuItem("Vary Height"); // - * this is broken at the moment and isn't that useful anyway! - * chb.setSelected(minmax.get(type) != null); chb.addActionListener(new - * ActionListener() { - * - * public void actionPerformed(ActionEvent e) { - * chb.setState(chb.getState()); if (chb.getState()) { minmax.put(type, - * null); } else { minmax.put(type, typeMinMax); } } - * - * }); - * - * men.add(chb); - */ - if (typeMinMax != null && typeMinMax[0] != null) - { - // if (table.getValueAt(row, column)); - // graduated colourschemes for those where minmax exists for the - // positional features - final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem( - "Graduated Colour"); - mxcol.setSelected(!featureColour.isSimpleColour()); - men.add(mxcol); - mxcol.addActionListener(new ActionListener() - { - JColorChooser colorChooser; + JColorChooser colorChooser; - @Override - public void actionPerformed(ActionEvent e) + @Override + public void actionPerformed(ActionEvent e) + { + if (e.getSource() == mxcol) + { + if (featureColour.isSimpleColour()) { - if (e.getSource() == mxcol) - { - if (featureColour.isSimpleColour()) - { - FeatureColourChooser fc = new FeatureColourChooser(me.fr, - type); - fc.addActionListener(this); - } - else - { - // bring up simple color chooser - colorChooser = new JColorChooser(); - JDialog dialog = JColorChooser.createDialog(me, - "Select new Colour", true, // modal - colorChooser, this, // OK button handler - null); // no CANCEL button handler - colorChooser.setColor(featureColour.getMaxColour()); - dialog.setVisible(true); - } - } - else - { - if (e.getSource() instanceof FeatureColourChooser) - { - FeatureColourChooser fc = (FeatureColourChooser) e - .getSource(); - table.setValueAt(fc.getLastColour(), selectedRow, 1); - table.validate(); - } - else - { - // probably the color chooser! - table.setValueAt(new FeatureColour(colorChooser.getColor()), - selectedRow, 1); - table.validate(); - me.updateFeatureRenderer( - ((FeatureTableModel) table.getModel()).getData(), - false); - } - } + FeatureColourChooser fc = new FeatureColourChooser(me.fr, type); + fc.addActionListener(this); } - - }); + else + { + // bring up simple color chooser + colorChooser = new JColorChooser(); + JDialog dialog = JColorChooser.createDialog(me, + "Select new Colour", true, // modal + colorChooser, this, // OK button handler + null); // no CANCEL button handler + colorChooser.setColor(featureColour.getMaxColour()); + dialog.setVisible(true); + } + } + else + { + if (e.getSource() instanceof FeatureColourChooser) + { + FeatureColourChooser fc = (FeatureColourChooser) e.getSource(); + table.setValueAt(fc.getLastColour(), selectedRow, 1); + table.validate(); + } + else + { + // probably the color chooser! + table.setValueAt(new FeatureColour(colorChooser.getColor()), + selectedRow, 1); + table.validate(); + me.updateFeatureRenderer( + ((FeatureTableModel) table.getModel()).getData(), false); + } + } } - } + + }); + JMenuItem selCols = new JMenuItem( MessageManager.getString("label.select_columns_containing")); selCols.addActionListener(new ActionListener() @@ -472,20 +523,10 @@ public class FeatureSettings extends JPanel 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 typeWidth = null; - @Override synchronized public void discoverAllFeatureData() { - Set allGroups = new HashSet(); + Set allGroups = new HashSet<>(); AlignmentI alignment = af.getViewport().getAlignment(); for (int i = 0; i < alignment.getHeight(); i++) @@ -501,6 +542,8 @@ public class FeatureSettings extends JPanel } } + populateFilterableFeatures(); + resetTable(null); validate(); @@ -535,21 +578,14 @@ public class FeatureSettings extends JPanel public void itemStateChanged(ItemEvent evt) { fr.setGroupVisibility(check.getText(), check.isSelected()); - af.alignPanel.getSeqPanel().seqCanvas.repaint(); - if (af.alignPanel.overviewPanel != null) - { - af.alignPanel.overviewPanel.updateOverviewImage(); - } - resetTable(new String[] { grp }); + af.alignPanel.paintAlignment(true, true); } }); groupPanel.add(check); return visible; } - boolean resettingTable = false; - synchronized void resetTable(String[] groupChanged) { if (resettingTable) @@ -557,12 +593,12 @@ public class FeatureSettings extends JPanel return; } resettingTable = true; - typeWidth = new Hashtable(); + typeWidth = new Hashtable<>(); // TODO: change avWidth calculation to 'per-sequence' average and use long // rather than float - Set displayableTypes = new HashSet(); - Set foundGroups = new HashSet(); + Set displayableTypes = new HashSet<>(); + Set foundGroups = new HashSet<>(); /* * determine which feature types may be visible depending on @@ -578,7 +614,7 @@ public class FeatureSettings extends JPanel * and keep track of which groups are visible */ Set groups = seq.getFeatures().getFeatureGroups(true); - Set visibleGroups = new HashSet(); + Set visibleGroups = new HashSet<>(); for (String group : groups) { if (group == null || checkGroupState(group)) @@ -1058,66 +1094,30 @@ public class FeatureSettings extends JPanel { if (fr.setFeaturePriority(data, visibleNew)) { - af.alignPanel.paintAlignment(true); + af.alignPanel.paintAlignment(true, true); } } - 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 @@ -1126,8 +1126,10 @@ public class FeatureSettings extends JPanel 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 @@ -1136,9 +1138,10 @@ public class FeatureSettings extends JPanel 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 @@ -1147,9 +1150,9 @@ public class FeatureSettings extends JPanel 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 @@ -1158,8 +1161,9 @@ public class FeatureSettings extends JPanel 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 @@ -1190,20 +1194,23 @@ public class FeatureSettings extends JPanel } } }); + + 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 @@ -1212,8 +1219,10 @@ public class FeatureSettings extends JPanel 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 @@ -1222,8 +1231,10 @@ public class FeatureSettings extends JPanel 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 @@ -1237,8 +1248,11 @@ public class FeatureSettings extends JPanel @Override public void stateChanged(ChangeEvent evt) { - fr.setTransparency((100 - transparency.getValue()) / 100f); - af.alignPanel.paintAlignment(true); + if (!inConstruction) + { + fr.setTransparency((100 - transparency.getValue()) / 100f); + af.alignPanel.paintAlignment(true,true); + } } }); @@ -1263,6 +1277,8 @@ public class FeatureSettings extends JPanel saveDAS_actionPerformed(e); } }); + + JPanel dasButtonPanel = new JPanel(); dasButtonPanel.setBorder(BorderFactory.createEtchedBorder()); dasSettingsPane.setBorder(null); cancelDAS.setEnabled(false); @@ -1275,32 +1291,475 @@ public class FeatureSettings extends JPanel 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(); + filtersPanel.setLayout(new BoxLayout(filtersPanel, BoxLayout.Y_AXIS)); + 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(); + chooseFiltersPanel.setLayout(new BoxLayout(chooseFiltersPanel, + BoxLayout.Y_AXIS)); + 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 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 // todo i18n + .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(); + filters.clear(); + + /* + * look up attributes known for feature type + */ + String selectedType = (String) filteredFeatureChoice.getSelectedItem(); + List 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); + } + featureFilters.getMatchers().forEach(matcher -> filters.add(matcher)); + } + + /* + * 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 filterIndex = 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, filterIndex); + chooseFiltersPanel.add(row); + filterIndex++; + } + + filtersPane.validate(); + filtersPane.repaint(); + } + + /** + * A helper method that constructs a panel with one filter condition: + *
    + *
  • a drop-down list of attribute names to choose from
  • + *
  • a drop-down list of conditions to choose from
  • + *
  • a text field for input of a match pattern
  • + *
  • optionally, a 'remove' button
  • + *
+ * 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 filterIndex + * @return + */ + protected JPanel addFilter(String attribute, List attNames, + Condition cond, String pattern, int filterIndex) + { + JPanel filterRow = new JPanel(new FlowLayout(FlowLayout.LEFT)); + filterRow.setBackground(Color.white); + + /* + * inputs for attribute, condition, pattern + */ + /* + * drop-down choice of attribute, with description as a tooltip + * if we can obtain it + */ + String featureType = (String) filteredFeatureChoice.getSelectedItem(); + final JComboBox attCombo = populateAttributesDropdown( + featureType, attNames); + JComboBox 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, filterIndex); + filtersChanged(); + } + } + } + }; + ItemListener itemListener = new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + actionListener.actionPerformed(null); + } + }; + + 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(filterIndex); + filtersChanged(); + } + }); + filterRow.add(removeCondition); + } + + return filterRow; + } + + /** + * A helper method to build the drop-down choice of attributes for a feature. + * Where metadata is available with a description for an attribute, that is + * added as a tooltip. + * + * @param featureType + * @param attNames + */ + protected JComboBox populateAttributesDropdown( + String featureType, List attNames) + { + List tooltips = new ArrayList<>(); + FeatureAttributes fa = FeatureAttributes.getInstance(); + for (String attName : attNames) + { + String desc = fa.getDescription(featureType, attName); + if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH) + { + desc = desc.substring(0, MAX_TOOLTIP_LENGTH) + "..."; + } + tooltips.add(desc == null ? "" : desc); + } + + JComboBox attCombo = JvSwingUtils.buildComboWithTooltips( + attNames, tooltips); + if (attNames.isEmpty()) + { + attCombo.setToolTipText(MessageManager + .getString("label.no_attributes")); + } + return attCombo; + } + + /** + * Action on any change to feature filtering, namely + *
    + *
  • change of selected attribute
  • + *
  • change of selected condition
  • + *
  • change of match pattern
  • + *
  • removal of a condition
  • + *
+ * The action should be to + *
    + *
  • parse and validate the filters
  • + *
  • if valid, update the filter text box
  • + *
  • and apply the filters to the viewport
  • + *
+ */ + 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 attCombo, + JComboBox 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) @@ -1460,6 +1919,51 @@ public class FeatureSettings extends JPanel 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. + *

+ * 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 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 // /////////////////////////////////////////////////////////////////////// @@ -1558,12 +2062,7 @@ public class FeatureSettings extends JPanel boolean isSelected, boolean hasFocus, int row, int column) { FeatureColourI cellColour = (FeatureColourI) color; - // JLabel comp = new JLabel(); - // comp. setOpaque(true); - // comp. - // setBounds(getBounds()); - Color newColor; setToolTipText(baseTT); setBackground(tbl.getBackground()); if (!cellColour.isSimpleColour()) @@ -1571,14 +2070,12 @@ public class FeatureSettings extends JPanel Rectangle cr = tbl.getCellRect(row, column, false); FeatureSettings.renderGraduatedColor(this, cellColour, (int) cr.getWidth(), (int) cr.getHeight()); - } else { this.setText(""); this.setIcon(null); - newColor = cellColour.getColour(); - setBackground(newColor); + setBackground(cellColour.getColour()); } if (isSelected) { @@ -1629,28 +2126,43 @@ public class FeatureSettings extends JPanel int w, int h) { boolean thr = false; - String tt = ""; - String tx = ""; + StringBuilder tt = new StringBuilder(); + StringBuilder tx = new StringBuilder(); + + if (gcol.isColourByAttribute()) + { + tx.append(gcol.getAttributeName()); + } + else if (!gcol.isColourByLabel()) + { + tx.append(MessageManager.getString("label.score")); + } + tx.append(" "); if (gcol.isAboveThreshold()) { thr = true; - tx += ">"; - tt += "Thresholded (Above " + gcol.getThreshold() + ") "; + tx.append(">"); + tt.append("Thresholded (Above ").append(gcol.getThreshold()) + .append(") "); } if (gcol.isBelowThreshold()) { thr = true; - tx += "<"; - tt += "Thresholded (Below " + gcol.getThreshold() + ") "; + tx.append("<"); + tt.append("Thresholded (Below ").append(gcol.getThreshold()) + .append(") "); } if (gcol.isColourByLabel()) { - tt = "Coloured by label text. " + tt; + tt.append("Coloured by label text. ").append(tt); if (thr) { - tx += " "; + tx.append(" "); + } + if (!gcol.isColourByAttribute()) + { + tx.append("Label"); } - tx += "Label"; comp.setIcon(null); } else @@ -1666,16 +2178,17 @@ public class FeatureSettings extends JPanel // + ", " + minCol.getBlue() + ")"); } comp.setHorizontalAlignment(SwingConstants.CENTER); - comp.setText(tx); + comp.setText(tx.toString()); if (tt.length() > 0) { if (comp.getToolTipText() == null) { - comp.setToolTipText(tt); + comp.setToolTipText(tt.toString()); } else { - comp.setToolTipText(tt + " " + comp.getToolTipText()); + comp.setToolTipText(tt.append(" ").append(comp.getToolTipText()) + .toString()); } } } @@ -1797,7 +2310,8 @@ class ColorEditor extends AbstractCellEditor button.setBorderPainted(false); // Set up the dialog that the button brings up. colorChooser = new JColorChooser(); - dialog = JColorChooser.createDialog(button, "Select new Colour", true, // modal + dialog = JColorChooser.createDialog(button, + MessageManager.getString("label.select_new_colour"), true, // modal colorChooser, this, // OK button handler null); // no CANCEL button handler }