2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import jalview.api.FeatureColourI;
24 import jalview.datamodel.GraphLine;
25 import jalview.datamodel.features.FeatureAttributes;
26 import jalview.schemes.FeatureColour;
27 import jalview.util.MessageManager;
29 import java.awt.Color;
30 import java.awt.Dimension;
31 import java.awt.FlowLayout;
32 import java.awt.event.ActionEvent;
33 import java.awt.event.ActionListener;
34 import java.awt.event.FocusAdapter;
35 import java.awt.event.FocusEvent;
36 import java.awt.event.ItemEvent;
37 import java.awt.event.ItemListener;
38 import java.awt.event.MouseAdapter;
39 import java.awt.event.MouseEvent;
40 import java.util.ArrayList;
41 import java.util.List;
43 import javax.swing.BorderFactory;
44 import javax.swing.BoxLayout;
45 import javax.swing.ButtonGroup;
46 import javax.swing.JCheckBox;
47 import javax.swing.JColorChooser;
48 import javax.swing.JComboBox;
49 import javax.swing.JLabel;
50 import javax.swing.JPanel;
51 import javax.swing.JRadioButton;
52 import javax.swing.JSlider;
53 import javax.swing.JTextField;
54 import javax.swing.border.LineBorder;
55 import javax.swing.event.ChangeEvent;
56 import javax.swing.event.ChangeListener;
58 public class FeatureColourChooser extends JalviewDialog
60 private static final String COLON = ":";
62 private static final int MAX_TOOLTIP_LENGTH = 50;
64 private static int NO_COLOUR_OPTION = 0;
66 private static int MIN_COLOUR_OPTION = 1;
68 private static int MAX_COLOUR_OPTION = 2;
70 private FeatureRenderer fr;
72 private FeatureColourI cs;
74 private FeatureColourI oldcs;
76 private AlignmentPanel ap;
78 private boolean adjusting = false;
84 private float scaleFactor;
86 private String type = null;
88 private JPanel minColour = new JPanel();
90 private JPanel maxColour = new JPanel();
92 private Color noColour;
94 private JComboBox<String> threshold = new JComboBox<>();
96 private JSlider slider = new JSlider();
98 private JTextField thresholdValue = new JTextField(20);
100 private JCheckBox thresholdIsMin = new JCheckBox();
102 private GraphLine threshline;
104 private Color oldmaxColour;
106 private Color oldminColour;
108 private Color oldNoColour;
110 private ActionListener colourEditor = null;
113 * radio buttons to select what to colour by
114 * label, attribute text, score, attribute value
116 private JRadioButton byDescription = new JRadioButton();
118 private JRadioButton byAttributeText = new JRadioButton();
120 private JRadioButton byScore = new JRadioButton();
122 private JRadioButton byAttributeValue = new JRadioButton();
124 private ActionListener changeColourAction;
126 private ActionListener changeMinMaxAction;
129 * choice of option for 'colour for no value'
131 private JComboBox<String> noValueCombo;
134 * choice of attribute (if any) for 'colour by text'
136 private JComboBox<String> textAttributeCombo;
139 * choice of attribute (if any) for 'colour by value'
141 private JComboBox<String> valueAttributeCombo;
149 public FeatureColourChooser(FeatureRenderer frender, String theType)
151 this(frender, false, theType);
155 * Constructor, with option to make a blocking dialog (has to complete in the
156 * AWT event queue thread). Currently this option is always set to false.
162 FeatureColourChooser(FeatureRenderer frender, boolean blocking,
168 String title = MessageManager.formatMessage("label.variable_color_for",
169 new String[] { theType });
170 initDialogFrame(this, true, blocking, title, 470, 300);
172 slider.addChangeListener(new ChangeListener()
175 public void stateChanged(ChangeEvent evt)
179 thresholdValue.setText((slider.getValue() / scaleFactor) + "");
180 sliderValueChanged();
184 slider.addMouseListener(new MouseAdapter()
187 public void mouseReleased(MouseEvent evt)
190 * only update Overview and/or structure colouring
191 * when threshold slider drag ends (mouse up)
195 ap.paintAlignment(true, true);
200 // todo move all threshold setup inside a method
201 float mm[] = fr.getMinMax().get(theType)[0];
206 * ensure scale factor allows a scaled range with
207 * 10 integer divisions ('ticks'); if we have got here,
208 * we should expect that max != min
210 scaleFactor = (max == min) ? 1f : 100f / (max - min);
212 oldcs = fr.getFeatureColours().get(theType);
213 if (!oldcs.isSimpleColour())
215 if (oldcs.isAutoScaled())
218 cs = new FeatureColour((FeatureColour) oldcs, min, max);
222 cs = new FeatureColour((FeatureColour) oldcs);
228 * promote original simple color to a graduated color
229 * - by score if there is a score range, else by label
231 Color bl = oldcs.getColour();
236 // original colour becomes the maximum colour
237 cs = new FeatureColour(Color.white, bl, mm[0], mm[1]);
238 cs.setColourByLabel(mm[0] == mm[1]);
240 minColour.setBackground(oldminColour = cs.getMinColour());
241 maxColour.setBackground(oldmaxColour = cs.getMaxColour());
242 noColour = cs.getNoColour();
249 } catch (Exception ex)
251 ex.printStackTrace();
256 * set the initial state of options on screen
258 if (cs.isColourByLabel())
260 if (cs.isColourByAttribute())
262 byAttributeText.setSelected(true);
263 textAttributeCombo.setEnabled(true);
264 String[] attributeName = cs.getAttributeName();
266 .setSelectedItem(toAttributeDisplayName(attributeName));
270 byDescription.setSelected(true);
271 textAttributeCombo.setEnabled(false);
276 if (cs.isColourByAttribute())
278 byAttributeValue.setSelected(true);
279 String[] attributeName = cs.getAttributeName();
281 .setSelectedItem(toAttributeDisplayName(attributeName));
282 valueAttributeCombo.setEnabled(true);
287 byScore.setSelected(true);
288 valueAttributeCombo.setEnabled(false);
292 if (noColour == null)
294 noValueCombo.setSelectedIndex(NO_COLOUR_OPTION);
296 else if (noColour.equals(oldminColour))
298 noValueCombo.setSelectedIndex(MIN_COLOUR_OPTION);
300 else if (noColour.equals(oldmaxColour))
302 noValueCombo.setSelectedIndex(MAX_COLOUR_OPTION);
305 threshline = new GraphLine((max - min) / 2f, "Threshold", Color.black);
306 threshline.value = cs.getThreshold();
308 if (cs.hasThreshold())
310 // initialise threshold slider and selector
311 threshold.setSelectedIndex(cs.isAboveThreshold() ? 1 : 2);
312 slider.setEnabled(true);
313 slider.setValue((int) (cs.getThreshold() * scaleFactor));
314 thresholdValue.setEnabled(true);
324 * Configures the initial layout
326 private void jbInit()
328 this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
329 this.setBackground(Color.white);
331 changeColourAction = new ActionListener()
334 public void actionPerformed(ActionEvent e)
340 changeMinMaxAction = new ActionListener()
343 public void actionPerformed(ActionEvent e)
357 JPanel detailsPanel = new JPanel();
358 detailsPanel.setLayout(new BoxLayout(detailsPanel, BoxLayout.Y_AXIS));
360 JPanel colourByTextPanel = initColourByTextPanel();
361 detailsPanel.add(colourByTextPanel);
363 JPanel colourByValuePanel = initColourByValuePanel();
364 detailsPanel.add(colourByValuePanel);
367 * 4 radio buttons select between colour by description, by
368 * attribute text, by score, or by attribute value
370 ButtonGroup bg = new ButtonGroup();
371 bg.add(byDescription);
372 bg.add(byAttributeText);
374 bg.add(byAttributeValue);
376 JPanel okCancelPanel = initOkCancelPanel();
378 this.add(detailsPanel);
379 this.add(okCancelPanel);
383 * Updates the min-max range for a change in choice of Colour by Score, or
384 * Colour by Attribute (value)
386 protected void updateMinMax()
388 float[] minMax = null;
389 if (byScore.isSelected())
391 minMax = fr.getMinMax().get(type)[0];
393 else if (byAttributeValue.isSelected())
395 String attName = (String) valueAttributeCombo.getSelectedItem();
396 String[] attNames = fromAttributeDisplayName(attName);
397 minMax = FeatureAttributes.getInstance().getMinMax(type, attNames);
403 scaleFactor = (max == min) ? 1f : 100f / (max - min);
404 slider.setValue((int) (min * scaleFactor));
409 * Lay out fields for graduated colour by value
413 protected JPanel initColourByValuePanel()
415 JPanel byValuePanel = new JPanel();
416 byValuePanel.setLayout(new BoxLayout(byValuePanel, BoxLayout.Y_AXIS));
417 JvSwingUtils.createItalicTitledBorder(byValuePanel,
418 MessageManager.getString("label.colour_by_value"), true);
419 byValuePanel.setBackground(Color.white);
422 * first row - choose colour by score or by attribute, choose attribute
424 JPanel byWhatPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
425 byWhatPanel.setBackground(Color.white);
426 byValuePanel.add(byWhatPanel);
428 byScore.setText(MessageManager.getString("label.score"));
429 byWhatPanel.add(byScore);
430 byScore.addActionListener(changeMinMaxAction);
432 byAttributeValue.setText(MessageManager.getString("label.attribute"));
433 byAttributeValue.addActionListener(changeMinMaxAction);
434 byWhatPanel.add(byAttributeValue);
436 List<String[]> attNames = FeatureAttributes.getInstance()
437 .getAttributes(type);
438 valueAttributeCombo = populateAttributesDropdown(type, attNames, true);
441 * if no numeric atttibutes found, disable colour by attribute value
443 if (valueAttributeCombo.getItemCount() == 0)
445 byAttributeValue.setEnabled(false);
448 byWhatPanel.add(valueAttributeCombo);
451 * second row - min/max/no colours
453 JPanel colourRangePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
454 colourRangePanel.setBackground(Color.white);
455 byValuePanel.add(colourRangePanel);
457 minColour.setFont(JvSwingUtils.getLabelFont());
458 minColour.setBorder(BorderFactory.createLineBorder(Color.black));
459 minColour.setPreferredSize(new Dimension(40, 20));
460 minColour.setToolTipText(MessageManager.getString("label.min_colour"));
461 minColour.addMouseListener(new MouseAdapter()
464 public void mousePressed(MouseEvent e)
466 if (minColour.isEnabled())
468 minColour_actionPerformed();
473 maxColour.setFont(JvSwingUtils.getLabelFont());
474 maxColour.setBorder(BorderFactory.createLineBorder(Color.black));
475 maxColour.setPreferredSize(new Dimension(40, 20));
476 maxColour.setToolTipText(MessageManager.getString("label.max_colour"));
477 maxColour.addMouseListener(new MouseAdapter()
480 public void mousePressed(MouseEvent e)
482 if (maxColour.isEnabled())
484 maxColour_actionPerformed();
488 maxColour.setBorder(new LineBorder(Color.black));
490 noValueCombo = new JComboBox<>();
491 noValueCombo.addItem(MessageManager.getString("label.no_colour"));
492 noValueCombo.addItem(MessageManager.getString("label.min_colour"));
493 noValueCombo.addItem(MessageManager.getString("label.max_colour"));
494 noValueCombo.addItemListener(new ItemListener()
497 public void itemStateChanged(ItemEvent e)
503 JLabel minText = new JLabel(MessageManager.getString("label.min_value"));
504 minText.setFont(JvSwingUtils.getLabelFont());
505 JLabel maxText = new JLabel(MessageManager.getString("label.max_value"));
506 maxText.setFont(JvSwingUtils.getLabelFont());
507 JLabel noText = new JLabel(MessageManager.getString("label.no_value"));
508 noText.setFont(JvSwingUtils.getLabelFont());
510 colourRangePanel.add(minText);
511 colourRangePanel.add(minColour);
512 colourRangePanel.add(maxText);
513 colourRangePanel.add(maxColour);
514 colourRangePanel.add(noText);
515 colourRangePanel.add(noValueCombo);
518 * third row - threshold options and value
520 JPanel thresholdPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
521 thresholdPanel.setBackground(Color.white);
522 byValuePanel.add(thresholdPanel);
524 threshold.addActionListener(changeColourAction);
525 threshold.setToolTipText(MessageManager
526 .getString("label.threshold_feature_display_by_score"));
527 threshold.addItem(MessageManager
528 .getString("label.threshold_feature_no_threshold")); // index 0
529 threshold.addItem(MessageManager
530 .getString("label.threshold_feature_above_threshold")); // index 1
531 threshold.addItem(MessageManager
532 .getString("label.threshold_feature_below_threshold")); // index 2
534 thresholdValue.addActionListener(new ActionListener()
537 public void actionPerformed(ActionEvent e)
539 thresholdValue_actionPerformed();
542 thresholdValue.addFocusListener(new FocusAdapter()
545 public void focusLost(FocusEvent e)
547 thresholdValue_actionPerformed();
550 slider.setPaintLabels(false);
551 slider.setPaintTicks(true);
552 slider.setBackground(Color.white);
553 slider.setEnabled(false);
554 slider.setOpaque(false);
555 slider.setPreferredSize(new Dimension(100, 32));
556 slider.setToolTipText(MessageManager
557 .getString("label.adjust_threshold"));
558 thresholdValue.setEnabled(false);
559 thresholdValue.setColumns(7);
561 thresholdPanel.add(threshold);
562 thresholdPanel.add(slider);
563 thresholdPanel.add(thresholdValue);
566 * 4th row - threshold is min / max
568 JPanel isMinMaxPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
569 isMinMaxPanel.setBackground(Color.white);
570 byValuePanel.add(isMinMaxPanel);
571 thresholdIsMin.setBackground(Color.white);
572 thresholdIsMin.setText(MessageManager
573 .getString("label.threshold_minmax"));
574 thresholdIsMin.setToolTipText(MessageManager
575 .getString("label.toggle_absolute_relative_display_threshold"));
576 thresholdIsMin.addActionListener(changeColourAction);
577 isMinMaxPanel.add(thresholdIsMin);
583 * Action on user choice of no / min / max colour to use when there is no
586 protected void setNoValueColour()
588 int i = noValueCombo.getSelectedIndex();
589 if (i == NO_COLOUR_OPTION)
593 else if (i == MIN_COLOUR_OPTION)
595 noColour = minColour.getBackground();
597 else if (i == MAX_COLOUR_OPTION)
599 noColour = maxColour.getBackground();
605 * Lay out OK and Cancel buttons
609 protected JPanel initOkCancelPanel()
611 JPanel okCancelPanel = new JPanel();
612 okCancelPanel.setBackground(Color.white);
613 okCancelPanel.add(ok);
614 okCancelPanel.add(cancel);
615 return okCancelPanel;
619 * Lay out Colour by Label and attribute choice elements
623 protected JPanel initColourByTextPanel()
625 JPanel byTextPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
626 byTextPanel.setBackground(Color.white);
627 JvSwingUtils.createItalicTitledBorder(byTextPanel,
628 MessageManager.getString("label.colour_by_text"), true);
630 byDescription.setText(MessageManager.getString("label.label"));
631 byDescription.setToolTipText(MessageManager
632 .getString("label.colour_by_label_tip"));
633 byDescription.addActionListener(changeColourAction);
634 byTextPanel.add(byDescription);
636 byAttributeText.setText(MessageManager.getString("label.attribute"));
637 byAttributeText.addActionListener(changeColourAction);
638 byTextPanel.add(byAttributeText);
640 List<String[]> attNames = FeatureAttributes.getInstance()
641 .getAttributes(type);
642 textAttributeCombo = populateAttributesDropdown(type, attNames, false);
643 byTextPanel.add(textAttributeCombo);
646 * disable colour by attribute if no attributes
648 if (attNames.isEmpty())
650 byAttributeText.setEnabled(false);
657 * Action on clicking the 'minimum colour' - open a colour chooser dialog, and
658 * set the selected colour (if the user does not cancel out of the dialog)
660 protected void minColour_actionPerformed()
662 Color col = JColorChooser.showDialog(this,
663 MessageManager.getString("label.select_colour_minimum_value"),
664 minColour.getBackground());
667 minColour.setBackground(col);
668 minColour.setForeground(col);
675 * Action on clicking the 'maximum colour' - open a colour chooser dialog, and
676 * set the selected colour (if the user does not cancel out of the dialog)
678 protected void maxColour_actionPerformed()
680 Color col = JColorChooser.showDialog(this,
681 MessageManager.getString("label.select_colour_maximum_value"),
682 maxColour.getBackground());
685 maxColour.setBackground(col);
686 maxColour.setForeground(col);
693 * Constructs and sets the selected colour options as the colour for the
694 * feature type, and repaints the alignment, and optionally the Overview
695 * and/or structure viewer if open
697 * @param updateStructsAndOverview
699 void changeColour(boolean updateStructsAndOverview)
701 // Check if combobox is still adjusting
707 boolean aboveThreshold = false;
708 boolean belowThreshold = false;
709 if (threshold.getSelectedIndex() == 1)
711 aboveThreshold = true;
713 else if (threshold.getSelectedIndex() == 2)
715 belowThreshold = true;
717 boolean hasThreshold = aboveThreshold || belowThreshold;
719 slider.setEnabled(true);
720 thresholdValue.setEnabled(true);
723 * make the feature colour
726 if (cs.isColourByLabel())
728 acg = new FeatureColour(oldminColour, oldmaxColour, min, max);
732 acg = new FeatureColour(oldminColour = minColour.getBackground(),
733 oldmaxColour = maxColour.getBackground(),
734 oldNoColour = noColour, min, max);
736 String attribute = null;
737 textAttributeCombo.setEnabled(false);
738 valueAttributeCombo.setEnabled(false);
739 if (byAttributeText.isSelected())
741 attribute = (String) textAttributeCombo.getSelectedItem();
742 textAttributeCombo.setEnabled(true);
743 acg.setAttributeName(fromAttributeDisplayName(attribute));
745 else if (byAttributeValue.isSelected())
747 attribute = (String) valueAttributeCombo.getSelectedItem();
748 valueAttributeCombo.setEnabled(true);
749 acg.setAttributeName(fromAttributeDisplayName(attribute));
753 acg.setAttributeName((String[]) null);
758 slider.setEnabled(false);
759 thresholdValue.setEnabled(false);
760 thresholdValue.setText("");
761 thresholdIsMin.setEnabled(false);
763 else if (threshline == null)
766 * todo not yet implemented: visual indication of feature threshold
768 threshline = new GraphLine((max - min) / 2f, "Threshold",
775 acg.setThreshold(threshline.value);
777 float range = (max - min) * scaleFactor;
779 slider.setMinimum((int) (min * scaleFactor));
780 slider.setMaximum((int) (max * scaleFactor));
781 // slider.setValue((int) (threshline.value * scaleFactor));
782 slider.setValue(Math.round(threshline.value * scaleFactor));
783 thresholdValue.setText(threshline.value + "");
784 slider.setMajorTickSpacing((int) (range / 10f));
785 slider.setEnabled(true);
786 thresholdValue.setEnabled(true);
787 thresholdIsMin.setEnabled(!byDescription.isSelected());
791 acg.setAboveThreshold(aboveThreshold);
792 acg.setBelowThreshold(belowThreshold);
793 if (thresholdIsMin.isSelected() && hasThreshold)
795 acg.setAutoScaled(false);
798 acg = new FeatureColour((FeatureColour) acg, threshline.value, max);
802 acg = new FeatureColour((FeatureColour) acg, min, threshline.value);
807 acg.setAutoScaled(true);
809 acg.setColourByLabel(byDescription.isSelected()
810 || byAttributeText.isSelected());
812 if (acg.isColourByLabel())
814 maxColour.setEnabled(false);
815 minColour.setEnabled(false);
816 noValueCombo.setEnabled(false);
817 maxColour.setBackground(this.getBackground());
818 maxColour.setForeground(this.getBackground());
819 minColour.setBackground(this.getBackground());
820 minColour.setForeground(this.getBackground());
824 maxColour.setEnabled(true);
825 minColour.setEnabled(true);
826 noValueCombo.setEnabled(true);
827 maxColour.setBackground(oldmaxColour);
828 maxColour.setForeground(oldmaxColour);
829 minColour.setBackground(oldminColour);
830 minColour.setForeground(oldminColour);
831 noColour = oldNoColour;
835 * save the colour, and repaint stuff
837 fr.setColour(type, acg);
839 ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview);
842 private String[] fromAttributeDisplayName(String attribute)
844 return attribute == null ? null : attribute.split(COLON);
848 protected void raiseClosed()
850 if (this.colourEditor != null)
852 colourEditor.actionPerformed(new ActionEvent(this, 0, "CLOSED"));
857 public void okPressed()
863 public void cancelPressed()
869 * Action when the user cancels the dialog. All previous settings should be
870 * restored and rendered on the alignment, and any linked Overview window or
875 fr.setColour(type, oldcs);
876 ap.paintAlignment(true, true);
881 * Action on text entry of a threshold value
883 protected void thresholdValue_actionPerformed()
887 float f = Float.parseFloat(thresholdValue.getText());
888 slider.setValue((int) (f * scaleFactor));
889 threshline.value = f;
892 * force repaint of any Overview window or structure
894 ap.paintAlignment(true, true);
895 } catch (NumberFormatException ex)
901 * Action on change of threshold slider value. This may be done interactively
902 * (by moving the slider), or programmatically (to update the slider after
903 * manual input of a threshold value).
905 protected void sliderValueChanged()
908 * squash rounding errors by forcing min/max of slider to
909 * actual min/max of feature score range
911 int value = slider.getValue();
912 threshline.value = value == slider.getMaximum() ? max
913 : (value == slider.getMinimum() ? min : value / scaleFactor);
914 cs.setThreshold(threshline.value);
917 * repaint alignment, but not Overview or structure,
918 * to avoid overload while dragging the slider
923 void addActionListener(ActionListener graduatedColorEditor)
925 if (colourEditor != null)
928 "IMPLEMENTATION ISSUE: overwriting action listener for FeatureColourChooser");
930 colourEditor = graduatedColorEditor;
934 * Answers the last colour setting selected by user - either oldcs (which may
935 * be a java.awt.Color) or the new GraduatedColor
939 FeatureColourI getLastColour()
949 * A helper method to build the drop-down choice of attributes for a feature.
950 * Where metadata is available with a description for an attribute, that is
951 * added as a tooltip. The list may optionally be restricted to attributes for
952 * which we hold a range of numerical values (so suitable candidates for a
953 * graduated colour scheme).
955 * Attribute names may be 'simple' e.g. "AC" or 'compound' e.g. {"CSQ",
956 * "Allele"}. Compound names are rendered for display as (e.g.) CSQ:Allele.
960 * @param withNumericRange
962 protected JComboBox<String> populateAttributesDropdown(
963 String featureType, List<String[]> attNames,
964 boolean withNumericRange)
966 List<String> validAtts = new ArrayList<>();
967 List<String> tooltips = new ArrayList<>();
969 FeatureAttributes fa = FeatureAttributes.getInstance();
970 for (String[] attName : attNames)
972 if (withNumericRange)
974 float[] minMax = fa.getMinMax(featureType, attName);
980 validAtts.add(toAttributeDisplayName(attName));
981 String desc = fa.getDescription(featureType, attName);
982 if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH)
984 desc = desc.substring(0, MAX_TOOLTIP_LENGTH) + "...";
986 tooltips.add(desc == null ? "" : desc);
989 JComboBox<String> attCombo = JvSwingUtils.buildComboWithTooltips(
990 validAtts, tooltips);
992 attCombo.addItemListener(new ItemListener()
995 public void itemStateChanged(ItemEvent e)
997 changeMinMaxAction.actionPerformed(null);
1001 if (validAtts.isEmpty())
1003 attCombo.setToolTipText(MessageManager
1004 .getString(withNumericRange ? "label.no_numeric_attributes"
1005 : "label.no_attributes"));
1011 private String toAttributeDisplayName(String[] attName)
1013 return attName == null ? "" : String.join(COLON, attName);