git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@268473 13f79535-47bb-0310-9956-ffa450edef68master
@@ -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("<<null editor>>"); | |||
comp = new JLabel("<No editor available.>"); | |||
((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. | |||
* <p> | |||
* 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. | |||
* <p> | |||
* 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. | |||
* <p> | |||
* 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. | |||
* <p> | |||
* 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); | |||
} | |||
@@ -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 | |||
* <http://www.apache.org/>. | |||
*/ | |||
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. | |||
* <p> | |||
* 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 = "<null>"; | |||
} | |||
// 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. | |||
* <p> Returns null if the value can't be expressed | |||
* as an editable string. | |||
* <p> 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; | |||
} | |||
} | |||
} | |||
} | |||
} | |||