/*
* Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
* Copyright (C) $$Year-Rel$$ The Jalview Authors
*
* This file is part of Jalview.
*
* Jalview is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* Jalview is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Jalview. If not, see .
* The Jalview Authors are detailed in the 'AUTHORS' file.
*/
package jalview.gui;
import jalview.api.FeatureColourI;
import jalview.datamodel.GraphLine;
import jalview.schemes.FeatureColour;
import jalview.util.MessageManager;
import java.awt.BorderLayout;
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.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
import javax.swing.JColorChooser;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.border.LineBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class FeatureColourChooser extends JalviewDialog
{
// FeatureSettings fs;
private FeatureRenderer fr;
private FeatureColourI cs;
private FeatureColourI oldcs;
private AlignmentPanel ap;
private boolean adjusting = false;
final private float min;
final private float max;
final private float scaleFactor;
private String type = null;
private JPanel minColour = new JPanel();
private JPanel maxColour = new JPanel();
private JComboBox threshold = new JComboBox<>();
private JSlider slider = new JSlider();
private JTextField thresholdValue = new JTextField(20);
// TODO implement GUI for tolower flag
// JCheckBox toLower = new JCheckBox();
private JCheckBox thresholdIsMin = new JCheckBox();
private JCheckBox colourByLabel = new JCheckBox();
private GraphLine threshline;
private Color oldmaxColour;
private Color oldminColour;
private ActionListener colourEditor = null;
/**
* Constructor
*
* @param frender
* @param theType
*/
public FeatureColourChooser(FeatureRenderer frender, String theType)
{
this(frender, false, theType);
}
/**
* Constructor, with option to make a blocking dialog (has to complete in the
* AWT event queue thread). Currently this option is always set to false.
*
* @param frender
* @param blocking
* @param theType
*/
FeatureColourChooser(FeatureRenderer frender, boolean blocking,
String theType)
{
this.fr = frender;
this.type = theType;
ap = fr.ap;
String title = MessageManager
.formatMessage("label.graduated_color_for_params", new String[]
{ theType });
initDialogFrame(this, true, blocking, title, 480, 185);
slider.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent evt)
{
if (!adjusting)
{
thresholdValue.setText((slider.getValue() / scaleFactor) + "");
sliderValueChanged();
}
}
});
slider.addMouseListener(new MouseAdapter()
{
@Override
public void mouseReleased(MouseEvent evt)
{
/*
* only update Overview and/or structure colouring
* when threshold slider drag ends (mouse up)
*/
if (ap != null)
{
ap.paintAlignment(true, true);
}
}
});
float mm[] = fr.getMinMax().get(theType)[0];
min = mm[0];
max = mm[1];
/*
* ensure scale factor allows a scaled range with
* 10 integer divisions ('ticks'); if we have got here,
* we should expect that max != min
*/
scaleFactor = (max == min) ? 1f : 100f / (max - min);
oldcs = fr.getFeatureColours().get(theType);
if (!oldcs.isSimpleColour())
{
if (oldcs.isAutoScaled())
{
// update the scale
cs = new FeatureColour((FeatureColour) oldcs, min, max);
}
else
{
cs = new FeatureColour((FeatureColour) oldcs);
}
}
else
{
// promote original color to a graduated color
Color bl = oldcs.getColour();
if (bl == null)
{
bl = Color.BLACK;
}
// original colour becomes the maximum colour
cs = new FeatureColour(Color.white, bl, mm[0], mm[1]);
cs.setColourByLabel(false);
}
minColour.setBackground(oldminColour = cs.getMinColour());
maxColour.setBackground(oldmaxColour = cs.getMaxColour());
adjusting = true;
try
{
jbInit();
} catch (Exception ex)
{
}
// update the gui from threshold state
thresholdIsMin.setSelected(!cs.isAutoScaled());
colourByLabel.setSelected(cs.isColourByLabel());
if (cs.hasThreshold())
{
// initialise threshold slider and selector
threshold.setSelectedIndex(cs.isAboveThreshold() ? 1 : 2);
slider.setEnabled(true);
slider.setValue((int) (cs.getThreshold() * scaleFactor));
thresholdValue.setEnabled(true);
threshline = new GraphLine((max - min) / 2f, "Threshold",
Color.black);
threshline.value = cs.getThreshold();
}
adjusting = false;
changeColour(false);
waitForInput();
}
private void jbInit() throws Exception
{
minColour.setFont(JvSwingUtils.getLabelFont());
minColour.setBorder(BorderFactory.createLineBorder(Color.black));
minColour.setPreferredSize(new Dimension(40, 20));
minColour.setToolTipText(MessageManager.getString("label.min_colour"));
minColour.addMouseListener(new MouseAdapter()
{
@Override
public void mousePressed(MouseEvent e)
{
if (minColour.isEnabled())
{
minColour_actionPerformed();
}
}
});
maxColour.setFont(JvSwingUtils.getLabelFont());
maxColour.setBorder(BorderFactory.createLineBorder(Color.black));
maxColour.setPreferredSize(new Dimension(40, 20));
maxColour.setToolTipText(MessageManager.getString("label.max_colour"));
maxColour.addMouseListener(new MouseAdapter()
{
@Override
public void mousePressed(MouseEvent e)
{
if (maxColour.isEnabled())
{
maxColour_actionPerformed();
}
}
});
maxColour.setBorder(new LineBorder(Color.black));
JLabel minText = new JLabel(MessageManager.getString("label.min"));
minText.setFont(JvSwingUtils.getLabelFont());
JLabel maxText = new JLabel(MessageManager.getString("label.max"));
maxText.setFont(JvSwingUtils.getLabelFont());
this.setLayout(new BorderLayout());
JPanel jPanel1 = new JPanel();
jPanel1.setBackground(Color.white);
JPanel jPanel2 = new JPanel();
jPanel2.setLayout(new FlowLayout());
jPanel2.setBackground(Color.white);
threshold.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
threshold_actionPerformed();
}
});
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
JPanel jPanel3 = new JPanel();
jPanel3.setLayout(new FlowLayout());
thresholdValue.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
thresholdValue_actionPerformed();
}
});
thresholdValue.addFocusListener(new FocusAdapter()
{
@Override
public void focusLost(FocusEvent e)
{
thresholdValue_actionPerformed();
}
});
slider.setPaintLabels(false);
slider.setPaintTicks(true);
slider.setBackground(Color.white);
slider.setEnabled(false);
slider.setOpaque(false);
slider.setPreferredSize(new Dimension(100, 32));
slider.setToolTipText(
MessageManager.getString("label.adjust_threshold"));
thresholdValue.setEnabled(false);
thresholdValue.setColumns(7);
jPanel3.setBackground(Color.white);
thresholdIsMin.setBackground(Color.white);
thresholdIsMin
.setText(MessageManager.getString("label.threshold_minmax"));
thresholdIsMin.setToolTipText(MessageManager
.getString("label.toggle_absolute_relative_display_threshold"));
thresholdIsMin.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent actionEvent)
{
thresholdIsMin_actionPerformed();
}
});
colourByLabel.setBackground(Color.white);
colourByLabel
.setText(MessageManager.getString("label.colour_by_label"));
colourByLabel.setToolTipText(MessageManager.getString(
"label.display_features_same_type_different_label_using_different_colour"));
colourByLabel.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent actionEvent)
{
colourByLabel_actionPerformed();
}
});
JPanel colourPanel = new JPanel();
colourPanel.setBackground(Color.white);
jPanel1.add(ok);
jPanel1.add(cancel);
jPanel2.add(colourByLabel, BorderLayout.WEST);
jPanel2.add(colourPanel, BorderLayout.EAST);
colourPanel.add(minText);
colourPanel.add(minColour);
colourPanel.add(maxText);
colourPanel.add(maxColour);
this.add(jPanel3, BorderLayout.CENTER);
jPanel3.add(threshold);
jPanel3.add(slider);
jPanel3.add(thresholdValue);
jPanel3.add(thresholdIsMin);
this.add(jPanel1, BorderLayout.SOUTH);
this.add(jPanel2, BorderLayout.NORTH);
}
/**
* Action on clicking the 'minimum colour' - open a colour chooser dialog, and
* set the selected colour (if the user does not cancel out of the dialog)
*/
protected void minColour_actionPerformed()
{
Color col = JColorChooser.showDialog(this,
MessageManager.getString("label.select_colour_minimum_value"),
minColour.getBackground());
if (col != null)
{
minColour.setBackground(col);
minColour.setForeground(col);
}
minColour.repaint();
changeColour(true);
}
/**
* Action on clicking the 'maximum colour' - open a colour chooser dialog, and
* set the selected colour (if the user does not cancel out of the dialog)
*/
protected void maxColour_actionPerformed()
{
Color col = JColorChooser.showDialog(this,
MessageManager.getString("label.select_colour_maximum_value"),
maxColour.getBackground());
if (col != null)
{
maxColour.setBackground(col);
maxColour.setForeground(col);
}
maxColour.repaint();
changeColour(true);
}
/**
* Constructs and sets the selected colour options as the colour for the
* feature type, and repaints the alignment, and optionally the Overview
* and/or structure viewer if open
*
* @param updateStructsAndOverview
*/
void changeColour(boolean updateStructsAndOverview)
{
// Check if combobox is still adjusting
if (adjusting)
{
return;
}
boolean aboveThreshold = false;
boolean belowThreshold = false;
if (threshold.getSelectedIndex() == 1)
{
aboveThreshold = true;
}
else if (threshold.getSelectedIndex() == 2)
{
belowThreshold = true;
}
boolean hasThreshold = aboveThreshold || belowThreshold;
slider.setEnabled(true);
thresholdValue.setEnabled(true);
FeatureColourI acg;
if (cs.isColourByLabel())
{
acg = new FeatureColour(oldminColour, oldmaxColour, min, max);
}
else
{
acg = new FeatureColour(oldminColour = minColour.getBackground(),
oldmaxColour = maxColour.getBackground(), min, max);
}
if (!hasThreshold)
{
slider.setEnabled(false);
thresholdValue.setEnabled(false);
thresholdValue.setText("");
thresholdIsMin.setEnabled(false);
}
else if (threshline == null)
{
/*
* todo not yet implemented: visual indication of feature threshold
*/
threshline = new GraphLine((max - min) / 2f, "Threshold",
Color.black);
}
if (hasThreshold)
{
adjusting = true;
acg.setThreshold(threshline.value);
float range = (max - min) * scaleFactor;
slider.setMinimum((int) (min * scaleFactor));
slider.setMaximum((int) (max * scaleFactor));
// slider.setValue((int) (threshline.value * scaleFactor));
slider.setValue(Math.round(threshline.value * scaleFactor));
thresholdValue.setText(threshline.value + "");
slider.setMajorTickSpacing((int) (range / 10f));
slider.setEnabled(true);
thresholdValue.setEnabled(true);
thresholdIsMin.setEnabled(!colourByLabel.isSelected());
adjusting = false;
}
acg.setAboveThreshold(aboveThreshold);
acg.setBelowThreshold(belowThreshold);
if (thresholdIsMin.isSelected() && hasThreshold)
{
acg.setAutoScaled(false);
if (aboveThreshold)
{
acg = new FeatureColour((FeatureColour) acg, threshline.value, max);
}
else
{
acg = new FeatureColour((FeatureColour) acg, min, threshline.value);
}
}
else
{
acg.setAutoScaled(true);
}
acg.setColourByLabel(colourByLabel.isSelected());
if (acg.isColourByLabel())
{
maxColour.setEnabled(false);
minColour.setEnabled(false);
maxColour.setBackground(this.getBackground());
maxColour.setForeground(this.getBackground());
minColour.setBackground(this.getBackground());
minColour.setForeground(this.getBackground());
}
else
{
maxColour.setEnabled(true);
minColour.setEnabled(true);
maxColour.setBackground(oldmaxColour);
minColour.setBackground(oldminColour);
maxColour.setForeground(oldmaxColour);
minColour.setForeground(oldminColour);
}
fr.setColour(type, acg);
cs = acg;
ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview);
}
@Override
protected void raiseClosed()
{
if (this.colourEditor != null)
{
colourEditor.actionPerformed(new ActionEvent(this, 0, "CLOSED"));
}
}
@Override
public void okPressed()
{
changeColour(false);
}
@Override
public void cancelPressed()
{
reset();
}
/**
* Action when the user cancels the dialog. All previous settings should be
* restored and rendered on the alignment, and any linked Overview window or
* structure.
*/
void reset()
{
fr.setColour(type, oldcs);
ap.paintAlignment(true, true);
cs = null;
}
/**
* Action on change of choice of No / Above / Below Threshold
*/
protected void threshold_actionPerformed()
{
changeColour(true);
}
/**
* Action on text entry of a threshold value
*/
protected void thresholdValue_actionPerformed()
{
try
{
float f = Float.parseFloat(thresholdValue.getText());
slider.setValue((int) (f * scaleFactor));
threshline.value = f;
/*
* force repaint of any Overview window or structure
*/
ap.paintAlignment(true, true);
} catch (NumberFormatException ex)
{
}
}
/**
* Action on change of threshold slider value. This may be done interactively
* (by moving the slider), or programmatically (to update the slider after
* manual input of a threshold value).
*/
protected void sliderValueChanged()
{
/*
* squash rounding errors by forcing min/max of slider to
* actual min/max of feature score range
*/
int value = slider.getValue();
threshline.value = value == slider.getMaximum() ? max
: (value == slider.getMinimum() ? min : value / scaleFactor);
cs.setThreshold(threshline.value);
/*
* repaint alignment, but not Overview or structure,
* to avoid overload while dragging the slider
*/
changeColour(false);
}
protected void thresholdIsMin_actionPerformed()
{
changeColour(true);
}
protected void colourByLabel_actionPerformed()
{
changeColour(true);
}
void addActionListener(ActionListener graduatedColorEditor)
{
if (colourEditor != null)
{
System.err.println(
"IMPLEMENTATION ISSUE: overwriting action listener for FeatureColourChooser");
}
colourEditor = graduatedColorEditor;
}
/**
* Answers the last colour setting selected by user - either oldcs (which may
* be a java.awt.Color) or the new GraduatedColor
*
* @return
*/
FeatureColourI getLastColour()
{
if (cs == null)
{
return oldcs;
}
return cs;
}
}