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;
28 import jalview.util.matcher.Condition;
29 import jalview.util.matcher.KeyedMatcher;
30 import jalview.util.matcher.KeyedMatcherI;
31 import jalview.util.matcher.Matcher;
32 import jalview.util.matcher.MatcherI;
34 import java.awt.BorderLayout;
35 import java.awt.Color;
36 import java.awt.Dimension;
37 import java.awt.FlowLayout;
38 import java.awt.GridLayout;
39 import java.awt.event.ActionEvent;
40 import java.awt.event.ActionListener;
41 import java.awt.event.FocusAdapter;
42 import java.awt.event.FocusEvent;
43 import java.awt.event.MouseAdapter;
44 import java.awt.event.MouseEvent;
45 import java.util.Iterator;
47 import javax.swing.BorderFactory;
48 import javax.swing.JCheckBox;
49 import javax.swing.JColorChooser;
50 import javax.swing.JComboBox;
51 import javax.swing.JLabel;
52 import javax.swing.JPanel;
53 import javax.swing.JSlider;
54 import javax.swing.JTextField;
55 import javax.swing.border.LineBorder;
56 import javax.swing.event.ChangeEvent;
57 import javax.swing.event.ChangeListener;
59 public class FeatureColourChooser extends JalviewDialog
61 // FeatureSettings fs;
62 private FeatureRenderer fr;
64 private FeatureColourI cs;
66 private FeatureColourI oldcs;
68 private AlignmentPanel ap;
70 private boolean adjusting = false;
72 final private float min;
74 final private float max;
76 final private float scaleFactor;
78 private String type = null;
80 private JPanel minColour = new JPanel();
82 private JPanel maxColour = new JPanel();
84 private JComboBox<String> threshold = new JComboBox<>();
86 private JSlider slider = new JSlider();
88 private JTextField thresholdValue = new JTextField(20);
90 // TODO implement GUI for tolower flag
91 // JCheckBox toLower = new JCheckBox();
93 private JCheckBox thresholdIsMin = new JCheckBox();
95 private JCheckBox colourByLabel = new JCheckBox();
97 private GraphLine threshline;
99 private Color oldmaxColour;
101 private Color oldminColour;
103 private ActionListener colourEditor = null;
105 private JComboBox<String> filterAttribute;
107 private JComboBox<Condition> filterCondition;
109 private JTextField filterValue;
111 private JComboBox<String> filterAttribute2;
113 private JComboBox<Condition> filterCondition2;
115 private JTextField filterValue2;
123 public FeatureColourChooser(FeatureRenderer frender, String theType)
125 this(frender, false, theType);
129 * Constructor, with option to make a blocking dialog (has to complete in the
130 * AWT event queue thread). Currently this option is always set to false.
136 FeatureColourChooser(FeatureRenderer frender, boolean blocking,
142 String title = MessageManager
143 .formatMessage("label.graduated_color_for_params", new String[]
145 initDialogFrame(this, true, blocking, title, 480, 185);
147 slider.addChangeListener(new ChangeListener()
150 public void stateChanged(ChangeEvent evt)
154 thresholdValue.setText((slider.getValue() / scaleFactor) + "");
155 sliderValueChanged();
159 slider.addMouseListener(new MouseAdapter()
162 public void mouseReleased(MouseEvent evt)
165 * only update Overview and/or structure colouring
166 * when threshold slider drag ends (mouse up)
170 ap.paintAlignment(true, true);
175 float mm[] = fr.getMinMax().get(theType)[0];
180 * ensure scale factor allows a scaled range with
181 * 10 integer divisions ('ticks'); if we have got here,
182 * we should expect that max != min
184 scaleFactor = (max == min) ? 1f : 100f / (max - min);
186 oldcs = fr.getFeatureColours().get(theType);
187 if (!oldcs.isSimpleColour())
189 if (oldcs.isAutoScaled())
192 cs = new FeatureColour((FeatureColour) oldcs, min, max);
196 cs = new FeatureColour((FeatureColour) oldcs);
201 // promote original color to a graduated color
202 Color bl = oldcs.getColour();
207 // original colour becomes the maximum colour
208 cs = new FeatureColour(Color.white, bl, mm[0], mm[1]);
209 cs.setColourByLabel(false);
211 minColour.setBackground(oldminColour = cs.getMinColour());
212 maxColour.setBackground(oldmaxColour = cs.getMaxColour());
218 } catch (Exception ex)
221 // update the gui from threshold state
222 thresholdIsMin.setSelected(!cs.isAutoScaled());
223 colourByLabel.setSelected(cs.isColourByLabel());
224 if (cs.hasThreshold())
226 // initialise threshold slider and selector
227 threshold.setSelectedIndex(cs.isAboveThreshold() ? 1 : 2);
228 slider.setEnabled(true);
229 slider.setValue((int) (cs.getThreshold() * scaleFactor));
230 thresholdValue.setEnabled(true);
231 threshline = new GraphLine((max - min) / 2f, "Threshold",
233 threshline.value = cs.getThreshold();
236 setInitialFilters(cs.getAttributeFilters());
245 * Populates the attribute filter fields for the initial display
247 * @param attributeFilters
249 void setInitialFilters(KeyedMatcherI attributeFilters)
251 // todo generalise to populate N conditions
253 if (attributeFilters != null)
255 filterAttribute.setSelectedItem(attributeFilters.getKey());
256 filterCondition.setSelectedItem(attributeFilters.getMatcher()
258 filterValue.setText(attributeFilters.getMatcher().getPattern());
260 KeyedMatcherI second = attributeFilters.getSecondMatcher();
263 // todo add OR/AND condition to gui
264 filterAttribute2.setSelectedItem(second.getKey());
266 .setSelectedItem(second.getMatcher().getCondition());
267 filterValue2.setText(second.getMatcher().getPattern());
272 private void jbInit() throws Exception
274 this.setLayout(new GridLayout(4, 1));
276 JPanel colourByPanel = initColoursPanel();
278 JPanel thresholdPanel = initThresholdPanel();
280 JPanel okCancelPanel = initOkCancelPanel();
282 this.add(colourByPanel);
283 this.add(thresholdPanel);
286 * add filter by attributes options only if we know any attributes
288 Iterator<String> attributes = FeatureAttributes.getInstance()
289 .getAttributes(type).iterator();
290 if (attributes.hasNext())
292 JPanel filtersPanel = initFiltersPanel(attributes);
293 this.add(filtersPanel);
296 this.add(okCancelPanel);
300 * Lay out fields for threshold options
304 protected JPanel initThresholdPanel()
306 JPanel thresholdPanel = new JPanel();
307 thresholdPanel.setLayout(new FlowLayout());
308 threshold.addActionListener(new ActionListener()
311 public void actionPerformed(ActionEvent e)
316 threshold.setToolTipText(MessageManager
317 .getString("label.threshold_feature_display_by_score"));
318 threshold.addItem(MessageManager
319 .getString("label.threshold_feature_no_threshold")); // index 0
320 threshold.addItem(MessageManager
321 .getString("label.threshold_feature_above_threshold")); // index 1
322 threshold.addItem(MessageManager
323 .getString("label.threshold_feature_below_threshold")); // index 2
325 thresholdValue.addActionListener(new ActionListener()
328 public void actionPerformed(ActionEvent e)
330 thresholdValue_actionPerformed();
333 thresholdValue.addFocusListener(new FocusAdapter()
336 public void focusLost(FocusEvent e)
338 thresholdValue_actionPerformed();
341 slider.setPaintLabels(false);
342 slider.setPaintTicks(true);
343 slider.setBackground(Color.white);
344 slider.setEnabled(false);
345 slider.setOpaque(false);
346 slider.setPreferredSize(new Dimension(100, 32));
347 slider.setToolTipText(
348 MessageManager.getString("label.adjust_threshold"));
349 thresholdValue.setEnabled(false);
350 thresholdValue.setColumns(7);
351 thresholdPanel.setBackground(Color.white);
352 thresholdIsMin.setBackground(Color.white);
354 .setText(MessageManager.getString("label.threshold_minmax"));
355 thresholdIsMin.setToolTipText(MessageManager
356 .getString("label.toggle_absolute_relative_display_threshold"));
357 thresholdIsMin.addActionListener(new ActionListener()
360 public void actionPerformed(ActionEvent actionEvent)
365 thresholdPanel.add(threshold);
366 thresholdPanel.add(slider);
367 thresholdPanel.add(thresholdValue);
368 thresholdPanel.add(thresholdIsMin);
369 return thresholdPanel;
373 * Lay out OK and Cancel buttons
377 protected JPanel initOkCancelPanel()
379 JPanel okCancelPanel = new JPanel();
380 okCancelPanel.setBackground(Color.white);
381 okCancelPanel.add(ok);
382 okCancelPanel.add(cancel);
383 return okCancelPanel;
387 * Lay out Colour by Label and min/max colour widgets
391 protected JPanel initColoursPanel()
393 JPanel colourByPanel = new JPanel();
394 colourByPanel.setLayout(new FlowLayout());
395 colourByPanel.setBackground(Color.white);
396 minColour.setFont(JvSwingUtils.getLabelFont());
397 minColour.setBorder(BorderFactory.createLineBorder(Color.black));
398 minColour.setPreferredSize(new Dimension(40, 20));
399 minColour.setToolTipText(MessageManager.getString("label.min_colour"));
400 minColour.addMouseListener(new MouseAdapter()
403 public void mousePressed(MouseEvent e)
405 if (minColour.isEnabled())
407 minColour_actionPerformed();
411 maxColour.setFont(JvSwingUtils.getLabelFont());
412 maxColour.setBorder(BorderFactory.createLineBorder(Color.black));
413 maxColour.setPreferredSize(new Dimension(40, 20));
414 maxColour.setToolTipText(MessageManager.getString("label.max_colour"));
415 maxColour.addMouseListener(new MouseAdapter()
418 public void mousePressed(MouseEvent e)
420 if (maxColour.isEnabled())
422 maxColour_actionPerformed();
426 maxColour.setBorder(new LineBorder(Color.black));
427 JLabel minText = new JLabel(MessageManager.getString("label.min"));
428 minText.setFont(JvSwingUtils.getLabelFont());
429 JLabel maxText = new JLabel(MessageManager.getString("label.max"));
430 maxText.setFont(JvSwingUtils.getLabelFont());
432 JPanel colourPanel = new JPanel();
433 colourPanel.setBackground(Color.white);
434 colourPanel.add(minText);
435 colourPanel.add(minColour);
436 colourPanel.add(maxText);
437 colourPanel.add(maxColour);
438 colourByPanel.add(colourByLabel, BorderLayout.WEST);
439 colourByPanel.add(colourPanel, BorderLayout.EAST);
441 colourByLabel.setBackground(Color.white);
443 .setText(MessageManager.getString("label.colour_by_label"));
445 .setToolTipText(MessageManager
446 .getString("label.display_features_same_type_different_label_using_different_colour"));
447 colourByLabel.addActionListener(new ActionListener()
450 public void actionPerformed(ActionEvent actionEvent)
456 return colourByPanel;
460 * Action on clicking the 'minimum colour' - open a colour chooser dialog, and
461 * set the selected colour (if the user does not cancel out of the dialog)
463 protected void minColour_actionPerformed()
465 Color col = JColorChooser.showDialog(this,
466 MessageManager.getString("label.select_colour_minimum_value"),
467 minColour.getBackground());
470 minColour.setBackground(col);
471 minColour.setForeground(col);
478 * Action on clicking the 'maximum colour' - open a colour chooser dialog, and
479 * set the selected colour (if the user does not cancel out of the dialog)
481 protected void maxColour_actionPerformed()
483 Color col = JColorChooser.showDialog(this,
484 MessageManager.getString("label.select_colour_maximum_value"),
485 maxColour.getBackground());
488 maxColour.setBackground(col);
489 maxColour.setForeground(col);
496 * Constructs and sets the selected colour options as the colour for the
497 * feature type, and repaints the alignment, and optionally the Overview
498 * and/or structure viewer if open
500 * @param updateStructsAndOverview
502 void changeColour(boolean updateStructsAndOverview)
504 // Check if combobox is still adjusting
510 if (!validateInputs())
515 boolean aboveThreshold = false;
516 boolean belowThreshold = false;
517 if (threshold.getSelectedIndex() == 1)
519 aboveThreshold = true;
521 else if (threshold.getSelectedIndex() == 2)
523 belowThreshold = true;
525 boolean hasThreshold = aboveThreshold || belowThreshold;
527 slider.setEnabled(true);
528 thresholdValue.setEnabled(true);
531 if (cs.isColourByLabel())
533 acg = new FeatureColour(oldminColour, oldmaxColour, min, max);
537 acg = new FeatureColour(oldminColour = minColour.getBackground(),
538 oldmaxColour = maxColour.getBackground(), min, max);
543 slider.setEnabled(false);
544 thresholdValue.setEnabled(false);
545 thresholdValue.setText("");
546 thresholdIsMin.setEnabled(false);
548 else if (threshline == null)
551 * todo not yet implemented: visual indication of feature threshold
553 threshline = new GraphLine((max - min) / 2f, "Threshold",
560 acg.setThreshold(threshline.value);
562 float range = (max - min) * scaleFactor;
564 slider.setMinimum((int) (min * scaleFactor));
565 slider.setMaximum((int) (max * scaleFactor));
566 // slider.setValue((int) (threshline.value * scaleFactor));
567 slider.setValue(Math.round(threshline.value * scaleFactor));
568 thresholdValue.setText(threshline.value + "");
569 slider.setMajorTickSpacing((int) (range / 10f));
570 slider.setEnabled(true);
571 thresholdValue.setEnabled(true);
572 thresholdIsMin.setEnabled(!colourByLabel.isSelected());
576 acg.setAboveThreshold(aboveThreshold);
577 acg.setBelowThreshold(belowThreshold);
578 if (thresholdIsMin.isSelected() && hasThreshold)
580 acg.setAutoScaled(false);
583 acg = new FeatureColour((FeatureColour) acg, threshline.value, max);
587 acg = new FeatureColour((FeatureColour) acg, min, threshline.value);
592 acg.setAutoScaled(true);
594 acg.setColourByLabel(colourByLabel.isSelected());
595 if (acg.isColourByLabel())
597 maxColour.setEnabled(false);
598 minColour.setEnabled(false);
599 maxColour.setBackground(this.getBackground());
600 maxColour.setForeground(this.getBackground());
601 minColour.setBackground(this.getBackground());
602 minColour.setForeground(this.getBackground());
607 maxColour.setEnabled(true);
608 minColour.setEnabled(true);
609 maxColour.setBackground(oldmaxColour);
610 minColour.setBackground(oldminColour);
611 maxColour.setForeground(oldmaxColour);
612 minColour.setForeground(oldminColour);
616 * add attribute filters if entered
618 if (filterAttribute != null)
620 setAttributeFilters(acg);
623 fr.setColour(type, acg);
625 ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview);
629 * Checks inputs are valid, and answers true if they are, else false. Also
630 * sets the colour of invalid inputs to red.
634 boolean validateInputs()
636 // todo generalise to N filters
637 return isValidFilter(filterValue, filterCondition)
638 || isValidFilter(filterValue2, filterCondition2);
642 * Answers true unless a numeric condition has been selected with a
648 protected boolean isValidFilter(JTextField value, JComboBox<Condition> condition)
650 if (value == null || condition == null)
652 return true; // fields not populated
655 value.setBackground(Color.white);
656 String v1 = value.getText().trim();
659 Condition c1 = (Condition) condition.getSelectedItem();
665 } catch (NumberFormatException e)
667 value.setBackground(Color.red);
677 * Sets any attribute value filters entered in the dialog as filters on the
682 protected void setAttributeFilters(FeatureColourI acg)
684 String attribute = (String) filterAttribute.getSelectedItem();
685 Condition cond = (Condition) filterCondition.getSelectedItem();
686 String pattern = filterValue.getText().trim();
687 if (pattern.length() > 1)
689 MatcherI filter = new Matcher(cond, pattern);
690 KeyedMatcherI km = new KeyedMatcher(attribute, filter);
693 * is there a second condition?
694 * todo: generalise to N conditions
696 pattern = filterValue2.getText().trim();
697 if (pattern.length() > 1)
699 attribute = (String) filterAttribute2.getSelectedItem();
700 cond = (Condition) filterCondition2.getSelectedItem();
701 filter = new Matcher(cond, pattern);
702 km = km.and(attribute, filter);
704 acg.setAttributeFilters(km);
709 protected void raiseClosed()
711 if (this.colourEditor != null)
713 colourEditor.actionPerformed(new ActionEvent(this, 0, "CLOSED"));
718 public void okPressed()
724 public void cancelPressed()
730 * Action when the user cancels the dialog. All previous settings should be
731 * restored and rendered on the alignment, and any linked Overview window or
736 fr.setColour(type, oldcs);
737 ap.paintAlignment(true, true);
742 * Action on text entry of a threshold value
744 protected void thresholdValue_actionPerformed()
748 float f = Float.parseFloat(thresholdValue.getText());
749 slider.setValue((int) (f * scaleFactor));
750 threshline.value = f;
753 * force repaint of any Overview window or structure
755 ap.paintAlignment(true, true);
756 } catch (NumberFormatException ex)
762 * Action on change of threshold slider value. This may be done interactively
763 * (by moving the slider), or programmatically (to update the slider after
764 * manual input of a threshold value).
766 protected void sliderValueChanged()
769 * squash rounding errors by forcing min/max of slider to
770 * actual min/max of feature score range
772 int value = slider.getValue();
773 threshline.value = value == slider.getMaximum() ? max
774 : (value == slider.getMinimum() ? min : value / scaleFactor);
775 cs.setThreshold(threshline.value);
778 * repaint alignment, but not Overview or structure,
779 * to avoid overload while dragging the slider
784 void addActionListener(ActionListener graduatedColorEditor)
786 if (colourEditor != null)
789 "IMPLEMENTATION ISSUE: overwriting action listener for FeatureColourChooser");
791 colourEditor = graduatedColorEditor;
795 * Answers the last colour setting selected by user - either oldcs (which may
796 * be a java.awt.Color) or the new GraduatedColor
800 FeatureColourI getLastColour()
810 * Lay out fields for attribute value filters
816 protected JPanel initFiltersPanel(Iterator<String> attNames)
818 JPanel filtersPanel = new JPanel();
819 filtersPanel.setLayout(new GridLayout(2, 3));
820 filtersPanel.setBackground(Color.white);
823 * drop-down choice of attribute
825 filterAttribute = new JComboBox<>();
826 filterAttribute2 = new JComboBox<>();
827 while (attNames.hasNext())
829 String attName = attNames.next();
830 filterAttribute.addItem(attName);
831 filterAttribute2.addItem(attName);
833 filterAttribute.addActionListener(new ActionListener()
836 public void actionPerformed(ActionEvent e)
843 * drop-down choice of test condition
845 filterCondition = new JComboBox<>();
846 for (Condition cond : Condition.values())
848 filterCondition.addItem(cond);
850 filterCondition.addActionListener(new ActionListener()
853 public void actionPerformed(ActionEvent e)
859 filterValue = new JTextField(12);
860 filterValue.addActionListener(new ActionListener()
863 public void actionPerformed(ActionEvent e)
868 filterValue.addFocusListener(new FocusAdapter()
871 public void focusLost(FocusEvent e)
878 * repeat for a second filter
879 * todo: generalise to N filters
881 filterAttribute2.addActionListener(new ActionListener()
884 public void actionPerformed(ActionEvent e)
891 * drop-down choice of test condition
893 filterCondition2 = new JComboBox<>();
894 for (Condition cond : Condition.values())
896 filterCondition2.addItem(cond);
898 filterCondition2.addActionListener(new ActionListener()
901 public void actionPerformed(ActionEvent e)
907 filterValue2 = new JTextField(12);
908 filterValue2.addActionListener(new ActionListener()
911 public void actionPerformed(ActionEvent e)
916 filterValue2.addFocusListener(new FocusAdapter()
919 public void focusLost(FocusEvent e)
925 filtersPanel.add(filterAttribute);
926 filtersPanel.add(filterCondition);
927 filtersPanel.add(filterValue);
928 filtersPanel.add(filterAttribute2);
929 filtersPanel.add(filterCondition2);
930 filtersPanel.add(filterValue2);