// Copyright (c) 2006 - 2008, Markus Strauch. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF // THE POSSIBILITY OF SUCH DAMAGE. package net.sf.sdedit.ui.components.configuration; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeMap; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.ListCellRenderer; import javax.swing.border.TitledBorder; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import net.sf.sdedit.ui.components.ButtonPanel; /** * A component for configuring the properties of a {@linkplain Bean}. * * @author Markus Strauch * * @param <C> * the type of the data object to be configured */ @SuppressWarnings("serial") public class ConfigurationUI<C extends DataObject> extends JPanel { private Map<String, JPanel> categoryMap; private ConfiguratorFactory<C> configuratorFactory; private Map<String, List<Configurator<?, C>>> configurators; private Map<String, Integer> labelWidths; private JList categoryList; private JPanel right; private ConfigurationDialog parent; private Bean<C> bean; private Bean<C> formerState; private Bean<C> defaultBean; private ButtonPanel buttonPanel; private JPanel categoryListPanel; /** * Constructor. * * @param parent * the dialog that contains this configuration component * @param bean * the bean to be configured * @param defaultBean * a bean with values that can be restored, or null * @param saveAsDefault * a string for the button that is associated to the action that * saves the bean's values as the defaultBean's values, * optionally followed by '|' and a tool-tip, or null, if no such * button should be visible * @param loadDefault * a string for the button that is associated to the action that * restores the bean's values from the defaultBean's values, * optionally followed by '|' and a tool-tip, or null, if no such * button should be visible * @param description * a description to appear at the top of this configuration * component */ public ConfigurationUI(ConfigurationDialog parent, Bean<C> bean, Bean<C> defaultBean, String saveAsDefault, String loadDefault, String description) { super(); this.bean = bean; formerState = bean.copy(); this.defaultBean = defaultBean; setLayout(new BorderLayout()); this.parent = parent; right = new JPanel(); right.setLayout(new BorderLayout()); right.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0)); configuratorFactory = new ConfiguratorFactory<C>(); configurators = new HashMap<String, List<Configurator<?, C>>>(); categoryListPanel = new JPanel(); categoryListPanel.setLayout(new BorderLayout()); categoryListPanel.add(new JLabel("Categories:"), BorderLayout.NORTH); categoryList = new JList(); JScrollPane listScrollPane = new JScrollPane(categoryList); categoryListPanel.add(listScrollPane, BorderLayout.CENTER); add(categoryListPanel, BorderLayout.WEST); add(right, BorderLayout.CENTER); if (description != null) { JLabel descriptionLabel = new JLabel(description); descriptionLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); add(descriptionLabel, BorderLayout.NORTH); } labelWidths = new HashMap<String, Integer>(); categoryMap = new TreeMap<String, JPanel>(); categoryList.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { String category = (String) categoryList.getSelectedValue(); if (category != null) { JPanel panel = categoryMap.get(category); right.removeAll(); right.add(panel, BorderLayout.CENTER); right.updateUI(); } } }); final ListCellRenderer lcr = categoryList.getCellRenderer(); categoryList.setCellRenderer(new ListCellRenderer() { public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { JComponent comp = (JComponent) lcr .getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); comp.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); return comp; } }); buttonPanel = new ButtonPanel(); add(buttonPanel, BorderLayout.SOUTH); buttonPanel.addAction(cancel); if (loadDefault != null) { String[] split = loadDefault.split("\\|"); String name = split[0]; String tooltip = split.length == 1 ? "" : split[1]; buttonPanel.addAction(restoreDefaultsAction(name, tooltip)); } if (saveAsDefault != null) { String[] split = saveAsDefault.split("\\|"); String name = split[0]; String tooltip = split.length == 1 ? "" : split[1]; buttonPanel.addAction(saveAsDefaultAction(name, tooltip)); } buttonPanel.addAction(ok); init(bean, defaultBean); refreshAll(); } public void hideCategoryList() { remove(categoryListPanel); } public void hideButtons() { remove(buttonPanel); } public void setEnabled(boolean enabled) { super.setEnabled(enabled); categoryList.setEnabled(enabled); for (Configurator<?, C> configurator : getConfigurators()) { configurator.setEnabled(enabled && configurator.isDependencySatisfied()); } } private List<Configurator<?, C>> getConfigurators() { List<Configurator<?, C>> list = new LinkedList<Configurator<?, C>>(); for (List<Configurator<?, C>> sublist : configurators.values()) { list.addAll(sublist); } return list; } /** * Returns for each property category of the bean the panel where the * properties can be configured * * @return the panels for the bean's property categories */ public Collection<JPanel> getCategoryPanels() { return categoryMap.values(); } private void init(Bean<C> bean, Bean<C> defaultObject) { for (PropertyDescriptor property : bean.getProperties()) { if (property.getWriteMethod().getAnnotation(Adjustable.class) .editable()) { add(bean, property, defaultObject); } } for (Map.Entry<String, List<Configurator<?,C>>> entry : configurators.entrySet()) { int width = labelWidths.get(entry.getKey()); for (Configurator<?,C> configurator : entry.getValue()) { configurator.setLabelWidth(width); } } } @SuppressWarnings("unchecked") private void add(Bean<C> bean, PropertyDescriptor property, Bean<C> defaultObject) { Method writeMethod = property.getWriteMethod(); if (writeMethod.isAnnotationPresent(Adjustable.class)) { Adjustable adj = writeMethod.getAnnotation(Adjustable.class); if (adj.editable()) { String category = adj.category(); JPanel categoryPanel = categoryMap.get(category); int labelWidth = 0; if (categoryPanel == null) { categoryPanel = new JPanel(); categoryPanel.setLayout(new BoxLayout(categoryPanel, BoxLayout.Y_AXIS)); categoryPanel.setBorder(new TitledBorder(category)); configurators.put(category, new LinkedList<Configurator<?, C>>()); categoryMap.put(category, categoryPanel); categoryList.setListData(categoryMap.keySet().toArray()); if (categoryMap.keySet().iterator().next().equals(category)) { right.removeAll(); right.add(categoryPanel, BorderLayout.CENTER); } categoryList.setSelectedIndex(0); } else { labelWidth = labelWidths.get(category); } Configurator<?, C> configurator = configuratorFactory .createConfigurator(bean, property, defaultObject); if (adj.tooltip().length() > 0) { configurator.setToolTipText(adj.tooltip()); } labelWidth = Math.max(labelWidth, configurator.getLabelWidth()); labelWidths.put(category, labelWidth); int height = configurator.getPreferredSize().height; Dimension size = new Dimension(500, height); configurator.setMinimumSize(size); configurator.setPreferredSize(size); configurator.setMaximumSize(size); configurator.setAlignmentX(0F); configurators.get(category).add(configurator); categoryPanel.add(configurator); int gap = 15 * adj.gap(); categoryPanel .add(Box.createRigidArea(new Dimension(1, gap))); } } else { throw new IllegalArgumentException( "No Adjustable annotation present for property " + property.getName()); } } public void setBean(Bean<C> bean) { this.bean = bean; this.formerState = bean.copy(); for (Configurator<?, C> configurator : getConfigurators()) { configurator.setBean(bean); } } private Action ok = new AbstractAction() { { putValue(Action.NAME, "OK"); putValue(Action.SHORT_DESCRIPTION, "Acknowledges all changes made since this dialog has been opened"); } public void actionPerformed(ActionEvent e) { parent.setVisible(false); parent.apply(); } }; /** * Calls {@linkplain Configurator#refresh()} on all configurators that * belong to this configuration component, so that they will reflect the * values of their corresponding properties thereafter. */ public void refreshAll() { for (Configurator<?, C> configurator : getConfigurators()) { configurator.refresh(); } } public void apply() { formerState.takeValuesFrom(bean); } public void cancel() { bean.takeValuesFrom(formerState); } private Action cancel = new AbstractAction() { { putValue(Action.NAME, "Cancel"); putValue(Action.SHORT_DESCRIPTION, "Cancels all changes made since this dialog has been opened"); } public void actionPerformed(ActionEvent e) { parent.cancel(); parent.setVisible(false); } }; private Action restoreDefaultsAction(final String name, final String tooltip) { return new AbstractAction() { { putValue(Action.NAME, name); putValue(Action.SHORT_DESCRIPTION, tooltip); } public void actionPerformed(ActionEvent e) { if (defaultBean != null) { bean.takeValuesFrom(defaultBean); } } }; } private Action saveAsDefaultAction(final String name, final String tooltip) { return new AbstractAction() { { putValue(Action.NAME, name); putValue(Action.SHORT_DESCRIPTION, tooltip); } public void actionPerformed(ActionEvent e) { if (defaultBean != null) { defaultBean.takeValuesFrom(bean); } } }; } }