/** * 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; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Insets; import java.awt.Point; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.regex.Pattern; import javax.swing.BorderFactory; import javax.swing.JCheckBox; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingUtilities; import javax.swing.border.Border; import javax.swing.event.CellEditorListener; import javax.swing.event.ChangeEvent; import com.rapidminer.gui.look.Colors; import com.rapidminer.gui.properties.celleditors.value.AttributeFileValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.AttributeOrderingCellEditor; import com.rapidminer.gui.properties.celleditors.value.AttributeValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.AttributesValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.ColorValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.ConfigurableValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.ConfigurationWizardValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.CronExpressionCellEditor; import com.rapidminer.gui.properties.celleditors.value.DateFormatValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.DateValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.DefaultPropertyValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.EnumerationValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.ExpressionValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.FilterValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.InnerOperatorValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.LinkButtonValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.ListValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.MatrixValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.OAuthValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.OperatorValueValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.ParameterTupelCellEditor; import com.rapidminer.gui.properties.celleditors.value.PreviewValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.ProcessLocationValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.PropertyValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.RegexpValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.RemoteFileValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.RepositoryLocationValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.SimpleFileValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.SimpleSuggestionBoxValueCellEditor; import com.rapidminer.gui.properties.celleditors.value.TextValueCellEditor; import com.rapidminer.gui.tools.SwingTools; import com.rapidminer.gui.tools.components.ToolTipWindow; import com.rapidminer.gui.tools.components.ToolTipWindow.TipProvider; import com.rapidminer.operator.Operator; import com.rapidminer.parameter.ParameterType; import com.rapidminer.parameter.ParameterTypeAttribute; import com.rapidminer.parameter.ParameterTypeAttributeFile; import com.rapidminer.parameter.ParameterTypeAttributeOrderingRules; import com.rapidminer.parameter.ParameterTypeAttributes; import com.rapidminer.parameter.ParameterTypeBoolean; import com.rapidminer.parameter.ParameterTypeCategory; import com.rapidminer.parameter.ParameterTypeChar; import com.rapidminer.parameter.ParameterTypeColor; import com.rapidminer.parameter.ParameterTypeConfiguration; import com.rapidminer.parameter.ParameterTypeCronExpression; import com.rapidminer.parameter.ParameterTypeDate; import com.rapidminer.parameter.ParameterTypeDateFormat; import com.rapidminer.parameter.ParameterTypeDouble; import com.rapidminer.parameter.ParameterTypeEnumeration; import com.rapidminer.parameter.ParameterTypeExpression; import com.rapidminer.parameter.ParameterTypeFile; import com.rapidminer.parameter.ParameterTypeFilter; import com.rapidminer.parameter.ParameterTypeInnerOperator; import com.rapidminer.parameter.ParameterTypeInt; import com.rapidminer.parameter.ParameterTypeLinkButton; import com.rapidminer.parameter.ParameterTypeList; import com.rapidminer.parameter.ParameterTypeMatrix; import com.rapidminer.parameter.ParameterTypeOAuth; import com.rapidminer.parameter.ParameterTypePassword; import com.rapidminer.parameter.ParameterTypePreview; import com.rapidminer.parameter.ParameterTypeProcessLocation; import com.rapidminer.parameter.ParameterTypeRegexp; import com.rapidminer.parameter.ParameterTypeRemoteFile; import com.rapidminer.parameter.ParameterTypeRepositoryLocation; import com.rapidminer.parameter.ParameterTypeStringCategory; import com.rapidminer.parameter.ParameterTypeSuggestion; import com.rapidminer.parameter.ParameterTypeText; import com.rapidminer.parameter.ParameterTypeTupel; import com.rapidminer.parameter.ParameterTypeValue; import com.rapidminer.tools.I18N; import com.rapidminer.tools.LogService; import com.rapidminer.tools.config.ParameterTypeConfigurable; /** * @author Simon Fischer * */ public abstract class PropertyPanel extends JPanel { /** * Can be used to decorate the parameter editor panels. See * {@link PropertyPanel#addPropertyEditorDecorator(PropertyEditorDecorator)}. * * @since 6.3.0 */ public static interface PropertyEditorDecorator { /** * Called once each time the operator property panel is created. * * @param parameterEditor * the original parameter editor panel * @param type * the parameter for which this editor panel is * @param typesOperator * the operator for which this editor panel is * @return this panel will be used as the parameter editor panel */ public JPanel decorate(JPanel parameterEditor, ParameterType type, Operator typesOperator); } private final List<PropertyEditorDecorator> editorDecorators = new ArrayList<>(); /** * Add the given decorator. * * @param d * the decorator; must not be {@code null} */ public void addPropertyEditorDecorator(PropertyEditorDecorator d) { if (d == null) { throw new IllegalArgumentException("d must not be null!"); } editorDecorators.add(d); } /** * Remove the given decorator. If the decorator is already removed, does nothing. * * @param d * the decorator; must not be {@code null} */ public void removePropertyEditorDecorator(PropertyEditorDecorator d) { if (d == null) { throw new IllegalArgumentException("d must not be null!"); } editorDecorators.remove(d); } private static final long serialVersionUID = -3478661102690417293L; private final GridBagLayout layout = new GridBagLayout(); /** Maps parameter type keys to currently displayed editors. */ private final Map<String, PropertyValueCellEditor> currentEditors = new LinkedHashMap<>(); /** Types currently displayed by editors. */ private Collection<ParameterType> currentTypes; private boolean showHelpButtons = true; public static final int VALUE_CELL_EDITOR_HEIGHT = 32; /** Color for the lines separating the entries */ private static final Color SEPARATION_LINE_COLOR = Colors.PANEL_SEPARATOR; /** Initial size of the tooltip string builder */ private static final int TOOLTIP_INITIAL_SIZE = 512; /** Closing tags that would stop the Swing HTML parser */ private static final Pattern CLOSING_TAGS = Pattern.compile("</(body|html)>", Pattern.CASE_INSENSITIVE); private static final Border PANEL_BORDER = BorderFactory.createCompoundBorder( BorderFactory.createMatteBorder(0, 0, 1, 0, SEPARATION_LINE_COLOR), BorderFactory.createEmptyBorder(0, 0, 3, 0)); private static Map<Class<? extends ParameterType>, Class<? extends PropertyValueCellEditor>> knownValueEditors = new HashMap<>(); static { registerPropertyValueCellEditor(ParameterTypePassword.class, DefaultPropertyValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeConfiguration.class, ConfigurationWizardValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypePreview.class, PreviewValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeColor.class, ColorValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeCategory.class, DefaultPropertyValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeStringCategory.class, DefaultPropertyValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeBoolean.class, DefaultPropertyValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeChar.class, DefaultPropertyValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeInt.class, DefaultPropertyValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeDouble.class, DefaultPropertyValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeAttributeFile.class, AttributeFileValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeFile.class, SimpleFileValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeRepositoryLocation.class, RepositoryLocationValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeProcessLocation.class, ProcessLocationValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeValue.class, OperatorValueValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeInnerOperator.class, InnerOperatorValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeList.class, ListValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeMatrix.class, MatrixValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeExpression.class, ExpressionValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeText.class, TextValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeAttribute.class, AttributeValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeTupel.class, ParameterTupelCellEditor.class); registerPropertyValueCellEditor(ParameterTypeRegexp.class, RegexpValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeAttributes.class, AttributesValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeEnumeration.class, EnumerationValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeDateFormat.class, DateFormatValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeConfigurable.class, ConfigurableValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeAttributeOrderingRules.class, AttributeOrderingCellEditor.class); registerPropertyValueCellEditor(ParameterTypeCronExpression.class, CronExpressionCellEditor.class); registerPropertyValueCellEditor(ParameterTypeDate.class, DateValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeFilter.class, FilterValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeSuggestion.class, SimpleSuggestionBoxValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeOAuth.class, OAuthValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeRemoteFile.class, RemoteFileValueCellEditor.class); registerPropertyValueCellEditor(ParameterTypeLinkButton.class, LinkButtonValueCellEditor.class); } /** * This method allows extensions to register own ParameterTypes and their editors. Please keep * in mind, that this method has to be called before any operator creation! That means, it has * to be performed during init of the extension. This method will register this value cell * editor as well in the PropertyTable. * * @param typeClass * The class of the new ParameterType for which the editor should be used * @param editor * The class of the PropertyValueCellEditor */ public static void registerPropertyValueCellEditor(Class<? extends ParameterType> typeClass, Class<? extends PropertyValueCellEditor> editor) { knownValueEditors.put(typeClass, editor); PropertyTable.registerPropertyValueCellEditor(typeClass, editor); } private PropertyValueCellEditor instantiateValueCellEditor(final ParameterType type) { return instantiateValueCellEditor(type, getOperator()); } public PropertyPanel() { setLayout(layout); } public void setupComponents() { if (SwingUtilities.isEventDispatchThread()) { setupComponentsNow(); } else { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { setupComponentsNow(); } }); } } public void fireEditingStoppedEvent() { Map<String, PropertyValueCellEditor> currentEditors = new LinkedHashMap<>(); currentEditors.putAll(this.currentEditors); if (currentEditors.size() > 0) { for (String key : currentEditors.keySet()) { currentEditors.get(key).stopCellEditing(); } } } private void setupComponentsNow() { removeAll(); currentEditors.clear(); currentTypes = getProperties(); if (currentTypes == null) { revalidate(); repaint(); return; } GridBagConstraints c = new GridBagConstraints(); c.anchor = GridBagConstraints.FIRST_LINE_START; c.fill = GridBagConstraints.HORIZONTAL; c.insets = new Insets(4, 4, 4, 4); int row = 0; for (final ParameterType type : currentTypes) { final PropertyValueCellEditor editor = instantiateValueCellEditor(type); currentEditors.put(type.getKey(), editor); Object value; value = getValue(type); if (value == null) { value = type.getDefaultValue(); } Component editorComponent = editor.getTableCellEditorComponent(null, value, false, row, 1); if (!isEnabled()) { SwingTools.setEnabledRecursive(editorComponent, false); } final Operator typesOperator = getOperator(); editor.addCellEditorListener(new CellEditorListener() { @Override public void editingCanceled(ChangeEvent e) {} @Override public void editingStopped(ChangeEvent e) { Object valueObj = editor.getCellEditorValue(); String value = type.toString(valueObj); String last; last = getValue(type); if (value != null && last == null || last == null && value != null || value != null && last != null && !value.equals(last)) { setValue(typesOperator, type, value, false); } } }); c.gridx = 0; c.gridy = row; c.weightx = 1; c.weighty = 0; add(createParameterPanel(type, editor, editorComponent), c); row++; } c.gridx = 0; c.gridy = row; c.weightx = 1; c.weighty = 1; c.fill = GridBagConstraints.BOTH; // Push panel contents to top JLabel dummyLabel = new JLabel(); dummyLabel.setOpaque(false); layout.setConstraints(dummyLabel, c); add(dummyLabel); revalidate(); repaint(); } /** * Creates a JPanel for a ParameterType. * * @param type * The ParameterType, for which the panel is created * @param tooltipText * The tool tip for the current ParameterType * @param editor * Editor for the current ParameterType * @param editorComponent * Editor component for the current ParameterType * * @return JPanel for the given ParameterType */ protected JPanel createParameterPanel(ParameterType type, PropertyValueCellEditor editor, Component editorComponent) { JPanel parameterPanel = null; if (editor.rendersLabel()) { parameterPanel = new JPanel(new BorderLayout()); parameterPanel.setOpaque(isOpaque()); parameterPanel.setBackground(getBackground()); parameterPanel.setPreferredSize( new Dimension((int) parameterPanel.getPreferredSize().getWidth(), VALUE_CELL_EDITOR_HEIGHT)); parameterPanel.add(editorComponent, editorComponent instanceof JCheckBox ? BorderLayout.WEST : BorderLayout.CENTER); } else { parameterPanel = new JPanel(new GridLayout(1, 2)); parameterPanel.setOpaque(isOpaque()); parameterPanel.setBackground(getBackground()); parameterPanel.setPreferredSize( new Dimension((int) parameterPanel.getPreferredSize().getWidth(), VALUE_CELL_EDITOR_HEIGHT)); final JLabel label = new JLabel(type.getKey().replace('_', ' ') + " "); label.setOpaque(isOpaque()); label.setFont(getFont()); label.setBackground(getBackground()); int style = Font.PLAIN; if (!type.isOptional()) { style |= Font.BOLD; } if (type.isExpert()) { style |= Font.ITALIC; } label.setFont(label.getFont().deriveFont(style)); label.setLabelFor(editorComponent); if (!isEnabled()) { SwingTools.setEnabledRecursive(label, false); } parameterPanel.add(label); parameterPanel.add(editorComponent); } for (PropertyEditorDecorator decorator : editorDecorators) { parameterPanel = decorator.decorate(parameterPanel, type, getOperator()); } JPanel surroundingPanel = new JPanel(new BorderLayout()); surroundingPanel.add(parameterPanel, BorderLayout.CENTER); surroundingPanel.setBorder(PANEL_BORDER); if (showHelpButtons) { addHelpLabel(type.getKey(), type.getKey().replace("_", " "), type.getDescription(), type.getRange(), type.isOptional(), surroundingPanel); } return surroundingPanel; } /** * Adds a help icon to the provided JPanel which shows a tooltip when hovering over it. * * @param key * the key of the parameter * @param title * the tooltip title * @param description * the tooltip description * @param range * the parameter range. Can be <code>null</code> in case the parameter does not have * a range * @param isOptional * whether the parameter is optional * @param labelPanel * the panel which will be used to add the label. The panel needs to have a * {@link BorderLayout} as layout manager as the label will be added with the * constraint {@link BorderLayout#EAST}. */ protected final void addHelpLabel(final String key, final String title, final String description, final String range, final boolean isOptional, JPanel labelPanel) { // cannot just call {@link SwingTools#addTooltipHelpIconToLabel} since {@link // #getToolTipText} must be called in the TipProvider because of the caching in {@link // OperatorPropertyPanel#getToolTipText} final JLabel helpLabel = SwingTools.initializeHelpLabel(labelPanel); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { TipProvider tipProvider = new TipProvider() { @Override public String getTip(Object id) { if (id == null) { return null; } else { return getToolTipText(key, title, description, range, isOptional); } } @Override public Object getIdUnder(Point point) { return helpLabel; } @Override public Component getCustomComponent(Object id) { return null; } }; SwingTools.setupTooltip(tipProvider, getDialogOwner(), helpLabel); } }); } protected boolean hasEditorFor(ParameterType type) { return currentEditors.containsKey(type.getKey()); } protected int getNumberOfEditors() { return currentEditors.size(); } protected PropertyValueCellEditor getEditorForKey(String key) { return currentEditors.get(key); } protected abstract String getValue(ParameterType type); protected abstract void setValue(Operator operator, ParameterType type, String value); protected abstract Collection<ParameterType> getProperties(); protected abstract Operator getOperator(); /** * @return the dialog owner (if the {@link PropertyPanel}) has one */ protected JDialog getDialogOwner() { return null; } /** * Subclasses of PropertyPanel (e.g. GenericParameterPanel) can overwrite this method in order * to specify if GUI elements should be updated after setting the Value. **/ protected void setValue(Operator operator, ParameterType type, String value, boolean updateComponents) { setValue(operator, type, value); } public static PropertyValueCellEditor instantiateValueCellEditor(final ParameterType type, Operator operator) { PropertyValueCellEditor editor; Class<?> typeClass = type.getClass(); do { Class<? extends PropertyValueCellEditor> editorClass = knownValueEditors.get(typeClass); if (editorClass != null) { try { Constructor<? extends PropertyValueCellEditor> constructor = editorClass .getConstructor(new Class[] { typeClass }); editor = constructor.newInstance(new Object[] { type }); } catch (Exception e) { LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.gui.properties.PropertyPanel.construct_property_editor_error", e), e); editor = new DefaultPropertyValueCellEditor(type); } break; } else { typeClass = typeClass.getSuperclass(); editor = new DefaultPropertyValueCellEditor(type); } } while (typeClass != null); editor.setOperator(operator); return editor; } /** * Creates a text for the {@link ToolTipWindow}. */ protected String getToolTipText(String key, String title, String description, String range, boolean isOptional) { // Title StringBuilder sb = new StringBuilder(TOOLTIP_INITIAL_SIZE); sb.append("<h3 style=\"padding-bottom:4px\">" + title + "</h3>"); // Description if (description != null && !description.isEmpty()) { sb.append("<p style=\"padding-bottom:4px\">"); sb.append("<b>" + I18N.getGUIMessage("gui.dialog.settings.description") + "</b>: "); // prevent the Swing HTML parser from stopping here sb.append(CLOSING_TAGS.matcher(description).replaceAll("")); sb.append("</p>"); } // Range if (range != null && !range.isEmpty()) { sb.append("<p style=\"padding-bottom:4px\">"); sb.append("<b>" + I18N.getGUIMessage("gui.dialog.settings.range") + "</b>: "); sb.append(range); sb.append("</p>"); } // Optional/required sb.append("<p style=\"padding-bottom:4px\">"); sb.append("<b>" + I18N.getGUIMessage("gui.dialog.settings.optional") + "</b>: "); if (isOptional) { sb.append(I18N.getGUIMessage("gui.dialog.settings.true")); } else { sb.append(I18N.getGUIMessage("gui.dialog.settings.false")); } sb.append("</p>"); return sb.toString(); } /** * @param showHelp * defines whether the parameter help icon should be shown */ public void setShowParameterHelp(boolean showHelp) { this.showHelpButtons = showHelp; } /** * @return returns <code>true</code> in case the parameter help icon should be shown */ public boolean isShowParameterHelp() { return showHelpButtons; } }