X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fgui%2FFeatureColourChooser.java;h=d3d9e1a4bee66b604eac32e1d47551ddeb673f25;hb=fac8703b72442854d4b91fd71483455058b41e2b;hp=3b39c2cd32399c22c27a5a36a6ebcacd4e9a25e2;hpb=a928b501e71cef2627004a73e2d5c460b44b2d7b;p=jalview.git diff --git a/src/jalview/gui/FeatureColourChooser.java b/src/jalview/gui/FeatureColourChooser.java index 3b39c2c..d3d9e1a 100644 --- a/src/jalview/gui/FeatureColourChooser.java +++ b/src/jalview/gui/FeatureColourChooser.java @@ -1,191 +1,466 @@ /* - * Jalview - A Sequence Alignment Editor and Viewer (Development Version 2.4.1) - * Copyright (C) 2009 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors * - * This program 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 2 - * of the License, or (at your option) any later version. + * This file is part of Jalview. * - * This program 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. + * 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 this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. */ package jalview.gui; -import java.util.*; +import jalview.api.FeatureColourI; +import jalview.datamodel.GraphLine; +import jalview.datamodel.features.FeatureAttributes; +import jalview.schemes.FeatureColour; +import jalview.util.MessageManager; -import java.awt.*; -import java.awt.event.*; -import javax.swing.*; -import javax.swing.border.LineBorder; -import javax.swing.event.*; - -import jalview.datamodel.*; -import jalview.schemes.*; +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 JPanel +public class FeatureColourChooser extends JalviewDialog { - JDialog frame; - - -// FeatureSettings fs; - FeatureRenderer fr; - - - private GraduatedColor cs; - private Object oldcs; - /** - * - * @return the last colour setting selected by user - either oldcs (which may be a java.awt.Color) or the new GraduatedColor - */ - public Object getLastColour() { - if (cs==null) - { - return oldcs; - } - return cs; - } - Hashtable oldgroupColours; - - AlignmentPanel ap; - + 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; - boolean adjusting = false; + private FeatureColourI cs; + + private FeatureColourI oldcs; + + private AlignmentPanel ap; + + private boolean adjusting = false; private float min; private float max; - String type = null; - public FeatureColourChooser(FeatureRenderer frender, String type) + + 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,type); + this(frender, false, theType); } - public FeatureColourChooser(FeatureRenderer frender, boolean block, String type) - { + + /** + * 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 = type; + this.type = theType; ap = fr.ap; - frame = new JDialog(Desktop.instance,true); - frame.setTitle("Graduated Feature Colour for "+type); - Rectangle deskr = Desktop.instance.getBounds(); - frame.setBounds(new Rectangle((int) (deskr.getCenterX()-240),(int) (deskr.getCenterY()-92),480,185)); - frame.setContentPane(this); - //frame.setLayer(JLayeredPane.PALETTE_LAYER); - //Desktop.addInternalFrame(frame, "Graduated Feature Colour for "+type, 480, 145); + 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(((float) slider.getValue() / 1000f) + ""); - valueChanged(); + thresholdValue.setText((slider.getValue() / scaleFactor) + ""); + sliderValueChanged(); } } }); slider.addMouseListener(new MouseAdapter() { + @Override public void mouseReleased(MouseEvent evt) { - if (ap!=null) { ap.paintAlignment(true); }; + /* + * only update Overview and/or structure colouring + * when threshold slider drag ends (mouse up) + */ + if (ap != null) + { + ap.paintAlignment(true, true); + } } }); - float mm[] = ((float[][]) fr.minmax.get(type))[0]; + // todo move all threshold setup inside a method + float mm[] = fr.getMinMax().get(theType)[0]; min = mm[0]; max = mm[1]; - oldcs = fr.featureColours.get(type); - if (oldcs instanceof GraduatedColor) + + /* + * 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 (((GraduatedColor)oldcs).isAutoScale()) + if (oldcs.isAutoScaled()) { // update the scale - cs = new GraduatedColor((GraduatedColor) oldcs, min, max); - } else { - cs = new GraduatedColor((GraduatedColor) oldcs); + cs = new FeatureColour((FeatureColour) oldcs, min, max); + } + else + { + cs = new FeatureColour((FeatureColour) oldcs); } - } else { - // promote original color to a graduated color - Color bl = Color.black; - if (oldcs instanceof Color) + } + 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) oldcs; + bl = Color.BLACK; } // original colour becomes the maximum colour - cs = new GraduatedColor(Color.white,bl,mm[0],mm[1]); - cs.setColourByLabel(false); + cs = new FeatureColour(Color.white, bl, mm[0], mm[1]); + cs.setColourByLabel(mm[0] == mm[1]); } - minColour.setBackground(oldminColour=cs.getMinColor()); - maxColour.setBackground(oldmaxColour=cs.getMaxColor()); + 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); } - // update the gui from threshold state - thresholdIsMin.setSelected(!cs.isAutoScale()); - colourByLabel.setSelected(cs.isColourByLabel()); - if (cs.getThreshType()!=AnnotationColourGradient.NO_THRESHOLD) + 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.getThreshType()==AnnotationColourGradient.ABOVE_THRESHOLD ? 1 : 2); - slider.setEnabled(true); + threshold.setSelectedIndex(cs.isAboveThreshold() ? 1 : 2); + slider.setEnabled(true); + slider.setValue((int) (cs.getThreshold() * scaleFactor)); thresholdValue.setEnabled(true); - threshline = new jalview.datamodel.GraphLine( - (max - min) / 2f, - "Threshold", Color.black); - - } + } adjusting = false; - changeColour(); - if (!block) + 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() { - new Thread(new Runnable() { + @Override + public void actionPerformed(ActionEvent e) + { + changeColour(true); + } + }; - public void run() + changeMinMaxAction = new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) { - frame.show(); + updateMinMax(); + changeColour(true); } - - }).start(); - } else { - frame.show(); - } + }; + + /* + * 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); } - public FeatureColourChooser() + /** + * Updates the min-max range for a change in choice of Colour by Score, or + * Colour by Attribute (value) + */ + protected void updateMinMax() { - try + float[] minMax = null; + if (byScore.isSelected()) { - jbInit(); - } catch (Exception ex) + minMax = fr.getMinMax().get(type)[0]; + } + else if (byAttributeValue.isSelected()) { - ex.printStackTrace(); + 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)); } } - private void jbInit() throws Exception + /** + * Lay out fields for graduated colour by value + * + * @return + */ + protected JPanel initColourByValuePanel() { - - minColour.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11)); + 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("Minimum Colour"); + minColour.setToolTipText(MessageManager.getString("label.min_colour")); minColour.addMouseListener(new MouseAdapter() { + @Override public void mousePressed(MouseEvent e) { if (minColour.isEnabled()) @@ -194,12 +469,14 @@ public class FeatureColourChooser extends JPanel } } }); - maxColour.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11)); + + maxColour.setFont(JvSwingUtils.getLabelFont()); maxColour.setBorder(BorderFactory.createLineBorder(Color.black)); maxColour.setPreferredSize(new Dimension(40, 20)); - maxColour.setToolTipText("Maximum Colour"); + maxColour.setToolTipText(MessageManager.getString("label.max_colour")); maxColour.addMouseListener(new MouseAdapter() { + @Override public void mousePressed(MouseEvent e) { if (maxColour.isEnabled()) @@ -209,49 +486,65 @@ public class FeatureColourChooser extends JPanel } }); maxColour.setBorder(new LineBorder(Color.black)); - minText.setText("Min:"); - minText.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11)); - maxText.setText("Max:"); - maxText.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11)); - ok.setOpaque(false); - ok.setText("OK"); - ok.addActionListener(new ActionListener() - { - public void actionPerformed(ActionEvent e) - { - ok_actionPerformed(e); - } - }); - cancel.setOpaque(false); - cancel.setText("Cancel"); - cancel.addActionListener(new ActionListener() + + 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() { - public void actionPerformed(ActionEvent e) + @Override + public void itemStateChanged(ItemEvent e) { - cancel_actionPerformed(e); + setNoValueColour(); } }); - this.setLayout(borderLayout1); - jPanel2.setLayout(flowLayout1); - jPanel1.setBackground(Color.white); - jPanel2.setBackground(Color.white); - threshold.addActionListener(new ActionListener() + + 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) { - threshold_actionPerformed(e); + thresholdValue_actionPerformed(); } }); - threshold.setToolTipText("Threshold the feature display by score."); - threshold.addItem("No Threshold"); // index 0 - threshold.addItem("Above Threshold"); // index 1 - threshold.addItem("Below Threshold"); // index 2 - jPanel3.setLayout(flowLayout2); - thresholdValue.addActionListener(new ActionListener() + thresholdValue.addFocusListener(new FocusAdapter() { - public void actionPerformed(ActionEvent e) + @Override + public void focusLost(FocusEvent e) { - thresholdValue_actionPerformed(e); + thresholdValue_actionPerformed(); } }); slider.setPaintLabels(false); @@ -260,117 +553,150 @@ public class FeatureColourChooser extends JPanel slider.setEnabled(false); slider.setOpaque(false); slider.setPreferredSize(new Dimension(100, 32)); - slider.setToolTipText("Adjust threshold"); + slider.setToolTipText(MessageManager + .getString("label.adjust_threshold")); thresholdValue.setEnabled(false); thresholdValue.setColumns(7); - jPanel3.setBackground(Color.white); + + 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("Threshold is Min/Max"); - thresholdIsMin.setToolTipText("Toggle between absolute and relative display threshold."); - thresholdIsMin.addActionListener(new ActionListener() + 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) { - public void actionPerformed(ActionEvent actionEvent) - { - thresholdIsMin_actionPerformed(actionEvent); - } - }); - colourByLabel.setBackground(Color.white); - colourByLabel.setText("Colour by Label"); - colourByLabel.setToolTipText("Display features of the same type with a different label using a different colour. (e.g. domain features)"); - colourByLabel.addActionListener(new ActionListener() + noColour = null; + } + else if (i == MIN_COLOUR_OPTION) { - public void actionPerformed(ActionEvent actionEvent) - { - colourByLabel_actionPerformed(actionEvent); - } - }); - colourPanel.setBackground(Color.white); - jPanel1.add(ok); - jPanel1.add(cancel); - jPanel2.add(colourByLabel,java.awt.BorderLayout.WEST); - jPanel2.add(colourPanel,java.awt.BorderLayout.EAST); - colourPanel.add(minText); - colourPanel.add(minColour); - colourPanel.add(maxText); - colourPanel.add(maxColour); - this.add(jPanel3, java.awt.BorderLayout.CENTER); - jPanel3.add(threshold); - jPanel3.add(slider); - jPanel3.add(thresholdValue); - jPanel3.add(thresholdIsMin); - this.add(jPanel1, java.awt.BorderLayout.SOUTH); - this.add(jPanel2, java.awt.BorderLayout.NORTH); + 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; + } - JLabel minText = new JLabel(); - JLabel maxText = new JLabel(); - JPanel minColour = new JPanel(); - - JPanel maxColour = new JPanel(); - - JButton ok = new JButton(); - - JButton cancel = new JButton(); - JPanel colourPanel = new JPanel(); - JPanel jPanel1 = new JPanel(); - - JPanel jPanel2 = new JPanel(); - - BorderLayout borderLayout1 = new BorderLayout(); - - JComboBox threshold = new JComboBox(); - - FlowLayout flowLayout1 = new FlowLayout(); - - JPanel jPanel3 = new JPanel(); - - FlowLayout flowLayout2 = new FlowLayout(); - - JSlider slider = new JSlider(); - - JTextField thresholdValue = new JTextField(20); - // TODO implement GUI for tolower flag - // JCheckBox toLower = new JCheckBox(); - - JCheckBox thresholdIsMin = new JCheckBox(); - JCheckBox colourByLabel = new JCheckBox(); - - private GraphLine threshline; - - - private Color oldmaxColour; - + /** + * 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); + } - private Color oldminColour; + return byTextPanel; + } - public void minColour_actionPerformed() + /** + * 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, - "Select Colour for Minimum Value", minColour.getBackground()); + MessageManager.getString("label.select_colour_minimum_value"), + minColour.getBackground()); if (col != null) { minColour.setBackground(col); minColour.setForeground(col); } minColour.repaint(); - changeColour(); + changeColour(true); } - public void maxColour_actionPerformed() + /** + * 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, - "Select Colour for Maximum Value", maxColour.getBackground()); + MessageManager.getString("label.select_colour_maximum_value"), + maxColour.getBackground()); if (col != null) { maxColour.setBackground(col); maxColour.setForeground(col); } maxColour.repaint(); - changeColour(); + changeColour(true); } - void changeColour() + /** + * 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) @@ -378,188 +704,313 @@ public class FeatureColourChooser extends JPanel return; } - - int aboveThreshold = AnnotationColourGradient.NO_THRESHOLD; - if (threshold.getSelectedItem().equals("Above Threshold")) + boolean aboveThreshold = false; + boolean belowThreshold = false; + if (threshold.getSelectedIndex() == 1) { - aboveThreshold = AnnotationColourGradient.ABOVE_THRESHOLD; + aboveThreshold = true; } - else if (threshold.getSelectedItem().equals("Below Threshold")) + else if (threshold.getSelectedIndex() == 2) { - aboveThreshold = AnnotationColourGradient.BELOW_THRESHOLD; - } + belowThreshold = true; + } + boolean hasThreshold = aboveThreshold || belowThreshold; slider.setEnabled(true); thresholdValue.setEnabled(true); - - GraduatedColor acg; + + /* + * make the feature colour + */ + FeatureColourI acg; if (cs.isColourByLabel()) - { - acg = new GraduatedColor(oldminColour, oldmaxColour, min, max); - } else { - acg = new GraduatedColor(oldminColour=minColour.getBackground(), oldmaxColour=maxColour.getBackground(), min, max); - - } + { + 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 (aboveThreshold == AnnotationColourGradient.NO_THRESHOLD) + if (!hasThreshold) { slider.setEnabled(false); thresholdValue.setEnabled(false); thresholdValue.setText(""); thresholdIsMin.setEnabled(false); } - else if (aboveThreshold != AnnotationColourGradient.NO_THRESHOLD - && threshline == null) + else if (threshline == null) { - // todo visual indication of feature threshold - threshline = new jalview.datamodel.GraphLine( - (max - min) / 2f, - "Threshold", Color.black); + /* + * todo not yet implemented: visual indication of feature threshold + */ + threshline = new GraphLine((max - min) / 2f, "Threshold", + Color.black); } - if (aboveThreshold != AnnotationColourGradient.NO_THRESHOLD) + if (hasThreshold) { adjusting = true; - acg.setThresh(threshline.value); + acg.setThreshold(threshline.value); - float range = max * 1000f - - min * 1000f; + float range = (max - min) * scaleFactor; - slider.setMinimum((int) (min * 1000)); - slider.setMaximum((int) (max * 1000)); - slider.setValue((int) (threshline.value * 1000)); + 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(!colourByLabel.isSelected()); + thresholdIsMin.setEnabled(!byDescription.isSelected()); adjusting = false; } - acg.setThreshType(aboveThreshold); - if (thresholdIsMin.isSelected() && aboveThreshold != AnnotationColourGradient.NO_THRESHOLD) + acg.setAboveThreshold(aboveThreshold); + acg.setBelowThreshold(belowThreshold); + if (thresholdIsMin.isSelected() && hasThreshold) { acg.setAutoScaled(false); - if (aboveThreshold==AnnotationColourGradient.ABOVE_THRESHOLD) - { - acg = new GraduatedColor(acg, threshline.value, max); - } else { - acg = new GraduatedColor(acg, min,threshline.value); + if (aboveThreshold) + { + acg = new FeatureColour((FeatureColour) acg, threshline.value, max); + } + else + { + acg = new FeatureColour((FeatureColour) acg, min, threshline.value); } - } else { + } + else + { acg.setAutoScaled(true); } - acg.setColourByLabel(colourByLabel.isSelected()); + 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 { + } + else + { maxColour.setEnabled(true); minColour.setEnabled(true); + noValueCombo.setEnabled(true); maxColour.setBackground(oldmaxColour); - minColour.setBackground(oldminColour); maxColour.setForeground(oldmaxColour); + minColour.setBackground(oldminColour); minColour.setForeground(oldminColour); + noColour = oldNoColour; } - fr.featureColours.put(type,acg); + + /* + * save the colour, and repaint stuff + */ + fr.setColour(type, acg); cs = acg; - ap.paintAlignment(false); + ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview); } - private void raiseClosed() { - if (this.colourEditor!=null) - { - colourEditor.actionPerformed(new ActionEvent(this, 0, "CLOSED")); - } - } - public void ok_actionPerformed(ActionEvent e) + + private String[] fromAttributeDisplayName(String attribute) { - changeColour(); - try - { - frame.dispose(); - raiseClosed(); - } catch (Exception ex) - { - } + return attribute == null ? null : attribute.split(COLON); } - public void cancel_actionPerformed(ActionEvent e) + @Override + protected void raiseClosed() { - reset(); - try - { - frame.dispose(); -// frame.setClosed(true); - raiseClosed(); - } catch (Exception ex) + if (this.colourEditor != null) { + colourEditor.actionPerformed(new ActionEvent(this, 0, "CLOSED")); } } - void reset() + @Override + public void okPressed() { - fr.featureColours.put(type, oldcs); - ap.paintAlignment(false); - cs = null; + changeColour(false); } - public void thresholdCheck_actionPerformed(ActionEvent e) + @Override + public void cancelPressed() { - changeColour(); - } - - public void annotations_actionPerformed(ActionEvent e) - { - changeColour(); + reset(); } - public void threshold_actionPerformed(ActionEvent e) + /** + * 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() { - changeColour(); + fr.setColour(type, oldcs); + ap.paintAlignment(true, true); + cs = null; } - public void thresholdValue_actionPerformed(ActionEvent e) + /** + * Action on text entry of a threshold value + */ + protected void thresholdValue_actionPerformed() { try { float f = Float.parseFloat(thresholdValue.getText()); - slider.setValue((int) (f * 1000)); + slider.setValue((int) (f * scaleFactor)); threshline.value = f; + + /* + * force repaint of any Overview window or structure + */ + ap.paintAlignment(true, true); } catch (NumberFormatException ex) { } } - public void valueChanged() + /** + * 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 = (float) slider.getValue() / 1000f; - cs.setThresh(threshline.value); - changeColour(); - ap.paintAlignment(false); + /* + * 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); } - public void thresholdIsMin_actionPerformed(ActionEvent actionEvent) + void addActionListener(ActionListener graduatedColorEditor) { - changeColour(); + if (colourEditor != null) + { + System.err.println( + "IMPLEMENTATION ISSUE: overwriting action listener for FeatureColourChooser"); + } + colourEditor = graduatedColorEditor; } - public void colourByLabel_actionPerformed(ActionEvent actionEvent) + + /** + * Answers the last colour setting selected by user - either oldcs (which may + * be a java.awt.Color) or the new GraduatedColor + * + * @return + */ + FeatureColourI getLastColour() { - changeColour(); + if (cs == null) + { + return oldcs; + } + return cs; } - ActionListener colourEditor=null; - public void addActionListener(ActionListener graduatedColorEditor) + + /** + * 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) { - if (colourEditor!=null) + List validAtts = new ArrayList<>(); + List tooltips = new ArrayList<>(); + + FeatureAttributes fa = FeatureAttributes.getInstance(); + for (String[] attName : attNames) { - System.err.println("IMPLEMENTATION ISSUE: overwriting action listener for FeatureColourChooser"); + 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); } - colourEditor = graduatedColorEditor; + + 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); } }