package aima.gui.swing.applications.robotics.components; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.HashMap; import java.util.LinkedList; import java.util.Map.Entry; import java.util.Properties; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.WindowConstants; import aima.gui.swing.framework.util.GuiBase; /** * This class can manage parameters of an application bundled in a single GUI.<br/> * A single parameter is described by a set of strings. The first string is the value, which will be saved to and restored from a file and displayed in a text field. * The next string is a key by which the value is identified. This key has to be unique against all other settings and all special settings. * The last string may be a user readable label of the value that will be displayed next to the text field.<br/> * If the parameter can not hold its value within a single string, it is possible to add classes extending {@link SpecialSetting} which extends {@link JPanel} to the GUI.<br/> * For any parameter that is in use by this class listeners may be added as {@link ISettingsListener}. * * @author Arno von Borries * @author Jan Phillip Kretzschmar * @author Andreas Walscheid * */ public class Settings { private Properties values = new Properties(); private HashMap<String,String> names = new HashMap<String,String>(); private HashMap<String,SettingsListenerList> listeners = new HashMap<String,SettingsListenerList>(); private HashMap<String,SpecialSetting> specials = new HashMap<String,SpecialSetting>(); private LinkedList<String> order = new LinkedList<String>(); private Gui gui = new Gui(); /** * Builds the GUI for all registered settings. * All registered settings will become visible in the GUI. */ public void buildGui() { gui.buildPanels(); } /** * Shows the GUI. */ public void show() { gui.showGui(); } /** * Restores all values that were saved to the file in a previous instance. * @param file the file in which the values are saved. */ public void loadSettings(File file) { if(!file.canRead()) return; FileInputStream input = null; try { input = new FileInputStream(file); values.load(input); } catch (IOException e) { e.printStackTrace(); } finally { if(input != null) { try { input.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * Saves all values to the file. In addition all {@link SpecialSetting} are saved, too. * @param file the file to be saved to. */ public void saveSettings(File file) { if(!file.exists()) { try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } if(!file.canWrite()) return; for(SpecialSetting special: specials.values()) { special.saveSettings(values); } FileOutputStream output = null; try { output = new FileOutputStream(file); values.store(output,null); } catch (IOException e) { e.printStackTrace(); } finally { if(output != null) { try { output.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * Registers a setting identified by a given key which will be displayed with the specified name in the GUI. * The default value will be stored in the values if the settings do not already contain a value for that key. * If you want to force a value into the settings call {@code setSetting}. * * @param key the key under which the setting is identified. * @param name the text that will be displayed in the GUI next to the text field containing the value. * @param defaultValue the value that should be stored as a default if a value does not already exist. */ public void registerSetting(String key, String name, String defaultValue) { if(!values.containsKey(key)) { values.setProperty(key, defaultValue); SettingsListenerList list = listeners.get(key); if(list != null) list.notify(key, defaultValue); } if(!order.contains(key)) { names.put(key, name); order.add(key); } } /** * Sets the value of a property identified by the provided key. * This method does not register the property in the GUI. For that purpose call {@code registerSetting}. * * @param key the key under which the setting is identified. * @param value the value to be set. */ public void setSetting(String key, String value) { values.setProperty(key, value); SettingsListenerList list = listeners.get(key); if(list != null) list.notify(key, value); } /** * Checks whether a value is stored for a given key. * * @param key the key which will be searched for. * @return true if a value is stored for the key. */ public boolean existsSetting(String key) { return values.containsKey(key); } /** * Checks whether a setting identified by the given key is registered in the GUI. * * @param key the key which should be searched for. * @return true if the setting is displayed. */ public boolean isSettingDisplayed(String key) { return order.contains(key); } /** * Returns the value that is stored for the key. * The method returns {@code null} if no value is found for the provided key. * * @param key the key to search for. * @return the value that is stored for the given key. */ public String getSetting(String key) { return values.getProperty(key); } /** * Returns the value that is stored for the key. * The method returns {@code defaultValue} if no value is found for the provided key. * * @param key the key to search for. * @param defaultValue the value to be returned if no value is stored for the given key. * @return the value that is stored for the given key. */ public String getSetting(String key, String defaultValue) { String result = values.getProperty(key); return result == null ? defaultValue : result; } /** * Registers a special setting identified by a given key. This special setting will be displayed in the GUI. * After it has been registered the settings that were stored before are loaded into the fragment. * * @param key the key through which the special setting is identified. * @param fragment the actual special setting. */ public void registerSpecialSetting(String key, SpecialSetting fragment) { specials.put(key, fragment); order.add(key); fragment.loadSettings(values); } /** * Returns the special setting that is stored for the key. * The method returns {@code null} if no special setting can be found for the provided key. * * @param key the key to search for. * @return the special setting that is stored for the given key. */ public SpecialSetting getSpecialSetting(String key) { return specials.get(key); } /** * Registers a listener for a given key that will be notified if the value of the key changes. * This does not apply to special settings. * * @param key the key on which is to be listened. * @param listener an {@link ISettingsListener} that will be notified if the value of the key changes. */ public void registerListener(String key, ISettingsListener listener) { SettingsListenerList list = listeners.get(key); if(list == null) { list = new SettingsListenerList(); listeners.put(key,list); } list.add(listener); } /** * Removes the listener from the specified key. * * @param key the key on which the listener is registered. * @param listener the listener that will be removed from the key. */ public void removeListener(String key, ISettingsListener listener) { SettingsListenerList list = listeners.get(key); if(list != null) { list.remove(listener); } } /** * Notifies all registered listeners about the current value of the key they are listening on. */ public void notifyAllListeners() { for(Entry<String, SettingsListenerList> entry: listeners.entrySet()) { entry.getValue().notify(entry.getKey(),values.getProperty(entry.getKey())); } for(SpecialSetting special:specials.values()) { special.notifyChangeListener(); } } /** * Updates the value for a given key in the GUI from the stored values. * @param key the key on which the GUI will be updated. */ public void updateGuiSetting(String key) { gui.revertSetting(key); } /** * Enables all GUI buttons. */ public void enableGuiButtons() { gui.enableButtons(); } /** * Disables all GUI buttons. */ public void disableGuiButtons() { gui.disableButtons(); } /** * A basic list that supports add and remove operations. All settings listeners in the list can be notified. * * @author Arno von Borries * @author Jan Phillip Kretzschmar * @author Andreas Walscheid * */ private static class SettingsListenerList { private LinkedList<ISettingsListener> listeners = new LinkedList<ISettingsListener>(); private void notify(String key, String value) { boolean success = true; for(ISettingsListener listener: listeners) { success = listener.notifySetting(key, value) ? success : false; } if(!success) { GuiBase.showMessageBox("A setting may be mallformed."); } } private void add(ISettingsListener listener) { listeners.add(listener); } private void remove(ISettingsListener listener) { listeners.remove(listener); } } /** * A settings listener can be registered for a specific key. * If the value for that key changes all registered listeners on that key will be notified with the new value and the key. * * @author Arno von Borries * @author Jan Phillip Kretzschmar * @author Andreas Walscheid * */ public static interface ISettingsListener { /** * This method will be called to notify the settings listener. * @param key the key to which the value belongs. * @param value the value that (may have) changed. * @return false if the setting was not used. */ public boolean notifySetting(String key, String value); } /** * A special setting is a {@code JPanel}. This panel can be added to the GUI if a simple text field is unsuitable to display and edit a setting.<br/> * Thus this special setting has to take care of storing and managing its values. * This means that it has to support settings listeners on its own and can not register these listeners in the {@link Settings} class. * Values that should be saved on exit and restored on startup of the application can be saved in a provided {@link Properties} however. * The keys used by the special setting to store values have to be unique against all simple settings and other special settings stored values. * * @author Arno von Borries * @author Jan Phillip Kretzschmar * @author Andreas Walscheid * */ @SuppressWarnings("serial") public static abstract class SpecialSetting extends JPanel { /** * Loads values from the provided key value storage that have been saved before. * @param values the {@link Properties} which contains all values that have been stored. */ public abstract void loadSettings(Properties values); /** * Save values to the provided key value storage. * Be aware that the storage may already contain entries from other settings and from a previous save operation of this special setting. * @param values the {@link Properties} which the values should be stored to. */ public abstract void saveSettings(Properties values); /** * Restores the current value(s) into the GUI. This does not notify any listeners. */ public abstract void revertGui(); /** * Saves the value(s) currently set in the GUI to the current value(s). This notifies all registered listeners. */ public abstract void saveGui(); /** * Notifies all registered listeners on this special setting with the current value(s). */ public abstract void notifyChangeListener(); } /** * This class shows a GUI where you edit settings. * * @author Arno von Borries * @author Jan Phillip Kretzschmar * @author Andreas Walscheid * */ protected class Gui extends JFrame { private static final long serialVersionUID = 1L; private final Dimension guiSize = new Dimension(500, 600); private JPanel inScrollPane; private JScrollPane scrollPane; private HashMap<String,KeyPanel> keyPanels = new HashMap<String,KeyPanel>(); private JButton btnSave; private JButton btnAbort; /** * The constructor generates the GUI. * The GUI contains a save button, a revert button and a scroll-able field where the setting are stored. */ protected Gui() { setSize(guiSize); setResizable(false); setTitle("MCL Settings"); setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); //scrollPane: inScrollPane = new JPanel(); inScrollPane.setLayout(new BoxLayout(inScrollPane,BoxLayout.Y_AXIS)); scrollPane = new JScrollPane(inScrollPane, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); //btnPanel: btnSave = new JButton("Save"); btnSave.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { save(); } }); btnAbort = new JButton("Revert"); btnAbort.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { revert(); } }); //Apply same size to both buttons: Dimension saveDimension = btnSave.getPreferredSize(); Dimension abortDimension = btnAbort.getPreferredSize(); Dimension bothDimension = new Dimension( (int) ((saveDimension.getWidth() > abortDimension.getWidth() ? saveDimension.getWidth() : abortDimension.getWidth()) * 2), (int) ((saveDimension.getHeight() > abortDimension.getHeight() ? saveDimension.getHeight() : abortDimension.getHeight()) * 2) ); btnSave.setPreferredSize(bothDimension); btnAbort.setPreferredSize(bothDimension); JPanel btnPanel = new JPanel(); btnPanel.setLayout(new FlowLayout()); btnPanel.add(btnSave); btnPanel.add(GuiBase.getClearanceComp()); btnPanel.add(btnAbort); btnPanel.setAlignmentX(CENTER_ALIGNMENT); JPanel outerBtnPanel = new JPanel(); outerBtnPanel.setLayout(new BoxLayout(outerBtnPanel,BoxLayout.Y_AXIS)); outerBtnPanel.add(GuiBase.getClearanceComp()); outerBtnPanel.add(btnPanel); outerBtnPanel.add(GuiBase.getClearanceComp()); //Put panels together: JPanel contentPanel = new JPanel(); contentPanel.setLayout(new BorderLayout()); contentPanel.add(scrollPane, BorderLayout.CENTER); contentPanel.add(outerBtnPanel, BorderLayout.SOUTH); add(contentPanel, BorderLayout.CENTER); } /** * Creates all key panels and special settings that have been registered. */ protected void buildPanels() { keyPanels.clear(); inScrollPane.removeAll(); for(String key: order) { String name = names.get(key); if(name != null) { String value = values.getProperty(key); KeyPanel keyPanel = new KeyPanel(name, value); keyPanel.setAlignmentX(LEFT_ALIGNMENT); keyPanel.setMaximumSize(guiSize); inScrollPane.add(keyPanel); keyPanels.put(key,keyPanel); } else { SpecialSetting special = specials.get(key); special.setAlignmentX(LEFT_ALIGNMENT); inScrollPane.add(special); } } } /** * Shows the GUI. */ protected void showGui() { this.setVisible(true); } /** * Enables all buttons that are registered in the Settings. */ protected void enableButtons() { btnSave.setEnabled(true); btnAbort.setEnabled(true); for(SpecialSetting special: specials.values()) { if(special instanceof ButtonPanel) ((ButtonPanel) special).enableButton(); } setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); } /** * Disables all buttons that are registered in the Settings. */ protected void disableButtons() { btnSave.setEnabled(false); btnAbort.setEnabled(false); for(SpecialSetting special: specials.values()) { if(special instanceof ButtonPanel) ((ButtonPanel) special).disableButton(); } setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); } /** * Restores the stored value for a single field. * @param key the key identifying the field and value. */ protected void revertSetting(String key) { KeyPanel kp = keyPanels.get(key); kp.setValue(getSetting(key)); } /** * Restores the previous values. */ protected void revert() { for(Entry<String, KeyPanel> entry: keyPanels.entrySet()) { String name = names.get(entry.getKey()); if(name != null) { entry.getValue().setValue(getSetting(entry.getKey())); } } for(SpecialSetting special: specials.values()) { special.revertGui(); } } /** * Saves all values and special settings from the GUI. */ protected void save() { for(Entry<String, KeyPanel> entry: keyPanels.entrySet()) { String name = names.get(entry.getKey()); if(name != null) { setSetting(entry.getKey(), entry.getValue().getValue()); } } for(SpecialSetting special: specials.values()) { special.saveGui(); } } } }