/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* 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
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.gui.new_plotter.gui.dialog;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Rectangle2D;
import java.util.Vector;
import javax.swing.ButtonGroup;
import javax.swing.DefaultComboBoxModel;
import javax.swing.InputVerifier;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSeparator;
import javax.swing.JTextField;
import javax.swing.WindowConstants;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.XYPlot;
import com.rapidminer.gui.ApplicationFrame;
import com.rapidminer.gui.new_plotter.configuration.AxisParallelLineConfiguration;
import com.rapidminer.gui.new_plotter.configuration.LineFormat.LineStyle;
import com.rapidminer.gui.new_plotter.configuration.PlotConfiguration;
import com.rapidminer.gui.new_plotter.configuration.RangeAxisConfig;
import com.rapidminer.gui.new_plotter.engine.jfreechart.JFreeChartPlotEngine;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.tools.I18N;
/**
* This dialog allows the user to configure the addition of a crosshair line in the new charts.
*
* @author Marco Boeck
*
*/
public class AddParallelLineDialog extends JDialog {
/** the ok {@link JButton} */
private JButton okButton;
/** the cancel {@link JButton} */
private JButton cancelButton;
/** the {@link JTextField} for the x value */
private JTextField xField;
/** the {@link JTextField} for the y value */
private JTextField yField;
/** the x value the user specified */
private double x;
/** the y value the user specified */
private double y;
/** if selected, the line will be horizontal */
private JRadioButton horizontalLineRadiobutton;
/** if selected, the line will be vertical */
private JRadioButton verticalLineRadiobutton;
/**
* the {@link JComboBox} where the {@link RangeAxisConfig} will be selected if horizontal line
* is selected
*/
private JComboBox<RangeAxisConfig> rangeAxisSelectionCombobox;
/** this button modifies the line */
private JButton modifyLineButton;
/** the current {@link JFreeChartPlotEngine} */
private JFreeChartPlotEngine engine;
/** the current {@link PlotConfiguration} */
private PlotConfiguration plotConfig;
/** the position of the mouse, determines preselected x/y values */
private Point mousePosition;
/** the {@link EditParallelLineDialog} instance */
private EditParallelLineDialog dialog;
/** the current line to add */
private AxisParallelLineConfiguration line;
private static final long serialVersionUID = 1932257219370926682L;
/**
* Creates a new {@link AddParallelLineDialog}.
*/
public AddParallelLineDialog() {
super(ApplicationFrame.getApplicationFrame());
x = 1.0;
y = 1.0;
setupGUI();
}
/**
* Setup the GUI.
*/
private void setupGUI() {
JPanel mainPanel = new JPanel();
this.setContentPane(mainPanel);
// start layout
mainPanel.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weightx = 1;
gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets(5, 5, 2, 5);
horizontalLineRadiobutton = new JRadioButton(I18N.getMessage(I18N.getGUIBundle(),
"gui.action.add_parallel_line.horizontal.label"));
horizontalLineRadiobutton.setToolTipText(I18N.getMessage(I18N.getGUIBundle(),
"gui.action.add_parallel_line.horizontal.tip"));
horizontalLineRadiobutton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setHorizontalLineSelected();
}
});
horizontalLineRadiobutton.setSelected(true);
this.add(horizontalLineRadiobutton, gbc);
gbc.gridx = 1;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.EAST;
verticalLineRadiobutton = new JRadioButton(I18N.getMessage(I18N.getGUIBundle(),
"gui.action.add_parallel_line.vertical.label"));
verticalLineRadiobutton.setToolTipText(I18N.getMessage(I18N.getGUIBundle(),
"gui.action.add_parallel_line.vertical.tip"));
verticalLineRadiobutton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setVerticalLineSelected();
}
});
this.add(verticalLineRadiobutton, gbc);
ButtonGroup group = new ButtonGroup();
group.add(horizontalLineRadiobutton);
group.add(verticalLineRadiobutton);
gbc.gridx = 0;
gbc.gridy = 1;
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1;
gbc.gridwidth = 2;
gbc.anchor = GridBagConstraints.CENTER;
rangeAxisSelectionCombobox = new JComboBox<>();
rangeAxisSelectionCombobox.setToolTipText(
I18N.getMessage(I18N.getGUIBundle(), "gui.action.add_parallel_line.range_axis_combobox.tip"));
rangeAxisSelectionCombobox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
updateYFieldValue();
}
});
this.add(rangeAxisSelectionCombobox, gbc);
gbc.gridx = 0;
gbc.gridy = 2;
gbc.fill = GridBagConstraints.NONE;
gbc.weightx = 1;
gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets(5, 5, 2, 5);
JLabel xLabel = new JLabel(I18N.getMessage(I18N.getGUIBundle(), "gui.action.add_parallel_line.width.label"));
this.add(xLabel, gbc);
gbc.gridx = 1;
gbc.gridy = 2;
gbc.insets = new Insets(2, 5, 2, 5);
gbc.fill = GridBagConstraints.HORIZONTAL;
xField = new JTextField();
xField.setText(String.valueOf(x));
xField.setInputVerifier(new InputVerifier() {
@Override
public boolean verify(JComponent input) {
return verifyYInput(input);
}
});
xField.setToolTipText(I18N.getMessage(I18N.getGUIBundle(), "gui.action.add_parallel_line.width.tip"));
xField.setEnabled(false);
this.add(xField, gbc);
gbc.gridx = 0;
gbc.gridy = 3;
gbc.fill = GridBagConstraints.NONE;
JLabel yLabel = new JLabel(I18N.getMessage(I18N.getGUIBundle(), "gui.action.add_parallel_line.height.label"));
this.add(yLabel, gbc);
gbc.gridx = 1;
gbc.gridy = 3;
gbc.fill = GridBagConstraints.HORIZONTAL;
yField = new JTextField();
yField.setToolTipText(I18N.getMessage(I18N.getGUIBundle(), "gui.action.add_parallel_line.height.tip"));
yField.setText(String.valueOf(y));
yField.setInputVerifier(new InputVerifier() {
@Override
public boolean verify(JComponent input) {
return verifyXInput(input);
}
});
this.add(yField, gbc);
gbc.gridx = 0;
gbc.gridy = 4;
gbc.fill = GridBagConstraints.NONE;
gbc.insets = new Insets(10, 5, 0, 5);
modifyLineButton = new JButton();
modifyLineButton
.setToolTipText(I18N.getMessage(I18N.getGUIBundle(), "gui.action.add_parallel_line.modify_line.tip"));
modifyLineButton.setIcon(SwingTools.createIcon("16/"
+ I18N.getMessage(I18N.getGUIBundle(), "gui.action.add_parallel_line.modify_line.icon")));
modifyLineButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
modifyLine();
}
});
this.add(modifyLineButton, gbc);
gbc.gridx = 0;
gbc.gridy = 5;
gbc.gridwidth = 2;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.anchor = GridBagConstraints.CENTER;
gbc.insets = new Insets(15, 5, 5, 5);
this.add(new JSeparator(), gbc);
gbc.gridx = 0;
gbc.gridy = 6;
gbc.gridwidth = 1;
gbc.fill = GridBagConstraints.NONE;
gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets(5, 5, 5, 5);
okButton = new JButton(I18N.getMessage(I18N.getGUIBundle(), "gui.action.add_parallel_line.ok.label"));
okButton.setToolTipText(I18N.getMessage(I18N.getGUIBundle(), "gui.action.add_parallel_line.ok.tip"));
okButton.setIcon(SwingTools.createIcon("24/"
+ I18N.getMessage(I18N.getGUIBundle(), "gui.action.add_parallel_line.ok.icon")));
okButton.setMnemonic(I18N.getMessage(I18N.getGUIBundle(), "gui.action.add_parallel_line.ok.mne").toCharArray()[0]);
okButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
boolean successful = addSpecifiedLine();
// don't dispose dialog if not successful
if (!successful) {
return;
}
AddParallelLineDialog.this.dispose();
}
});
okButton.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
okButton.doClick();
}
}
});
this.add(okButton, gbc);
gbc.gridx = 1;
gbc.gridy = 6;
gbc.fill = GridBagConstraints.NONE;
gbc.anchor = GridBagConstraints.EAST;
cancelButton = new JButton(I18N.getMessage(I18N.getGUIBundle(), "gui.action.add_parallel_line.cancel.label"));
cancelButton.setToolTipText(I18N.getMessage(I18N.getGUIBundle(), "gui.action.add_parallel_line.cancel.tip"));
cancelButton.setIcon(SwingTools.createIcon("24/"
+ I18N.getMessage(I18N.getGUIBundle(), "gui.action.add_parallel_line.cancel.icon")));
cancelButton.setMnemonic(I18N.getMessage(I18N.getGUIBundle(), "gui.action.add_parallel_line.cancel.mne")
.toCharArray()[0]);
cancelButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// cancel requested, close dialog
AddParallelLineDialog.this.dispose();
}
});
this.add(cancelButton, gbc);
// misc settings
this.setMinimumSize(new Dimension(300, 250));
// center dialog
this.setLocationRelativeTo(getOwner());
this.setTitle(I18N.getMessage(I18N.getGUIBundle(), "gui.action.add_parallel_line.title.label"));
this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
this.setModal(true);
this.addWindowListener(new WindowAdapter() {
@Override
public void windowActivated(WindowEvent e) {
okButton.requestFocusInWindow();
}
});
}
/**
* Sets the mouse position (defines preselected x/y values).
*
* @param mousePosition
*/
public void setMousePosition(Point mousePosition) {
if (mousePosition == null) {
throw new IllegalArgumentException("mousePosition must not be null!");
}
this.mousePosition = mousePosition;
}
/**
* Sets the current {@link JFreeChartPlotEngine} for this dialog.
*
* @param engine
*/
public void setChartEngine(JFreeChartPlotEngine engine) {
if (engine == null) {
throw new IllegalArgumentException("engine must not be null!");
}
this.engine = engine;
setPlotConfiguration(engine.getPlotInstance().getMasterPlotConfiguration());
}
/**
* Shows the dialog.
*/
public void showDialog() {
if (line == null) {
line = createLine(1.0);
} else {
line = line.clone();
}
if (horizontalLineRadiobutton.isSelected()) {
line.setValue(Double.parseDouble(yField.getText()));
} else {
line.setValue(Double.parseDouble(xField.getText()));
}
setVisible(true);
}
/**
* Sets the current {@link PlotConfiguration} for this dialog.
*
* @param plotConfig
*/
private void setPlotConfiguration(PlotConfiguration plotConfig) {
if (plotConfig == null) {
throw new IllegalArgumentException("plotConfig must not be null!");
}
this.plotConfig = plotConfig;
Vector<RangeAxisConfig> rangeConfigsVector = new Vector<RangeAxisConfig>();
String selectedItem = String.valueOf(rangeAxisSelectionCombobox.getSelectedItem());
for (RangeAxisConfig config : this.plotConfig.getRangeAxisConfigs()) {
rangeConfigsVector.add(config);
}
rangeAxisSelectionCombobox.setModel(new DefaultComboBoxModel<>(rangeConfigsVector));
// reselect the previously selected RangeAxisConfig (if it is still there)
if (selectedItem != null) {
for (int i = 0; i < rangeAxisSelectionCombobox.getItemCount(); i++) {
if (String.valueOf(rangeAxisSelectionCombobox.getItemAt(i)).equals(selectedItem)) {
rangeAxisSelectionCombobox.setSelectedIndex(i);
break;
}
}
}
// calculate preselected x/y values
if (mousePosition != null) {
Rectangle2D plotArea = engine.getChartPanel().getScreenDataArea();
if (engine.getChartPanel().getChart().getPlot() instanceof XYPlot) {
XYPlot plot = (XYPlot) engine.getChartPanel().getChart().getPlot();
// calculate x value
double chartX = plot.getDomainAxis().java2DToValue(mousePosition.getX(), plotArea, plot.getDomainAxisEdge());
xField.setText(String.valueOf(chartX));
// calculate y value
for (int i = 0; i < plot.getRangeAxisCount(); i++) {
ValueAxis config = plot.getRangeAxis(i);
if (config != null && config.getLabel() != null) {
if (config.getLabel().equals(String.valueOf(rangeAxisSelectionCombobox.getSelectedItem()))) {
double chartY = config.java2DToValue(mousePosition.getY(), plotArea, plot.getRangeAxisEdge());
yField.setText(String.valueOf(chartY));
}
}
}
}
}
}
/**
* Horizontal line selected.
*/
private void setHorizontalLineSelected() {
// horizontal line, so only y-value and rangeAxis is of interest, disable other fields
rangeAxisSelectionCombobox.setEnabled(true);
xField.setEnabled(false);
yField.setEnabled(true);
okButton.requestFocusInWindow();
}
/**
* Vertical line selected.
*/
private void setVerticalLineSelected() {
// vertical line, so only x-value is of interest, disable other fields
rangeAxisSelectionCombobox.setEnabled(false);
xField.setEnabled(true);
yField.setEnabled(false);
okButton.requestFocusInWindow();
}
/**
* Updates the preselected y-value.
*/
private void updateYFieldValue() {
// update preselected y value because range axis has been changed
if (mousePosition != null) {
Rectangle2D plotArea = engine.getChartPanel().getScreenDataArea();
if (engine.getChartPanel().getChart().getPlot() instanceof XYPlot) {
XYPlot plot = (XYPlot) engine.getChartPanel().getChart().getPlot();
// calculate y value
for (int i = 0; i < plot.getRangeAxisCount(); i++) {
ValueAxis config = plot.getRangeAxis(i);
if (config != null && config.getLabel() != null) {
if (config.getLabel().equals(String.valueOf(rangeAxisSelectionCombobox.getSelectedItem()))) {
double chartY = config.java2DToValue(mousePosition.getY(), plotArea, plot.getRangeAxisEdge());
yField.setText(String.valueOf(chartY));
}
}
}
}
}
}
/**
* Verify that the y-value is correct.
*
* @param input
* @return true if the value is valid; false otherwise
*/
private boolean verifyYInput(JComponent input) {
JTextField textField = (JTextField) input;
String inputString = textField.getText();
try {
Double.parseDouble(inputString);
} catch (NumberFormatException e) {
textField.setForeground(Color.RED);
return false;
}
textField.setForeground(Color.BLACK);
return true;
}
/**
* Verify that the x-value is correct.
*
* @param input
* @return true if the value is valid; false otherwise
*/
private boolean verifyXInput(JComponent input) {
JTextField textField = (JTextField) input;
String inputString = textField.getText();
try {
Double.parseDouble(inputString);
} catch (NumberFormatException e) {
textField.setForeground(Color.RED);
return false;
}
textField.setForeground(Color.BLACK);
return true;
}
/**
* Creates the specified line.
*
* @return true if the line has been created; false otherwise
*/
private boolean addSpecifiedLine() {
if (horizontalLineRadiobutton.isSelected()) {
Object selectedItem = rangeAxisSelectionCombobox.getSelectedItem();
if (selectedItem != null && selectedItem instanceof RangeAxisConfig) {
// make sure y value is valid, otherwise don't do anything!
if (!yField.getInputVerifier().verify(yField)) {
yField.requestFocusInWindow();
return false;
}
RangeAxisConfig config = (RangeAxisConfig) selectedItem;
if (line == null) {
line = createLine(Double.parseDouble(yField.getText()));
}
line.setValue(Double.parseDouble(yField.getText()));
config.getCrossHairLines().addLine(line);
}
} else if (verticalLineRadiobutton.isSelected()) {
// make sure x value is valid, otherwise don't do anything!
if (!xField.getInputVerifier().verify(xField)) {
xField.requestFocusInWindow();
return false;
}
if (line == null) {
line = createLine(Double.parseDouble(xField.getText()));
}
line.setValue(Double.parseDouble(xField.getText()));
plotConfig.getDomainConfigManager().getCrosshairLines().addLine(line);
}
return true;
}
/**
* Modifes the line.
*/
private void modifyLine() {
if (line == null) {
line = createLine(1.0);
}
if (dialog == null) {
dialog = new EditParallelLineDialog();
}
if (horizontalLineRadiobutton.isSelected()) {
// make sure y value is valid, otherwise don't do anything!
if (!yField.getInputVerifier().verify(yField)) {
yField.requestFocusInWindow();
return;
}
line.setValue(Double.parseDouble(yField.getText()));
} else {
// make sure x value is valid, otherwise don't do anything!
if (!xField.getInputVerifier().verify(xField)) {
xField.requestFocusInWindow();
return;
}
line.setValue(Double.parseDouble(xField.getText()));
}
dialog.setLine(line, false);
dialog.showDialog();
}
/**
* Creates a new {@link AxisParallelLineConfiguration} with solid {@link LineStyle}.
*
* @param value
* @return
*/
private AxisParallelLineConfiguration createLine(double value) {
AxisParallelLineConfiguration line = new AxisParallelLineConfiguration(value, false);
line.getFormat().setStyle(LineStyle.SOLID);
return line;
}
}