diff --git a/src/antidote/org/apache/tools/ant/gui/customizer/DynamicCustomizer.java b/src/antidote/org/apache/tools/ant/gui/customizer/DynamicCustomizer.java index 80af93c9d..b374ceb06 100644 --- a/src/antidote/org/apache/tools/ant/gui/customizer/DynamicCustomizer.java +++ b/src/antidote/org/apache/tools/ant/gui/customizer/DynamicCustomizer.java @@ -59,9 +59,7 @@ import java.beans.*; import javax.swing.*; import java.util.*; import java.io.File; -import java.awt.GridBagLayout; -import java.awt.GridBagConstraints; -import java.awt.Component; +import java.awt.*; /** * Widget for dynamically constructing a property editor based on the @@ -70,7 +68,7 @@ import java.awt.Component; * @version $Revision$ * @author Simeon Fitch */ -public class DynamicCustomizer extends JPanel implements Customizer { +public class DynamicCustomizer extends JPanel implements Customizer, Scrollable { static { PropertyEditorManager.registerEditor( String.class, StringPropertyEditor.class); @@ -88,6 +86,8 @@ public class DynamicCustomizer extends JPanel implements Customizer { Properties.class, PropertiesPropertyEditor.class); PropertyEditorManager.registerEditor( File.class, FilePropertyEditor.class); + PropertyEditorManager.registerEditor( + Object.class, ObjectPropertyEditor.class); } /** Property name that PropertyDescriptors can save in their property @@ -109,7 +109,7 @@ public class DynamicCustomizer extends JPanel implements Customizer { private boolean _readOnly = false; /** List of property change listeners interested when the bean * being edited has been changed. */ - private List _changeListeners = new LinkedList(); + private java.util.List _changeListeners = new LinkedList(); /** Flag to trun off event propogation. */ private boolean _squelchChangeEvents = false; @@ -152,7 +152,7 @@ public class DynamicCustomizer extends JPanel implements Customizer { // Lookup the editor. PropertyEditor editor = getEditorForProperty(props[i]); - if(editor == null) continue; + // Add a listener to the editor so we know when to update // the bean's fields. editor.addPropertyChangeListener(_eListener); @@ -161,7 +161,8 @@ public class DynamicCustomizer extends JPanel implements Customizer { // that makes use of the "paintable" capability of the editor. Component comp = editor.getCustomEditor(); if(comp == null) { - comp = new JLabel("<>"); + comp = new JLabel(""); + ((JLabel)comp).setBorder(BorderFactory.createEtchedBorder()); } // See if it is a read-only property. If so, then just @@ -183,7 +184,6 @@ public class DynamicCustomizer extends JPanel implements Customizer { } } - // Add the label and fields. add(label, gbc.forLabel()); add(comp, gbc.forField()); @@ -272,6 +272,11 @@ public class DynamicCustomizer extends JPanel implements Customizer { } } + // In the worse case we resort to the generic editor for Object types. + if(retval == null) { + retval = PropertyEditorManager.findEditor(Object.class); + } + return retval; } @@ -316,6 +321,111 @@ public class DynamicCustomizer extends JPanel implements Customizer { } } + /** + * Returns the preferred size of the viewport for a view component. + * For example the preferredSize of a JList component is the size + * required to acommodate all of the cells in its list however the + * value of preferredScrollableViewportSize is the size required for + * JList.getVisibleRowCount() rows. A component without any properties + * that would effect the viewport size should just return + * getPreferredSize() here. + * + * @return The preferredSize of a JViewport whose view is this Scrollable. + * @see JViewport#getPreferredSize + */ + public Dimension getPreferredScrollableViewportSize() { + Dimension size = getPreferredSize(); + Dimension retval = new Dimension(); + retval.width = size.width > 600 ? 600 : size.width; + retval.height = size.height > 400 ? 400 : size.height; + return retval; + } + + + /** + * Components that display logical rows or columns should compute + * the scroll increment that will completely expose one new row + * or column, depending on the value of orientation. Ideally, + * components should handle a partially exposed row or column by + * returning the distance required to completely expose the item. + *

+ * Scrolling containers, like JScrollPane, will use this method + * each time the user requests a unit scroll. + * + * @param visibleRect The view area visible within the viewport + * @param orientation Either SwingConstants.VERTICAL or + * SwingConstants.HORIZONTAL. + * @param direction Less than zero to scroll up/left, + * greater than zero for down/right. + * @return The "unit" increment for scrolling in the specified direction + * @see JScrollBar#setUnitIncrement + */ + public int getScrollableUnitIncrement(Rectangle visibleRect, + int orientation, int direction) { + return 1; + } + + + /** + * Components that display logical rows or columns should compute + * the scroll increment that will completely expose one block + * of rows or columns, depending on the value of orientation. + *

+ * Scrolling containers, like JScrollPane, will use this method + * each time the user requests a block scroll. + * + * @param visibleRect The view area visible within the viewport + * @param orientation Either SwingConstants.VERTICAL or + * SwingConstants.HORIZONTAL. + * @param direction Less than zero to scroll up/left, + * greater than zero for down/right. + * @return The "block" increment for scrolling in the specified direction. + * @see JScrollBar#setBlockIncrement + */ + public int getScrollableBlockIncrement(Rectangle visibleRect, + int orientation, int direction) { + return orientation == SwingConstants.VERTICAL ? + visibleRect.height / 2 : visibleRect.width / 2; + } + + + /** + * Return true if a viewport should always force the width of this + * Scrollable to match the width of the viewport. For example a noraml + * text view that supported line wrapping would return true here, since it + * would be undesirable for wrapped lines to disappear beyond the right + * edge of the viewport. Note that returning true for a Scrollable + * whose ancestor is a JScrollPane effectively disables horizontal + * scrolling. + *

+ * Scrolling containers, like JViewport, will use this method each + * time they are validated. + * + * @return True if a viewport should force the Scrollables + * width to match its own. + */ + public boolean getScrollableTracksViewportWidth() { + return true; + } + + /** + * Return true if a viewport should always force the height of this + * Scrollable to match the height of the viewport. For example a + * columnar text view that flowed text in left to right columns + * could effectively disable vertical scrolling by returning + * true here. + *

+ * Scrolling containers, like JViewport, will use this method each + * time they are validated. + * + * @return True if a viewport should force the Scrollables + * height to match its own. + */ + public boolean getScrollableTracksViewportHeight() { + return false; + } + + /** Class for receiving change events from the PropertyEditor objects. */ private class EditorChangeListener implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent e) { @@ -354,7 +464,7 @@ public class DynamicCustomizer extends JPanel implements Customizer { Integer i2 = (Integer) p2.getValue(SORT_ORDER); if(i1 == null && i2 == null) { - return 0; + return p1.getName().compareTo(p2.getName()); } else if(i1 != null) { return i1.compareTo(i2); @@ -365,21 +475,26 @@ public class DynamicCustomizer extends JPanel implements Customizer { } } + + /*----------------------------------------------------------------------*/ + /** Class for testing this. */ - private static class TestClass { - private String _String = null; - private String[] _StringArray = null; - private int _int = 0; - private Integer _Integer = null; - private double _double = 0; - private Double _Double = null; - private Properties _Properties = null; - private File _File = null; - - public void setString(String string) { + private static class TestClass implements Cloneable { + private String _String = "This string is my name."; + private String[] _StringArray = { "one", "two", "three" }; + private int _int = Integer.MIN_VALUE; + private Integer _Integer = new Integer(Integer.MAX_VALUE); + private double _double = Double.MIN_VALUE; + private Double _Double = new Double(Double.MAX_VALUE); + private Properties _Properties = System.getProperties(); + private File _File = new File("/"); + private Object _Object = new Font("Monospaced", Font.PLAIN, 12); + private JButton _button = new JButton("I'm a button!"); + + public void setName(String string) { _String = string; } - public String getString() { + public String getName() { return _String; } @@ -431,6 +546,31 @@ public class DynamicCustomizer extends JPanel implements Customizer { public File getFile() { return _File; } + + public void setButton(JButton button) { + _button = button; + } + + public JButton getButton() { + return _button; + } + + public void setObject(Object o) { + _Object = o; + } + + public Object getObject() { + return _Object; + } + + public Object clone() { + try { + return super.clone(); + } + catch(CloneNotSupportedException ex) { + return null; + } + } } @@ -442,12 +582,22 @@ public class DynamicCustomizer extends JPanel implements Customizer { public static void main(String[] args) { try { - Class c = args.length > 0 ? Class.forName(args[0]) : TestClass.class; - JFrame f = new JFrame(c.getName()); - DynamicCustomizer custom = - new DynamicCustomizer(c); - custom.setObject(c.newInstance()); - f.getContentPane().add(custom); + Class type = null; + Object instance = null; + if(args.length > 0) { + type = Class.forName(args[0]); + instance = type.newInstance(); + } + else { + type = TestClass.class; + instance = new TestClass(); + ((TestClass)instance).setObject(new TestClass()); + } + + JFrame f = new JFrame(type.getName()); + DynamicCustomizer custom = new DynamicCustomizer(type); + custom.setObject(instance); + f.getContentPane().add(new JScrollPane(custom)); f.pack(); f.setVisible(true); } diff --git a/src/antidote/org/apache/tools/ant/gui/customizer/ObjectPropertyEditor.java b/src/antidote/org/apache/tools/ant/gui/customizer/ObjectPropertyEditor.java new file mode 100644 index 000000000..4b6965da8 --- /dev/null +++ b/src/antidote/org/apache/tools/ant/gui/customizer/ObjectPropertyEditor.java @@ -0,0 +1,313 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. 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. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Ant", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR + * ITS 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ +package org.apache.tools.ant.gui.customizer; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionListener; +import java.awt.event.ActionEvent; +import java.lang.reflect.Method; +import javax.swing.border.BevelBorder; + +/** + * Custom property editor for generic Object types. Useful for + * complex objects where using the DynamicCustomizer may be useful. + * + * @version $Revision$ + * @author Simeon Fitch + */ +public class ObjectPropertyEditor extends AbstractPropertyEditor { + /** Area for typing in the file name. */ + private JTextField _widget = null; + /** Container for the editor. */ + private JPanel _container = null; + /** The current object value. */ + private Object _value = null; + /** The editing button. */ + private JButton _button = null; + /** Flag to indicate that cancellation of editing is supported. */ + private boolean _supportCancel = true; + /** Original value. Only used if _supportCancel is true. */ + private Object _original = null; + + /** + * Default ctor. + * + */ + public ObjectPropertyEditor() { + _container = new JPanel(new BorderLayout()); + + _widget = new JTextField(25); + _widget.setEditable(false); + _widget.addFocusListener(new FocusHandler(this)); + _widget.setBorder( + BorderFactory.createBevelBorder(BevelBorder.LOWERED)); + + _container.add(_widget, BorderLayout.CENTER); + + _button = new JButton("Edit..."); + _button.addActionListener(new ActionHandler()); + _container.add(_button, BorderLayout.EAST); + } + + /** + * Get the child editing component. Uses JComponent so we can have tool + * tips, etc. + * + * @return Child editing component. + */ + protected Component getChild() { + return _container; + } + + + /** + * This method is intended for use when generating Java code to set + * the value of the property. It should return a fragment of Java code + * that can be used to initialize a variable with the current property + * value. + *

+ * Example results are "2", "new Color(127,127,34)", "Color.orange", etc. + * + * @return A fragment of Java code representing an initializer for the + * current value. + */ + public String getJavaInitializationString() { + return null; + } + + /** + * Set (or change) the object that is to be edited. Builtin types such + * as "int" must be wrapped as the corresponding object type such as + * "java.lang.Integer". + * + * @param value The new target object to be edited. Note that this + * object should not be modified by the PropertyEditor, rather + * the PropertyEditor should create a new object to hold any + * modified value. + * @exception IllegalArgumentException thrown if value can't be cloned. + */ + public void setValue(Object value) { + + if(_supportCancel && value != _value) { + try { + _value = makeClone(value); + } + catch(CloneNotSupportedException ex){ + // If cloning doesn't work then we can't support a "cancel" + // option on the editing dialog. + _supportCancel = false; + } + _original = value; + } + + _value = value; + + _button.setEnabled(_value != null); + + _widget.setText(getAsString(_value)); + } + + /** + * Convert the given value into some appropriate string. NB: This method + * can be continually improved to be made more and more smart over time. + * + * @param value Value to convert. + * @return String value to display. + */ + private String getAsString(Object value) { + String retval = null; + if(value == null) { + retval = ""; + } + + // We try to be smart by querying for various, logical string + // representation of the value. + if(retval == null) { + try { + Method m = value.getClass().getMethod("getName", null); + retval = (String) m.invoke(value, null); + } + catch(Exception ex) { + } + } + if(retval == null) { + try { + Method m = value.getClass().getMethod("getLabel", null); + retval = (String) m.invoke(value, null); + } + catch(Exception ex) { + } + + } + if(retval == null) { + try { + Method m = value.getClass().getMethod("getText", null); + retval = (String) m.invoke(value, null); + } + catch(Exception ex) { + } + } + + if(retval == null) { + retval = value.toString(); + } + + if(retval.length() > 256) { + retval = retval.substring(0, 253) + "..."; + } + + return retval; + } + + /** + * Attampt to make a clone of the given value. + * + * @param value Value to clone. + * @return Cloned value, or null if value given was null. + * @exception IllegalArgumentException thrown if value can't be cloned. + */ + private Object makeClone(Object value) throws CloneNotSupportedException { + Object retval = null; + if(value != null) { + try { + Method m = value.getClass().getMethod("clone", null); + retval = m.invoke(value, null); + } + catch(Throwable ex) { + throw new CloneNotSupportedException( + "This editor only supports types that have publically " + + "accessible clone() methods.\n" + + value.getClass().getName() + + " does not have such a method."); + } + } + return retval; + } + + /** + * @return The value of the property. Builtin types + * such as "int" will be wrapped as the corresponding + * object type such as "java.lang.Integer". */ + public Object getValue() { + return _value; + } + + /** + * Set the property value by parsing a given String. May raise + * java.lang.IllegalArgumentException if either the String is + * badly formatted or if this kind of property can't be expressed + * as text. + * @param text The string to be parsed. + */ + public void setAsText(String text) throws IllegalArgumentException { + throw new IllegalArgumentException("String conversion not supported."); + } + + /** + * @return The property value as a human editable string. + *

Returns null if the value can't be expressed + * as an editable string. + *

If a non-null value is returned, then the PropertyEditor should + * be prepared to parse that string back in setAsText(). + */ + public String getAsText() { + return null; + } + + /** Handler for presses of the edit button. */ + private class ActionHandler implements ActionListener { + public void actionPerformed(ActionEvent e) { + if(_value == null) return; + Class type = _value.getClass(); + DynamicCustomizer c = new DynamicCustomizer(type); + c.setObject(_value); + + int returnVal = JOptionPane.OK_OPTION; + if(_supportCancel) { + returnVal = JOptionPane.showConfirmDialog( + getChild(), new JScrollPane(c), "Editing...", + JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); + } + else { + JOptionPane.showMessageDialog( + getChild(), new JScrollPane(c), "Editing...", + JOptionPane.PLAIN_MESSAGE); + returnVal = JOptionPane.OK_OPTION; + } + + if(returnVal == JOptionPane.OK_OPTION) { + Object oldValue = _original; + Object newValue = _value; + + setValue(newValue); + firePropertyChange(oldValue, newValue); + } + else { + try { + _value = makeClone(_original); + } + catch(CloneNotSupportedException ex) { + // XXX log me. Shouldn't have gotten here as + // the test for cloneability should have already been done. + ex.printStackTrace(); + _supportCancel = false; + } + } + } + } + +} + +