/**
* 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.bubble;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Window;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import com.rapidminer.gui.RapidMinerGUI;
import com.rapidminer.gui.properties.PropertyPanel.PropertyEditorDecorator;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.operator.Operator;
import com.rapidminer.parameter.ParameterType;
import com.rapidminer.parameter.UndefinedParameterError;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.Observable;
import com.rapidminer.tools.Observer;
/**
* This class creates a speech bubble-shaped JDialog, which can be attached to an {@link Operator}.
* See {@link OperatorInfoBubble} for more details. If the missing parameter is inserted, this
* bubble vanishes.
* <p>
* If the perspective is incorrect, the dockable not shown or the subprocess currently viewed is
* wrong, automatically corrects everything to ensure the bubble is shown if the
* {@code ensureVisibility} parameter is set.
* </p>
*
* @author Nils Woehler
* @since 6.5.0
*
*/
public class ParameterErrorInfoBubble extends OperatorInfoBubble {
private static final Icon WARNING_ICON = SwingTools.createIcon("16/sign_warning.png");
private static final Icon ERROR_ICON = SwingTools.createIcon("16/error.png");
/**
* Builder for {@link ParameterErrorInfoBubble}s. After calling all relevant setters, call
* {@link #build()} to create the actual dialog instance.
*
* @author Nils Woehler
* @since 6.5.0
*
*/
public static class ParameterErrorBubbleBuilder
extends BubbleWindowBuilder<ParameterErrorInfoBubble, ParameterErrorBubbleBuilder> {
private final Operator attachTo;
private final ParameterType parameter;
private final String decorateI18N;
private boolean hideOnDisable;
private boolean hideOnRun;
private boolean ensureVisible;
public ParameterErrorBubbleBuilder(final Window owner, final Operator attachTo, final ParameterType parameter,
final String decorateI18N, final String i18nKey, final Object... arguments) {
super(owner, i18nKey, arguments);
this.attachTo = attachTo;
this.parameter = parameter;
this.decorateI18N = decorateI18N;
}
/**
* Sets whether to hide the bubble when the operator is disabled. Defaults to {@code false}.
*
* @param hideOnDisable
* {@code true} if the bubble should be hidden upon disable; {@code false}
* otherwise
* @return the builder instance
*/
public ParameterErrorBubbleBuilder setHideOnDisable(final boolean hideOnDisable) {
this.hideOnDisable = hideOnDisable;
return this;
}
/**
* Sets whether to hide the bubble when the process is run. Defaults to {@code false}.
*
* @param hideOnRun
* {@code true} if the bubble should be hidden upon running a process;
* {@code false} otherwise
* @return the builder instance
*/
public ParameterErrorBubbleBuilder setHideOnProcessRun(final boolean hideOnRun) {
this.hideOnRun = hideOnRun;
return this;
}
/**
* Sets whether to make sure the bubble is visible by automatically switching perspective,
* opening/showing the process dockable and changing the subprocess. Defaults to
* {@code false}.
*
* @param ensureVisible
* {@code true} if the bubble should be hidden upon disable; {@code false}
* otherwise
* @return the builder instance
*/
public ParameterErrorBubbleBuilder setEnsureVisible(final boolean ensureVisible) {
this.ensureVisible = ensureVisible;
return this;
}
@Override
public ParameterErrorInfoBubble build() {
return new ParameterErrorInfoBubble(owner, style, alignment, i18nKey, attachTo, parameter, decorateI18N,
componentsToAdd, hideOnDisable, hideOnRun, ensureVisible, moveable, showCloseButton, arguments);
}
@Override
public ParameterErrorBubbleBuilder getThis() {
return this;
}
}
private static final long serialVersionUID = 1L;
private final String decorateI18N;
private final PropertyEditorDecorator decorator;
private final ParameterType parameter;
private Observer<String> parameterObserver;
/**
* Creates a MissingParameterOperatorInfoBubble which points to an {@link Operator}.
*
* @param owner
* the {@link Window} on which this {@link BubbleWindow} should be shown.
* @param preferredAlignment
* offer for alignment but the Class will calculate by itself whether the position is
* usable.
* @param i18nKey
* of the message which should be shown
* @param toAttach
* the operator the bubble should be attached to
* @param parameter
* the parameter with the missing value
* @param style
* the bubble style
* @param decorateI18N
* the I18N key used for the parameter panel decorator
* @param componentsToAdd
* array of JComponents which will be added to the Bubble or {@code null}
* @param hideOnDisable
* if {@code true}, the bubble will be removed once the operator becomes disabled
* @param hideOnRun
* if {@code true}, the bubble will be removed once the process is executed
* @param ensureVisible
* if {@code true}, will automatically make sure the bubble will be visible by
* manipulating the GUI
* @param moveable
* if {@code true} the user can drag the bubble around on screen
* @param showCloseButton
* if {@code true} the user can close the bubble via an "x" button in the top right
* corner
* @param arguments
* arguments to pass thought to the I18N Object
*/
private ParameterErrorInfoBubble(Window owner, BubbleStyle style, AlignedSide preferredAlignment, String i18nKey,
Operator toAttach, final ParameterType parameter, String decorateI18N, JComponent[] componentsToAdd,
boolean hideOnDisable, boolean hideOnRun, boolean ensureVisible, boolean moveable, boolean showCloseButton,
Object... arguments) {
super(owner, style, preferredAlignment, i18nKey, toAttach, componentsToAdd, hideOnDisable, hideOnRun, ensureVisible,
moveable, showCloseButton, true, arguments);
if (parameter == null) {
throw new IllegalArgumentException("parameter must not be null");
}
if (decorateI18N == null) {
throw new IllegalArgumentException("decorator I18N key must not be null");
}
this.parameter = parameter;
this.decorateI18N = decorateI18N;
// Decorator to highlight parameter in operator property panel
this.decorator = new PropertyEditorDecorator() {
@Override
public JPanel decorate(JPanel parameterEditor, ParameterType type, Operator typesOperator) {
if (typesOperator.equals(getOperator()) && type.getKey().equals(parameter.getKey())) {
return decorateParameterPanel(parameterEditor);
}
return parameterEditor;
}
};
}
@Override
protected void registerSpecificListener() {
super.registerSpecificListener();
// Parameter observer that checks whether the missing parameter value has been added
this.parameterObserver = new Observer<String>() {
@Override
public void update(Observable<String> observable, String key) {
try {
if (parameter != null && parameter.getKey().equals(key)) {
// try to fetch parameter value
String value = getOperator().getParameter(key);
// check if value is empty
if (value != null && !value.trim().isEmpty()) {
// kill bubble in case the parameter is not missing anymore
killBubble(true);
}
}
} catch (UndefinedParameterError e) {
// parameter is still missing
}
}
};
getOperator().getParameters().addObserver(parameterObserver, false);
}
@Override
protected void unregisterSpecificListeners() {
super.unregisterSpecificListeners();
// remove parameter highlighter again
RapidMinerGUI.getMainFrame().getPropertyPanel().removePropertyEditorDecorator(decorator);
RapidMinerGUI.getMainFrame().getPropertyPanel().setupComponents();
// remove parameter observer
getOperator().getParameters().removeObserver(parameterObserver);
}
/**
* Decorates the given parameter editor panel with a "mandatory parameter" hint.
*
* @param parameterEditor
* the parameter editor panel
* @return the decorated panel
*/
private JPanel decorateParameterPanel(final JPanel parameterEditor) {
JPanel parentPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.weightx = 1.0;
gbc.weighty = 1.0;
gbc.fill = GridBagConstraints.BOTH;
parentPanel.add(parameterEditor, gbc);
JLabel warningLabel = new JLabel(I18N.getGUIMessage("gui.bubble." + decorateI18N + ".label"));
boolean isError = getStyle() == BubbleStyle.ERROR;
warningLabel.setIcon(isError ? ERROR_ICON : WARNING_ICON);
warningLabel.setBackground(isError ? BubbleStyle.ERROR.getColor() : BubbleStyle.WARNING.getColor());
warningLabel.setOpaque(true);
warningLabel.setBorder(new EmptyBorder(1, 1, 1, 0));
gbc.gridy += 1;
gbc.weighty = 0.0;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(2, 0, 0, 0);
parentPanel.add(warningLabel, gbc);
return parentPanel;
}
@Override
public void setVisible(boolean visible) {
if (visible) {
// only add decorator if not yet visible
if (!isVisible()) {
RapidMinerGUI.getMainFrame().getPropertyPanel().addPropertyEditorDecorator(decorator);
RapidMinerGUI.getMainFrame().getPropertyPanel().setupComponents();
}
} else {
RapidMinerGUI.getMainFrame().getPropertyPanel().removePropertyEditorDecorator(decorator);
RapidMinerGUI.getMainFrame().getPropertyPanel().setupComponents();
}
super.setVisible(visible);
}
}