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 int MAX_TOOLTIP_LENGTH = 50;
62 private static int NO_COLOUR_OPTION = 0;
64 private static int MIN_COLOUR_OPTION = 1;
66 private static int MAX_COLOUR_OPTION = 2;
68 private FeatureRenderer fr;
70 private FeatureColourI cs;
72 private FeatureColourI oldcs;
74 private AlignmentPanel ap;
76 private boolean adjusting = false;
82 private float scaleFactor;
84 private String type = null;
86 private JPanel minColour = new JPanel();
88 private JPanel maxColour = new JPanel();
90 private Color noColour;
92 private JComboBox<String> threshold = new JComboBox<>();
94 private JSlider slider = new JSlider();
96 private JTextField thresholdValue = new JTextField(20);
98 private JCheckBox thresholdIsMin = new JCheckBox();
100 private GraphLine threshline;
102 private Color oldmaxColour;
104 private Color oldminColour;
106 private Color oldNoColour;
108 private ActionListener colourEditor = null;
111 * radio buttons to select what to colour by
112 * label, attribute text, score, attribute value
114 private JRadioButton byDescription = new JRadioButton();
116 private JRadioButton byAttributeText = new JRadioButton();
118 private JRadioButton byScore = new JRadioButton();
120 private JRadioButton byAttributeValue = new JRadioButton();
122 private ActionListener changeColourAction;
124 private ActionListener changeMinMaxAction;
127 * choice of option for 'colour for no value'
129 private JComboBox<String> noValueCombo;
132 * choice of attribute (if any) for 'colour by text'
134 private JComboBox<String> textAttributeCombo;
137 * choice of attribute (if any) for 'colour by value'
139 private JComboBox<String> valueAttributeCombo;
147 public FeatureColourChooser(FeatureRenderer frender, String theType)
149 this(frender, false, theType);
153 * Constructor, with option to make a blocking dialog (has to complete in the
154 * AWT event queue thread). Currently this option is always set to false.
160 FeatureColourChooser(FeatureRenderer frender, boolean blocking,
166 String title = MessageManager
167 .formatMessage("label.graduated_color_for_params", new String[]
169 initDialogFrame(this, true, blocking, title, 470, 300);
171 slider.addChangeListener(new ChangeListener()
174 public void stateChanged(ChangeEvent evt)
178 thresholdValue.setText((slider.getValue() / scaleFactor) + "");
179 sliderValueChanged();
183 slider.addMouseListener(new MouseAdapter()
186 public void mouseReleased(MouseEvent evt)
189 * only update Overview and/or structure colouring
190 * when threshold slider drag ends (mouse up)
194 ap.paintAlignment(true, true);
199 // todo move all threshold setup inside a method
200 float mm[] = fr.getMinMax().get(theType)[0];
205 * ensure scale factor allows a scaled range with
206 * 10 integer divisions ('ticks'); if we have got here,
207 * we should expect that max != min
209 scaleFactor = (max == min) ? 1f : 100f / (max - min);
211 oldcs = fr.getFeatureColours().get(theType);
212 if (!oldcs.isSimpleColour())
214 if (oldcs.isAutoScaled())
217 cs = new FeatureColour((FeatureColour) oldcs, min, max);
221 cs = new FeatureColour((FeatureColour) oldcs);
227 * promote original simple color to a graduated color
228 * - by score if there is a score range, else by label
230 Color bl = oldcs.getColour();
235 // original colour becomes the maximum colour
236 cs = new FeatureColour(Color.white, bl, mm[0], mm[1]);
237 cs.setColourByLabel(mm[0] == mm[1]);
239 minColour.setBackground(oldminColour = cs.getMinColour());
240 maxColour.setBackground(oldmaxColour = cs.getMaxColour());
241 noColour = cs.getNoColour();
248 } catch (Exception ex)
250 ex.printStackTrace();
255 * set the initial state of options on screen
257 if (cs.isColourByLabel())
259 if (cs.isColourByAttribute())
261 byAttributeText.setSelected(true);
262 textAttributeCombo.setEnabled(true);
263 textAttributeCombo.setSelectedItem(cs.getAttributeName());
267 byDescription.setSelected(true);
268 textAttributeCombo.setEnabled(false);
273 if (cs.isColourByAttribute())
275 byAttributeValue.setSelected(true);
276 String attributeName = cs.getAttributeName();
277 valueAttributeCombo.setSelectedItem(attributeName);
278 valueAttributeCombo.setEnabled(true);
283 byScore.setSelected(true);
284 valueAttributeCombo.setEnabled(false);
288 if (noColour == null)
290 noValueCombo.setSelectedIndex(NO_COLOUR_OPTION);
292 else if (noColour.equals(oldminColour))
294 noValueCombo.setSelectedIndex(MIN_COLOUR_OPTION);
296 else if (noColour.equals(oldmaxColour))
298 noValueCombo.setSelectedIndex(MAX_COLOUR_OPTION);
301 threshline = new GraphLine((max - min) / 2f, "Threshold", Color.black);
302 threshline.value = cs.getThreshold();
304 if (cs.hasThreshold())
306 // initialise threshold slider and selector
307 threshold.setSelectedIndex(cs.isAboveThreshold() ? 1 : 2);
308 slider.setEnabled(true);
309 slider.setValue((int) (cs.getThreshold() * scaleFactor));
310 thresholdValue.setEnabled(true);
320 * Configures the initial layout
322 private void jbInit()
324 this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
325 this.setBackground(Color.white);
327 changeColourAction = new ActionListener()
330 public void actionPerformed(ActionEvent e)
336 changeMinMaxAction = new ActionListener()
339 public void actionPerformed(ActionEvent e)
353 JPanel detailsPanel = new JPanel();
354 detailsPanel.setLayout(new BoxLayout(detailsPanel, BoxLayout.Y_AXIS));
356 JPanel colourByTextPanel = initColourByTextPanel();
357 detailsPanel.add(colourByTextPanel);
359 JPanel colourByValuePanel = initColourByValuePanel();
360 detailsPanel.add(colourByValuePanel);
363 * 4 radio buttons select between colour by description, by
364 * attribute text, by score, or by attribute value
366 ButtonGroup bg = new ButtonGroup();
367 bg.add(byDescription);
368 bg.add(byAttributeText);
370 bg.add(byAttributeValue);
372 JPanel okCancelPanel = initOkCancelPanel();
374 this.add(detailsPanel);
375 this.add(okCancelPanel);
379 * Updates the min-max range for a change in choice of Colour by Score, or
380 * Colour by Attribute (value)
382 protected void updateMinMax()
384 float[] minMax = null;
385 if (byScore.isSelected())
387 minMax = fr.getMinMax().get(type)[0];
389 else if (byAttributeValue.isSelected())
391 String attName = (String) valueAttributeCombo.getSelectedItem();
392 minMax = FeatureAttributes.getInstance().getMinMax(type, attName);
398 scaleFactor = (max == min) ? 1f : 100f / (max - min);
399 slider.setValue((int) (min * scaleFactor));
404 * Lay out fields for graduated colour by value
408 protected JPanel initColourByValuePanel()
410 JPanel byValuePanel = new JPanel();
411 byValuePanel.setLayout(new BoxLayout(byValuePanel, BoxLayout.Y_AXIS));
412 JvSwingUtils.createItalicTitledBorder(byValuePanel,
413 MessageManager.getString("label.colour_by_value"), true);
414 byValuePanel.setBackground(Color.white);
417 * first row - choose colour by score or by attribute, choose attribute
419 JPanel byWhatPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
420 byWhatPanel.setBackground(Color.white);
421 byValuePanel.add(byWhatPanel);
423 byScore.setText(MessageManager.getString("label.score"));
424 byWhatPanel.add(byScore);
425 byScore.addActionListener(changeMinMaxAction);
427 byAttributeValue.setText(MessageManager.getString("label.attribute"));
428 byAttributeValue.addActionListener(changeMinMaxAction);
429 byWhatPanel.add(byAttributeValue);
431 List<String> attNames = FeatureAttributes.getInstance().getAttributes(
433 valueAttributeCombo = populateAttributesDropdown(type, attNames, true);
436 * if no numeric atttibutes found, disable colour by attribute value
438 if (valueAttributeCombo.getItemCount() == 0)
440 byAttributeValue.setEnabled(false);
443 byWhatPanel.add(valueAttributeCombo);
446 * second row - min/max/no colours
448 JPanel colourRangePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
449 colourRangePanel.setBackground(Color.white);
450 byValuePanel.add(colourRangePanel);
452 minColour.setFont(JvSwingUtils.getLabelFont());
453 minColour.setBorder(BorderFactory.createLineBorder(Color.black));
454 minColour.setPreferredSize(new Dimension(40, 20));
455 minColour.setToolTipText(MessageManager.getString("label.min_colour"));
456 minColour.addMouseListener(new MouseAdapter()
459 public void mousePressed(MouseEvent e)
461 if (minColour.isEnabled())
463 minColour_actionPerformed();
468 maxColour.setFont(JvSwingUtils.getLabelFont());
469 maxColour.setBorder(BorderFactory.createLineBorder(Color.black));
470 maxColour.setPreferredSize(new Dimension(40, 20));
471 maxColour.setToolTipText(MessageManager.getString("label.max_colour"));
472 maxColour.addMouseListener(new MouseAdapter()
475 public void mousePressed(MouseEvent e)
477 if (maxColour.isEnabled())
479 maxColour_actionPerformed();
483 maxColour.setBorder(new LineBorder(Color.black));
485 noValueCombo = new JComboBox<>();
486 noValueCombo.addItem(MessageManager.getString("label.no_colour"));
487 noValueCombo.addItem(MessageManager.getString("label.min_colour"));
488 noValueCombo.addItem(MessageManager.getString("label.max_colour"));
489 noValueCombo.addItemListener(new ItemListener()
492 public void itemStateChanged(ItemEvent e)
498 JLabel minText = new JLabel(MessageManager.getString("label.min_value"));
499 minText.setFont(JvSwingUtils.getLabelFont());
500 JLabel maxText = new JLabel(MessageManager.getString("label.max_value"));
501 maxText.setFont(JvSwingUtils.getLabelFont());
502 JLabel noText = new JLabel(MessageManager.getString("label.no_value"));
503 noText.setFont(JvSwingUtils.getLabelFont());
505 colourRangePanel.add(minText);
506 colourRangePanel.add(minColour);
507 colourRangePanel.add(maxText);
508 colourRangePanel.add(maxColour);
509 colourRangePanel.add(noText);
510 colourRangePanel.add(noValueCombo);
513 * third row - threshold options and value
515 JPanel thresholdPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
516 thresholdPanel.setBackground(Color.white);
517 byValuePanel.add(thresholdPanel);
519 threshold.addActionListener(changeColourAction);
520 threshold.setToolTipText(MessageManager
521 .getString("label.threshold_feature_display_by_score"));
522 threshold.addItem(MessageManager
523 .getString("label.threshold_feature_no_threshold")); // index 0
524 threshold.addItem(MessageManager
525 .getString("label.threshold_feature_above_threshold")); // index 1
526 threshold.addItem(MessageManager
527 .getString("label.threshold_feature_below_threshold")); // index 2
529 thresholdValue.addActionListener(new ActionListener()
532 public void actionPerformed(ActionEvent e)
534 thresholdValue_actionPerformed();
537 thresholdValue.addFocusListener(new FocusAdapter()
540 public void focusLost(FocusEvent e)
542 thresholdValue_actionPerformed();
545 slider.setPaintLabels(false);
546 slider.setPaintTicks(true);
547 slider.setBackground(Color.white);
548 slider.setEnabled(false);
549 slider.setOpaque(false);
550 slider.setPreferredSize(new Dimension(100, 32));
551 slider.setToolTipText(MessageManager
552 .getString("label.adjust_threshold"));
553 thresholdValue.setEnabled(false);
554 thresholdValue.setColumns(7);
556 thresholdPanel.add(threshold);
557 thresholdPanel.add(slider);
558 thresholdPanel.add(thresholdValue);
561 * 4th row - threshold is min / max
563 JPanel isMinMaxPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
564 isMinMaxPanel.setBackground(Color.white);
565 byValuePanel.add(isMinMaxPanel);
566 thresholdIsMin.setBackground(Color.white);
567 thresholdIsMin.setText(MessageManager
568 .getString("label.threshold_minmax"));
569 thresholdIsMin.setToolTipText(MessageManager
570 .getString("label.toggle_absolute_relative_display_threshold"));
571 thresholdIsMin.addActionListener(changeColourAction);
572 isMinMaxPanel.add(thresholdIsMin);
578 * Action on user choice of no / min / max colour to use when there is no
581 protected void setNoValueColour()
583 int i = noValueCombo.getSelectedIndex();
584 if (i == NO_COLOUR_OPTION)
588 else if (i == MIN_COLOUR_OPTION)
590 noColour = minColour.getBackground();
592 else if (i == MAX_COLOUR_OPTION)
594 noColour = maxColour.getBackground();
600 * Lay out OK and Cancel buttons
604 protected JPanel initOkCancelPanel()
606 JPanel okCancelPanel = new JPanel();
607 okCancelPanel.setBackground(Color.white);
608 okCancelPanel.add(ok);
609 okCancelPanel.add(cancel);
610 return okCancelPanel;
614 * Lay out Colour by Label and attribute choice elements
618 protected JPanel initColourByTextPanel()
620 JPanel byTextPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
621 byTextPanel.setBackground(Color.white);
622 JvSwingUtils.createItalicTitledBorder(byTextPanel,
623 MessageManager.getString("label.colour_by_text"), true);
625 byDescription.setText(MessageManager.getString("label.label"));
626 byDescription.setToolTipText(MessageManager
627 .getString("label.colour_by_label_tip"));
628 byDescription.addActionListener(changeColourAction);
629 byTextPanel.add(byDescription);
631 byAttributeText.setText(MessageManager.getString("label.attribute"));
632 byAttributeText.addActionListener(changeColourAction);
633 byTextPanel.add(byAttributeText);
635 List<String> attNames = FeatureAttributes.getInstance().getAttributes(
637 textAttributeCombo = populateAttributesDropdown(type, attNames, false);
638 byTextPanel.add(textAttributeCombo);
641 * disable colour by attribute if no attributes
643 if (attNames.isEmpty())
645 byAttributeText.setEnabled(false);
652 * Action on clicking the 'minimum colour' - open a colour chooser dialog, and
653 * set the selected colour (if the user does not cancel out of the dialog)
655 protected void minColour_actionPerformed()
657 Color col = JColorChooser.showDialog(this,
658 MessageManager.getString("label.select_colour_minimum_value"),
659 minColour.getBackground());
662 minColour.setBackground(col);
663 minColour.setForeground(col);
670 * Action on clicking the 'maximum colour' - open a colour chooser dialog, and
671 * set the selected colour (if the user does not cancel out of the dialog)
673 protected void maxColour_actionPerformed()
675 Color col = JColorChooser.showDialog(this,
676 MessageManager.getString("label.select_colour_maximum_value"),
677 maxColour.getBackground());
680 maxColour.setBackground(col);
681 maxColour.setForeground(col);
688 * Constructs and sets the selected colour options as the colour for the
689 * feature type, and repaints the alignment, and optionally the Overview
690 * and/or structure viewer if open
692 * @param updateStructsAndOverview
694 void changeColour(boolean updateStructsAndOverview)
696 // Check if combobox is still adjusting
702 boolean aboveThreshold = false;
703 boolean belowThreshold = false;
704 if (threshold.getSelectedIndex() == 1)
706 aboveThreshold = true;
708 else if (threshold.getSelectedIndex() == 2)
710 belowThreshold = true;
712 boolean hasThreshold = aboveThreshold || belowThreshold;
714 slider.setEnabled(true);
715 thresholdValue.setEnabled(true);
718 * make the feature colour
721 if (cs.isColourByLabel())
723 acg = new FeatureColour(oldminColour, oldmaxColour, min, max);
727 acg = new FeatureColour(oldminColour = minColour.getBackground(),
728 oldmaxColour = maxColour.getBackground(),
729 oldNoColour = noColour, min, max);
731 String attribute = null;
732 textAttributeCombo.setEnabled(false);
733 valueAttributeCombo.setEnabled(false);
734 if (byAttributeText.isSelected())
736 attribute = (String) textAttributeCombo.getSelectedItem();
737 textAttributeCombo.setEnabled(true);
739 else if (byAttributeValue.isSelected())
741 attribute = (String) valueAttributeCombo.getSelectedItem();
742 valueAttributeCombo.setEnabled(true);
744 acg.setAttributeName(attribute);
748 slider.setEnabled(false);
749 thresholdValue.setEnabled(false);
750 thresholdValue.setText("");
751 thresholdIsMin.setEnabled(false);
753 else if (threshline == null)
756 * todo not yet implemented: visual indication of feature threshold
758 threshline = new GraphLine((max - min) / 2f, "Threshold",
765 acg.setThreshold(threshline.value);
767 float range = (max - min) * scaleFactor;
769 slider.setMinimum((int) (min * scaleFactor));
770 slider.setMaximum((int) (max * scaleFactor));
771 // slider.setValue((int) (threshline.value * scaleFactor));
772 slider.setValue(Math.round(threshline.value * scaleFactor));
773 thresholdValue.setText(threshline.value + "");
774 slider.setMajorTickSpacing((int) (range / 10f));
775 slider.setEnabled(true);
776 thresholdValue.setEnabled(true);
777 thresholdIsMin.setEnabled(!byDescription.isSelected());
781 acg.setAboveThreshold(aboveThreshold);
782 acg.setBelowThreshold(belowThreshold);
783 if (thresholdIsMin.isSelected() && hasThreshold)
785 acg.setAutoScaled(false);
788 acg = new FeatureColour((FeatureColour) acg, threshline.value, max);
792 acg = new FeatureColour((FeatureColour) acg, min, threshline.value);
797 acg.setAutoScaled(true);
799 acg.setColourByLabel(byDescription.isSelected()
800 || byAttributeText.isSelected());
802 if (acg.isColourByLabel())
804 maxColour.setEnabled(false);
805 minColour.setEnabled(false);
806 noValueCombo.setEnabled(false);
807 maxColour.setBackground(this.getBackground());
808 maxColour.setForeground(this.getBackground());
809 minColour.setBackground(this.getBackground());
810 minColour.setForeground(this.getBackground());
814 maxColour.setEnabled(true);
815 minColour.setEnabled(true);
816 noValueCombo.setEnabled(true);
817 maxColour.setBackground(oldmaxColour);
818 maxColour.setForeground(oldmaxColour);
819 minColour.setBackground(oldminColour);
820 minColour.setForeground(oldminColour);
821 noColour = oldNoColour;
825 * save the colour, and repaint stuff
827 fr.setColour(type, acg);
829 ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview);
833 protected void raiseClosed()
835 if (this.colourEditor != null)
837 colourEditor.actionPerformed(new ActionEvent(this, 0, "CLOSED"));
842 public void okPressed()
848 public void cancelPressed()
854 * Action when the user cancels the dialog. All previous settings should be
855 * restored and rendered on the alignment, and any linked Overview window or
860 fr.setColour(type, oldcs);
861 ap.paintAlignment(true, true);
866 * Action on text entry of a threshold value
868 protected void thresholdValue_actionPerformed()
872 float f = Float.parseFloat(thresholdValue.getText());
873 slider.setValue((int) (f * scaleFactor));
874 threshline.value = f;
877 * force repaint of any Overview window or structure
879 ap.paintAlignment(true, true);
880 } catch (NumberFormatException ex)
886 * Action on change of threshold slider value. This may be done interactively
887 * (by moving the slider), or programmatically (to update the slider after
888 * manual input of a threshold value).
890 protected void sliderValueChanged()
893 * squash rounding errors by forcing min/max of slider to
894 * actual min/max of feature score range
896 int value = slider.getValue();
897 threshline.value = value == slider.getMaximum() ? max
898 : (value == slider.getMinimum() ? min : value / scaleFactor);
899 cs.setThreshold(threshline.value);
902 * repaint alignment, but not Overview or structure,
903 * to avoid overload while dragging the slider
908 void addActionListener(ActionListener graduatedColorEditor)
910 if (colourEditor != null)
913 "IMPLEMENTATION ISSUE: overwriting action listener for FeatureColourChooser");
915 colourEditor = graduatedColorEditor;
919 * Answers the last colour setting selected by user - either oldcs (which may
920 * be a java.awt.Color) or the new GraduatedColor
924 FeatureColourI getLastColour()
934 * A helper method to build the drop-down choice of attributes for a feature.
935 * Where metadata is available with a description for an attribute, that is
936 * added as a tooltip. The list may be restricted to attributes for which we
937 * hold a range of numerical values (so suitable candidates for a graduated
942 * @param withNumericRange
944 protected JComboBox<String> populateAttributesDropdown(
945 String featureType, List<String> attNames,
946 boolean withNumericRange)
948 List<String> validAtts = new ArrayList<>();
949 List<String> tooltips = new ArrayList<>();
951 FeatureAttributes fa = FeatureAttributes.getInstance();
952 for (String attName : attNames)
954 if (withNumericRange)
956 float[] minMax = fa.getMinMax(featureType, attName);
962 validAtts.add(attName);
963 String desc = fa.getDescription(featureType, attName);
964 if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH)
966 desc = desc.substring(0, MAX_TOOLTIP_LENGTH) + "...";
968 tooltips.add(desc == null ? "" : desc);
971 JComboBox<String> attCombo = JvSwingUtils.buildComboWithTooltips(
972 validAtts, tooltips);
974 attCombo.addItemListener(new ItemListener()
977 public void itemStateChanged(ItemEvent e)
979 changeMinMaxAction.actionPerformed(null);
983 if (validAtts.isEmpty())
985 attCombo.setToolTipText(MessageManager
986 .getString(withNumericRange ? "label.no_numeric_attributes"
987 : "label.no_attributes"));