From e16e9699b56f3db4e42dc694aaafad76dae66c4b Mon Sep 17 00:00:00 2001 From: gmungoc Date: Wed, 22 Nov 2017 15:24:52 +0000 Subject: [PATCH] JAL-2808 JAL-2069 FeatureTypeSettings (with new Filters tab) replaces FeatureColourChooser (not applet) --- resources/lang/Messages.properties | 31 +- resources/lang/Messages_es.properties | 46 +- src/jalview/appletgui/FeatureColourChooser.java | 8 +- src/jalview/appletgui/FeatureSettings.java | 19 +- src/jalview/gui/FeatureColourChooser.java | 1016 ------------- src/jalview/gui/FeatureRenderer.java | 44 +- src/jalview/gui/FeatureSettings.java | 1144 ++++++--------- src/jalview/gui/FeatureTypeSettings.java | 1548 ++++++++++++++++++++ .../seqfeatures/FeatureRendererModel.java | 26 +- 9 files changed, 2081 insertions(+), 1801 deletions(-) delete mode 100644 src/jalview/gui/FeatureColourChooser.java create mode 100644 src/jalview/gui/FeatureTypeSettings.java diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index 9f1c71b..a592282 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -242,7 +242,6 @@ label.documentation = Documentation label.about = About... label.show_sequence_limits = Show Sequence Limits action.feature_settings = Feature Settings... -label.feature_settings = Feature Settings label.all_columns = All Columns label.all_sequences = All Sequences label.selected_columns = Selected Columns @@ -282,9 +281,9 @@ label.selection = Selection label.group_colour = Group Colour label.sequence = Sequence label.view_pdb_structure = View PDB Structure -label.min_value = Min value: -label.max_value = Max value: -label.no_value = No value: +label.min_value = Min value +label.max_value = Max value +label.no_value = No value label.new_feature = New Feature label.match_case = Match Case label.view_alignment_editor = View in alignment editor @@ -533,7 +532,6 @@ label.threshold_feature_above_threshold = Above Threshold label.threshold_feature_below_threshold = Below Threshold label.adjust_threshold = Adjust threshold label.toggle_absolute_relative_display_threshold = Toggle between absolute and relative display threshold. -label.colour_by_label_tip = Display features of the same type with a different label using a different colour. (e.g. domain features) label.select_colour_minimum_value = Select Colour for Minimum Value label.select_colour_maximum_value = Select Colour for Maximum Value label.open_url_param = Open URL {0} @@ -871,7 +869,7 @@ label.msa_service_is_unknown = The Multiple Sequence Alignment Service named {0} label.service_called_is_not_seq_search_service = The Service called \n{0}\nis not a \nSequence Search Service\! label.seq_search_service_is_unknown = The Sequence Search Service named {0} is unknown label.feature_type = Feature Type -label.display = Display +label.show = Show label.service_url = Service URL label.copied_sequences = Copied sequences label.cut_sequences = Cut Sequences @@ -1336,20 +1334,23 @@ label.matchCondition_le = <= label.matchCondition_gt = > label.matchCondition_ge = >= label.numeric_required = The value should be numeric -label.no_attributes = No attributes known -label.no_numeric_attributes = No numeric attributes known +label.filter = Filter label.filters = Filters -label.match_condition = Match condition label.join_conditions = Join conditions with -label.feature_to_filter = Feature to filter -label.colour_by_value = Colour by value -label.colour_by_text = Colour by text label.score = Score -label.attribute = Attribute label.colour_by_label = Colour by label label.variable_colour = Variable colour -label.select_new_colour = Select new colour -label.no_feature_attributes = No feature attributes found +label.select_colour = Select colour option.enable_disable_autosearch = When ticked, search is performed automatically. option.autosearch = Autosearch label.retrieve_ids = Retrieve IDs +label.display_settings_for = Display settings for {0} features +label.simple = Simple +label.simple_colour = Simple Colour +label.colour_by_text = Colour by text +label.graduated_colour = Graduated Colour +label.by_text_of = By text of +label.by_range_of = By range of +label.filters_tooltip = Click to set or amend filters +label.or = Or +label.and = And \ No newline at end of file diff --git a/resources/lang/Messages_es.properties b/resources/lang/Messages_es.properties index a7fff8e..31c3a86 100644 --- a/resources/lang/Messages_es.properties +++ b/resources/lang/Messages_es.properties @@ -226,7 +226,6 @@ label.automatic_scrolling = Desplazamiento autom label.documentation = Documentación label.about = Acerca de... label.show_sequence_limits = Mostrar los límites de la secuencia -label.feature_settings = Ajustar funciones... label.all_columns = Todas las columnas label.all_sequences = Todas las secuencias label.selected_columns = Columnas seleccionadas @@ -243,6 +242,7 @@ label.apply_all_groups = Aplicar a todos los grupos label.autocalculated_annotation = Anotación autocalculada label.min_colour = Color mínimo label.max_colour = Color máximo +label.no_colour = Sin color label.use_original_colours = Usar colores originales label.threshold_minmax = El umbral es mín/máx label.represent_group_with = Representar al grupo con @@ -250,8 +250,9 @@ label.selection = Seleccionar label.group_colour = Color del grupo label.sequence = Secuencia label.view_pdb_structure = Ver estructura PDB -label.min = Mín: -label.max = Máx: +label.max_value = Valor máximo +label.min_value = Valor mínimo +label.no_value = Sin valor label.colour_by_label = Color por etiquetas label.new_feature = Nueva función label.match_case = Hacer corresponder mayúsculas y minúsculas @@ -456,6 +457,10 @@ label.settings_for_type = Ajustes para {0} label.view_full_application = Ver en la aplicación completa label.load_associated_tree = Cargar árbol asociado ... label.load_features_annotations = Cargar características/anotaciones ... +label.load_vcf = Cargar variantes SNP desde fichero VCF texto o tab-indexado +label.load_vcf_file = Cargar fichero VCF +label.searching_vcf = Cargando variantes VCF... +label.added_vcf= {0} variantes VCF añadidas a {1} secuencia(s) label.export_features = Exportar características... label.export_annotations = Exportar anotaciones ... label.to_upper_case = Pasar a mayúsculas @@ -489,7 +494,6 @@ label.threshold_feature_above_threshold = Por encima del umbral label.threshold_feature_below_threshold = Por debajo del umbral label.adjust_threshold = Ajustar umbral label.toggle_absolute_relative_display_threshold = Cambiar entre mostrar el umbral absoluto y el relativo. -label.display_features_same_type_different_label_using_different_colour = Mostrar las características del mismo tipo con una etiqueta diferente y empleando un color distinto (p.e. características del dominio) label.select_colour_minimum_value = Seleccionar el color para el valor mínimo label.select_colour_maximum_value = Seleccionar el color para el valor máximo label.open_url_param = Abrir URL {0} @@ -791,7 +795,7 @@ label.msa_service_is_unknown = El Servicio de Alineamiento M label.service_called_is_not_seq_search_service = El Servicio llamando \n{0}\nno es un \nServicio de B\u00FAsqueda de Secuencias\! label.seq_search_service_is_unknown = El Servicio de Búsqueda de Sencuencias llamado {0} es desconocido label.feature_type = Tipo de característisca -label.display = Representación +label.show = Mostrar label.service_url = URL del servicio label.copied_sequences = Secuencias copiadas label.cut_sequences = Cortar secuencias @@ -1319,3 +1323,35 @@ label.select_hidden_colour = Seleccionar color de las regiones ocultas label.overview = Resumen label.reset_to_defaults = Restablecen a los predeterminados label.oview_calc = Recalculando resumen +label.feature_details = Detalles de característica +label.matchCondition_contains = Contiene +label.matchCondition_notcontains = No contiene +label.matchCondition_matches = Es igual a +label.matchCondition_notmatches = No es igual a +label.matchCondition_eq = = +label.matchCondition_ne = not = +label.matchCondition_lt = < +label.matchCondition_le = <= +label.matchCondition_gt = > +label.matchCondition_ge = >= +label.numeric_required = Valor numérico requerido +label.filter = Filtro +label.filters = Filtros +label.join_conditions = Combinar condiciones con +label.score = Puntuación +label.colour_by_label = Colorear por texto +label.variable_colour = Color variable +label.select_colour = Seleccionar color +option.enable_disable_autosearch = Marque para buscar automáticamente +option.autosearch = Búsqueda automática +label.retrieve_ids = Recuperar IDs +label.display_settings_for = Visualización de características {0} +label.simple = Simple +label.simple_colour = Color simple +label.colour_by_text = Colorear por texto +label.graduated_colour = Color graduado +label.by_text_of = Por texto de +label.by_range_of = Por rango de +label.filters_tooltip = Haga clic para configurar o modificar los filtros +label.or = O +label.and = Y \ No newline at end of file diff --git a/src/jalview/appletgui/FeatureColourChooser.java b/src/jalview/appletgui/FeatureColourChooser.java index e9c377a..d9eae11 100644 --- a/src/jalview/appletgui/FeatureColourChooser.java +++ b/src/jalview/appletgui/FeatureColourChooser.java @@ -58,6 +58,8 @@ public class FeatureColourChooser extends Panel implements ActionListener, */ private static final int SCALE_FACTOR_1K = 1000; + private static final String COLON = ":"; + private JVDialog frame; private Frame owner; @@ -198,8 +200,10 @@ public class FeatureColourChooser extends Panel implements ActionListener, private void jbInit() throws Exception { - Label minLabel = new Label(MessageManager.getString("label.min_value")); - Label maxLabel = new Label(MessageManager.getString("label.max_value")); + Label minLabel = new Label( + MessageManager.getString("label.min_value") + COLON); + Label maxLabel = new Label( + MessageManager.getString("label.max_value") + COLON); minLabel.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11)); maxLabel.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11)); // minColour.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11)); diff --git a/src/jalview/appletgui/FeatureSettings.java b/src/jalview/appletgui/FeatureSettings.java index 9a67499..39a2747 100755 --- a/src/jalview/appletgui/FeatureSettings.java +++ b/src/jalview/appletgui/FeatureSettings.java @@ -20,6 +20,10 @@ */ package jalview.appletgui; +import static jalview.viewmodel.seqfeatures.FeatureRendererModel.COLOUR_COLUMN; +import static jalview.viewmodel.seqfeatures.FeatureRendererModel.SHOW_COLUMN; +import static jalview.viewmodel.seqfeatures.FeatureRendererModel.TYPE_COLUMN; + import jalview.api.FeatureColourI; import jalview.api.FeatureSettingsControllerI; import jalview.datamodel.AlignmentI; @@ -584,18 +588,23 @@ public class FeatureSettings extends Panel Component[] comps = featurePanel.getComponents(); int cSize = comps.length; - Object[][] tmp = new Object[cSize][3]; + /* + * temporary! leave column[2] empty - used for Filter in + * gui.FeatureSettings + */ + int columnCount = 4; + Object[][] tmp = new Object[cSize][columnCount]; int tmpSize = 0; for (int i = 0; i < cSize; i++) { MyCheckbox check = (MyCheckbox) comps[i]; - tmp[tmpSize][0] = check.type; - tmp[tmpSize][1] = fr.getFeatureStyle(check.type); - tmp[tmpSize][2] = new Boolean(check.getState()); + tmp[tmpSize][TYPE_COLUMN /* 0 */] = check.type; + tmp[tmpSize][COLOUR_COLUMN /* 1 */] = fr.getFeatureStyle(check.type); + tmp[tmpSize][SHOW_COLUMN /* 3 */] = new Boolean(check.getState()); tmpSize++; } - Object[][] data = new Object[tmpSize][3]; + Object[][] data = new Object[tmpSize][columnCount]; System.arraycopy(tmp, 0, data, 0, tmpSize); fr.setFeaturePriority(data); diff --git a/src/jalview/gui/FeatureColourChooser.java b/src/jalview/gui/FeatureColourChooser.java deleted file mode 100644 index d3d9e1a..0000000 --- a/src/jalview/gui/FeatureColourChooser.java +++ /dev/null @@ -1,1016 +0,0 @@ -/* - * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) - * Copyright (C) $$Year-Rel$$ The Jalview Authors - * - * This file is part of Jalview. - * - * Jalview is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 - * of the License, or (at your option) any later version. - * - * Jalview is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Jalview. If not, see . - * The Jalview Authors are detailed in the 'AUTHORS' file. - */ -package jalview.gui; - -import jalview.api.FeatureColourI; -import jalview.datamodel.GraphLine; -import jalview.datamodel.features.FeatureAttributes; -import jalview.schemes.FeatureColour; -import jalview.util.MessageManager; - -import java.awt.Color; -import java.awt.Dimension; -import java.awt.FlowLayout; -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.awt.event.MouseEvent; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.BorderFactory; -import javax.swing.BoxLayout; -import javax.swing.ButtonGroup; -import javax.swing.JCheckBox; -import javax.swing.JColorChooser; -import javax.swing.JComboBox; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JRadioButton; -import javax.swing.JSlider; -import javax.swing.JTextField; -import javax.swing.border.LineBorder; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - -public class FeatureColourChooser extends JalviewDialog -{ - private static final String COLON = ":"; - - private static final int MAX_TOOLTIP_LENGTH = 50; - - private static int NO_COLOUR_OPTION = 0; - - private static int MIN_COLOUR_OPTION = 1; - - private static int MAX_COLOUR_OPTION = 2; - - private FeatureRenderer fr; - - private FeatureColourI cs; - - private FeatureColourI oldcs; - - private AlignmentPanel ap; - - private boolean adjusting = false; - - private float min; - - private float max; - - private float scaleFactor; - - private String type = null; - - private JPanel minColour = new JPanel(); - - private JPanel maxColour = new JPanel(); - - private Color noColour; - - private JComboBox threshold = new JComboBox<>(); - - private JSlider slider = new JSlider(); - - private JTextField thresholdValue = new JTextField(20); - - private JCheckBox thresholdIsMin = new JCheckBox(); - - private GraphLine threshline; - - private Color oldmaxColour; - - private Color oldminColour; - - private Color oldNoColour; - - private ActionListener colourEditor = null; - - /* - * radio buttons to select what to colour by - * label, attribute text, score, attribute value - */ - private JRadioButton byDescription = new JRadioButton(); - - private JRadioButton byAttributeText = new JRadioButton(); - - private JRadioButton byScore = new JRadioButton(); - - private JRadioButton byAttributeValue = new JRadioButton(); - - private ActionListener changeColourAction; - - private ActionListener changeMinMaxAction; - - /* - * choice of option for 'colour for no value' - */ - private JComboBox noValueCombo; - - /* - * choice of attribute (if any) for 'colour by text' - */ - private JComboBox textAttributeCombo; - - /* - * choice of attribute (if any) for 'colour by value' - */ - private JComboBox valueAttributeCombo; - - /** - * Constructor - * - * @param frender - * @param theType - */ - public FeatureColourChooser(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 - */ - FeatureColourChooser(FeatureRenderer frender, boolean blocking, - String theType) - { - this.fr = frender; - this.type = theType; - ap = fr.ap; - String title = MessageManager.formatMessage("label.variable_color_for", - new String[] { theType }); - initDialogFrame(this, true, blocking, title, 470, 300); - - slider.addChangeListener(new ChangeListener() - { - @Override - public void stateChanged(ChangeEvent evt) - { - if (!adjusting) - { - thresholdValue.setText((slider.getValue() / scaleFactor) + ""); - sliderValueChanged(); - } - } - }); - slider.addMouseListener(new MouseAdapter() - { - @Override - public void mouseReleased(MouseEvent evt) - { - /* - * only update Overview and/or structure colouring - * when threshold slider drag ends (mouse up) - */ - if (ap != null) - { - ap.paintAlignment(true, true); - } - } - }); - - // todo move all threshold setup inside a method - float mm[] = fr.getMinMax().get(theType)[0]; - min = mm[0]; - max = mm[1]; - - /* - * ensure scale factor allows a scaled range with - * 10 integer divisions ('ticks'); if we have got here, - * we should expect that max != min - */ - scaleFactor = (max == min) ? 1f : 100f / (max - min); - - oldcs = fr.getFeatureColours().get(theType); - if (!oldcs.isSimpleColour()) - { - if (oldcs.isAutoScaled()) - { - // update the scale - cs = new FeatureColour((FeatureColour) oldcs, min, max); - } - else - { - cs = new FeatureColour((FeatureColour) oldcs); - } - } - else - { - /* - * promote original simple color to a graduated color - * - by score if there is a score range, else by label - */ - Color bl = oldcs.getColour(); - if (bl == null) - { - bl = Color.BLACK; - } - // original colour becomes the maximum colour - cs = new FeatureColour(Color.white, bl, mm[0], mm[1]); - cs.setColourByLabel(mm[0] == mm[1]); - } - minColour.setBackground(oldminColour = cs.getMinColour()); - maxColour.setBackground(oldmaxColour = cs.getMaxColour()); - noColour = cs.getNoColour(); - - adjusting = true; - - try - { - jbInit(); - } catch (Exception ex) - { - ex.printStackTrace(); - return; - } - - /* - * set the initial state of options on screen - */ - if (cs.isColourByLabel()) - { - if (cs.isColourByAttribute()) - { - byAttributeText.setSelected(true); - textAttributeCombo.setEnabled(true); - String[] attributeName = cs.getAttributeName(); - textAttributeCombo - .setSelectedItem(toAttributeDisplayName(attributeName)); - } - else - { - byDescription.setSelected(true); - textAttributeCombo.setEnabled(false); - } - } - else - { - if (cs.isColourByAttribute()) - { - byAttributeValue.setSelected(true); - String[] attributeName = cs.getAttributeName(); - valueAttributeCombo - .setSelectedItem(toAttributeDisplayName(attributeName)); - valueAttributeCombo.setEnabled(true); - updateMinMax(); - } - else - { - byScore.setSelected(true); - valueAttributeCombo.setEnabled(false); - } - } - - if (noColour == null) - { - noValueCombo.setSelectedIndex(NO_COLOUR_OPTION); - } - else if (noColour.equals(oldminColour)) - { - noValueCombo.setSelectedIndex(MIN_COLOUR_OPTION); - } - else if (noColour.equals(oldmaxColour)) - { - noValueCombo.setSelectedIndex(MAX_COLOUR_OPTION); - } - - threshline = new GraphLine((max - min) / 2f, "Threshold", Color.black); - threshline.value = cs.getThreshold(); - - if (cs.hasThreshold()) - { - // initialise threshold slider and selector - threshold.setSelectedIndex(cs.isAboveThreshold() ? 1 : 2); - slider.setEnabled(true); - slider.setValue((int) (cs.getThreshold() * scaleFactor)); - thresholdValue.setEnabled(true); - } - - adjusting = false; - - changeColour(false); - waitForInput(); - } - - /** - * Configures the initial layout - */ - private void jbInit() - { - this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - this.setBackground(Color.white); - - changeColourAction = new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - changeColour(true); - } - }; - - changeMinMaxAction = new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - updateMinMax(); - changeColour(true); - } - }; - - /* - * this panel - * detailsPanel - * colourByTextPanel - * colourByScorePanel - * okCancelPanel - */ - JPanel detailsPanel = new JPanel(); - detailsPanel.setLayout(new BoxLayout(detailsPanel, BoxLayout.Y_AXIS)); - - JPanel colourByTextPanel = initColourByTextPanel(); - detailsPanel.add(colourByTextPanel); - - JPanel colourByValuePanel = initColourByValuePanel(); - detailsPanel.add(colourByValuePanel); - - /* - * 4 radio buttons select between colour by description, by - * attribute text, by score, or by attribute value - */ - ButtonGroup bg = new ButtonGroup(); - bg.add(byDescription); - bg.add(byAttributeText); - bg.add(byScore); - bg.add(byAttributeValue); - - JPanel okCancelPanel = initOkCancelPanel(); - - this.add(detailsPanel); - this.add(okCancelPanel); - } - - /** - * Updates the min-max range for a change in choice of Colour by Score, or - * Colour by Attribute (value) - */ - protected void updateMinMax() - { - float[] minMax = null; - if (byScore.isSelected()) - { - minMax = fr.getMinMax().get(type)[0]; - } - else if (byAttributeValue.isSelected()) - { - String attName = (String) valueAttributeCombo.getSelectedItem(); - String[] attNames = fromAttributeDisplayName(attName); - minMax = FeatureAttributes.getInstance().getMinMax(type, attNames); - } - if (minMax != null) - { - min = minMax[0]; - max = minMax[1]; - scaleFactor = (max == min) ? 1f : 100f / (max - min); - slider.setValue((int) (min * scaleFactor)); - } - } - - /** - * Lay out fields for graduated colour by value - * - * @return - */ - protected JPanel initColourByValuePanel() - { - JPanel byValuePanel = new JPanel(); - byValuePanel.setLayout(new BoxLayout(byValuePanel, BoxLayout.Y_AXIS)); - JvSwingUtils.createItalicTitledBorder(byValuePanel, - MessageManager.getString("label.colour_by_value"), true); - byValuePanel.setBackground(Color.white); - - /* - * first row - choose colour by score or by attribute, choose attribute - */ - JPanel byWhatPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); - byWhatPanel.setBackground(Color.white); - byValuePanel.add(byWhatPanel); - - byScore.setText(MessageManager.getString("label.score")); - byWhatPanel.add(byScore); - byScore.addActionListener(changeMinMaxAction); - - byAttributeValue.setText(MessageManager.getString("label.attribute")); - byAttributeValue.addActionListener(changeMinMaxAction); - byWhatPanel.add(byAttributeValue); - - List attNames = FeatureAttributes.getInstance() - .getAttributes(type); - valueAttributeCombo = populateAttributesDropdown(type, attNames, true); - - /* - * if no numeric atttibutes found, disable colour by attribute value - */ - if (valueAttributeCombo.getItemCount() == 0) - { - byAttributeValue.setEnabled(false); - } - - byWhatPanel.add(valueAttributeCombo); - - /* - * second row - min/max/no colours - */ - JPanel colourRangePanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); - colourRangePanel.setBackground(Color.white); - byValuePanel.add(colourRangePanel); - - 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)); - - noValueCombo = new JComboBox<>(); - noValueCombo.addItem(MessageManager.getString("label.no_colour")); - noValueCombo.addItem(MessageManager.getString("label.min_colour")); - noValueCombo.addItem(MessageManager.getString("label.max_colour")); - noValueCombo.addItemListener(new ItemListener() - { - @Override - public void itemStateChanged(ItemEvent e) - { - setNoValueColour(); - } - }); - - JLabel minText = new JLabel(MessageManager.getString("label.min_value")); - minText.setFont(JvSwingUtils.getLabelFont()); - JLabel maxText = new JLabel(MessageManager.getString("label.max_value")); - maxText.setFont(JvSwingUtils.getLabelFont()); - JLabel noText = new JLabel(MessageManager.getString("label.no_value")); - noText.setFont(JvSwingUtils.getLabelFont()); - - colourRangePanel.add(minText); - colourRangePanel.add(minColour); - colourRangePanel.add(maxText); - colourRangePanel.add(maxColour); - colourRangePanel.add(noText); - colourRangePanel.add(noValueCombo); - - /* - * third row - threshold options and value - */ - JPanel thresholdPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); - thresholdPanel.setBackground(Color.white); - byValuePanel.add(thresholdPanel); - - threshold.addActionListener(changeColourAction); - threshold.setToolTipText(MessageManager - .getString("label.threshold_feature_display_by_score")); - threshold.addItem(MessageManager - .getString("label.threshold_feature_no_threshold")); // index 0 - threshold.addItem(MessageManager - .getString("label.threshold_feature_above_threshold")); // index 1 - threshold.addItem(MessageManager - .getString("label.threshold_feature_below_threshold")); // index 2 - - thresholdValue.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - thresholdValue_actionPerformed(); - } - }); - thresholdValue.addFocusListener(new FocusAdapter() - { - @Override - public void focusLost(FocusEvent e) - { - thresholdValue_actionPerformed(); - } - }); - slider.setPaintLabels(false); - slider.setPaintTicks(true); - slider.setBackground(Color.white); - slider.setEnabled(false); - slider.setOpaque(false); - slider.setPreferredSize(new Dimension(100, 32)); - slider.setToolTipText(MessageManager - .getString("label.adjust_threshold")); - thresholdValue.setEnabled(false); - thresholdValue.setColumns(7); - - thresholdPanel.add(threshold); - thresholdPanel.add(slider); - thresholdPanel.add(thresholdValue); - - /* - * 4th row - threshold is min / max - */ - JPanel isMinMaxPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); - isMinMaxPanel.setBackground(Color.white); - byValuePanel.add(isMinMaxPanel); - thresholdIsMin.setBackground(Color.white); - thresholdIsMin.setText(MessageManager - .getString("label.threshold_minmax")); - thresholdIsMin.setToolTipText(MessageManager - .getString("label.toggle_absolute_relative_display_threshold")); - thresholdIsMin.addActionListener(changeColourAction); - isMinMaxPanel.add(thresholdIsMin); - - return byValuePanel; - } - - /** - * Action on user choice of no / min / max colour to use when there is no - * value to colour by - */ - protected void setNoValueColour() - { - int i = noValueCombo.getSelectedIndex(); - if (i == NO_COLOUR_OPTION) - { - noColour = null; - } - else if (i == MIN_COLOUR_OPTION) - { - noColour = minColour.getBackground(); - } - else if (i == MAX_COLOUR_OPTION) - { - noColour = maxColour.getBackground(); - } - changeColour(true); - } - - /** - * 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 attribute choice elements - * - * @return - */ - protected JPanel initColourByTextPanel() - { - JPanel byTextPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); - byTextPanel.setBackground(Color.white); - JvSwingUtils.createItalicTitledBorder(byTextPanel, - MessageManager.getString("label.colour_by_text"), true); - - byDescription.setText(MessageManager.getString("label.label")); - byDescription.setToolTipText(MessageManager - .getString("label.colour_by_label_tip")); - byDescription.addActionListener(changeColourAction); - byTextPanel.add(byDescription); - - byAttributeText.setText(MessageManager.getString("label.attribute")); - byAttributeText.addActionListener(changeColourAction); - byTextPanel.add(byAttributeText); - - List attNames = FeatureAttributes.getInstance() - .getAttributes(type); - textAttributeCombo = populateAttributesDropdown(type, attNames, false); - byTextPanel.add(textAttributeCombo); - - /* - * disable colour by attribute if no attributes - */ - if (attNames.isEmpty()) - { - byAttributeText.setEnabled(false); - } - - return byTextPanel; - } - - /** - * Action on clicking the 'minimum colour' - open a colour chooser dialog, and - * set the selected colour (if the user does not cancel out of the dialog) - */ - protected void minColour_actionPerformed() - { - Color col = JColorChooser.showDialog(this, - MessageManager.getString("label.select_colour_minimum_value"), - minColour.getBackground()); - if (col != null) - { - minColour.setBackground(col); - minColour.setForeground(col); - } - minColour.repaint(); - changeColour(true); - } - - /** - * Action on clicking the 'maximum colour' - open a colour chooser dialog, and - * set the selected colour (if the user does not cancel out of the dialog) - */ - protected void maxColour_actionPerformed() - { - Color col = JColorChooser.showDialog(this, - MessageManager.getString("label.select_colour_maximum_value"), - maxColour.getBackground()); - if (col != null) - { - maxColour.setBackground(col); - maxColour.setForeground(col); - } - maxColour.repaint(); - changeColour(true); - } - - /** - * 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 - */ - void changeColour(boolean updateStructsAndOverview) - { - // Check if combobox is still adjusting - if (adjusting) - { - return; - } - - boolean aboveThreshold = false; - boolean belowThreshold = false; - if (threshold.getSelectedIndex() == 1) - { - aboveThreshold = true; - } - else if (threshold.getSelectedIndex() == 2) - { - belowThreshold = true; - } - boolean hasThreshold = aboveThreshold || belowThreshold; - - slider.setEnabled(true); - thresholdValue.setEnabled(true); - - /* - * make the feature colour - */ - FeatureColourI acg; - if (cs.isColourByLabel()) - { - acg = new FeatureColour(oldminColour, oldmaxColour, min, max); - } - else - { - acg = new FeatureColour(oldminColour = minColour.getBackground(), - oldmaxColour = maxColour.getBackground(), - oldNoColour = noColour, min, max); - } - String attribute = null; - textAttributeCombo.setEnabled(false); - valueAttributeCombo.setEnabled(false); - if (byAttributeText.isSelected()) - { - attribute = (String) textAttributeCombo.getSelectedItem(); - textAttributeCombo.setEnabled(true); - acg.setAttributeName(fromAttributeDisplayName(attribute)); - } - else if (byAttributeValue.isSelected()) - { - attribute = (String) valueAttributeCombo.getSelectedItem(); - valueAttributeCombo.setEnabled(true); - acg.setAttributeName(fromAttributeDisplayName(attribute)); - } - else - { - acg.setAttributeName((String[]) null); - } - - if (!hasThreshold) - { - slider.setEnabled(false); - thresholdValue.setEnabled(false); - thresholdValue.setText(""); - thresholdIsMin.setEnabled(false); - } - else if (threshline == null) - { - /* - * todo not yet implemented: visual indication of feature threshold - */ - threshline = new GraphLine((max - min) / 2f, "Threshold", - Color.black); - } - - if (hasThreshold) - { - adjusting = true; - acg.setThreshold(threshline.value); - - float range = (max - min) * scaleFactor; - - slider.setMinimum((int) (min * scaleFactor)); - slider.setMaximum((int) (max * scaleFactor)); - // slider.setValue((int) (threshline.value * scaleFactor)); - slider.setValue(Math.round(threshline.value * scaleFactor)); - thresholdValue.setText(threshline.value + ""); - slider.setMajorTickSpacing((int) (range / 10f)); - slider.setEnabled(true); - thresholdValue.setEnabled(true); - thresholdIsMin.setEnabled(!byDescription.isSelected()); - adjusting = false; - } - - acg.setAboveThreshold(aboveThreshold); - acg.setBelowThreshold(belowThreshold); - if (thresholdIsMin.isSelected() && hasThreshold) - { - acg.setAutoScaled(false); - if (aboveThreshold) - { - acg = new FeatureColour((FeatureColour) acg, threshline.value, max); - } - else - { - acg = new FeatureColour((FeatureColour) acg, min, threshline.value); - } - } - else - { - acg.setAutoScaled(true); - } - acg.setColourByLabel(byDescription.isSelected() - || byAttributeText.isSelected()); - - if (acg.isColourByLabel()) - { - maxColour.setEnabled(false); - minColour.setEnabled(false); - noValueCombo.setEnabled(false); - maxColour.setBackground(this.getBackground()); - maxColour.setForeground(this.getBackground()); - minColour.setBackground(this.getBackground()); - minColour.setForeground(this.getBackground()); - } - else - { - maxColour.setEnabled(true); - minColour.setEnabled(true); - noValueCombo.setEnabled(true); - maxColour.setBackground(oldmaxColour); - maxColour.setForeground(oldmaxColour); - minColour.setBackground(oldminColour); - minColour.setForeground(oldminColour); - noColour = oldNoColour; - } - - /* - * save the colour, and repaint stuff - */ - fr.setColour(type, acg); - cs = acg; - ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview); - } - - private String[] fromAttributeDisplayName(String attribute) - { - return attribute == null ? null : attribute.split(COLON); - } - - @Override - protected void raiseClosed() - { - if (this.colourEditor != null) - { - colourEditor.actionPerformed(new ActionEvent(this, 0, "CLOSED")); - } - } - - @Override - public void okPressed() - { - changeColour(false); - } - - @Override - public void cancelPressed() - { - reset(); - } - - /** - * Action when the user cancels the dialog. All previous settings should be - * restored and rendered on the alignment, and any linked Overview window or - * structure. - */ - void reset() - { - fr.setColour(type, oldcs); - ap.paintAlignment(true, true); - cs = null; - } - - /** - * Action on text entry of a threshold value - */ - protected void thresholdValue_actionPerformed() - { - try - { - float f = Float.parseFloat(thresholdValue.getText()); - slider.setValue((int) (f * scaleFactor)); - threshline.value = f; - - /* - * force repaint of any Overview window or structure - */ - ap.paintAlignment(true, true); - } catch (NumberFormatException ex) - { - } - } - - /** - * Action on change of threshold slider value. This may be done interactively - * (by moving the slider), or programmatically (to update the slider after - * manual input of a threshold value). - */ - protected void sliderValueChanged() - { - /* - * squash rounding errors by forcing min/max of slider to - * actual min/max of feature score range - */ - int value = slider.getValue(); - threshline.value = value == slider.getMaximum() ? max - : (value == slider.getMinimum() ? min : value / scaleFactor); - cs.setThreshold(threshline.value); - - /* - * repaint alignment, but not Overview or structure, - * to avoid overload while dragging the slider - */ - changeColour(false); - } - - void addActionListener(ActionListener graduatedColorEditor) - { - if (colourEditor != null) - { - System.err.println( - "IMPLEMENTATION ISSUE: overwriting action listener for FeatureColourChooser"); - } - colourEditor = graduatedColorEditor; - } - - /** - * Answers the last colour setting selected by user - either oldcs (which may - * be a java.awt.Color) or the new GraduatedColor - * - * @return - */ - FeatureColourI getLastColour() - { - if (cs == null) - { - return oldcs; - } - return cs; - } - - /** - * 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. The list may optionally be restricted to attributes for - * which we hold a range of numerical values (so suitable candidates for a - * graduated colour scheme). - *

