// 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.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyDescriptor; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.SwingUtilities; /** * An abstract base class for the components used by * {@linkplain ConfigurationUI} for configuring properties of a * {@linkplain Bean}. * * @author Markus Strauch * * @param <C> * the type of the data object to be configured * @param <T> * the type of the property to be configured */ public abstract class Configurator<T, C extends DataObject> extends JPanel implements PropertyChangeListener, ActionListener { /** * The bean to be configured. */ private Bean<C> bean; /** * The particular property that is configured. */ private PropertyDescriptor property; private String[] dependentProperty; private String[] dependentValue; private boolean[] isDependencyEquality; protected Configurator(Bean<C> bean, PropertyDescriptor property) { super(); this.bean = bean; this.property = property; Adjustable adj = property.getWriteMethod().getAnnotation( Adjustable.class); if (!adj.depends().equals("")) { String[] dependencies = adj.depends().split(","); int n = dependencies.length; dependentProperty = new String[n]; dependentValue = new String[n]; isDependencyEquality = new boolean[n]; for (int i = 0; i < n; i++) { String dependency = dependencies[i]; String[] split; if (dependency.indexOf('!') == -1) { isDependencyEquality[i] = true; split = dependency.split("="); } else { isDependencyEquality[i] = false; split = dependency.split("!="); } dependentProperty[i] = split[0]; dependentValue[i] = split[1]; } } bean.addPropertyChangeListener(this); } public int getLabelWidth() { return -1; } public void setLabelWidth(int width) { } /** * Changes the components for configuring the property such that they * reflect the given value. * * @param value * the current value of the property */ protected abstract void refresh(T value); /** * Returns a non-null value that is used as a replacement when a property * has the value <tt>null</tt>. * * @return a non-null value that is used as a replacement when a property * has the value <tt>null</tt> */ protected abstract T getNullValue(); /** * Returns true if there is no boolean property that the configurability of * the property managed by this <tt>Configurator</tt> depends on, or if * there is such a boolean property that is set <tt>true</tt>. * * @return flag denoting if the boolean property the configurability depends * on is true */ public boolean isDependencySatisfied() { if (dependentProperty == null) { return true; } for (int i = 0; i < dependentProperty.length; i++) { String value = bean.getValue(dependentProperty[i]).toString(); boolean sat = dependentValue[i].equals(value); if (!isDependencyEquality[i]) { sat = !sat; } if (!sat) { return false; } } return true; } public void setBean(Bean<C> bean) { this.bean = bean; bean.addPropertyChangeListener(this); refresh(); } /** * Returns the underlying <tt>Bean</tt>, of which a property is * configured by this <tt>Configurator</tt>. * * @return the underlying <tt>Bean</tt>, of which a property is * configured by this <tt>Configurator</tt> */ public Bean<C> getBean() { return bean; } /** * Returns the <tt>PropertyDescriptor</tt> for the particular property * that is being configured. * * @return the <tt>PropertyDescriptor</tt> for the particular property * that is being configured */ public PropertyDescriptor getProperty() { return property; } /** * Changes the component(s) used for configuration such that the current * value of the property is reflected */ @SuppressWarnings("unchecked") public void refresh() { refresh(getValue()); setEnabled(isDependencySatisfied()); } /** * This method is called by the underlying {@linkplain Bean} when one of its * properties changes. If that property is the one that is configured by * this <tt>Configurator</tt>, we call {@linkplain #refresh(Object)} with * the new value as a parameter, so it is graphically reflected. * <p> * We always check if the dependency of this <tt>Configurator</tt> is * satisfied, i. e. we disable/enable it, depending on the state of the * property it depends on. * * @param evt * encapsulates the property change */ public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(property.getName())) { refresh((T) evt.getNewValue()); } setEnabled(isDependencySatisfied()); } /** * Applies the given value, which means that the property that this * Configurator configures is set to the value. * * @param value * the value to be applied */ protected void applyValue(T value) { bean.setValue(property, value); } /** * Returns the current value of the underlying property, or the non-null * {@linkplain #getNullValue()} if the property has the value <tt>null</tt>. * * @return the current value of the underlying property, or the non-null * {@linkplain #getNullValue()} if the property has the value * <tt>null</tt> */ @SuppressWarnings("unchecked") protected T getValue() { T value = (T) getBean().getValue(getProperty().getName()); return value != null ? value : getNullValue(); } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); _setEnabled(enabled); for (Component comp : getComponents()) { comp.setEnabled(enabled); } } @Override public void setToolTipText(String tooltip) { super.setToolTipText(tooltip); for (Component comp : getComponents()) { ((JComponent) comp).setToolTipText(tooltip); } } public void actionPerformed(final ActionEvent e) { // invoke this later to assert that all component models are in // the appropriate state (reflecting the action) - we then apply the // value to the underlying bean SwingUtilities.invokeLater(new Runnable() { public void run() { _actionPerformed(e); } }); } protected Adjustable getAdjustable() { return property.getWriteMethod().getAnnotation(Adjustable.class); } /** * Change the underlying bean such that the it is consistent with the value * displayed by this Configurator. * * @param evt */ protected abstract void _actionPerformed(ActionEvent evt); protected abstract void _setEnabled(boolean enabled); // /** // * Enable or disable the components belonging to this Configurator. // * // * @param on // */ // protected abstract void _setEnabled (boolean on); }