/**
* 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.properties.celleditors.value;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import javax.swing.AbstractCellEditor;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JEditorPane;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.ScrollPaneConstants;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import com.rapidminer.Process;
import com.rapidminer.gui.properties.ExpressionPropertyDialog;
import com.rapidminer.gui.tools.ExtendedJScrollPane;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.operator.Operator;
import com.rapidminer.parameter.ParameterTypeExpression;
/**
* Cell editor that contains an expression value
*
* @author Ingo Mierswa, Nils Woehler, Sabrina Kirstein
*
*/
public class ExpressionValueCellEditor extends AbstractCellEditor implements PropertyValueCellEditor {
private static final long serialVersionUID = 2355429695124754211L;
/** defines whether the overflow indicator should be shown */
private boolean showOverflow = false;
/** button which opens the {@link ExpressionPropertyDialog} */
private JButton button;
/** the controlling process */
private Process controllingProcess;
/** used to get the line count of the current text */
private RSyntaxTextArea currentExpression = new RSyntaxTextArea();
/** margin of the expression */
private Insets margin = new Insets(8, 5, 8, 5);
/** background color of the overflow indicator */
private static final Color LIGHTER_GRAY = new Color(237, 237, 237);
/** name of the calculator icon */
private static final String CALCULATOR_NAME = "calculator.png";
/** icon showing the calculator */
private static Icon CALCULATOR_ICON = null;
static {
CALCULATOR_ICON = SwingTools.createIcon("16/" + CALCULATOR_NAME);
}
/** panel which is returned */
private final JPanel panel = new JPanel();
/** {@link JEditorPane}, which contains the expression */
private final JEditorPane editorPane = new JEditorPane();
/** parameter type */
private final ParameterTypeExpression type;
/** layout of the panel */
private final GridBagLayout gridBagLayout = new GridBagLayout();
public ExpressionValueCellEditor(ParameterTypeExpression type) {
this.type = type;
panel.setLayout(gridBagLayout);
panel.setToolTipText(type.getDescription());
editorPane.setMargin(margin);
editorPane.setAlignmentX(JEditorPane.LEFT_ALIGNMENT);
editorPane.setToolTipText(type.getDescription());
editorPane.addFocusListener(new FocusListener() {
@Override
public void focusLost(FocusEvent e) {
// fire only if the focus didn't move to the button. If this check
// would not be included, fireEditingStopped() would prevent the button's
// ActionEvent from being fired. The user would have to click a second time to
// trigger the button action.
// Additionally, the event is only fired if the focus loss is permamently,
// i.e. it is not fired if the user e.g. just switched to another window.
// Otherwise any changes made after switching back to rapidminer would
// not be saved for the same reasons as stated above.
Component oppositeComponent = e.getOppositeComponent();
if (oppositeComponent != button && !e.isTemporary()) {
fireEditingStopped();
}
resetEditorPanePosition();
updateOverflowIndicator();
}
@Override
public void focusGained(FocusEvent e) {
resetEditorPanePosition();
// don't show the overflow indicator if the user is editing the field
showOverflow = false;
panel.repaint();
}
});
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.weighty = 1;
c.weightx = 1;
ExtendedJScrollPane scrollPane = new ExtendedJScrollPane(editorPane) {
private static final long serialVersionUID = 1L;
@Override
public void paint(Graphics g) {
super.paint(g);
if (showOverflow) {
drawOverflowIndicator(g, getPreferredSize().width);
}
};
};
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
scrollPane.setBorder(null);
panel.add(scrollPane, c);
button = new JButton(CALCULATOR_ICON);
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
buttonPressed();
resetEditorPanePosition();
updateOverflowIndicator();
}
});
button.addFocusListener(new FocusListener() {
@Override
public void focusLost(FocusEvent e) {
if (e.getOppositeComponent() != editorPane && !e.isTemporary()) {
fireEditingStopped();
}
resetEditorPanePosition();
updateOverflowIndicator();
}
@Override
public void focusGained(FocusEvent e) {
resetEditorPanePosition();
updateOverflowIndicator();
}
});
c.gridwidth = GridBagConstraints.REMAINDER;
c.weightx = 0;
panel.add(button, c);
}
/** Does nothing. */
@Override
public void setOperator(Operator operator) {
this.controllingProcess = operator.getProcess();
}
@Override
public boolean rendersLabel() {
return false;
}
@Override
public Object getCellEditorValue() {
return editorPane.getText().trim().length() == 0 ? null : editorPane.getText().trim();
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int col) {
editorPane.setText(value == null ? "" : value.toString());
resetEditorPanePosition();
updateOverflowIndicator();
return panel;
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
int row, int column) {
return getTableCellEditorComponent(table, value, isSelected, row, column);
}
@Override
public boolean useEditorAsRenderer() {
return false;
}
/** set the text of the expression and update the panel */
protected void setText(String text) {
if (text == null) {
editorPane.setText("");
} else {
editorPane.setText(text);
}
resetEditorPanePosition();
updateOverflowIndicator();
}
/** Reset the position of the caret, when the expression lost its focus */
private void resetEditorPanePosition() {
editorPane.setCaretPosition(0);
editorPane.setMargin(margin);
}
/**
* Open the {@link ExpressionPropertyDialog} when the button was pressed
*/
private void buttonPressed() {
Object value = getCellEditorValue();
String initial = value == null ? null : value.toString();
ExpressionPropertyDialog dialog = new ExpressionPropertyDialog(type, controllingProcess, initial);
dialog.setVisible(true);
if (dialog.isOk()) {
setText(dialog.getExpression());
}
fireEditingStopped();
resetEditorPanePosition();
updateOverflowIndicator();
}
/**
* Draws indicator in case the expression text overflows on the y axis.
*
* @param g
* the graphics context to draw upon
* @param maxX
* maximal width
*/
private void drawOverflowIndicator(final Graphics g, int maxX) {
int width = 25;
int height = 10;
int xOffset = 10;
int stepSize = width / 5;
int dotSize = 3;
int x = maxX - width - xOffset;
int y = button.getSize().height - height;
g.setColor(LIGHTER_GRAY);
g.fillRect(x, y, width, width);
g.setColor(Color.GRAY);
g.drawRoundRect(x, y, width, width, 5, 5);
g.setColor(Color.BLACK);
g.fillOval(x + stepSize, y + 4, dotSize, dotSize);
g.fillOval(x + stepSize * 2, y + 4, dotSize, dotSize);
g.fillOval(x + stepSize * 3, y + 4, dotSize, dotSize);
g.dispose();
}
/**
* @return the line count of the current text
*/
private int getLineCount() {
currentExpression.setText(editorPane.getText());
return currentExpression.getLineCount();
}
/**
* Check whether the overflow indicator should be shown (if the expression has more than one
* line) and update the indicator
*/
private void updateOverflowIndicator() {
if (getLineCount() > 1) {
showOverflow = true;
panel.repaint();
} else {
showOverflow = false;
panel.repaint();
}
}
}