- * Attribute names may be 'simple' e.g. "AC" or 'compound' e.g. {"CSQ", - * "Allele"}. Compound names are rendered for display as (e.g.) CSQ:Allele. - * - * @param featureType - * @param attNames - * @param withNumericRange - */ - protected JComboBox populateAttributesDropdown( - String featureType, List attNames, - boolean withNumericRange) - { - List validAtts = new ArrayList<>(); - List tooltips = new ArrayList<>(); - - FeatureAttributes fa = FeatureAttributes.getInstance(); - for (String[] attName : attNames) - { - if (withNumericRange) - { - float[] minMax = fa.getMinMax(featureType, attName); - if (minMax == null) - { - continue; - } - } - validAtts.add(toAttributeDisplayName(attName)); - 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( - validAtts, tooltips); - - attCombo.addItemListener(new ItemListener() - { - @Override - public void itemStateChanged(ItemEvent e) - { - changeMinMaxAction.actionPerformed(null); - } - }); - - if (validAtts.isEmpty()) - { - attCombo.setToolTipText(MessageManager - .getString(withNumericRange ? "label.no_numeric_attributes" - : "label.no_attributes")); - } - - return attCombo; - } - - private String toAttributeDisplayName(String[] attName) - { - return attName == null ? "" : String.join(COLON, attName); - } - -} diff --git a/src/jalview/gui/FeatureRenderer.java b/src/jalview/gui/FeatureRenderer.java index 9c4b009..46f574e 100644 --- a/src/jalview/gui/FeatureRenderer.java +++ b/src/jalview/gui/FeatureRenderer.java @@ -180,15 +180,15 @@ public class FeatureRenderer final JSpinner end = new JSpinner(); start.setPreferredSize(new Dimension(80, 20)); end.setPreferredSize(new Dimension(80, 20)); - final FeatureRenderer me = this; final JLabel colour = new JLabel(); colour.setOpaque(true); // colour.setBorder(BorderFactory.createEtchedBorder()); colour.setMaximumSize(new Dimension(30, 16)); colour.addMouseListener(new MouseAdapter() { - FeatureColourChooser fcc = null; - + /* + * open colour chooser on click in colour panel + */ @Override public void mousePressed(MouseEvent evt) { @@ -205,28 +205,26 @@ public class FeatureRenderer } else { - if (fcc == null) + /* + * variable colour dialog - on OK, refetch the updated + * feature colour and update this display + */ + final String ft = features.get(featureIndex).getType(); + final String type = ft == null ? lastFeatureAdded : ft; + FeatureTypeSettings fcc = new FeatureTypeSettings( + FeatureRenderer.this, type); + fcc.setRequestFocusEnabled(true); + fcc.requestFocus(); + fcc.addActionListener(new ActionListener() { - final String ft = features.get(featureIndex).getType(); - final String type = ft == null ? lastFeatureAdded : ft; - fcc = new FeatureColourChooser(me, type); - fcc.setRequestFocusEnabled(true); - fcc.requestFocus(); - - fcc.addActionListener(new ActionListener() + @Override + public void actionPerformed(ActionEvent e) { - - @Override - public void actionPerformed(ActionEvent e) - { - fcol = fcc.getLastColour(); - fcc = null; - setColour(type, fcol); - updateColourButton(mainPanel, colour, fcol); - } - }); - - } + fcol = FeatureRenderer.this.getFeatureStyle(ft); + setColour(type, fcol); + updateColourButton(mainPanel, colour, fcol); + } + }); } } }); diff --git a/src/jalview/gui/FeatureSettings.java b/src/jalview/gui/FeatureSettings.java index 974b387..fedfe3f 100644 --- a/src/jalview/gui/FeatureSettings.java +++ b/src/jalview/gui/FeatureSettings.java @@ -20,12 +20,16 @@ */ package jalview.gui; +import static jalview.viewmodel.seqfeatures.FeatureRendererModel.COLOUR_COLUMN; +import static jalview.viewmodel.seqfeatures.FeatureRendererModel.FILTER_COLUMN; +import static jalview.viewmodel.seqfeatures.FeatureRendererModel.SHOW_COLUMN; +import static jalview.viewmodel.seqfeatures.FeatureRendererModel.TYPE_COLUMN; + import jalview.api.FeatureColourI; 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; @@ -35,10 +39,6 @@ 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; @@ -49,16 +49,13 @@ 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.LayoutManager; +import java.awt.Point; 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; @@ -72,7 +69,6 @@ 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; @@ -85,14 +81,11 @@ 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; @@ -100,26 +93,24 @@ 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; +import javax.swing.table.TableColumn; public class FeatureSettings extends JPanel implements FeatureSettingsControllerI { + private static final int COLUMN_COUNT = 4; + private static final String COLON = ":"; private static final int MIN_WIDTH = 400; @@ -156,7 +147,7 @@ public class FeatureSettings extends JPanel JPanel groupPanel; JSlider transparency = new JSlider(); - + /* * when true, constructor is still executing - so ignore UI events */ @@ -182,29 +173,6 @@ public class FeatureSettings extends JPanel */ 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; - - // set white normally, black to debug layout - private Color debugBorderColour = Color.white; - /** * Constructor * @@ -235,25 +203,48 @@ public class FeatureSettings extends JPanel @Override public String getToolTipText(MouseEvent e) { - if (table.columnAtPoint(e.getPoint()) == 0) + String tip = null; + int column = table.columnAtPoint(e.getPoint()); + switch (column) { - /* - * Tooltip for feature name only - */ - return JvSwingUtils.wrapTooltip(true, MessageManager + case TYPE_COLUMN: + tip = JvSwingUtils.wrapTooltip(true, MessageManager .getString("label.feature_settings_click_drag")); + break; + case FILTER_COLUMN: + int row = table.rowAtPoint(e.getPoint()); + KeyedMatcherSet o = (KeyedMatcherSet) table.getValueAt(row, + column); + tip = o.isEmpty() + ? MessageManager.getString("label.filters_tooltip") + : o.toString(); + break; + default: + break; } - return null; + return tip; } }; table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12)); table.setFont(new Font("Verdana", Font.PLAIN, 12)); - table.setDefaultRenderer(Color.class, new ColorRenderer()); - - table.setDefaultEditor(Color.class, new ColorEditor(this)); + // table.setDefaultRenderer(Color.class, new ColorRenderer()); + // table.setDefaultEditor(Color.class, new ColorEditor(this)); + // table.setDefaultEditor(FeatureColour.class, new ColorEditor(this)); table.setDefaultRenderer(FeatureColour.class, new ColorRenderer()); + + table.setDefaultEditor(KeyedMatcherSet.class, new FilterEditor(this)); + table.setDefaultRenderer(KeyedMatcherSet.class, new FilterRenderer()); + + TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75, + new ColorRenderer(), new ColorEditor(this)); + table.addColumn(colourColumn); + + TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75, + new FilterRenderer(), new FilterEditor(this)); + table.addColumn(filterColumn); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.addMouseListener(new MouseAdapter() @@ -262,11 +253,12 @@ public class FeatureSettings extends JPanel public void mousePressed(MouseEvent evt) { selectedRow = table.rowAtPoint(evt.getPoint()); + String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN); if (evt.isPopupTrigger()) { - popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0), - table.getValueAt(selectedRow, 1), fr.getMinMax(), - evt.getX(), evt.getY()); + Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN); + popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(), + evt.getY()); } else if (evt.getClickCount() == 2) { @@ -274,8 +266,7 @@ public class FeatureSettings extends JPanel boolean toggleSelection = Platform.isControlDown(evt); boolean extendSelection = evt.isShiftDown(); fr.ap.alignFrame.avc.markColumnsContainingFeatures( - invertSelection, extendSelection, toggleSelection, - (String) table.getValueAt(selectedRow, 0)); + invertSelection, extendSelection, toggleSelection, type); } } @@ -286,9 +277,10 @@ public class FeatureSettings extends JPanel selectedRow = table.rowAtPoint(evt.getPoint()); if (evt.isPopupTrigger()) { - popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0), - table.getValueAt(selectedRow, 1), fr.getMinMax(), - evt.getX(), evt.getY()); + String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN); + Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN); + popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(), + evt.getY()); } } }); @@ -344,8 +336,8 @@ public class FeatureSettings extends JPanel if (!fs.resettingTable && !fs.handlingUpdate) { fs.handlingUpdate = true; - fs.resetTable(null); // new groups may be added with new seuqence - // feature types only + fs.resetTable(null); + // new groups may be added with new sequence feature types only fs.handlingUpdate = false; } } @@ -383,7 +375,7 @@ public class FeatureSettings extends JPanel inConstruction = false; } - protected void popupSort(final int selectedRow, final String type, + protected void popupSort(final int rowSelected, final String type, final Object typeCol, final Map minmax, int x, int y) { @@ -443,15 +435,17 @@ public class FeatureSettings extends JPanel { if (featureColour.isSimpleColour()) { - FeatureColourChooser fc = new FeatureColourChooser(me.fr, type); + FeatureTypeSettings fc = new FeatureTypeSettings(me.fr, type); fc.addActionListener(this); } else { // bring up simple color chooser colorChooser = new JColorChooser(); + String title = MessageManager + .getString("label.select_colour"); JDialog dialog = JColorChooser.createDialog(me, - "Select new Colour", true, // modal + title, true, // modal colorChooser, this, // OK button handler null); // no CANCEL button handler colorChooser.setColor(featureColour.getMaxColour()); @@ -460,20 +454,25 @@ public class FeatureSettings extends JPanel } else { - if (e.getSource() instanceof FeatureColourChooser) + if (e.getSource() instanceof FeatureTypeSettings) { - FeatureColourChooser fc = (FeatureColourChooser) e.getSource(); - table.setValueAt(fc.getLastColour(), selectedRow, 1); + /* + * update after OK in feature colour dialog; the updated + * colour will have already been set in the FeatureRenderer + */ + FeatureColourI fci = fr.getFeatureColours().get(type); + table.setValueAt(fci, rowSelected, 1); table.validate(); } else { // probably the color chooser! table.setValueAt(new FeatureColour(colorChooser.getColor()), - selectedRow, 1); + rowSelected, 1); table.validate(); me.updateFeatureRenderer( - ((FeatureTableModel) table.getModel()).getData(), false); + ((FeatureTableModel) table.getModel()).getData(), + false); } } } @@ -548,8 +547,6 @@ public class FeatureSettings extends JPanel } } - populateFilterableFeatures(); - resetTable(null); validate(); @@ -654,7 +651,8 @@ public class FeatureSettings extends JPanel } } - Object[][] data = new Object[displayableTypes.size()][3]; + int columnCount = COLUMN_COUNT; + Object[][] data = new Object[displayableTypes.size()][columnCount]; int dataIndex = 0; if (fr.hasRenderOrder()) @@ -677,9 +675,13 @@ public class FeatureSettings extends JPanel continue; } - data[dataIndex][0] = type; - data[dataIndex][1] = fr.getFeatureStyle(type); - data[dataIndex][2] = new Boolean( + data[dataIndex][TYPE_COLUMN] = type; + data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type); + KeyedMatcherSetI featureFilter = fr.getFeatureFilter(type); + data[dataIndex][FILTER_COLUMN] = featureFilter == null + ? new KeyedMatcherSet() + : featureFilter; + data[dataIndex][SHOW_COLUMN] = new Boolean( af.getViewport().getFeaturesDisplayed().isVisible(type)); dataIndex++; displayableTypes.remove(type); @@ -693,27 +695,31 @@ public class FeatureSettings extends JPanel while (!displayableTypes.isEmpty()) { String type = displayableTypes.iterator().next(); - data[dataIndex][0] = type; + data[dataIndex][TYPE_COLUMN] = type; - data[dataIndex][1] = fr.getFeatureStyle(type); - if (data[dataIndex][1] == null) + data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type); + if (data[dataIndex][COLOUR_COLUMN] == null) { // "Colour has been updated in another view!!" fr.clearRenderOrder(); return; } - - data[dataIndex][2] = new Boolean(true); + KeyedMatcherSetI featureFilter = fr.getFeatureFilter(type); + data[dataIndex][FILTER_COLUMN] = featureFilter == null + ? new KeyedMatcherSet() + : featureFilter; + data[dataIndex][SHOW_COLUMN] = new Boolean(true); dataIndex++; displayableTypes.remove(type); } if (originalData == null) { - originalData = new Object[data.length][3]; + int size = data[0].length; + originalData = new Object[data.length][size]; for (int i = 0; i < data.length; i++) { - System.arraycopy(data[i], 0, originalData[i], 0, 3); + System.arraycopy(data[i], 0, originalData[i], 0, size); } } else @@ -734,8 +740,8 @@ public class FeatureSettings extends JPanel } /** - * Updates 'originalData' (used for restore on Cancel) if we detect that - * changes have been made outwith this dialog + * Updates 'originalData' (used for restore on Cancel) if we detect that changes + * have been made outwith this dialog *

    *
  • a new feature type added (and made visible)
  • *
  • a feature colour changed (in the Amend Features dialog)
  • @@ -751,27 +757,27 @@ public class FeatureSettings extends JPanel .getData(); for (Object[] row : foundData) { - String type = (String) row[0]; + String type = (String) row[TYPE_COLUMN]; boolean found = false; for (Object[] current : currentData) { - if (type.equals(current[0])) + if (type.equals(current[TYPE_COLUMN])) { found = true; /* * currently dependent on object equality here; * really need an equals method on FeatureColour */ - if (!row[1].equals(current[1])) + if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN])) { /* * feature colour has changed externally - update originalData */ for (Object[] original : originalData) { - if (type.equals(original[0])) + if (type.equals(original[TYPE_COLUMN])) { - original[1] = row[1]; + original[COLOUR_COLUMN] = row[COLOUR_COLUMN]; break; } } @@ -784,10 +790,11 @@ public class FeatureSettings extends JPanel /* * new feature detected - add to original data (on top) */ - Object[][] newData = new Object[originalData.length + 1][3]; + int size = currentData[0].length; + Object[][] newData = new Object[originalData.length + 1][size]; for (int i = 0; i < originalData.length; i++) { - System.arraycopy(originalData[i], 0, newData[i + 1], 0, 3); + System.arraycopy(originalData[i], 0, newData[i + 1], 0, size); } newData[0] = row; originalData = newData; @@ -797,8 +804,8 @@ public class FeatureSettings extends JPanel /** * Remove from the groups panel any checkboxes for groups that are not in the - * foundGroups set. This enables removing a group from the display when the - * last feature in that group is deleted. + * foundGroups set. This enables removing a group from the display when the last + * feature in that group is deleted. * * @param foundGroups */ @@ -1004,9 +1011,9 @@ public class FeatureSettings extends JPanel { for (int i = 0; i < table.getRowCount(); i++) { - Boolean value = (Boolean) table.getValueAt(i, 2); + Boolean value = (Boolean) table.getValueAt(i, SHOW_COLUMN); - table.setValueAt(new Boolean(!value.booleanValue()), i, 2); + table.setValueAt(new Boolean(!value.booleanValue()), i, SHOW_COLUMN); } } @@ -1020,17 +1027,16 @@ public class FeatureSettings extends JPanel float[] width = new float[data.length]; float[] awidth; float max = 0; - int num = 0; + for (int i = 0; i < data.length; i++) { - awidth = typeWidth.get(data[i][0]); + awidth = typeWidth.get(data[i][TYPE_COLUMN]); if (awidth[0] > 0) { width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better // weight - but have to make per // sequence, too (awidth[2]) // if (width[i]==1) // hack to distinguish single width sequences. - num++; } else { @@ -1047,16 +1053,17 @@ public class FeatureSettings extends JPanel // awidth = (float[]) typeWidth.get(data[i][0]); if (width[i] == 0) { - width[i] = fr.getOrder(data[i][0].toString()); + width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString()); if (width[i] < 0) { - width[i] = fr.setOrder(data[i][0].toString(), i / data.length); + width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(), + i / data.length); } } else { width[i] /= max; // normalize - fr.setOrder(data[i][0].toString(), width[i]); // store for later + fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later } if (i > 0) { @@ -1090,8 +1097,8 @@ public class FeatureSettings extends JPanel } /** - * Update the priority order of features; only repaint if this changed the - * order of visible features + * Update the priority order of features; only repaint if this changed the order + * of visible features * * @param data * @param visibleNew @@ -1111,8 +1118,6 @@ public class FeatureSettings extends JPanel JPanel settingsPane = new JPanel(); settingsPane.setLayout(new BorderLayout()); - filtersPane = new JPanel(); - dasSettingsPane.setLayout(new BorderLayout()); JPanel bigPanel = new JPanel(); @@ -1257,7 +1262,7 @@ public class FeatureSettings extends JPanel if (!inConstruction) { fr.setTransparency((100 - transparency.getValue()) / 100f); - af.alignPanel.paintAlignment(true,true); + af.alignPanel.paintAlignment(true, true); } } }); @@ -1298,15 +1303,6 @@ public class FeatureSettings extends JPanel } }); - JTabbedPane tabbedPane = new JTabbedPane(); - this.add(tabbedPane, BorderLayout.CENTER); - tabbedPane.addTab(MessageManager.getString("label.feature_settings"), - settingsPane); - 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); @@ -1331,445 +1327,7 @@ public class FeatureSettings extends JPanel dasButtonPanel.add(saveDAS); 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); - JvSwingUtils.createItalicTitledBorder(chooseTypePanel, - MessageManager.getString("label.feature_type"), true); - 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); - JvSwingUtils.createItalicTitledBorder(filtersPanel, - MessageManager.getString("label.filters"), true); - - /* - * add AND or OR radio buttons - */ - JPanel andOrPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); - andOrPanel.setBackground(Color.white); - andOrPanel.setBorder(BorderFactory.createLineBorder(debugBorderColour)); - 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(); - LayoutManager box = new BoxLayout(chooseFiltersPanel, - BoxLayout.Y_AXIS); - chooseFiltersPanel.setLayout(box); - filtersPanel.add(chooseFiltersPanel); - - /* - * a read-only text view of the current filters - */ - JPanel showFiltersPanel = new JPanel(new BorderLayout(5, 5)); - showFiltersPanel.setBackground(Color.white); - JvSwingUtils.createItalicTitledBorder(showFiltersPanel, - MessageManager.getString("label.match_condition"), true); - filtersAsText = new JTextArea(); - filtersAsText.setLineWrap(true); - filtersAsText.setWrapStyleWord(true); - filtersAsText.setEnabled(false); // for display only - 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.addItem(MessageManager - .getString("label.no_feature_attributes")); - filteredFeatureChoice.setEnabled(false); - } - - 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], "", - (String) null); - filters.add(noFilter); - - /* - * render the conditions in rows, each in its own JPanel - */ - int filterIndex = 0; - for (KeyedMatcherI 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)); - chooseFiltersPanel.add(row); - filterIndex++; - } - // chooseFiltersPanel.add(Box.createVerticalGlue()); - - 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 attName - * @param attNames - * @param cond - * @param pattern - * @param filterIndex - * @return - */ - protected JPanel addFilter(String[] attName, List attNames, - Condition cond, String pattern, int filterIndex) - { - JPanel filterRow = new JPanel(new FlowLayout(FlowLayout.LEFT)); - filterRow.setBackground(Color.white); - - /* - * 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 (attName == null) // the 'add a condition' row - { - attCombo.setSelectedItem(null); - } - else - { - attCombo.setSelectedItem(String.join(COLON, attName)); - } - 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 drawing '-' or 'x' - 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 displayNames = new ArrayList<>(); - 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) + "..."; - } - displayNames.add(String.join(COLON, attName)); - tooltips.add(desc == null ? "" : desc); - } - - JComboBox attCombo = JvSwingUtils.buildComboWithTooltips( - displayNames, 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(cond, pattern, - attName.split(COLON)); - - filters.set(filterIndex, km); + this.add(settingsPane); } public void fetchDAS_actionPerformed(ActionEvent e) @@ -1929,68 +1487,25 @@ 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 // /////////////////////////////////////////////////////////////////////// class FeatureTableModel extends AbstractTableModel { - FeatureTableModel(Object[][] data) - { - this.data = data; - } private String[] columnNames = { MessageManager.getString("label.feature_type"), MessageManager.getString("action.colour"), - MessageManager.getString("label.display") }; + MessageManager.getString("label.filter"), + MessageManager.getString("label.show") }; private Object[][] data; + FeatureTableModel(Object[][] data) + { + this.data = data; + } + public Object[][] getData() { return data; @@ -2030,10 +1545,14 @@ public class FeatureSettings extends JPanel return data[row][col]; } + /** + * Answers the class of the object in column c of the first row of the table + */ @Override - public Class getColumnClass(int c) + public Class getColumnClass(int c) { - return getValueAt(0, c).getClass(); + Object v = getValueAt(0, c); + return v == null ? null : v.getClass(); } @Override @@ -2110,6 +1629,54 @@ public class FeatureSettings extends JPanel } } + class FilterRenderer extends JLabel implements TableCellRenderer + { + javax.swing.border.Border unselectedBorder = null; + + javax.swing.border.Border selectedBorder = null; + + public FilterRenderer() + { + setOpaque(true); // MUST do this for background to show up. + setHorizontalTextPosition(SwingConstants.CENTER); + setVerticalTextPosition(SwingConstants.CENTER); + } + + @Override + public Component getTableCellRendererComponent(JTable tbl, + Object filter, boolean isSelected, boolean hasFocus, int row, + int column) + { + KeyedMatcherSetI theFilter = (KeyedMatcherSetI) filter; + setOpaque(true); + String asText = theFilter.toString(); + setBackground(tbl.getBackground()); + this.setText(asText); + this.setIcon(null); + + if (isSelected) + { + if (selectedBorder == null) + { + selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, + tbl.getSelectionBackground()); + } + setBorder(selectedBorder); + } + else + { + if (unselectedBorder == null) + { + unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, + tbl.getBackground()); + } + setBorder(unselectedBorder); + } + + return this; + } + } + /** * update comp using rendering settings from gcol * @@ -2197,11 +1764,250 @@ public class FeatureSettings extends JPanel } else { - comp.setToolTipText(tt.append(" ").append(comp.getToolTipText()) - .toString()); + comp.setToolTipText( + tt.append(" ").append(comp.getToolTipText()).toString()); } } } + + class ColorEditor extends AbstractCellEditor + implements TableCellEditor, ActionListener + { + FeatureSettings me; + + FeatureColourI currentColor; + + FeatureTypeSettings chooser; + + String type; + + JButton button; + + JColorChooser colorChooser; + + JDialog dialog; + + protected static final String EDIT = "edit"; + + int rowSelected = 0; + + public ColorEditor(FeatureSettings me) + { + this.me = me; + // Set up the editor (from the table's point of view), + // which is a button. + // This button brings up the color chooser dialog, + // which is the editor from the user's point of view. + button = new JButton(); + button.setActionCommand(EDIT); + button.addActionListener(this); + button.setBorderPainted(false); + // Set up the dialog that the button brings up. + colorChooser = new JColorChooser(); + dialog = JColorChooser.createDialog(button, + MessageManager.getString("label.select_colour"), true, // modal + colorChooser, this, // OK button handler + null); // no CANCEL button handler + } + + /** + * Handles events from the editor button and from the dialog's OK button. + */ + @Override + public void actionPerformed(ActionEvent e) + { + // todo test e.getSource() instead here + if (EDIT.equals(e.getActionCommand())) + { + // The user has clicked the cell, so + // bring up the dialog. + if (currentColor.isSimpleColour()) + { + // bring up simple color chooser + button.setBackground(currentColor.getColour()); + colorChooser.setColor(currentColor.getColour()); + dialog.setVisible(true); + } + else + { + // bring up graduated chooser. + chooser = new FeatureTypeSettings(me.fr, type); + chooser.setRequestFocusEnabled(true); + chooser.requestFocus(); + chooser.addActionListener(this); + chooser.showTab(true); + } + // Make the renderer reappear. + fireEditingStopped(); + + } + else + { + if (currentColor.isSimpleColour()) + { + /* + * read off colour picked in colour chooser after OK pressed + */ + currentColor = new FeatureColour(colorChooser.getColor()); + me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN); + } + else + { + /* + * after OK in variable colour dialog, any changes to colour + * (or filters!) are already set in FeatureRenderer, so just + * update table data without triggering updateFeatureRenderer + */ + currentColor = fr.getFeatureColours().get(type); + KeyedMatcherSetI currentFilter = me.fr.getFeatureFilter(type); + if (currentFilter == null) + { + currentFilter = new KeyedMatcherSet(); + } + Object[] data = ((FeatureTableModel) table.getModel()) + .getData()[rowSelected]; + data[COLOUR_COLUMN] = currentColor; + data[FILTER_COLUMN] = currentFilter; + } + fireEditingStopped(); + me.table.validate(); + } + } + + // Implement the one CellEditor method that AbstractCellEditor doesn't. + @Override + public Object getCellEditorValue() + { + return currentColor; + } + + // Implement the one method defined by TableCellEditor. + @Override + public Component getTableCellEditorComponent(JTable theTable, Object value, + boolean isSelected, int row, int column) + { + currentColor = (FeatureColourI) value; + this.rowSelected = row; + type = me.table.getValueAt(row, TYPE_COLUMN).toString(); + button.setOpaque(true); + button.setBackground(me.getBackground()); + if (!currentColor.isSimpleColour()) + { + JLabel btn = new JLabel(); + btn.setSize(button.getSize()); + FeatureSettings.renderGraduatedColor(btn, currentColor); + button.setBackground(btn.getBackground()); + button.setIcon(btn.getIcon()); + button.setText(btn.getText()); + } + else + { + button.setText(""); + button.setIcon(null); + button.setBackground(currentColor.getColour()); + } + return button; + } + } + + /** + * The cell editor for the Filter column. It displays the text of any filters + * for the feature type in that row (in full as a tooltip, possible abbreviated + * as display text). On click in the cell, opens the Feature Display Settings + * dialog at the Filters tab. + */ + class FilterEditor extends AbstractCellEditor + implements TableCellEditor, ActionListener + { + FeatureSettings me; + + KeyedMatcherSetI currentFilter; + + Point lastLocation; + + String type; + + JButton button; + + protected static final String EDIT = "edit"; + + int rowSelected = 0; + + public FilterEditor(FeatureSettings me) + { + this.me = me; + button = new JButton(); + button.setActionCommand(EDIT); + button.addActionListener(this); + button.setBorderPainted(false); + } + + /** + * Handles events from the editor button + */ + @Override + public void actionPerformed(ActionEvent e) + { + if (button == e.getSource()) + { + FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type); + chooser.addActionListener(this); + chooser.setRequestFocusEnabled(true); + chooser.requestFocus(); + if (lastLocation != null) + { + // todo open at its last position on screen + chooser.setBounds(lastLocation.x, lastLocation.y, + chooser.getWidth(), chooser.getHeight()); + chooser.validate(); + } + chooser.showTab(false); + fireEditingStopped(); + } + else if (e.getSource() instanceof Component) + { + + /* + * after OK in variable colour dialog, any changes to filter + * (or colours!) are already set in FeatureRenderer, so just + * update table data without triggering updateFeatureRenderer + */ + FeatureColourI currentColor = fr.getFeatureColours().get(type); + currentFilter = me.fr.getFeatureFilter(type); + if (currentFilter == null) + { + currentFilter = new KeyedMatcherSet(); + } + Object[] data = ((FeatureTableModel) table.getModel()) + .getData()[rowSelected]; + data[COLOUR_COLUMN] = currentColor; + data[FILTER_COLUMN] = currentFilter; + fireEditingStopped(); + me.table.validate(); + } + } + + @Override + public Object getCellEditorValue() + { + return currentFilter; + } + + @Override + public Component getTableCellEditorComponent(JTable theTable, Object value, + boolean isSelected, int row, int column) + { + currentFilter = (KeyedMatcherSetI) value; + this.rowSelected = row; + type = me.table.getValueAt(row, TYPE_COLUMN).toString(); + button.setOpaque(true); + button.setBackground(me.getBackground()); + button.setText(currentFilter.toString()); + button.setToolTipText(currentFilter.toString()); + button.setIcon(null); + return button; + } + } } class FeatureIcon implements Icon @@ -2285,125 +2091,3 @@ class FeatureIcon implements Icon } } } - -class ColorEditor extends AbstractCellEditor - implements TableCellEditor, ActionListener -{ - FeatureSettings me; - - FeatureColourI currentColor; - - FeatureColourChooser chooser; - - String type; - - JButton button; - - JColorChooser colorChooser; - - JDialog dialog; - - protected static final String EDIT = "edit"; - - int selectedRow = 0; - - public ColorEditor(FeatureSettings me) - { - this.me = me; - // Set up the editor (from the table's point of view), - // which is a button. - // This button brings up the color chooser dialog, - // which is the editor from the user's point of view. - button = new JButton(); - button.setActionCommand(EDIT); - button.addActionListener(this); - button.setBorderPainted(false); - // Set up the dialog that the button brings up. - colorChooser = new JColorChooser(); - dialog = JColorChooser.createDialog(button, - MessageManager.getString("label.select_new_colour"), true, // modal - colorChooser, this, // OK button handler - null); // no CANCEL button handler - } - - /** - * Handles events from the editor button and from the dialog's OK button. - */ - @Override - public void actionPerformed(ActionEvent e) - { - - if (EDIT.equals(e.getActionCommand())) - { - // The user has clicked the cell, so - // bring up the dialog. - if (currentColor.isSimpleColour()) - { - // bring up simple color chooser - button.setBackground(currentColor.getColour()); - colorChooser.setColor(currentColor.getColour()); - dialog.setVisible(true); - } - else - { - // bring up graduated chooser. - chooser = new FeatureColourChooser(me.fr, type); - chooser.setRequestFocusEnabled(true); - chooser.requestFocus(); - chooser.addActionListener(this); - } - // Make the renderer reappear. - fireEditingStopped(); - - } - else - { // User pressed dialog's "OK" button. - if (currentColor.isSimpleColour()) - { - currentColor = new FeatureColour(colorChooser.getColor()); - } - else - { - currentColor = chooser.getLastColour(); - } - me.table.setValueAt(getCellEditorValue(), selectedRow, 1); - fireEditingStopped(); - me.table.validate(); - } - } - - // Implement the one CellEditor method that AbstractCellEditor doesn't. - @Override - public Object getCellEditorValue() - { - return currentColor; - } - - // Implement the one method defined by TableCellEditor. - @Override - public Component getTableCellEditorComponent(JTable table, Object value, - boolean isSelected, int row, int column) - { - currentColor = (FeatureColourI) value; - this.selectedRow = row; - type = me.table.getValueAt(row, 0).toString(); - button.setOpaque(true); - button.setBackground(me.getBackground()); - if (!currentColor.isSimpleColour()) - { - JLabel btn = new JLabel(); - btn.setSize(button.getSize()); - FeatureSettings.renderGraduatedColor(btn, currentColor); - button.setBackground(btn.getBackground()); - button.setIcon(btn.getIcon()); - button.setText(btn.getText()); - } - else - { - button.setText(""); - button.setIcon(null); - button.setBackground(currentColor.getColour()); - } - return button; - } -} diff --git a/src/jalview/gui/FeatureTypeSettings.java b/src/jalview/gui/FeatureTypeSettings.java new file mode 100644 index 0000000..e280091 --- /dev/null +++ b/src/jalview/gui/FeatureTypeSettings.java @@ -0,0 +1,1548 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.gui; + +import jalview.api.AlignmentViewPanel; +import jalview.api.FeatureColourI; +import jalview.datamodel.GraphLine; +import jalview.datamodel.features.FeatureAttributes; +import jalview.datamodel.features.FeatureAttributes.Datatype; +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.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.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JColorChooser; +import javax.swing.JComboBox; +import javax.swing.JLabel; +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; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.plaf.basic.BasicArrowButton; + +/** + * A dialog where the user can configure colour scheme, and any filters, for one + * feature type + *

    + * (Was FeatureColourChooser prior to Jalview 1.11, renamed with the addition of + * filter options) + */ +public class FeatureTypeSettings extends JalviewDialog +{ + private static final int RADIO_WIDTH = 130; + + private static final String COLON = ":"; + + private static final int MAX_TOOLTIP_LENGTH = 50; + + private static final int NO_COLOUR_OPTION = 0; + + private static final int MIN_COLOUR_OPTION = 1; + + private static final int MAX_COLOUR_OPTION = 2; + + private static final int ABOVE_THRESHOLD_OPTION = 1; + + private static final int BELOW_THRESHOLD_OPTION = 2; + + /* + * FeatureRenderer holds colour scheme and filters for feature types + */ + private final FeatureRenderer fr; // todo refactor to allow interface type here + + /* + * the view panel to update when settings change + */ + private final AlignmentViewPanel ap; + + private final String featureType; + + /* + * the colour and filters to reset to on Cancel + */ + private final FeatureColourI originalColour; + + private final KeyedMatcherSetI originalFilter; + + /* + * set flag to true when setting values programmatically, + * to avoid invocation of action handlers + */ + private boolean adjusting = false; + + private float min; + + private float max; + + private float scaleFactor; + + /* + * radio button group, to select what to colour by: + * simple colour, by category (text), or graduated + */ + private JRadioButton simpleColour = new JRadioButton(); + + private JRadioButton byCategory = new JRadioButton(); + + private JRadioButton graduatedColour = new JRadioButton(); + + private JPanel singleColour = new JPanel(); + + private JPanel minColour = new JPanel(); + + private JPanel maxColour = new JPanel(); + + private JComboBox threshold = new JComboBox<>(); + + private JSlider slider = new JSlider(); + + private JTextField thresholdValue = new JTextField(20); + + private JCheckBox thresholdIsMin = new JCheckBox(); + + private GraphLine threshline; + + private ActionListener featureSettings = null; + + private ActionListener changeColourAction; + + /* + * choice of option for 'colour for no value' + */ + private JComboBox noValueCombo; + + /* + * choice of what to colour by text (Label or attribute) + */ + private JComboBox colourByTextCombo; + + /* + * choice of what to colour by range (Score or attribute) + */ + private JComboBox colourByRangeCombo; + + private JRadioButton andFilters; + + private JRadioButton orFilters; + + /* + * filters for the currently selected feature type + */ + private List filters; + + // set white normally, black to debug layout + private Color debugBorderColour = Color.white; + + private JPanel chooseFiltersPanel; + + private JTabbedPane tabbedPane; + + /** + * Constructor + * + * @param frender + * @param theType + */ + 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(); + } catch (Exception ex) + { + 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); + + waitForInput(); + } + + /** + * Configures the widgets on the Colours tab according to the current feature + * colour scheme + */ + private void updateColoursTab() + { + FeatureColourI fc = fr.getFeatureColours().get(featureType); + + /* + * suppress action handling while updating values programmatically + */ + adjusting = true; + try + { + /* + * single colour + */ + if (fc.isSimpleColour()) + { + simpleColour.setSelected(true); + singleColour.setBackground(fc.getColour()); + singleColour.setForeground(fc.getColour()); + } + + /* + * colour by text (Label or attribute text) + */ + if (fc.isColourByLabel()) + { + byCategory.setSelected(true); + colourByTextCombo.setEnabled(colourByTextCombo.getItemCount() > 1); + if (fc.isColourByAttribute()) + { + String[] attributeName = fc.getAttributeName(); + colourByTextCombo + .setSelectedItem(toAttributeDisplayName(attributeName)); + } + else + { + colourByTextCombo + .setSelectedItem(MessageManager.getString("label.label")); + } + } + else + { + colourByTextCombo.setEnabled(false); + } + + if (!fc.isGraduatedColour()) + { + colourByRangeCombo.setEnabled(false); + minColour.setEnabled(false); + maxColour.setEnabled(false); + noValueCombo.setEnabled(false); + threshold.setEnabled(false); + slider.setEnabled(false); + thresholdValue.setEnabled(false); + thresholdIsMin.setEnabled(false); + return; + } + + /* + * Graduated colour, by score or attribute value range + */ + graduatedColour.setSelected(true); + colourByRangeCombo.setEnabled(colourByRangeCombo.getItemCount() > 1); + minColour.setEnabled(true); + maxColour.setEnabled(true); + noValueCombo.setEnabled(true); + threshold.setEnabled(true); + minColour.setBackground(fc.getMinColour()); + maxColour.setBackground(fc.getMaxColour()); + + if (fc.isColourByAttribute()) + { + String[] attributeName = fc.getAttributeName(); + colourByRangeCombo + .setSelectedItem(toAttributeDisplayName(attributeName)); + } + else + { + colourByRangeCombo + .setSelectedItem(MessageManager.getString("label.score")); + } + Color noColour = fc.getNoColour(); + if (noColour == null) + { + noValueCombo.setSelectedIndex(NO_COLOUR_OPTION); + } + else if (noColour.equals(fc.getMinColour())) + { + noValueCombo.setSelectedIndex(MIN_COLOUR_OPTION); + } + else if (noColour.equals(fc.getMaxColour())) + { + noValueCombo.setSelectedIndex(MAX_COLOUR_OPTION); + } + + /* + * update min-max scaling if there is a range to work with, + * else disable the widgets (this shouldn't happen if only + * valid options are offered in the combo box) + */ + scaleFactor = (max == min) ? 1f : 100f / (max - min); + float range = (max - min) * scaleFactor; + slider.setMinimum((int) (min * scaleFactor)); + slider.setMaximum((int) (max * scaleFactor)); + slider.setMajorTickSpacing((int) (range / 10f)); + + threshline = new GraphLine((max - min) / 2f, "Threshold", + Color.black); + threshline.value = fc.getThreshold(); + + if (fc.hasThreshold()) + { + threshold.setSelectedIndex( + fc.isAboveThreshold() ? ABOVE_THRESHOLD_OPTION + : BELOW_THRESHOLD_OPTION); + slider.setEnabled(true); + slider.setValue((int) (fc.getThreshold() * scaleFactor)); + thresholdValue.setText(String.valueOf(getRoundedSliderValue())); + thresholdValue.setEnabled(true); + thresholdIsMin.setEnabled(true); + } + else + { + slider.setEnabled(false); + thresholdValue.setEnabled(false); + thresholdIsMin.setEnabled(false); + } + thresholdIsMin.setSelected(!fc.isAutoScaled()); + } finally + { + adjusting = false; + } + } + + /** + * Configures the initial layout + */ + private void initialise() + { + this.setLayout(new BorderLayout()); + tabbedPane = new JTabbedPane(); + this.add(tabbedPane, BorderLayout.CENTER); + + /* + * an ActionListener that applies colour changes + */ + changeColourAction = new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + colourChanged(true); + } + }; + + /* + * first tab: colour options + */ + JPanel coloursPanel = initialiseColoursPanel(); + tabbedPane.addTab(MessageManager.getString("action.colour"), + coloursPanel); + + /* + * second tab: filter options + */ + JPanel filtersPanel = initialiseFiltersPanel(); + tabbedPane.addTab(MessageManager.getString("label.filters"), + filtersPanel); + + JPanel okCancelPanel = initialiseOkCancelPanel(); + + this.add(okCancelPanel, BorderLayout.SOUTH); + } + + /** + * Updates the min-max range if Colour By selected item is Score, or an + * attribute, with a min-max range + */ + protected void updateMinMax() + { + if (!graduatedColour.isSelected()) + { + return; + } + + float[] minMax = null; + String colourBy = (String) colourByRangeCombo.getSelectedItem(); + if (MessageManager.getString("label.score").equals(colourBy)) + { + 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]; + } + } + + /** + * Lay out fields for graduated colour (by score or attribute value) + * + * @return + */ + private JPanel initialiseGraduatedColourPanel() + { + JPanel graduatedColourPanel = new JPanel(); + graduatedColourPanel.setLayout( + new BoxLayout(graduatedColourPanel, BoxLayout.Y_AXIS)); + JvSwingUtils.createTitledBorder(graduatedColourPanel, + MessageManager.getString("label.graduated_colour"), true); + graduatedColourPanel.setBackground(Color.white); + + /* + * first row: graduated colour radio button, score/attribute drop-down + */ + JPanel graduatedChoicePanel = new JPanel( + new FlowLayout(FlowLayout.LEFT)); + graduatedChoicePanel.setBackground(Color.white); + graduatedColour = new JRadioButton( + MessageManager.getString("label.by_range_of") + COLON); + graduatedColour.setPreferredSize(new Dimension(RADIO_WIDTH, 20)); + graduatedColour.addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + if (graduatedColour.isSelected()) + { + colourChanged(true); + } + } + }); + graduatedChoicePanel.add(graduatedColour); + + List attNames = FeatureAttributes.getInstance() + .getAttributes(featureType); + colourByRangeCombo = populateAttributesDropdown(attNames, true, false); + colourByRangeCombo.addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + colourChanged(true); + } + }); + + /* + * disable graduated colour option if no range found + */ + graduatedColour.setEnabled(colourByRangeCombo.getItemCount() > 0); + + graduatedChoicePanel.add(colourByRangeCombo); + graduatedColourPanel.add(graduatedChoicePanel); + + /* + * second row - min/max/no colours + */ + JPanel colourRangePanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + colourRangePanel.setBackground(Color.white); + graduatedColourPanel.add(colourRangePanel); + + 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()) + { + showColourChooser(minColour, "label.select_colour_minimum_value"); + } + } + }); + + 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()) + { + showColourChooser(maxColour, "label.select_colour_maximum_value"); + } + } + }); + 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 + */ + FeatureColourI fc = fr.getFeatureColours().get(featureType); + Color bg = fc.isSimpleColour() ? fc.getColour() : Color.BLACK; + maxColour.setBackground(bg); + minColour.setBackground(ColorUtils.bleachColour(bg, 0.9f)); + + noValueCombo = new JComboBox<>(); + noValueCombo.addItem(MessageManager.getString("label.no_colour")); + noValueCombo.addItem(MessageManager.getString("label.min_colour")); + noValueCombo.addItem(MessageManager.getString("label.max_colour")); + noValueCombo.addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + colourChanged(true); + } + }); + + JLabel minText = new JLabel( + MessageManager.getString("label.min_value") + COLON); + minText.setFont(JvSwingUtils.getLabelFont()); + JLabel maxText = new JLabel( + MessageManager.getString("label.max_value") + COLON); + maxText.setFont(JvSwingUtils.getLabelFont()); + JLabel noText = new JLabel( + MessageManager.getString("label.no_value") + COLON); + noText.setFont(JvSwingUtils.getLabelFont()); + + colourRangePanel.add(minText); + colourRangePanel.add(minColour); + colourRangePanel.add(maxText); + colourRangePanel.add(maxColour); + colourRangePanel.add(noText); + colourRangePanel.add(noValueCombo); + + /* + * third row - threshold options and value + */ + JPanel thresholdPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + thresholdPanel.setBackground(Color.white); + graduatedColourPanel.add(thresholdPanel); + + threshold.addActionListener(changeColourAction); + threshold.setToolTipText(MessageManager + .getString("label.threshold_feature_display_by_score")); + threshold.addItem(MessageManager + .getString("label.threshold_feature_no_threshold")); // index 0 + threshold.addItem(MessageManager + .getString("label.threshold_feature_above_threshold")); // index 1 + threshold.addItem(MessageManager + .getString("label.threshold_feature_below_threshold")); // index 2 + + thresholdValue.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + thresholdValue_actionPerformed(); + } + }); + thresholdValue.addFocusListener(new FocusAdapter() + { + @Override + public void focusLost(FocusEvent e) + { + thresholdValue_actionPerformed(); + } + }); + slider.setPaintLabels(false); + slider.setPaintTicks(true); + slider.setBackground(Color.white); + slider.setEnabled(false); + slider.setOpaque(false); + slider.setPreferredSize(new Dimension(100, 32)); + slider.setToolTipText( + MessageManager.getString("label.adjust_threshold")); + + slider.addChangeListener(new ChangeListener() + { + @Override + public void stateChanged(ChangeEvent evt) + { + if (!adjusting) + { + thresholdValue + .setText(String.valueOf(slider.getValue() / scaleFactor)); + sliderValueChanged(); + } + } + }); + slider.addMouseListener(new MouseAdapter() + { + @Override + public void mouseReleased(MouseEvent evt) + { + /* + * only update Overview and/or structure colouring + * when threshold slider drag ends (mouse up) + */ + if (ap != null) + { + ap.paintAlignment(true, true); + } + } + }); + + thresholdValue.setEnabled(false); + thresholdValue.setColumns(7); + + thresholdPanel.add(threshold); + thresholdPanel.add(slider); + thresholdPanel.add(thresholdValue); + + thresholdIsMin.setBackground(Color.white); + thresholdIsMin + .setText(MessageManager.getString("label.threshold_minmax")); + thresholdIsMin.setToolTipText(MessageManager + .getString("label.toggle_absolute_relative_display_threshold")); + thresholdIsMin.addActionListener(changeColourAction); + thresholdPanel.add(thresholdIsMin); + + return graduatedColourPanel; + } + + /** + * Lay out OK and Cancel buttons + * + * @return + */ + private JPanel initialiseOkCancelPanel() + { + JPanel okCancelPanel = new JPanel(); + // okCancelPanel.setBackground(Color.white); + okCancelPanel.add(ok); + okCancelPanel.add(cancel); + return okCancelPanel; + } + + /** + * Lay out Colour options panel, containing + *

      + *
    • plain colour, with colour picker
    • + *
    • colour by text, with choice of Label or other attribute
    • + *
    • colour by range, of score or other attribute, when available
    • + *
    + * + * @return + */ + private JPanel initialiseColoursPanel() + { + JPanel colourByPanel = new JPanel(); + colourByPanel.setLayout(new BoxLayout(colourByPanel, BoxLayout.Y_AXIS)); + + /* + * 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( + MessageManager.getString("label.simple_colour")); + simpleColour.setPreferredSize(new Dimension(RADIO_WIDTH, 20)); + simpleColour.addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + if (simpleColour.isSelected() && !adjusting) + { + showColourChooser(singleColour, "label.select_colour"); + } + } + + }); + + singleColour.setFont(JvSwingUtils.getLabelFont()); + singleColour.setBorder(BorderFactory.createLineBorder(Color.black)); + singleColour.setPreferredSize(new Dimension(40, 20)); + singleColour.addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent e) + { + if (simpleColour.isSelected()) + { + showColourChooser(singleColour, "label.select_colour"); + } + } + }); + simpleColourPanel.add(simpleColour); // radio button + simpleColourPanel.add(singleColour); // colour picker button + + /* + * colour by text (category) radio button and drop-down choice list + */ + JPanel byTextPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + byTextPanel.setBackground(Color.white); + JvSwingUtils.createTitledBorder(byTextPanel, + MessageManager.getString("label.colour_by_text"), true); + colourByPanel.add(byTextPanel); + byCategory = new JRadioButton( + MessageManager.getString("label.by_text_of") + COLON); + byCategory.setPreferredSize(new Dimension(RADIO_WIDTH, 20)); + byCategory.addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + if (byCategory.isSelected()) + { + colourChanged(true); + } + } + }); + byTextPanel.add(byCategory); + + List attNames = FeatureAttributes.getInstance() + .getAttributes(featureType); + colourByTextCombo = populateAttributesDropdown(attNames, false, true); + colourByTextCombo.addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + colourChanged(true); + } + }); + byTextPanel.add(colourByTextCombo); + + /* + * graduated colour panel + */ + JPanel graduatedColourPanel = initialiseGraduatedColourPanel(); + colourByPanel.add(graduatedColourPanel); + + /* + * 3 radio buttons select between simple colour, + * by category (text), or graduated + */ + ButtonGroup bg = new ButtonGroup(); + bg.add(simpleColour); + bg.add(byCategory); + bg.add(graduatedColour); + + return colourByPanel; + } + + private void showColourChooser(JPanel colourPanel, String key) + { + Color col = JColorChooser.showDialog(this, + MessageManager.getString(key), colourPanel.getBackground()); + if (col != null) + { + colourPanel.setBackground(col); + colourPanel.setForeground(col); + } + colourPanel.repaint(); + colourChanged(true); + } + + /** + * 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 + */ + void colourChanged(boolean updateStructsAndOverview) + { + if (adjusting) + { + /* + * ignore action handlers while setting values programmatically + */ + return; + } + + /* + * ensure min-max range is for the latest choice of + * 'graduated colour by' + */ + updateMinMax(); + + FeatureColourI acg = makeColourFromInputs(); + + /* + * save the colour, and repaint stuff + */ + fr.setColour(featureType, acg); + ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview); + + updateColoursTab(); + } + + /** + * Converts the input values into an instance of FeatureColour + * + * @return + */ + 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 + */ + 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 + { + thresh = Float.valueOf(thresholdValue.getText()); + } catch (NumberFormatException e) + { + // 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(); + if (thresholdIsMin.isSelected() + && thresholdOption == ABOVE_THRESHOLD_OPTION) + { + minValue = thresh; + } + if (thresholdIsMin.isSelected() + && thresholdOption == BELOW_THRESHOLD_OPTION) + { + maxValue = thresh; + } + + /* + * make the graduated colour + */ + FeatureColourI fc = new FeatureColour(minColour.getBackground(), + maxColour.getBackground(), noColour, minValue, maxValue); + + /* + * set attribute to colour by if selected + */ + String byWhat = (String) colourByRangeCombo.getSelectedItem(); + if (!MessageManager.getString("label.score").equals(byWhat)) + { + fc.setAttributeName(fromAttributeDisplayName(byWhat)); + } + + /* + * set threshold options and 'autoscaled' which is + * false if 'threshold is min/max' is selected + * else true (colour range is on actual range of values) + */ + fc.setThreshold(thresh); + fc.setAutoScaled(!thresholdIsMin.isSelected()); + fc.setAboveThreshold(thresholdOption == ABOVE_THRESHOLD_OPTION); + fc.setBelowThreshold(thresholdOption == BELOW_THRESHOLD_OPTION); + + if (threshline == null) + { + /* + * todo not yet implemented: visual indication of feature threshold + */ + threshline = new GraphLine((max - min) / 2f, "Threshold", + Color.black); + } + + 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() + { + if (this.featureSettings != null) + { + featureSettings.actionPerformed(new ActionEvent(this, 0, "CLOSED")); + } + } + + /** + * Action on OK is just to dismiss the dialog - any changes have already been + * applied + */ + @Override + public void okPressed() + { + } + + /** + * Action on Cancel is to restore colour scheme and filters as they were when + * the dialog was opened + */ + @Override + public void cancelPressed() + { + fr.setColour(featureType, originalColour); + fr.setFeatureFilter(featureType, originalFilter); + ap.paintAlignment(true, true); + } + + /** + * Action on text entry of a threshold value + */ + protected void thresholdValue_actionPerformed() + { + try + { + adjusting = true; + float f = Float.parseFloat(thresholdValue.getText()); + 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); + } catch (NumberFormatException ex) + { + thresholdValue.setBackground(Color.red); // not ok + } finally + { + adjusting = false; + } + } + + /** + * Action on change of threshold slider value. This may be done interactively + * (by moving the slider), or programmatically (to update the slider after + * manual input of a threshold value). + */ + protected void sliderValueChanged() + { + threshline.value = getRoundedSliderValue(); + + /* + * repaint alignment, but not Overview or structure, + * to avoid overload while dragging the slider + */ + colourChanged(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 + * + * @return + */ + private float getRoundedSliderValue() + { + int value = slider.getValue(); + float f = value == slider.getMaximum() ? max + : (value == slider.getMinimum() ? min : value / scaleFactor); + return f; + } + + void addActionListener(ActionListener listener) + { + if (featureSettings != null) + { + System.err.println( + "IMPLEMENTATION ISSUE: overwriting action listener for FeatureColourChooser"); + } + featureSettings = listener; + } + + /** + * 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. + *

    + * Where metadata is available with a description for an attribute, that is + * added as a tooltip. + *

    + * Attribute names may be 'simple' e.g. "AC" or 'compound' e.g. {"CSQ", + * "Allele"}. Compound names are rendered for display as (e.g.) CSQ:Allele. + *

    + * This method does not add any ActionListener to the JComboBox. + * + * @param attNames + * @param withRange + * @param withText + */ + protected JComboBox populateAttributesDropdown( + List attNames, boolean withRange, boolean withText) + { + List displayAtts = new ArrayList<>(); + List tooltips = new ArrayList<>(); + + if (withText) + { + displayAtts.add(MessageManager.getString("label.label")); + 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")); + } + } + + FeatureAttributes fa = FeatureAttributes.getInstance(); + for (String[] attName : attNames) + { + float[] minMax = fa.getMinMax(featureType, attName); + boolean hasRange = minMax != null && minMax[0] != minMax[1]; + if (!withText && !hasRange) + { + continue; + } + displayAtts.add(toAttributeDisplayName(attName)); + 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(displayAtts, tooltips); + + return attCombo; + } + + /** + * Populates initial layout of the feature attribute filters panel + */ + private JPanel initialiseFiltersPanel() + { + filters = new ArrayList<>(); + + JPanel filtersPanel = new JPanel(); + filtersPanel.setLayout(new BoxLayout(filtersPanel, BoxLayout.Y_AXIS)); + filtersPanel.setBackground(Color.white); + JvSwingUtils.createTitledBorder(filtersPanel, + MessageManager.getString("label.filters"), true); + + JPanel andOrPanel = initialiseAndOrPanel(); + filtersPanel.add(andOrPanel); + + /* + * panel with filters - populated by refreshFiltersDisplay + */ + chooseFiltersPanel = new JPanel(); + LayoutManager box = new BoxLayout(chooseFiltersPanel, BoxLayout.Y_AXIS); + chooseFiltersPanel.setLayout(box); + filtersPanel.add(chooseFiltersPanel); + + return filtersPanel; + } + + /** + * Lays out the panel with radio buttons to AND or OR filter conditions + * + * @return + */ + private JPanel initialiseAndOrPanel() + { + 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() + { + @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); + return andOrPanel; + } + + /** + * 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 + * removed, added or amended. + */ + private void updateFiltersTab() + { + /* + * clear the panel and list of filter conditions + */ + chooseFiltersPanel.removeAll(); + filters.clear(); + + /* + * look up attributes known for feature type + */ + List attNames = FeatureAttributes.getInstance() + .getAttributes(featureType); + + /* + * if this feature type has filters set, load them first + */ + KeyedMatcherSetI featureFilters = fr.getFeatureFilter(featureType); + if (featureFilters != null) + { + 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], "", + (String) null); + filters.add(noFilter); + + /* + * render the conditions in rows, each in its own JPanel + */ + int filterIndex = 0; + for (KeyedMatcherI 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)); + chooseFiltersPanel.add(row); + filterIndex++; + } + + this.validate(); + this.repaint(); + } + + /** + * A helper method that constructs a row (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 attName + * @param attNames + * @param cond + * @param pattern + * @param filterIndex + * @return + */ + protected JPanel addFilter(String[] attName, List attNames, + Condition cond, String pattern, int filterIndex) + { + JPanel filterRow = new JPanel(new FlowLayout(FlowLayout.LEFT)); + filterRow.setBackground(Color.white); + + /* + * drop-down choice of attribute, with description as a tooltip + * if we can obtain it + */ + final JComboBox attCombo = populateAttributesDropdown(attNames, + true, true); + 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 (attName == null) // the 'add a condition' row + { + attCombo.setSelectedIndex(0); + } + else + { + attCombo.setSelectedItem(toAttributeDisplayName(attName)); + } + attCombo.addItemListener(itemListener); + + filterRow.add(attCombo); + + /* + * drop-down choice of test condition + */ + populateConditions((String) attCombo.getSelectedItem(), cond, + condCombo); + 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 drawing '-' or 'x' + 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; + } + + /** + * 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. + * + * @param attName + * @param cond + * @param condCombo + */ + private void populateConditions(String attName, Condition cond, + JComboBox condCombo) + { + Datatype type = FeatureAttributes.getInstance().getDatatype(featureType, + attName); + if (MessageManager.getString("label.label").equals(attName)) + { + type = Datatype.Character; + } + else if (MessageManager.getString("label.score").equals(attName)) + { + type = Datatype.Number; + } + + for (Condition c : Condition.values()) + { + if ((c.isNumeric() && type != Datatype.Character) + || (!c.isNumeric() && type != Datatype.Number)) + { + condCombo.addItem(c); + } + } + + /* + * set the selected condition (does nothing if not in the list) + */ + if (cond != null) + { + condCombo.setSelectedItem(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. + *

    + * 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; + } + + /** + * 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(cond, pattern, + 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); + } + + /** + * 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 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(); + + 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.isEmpty() ? null : combined); + ap.paintAlignment(true, true); + + updateFiltersTab(); + } +} diff --git a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java index 6afec67..4797675 100644 --- a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java +++ b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java @@ -49,6 +49,17 @@ import java.util.concurrent.ConcurrentHashMap; public abstract class FeatureRendererModel implements jalview.api.FeatureRenderer { + /* + * column indices of fields in Feature Settings table + * todo: transfer valuers as data beans instead of Object[][] + */ + public static final int TYPE_COLUMN = 0; + + public static final int COLOUR_COLUMN = 1; + + public static final int FILTER_COLUMN = 2; + + public static final int SHOW_COLUMN = 3; /* * global transparency for feature @@ -720,9 +731,9 @@ public abstract class FeatureRendererModel { for (int i = 0; i < data.length; i++) { - String type = data[i][0].toString(); - setColour(type, (FeatureColourI) data[i][1]); - if (((Boolean) data[i][2]).booleanValue()) + String type = data[i][TYPE_COLUMN].toString(); + setColour(type, (FeatureColourI) data[i][COLOUR_COLUMN]); + if (((Boolean) data[i][SHOW_COLUMN]).booleanValue()) { av_featuresdisplayed.setVisible(type); } @@ -1124,8 +1135,13 @@ public abstract class FeatureRendererModel protected boolean featureMatchesFilters(SequenceFeature sf) { KeyedMatcherSetI filter = featureFilters.get(sf.getType()); - return filter == null ? true : filter.matches(key -> sf - .getValueAsString(key)); + // TODO temporary fudge for Score and Label + return filter == null ? true + : filter.matches( + key -> "Label".equals(key[0]) ? sf.getDescription() + : ("Score".equals(key[0]) + ? String.valueOf(sf.getScore()) + : sf.getValueAsString(key))); } } -- 1.7.10.2