/**
* 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.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Properties;
import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingConstants;
import javax.swing.Timer;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.jdesktop.swingx.prompt.PromptSupport;
import com.rapidminer.gui.ApplicationFrame;
import com.rapidminer.gui.tools.ResourceAction;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.gui.tools.TextFieldWithAction;
import com.rapidminer.gui.tools.dialogs.ButtonDialog;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.ParameterService;
/**
* The settings dialog for user settings.
*
* <p>
* Settings are stored in the file <i>rapidminer-studio-settings.cfg</i> in the user directory
* <i>.RapidMiner</i> and can overwrite system wide settings. The settings are grouped in
* {@link SettingsTabs} each of which contains a {@link SettingsPropertyPanel}. Each setting is
* represented by a {@link SettingsItem}, which maintains the tab, grouping, key, i18n of its title
* and description.
* </p>
*
* <p>
* To add a new preference, you have to use the method {@link ParameterService#registerParameter()}.
* Your new parameter can be added to the i18n by adding the related key and a value to the resource
* file <i>Settings.properties</i>. Configure the structure of your properties by editing the
* resource file <i>settings.xml</i>. This affects the order and sub-groups. Extensions can use the
* resource files <i>SettingsMYEXT.properties</i> and <i>settingsMYEXT.xml</i>. This is documented
* in <i>How to extend RapidMiner</i>, available at
* <a href="http://rapidminer.com/documentation/">http://rapidminer.com/documentation/</a>
* </p>
*
* @author Ingo Mierswa, Adrian Wilke
*/
public class SettingsDialog extends ButtonDialog {
private static final long serialVersionUID = 6665295638614289994L;
/**
* the delay before filtering is started after the user finished typing in milliseconds:
* {@value}
*/
private static final int FILTER_TIMER_DELAY = 500;
/** the identifier of the search focus action */
private static final String ACTION_NAME_SEARCH = "focusSearchField";
/**
* icon used in the {@link TextFieldWithAction} when the filter remove action is hovered
*/
private final ImageIcon CLEAR_FILTER_HOVERED_ICON = SwingTools.createIcon("16/x-mark_orange.png");
/**
* the main container which contains the {@link #tabs} or the {@link #noMatchingSettingsLabel}.
*/
private JPanel container;
/**
* the displayed tabs which includes the settings
*/
private SettingsTabs tabs;
/**
* cache for the displayed properties
*/
private Properties propertyCache = new Properties();
/**
* this label will be shown if no matching settings could be found
*/
private JLabel noMatchingSettingsLabel = new JLabel(I18N.getGUILabel("settings.no_matching_settings"),
SwingConstants.CENTER);
/**
* Sets up the related {@link SettingsTabs} and buttons.
*/
public SettingsDialog() {
this(null);
}
/**
* Sets up the related {@link SettingsTabs} and buttons.
*
* Selects the specified selected tab.
*
* @param initialSelectedTab
* A key of a preferences group to identify the initial selected tab.
*/
public SettingsDialog(String initialSelectedTab) {
super(ApplicationFrame.getApplicationFrame(), "settings", ModalityType.APPLICATION_MODAL, new Object[] {});
// main component container
container = new JPanel(new BorderLayout());
container.add(createSearchPanel(), BorderLayout.NORTH);
container.add(createTabs(initialSelectedTab, null));
// Create buttons
Collection<AbstractButton> buttons = new LinkedList<>();
buttons.add(new JButton(new ResourceAction("settings_ok") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
try {
updateFilter(null);
tabs.save();
setConfirmed(true);
dispose();
} catch (IOException ioe) {
SwingTools.showSimpleErrorMessage(SettingsDialog.this, "cannot_save_properties", ioe);
}
}
}));
buttons.add(makeCancelButton());
layoutDefault(container, NORMAL_EXTENDED, buttons);
addWindowListener(new BetaFeaturesListener());
addWindowListener(new AdditionalPermissionsListener());
}
/**
* Creates the settings tabs in regard to the filter.
*/
private JComponent createTabs(String initialSelectedTab, String filter) {
// Create tabs
tabs = new SettingsTabs(this, filter, propertyCache);
// Select tab, if known
if (initialSelectedTab != null) {
tabs.selectTab(initialSelectedTab);
}
if (tabs.getTabCount() == 0) {
return noMatchingSettingsLabel;
} else {
return tabs;
}
}
/**
* Creates the search panel.
*/
private JPanel createSearchPanel() {
JPanel searchPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.weightx = 1;
gbc.insets = new Insets(0, 5, 5, 0);
searchPanel.add(Box.createHorizontalGlue(), gbc);
gbc.gridx += 1;
gbc.weightx = 0;
JLabel filterLabel = new JLabel(I18N.getGUILabel("settings.filter"));
searchPanel.add(filterLabel, gbc);
final JTextField filterNameField = new JTextField(10);
filterNameField.setMinimumSize(new Dimension(300, 15));
filterNameField.setPreferredSize(new Dimension(300, 15));
final ResourceAction filterAction = new ResourceAction(true, "settings.filter") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(final ActionEvent e) {
updateFilter(filterNameField.getText());
}
};
final DocumentListener filterListener = new DocumentListener() {
private Timer updateTimer;
{
updateTimer = new Timer(FILTER_TIMER_DELAY, filterAction);
updateTimer.setRepeats(false);
}
@Override
public void removeUpdate(final DocumentEvent e) {
updateTimer.restart();
}
@Override
public void insertUpdate(final DocumentEvent e) {
updateTimer.restart();
}
@Override
public void changedUpdate(final DocumentEvent e) {
updateTimer.restart();
}
};
filterNameField.setToolTipText(I18N.getMessage(I18N.getGUIBundle(), "gui.label.settings.filter_field.tip"));
filterNameField.addActionListener(filterAction);
filterNameField.getDocument().addDocumentListener(filterListener);
filterNameField.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke(KeyEvent.VK_F, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
ACTION_NAME_SEARCH);
filterNameField.getActionMap().put(ACTION_NAME_SEARCH, new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(final ActionEvent e) {
filterNameField.requestFocusInWindow();
}
});
PromptSupport.setPrompt(I18N.getMessage(I18N.getGUIBundle(), "gui.label.settings.filter_field.prompt"),
filterNameField);
PromptSupport.setFontStyle(Font.ITALIC, filterNameField);
ResourceAction deleteFilterAction = new ResourceAction(true, "settings.filter_delete") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(final ActionEvent e) {
// immediately show the filter update by calling the update filter method
// prevent duplicate updates by removing and re-adding the corresponding listener
filterNameField.getDocument().removeDocumentListener(filterListener);
filterNameField.setText("");
updateFilter(null);
filterNameField.getDocument().addDocumentListener(filterListener);
}
};
TextFieldWithAction searchField = new TextFieldWithAction(filterNameField, deleteFilterAction,
CLEAR_FILTER_HOVERED_ICON);
searchField.setMinimumSize(new Dimension(140, 20));
searchField.setPreferredSize(new Dimension(140, 20));
gbc.gridx += 1;
searchPanel.add(searchField, gbc);
return searchPanel;
}
private void updateFilter(String filter) {
container.remove(tabs);
container.remove(noMatchingSettingsLabel);
container.add(createTabs(null, filter), BorderLayout.CENTER);
container.revalidate();
container.repaint();
}
@Override
public String getInfoText() {
return I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.settings.message");
}
@Override
public void setVisible(boolean b) {
if (tabs != null) {
tabs.requestFocusInWindow();
}
super.setVisible(b);
}
}