/**
* 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.tools;
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.KeyEvent;
import java.lang.ref.WeakReference;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import com.rapidminer.Process;
import com.rapidminer.gui.RapidMinerGUI;
import com.rapidminer.gui.flow.QuickFixDialog;
import com.rapidminer.gui.renderer.RendererService;
import com.rapidminer.gui.tools.bubble.BubbleWindow;
import com.rapidminer.gui.tools.bubble.BubbleWindow.AlignedSide;
import com.rapidminer.gui.tools.bubble.BubbleWindow.BubbleStyle;
import com.rapidminer.gui.tools.bubble.OperatorInfoBubble;
import com.rapidminer.gui.tools.bubble.OperatorInfoBubble.OperatorBubbleBuilder;
import com.rapidminer.gui.tools.bubble.ParameterErrorInfoBubble;
import com.rapidminer.gui.tools.bubble.ParameterErrorInfoBubble.ParameterErrorBubbleBuilder;
import com.rapidminer.gui.tools.bubble.PortInfoBubble;
import com.rapidminer.gui.tools.bubble.PortInfoBubble.PortBubbleBuilder;
import com.rapidminer.gui.tools.components.LinkLocalButton;
import com.rapidminer.operator.ExecutionUnit;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.PortUserError;
import com.rapidminer.operator.ProcessRootOperator;
import com.rapidminer.operator.ProcessSetupError;
import com.rapidminer.operator.ProcessSetupError.Severity;
import com.rapidminer.operator.UserError;
import com.rapidminer.operator.error.AttributeNotFoundError;
import com.rapidminer.operator.error.ParameterError;
import com.rapidminer.operator.error.ProcessExecutionUserErrorError;
import com.rapidminer.operator.ports.InputPort;
import com.rapidminer.operator.ports.OutputPort;
import com.rapidminer.operator.ports.Port;
import com.rapidminer.parameter.CombinedParameterType;
import com.rapidminer.parameter.ParameterType;
import com.rapidminer.parameter.ParameterTypeAttributes;
import com.rapidminer.parameter.UndefinedParameterError;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.ParameterService;
/**
* This class contains GUI utility methods related to {@link com.rapidminer.Process}es.
*
* @author Marco Boeck
* @since 6.5.0
*
*/
public class ProcessGUITools {
private static class BubbleDelegator {
private WeakReference<BubbleWindow> bubble;
private void setBubbleWindow(BubbleWindow bubble) {
this.bubble = new WeakReference<BubbleWindow>(bubble);
}
/**
* @return the bubble or {@code null}
*/
private BubbleWindow getBubble() {
return bubble.get();
}
}
/**
* Private constructor which throws if called.
*/
private ProcessGUITools() {
throw new UnsupportedOperationException("Static utility class");
}
/**
* Displays the best machting error bubble depending on the given {@link UserError}. If no
* explicit match can be found, displays a generic error bubble.
*
* @param error
* the error in question, must not be {@code null}
* @return the bubble instance, never {@code null}
*/
public static BubbleWindow displayBubbleForUserError(final UserError error) {
if (error == null) {
throw new IllegalArgumentException("userError must not be null!");
}
if (error instanceof PortUserError) {
PortUserError userError = (PortUserError) error;
if (userError.getCode() == 149) {
// for "no data" errors we display an error bubble instead of a dialog
return displayInputPortNoDataInformation(userError.getPort());
} else if (userError.getCode() == 156) {
return displayInputPortWrongTypeInformation(userError.getPort(), userError.getExpectedType(),
userError.getActualType());
} else {
return displayGenericPortError(userError);
}
} else if (error.getClass().equals(UndefinedParameterError.class)) {
UndefinedParameterError userError = (UndefinedParameterError) error;
if (userError.getCode() == 205 || userError.getCode() == 217) {
// for "missing mandatory parameter" errors we display an error bubble
// instead of a dialog
Operator op = userError.getOperator();
ParameterType param = op != null ? op.getParameterType(userError.getKey()) : null;
if (op != null && param != null) {
return displayMissingMandatoryParameterInformation(op, param);
} else {
return displayGenericUserError(error);
}
} else {
Operator op = userError.getOperator();
ParameterType param = op != null ? op.getParameterType(userError.getKey()) : null;
if (op != null && param != null) {
return displayMissingMandatoryParameterInformation(op, param);
} else {
return displayGenericUserError(error);
}
}
} else if (error instanceof ParameterError) {
ParameterError userError = (ParameterError) error;
Operator op = userError.getOperator();
ParameterType param = op != null ? op.getParameterType(userError.getKey()) : null;
if (op != null && param != null) {
return displayGenericParameterError(userError);
} else {
return displayGenericUserError(error);
}
} else if (error instanceof AttributeNotFoundError) {
AttributeNotFoundError userError = (AttributeNotFoundError) error;
Operator op = userError.getOperator();
ParameterType param = op != null ? op.getParameterType(userError.getKey()) : null;
if (op != null && param != null) {
return displayAttributeNotFoundParameterInformation(userError);
} else {
return displayGenericUserError(error);
}
} else if (error instanceof ProcessExecutionUserErrorError) {
ProcessExecutionUserErrorError userError = (ProcessExecutionUserErrorError) error;
if (userError.getUserError() != null && userError.getUserError().getOperator() != null) {
return displayUserErrorInExecutedProcess(userError);
} else {
return displayGenericUserError(error);
}
} else {
return displayGenericUserError(error);
}
}
/**
* Displays a warning bubble that alerts the user that he failed to connect any of the process
* result ports.
*
* The bubble is located at the first result port of the outermost process and the process view
* will change to said port. The {@link ResultWarningPreventionRegistry} contains a list with
* Operators which can suppress this warning.
*
* @param process
* the process in question
* @return the {@link PortInfoBubble} instance, never {@code null}
*/
public static PortInfoBubble displayPrecheckNoResultPortInformation(final Process process) {
if (process == null) {
throw new IllegalArgumentException("port must not be null!");
}
Port firstResultPort = process.getRootOperator().getSubprocess(0).getInnerSinks().getPortByIndex(0);
JButton ackButton = new JButton(I18N.getGUIMessage("gui.bubble.process_unconnected_result_port.button.label"));
ackButton.setToolTipText(I18N.getGUIMessage("gui.bubble.process_unconnected_result_port.button.tip"));
final BubbleDelegator bubbleDelegator = new BubbleDelegator();
final JCheckBox dismissForeverCheckBox = new JCheckBox(
I18N.getGUIMessage("gui.bubble.process_unconnected_result_port.button_dismiss.label"));
dismissForeverCheckBox
.setToolTipText(I18N.getGUIMessage("gui.bubble.process_unconnected_result_port.button_dismiss.tip"));
ResourceAction runAnywayAction = new ResourceAction("process_unconnected_result_port.button_run_anyway", "F11") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(final ActionEvent e) {
BubbleWindow bubble = bubbleDelegator.getBubble();
if (bubble != null) {
bubble.killBubble(true);
}
if (dismissForeverCheckBox.isSelected()) {
// store setting
ParameterService.setParameterValue(RapidMinerGUI.PROPERTY_SHOW_NO_RESULT_WARNING,
String.valueOf(Boolean.FALSE));
ParameterService.saveParameters();
}
// run process without checking for problems
RapidMinerGUI.getMainFrame().runProcess(false);
}
};
LinkLocalButton runAnywayButton = new LinkLocalButton(runAnywayAction);
runAnywayButton
.setToolTipText(I18N.getGUIMessage("gui.bubble.process_unconnected_result_port.button_run_anyway.tip"));
runAnywayButton.registerKeyboardAction(runAnywayAction, KeyStroke.getKeyStroke(KeyEvent.VK_F11, 0),
JComponent.WHEN_IN_FOCUSED_WINDOW);
PortBubbleBuilder builder = new PortBubbleBuilder(RapidMinerGUI.getMainFrame(), firstResultPort,
"process_unconnected_result_port");
JPanel actionPanel = new JPanel(new GridBagLayout());
actionPanel.setOpaque(false);
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets(0, 0, 5, 0);
gbc.gridwidth = 2;
dismissForeverCheckBox.setOpaque(false);
actionPanel.add(dismissForeverCheckBox, gbc);
gbc.gridy += 1;
gbc.gridwidth = 1;
gbc.insets = new Insets(0, 0, 0, 10);
actionPanel.add(ackButton, gbc);
gbc.gridx += 1;
actionPanel.add(runAnywayButton, gbc);
final PortInfoBubble noResultConnectionBubble = builder.setHideOnConnection(true).setAlignment(AlignedSide.LEFT)
.setStyle(BubbleStyle.WARNING).setHideOnProcessRun(false).setEnsureVisible(true).hideCloseButton()
.setAdditionalComponents(new JComponent[] { actionPanel }).build();
ackButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
noResultConnectionBubble.killBubble(true);
if (dismissForeverCheckBox.isSelected()) {
// store setting
ParameterService.setParameterValue(RapidMinerGUI.PROPERTY_SHOW_NO_RESULT_WARNING,
String.valueOf(Boolean.FALSE));
ParameterService.saveParameters();
}
}
});
bubbleDelegator.setBubbleWindow(noResultConnectionBubble);
noResultConnectionBubble.setVisible(true);
return noResultConnectionBubble;
}
/**
* Displays a warning bubble that alerts the user that a mandatory parameter of an operator
* needs a value because it has no default value. The bubble is located at the operator and the
* process view will change to said operator. This is a bubble which occurs during the check for
* errors before process execution so it contains a link button to run the process anyway.
*
* @param op
* the operator for which to display the warning
* @param param
* the parameter for which to display the warning
* @return the {@link OperatorInfoBubble} instance, never {@code null}
*/
public static OperatorInfoBubble displayPrecheckMissingMandatoryParameterWarning(final Operator op,
final ParameterType param) {
return displayPrecheckMissingMandatoryParameterWarning(op, param, true);
}
/**
* Displays a warning bubble that alerts the user that a mandatory parameter of an operator
* needs a value because it has no default value. The bubble is located at the operator and the
* process view will change to said operator. This is a bubble which occurs during the check for
* errors before process execution so it contains a link button to run the process anyway.
*
* @param op
* the operator for which to display the warning
* @param param
* the parameter for which to display the warning
* @param showRunAnyway
* whether the run anyway button should be shown
* @return the {@link OperatorInfoBubble} instance, never {@code null}
*/
public static OperatorInfoBubble displayPrecheckMissingMandatoryParameterWarning(final Operator op,
final ParameterType param, boolean showRunAnyway) {
if (op == null) {
throw new IllegalArgumentException("op must not be null!");
}
if (param == null) {
throw new IllegalArgumentException("param must not be null!");
}
// not the user entered name because that could be god knows how long
String opName = op.getOperatorDescription().getName();
return displayPrecheckMissingParameterWarning(op, param, false, showRunAnyway,
"process_precheck_mandatory_parameter_unset", opName, param.getKey());
}
/**
* Displays a warning bubble that alerts the user that an input port of an operator expects
* input but is not connected. The bubble is located at the port and the process view will
* change to said port. This is a bubble which occurs during the check for errors before process
* execution so it contains a link button to run the process anyway.
*
* @param port
* the port for which to display the error
* @return the {@link PortInfoBubble} instance, never {@code null}
*/
public static PortInfoBubble displayPrecheckInputPortDisconnectedWarning(final Port port) {
return displayPrecheckInputPortDisconnectedWarning(port, true);
}
/**
* Displays a warning bubble that alerts the user that an input port of an operator expects
* input but is not connected. The bubble is located at the port and the process view will
* change to said port. This is a bubble which occurs during the check for errors before process
* execution so it contains a link button to run the process anyway if showRunAnyway is
* {@code true}.
*
* @param port
* the port for which to display the error
* @param showRunAnyway
* whether the run anyway button should be shown
* @return the {@link PortInfoBubble} instance, never {@code null}
*/
public static PortInfoBubble displayPrecheckInputPortDisconnectedWarning(final Port port, boolean showRunAnyway) {
if (port == null) {
throw new IllegalArgumentException("port must not be null!");
}
// PortOwner is an interface only implemented from anonymous inner classes
// so check enclosing class and differentiate operator input ports and subprocess
// (result) input ports
String key;
if (ExecutionUnit.class.isAssignableFrom(port.getPorts().getOwner().getClass().getEnclosingClass())) {
key = "process_precheck_mandatory_input_port_unconnected_inner";
} else {
key = "process_precheck_mandatory_input_port_unconnected";
}
return displayPrecheckMissingInputPortWarning(port, true, false, showRunAnyway, key);
}
/**
* Displays an error bubble that alerts the user that the attribute specified in the parameters
* of an operator was not found. The bubble is located at the operator and the process view will
* change to said port. This method is used after the error occurred during process execution.
*
* @param error
* the error containing all the information about the operator, the parameter and the
* name of the attribute which was not found
* @return the {@link OperatorInfoBubble} instance, never {@code null}
*/
public static OperatorInfoBubble displayAttributeNotFoundParameterInformation(final AttributeNotFoundError error) {
if (error == null) {
throw new IllegalArgumentException("error must not be null!");
}
if (error.getOperator() == null) {
throw new IllegalArgumentException("error operator must not be null!");
}
if (error.getKey() == null) {
throw new IllegalArgumentException("error parameter key must not be null!");
}
String key;
switch (error.getCode()) {
case AttributeNotFoundError.ATTRIBUTE_NOT_FOUND_IN_REGULAR:
key = "process_regular_attribute_not_found_parameter";
break;
case AttributeNotFoundError.ATTRIBUTE_NOT_FOUND:
default:
key = "process_attribute_not_found_parameter";
}
return displayAttributeNotFoundParameterInformation(error, true, key, error.getAttributeName(), error.getKey());
}
/**
* Displays an error bubble that alerts the user that a mandatory parameter of an operator was
* not set and has no default value. The bubble is located at the operator and the process view
* will change to said port. This method is used after the error occurred during process
* execution.
*
* @param op
* the operator for which to display the error
* @param param
* the parameter for which to display the error
* @return the {@link OperatorInfoBubble} instance, never {@code null}
*/
public static OperatorInfoBubble displayMissingMandatoryParameterInformation(final Operator op,
final ParameterType param) {
if (op == null) {
throw new IllegalArgumentException("op must not be null!");
}
if (param == null) {
throw new IllegalArgumentException("param must not be null!");
}
String key = "process_mandatory_parameter_missing";
// not the user entered name because that could be god knows how long
String opName = op.getOperatorDescription().getName();
return displayMissingMandatoryParameterInformation(op, param, true, key, opName, param.getKey());
}
/**
* Displays an error bubble that alerts the user that an input port of an operator expected
* input but did not receive any. The bubble is located at the port and the process view will
* change to said port. This method is used after the error occurred during process execution.
*
* @param port
* the port for which to display the error
* @return the {@link PortInfoBubble} instance, never {@code null}
*/
public static PortInfoBubble displayInputPortNoDataInformation(final Port port) {
if (port == null) {
throw new IllegalArgumentException("port must not be null!");
}
String key;
if (port.isConnected()) {
key = "process_mandatory_input_port_no_data";
} else {
// PortOwner is an interface only implemented from anonymous inner classes
// so check enclosing class and differentiate operator input ports and subprocess
// (result) input ports
if (ExecutionUnit.class.isAssignableFrom(port.getPorts().getOwner().getClass().getEnclosingClass())) {
key = "process_mandatory_input_port_no_data_unconnected_inner";
} else {
key = "process_mandatory_input_port_no_data_unconnected";
}
}
String opName = "";
if (port instanceof InputPort) {
InputPort inPort = (InputPort) port;
OutputPort source = inPort.getSource();
if (source != null) {
// not the user entered name because that could be god knows how long
opName = source.getPorts().getOwner().getOperator().getOperatorDescription().getName();
}
}
return displayMissingInputPortInformation(port, !port.isConnected(), true, key, opName);
}
/**
* Displays an error bubble that alerts the user that an input port of an operator expected
* input of a certain type but received the wrong type. The bubble is located at the port and
* the process view will change to said port. This method is used after the error occurred
* during process execution.
*
* @param port
* the port for which to display the error
* @param expectedType
* the expected data type for the port
* @param actualType
* the actually delivered type for the port
* @return the {@link PortInfoBubble} instance, never {@code null}
*/
public static PortInfoBubble displayInputPortWrongTypeInformation(final Port port, final Class<?> expectedType,
final Class<?> actualType) {
if (port == null) {
throw new IllegalArgumentException("port must not be null!");
}
if (expectedType == null) {
throw new IllegalArgumentException("expectedType must not be null!");
}
if (actualType == null) {
throw new IllegalArgumentException("actualType must not be null!");
}
String key = "process_mandatory_input_port_wrong_type";
return displayMissingInputPortInformation(port, true, true, key, RendererService.getName(expectedType),
RendererService.getName(actualType));
}
/**
* Displays an information bubble pointing to an ExecuteProcess operator to indicate a
* {@link UserError} occurred inside the process executed by the operator.
*
* @param userError
* the error instance
* @return the {@link OperatorInfoBubble} instance, never {@code null}
*/
public static OperatorInfoBubble displayUserErrorInExecutedProcess(final ProcessExecutionUserErrorError userError) {
String referencedOperatorName = userError.getUserError().getOperator().getName();
return displayUserErrorInExecutedProcess(userError, "executed_process_usererror", referencedOperatorName,
userError.getUserError().getDetails());
}
/**
* Displays an information bubble pointing to an operator to indicate a {@link UserError} was
* thrown by the operator.
*
* @param userError
* the error instance
* @return the {@link OperatorInfoBubble} instance, never {@code null}
*/
public static OperatorInfoBubble displayGenericUserError(final UserError userError) {
if (userError == null) {
throw new IllegalArgumentException("userError must not be null!");
}
return displayGenericUserError(userError, "generic_usererror");
}
/**
* Displays an information bubble pointing to a port to indicate a {@link PortUserError} was
* thrown.
*
* @param portError
* the error instance
* @return the {@link PortInfoBubble} instance, never {@code null}
*/
public static PortInfoBubble displayGenericPortError(final PortUserError portError) {
if (portError == null) {
throw new IllegalArgumentException("portError must not be null!");
}
return displayGenericPortError(portError, "generic_usererror");
}
/**
* Displays an information bubble pointing to an operator to indicate a {@link UserError} was
* thrown by the operator.
*
* @param userError
* the error instance
* @return the {@link OperatorInfoBubble} instance, never {@code null}
*/
public static OperatorInfoBubble displayGenericParameterError(final ParameterError userError) {
if (userError == null) {
throw new IllegalArgumentException("userError must not be null!");
}
Operator op = userError.getOperator();
if (op == null) {
throw new IllegalArgumentException("ParameterError operator must not be null!");
}
ParameterType param = op.getParameterType(userError.getKey());
if (param == null) {
throw new IllegalArgumentException("ParameterError key must point to a valid ParameterType!");
}
return displayGenericParameterError(userError, op, param, "generic_paramerror");
}
/**
* Displays a warning bubble that alerts the user that a mandatory parameter of an operator has
* no value and no default value. The bubble is located at the operator and the process view
* will change to said port.
*
* @param op
* the operator for which to display the warning
* @param param
* the parameter for which to display the warning
* @param i18nKey
* the i18n key which defines the title, text and button label for the bubble. Format
* is "gui.bubble.{i18nKey}.title", "gui.bubble.{i18nKey}.body" and
* "gui.bubble.{i18nKey}.button.label".
* @param isError
* if {@code true}, an error bubble will be shown; otherwise a warning bubble is
* displayed
* @param showRunAnyway
* whether the run anyway button is shown
* @param arguments
* optional i18n arguments
* @return the {@link OperatorInfoBubble} instance, never {@code null}
*/
private static OperatorInfoBubble displayPrecheckMissingParameterWarning(final Operator op, final ParameterType param,
final boolean isError, final boolean showRunAnyway, final String i18nKey, final Object... arguments) {
final BubbleDelegator bubbleDelegator = new BubbleDelegator();
final JPanel linkPanel = new JPanel();
if (showRunAnyway) {
ResourceAction runAnywayAction = new ResourceAction(i18nKey + ".button_run_anyway", "F11") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(final ActionEvent e) {
BubbleWindow bubble = bubbleDelegator.getBubble();
if (bubble != null) {
bubble.killBubble(true);
}
// run process without checking for problems
RapidMinerGUI.getMainFrame().runProcess(false);
}
};
LinkLocalButton runAnywayButton = new LinkLocalButton(runAnywayAction);
runAnywayButton.setToolTipText(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button_run_anyway.tip"));
runAnywayButton.registerKeyboardAction(runAnywayAction, KeyStroke.getKeyStroke(KeyEvent.VK_F11, 0),
JComponent.WHEN_IN_FOCUSED_WINDOW);
linkPanel.add(runAnywayButton);
}
final JButton ackButton = new JButton(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button.label", arguments));
ackButton.setToolTipText(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button.tip"));
ParameterErrorBubbleBuilder builder = new ParameterErrorBubbleBuilder(RapidMinerGUI.getMainFrame(), op, param,
"mandatory_parameter_decoration", i18nKey, arguments);
final OperatorInfoBubble missingParameterBubble = builder.setHideOnDisable(true).setAlignment(AlignedSide.BOTTOM)
.setStyle(isError ? BubbleStyle.ERROR : BubbleStyle.WARNING).setEnsureVisible(true).hideCloseButton()
.setHideOnProcessRun(true).setAdditionalComponents(new JComponent[] { ackButton, linkPanel }).build();
ackButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
missingParameterBubble.killBubble(true);
}
});
bubbleDelegator.setBubbleWindow(missingParameterBubble);
missingParameterBubble.setVisible(true);
return missingParameterBubble;
}
/**
* Displays a warning bubble that alerts the user that an input port of an operator expected
* input but that there was a problem. The bubble is located at the port and the process view
* will change to said port. execution.
*
* @param port
* the port for which to display the warning
* @param i18nKey
* the i18n key which defines the title, text and button label for the bubble. Format
* is "gui.bubble.{i18nKey}.title", "gui.bubble.{i18nKey}.body" and
* "gui.bubble.{i18nKey}.button.label".
* @param hideOnConnection
* if {@code true}, the bubble will be hidden once the port is connected
* @param isError
* if {@code true}, an error bubble will be shown; otherwise a warning bubble is
* displayed
* @param showRunAnyway
* whether the run anyway button is shown
* @param arguments
* optional i18n arguments
* @return the {@link PortInfoBubble} instance, never {@code null}
*/
private static PortInfoBubble displayPrecheckMissingInputPortWarning(final Port port, final boolean hideOnConnection,
final boolean isError, final boolean showRunAnyway, final String i18nKey, final Object... arguments) {
final BubbleDelegator bubbleDelegator = new BubbleDelegator();
final JPanel linkPanel = new JPanel();
if (showRunAnyway) {
ResourceAction runAnywayAction = new ResourceAction(i18nKey + ".button_run_anyway", "F11") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(final ActionEvent e) {
BubbleWindow bubble = bubbleDelegator.getBubble();
if (bubble != null) {
bubble.killBubble(true);
}
// run process without checking for problems
RapidMinerGUI.getMainFrame().runProcess(false);
}
};
LinkLocalButton runAnywayButton = new LinkLocalButton(runAnywayAction);
runAnywayButton.setToolTipText(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button_run_anyway.tip"));
runAnywayButton.registerKeyboardAction(runAnywayAction, KeyStroke.getKeyStroke(KeyEvent.VK_F11, 0),
JComponent.WHEN_IN_FOCUSED_WINDOW);
}
final JButton ackButton = new JButton(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button.label", arguments));
ackButton.setToolTipText(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button.tip"));
PortBubbleBuilder builder = new PortBubbleBuilder(RapidMinerGUI.getMainFrame(), port, i18nKey, arguments);
final PortInfoBubble missingInputBubble = builder.setHideOnConnection(hideOnConnection).setHideOnDisable(true)
.setAlignment(AlignedSide.LEFT).setStyle(isError ? BubbleStyle.ERROR : BubbleStyle.WARNING)
.setEnsureVisible(true).hideCloseButton().setHideOnProcessRun(true)
.setAdditionalComponents(new JComponent[] { ackButton, linkPanel }).build();
ackButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
missingInputBubble.killBubble(true);
}
});
bubbleDelegator.setBubbleWindow(missingInputBubble);
missingInputBubble.setVisible(true);
return missingInputBubble;
}
/**
* Displays an information bubble that alerts the user that the attribute specified in the
* operator parameters was not found. The bubble is located at the operator and the process view
* will change to said operator. This method is used after the error occurred during process
* execution.
*
* @param error
* the error containing all the information about the operator, the parameter and the
* name of the attribute which was not found
* @param i18nKey
* the i18n key which defines the title, text and button label for the bubble. Format
* is "gui.bubble.{i18nKey}.title", "gui.bubble.{i18nKey}.body" and
* "gui.bubble.{i18nKey}.button.label".
* @param isError
* if {@code true}, an error bubble will be shown; otherwise a warning bubble is
* displayed
* @param arguments
* optional i18n arguments
* @return the {@link OperatorInfoBubble} instance, never {@code null}
*/
private static OperatorInfoBubble displayAttributeNotFoundParameterInformation(final AttributeNotFoundError error,
final boolean isError, final String i18nKey, final Object... arguments) {
final Operator op = error.getOperator();
final ParameterType param = op.getParameterType(error.getKey());
final JButton ackButton = new JButton(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button.label", arguments));
ackButton.setToolTipText(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button.tip"));
String decoratorKey = param instanceof CombinedParameterType || param instanceof ParameterTypeAttributes
? "attributes_not_found_decoration" : "attribute_not_found_decoration";
ParameterErrorBubbleBuilder builder = new ParameterErrorBubbleBuilder(RapidMinerGUI.getMainFrame(), op, param,
decoratorKey, i18nKey, arguments);
final ParameterErrorInfoBubble attributeNotFoundParameterBubble = builder.setHideOnDisable(true)
.setAlignment(AlignedSide.BOTTOM).setStyle(isError ? BubbleStyle.ERROR : BubbleStyle.WARNING)
.setEnsureVisible(true).hideCloseButton().setHideOnProcessRun(true)
.setAdditionalComponents(new JComponent[] { ackButton }).build();
ackButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
attributeNotFoundParameterBubble.killBubble(true);
}
});
attributeNotFoundParameterBubble.setVisible(true);
return attributeNotFoundParameterBubble;
}
/**
* Displays an information bubble that alerts the user that a mandatory operator parameter was
* not set and had no default value. The bubble is located at the operator and the process view
* will change to said operator. This method is used after the error occurred during process
* execution.
*
* @param op
* the operator for which to display the error
* @param param
* the parameter for which to display the error
* @param i18nKey
* the i18n key which defines the title, text and button label for the bubble. Format
* is "gui.bubble.{i18nKey}.title", "gui.bubble.{i18nKey}.body" and
* "gui.bubble.{i18nKey}.button.label".
* @param isError
* if {@code true}, an error bubble will be shown; otherwise a warning bubble is
* displayed
* @param arguments
* optional i18n arguments
* @return the {@link OperatorInfoBubble} instance, never {@code null}
*/
private static OperatorInfoBubble displayMissingMandatoryParameterInformation(final Operator op,
final ParameterType param, final boolean isError, final String i18nKey, final Object... arguments) {
final JButton ackButton = new JButton(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button.label", arguments));
ackButton.setToolTipText(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button.tip"));
ParameterErrorBubbleBuilder builder = new ParameterErrorBubbleBuilder(RapidMinerGUI.getMainFrame(), op, param,
"mandatory_parameter_decoration", i18nKey, arguments);
final OperatorInfoBubble missingParameterBubble = builder.setHideOnDisable(true).setAlignment(AlignedSide.BOTTOM)
.setStyle(isError ? BubbleStyle.ERROR : BubbleStyle.WARNING).setEnsureVisible(true).hideCloseButton()
.setHideOnProcessRun(true).setAdditionalComponents(new JComponent[] { ackButton }).build();
ackButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
missingParameterBubble.killBubble(true);
}
});
missingParameterBubble.setVisible(true);
return missingParameterBubble;
}
/**
* Displays an information bubble that alerts the user that an input port of an operator
* expected input but that there was a problem. The bubble is located at the port and the
* process view will change to said port. This method is used after the error occurred during
* process execution.
*
* @param port
* the port for which to display the error
* @param i18nKey
* the i18n key which defines the title, text and button label for the bubble. Format
* is "gui.bubble.{i18nKey}.title", "gui.bubble.{i18nKey}.body" and
* "gui.bubble.{i18nKey}.button.label".
* @param hideOnConnection
* if {@code true}, the bubble will be hidden once the port is connected
* @param isError
* if {@code true}, an error bubble will be shown; otherwise a warning bubble is
* displayed
* @param arguments
* optional i18n arguments
* @return the {@link PortInfoBubble} instance, never {@code null}
*/
private static PortInfoBubble displayMissingInputPortInformation(final Port port, final boolean hideOnConnection,
final boolean isError, final String i18nKey, final Object... arguments) {
final JButton ackButton = new JButton(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button.label", arguments));
ackButton.setToolTipText(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button.tip"));
PortBubbleBuilder builder = new PortBubbleBuilder(RapidMinerGUI.getMainFrame(), port, i18nKey, arguments);
final PortInfoBubble missingInputBubble = builder.setHideOnConnection(hideOnConnection).setHideOnDisable(true)
.setAlignment(AlignedSide.LEFT).setStyle(isError ? BubbleStyle.ERROR : BubbleStyle.WARNING)
.setEnsureVisible(true).hideCloseButton().setHideOnProcessRun(true)
.setAdditionalComponents(new JComponent[] { ackButton }).build();
ackButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
missingInputBubble.killBubble(true);
}
});
missingInputBubble.setVisible(true);
return missingInputBubble;
}
/**
* Displays an information bubble pointing to an operator to indicate a {@link ParameterError}
* was thrown by that operator. It will also select the operator and highlight the parameter in
* question.
*
* @param error
* the error instance
* @param op
* the operator for which to display the error
* @param param
* the parameter for which to display the error
* @param i18nKey
* the i18n key which defines the title, text and button label for the bubble. Format
* is "gui.bubble.{i18nKey}.title", "gui.bubble.{i18nKey}.body" and
* "gui.bubble.{i18nKey}.button.label".
* @return the {@link OperatorInfoBubble} instance, never {@code null}
*/
private static OperatorInfoBubble displayGenericParameterError(final ParameterError error, final Operator op,
final ParameterType param, final String i18nKey) {
final JButton ackButton = new JButton(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button.label"));
ackButton.setToolTipText(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button.tip"));
final BubbleDelegator bubbleDelegator = new BubbleDelegator();
final String message = removeTerminationCharacters(error.getMessage());
final JPanel linkPanel = new JPanel();
LinkLocalButton showDetailsButton = new LinkLocalButton(new ResourceAction(i18nKey + ".button_show_details") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(final ActionEvent e) {
BubbleWindow bubble = bubbleDelegator.getBubble();
if (bubble != null) {
String text = I18N.getMessage(I18N.getGUIBundle(), "gui.bubble." + i18nKey + ".body", message,
error.getDetails());
bubble.setMainText(text);
linkPanel.removeAll();
bubble.pack();
}
}
});
showDetailsButton.setToolTipText(I18N.getGUIMessage("gui.action." + i18nKey + ".button_show_details.tip"));
linkPanel.add(showDetailsButton);
ParameterErrorBubbleBuilder builder = new ParameterErrorBubbleBuilder(RapidMinerGUI.getMainFrame(),
error.getOperator(), param, "generic_parameter_decoration", i18nKey, message, "");
// if no operator or root operator, show in middle, otherwise below
AlignedSide prefSide = error.getOperator() == null || error.getOperator() instanceof ProcessRootOperator
? AlignedSide.MIDDLE : AlignedSide.BOTTOM;
final OperatorInfoBubble userErrorBubble = builder.setHideOnDisable(true).setAlignment(prefSide)
.setStyle(BubbleStyle.ERROR).setEnsureVisible(true).hideCloseButton().setHideOnProcessRun(true)
.setAdditionalComponents(new JComponent[] { ackButton, linkPanel }).build();
ackButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
userErrorBubble.killBubble(true);
}
});
bubbleDelegator.setBubbleWindow(userErrorBubble);
if (error.getErrorName() != null && !error.getErrorName().trim().isEmpty()) {
userErrorBubble.setHeadline(error.getErrorName());
}
userErrorBubble.setVisible(true);
return userErrorBubble;
}
/**
* Shortens the provided message by removing the trailing termination characters like '.' or a
* '!'.
*
* @param message
* the message to shorten
* @return the message without the trailing termination characters
*/
private static String removeTerminationCharacters(final String message) {
if (message == null) {
throw new IllegalArgumentException("Message must not be null");
}
String errMsg = message.trim();
while (errMsg.endsWith(".") || errMsg.endsWith("!")) {
errMsg = errMsg.substring(0, errMsg.length() - 1);
}
return errMsg;
}
/**
* Displays an information bubble pointing to an operator to indicate a {@link UserError} was
* thrown by that operator.
*
* @param error
* the error instance
* @param i18nKey
* the i18n key which defines the title, text and button label for the bubble. Format
* is "gui.bubble.{i18nKey}.title", "gui.bubble.{i18nKey}.body" and
* "gui.bubble.{i18nKey}.button.label".
* @return the {@link OperatorInfoBubble} instance, never {@code null}
*/
private static OperatorInfoBubble displayGenericUserError(final UserError error, final String i18nKey) {
final JButton ackButton = new JButton(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button.label"));
ackButton.setToolTipText(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button.tip"));
final BubbleDelegator bubbleDelegator = new BubbleDelegator();
final String message = removeTerminationCharacters(error.getMessage());
final JPanel linkPanel = new JPanel();
LinkLocalButton showDetailsButton = new LinkLocalButton(new ResourceAction(i18nKey + ".button_show_details") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(final ActionEvent e) {
BubbleWindow bubble = bubbleDelegator.getBubble();
if (bubble != null) {
String text = I18N.getMessage(I18N.getGUIBundle(), "gui.bubble." + i18nKey + ".body", message,
error.getDetails());
bubble.setMainText(text);
linkPanel.removeAll();
bubble.pack();
}
}
});
showDetailsButton.setToolTipText(I18N.getGUIMessage("gui.action." + i18nKey + ".button_show_details.tip"));
linkPanel.add(showDetailsButton);
OperatorBubbleBuilder builder = new OperatorBubbleBuilder(RapidMinerGUI.getMainFrame(), error.getOperator(), i18nKey,
message, "");
// if no operator, root operator or orphan, e.g. because operator is used internally by
// another operator, show in middle, otherwise below
AlignedSide prefSide = error.getOperator() == null || error.getOperator().getParent() == null ? AlignedSide.MIDDLE
: AlignedSide.BOTTOM;
final OperatorInfoBubble userErrorBubble = builder.setHideOnDisable(true).setAlignment(prefSide)
.setStyle(BubbleStyle.ERROR).setEnsureVisible(true).hideCloseButton().setHideOnProcessRun(true)
.setAdditionalComponents(new JComponent[] { ackButton, linkPanel }).build();
ackButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
userErrorBubble.killBubble(true);
}
});
bubbleDelegator.setBubbleWindow(userErrorBubble);
if (error.getErrorName() != null && !error.getErrorName().trim().isEmpty()) {
userErrorBubble.setHeadline(error.getErrorName());
}
userErrorBubble.setVisible(true);
return userErrorBubble;
}
/**
* Displays an information bubble pointing to a port to indicate a {@link PortUserError} was
* thrown.
*
* @param error
* the error instance
* @param i18nKey
* the i18n key which defines the title, text and button label for the bubble. Format
* is "gui.bubble.{i18nKey}.title", "gui.bubble.{i18nKey}.body" and
* "gui.bubble.{i18nKey}.button.label".
* @return the {@link PortInfoBubble} instance, never {@code null}
*/
private static PortInfoBubble displayGenericPortError(final PortUserError error, final String i18nKey) {
final JButton ackButton = new JButton(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button.label"));
ackButton.setToolTipText(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button.tip"));
final BubbleDelegator bubbleDelegator = new BubbleDelegator();
final String message = removeTerminationCharacters(error.getMessage());
final JPanel linkPanel = new JPanel();
LinkLocalButton showDetailsButton = new LinkLocalButton(new ResourceAction(i18nKey + ".button_show_details") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(final ActionEvent e) {
BubbleWindow bubble = bubbleDelegator.getBubble();
if (bubble != null) {
String text = I18N.getMessage(I18N.getGUIBundle(), "gui.bubble." + i18nKey + ".body", message,
error.getDetails());
bubble.setMainText(text);
linkPanel.removeAll();
bubble.pack();
}
}
});
showDetailsButton.setToolTipText(I18N.getGUIMessage("gui.action." + i18nKey + ".button_show_details.tip"));
linkPanel.add(showDetailsButton);
// input ports (located left) show the "hook" of the bubble on the left and vice versa
AlignedSide prefSide = error.getPort() instanceof InputPort ? AlignedSide.LEFT : AlignedSide.RIGHT;
PortBubbleBuilder builder = new PortBubbleBuilder(RapidMinerGUI.getMainFrame(), error.getPort(), i18nKey, message,
"");
final PortInfoBubble portErrorBubble = builder.setHideOnDisable(true).setAlignment(prefSide)
.setStyle(BubbleStyle.ERROR).setEnsureVisible(true).hideCloseButton().setHideOnProcessRun(true)
.setAdditionalComponents(new JComponent[] { ackButton, linkPanel }).build();
ackButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
portErrorBubble.killBubble(true);
}
});
bubbleDelegator.setBubbleWindow(portErrorBubble);
if (error.getErrorName() != null && !error.getErrorName().trim().isEmpty()) {
portErrorBubble.setHeadline(error.getErrorName());
}
portErrorBubble.setVisible(true);
return portErrorBubble;
}
/**
* Displays an information bubble pointing to an ExecuteProcess operator to indicate a
* {@link UserError} occurred inside the process executed by the operator.
*
* @param error
* the error instance
* @param i18nKey
* the i18n key which defines the title, text and button label for the bubble. Format
* is "gui.bubble.{i18nKey}.title", "gui.bubble.{i18nKey}.body" and
* "gui.bubble.{i18nKey}.button.label".
* @param arguments
* optional i18n arguments
* @return the {@link OperatorInfoBubble} instance, never {@code null}
*/
private static OperatorInfoBubble displayUserErrorInExecutedProcess(final ProcessExecutionUserErrorError error,
final String i18nKey, final Object... arguments) {
final JButton ackButton = new JButton(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button.label", arguments));
ackButton.setToolTipText(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button.tip"));
final BubbleDelegator bubbleDelegator = new BubbleDelegator();
LinkLocalButton showDetailsButton = new LinkLocalButton(new ResourceAction(i18nKey + ".button_show_details") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(final ActionEvent e) {
if (RapidMinerGUI.getMainFrame().close(true)) {
// kill bubble
BubbleWindow bubble = bubbleDelegator.getBubble();
if (bubble != null) {
bubble.killBubble(true);
}
// open process which caused the error
Operator op = error.getUserError().getOperator();
final Process causingProcess = op.getProcess();
RapidMinerGUI.getMainFrame().setOpenedProcess(causingProcess);
// show new error bubble in the newly opened process
displayBubbleForUserError(error.getUserError());
}
}
});
showDetailsButton.setToolTipText(I18N.getGUIMessage("gui.action." + i18nKey + ".button_show_details.tip"));
OperatorBubbleBuilder builder = new OperatorBubbleBuilder(RapidMinerGUI.getMainFrame(), error.getOperator(), i18nKey,
arguments);
final OperatorInfoBubble userErrorBubble = builder.setHideOnDisable(true).setHideOnProcessRun(true)
.setAlignment(AlignedSide.BOTTOM).setStyle(BubbleStyle.ERROR).setEnsureVisible(true).hideCloseButton()
.setHideOnProcessRun(true).setAdditionalComponents(new JComponent[] { ackButton, showDetailsButton })
.build();
ackButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
userErrorBubble.killBubble(true);
}
});
bubbleDelegator.setBubbleWindow(userErrorBubble);
userErrorBubble.setVisible(true);
return userErrorBubble;
}
/**
* Displays an information bubble pointing to an operator to indicate a
* {@link ProcessSetupError} for that operator.
*
* @param processSetupError
* the {@link ProcessSetupError} to display
* @return the {@link OperatorInfoBubble} instance, never {@code null}
*/
public static OperatorInfoBubble displayProcessSetupError(final ProcessSetupError processSetupError) {
final String i18nKey = "process_setup_error";
final JButton ackButton = new JButton(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button.label"));
ackButton.setToolTipText(I18N.getGUIMessage("gui.bubble." + i18nKey + ".button.tip"));
final BubbleDelegator bubbleDelegator = new BubbleDelegator();
final String message = removeTerminationCharacters(processSetupError.getMessage());
final JPanel linkPanel = new JPanel();
LinkLocalButton showQuickFixesButton = null;
if (!processSetupError.getQuickFixes().isEmpty()) {
showQuickFixesButton = new LinkLocalButton(new ResourceActionAdapter(i18nKey + ".button_show_quickfixes"));
showQuickFixesButton.setToolTipText(I18N.getGUIMessage("gui.action." + i18nKey + ".button_show_quickfixes.tip"));
linkPanel.add(showQuickFixesButton);
}
OperatorBubbleBuilder builder = new OperatorBubbleBuilder(RapidMinerGUI.getMainFrame(),
processSetupError.getOwner().getOperator(), i18nKey, message, "");
BubbleStyle style = processSetupError.getSeverity() == Severity.INFORMATION ? BubbleStyle.INFORMATION
: BubbleStyle.WARNING;
final OperatorInfoBubble processSetupBubble = builder.setHideOnDisable(true).setAlignment(AlignedSide.BOTTOM)
.setStyle(style).setEnsureVisible(true).hideCloseButton().setHideOnProcessRun(true)
.setAdditionalComponents(new JComponent[] { ackButton, linkPanel }).build();
if (!processSetupError.getQuickFixes().isEmpty()) {
showQuickFixesButton.setAction(new ResourceAction(i18nKey + ".button_show_quickfixes") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(final ActionEvent e) {
// kill bubble when quick fix dialog is shown
processSetupBubble.killBubble(true);
new QuickFixDialog(processSetupError.getQuickFixes()).setVisible(true);
}
});
}
ackButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
processSetupBubble.killBubble(true);
}
});
bubbleDelegator.setBubbleWindow(processSetupBubble);
processSetupBubble.setVisible(true);
return processSetupBubble;
}
}