diff options
| author | Benjamin Culkin <scorpress@gmail.com> | 2024-05-20 17:58:16 -0400 |
|---|---|---|
| committer | Benjamin Culkin <scorpress@gmail.com> | 2024-05-20 17:58:16 -0400 |
| commit | 40a9d99496e098562f090fb7ffce9e749011b131 (patch) | |
| tree | 437df24d65470582e943e494a52db8ed65a881ae /projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy | |
| parent | ff072dfe782f6f22123cd4ba050828d35c0d0fbd (diff) | |
Formatting pass
Diffstat (limited to 'projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy')
60 files changed, 18040 insertions, 21696 deletions
diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ActionAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ActionAssociation.java index cc3af69..ae3fa50 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ActionAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ActionAssociation.java @@ -31,305 +31,228 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* ActionAssociation binds any ActionEvent broadcaster -* (typically Buttons and the like) to a display group. -* Actions are invoked on all selected objects in the -* display group bound to the action aspect. -* Bindings are: -* <ul> -* <li>action: a method to be invoked on selected objects. -* If the argument aspect is bound, the method must take -* one argument. Otherwise, the method must take no arguments.</li> -* <li>argument: the attribute of the selected object(s) (possibly -* from a different display group) that will be used as an argument -* to the action method</li> -* <li>enabled: a boolean property that determines whether -* the controlled component is enabled</li> -* <li>visible: a boolean property that determines whether -* the controlled component is visible</li> -* </ul> -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class ActionAssociation extends EOAssociation - implements ActionListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - ActionAspect, ArgumentAspect, EnabledAspect, VisibleAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "target" - } ); - - static NSSelector addActionListener = - new NSSelector( "addActionListener", - new Class[] { ActionListener.class } ); - static NSSelector removeActionListener = - new NSSelector( "removeActionListener", - new Class[] { ActionListener.class } ); + * ActionAssociation binds any ActionEvent broadcaster (typically Buttons and + * the like) to a display group. Actions are invoked on all selected objects in + * the display group bound to the action aspect. Bindings are: + * <ul> + * <li>action: a method to be invoked on selected objects. If the argument + * aspect is bound, the method must take one argument. Otherwise, the method + * must take no arguments.</li> + * <li>argument: the attribute of the selected object(s) (possibly from a + * different display group) that will be used as an argument to the action + * method</li> + * <li>enabled: a boolean property that determines whether the controlled + * component is enabled</li> + * <li>visible: a boolean property that determines whether the controlled + * component is visible</li> + * </ul> + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class ActionAssociation extends EOAssociation implements ActionListener { + static final NSArray aspects = new NSArray( + new Object[] { ActionAspect, ArgumentAspect, EnabledAspect, VisibleAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature, + AttributeToOneAspectSignature, AttributeToOneAspectSignature, AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "target" }); - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - */ - public ActionAssociation ( Object anObject ) - { - super( anObject ); - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect. <ul> - * <li>"A" attribute: the aspect can be bound to - * an attribute.</li> - * <li>"1" to-one: the aspect can be bound to a - * property that returns a single object.</li> - * <li>"M" to-one: the aspect can be bound to a - * property that returns multiple objects.</li> - * </ul> - * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return - ( addActionListener.implementedByObject( anObject ) ) - && ( removeActionListener.implementedByObject( anObject ) ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. - */ - public static String primaryAspect () - { - return ActionAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Establishes a connection between this association - * and the controlled object. Subclasses should begin - * listening for events from their controlled object here. - */ - public void establishConnection () - { - try - { - addActionListener.invoke( object(), this ); - } - catch ( Exception exc ) - { - throw new WotonomyException( "EOActionAssociation: " + - "could not add action listener to object:" + object() ); - } - super.establishConnection(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - try - { - removeActionListener.invoke( object(), this ); + static NSSelector addActionListener = new NSSelector("addActionListener", new Class[] { ActionListener.class }); + static NSSelector removeActionListener = new NSSelector("removeActionListener", + new Class[] { ActionListener.class }); + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. + */ + public ActionAssociation(Object anObject) { + super(anObject); + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + * <ul> + * <li>"A" attribute: the aspect can be bound to an attribute.</li> + * <li>"1" to-one: the aspect can be bound to a property that returns a single + * object.</li> + * <li>"M" to-one: the aspect can be bound to a property that returns multiple + * objects.</li> + * </ul> + * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return (addActionListener.implementedByObject(anObject)) + && (removeActionListener.implementedByObject(anObject)); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. + */ + public static String primaryAspect() { + return ActionAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Establishes a connection between this association and the controlled object. + * Subclasses should begin listening for events from their controlled object + * here. + */ + public void establishConnection() { + try { + addActionListener.invoke(object(), this); + } catch (Exception exc) { + throw new WotonomyException( + "EOActionAssociation: " + "could not add action listener to object:" + object()); } - catch ( Exception exc ) - { - throw new WotonomyException( "EOActionAssociation: " + - "could not add action listener to object:" + object() ); + super.establishConnection(); + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + try { + removeActionListener.invoke(object(), this); + } catch (Exception exc) { + throw new WotonomyException( + "EOActionAssociation: " + "could not add action listener to object:" + object()); } - super.breakConnection(); - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - * This implementation does nothing. - */ - public void subjectChanged () - { + super.breakConnection(); + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. This implementation does nothing. + */ + public void subjectChanged() { Object component = object(); EODisplayGroup displayGroup; String key; - - if ( component instanceof Component ) - { + + if (component instanceof Component) { // enabled aspect - displayGroup = displayGroupForAspect( EnabledAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( EnabledAspect ); - ((Component)component).setEnabled( - displayGroup.enabledToSetSelectedObjectValueForKey( key ) ); - Object value = - displayGroup.selectedObjectValueForKey( key ); - Boolean converted = null; - if ( value != null ) - { - converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - } - if ( converted == null ) converted = Boolean.FALSE; - if ( converted.booleanValue() != - ((Component)component).isEnabled() ) - { - ((Component)component).setEnabled( - converted.booleanValue() ); - } + displayGroup = displayGroupForAspect(EnabledAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(EnabledAspect); + ((Component) component).setEnabled(displayGroup.enabledToSetSelectedObjectValueForKey(key)); + Object value = displayGroup.selectedObjectValueForKey(key); + Boolean converted = null; + if (value != null) { + converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + } + if (converted == null) + converted = Boolean.FALSE; + if (converted.booleanValue() != ((Component) component).isEnabled()) { + ((Component) component).setEnabled(converted.booleanValue()); + } } - + // visible aspect - displayGroup = displayGroupForAspect( VisibleAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( VisibleAspect ); - Object value = - displayGroup.selectedObjectValueForKey( key ); - Boolean converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - if ( converted != null ) - { - if ( converted.booleanValue() != - ((Component)component).isVisible() ) - { - ((Component)component).setVisible( - converted.booleanValue() ); + displayGroup = displayGroupForAspect(VisibleAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(VisibleAspect); + Object value = displayGroup.selectedObjectValueForKey(key); + Boolean converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + if (converted != null) { + if (converted.booleanValue() != ((Component) component).isVisible()) { + ((Component) component).setVisible(converted.booleanValue()); } - } + } } } - } - - // interface ActionListener - - public void actionPerformed( ActionEvent evt ) - { + } + + // interface ActionListener + + public void actionPerformed(ActionEvent evt) { EODisplayGroup actionDisplayGroup = null; String actionKey = null; - + // action aspect - actionDisplayGroup = displayGroupForAspect( ActionAspect ); - if ( actionDisplayGroup != null ) - { - actionKey = displayGroupKeyForAspect( ActionAspect ); + actionDisplayGroup = displayGroupForAspect(ActionAspect); + if (actionDisplayGroup != null) { + actionKey = displayGroupKeyForAspect(ActionAspect); - //TODO: argument aspect not implemented + // TODO: argument aspect not implemented - try - { - - NSSelector selector = new NSSelector( actionKey ); - Enumeration e = - actionDisplayGroup.selectedObjects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - selector.invoke( e.nextElement() ); + try { + + NSSelector selector = new NSSelector(actionKey); + Enumeration e = actionDisplayGroup.selectedObjects().objectEnumerator(); + while (e.hasMoreElements()) { + selector.invoke(e.nextElement()); } - } - catch ( Exception exc ) - { - throw new WotonomyException( - "ActionAssociation: error invoking action: " + actionKey, exc ); + } catch (Exception exc) { + throw new WotonomyException("ActionAssociation: error invoking action: " + actionKey, exc); } } - } + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.4 2004/01/28 18:34:57 mpowers - * Better handling for enabling. - * Now respecting enabledToSetSelectedObjectValueForKey from display group. + * Revision 1.4 2004/01/28 18:34:57 mpowers Better handling for enabling. Now + * respecting enabledToSetSelectedObjectValueForKey from display group. * - * Revision 1.3 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.3 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.2 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.2 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.1.1.1 2000/12/21 15:48:28 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:48:28 mpowers Contributing wotonomy. * - * Revision 1.5 2000/12/20 16:25:40 michael - * Added log to all files. + * Revision 1.5 2000/12/20 16:25:40 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/AdjustableAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/AdjustableAssociation.java index 2dc7fec..4f90c79 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/AdjustableAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/AdjustableAssociation.java @@ -29,299 +29,234 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* AdjustableAssociation binds any Adjustable component to -* a display group. Components implementing the adjustable -* interface include: ScrollBar and JScrollBar. -* Bindings are: -* <ul> -* <li>value: a property convertable to/from a string</li> -* <li>enabled: a boolean property that determines whether -* the user can select the text in the field</li> -* </ul> -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class AdjustableAssociation extends EOAssociation - implements AdjustmentListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - ValueAspect, EnabledAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "value" - } ); - - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - */ - public AdjustableAssociation ( Object anObject ) - { - super( anObject ); - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect. <ul> - * <li>"A" attribute: the aspect can be bound to - * an attribute.</li> - * <li>"1" to-one: the aspect can be bound to a - * property that returns a single object.</li> - * <li>"M" to-one: the aspect can be bound to a - * property that returns multiple objects.</li> - * </ul> - * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return ( anObject instanceof Adjustable ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return ValueAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Establishes a connection between this association - * and the controlled object. This implementation - * attempts to add this class as an ActionListener - * and as a FocusListener to the specified object. - */ - public void establishConnection () - { - component().addAdjustmentListener( this ); - super.establishConnection(); - + * AdjustableAssociation binds any Adjustable component to a display group. + * Components implementing the adjustable interface include: ScrollBar and + * JScrollBar. Bindings are: + * <ul> + * <li>value: a property convertable to/from a string</li> + * <li>enabled: a boolean property that determines whether the user can select + * the text in the field</li> + * </ul> + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class AdjustableAssociation extends EOAssociation implements AdjustmentListener { + static final NSArray aspects = new NSArray(new Object[] { ValueAspect, EnabledAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature, + AttributeToOneAspectSignature, AttributeToOneAspectSignature, AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "value" }); + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. + */ + public AdjustableAssociation(Object anObject) { + super(anObject); + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + * <ul> + * <li>"A" attribute: the aspect can be bound to an attribute.</li> + * <li>"1" to-one: the aspect can be bound to a property that returns a single + * object.</li> + * <li>"M" to-one: the aspect can be bound to a property that returns multiple + * objects.</li> + * </ul> + * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return (anObject instanceof Adjustable); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return ValueAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Establishes a connection between this association and the controlled object. + * This implementation attempts to add this class as an ActionListener and as a + * FocusListener to the specified object. + */ + public void establishConnection() { + component().addAdjustmentListener(this); + super.establishConnection(); + // forces update from bindings subjectChanged(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - component().removeAdjustmentListener( this ); - super.breakConnection(); - } + } - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged () - { + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + component().removeAdjustmentListener(this); + super.breakConnection(); + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { Adjustable component = component(); EODisplayGroup displayGroup; String key; Object value; - + // value aspect - displayGroup = displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( ValueAspect ); - if ( component instanceof Component ) - { - ((Component)component).setEnabled( - displayGroup.enabledToSetSelectedObjectValueForKey( key ) ); - } - value = displayGroup.selectedObjectValueForKey( key ); - + displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(ValueAspect); + if (component instanceof Component) { + ((Component) component).setEnabled(displayGroup.enabledToSetSelectedObjectValueForKey(key)); + } + value = displayGroup.selectedObjectValueForKey(key); + // convert value to int - value = ValueConverter.convertObjectToClass( - value, Integer.class ); + value = ValueConverter.convertObjectToClass(value, Integer.class); int intValue; - if ( value == null ) - { + if (value == null) { intValue = 0; + } else { + intValue = ((Integer) value).intValue(); } - else - { - intValue = ((Integer)value).intValue(); - } - - if ( component.getValue() != intValue ) - { - component.setValue( intValue ); + + if (component.getValue() != intValue) { + component.setValue(intValue); } } // enabled aspect - displayGroup = displayGroupForAspect( EnabledAspect ); - key = displayGroupKeyForAspect( EnabledAspect ); - if ( ( ( displayGroup != null ) || ( key != null ) ) - && ( component instanceof Component ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { + displayGroup = displayGroupForAspect(EnabledAspect); + key = displayGroupKeyForAspect(EnabledAspect); + if (((displayGroup != null) || (key != null)) && (component instanceof Component)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { // treat bound key without display group as a value - value = key; + value = key; + } + Boolean converted = null; + if (value != null) { + converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + } + if (converted == null) + converted = Boolean.FALSE; + if (converted.booleanValue() != ((Component) component).isEnabled()) { + ((Component) component).setEnabled(converted.booleanValue()); } - Boolean converted = null; - if ( value != null ) - { - converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - } - if ( converted == null ) converted = Boolean.FALSE; - if ( converted.booleanValue() != ((Component)component).isEnabled() ) - { - ((Component)component).setEnabled( converted.booleanValue() ); - } } - } - - /** - * Forces this association to cause the object to - * stop editing and validate the user's input. - * @return false if there were problems validating, - * or true to continue. - */ - public boolean endEditing () - { + } + + /** + * Forces this association to cause the object to stop editing and validate the + * user's input. + * + * @return false if there were problems validating, or true to continue. + */ + public boolean endEditing() { return writeValueToDisplayGroup(); - } - + } + /** - * Writes the value currently in the component - * to the selected object in the display group - * bound to the value aspect. - * @return false if there were problems validating, - * or true to continue. - */ - protected boolean writeValueToDisplayGroup() - { - EODisplayGroup displayGroup = - displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - String key = displayGroupKeyForAspect( ValueAspect ); - Object value = new Integer( component().getValue() ); - return displayGroup.setSelectedObjectValue( value, key ); + * Writes the value currently in the component to the selected object in the + * display group bound to the value aspect. + * + * @return false if there were problems validating, or true to continue. + */ + protected boolean writeValueToDisplayGroup() { + EODisplayGroup displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + String key = displayGroupKeyForAspect(ValueAspect); + Object value = new Integer(component().getValue()); + return displayGroup.setSelectedObjectValue(value, key); } return false; } - // interface AdjustmentListener - + // interface AdjustmentListener + /** - * Updates object on action performed. - */ - public void adjustmentValueChanged(AdjustmentEvent e) - { + * Updates object on action performed. + */ + public void adjustmentValueChanged(AdjustmentEvent e) { writeValueToDisplayGroup(); } - - private Adjustable component() - { - return (Adjustable) object(); + + private Adjustable component() { + return (Adjustable) object(); } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:23 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:23 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.3 2004/01/28 18:34:57 mpowers - * Better handling for enabling. - * Now respecting enabledToSetSelectedObjectValueForKey from display group. + * Revision 1.3 2004/01/28 18:34:57 mpowers Better handling for enabling. Now + * respecting enabledToSetSelectedObjectValueForKey from display group. * - * Revision 1.2 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.2 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.1.1.1 2000/12/21 15:48:35 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:48:35 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:40 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:40 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ButtonAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ButtonAssociation.java index 38cf38b..5a1c85c 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ButtonAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ButtonAssociation.java @@ -33,412 +33,312 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* ButtonAssociation binds any component that uses a ButtonModel -* (all Swing button classes) to a display group. This association -* should be used to handle individual JRadioButtons and JCheckBoxes. -* Bindings are: -* <ul> -* <li>value: a boolean property that determines the -* selected state of the button model. This will set -* the value for radio buttons and check boxes.</li> -* <li>enabled: a boolean property that determines the -* enabled state of the button model.</li> -* <li>visible: a boolean property that determines the -* visible state of the button model.</li> -* </ul> -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class ButtonAssociation extends EOAssociation - implements ChangeListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - ValueAspect, EnabledAspect, VisibleAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "model.selected" - } ); - - static NSSelector getModel = - new NSSelector( "getModel", new Class[] {} ); - - protected ButtonModel buttonModel; - protected boolean lastKnownValue; - - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - * This implementation expects a ButtonModel or a class - * that has a "getModel" method that returns a ButtonModel. - */ - public ButtonAssociation ( Object anObject ) - { - super( anObject ); - - if ( anObject instanceof ButtonModel ) - { - buttonModel = (ButtonModel) anObject; - } - else - { - try - { - buttonModel = (ButtonModel) getModel.invoke( anObject ); - } - catch ( Exception exc ) - { - throw new WotonomyException( "EOButtonAssociation: " + - "could not retrieve a button model from object:" + anObject ); - } - } - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect. <ul> - * <li>"A" attribute: the aspect can be bound to - * an attribute.</li> - * <li>"1" to-one: the aspect can be bound to a - * property that returns a single object.</li> - * <li>"M" to-one: the aspect can be bound to a - * property that returns multiple objects.</li> - * </ul> - * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - * This implementation expects a ButtonModel or a class - * that has a "getModel" method that returns a ButtonModel. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return - ( anObject instanceof ButtonModel ) - || ( getModel.implementedByObject( anObject ) ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. - */ - public static String primaryAspect () - { - return ValueAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Establishes a connection between this association - * and the controlled object. Subclasses should begin - * listening for events from their controlled object here. - */ - public void establishConnection () - { - buttonModel.addChangeListener( this ); - super.establishConnection(); - subjectChanged(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - buttonModel.removeChangeListener( this ); - super.breakConnection(); - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - * This implementation does nothing. - */ - public void subjectChanged () - { - Object component = object(); - EODisplayGroup displayGroup; - String key; - - // value aspect - displayGroup = displayGroupForAspect( ValueAspect ); - - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( ValueAspect ); - if ( component instanceof Component ) - { - ((Component)component).setEnabled( - displayGroup.enabledToSetSelectedObjectValueForKey( key ) ); - } - - Object value; - if ( displayGroup.selectedObjects().size() > 1 ) - { - // if there're more than one object selected, set - // the value to blank for all of them. - Object previousValue; - - Iterator indexIterator = displayGroup.selectionIndexes(). - iterator(); - - // get value for the first selected object. - int initialIndex = ( (Integer)indexIterator.next() ).intValue(); - previousValue = displayGroup.valueForObjectAtIndex( - initialIndex, key ); - value = null; - - // go through the rest of the selected objects, compare each - // value with the previous one. continue comparing if two - // values are equal, break the while loop if they're different. - // the final value will be the common value of all selected objects - // if there is one, or be blank if there is not. - while ( indexIterator.hasNext() ) - { - int index = ( (Integer)indexIterator.next() ).intValue(); - Object currentValue = displayGroup.valueForObjectAtIndex( - index, key ); - if ( currentValue != null && !currentValue.equals( previousValue ) ) - { - value = null; - break; - } - else - { - // currentValue is the same as the previous one - value = currentValue; - } - - } // end while - - } - else // displayGroup has only one object - { - value = - displayGroup.selectedObjectValueForKey( key ); - } // end checking size of displayGroup - - buttonModel.setArmed( false ); - buttonModel.setPressed( false ); - - if ( value != null ) - { - Boolean converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - if ( converted != null ) - { - lastKnownValue = converted.booleanValue(); - if ( converted.booleanValue() != - buttonModel.isSelected() ) - { - buttonModel.removeChangeListener( this ); - buttonModel.setSelected( - converted.booleanValue() ); - buttonModel.addChangeListener( this ); - } - } // end checking converted == null - } - else - { - buttonModel.setArmed( true ); - buttonModel.setPressed( true ); - } - } - - // enabled aspect - displayGroup = displayGroupForAspect( EnabledAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( EnabledAspect ); - Object value = - displayGroup.selectedObjectValueForKey( key ); - Boolean converted = null; - if ( value != null ) - { - converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - } - if ( converted == null ) converted = Boolean.FALSE; - if ( converted.booleanValue() != - buttonModel.isEnabled() ) - { - buttonModel.removeChangeListener( this ); - buttonModel.setEnabled( - converted.booleanValue() ); - buttonModel.addChangeListener( this ); - } - } - - // visible aspect - displayGroup = displayGroupForAspect( VisibleAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( VisibleAspect ); - Object value = - displayGroup.selectedObjectValueForKey( key ); - Boolean converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - if ( converted != null ) - { - if ( converted.booleanValue() != - ((Component)component).isVisible() ) - { - ((Component)component).setVisible( - converted.booleanValue() ); - } - } - } - } - - /** - * Writes the value currently in the component - * to the selected object in the display group - * bound to the value aspect. - * @return false if there were problems validating, - * or true to continue. - */ - protected boolean writeValueToDisplayGroup() - { - EODisplayGroup displayGroup = - displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - boolean returnValue = true; - String key = displayGroupKeyForAspect( ValueAspect ); - Object value = new Boolean( buttonModel.isSelected() ); - - Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); - while ( selectedIterator.hasNext() ) - { - int index = ( (Integer)selectedIterator.next() ).intValue(); - - if ( !displayGroup.setValueForObjectAtIndex( value, index, key ) ) - { - returnValue = false; - } - } - return returnValue; - - } - return false; - } - // interface ChangeListener - - public void stateChanged(ChangeEvent e) - { - if ( buttonModel.isSelected() != lastKnownValue ) - { - lastKnownValue = buttonModel.isSelected(); - writeValueToDisplayGroup(); - } - } + * ButtonAssociation binds any component that uses a ButtonModel (all Swing + * button classes) to a display group. This association should be used to handle + * individual JRadioButtons and JCheckBoxes. Bindings are: + * <ul> + * <li>value: a boolean property that determines the selected state of the + * button model. This will set the value for radio buttons and check boxes.</li> + * <li>enabled: a boolean property that determines the enabled state of the + * button model.</li> + * <li>visible: a boolean property that determines the visible state of the + * button model.</li> + * </ul> + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class ButtonAssociation extends EOAssociation implements ChangeListener { + static final NSArray aspects = new NSArray(new Object[] { ValueAspect, EnabledAspect, VisibleAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature, + AttributeToOneAspectSignature, AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "model.selected" }); + + static NSSelector getModel = new NSSelector("getModel", new Class[] {}); + + protected ButtonModel buttonModel; + protected boolean lastKnownValue; + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. This implementation expects a ButtonModel or a + * class that has a "getModel" method that returns a ButtonModel. + */ + public ButtonAssociation(Object anObject) { + super(anObject); + + if (anObject instanceof ButtonModel) { + buttonModel = (ButtonModel) anObject; + } else { + try { + buttonModel = (ButtonModel) getModel.invoke(anObject); + } catch (Exception exc) { + throw new WotonomyException( + "EOButtonAssociation: " + "could not retrieve a button model from object:" + anObject); + } + } + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + * <ul> + * <li>"A" attribute: the aspect can be bound to an attribute.</li> + * <li>"1" to-one: the aspect can be bound to a property that returns a single + * object.</li> + * <li>"M" to-one: the aspect can be bound to a property that returns multiple + * objects.</li> + * </ul> + * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. This + * implementation expects a ButtonModel or a class that has a "getModel" method + * that returns a ButtonModel. + */ + public static boolean isUsableWithObject(Object anObject) { + return (anObject instanceof ButtonModel) || (getModel.implementedByObject(anObject)); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. + */ + public static String primaryAspect() { + return ValueAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Establishes a connection between this association and the controlled object. + * Subclasses should begin listening for events from their controlled object + * here. + */ + public void establishConnection() { + buttonModel.addChangeListener(this); + super.establishConnection(); + subjectChanged(); + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + buttonModel.removeChangeListener(this); + super.breakConnection(); + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. This implementation does nothing. + */ + public void subjectChanged() { + Object component = object(); + EODisplayGroup displayGroup; + String key; + + // value aspect + displayGroup = displayGroupForAspect(ValueAspect); + + if (displayGroup != null) { + key = displayGroupKeyForAspect(ValueAspect); + if (component instanceof Component) { + ((Component) component).setEnabled(displayGroup.enabledToSetSelectedObjectValueForKey(key)); + } + + Object value; + if (displayGroup.selectedObjects().size() > 1) { + // if there're more than one object selected, set + // the value to blank for all of them. + Object previousValue; + + Iterator indexIterator = displayGroup.selectionIndexes().iterator(); + + // get value for the first selected object. + int initialIndex = ((Integer) indexIterator.next()).intValue(); + previousValue = displayGroup.valueForObjectAtIndex(initialIndex, key); + value = null; + + // go through the rest of the selected objects, compare each + // value with the previous one. continue comparing if two + // values are equal, break the while loop if they're different. + // the final value will be the common value of all selected objects + // if there is one, or be blank if there is not. + while (indexIterator.hasNext()) { + int index = ((Integer) indexIterator.next()).intValue(); + Object currentValue = displayGroup.valueForObjectAtIndex(index, key); + if (currentValue != null && !currentValue.equals(previousValue)) { + value = null; + break; + } else { + // currentValue is the same as the previous one + value = currentValue; + } + + } // end while + + } else // displayGroup has only one object + { + value = displayGroup.selectedObjectValueForKey(key); + } // end checking size of displayGroup + + buttonModel.setArmed(false); + buttonModel.setPressed(false); + + if (value != null) { + Boolean converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + if (converted != null) { + lastKnownValue = converted.booleanValue(); + if (converted.booleanValue() != buttonModel.isSelected()) { + buttonModel.removeChangeListener(this); + buttonModel.setSelected(converted.booleanValue()); + buttonModel.addChangeListener(this); + } + } // end checking converted == null + } else { + buttonModel.setArmed(true); + buttonModel.setPressed(true); + } + } + + // enabled aspect + displayGroup = displayGroupForAspect(EnabledAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(EnabledAspect); + Object value = displayGroup.selectedObjectValueForKey(key); + Boolean converted = null; + if (value != null) { + converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + } + if (converted == null) + converted = Boolean.FALSE; + if (converted.booleanValue() != buttonModel.isEnabled()) { + buttonModel.removeChangeListener(this); + buttonModel.setEnabled(converted.booleanValue()); + buttonModel.addChangeListener(this); + } + } + + // visible aspect + displayGroup = displayGroupForAspect(VisibleAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(VisibleAspect); + Object value = displayGroup.selectedObjectValueForKey(key); + Boolean converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + if (converted != null) { + if (converted.booleanValue() != ((Component) component).isVisible()) { + ((Component) component).setVisible(converted.booleanValue()); + } + } + } + } + + /** + * Writes the value currently in the component to the selected object in the + * display group bound to the value aspect. + * + * @return false if there were problems validating, or true to continue. + */ + protected boolean writeValueToDisplayGroup() { + EODisplayGroup displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + boolean returnValue = true; + String key = displayGroupKeyForAspect(ValueAspect); + Object value = new Boolean(buttonModel.isSelected()); + + Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); + while (selectedIterator.hasNext()) { + int index = ((Integer) selectedIterator.next()).intValue(); + + if (!displayGroup.setValueForObjectAtIndex(value, index, key)) { + returnValue = false; + } + } + return returnValue; + + } + return false; + } + // interface ChangeListener + + public void stateChanged(ChangeEvent e) { + if (buttonModel.isSelected() != lastKnownValue) { + lastKnownValue = buttonModel.isSelected(); + writeValueToDisplayGroup(); + } + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.8 2004/01/28 18:34:57 mpowers - * Better handling for enabling. - * Now respecting enabledToSetSelectedObjectValueForKey from display group. + * Revision 1.8 2004/01/28 18:34:57 mpowers Better handling for enabling. Now + * respecting enabledToSetSelectedObjectValueForKey from display group. * - * Revision 1.7 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.7 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.6 2001/07/30 16:32:55 mpowers - * Implemented support for bulk-editing. Detail associations will now - * apply changes to all selected objects. + * Revision 1.6 2001/07/30 16:32:55 mpowers Implemented support for + * bulk-editing. Detail associations will now apply changes to all selected + * objects. * - * Revision 1.5 2001/06/29 22:28:19 mpowers - * Tabs to spaces. + * Revision 1.5 2001/06/29 22:28:19 mpowers Tabs to spaces. * - * Revision 1.4 2001/06/29 22:17:31 mpowers - * Now updating the component on establishConnection. + * Revision 1.4 2001/06/29 22:17:31 mpowers Now updating the component on + * establishConnection. * - * Revision 1.3 2001/02/27 02:10:38 mpowers - * No longer updating values to the display group if the value - * has not changed. + * Revision 1.3 2001/02/27 02:10:38 mpowers No longer updating values to the + * display group if the value has not changed. * - * Revision 1.2 2001/02/21 20:33:01 mpowers - * Fixed bug with change listener. + * Revision 1.2 2001/02/21 20:33:01 mpowers Fixed bug with change listener. * - * Revision 1.1.1.1 2000/12/21 15:48:38 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:48:38 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:40 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:40 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ComboBoxAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ComboBoxAssociation.java index d0a087e..e6cd0aa 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ComboBoxAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ComboBoxAssociation.java @@ -37,664 +37,529 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* ComboBoxAssociation binds JComboBoxes to -* display groups. Bindings are: -* <ul> -* -* <li>value: optional - a property of the selected object in the -* display group that will be bind to the item the user -* selects or the text that the user enters in the field. -* If the value aspect is not bound, then the combo box works -* as an "overview" assocation and changing the selected object -* in the combobox will modify the selection of the display group -* bound to the objects or the titles display groups (in that order).</li> -* -* <li>titles: optional - a property of the objects in the bound -* display group that will appear in the list. If the -* objects aspect is not bound, this property is also -* used to populate the value binding. If the titles -* aspect itself is not bound, the items already in the -* combobox will be used to update the value in the -* selected object in the bound display group.</li> -* -* <li>objects: optional - if specified, when the user -* selects a title in the list, the property of the -* object at the corresponding index of the bound display -* group will be used to populate the value binding. -* If the objects aspect is used with an editable combo -* box, any value entered that does not match one of the -* titles in the list will produce a null value.</li> -* -* <li>enabled: optional - a boolean property of the -* selected object in the display group that determines whether -* the user can edit the field.</li> -* -* </ul> -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class ComboBoxAssociation extends EOAssociation - implements FocusListener, ActionListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - TitlesAspect, ValueAspect, - ObjectsAspect, EnabledAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "text" - } ); - - private boolean wasNull; - private static final String EMPTY_STRING = ""; - - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - */ - public ComboBoxAssociation ( Object anObject ) - { - super( anObject ); - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect. <ul> - * <li>"A" attribute: the aspect can be bound to - * an attribute.</li> - * <li>"1" to-one: the aspect can be bound to a - * property that returns a single object.</li> - * <li>"M" to-one: the aspect can be bound to a - * property that returns multiple objects.</li> - * </ul> - * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return ( anObject instanceof JComboBox ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return ValueAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Establishes a connection between this association - * and the controlled object. Subclasses should begin - * listening for events from their controlled object here. - */ - public void establishConnection () - { - super.establishConnection(); - - // prepopulate titles - EODisplayGroup displayGroup = - displayGroupForAspect( TitlesAspect ); - if ( displayGroup != null ) - { - String key = displayGroupKeyForAspect( TitlesAspect ); - populateTitles( displayGroup, key ); - } - addAsListener(); - subjectChanged(); - } - - protected void addAsListener() - { - component().addActionListener( this ); - component().addFocusListener( this ); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - removeAsListener(); - super.breakConnection(); - } - - protected void removeAsListener() - { - component().removeActionListener( this ); - component().removeFocusListener( this ); - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged () - { - removeAsListener(); - - JComboBox component = component(); - EODisplayGroup displayGroup; - String key; - - // titles aspect - displayGroup = displayGroupForAspect( TitlesAspect ); - if ( displayGroup != null ) - { - ComboBoxModel model = component().getModel(); - // if first time, or if backing group has changed + * ComboBoxAssociation binds JComboBoxes to display groups. Bindings are: + * <ul> + * + * <li>value: optional - a property of the selected object in the display group + * that will be bind to the item the user selects or the text that the user + * enters in the field. If the value aspect is not bound, then the combo box + * works as an "overview" assocation and changing the selected object in the + * combobox will modify the selection of the display group bound to the objects + * or the titles display groups (in that order).</li> + * + * <li>titles: optional - a property of the objects in the bound display group + * that will appear in the list. If the objects aspect is not bound, this + * property is also used to populate the value binding. If the titles aspect + * itself is not bound, the items already in the combobox will be used to update + * the value in the selected object in the bound display group.</li> + * + * <li>objects: optional - if specified, when the user selects a title in the + * list, the property of the object at the corresponding index of the bound + * display group will be used to populate the value binding. If the objects + * aspect is used with an editable combo box, any value entered that does not + * match one of the titles in the list will produce a null value.</li> + * + * <li>enabled: optional - a boolean property of the selected object in the + * display group that determines whether the user can edit the field.</li> + * + * </ul> + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class ComboBoxAssociation extends EOAssociation implements FocusListener, ActionListener { + static final NSArray aspects = new NSArray( + new Object[] { TitlesAspect, ValueAspect, ObjectsAspect, EnabledAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature, + AttributeToOneAspectSignature, AttributeToOneAspectSignature, AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "text" }); + + private boolean wasNull; + private static final String EMPTY_STRING = ""; + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. + */ + public ComboBoxAssociation(Object anObject) { + super(anObject); + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + * <ul> + * <li>"A" attribute: the aspect can be bound to an attribute.</li> + * <li>"1" to-one: the aspect can be bound to a property that returns a single + * object.</li> + * <li>"M" to-one: the aspect can be bound to a property that returns multiple + * objects.</li> + * </ul> + * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return (anObject instanceof JComboBox); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return ValueAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Establishes a connection between this association and the controlled object. + * Subclasses should begin listening for events from their controlled object + * here. + */ + public void establishConnection() { + super.establishConnection(); + + // prepopulate titles + EODisplayGroup displayGroup = displayGroupForAspect(TitlesAspect); + if (displayGroup != null) { + String key = displayGroupKeyForAspect(TitlesAspect); + populateTitles(displayGroup, key); + } + addAsListener(); + subjectChanged(); + } + + protected void addAsListener() { + component().addActionListener(this); + component().addFocusListener(this); + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + removeAsListener(); + super.breakConnection(); + } + + protected void removeAsListener() { + component().removeActionListener(this); + component().removeFocusListener(this); + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { + removeAsListener(); + + JComboBox component = component(); + EODisplayGroup displayGroup; + String key; + + // titles aspect + displayGroup = displayGroupForAspect(TitlesAspect); + if (displayGroup != null) { + ComboBoxModel model = component().getModel(); + // if first time, or if backing group has changed // if ( ( ! ( model instanceof ComboBoxAssociationModel ) ) // || ( displayGroup.contentsChanged() ) ) // { - key = displayGroupKeyForAspect( TitlesAspect ); - populateTitles( displayGroup, key ); + key = displayGroupKeyForAspect(TitlesAspect); + populateTitles(displayGroup, key); // } - } - - // value aspect - displayGroup = displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( ValueAspect ); - component.setEnabled( - displayGroup.enabledToSetSelectedObjectValueForKey( key ) ); - //Object value = displayGroup.selectedObjectValueForKey( key ); - Object value; - - - if ( displayGroup.selectedObjects().size() > 1 ) - { - - Object previousValue; - - Iterator indexIterator = displayGroup.selectionIndexes(). - iterator(); - - // get value for the first selected object. - int initialIndex = ( (Integer)indexIterator.next() ).intValue(); - previousValue = displayGroup.valueForObjectAtIndex( - initialIndex, key ); - value = null; - - // go through the rest of the selected objects, compare each - // value with the previous one. continue comparing if two - // values are equal, break the while loop if they're different. - // the final value will be the common value of all selected objects - // if there is one, or be blank if there is not. - while ( indexIterator.hasNext() ) - { - int index = ( (Integer)indexIterator.next() ).intValue(); - Object currentValue = displayGroup.valueForObjectAtIndex( - index, key ); - - if ( currentValue != null && !currentValue.equals( previousValue ) ) - { - value = null; - break; - } - else - { - // currentValue is the same as the previous one - value = currentValue; - } - - } // end while - - } else { - // if there's only one object selected. - value = displayGroup.selectedObjectValueForKey( key ); - } // end checking the size of selected objects in displayGroup - - - // objects aspect - EODisplayGroup objectsDisplayGroup = - displayGroupForAspect( ObjectsAspect ); - if ( ( objectsDisplayGroup != null ) && ( value != null ) ) - { - String objectKey = displayGroupKeyForAspect( ObjectsAspect ); - Object match; - int index = NSArray.NotFound; - int count = objectsDisplayGroup.displayedObjects().count(); - for ( int i = 0; i < count; i++ ) - { - match = objectsDisplayGroup.valueForObjectAtIndex( i, objectKey ); - if ( value.equals( match ) ) - { - index = i; - } - } - if ( index == NSArray.NotFound ) - { - if ( component.getSelectedItem() != null ) - { - component.setSelectedItem( null ); - } - } - else - { - if ( component.getSelectedIndex() != index ) - { - component.setSelectedIndex( index ); - } - } - } - else - { - component.setSelectedItem( value ); - } - } - else // values aspect not bound - { - // use objects group if specified - EODisplayGroup sourceGroup = - displayGroupForAspect( ObjectsAspect ); - if ( sourceGroup == null ) - { - // fall back on titles group - sourceGroup = displayGroupForAspect( TitlesAspect ); - } - - if ( sourceGroup != null ) - { - List selection = sourceGroup.selectionIndexes(); - if ( ( selection != null ) && ( selection.size() > 0 ) ) - { - component.setSelectedIndex( ((Integer)selection.get(0)).intValue() ); - } - else - { - // the combo box model decides what to do with this value - component.setSelectedItem( null ); - } - } - } - - // enabled aspect - displayGroup = displayGroupForAspect( EnabledAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( EnabledAspect ); - Object value = - displayGroup.selectedObjectValueForKey( key ); - Boolean converted = null; - if ( value != null ) - { - converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - } - if ( converted == null ) converted = Boolean.FALSE; - if ( converted.booleanValue() != component.isEnabled() ) - { - component.setEnabled( converted.booleanValue() ); - } - } - - addAsListener(); - } - - /** - * Called to repopulate the title list from the - * specified display group. - */ - protected void populateTitles( - EODisplayGroup displayGroup, String key ) - { - component().setModel( - new ComboBoxAssociationModel( displayGroup, key ) ); - } - - /** - * Forces this association to cause the object to - * stop editing and validate the user's input. - * @return false if there were problems validating, - * or true to continue. - */ - public boolean endEditing () - { - return writeValueToDisplayGroup(); - } - - /** - * Writes the value currently in the component - * to the selected object in the display group - * bound to the value aspect. - * @return false if there were problems validating, - * or true to continue. - */ - protected boolean writeValueToDisplayGroup() - { - JComboBox component = component(); - EODisplayGroup displayGroup; - String key; - - // selected title aspect - displayGroup = displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( ValueAspect ); - Object value = null; - - // selected object aspect, if any - EODisplayGroup objectsGroup = - displayGroupForAspect( ObjectsAspect ); - if ( objectsGroup != null ) - { - try - { - String objectKey = displayGroupKeyForAspect( ObjectsAspect ); - int index = component.getSelectedIndex(); - if ( index != -1 ) - { - value = objectsGroup - .valueForObjectAtIndex( index, objectKey ); - } - else // selected index is -1 - { - // the combo box is probably editable, - // so there is no corresponding object. - value = null; - } - } - catch ( NullPointerException npe ) - { - // catches NPE on line 436 of JComboBox.java: - // this is a common developer error - throw new WotonomyException( "ComboBoxAssociation: " + - "The object in the VALUE property may not have been found in the " + - "objects in the TITLES group.", npe ); - } - } - else // just use the selected item - { - value = component.getSelectedItem(); - } - - boolean returnValue = true; - if ( displayGroup.selectedObjects().size() == 1 ) - { // displayGroup has only one object - // only set value if changed - Object existingValue = displayGroup.selectedObjectValueForKey( key ); - if ( value == existingValue ) return true; - if ( ( existingValue != null ) && ( existingValue.equals( value ) ) ) return true; - - // value has changed: update the value. - return displayGroup.setSelectedObjectValue( value, key ); - } - else if ( displayGroup.selectedObjects().size() > 1 ) - { - // displayGroup has more than one object - Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); - while ( selectedIterator.hasNext() ) - { - int index = ( (Integer)selectedIterator.next() ).intValue(); - - if ( !displayGroup.setValueForObjectAtIndex( value, index, key ) ) - { - returnValue = false; - } - } - return returnValue; - - } // end checking size of displayGroup - - } - else // values aspect not bound - { - // use objects group if specified - EODisplayGroup sourceGroup = - displayGroupForAspect( ObjectsAspect ); - if ( sourceGroup == null ) - { - // fall back on titles group - sourceGroup = displayGroupForAspect( TitlesAspect ); - } - - if ( sourceGroup != null ) - { - int index = component.getSelectedIndex(); - if ( index != -1 ) - { - sourceGroup.setSelectionIndexes( new NSArray( new Integer( index ) ) ); - } - else - { - sourceGroup.setSelectedObject( null ); - } - return true; - } - } - - return false; - } - - // interface ActionListener - - /** - * Updates object on action performed. - */ - public void actionPerformed( ActionEvent evt ) - { - writeValueToDisplayGroup(); - } - - // interface FocusListener - - /** - * Notifies of beginning of edit. - */ - public void focusGained(FocusEvent evt) - { - Object o; - EODisplayGroup displayGroup; - Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidBeginEditing( this ); - } - } - } - - /** - * Updates object on focus lost and notifies of end of edit. - */ - public void focusLost(FocusEvent evt) - { - if ( component().isEditable() ) - { - if ( endEditing() ) - { - Object o; - EODisplayGroup displayGroup; - Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidEndEditing( this ); - } - } - } - } - } - - // convenience - - private JComboBox component() - { - return (JComboBox) object(); - } - - /** - * Used as the data model for the controlled combo box. - */ - private class ComboBoxAssociationModel extends AbstractListModel - implements ComboBoxModel - { - EODisplayGroup displayGroup; - String key; - Object selectedItem; - - ComboBoxAssociationModel( - EODisplayGroup aDisplayGroup, String aKey ) - { - displayGroup = aDisplayGroup; - key = aKey; - selectedItem = null; - } - - public Object getElementAt(int index) - { - return displayGroup.valueForObjectAtIndex( index, key ); - } - - public int getSize() - { - return displayGroup.displayedObjects().count(); - } - - public void setSelectedItem(Object anItem) - { //System.out.println( "setSelectedItem: " + anItem ); - selectedItem = anItem; - - // must do this to notify an editable combo, - // otherwise the wrong value appears. - fireContentsChanged( this, -1, -1 ); - } - - public Object getSelectedItem() - { //System.out.println( "getSelectedItem: " + selectedItem ); - return selectedItem; - } - } + } + + // value aspect + displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(ValueAspect); + component.setEnabled(displayGroup.enabledToSetSelectedObjectValueForKey(key)); + // Object value = displayGroup.selectedObjectValueForKey( key ); + Object value; + + if (displayGroup.selectedObjects().size() > 1) { + + Object previousValue; + + Iterator indexIterator = displayGroup.selectionIndexes().iterator(); + + // get value for the first selected object. + int initialIndex = ((Integer) indexIterator.next()).intValue(); + previousValue = displayGroup.valueForObjectAtIndex(initialIndex, key); + value = null; + + // go through the rest of the selected objects, compare each + // value with the previous one. continue comparing if two + // values are equal, break the while loop if they're different. + // the final value will be the common value of all selected objects + // if there is one, or be blank if there is not. + while (indexIterator.hasNext()) { + int index = ((Integer) indexIterator.next()).intValue(); + Object currentValue = displayGroup.valueForObjectAtIndex(index, key); + + if (currentValue != null && !currentValue.equals(previousValue)) { + value = null; + break; + } else { + // currentValue is the same as the previous one + value = currentValue; + } + + } // end while + + } else { + // if there's only one object selected. + value = displayGroup.selectedObjectValueForKey(key); + } // end checking the size of selected objects in displayGroup + + // objects aspect + EODisplayGroup objectsDisplayGroup = displayGroupForAspect(ObjectsAspect); + if ((objectsDisplayGroup != null) && (value != null)) { + String objectKey = displayGroupKeyForAspect(ObjectsAspect); + Object match; + int index = NSArray.NotFound; + int count = objectsDisplayGroup.displayedObjects().count(); + for (int i = 0; i < count; i++) { + match = objectsDisplayGroup.valueForObjectAtIndex(i, objectKey); + if (value.equals(match)) { + index = i; + } + } + if (index == NSArray.NotFound) { + if (component.getSelectedItem() != null) { + component.setSelectedItem(null); + } + } else { + if (component.getSelectedIndex() != index) { + component.setSelectedIndex(index); + } + } + } else { + component.setSelectedItem(value); + } + } else // values aspect not bound + { + // use objects group if specified + EODisplayGroup sourceGroup = displayGroupForAspect(ObjectsAspect); + if (sourceGroup == null) { + // fall back on titles group + sourceGroup = displayGroupForAspect(TitlesAspect); + } + + if (sourceGroup != null) { + List selection = sourceGroup.selectionIndexes(); + if ((selection != null) && (selection.size() > 0)) { + component.setSelectedIndex(((Integer) selection.get(0)).intValue()); + } else { + // the combo box model decides what to do with this value + component.setSelectedItem(null); + } + } + } + + // enabled aspect + displayGroup = displayGroupForAspect(EnabledAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(EnabledAspect); + Object value = displayGroup.selectedObjectValueForKey(key); + Boolean converted = null; + if (value != null) { + converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + } + if (converted == null) + converted = Boolean.FALSE; + if (converted.booleanValue() != component.isEnabled()) { + component.setEnabled(converted.booleanValue()); + } + } + + addAsListener(); + } + + /** + * Called to repopulate the title list from the specified display group. + */ + protected void populateTitles(EODisplayGroup displayGroup, String key) { + component().setModel(new ComboBoxAssociationModel(displayGroup, key)); + } + + /** + * Forces this association to cause the object to stop editing and validate the + * user's input. + * + * @return false if there were problems validating, or true to continue. + */ + public boolean endEditing() { + return writeValueToDisplayGroup(); + } + + /** + * Writes the value currently in the component to the selected object in the + * display group bound to the value aspect. + * + * @return false if there were problems validating, or true to continue. + */ + protected boolean writeValueToDisplayGroup() { + JComboBox component = component(); + EODisplayGroup displayGroup; + String key; + + // selected title aspect + displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(ValueAspect); + Object value = null; + + // selected object aspect, if any + EODisplayGroup objectsGroup = displayGroupForAspect(ObjectsAspect); + if (objectsGroup != null) { + try { + String objectKey = displayGroupKeyForAspect(ObjectsAspect); + int index = component.getSelectedIndex(); + if (index != -1) { + value = objectsGroup.valueForObjectAtIndex(index, objectKey); + } else // selected index is -1 + { + // the combo box is probably editable, + // so there is no corresponding object. + value = null; + } + } catch (NullPointerException npe) { + // catches NPE on line 436 of JComboBox.java: + // this is a common developer error + throw new WotonomyException( + "ComboBoxAssociation: " + "The object in the VALUE property may not have been found in the " + + "objects in the TITLES group.", + npe); + } + } else // just use the selected item + { + value = component.getSelectedItem(); + } + + boolean returnValue = true; + if (displayGroup.selectedObjects().size() == 1) { // displayGroup has only one object + // only set value if changed + Object existingValue = displayGroup.selectedObjectValueForKey(key); + if (value == existingValue) + return true; + if ((existingValue != null) && (existingValue.equals(value))) + return true; + + // value has changed: update the value. + return displayGroup.setSelectedObjectValue(value, key); + } else if (displayGroup.selectedObjects().size() > 1) { + // displayGroup has more than one object + Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); + while (selectedIterator.hasNext()) { + int index = ((Integer) selectedIterator.next()).intValue(); + + if (!displayGroup.setValueForObjectAtIndex(value, index, key)) { + returnValue = false; + } + } + return returnValue; + + } // end checking size of displayGroup + + } else // values aspect not bound + { + // use objects group if specified + EODisplayGroup sourceGroup = displayGroupForAspect(ObjectsAspect); + if (sourceGroup == null) { + // fall back on titles group + sourceGroup = displayGroupForAspect(TitlesAspect); + } + + if (sourceGroup != null) { + int index = component.getSelectedIndex(); + if (index != -1) { + sourceGroup.setSelectionIndexes(new NSArray(new Integer(index))); + } else { + sourceGroup.setSelectedObject(null); + } + return true; + } + } + + return false; + } + + // interface ActionListener + + /** + * Updates object on action performed. + */ + public void actionPerformed(ActionEvent evt) { + writeValueToDisplayGroup(); + } + + // interface FocusListener + + /** + * Notifies of beginning of edit. + */ + public void focusGained(FocusEvent evt) { + Object o; + EODisplayGroup displayGroup; + Enumeration e = aspects().objectEnumerator(); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidBeginEditing(this); + } + } + } + + /** + * Updates object on focus lost and notifies of end of edit. + */ + public void focusLost(FocusEvent evt) { + if (component().isEditable()) { + if (endEditing()) { + Object o; + EODisplayGroup displayGroup; + Enumeration e = aspects().objectEnumerator(); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidEndEditing(this); + } + } + } + } + } + + // convenience + + private JComboBox component() { + return (JComboBox) object(); + } + + /** + * Used as the data model for the controlled combo box. + */ + private class ComboBoxAssociationModel extends AbstractListModel implements ComboBoxModel { + EODisplayGroup displayGroup; + String key; + Object selectedItem; + + ComboBoxAssociationModel(EODisplayGroup aDisplayGroup, String aKey) { + displayGroup = aDisplayGroup; + key = aKey; + selectedItem = null; + } + + public Object getElementAt(int index) { + return displayGroup.valueForObjectAtIndex(index, key); + } + + public int getSize() { + return displayGroup.displayedObjects().count(); + } + + public void setSelectedItem(Object anItem) { // System.out.println( "setSelectedItem: " + anItem ); + selectedItem = anItem; + + // must do this to notify an editable combo, + // otherwise the wrong value appears. + fireContentsChanged(this, -1, -1); + } + + public Object getSelectedItem() { // System.out.println( "getSelectedItem: " + selectedItem ); + return selectedItem; + } + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.13 2004/01/28 18:34:57 mpowers - * Better handling for enabling. - * Now respecting enabledToSetSelectedObjectValueForKey from display group. + * Revision 1.13 2004/01/28 18:34:57 mpowers Better handling for enabling. Now + * respecting enabledToSetSelectedObjectValueForKey from display group. * - * Revision 1.12 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.12 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.11 2001/07/30 16:32:55 mpowers - * Implemented support for bulk-editing. Detail associations will now - * apply changes to all selected objects. + * Revision 1.11 2001/07/30 16:32:55 mpowers Implemented support for + * bulk-editing. Detail associations will now apply changes to all selected + * objects. * - * Revision 1.10 2001/07/23 20:17:56 mpowers - * Now works as an overview association if the values aspect is not bound. + * Revision 1.10 2001/07/23 20:17:56 mpowers Now works as an overview + * association if the values aspect is not bound. * - * Revision 1.9 2001/06/30 14:57:29 mpowers - * Removed a println. + * Revision 1.9 2001/06/30 14:57:29 mpowers Removed a println. * - * Revision 1.8 2001/06/29 22:28:19 mpowers - * Tabs to spaces. + * Revision 1.8 2001/06/29 22:28:19 mpowers Tabs to spaces. * - * Revision 1.7 2001/06/29 22:17:31 mpowers - * Now updating the component on establishConnection. + * Revision 1.7 2001/06/29 22:17:31 mpowers Now updating the component on + * establishConnection. * - * Revision 1.6 2001/05/14 15:24:49 mpowers - * Only updating if change was made. Feels like I had fixed this here before. + * Revision 1.6 2001/05/14 15:24:49 mpowers Only updating if change was made. + * Feels like I had fixed this here before. * - * Revision 1.5 2001/04/09 21:41:08 mpowers - * Fixed a bug I thought that I had fixed before. + * Revision 1.5 2001/04/09 21:41:08 mpowers Fixed a bug I thought that I had + * fixed before. * - * Revision 1.4 2001/03/01 20:37:17 mpowers - * Updated docs to emphasize that titles aspect is optional. + * Revision 1.4 2001/03/01 20:37:17 mpowers Updated docs to emphasize that + * titles aspect is optional. * - * Revision 1.3 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.3 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.2 2001/01/10 17:01:08 mpowers - * Caught a common developer error. + * Revision 1.2 2001/01/10 17:01:08 mpowers Caught a common developer error. * - * Revision 1.1.1.1 2000/12/21 15:48:43 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:48:43 mpowers Contributing wotonomy. * - * Revision 1.8 2000/12/20 16:25:40 michael - * Added log to all files. + * Revision 1.8 2000/12/20 16:25:40 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DateAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DateAssociation.java index ba50879..c201ae3 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DateAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DateAssociation.java @@ -36,578 +36,430 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* DateAssociation binds any component that has a set and get Date methods and -* fire actions events when the date has been changed. Bindings are: -* <ul> -* <li>value: a property convertable to/from a date</li> -* <li>editable: a boolean property that determines whether -* the user can edit the date in the component</li> -* <li>enabled: a boolean property that determines whether -* the component is enabled or disabled</li> -* </ul> -* -* @author rob@yahoo.com -* @version $Revision: 904 $ -*/ -public class DateAssociation extends EOAssociation - implements ActionListener, FocusListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - ValueAspect, EnabledAspect, EditableAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "date", "enabled", "editable" - } ); - - private final static NSSelector getDate = - new NSSelector( "getDate" ); - private final static NSSelector setDate = - new NSSelector( "setDate", - new Class[] { Date.class } ); - private final static NSSelector addActionListener = - new NSSelector( "addActionListener", - new Class[] { ActionListener.class } ); - private final static NSSelector removeActionListener = - new NSSelector( "removeActionListener", - new Class[] { ActionListener.class } ); - private final static NSSelector addFocusListener = - new NSSelector( "addFocusListener", - new Class[] { FocusListener.class } ); - private final static NSSelector removeFocusListener = - new NSSelector( "removeFocusListener", - new Class[] { FocusListener.class } ); - private final static NSSelector setEditable = - new NSSelector( "setEditable", - new Class[] { boolean.class } ); + * DateAssociation binds any component that has a set and get Date methods and + * fire actions events when the date has been changed. Bindings are: + * <ul> + * <li>value: a property convertable to/from a date</li> + * <li>editable: a boolean property that determines whether the user can edit + * the date in the component</li> + * <li>enabled: a boolean property that determines whether the component is + * enabled or disabled</li> + * </ul> + * + * @author rob@yahoo.com + * @version $Revision: 904 $ + */ +public class DateAssociation extends EOAssociation implements ActionListener, FocusListener { + static final NSArray aspects = new NSArray(new Object[] { ValueAspect, EnabledAspect, EditableAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature, + AttributeToOneAspectSignature, AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "date", "enabled", "editable" }); + + private final static NSSelector getDate = new NSSelector("getDate"); + private final static NSSelector setDate = new NSSelector("setDate", new Class[] { Date.class }); + private final static NSSelector addActionListener = new NSSelector("addActionListener", + new Class[] { ActionListener.class }); + private final static NSSelector removeActionListener = new NSSelector("removeActionListener", + new Class[] { ActionListener.class }); + private final static NSSelector addFocusListener = new NSSelector("addFocusListener", + new Class[] { FocusListener.class }); + private final static NSSelector removeFocusListener = new NSSelector("removeFocusListener", + new Class[] { FocusListener.class }); + private final static NSSelector setEditable = new NSSelector("setEditable", new Class[] { boolean.class }); // dirty handling private boolean needsUpdate; - private Date nullValue; // placeholder for null value flag - - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - */ - public DateAssociation ( Object anObject ) - { - super( anObject ); + private Date nullValue; // placeholder for null value flag + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. + */ + public DateAssociation(Object anObject) { + super(anObject); needsUpdate = false; - nullValue = null; - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect. <ul> - * <li>"A" attribute: the aspect can be bound to - * an attribute.</li> - * <li>"1" to-one: the aspect can be bound to a - * property that returns a single object.</li> - * <li>"M" to-one: the aspect can be bound to a - * property that returns multiple objects.</li> - * </ul> - * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return setDate.implementedByObject( anObject ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return ValueAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Establishes a connection between this association - * and the controlled object. This implementation - * attempts to add this class as an ActionListener - * and Focus Listener to the specified object. - */ - public void establishConnection () - { + nullValue = null; + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + * <ul> + * <li>"A" attribute: the aspect can be bound to an attribute.</li> + * <li>"1" to-one: the aspect can be bound to a property that returns a single + * object.</li> + * <li>"M" to-one: the aspect can be bound to a property that returns multiple + * objects.</li> + * </ul> + * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return setDate.implementedByObject(anObject); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return ValueAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Establishes a connection between this association and the controlled object. + * This implementation attempts to add this class as an ActionListener and Focus + * Listener to the specified object. + */ + public void establishConnection() { Object component = object(); - try - { - if ( addActionListener.implementedByObject( component ) ) - { - addActionListener.invoke( component, this ); + try { + if (addActionListener.implementedByObject(component)) { + addActionListener.invoke(component, this); } - if ( addFocusListener.implementedByObject( component ) ) - { - addFocusListener.invoke( component, this ); + if (addFocusListener.implementedByObject(component)) { + addFocusListener.invoke(component, this); } - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while establishing connection", exc ); + } catch (Exception exc) { + throw new WotonomyException("Error while establishing connection", exc); } - super.establishConnection(); + super.establishConnection(); // forces update from bindings subjectChanged(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { Object component = object(); - try - { - if ( removeActionListener.implementedByObject( component ) ) - { - removeActionListener.invoke( component, this ); + try { + if (removeActionListener.implementedByObject(component)) { + removeActionListener.invoke(component, this); } - if ( removeFocusListener.implementedByObject( component ) ) - { - removeFocusListener.invoke( component, this ); + if (removeFocusListener.implementedByObject(component)) { + removeFocusListener.invoke(component, this); } + } catch (Exception exc) { + throw new WotonomyException("Error while breaking connection", exc); } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while breaking connection", exc ); - } - super.breakConnection(); - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged () - { + super.breakConnection(); + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { Object component = object(); EODisplayGroup displayGroup; String key; Object value; // value aspect - displayGroup = displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( ValueAspect ); - if ( component instanceof Component ) - { - ((Component)component).setEnabled( - displayGroup.enabledToSetSelectedObjectValueForKey( key ) ); - } - if ( displayGroup.selectedObjects().size() > 1 ) - { - // if there're more than one object selected, set - // the value to blank for all of them. - Object previousValue; - - Iterator indexIterator = displayGroup.selectionIndexes(). - iterator(); - - // get value for the first selected object. - int initialIndex = ( (Integer)indexIterator.next() ).intValue(); - previousValue = displayGroup.valueForObjectAtIndex( - initialIndex, key ); - value = null; - - // go through the rest of the selected objects, compare each - // value with the previous one. continue comparing if two - // values are equal, break the while loop if they're different. - // the final value will be the common value of all selected objects - // if there is one, or be blank if there is not. - while ( indexIterator.hasNext() ) - { - int index = ( (Integer)indexIterator.next() ).intValue(); - Object currentValue = displayGroup.valueForObjectAtIndex( - index, key ); - if ( currentValue != null && !currentValue.equals( previousValue ) ) - { - value = null; - break; - } - else - { - // currentValue is the same as the previous one - value = currentValue; - } - - } // end while - - } else { - - value = displayGroup.selectedObjectValueForKey( key ); - } // end checking size of displayGroup - - // convert value to date - try - { - Date dateValue = null; - // (Date) ValueConverter.convertObjectToClass( value, Date.class ); - - if ( value instanceof Date ) - { - dateValue = (Date) value; - } - - if ( ( dateValue == null ) && ( value instanceof Calendar ) ) - { - dateValue = ( ( Calendar )value ).getTime(); - } - - if ( dateValue == null ) - { - // current time (placeholder) - nullValue = new Date(); - dateValue = nullValue; - } - else - { - nullValue = null; - } - - if ( !dateValue.equals( getDate.invoke( component ) ) ) - { // No need to update if there is no change. - setDate.invoke( component, dateValue ); - needsUpdate = false; - } - - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while updating component connection", exc ); - } + displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(ValueAspect); + if (component instanceof Component) { + ((Component) component).setEnabled(displayGroup.enabledToSetSelectedObjectValueForKey(key)); + } + if (displayGroup.selectedObjects().size() > 1) { + // if there're more than one object selected, set + // the value to blank for all of them. + Object previousValue; + + Iterator indexIterator = displayGroup.selectionIndexes().iterator(); + + // get value for the first selected object. + int initialIndex = ((Integer) indexIterator.next()).intValue(); + previousValue = displayGroup.valueForObjectAtIndex(initialIndex, key); + value = null; + + // go through the rest of the selected objects, compare each + // value with the previous one. continue comparing if two + // values are equal, break the while loop if they're different. + // the final value will be the common value of all selected objects + // if there is one, or be blank if there is not. + while (indexIterator.hasNext()) { + int index = ((Integer) indexIterator.next()).intValue(); + Object currentValue = displayGroup.valueForObjectAtIndex(index, key); + if (currentValue != null && !currentValue.equals(previousValue)) { + value = null; + break; + } else { + // currentValue is the same as the previous one + value = currentValue; + } + + } // end while + + } else { + + value = displayGroup.selectedObjectValueForKey(key); + } // end checking size of displayGroup + + // convert value to date + try { + Date dateValue = null; + // (Date) ValueConverter.convertObjectToClass( value, Date.class ); + + if (value instanceof Date) { + dateValue = (Date) value; + } + + if ((dateValue == null) && (value instanceof Calendar)) { + dateValue = ((Calendar) value).getTime(); + } + + if (dateValue == null) { + // current time (placeholder) + nullValue = new Date(); + dateValue = nullValue; + } else { + nullValue = null; + } + + if (!dateValue.equals(getDate.invoke(component))) { // No need to update if there is no change. + setDate.invoke(component, dateValue); + needsUpdate = false; + } + + } catch (Exception exc) { + throw new WotonomyException("Error while updating component connection", exc); + } } // enabled aspect - displayGroup = displayGroupForAspect( EnabledAspect ); - key = displayGroupKeyForAspect( EnabledAspect ); - if ( ( ( displayGroup != null ) || ( key != null ) ) - && ( component instanceof Component ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { + displayGroup = displayGroupForAspect(EnabledAspect); + key = displayGroupKeyForAspect(EnabledAspect); + if (((displayGroup != null) || (key != null)) && (component instanceof Component)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { // treat bound key without display group as a value value = key; } - Boolean converted = null; - if ( value != null ) - { - converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - } - if ( converted == null ) converted = Boolean.FALSE; - if ( converted.booleanValue() != ((Component)component).isEnabled() ) - { - ((Component)component).setEnabled( converted.booleanValue() ); - } + Boolean converted = null; + if (value != null) { + converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + } + if (converted == null) + converted = Boolean.FALSE; + if (converted.booleanValue() != ((Component) component).isEnabled()) { + ((Component) component).setEnabled(converted.booleanValue()); + } } // editable aspect - displayGroup = displayGroupForAspect( EditableAspect ); - key = displayGroupKeyForAspect( EditableAspect ); - if ( ( ( displayGroup != null ) || ( key != null ) ) - && ( setEditable.implementedByObject( component ) ) ) - { - try - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { - // treat bound key without display group as a value - value = key; - } - Boolean converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - - if ( converted != null ) - { - setEditable.invoke( component, converted ); - } - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while updating component connection (editable aspect)", exc ); + displayGroup = displayGroupForAspect(EditableAspect); + key = displayGroupKeyForAspect(EditableAspect); + if (((displayGroup != null) || (key != null)) && (setEditable.implementedByObject(component))) { + try { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { + // treat bound key without display group as a value + value = key; + } + Boolean converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + + if (converted != null) { + setEditable.invoke(component, converted); + } + } catch (Exception exc) { + throw new WotonomyException("Error while updating component connection (editable aspect)", exc); } } - } - - /** - * Forces this association to cause the object to - * stop editing and validate the user's input. - * @return false if there were problems validating, - * or true to continue. - */ - public boolean endEditing () - { + } + + /** + * Forces this association to cause the object to stop editing and validate the + * user's input. + * + * @return false if there were problems validating, or true to continue. + */ + public boolean endEditing() { return writeValueToDisplayGroup(); - } + } /** - * Writes the value currently in the component - * to the selected object in the display group - * bound to the value aspect. - * @return false if there were problems validating, - * or true to continue. - */ - protected boolean writeValueToDisplayGroup() - { - if ( !needsUpdate ) return true; - - EODisplayGroup displayGroup = - displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - String key = displayGroupKeyForAspect( ValueAspect ); - Object component = object(); - Object value = null; - try - { - if ( getDate.implementedByObject( component ) ) - { - value = getDate.invoke( component ); - } - if ( nullValue != null ) - { - if ( nullValue.equals( value ) ) - { - value = null; - } - } - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error updating display group", exc ); - } - - needsUpdate = false; - - boolean returnValue = true; - Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); - while ( selectedIterator.hasNext() ) - { - int index = ( (Integer)selectedIterator.next() ).intValue(); - - if ( !displayGroup.setValueForObjectAtIndex( value, index, key ) ) - { - returnValue = false; - } - } - return returnValue; - - } - return false; + * Writes the value currently in the component to the selected object in the + * display group bound to the value aspect. + * + * @return false if there were problems validating, or true to continue. + */ + protected boolean writeValueToDisplayGroup() { + if (!needsUpdate) + return true; + + EODisplayGroup displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + String key = displayGroupKeyForAspect(ValueAspect); + Object component = object(); + Object value = null; + try { + if (getDate.implementedByObject(component)) { + value = getDate.invoke(component); + } + if (nullValue != null) { + if (nullValue.equals(value)) { + value = null; + } + } + } catch (Exception exc) { + throw new WotonomyException("Error updating display group", exc); + } + + needsUpdate = false; + + boolean returnValue = true; + Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); + while (selectedIterator.hasNext()) { + int index = ((Integer) selectedIterator.next()).intValue(); + + if (!displayGroup.setValueForObjectAtIndex(value, index, key)) { + returnValue = false; + } + } + return returnValue; + + } + return false; } - // interface ActionListener + // interface ActionListener /** - * Updates object on action performed. - */ - public void actionPerformed( ActionEvent evt ) - { - needsUpdate = true; - writeValueToDisplayGroup(); // TODO: Should we do this here or on focus lost? + * Updates object on action performed. + */ + public void actionPerformed(ActionEvent evt) { + needsUpdate = true; + writeValueToDisplayGroup(); // TODO: Should we do this here or on focus lost? } - // interface FocusListener + // interface FocusListener /** - * Notifies of beginning of edit. - */ - public void focusGained(FocusEvent evt) - { + * Notifies of beginning of edit. + */ + public void focusGained(FocusEvent evt) { Object o; EODisplayGroup displayGroup; - Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidBeginEditing( this ); + Enumeration e = aspects().objectEnumerator(); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidBeginEditing(this); } } - } + } /** - * Updates object on focus lost and notifies of end of edit. - */ - public void focusLost(FocusEvent evt) - { - if ( endEditing() ) - { + * Updates object on focus lost and notifies of end of edit. + */ + public void focusLost(FocusEvent evt) { + if (endEditing()) { Object o; EODisplayGroup displayGroup; Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidEndEditing( this ); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidEndEditing(this); } } - } - else - { + } else { // probably should notify of a validation error here, // but how to also handle actionPerformed without copying code? -/* - Object value = null; - try - { - if ( getText.implementedByObject( object() ) ) - { - value = getText.invoke( object() ); - } - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error updating display group", exc ); - } - - EODisplayGroup displayGroup = - displayGroupForAspect( ValueAspect ); - String key = displayGroupKeyForAspect( ValueAspect ); - if ( displayGroup != null ) - { - if ( displayGroup.associationFailedToValidateValue( - this, (String) value, key, object(), - "That format was not recognized." ) ) - { - new net.wotonomy.ui.swing.util.StackTraceInspector(); - } - if ( object() instanceof Component ) - { - ((Component)object()).requestFocus(); - } - } -*/ + /* + * Object value = null; try { if ( getText.implementedByObject( object() ) ) { + * value = getText.invoke( object() ); } } catch ( Exception exc ) { throw new + * WotonomyException( "Error updating display group", exc ); } + * + * EODisplayGroup displayGroup = displayGroupForAspect( ValueAspect ); String + * key = displayGroupKeyForAspect( ValueAspect ); if ( displayGroup != null ) { + * if ( displayGroup.associationFailedToValidateValue( this, (String) value, + * key, object(), "That format was not recognized." ) ) { new + * net.wotonomy.ui.swing.util.StackTraceInspector(); } if ( object() instanceof + * Component ) { ((Component)object()).requestFocus(); } } + */ } - } + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.7 2004/01/28 18:34:57 mpowers - * Better handling for enabling. - * Now respecting enabledToSetSelectedObjectValueForKey from display group. + * Revision 1.7 2004/01/28 18:34:57 mpowers Better handling for enabling. Now + * respecting enabledToSetSelectedObjectValueForKey from display group. * - * Revision 1.6 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.6 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.5 2001/07/30 16:32:55 mpowers - * Implemented support for bulk-editing. Detail associations will now - * apply changes to all selected objects. + * Revision 1.5 2001/07/30 16:32:55 mpowers Implemented support for + * bulk-editing. Detail associations will now apply changes to all selected + * objects. * - * Revision 1.4 2001/02/17 17:23:49 mpowers - * More changes to support compiling with jdk1.1 collections. + * Revision 1.4 2001/02/17 17:23:49 mpowers More changes to support compiling + * with jdk1.1 collections. * - * Revision 1.3 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.3 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.2 2001/01/17 16:25:26 mpowers - * Now catching null values from data object. + * Revision 1.2 2001/01/17 16:25:26 mpowers Now catching null values from data + * object. * - * Revision 1.1 2001/01/10 22:26:32 mpowers - * Contributing DateAssociation. + * Revision 1.1 2001/01/10 22:26:32 mpowers Contributing DateAssociation. * - * Revision 1.1 2001/01/10 21:30:27 rglista - * Initial checkin + * Revision 1.1 2001/01/10 21:30:27 rglista Initial checkin * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupActionAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupActionAssociation.java index 290480d..f891ede 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupActionAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupActionAssociation.java @@ -27,108 +27,82 @@ import net.wotonomy.foundation.internal.WotonomyException; import net.wotonomy.ui.EODisplayGroup; /** -* ActionAssociation binds any ActionEvent broadcaster -* (typically Buttons and the like) to a display group, -* but invokes actions directly on the bound display -* group rather than the selected objects. -* Bindings are: -* <ul> -* <li>action: a method to be invoked on the bound display group. -* If the argument aspect is bound, the method must take -* one argument. Otherwise, the method must take no arguments.</li> -* <li>argument: the attribute of the selected object(s) (possibly -* from a different display group) that will be used as an argument -* to the action method</li> -* <li>enabled: a boolean property that determines whether -* the controlled component is enabled</li> -* <li>visible: a boolean property that determines whether -* the controlled component is visible</li> -* </ul> -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class DisplayGroupActionAssociation extends ActionAssociation -{ - static final NSArray aspects = - new NSArray( new Object[] { - ActionAspect, ArgumentAspect, EnabledAspect, VisibleAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "target" - } ); - - static NSSelector addActionListener = - new NSSelector( "addActionListener", - new Class[] { ActionListener.class } ); - static NSSelector removeActionListener = - new NSSelector( "removeActionListener", - new Class[] { ActionListener.class } ); - - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - */ - public DisplayGroupActionAssociation ( Object anObject ) - { - super( anObject ); - } - - // interface ActionListener - - public void actionPerformed( ActionEvent evt ) - { + * ActionAssociation binds any ActionEvent broadcaster (typically Buttons and + * the like) to a display group, but invokes actions directly on the bound + * display group rather than the selected objects. Bindings are: + * <ul> + * <li>action: a method to be invoked on the bound display group. If the + * argument aspect is bound, the method must take one argument. Otherwise, the + * method must take no arguments.</li> + * <li>argument: the attribute of the selected object(s) (possibly from a + * different display group) that will be used as an argument to the action + * method</li> + * <li>enabled: a boolean property that determines whether the controlled + * component is enabled</li> + * <li>visible: a boolean property that determines whether the controlled + * component is visible</li> + * </ul> + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class DisplayGroupActionAssociation extends ActionAssociation { + static final NSArray aspects = new NSArray( + new Object[] { ActionAspect, ArgumentAspect, EnabledAspect, VisibleAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature, + AttributeToOneAspectSignature, AttributeToOneAspectSignature, AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "target" }); + + static NSSelector addActionListener = new NSSelector("addActionListener", new Class[] { ActionListener.class }); + static NSSelector removeActionListener = new NSSelector("removeActionListener", + new Class[] { ActionListener.class }); + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. + */ + public DisplayGroupActionAssociation(Object anObject) { + super(anObject); + } + + // interface ActionListener + + public void actionPerformed(ActionEvent evt) { EODisplayGroup actionDisplayGroup = null; String actionKey = null; - + // action aspect - actionDisplayGroup = displayGroupForAspect( ActionAspect ); - if ( actionDisplayGroup != null ) - { - actionKey = displayGroupKeyForAspect( ActionAspect ); + actionDisplayGroup = displayGroupForAspect(ActionAspect); + if (actionDisplayGroup != null) { + actionKey = displayGroupKeyForAspect(ActionAspect); - //TODO: argument aspect not implemented + // TODO: argument aspect not implemented - try - { - NSSelector.invoke( actionKey, actionDisplayGroup ); - } - catch ( Exception exc ) - { - throw new WotonomyException( "DisplayGroupActionAssociation: " - + "error invoking action: " + actionKey, exc ); + try { + NSSelector.invoke(actionKey, actionDisplayGroup); + } catch (Exception exc) { + throw new WotonomyException("DisplayGroupActionAssociation: " + "error invoking action: " + actionKey, + exc); } } } - + } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.2 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.2 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.1.1.1 2000/12/21 15:48:46 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:48:46 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:40 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:40 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupInspector.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupInspector.java index c8ecd36..d782252 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupInspector.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupInspector.java @@ -35,86 +35,75 @@ import net.wotonomy.ui.swing.util.ObjectInspector; import net.wotonomy.ui.swing.util.WindowUtilities; /** -* The DisplayGroupInspector displays a JFrame that -* shows allows you to view and manipulate a display group. -* -* @author michael@mpowers.net -* @version $Revision: 904 $ -*/ + * The DisplayGroupInspector displays a JFrame that shows allows you to view and + * manipulate a display group. + * + * @author michael@mpowers.net + * @version $Revision: 904 $ + */ -public class DisplayGroupInspector -{ - protected JList list; - protected EODisplayGroup displayGroup; - -/** -* Displays and manipulats the specified display group. -*/ - public DisplayGroupInspector( EODisplayGroup aDisplayGroup ) - { - displayGroup = aDisplayGroup; - - list = new JList(); - list.addMouseListener( new MouseAdapter() - { - public void mouseClicked( MouseEvent e ) - { - if ( e.getClickCount() == 2 ) - { - Object selection = displayGroup.selectedObject(); - if ( selection != null ) - { - new ObjectInspector( selection ); - } - } - } - } ); - - EOAssociation assoc = new ListAssociation( list ); - assoc.bindAspect( EOAssociation.TitlesAspect, displayGroup, "" ); - assoc.establishConnection(); - - initLayout(); - - } - - protected void initLayout() - { - JPanel panel = new JPanel(); - panel.setLayout( new BorderLayout() ); - panel.setBorder( new EmptyBorder( 10, 10, 10, 10 ) ); - - JScrollPane scrollPane = new JScrollPane( list ); - scrollPane.setPreferredSize( new Dimension( 200, 200 ) ); - panel.add( scrollPane, BorderLayout.CENTER ); - - JFrame window = new JFrame(); - window.setTitle( "Display Group Inspector" ); - window.getContentPane().add( panel ); - - window.pack(); - WindowUtilities.cascade( window ); - window.show(); - } +public class DisplayGroupInspector { + protected JList list; + protected EODisplayGroup displayGroup; + + /** + * Displays and manipulats the specified display group. + */ + public DisplayGroupInspector(EODisplayGroup aDisplayGroup) { + displayGroup = aDisplayGroup; + + list = new JList(); + list.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + Object selection = displayGroup.selectedObject(); + if (selection != null) { + new ObjectInspector(selection); + } + } + } + }); + + EOAssociation assoc = new ListAssociation(list); + assoc.bindAspect(EOAssociation.TitlesAspect, displayGroup, ""); + assoc.establishConnection(); + + initLayout(); + + } + + protected void initLayout() { + JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + panel.setBorder(new EmptyBorder(10, 10, 10, 10)); + + JScrollPane scrollPane = new JScrollPane(list); + scrollPane.setPreferredSize(new Dimension(200, 200)); + panel.add(scrollPane, BorderLayout.CENTER); + + JFrame window = new JFrame(); + window.setTitle("Display Group Inspector"); + window.getContentPane().add(panel); + + window.pack(); + WindowUtilities.cascade(window); + window.show(); + } } - + /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.3 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.3 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.2 2003/06/26 23:28:00 mpowers - * Added double click. + * Revision 1.2 2003/06/26 23:28:00 mpowers Added double click. * - * Revision 1.1 2001/05/29 19:57:47 mpowers - * Added some neglected files. + * Revision 1.1 2001/05/29 19:57:47 mpowers Added some neglected files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupNode.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupNode.java index 1756285..d530f36 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupNode.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupNode.java @@ -46,901 +46,727 @@ import net.wotonomy.ui.EODisplayGroup; import net.wotonomy.ui.swing.TreeModelAssociation.DelegatingTreeDataSource; /** -* DisplayGroupNodes are used as nodes in the -* TreeModelAssociation's implementation of TreeModel, -* and is tightly coupled with TreeModelAssociation -* and MasterDetailAssociation. <br><br> -* -* Even though it is no longer package access, -* don't rely on this class because we want to -* have the option of completely replacing this -* approach in the future. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ - abstract public class DisplayGroupNode - extends EODisplayGroup - { - protected TreeModelAssociation parentAssociation; - protected EODelayedObserver targetObserver; - protected NSMutableDictionary childNodes; - protected EODisplayGroup parentGroup; - protected Object target; - protected boolean isFetched; - protected boolean isFetchNeeded; - protected boolean useParentOrderings; - protected boolean useParentQualifier; - - /** - * Constructor for all nodes. - * Root node must have a null target. - */ - public DisplayGroupNode( - TreeModelAssociation aParentAssociation, - EODisplayGroup aParentGroup, - Object aTarget ) - { + * DisplayGroupNodes are used as nodes in the TreeModelAssociation's + * implementation of TreeModel, and is tightly coupled with TreeModelAssociation + * and MasterDetailAssociation. <br> + * <br> + * + * Even though it is no longer package access, don't rely on this class because + * we want to have the option of completely replacing this approach in the + * future. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +abstract public class DisplayGroupNode extends EODisplayGroup { + protected TreeModelAssociation parentAssociation; + protected EODelayedObserver targetObserver; + protected NSMutableDictionary childNodes; + protected EODisplayGroup parentGroup; + protected Object target; + protected boolean isFetched; + protected boolean isFetchNeeded; + protected boolean useParentOrderings; + protected boolean useParentQualifier; + + /** + * Constructor for all nodes. Root node must have a null target. + */ + public DisplayGroupNode(TreeModelAssociation aParentAssociation, EODisplayGroup aParentGroup, Object aTarget) { //new net.wotonomy.ui.swing.util.StackTraceInspector( ""+aTarget ); //System.out.println( "DisplayGroupNode.new: " + aTarget ); - parentAssociation = aParentAssociation; - target = null; - targetObserver = null; - parentGroup = aParentGroup; - childNodes = new NSMutableDictionary(); - isFetched = false; - isFetchNeeded = false; - useParentOrderings = true; - useParentQualifier = true; - - EODataSource parentSource = null; - if ( parentGroup != null ) - { - parentSource = parentGroup.dataSource(); - } - else - if ( parentAssociation.titlesDisplayGroup != null ) - { - parentSource = parentAssociation.titlesDisplayGroup.dataSource(); - } - - // create child datasource - if ( aTarget != null ) // not root node - { - if ( parentAssociation.childrenKey != null ) - { - if ( parentSource == null ) - { - throw new WotonomyException( - "Need a data source when children aspect is bound." ); - } - - NSArray displayedObjects = parentGroup.displayedObjects(); - EODataSource childSource = parentSource.dataSourceQualifiedByKey( - parentAssociation.childrenKey ); - childSource.qualifyWithRelationshipKey( - parentAssociation.childrenKey, aTarget ); - - // create new display group using child data source - this.setDataSource( childSource ); - - // establish observer for target object - setTarget( aTarget ); - } - else // only titles is bound - { - // establish observer for target object - setTarget( aTarget ); - - setDataSource( new PropertyDataSource() - { - public NSArray fetchObjects() - { - return new NSArray(); - } - } ); - } - } - else // else root node - { - // root node uses PropertyDataSource by default - if ( parentSource == null ) - { - setDataSource( new PropertyDataSource() - { - public NSArray fetchObjects() - { - if ( parentGroup != null ) - { - return parentGroup.displayedObjects(); - } - return null; - } - } ); - } - else - { - // root node uses parent source directly - setDataSource( parentSource ); - } - } - } - - /** - * Overridden to unregister as an editor of the editing context, - * since we don't directly present a user interface. - */ - public void setDataSource ( EODataSource aDataSource ) - { - super.setDataSource( aDataSource ); - if ( ( aDataSource != null ) - && ( aDataSource.editingContext() != null ) ) - { - aDataSource.editingContext().removeEditor( this ); - } - } - - /** - * Returns whether the node should call fetch(). - */ - protected boolean isFetched() - { - if ( isFetchNeeded() ) - { - setFetchNeeded( false ); - fetch(); - } - return isFetched; - } - - /** - * Sets whether the node should call fetch(). - */ - protected void setFetched( boolean fetched ) - { + parentAssociation = aParentAssociation; + target = null; + targetObserver = null; + parentGroup = aParentGroup; + childNodes = new NSMutableDictionary(); + isFetched = false; + isFetchNeeded = false; + useParentOrderings = true; + useParentQualifier = true; + + EODataSource parentSource = null; + if (parentGroup != null) { + parentSource = parentGroup.dataSource(); + } else if (parentAssociation.titlesDisplayGroup != null) { + parentSource = parentAssociation.titlesDisplayGroup.dataSource(); + } + + // create child datasource + if (aTarget != null) // not root node + { + if (parentAssociation.childrenKey != null) { + if (parentSource == null) { + throw new WotonomyException("Need a data source when children aspect is bound."); + } + + NSArray displayedObjects = parentGroup.displayedObjects(); + EODataSource childSource = parentSource.dataSourceQualifiedByKey(parentAssociation.childrenKey); + childSource.qualifyWithRelationshipKey(parentAssociation.childrenKey, aTarget); + + // create new display group using child data source + this.setDataSource(childSource); + + // establish observer for target object + setTarget(aTarget); + } else // only titles is bound + { + // establish observer for target object + setTarget(aTarget); + + setDataSource(new PropertyDataSource() { + public NSArray fetchObjects() { + return new NSArray(); + } + }); + } + } else // else root node + { + // root node uses PropertyDataSource by default + if (parentSource == null) { + setDataSource(new PropertyDataSource() { + public NSArray fetchObjects() { + if (parentGroup != null) { + return parentGroup.displayedObjects(); + } + return null; + } + }); + } else { + // root node uses parent source directly + setDataSource(parentSource); + } + } + } + + /** + * Overridden to unregister as an editor of the editing context, since we don't + * directly present a user interface. + */ + public void setDataSource(EODataSource aDataSource) { + super.setDataSource(aDataSource); + if ((aDataSource != null) && (aDataSource.editingContext() != null)) { + aDataSource.editingContext().removeEditor(this); + } + } + + /** + * Returns whether the node should call fetch(). + */ + protected boolean isFetched() { + if (isFetchNeeded()) { + setFetchNeeded(false); + fetch(); + } + return isFetched; + } + + /** + * Sets whether the node should call fetch(). + */ + protected void setFetched(boolean fetched) { //System.out.println( "DisplayGroupNode.setFetched: " + fetched + " : " + this + " : " + target ); //net.wotonomy.ui.swing.util.StackTraceInspector.printShortStackTrace(); - isFetched = fetched; - } - - /** - * Returns whether the node is in need of a refetch. - */ - protected boolean isFetchNeeded() - { - return isFetchNeeded; - } - - /** - * Returns whether the node should call fetch(). - */ - protected void setFetchNeeded( boolean fetchNeeded ) - { + isFetched = fetched; + } + + /** + * Returns whether the node is in need of a refetch. + */ + protected boolean isFetchNeeded() { + return isFetchNeeded; + } + + /** + * Returns whether the node should call fetch(). + */ + protected void setFetchNeeded(boolean fetchNeeded) { //System.out.println( "DisplayGroupNode.setFetchNeeded: " + fetchNeeded + " : " + this + " : " + target ); //net.wotonomy.ui.swing.util.StackTraceInspector.printShortStackTrace(); - isFetchNeeded = fetchNeeded; - } - - /** - * Subclasses should override this method to fire an appropriate insertion event. - */ - protected void fireNodesInserted( Object[] path, int[] indexes, Object[] objects ) - { + isFetchNeeded = fetchNeeded; + } + + /** + * Subclasses should override this method to fire an appropriate insertion + * event. + */ + protected void fireNodesInserted(Object[] path, int[] indexes, Object[] objects) { //System.out.println( "fireNodesInserted: " + this ); - parentAssociation.fireTreeNodesInserted( - this, path, indexes, objects ); - } - - /** - * Subclasses should override this method to fire an appropriate change event. - */ - protected void fireNodesChanged( Object[] path, int[] indexes, Object[] objects ) - { + parentAssociation.fireTreeNodesInserted(this, path, indexes, objects); + } + + /** + * Subclasses should override this method to fire an appropriate change event. + */ + protected void fireNodesChanged(Object[] path, int[] indexes, Object[] objects) { //System.out.println( "fireNodesChanged: " + this ); - parentAssociation.fireTreeNodesChanged( - this, path, indexes, objects ); - } - - /** - * Subclasses should override this method to fire an appropriate deletion event. - */ - protected void fireNodesRemoved( Object[] path, int[] indexes, Object[] objects ) - { + parentAssociation.fireTreeNodesChanged(this, path, indexes, objects); + } + + /** + * Subclasses should override this method to fire an appropriate deletion event. + */ + protected void fireNodesRemoved(Object[] path, int[] indexes, Object[] objects) { //System.out.println( "fireNodesRemoved: " + this ); - parentAssociation.fireTreeNodesRemoved( - this, path, indexes, objects ); - } - - /** - * Subclasses should override this method to fire an appropriate event. - */ - protected void fireStructureChanged( Object[] path, int[] indexes, Object[] objects ) - { - parentAssociation.fireTreeStructureChanged( - this, path, indexes, objects ); - } - - /** - * Overridden to broadcast a tree event after super executes. - */ - public void insertObjectAtIndex ( Object anObject, int anIndex ) - { - int count = getChildCount(); // gets old count - if ( target == null ) - { - // if root node, forward to parent: - // circumventing delegating data source, if any - EODataSource dataSource = parentGroup.dataSource(); - if ( dataSource instanceof DelegatingTreeDataSource ) - { - parentGroup.setDataSource( - ((DelegatingTreeDataSource)dataSource).delegateDataSource ); - } - parentGroup.insertObjectAtIndex( anObject, anIndex ); - if ( dataSource instanceof DelegatingTreeDataSource ) - { - parentGroup.setDataSource( dataSource ); - } - return; // prevent event from firing (?) - } - else // not root node - { - super.insertObjectAtIndex( anObject, anIndex ); - } - } - - /** - * Overridden to broadcast a tree event after super executes. - */ - public boolean deleteObjectAtIndex ( int anIndex ) - { - boolean result; - Object node = getChildNodeAt( anIndex ); - if ( target == null ) - { - // if root node, forward to parent: - result = parentGroup.deleteObjectAtIndex( anIndex ); - } - else // not root node - { - result = super.deleteObjectAtIndex( anIndex ); - } - - return result; - } - - /** - * Returns the child node that corresponds to the - * specified index, creating it if necessary. - * The index must be within bounds or an exception - * is thrown. - */ - public DisplayGroupNode getChildNodeAt( int anIndex ) - { - boolean wasFetched = isFetched(); - if ( ! wasFetched ) fetch(); - Object o = displayedObjects.objectAtIndex( anIndex ); - DisplayGroupNode result = getChildNodeForObject( o ); - if ( result == null ) - { - result = createChildNodeForObject( o ); - } - return result; - } - - /** - * Returns a child node that corresponds to the - * specified object, returning null if not found. - */ - protected DisplayGroupNode getChildNodeForObject( Object anObject ) - { - return (DisplayGroupNode) - childNodes.objectForKey( new ReferenceKey( anObject ) ); - } - - /** - * Creates a child node that corresponds to the - * specified object. - */ - private DisplayGroupNode createChildNodeForObject( Object anObject ) - { - DisplayGroupNode result = parentAssociation.createNode( this, anObject ); - childNodes.setObjectForKey( result, new ReferenceKey( anObject ) ); - return result; - } - - /** - * Returns a tree path of all DisplayGroupNodes leading - * to this node, including the root node (but excluding the - * titles display group). - */ - public TreePath treePath() - { - List path = new LinkedList(); - EODisplayGroup node = this; - while ( node instanceof DisplayGroupNode ) - { - // insert at head of list - path.add( 0, node ); - node = ((DisplayGroupNode)node).parentGroup; - } - return new TreePath( path.toArray() ); - } - - /** - * Overridden to return the parent group's - * sort ordering if useParentOrderings is true. - * useParentOrderings is true by default. - */ - public NSArray sortOrderings() - { - if ( ( useParentOrderings ) - && ( parentGroup != null ) ) - { - return parentGroup.sortOrderings(); - } - return super.sortOrderings(); - } - - /** - * Overridden to set useParentOrderings to false, - * or true if aList is null. - */ - public void setSortOrderings ( List aList ) - { - if ( aList == null ) - { - useParentOrderings = true; - } - else - { - useParentOrderings = false; - super.setSortOrderings( aList ); - } - } - - /** - * Overridden to return the parent group's - * qualifier if useParentQualifier is true. - * useParentQualifier is true by default. - */ - public EOQualifier qualifier() - { - if ( ( useParentQualifier ) - && ( parentGroup != null ) ) - { - return parentGroup.qualifier(); - } - return super.qualifier(); - } - - /** - * Overridden to set useParentQualifier to false, - * or true if aList is null. - */ - public void setQualifier ( EOQualifier aQualifier ) - { - if ( aQualifier == null ) - { - useParentQualifier = true; - } - else - { - useParentQualifier = false; - super.setQualifier( aQualifier ); - } - } - - /** - * Overridden to set isFetched to true. - */ - public boolean fetch() - { + parentAssociation.fireTreeNodesRemoved(this, path, indexes, objects); + } + + /** + * Subclasses should override this method to fire an appropriate event. + */ + protected void fireStructureChanged(Object[] path, int[] indexes, Object[] objects) { + parentAssociation.fireTreeStructureChanged(this, path, indexes, objects); + } + + /** + * Overridden to broadcast a tree event after super executes. + */ + public void insertObjectAtIndex(Object anObject, int anIndex) { + int count = getChildCount(); // gets old count + if (target == null) { + // if root node, forward to parent: + // circumventing delegating data source, if any + EODataSource dataSource = parentGroup.dataSource(); + if (dataSource instanceof DelegatingTreeDataSource) { + parentGroup.setDataSource(((DelegatingTreeDataSource) dataSource).delegateDataSource); + } + parentGroup.insertObjectAtIndex(anObject, anIndex); + if (dataSource instanceof DelegatingTreeDataSource) { + parentGroup.setDataSource(dataSource); + } + return; // prevent event from firing (?) + } else // not root node + { + super.insertObjectAtIndex(anObject, anIndex); + } + } + + /** + * Overridden to broadcast a tree event after super executes. + */ + public boolean deleteObjectAtIndex(int anIndex) { + boolean result; + Object node = getChildNodeAt(anIndex); + if (target == null) { + // if root node, forward to parent: + result = parentGroup.deleteObjectAtIndex(anIndex); + } else // not root node + { + result = super.deleteObjectAtIndex(anIndex); + } + + return result; + } + + /** + * Returns the child node that corresponds to the specified index, creating it + * if necessary. The index must be within bounds or an exception is thrown. + */ + public DisplayGroupNode getChildNodeAt(int anIndex) { + boolean wasFetched = isFetched(); + if (!wasFetched) + fetch(); + Object o = displayedObjects.objectAtIndex(anIndex); + DisplayGroupNode result = getChildNodeForObject(o); + if (result == null) { + result = createChildNodeForObject(o); + } + return result; + } + + /** + * Returns a child node that corresponds to the specified object, returning null + * if not found. + */ + protected DisplayGroupNode getChildNodeForObject(Object anObject) { + return (DisplayGroupNode) childNodes.objectForKey(new ReferenceKey(anObject)); + } + + /** + * Creates a child node that corresponds to the specified object. + */ + private DisplayGroupNode createChildNodeForObject(Object anObject) { + DisplayGroupNode result = parentAssociation.createNode(this, anObject); + childNodes.setObjectForKey(result, new ReferenceKey(anObject)); + return result; + } + + /** + * Returns a tree path of all DisplayGroupNodes leading to this node, including + * the root node (but excluding the titles display group). + */ + public TreePath treePath() { + List path = new LinkedList(); + EODisplayGroup node = this; + while (node instanceof DisplayGroupNode) { + // insert at head of list + path.add(0, node); + node = ((DisplayGroupNode) node).parentGroup; + } + return new TreePath(path.toArray()); + } + + /** + * Overridden to return the parent group's sort ordering if useParentOrderings + * is true. useParentOrderings is true by default. + */ + public NSArray sortOrderings() { + if ((useParentOrderings) && (parentGroup != null)) { + return parentGroup.sortOrderings(); + } + return super.sortOrderings(); + } + + /** + * Overridden to set useParentOrderings to false, or true if aList is null. + */ + public void setSortOrderings(List aList) { + if (aList == null) { + useParentOrderings = true; + } else { + useParentOrderings = false; + super.setSortOrderings(aList); + } + } + + /** + * Overridden to return the parent group's qualifier if useParentQualifier is + * true. useParentQualifier is true by default. + */ + public EOQualifier qualifier() { + if ((useParentQualifier) && (parentGroup != null)) { + return parentGroup.qualifier(); + } + return super.qualifier(); + } + + /** + * Overridden to set useParentQualifier to false, or true if aList is null. + */ + public void setQualifier(EOQualifier aQualifier) { + if (aQualifier == null) { + useParentQualifier = true; + } else { + useParentQualifier = false; + super.setQualifier(aQualifier); + } + } + + /** + * Overridden to set isFetched to true. + */ + public boolean fetch() { //System.out.println( "DisplayGroupNode.fetch: " + this + " : " ); //if ( getClass().getName().indexOf( "Activity" ) != -1 ) //{ // new net.wotonomy.ui.swing.util.StackTraceInspector( this.toString() ); //} - // set flag - setFetched( true ); + // set flag + setFetched(true); - // skip root node - if ( target == null ) return true; + // skip root node + if (target == null) + return true; - // requalify - dataSource().qualifyWithRelationshipKey( - parentAssociation.childrenKey, target ); + // requalify + dataSource().qualifyWithRelationshipKey(parentAssociation.childrenKey, target); - // call to super - return super.fetch(); + // call to super + return super.fetch(); //boolean result = super.fetch(); //System.out.println( displayedObjects() ); //return result; - } - - /** - * Returns the object at the appropriate index - * in the parent display group. - */ - public Object object() - { - // if root node - if ( target == null ) - { - return parentAssociation.rootLabel(); - } - return target; - } - - /** - * Returns the string value of the title property - * on the object in the parent display group corresponding - * to this index. The tree renderer asks JTrees to - * call this method to retrieve a value for display. - */ - public String toString() - { - Object result = getUserObject(); - if ( result == null ) result = "[null]"; - return result.toString(); - } - - // parts of interface TreeNode - - public int getChildCount() - { - if ( ! isFetched() ) fetch(); + } + + /** + * Returns the object at the appropriate index in the parent display group. + */ + public Object object() { + // if root node + if (target == null) { + return parentAssociation.rootLabel(); + } + return target; + } + + /** + * Returns the string value of the title property on the object in the parent + * display group corresponding to this index. The tree renderer asks JTrees to + * call this method to retrieve a value for display. + */ + public String toString() { + Object result = getUserObject(); + if (result == null) + result = "[null]"; + return result.toString(); + } + + // parts of interface TreeNode + + public int getChildCount() { + if (!isFetched()) + fetch(); //if ( toString().indexOf("154.16406")!=-1){ //System.out.println( "getChildCount: " + displayedObjects.count() + " : " + this ); //new RuntimeException().printStackTrace(); //net.wotonomy.ui.swing.util.StackTraceInspector.printShortStackTrace(); //} - return displayedObjects.count(); - } - - public int getIndex(DisplayGroupNode node) - { - if ( ! isFetched() ) fetch(); - return displayedObjects.indexOfObject( - ((DisplayGroupNode)node).target ); - } - - public boolean getAllowsChildren() - { - return true; - } - - public boolean isLeaf() - { - // if not root node and isLeaf aspect is bound - if ( ( target != null ) - && ( parentGroup != null ) - && ( parentAssociation.leafKey != null ) ) - { - Object value; - if ( parentAssociation.leafDisplayGroup != null ) - { - value = parentGroup.valueForObject( - target, parentAssociation.leafKey ); - } - else - { - value = parentAssociation.leafKey; - } - - // getBoolean returns true for zero, among other things - Object result = ValueConverter.getBoolean( value ); - if ( result != null ) - { - return ((Boolean)result).booleanValue(); - } - } - - // otherwise, we have to fetch and return count - return ( getChildCount() == 0 ); - } - - public Enumeration children() - { - int count = getChildCount(); - Vector v = new Vector(); - for ( int i = 0; i < count; i++ ) - { - v.add( getChildNodeAt( i ) ); - } - return v.elements(); - } - - // parts of interface MutableTreeNode - - public void insert(DisplayGroupNode aChild, int anIndex) - { - insertObjectAtIndex( - ((DisplayGroupNode)aChild).object(), anIndex ); - } - - public void remove(int index) - { - deleteObjectAtIndex( index ); - } - - /** - * Removes the node at the index corresponding - * to the index of the object. - */ - public void remove(DisplayGroupNode node) - { - remove( getIndex( node ) ); - } - - /** - * Removes our object from the parent display group. - */ - public void removeFromParent() - { - int index = parentGroup.displayedObjects().indexOfIdenticalObject( target ); - if ( index != NSArray.NotFound ) - { - parentGroup.deleteObjectAtIndex( index ); - } - else - { - throw new WotonomyException( - "Object not found in parent group: " + target ); - } - } - - /** - * Removes our object from the parent display group - * and adds it to the end of the specified node's children. - */ - public void setParent(DisplayGroupNode newParent) - { - removeFromParent(); - newParent.insertObjectAtIndex( - object(), newParent.displayedObjects.size() ); - } - - /** - * Returns the value of the displayed property in the parent display group - * at the index that corresponds to the index of this node. - */ - public Object getUserObject() - { - return valueForKey( parentAssociation.titlesKey ); - } - - /** - * Sets the value of the displayed property in the parent display group - * at the index that corresponds to the index of this node. - */ - public void setUserObject( Object aValue ) - { - setValueForKey( aValue, parentAssociation.titlesKey ); - } - - /** - * Returns a value from the object in the parent display group - * at the index that corresponds to the index of this node. - * For the root node, if the titles key is specified, the root - * label is returned, otherwise null is returned. - */ - public Object valueForKey( String aKey ) - { - // if root node - if ( target == null ) - { - // compare by ref is okay for strings - if ( aKey == parentAssociation.titlesKey ) - { - return parentAssociation.rootLabel(); - } - return null; - } - return parentGroup.valueForObject( target, aKey ); - } - - /** - * Sets a value on the object in the parent display group - * at the index that corresponds to the index of this node. - * For the root node, this method only works if aKey is the - * titlesAspect's key, otherwise does nothing. - */ - public void setValueForKey(Object aValue, String aKey) - { - // if root node, return. - if ( target == null ) - { - // compare by ref is okay for strings - if ( aKey == parentAssociation.titlesKey ) - { - parentAssociation.setRootLabel( aValue ); - - // how to handle root node? tree event docs don't say. - fireNodesChanged ( treePath().getPath(), - new int[] { 0 }, - new Object[] { this } ); - } - return; - } - - parentGroup.setValueForObject( - aValue, target, aKey ); - } - - /** - * Perform any clean up in this method. - * The node will not be reused after this method is called. - * This implementation removes itself from the parent's - * set of child nodes, sets target and datasource to null, - * and then calls disposeChildNodes(). - */ - protected void dispose() - { //System.out.println( "dispose: " + this.getClass().getName() + " : " + this ); - if ( parentGroup != null ) - { - ((DisplayGroupNode)parentGroup).childNodes.remove( - new ReferenceKey( target ) ); - } - setTarget( (Object) null ); - setDataSource( null ); - disposeChildNodes(); - } - - /** - * Calls dispose() on all child nodes. - */ - protected void disposeChildNodes() - { - Iterator i = new LinkedList(childNodes.values()).iterator(); - while ( i.hasNext() ) - { - ((DisplayGroupNode) i.next()).dispose(); - } - } - - /** - * Called after the target object posts a change notification. - * This implementation re-fetches which triggers - * updateDisplayedObjects to broadcast any tree events. - * This method marks the parent object as changed if: - * (1) this object is not registered in the editing context - * of the titles display group's data source (if any), AND - * (2) the children key is not in the list of attributes - * of the parent object's EOClassDescription. - */ - public void targetChanged() - { - // if not root node - if ( target != null ) - { - // if we're not root and not fetched, stop here. - //FIXME: with this, some nodes have old values when moved. - //FIXME: without this, nodes are unnecessarily fetched. - //FIXME: might have parent modify isFetched of certain child nodes. - if ( isFetched() ) - { - fetch(); - } - else // not fetched - just update the display - { - updateDisplayedObjects(); - } -/* -//disabling this for performance reasons: -//might reenable later or find an alternate approach - // check to see if we need to mark the parent object as changed - EOEditingContext context = dataSource().editingContext(); - if ( ( context == null ) - || ( context.globalIDForObject( target ) == null ) ) - { - DisplayGroupNode parentNode = (DisplayGroupNode) parentGroup; - if ( parentNode.target != null ) - { - // only notify if childrenKey is an attribute of parentDesc - // (and therefore not a toOne or toMany relationship) - EOClassDescription parentDesc = - EOClassDescription.classDescriptionForClass( - parentNode.target.getClass() ); - if ( parentDesc.attributeKeys().contains( parentAssociation.childrenKey ) ) - { - // only notify if no context is already observing the object - // and we are an attribute key - EOObserverCenter.notifyObserversObjectWillChange( parentNode.target ); - } - } - } -*/ - } - else // root node - { - setObjectArray( parentAssociation.titlesDisplayGroup.displayedObjects() ); - } - - // finally, broadcast change event for this node - // even though we're not sure if the displayed value changed. - fireNodeChanged(); - } - - /** - * Fires a change event for this node. - */ - public void fireNodeChanged() - { - // if not root node - if ( target != null ) - { - int index = ((DisplayGroupNode)parentGroup).getIndex( this ); - if ( ( index != -1 ) - && ( treePath().getParentPath() != null ) ) - { - fireNodesChanged ( - treePath().getParentPath().getPath(), - new int[] { index }, - new Object[] { this } ); - } - } - } - -Object[] previouslyDisplayedObjects = new Object[0]; - /** - * Overridden to call to super, fire any tree events, and then - * call updateDisplayedObjects on all fetched child nodes. - * This method compares this node's displayed objects against - * the list of child nodes, synchronizes them, and then broadcasts - * only the necessary events to bring the view component up to date. - */ - public void updateDisplayedObjects() - { + return displayedObjects.count(); + } + + public int getIndex(DisplayGroupNode node) { + if (!isFetched()) + fetch(); + return displayedObjects.indexOfObject(((DisplayGroupNode) node).target); + } + + public boolean getAllowsChildren() { + return true; + } + + public boolean isLeaf() { + // if not root node and isLeaf aspect is bound + if ((target != null) && (parentGroup != null) && (parentAssociation.leafKey != null)) { + Object value; + if (parentAssociation.leafDisplayGroup != null) { + value = parentGroup.valueForObject(target, parentAssociation.leafKey); + } else { + value = parentAssociation.leafKey; + } + + // getBoolean returns true for zero, among other things + Object result = ValueConverter.getBoolean(value); + if (result != null) { + return ((Boolean) result).booleanValue(); + } + } + + // otherwise, we have to fetch and return count + return (getChildCount() == 0); + } + + public Enumeration children() { + int count = getChildCount(); + Vector v = new Vector(); + for (int i = 0; i < count; i++) { + v.add(getChildNodeAt(i)); + } + return v.elements(); + } + + // parts of interface MutableTreeNode + + public void insert(DisplayGroupNode aChild, int anIndex) { + insertObjectAtIndex(((DisplayGroupNode) aChild).object(), anIndex); + } + + public void remove(int index) { + deleteObjectAtIndex(index); + } + + /** + * Removes the node at the index corresponding to the index of the object. + */ + public void remove(DisplayGroupNode node) { + remove(getIndex(node)); + } + + /** + * Removes our object from the parent display group. + */ + public void removeFromParent() { + int index = parentGroup.displayedObjects().indexOfIdenticalObject(target); + if (index != NSArray.NotFound) { + parentGroup.deleteObjectAtIndex(index); + } else { + throw new WotonomyException("Object not found in parent group: " + target); + } + } + + /** + * Removes our object from the parent display group and adds it to the end of + * the specified node's children. + */ + public void setParent(DisplayGroupNode newParent) { + removeFromParent(); + newParent.insertObjectAtIndex(object(), newParent.displayedObjects.size()); + } + + /** + * Returns the value of the displayed property in the parent display group at + * the index that corresponds to the index of this node. + */ + public Object getUserObject() { + return valueForKey(parentAssociation.titlesKey); + } + + /** + * Sets the value of the displayed property in the parent display group at the + * index that corresponds to the index of this node. + */ + public void setUserObject(Object aValue) { + setValueForKey(aValue, parentAssociation.titlesKey); + } + + /** + * Returns a value from the object in the parent display group at the index that + * corresponds to the index of this node. For the root node, if the titles key + * is specified, the root label is returned, otherwise null is returned. + */ + public Object valueForKey(String aKey) { + // if root node + if (target == null) { + // compare by ref is okay for strings + if (aKey == parentAssociation.titlesKey) { + return parentAssociation.rootLabel(); + } + return null; + } + return parentGroup.valueForObject(target, aKey); + } + + /** + * Sets a value on the object in the parent display group at the index that + * corresponds to the index of this node. For the root node, this method only + * works if aKey is the titlesAspect's key, otherwise does nothing. + */ + public void setValueForKey(Object aValue, String aKey) { + // if root node, return. + if (target == null) { + // compare by ref is okay for strings + if (aKey == parentAssociation.titlesKey) { + parentAssociation.setRootLabel(aValue); + + // how to handle root node? tree event docs don't say. + fireNodesChanged(treePath().getPath(), new int[] { 0 }, new Object[] { this }); + } + return; + } + + parentGroup.setValueForObject(aValue, target, aKey); + } + + /** + * Perform any clean up in this method. The node will not be reused after this + * method is called. This implementation removes itself from the parent's set of + * child nodes, sets target and datasource to null, and then calls + * disposeChildNodes(). + */ + protected void dispose() { // System.out.println( "dispose: " + this.getClass().getName() + " : " + this ); + if (parentGroup != null) { + ((DisplayGroupNode) parentGroup).childNodes.remove(new ReferenceKey(target)); + } + setTarget((Object) null); + setDataSource(null); + disposeChildNodes(); + } + + /** + * Calls dispose() on all child nodes. + */ + protected void disposeChildNodes() { + Iterator i = new LinkedList(childNodes.values()).iterator(); + while (i.hasNext()) { + ((DisplayGroupNode) i.next()).dispose(); + } + } + + /** + * Called after the target object posts a change notification. This + * implementation re-fetches which triggers updateDisplayedObjects to broadcast + * any tree events. This method marks the parent object as changed if: (1) this + * object is not registered in the editing context of the titles display group's + * data source (if any), AND (2) the children key is not in the list of + * attributes of the parent object's EOClassDescription. + */ + public void targetChanged() { + // if not root node + if (target != null) { + // if we're not root and not fetched, stop here. + // FIXME: with this, some nodes have old values when moved. + // FIXME: without this, nodes are unnecessarily fetched. + // FIXME: might have parent modify isFetched of certain child nodes. + if (isFetched()) { + fetch(); + } else // not fetched - just update the display + { + updateDisplayedObjects(); + } + /* + * //disabling this for performance reasons: //might reenable later or find an + * alternate approach // check to see if we need to mark the parent object as + * changed EOEditingContext context = dataSource().editingContext(); if ( ( + * context == null ) || ( context.globalIDForObject( target ) == null ) ) { + * DisplayGroupNode parentNode = (DisplayGroupNode) parentGroup; if ( + * parentNode.target != null ) { // only notify if childrenKey is an attribute + * of parentDesc // (and therefore not a toOne or toMany relationship) + * EOClassDescription parentDesc = EOClassDescription.classDescriptionForClass( + * parentNode.target.getClass() ); if ( parentDesc.attributeKeys().contains( + * parentAssociation.childrenKey ) ) { // only notify if no context is already + * observing the object // and we are an attribute key + * EOObserverCenter.notifyObserversObjectWillChange( parentNode.target ); } } } + */ + } else // root node + { + setObjectArray(parentAssociation.titlesDisplayGroup.displayedObjects()); + } + + // finally, broadcast change event for this node + // even though we're not sure if the displayed value changed. + fireNodeChanged(); + } + + /** + * Fires a change event for this node. + */ + public void fireNodeChanged() { + // if not root node + if (target != null) { + int index = ((DisplayGroupNode) parentGroup).getIndex(this); + if ((index != -1) && (treePath().getParentPath() != null)) { + fireNodesChanged(treePath().getParentPath().getPath(), new int[] { index }, new Object[] { this }); + } + } + } + + Object[] previouslyDisplayedObjects = new Object[0]; + + /** + * Overridden to call to super, fire any tree events, and then call + * updateDisplayedObjects on all fetched child nodes. This method compares this + * node's displayed objects against the list of child nodes, synchronizes them, + * and then broadcasts only the necessary events to bring the view component up + * to date. + */ + public void updateDisplayedObjects() { //System.out.println( "updateDisplayedObjects: " + " : " + this ); //net.wotonomy.ui.swing.util.StackTraceInspector.printShortStackTrace(); //new RuntimeException().printStackTrace(); - super.updateDisplayedObjects(); - - // diff lists - boolean proceed = true; - Object[] oldObjects = previouslyDisplayedObjects; - Object[] newObjects = displayedObjects.toArray(); - if ( oldObjects.length == newObjects.length ) - { - proceed = false; - for ( int i = 0; i < newObjects.length; i++ ) - { - if ( oldObjects[i] != newObjects[i] ) - { - proceed = true; - break; - } - } - } - - // this should be set before firing the change events - // in case some clients end up calling this again. - previouslyDisplayedObjects = newObjects; - - DisplayGroupNode node; - Iterator i = childNodes.values().iterator(); - while ( i.hasNext() ) - { - node = (DisplayGroupNode) i.next(); - if ( !node.isFetchNeeded() ) - { - node.updateDisplayedObjects(); - } - } - - if ( proceed ) - { + super.updateDisplayedObjects(); + + // diff lists + boolean proceed = true; + Object[] oldObjects = previouslyDisplayedObjects; + Object[] newObjects = displayedObjects.toArray(); + if (oldObjects.length == newObjects.length) { + proceed = false; + for (int i = 0; i < newObjects.length; i++) { + if (oldObjects[i] != newObjects[i]) { + proceed = true; + break; + } + } + } + + // this should be set before firing the change events + // in case some clients end up calling this again. + previouslyDisplayedObjects = newObjects; + + DisplayGroupNode node; + Iterator i = childNodes.values().iterator(); + while (i.hasNext()) { + node = (DisplayGroupNode) i.next(); + if (!node.isFetchNeeded()) { + node.updateDisplayedObjects(); + } + } + + if (proceed) { //System.out.println( "DisplayGroupNode.firingEventsForChanges: " ); //new RuntimeException().printStackTrace(); - fireEventsForChanges( oldObjects, newObjects ); - } - - } - - /** - * Called by processRecentChanges to analyze the - * differences between the lists and broadcast the - * appropriate events. - */ - protected void fireEventsForChanges( - Object[] oldObjects, Object[] newObjects ) - { - // structure changed causes havoc while - // establishing connection in some cases - //if ( oldObjects.length == 0 || newObjects.length == 0 ) - //{ - // fireStructureChanged( treePath().getPath(), null, null ); - // return; - //} - - int insertCount = 0; - int deleteCount = 0; - Object[] inserts = new Object[ newObjects.length ]; - Object[] deletes = new Object[ oldObjects.length ]; - - int i; - int n = -1, o = -1; // last match - int n1 = 0, o1 = 0; // current match test - int n2 = 0, o2 = 0; // scan ahead - - while ( o1 < oldObjects.length && n1 < newObjects.length ) - { - if ( newObjects[n1] == oldObjects[o1] ) - { - // mark as match and continue - o = o1; - n = n1; - } - else - { - // scan ahead for the next match, if any - o2 = o1; - n2 = n1; - - while ( o2 < oldObjects.length || n2 < newObjects.length ) - { - if ( o2 < oldObjects.length && newObjects[n1] == oldObjects[o2] ) - { - // run o1 to o2: mark as deletes - for ( i = o1; i < o2; i++ ) - { // System.out.println( "delete : " + i ); - deletes[i] = oldObjects[i]; - deleteCount++; - } - o1 = o2; // reset test - o = o1; // set match - n = n1; // set match - break; - } - if ( n2 < newObjects.length && newObjects[n2] == oldObjects[o1] ) - { - // run n1 to n2: mark as inserts - for ( i = n1; i < n2; i++ ) - { // System.out.println( "insert : " + i ); - inserts[i] = newObjects[i]; - insertCount++; - } - n1 = n2; // reset test - n = n1; // set match - o = o1; // set match - break; - } - o2++; - n2++; - } - } - if (n != n1) - { - inserts[n1] = newObjects[n1]; - insertCount++; - deletes[o1] = oldObjects[o1]; - deleteCount++; - //increment even though no match: - //the new object was marked as inserted and - //the old object was marked as deleted. - n = n1; - o = o1; - } - o1++; - n1++; - } - - // run o to end of oldObjects: mark as deletes - for ( i = o+1; i < oldObjects.length; i++ ) - { // System.out.println( "delete : " + i ); - deletes[i] = oldObjects[i]; - deleteCount++; - } - - // run n to end of newObjects: mark as inserts - for ( i = n+1; i < newObjects.length; i++ ) - { // System.out.println( "insert : " + i ); - inserts[i] = newObjects[i]; - insertCount++; - } + fireEventsForChanges(oldObjects, newObjects); + } + + } + + /** + * Called by processRecentChanges to analyze the differences between the lists + * and broadcast the appropriate events. + */ + protected void fireEventsForChanges(Object[] oldObjects, Object[] newObjects) { + // structure changed causes havoc while + // establishing connection in some cases + // if ( oldObjects.length == 0 || newObjects.length == 0 ) + // { + // fireStructureChanged( treePath().getPath(), null, null ); + // return; + // } + + int insertCount = 0; + int deleteCount = 0; + Object[] inserts = new Object[newObjects.length]; + Object[] deletes = new Object[oldObjects.length]; + + int i; + int n = -1, o = -1; // last match + int n1 = 0, o1 = 0; // current match test + int n2 = 0, o2 = 0; // scan ahead + + while (o1 < oldObjects.length && n1 < newObjects.length) { + if (newObjects[n1] == oldObjects[o1]) { + // mark as match and continue + o = o1; + n = n1; + } else { + // scan ahead for the next match, if any + o2 = o1; + n2 = n1; + + while (o2 < oldObjects.length || n2 < newObjects.length) { + if (o2 < oldObjects.length && newObjects[n1] == oldObjects[o2]) { + // run o1 to o2: mark as deletes + for (i = o1; i < o2; i++) { // System.out.println( "delete : " + i ); + deletes[i] = oldObjects[i]; + deleteCount++; + } + o1 = o2; // reset test + o = o1; // set match + n = n1; // set match + break; + } + if (n2 < newObjects.length && newObjects[n2] == oldObjects[o1]) { + // run n1 to n2: mark as inserts + for (i = n1; i < n2; i++) { // System.out.println( "insert : " + i ); + inserts[i] = newObjects[i]; + insertCount++; + } + n1 = n2; // reset test + n = n1; // set match + o = o1; // set match + break; + } + o2++; + n2++; + } + } + if (n != n1) { + inserts[n1] = newObjects[n1]; + insertCount++; + deletes[o1] = oldObjects[o1]; + deleteCount++; + // increment even though no match: + // the new object was marked as inserted and + // the old object was marked as deleted. + n = n1; + o = o1; + } + o1++; + n1++; + } + + // run o to end of oldObjects: mark as deletes + for (i = o + 1; i < oldObjects.length; i++) { // System.out.println( "delete : " + i ); + deletes[i] = oldObjects[i]; + deleteCount++; + } + + // run n to end of newObjects: mark as inserts + for (i = n + 1; i < newObjects.length; i++) { // System.out.println( "insert : " + i ); + inserts[i] = newObjects[i]; + insertCount++; + } //System.out.println( "done : " //+ o + " : " + o1 + " : " + o2 + " :: " + n + " : " + n1 + " : " + n2 ); @@ -948,571 +774,481 @@ Object[] previouslyDisplayedObjects = new Object[0]; //System.out.println( new NSArray( inserts ) ); //System.out.println( new NSArray( deletes ) ); //System.out.println( new NSArray( oldObjects ) ); - - int c; - Object[] nodes; - int[] indices; - - // broadcast delete event - c = 0; - nodes = new Object[ deleteCount ]; - indices = new int[ deleteCount ]; - for ( i = 0; i < deletes.length; i++ ) - { - if ( deletes[i] != null ) - { - indices[c] = i; - nodes[c] = getChildNodeForObject( deletes[i] ); - c++; - } - } - if ( c > 0 ) - { + + int c; + Object[] nodes; + int[] indices; + + // broadcast delete event + c = 0; + nodes = new Object[deleteCount]; + indices = new int[deleteCount]; + for (i = 0; i < deletes.length; i++) { + if (deletes[i] != null) { + indices[c] = i; + nodes[c] = getChildNodeForObject(deletes[i]); + c++; + } + } + if (c > 0) { // fireNodeChanged(); // force the jtree to get the correct child count - fireNodesRemoved( treePath().getPath(), indices, nodes ); - } - deletes = nodes; // retain for dispose check - - // broadcast insert event - c = 0; - nodes = new Object[ insertCount ]; - indices = new int[ insertCount ]; - for ( i = 0; i < inserts.length; i++ ) - { - if ( inserts[i] != null ) - { - indices[c] = i; - nodes[c] = getChildNodeForObject( inserts[i] ); - if ( nodes[c] == null ) - { - nodes[c] = createChildNodeForObject( newObjects[i] ); - } - c++; - } - } - if ( c > 0 ) - { - fireNodesInserted( treePath().getPath(), indices, nodes ); - } - - // dispose any delete nodes not on insert list - int j; - boolean found; - for ( i = 0; i < deletes.length; i++ ) - { - for ( j = 0; j < nodes.length; j++ ) - { - if ( deletes[i] == nodes[j] ) break; - } - - // did not break early, so not found, so dispose - if ( j == nodes.length ) - { - ((DisplayGroupNode)deletes[i]).dispose(); - } - } - } - - /** - * Sets the target object and creates an registers a target observer. - * If target was not previously null, the existing observer is unregistered. - * Protected access so subclasses and TreeModelAssociation can update our target. - */ - public void setTarget( Object aTarget ) - { - if ( target != null ) - { - EOObserverCenter.removeObserver( targetObserver, target ); - targetObserver.discardPendingNotification(); - } - - if ( aTarget != null ) - { - target = aTarget; - targetObserver = new TargetObserver( this ); - EOObserverCenter.addObserver( targetObserver, target ); - } - } - - /** - * Returns the parent display group, or null if parent is root. - */ - public DisplayGroupNode getParentGroup() - { - if ( parentGroup instanceof DisplayGroupNode ) - { - return (DisplayGroupNode)parentGroup; - } - // presumably the root node - return null; - } - - /** - * Gets all descendants of the this node. - */ - public List getDescendants() - { - return getDescendants( this, true ); - } - - /** - * Gets only the descendants of the this node - * whose children has been loaded - no fetching - * will occur. Useful for load-on-demand trees. - */ - public List getLoadedDescendants() - { - return getDescendants( this, false ); - } - - // breadth first traversal implementation - - /** - * Returns a list of all descendants of the - * specified node. Unfetched nodes are traversed - * only if forceLoad is true. - * This implementation is a breadth-first traversal - * of the nodes starting at the specified node. - */ - static private List getDescendants( DisplayGroupNode aNode, boolean forceLoad ) - { - if ( !forceLoad && !aNode.isFetched ) return NSArray.EmptyArray; - - LinkedList result = new LinkedList(); - LinkedList queue = new LinkedList(); - - queue.add( aNode ); - while ( ! queue.isEmpty() ) - { - checkNode( (DisplayGroupNode) queue.removeFirst(), - queue, result, forceLoad ); - } - - return result; - } - - /** - * Adds each fetched child node of the specified node to - * the result set (optionally forcing the child node to load) - * and adding child node to the end of the queue. - */ - static private void checkNode( DisplayGroupNode aNode, - LinkedList aQueue, LinkedList aResult, boolean forceLoad ) - { - DisplayGroupNode child; - int count = aNode.getChildCount(); - - for ( int i = 0; i < count; i++ ) - { - child = aNode.getChildNodeAt( i ); - - // add to queue if node has fetched children - if ( ( !child.isFetched ) && ( forceLoad ) ) - { - child.fetch(); - } - if ( child.isFetched ) - { - aQueue.addLast( child ); - } - - aResult.add( child ); - } - } - - /** - * Overridden to not fetch on InvalidateAllObjectsInStoreNotification - * unless we've already been fetched, preserving the load-on-demand - * functionality. - */ - public void objectsInvalidatedInEditingContext( NSNotification aNotification ) - { - if ( EOObjectStore.InvalidatedAllObjectsInStoreNotification - .equals( aNotification.name() ) ) - { + fireNodesRemoved(treePath().getPath(), indices, nodes); + } + deletes = nodes; // retain for dispose check + + // broadcast insert event + c = 0; + nodes = new Object[insertCount]; + indices = new int[insertCount]; + for (i = 0; i < inserts.length; i++) { + if (inserts[i] != null) { + indices[c] = i; + nodes[c] = getChildNodeForObject(inserts[i]); + if (nodes[c] == null) { + nodes[c] = createChildNodeForObject(newObjects[i]); + } + c++; + } + } + if (c > 0) { + fireNodesInserted(treePath().getPath(), indices, nodes); + } + + // dispose any delete nodes not on insert list + int j; + boolean found; + for (i = 0; i < deletes.length; i++) { + for (j = 0; j < nodes.length; j++) { + if (deletes[i] == nodes[j]) + break; + } + + // did not break early, so not found, so dispose + if (j == nodes.length) { + ((DisplayGroupNode) deletes[i]).dispose(); + } + } + } + + /** + * Sets the target object and creates an registers a target observer. If target + * was not previously null, the existing observer is unregistered. Protected + * access so subclasses and TreeModelAssociation can update our target. + */ + public void setTarget(Object aTarget) { + if (target != null) { + EOObserverCenter.removeObserver(targetObserver, target); + targetObserver.discardPendingNotification(); + } + + if (aTarget != null) { + target = aTarget; + targetObserver = new TargetObserver(this); + EOObserverCenter.addObserver(targetObserver, target); + } + } + + /** + * Returns the parent display group, or null if parent is root. + */ + public DisplayGroupNode getParentGroup() { + if (parentGroup instanceof DisplayGroupNode) { + return (DisplayGroupNode) parentGroup; + } + // presumably the root node + return null; + } + + /** + * Gets all descendants of the this node. + */ + public List getDescendants() { + return getDescendants(this, true); + } + + /** + * Gets only the descendants of the this node whose children has been loaded - + * no fetching will occur. Useful for load-on-demand trees. + */ + public List getLoadedDescendants() { + return getDescendants(this, false); + } + + // breadth first traversal implementation + + /** + * Returns a list of all descendants of the specified node. Unfetched nodes are + * traversed only if forceLoad is true. This implementation is a breadth-first + * traversal of the nodes starting at the specified node. + */ + static private List getDescendants(DisplayGroupNode aNode, boolean forceLoad) { + if (!forceLoad && !aNode.isFetched) + return NSArray.EmptyArray; + + LinkedList result = new LinkedList(); + LinkedList queue = new LinkedList(); + + queue.add(aNode); + while (!queue.isEmpty()) { + checkNode((DisplayGroupNode) queue.removeFirst(), queue, result, forceLoad); + } + + return result; + } + + /** + * Adds each fetched child node of the specified node to the result set + * (optionally forcing the child node to load) and adding child node to the end + * of the queue. + */ + static private void checkNode(DisplayGroupNode aNode, LinkedList aQueue, LinkedList aResult, boolean forceLoad) { + DisplayGroupNode child; + int count = aNode.getChildCount(); + + for (int i = 0; i < count; i++) { + child = aNode.getChildNodeAt(i); + + // add to queue if node has fetched children + if ((!child.isFetched) && (forceLoad)) { + child.fetch(); + } + if (child.isFetched) { + aQueue.addLast(child); + } + + aResult.add(child); + } + } + + /** + * Overridden to not fetch on InvalidateAllObjectsInStoreNotification unless + * we've already been fetched, preserving the load-on-demand functionality. + */ + public void objectsInvalidatedInEditingContext(NSNotification aNotification) { + if (EOObjectStore.InvalidatedAllObjectsInStoreNotification.equals(aNotification.name())) { //System.out.println( "DisplayGroupNode.objectsInvalidatedInEditingContext: " + aNotification.name() ); - if ( parentAssociation.isVisible( this ) && targetObserver != null ) - { - targetObserver.objectWillChange( target ); // force ui to update - fireNodeChanged(); - } - else // make sure we fetch children when we do become visible - setFetchNeeded( true ); - return; - } - else - if ( ( EOEditingContext.ObjectsChangedInEditingContextNotification - .equals( aNotification.name() ) ) - || ( EOEditingContext.EditingContextDidSaveChangesNotification - .equals( aNotification.name() ) ) ) - { - int index; - Enumeration e; - boolean didChange = false; - NSDictionary userInfo = aNotification.userInfo(); - - // if our target object was deleted - NSArray deletes = (NSArray) userInfo.objectForKey( - EOObjectStore.DeletedKey ); - if ( deletes.indexOfIdenticalObject( target ) != NSArray.NotFound ) - { + if (parentAssociation.isVisible(this) && targetObserver != null) { + targetObserver.objectWillChange(target); // force ui to update + fireNodeChanged(); + } else // make sure we fetch children when we do become visible + setFetchNeeded(true); + return; + } else if ((EOEditingContext.ObjectsChangedInEditingContextNotification.equals(aNotification.name())) + || (EOEditingContext.EditingContextDidSaveChangesNotification.equals(aNotification.name()))) { + int index; + Enumeration e; + boolean didChange = false; + NSDictionary userInfo = aNotification.userInfo(); + + // if our target object was deleted + NSArray deletes = (NSArray) userInfo.objectForKey(EOObjectStore.DeletedKey); + if (deletes.indexOfIdenticalObject(target) != NSArray.NotFound) { //System.out.println( "DisplayGroupNode.objectsInvalidatedInEditingContext: delete: " + this + " : " + aNotification.name() ); - if ( parentAssociation.isVisible( this ) && targetObserver != null ) - { - targetObserver.objectWillChange( target ); // force ui to update - fireNodeChanged(); - } - else // make sure we fetch children when we do become visible - setFetchNeeded( true ); - return; - } - - // if our target object was invalidated - NSArray invalidates = (NSArray) userInfo.objectForKey( - EOObjectStore.InvalidatedKey ); - if ( invalidates != null && - invalidates.indexOfIdenticalObject( target ) != NSArray.NotFound ) - { + if (parentAssociation.isVisible(this) && targetObserver != null) { + targetObserver.objectWillChange(target); // force ui to update + fireNodeChanged(); + } else // make sure we fetch children when we do become visible + setFetchNeeded(true); + return; + } + + // if our target object was invalidated + NSArray invalidates = (NSArray) userInfo.objectForKey(EOObjectStore.InvalidatedKey); + if (invalidates != null && invalidates.indexOfIdenticalObject(target) != NSArray.NotFound) { //System.out.println( "DisplayGroupNode.objectsInvalidatedInEditingContext: invalidate: " + this + " : " + aNotification.name() ); - if ( parentAssociation.isVisible( this ) && targetObserver != null ) - { - targetObserver.objectWillChange( target ); // force ui to update - fireNodeChanged(); - } - else // make sure we fetch children when we do become visible - setFetchNeeded( true ); - return; - } - - // if our target object was updated, set fetchNeeded plus fire changed event - NSArray updates = (NSArray) userInfo.objectForKey( - EOObjectStore.UpdatedKey ); - if ( updates.indexOfIdenticalObject( target ) != NSArray.NotFound ) - { - if ( parentAssociation.isVisible( this ) && targetObserver != null ) - { - targetObserver.objectWillChange( target ); // force ui to update - fireNodeChanged(); - if ( object() instanceof Component ) ((Component)object()).repaint(); - } - else // make sure we fetch children when we do become visible - setFetchNeeded( true ); - return; - } - } - - super.objectsInvalidatedInEditingContext( aNotification ); - - } - - // inner classes - - /** - * Private class used to force a hashmap to - * perform key comparisons by reference. - */ - private class ReferenceKey - { - private int hashCode; - private Object referent; - - public ReferenceKey( Object anObject ) - { - referent = anObject; - hashCode = anObject.hashCode(); - } - - /** - * Returns the actual key's hash code. - */ - public int hashCode() - { - return hashCode; - } - - /** - * Compares by reference. - */ - public boolean equals( Object anObject ) - { - if ( anObject instanceof ReferenceKey ) - { - return ((ReferenceKey)anObject).referent == referent; - } - return false; - } - } - - /** - * A private class to observe the target object of this node. - */ - private class TargetObserver extends EODelayedObserver - { - Reference ref; - - /** - * Pass in the display group node that will be updated - * when the target changes. - */ - public TargetObserver( DisplayGroupNode aDisplayGroup ) - { - ref = new WeakReference( aDisplayGroup ); - } - - /** - * Repopulate our display group, and calculate the deltas - * so we can broadcast appropriate events. - */ - public void subjectChanged () - { - DisplayGroupNode node = (DisplayGroupNode) ref.get(); - if ( node == null ) return; // node is null if gc'd. - //FIXME: should un-register self from observer center?? - - node.targetChanged(); - } - } + if (parentAssociation.isVisible(this) && targetObserver != null) { + targetObserver.objectWillChange(target); // force ui to update + fireNodeChanged(); + } else // make sure we fetch children when we do become visible + setFetchNeeded(true); + return; + } + + // if our target object was updated, set fetchNeeded plus fire changed event + NSArray updates = (NSArray) userInfo.objectForKey(EOObjectStore.UpdatedKey); + if (updates.indexOfIdenticalObject(target) != NSArray.NotFound) { + if (parentAssociation.isVisible(this) && targetObserver != null) { + targetObserver.objectWillChange(target); // force ui to update + fireNodeChanged(); + if (object() instanceof Component) + ((Component) object()).repaint(); + } else // make sure we fetch children when we do become visible + setFetchNeeded(true); + return; + } + } + + super.objectsInvalidatedInEditingContext(aNotification); + + } + + // inner classes + + /** + * Private class used to force a hashmap to perform key comparisons by + * reference. + */ + private class ReferenceKey { + private int hashCode; + private Object referent; + + public ReferenceKey(Object anObject) { + referent = anObject; + hashCode = anObject.hashCode(); + } + + /** + * Returns the actual key's hash code. + */ + public int hashCode() { + return hashCode; + } + + /** + * Compares by reference. + */ + public boolean equals(Object anObject) { + if (anObject instanceof ReferenceKey) { + return ((ReferenceKey) anObject).referent == referent; + } + return false; + } + } + + /** + * A private class to observe the target object of this node. + */ + private class TargetObserver extends EODelayedObserver { + Reference ref; + + /** + * Pass in the display group node that will be updated when the target changes. + */ + public TargetObserver(DisplayGroupNode aDisplayGroup) { + ref = new WeakReference(aDisplayGroup); + } + + /** + * Repopulate our display group, and calculate the deltas so we can broadcast + * appropriate events. + */ + public void subjectChanged() { + DisplayGroupNode node = (DisplayGroupNode) ref.get(); + if (node == null) + return; // node is null if gc'd. + // FIXME: should un-register self from observer center?? + + node.targetChanged(); + } + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.64 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.64 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.63 2003/06/06 14:20:07 mpowers - * getLoadedDescendants was forcing a fetch of the node it was called on. + * Revision 1.63 2003/06/06 14:20:07 mpowers getLoadedDescendants was forcing a + * fetch of the node it was called on. * - * Revision 1.62 2003/06/03 14:48:33 mpowers - * Clean-up of notification handling for updates/invalidation/etc. - * Now fetching immediately on notification if the node is visible. - * This averts the infamous IndexOutOfBoundsException that occurs - * if fetching happens during repaint, because the BasicTreeUI is - * caching the number of child nodes before painting begins. + * Revision 1.62 2003/06/03 14:48:33 mpowers Clean-up of notification handling + * for updates/invalidation/etc. Now fetching immediately on notification if the + * node is visible. This averts the infamous IndexOutOfBoundsException that + * occurs if fetching happens during repaint, because the BasicTreeUI is caching + * the number of child nodes before painting begins. * - * Revision 1.61 2003/01/18 23:33:29 mpowers - * Fixing the build. + * Revision 1.61 2003/01/18 23:33:29 mpowers Fixing the build. * - * Revision 1.60 2002/05/31 15:03:10 mpowers - * Fixes for the previous fix. Fat props to yjcheung. + * Revision 1.60 2002/05/31 15:03:10 mpowers Fixes for the previous fix. Fat + * props to yjcheung. * - * Revision 1.59 2002/05/28 15:31:36 mpowers - * Fix for updateDisplayedObjects for a subtle case where a node appears in - * the position that another node was moved from. + * Revision 1.59 2002/05/28 15:31:36 mpowers Fix for updateDisplayedObjects for + * a subtle case where a node appears in the position that another node was + * moved from. * - * Revision 1.58 2002/05/24 14:42:02 mpowers - * Prevent repeat events from firing if firing events loops back. + * Revision 1.58 2002/05/24 14:42:02 mpowers Prevent repeat events from firing + * if firing events loops back. * - * Revision 1.57 2002/04/23 19:12:28 mpowers - * Reimplemented fireEventsForChanges. Fitter and happier. + * Revision 1.57 2002/04/23 19:12:28 mpowers Reimplemented fireEventsForChanges. + * Fitter and happier. * - * Revision 1.56 2002/04/19 21:18:45 mpowers - * Removed tree event coalescing, which was causing way too many problems. - * The fireChangeEvent algorithm is way faster than before, so we should - * still be better off than before. At least now, we don't have to track - * whether the view component has encountered a particular node. + * Revision 1.56 2002/04/19 21:18:45 mpowers Removed tree event coalescing, + * which was causing way too many problems. The fireChangeEvent algorithm is way + * faster than before, so we should still be better off than before. At least + * now, we don't have to track whether the view component has encountered a + * particular node. * - * Revision 1.55 2002/04/19 20:53:22 mpowers - * Now firing event fewer events in fireEventsForChanges. + * Revision 1.55 2002/04/19 20:53:22 mpowers Now firing event fewer events in + * fireEventsForChanges. * - * Revision 1.54 2002/04/15 21:52:50 mpowers - * Tightening up TreeModelAssociation and DisplayGroupNode. - * Now only firing root structure changed once. - * Now disposing of root's children. - * Better event coalescing. + * Revision 1.54 2002/04/15 21:52:50 mpowers Tightening up TreeModelAssociation + * and DisplayGroupNode. Now only firing root structure changed once. Now + * disposing of root's children. Better event coalescing. * - * Revision 1.53 2002/04/12 20:35:20 mpowers - * Now correctly setting parent display group and data source on creation. + * Revision 1.53 2002/04/12 20:35:20 mpowers Now correctly setting parent + * display group and data source on creation. * - * Revision 1.52 2002/04/10 21:20:04 mpowers - * Better handling for tree nodes when working with editing contexts. - * Better handling for invalidation. No longer broadcasting events - * when nodes have not been "registered" in the tree. + * Revision 1.52 2002/04/10 21:20:04 mpowers Better handling for tree nodes when + * working with editing contexts. Better handling for invalidation. No longer + * broadcasting events when nodes have not been "registered" in the tree. * - * Revision 1.51 2002/04/03 20:13:36 mpowers - * Now differentiating between node instantiation caused by model expansion - * (user initiated) and by modifications to the model. - * Dispose now disposes all children. + * Revision 1.51 2002/04/03 20:13:36 mpowers Now differentiating between node + * instantiation caused by model expansion (user initiated) and by modifications + * to the model. Dispose now disposes all children. * - * Revision 1.50 2002/03/23 16:20:27 mpowers - * Optimized processRecentChanges, minimized tree events. + * Revision 1.50 2002/03/23 16:20:27 mpowers Optimized processRecentChanges, + * minimized tree events. * - * Revision 1.49 2002/03/11 03:15:06 mpowers - * Optimized processRecentChanges, minimize event firing, coalescing changes. - * Still need a better diff algorithm to avoid removing nodes. + * Revision 1.49 2002/03/11 03:15:06 mpowers Optimized processRecentChanges, + * minimize event firing, coalescing changes. Still need a better diff algorithm + * to avoid removing nodes. * - * Revision 1.48 2002/03/10 00:59:39 mpowers - * Interim version: coalesces calls to process recent changes. - * Still does not handle rearranged nodes. + * Revision 1.48 2002/03/10 00:59:39 mpowers Interim version: coalesces calls to + * process recent changes. Still does not handle rearranged nodes. * - * Revision 1.47 2002/03/09 17:33:45 mpowers - * Nodes now track their child nodes by reference, not index. + * Revision 1.47 2002/03/09 17:33:45 mpowers Nodes now track their child nodes + * by reference, not index. * - * Revision 1.46 2002/03/08 23:19:07 mpowers - * Added getParentGroup to DisplayGroupNode. + * Revision 1.46 2002/03/08 23:19:07 mpowers Added getParentGroup to + * DisplayGroupNode. * - * Revision 1.45 2002/03/06 13:04:16 mpowers - * Implemented cascading qualifiers in tree nodes. + * Revision 1.45 2002/03/06 13:04:16 mpowers Implemented cascading qualifiers in + * tree nodes. * - * Revision 1.44 2002/02/27 23:19:17 mpowers - * Refactoring of TreeAssociation to create TreeModelAssociation parent. + * Revision 1.44 2002/02/27 23:19:17 mpowers Refactoring of TreeAssociation to + * create TreeModelAssociation parent. * - * Revision 1.42 2002/02/19 22:28:46 mpowers - * DisplayGroupNodes immediately unregister themselves as editors. + * Revision 1.42 2002/02/19 22:28:46 mpowers DisplayGroupNodes immediately + * unregister themselves as editors. * - * Revision 1.41 2002/02/13 16:27:38 mpowers - * Exposing setTarget. + * Revision 1.41 2002/02/13 16:27:38 mpowers Exposing setTarget. * - * Revision 1.40 2001/11/02 20:55:46 mpowers - * Now using fixed index to send node removed events. This preserves the - * expanded state of the nodes in the corresponding jtree. + * Revision 1.40 2001/11/02 20:55:46 mpowers Now using fixed index to send node + * removed events. This preserves the expanded state of the nodes in the + * corresponding jtree. * - * Revision 1.39 2001/09/21 21:09:25 mpowers - * Exposed more fields as protected. + * Revision 1.39 2001/09/21 21:09:25 mpowers Exposed more fields as protected. * - * Revision 1.38 2001/09/19 15:36:08 mpowers - * Refined behavior for isFetched after notification handling. + * Revision 1.38 2001/09/19 15:36:08 mpowers Refined behavior for isFetched + * after notification handling. * - * Revision 1.37 2001/09/13 14:51:18 mpowers - * DisplayGroupNodes now dispose themselves and mark their parent for update - * when they receive notification that their target has been deleted. + * Revision 1.37 2001/09/13 14:51:18 mpowers DisplayGroupNodes now dispose + * themselves and mark their parent for update when they receive notification + * that their target has been deleted. * - * Revision 1.36 2001/09/10 14:10:24 mpowers - * Fix for notification handling. + * Revision 1.36 2001/09/10 14:10:24 mpowers Fix for notification handling. * - * Revision 1.35 2001/07/30 16:17:01 mpowers - * Minor code cleanup. + * Revision 1.35 2001/07/30 16:17:01 mpowers Minor code cleanup. * - * Revision 1.34 2001/07/18 22:13:39 mpowers - * getLoadedDescendants now works as advertised. - * Now correctly handling invalidateAllObjects notification. + * Revision 1.34 2001/07/18 22:13:39 mpowers getLoadedDescendants now works as + * advertised. Now correctly handling invalidateAllObjects notification. * - * Revision 1.33 2001/07/18 13:03:32 mpowers - * TreeNodes now refetch only on demand. Previously, once a node had - * been fetched, it was always refetched after an invalidate, even if - * the node was not being displayed. + * Revision 1.33 2001/07/18 13:03:32 mpowers TreeNodes now refetch only on + * demand. Previously, once a node had been fetched, it was always refetched + * after an invalidate, even if the node was not being displayed. * - * Revision 1.32 2001/06/18 14:10:28 mpowers - * Cleaned up event firing: no longer firing insert or remove events twice. + * Revision 1.32 2001/06/18 14:10:28 mpowers Cleaned up event firing: no longer + * firing insert or remove events twice. * - * Revision 1.31 2001/06/09 16:15:39 mpowers - * Revised the targetChanged scheme because oldObjects and newObjects were - * identical after the target object is invalidated. + * Revision 1.31 2001/06/09 16:15:39 mpowers Revised the targetChanged scheme + * because oldObjects and newObjects were identical after the target object is + * invalidated. * - * Revision 1.30 2001/05/21 22:17:19 mpowers - * Fix for tree out-of-synch problems when nodes are inserted. + * Revision 1.30 2001/05/21 22:17:19 mpowers Fix for tree out-of-synch problems + * when nodes are inserted. * - * Revision 1.29 2001/05/18 21:07:46 mpowers - * Playing with refresh options. + * Revision 1.29 2001/05/18 21:07:46 mpowers Playing with refresh options. * - * Revision 1.28 2001/05/14 15:25:43 mpowers - * DisplayGroupNodes now only respond to InvalidateAllObjectsInStore - * if they are already fetched. + * Revision 1.28 2001/05/14 15:25:43 mpowers DisplayGroupNodes now only respond + * to InvalidateAllObjectsInStore if they are already fetched. * - * Revision 1.27 2001/05/08 19:55:58 mpowers - * Fix for node children not refreshing after sibling was inserted. + * Revision 1.27 2001/05/08 19:55:58 mpowers Fix for node children not + * refreshing after sibling was inserted. * - * Revision 1.26 2001/05/08 18:47:34 mpowers - * Minor fixes for d3. + * Revision 1.26 2001/05/08 18:47:34 mpowers Minor fixes for d3. * - * Revision 1.25 2001/05/06 22:22:55 mpowers - * Debugging. + * Revision 1.25 2001/05/06 22:22:55 mpowers Debugging. * - * Revision 1.24 2001/05/04 14:42:58 mpowers - * Now getting stored values in KeyValueCoding. - * MasterDetail now marks dirty based on whether it's an attribute - * or relation. - * Implemented editing context marker. + * Revision 1.24 2001/05/04 14:42:58 mpowers Now getting stored values in + * KeyValueCoding. MasterDetail now marks dirty based on whether it's an + * attribute or relation. Implemented editing context marker. * - * Revision 1.23 2001/05/02 18:00:43 mpowers - * Removed debug code. + * Revision 1.23 2001/05/02 18:00:43 mpowers Removed debug code. * - * Revision 1.22 2001/05/02 17:31:20 mpowers - * DisplayGroupNode now does a better job determining when to mark its - * parent dirty. + * Revision 1.22 2001/05/02 17:31:20 mpowers DisplayGroupNode now does a better + * job determining when to mark its parent dirty. * - * Revision 1.21 2001/05/01 00:52:32 mpowers - * Implemented breadth-first traversal of tree for node. + * Revision 1.21 2001/05/01 00:52:32 mpowers Implemented breadth-first traversal + * of tree for node. * - * Revision 1.20 2001/04/26 01:15:19 mpowers - * Major clean-up of DisplayGroupNode: fitter, happier, more productive. + * Revision 1.20 2001/04/26 01:15:19 mpowers Major clean-up of DisplayGroupNode: + * fitter, happier, more productive. * - * Revision 1.19 2001/04/22 23:13:35 mpowers - * Minor bug. + * Revision 1.19 2001/04/22 23:13:35 mpowers Minor bug. * - * Revision 1.18 2001/04/22 23:05:33 mpowers - * Totally revised DisplayGroupNode so each object gets its own node - * (so the nodes are no longer fixed by index). + * Revision 1.18 2001/04/22 23:05:33 mpowers Totally revised DisplayGroupNode so + * each object gets its own node (so the nodes are no longer fixed by index). * - * Revision 1.17 2001/04/21 23:05:12 mpowers - * A fairly major revisiting. I've decided to scrap the pass-thru approach - * where every node simply represents an index and not an object. - * The next update will have each node correspond to a specific object. + * Revision 1.17 2001/04/21 23:05:12 mpowers A fairly major revisiting. I've + * decided to scrap the pass-thru approach where every node simply represents an + * index and not an object. The next update will have each node correspond to a + * specific object. * - * Revision 1.16 2001/04/13 16:37:37 mpowers - * Handling bounds checking. + * Revision 1.16 2001/04/13 16:37:37 mpowers Handling bounds checking. * - * Revision 1.15 2001/04/03 20:36:01 mpowers - * Fixed refaulting/reverting/invalidating to be self-consistent. + * Revision 1.15 2001/04/03 20:36:01 mpowers Fixed + * refaulting/reverting/invalidating to be self-consistent. * - * Revision 1.14 2001/03/27 17:45:51 mpowers - * More index bounds checking. + * Revision 1.14 2001/03/27 17:45:51 mpowers More index bounds checking. * - * Revision 1.13 2001/03/22 21:25:42 mpowers - * Fixed some nasty issues with jtree's internal state and array bounds. + * Revision 1.13 2001/03/22 21:25:42 mpowers Fixed some nasty issues with + * jtree's internal state and array bounds. * - * Revision 1.12 2001/03/19 22:18:58 mpowers - * Root node now mirrors contents of titles display group. + * Revision 1.12 2001/03/19 22:18:58 mpowers Root node now mirrors contents of + * titles display group. * - * Revision 1.11 2001/03/19 21:38:36 mpowers - * Improved redisplay after edit. Editing nodes off root now works. + * Revision 1.11 2001/03/19 21:38:36 mpowers Improved redisplay after edit. + * Editing nodes off root now works. * - * Revision 1.10 2001/03/09 22:08:38 mpowers - * Removed unused line. + * Revision 1.10 2001/03/09 22:08:38 mpowers Removed unused line. * - * Revision 1.9 2001/03/07 16:41:04 mpowers - * Now checking size of parent displayed objects array so that we don't - * get array out of bounds execeptions from isLeaf() or object() when - * those messages are called after the TreeAssociation fires a - * nodesDeleted event. I believe that JTree is mistakenly rendering - * those nodes one last time before erasing them. + * Revision 1.9 2001/03/07 16:41:04 mpowers Now checking size of parent + * displayed objects array so that we don't get array out of bounds execeptions + * from isLeaf() or object() when those messages are called after the + * TreeAssociation fires a nodesDeleted event. I believe that JTree is + * mistakenly rendering those nodes one last time before erasing them. * - * Revision 1.8 2001/03/06 23:21:27 mpowers - * Now only notifying parent if the object is not registered in the - * editing context, if any. + * Revision 1.8 2001/03/06 23:21:27 mpowers Now only notifying parent if the + * object is not registered in the editing context, if any. * - * Revision 1.7 2001/02/20 16:38:55 mpowers - * MasterDetailAssociations now observe their controlled display group's - * objects for changes to that the parent object will be marked as updated. - * Before, only inserts and deletes to an object's items are registered. - * Also, moved ObservableArray to package access. + * Revision 1.7 2001/02/20 16:38:55 mpowers MasterDetailAssociations now observe + * their controlled display group's objects for changes to that the parent + * object will be marked as updated. Before, only inserts and deletes to an + * object's items are registered. Also, moved ObservableArray to package access. * - * Revision 1.6 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.6 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.5 2001/01/31 17:59:52 mpowers - * Fixed isLeaf aspect of TreeAssociation. + * Revision 1.5 2001/01/31 17:59:52 mpowers Fixed isLeaf aspect of + * TreeAssociation. * - * Revision 1.4 2001/01/25 02:16:25 mpowers - * TreeAssociation now returns DisplayGroupNode.getUserObject. + * Revision 1.4 2001/01/25 02:16:25 mpowers TreeAssociation now returns + * DisplayGroupNode.getUserObject. * - * Revision 1.3 2001/01/24 18:14:40 mpowers - * Fixed problem with leaving children aspect unspecified. + * Revision 1.3 2001/01/24 18:14:40 mpowers Fixed problem with leaving children + * aspect unspecified. * - * Revision 1.2 2001/01/24 16:35:37 mpowers - * Improved documentation on TreeAssociation. - * SortOrderings are now inherited from parent nodes. - * Updates after sorting are still lost on TreeController. + * Revision 1.2 2001/01/24 16:35:37 mpowers Improved documentation on + * TreeAssociation. SortOrderings are now inherited from parent nodes. Updates + * after sorting are still lost on TreeController. * - * Revision 1.1 2001/01/24 14:17:12 mpowers - * Major revision to TreeAssociation. Can now add and remove nodes. - * DisplayGroupNode is now it's own class. + * Revision 1.1 2001/01/24 14:17:12 mpowers Major revision to TreeAssociation. + * Can now add and remove nodes. DisplayGroupNode is now it's own class. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ListAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ListAssociation.java index ec22bfd..440765c 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ListAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ListAssociation.java @@ -34,206 +34,166 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* TextAssociation binds a JList to a display group's -* list of displayable objects. Bindings are: -* <ul> -* <li>titles: a property convertable to a string for -* display in the cells of the list</li> -* </ul> -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class ListAssociation extends EOAssociation - implements ListSelectionListener, - ListDataListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - TitlesAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "items" - } ); - - protected DefaultListModel model; - - /** - * Constructor expecting a JList. Throws an - * exception if it doesn't receive one. - * Note: This sets the JList's model to a DefaultListModel. - */ - public ListAssociation ( Object anObject ) - { - super( anObject ); - model = new DefaultListModel(); - ((JList)anObject).setModel( model ); - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect. <ul> - * <li>"A" attribute: the aspect can be bound to - * an attribute.</li> - * <li>"1" to-one: the aspect can be bound to a - * property that returns a single object.</li> - * <li>"M" to-one: the aspect can be bound to a - * property that returns multiple objects.</li> - * </ul> - * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return ( anObject instanceof JList ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return TitlesAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Establishes a connection between this association - * and the controlled object. Subclasses should begin - * listening for events from their controlled object here. - */ - public void establishConnection () - { - addAsListener(); - super.establishConnection(); - populateFromDisplayGroup(); - selectFromDisplayGroup(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - removeAsListener(); - super.breakConnection(); - } - - protected void addAsListener() - { // System.out.println( "ListAssociation.addAsListener: " + ++count ); - component().getModel().addListDataListener( this ); - component().addListSelectionListener( this ); - } - - protected void removeAsListener() - { // System.out.println( "ListAssociation.removeAsListener: " + --count ); - component().getModel().removeListDataListener( this ); - component().removeListSelectionListener( this ); - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged () - { + * TextAssociation binds a JList to a display group's list of displayable + * objects. Bindings are: + * <ul> + * <li>titles: a property convertable to a string for display in the cells of + * the list</li> + * </ul> + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class ListAssociation extends EOAssociation implements ListSelectionListener, ListDataListener { + static final NSArray aspects = new NSArray(new Object[] { TitlesAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "items" }); + + protected DefaultListModel model; + + /** + * Constructor expecting a JList. Throws an exception if it doesn't receive one. + * Note: This sets the JList's model to a DefaultListModel. + */ + public ListAssociation(Object anObject) { + super(anObject); + model = new DefaultListModel(); + ((JList) anObject).setModel(model); + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + * <ul> + * <li>"A" attribute: the aspect can be bound to an attribute.</li> + * <li>"1" to-one: the aspect can be bound to a property that returns a single + * object.</li> + * <li>"M" to-one: the aspect can be bound to a property that returns multiple + * objects.</li> + * </ul> + * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return (anObject instanceof JList); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return TitlesAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Establishes a connection between this association and the controlled object. + * Subclasses should begin listening for events from their controlled object + * here. + */ + public void establishConnection() { + addAsListener(); + super.establishConnection(); + populateFromDisplayGroup(); + selectFromDisplayGroup(); + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + removeAsListener(); + super.breakConnection(); + } + + protected void addAsListener() { // System.out.println( "ListAssociation.addAsListener: " + ++count ); + component().getModel().addListDataListener(this); + component().addListSelectionListener(this); + } + + protected void removeAsListener() { // System.out.println( "ListAssociation.removeAsListener: " + --count ); + component().getModel().removeListDataListener(this); + component().removeListSelectionListener(this); + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { EODisplayGroup displayGroup; - + // titles aspect - displayGroup = displayGroupForAspect( TitlesAspect ); - if ( displayGroup != null ) - { + displayGroup = displayGroupForAspect(TitlesAspect); + if (displayGroup != null) { //System.out.println( "subjectChanged: " + //displayGroup.contentsChanged() + " : " + displayGroup.selectionChanged() //+ " : " + displayGroup.updatedObjectIndex() ); //new net.wotonomy.ui.swing.util.StackTraceInspector(); - if ( displayGroup.contentsChanged() ) - { + if (displayGroup.contentsChanged()) { populateFromDisplayGroup(); } - if ( displayGroup.selectionChanged() ) - { + if (displayGroup.selectionChanged()) { selectFromDisplayGroup(); } } - } - - private void populateFromDisplayGroup() - { + } + + private void populateFromDisplayGroup() { JList component = component(); - EODisplayGroup displayGroup = displayGroupForAspect( TitlesAspect ); - String key = displayGroupKeyForAspect( TitlesAspect ); - + EODisplayGroup displayGroup = displayGroupForAspect(TitlesAspect); + String key = displayGroupKeyForAspect(TitlesAspect); + removeAsListener(); - + // remember selection int[] selectedIndices = component().getSelectedIndices(); - + // clear the model model.removeAllElements(); @@ -241,128 +201,111 @@ public class ListAssociation extends EOAssociation Object value; int size = displayGroup.displayedObjects().count(); //System.out.println( "populateFromDisplayGroup: " + size ); - for ( int i = 0; i < size; i++ ) - { - value = displayGroup.valueForObjectAtIndex( i, key ); - if ( value == null ) value = "[null]"; - model.addElement( value ); + for (int i = 0; i < size; i++) { + value = displayGroup.valueForObjectAtIndex(i, key); + if (value == null) + value = "[null]"; + model.addElement(value); } - + // select the same indexes - for ( int i = 0; i < selectedIndices.length; i++ ) - { - component.addSelectionInterval( - selectedIndices[i], selectedIndices[i] ); // adds one row + for (int i = 0; i < selectedIndices.length; i++) { + component.addSelectionInterval(selectedIndices[i], selectedIndices[i]); // adds one row } addAsListener(); } - - private void selectFromDisplayGroup() - { + + private void selectFromDisplayGroup() { JList component = component(); - EODisplayGroup displayGroup = displayGroupForAspect( TitlesAspect ); + EODisplayGroup displayGroup = displayGroupForAspect(TitlesAspect); removeAsListener(); - + int index; component.clearSelection(); - Enumeration e = - displayGroup.selectionIndexes().objectEnumerator(); - - while ( e.hasMoreElements() ) - { // add selections one-by-one to support non-contiguous - index = ((Number)e.nextElement()).intValue(); - component.addSelectionInterval( - index, index ); // adds one row + Enumeration e = displayGroup.selectionIndexes().objectEnumerator(); + + while (e.hasMoreElements()) { // add selections one-by-one to support non-contiguous + index = ((Number) e.nextElement()).intValue(); + component.addSelectionInterval(index, index); // adds one row } - + addAsListener(); } - + // interface ListSelectionListener - public void valueChanged(ListSelectionEvent e) - { - final EODisplayGroup displayGroup = - displayGroupForAspect( TitlesAspect ); - if ( ( displayGroup != null ) && ( ! e.getValueIsAdjusting() ) ) - { - int[] selectedIndices = component().getSelectedIndices(); + public void valueChanged(ListSelectionEvent e) { + final EODisplayGroup displayGroup = displayGroupForAspect(TitlesAspect); + if ((displayGroup != null) && (!e.getValueIsAdjusting())) { + int[] selectedIndices = component().getSelectedIndices(); final NSMutableArray indexList = new NSMutableArray(); - for ( int i = 0; i < selectedIndices.length; i++ ) - { - indexList.addObject( new Integer( selectedIndices[i] ) ); + for (int i = 0; i < selectedIndices.length; i++) { + indexList.addObject(new Integer(selectedIndices[i])); } - - // invoke later so the component is repainted before - // any potentially lengthy second-order effects happen: - // this improves user-perceived responsiveness of big apps - SwingUtilities.invokeLater( new Runnable() { - public void run() - { - displayGroup.setSelectionIndexes( indexList ); - } - }); - } - } - - // interface ListDataListener - - public void intervalAdded(ListDataEvent e) - { - // System.out.println( "intervalAdded" ); - contentsChanged(e); - } - public void intervalRemoved(ListDataEvent e) - { - // System.out.println( "intervalRemoved" ); - contentsChanged(e); - } - public void contentsChanged(ListDataEvent e) - { - // System.out.println( "contentsChanged" ); - - // if we were editing a property, + + // invoke later so the component is repainted before + // any potentially lengthy second-order effects happen: + // this improves user-perceived responsiveness of big apps + SwingUtilities.invokeLater(new Runnable() { + public void run() { + displayGroup.setSelectionIndexes(indexList); + } + }); + } + } + + // interface ListDataListener + + public void intervalAdded(ListDataEvent e) { + // System.out.println( "intervalAdded" ); + contentsChanged(e); + } + + public void intervalRemoved(ListDataEvent e) { + // System.out.println( "intervalRemoved" ); + contentsChanged(e); + } + + public void contentsChanged(ListDataEvent e) { + // System.out.println( "contentsChanged" ); + + // if we were editing a property, // we'd notify our display group now. - } - + } + // convenience - private JList component() - { - return (JList) object(); - } + private JList component() { + return (JList) object(); + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.5 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.5 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.4 2002/05/15 14:05:55 mpowers - * Now appropriately selectingFromDisplayGroup on establishConnection. + * Revision 1.4 2002/05/15 14:05:55 mpowers Now appropriately + * selectingFromDisplayGroup on establishConnection. * - * Revision 1.3 2001/09/14 13:40:26 mpowers - * User-initiated selection changes are now handled on the next event loop - * so that the component repaints the new selection before any potentially - * lengthy logic is triggered by the selection change. + * Revision 1.3 2001/09/14 13:40:26 mpowers User-initiated selection changes are + * now handled on the next event loop so that the component repaints the new + * selection before any potentially lengthy logic is triggered by the selection + * change. * - * Revision 1.2 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.2 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.1.1.1 2000/12/21 15:48:49 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:48:49 mpowers Contributing wotonomy. * - * Revision 1.5 2000/12/20 16:25:41 michael - * Added log to all files. + * Revision 1.5 2000/12/20 16:25:41 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/MutableDisplayGroupNode.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/MutableDisplayGroupNode.java index f1568ec..5293bc9 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/MutableDisplayGroupNode.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/MutableDisplayGroupNode.java @@ -32,185 +32,143 @@ import net.wotonomy.foundation.internal.WotonomyException; import net.wotonomy.ui.EODisplayGroup; /** -* A DisplayGroupNode that exposes the MutableTreeNode interface. -* This was required so that other subclasses of DisplayGroupNode -* could opt out of supporting MutableTreeNode (so that they can -* implement IlvActivity, for example). -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ - public class MutableDisplayGroupNode - extends DisplayGroupNode implements MutableTreeNode - { - /** - * Constructor for all nodes. - * Root node must have a null delegate. - */ - public MutableDisplayGroupNode( - TreeModelAssociation aParentAssociation, - EODisplayGroup aParentGroup, - Object anObject ) - { - super( aParentAssociation, aParentGroup, anObject ); - } - - public int getIndex(TreeNode node) - { - return getIndex( (DisplayGroupNode) node ); - } - - public TreeNode getChildAt(int childIndex) - { - return (TreeNode) getChildNodeAt( childIndex ); - } - - public TreeNode getParent() - { - Object parent = getParentGroup(); - if ( parent instanceof TreeNode ) - { - return (TreeNode) parent; - } - return null; - } - - public void insert(MutableTreeNode aChild, int anIndex) - { - if ( aChild instanceof DisplayGroupNode ) - { - insertObjectAtIndex( - ((DisplayGroupNode)aChild).object(), anIndex ); - } - else // not a display group node - { - throw new WotonomyException( - "Cannot insert nodes of type: " + aChild ); - } - } - - /** - * Removes the node at the index corresponding - * to the index of the object. - */ - public void remove(MutableTreeNode node) - { - if ( node instanceof DisplayGroupNode ) - { - remove((DisplayGroupNode)node); - } - else // not a display group node - { - throw new WotonomyException( - "Cannot insert nodes of type: " + node ); - } - } - - /** - * Removes the value in the parent display group - * at the index that corresponds to the index of this node - * and add it to the end of the display group that corresponds - * to the user value of the specified node. - */ - public void setParent(MutableTreeNode newParent) - { - if ( newParent instanceof DisplayGroupNode ) - { - setParent((DisplayGroupNode)newParent); - } - else // not a display group node - { - throw new WotonomyException( - "Cannot set parent to nodes of type: " + newParent ); - } - } - - /** - * Overridden to remember expanded state for nodes - * after nodes have been rearranged. - */ - protected void fireEventsForChanges( - Object[] oldObjects, Object[] newObjects ) - { - if ( !( parentAssociation.object() instanceof JTree ) ) - { - super.fireEventsForChanges( oldObjects, newObjects ); - return; - } - - JTree tree = (JTree) parentAssociation.object(); - Map expansionMap = new HashMap(); - DisplayGroupNode node; - TreePath path; - for ( int i = 0; i < oldObjects.length; i++ ) - { - node = (DisplayGroupNode) - getChildNodeForObject( oldObjects[i] ); - if ( node != null && ! node.isLeaf() ) - { - expansionMap.put( node, new Boolean( - tree.isExpanded( node.treePath() ) ) ); - } - } - - super.fireEventsForChanges( oldObjects, newObjects ); - - Object value; - Iterator iterator = new LinkedList( childNodes.values() ).iterator(); - while ( iterator.hasNext() ) - { - node = (DisplayGroupNode) iterator.next(); - value = expansionMap.get( node ); - if ( value != null ) - { - if ( Boolean.TRUE.equals( value ) ) - { - tree.expandPath( node.treePath() ); - } - else - { - tree.collapsePath( node.treePath() ); - } - } - } - } - } + * A DisplayGroupNode that exposes the MutableTreeNode interface. This was + * required so that other subclasses of DisplayGroupNode could opt out of + * supporting MutableTreeNode (so that they can implement IlvActivity, for + * example). + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class MutableDisplayGroupNode extends DisplayGroupNode implements MutableTreeNode { + /** + * Constructor for all nodes. Root node must have a null delegate. + */ + public MutableDisplayGroupNode(TreeModelAssociation aParentAssociation, EODisplayGroup aParentGroup, + Object anObject) { + super(aParentAssociation, aParentGroup, anObject); + } + + public int getIndex(TreeNode node) { + return getIndex((DisplayGroupNode) node); + } + + public TreeNode getChildAt(int childIndex) { + return (TreeNode) getChildNodeAt(childIndex); + } + + public TreeNode getParent() { + Object parent = getParentGroup(); + if (parent instanceof TreeNode) { + return (TreeNode) parent; + } + return null; + } + + public void insert(MutableTreeNode aChild, int anIndex) { + if (aChild instanceof DisplayGroupNode) { + insertObjectAtIndex(((DisplayGroupNode) aChild).object(), anIndex); + } else // not a display group node + { + throw new WotonomyException("Cannot insert nodes of type: " + aChild); + } + } + + /** + * Removes the node at the index corresponding to the index of the object. + */ + public void remove(MutableTreeNode node) { + if (node instanceof DisplayGroupNode) { + remove((DisplayGroupNode) node); + } else // not a display group node + { + throw new WotonomyException("Cannot insert nodes of type: " + node); + } + } + + /** + * Removes the value in the parent display group at the index that corresponds + * to the index of this node and add it to the end of the display group that + * corresponds to the user value of the specified node. + */ + public void setParent(MutableTreeNode newParent) { + if (newParent instanceof DisplayGroupNode) { + setParent((DisplayGroupNode) newParent); + } else // not a display group node + { + throw new WotonomyException("Cannot set parent to nodes of type: " + newParent); + } + } + + /** + * Overridden to remember expanded state for nodes after nodes have been + * rearranged. + */ + protected void fireEventsForChanges(Object[] oldObjects, Object[] newObjects) { + if (!(parentAssociation.object() instanceof JTree)) { + super.fireEventsForChanges(oldObjects, newObjects); + return; + } + + JTree tree = (JTree) parentAssociation.object(); + Map expansionMap = new HashMap(); + DisplayGroupNode node; + TreePath path; + for (int i = 0; i < oldObjects.length; i++) { + node = (DisplayGroupNode) getChildNodeForObject(oldObjects[i]); + if (node != null && !node.isLeaf()) { + expansionMap.put(node, new Boolean(tree.isExpanded(node.treePath()))); + } + } + + super.fireEventsForChanges(oldObjects, newObjects); + + Object value; + Iterator iterator = new LinkedList(childNodes.values()).iterator(); + while (iterator.hasNext()) { + node = (DisplayGroupNode) iterator.next(); + value = expansionMap.get(node); + if (value != null) { + if (Boolean.TRUE.equals(value)) { + tree.expandPath(node.treePath()); + } else { + tree.collapsePath(node.treePath()); + } + } + } + } +} /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.8 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.8 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.7 2002/04/23 19:12:28 mpowers - * Reimplemented fireEventsForChanges. Fitter and happier. + * Revision 1.7 2002/04/23 19:12:28 mpowers Reimplemented fireEventsForChanges. + * Fitter and happier. * - * Revision 1.6 2002/04/10 21:20:04 mpowers - * Better handling for tree nodes when working with editing contexts. - * Better handling for invalidation. No longer broadcasting events - * when nodes have not been "registered" in the tree. + * Revision 1.6 2002/04/10 21:20:04 mpowers Better handling for tree nodes when + * working with editing contexts. Better handling for invalidation. No longer + * broadcasting events when nodes have not been "registered" in the tree. * - * Revision 1.5 2002/04/03 20:01:47 mpowers - * Now remembers expanded state. + * Revision 1.5 2002/04/03 20:01:47 mpowers Now remembers expanded state. * - * Revision 1.4 2002/03/08 23:19:07 mpowers - * Added getParentGroup to DisplayGroupNode. + * Revision 1.4 2002/03/08 23:19:07 mpowers Added getParentGroup to + * DisplayGroupNode. * - * Revision 1.3 2002/02/27 23:19:17 mpowers - * Refactoring of TreeAssociation to create TreeModelAssociation parent. + * Revision 1.3 2002/02/27 23:19:17 mpowers Refactoring of TreeAssociation to + * create TreeModelAssociation parent. * - * Revision 1.2 2001/04/22 23:05:33 mpowers - * Totally revised DisplayGroupNode so each object gets its own node - * (so the nodes are no longer fixed by index). + * Revision 1.2 2001/04/22 23:05:33 mpowers Totally revised DisplayGroupNode so + * each object gets its own node (so the nodes are no longer fixed by index). * - * Revision 1.1 2001/04/21 23:05:56 mpowers - * Contributing the tree-specific concrete subclass of DisplayGroupNode. + * Revision 1.1 2001/04/21 23:05:56 mpowers Contributing the tree-specific + * concrete subclass of DisplayGroupNode. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/NotificationInspector.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/NotificationInspector.java index d105d89..4f65672 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/NotificationInspector.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/NotificationInspector.java @@ -49,285 +49,252 @@ import net.wotonomy.ui.swing.util.WindowUtilities; import net.wotonomy.control.internal.Surrogate; /** -* The NotificationInspector displays a JFrame that -* displays notifications as they occur. <br><br> -* -* @author michael@mpowers.net -* @version $Revision: 904 $ -*/ - -public class NotificationInspector - implements MouseListener, ActionListener -{ - protected JTable table; - protected JPopupMenu popupMenu; - protected EODisplayGroup displayGroup; - - // key command to copy contents to clipboard - static public final String COPY = "COPY"; - - protected static final String CLEAR_ALL = "Clear All"; - protected static final String CLEAR_SELECTED = "Clear Selected"; - - -/** -* Displays all notifications on the default notification center. -*/ - public NotificationInspector() - { - this( NSNotificationCenter.defaultCenter() ); - } - -/** -* Displays all notifications from the specified notification center. -*/ - public NotificationInspector( NSNotificationCenter aCenter ) - { - this( aCenter, null, null ); - } - -/** -* Displays notifications from the default notification center -* using the specified name and object filters. -*/ - public NotificationInspector( String notificationName, Object anObject ) - { - this( NSNotificationCenter.defaultCenter(), - notificationName, anObject ); - } + * The NotificationInspector displays a JFrame that displays notifications as + * they occur. <br> + * <br> + * + * @author michael@mpowers.net + * @version $Revision: 904 $ + */ -/** -* Displays notifications on the specified notification center -* using the specified name and object filters. -*/ - public NotificationInspector( NSNotificationCenter aCenter, - String notificationName, Object anObject ) - { - // show stack traces - NSNotification.showStack = true; - - // register for notifications - NSSelector handleNotification = - new NSSelector( "handleNotification", - new Class[] { NSNotification.class } ); - aCenter.addObserver( - this, handleNotification, notificationName, anObject ); - - table = new JTable(); - - popupMenu = new JPopupMenu(); - JMenuItem menuItem = popupMenu.add( CLEAR_SELECTED ); - menuItem.addActionListener( this ); - menuItem = popupMenu.add( CLEAR_ALL ); - menuItem.addActionListener( this ); - - displayGroup = new EODisplayGroup(); - - TableColumn column; - TableColumnAssociation assoc; - - column = new TableColumn(); - column.setHeaderValue( "Time" ); - column.setCellRenderer( new FormattedCellRenderer( new SimpleDateFormat( "hh:mm:ss:SS" ) ) ); - column.setPreferredWidth( 90 ); - column.setMaxWidth( 90 ); - - assoc = new TableColumnAssociation( column ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "time" ); - assoc.setTable( table ); - assoc.establishConnection(); - - column = new TableColumn(); - column.setHeaderValue( "Type" ); - - assoc = new TableColumnAssociation( column ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "name" ); - assoc.setTable( table ); - assoc.establishConnection(); - - column = new TableColumn(); - column.setHeaderValue( "Object" ); - - assoc = new TableColumnAssociation( column ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "objectString" ); - assoc.setTable( table ); - assoc.establishConnection(); - - column = new TableColumn(); - column.setHeaderValue( "Info" ); - - assoc = new TableColumnAssociation( column ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "userInfoString" ); - assoc.setTable( table ); - assoc.establishConnection(); - - initLayout(); - } - - protected void initLayout() - { - table.addMouseListener( this ); // listen for double-clicks - - JPanel panel = new JPanel(); - panel.setBorder( new EmptyBorder( new Insets( 10, 10, 10, 10 ) ) ); - panel.setLayout( new BorderLayout( 10, 10 ) ); - - JScrollPane scrollPane = new JScrollPane( table ); - //scrollPane.setPreferredSize( new Dimension( 500, 250 ) ); - panel.add( scrollPane, BorderLayout.CENTER ); - - JFrame window = new JFrame(); - window.setTitle( "Notification Inspector" ); - window.getContentPane().add( panel ); - - //window.pack(); - window.setSize( 800, 400 ); - WindowUtilities.cascade( window ); - - // size the columns. +public class NotificationInspector implements MouseListener, ActionListener { + protected JTable table; + protected JPopupMenu popupMenu; + protected EODisplayGroup displayGroup; + + // key command to copy contents to clipboard + static public final String COPY = "COPY"; + + protected static final String CLEAR_ALL = "Clear All"; + protected static final String CLEAR_SELECTED = "Clear Selected"; + + /** + * Displays all notifications on the default notification center. + */ + public NotificationInspector() { + this(NSNotificationCenter.defaultCenter()); + } + + /** + * Displays all notifications from the specified notification center. + */ + public NotificationInspector(NSNotificationCenter aCenter) { + this(aCenter, null, null); + } + + /** + * Displays notifications from the default notification center using the + * specified name and object filters. + */ + public NotificationInspector(String notificationName, Object anObject) { + this(NSNotificationCenter.defaultCenter(), notificationName, anObject); + } + + /** + * Displays notifications on the specified notification center using the + * specified name and object filters. + */ + public NotificationInspector(NSNotificationCenter aCenter, String notificationName, Object anObject) { + // show stack traces + NSNotification.showStack = true; + + // register for notifications + NSSelector handleNotification = new NSSelector("handleNotification", new Class[] { NSNotification.class }); + aCenter.addObserver(this, handleNotification, notificationName, anObject); + + table = new JTable(); + + popupMenu = new JPopupMenu(); + JMenuItem menuItem = popupMenu.add(CLEAR_SELECTED); + menuItem.addActionListener(this); + menuItem = popupMenu.add(CLEAR_ALL); + menuItem.addActionListener(this); + + displayGroup = new EODisplayGroup(); + + TableColumn column; + TableColumnAssociation assoc; + + column = new TableColumn(); + column.setHeaderValue("Time"); + column.setCellRenderer(new FormattedCellRenderer(new SimpleDateFormat("hh:mm:ss:SS"))); + column.setPreferredWidth(90); + column.setMaxWidth(90); + + assoc = new TableColumnAssociation(column); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "time"); + assoc.setTable(table); + assoc.establishConnection(); + + column = new TableColumn(); + column.setHeaderValue("Type"); + + assoc = new TableColumnAssociation(column); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "name"); + assoc.setTable(table); + assoc.establishConnection(); + + column = new TableColumn(); + column.setHeaderValue("Object"); + + assoc = new TableColumnAssociation(column); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "objectString"); + assoc.setTable(table); + assoc.establishConnection(); + + column = new TableColumn(); + column.setHeaderValue("Info"); + + assoc = new TableColumnAssociation(column); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "userInfoString"); + assoc.setTable(table); + assoc.establishConnection(); + + initLayout(); + } + + protected void initLayout() { + table.addMouseListener(this); // listen for double-clicks + + JPanel panel = new JPanel(); + panel.setBorder(new EmptyBorder(new Insets(10, 10, 10, 10))); + panel.setLayout(new BorderLayout(10, 10)); + + JScrollPane scrollPane = new JScrollPane(table); + // scrollPane.setPreferredSize( new Dimension( 500, 250 ) ); + panel.add(scrollPane, BorderLayout.CENTER); + + JFrame window = new JFrame(); + window.setTitle("Notification Inspector"); + window.getContentPane().add(panel); + + // window.pack(); + window.setSize(800, 400); + WindowUtilities.cascade(window); + + // size the columns. // table.getColumnModel().getColumn( 0 ).setPreferredWidth( 100 ); - window.show(); - } - -/** -* Handles the notification. -*/ - public void handleNotification( NSNotification aNotification ) - { - Surrogate s = new Surrogate( new Object[] { aNotification } ); - s.directPut( "time", new Date() ); - s.directPut( "objectString", ""+aNotification.object() ); // snapshot of state - s.directPut( "userInfoString", ""+aNotification.userInfo() ); // snapshot of info - displayGroup.insertObjectAtIndex( s, 0 ); - } - - // interface ActionListener - - /** - * Method used to listen for the action event from the popup menu items. - */ - public void actionPerformed( ActionEvent e ) - { - if ( CLEAR_SELECTED.equals( e.getActionCommand() ) ) - { - NSMutableArray objects = new NSMutableArray( displayGroup.allObjects() ); - objects.removeAll( displayGroup.selectedObjects() ); - displayGroup.setObjectArray( objects ); - displayGroup.updateDisplayedObjects(); - } - else if ( CLEAR_ALL.equals( e.getActionCommand() ) ) - { - displayGroup.setObjectArray( null ); - displayGroup.updateDisplayedObjects(); - } - } - - // interface MouseListener - - /** - * Double click to launch object inspector. - */ - - public void mouseClicked(MouseEvent e) - { - if ( e.getSource() == table ) - { - if ( e.getClickCount() > 1 ) - { - int row = table.rowAtPoint( e.getPoint() ); - int col = table.columnAtPoint( e.getPoint() ); - col = table.convertColumnIndexToModel( col ); - - if ( row == -1 ) return; - - if ( col == 0 ) // time - { - new StackTraceInspector( ( (NSNotification) ( (Surrogate) - displayGroup.displayedObjects().objectAtIndex( row ) ).getDelegate() ).stackTrace() ); - } - else - if ( col == 2 ) // object - { - new ObjectInspector( ( (NSNotification) ( (Surrogate) - displayGroup.displayedObjects().objectAtIndex( row ) ).getDelegate() ).object() ); - } - else - if ( col == 3 ) // info - { - new ObjectInspector( ( (NSNotification) ( (Surrogate) - displayGroup.displayedObjects().objectAtIndex( row ) ).getDelegate() ).userInfo() ); - } - else - { - new ObjectInspector( ( (Surrogate) - displayGroup.displayedObjects().objectAtIndex( row ) ).getDelegate() ); - } - } - else - { - // Click count is 1 then, check for popup trigger. - if ( e.isPopupTrigger() ) - { - popupMenu.show( table, e.getX(), e.getY() ); - } - } - } - } - - public void mouseReleased(MouseEvent e) - { - if ( e.getSource() == table ) - { - if ( e.isPopupTrigger() && ( e.getClickCount() == 1 ) ) - { - popupMenu.show( table, e.getX(), e.getY() ); - } - } - } - - public void mousePressed(MouseEvent e) - { - if ( e.getSource() == table ) - { - if ( e.isPopupTrigger() && ( e.getClickCount() == 1 ) ) - { - popupMenu.show( table, e.getX(), e.getY() ); - } - } - } - - public void mouseEntered(MouseEvent e) {} - public void mouseExited(MouseEvent e) {} + window.show(); + } + + /** + * Handles the notification. + */ + public void handleNotification(NSNotification aNotification) { + Surrogate s = new Surrogate(new Object[] { aNotification }); + s.directPut("time", new Date()); + s.directPut("objectString", "" + aNotification.object()); // snapshot of state + s.directPut("userInfoString", "" + aNotification.userInfo()); // snapshot of info + displayGroup.insertObjectAtIndex(s, 0); + } + + // interface ActionListener + + /** + * Method used to listen for the action event from the popup menu items. + */ + public void actionPerformed(ActionEvent e) { + if (CLEAR_SELECTED.equals(e.getActionCommand())) { + NSMutableArray objects = new NSMutableArray(displayGroup.allObjects()); + objects.removeAll(displayGroup.selectedObjects()); + displayGroup.setObjectArray(objects); + displayGroup.updateDisplayedObjects(); + } else if (CLEAR_ALL.equals(e.getActionCommand())) { + displayGroup.setObjectArray(null); + displayGroup.updateDisplayedObjects(); + } + } + + // interface MouseListener + + /** + * Double click to launch object inspector. + */ + + public void mouseClicked(MouseEvent e) { + if (e.getSource() == table) { + if (e.getClickCount() > 1) { + int row = table.rowAtPoint(e.getPoint()); + int col = table.columnAtPoint(e.getPoint()); + col = table.convertColumnIndexToModel(col); + + if (row == -1) + return; + + if (col == 0) // time + { + new StackTraceInspector( + ((NSNotification) ((Surrogate) displayGroup.displayedObjects().objectAtIndex(row)) + .getDelegate()).stackTrace()); + } else if (col == 2) // object + { + new ObjectInspector( + ((NSNotification) ((Surrogate) displayGroup.displayedObjects().objectAtIndex(row)) + .getDelegate()).object()); + } else if (col == 3) // info + { + new ObjectInspector( + ((NSNotification) ((Surrogate) displayGroup.displayedObjects().objectAtIndex(row)) + .getDelegate()).userInfo()); + } else { + new ObjectInspector(((Surrogate) displayGroup.displayedObjects().objectAtIndex(row)).getDelegate()); + } + } else { + // Click count is 1 then, check for popup trigger. + if (e.isPopupTrigger()) { + popupMenu.show(table, e.getX(), e.getY()); + } + } + } + } + + public void mouseReleased(MouseEvent e) { + if (e.getSource() == table) { + if (e.isPopupTrigger() && (e.getClickCount() == 1)) { + popupMenu.show(table, e.getX(), e.getY()); + } + } + } + + public void mousePressed(MouseEvent e) { + if (e.getSource() == table) { + if (e.isPopupTrigger() && (e.getClickCount() == 1)) { + popupMenu.show(table, e.getX(), e.getY()); + } + } + } + + public void mouseEntered(MouseEvent e) { + } + + public void mouseExited(MouseEvent e) { + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:23 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:23 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.6 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.6 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.5 2002/11/18 22:11:51 mpowers - * rglista's long-overdue enhancements: can now clear the display! + * Revision 1.5 2002/11/18 22:11:51 mpowers rglista's long-overdue enhancements: + * can now clear the display! * - * Revision 1.4 2002/10/24 18:19:03 mpowers - * Now telling NSNotification to generate stack traces. + * Revision 1.4 2002/10/24 18:19:03 mpowers Now telling NSNotification to + * generate stack traces. * - * Revision 1.3 2001/04/29 22:02:45 mpowers - * Work on id transposing between editing contexts. + * Revision 1.3 2001/04/29 22:02:45 mpowers Work on id transposing between + * editing contexts. * - * Revision 1.2 2001/04/09 21:40:25 mpowers - * Numerous usability enhancements. + * Revision 1.2 2001/04/09 21:40:25 mpowers Numerous usability enhancements. * - * Revision 1.1 2001/04/08 20:58:45 mpowers - * Contributing notification inspector. + * Revision 1.1 2001/04/08 20:58:45 mpowers Contributing notification inspector. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/RadioPanelAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/RadioPanelAssociation.java index d2e51ed..b287691 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/RadioPanelAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/RadioPanelAssociation.java @@ -28,430 +28,343 @@ import net.wotonomy.ui.EODisplayGroup; import net.wotonomy.ui.swing.components.RadioButtonPanel; /** -* RadioPanelAssociation binds RadioButtonPanels to -* display groups. It works exactly like a -* ComboBoxAssociation. Bindings are: -* <ul> -* -* <li>value: a property of the selected object in the -* display group that will be bind to the item the user -* selects or the text that the user enters in the field.</li> -* -* <li>titles: a property of the objects in the bound -* display group that will appear in the list. If the -* objects aspect is not bound, this property is also -* used to populate the value binding.</li> -* -* <li>objects: optional - if specified, when the user -* selects an title in the list, the property of the -* object at the corresponding index of the bound display -* group will be used to populate the value binding.</li> -* -* <li>enabled: a boolean property of the selected object in the -* display group that determines whether -* the user can edit the field.</li> -* -* </ul> -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class RadioPanelAssociation extends EOAssociation - implements ActionListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - TitlesAspect, ValueAspect, - ObjectsAspect, EnabledAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "text" - } ); - - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - */ - public RadioPanelAssociation ( Object anObject ) - { - super( anObject ); - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect. <ul> - * <li>"A" attribute: the aspect can be bound to - * an attribute.</li> - * <li>"1" to-one: the aspect can be bound to a - * property that returns a single object.</li> - * <li>"M" to-one: the aspect can be bound to a - * property that returns multiple objects.</li> - * </ul> - * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return ( anObject instanceof RadioButtonPanel ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return ValueAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Establishes a connection between this association - * and the controlled object. Subclasses should begin - * listening for events from their controlled object here. - */ - public void establishConnection () - { + * RadioPanelAssociation binds RadioButtonPanels to display groups. It works + * exactly like a ComboBoxAssociation. Bindings are: + * <ul> + * + * <li>value: a property of the selected object in the display group that will + * be bind to the item the user selects or the text that the user enters in the + * field.</li> + * + * <li>titles: a property of the objects in the bound display group that will + * appear in the list. If the objects aspect is not bound, this property is also + * used to populate the value binding.</li> + * + * <li>objects: optional - if specified, when the user selects an title in the + * list, the property of the object at the corresponding index of the bound + * display group will be used to populate the value binding.</li> + * + * <li>enabled: a boolean property of the selected object in the display group + * that determines whether the user can edit the field.</li> + * + * </ul> + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class RadioPanelAssociation extends EOAssociation implements ActionListener { + static final NSArray aspects = new NSArray( + new Object[] { TitlesAspect, ValueAspect, ObjectsAspect, EnabledAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature, + AttributeToOneAspectSignature, AttributeToOneAspectSignature, AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "text" }); + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. + */ + public RadioPanelAssociation(Object anObject) { + super(anObject); + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + * <ul> + * <li>"A" attribute: the aspect can be bound to an attribute.</li> + * <li>"1" to-one: the aspect can be bound to a property that returns a single + * object.</li> + * <li>"M" to-one: the aspect can be bound to a property that returns multiple + * objects.</li> + * </ul> + * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return (anObject instanceof RadioButtonPanel); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return ValueAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Establishes a connection between this association and the controlled object. + * Subclasses should begin listening for events from their controlled object + * here. + */ + public void establishConnection() { super.establishConnection(); // prepopulate titles - EODisplayGroup displayGroup = - displayGroupForAspect( TitlesAspect ); - if ( displayGroup != null ) - { - String key = displayGroupKeyForAspect( TitlesAspect ); - populateTitles( displayGroup, key ); + EODisplayGroup displayGroup = displayGroupForAspect(TitlesAspect); + if (displayGroup != null) { + String key = displayGroupKeyForAspect(TitlesAspect); + populateTitles(displayGroup, key); } - populateValue(); - addAsListener(); - } - - protected void addAsListener() - { - component().addActionListener( this ); + populateValue(); + addAsListener(); + } + + protected void addAsListener() { + component().addActionListener(this); } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { removeAsListener(); - super.breakConnection(); - } + super.breakConnection(); + } - protected void removeAsListener() - { - component().removeActionListener( this ); + protected void removeAsListener() { + component().removeActionListener(this); } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged () - { + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { removeAsListener(); - + RadioButtonPanel component = component(); EODisplayGroup displayGroup; String key; - + // titles aspect - displayGroup = displayGroupForAspect( TitlesAspect ); - if ( displayGroup != null ) - { + displayGroup = displayGroupForAspect(TitlesAspect); + if (displayGroup != null) { // if backing group has changed - if ( displayGroup.contentsChanged() ) - { - key = displayGroupKeyForAspect( TitlesAspect ); - populateTitles( displayGroup, key ); + if (displayGroup.contentsChanged()) { + key = displayGroupKeyForAspect(TitlesAspect); + populateTitles(displayGroup, key); } } - // value aspect - populateValue(); - + // value aspect + populateValue(); + // enabled aspect - displayGroup = displayGroupForAspect( EnabledAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( EnabledAspect ); - Object value = - displayGroup.selectedObjectValueForKey( key ); - Boolean converted = null; - if ( value != null ) - { - converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - } - if ( converted == null ) converted = Boolean.FALSE; - if ( converted.booleanValue() != component.isEnabled() ) - { - component.setEnabled( converted.booleanValue() ); - } + displayGroup = displayGroupForAspect(EnabledAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(EnabledAspect); + Object value = displayGroup.selectedObjectValueForKey(key); + Boolean converted = null; + if (value != null) { + converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + } + if (converted == null) + converted = Boolean.FALSE; + if (converted.booleanValue() != component.isEnabled()) { + component.setEnabled(converted.booleanValue()); + } } - + addAsListener(); - } - + } + /** - * Called to repopulate the title list from the - * specified display group. - */ - protected void populateTitles( - EODisplayGroup displayGroup, String key ) - { + * Called to repopulate the title list from the specified display group. + */ + protected void populateTitles(EODisplayGroup displayGroup, String key) { Object value; int count = displayGroup.displayedObjects().count(); - String[] titles = new String[ count ]; - for ( int i = 0; i < count; i++ ) - { - value = displayGroup.valueForObjectAtIndex( i, key ); - if ( value != null ) - { + String[] titles = new String[count]; + for (int i = 0; i < count; i++) { + value = displayGroup.valueForObjectAtIndex(i, key); + if (value != null) { titles[i] = value.toString(); - } - else - { - titles[i] = ""; + } else { + titles[i] = ""; } } - component().setLabels( titles ); + component().setLabels(titles); } - + /** - * Called to populate the value from the display group. - */ - protected void populateValue() - { + * Called to populate the value from the display group. + */ + protected void populateValue() { RadioButtonPanel component = component(); EODisplayGroup displayGroup; String key; // value aspect - displayGroup = displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( ValueAspect ); - component.setEnabled( - displayGroup.enabledToSetSelectedObjectValueForKey( key ) ); - - Object value = displayGroup.selectedObjectValueForKey( key ); + displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(ValueAspect); + component.setEnabled(displayGroup.enabledToSetSelectedObjectValueForKey(key)); + + Object value = displayGroup.selectedObjectValueForKey(key); // objects aspect - EODisplayGroup objectsDisplayGroup = - displayGroupForAspect( ObjectsAspect ); - if ( ( objectsDisplayGroup != null ) && ( value != null ) ) - { - String objectKey = displayGroupKeyForAspect( ObjectsAspect ); + EODisplayGroup objectsDisplayGroup = displayGroupForAspect(ObjectsAspect); + if ((objectsDisplayGroup != null) && (value != null)) { + String objectKey = displayGroupKeyForAspect(ObjectsAspect); Object match; int index = NSArray.NotFound; int count = objectsDisplayGroup.displayedObjects().count(); - for ( int i = 0; i < count; i++ ) - { - match = objectsDisplayGroup.valueForObjectAtIndex( i, objectKey ); - if ( value.equals( match ) ) - { + for (int i = 0; i < count; i++) { + match = objectsDisplayGroup.valueForObjectAtIndex(i, objectKey); + if (value.equals(match)) { index = i; } } - if ( index == NSArray.NotFound ) - { - if ( component.getValue() != null ) - { - component.setValue( null ); + if (index == NSArray.NotFound) { + if (component.getValue() != null) { + component.setValue(null); } - } - else - { + } else { String[] titles = component().getLabels(); - component.setValue( titles[ index ] ); + component.setValue(titles[index]); } - } - else - { - if ( value != null ) value = value.toString(); - component.setValue( (String) value ); + } else { + if (value != null) + value = value.toString(); + component.setValue((String) value); } } } - - /** - * Forces this association to cause the object to - * stop editing and validate the user's input. - * @return false if there were problems validating, - * or true to continue. - */ - public boolean endEditing () - { + + /** + * Forces this association to cause the object to stop editing and validate the + * user's input. + * + * @return false if there were problems validating, or true to continue. + */ + public boolean endEditing() { return writeValueToDisplayGroup(); - } - + } + /** - * Writes the value currently in the component - * to the selected object in the display group - * bound to the value aspect. - * @return false if there were problems validating, - * or true to continue. - */ - protected boolean writeValueToDisplayGroup() - { + * Writes the value currently in the component to the selected object in the + * display group bound to the value aspect. + * + * @return false if there were problems validating, or true to continue. + */ + protected boolean writeValueToDisplayGroup() { RadioButtonPanel component = component(); EODisplayGroup displayGroup; String key; - + // selected title aspect - displayGroup = displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( ValueAspect ); + displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(ValueAspect); Object value = null; // selected object aspect, if any - EODisplayGroup objectsGroup = - displayGroupForAspect( ObjectsAspect ); - if ( objectsGroup != null ) - { - String objectKey = displayGroupKeyForAspect( ObjectsAspect ); - String selectedValue = component.getValue(); - if ( selectedValue != null ) - { + EODisplayGroup objectsGroup = displayGroupForAspect(ObjectsAspect); + if (objectsGroup != null) { + String objectKey = displayGroupKeyForAspect(ObjectsAspect); + String selectedValue = component.getValue(); + if (selectedValue != null) { String[] titles = component.getLabels(); int index = -1; - for ( int i = 0; i < titles.length; i++ ) - { - if ( selectedValue.equals( titles[i] ) ) - { - index = i; + for (int i = 0; i < titles.length; i++) { + if (selectedValue.equals(titles[i])) { + index = i; } } - if ( index != -1 ) - { - value = objectsGroup - .valueForObjectAtIndex( index, objectKey ); + if (index != -1) { + value = objectsGroup.valueForObjectAtIndex(index, objectKey); } } - } - else // just use the selected item + } else // just use the selected item { - value = component.getValue(); + value = component.getValue(); } - return displayGroup.setSelectedObjectValue( value, key ); + return displayGroup.setSelectedObjectValue(value, key); } - + return false; } - - // interface ActionListener - + + // interface ActionListener + /** - * Updates object on action performed. - */ - public void actionPerformed( ActionEvent evt ) - { + * Updates object on action performed. + */ + public void actionPerformed(ActionEvent evt) { writeValueToDisplayGroup(); } - + // convenience - private RadioButtonPanel component() - { + private RadioButtonPanel component() { return (RadioButtonPanel) object(); } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.4 2004/01/28 18:34:57 mpowers - * Better handling for enabling. - * Now respecting enabledToSetSelectedObjectValueForKey from display group. + * Revision 1.4 2004/01/28 18:34:57 mpowers Better handling for enabling. Now + * respecting enabledToSetSelectedObjectValueForKey from display group. * - * Revision 1.3 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.3 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.2 2001/02/16 17:48:07 mpowers - * Populating titles or data not longer marks target object as changed. + * Revision 1.2 2001/02/16 17:48:07 mpowers Populating titles or data not longer + * marks target object as changed. * - * Revision 1.1.1.1 2000/12/21 15:48:52 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:48:52 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:41 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:41 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ReferenceInspector.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ReferenceInspector.java index 7194f23..afa5909 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ReferenceInspector.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ReferenceInspector.java @@ -43,241 +43,220 @@ import net.wotonomy.ui.swing.components.ButtonPanel; import net.wotonomy.ui.swing.util.WindowUtilities; /** -* ReferenceInspector tracks objects until they are garbage collected. -* Use it to track objects that you suspect are not being GCed. -* ReferenceInspector retains only weak references to the objects that -* it tracks, so when those weak references cannot be resolved, the -* object has been garbage collected. Note that under some GC -* implementations, adding a weak reference to an object will delay -* garbage collection for that object. -* -* @author michael@mpowers.net -* @version $Revision: 904 $ -*/ + * ReferenceInspector tracks objects until they are garbage collected. Use it to + * track objects that you suspect are not being GCed. ReferenceInspector retains + * only weak references to the objects that it tracks, so when those weak + * references cannot be resolved, the object has been garbage collected. Note + * that under some GC implementations, adding a weak reference to an object will + * delay garbage collection for that object. + * + * @author michael@mpowers.net + * @version $Revision: 904 $ + */ -public class ReferenceInspector - implements MouseListener, ActionListener -{ - protected JTable table; - protected JLabel memoryLabel; - - static protected EODisplayGroup displayGroup; - static protected JFrame window; - - /* Reference queue for cleared WeakKeys */ - private static ReferenceQueue queue = new ReferenceQueue(); - - // key command to copy contents to clipboard - static public final String COPY = "COPY"; +public class ReferenceInspector implements MouseListener, ActionListener { + protected JTable table; + protected JLabel memoryLabel; -/** -* Launches a new ReferenceInspector if one does not already exist. -*/ - public ReferenceInspector() - { - if ( window == null ) - { - table = new JTable(); - memoryLabel = new JLabel(); - - displayGroup = new EODisplayGroup(); - - TableColumn column; - TableColumnAssociation assoc; - - column = new TableColumn(); - column.setHeaderValue( "Object" ); - - assoc = new TableColumnAssociation( column ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "" ); - assoc.setTable( table ); - assoc.establishConnection(); - - column = new TableColumn(); - column.setHeaderValue( "Address" ); - column.setMaxWidth( 100 ); - - assoc = new TableColumnAssociation( column ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "identityHashCode" ); - assoc.setTable( table ); - assoc.establishConnection(); - - initLayout(); - } - window.show(); - } + static protected EODisplayGroup displayGroup; + static protected JFrame window; -/** -* Adds the specified object to the ReferenceInspector, launching -* a new ReferenceInspector if one does not already exist. -*/ - public ReferenceInspector( Object anObject ) - { - this(); - displayGroup.insertObjectAtIndex( new ExtendedWeakReference( anObject, queue ), 0 ); - window.show(); - } - - protected void initLayout() - { - table.addMouseListener( this ); // listen for double-clicks - - JPanel panel = new JPanel(); - panel.setBorder( new EmptyBorder( new Insets( 10, 10, 10, 10 ) ) ); - panel.setLayout( new BorderLayout( 10, 10 ) ); - - JScrollPane scrollPane = new JScrollPane( table ); - scrollPane.setPreferredSize( new Dimension( 500, 250 ) ); - panel.add( scrollPane, BorderLayout.CENTER ); - - ButtonPanel buttonPanel = new ButtonPanel( new String[] { "Update" } ); - buttonPanel.addActionListener( this ); - - JPanel bottomPanel = new JPanel(); - bottomPanel.setLayout( new BorderLayout() ); - bottomPanel.add( buttonPanel, BorderLayout.EAST ); - bottomPanel.add( memoryLabel, BorderLayout.CENTER ); - panel.add( bottomPanel, BorderLayout.SOUTH ); - - window = new JFrame(); - window.setTitle( "Reference Inspector" ); - window.getContentPane().add( panel ); + /* Reference queue for cleared WeakKeys */ + private static ReferenceQueue queue = new ReferenceQueue(); + + // key command to copy contents to clipboard + static public final String COPY = "COPY"; + + /** + * Launches a new ReferenceInspector if one does not already exist. + */ + public ReferenceInspector() { + if (window == null) { + table = new JTable(); + memoryLabel = new JLabel(); + + displayGroup = new EODisplayGroup(); + + TableColumn column; + TableColumnAssociation assoc; + + column = new TableColumn(); + column.setHeaderValue("Object"); + + assoc = new TableColumnAssociation(column); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, ""); + assoc.setTable(table); + assoc.establishConnection(); + + column = new TableColumn(); + column.setHeaderValue("Address"); + column.setMaxWidth(100); + + assoc = new TableColumnAssociation(column); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "identityHashCode"); + assoc.setTable(table); + assoc.establishConnection(); + + initLayout(); + } + window.show(); + } + + /** + * Adds the specified object to the ReferenceInspector, launching a new + * ReferenceInspector if one does not already exist. + */ + public ReferenceInspector(Object anObject) { + this(); + displayGroup.insertObjectAtIndex(new ExtendedWeakReference(anObject, queue), 0); + window.show(); + } + + protected void initLayout() { + table.addMouseListener(this); // listen for double-clicks + + JPanel panel = new JPanel(); + panel.setBorder(new EmptyBorder(new Insets(10, 10, 10, 10))); + panel.setLayout(new BorderLayout(10, 10)); + + JScrollPane scrollPane = new JScrollPane(table); + scrollPane.setPreferredSize(new Dimension(500, 250)); + panel.add(scrollPane, BorderLayout.CENTER); + + ButtonPanel buttonPanel = new ButtonPanel(new String[] { "Update" }); + buttonPanel.addActionListener(this); + + JPanel bottomPanel = new JPanel(); + bottomPanel.setLayout(new BorderLayout()); + bottomPanel.add(buttonPanel, BorderLayout.EAST); + bottomPanel.add(memoryLabel, BorderLayout.CENTER); + panel.add(bottomPanel, BorderLayout.SOUTH); + + window = new JFrame(); + window.setTitle("Reference Inspector"); + window.getContentPane().add(panel); // javax.swing.Timer timer = new javax.swing.Timer( 10000, this ); // timer.restart(); - - window.pack(); - WindowUtilities.cascade( window ); - window.show(); - } - - /* Remove all invalidated entries from the map, that is, remove all entries - whose keys have been discarded. This method should be invoked once by - each public mutator in this class. We don't invoke this method in - public accessors because that can lead to surprising - ConcurrentModificationExceptions. */ - private static void processQueue() - { + + window.pack(); + WindowUtilities.cascade(window); + window.show(); + } + + /* + * Remove all invalidated entries from the map, that is, remove all entries + * whose keys have been discarded. This method should be invoked once by each + * public mutator in this class. We don't invoke this method in public accessors + * because that can lead to surprising ConcurrentModificationExceptions. + */ + private static void processQueue() { // System.out.println( "ReferenceInspector.processQueue:"); - synchronized ( displayGroup ) - { - int idx; - WeakReference rk; - while ((rk = (WeakReference)queue.poll()) != null) - { + synchronized (displayGroup) { + int idx; + WeakReference rk; + while ((rk = (WeakReference) queue.poll()) != null) { // System.out.println( "ReferenceInspector.processQueue: removing object: " + rk ); - if ( rk != null ) - { - idx = displayGroup.displayedObjects().indexOfIdenticalObject( rk ); - if ( idx != NSArray.NotFound ) - { - displayGroup.deleteObjectAtIndex( idx ); - } - } - } - displayGroup.updateDisplayedObjects(); - } - } - - // interface ActionListener - - public void actionPerformed( ActionEvent evt ) - { - Runtime runtime = Runtime.getRuntime(); - runtime.gc(); - processQueue(); - - long totalMemory = runtime.totalMemory() / 1024; - long freeMemory = runtime.freeMemory() / 1024; - memoryLabel.setText( - Long.toString( totalMemory - freeMemory ) + "K / " + - Long.toString( totalMemory ) + "K" ); - } - - // interface MouseListener - - /** - * Double click to launch object inspector. - */ - - public void mouseClicked(MouseEvent e) - { - if ( e.getSource() == table ) - { - if ( e.getClickCount() > 1 ) - { - int row = table.rowAtPoint( e.getPoint() ); - int col = table.columnAtPoint( e.getPoint() ); - col = table.convertColumnIndexToModel( col ); - - if ( row == -1 ) return; - - if ( col == 0 ) // time - { - } - else - { - } - } - } - } - - public void mouseReleased(MouseEvent e) {} - public void mousePressed(MouseEvent e) {} - public void mouseEntered(MouseEvent e) {} - public void mouseExited(MouseEvent e) {} - - public class ExtendedWeakReference extends WeakReference - { - public ExtendedWeakReference(Object referent, ReferenceQueue q) - { - super( referent, q ); - } - - public String toString() - { - if ( get() != null ) - { - return get().toString(); - } - return null; - } - - public String identityHashCode() - { - if ( get() != null ) - { - return Integer.toHexString( System.identityHashCode( get() ) ); - } - return null; - } - - } + if (rk != null) { + idx = displayGroup.displayedObjects().indexOfIdenticalObject(rk); + if (idx != NSArray.NotFound) { + displayGroup.deleteObjectAtIndex(idx); + } + } + } + displayGroup.updateDisplayedObjects(); + } + } + + // interface ActionListener + + public void actionPerformed(ActionEvent evt) { + Runtime runtime = Runtime.getRuntime(); + runtime.gc(); + processQueue(); + + long totalMemory = runtime.totalMemory() / 1024; + long freeMemory = runtime.freeMemory() / 1024; + memoryLabel.setText(Long.toString(totalMemory - freeMemory) + "K / " + Long.toString(totalMemory) + "K"); + } + + // interface MouseListener + + /** + * Double click to launch object inspector. + */ + + public void mouseClicked(MouseEvent e) { + if (e.getSource() == table) { + if (e.getClickCount() > 1) { + int row = table.rowAtPoint(e.getPoint()); + int col = table.columnAtPoint(e.getPoint()); + col = table.convertColumnIndexToModel(col); + + if (row == -1) + return; + + if (col == 0) // time + { + } else { + } + } + } + } + + public void mouseReleased(MouseEvent e) { + } + + public void mousePressed(MouseEvent e) { + } + + public void mouseEntered(MouseEvent e) { + } + + public void mouseExited(MouseEvent e) { + } + + public class ExtendedWeakReference extends WeakReference { + public ExtendedWeakReference(Object referent, ReferenceQueue q) { + super(referent, q); + } + + public String toString() { + if (get() != null) { + return get().toString(); + } + return null; + } + + public String identityHashCode() { + if (get() != null) { + return Integer.toHexString(System.identityHashCode(get())); + } + return null; + } + + } } - + /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.5 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.5 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.4 2002/03/22 22:39:59 mpowers - * Now shows the window even if previously hidden. + * Revision 1.4 2002/03/22 22:39:59 mpowers Now shows the window even if + * previously hidden. * - * Revision 1.3 2001/10/02 14:22:39 mpowers - * Now shows used and heap memory usage. + * Revision 1.3 2001/10/02 14:22:39 mpowers Now shows used and heap memory + * usage. * - * Revision 1.2 2001/07/10 16:39:32 mpowers - * Removed printlns. + * Revision 1.2 2001/07/10 16:39:32 mpowers Removed printlns. * - * Revision 1.1 2001/07/10 16:32:50 mpowers - * Adding the reference inspector. + * Revision 1.1 2001/07/10 16:32:50 mpowers Adding the reference inspector. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/SliderAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/SliderAssociation.java index b836dcc..5a83240 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/SliderAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/SliderAssociation.java @@ -30,390 +30,309 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* SliderAssociation binds a JSlider component to -* a display group. Bindings are: -* <ul> -* <li>value: a property convertable to/from a string</li> -* <li>enabled: a boolean property that determines whether -* the user can select the text in the field</li> -* </ul> -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class SliderAssociation extends EOAssociation - implements ChangeListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - ValueAspect, EnabledAspect, VisibleAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "value" - } ); - - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - */ - public SliderAssociation ( Object anObject ) - { - super( anObject ); - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect. <ul> - * <li>"A" attribute: the aspect can be bound to - * an attribute.</li> - * <li>"1" to-one: the aspect can be bound to a - * property that returns a single object.</li> - * <li>"M" to-one: the aspect can be bound to a - * property that returns multiple objects.</li> - * </ul> - * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return ( anObject instanceof JSlider ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return ValueAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Establishes a connection between this association - * and the controlled object. This implementation - * attempts to add this class as an ActionListener - * and as a FocusListener to the specified object. - */ - public void establishConnection () - { - component().addChangeListener( this ); - super.establishConnection(); - + * SliderAssociation binds a JSlider component to a display group. Bindings are: + * <ul> + * <li>value: a property convertable to/from a string</li> + * <li>enabled: a boolean property that determines whether the user can select + * the text in the field</li> + * </ul> + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class SliderAssociation extends EOAssociation implements ChangeListener { + static final NSArray aspects = new NSArray(new Object[] { ValueAspect, EnabledAspect, VisibleAspect }); + static final NSArray aspectSignatures = new NSArray( + new Object[] { AttributeToOneAspectSignature, AttributeToOneAspectSignature, }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "value" }); + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. + */ + public SliderAssociation(Object anObject) { + super(anObject); + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + * <ul> + * <li>"A" attribute: the aspect can be bound to an attribute.</li> + * <li>"1" to-one: the aspect can be bound to a property that returns a single + * object.</li> + * <li>"M" to-one: the aspect can be bound to a property that returns multiple + * objects.</li> + * </ul> + * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return (anObject instanceof JSlider); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return ValueAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Establishes a connection between this association and the controlled object. + * This implementation attempts to add this class as an ActionListener and as a + * FocusListener to the specified object. + */ + public void establishConnection() { + component().addChangeListener(this); + super.establishConnection(); + // forces update from bindings subjectChanged(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - component().removeChangeListener( this ); - super.breakConnection(); - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged () - { + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + component().removeChangeListener(this); + super.breakConnection(); + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { JSlider component = component(); EODisplayGroup displayGroup; String key; Object value; - + // value aspect - displayGroup = displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( ValueAspect ); - component.setEnabled( - displayGroup.enabledToSetSelectedObjectValueForKey( key ) ); - - if ( displayGroup.selectedObjects().size() > 1 ) - { - // if there're more than one object selected, set - // the value to blank for all of them. - Object previousValue; - - Iterator indexIterator = displayGroup.selectionIndexes(). - iterator(); - - // get value for the first selected object. - int initialIndex = ( (Integer)indexIterator.next() ).intValue(); - previousValue = displayGroup.valueForObjectAtIndex( - initialIndex, key ); - value = null; - - // go through the rest of the selected objects, compare each - // value with the previous one. continue comparing if two - // values are equal, break the while loop if they're different. - // the final value will be the common value of all selected objects - // if there is one, or be blank if there is not. - while ( indexIterator.hasNext() ) - { - int index = ( (Integer)indexIterator.next() ).intValue(); - Object currentValue = displayGroup.valueForObjectAtIndex( - index, key ); - if ( currentValue != null && !currentValue.equals( previousValue ) ) - { - value = null; - break; - } - else - { - // currentValue is the same as the previous one - value = currentValue; - } - - } // end while - - } else { - - value = displayGroup.selectedObjectValueForKey( key ); - } // end checking size of displayGroup + displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(ValueAspect); + component.setEnabled(displayGroup.enabledToSetSelectedObjectValueForKey(key)); + + if (displayGroup.selectedObjects().size() > 1) { + // if there're more than one object selected, set + // the value to blank for all of them. + Object previousValue; + + Iterator indexIterator = displayGroup.selectionIndexes().iterator(); + + // get value for the first selected object. + int initialIndex = ((Integer) indexIterator.next()).intValue(); + previousValue = displayGroup.valueForObjectAtIndex(initialIndex, key); + value = null; + + // go through the rest of the selected objects, compare each + // value with the previous one. continue comparing if two + // values are equal, break the while loop if they're different. + // the final value will be the common value of all selected objects + // if there is one, or be blank if there is not. + while (indexIterator.hasNext()) { + int index = ((Integer) indexIterator.next()).intValue(); + Object currentValue = displayGroup.valueForObjectAtIndex(index, key); + if (currentValue != null && !currentValue.equals(previousValue)) { + value = null; + break; + } else { + // currentValue is the same as the previous one + value = currentValue; + } + + } // end while + + } else { + + value = displayGroup.selectedObjectValueForKey(key); + } // end checking size of displayGroup // convert value to int - value = ValueConverter.convertObjectToClass( value, Integer.class ); + value = ValueConverter.convertObjectToClass(value, Integer.class); int intValue; - if ( value == null ) - { + if (value == null) { intValue = 0; + } else { + intValue = ((Integer) value).intValue(); } - else - { - intValue = ((Integer)value).intValue(); - } - - if ( component.getValue() != intValue ) - { - component().removeChangeListener( this ); - component.setValue( intValue ); - component().addChangeListener( this ); + + if (component.getValue() != intValue) { + component().removeChangeListener(this); + component.setValue(intValue); + component().addChangeListener(this); } } // enabled aspect - displayGroup = displayGroupForAspect( EnabledAspect ); - key = displayGroupKeyForAspect( EnabledAspect ); - if ( ( displayGroup != null ) || ( key != null ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { + displayGroup = displayGroupForAspect(EnabledAspect); + key = displayGroupKeyForAspect(EnabledAspect); + if ((displayGroup != null) || (key != null)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { // treat bound key without display group as a value - value = key; + value = key; + } + Boolean converted = null; + if (value != null) { + converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + } + if (converted == null) + converted = Boolean.FALSE; + if (converted.booleanValue() != component.isEnabled()) { + component.setEnabled(converted.booleanValue()); } - Boolean converted = null; - if ( value != null ) - { - converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - } - if ( converted == null ) converted = Boolean.FALSE; - if ( converted.booleanValue() != component.isEnabled() ) - { - component.setEnabled( converted.booleanValue() ); - } } // visible aspect - displayGroup = displayGroupForAspect( VisibleAspect ); - key = displayGroupKeyForAspect( VisibleAspect ); - if ( ( displayGroup != null ) || ( key != null ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { + displayGroup = displayGroupForAspect(VisibleAspect); + key = displayGroupKeyForAspect(VisibleAspect); + if ((displayGroup != null) || (key != null)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { // treat bound key without display group as a value - value = key; + value = key; } - Boolean converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - - if ( converted != null ) - { - if ( converted.booleanValue() != component.isVisible() ) - { - component.setVisible( converted.booleanValue() ); + Boolean converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + + if (converted != null) { + if (converted.booleanValue() != component.isVisible()) { + component.setVisible(converted.booleanValue()); } - } + } } - } - - /** - * Forces this association to cause the object to - * stop editing and validate the user's input. - * @return false if there were problems validating, - * or true to continue. - */ - public boolean endEditing () - { + } + + /** + * Forces this association to cause the object to stop editing and validate the + * user's input. + * + * @return false if there were problems validating, or true to continue. + */ + public boolean endEditing() { return writeValueToDisplayGroup(); - } - + } + /** - * Writes the value currently in the component - * to the selected object in the display group - * bound to the value aspect. - * @return false if there were problems validating, - * or true to continue. - */ - protected boolean writeValueToDisplayGroup() - { - EODisplayGroup displayGroup = - displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - String key = displayGroupKeyForAspect( ValueAspect ); - Object value = new Integer( component().getValue() ); - - boolean returnValue = true; - Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); - while ( selectedIterator.hasNext() ) - { - int index = ( (Integer)selectedIterator.next() ).intValue(); - - if ( !displayGroup.setValueForObjectAtIndex( value, index, key ) ) - { - returnValue = false; - } - } - return returnValue; + * Writes the value currently in the component to the selected object in the + * display group bound to the value aspect. + * + * @return false if there were problems validating, or true to continue. + */ + protected boolean writeValueToDisplayGroup() { + EODisplayGroup displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + String key = displayGroupKeyForAspect(ValueAspect); + Object value = new Integer(component().getValue()); + + boolean returnValue = true; + Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); + while (selectedIterator.hasNext()) { + int index = ((Integer) selectedIterator.next()).intValue(); + + if (!displayGroup.setValueForObjectAtIndex(value, index, key)) { + returnValue = false; + } + } + return returnValue; } return false; } - // interface ChangeListener - + // interface ChangeListener + /** - * Updates object on change. - */ - public void stateChanged(ChangeEvent e) - { + * Updates object on change. + */ + public void stateChanged(ChangeEvent e) { writeValueToDisplayGroup(); } - - private JSlider component() - { - return (JSlider) object(); + + private JSlider component() { + return (JSlider) object(); } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.8 2004/01/28 18:34:57 mpowers - * Better handling for enabling. - * Now respecting enabledToSetSelectedObjectValueForKey from display group. + * Revision 1.8 2004/01/28 18:34:57 mpowers Better handling for enabling. Now + * respecting enabledToSetSelectedObjectValueForKey from display group. * - * Revision 1.7 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.7 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.6 2002/10/11 20:12:58 mpowers - * Updated aspect signature. + * Revision 1.6 2002/10/11 20:12:58 mpowers Updated aspect signature. * - * Revision 1.5 2002/10/11 20:08:14 mpowers - * Added visible aspect to slider association. + * Revision 1.5 2002/10/11 20:08:14 mpowers Added visible aspect to slider + * association. * - * Revision 1.4 2001/11/01 15:54:37 mpowers - * Minor update to aspect signature. + * Revision 1.4 2001/11/01 15:54:37 mpowers Minor update to aspect signature. * - * Revision 1.3 2001/07/30 16:32:55 mpowers - * Implemented support for bulk-editing. Detail associations will now - * apply changes to all selected objects. + * Revision 1.3 2001/07/30 16:32:55 mpowers Implemented support for + * bulk-editing. Detail associations will now apply changes to all selected + * objects. * - * Revision 1.2 2001/02/16 15:03:34 mpowers - * Fixed: slider sets value in display group after selection changed. + * Revision 1.2 2001/02/16 15:03:34 mpowers Fixed: slider sets value in display + * group after selection changed. * - * Revision 1.1.1.1 2000/12/21 15:48:55 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:48:55 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:41 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:41 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableAssociation.java index bc02f7d..edba674 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableAssociation.java @@ -52,754 +52,619 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* TableAssociation binds one or more TableColumnAssociations -* to a display group. You should not instantiate this class -* directly; use TableColumnAssociation.setTable() instead. -* Note that TableAssociation inserts itself as the controlled -* JTable's TableModel. -* -* Bindings are: -* <ul> -* <li>source: a property convertable to a string for -* display in the cells of the table column</li> -* <li>enabled: a property convertable to a string for -* display in the cells of the table column. -* Note that you can bind this aspect to a key equal to -* "true" or "false" and leave the display group null.</li> -* </ul> -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class TableAssociation extends EOAssociation - implements ActionListener, ListSelectionListener, FocusListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - SourceAspect, EnabledAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "tableModel", "tableHeader" - } ); - - // key command to copy contents to clipboard - static public final String COPY = "COPY"; + * TableAssociation binds one or more TableColumnAssociations to a display + * group. You should not instantiate this class directly; use + * TableColumnAssociation.setTable() instead. Note that TableAssociation inserts + * itself as the controlled JTable's TableModel. + * + * Bindings are: + * <ul> + * <li>source: a property convertable to a string for display in the cells of + * the table column</li> + * <li>enabled: a property convertable to a string for display in the cells of + * the table column. Note that you can bind this aspect to a key equal to "true" + * or "false" and leave the display group null.</li> + * </ul> + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class TableAssociation extends EOAssociation implements ActionListener, ListSelectionListener, FocusListener { + static final NSArray aspects = new NSArray(new Object[] { SourceAspect, EnabledAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "tableModel", "tableHeader" }); + + // key command to copy contents to clipboard + static public final String COPY = "COPY"; EODisplayGroup source; - EODisplayGroup sortTarget; // used by TreeColumnAssociation + EODisplayGroup sortTarget; // used by TreeColumnAssociation NSMutableArray columns; - JTableHeader tableHeader; - - boolean pleaseIgnore; - boolean selectionPaintedImmediately; - boolean selectionTracking; - - /** - * Constructor specifying the object to be controlled by this - * association. Throws an exception if the object is not - * a TableColumn. setTable() must be called before - * establishing the connection. - */ - public TableAssociation ( Object anObject ) - { - super( anObject ); + JTableHeader tableHeader; + + boolean pleaseIgnore; + boolean selectionPaintedImmediately; + boolean selectionTracking; + + /** + * Constructor specifying the object to be controlled by this association. + * Throws an exception if the object is not a TableColumn. setTable() must be + * called before establishing the connection. + */ + public TableAssociation(Object anObject) { + super(anObject); source = null; columns = new NSMutableArray(); - JTable aTable = ((JTable)anObject); - aTable.addFocusListener( this ); - aTable.setModel( - new TableAssociationModel( this ) ); - // set up keyboard events for cut-copy: Ctrl-C, Ctrl-X - - // why did sun make this harder to use? - //aTable.getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ).put( - // KeyStroke.getKeyStroke( KeyEvent.VK_C, java.awt.Event.CTRL_MASK ), COPY); - //aTable.getActionMap().put(COPY, this); - - aTable.registerKeyboardAction( this, COPY, - KeyStroke.getKeyStroke( KeyEvent.VK_C, - Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() ), - JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); - aTable.registerKeyboardAction( this, COPY, - KeyStroke.getKeyStroke( KeyEvent.VK_X, - Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() ), - JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); - tableHeader = new SortedTableHeader(); - tableHeader.setColumnModel( aTable.getColumnModel() ); - aTable.setTableHeader( tableHeader ); - tableHeader.addMouseListener( new TableHeaderListener() ); - pleaseIgnore = false; - selectionPaintedImmediately = false; - selectionTracking = false; - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect. <ul> - * <li>"A" attribute: the aspect can be bound to - * an attribute.</li> - * <li>"1" to-one: the aspect can be bound to a - * property that returns a single object.</li> - * <li>"M" to-one: the aspect can be bound to a - * property that returns multiple objects.</li> - * </ul> - * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return ( anObject instanceof JTable ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return ValueAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Binds the specified aspect of this association to the - * specified key on the specified display group. - */ - public void bindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey ) - { - if ( SourceAspect.equals( anAspect ) ) - { + JTable aTable = ((JTable) anObject); + aTable.addFocusListener(this); + aTable.setModel(new TableAssociationModel(this)); + // set up keyboard events for cut-copy: Ctrl-C, Ctrl-X + + // why did sun make this harder to use? + // aTable.getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ).put( + // KeyStroke.getKeyStroke( KeyEvent.VK_C, java.awt.Event.CTRL_MASK ), COPY); + // aTable.getActionMap().put(COPY, this); + + aTable.registerKeyboardAction(this, COPY, + KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + aTable.registerKeyboardAction(this, COPY, + KeyStroke.getKeyStroke(KeyEvent.VK_X, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + tableHeader = new SortedTableHeader(); + tableHeader.setColumnModel(aTable.getColumnModel()); + aTable.setTableHeader(tableHeader); + tableHeader.addMouseListener(new TableHeaderListener()); + pleaseIgnore = false; + selectionPaintedImmediately = false; + selectionTracking = false; + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + * <ul> + * <li>"A" attribute: the aspect can be bound to an attribute.</li> + * <li>"1" to-one: the aspect can be bound to a property that returns a single + * object.</li> + * <li>"M" to-one: the aspect can be bound to a property that returns multiple + * objects.</li> + * </ul> + * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return (anObject instanceof JTable); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return ValueAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Binds the specified aspect of this association to the specified key on the + * specified display group. + */ + public void bindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + if (SourceAspect.equals(anAspect)) { source = aDisplayGroup; } - super.bindAspect( anAspect, aDisplayGroup, aKey ); + super.bindAspect(anAspect, aDisplayGroup, aKey); } - /** - * Establishes a connection between this association - * and the controlled object. Subclasses should begin - * listening for events from their controlled object here. - */ - public void establishConnection () - { - if ( source == null ) - { - throw new WotonomyException( "No display group: " + - "please ensure that the TableColumnAssociation " + - "has a display group bound to the ValueAspect" ); - } - super.establishConnection(); - selectFromDisplayGroup(); - addAsListener(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - removeAsListener(); - super.breakConnection(); - } - - protected void addAsListener() - { - component().getSelectionModel() - .addListSelectionListener( this ); - } - - protected void removeAsListener() - { - component().getSelectionModel() - .removeListSelectionListener( this ); - } - - /** - * Forces this association to cause the object to - * stop editing and validate the user's input. - * @return false if there were problems validating, - * or true to continue. - */ - public boolean endEditing () - { - // stop any cell editing - CellEditor editor = component().getCellEditor(); - if ( editor != null ) - { - return editor.stopCellEditing(); - } - return true; - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged () - { - if ( source != null ) - { - if ( source.contentsChanged() ) - { - removeAsListener(); - ((TableAssociationModel)component().getModel()). - fireTableDataChanged(); + /** + * Establishes a connection between this association and the controlled object. + * Subclasses should begin listening for events from their controlled object + * here. + */ + public void establishConnection() { + if (source == null) { + throw new WotonomyException("No display group: " + "please ensure that the TableColumnAssociation " + + "has a display group bound to the ValueAspect"); + } + super.establishConnection(); + selectFromDisplayGroup(); + addAsListener(); + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + removeAsListener(); + super.breakConnection(); + } + + protected void addAsListener() { + component().getSelectionModel().addListSelectionListener(this); + } + + protected void removeAsListener() { + component().getSelectionModel().removeListSelectionListener(this); + } + + /** + * Forces this association to cause the object to stop editing and validate the + * user's input. + * + * @return false if there were problems validating, or true to continue. + */ + public boolean endEditing() { + // stop any cell editing + CellEditor editor = component().getCellEditor(); + if (editor != null) { + return editor.stopCellEditing(); + } + return true; + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { + if (source != null) { + if (source.contentsChanged()) { + removeAsListener(); + ((TableAssociationModel) component().getModel()).fireTableDataChanged(); selectFromDisplayGroup(); - addAsListener(); - - // if we caused this change, do nothing - if ( pleaseIgnore ) - { - pleaseIgnore = false; - } - else // otherwise, update the sort indicator - { - tableHeader.repaint(); - - // cancel any cell editing - CellEditor editor = component().getCellEditor(); - if ( editor != null ) - { - editor.cancelCellEditing(); - } - } - } - else - if ( source.selectionChanged() ) - { - removeAsListener(); + addAsListener(); + + // if we caused this change, do nothing + if (pleaseIgnore) { + pleaseIgnore = false; + } else // otherwise, update the sort indicator + { + tableHeader.repaint(); + + // cancel any cell editing + CellEditor editor = component().getCellEditor(); + if (editor != null) { + editor.cancelCellEditing(); + } + } + } else if (source.selectionChanged()) { + removeAsListener(); selectFromDisplayGroup(); - addAsListener(); + addAsListener(); } } - } + } - private void selectFromDisplayGroup() - { + private void selectFromDisplayGroup() { JTable component = component(); int index; component.getSelectionModel().clearSelection(); - Enumeration e = - source.selectionIndexes().objectEnumerator(); - - while ( e.hasMoreElements() ) - { // add selections one-by-one to support non-contiguous - index = ((Number)e.nextElement()).intValue(); - component.getSelectionModel().addSelectionInterval( - index, index ); // adds one row + Enumeration e = source.selectionIndexes().objectEnumerator(); + + while (e.hasMoreElements()) { // add selections one-by-one to support non-contiguous + index = ((Number) e.nextElement()).intValue(); + component.getSelectionModel().addSelectionInterval(index, index); // adds one row } } // interface ListSelectionListener - public void valueChanged(ListSelectionEvent e) - { - if ( source != null ) - { - if ( selectionTracking || !e.getValueIsAdjusting() ) - { - int[] selectedIndices = component().getSelectedRows(); - final NSMutableArray indexList = new NSMutableArray(); - for ( int i = 0; i < selectedIndices.length; i++ ) - { - indexList.addObject( new Integer( selectedIndices[i] ) ); - } - - // invoke later so the component is repainted before - // any potentially lengthy second-order effects happen: - // this improves user-perceived responsiveness of big apps - Runnable select = new Runnable() - { - public void run() - { - pleaseIgnore = true; - source.setSelectionIndexes( indexList ); - } - }; - if ( selectionPaintedImmediately ) - { - EventQueue.invokeLater( select ); - } - else - { - select.run(); - } - } - } - } - - /** - * Determines whether the selection should be painted - * immediately after the user clicks and therefore - * before the children display group is updated. - * When the children group is bound to many associations - * or is bound to a master-detail association, updating - * the display group can take a perceptibly long time. - * This property defaults to false. - * @see #setSelectionPaintedImmediately - */ - public boolean isSelectionPaintedImmediately() - { - return selectionPaintedImmediately; - } - - /** - * Sets whether the selection should be painted immediately. - * Setting this property to true will let the table paint - * first before the display group is updated. - */ - public void setSelectionPaintedImmediately( boolean isImmediate ) - { - selectionPaintedImmediately = isImmediate; - } - - /** - * Determines whether the selection is actively tracking - * the selection as the user moves the mouse. - * If true, selection will not be updated while the - * list selection event returns true for isValueAdjusting(). - * This property defaults to false. - * @see #setSelectionTracking - */ - public boolean isSelectionTracking() - { - return selectionTracking; - } - - /** - * Sets whether the selection is actively tracking - * the selection as the user moves the mouse. - * Setting this property to true will update the display - * group with each change to the selection. - * This means that any tree selection listers will - * also be notified before the display group is updated - * and will have to invokeLater if they want to see the - * updated display group. - */ - public void setSelectionTracking( boolean isTracking ) - { - selectionTracking = isTracking; - } - - // convenience - private JTable component() - { - return (JTable) object(); - } + public void valueChanged(ListSelectionEvent e) { + if (source != null) { + if (selectionTracking || !e.getValueIsAdjusting()) { + int[] selectedIndices = component().getSelectedRows(); + final NSMutableArray indexList = new NSMutableArray(); + for (int i = 0; i < selectedIndices.length; i++) { + indexList.addObject(new Integer(selectedIndices[i])); + } + + // invoke later so the component is repainted before + // any potentially lengthy second-order effects happen: + // this improves user-perceived responsiveness of big apps + Runnable select = new Runnable() { + public void run() { + pleaseIgnore = true; + source.setSelectionIndexes(indexList); + } + }; + if (selectionPaintedImmediately) { + EventQueue.invokeLater(select); + } else { + select.run(); + } + } + } + } /** - * Copies the contents of the table to the clipboard as a tab-delimited string. - */ - public void copyToClipboard() - { - Toolkit toolkit = Toolkit.getDefaultToolkit(); - Clipboard clipboard = toolkit.getSystemClipboard(); - StringSelection selection = - new StringSelection( getTabDelimitedString() ); - clipboard.setContents( selection, selection ); - } + * Determines whether the selection should be painted immediately after the user + * clicks and therefore before the children display group is updated. When the + * children group is bound to many associations or is bound to a master-detail + * association, updating the display group can take a perceptibly long time. + * This property defaults to false. + * + * @see #setSelectionPaintedImmediately + */ + public boolean isSelectionPaintedImmediately() { + return selectionPaintedImmediately; + } /** - * Converts the contents of the table to a tab-delimited string. - * @return A String containing the text contents of the table. - */ - public String getTabDelimitedString() - { - StringBuffer result = new StringBuffer(64); - - TableModel model = component().getModel(); - int cols = model.getColumnCount(); - int rows = model.getRowCount(); - - Object o = null; - for ( int y = 0; y < rows; y++ ) - { - for ( int x = 0; x < cols; x++ ) - { - o = model.getValueAt( y, x ); - if ( o == null ) o = ""; - result.append( o ); - result.append( '\t' ); - } - result.append( '\n' ); - } - - return result.toString(); - } - - // interface ActionEventListener - - public void actionPerformed(ActionEvent evt) - { - String cmd = evt.getActionCommand(); - - if ( COPY.equals( cmd ) ) - { - copyToClipboard(); - return; - } - } - - /** - * Used to render the little triangle over the sorted column(s). - */ - class SortedTableHeader extends JTableHeader - { - public void paint(Graphics g) - { - super.paint( g ); - Rectangle r; - TableColumnAssociation association; - int size = columns.size(); - NSArray orderings; - if ( sortTarget != null ) - { - orderings = sortTarget.sortOrderings(); - } - else - { - orderings = source.sortOrderings(); - } - for ( int i = 0; i < size; i++ ) - { - r = getHeaderRect( component().convertColumnIndexToView( i ) ); - association = (TableColumnAssociation) columns.objectAtIndex( i ); - association.drawSortIndicator( r, g, orderings ); - } - } - } - - /** - * Used to listen for clicks on the table header. - */ - class TableHeaderListener extends MouseAdapter - { - public void mouseClicked( MouseEvent evt ) - { - EODisplayGroup displayGroup = sortTarget; - if ( displayGroup == null ) displayGroup = source; - - if ( evt.getClickCount() > 0 ) - { - int columnClicked = tableHeader.columnAtPoint( evt.getPoint() ); - if ( columnClicked != -1 ) - { - columnClicked = component().convertColumnIndexToModel( columnClicked ); - TableColumnAssociation association = (TableColumnAssociation) - columns.objectAtIndex( columnClicked ); - if ( association.isSortable() ) - { - NSMutableArray newOrder = - new NSMutableArray( displayGroup.sortOrderings() ); - - // click once = asc, twice = desc, thrice = clear - EOSortOrdering sortOrdering; - int index = association.getIndexOfMatchingOrdering( newOrder ); - if ( index == -1 ) sortOrdering = null; - else if ( index == 1 ) sortOrdering = association.getSortOrdering( false ); - else sortOrdering = association.getSortOrdering( true ); - - pleaseIgnore = true; - tableHeader.repaint(); - - // stop any cell editing - CellEditor editor = component().getCellEditor(); - if ( editor != null ) - { - editor.stopCellEditing(); - } - - // remove existing key - if ( index != 0 ) - { - newOrder.removeObjectAtIndex( Math.abs( index ) - 1 ); - } - - // put new key at front of list - if ( sortOrdering != null ) - { - newOrder.insertObjectAtIndex( sortOrdering, 0 ); - } - - displayGroup.setSortOrderings( newOrder ); - displayGroup.updateDisplayedObjects(); - } - } - } - } - } + * Sets whether the selection should be painted immediately. Setting this + * property to true will let the table paint first before the display group is + * updated. + */ + public void setSelectionPaintedImmediately(boolean isImmediate) { + selectionPaintedImmediately = isImmediate; + } /** - * Notifies of beginning of edit. - */ - public void focusGained(FocusEvent evt) - { + * Determines whether the selection is actively tracking the selection as the + * user moves the mouse. If true, selection will not be updated while the list + * selection event returns true for isValueAdjusting(). This property defaults + * to false. + * + * @see #setSelectionTracking + */ + public boolean isSelectionTracking() { + return selectionTracking; + } + + /** + * Sets whether the selection is actively tracking the selection as the user + * moves the mouse. Setting this property to true will update the display group + * with each change to the selection. This means that any tree selection listers + * will also be notified before the display group is updated and will have to + * invokeLater if they want to see the updated display group. + */ + public void setSelectionTracking(boolean isTracking) { + selectionTracking = isTracking; + } + + // convenience + private JTable component() { + return (JTable) object(); + } + + /** + * Copies the contents of the table to the clipboard as a tab-delimited string. + */ + public void copyToClipboard() { + Toolkit toolkit = Toolkit.getDefaultToolkit(); + Clipboard clipboard = toolkit.getSystemClipboard(); + StringSelection selection = new StringSelection(getTabDelimitedString()); + clipboard.setContents(selection, selection); + } + + /** + * Converts the contents of the table to a tab-delimited string. + * + * @return A String containing the text contents of the table. + */ + public String getTabDelimitedString() { + StringBuffer result = new StringBuffer(64); + + TableModel model = component().getModel(); + int cols = model.getColumnCount(); + int rows = model.getRowCount(); + + Object o = null; + for (int y = 0; y < rows; y++) { + for (int x = 0; x < cols; x++) { + o = model.getValueAt(y, x); + if (o == null) + o = ""; + result.append(o); + result.append('\t'); + } + result.append('\n'); + } + + return result.toString(); + } + + // interface ActionEventListener + + public void actionPerformed(ActionEvent evt) { + String cmd = evt.getActionCommand(); + + if (COPY.equals(cmd)) { + copyToClipboard(); + return; + } + } + + /** + * Used to render the little triangle over the sorted column(s). + */ + class SortedTableHeader extends JTableHeader { + public void paint(Graphics g) { + super.paint(g); + Rectangle r; + TableColumnAssociation association; + int size = columns.size(); + NSArray orderings; + if (sortTarget != null) { + orderings = sortTarget.sortOrderings(); + } else { + orderings = source.sortOrderings(); + } + for (int i = 0; i < size; i++) { + r = getHeaderRect(component().convertColumnIndexToView(i)); + association = (TableColumnAssociation) columns.objectAtIndex(i); + association.drawSortIndicator(r, g, orderings); + } + } + } + + /** + * Used to listen for clicks on the table header. + */ + class TableHeaderListener extends MouseAdapter { + public void mouseClicked(MouseEvent evt) { + EODisplayGroup displayGroup = sortTarget; + if (displayGroup == null) + displayGroup = source; + + if (evt.getClickCount() > 0) { + int columnClicked = tableHeader.columnAtPoint(evt.getPoint()); + if (columnClicked != -1) { + columnClicked = component().convertColumnIndexToModel(columnClicked); + TableColumnAssociation association = (TableColumnAssociation) columns.objectAtIndex(columnClicked); + if (association.isSortable()) { + NSMutableArray newOrder = new NSMutableArray(displayGroup.sortOrderings()); + + // click once = asc, twice = desc, thrice = clear + EOSortOrdering sortOrdering; + int index = association.getIndexOfMatchingOrdering(newOrder); + if (index == -1) + sortOrdering = null; + else if (index == 1) + sortOrdering = association.getSortOrdering(false); + else + sortOrdering = association.getSortOrdering(true); + + pleaseIgnore = true; + tableHeader.repaint(); + + // stop any cell editing + CellEditor editor = component().getCellEditor(); + if (editor != null) { + editor.stopCellEditing(); + } + + // remove existing key + if (index != 0) { + newOrder.removeObjectAtIndex(Math.abs(index) - 1); + } + + // put new key at front of list + if (sortOrdering != null) { + newOrder.insertObjectAtIndex(sortOrdering, 0); + } + + displayGroup.setSortOrderings(newOrder); + displayGroup.updateDisplayedObjects(); + } + } + } + } + } + + /** + * Notifies of beginning of edit. + */ + public void focusGained(FocusEvent evt) { Object o; EODisplayGroup displayGroup; - Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidBeginEditing( this ); + Enumeration e = aspects().objectEnumerator(); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidBeginEditing(this); } } - } + } /** - * Updates object on focus lost and notifies of end of edit. - */ - public void focusLost(FocusEvent evt) - { - if ( ! component().isEditing() ) - { - Object o; - EODisplayGroup displayGroup; - Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidEndEditing( this ); - } - } - } - } + * Updates object on focus lost and notifies of end of edit. + */ + public void focusLost(FocusEvent evt) { + if (!component().isEditing()) { + Object o; + EODisplayGroup displayGroup; + Enumeration e = aspects().objectEnumerator(); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidEndEditing(this); + } + } + } + } /** - * Used as the model for the controlled table. - * Package access so TableColumnAssociation can recognize - * it and use the addColumnAssociation() method. - * Extends AbstractTableModel just so we get event - * broadcasting for free. - */ - static class TableAssociationModel extends AbstractTableModel - { + * Used as the model for the controlled table. Package access so + * TableColumnAssociation can recognize it and use the addColumnAssociation() + * method. Extends AbstractTableModel just so we get event broadcasting for + * free. + */ + static class TableAssociationModel extends AbstractTableModel { private TableAssociation parent; - private TableAssociationModel( TableAssociation aParent ) - { + private TableAssociationModel(TableAssociation aParent) { parent = aParent; } - - public TableAssociation getAssociation() - { - return parent; - } + + public TableAssociation getAssociation() { + return parent; + } /** - * Adds the column to the list of ColumnAssociations, - * and adds the corresponding column to the table - * at the next available index, setting the value of - * the column's model index accordingly. - * Establishes connection if no columns are currently - * associated. - */ - public void addColumnAssociation( - TableColumnAssociation aColumnAssociation ) - { - // establish connection if necessary - if ( parent.columns.size() == 0 ) - { - parent.establishConnection(); - } - + * Adds the column to the list of ColumnAssociations, and adds the corresponding + * column to the table at the next available index, setting the value of the + * column's model index accordingly. Establishes connection if no columns are + * currently associated. + */ + public void addColumnAssociation(TableColumnAssociation aColumnAssociation) { + // establish connection if necessary + if (parent.columns.size() == 0) { + parent.establishConnection(); + } + int newIndex = parent.columns.count(); - parent.columns.add( aColumnAssociation ); + parent.columns.add(aColumnAssociation); // add column to table TableColumn column = (TableColumn) aColumnAssociation.object(); - column.setModelIndex( newIndex ); - parent.component().addColumn( column ); - + column.setModelIndex(newIndex); + parent.component().addColumn(column); + } /** - * Removes the column from the list of ColumnAssociations, - * and removes the corresponding column from the table. - * Breaks connection if no more columns are associated. - */ - public void removeColumnAssociation( - TableColumnAssociation aColumnAssociation ) - { - int index = parent.columns.indexOfIdenticalObject( aColumnAssociation ); - if ( index == NSArray.NotFound ) return; - - parent.columns.removeObjectAtIndex( index ); + * Removes the column from the list of ColumnAssociations, and removes the + * corresponding column from the table. Breaks connection if no more columns are + * associated. + */ + public void removeColumnAssociation(TableColumnAssociation aColumnAssociation) { + int index = parent.columns.indexOfIdenticalObject(aColumnAssociation); + if (index == NSArray.NotFound) + return; + + parent.columns.removeObjectAtIndex(index); // remove column from table TableColumn column = (TableColumn) aColumnAssociation.object(); - parent.component().removeColumn( column ); - - // break connection if necessary - if ( parent.columns.size() == 0 ) - { - parent.breakConnection(); - } + parent.component().removeColumn(column); + + // break connection if necessary + if (parent.columns.size() == 0) { + parent.breakConnection(); + } } - public int getRowCount() - { - if ( parent.source == null ) return 0; + public int getRowCount() { + if (parent.source == null) + return 0; return parent.source.displayedObjects().count(); } - public int getColumnCount() - { + public int getColumnCount() { return parent.columns.count(); } /** - * Attempts to retrieve the header value from the specified column, - * or returns " " if the value is null. - */ - public String getColumnName(int columnIndex) - { - TableColumnAssociation association = (TableColumnAssociation) - parent.columns.objectAtIndex( columnIndex ); - Object value = ((TableColumn)association.object()).getHeaderValue(); - if ( value != null ) return value.toString(); - return " "; + * Attempts to retrieve the header value from the specified column, or returns " + * " if the value is null. + */ + public String getColumnName(int columnIndex) { + TableColumnAssociation association = (TableColumnAssociation) parent.columns.objectAtIndex(columnIndex); + Object value = ((TableColumn) association.object()).getHeaderValue(); + if (value != null) + return value.toString(); + return " "; } /** - * Returns the class of the first item in the - * display group bound to the column. - */ - public Class getColumnClass(int columnIndex) - { - Object value; - int count = getRowCount(); - for( int i = 0; i < count; i++ ) - { //First row in column is null find one that is not. - value = ((TableColumnAssociation)parent.columns. - objectAtIndex( columnIndex ) ).valueAtIndex( i ); - if ( value != null ) return value.getClass(); - } - return Object.class; + * Returns the class of the first item in the display group bound to the column. + */ + public Class getColumnClass(int columnIndex) { + Object value; + int count = getRowCount(); + for (int i = 0; i < count; i++) { // First row in column is null find one that is not. + value = ((TableColumnAssociation) parent.columns.objectAtIndex(columnIndex)).valueAtIndex(i); + if (value != null) + return value.getClass(); + } + return Object.class; } - /** - * Calls the column association's isEditableAtRow method. - */ - public boolean isCellEditable(int rowIndex, - int columnIndex) - { - return - ((TableColumnAssociation)parent.columns.objectAtIndex( - columnIndex ) ).isEditableAtRow( rowIndex ); + /** + * Calls the column association's isEditableAtRow method. + */ + public boolean isCellEditable(int rowIndex, int columnIndex) { + return ((TableColumnAssociation) parent.columns.objectAtIndex(columnIndex)).isEditableAtRow(rowIndex); } - /** - * Calls the column association's valueAtIndex method. - */ - public Object getValueAt(int rowIndex, - int columnIndex) - { - return - ((TableColumnAssociation)parent.columns.objectAtIndex( - columnIndex ) ).valueAtIndex( rowIndex ); + /** + * Calls the column association's valueAtIndex method. + */ + public Object getValueAt(int rowIndex, int columnIndex) { + return ((TableColumnAssociation) parent.columns.objectAtIndex(columnIndex)).valueAtIndex(rowIndex); } - /** - * Calls the column association's setValueAtIndex method. - */ - public void setValueAt(Object aValue, - int rowIndex, - int columnIndex) - { - Object existingValue = getValueAt( rowIndex, columnIndex ); - - // don't update display group if value is the same as before - if ( aValue == existingValue ) return; // handles null case - if ( existingValue != null ) // both are not null - { - Object newValue = aValue; - - // if different types - if ( newValue.getClass() != existingValue.getClass() ) - { - // convert to string since most editors do anyway - newValue = newValue.toString(); - existingValue = existingValue.toString(); - } - if ( newValue.equals( existingValue ) ) - { - // same value - do nothing - return; - } - } - - ((TableColumnAssociation)parent.columns.objectAtIndex( - columnIndex ) ).setValueAtIndex( aValue, rowIndex ); + /** + * Calls the column association's setValueAtIndex method. + */ + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + Object existingValue = getValueAt(rowIndex, columnIndex); + + // don't update display group if value is the same as before + if (aValue == existingValue) + return; // handles null case + if (existingValue != null) // both are not null + { + Object newValue = aValue; + + // if different types + if (newValue.getClass() != existingValue.getClass()) { + // convert to string since most editors do anyway + newValue = newValue.toString(); + existingValue = existingValue.toString(); + } + if (newValue.equals(existingValue)) { + // same value - do nothing + return; + } + } + + ((TableColumnAssociation) parent.columns.objectAtIndex(columnIndex)).setValueAtIndex(aValue, rowIndex); } } @@ -807,121 +672,106 @@ public class TableAssociation extends EOAssociation } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.31 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.31 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.30 2002/11/16 16:33:31 mpowers - * Now using platform-specific accelerator key for shortcuts. + * Revision 1.30 2002/11/16 16:33:31 mpowers Now using platform-specific + * accelerator key for shortcuts. * - * Revision 1.29 2002/05/24 14:41:29 mpowers - * Throwing an exception for clarity. + * Revision 1.29 2002/05/24 14:41:29 mpowers Throwing an exception for clarity. * - * Revision 1.28 2002/04/10 21:20:04 mpowers - * Better handling for tree nodes when working with editing contexts. - * Better handling for invalidation. No longer broadcasting events - * when nodes have not been "registered" in the tree. + * Revision 1.28 2002/04/10 21:20:04 mpowers Better handling for tree nodes when + * working with editing contexts. Better handling for invalidation. No longer + * broadcasting events when nodes have not been "registered" in the tree. * - * Revision 1.27 2002/03/05 23:18:28 mpowers - * Added documentation. - * Added isSelectionPaintedImmediate and isSelectionTracking attributes - * to TableAssociation. - * Added getTableAssociation to TableColumnAssociation. + * Revision 1.27 2002/03/05 23:18:28 mpowers Added documentation. Added + * isSelectionPaintedImmediate and isSelectionTracking attributes to + * TableAssociation. Added getTableAssociation to TableColumnAssociation. * - * Revision 1.25 2002/03/04 22:49:53 mpowers - * Now working with TreeColumnAssociation to delegate sorting behavior. + * Revision 1.25 2002/03/04 22:49:53 mpowers Now working with + * TreeColumnAssociation to delegate sorting behavior. * - * Revision 1.23 2002/03/04 03:58:17 mpowers - * Refined table header click behavior. + * Revision 1.23 2002/03/04 03:58:17 mpowers Refined table header click + * behavior. * - * Revision 1.22 2002/03/01 23:41:39 mpowers - * Added facility to get table association from model. + * Revision 1.22 2002/03/01 23:41:39 mpowers Added facility to get table + * association from model. * - * Revision 1.21 2002/03/01 20:41:22 mpowers - * Cleaned up unused code. + * Revision 1.21 2002/03/01 20:41:22 mpowers Cleaned up unused code. * - * Revision 1.20 2002/03/01 15:42:00 mpowers - * Table column headers now always show their sort indicator. - * A third table-column click clears the sort for that column. + * Revision 1.20 2002/03/01 15:42:00 mpowers Table column headers now always + * show their sort indicator. A third table-column click clears the sort for + * that column. * - * Revision 1.19 2002/02/28 23:01:39 mpowers - * TableColumnAssociations add and remove themselves from the TableAssociation - * when their connection is established and broken respectively. - * TableAssociations now break connection if they have no column associations. + * Revision 1.19 2002/02/28 23:01:39 mpowers TableColumnAssociations add and + * remove themselves from the TableAssociation when their connection is + * established and broken respectively. TableAssociations now break connection + * if they have no column associations. * - * Revision 1.18 2002/02/28 22:45:27 mpowers - * Now registers as editing association when table gets focus, so that - * endEditing can appropriate commit any table cell editors. + * Revision 1.18 2002/02/28 22:45:27 mpowers Now registers as editing + * association when table gets focus, so that endEditing can appropriate commit + * any table cell editors. * - * Revision 1.17 2001/10/26 20:01:50 mpowers - * We're again cancelling instead of stopping editing on subjectChanged. - * I believe that the introduction of NSRunLoop has allowed us to return - * to the correct behavior. + * Revision 1.17 2001/10/26 20:01:50 mpowers We're again cancelling instead of + * stopping editing on subjectChanged. I believe that the introduction of + * NSRunLoop has allowed us to return to the correct behavior. * - * Revision 1.16 2001/09/14 13:40:26 mpowers - * User-initiated selection changes are now handled on the next event loop - * so that the component repaints the new selection before any potentially - * lengthy logic is triggered by the selection change. + * Revision 1.16 2001/09/14 13:40:26 mpowers User-initiated selection changes + * are now handled on the next event loop so that the component repaints the new + * selection before any potentially lengthy logic is triggered by the selection + * change. * - * Revision 1.15 2001/07/25 15:03:01 mpowers - * getColumnName now checks for a column header value before defaulting " ". + * Revision 1.15 2001/07/25 15:03:01 mpowers getColumnName now checks for a + * column header value before defaulting " ". * - * Revision 1.14 2001/06/05 19:09:25 mpowers - * Fixed broken build. + * Revision 1.14 2001/06/05 19:09:25 mpowers Fixed broken build. * - * Revision 1.13 2001/06/05 19:08:12 mpowers - * Better determination of column class. Previously, if the first object - * was null, Object.class was used. Now we get the first non-null object. + * Revision 1.13 2001/06/05 19:08:12 mpowers Better determination of column + * class. Previously, if the first object was null, Object.class was used. Now + * we get the first non-null object. * - * Revision 1.12 2001/05/02 17:39:01 mpowers - * Now selects from display group after initial population. + * Revision 1.12 2001/05/02 17:39:01 mpowers Now selects from display group + * after initial population. * - * Revision 1.11 2001/03/29 23:05:22 mpowers - * Fixes for editing table cells. + * Revision 1.11 2001/03/29 23:05:22 mpowers Fixes for editing table cells. * - * Revision 1.10 2001/03/09 22:09:22 mpowers - * Now better handling jdk1.1 for rendering the column header. + * Revision 1.10 2001/03/09 22:09:22 mpowers Now better handling jdk1.1 for + * rendering the column header. * - * Revision 1.9 2001/02/27 02:10:38 mpowers - * No longer updating values to the display group if the value - * has not changed. + * Revision 1.9 2001/02/27 02:10:38 mpowers No longer updating values to the + * display group if the value has not changed. * - * Revision 1.8 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.8 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.7 2001/02/16 17:47:17 mpowers - * Now cancelling any cell editing on subjectChanged(). + * Revision 1.7 2001/02/16 17:47:17 mpowers Now cancelling any cell editing on + * subjectChanged(). * - * Revision 1.6 2001/01/12 19:11:56 mpowers - * Fixed table column click sorting. + * Revision 1.6 2001/01/12 19:11:56 mpowers Fixed table column click sorting. * - * Revision 1.5 2001/01/12 17:20:30 mpowers - * Moved EOSortOrdering creation to ColumnAssociation. + * Revision 1.5 2001/01/12 17:20:30 mpowers Moved EOSortOrdering creation to + * ColumnAssociation. * - * Revision 1.4 2001/01/11 21:55:57 mpowers - * Implemented sort indicator for table column headers. + * Revision 1.4 2001/01/11 21:55:57 mpowers Implemented sort indicator for table + * column headers. * - * Revision 1.3 2001/01/11 20:34:26 mpowers - * Implemented EOSortOrdering and added support in framework. - * Added header-click to sort table columns. + * Revision 1.3 2001/01/11 20:34:26 mpowers Implemented EOSortOrdering and added + * support in framework. Added header-click to sort table columns. * - * Revision 1.2 2001/01/08 20:40:51 mpowers - * JTables in 1.3 clear their selection when the data model changes, - * which sends a list selection event. We now reset the selection again - * after the data changes so we're compatible across 1.2 and 1.3. + * Revision 1.2 2001/01/08 20:40:51 mpowers JTables in 1.3 clear their selection + * when the data model changes, which sends a list selection event. We now reset + * the selection again after the data changes so we're compatible across 1.2 and + * 1.3. * - * Revision 1.1.1.1 2000/12/21 15:49:00 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:49:00 mpowers Contributing wotonomy. * - * Revision 1.7 2000/12/20 16:25:41 michael - * Added log to all files. + * Revision 1.7 2000/12/20 16:25:41 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableColumnAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableColumnAssociation.java index e1c32f3..f279926 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableColumnAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableColumnAssociation.java @@ -38,671 +38,544 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; import net.wotonomy.ui.swing.TableAssociation.TableAssociationModel; - /** -* TableColumnAssociation binds a column of a JTable -* to a property of the elements of a display group. -* Bindings are: -* <ul> -* <li>value: a property convertable to a string for -* display in the cells of the table column</li> -* <li>editable: a property convertable to a boolean -* that determines the editability of the corresponding -* cells in the column.</li> -* </ul> - -* Because TableColumns do not have a handle to their -* containing JTable, setTable() must be called before -* calling establishConnection(). This will add the -* controlled TableColumn to the specified JTable. -* -* Columns appear in the table in the order in which -* setTable is called on the corresponding association. -* The original table model index is ignored. -* -* Column names appear in the table based on the value -* of TableColumn.getHeaderValue(). -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class TableColumnAssociation extends EOAssociation -{ - static final NSArray aspects = - new NSArray( new Object[] { - ValueAspect, EditableAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "table" - } ); - - static Color[] sortIndicatorColorList; - + * TableColumnAssociation binds a column of a JTable to a property of the + * elements of a display group. Bindings are: + * <ul> + * <li>value: a property convertable to a string for display in the cells of the + * table column</li> + * <li>editable: a property convertable to a boolean that determines the + * editability of the corresponding cells in the column.</li> + * </ul> + * + * Because TableColumns do not have a handle to their containing JTable, + * setTable() must be called before calling establishConnection(). This will add + * the controlled TableColumn to the specified JTable. + * + * Columns appear in the table in the order in which setTable is called on the + * corresponding association. The original table model index is ignored. + * + * Column names appear in the table based on the value of + * TableColumn.getHeaderValue(). + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class TableColumnAssociation extends EOAssociation { + static final NSArray aspects = new NSArray(new Object[] { ValueAspect, EditableAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "table" }); + + static Color[] sortIndicatorColorList; + EODisplayGroup valueDisplayGroup, editableDisplayGroup; String valueKey, editableKey; - boolean sortable; - boolean sortCaseSensitive; - JTable table; - - /** - * Constructor specifying the object to be controlled by this - * association. Throws an exception if the object is not - * a TableColumn. setTable() must be called before - * establishing the connection. - */ - public TableColumnAssociation ( Object anObject ) - { - super( anObject ); + boolean sortable; + boolean sortCaseSensitive; + JTable table; + + /** + * Constructor specifying the object to be controlled by this association. + * Throws an exception if the object is not a TableColumn. setTable() must be + * called before establishing the connection. + */ + public TableColumnAssociation(Object anObject) { + super(anObject); valueDisplayGroup = null; valueKey = null; editableDisplayGroup = null; editableKey = null; - sortable = true; - sortCaseSensitive = true; - table = null; - } - + sortable = true; + sortCaseSensitive = true; + table = null; + } + /** - * Sets the table to be used for this column association. - * If no TableAssociation exists for the specified table, - * one will be created automatically. The controlled - * table column will be added to the table. Note that - * the table column's model index is ignored: table columns - * appear in the table in the order in which setTable is - * called on their corresponding associations. - */ - public void setTable( JTable aTable ) - { - table = aTable; - if ( table == null ) return; - - // creates table association if not existing - getTableAssociation(); + * Sets the table to be used for this column association. If no TableAssociation + * exists for the specified table, one will be created automatically. The + * controlled table column will be added to the table. Note that the table + * column's model index is ignored: table columns appear in the table in the + * order in which setTable is called on their corresponding associations. + */ + public void setTable(JTable aTable) { + table = aTable; + if (table == null) + return; + + // creates table association if not existing + getTableAssociation(); } - - /** - * Returns the table association for this table column, - * or null if no table has been set. This method will - * create the association if none exists for the table. - */ - public TableAssociation getTableAssociation() - { - if ( table == null ) return null; - - TableAssociation result; - if ( ! ( table.getModel() instanceof TableAssociationModel ) ) - { - result = new TableAssociation( table ); - result.bindAspect( - SourceAspect, displayGroupForAspect( ValueAspect ), "" ); + + /** + * Returns the table association for this table column, or null if no table has + * been set. This method will create the association if none exists for the + * table. + */ + public TableAssociation getTableAssociation() { + if (table == null) + return null; + + TableAssociation result; + if (!(table.getModel() instanceof TableAssociationModel)) { + result = new TableAssociation(table); + result.bindAspect(SourceAspect, displayGroupForAspect(ValueAspect), ""); + } else { + result = ((TableAssociationModel) table.getModel()).getAssociation(); } - else - { - result = ((TableAssociationModel)table.getModel()).getAssociation(); - } - return result; - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect. <ul> - * <li>"A" attribute: the aspect can be bound to - * an attribute.</li> - * <li>"1" to-one: the aspect can be bound to a - * property that returns a single object.</li> - * <li>"M" to-one: the aspect can be bound to a - * property that returns multiple objects.</li> - * </ul> - * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return ( anObject instanceof TableColumn ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return ValueAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Binds the specified aspect of this association to the - * specified key on the specified display group. - */ - public void bindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey ) - { - if ( ValueAspect.equals( anAspect ) ) - { - valueDisplayGroup = aDisplayGroup; + return result; + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + * <ul> + * <li>"A" attribute: the aspect can be bound to an attribute.</li> + * <li>"1" to-one: the aspect can be bound to a property that returns a single + * object.</li> + * <li>"M" to-one: the aspect can be bound to a property that returns multiple + * objects.</li> + * </ul> + * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return (anObject instanceof TableColumn); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return ValueAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Binds the specified aspect of this association to the specified key on the + * specified display group. + */ + public void bindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + if (ValueAspect.equals(anAspect)) { + valueDisplayGroup = aDisplayGroup; valueKey = aKey; } - if ( EditableAspect.equals( anAspect ) ) - { + if (EditableAspect.equals(anAspect)) { editableDisplayGroup = aDisplayGroup; editableKey = aKey; } - super.bindAspect( anAspect, aDisplayGroup, aKey ); + super.bindAspect(anAspect, aDisplayGroup, aKey); } - - /** - * Establishes a connection between this association - * and the controlled object. Subclasses should begin - * listening for events from their controlled object here. - */ - public void establishConnection () - { - addAsListener(); - - if ( table == null ) throw new WotonomyException( - "A table must be specified by calling setTable()" ); + + /** + * Establishes a connection between this association and the controlled object. + * Subclasses should begin listening for events from their controlled object + * here. + */ + public void establishConnection() { + addAsListener(); + + if (table == null) + throw new WotonomyException("A table must be specified by calling setTable()"); // add association to model - TableAssociationModel model = - (TableAssociationModel) table.getModel(); - model.addColumnAssociation( this ); - - super.establishConnection(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - removeAsListener(); - - if ( table == null ) throw new WotonomyException( - "TableColumnAssociation's table may not be null" ); + TableAssociationModel model = (TableAssociationModel) table.getModel(); + model.addColumnAssociation(this); + + super.establishConnection(); + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + removeAsListener(); + + if (table == null) + throw new WotonomyException("TableColumnAssociation's table may not be null"); // remove association from model - TableAssociationModel model = - (TableAssociationModel) table.getModel(); - model.removeColumnAssociation( this ); - - super.breakConnection(); - } - - protected void addAsListener() - { - } - - protected void removeAsListener() - { - } - + TableAssociationModel model = (TableAssociationModel) table.getModel(); + model.removeColumnAssociation(this); + + super.breakConnection(); + } + + protected void addAsListener() { + } + + protected void removeAsListener() { + } + /** - * Returns the value to be displayed at the specified index. - * This method is called by the TableAssocation to populate - * the table model. - * This implementation simply retrieves the value from the - * display group bound to the value aspect. - */ - public Object valueAtIndex( int aRowIndex ) - { - if ( valueDisplayGroup != null ) - { - return valueDisplayGroup.valueForObjectAtIndex( - aRowIndex, valueKey ); + * Returns the value to be displayed at the specified index. This method is + * called by the TableAssocation to populate the table model. This + * implementation simply retrieves the value from the display group bound to the + * value aspect. + */ + public Object valueAtIndex(int aRowIndex) { + if (valueDisplayGroup != null) { + return valueDisplayGroup.valueForObjectAtIndex(aRowIndex, valueKey); } return null; } - + /** - * Sets a value for the specified index. This method is - * called by the TableAssocation after a cell has been - * edited. - * This implementation simply sets the value in the - * display group bound to the value aspect. - */ - public void setValueAtIndex( Object aValue, int aRowIndex ) - { - if ( valueDisplayGroup != null ) - { - valueDisplayGroup.setValueForObjectAtIndex( - aValue, aRowIndex, valueKey ); + * Sets a value for the specified index. This method is called by the + * TableAssocation after a cell has been edited. This implementation simply sets + * the value in the display group bound to the value aspect. + */ + public void setValueAtIndex(Object aValue, int aRowIndex) { + if (valueDisplayGroup != null) { + valueDisplayGroup.setValueForObjectAtIndex(aValue, aRowIndex, valueKey); } } - - /** - * Returns whether this column should be sorted when the - * user clicks on the column header. Defaults to true. - */ - public boolean isSortable() - { - return sortable; - } - - /** - * Sets whether this column should be sorted when the - * user clicks on the column header. - */ - public void setSortable( boolean isSortable ) - { - sortable = isSortable; - } - - /** - * Returns whether this column should be sorted - * in a case sensitive manner. Defaults to true. - */ - public boolean isSortCaseSensitive() - { - return sortCaseSensitive; - } - - /** - * Sets whether this column should be sorted when - * in a case sensitive manner. - * If false, the column contents should be string values. - */ - public void setSortCaseSensitive( boolean isCaseSensitive ) - { - sortCaseSensitive = isCaseSensitive; - } /** - * Called by the TableAssociation to determine whether - * the value at the specified row is editable. - * This is determined by the binding of the Editable aspect, - * looking at the value of the corresponding index in that - * display group. Note: because the display group may - * not have the same number if items, the selected index is - * used if the editable display group is not the same as the - * the value display group. - */ - public boolean isEditableAtRow( int aRowIndex ) - { - if ( editableKey == null ) return false; + * Returns whether this column should be sorted when the user clicks on the + * column header. Defaults to true. + */ + public boolean isSortable() { + return sortable; + } + + /** + * Sets whether this column should be sorted when the user clicks on the column + * header. + */ + public void setSortable(boolean isSortable) { + sortable = isSortable; + } + + /** + * Returns whether this column should be sorted in a case sensitive manner. + * Defaults to true. + */ + public boolean isSortCaseSensitive() { + return sortCaseSensitive; + } + + /** + * Sets whether this column should be sorted when in a case sensitive manner. If + * false, the column contents should be string values. + */ + public void setSortCaseSensitive(boolean isCaseSensitive) { + sortCaseSensitive = isCaseSensitive; + } + + /** + * Called by the TableAssociation to determine whether the value at the + * specified row is editable. This is determined by the binding of the Editable + * aspect, looking at the value of the corresponding index in that display + * group. Note: because the display group may not have the same number if items, + * the selected index is used if the editable display group is not the same as + * the the value display group. + */ + public boolean isEditableAtRow(int aRowIndex) { + if (editableKey == null) + return false; Object value = null; - if ( editableDisplayGroup != null ) - { + if (editableDisplayGroup != null) { // if using the same group for both, return the value for the index - if ( editableDisplayGroup.equals( valueDisplayGroup ) ) - { - value = - editableDisplayGroup.valueForObjectAtIndex( aRowIndex, editableKey ); - } - else // using an external display group to determine editability + if (editableDisplayGroup.equals(valueDisplayGroup)) { + value = editableDisplayGroup.valueForObjectAtIndex(aRowIndex, editableKey); + } else // using an external display group to determine editability { // ignore index and use the selected object value from display group - value = - editableDisplayGroup.selectedObjectValueForKey( editableKey ); + value = editableDisplayGroup.selectedObjectValueForKey(editableKey); } - } - else - { + } else { // treat bound key without display group as a value - value = editableKey; + value = editableKey; } - if ( value == null ) return false; // null defaults to false - Boolean result = (Boolean) - ValueConverter.convertObjectToClass( value, Boolean.class ); - if ( result == null ) return true; // non-null defaults to true + if (value == null) + return false; // null defaults to false + Boolean result = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + if (result == null) + return true; // non-null defaults to true return result.booleanValue(); } - - // convenience - - private TableColumn component() - { - return (TableColumn) object(); - } - - /** - * Called by TableAssociation to get a EOSortOrdering suitable - * for the information in this column. - * This implementation returns a EOSortOrdering with the key - * equal to the value aspect's key and the appropriate selector - * for the specified ascending value and the case sensitivity - * of this column. - * Override to customize the sort for your column. - */ - public EOSortOrdering getSortOrdering( boolean isAscending ) - { - if ( isAscending ) - { - if ( isSortCaseSensitive() ) - { - return new EOSortOrdering( - valueKey, - EOSortOrdering.CompareAscending ) ; - } - else - { - return new EOSortOrdering( - valueKey, - EOSortOrdering.CompareCaseInsensitiveAscending ) ; - } - } - else - { - if ( isSortCaseSensitive() ) - { - return new EOSortOrdering( - valueKey, - EOSortOrdering.CompareDescending ) ; - } - else - { - return new EOSortOrdering( - valueKey, - EOSortOrdering.CompareCaseInsensitiveDescending ) ; - } - } - } - - /** - * Returns the one-based index of this assocation's sort ordering - * in the specified list of orderings. If the sign of the returned - * value is negative, the ordering is descending. If the return - * value is zero, no matching ordering was found. - */ - protected int getIndexOfMatchingOrdering( List orderings ) - { - // find index of matching ordering - int index = 0; - EOSortOrdering ordering = null; - Iterator i = orderings.iterator(); - while ( i.hasNext() ) - { - index++; - ordering = (EOSortOrdering) i.next(); - if ( ordering.key().equals( valueKey ) ) - { - // determine ascending or descending - if ( getSortOrdering( true ).equals( ordering ) ) - { - return index; - } - else - if ( getSortOrdering( false ).equals( ordering ) ) - { - return -index; - } - } - } - return 0; - - } - - /** - * Called by TableAssociation to draw some indicator in the - * specified rectangle using the specified graphics to indicate - * the specified sort state. The rectangle corresponds to the - * bounds of the column header. - * This implementation draws a small transparent gray triangle at - * the right edge of the bounding rectangle. - * Override to do something different or to do nothing at all. - */ - protected void drawSortIndicator( Rectangle aBoundingRectangle, - Graphics aGraphicsContext, List orderings ) - { - int index = getIndexOfMatchingOrdering( orderings ); - if ( index == 0 ) return; - - boolean isAscending = ( index > 0 ); - index = Math.abs( index ); - - // turn on anti-aliasing - if ( aGraphicsContext instanceof Graphics2D ) - { - ((Graphics2D)aGraphicsContext).setRenderingHint( - RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON ); - } - - Rectangle r = new Rectangle( aBoundingRectangle ); - - // resize to a right-justified square, sides equal to height - r.setBounds( r.x + r.width - r.height, r.y, r.height, r.height ); - - // resize to about a third smaller - int portion = r.height / 3; - r.grow( -portion, -portion ); - - // transparencies cause java2d printing to rasterize, - // resulting in excessive memory usage and print time. - // aGraphicsContext.setColor( new Color( 0, 0, 0, 255 / (index*2) ) ); - aGraphicsContext.setColor( getSortIndicatorColor( index ) ); - - Polygon triangle; - if ( !isAscending ) - { - triangle = new Polygon( - new int[] { r.x, r.x+r.width/2, r.x+r.width }, - new int[] { r.y, r.y+r.height, r.y }, 3 ); - } - else - { - triangle = new Polygon( - new int[] { r.x, r.x+r.width/2, r.x+r.width }, - new int[] { r.y+r.height, r.y, r.y+r.height }, 3 ); - } - aGraphicsContext.fillPolygon( triangle ); - } - - /** - * Returns a color to be used by the sort indicator based on the index - * of the sorting column. The goal of this method is to make the color - * appear lighter and lighter, the "less" primary the sort order for this - * column is. This can be acheives simply though a "transparent" color, - * however, during printing of the corresponding table, java print - * kicks into "raster" based printing when printing a component with - * a transparent color instead of "vector" based printing. Raster - * based printing can take up to 20-30 times longer to print than - * vector printing and consume several times the amount of memory. - * Raster-based printing should be avoided at all costs if the a component - * is to be printed (as of Java 1.3.1). - * @param index The "sort" index of the associated table column. The higher - * the index, the lighter the color will be. An index of 0 will - * return null. - * @return The color to use when rendering the sort indicator. - */ - protected static Color getSortIndicatorColor( int index ) - { - if ( index == 0 ) return null; - - // Create the color list if not already created. - if ( sortIndicatorColorList == null ) - { - // Default size to 13 elements, it would be extremely rare that a - // user sorts more than 12 columns at a time (although possible). - // (Index 0 is not used.) - sortIndicatorColorList = new Color[ 13 ]; - } - - // Get the color out of the color list. Use the index directly as - // an index into an ordered list. If the color has already been - // created for that index, then return it, otherwise create the color. - if ( ( index < sortIndicatorColorList.length ) && - ( sortIndicatorColorList[ index ] != null ) ) - { - return sortIndicatorColorList[ index ]; - } - - // The following logic performs the same affect as the above - // transparent color, without actually using a transparent color. - // Start with the table header's background color and derive a color - // that is "darker" than that color. Any color this logic creates will - // be between those two colors. - Color lightColor = java.awt.SystemColor.control; - Color darkColor = lightColor.darker().darker(); - - // Make the light color (the upper bound) a little darker, so that even - // the lightest triangle will still be slightly visible. - lightColor = new Color( - Math.max( ( int )( lightColor.getRed() * 0.9), 0 ), - Math.max( ( int )( lightColor.getGreen() * 0.9), 0 ), - Math.max( ( int )( lightColor.getBlue() * 0.9), 0) ); - - // Subtract the light color from the dark color. This is the range - // between the two colors. - Color difference = new Color( lightColor.getRed() - darkColor.getRed(), - lightColor.getGreen() - darkColor.getGreen(), - lightColor.getBlue() - darkColor.getBlue() ); - - // If the index is 1, user the dark color as is. Otherwise scale the - // color closer and closer to the lighter color as the index gets - // biggger and bigger. - if ( index > 1 ) - { - float factor = ( float )Math.pow( 0.5, ( index - 1 ) ); - darkColor = new Color( - Math.max( lightColor.getRed() - ( int )( difference.getRed() * factor ), 0 ), - Math.max( lightColor.getGreen() - ( int )( difference.getGreen() * factor ), 0 ), - Math.max( lightColor.getBlue() - ( int )( difference.getBlue() * factor ), 0 ) ); - } - - // Cache the created color in the color list for this index. - if ( index >= sortIndicatorColorList.length ) - { - // The color list is too small, create a new larger list with - // some padding for even larger indicies. - Color[] oldList = sortIndicatorColorList; - sortIndicatorColorList = new Color[ index + 5 ]; - System.arraycopy( oldList, 0, sortIndicatorColorList, 0, oldList.length ); - } - sortIndicatorColorList[ index ] = darkColor; - - return darkColor; - } + + // convenience + + private TableColumn component() { + return (TableColumn) object(); + } + + /** + * Called by TableAssociation to get a EOSortOrdering suitable for the + * information in this column. This implementation returns a EOSortOrdering with + * the key equal to the value aspect's key and the appropriate selector for the + * specified ascending value and the case sensitivity of this column. Override + * to customize the sort for your column. + */ + public EOSortOrdering getSortOrdering(boolean isAscending) { + if (isAscending) { + if (isSortCaseSensitive()) { + return new EOSortOrdering(valueKey, EOSortOrdering.CompareAscending); + } else { + return new EOSortOrdering(valueKey, EOSortOrdering.CompareCaseInsensitiveAscending); + } + } else { + if (isSortCaseSensitive()) { + return new EOSortOrdering(valueKey, EOSortOrdering.CompareDescending); + } else { + return new EOSortOrdering(valueKey, EOSortOrdering.CompareCaseInsensitiveDescending); + } + } + } + + /** + * Returns the one-based index of this assocation's sort ordering in the + * specified list of orderings. If the sign of the returned value is negative, + * the ordering is descending. If the return value is zero, no matching ordering + * was found. + */ + protected int getIndexOfMatchingOrdering(List orderings) { + // find index of matching ordering + int index = 0; + EOSortOrdering ordering = null; + Iterator i = orderings.iterator(); + while (i.hasNext()) { + index++; + ordering = (EOSortOrdering) i.next(); + if (ordering.key().equals(valueKey)) { + // determine ascending or descending + if (getSortOrdering(true).equals(ordering)) { + return index; + } else if (getSortOrdering(false).equals(ordering)) { + return -index; + } + } + } + return 0; + + } + + /** + * Called by TableAssociation to draw some indicator in the specified rectangle + * using the specified graphics to indicate the specified sort state. The + * rectangle corresponds to the bounds of the column header. This implementation + * draws a small transparent gray triangle at the right edge of the bounding + * rectangle. Override to do something different or to do nothing at all. + */ + protected void drawSortIndicator(Rectangle aBoundingRectangle, Graphics aGraphicsContext, List orderings) { + int index = getIndexOfMatchingOrdering(orderings); + if (index == 0) + return; + + boolean isAscending = (index > 0); + index = Math.abs(index); + + // turn on anti-aliasing + if (aGraphicsContext instanceof Graphics2D) { + ((Graphics2D) aGraphicsContext).setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + } + + Rectangle r = new Rectangle(aBoundingRectangle); + + // resize to a right-justified square, sides equal to height + r.setBounds(r.x + r.width - r.height, r.y, r.height, r.height); + + // resize to about a third smaller + int portion = r.height / 3; + r.grow(-portion, -portion); + + // transparencies cause java2d printing to rasterize, + // resulting in excessive memory usage and print time. + // aGraphicsContext.setColor( new Color( 0, 0, 0, 255 / (index*2) ) ); + aGraphicsContext.setColor(getSortIndicatorColor(index)); + + Polygon triangle; + if (!isAscending) { + triangle = new Polygon(new int[] { r.x, r.x + r.width / 2, r.x + r.width }, + new int[] { r.y, r.y + r.height, r.y }, 3); + } else { + triangle = new Polygon(new int[] { r.x, r.x + r.width / 2, r.x + r.width }, + new int[] { r.y + r.height, r.y, r.y + r.height }, 3); + } + aGraphicsContext.fillPolygon(triangle); + } + + /** + * Returns a color to be used by the sort indicator based on the index of the + * sorting column. The goal of this method is to make the color appear lighter + * and lighter, the "less" primary the sort order for this column is. This can + * be acheives simply though a "transparent" color, however, during printing of + * the corresponding table, java print kicks into "raster" based printing when + * printing a component with a transparent color instead of "vector" based + * printing. Raster based printing can take up to 20-30 times longer to print + * than vector printing and consume several times the amount of memory. + * Raster-based printing should be avoided at all costs if the a component is to + * be printed (as of Java 1.3.1). + * + * @param index The "sort" index of the associated table column. The higher the + * index, the lighter the color will be. An index of 0 will return + * null. + * @return The color to use when rendering the sort indicator. + */ + protected static Color getSortIndicatorColor(int index) { + if (index == 0) + return null; + + // Create the color list if not already created. + if (sortIndicatorColorList == null) { + // Default size to 13 elements, it would be extremely rare that a + // user sorts more than 12 columns at a time (although possible). + // (Index 0 is not used.) + sortIndicatorColorList = new Color[13]; + } + + // Get the color out of the color list. Use the index directly as + // an index into an ordered list. If the color has already been + // created for that index, then return it, otherwise create the color. + if ((index < sortIndicatorColorList.length) && (sortIndicatorColorList[index] != null)) { + return sortIndicatorColorList[index]; + } + + // The following logic performs the same affect as the above + // transparent color, without actually using a transparent color. + // Start with the table header's background color and derive a color + // that is "darker" than that color. Any color this logic creates will + // be between those two colors. + Color lightColor = java.awt.SystemColor.control; + Color darkColor = lightColor.darker().darker(); + + // Make the light color (the upper bound) a little darker, so that even + // the lightest triangle will still be slightly visible. + lightColor = new Color(Math.max((int) (lightColor.getRed() * 0.9), 0), + Math.max((int) (lightColor.getGreen() * 0.9), 0), Math.max((int) (lightColor.getBlue() * 0.9), 0)); + + // Subtract the light color from the dark color. This is the range + // between the two colors. + Color difference = new Color(lightColor.getRed() - darkColor.getRed(), + lightColor.getGreen() - darkColor.getGreen(), lightColor.getBlue() - darkColor.getBlue()); + + // If the index is 1, user the dark color as is. Otherwise scale the + // color closer and closer to the lighter color as the index gets + // biggger and bigger. + if (index > 1) { + float factor = (float) Math.pow(0.5, (index - 1)); + darkColor = new Color(Math.max(lightColor.getRed() - (int) (difference.getRed() * factor), 0), + Math.max(lightColor.getGreen() - (int) (difference.getGreen() * factor), 0), + Math.max(lightColor.getBlue() - (int) (difference.getBlue() * factor), 0)); + } + + // Cache the created color in the color list for this index. + if (index >= sortIndicatorColorList.length) { + // The color list is too small, create a new larger list with + // some padding for even larger indicies. + Color[] oldList = sortIndicatorColorList; + sortIndicatorColorList = new Color[index + 5]; + System.arraycopy(oldList, 0, sortIndicatorColorList, 0, oldList.length); + } + sortIndicatorColorList[index] = darkColor; + + return darkColor; + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.16 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.16 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.15 2002/08/22 15:42:49 mpowers - * No longer using transparency to render sort indicator (see comments). + * Revision 1.15 2002/08/22 15:42:49 mpowers No longer using transparency to + * render sort indicator (see comments). * - * Revision 1.14 2002/04/12 21:05:57 mpowers - * Now distinguishing changes in titles group even better. + * Revision 1.14 2002/04/12 21:05:57 mpowers Now distinguishing changes in + * titles group even better. * - * Revision 1.13 2002/03/05 23:18:28 mpowers - * Added documentation. - * Added isSelectionPaintedImmediate and isSelectionTracking attributes - * to TableAssociation. - * Added getTableAssociation to TableColumnAssociation. + * Revision 1.13 2002/03/05 23:18:28 mpowers Added documentation. Added + * isSelectionPaintedImmediate and isSelectionTracking attributes to + * TableAssociation. Added getTableAssociation to TableColumnAssociation. * - * Revision 1.12 2002/03/04 22:11:43 mpowers - * Darkened the sort indicator to better differentiate the first sort. + * Revision 1.12 2002/03/04 22:11:43 mpowers Darkened the sort indicator to + * better differentiate the first sort. * - * Revision 1.11 2002/03/04 03:58:17 mpowers - * Refined table header click behavior. + * Revision 1.11 2002/03/04 03:58:17 mpowers Refined table header click + * behavior. * - * Revision 1.10 2002/03/01 15:42:00 mpowers - * Table column headers now always show their sort indicator. - * A third table-column click clears the sort for that column. + * Revision 1.10 2002/03/01 15:42:00 mpowers Table column headers now always + * show their sort indicator. A third table-column click clears the sort for + * that column. * - * Revision 1.9 2002/02/28 23:01:39 mpowers - * TableColumnAssociations add and remove themselves from the TableAssociation - * when their connection is established and broken respectively. - * TableAssociations now break connection if they have no column associations. + * Revision 1.9 2002/02/28 23:01:39 mpowers TableColumnAssociations add and + * remove themselves from the TableAssociation when their connection is + * established and broken respectively. TableAssociations now break connection + * if they have no column associations. * - * Revision 1.8 2001/06/05 16:03:56 mpowers - * Flipped the triangle to be consistent with Aqua. + * Revision 1.8 2001/06/05 16:03:56 mpowers Flipped the triangle to be + * consistent with Aqua. * - * Revision 1.7 2001/03/09 22:09:22 mpowers - * Now better handling jdk1.1 for rendering the column header. + * Revision 1.7 2001/03/09 22:09:22 mpowers Now better handling jdk1.1 for + * rendering the column header. * - * Revision 1.6 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.6 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.5 2001/01/12 19:11:56 mpowers - * Fixed table column click sorting. + * Revision 1.5 2001/01/12 19:11:56 mpowers Fixed table column click sorting. * - * Revision 1.4 2001/01/12 17:20:30 mpowers - * Moved EOSortOrdering creation to ColumnAssociation. + * Revision 1.4 2001/01/12 17:20:30 mpowers Moved EOSortOrdering creation to + * ColumnAssociation. * - * Revision 1.3 2001/01/11 21:55:57 mpowers - * Implemented sort indicator for table column headers. + * Revision 1.3 2001/01/11 21:55:57 mpowers Implemented sort indicator for table + * column headers. * - * Revision 1.2 2001/01/11 20:34:26 mpowers - * Implemented EOSortOrdering and added support in framework. - * Added header-click to sort table columns. + * Revision 1.2 2001/01/11 20:34:26 mpowers Implemented EOSortOrdering and added + * support in framework. Added header-click to sort table columns. * - * Revision 1.1.1.1 2000/12/21 15:49:03 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:49:03 mpowers Contributing wotonomy. * - * Revision 1.5 2000/12/20 16:25:41 michael - * Added log to all files. + * Revision 1.5 2000/12/20 16:25:41 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TextAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TextAssociation.java index 6aa27c3..ea2b47a 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TextAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TextAssociation.java @@ -54,1159 +54,923 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* TextAssociation binds JTextComponents and other objects -* with getText() and setText() methods to a display group. -* Note that JLabels are supported with both the Text and -* Icon aspects. -* Bindings are: -* <ul> -* <li>value: a property convertable to/from a string</li> -* <li>editable: a boolean property that determines whether -* the user can edit the text in the field</li> -* <li>enabled: a boolean property that determines whether -* the user can select the text in the field</li> -* <li>visible: a boolean property that determines whether -* the field is visible</li> -* <li>label: a boolean property that determines whether -* field should appear as a read-only, selectable label</li> -* <li>icon: a property that returns a Swing icon, for use -* with JLabels and other components with setIcon() methods. -* If bound to a static string, the string will be used to -* load an image resource from the selected object's class.</li> -* </ul> -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class TextAssociation extends EOAssociation - implements FocusListener, ActionListener, DocumentListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - ValueAspect, EnabledAspect, EditableAspect, VisibleAspect, LabelAspect, IconAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "text", "enabled", "editable", "visible" - } ); - - private final static NSSelector getText = - new NSSelector( "getText" ); - private final static NSSelector setText = - new NSSelector( "setText", - new Class[] { String.class } ); - private final static NSSelector getDocument = - new NSSelector( "getDocument" ); - private final static NSSelector setIcon = - new NSSelector( "setIcon", - new Class[] { Icon.class } ); - private final static NSSelector addActionListener = - new NSSelector( "addActionListener", - new Class[] { ActionListener.class } ); - private final static NSSelector removeActionListener = - new NSSelector( "removeActionListener", - new Class[] { ActionListener.class } ); - private final static NSSelector addFocusListener = - new NSSelector( "addFocusListener", - new Class[] { FocusListener.class } ); - private final static NSSelector removeFocusListener = - new NSSelector( "removeFocusListener", - new Class[] { FocusListener.class } ); - - // null handling - protected boolean wasNull; - protected static final String EMPTY_STRING = ""; - - // dirty handling - protected boolean needsUpdate; - protected boolean hasDocument; - protected boolean isListening; - - // formatting - protected Format format; - - // on-the-fly validation - protected boolean activeUpdate; - - // type conversion - protected Class lastKnownType; - - // cache the value aspect - private EODisplayGroup valueDisplayGroup; - private String valueKey; - - // hacky flags needed for no activeUpdate - private boolean pleaseIgnoreNextChange = false; - private boolean pleaseAcceptNextChange = false; - private boolean externallyChanged = true; - - - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - */ - public TextAssociation ( Object anObject ) - { - super( anObject ); - wasNull = false; - needsUpdate = false; - activeUpdate = true; - hasDocument = false; - isListening = true; - valueDisplayGroup = null; - valueKey = null; - format = null; - lastKnownType = null; - - // register for idle notifications - NSSelector handleNotification = - new NSSelector( "handleNotification", - new Class[] { NSNotification.class } ); - NSNotificationCenter.defaultCenter().addObserver( - this, handleNotification, null, this ); - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect. <ul> - * <li>"A" attribute: the aspect can be bound to - * an attribute.</li> - * <li>"1" to-one: the aspect can be bound to a - * property that returns a single object.</li> - * <li>"M" to-one: the aspect can be bound to a - * property that returns multiple objects.</li> - * </ul> - * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return setText.implementedByObject( anObject ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return ValueAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Binds the specified aspect of this association to the - * specified key on the specified display group. - */ - public void bindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey ) - { - if ( ValueAspect.equals( anAspect ) ) - { - valueDisplayGroup = aDisplayGroup; - valueKey = aKey; - } - super.bindAspect( anAspect, aDisplayGroup, aKey ); - } - - /** - * Establishes a connection between this association - * and the controlled object. This implementation - * attempts to add this class as an ActionListener - * and as a FocusListener to the specified object. - */ - public void establishConnection () - { - Object component = object(); - try - { - if ( addActionListener.implementedByObject( component ) ) - { - addActionListener.invoke( component, this ); - } - if ( addFocusListener.implementedByObject( component ) ) - { - addFocusListener.invoke( component, this ); - } - hasDocument = false; - if ( getDocument.implementedByObject( component ) ) - { - Object document = getDocument.invoke( component ); - if ( document instanceof Document ) - { - ((Document)document).addDocumentListener( this ); - hasDocument = true; - } - } - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while establishing connection", exc ); - } - - super.establishConnection(); - - // forces update from bindings - subjectChanged(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - Object component = object(); - try - { - if ( removeActionListener.implementedByObject( component ) ) - { - removeActionListener.invoke( component, this ); - } - if ( removeFocusListener.implementedByObject( component ) ) - { - removeFocusListener.invoke( component, this ); - } - if ( getDocument.implementedByObject( component ) ) - { - Object document = getDocument.invoke( component ); - if ( document instanceof Document ) - { - ((Document)document).removeDocumentListener( this ); - } - } - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while breaking connection", exc ); - } - super.breakConnection(); - } - - public void objectWillChange( Object anObject ) - { - super.objectWillChange( anObject ); - externallyChanged = true; - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged() - { - if ( pleaseIgnoreNextChange ) - { - pleaseIgnoreNextChange = false; - externallyChanged = false; - return; - } - - externallyChanged = true; - - Object component = object(); - EODisplayGroup displayGroup; - String key; - Object value; - - // value aspect - displayGroup = valueDisplayGroup; - if ( displayGroup != null ) - { - if ( component instanceof Component ) - { - ((Component)component).setEnabled( - displayGroup.enabledToSetSelectedObjectValueForKey( valueKey ) ); - } - - // if activeUpdate or we are not the editing association - if ( activeUpdate || displayGroup.editingAssociation() != this || pleaseAcceptNextChange ) - { - pleaseAcceptNextChange = false; - key = valueKey; - - if ( displayGroup.selectedObjects().size() > 1 ) - { - // if there're more than one object selected, set - // the value to blank for all of them. - Object previousValue; - - Iterator indexIterator = displayGroup.selectionIndexes(). - iterator(); - - // get value for the first selected object. - int initialIndex = ( (Integer)indexIterator.next() ).intValue(); - previousValue = displayGroup.valueForObjectAtIndex( - initialIndex, key ); - value = previousValue; - - // go through the rest of the selected objects, compare each - // value with the previous one. continue comparing if two - // values are equal, break the while loop if they're different. - // the final value will be the common value of all selected objects - // if there is one, or be blank if there is not. - while ( indexIterator.hasNext() ) - { - int index = ( (Integer)indexIterator.next() ).intValue(); - Object currentValue = displayGroup.valueForObjectAtIndex( - index, key ); - if ( currentValue != null && previousValue != null - && !currentValue.toString().equals( previousValue.toString() ) ) { - value = null; - break; - } - - } // end while - - } else { - - // if there's only one object selected. - value = displayGroup.selectedObjectValueForKey( key ); - } // end checking the size of selected objects in displayGroup - - // null handling - if ( value == null ) - { - wasNull = true; - value = EMPTY_STRING; - lastKnownType = null; - } - else - { - wasNull = false; - lastKnownType = value.getClass(); - if ( format() != null ) - { - try - { - value = format().format( value ); - } - catch ( IllegalArgumentException exc ) - { - value = value.toString(); - } - } - } - - - try - { - if ( needToReadValueFromDisplayGroup( value.toString(), getText ) ) - { - // No need to listen for any events that might get fired - // while setting the text since we are the one setting it. - boolean wasListening = isListening; - isListening = false; - - // setText is an expensive operation - setText.invoke( component, value.toString() ); - - isListening = wasListening; - needsUpdate = false; - } - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while updating component connection", exc ); - } - } - } - - // icon aspect - displayGroup = displayGroupForAspect( IconAspect ); - key = displayGroupKeyForAspect( IconAspect ); - if ( key != null ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { - // treat bound key without display group - // as a resource to be loaded from the selected class. - value = null; - Object o = displayGroup.selectedObject(); - if ( o != null ) - { - URL url = o.getClass().getResource( key ); - if ( url != null ) - { - value = new ImageIcon( url ); - } - } - } - - try - { - setIcon.invoke( component, value ); - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while updating component connection", exc ); - } - } - - // enabled aspect - displayGroup = displayGroupForAspect( EnabledAspect ); - key = displayGroupKeyForAspect( EnabledAspect ); - if ( ( key != null ) - && ( component instanceof Component ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { - // treat bound key without display group as a value - value = key; - } - Boolean converted = null; - if ( value != null ) - { - converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - } - if ( converted == null ) converted = Boolean.FALSE; - if ( ((Component)component).isEnabled() != converted.booleanValue() ) - { - ((Component)component).setEnabled( converted.booleanValue() ); - } - } - - // editable aspect - displayGroup = displayGroupForAspect( EditableAspect ); - key = displayGroupKeyForAspect( EditableAspect ); - if ( ( key != null ) - && ( component instanceof JTextComponent ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { - // treat bound key without display group as a value - value = key; - } - Boolean converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - - if ( converted != null ) - { - if ( converted.booleanValue() != ((JTextComponent)component).isEditable() ) - { - ((JTextComponent)component).setEditable( converted.booleanValue() ); - } - } - } - - // visible aspect - displayGroup = displayGroupForAspect( VisibleAspect ); - key = displayGroupKeyForAspect( VisibleAspect ); - if ( ( key != null ) - && ( component instanceof Component ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { - // treat bound key without display group as a value - value = key; - } - Boolean converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - - if ( converted != null ) - { - if ( converted.booleanValue() != ((Component)component).isVisible() ) - { - ((Component)component).setVisible( converted.booleanValue() ); - } - } - } - - // label aspect - displayGroup = displayGroupForAspect( LabelAspect ); - key = displayGroupKeyForAspect( LabelAspect ); - - if ( ( key != null ) - && ( component instanceof JTextComponent ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { - // treat bound key without display group as a value - value = key; - } - Boolean converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - - if ( converted != null ) - { - if ( converted.booleanValue() ) - { - if ( component instanceof JTextComponent ) - { - if ( component instanceof JTextArea ) - { - areaToLabel( (JTextArea) component ); - } - else - { - fieldToLabel( (JTextComponent) component ); - } - } - } - else - { - if ( component instanceof JTextComponent ) - { - if ( component instanceof JTextArea ) - { - labelToArea( (JTextArea) component ); - } - else - { - labelToField( (JTextComponent ) component ); - } - } - } - } - } - } - - private void fieldToLabel( JTextComponent aTextField ) - { - // turn on wrapping and disable editing and highlighting - - aTextField.setEditable(false); - aTextField.setOpaque(false); - - // Set the border, colors and font to that of a label - - //LookAndFeel.installBorder(aTextField, "Label.border"); - aTextField.setBorder( null ); - - LookAndFeel.installColorsAndFont(aTextField, - "Label.background", - "Label.foreground", - "Label.font"); - } - - private void labelToField( JTextComponent aTextField ) - { - // turn on wrapping and disable editing and highlighting - - aTextField.setEditable(true); - aTextField.setOpaque(true); - - // Set the border, colors and font to that of a label - - LookAndFeel.installBorder(aTextField, "TextField.border"); - - LookAndFeel.installColorsAndFont(aTextField, - "TextField.background", - "TextField.foreground", - "TextField.font"); - } - - private void areaToLabel( JTextArea aTextArea ) - { - // turn on wrapping and disable editing and highlighting - - aTextArea.setLineWrap(true); - aTextArea.setWrapStyleWord(true); - aTextArea.setEditable(false); - - // Set the text area's border, colors and font to - // that of a label - - //LookAndFeel.installBorder(aTextArea, "Label.border"); - aTextArea.setBorder( null ); - - LookAndFeel.installColorsAndFont(aTextArea, - "Label.background", - "Label.foreground", - "Label.font"); - - } - - private void labelToArea( JTextArea aTextArea ) - { - // turn on wrapping and disable editing and highlighting - - aTextArea.setEditable(true); - - // Set the border, colors and font to that of a label - - LookAndFeel.installBorder(aTextArea, "TextArea.border"); - - LookAndFeel.installColorsAndFont(aTextArea, - "TextArea.background", - "TextArea.foreground", - "TextArea.font"); - } - - - /** - * Forces this association to cause the object to - * stop editing and validate the user's input. - * @return false if there were problems validating, - * or true to continue. - */ - public boolean endEditing() - { - pleaseAcceptNextChange = true; - pleaseIgnoreNextChange = false; - return writeValueToDisplayGroup(); - } - - /** - * Writes the value currently in the component - * to the selected object in the display group - * bound to the value aspect. - * @return false if there were problems validating, - * or true to continue. - */ - protected boolean writeValueToDisplayGroup() - { - boolean returnValue = true; - if ( hasDocument && !needsUpdate ) return true; - - EODisplayGroup displayGroup = valueDisplayGroup; - if ( displayGroup != null ) - { - String key = valueKey; - Object component = object(); - Object value = null; - try - { - //if ( getText.implementedByObject( component ) ) - //{ - value = getText.invoke( component ); - //} - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error updating display group", exc ); - } - - if ( ( wasNull ) && ( EMPTY_STRING.equals( value ) ) ) - { - value = null; - } - else - if ( format() != null ) - { - try - { - value = format().parseObject( value.toString() ); - } - catch ( ParseException exc ) - { - String message = exc.getMessage(); - //"That format was not recognized."; - if ( displayGroup.associationFailedToValidateValue( - this, value.toString(), key, exc, message ) ) - { - boolean wasListening = isListening; - isListening = false; - JOptionPane.showMessageDialog( - (Component)component, message ); - isListening = wasListening; - } - needsUpdate = false; - return false; - } - } - - if ( ( lastKnownType != null ) && ( value != null ) ) - { - // convert back to last known type, if necessary/possible - Class type = value.getClass(); - if ( ( type != null ) && ( type != lastKnownType ) ) - { - Object converted = - ValueConverter.convertObjectToClass( - value, lastKnownType ); - if ( converted != null ) - { - value = converted; - } - // else: not possible, ignore - } - } - - needsUpdate = false; - - // only update if the value is different from the one in the display group - if ( ! needToWriteValueToDisplayGroup( value, displayGroup ) ) return true; - - // we might lose focus if display group displays a validation message - boolean wasListening = isListening; - isListening = false; - - Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); - while ( selectedIterator.hasNext() ) - { - int index = ( (Integer)selectedIterator.next() ).intValue(); - - if ( displayGroup.setValueForObjectAtIndex( value, index, key ) ) - { - needsUpdate = false; - } - else - { - needsUpdate = false; - returnValue = false; - } - } - isListening = wasListening; - - } - return returnValue; - } - - /** - * Called to determine whether the display group needs to be - * updated. This implementation reads the value from the display - * group and only returns true if the specified value is different. - * This is done as an optimization since writes are more expensive - * than reads. Override to customize this behavior. - */ - protected boolean needToWriteValueToDisplayGroup( - Object aValue, EODisplayGroup aDisplayGroup ) - { - Object existingValue = aDisplayGroup.selectedObjectValueForKey( valueKey ); - if ( aDisplayGroup.selectedObjects().size() == 1 ) - { - if ( existingValue == aValue ) return false; - if ( ( existingValue != null ) && ( existingValue.equals( aValue ) ) ) return false; - if ( ( aValue != null ) && ( aValue.equals( existingValue ) ) ) return false; - } - return true; - } - - /** - * Called to determine whether the controlled component needs to be - * updated. This implementation reads the value from the selector - * and only returns true if the specified value is different. - * This is done as an optimization since updating the component - * can be an expensive operation. Override to customize this behavior. - */ - protected boolean needToReadValueFromDisplayGroup( - Object aValue, NSSelector aSelector ) - throws IllegalAccessException, InvocationTargetException, NoSuchMethodException - { - return !aValue.toString().equals( aSelector.invoke( object() ) ); - } - - /** - * Sets the Format that is used to convert values from the display - * group to and from text that is displayed in the component. - */ - public void setFormat( Format aFormat ) - { - format = aFormat; - } - - /** - * Gets the Format that is used to convert values from the display - * group to and from text that is displayed in the component. - */ - public Format format() - { - return format; - } - - /** - * Returns whether the text association is configured to actively - * update the model in response to changes in the component. - */ - public boolean isActiveUpdate() - { - return activeUpdate; - } - - /** - * Sets whether the text association should actively - * update the model in response to changes in the component. - * Default is true. False indicates that the model will be updated - * only when the component loses focus or fires an action event. - */ - public void setActiveUpdate( boolean isActiveUpdate ) - { - activeUpdate = isActiveUpdate; - } - - // interface ActionListener - - /** - * Updates object on action performed. - */ - public void actionPerformed( ActionEvent evt ) - { - if ( ! isListening ) return; - if ( needsUpdate ) - { - pleaseAcceptNextChange = true; // needed if activeUpdate = false - writeValueToDisplayGroup(); - } - } - - // interface FocusListener - - /** - * Notifies of beginning of edit. - */ - public void focusGained(FocusEvent evt) - { - if ( ! isListening ) return; - - pleaseAcceptNextChange = true; - externallyChanged = true; - - Object o; - EODisplayGroup displayGroup; - Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidBeginEditing( this ); - } - } - } - - /** - * Updates object on focus lost and notifies of end of edit. - */ - public void focusLost(FocusEvent evt) - { - if ( ! isListening ) return; - if ( endEditing() ) - { - Object o; - EODisplayGroup displayGroup; - Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidEndEditing( this ); - } - } - } - else - { - // probably should notify of a validation error here, - } - } - - /** - * Queues a notification to PostWhenIdle. - */ - protected void queueUpdate(DocumentEvent e) - { - if ( e.getDocument() instanceof DefaultStyledDocument ) - { - if ( e instanceof AbstractDocument.DefaultDocumentEvent ) - { - int docLength = e.getDocument().getLength(); - - if ( ( e.getType().equals( DocumentEvent.EventType.CHANGE ) ) ) - { - if ( e.getOffset() == 0 && e.getLength() == docLength ) - { - // ignore document events for the whole document - // since default styled document broadcasts these - // using invokeLater, and we've already received - // notification about the actual style change. - // see: DefaultStyledDocument.ChangeUpdateRunnable - return; - } - } - } - } - - NSNotificationQueue.defaultQueue().enqueueNotification( - new NSNotification( "TextAssociation.DocumentChanged", this, - new NSDictionary( new Object[] { "event" }, new Object[] { e } ) ), - NSNotificationQueue.PostWhenIdle ); - } - - /** - * Handles idle notification. - */ - public void handleNotification( NSNotification aNotification ) - { - if ( activeUpdate ) - { - writeValueToDisplayGroup(); - } - } - - // interface DocumentListener - - public void insertUpdate(DocumentEvent e) - { - if ( ! isListening ) return; - needsUpdate = true; - queueUpdate( e ); - } - - public void removeUpdate(DocumentEvent e) - { - if ( ! isListening ) return; - needsUpdate = true; - queueUpdate( e ); - } - - public void changedUpdate(DocumentEvent e) - { - if ( ! isListening ) return; - needsUpdate = true; - queueUpdate( e ); - } + * TextAssociation binds JTextComponents and other objects with getText() and + * setText() methods to a display group. Note that JLabels are supported with + * both the Text and Icon aspects. Bindings are: + * <ul> + * <li>value: a property convertable to/from a string</li> + * <li>editable: a boolean property that determines whether the user can edit + * the text in the field</li> + * <li>enabled: a boolean property that determines whether the user can select + * the text in the field</li> + * <li>visible: a boolean property that determines whether the field is + * visible</li> + * <li>label: a boolean property that determines whether field should appear as + * a read-only, selectable label</li> + * <li>icon: a property that returns a Swing icon, for use with JLabels and + * other components with setIcon() methods. If bound to a static string, the + * string will be used to load an image resource from the selected object's + * class.</li> + * </ul> + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class TextAssociation extends EOAssociation implements FocusListener, ActionListener, DocumentListener { + static final NSArray aspects = new NSArray( + new Object[] { ValueAspect, EnabledAspect, EditableAspect, VisibleAspect, LabelAspect, IconAspect }); + static final NSArray aspectSignatures = new NSArray( + new Object[] { AttributeToOneAspectSignature, AttributeToOneAspectSignature, AttributeToOneAspectSignature, + AttributeToOneAspectSignature, AttributeToOneAspectSignature, AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "text", "enabled", "editable", "visible" }); + + private final static NSSelector getText = new NSSelector("getText"); + private final static NSSelector setText = new NSSelector("setText", new Class[] { String.class }); + private final static NSSelector getDocument = new NSSelector("getDocument"); + private final static NSSelector setIcon = new NSSelector("setIcon", new Class[] { Icon.class }); + private final static NSSelector addActionListener = new NSSelector("addActionListener", + new Class[] { ActionListener.class }); + private final static NSSelector removeActionListener = new NSSelector("removeActionListener", + new Class[] { ActionListener.class }); + private final static NSSelector addFocusListener = new NSSelector("addFocusListener", + new Class[] { FocusListener.class }); + private final static NSSelector removeFocusListener = new NSSelector("removeFocusListener", + new Class[] { FocusListener.class }); + + // null handling + protected boolean wasNull; + protected static final String EMPTY_STRING = ""; + + // dirty handling + protected boolean needsUpdate; + protected boolean hasDocument; + protected boolean isListening; + + // formatting + protected Format format; + + // on-the-fly validation + protected boolean activeUpdate; + + // type conversion + protected Class lastKnownType; + + // cache the value aspect + private EODisplayGroup valueDisplayGroup; + private String valueKey; + + // hacky flags needed for no activeUpdate + private boolean pleaseIgnoreNextChange = false; + private boolean pleaseAcceptNextChange = false; + private boolean externallyChanged = true; + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. + */ + public TextAssociation(Object anObject) { + super(anObject); + wasNull = false; + needsUpdate = false; + activeUpdate = true; + hasDocument = false; + isListening = true; + valueDisplayGroup = null; + valueKey = null; + format = null; + lastKnownType = null; + + // register for idle notifications + NSSelector handleNotification = new NSSelector("handleNotification", new Class[] { NSNotification.class }); + NSNotificationCenter.defaultCenter().addObserver(this, handleNotification, null, this); + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + * <ul> + * <li>"A" attribute: the aspect can be bound to an attribute.</li> + * <li>"1" to-one: the aspect can be bound to a property that returns a single + * object.</li> + * <li>"M" to-one: the aspect can be bound to a property that returns multiple + * objects.</li> + * </ul> + * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return setText.implementedByObject(anObject); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return ValueAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Binds the specified aspect of this association to the specified key on the + * specified display group. + */ + public void bindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + if (ValueAspect.equals(anAspect)) { + valueDisplayGroup = aDisplayGroup; + valueKey = aKey; + } + super.bindAspect(anAspect, aDisplayGroup, aKey); + } + + /** + * Establishes a connection between this association and the controlled object. + * This implementation attempts to add this class as an ActionListener and as a + * FocusListener to the specified object. + */ + public void establishConnection() { + Object component = object(); + try { + if (addActionListener.implementedByObject(component)) { + addActionListener.invoke(component, this); + } + if (addFocusListener.implementedByObject(component)) { + addFocusListener.invoke(component, this); + } + hasDocument = false; + if (getDocument.implementedByObject(component)) { + Object document = getDocument.invoke(component); + if (document instanceof Document) { + ((Document) document).addDocumentListener(this); + hasDocument = true; + } + } + } catch (Exception exc) { + throw new WotonomyException("Error while establishing connection", exc); + } + + super.establishConnection(); + + // forces update from bindings + subjectChanged(); + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + Object component = object(); + try { + if (removeActionListener.implementedByObject(component)) { + removeActionListener.invoke(component, this); + } + if (removeFocusListener.implementedByObject(component)) { + removeFocusListener.invoke(component, this); + } + if (getDocument.implementedByObject(component)) { + Object document = getDocument.invoke(component); + if (document instanceof Document) { + ((Document) document).removeDocumentListener(this); + } + } + } catch (Exception exc) { + throw new WotonomyException("Error while breaking connection", exc); + } + super.breakConnection(); + } + + public void objectWillChange(Object anObject) { + super.objectWillChange(anObject); + externallyChanged = true; + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { + if (pleaseIgnoreNextChange) { + pleaseIgnoreNextChange = false; + externallyChanged = false; + return; + } + + externallyChanged = true; + + Object component = object(); + EODisplayGroup displayGroup; + String key; + Object value; + + // value aspect + displayGroup = valueDisplayGroup; + if (displayGroup != null) { + if (component instanceof Component) { + ((Component) component).setEnabled(displayGroup.enabledToSetSelectedObjectValueForKey(valueKey)); + } + + // if activeUpdate or we are not the editing association + if (activeUpdate || displayGroup.editingAssociation() != this || pleaseAcceptNextChange) { + pleaseAcceptNextChange = false; + key = valueKey; + + if (displayGroup.selectedObjects().size() > 1) { + // if there're more than one object selected, set + // the value to blank for all of them. + Object previousValue; + + Iterator indexIterator = displayGroup.selectionIndexes().iterator(); + + // get value for the first selected object. + int initialIndex = ((Integer) indexIterator.next()).intValue(); + previousValue = displayGroup.valueForObjectAtIndex(initialIndex, key); + value = previousValue; + + // go through the rest of the selected objects, compare each + // value with the previous one. continue comparing if two + // values are equal, break the while loop if they're different. + // the final value will be the common value of all selected objects + // if there is one, or be blank if there is not. + while (indexIterator.hasNext()) { + int index = ((Integer) indexIterator.next()).intValue(); + Object currentValue = displayGroup.valueForObjectAtIndex(index, key); + if (currentValue != null && previousValue != null + && !currentValue.toString().equals(previousValue.toString())) { + value = null; + break; + } + + } // end while + + } else { + + // if there's only one object selected. + value = displayGroup.selectedObjectValueForKey(key); + } // end checking the size of selected objects in displayGroup + + // null handling + if (value == null) { + wasNull = true; + value = EMPTY_STRING; + lastKnownType = null; + } else { + wasNull = false; + lastKnownType = value.getClass(); + if (format() != null) { + try { + value = format().format(value); + } catch (IllegalArgumentException exc) { + value = value.toString(); + } + } + } + + try { + if (needToReadValueFromDisplayGroup(value.toString(), getText)) { + // No need to listen for any events that might get fired + // while setting the text since we are the one setting it. + boolean wasListening = isListening; + isListening = false; + + // setText is an expensive operation + setText.invoke(component, value.toString()); + + isListening = wasListening; + needsUpdate = false; + } + } catch (Exception exc) { + throw new WotonomyException("Error while updating component connection", exc); + } + } + } + + // icon aspect + displayGroup = displayGroupForAspect(IconAspect); + key = displayGroupKeyForAspect(IconAspect); + if (key != null) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { + // treat bound key without display group + // as a resource to be loaded from the selected class. + value = null; + Object o = displayGroup.selectedObject(); + if (o != null) { + URL url = o.getClass().getResource(key); + if (url != null) { + value = new ImageIcon(url); + } + } + } + + try { + setIcon.invoke(component, value); + } catch (Exception exc) { + throw new WotonomyException("Error while updating component connection", exc); + } + } + + // enabled aspect + displayGroup = displayGroupForAspect(EnabledAspect); + key = displayGroupKeyForAspect(EnabledAspect); + if ((key != null) && (component instanceof Component)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { + // treat bound key without display group as a value + value = key; + } + Boolean converted = null; + if (value != null) { + converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + } + if (converted == null) + converted = Boolean.FALSE; + if (((Component) component).isEnabled() != converted.booleanValue()) { + ((Component) component).setEnabled(converted.booleanValue()); + } + } + + // editable aspect + displayGroup = displayGroupForAspect(EditableAspect); + key = displayGroupKeyForAspect(EditableAspect); + if ((key != null) && (component instanceof JTextComponent)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { + // treat bound key without display group as a value + value = key; + } + Boolean converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + + if (converted != null) { + if (converted.booleanValue() != ((JTextComponent) component).isEditable()) { + ((JTextComponent) component).setEditable(converted.booleanValue()); + } + } + } + + // visible aspect + displayGroup = displayGroupForAspect(VisibleAspect); + key = displayGroupKeyForAspect(VisibleAspect); + if ((key != null) && (component instanceof Component)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { + // treat bound key without display group as a value + value = key; + } + Boolean converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + + if (converted != null) { + if (converted.booleanValue() != ((Component) component).isVisible()) { + ((Component) component).setVisible(converted.booleanValue()); + } + } + } + + // label aspect + displayGroup = displayGroupForAspect(LabelAspect); + key = displayGroupKeyForAspect(LabelAspect); + + if ((key != null) && (component instanceof JTextComponent)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { + // treat bound key without display group as a value + value = key; + } + Boolean converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + + if (converted != null) { + if (converted.booleanValue()) { + if (component instanceof JTextComponent) { + if (component instanceof JTextArea) { + areaToLabel((JTextArea) component); + } else { + fieldToLabel((JTextComponent) component); + } + } + } else { + if (component instanceof JTextComponent) { + if (component instanceof JTextArea) { + labelToArea((JTextArea) component); + } else { + labelToField((JTextComponent) component); + } + } + } + } + } + } + + private void fieldToLabel(JTextComponent aTextField) { + // turn on wrapping and disable editing and highlighting + + aTextField.setEditable(false); + aTextField.setOpaque(false); + + // Set the border, colors and font to that of a label + + // LookAndFeel.installBorder(aTextField, "Label.border"); + aTextField.setBorder(null); + + LookAndFeel.installColorsAndFont(aTextField, "Label.background", "Label.foreground", "Label.font"); + } + + private void labelToField(JTextComponent aTextField) { + // turn on wrapping and disable editing and highlighting + + aTextField.setEditable(true); + aTextField.setOpaque(true); + + // Set the border, colors and font to that of a label + + LookAndFeel.installBorder(aTextField, "TextField.border"); + + LookAndFeel.installColorsAndFont(aTextField, "TextField.background", "TextField.foreground", "TextField.font"); + } + + private void areaToLabel(JTextArea aTextArea) { + // turn on wrapping and disable editing and highlighting + + aTextArea.setLineWrap(true); + aTextArea.setWrapStyleWord(true); + aTextArea.setEditable(false); + + // Set the text area's border, colors and font to + // that of a label + + // LookAndFeel.installBorder(aTextArea, "Label.border"); + aTextArea.setBorder(null); + + LookAndFeel.installColorsAndFont(aTextArea, "Label.background", "Label.foreground", "Label.font"); + + } + + private void labelToArea(JTextArea aTextArea) { + // turn on wrapping and disable editing and highlighting + + aTextArea.setEditable(true); + + // Set the border, colors and font to that of a label + + LookAndFeel.installBorder(aTextArea, "TextArea.border"); + + LookAndFeel.installColorsAndFont(aTextArea, "TextArea.background", "TextArea.foreground", "TextArea.font"); + } + + /** + * Forces this association to cause the object to stop editing and validate the + * user's input. + * + * @return false if there were problems validating, or true to continue. + */ + public boolean endEditing() { + pleaseAcceptNextChange = true; + pleaseIgnoreNextChange = false; + return writeValueToDisplayGroup(); + } + + /** + * Writes the value currently in the component to the selected object in the + * display group bound to the value aspect. + * + * @return false if there were problems validating, or true to continue. + */ + protected boolean writeValueToDisplayGroup() { + boolean returnValue = true; + if (hasDocument && !needsUpdate) + return true; + + EODisplayGroup displayGroup = valueDisplayGroup; + if (displayGroup != null) { + String key = valueKey; + Object component = object(); + Object value = null; + try { + // if ( getText.implementedByObject( component ) ) + // { + value = getText.invoke(component); + // } + } catch (Exception exc) { + throw new WotonomyException("Error updating display group", exc); + } + + if ((wasNull) && (EMPTY_STRING.equals(value))) { + value = null; + } else if (format() != null) { + try { + value = format().parseObject(value.toString()); + } catch (ParseException exc) { + String message = exc.getMessage(); + // "That format was not recognized."; + if (displayGroup.associationFailedToValidateValue(this, value.toString(), key, exc, message)) { + boolean wasListening = isListening; + isListening = false; + JOptionPane.showMessageDialog((Component) component, message); + isListening = wasListening; + } + needsUpdate = false; + return false; + } + } + + if ((lastKnownType != null) && (value != null)) { + // convert back to last known type, if necessary/possible + Class type = value.getClass(); + if ((type != null) && (type != lastKnownType)) { + Object converted = ValueConverter.convertObjectToClass(value, lastKnownType); + if (converted != null) { + value = converted; + } + // else: not possible, ignore + } + } + + needsUpdate = false; + + // only update if the value is different from the one in the display group + if (!needToWriteValueToDisplayGroup(value, displayGroup)) + return true; + + // we might lose focus if display group displays a validation message + boolean wasListening = isListening; + isListening = false; + + Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); + while (selectedIterator.hasNext()) { + int index = ((Integer) selectedIterator.next()).intValue(); + + if (displayGroup.setValueForObjectAtIndex(value, index, key)) { + needsUpdate = false; + } else { + needsUpdate = false; + returnValue = false; + } + } + isListening = wasListening; + + } + return returnValue; + } + + /** + * Called to determine whether the display group needs to be updated. This + * implementation reads the value from the display group and only returns true + * if the specified value is different. This is done as an optimization since + * writes are more expensive than reads. Override to customize this behavior. + */ + protected boolean needToWriteValueToDisplayGroup(Object aValue, EODisplayGroup aDisplayGroup) { + Object existingValue = aDisplayGroup.selectedObjectValueForKey(valueKey); + if (aDisplayGroup.selectedObjects().size() == 1) { + if (existingValue == aValue) + return false; + if ((existingValue != null) && (existingValue.equals(aValue))) + return false; + if ((aValue != null) && (aValue.equals(existingValue))) + return false; + } + return true; + } + + /** + * Called to determine whether the controlled component needs to be updated. + * This implementation reads the value from the selector and only returns true + * if the specified value is different. This is done as an optimization since + * updating the component can be an expensive operation. Override to customize + * this behavior. + */ + protected boolean needToReadValueFromDisplayGroup(Object aValue, NSSelector aSelector) + throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { + return !aValue.toString().equals(aSelector.invoke(object())); + } + + /** + * Sets the Format that is used to convert values from the display group to and + * from text that is displayed in the component. + */ + public void setFormat(Format aFormat) { + format = aFormat; + } + + /** + * Gets the Format that is used to convert values from the display group to and + * from text that is displayed in the component. + */ + public Format format() { + return format; + } + + /** + * Returns whether the text association is configured to actively update the + * model in response to changes in the component. + */ + public boolean isActiveUpdate() { + return activeUpdate; + } + + /** + * Sets whether the text association should actively update the model in + * response to changes in the component. Default is true. False indicates that + * the model will be updated only when the component loses focus or fires an + * action event. + */ + public void setActiveUpdate(boolean isActiveUpdate) { + activeUpdate = isActiveUpdate; + } + + // interface ActionListener + + /** + * Updates object on action performed. + */ + public void actionPerformed(ActionEvent evt) { + if (!isListening) + return; + if (needsUpdate) { + pleaseAcceptNextChange = true; // needed if activeUpdate = false + writeValueToDisplayGroup(); + } + } + + // interface FocusListener + + /** + * Notifies of beginning of edit. + */ + public void focusGained(FocusEvent evt) { + if (!isListening) + return; + + pleaseAcceptNextChange = true; + externallyChanged = true; + + Object o; + EODisplayGroup displayGroup; + Enumeration e = aspects().objectEnumerator(); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidBeginEditing(this); + } + } + } + + /** + * Updates object on focus lost and notifies of end of edit. + */ + public void focusLost(FocusEvent evt) { + if (!isListening) + return; + if (endEditing()) { + Object o; + EODisplayGroup displayGroup; + Enumeration e = aspects().objectEnumerator(); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidEndEditing(this); + } + } + } else { + // probably should notify of a validation error here, + } + } + + /** + * Queues a notification to PostWhenIdle. + */ + protected void queueUpdate(DocumentEvent e) { + if (e.getDocument() instanceof DefaultStyledDocument) { + if (e instanceof AbstractDocument.DefaultDocumentEvent) { + int docLength = e.getDocument().getLength(); + + if ((e.getType().equals(DocumentEvent.EventType.CHANGE))) { + if (e.getOffset() == 0 && e.getLength() == docLength) { + // ignore document events for the whole document + // since default styled document broadcasts these + // using invokeLater, and we've already received + // notification about the actual style change. + // see: DefaultStyledDocument.ChangeUpdateRunnable + return; + } + } + } + } + + NSNotificationQueue.defaultQueue().enqueueNotification( + new NSNotification("TextAssociation.DocumentChanged", this, + new NSDictionary(new Object[] { "event" }, new Object[] { e })), + NSNotificationQueue.PostWhenIdle); + } + + /** + * Handles idle notification. + */ + public void handleNotification(NSNotification aNotification) { + if (activeUpdate) { + writeValueToDisplayGroup(); + } + } + + // interface DocumentListener + + public void insertUpdate(DocumentEvent e) { + if (!isListening) + return; + needsUpdate = true; + queueUpdate(e); + } + + public void removeUpdate(DocumentEvent e) { + if (!isListening) + return; + needsUpdate = true; + queueUpdate(e); + } + + public void changedUpdate(DocumentEvent e) { + if (!isListening) + return; + needsUpdate = true; + queueUpdate(e); + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.41 2004/02/05 02:18:18 mpowers - * Now setting border to null to new Aqua LAF behaves. + * Revision 1.41 2004/02/05 02:18:18 mpowers Now setting border to null to new + * Aqua LAF behaves. * - * Revision 1.40 2004/01/28 22:47:56 mpowers - * un-activeUpdate was brokne. + * Revision 1.40 2004/01/28 22:47:56 mpowers un-activeUpdate was brokne. * - * Revision 1.39 2004/01/28 18:34:57 mpowers - * Better handling for enabling. - * Now respecting enabledToSetSelectedObjectValueForKey from display group. + * Revision 1.39 2004/01/28 18:34:57 mpowers Better handling for enabling. Now + * respecting enabledToSetSelectedObjectValueForKey from display group. * - * Revision 1.38 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.38 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.37 2003/02/06 16:21:34 mpowers - * Fix for activeUpdate: no longer bothering with editing context's changes. + * Revision 1.37 2003/02/06 16:21:34 mpowers Fix for activeUpdate: no longer + * bothering with editing context's changes. * - * Revision 1.36 2002/10/24 18:19:24 mpowers - * Bug fix - thanks to dwang. + * Revision 1.36 2002/10/24 18:19:24 mpowers Bug fix - thanks to dwang. * - * Revision 1.35 2002/08/02 19:19:30 mpowers - * Added control points for when to read or write from the display group. - * Added flags needed to fix problems with non-activeUpdate and commit key. + * Revision 1.35 2002/08/02 19:19:30 mpowers Added control points for when to + * read or write from the display group. Added flags needed to fix problems with + * non-activeUpdate and commit key. * - * Revision 1.33 2002/03/08 23:18:01 mpowers - * Added visible aspect. + * Revision 1.33 2002/03/08 23:18:01 mpowers Added visible aspect. * - * Revision 1.32 2002/03/06 16:13:53 mpowers - * Yet another fix for style document changes: swing's DefaultStyledDocument - * using an invoke later to launch a final StyleChanged event, which occurs - * after the TextAssociation has reestablished itself as a document listener, - * causing the item to be marked dirty. We're now handling this case. + * Revision 1.32 2002/03/06 16:13:53 mpowers Yet another fix for style document + * changes: swing's DefaultStyledDocument using an invoke later to launch a + * final StyleChanged event, which occurs after the TextAssociation has + * reestablished itself as a document listener, causing the item to be marked + * dirty. We're now handling this case. * - * Revision 1.31 2002/03/04 22:10:37 mpowers - * Supressing active update only marks dirty when contents have changed. + * Revision 1.31 2002/03/04 22:10:37 mpowers Supressing active update only marks + * dirty when contents have changed. * - * Revision 1.30 2002/02/23 16:19:12 mpowers - * Now only marking an editing context as dirty if it's not already dirty. + * Revision 1.30 2002/02/23 16:19:12 mpowers Now only marking an editing context + * as dirty if it's not already dirty. * - * Revision 1.29 2002/02/19 18:38:29 mpowers - * Minor optimization: activeUpdate now checked in handleNotification. + * Revision 1.29 2002/02/19 18:38:29 mpowers Minor optimization: activeUpdate + * now checked in handleNotification. * - * Revision 1.28 2002/02/19 16:36:47 mpowers - * Better support for active update: objects are now marked as changed - * even though the model itself is not updated -- this allows editing - * context itself to be marked as having changes to be saved. + * Revision 1.28 2002/02/19 16:36:47 mpowers Better support for active update: + * objects are now marked as changed even though the model itself is not updated + * -- this allows editing context itself to be marked as having changes to be + * saved. * - * Revision 1.27 2002/01/23 19:50:11 mpowers - * Fix for a null pointer when value is null and last known type is not. - * (from dwang) + * Revision 1.27 2002/01/23 19:50:11 mpowers Fix for a null pointer when value + * is null and last known type is not. (from dwang) * - * Revision 1.26 2002/01/14 19:37:22 mpowers - * Fix for NPE when value is null and auto update is false. + * Revision 1.26 2002/01/14 19:37:22 mpowers Fix for NPE when value is null and + * auto update is false. * - * Revision 1.25 2001/12/10 03:16:11 mpowers - * Fixed bug with isListening when no items are in display group. + * Revision 1.25 2001/12/10 03:16:11 mpowers Fixed bug with isListening when no + * items are in display group. * - * Revision 1.24 2001/11/16 19:14:51 mpowers - * Brought back the idea of configuring whether updates occur on each change. + * Revision 1.24 2001/11/16 19:14:51 mpowers Brought back the idea of + * configuring whether updates occur on each change. * - * Revision 1.23 2001/11/08 20:06:06 mpowers - * Now performing type-conversion as a convenience. + * Revision 1.23 2001/11/08 20:06:06 mpowers Now performing type-conversion as a + * convenience. * - * Revision 1.22 2001/11/04 18:24:20 mpowers - * Better handling for non-string values when bulk-editing. + * Revision 1.22 2001/11/04 18:24:20 mpowers Better handling for non-string + * values when bulk-editing. * - * Revision 1.21 2001/11/01 15:53:34 mpowers - * Now that NSNotificationQueue correctly implements PostWhenIdle, we can - * finally discard our use of Swing's Timer in favor of using the queue - * to coalesce document changed events. + * Revision 1.21 2001/11/01 15:53:34 mpowers Now that NSNotificationQueue + * correctly implements PostWhenIdle, we can finally discard our use of Swing's + * Timer in favor of using the queue to coalesce document changed events. * - * Revision 1.20 2001/10/26 19:58:06 mpowers - * Better handling for non-string types. We were testing with equals with the - * new value against the existing value in the component. Now we convert - * the new value to a string before comparing. Fixes case for properties - * of non-String types, like StringBuffer. + * Revision 1.20 2001/10/26 19:58:06 mpowers Better handling for non-string + * types. We were testing with equals with the new value against the existing + * value in the component. Now we convert the new value to a string before + * comparing. Fixes case for properties of non-String types, like StringBuffer. * - * Revision 1.19 2001/09/30 21:57:14 mpowers - * Timers were not getting cleaned up if breakConnection was called - * before the timer got a chance to fire. + * Revision 1.19 2001/09/30 21:57:14 mpowers Timers were not getting cleaned up + * if breakConnection was called before the timer got a chance to fire. * - * Revision 1.18 2001/08/22 15:42:26 mpowers - * Added support for JTextComponent label-izing. + * Revision 1.18 2001/08/22 15:42:26 mpowers Added support for JTextComponent + * label-izing. * - * Revision 1.17 2001/07/30 16:32:55 mpowers - * Implemented support for bulk-editing. Detail associations will now - * apply changes to all selected objects. + * Revision 1.17 2001/07/30 16:32:55 mpowers Implemented support for + * bulk-editing. Detail associations will now apply changes to all selected + * objects. * - * Revision 1.16 2001/07/17 19:53:37 mpowers - * Made some private fields protected for benefit of subclassers. + * Revision 1.16 2001/07/17 19:53:37 mpowers Made some private fields protected + * for benefit of subclassers. * - * Revision 1.15 2001/06/30 14:59:36 mpowers - * LabelAspect now sets the text field's opaque setting. + * Revision 1.15 2001/06/30 14:59:36 mpowers LabelAspect now sets the text + * field's opaque setting. * - * Revision 1.14 2001/06/29 14:54:08 mpowers - * Another fix for timers - timers were definitely causing a memory leak. + * Revision 1.14 2001/06/29 14:54:08 mpowers Another fix for timers - timers + * were definitely causing a memory leak. * - * Revision 1.13 2001/06/26 21:37:19 mpowers - * Fixed a null pointer in the new key timer scheme. + * Revision 1.13 2001/06/26 21:37:19 mpowers Fixed a null pointer in the new key + * timer scheme. * - * Revision 1.12 2001/06/25 14:46:03 mpowers - * Fixed a memory leak involving the use of timers. + * Revision 1.12 2001/06/25 14:46:03 mpowers Fixed a memory leak involving the + * use of timers. * - * Revision 1.11 2001/06/01 19:14:59 mpowers - * Text association's enabled aspect is now more discriminating. + * Revision 1.11 2001/06/01 19:14:59 mpowers Text association's enabled aspect + * is now more discriminating. * - * Revision 1.10 2001/05/18 21:07:24 mpowers - * Changed the way we handle failure to update object value. + * Revision 1.10 2001/05/18 21:07:24 mpowers Changed the way we handle failure + * to update object value. * - * Revision 1.9 2001/03/13 21:39:58 mpowers - * Improved validation handling. + * Revision 1.9 2001/03/13 21:39:58 mpowers Improved validation handling. * - * Revision 1.8 2001/03/12 12:49:10 mpowers - * Improved validation handling. - * Having a formatter disables auto-updating. + * Revision 1.8 2001/03/12 12:49:10 mpowers Improved validation handling. Having + * a formatter disables auto-updating. * - * Revision 1.7 2001/03/09 22:08:13 mpowers - * Now handling any objects that have a valid Document. - * No longer checking enabled before updating the enabled state. + * Revision 1.7 2001/03/09 22:08:13 mpowers Now handling any objects that have a + * valid Document. No longer checking enabled before updating the enabled state. * - * Revision 1.6 2001/03/07 19:57:32 mpowers - * Fixed paste error in IconAspect. + * Revision 1.6 2001/03/07 19:57:32 mpowers Fixed paste error in IconAspect. * - * Revision 1.4 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.4 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.3 2001/01/31 19:12:33 mpowers - * Implemented auto-updating in TextComponent. + * Revision 1.3 2001/01/31 19:12:33 mpowers Implemented auto-updating in + * TextComponent. * - * Revision 1.2 2001/01/10 15:53:58 mpowers - * Preventing a null pointer exception if getText were to return null, - * which doesn't happen for JTextFields but might happen for other objects. + * Revision 1.2 2001/01/10 15:53:58 mpowers Preventing a null pointer exception + * if getText were to return null, which doesn't happen for JTextFields but + * might happen for other objects. * - * Revision 1.1.1.1 2000/12/21 15:49:08 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:49:08 mpowers Contributing wotonomy. * - * Revision 1.13 2000/12/20 16:25:41 michael - * Added log to all files. + * Revision 1.13 2000/12/20 16:25:41 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TimedTextAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TimedTextAssociation.java index 49879e9..a6e993c 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TimedTextAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TimedTextAssociation.java @@ -48,75 +48,51 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* TimedTextAssociation works like TextAssociation, -* but instead of using a delayed event to update the -* model, it uses a timer so that the model is only -* updated if the user pauses typing for some short interval. -* This is useful when the update and/or re-read of the model -* is a costly operation. -* Bindings are: -* <ul> -* <li>value: a property convertable to/from a string</li> -* <li>editable: a boolean property that determines whether -* the user can edit the text in the field</li> -* <li>enabled: a boolean property that determines whether -* the user can select the text in the field</li> -* <li>label: a boolean property that determines whether -* field should appear as a read-only, selectable label</li> -* <li>icon: a property that returns a Swing icon, for use -* with JLabels and other components with setIcon() methods. -* If bound to a static string, the string will be used to -* load an image resource from the selected object's class.</li> -* </ul> -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class TimedTextAssociation extends EOAssociation - implements FocusListener, ActionListener, DocumentListener -{ - //TODO: need to refactor this so that it can subclass text association. - //This implementation is basically a branch from the v1.20 TextAssociation. - - static final NSArray aspects = - new NSArray( new Object[] { - ValueAspect, EnabledAspect, EditableAspect, LabelAspect, IconAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "text", "enabled", "editable" - } ); - - private final static NSSelector getText = - new NSSelector( "getText" ); - private final static NSSelector setText = - new NSSelector( "setText", - new Class[] { String.class } ); - private final static NSSelector getDocument = - new NSSelector( "getDocument" ); - private final static NSSelector setIcon = - new NSSelector( "setIcon", - new Class[] { Icon.class } ); - private final static NSSelector addActionListener = - new NSSelector( "addActionListener", - new Class[] { ActionListener.class } ); - private final static NSSelector removeActionListener = - new NSSelector( "removeActionListener", - new Class[] { ActionListener.class } ); - private final static NSSelector addFocusListener = - new NSSelector( "addFocusListener", - new Class[] { FocusListener.class } ); - private final static NSSelector removeFocusListener = - new NSSelector( "removeFocusListener", - new Class[] { FocusListener.class } ); + * TimedTextAssociation works like TextAssociation, but instead of using a + * delayed event to update the model, it uses a timer so that the model is only + * updated if the user pauses typing for some short interval. This is useful + * when the update and/or re-read of the model is a costly operation. Bindings + * are: + * <ul> + * <li>value: a property convertable to/from a string</li> + * <li>editable: a boolean property that determines whether the user can edit + * the text in the field</li> + * <li>enabled: a boolean property that determines whether the user can select + * the text in the field</li> + * <li>label: a boolean property that determines whether field should appear as + * a read-only, selectable label</li> + * <li>icon: a property that returns a Swing icon, for use with JLabels and + * other components with setIcon() methods. If bound to a static string, the + * string will be used to load an image resource from the selected object's + * class.</li> + * </ul> + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class TimedTextAssociation extends EOAssociation implements FocusListener, ActionListener, DocumentListener { + // TODO: need to refactor this so that it can subclass text association. + // This implementation is basically a branch from the v1.20 TextAssociation. + + static final NSArray aspects = new NSArray( + new Object[] { ValueAspect, EnabledAspect, EditableAspect, LabelAspect, IconAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature, + AttributeToOneAspectSignature, AttributeToOneAspectSignature, AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "text", "enabled", "editable" }); + + private final static NSSelector getText = new NSSelector("getText"); + private final static NSSelector setText = new NSSelector("setText", new Class[] { String.class }); + private final static NSSelector getDocument = new NSSelector("getDocument"); + private final static NSSelector setIcon = new NSSelector("setIcon", new Class[] { Icon.class }); + private final static NSSelector addActionListener = new NSSelector("addActionListener", + new Class[] { ActionListener.class }); + private final static NSSelector removeActionListener = new NSSelector("removeActionListener", + new Class[] { ActionListener.class }); + private final static NSSelector addFocusListener = new NSSelector("addFocusListener", + new Class[] { FocusListener.class }); + private final static NSSelector removeFocusListener = new NSSelector("removeFocusListener", + new Class[] { FocusListener.class }); // null handling protected boolean wasNull; @@ -125,227 +101,186 @@ public class TimedTextAssociation extends EOAssociation // dirty handling protected boolean needsUpdate; protected boolean hasDocument; - protected boolean isListening; + protected boolean isListening; // formatting protected Format format; - // cache the value aspect - private EODisplayGroup valueDisplayGroup; - private String valueKey; - - // coalescing document events - protected boolean autoUpdating; - protected int interval = 400; // adjust as needed - protected Timer keyTimer; - - // NOTE: a new key timer is created for each use and - // is disposed when the timer is stopped. - // Swing's Timer class is kept in a static list of timers - // and each retains a strong reference to their listeners. - // This caused a memory leak as associations typically - // refer to their controlled component which is referred - // to by its parents and so on until no application window - // will ever get garbage collected. yikes. - - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - */ - public TimedTextAssociation ( Object anObject ) - { - super( anObject ); + // cache the value aspect + private EODisplayGroup valueDisplayGroup; + private String valueKey; + + // coalescing document events + protected boolean autoUpdating; + protected int interval = 400; // adjust as needed + protected Timer keyTimer; + + // NOTE: a new key timer is created for each use and + // is disposed when the timer is stopped. + // Swing's Timer class is kept in a static list of timers + // and each retains a strong reference to their listeners. + // This caused a memory leak as associations typically + // refer to their controlled component which is referred + // to by its parents and so on until no application window + // will ever get garbage collected. yikes. + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. + */ + public TimedTextAssociation(Object anObject) { + super(anObject); wasNull = false; needsUpdate = false; hasDocument = false; - isListening = true; - valueDisplayGroup = null; - valueKey = null; - - autoUpdating = true; - keyTimer = null; - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect. <ul> - * <li>"A" attribute: the aspect can be bound to - * an attribute.</li> - * <li>"1" to-one: the aspect can be bound to a - * property that returns a single object.</li> - * <li>"M" to-one: the aspect can be bound to a - * property that returns multiple objects.</li> - * </ul> - * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return setText.implementedByObject( anObject ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return ValueAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Binds the specified aspect of this association to the - * specified key on the specified display group. - */ - public void bindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey ) - { - if ( ValueAspect.equals( anAspect ) ) - { + isListening = true; + valueDisplayGroup = null; + valueKey = null; + + autoUpdating = true; + keyTimer = null; + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + * <ul> + * <li>"A" attribute: the aspect can be bound to an attribute.</li> + * <li>"1" to-one: the aspect can be bound to a property that returns a single + * object.</li> + * <li>"M" to-one: the aspect can be bound to a property that returns multiple + * objects.</li> + * </ul> + * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return setText.implementedByObject(anObject); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return ValueAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Binds the specified aspect of this association to the specified key on the + * specified display group. + */ + public void bindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + if (ValueAspect.equals(anAspect)) { valueDisplayGroup = aDisplayGroup; valueKey = aKey; } - super.bindAspect( anAspect, aDisplayGroup, aKey ); + super.bindAspect(anAspect, aDisplayGroup, aKey); } - /** - * Establishes a connection between this association - * and the controlled object. This implementation - * attempts to add this class as an ActionListener - * and as a FocusListener to the specified object. - */ - public void establishConnection () - { + /** + * Establishes a connection between this association and the controlled object. + * This implementation attempts to add this class as an ActionListener and as a + * FocusListener to the specified object. + */ + public void establishConnection() { Object component = object(); - try - { - if ( addActionListener.implementedByObject( component ) ) - { - addActionListener.invoke( component, this ); + try { + if (addActionListener.implementedByObject(component)) { + addActionListener.invoke(component, this); } - if ( addFocusListener.implementedByObject( component ) ) - { - addFocusListener.invoke( component, this ); + if (addFocusListener.implementedByObject(component)) { + addFocusListener.invoke(component, this); } - hasDocument = false; - if ( getDocument.implementedByObject( component ) ) - { - Object document = getDocument.invoke( component ); - if ( document instanceof Document ) - { - ((Document)document).addDocumentListener( this ); - hasDocument = true; - } + hasDocument = false; + if (getDocument.implementedByObject(component)) { + Object document = getDocument.invoke(component); + if (document instanceof Document) { + ((Document) document).addDocumentListener(this); + hasDocument = true; + } } - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while establishing connection", exc ); + } catch (Exception exc) { + throw new WotonomyException("Error while establishing connection", exc); } - super.establishConnection(); + super.establishConnection(); // forces update from bindings subjectChanged(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { Object component = object(); - try - { - if ( removeActionListener.implementedByObject( component ) ) - { - removeActionListener.invoke( component, this ); + try { + if (removeActionListener.implementedByObject(component)) { + removeActionListener.invoke(component, this); } - if ( removeFocusListener.implementedByObject( component ) ) - { - removeFocusListener.invoke( component, this ); + if (removeFocusListener.implementedByObject(component)) { + removeFocusListener.invoke(component, this); } - if ( getDocument.implementedByObject( component ) ) - { - Object document = getDocument.invoke( component ); - if ( document instanceof Document ) - { - ((Document)document).removeDocumentListener( this ); - } + if (getDocument.implementedByObject(component)) { + Object document = getDocument.invoke(component); + if (document instanceof Document) { + ((Document) document).removeDocumentListener(this); + } } + } catch (Exception exc) { + throw new WotonomyException("Error while breaking connection", exc); } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while breaking connection", exc ); - } - super.breakConnection(); - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged () - { + super.breakConnection(); + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { Object component = object(); EODisplayGroup displayGroup; String key; @@ -353,677 +288,538 @@ public class TimedTextAssociation extends EOAssociation // value aspect displayGroup = valueDisplayGroup; - if ( displayGroup != null ) - { - if ( component instanceof Component ) - { - ((Component)component).setEnabled( - displayGroup.enabledToSetSelectedObjectValueForKey( valueKey ) ); - } - + if (displayGroup != null) { + if (component instanceof Component) { + ((Component) component).setEnabled(displayGroup.enabledToSetSelectedObjectValueForKey(valueKey)); + } + key = valueKey; - if ( displayGroup.selectedObjects().size() > 1 ) - { - // if there're more than one object selected, set - // the value to blank for all of them. - Object previousValue; - - Iterator indexIterator = displayGroup.selectionIndexes(). - iterator(); - - // get value for the first selected object. - int initialIndex = ( (Integer)indexIterator.next() ).intValue(); - previousValue = displayGroup.valueForObjectAtIndex( - initialIndex, key ); - value = null; - - // go through the rest of the selected objects, compare each - // value with the previous one. continue comparing if two - // values are equal, break the while loop if they're different. - // the final value will be the common value of all selected objects - // if there is one, or be blank if there is not. - while ( indexIterator.hasNext() ) - { - int index = ( (Integer)indexIterator.next() ).intValue(); - Object currentValue = displayGroup.valueForObjectAtIndex( - index, key ); - if ( currentValue != null && !currentValue.equals( previousValue ) ) - { - value = null; - break; - } - else - { - // currentValue is the same as the previous one - value = currentValue; - } - - } // end while - - } else { - - // if there's only one object selected. - value = displayGroup.selectedObjectValueForKey( key ); - } // end checking the size of selected objects in displayGroup - - // convert value to string - if ( value == null ) - { - wasNull = true; - value = EMPTY_STRING; - } - else - { - wasNull = false; - if ( format() != null ) - { - try - { - value = format().format( value ); - } - catch ( IllegalArgumentException exc ) - { - value = value.toString(); - } - } - } - - - try - { - if ( ! value.toString().equals( getText.invoke( component ) ) ) - { - // No need to listen for any events that might get fired - // while setting the text since we are the one setting it. - boolean wasListening = isListening; - isListening = false; - - // setText is an expensive operation - setText.invoke( component, value.toString() ); - - isListening = wasListening; - needsUpdate = false; - } + if (displayGroup.selectedObjects().size() > 1) { + // if there're more than one object selected, set + // the value to blank for all of them. + Object previousValue; + + Iterator indexIterator = displayGroup.selectionIndexes().iterator(); + + // get value for the first selected object. + int initialIndex = ((Integer) indexIterator.next()).intValue(); + previousValue = displayGroup.valueForObjectAtIndex(initialIndex, key); + value = null; + + // go through the rest of the selected objects, compare each + // value with the previous one. continue comparing if two + // values are equal, break the while loop if they're different. + // the final value will be the common value of all selected objects + // if there is one, or be blank if there is not. + while (indexIterator.hasNext()) { + int index = ((Integer) indexIterator.next()).intValue(); + Object currentValue = displayGroup.valueForObjectAtIndex(index, key); + if (currentValue != null && !currentValue.equals(previousValue)) { + value = null; + break; + } else { + // currentValue is the same as the previous one + value = currentValue; + } + + } // end while + + } else { + + // if there's only one object selected. + value = displayGroup.selectedObjectValueForKey(key); + } // end checking the size of selected objects in displayGroup + + // convert value to string + if (value == null) { + wasNull = true; + value = EMPTY_STRING; + } else { + wasNull = false; + if (format() != null) { + try { + value = format().format(value); + } catch (IllegalArgumentException exc) { + value = value.toString(); + } + } } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while updating component connection", exc ); + + try { + if (!value.toString().equals(getText.invoke(component))) { + // No need to listen for any events that might get fired + // while setting the text since we are the one setting it. + boolean wasListening = isListening; + isListening = false; + + // setText is an expensive operation + setText.invoke(component, value.toString()); + + isListening = wasListening; + needsUpdate = false; + } + } catch (Exception exc) { + throw new WotonomyException("Error while updating component connection", exc); } } // icon aspect - displayGroup = displayGroupForAspect( IconAspect ); - key = displayGroupKeyForAspect( IconAspect ); - if ( key != null ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { - // treat bound key without display group - // as a resource to be loaded from the selected class. - value = null; - Object o = displayGroup.selectedObject(); - if ( o != null ) - { - URL url = o.getClass().getResource( key ); - if ( url != null ) - { - value = new ImageIcon( url ); - } - } + displayGroup = displayGroupForAspect(IconAspect); + key = displayGroupKeyForAspect(IconAspect); + if (key != null) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { + // treat bound key without display group + // as a resource to be loaded from the selected class. + value = null; + Object o = displayGroup.selectedObject(); + if (o != null) { + URL url = o.getClass().getResource(key); + if (url != null) { + value = new ImageIcon(url); + } + } } - try - { - setIcon.invoke( component, value ); - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while updating component connection", exc ); + try { + setIcon.invoke(component, value); + } catch (Exception exc) { + throw new WotonomyException("Error while updating component connection", exc); } } // enabled aspect - displayGroup = displayGroupForAspect( EnabledAspect ); - key = displayGroupKeyForAspect( EnabledAspect ); - if ( ( key != null ) - && ( component instanceof Component ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { + displayGroup = displayGroupForAspect(EnabledAspect); + key = displayGroupKeyForAspect(EnabledAspect); + if ((key != null) && (component instanceof Component)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { // treat bound key without display group as a value value = key; } - Boolean converted = null; - if ( value != null ) - { - converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - } - if ( converted == null ) converted = Boolean.FALSE; - if ( ((Component)component).isEnabled() != converted.booleanValue() ) - { - ((Component)component).setEnabled( converted.booleanValue() ); - } + Boolean converted = null; + if (value != null) { + converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + } + if (converted == null) + converted = Boolean.FALSE; + if (((Component) component).isEnabled() != converted.booleanValue()) { + ((Component) component).setEnabled(converted.booleanValue()); + } } // editable aspect - displayGroup = displayGroupForAspect( EditableAspect ); - key = displayGroupKeyForAspect( EditableAspect ); - if ( ( key != null ) - && ( component instanceof JTextComponent ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { + displayGroup = displayGroupForAspect(EditableAspect); + key = displayGroupKeyForAspect(EditableAspect); + if ((key != null) && (component instanceof JTextComponent)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { // treat bound key without display group as a value value = key; } - Boolean converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - - if ( converted != null ) - { - if ( converted.booleanValue() != ((JTextComponent)component).isEditable() ) - { - ((JTextComponent)component).setEditable( converted.booleanValue() ); + Boolean converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + + if (converted != null) { + if (converted.booleanValue() != ((JTextComponent) component).isEditable()) { + ((JTextComponent) component).setEditable(converted.booleanValue()); } } } // label aspect - displayGroup = displayGroupForAspect( LabelAspect ); - key = displayGroupKeyForAspect( LabelAspect ); - - if ( ( key != null ) - && ( component instanceof JTextComponent ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { + displayGroup = displayGroupForAspect(LabelAspect); + key = displayGroupKeyForAspect(LabelAspect); + + if ((key != null) && (component instanceof JTextComponent)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { // treat bound key without display group as a value value = key; } - Boolean converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - - if ( converted != null ) - { - if ( converted.booleanValue() ) - { - if ( component instanceof JTextComponent ) - { - if ( component instanceof JTextArea ) - { - areaToLabel( (JTextArea) component ); - } - else - { - fieldToLabel( (JTextComponent) component ); - } - } + Boolean converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + + if (converted != null) { + if (converted.booleanValue()) { + if (component instanceof JTextComponent) { + if (component instanceof JTextArea) { + areaToLabel((JTextArea) component); + } else { + fieldToLabel((JTextComponent) component); + } + } + } else { + if (component instanceof JTextComponent) { + if (component instanceof JTextArea) { + labelToArea((JTextArea) component); + } else { + labelToField((JTextComponent) component); + } + } } - else - { - if ( component instanceof JTextComponent ) - { - if ( component instanceof JTextArea ) - { - labelToArea( (JTextArea) component ); - } - else - { - labelToField( (JTextComponent ) component ); - } - } - } } } - } + } - private void fieldToLabel( JTextComponent aTextField ) - { - // turn on wrapping and disable editing and highlighting + private void fieldToLabel(JTextComponent aTextField) { + // turn on wrapping and disable editing and highlighting - aTextField.setEditable(false); - aTextField.setOpaque(false); + aTextField.setEditable(false); + aTextField.setOpaque(false); - // Set the border, colors and font to that of a label + // Set the border, colors and font to that of a label - LookAndFeel.installBorder(aTextField, "Label.border"); + LookAndFeel.installBorder(aTextField, "Label.border"); - LookAndFeel.installColorsAndFont(aTextField, - "Label.background", - "Label.foreground", - "Label.font"); - } + LookAndFeel.installColorsAndFont(aTextField, "Label.background", "Label.foreground", "Label.font"); + } - private void labelToField( JTextComponent aTextField ) - { - // turn on wrapping and disable editing and highlighting + private void labelToField(JTextComponent aTextField) { + // turn on wrapping and disable editing and highlighting - aTextField.setEditable(true); - aTextField.setOpaque(true); + aTextField.setEditable(true); + aTextField.setOpaque(true); - // Set the border, colors and font to that of a label + // Set the border, colors and font to that of a label - LookAndFeel.installBorder(aTextField, "TextField.border"); + LookAndFeel.installBorder(aTextField, "TextField.border"); - LookAndFeel.installColorsAndFont(aTextField, - "TextField.background", - "TextField.foreground", - "TextField.font"); - } + LookAndFeel.installColorsAndFont(aTextField, "TextField.background", "TextField.foreground", "TextField.font"); + } - private void areaToLabel( JTextArea aTextArea ) - { - // turn on wrapping and disable editing and highlighting + private void areaToLabel(JTextArea aTextArea) { + // turn on wrapping and disable editing and highlighting - aTextArea.setLineWrap(true); - aTextArea.setWrapStyleWord(true); - aTextArea.setEditable(false); + aTextArea.setLineWrap(true); + aTextArea.setWrapStyleWord(true); + aTextArea.setEditable(false); - // Set the text area's border, colors and font to - // that of a label + // Set the text area's border, colors and font to + // that of a label - LookAndFeel.installBorder(aTextArea, "Label.border"); + LookAndFeel.installBorder(aTextArea, "Label.border"); - LookAndFeel.installColorsAndFont(aTextArea, - "Label.background", - "Label.foreground", - "Label.font"); + LookAndFeel.installColorsAndFont(aTextArea, "Label.background", "Label.foreground", "Label.font"); } - private void labelToArea( JTextArea aTextArea ) - { - // turn on wrapping and disable editing and highlighting + private void labelToArea(JTextArea aTextArea) { + // turn on wrapping and disable editing and highlighting - aTextArea.setEditable(true); + aTextArea.setEditable(true); - // Set the border, colors and font to that of a label + // Set the border, colors and font to that of a label - LookAndFeel.installBorder(aTextArea, "TextArea.border"); + LookAndFeel.installBorder(aTextArea, "TextArea.border"); - LookAndFeel.installColorsAndFont(aTextArea, - "TextArea.background", - "TextArea.foreground", - "TextArea.font"); + LookAndFeel.installColorsAndFont(aTextArea, "TextArea.background", "TextArea.foreground", "TextArea.font"); } - - /** - * Forces this association to cause the object to - * stop editing and validate the user's input. - * @return false if there were problems validating, - * or true to continue. - */ - public boolean endEditing () - { - if ( keyTimer != null ) - { - keyTimer.stop(); - keyTimer.removeActionListener( this ); - keyTimer = null; - } + /** + * Forces this association to cause the object to stop editing and validate the + * user's input. + * + * @return false if there were problems validating, or true to continue. + */ + public boolean endEditing() { + if (keyTimer != null) { + keyTimer.stop(); + keyTimer.removeActionListener(this); + keyTimer = null; + } return writeValueToDisplayGroup(); - } + } /** - * Writes the value currently in the component - * to the selected object in the display group - * bound to the value aspect. - * @return false if there were problems validating, - * or true to continue. - */ - protected boolean writeValueToDisplayGroup() - { - boolean returnValue = true; - if ( hasDocument && !needsUpdate ) return true; - - EODisplayGroup displayGroup = valueDisplayGroup; - if ( displayGroup != null ) - { - String key = valueKey; - Object component = object(); - Object value = null; - try - { - //if ( getText.implementedByObject( component ) ) - //{ - value = getText.invoke( component ); - //} - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error updating display group", exc ); - } - - if ( ( wasNull ) && ( EMPTY_STRING.equals( value ) ) ) - { - value = null; - } - else - if ( format() != null ) - { - try - { - value = format().parseObject( value.toString() ); - } - catch ( ParseException exc ) - { - String message = exc.getMessage(); - //"That format was not recognized."; - if ( displayGroup.associationFailedToValidateValue( - this, value.toString(), key, exc, message ) ) - { - boolean wasListening = isListening; - isListening = false; - JOptionPane.showMessageDialog( - (Component)component, message ); - isListening = wasListening; - } - needsUpdate = false; - return false; - } - } - - needsUpdate = false; - - // only update if the value is different from the one in the display group - Object existingValue = displayGroup.selectedObjectValueForKey( key ); - if ( displayGroup.selectedObjects().size() == 1 ) - { - if ( existingValue == value ) return true; - if ( ( existingValue != null ) && ( existingValue.equals( value ) ) ) return true; - if ( ( value != null ) && ( value.equals( existingValue ) ) ) return true; - } - - // we might lose focus if display group displays a validation message - boolean wasListening = isListening; - isListening = false; - - Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); - while ( selectedIterator.hasNext() ) - { - int index = ( (Integer)selectedIterator.next() ).intValue(); - - if ( displayGroup.setValueForObjectAtIndex( value, index, key ) ) - { - isListening = wasListening; - needsUpdate = false; - } - else - { - isListening = wasListening; - needsUpdate = false; - returnValue = false; - } - } - - } - return returnValue; + * Writes the value currently in the component to the selected object in the + * display group bound to the value aspect. + * + * @return false if there were problems validating, or true to continue. + */ + protected boolean writeValueToDisplayGroup() { + boolean returnValue = true; + if (hasDocument && !needsUpdate) + return true; + + EODisplayGroup displayGroup = valueDisplayGroup; + if (displayGroup != null) { + String key = valueKey; + Object component = object(); + Object value = null; + try { + // if ( getText.implementedByObject( component ) ) + // { + value = getText.invoke(component); + // } + } catch (Exception exc) { + throw new WotonomyException("Error updating display group", exc); + } + + if ((wasNull) && (EMPTY_STRING.equals(value))) { + value = null; + } else if (format() != null) { + try { + value = format().parseObject(value.toString()); + } catch (ParseException exc) { + String message = exc.getMessage(); + // "That format was not recognized."; + if (displayGroup.associationFailedToValidateValue(this, value.toString(), key, exc, message)) { + boolean wasListening = isListening; + isListening = false; + JOptionPane.showMessageDialog((Component) component, message); + isListening = wasListening; + } + needsUpdate = false; + return false; + } + } + + needsUpdate = false; + + // only update if the value is different from the one in the display group + Object existingValue = displayGroup.selectedObjectValueForKey(key); + if (displayGroup.selectedObjects().size() == 1) { + if (existingValue == value) + return true; + if ((existingValue != null) && (existingValue.equals(value))) + return true; + if ((value != null) && (value.equals(existingValue))) + return true; + } + + // we might lose focus if display group displays a validation message + boolean wasListening = isListening; + isListening = false; + + Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); + while (selectedIterator.hasNext()) { + int index = ((Integer) selectedIterator.next()).intValue(); + + if (displayGroup.setValueForObjectAtIndex(value, index, key)) { + isListening = wasListening; + needsUpdate = false; + } else { + isListening = wasListening; + needsUpdate = false; + returnValue = false; + } + } + + } + return returnValue; } /** - * Sets the Format that is used to convert values from the display - * group to and from text that is displayed in the component. - * Having a formatter disables auto-updating. - */ - public void setFormat( Format aFormat ) - { + * Sets the Format that is used to convert values from the display group to and + * from text that is displayed in the component. Having a formatter disables + * auto-updating. + */ + public void setFormat(Format aFormat) { format = aFormat; } /** - * Gets the Format that is used to convert values from the display - * group to and from text that is displayed in the component. - */ - public Format format() - { + * Gets the Format that is used to convert values from the display group to and + * from text that is displayed in the component. + */ + public Format format() { return format; } - // interface ActionListener + // interface ActionListener /** - * Updates object on action performed. - */ - public void actionPerformed( ActionEvent evt ) - { - if ( keyTimer != null ) - { - keyTimer.stop(); - keyTimer.removeActionListener( this ); - keyTimer = null; - } - if ( ! isListening ) return; - if ( needsUpdate ) - { - writeValueToDisplayGroup(); - } + * Updates object on action performed. + */ + public void actionPerformed(ActionEvent evt) { + if (keyTimer != null) { + keyTimer.stop(); + keyTimer.removeActionListener(this); + keyTimer = null; + } + if (!isListening) + return; + if (needsUpdate) { + writeValueToDisplayGroup(); + } } - // interface FocusListener + // interface FocusListener /** - * Notifies of beginning of edit. - */ - public void focusGained(FocusEvent evt) - { - if ( ! isListening ) return; + * Notifies of beginning of edit. + */ + public void focusGained(FocusEvent evt) { + if (!isListening) + return; Object o; EODisplayGroup displayGroup; - Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidBeginEditing( this ); + Enumeration e = aspects().objectEnumerator(); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidBeginEditing(this); } } - } + } /** - * Updates object on focus lost and notifies of end of edit. - */ - public void focusLost(FocusEvent evt) - { - if ( ! isListening ) return; - if ( endEditing() ) - { + * Updates object on focus lost and notifies of end of edit. + */ + public void focusLost(FocusEvent evt) { + if (!isListening) + return; + if (endEditing()) { Object o; EODisplayGroup displayGroup; Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidEndEditing( this ); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidEndEditing(this); } } - } - else - { + } else { // probably should notify of a validation error here, } - } - - /** - * Returns whether the data model is updated for every change - * in the controlled component. If false, the data is only - * updated on focus lost or the enter key. Default is true. - */ - public boolean isAutoUpdating() - { - if ( format() != null ) return false; - return autoUpdating; - } - - /** - * Sets whether the data model is updated for every change - * in the controlled component. - */ - public void setAutoUpdating( boolean isAutoUpdating ) - { - autoUpdating = isAutoUpdating; - } - - /** - * Triggers the key timer to start. - */ - protected void queueUpdate() - { - if ( isAutoUpdating() ) - { - if ( keyTimer == null ) - { - keyTimer = new Timer( interval, this ); - } - keyTimer.restart(); - } - } - - // interface DocumentListener - - public void insertUpdate(DocumentEvent e) - { - if ( ! isListening ) return; - needsUpdate = true; - queueUpdate(); - } - - public void removeUpdate(DocumentEvent e) - { - if ( ! isListening ) return; - needsUpdate = true; - queueUpdate(); - } - - public void changedUpdate(DocumentEvent e) - { - if ( ! isListening ) return; - needsUpdate = true; - queueUpdate(); - } + } + + /** + * Returns whether the data model is updated for every change in the controlled + * component. If false, the data is only updated on focus lost or the enter key. + * Default is true. + */ + public boolean isAutoUpdating() { + if (format() != null) + return false; + return autoUpdating; + } + + /** + * Sets whether the data model is updated for every change in the controlled + * component. + */ + public void setAutoUpdating(boolean isAutoUpdating) { + autoUpdating = isAutoUpdating; + } + + /** + * Triggers the key timer to start. + */ + protected void queueUpdate() { + if (isAutoUpdating()) { + if (keyTimer == null) { + keyTimer = new Timer(interval, this); + } + keyTimer.restart(); + } + } + + // interface DocumentListener + + public void insertUpdate(DocumentEvent e) { + if (!isListening) + return; + needsUpdate = true; + queueUpdate(); + } + + public void removeUpdate(DocumentEvent e) { + if (!isListening) + return; + needsUpdate = true; + queueUpdate(); + } + + public void changedUpdate(DocumentEvent e) { + if (!isListening) + return; + needsUpdate = true; + queueUpdate(); + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.3 2004/01/28 18:34:57 mpowers - * Better handling for enabling. - * Now respecting enabledToSetSelectedObjectValueForKey from display group. + * Revision 1.3 2004/01/28 18:34:57 mpowers Better handling for enabling. Now + * respecting enabledToSetSelectedObjectValueForKey from display group. * - * Revision 1.2 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.2 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.1 2001/12/20 18:57:24 mpowers - * (Re-)Contributing TimedTextAssociation. Just like TA, except uses timers. + * Revision 1.1 2001/12/20 18:57:24 mpowers (Re-)Contributing + * TimedTextAssociation. Just like TA, except uses timers. * - * Revision 1.20 2001/10/26 19:58:06 mpowers - * Better handling for non-string types. We were testing with equals with the - * new value against the existing value in the component. Now we convert - * the new value to a string before comparing. Fixes case for properties - * of non-String types, like StringBuffer. + * Revision 1.20 2001/10/26 19:58:06 mpowers Better handling for non-string + * types. We were testing with equals with the new value against the existing + * value in the component. Now we convert the new value to a string before + * comparing. Fixes case for properties of non-String types, like StringBuffer. * - * Revision 1.19 2001/09/30 21:57:14 mpowers - * Timers were not getting cleaned up if breakConnection was called - * before the timer got a chance to fire. + * Revision 1.19 2001/09/30 21:57:14 mpowers Timers were not getting cleaned up + * if breakConnection was called before the timer got a chance to fire. * - * Revision 1.18 2001/08/22 15:42:26 mpowers - * Added support for JTextComponent label-izing. + * Revision 1.18 2001/08/22 15:42:26 mpowers Added support for JTextComponent + * label-izing. * - * Revision 1.17 2001/07/30 16:32:55 mpowers - * Implemented support for bulk-editing. Detail associations will now - * apply changes to all selected objects. + * Revision 1.17 2001/07/30 16:32:55 mpowers Implemented support for + * bulk-editing. Detail associations will now apply changes to all selected + * objects. * - * Revision 1.16 2001/07/17 19:53:37 mpowers - * Made some private fields protected for benefit of subclassers. + * Revision 1.16 2001/07/17 19:53:37 mpowers Made some private fields protected + * for benefit of subclassers. * - * Revision 1.15 2001/06/30 14:59:36 mpowers - * LabelAspect now sets the text field's opaque setting. + * Revision 1.15 2001/06/30 14:59:36 mpowers LabelAspect now sets the text + * field's opaque setting. * - * Revision 1.14 2001/06/29 14:54:08 mpowers - * Another fix for timers - timers were definitely causing a memory leak. + * Revision 1.14 2001/06/29 14:54:08 mpowers Another fix for timers - timers + * were definitely causing a memory leak. * - * Revision 1.13 2001/06/26 21:37:19 mpowers - * Fixed a null pointer in the new key timer scheme. + * Revision 1.13 2001/06/26 21:37:19 mpowers Fixed a null pointer in the new key + * timer scheme. * - * Revision 1.12 2001/06/25 14:46:03 mpowers - * Fixed a memory leak involving the use of timers. + * Revision 1.12 2001/06/25 14:46:03 mpowers Fixed a memory leak involving the + * use of timers. * - * Revision 1.11 2001/06/01 19:14:59 mpowers - * Text association's enabled aspect is now more discriminating. + * Revision 1.11 2001/06/01 19:14:59 mpowers Text association's enabled aspect + * is now more discriminating. * - * Revision 1.10 2001/05/18 21:07:24 mpowers - * Changed the way we handle failure to update object value. + * Revision 1.10 2001/05/18 21:07:24 mpowers Changed the way we handle failure + * to update object value. * - * Revision 1.9 2001/03/13 21:39:58 mpowers - * Improved validation handling. + * Revision 1.9 2001/03/13 21:39:58 mpowers Improved validation handling. * - * Revision 1.8 2001/03/12 12:49:10 mpowers - * Improved validation handling. - * Having a formatter disables auto-updating. + * Revision 1.8 2001/03/12 12:49:10 mpowers Improved validation handling. Having + * a formatter disables auto-updating. * - * Revision 1.7 2001/03/09 22:08:13 mpowers - * Now handling any objects that have a valid Document. - * No longer checking enabled before updating the enabled state. + * Revision 1.7 2001/03/09 22:08:13 mpowers Now handling any objects that have a + * valid Document. No longer checking enabled before updating the enabled state. * - * Revision 1.6 2001/03/07 19:57:32 mpowers - * Fixed paste error in IconAspect. + * Revision 1.6 2001/03/07 19:57:32 mpowers Fixed paste error in IconAspect. * - * Revision 1.4 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.4 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.3 2001/01/31 19:12:33 mpowers - * Implemented auto-updating in TextComponent. + * Revision 1.3 2001/01/31 19:12:33 mpowers Implemented auto-updating in + * TextComponent. * - * Revision 1.2 2001/01/10 15:53:58 mpowers - * Preventing a null pointer exception if getText were to return null, - * which doesn't happen for JTextFields but might happen for other objects. + * Revision 1.2 2001/01/10 15:53:58 mpowers Preventing a null pointer exception + * if getText were to return null, which doesn't happen for JTextFields but + * might happen for other objects. * - * Revision 1.1.1.1 2000/12/21 15:49:08 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:49:08 mpowers Contributing wotonomy. * - * Revision 1.13 2000/12/20 16:25:41 michael - * Added log to all files. + * Revision 1.13 2000/12/20 16:25:41 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeAssociation.java index 728643b..b0e3b09 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeAssociation.java @@ -39,172 +39,144 @@ import net.wotonomy.foundation.NSMutableArray; import net.wotonomy.ui.EODisplayGroup; /** -* TreeAssociation is a TreeModelAssociation further -* customized for JTrees. It binds a JTree to a display group's -* list of displayable objects, each of which may have -* a list of child objects managed by another display -* group, and so on. TreeAssociation works exactly -* like a ListAssociation, with the additional capability -* to specify a "Children" aspect, that will allow child -* objects to be retrieved from a parent display group. -* Note that the children aspect requires the bound -* display group to have a DataSource that can vend a -* DataSource appropriate for the bound key That data -* source is then used to create data sources for -* child nodes, and so on. -* -* <ul> -* -* <li>titles: a property convertable to a string for -* display in the nodes of the tree. The objects in -* the bound display group will be used to populate the -* initial root nodes of the tree (more accurately, -* children of the offscreen root node in the tree).</li> -* -* <li>children: a property of a node value that returns -* zero, one or many objects, each of which will correspond -* to a child node for the corresponding node in the tree. -* The data source of the bound display group is replaced -* a data source that populates the display group with -* the visible nodes in the tree component as determined by -* calling fetchObjectsIntoChildrenGroup. -* If this aspect is not bound, the tree behaves like a list. -* <br><br> -* Binding this aspect with a null display group is the same -* as binding it with the titles display group. -* In this configuration the contents of the titles -* display group will be replaced with the visible nodes in the -* tree component, as specified above, replacing the existing -* data source. -* <br><br> -* In that case, the display groups for the nodes in -* the tree will still use the original data source -* for resolving their children key, and programmatically -* setting the contents of the display group will still -* repopulate the root nodes of the tree. -* </li> -* -* <li>isLeaf: a property of a node value that returns -* a value convertable to a boolean value (aside from -* an actual boolean value, zeroes evaluate to true, -* as does any String containing "yes" or "true" or that -* is convertable to a number equal to zero; other values -* evaluate to false). -* <br><br> -* If the isLeaf aspect is not bound, -* the tree must force nodes to load their children to -* determine whether they are leaf nodes (in effect -* loading the grandchildren for any expanded node). -* If bound, child loading is deferred until the node -* is actually expanded. -* <br><br> -* For example, binding this value to a null -* display group and the key "false" will result in a -* deferred-loading tree that works much like Windows -* Explorer's network volume browser - all nodes appear -* with "pluses" until they are expanded. -* <br><br> -* Note that the display group is ignored: the property -* will be applied directly to the object corresponding -* to the node.</li> -* -* </ul> -* -* All other usage is as TreeModelAssociation. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ + * TreeAssociation is a TreeModelAssociation further customized for JTrees. It + * binds a JTree to a display group's list of displayable objects, each of which + * may have a list of child objects managed by another display group, and so on. + * TreeAssociation works exactly like a ListAssociation, with the additional + * capability to specify a "Children" aspect, that will allow child objects to + * be retrieved from a parent display group. Note that the children aspect + * requires the bound display group to have a DataSource that can vend a + * DataSource appropriate for the bound key That data source is then used to + * create data sources for child nodes, and so on. + * + * <ul> + * + * <li>titles: a property convertable to a string for display in the nodes of + * the tree. The objects in the bound display group will be used to populate the + * initial root nodes of the tree (more accurately, children of the offscreen + * root node in the tree).</li> + * + * <li>children: a property of a node value that returns zero, one or many + * objects, each of which will correspond to a child node for the corresponding + * node in the tree. The data source of the bound display group is replaced a + * data source that populates the display group with the visible nodes in the + * tree component as determined by calling fetchObjectsIntoChildrenGroup. If + * this aspect is not bound, the tree behaves like a list. <br> + * <br> + * Binding this aspect with a null display group is the same as binding it with + * the titles display group. In this configuration the contents of the titles + * display group will be replaced with the visible nodes in the tree component, + * as specified above, replacing the existing data source. <br> + * <br> + * In that case, the display groups for the nodes in the tree will still use the + * original data source for resolving their children key, and programmatically + * setting the contents of the display group will still repopulate the root + * nodes of the tree.</li> + * + * <li>isLeaf: a property of a node value that returns a value convertable to a + * boolean value (aside from an actual boolean value, zeroes evaluate to true, + * as does any String containing "yes" or "true" or that is convertable to a + * number equal to zero; other values evaluate to false). <br> + * <br> + * If the isLeaf aspect is not bound, the tree must force nodes to load their + * children to determine whether they are leaf nodes (in effect loading the + * grandchildren for any expanded node). If bound, child loading is deferred + * until the node is actually expanded. <br> + * <br> + * For example, binding this value to a null display group and the key "false" + * will result in a deferred-loading tree that works much like Windows + * Explorer's network volume browser - all nodes appear with "pluses" until they + * are expanded. <br> + * <br> + * Note that the display group is ignored: the property will be applied directly + * to the object corresponding to the node.</li> + * + * </ul> + * + * All other usage is as TreeModelAssociation. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ public class TreeAssociation extends TreeModelAssociation - implements FocusListener, TreeExpansionListener, TreeWillExpandListener, Runnable -{ - private boolean isExpanding; - private Vector nodeQueue; - private Vector structureQueue; - private boolean isRunning; - - /** - * Constructor expecting a JTree. - */ - public TreeAssociation ( Object anObject ) - { - super( anObject ); - init(); - } - - /** - * Constructor expecting a JTree or similar component - * and specifying a label for the root node. - */ - public TreeAssociation( Object anObject, Object aRootLabel ) - { - super( anObject ); - init(); - rootLabel = aRootLabel; - rootNode.setUserObject( aRootLabel ); - } - - // convenience - private JTree component() - { - return (JTree) object(); - } - - /** - * Called by both constructors. - */ - protected void init() - { - isExpanding = false; - isRunning = false; - nodeQueue = new Vector(); - structureQueue = new Vector(); - component().addFocusListener( this ); - component().addTreeExpansionListener( this ); - component().addTreeWillExpandListener( this ); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return ( anObject instanceof JTree ); - } - - /** - * Overridden to not fire events during initial population. - */ - public void establishConnection () - { - isExpanding = true; - super.establishConnection(); - isExpanding = false; - } - - // interface TreeSelectionListener - - public void valueChanged(TreeSelectionEvent e) - { - if ( ! isListening ) return; + implements FocusListener, TreeExpansionListener, TreeWillExpandListener, Runnable { + private boolean isExpanding; + private Vector nodeQueue; + private Vector structureQueue; + private boolean isRunning; + + /** + * Constructor expecting a JTree. + */ + public TreeAssociation(Object anObject) { + super(anObject); + init(); + } + + /** + * Constructor expecting a JTree or similar component and specifying a label for + * the root node. + */ + public TreeAssociation(Object anObject, Object aRootLabel) { + super(anObject); + init(); + rootLabel = aRootLabel; + rootNode.setUserObject(aRootLabel); + } + + // convenience + private JTree component() { + return (JTree) object(); + } + + /** + * Called by both constructors. + */ + protected void init() { + isExpanding = false; + isRunning = false; + nodeQueue = new Vector(); + structureQueue = new Vector(); + component().addFocusListener(this); + component().addTreeExpansionListener(this); + component().addTreeWillExpandListener(this); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return (anObject instanceof JTree); + } + + /** + * Overridden to not fire events during initial population. + */ + public void establishConnection() { + isExpanding = true; + super.establishConnection(); + isExpanding = false; + } + + // interface TreeSelectionListener + + public void valueChanged(TreeSelectionEvent e) { + if (!isListening) + return; // NOTE: This approach causes focus rectangle to perceptibly trail // the selection rectangle, presumably because we're called after the // new selection has been processed but before the focus is processed. // Users don't like it, so we're going with a preference to use // invokeLater. -/* - // paint immediately before updating the display group and - // before any potentially lengthy second-order effects happen: - // this improves user-perceived responsiveness of big apps - if ( object() instanceof javax.swing.JComponent ) - { - javax.swing.JComponent component = (javax.swing.JComponent)object(); - component.paintImmediately( component.getBounds() ); - } - selectFromSelectionModel(); -*/ + /* + * // paint immediately before updating the display group and // before any + * potentially lengthy second-order effects happen: // this improves + * user-perceived responsiveness of big apps if ( object() instanceof + * javax.swing.JComponent ) { javax.swing.JComponent component = + * (javax.swing.JComponent)object(); component.paintImmediately( + * component.getBounds() ); } selectFromSelectionModel(); + */ // NOTE: This approach uses invoke later to cause the update of // the display group (which could be lengthly if that in turn @@ -214,369 +186,291 @@ public class TreeAssociation extends TreeModelAssociation // will have to do a similar invoke later if they check the display // group. - Runnable select = new Runnable() - { - public void run() - { - selectFromSelectionModel(); - } - }; - - if ( selectionPaintedImmediately ) - { - if ( object() instanceof java.awt.Component ) - { - ((java.awt.Component)object()).repaint(); - } - EventQueue.invokeLater( select ); - } - else - { - select.run(); - } - } - - /** - * Overridden to check whether the node is visible - * in the tree on screen. Offscreen in a scrollpane - * does not count. - */ - public boolean isVisible(Object node) - { - JTree tree = (JTree) object(); - TreePath path = ((DisplayGroupNode)node).treePath(); - if ( tree.isVisible( path ) ) - { - Rectangle rowRect = tree.getPathBounds( path ); - if ( rowRect != null ) - { - Rectangle visible = tree.getVisibleRect(); - if ( visible != null ) - { + Runnable select = new Runnable() { + public void run() { + selectFromSelectionModel(); + } + }; + + if (selectionPaintedImmediately) { + if (object() instanceof java.awt.Component) { + ((java.awt.Component) object()).repaint(); + } + EventQueue.invokeLater(select); + } else { + select.run(); + } + } + + /** + * Overridden to check whether the node is visible in the tree on screen. + * Offscreen in a scrollpane does not count. + */ + public boolean isVisible(Object node) { + JTree tree = (JTree) object(); + TreePath path = ((DisplayGroupNode) node).treePath(); + if (tree.isVisible(path)) { + Rectangle rowRect = tree.getPathBounds(path); + if (rowRect != null) { + Rectangle visible = tree.getVisibleRect(); + if (visible != null) { //System.out.println( "isVisible: intersects: " + visible.intersects( rowRect ) ); - return visible.intersects( rowRect ); - } - } - } + return visible.intersects(rowRect); + } + } + } //System.out.println( "isVisible: false" ); - return false; - } - - /** - * Fires a tree nodes changed event to all listeners. - * Provided as a convenience if you need to make manual - * changes to the tree model. - */ - public void fireTreeNodesChanged(final Object source, - final Object[] path, - final int[] childIndices, - final Object[] children) - { - if ( !isExpanding ) - { - for ( int i = 0; i < children.length; i++ ) - { - nodeQueue.add( children[i] ); - } - if ( !isRunning ) - { - isRunning = true; - EventQueue.invokeLater( this ); - } - } - } - - /** - * Fires a tree nodes inserted event to all listeners. - * Provided as a convenience if you need to make manual - * changes to the tree model. - */ - public void fireTreeNodesInserted(Object source, - Object[] path, - int[] childIndices, - Object[] children) - { - if ( !isExpanding ) - { - super.fireTreeNodesInserted( source, path, childIndices, children ); - EventQueue.invokeLater( this ); - } - } - - /** - * Fires a tree nodes removed event to all listeners. - * Provided as a convenience if you need to make manual - * changes to the tree model. - */ - public void fireTreeNodesRemoved(Object source, - Object[] path, - int[] childIndices, - Object[] children) - { - if ( !isExpanding ) - { - super.fireTreeNodesRemoved( source, path, childIndices, children ); - EventQueue.invokeLater( this ); - } - } - - /** - * Fires a tree structure changed event to all listeners. - * Provided as a convenience if you need to make manual - * changes to the tree model. - */ - public void fireTreeStructureChanged(Object source, - Object[] path, - int[] childIndices, - Object[] children) - { - if ( !isExpanding ) - { - structureQueue.add( path[path.length-1] ); - if ( !isRunning ) - { - isRunning = true; - EventQueue.invokeLater( this ); - } - } - } - - /** - * Overridden to return all visible rows in the tree. - */ - public NSArray objectsFetchedIntoChildrenGroup() - { - JTree tree = (JTree) object(); - NSMutableArray objectList = new NSMutableArray(); - - int count = tree.getRowCount(); - for ( int i = 0; i < count; i++ ) - { - objectList.add( - ((DisplayGroupNode) tree.getPathForRow( i ).getLastPathComponent()).object() ); - } + return false; + } + + /** + * Fires a tree nodes changed event to all listeners. Provided as a convenience + * if you need to make manual changes to the tree model. + */ + public void fireTreeNodesChanged(final Object source, final Object[] path, final int[] childIndices, + final Object[] children) { + if (!isExpanding) { + for (int i = 0; i < children.length; i++) { + nodeQueue.add(children[i]); + } + if (!isRunning) { + isRunning = true; + EventQueue.invokeLater(this); + } + } + } + + /** + * Fires a tree nodes inserted event to all listeners. Provided as a convenience + * if you need to make manual changes to the tree model. + */ + public void fireTreeNodesInserted(Object source, Object[] path, int[] childIndices, Object[] children) { + if (!isExpanding) { + super.fireTreeNodesInserted(source, path, childIndices, children); + EventQueue.invokeLater(this); + } + } + + /** + * Fires a tree nodes removed event to all listeners. Provided as a convenience + * if you need to make manual changes to the tree model. + */ + public void fireTreeNodesRemoved(Object source, Object[] path, int[] childIndices, Object[] children) { + if (!isExpanding) { + super.fireTreeNodesRemoved(source, path, childIndices, children); + EventQueue.invokeLater(this); + } + } + + /** + * Fires a tree structure changed event to all listeners. Provided as a + * convenience if you need to make manual changes to the tree model. + */ + public void fireTreeStructureChanged(Object source, Object[] path, int[] childIndices, Object[] children) { + if (!isExpanding) { + structureQueue.add(path[path.length - 1]); + if (!isRunning) { + isRunning = true; + EventQueue.invokeLater(this); + } + } + } + + /** + * Overridden to return all visible rows in the tree. + */ + public NSArray objectsFetchedIntoChildrenGroup() { + JTree tree = (JTree) object(); + NSMutableArray objectList = new NSMutableArray(); + + int count = tree.getRowCount(); + for (int i = 0; i < count; i++) { + objectList.add(((DisplayGroupNode) tree.getPathForRow(i).getLastPathComponent()).object()); + } //new net.wotonomy.ui.swing.util.StackTraceInspector( Integer.toString( objectList.size() ) ); - return objectList; - } + return objectList; + } + + // interface FocusListener - // interface FocusListener - /** - * Notifies of beginning of edit. - */ - public void focusGained(FocusEvent evt) - { + * Notifies of beginning of edit. + */ + public void focusGained(FocusEvent evt) { Object o; EODisplayGroup displayGroup; - Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidBeginEditing( this ); + Enumeration e = aspects().objectEnumerator(); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidBeginEditing(this); } } - } + } /** - * Updates object on focus lost and notifies of end of edit. - */ - public void focusLost(FocusEvent evt) - { - if ( ! component().isEditing() ) - { - Object o; - EODisplayGroup displayGroup; - Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidEndEditing( this ); - } - } - } - } - - // interface TreeWillExpandListener - - public void treeWillExpand(TreeExpansionEvent event) - throws ExpandVetoException - { - isExpanding = true; - } - - public void treeWillCollapse(TreeExpansionEvent event) - throws ExpandVetoException - { - // do nothing - } - - // interface TreeExpansionListener - - /** - * Updates the children display group, if any. - */ - public void treeExpanded(TreeExpansionEvent event) - { //System.out.println( "treeExpanded: " + event.getPath().getLastPathComponent() ); - isExpanding = false; - if ( childrenDisplayGroup != null ) - { - removeAsListener(); // prevent data source refetch: see fetchObjects() - childrenDisplayGroup.fetch(); - addAsListener(); - } - } - - /** - * Updates the children display group, if any. - */ - public void treeCollapsed(TreeExpansionEvent event) - { - if ( childrenDisplayGroup != null ) - { - removeAsListener(); // prevent data source refetch: see fetchObjects() - childrenDisplayGroup.fetch(); - addAsListener(); - } - } - - // interface Runnable - - /** - * Fires any queued node changed and structure changed events. - * Typically invoked on a delayed event loop. - */ - public void run() - { - DisplayGroupNode node; - int index; - Iterator i; - - i = nodeQueue.iterator(); - while ( i.hasNext() ) - { - node = (DisplayGroupNode) i.next(); - index = ((DisplayGroupNode)node.parentGroup).getIndex( node ); - if ( ( index != -1 ) - && ( node.treePath().getParentPath() != null ) ) - { - super.fireTreeNodesChanged( - node, - node.treePath().getParentPath().getPath(), - new int[] { index }, - new Object[] { node } ); - } - } - nodeQueue.clear(); - - i = structureQueue.iterator(); - while ( i.hasNext() ) - { - node = (DisplayGroupNode) i.next(); - super.fireTreeStructureChanged( - node, - node.treePath().getPath(), - null, - null ); - } - structureQueue.clear(); - - isRunning = false; -/* - EventQueue.invokeLater( new Runnable() { public void run() { - ((JTree)object()).treeDidChange(); - ((JTree)object()).getParent().invalidate(); - ((JTree)object()).getParent().validate(); - ((JTree)object()).getParent().update( ((JTree)object()).getGraphics() ); - -// ((JTree)object()).getParent().doLayout(); -// ((JTree)object()).getParent().repaint(); -// ((JTree)object()).repaint(); -// ((JTree)object()).updateUI(); - } } ); -*/ - } + * Updates object on focus lost and notifies of end of edit. + */ + public void focusLost(FocusEvent evt) { + if (!component().isEditing()) { + Object o; + EODisplayGroup displayGroup; + Enumeration e = aspects().objectEnumerator(); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidEndEditing(this); + } + } + } + } + + // interface TreeWillExpandListener + + public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException { + isExpanding = true; + } + + public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException { + // do nothing + } + + // interface TreeExpansionListener + + /** + * Updates the children display group, if any. + */ + public void treeExpanded(TreeExpansionEvent event) { // System.out.println( "treeExpanded: " + + // event.getPath().getLastPathComponent() ); + isExpanding = false; + if (childrenDisplayGroup != null) { + removeAsListener(); // prevent data source refetch: see fetchObjects() + childrenDisplayGroup.fetch(); + addAsListener(); + } + } + + /** + * Updates the children display group, if any. + */ + public void treeCollapsed(TreeExpansionEvent event) { + if (childrenDisplayGroup != null) { + removeAsListener(); // prevent data source refetch: see fetchObjects() + childrenDisplayGroup.fetch(); + addAsListener(); + } + } + + // interface Runnable + + /** + * Fires any queued node changed and structure changed events. Typically invoked + * on a delayed event loop. + */ + public void run() { + DisplayGroupNode node; + int index; + Iterator i; + + i = nodeQueue.iterator(); + while (i.hasNext()) { + node = (DisplayGroupNode) i.next(); + index = ((DisplayGroupNode) node.parentGroup).getIndex(node); + if ((index != -1) && (node.treePath().getParentPath() != null)) { + super.fireTreeNodesChanged(node, node.treePath().getParentPath().getPath(), new int[] { index }, + new Object[] { node }); + } + } + nodeQueue.clear(); + + i = structureQueue.iterator(); + while (i.hasNext()) { + node = (DisplayGroupNode) i.next(); + super.fireTreeStructureChanged(node, node.treePath().getPath(), null, null); + } + structureQueue.clear(); + + isRunning = false; + /* + * EventQueue.invokeLater( new Runnable() { public void run() { + * ((JTree)object()).treeDidChange(); + * ((JTree)object()).getParent().invalidate(); + * ((JTree)object()).getParent().validate(); + * ((JTree)object()).getParent().update( ((JTree)object()).getGraphics() ); + * + * // ((JTree)object()).getParent().doLayout(); // + * ((JTree)object()).getParent().repaint(); // ((JTree)object()).repaint(); // + * ((JTree)object()).updateUI(); } } ); + */ + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.55 2004/02/05 02:18:50 mpowers - * Super was calling back into this class before init() was called. + * Revision 1.55 2004/02/05 02:18:50 mpowers Super was calling back into this + * class before init() was called. * - * Revision 1.54 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.54 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.53 2003/06/03 14:49:48 mpowers - * Now correctly calculating isVisible based on the component visible rect. - * Now deferring node changed events to a later event queue to allow repaints - * to happen after all changes have taken effect. + * Revision 1.53 2003/06/03 14:49:48 mpowers Now correctly calculating isVisible + * based on the component visible rect. Now deferring node changed events to a + * later event queue to allow repaints to happen after all changes have taken + * effect. * - * Revision 1.52 2002/05/03 21:41:18 mpowers - * No longer clearing the selection model when updating from display group: - * we now only modify if a change needs to be made. - * No longer listening for selection change during firing of delete events: - * delete events cause JTree's to update their selection model. - * Fix for paintsSelectionImmediately: TreeAssociation.processRecentChanges() - * must happen after the screen is painted, or the selection is not displayed. + * Revision 1.52 2002/05/03 21:41:18 mpowers No longer clearing the selection + * model when updating from display group: we now only modify if a change needs + * to be made. No longer listening for selection change during firing of delete + * events: delete events cause JTree's to update their selection model. Fix for + * paintsSelectionImmediately: TreeAssociation.processRecentChanges() must + * happen after the screen is painted, or the selection is not displayed. * - * Revision 1.51 2002/04/19 21:18:45 mpowers - * Removed tree event coalescing, which was causing way too many problems. - * The fireChangeEvent algorithm is way faster than before, so we should - * still be better off than before. At least now, we don't have to track - * whether the view component has encountered a particular node. + * Revision 1.51 2002/04/19 21:18:45 mpowers Removed tree event coalescing, + * which was causing way too many problems. The fireChangeEvent algorithm is way + * faster than before, so we should still be better off than before. At least + * now, we don't have to track whether the view component has encountered a + * particular node. * - * Revision 1.49 2002/04/12 21:05:58 mpowers - * Now distinguishing changes in titles group even better. + * Revision 1.49 2002/04/12 21:05:58 mpowers Now distinguishing changes in + * titles group even better. * - * Revision 1.48 2002/04/12 20:36:31 mpowers - * Now distinguishing between changes made on titles group by tree expansion - * versus external changes which should cause us to repopulate root nodes. + * Revision 1.48 2002/04/12 20:36:31 mpowers Now distinguishing between changes + * made on titles group by tree expansion versus external changes which should + * cause us to repopulate root nodes. * - * Revision 1.47 2002/04/10 21:20:04 mpowers - * Better handling for tree nodes when working with editing contexts. - * Better handling for invalidation. No longer broadcasting events - * when nodes have not been "registered" in the tree. + * Revision 1.47 2002/04/10 21:20:04 mpowers Better handling for tree nodes when + * working with editing contexts. Better handling for invalidation. No longer + * broadcasting events when nodes have not been "registered" in the tree. * - * Revision 1.46 2002/03/27 20:44:53 mpowers - * Added isVisible test for node. + * Revision 1.46 2002/03/27 20:44:53 mpowers Added isVisible test for node. * - * Revision 1.45 2002/03/08 23:19:57 mpowers - * Refactoring of DelegatingTreeDataSource to facilitate binding of titles - * and children aspects to the same display group. + * Revision 1.45 2002/03/08 23:19:57 mpowers Refactoring of + * DelegatingTreeDataSource to facilitate binding of titles and children aspects + * to the same display group. * - * Revision 1.44 2002/03/07 23:04:36 mpowers - * Refining TreeColumnAssociation. + * Revision 1.44 2002/03/07 23:04:36 mpowers Refining TreeColumnAssociation. * - * Revision 1.43 2002/03/06 13:04:15 mpowers - * Implemented cascading qualifiers in tree nodes. + * Revision 1.43 2002/03/06 13:04:15 mpowers Implemented cascading qualifiers in + * tree nodes. * - * Revision 1.42 2002/03/05 23:18:28 mpowers - * Added documentation. - * Added isSelectionPaintedImmediate and isSelectionTracking attributes - * to TableAssociation. - * Added getTableAssociation to TableColumnAssociation. + * Revision 1.42 2002/03/05 23:18:28 mpowers Added documentation. Added + * isSelectionPaintedImmediate and isSelectionTracking attributes to + * TableAssociation. Added getTableAssociation to TableColumnAssociation. * - * Revision 1.41 2002/03/01 23:42:08 mpowers - * Implemented TreeColumnAssociation, and updated documentation. + * Revision 1.41 2002/03/01 23:42:08 mpowers Implemented TreeColumnAssociation, + * and updated documentation. * - * Revision 1.40 2002/03/01 20:41:39 mpowers - * Now a focus listener and an expansion listener. + * Revision 1.40 2002/03/01 20:41:39 mpowers Now a focus listener and an + * expansion listener. * - * Revision 1.39 2002/02/27 23:19:17 mpowers - * Refactoring of TreeAssociation to create TreeModelAssociation parent. + * Revision 1.39 2002/02/27 23:19:17 mpowers Refactoring of TreeAssociation to + * create TreeModelAssociation parent. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeColumnAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeColumnAssociation.java index f6c90d0..26f2eda 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeColumnAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeColumnAssociation.java @@ -26,306 +26,254 @@ import net.wotonomy.ui.EODisplayGroup; import net.wotonomy.ui.swing.components.TreeTableCellRenderer; /** -* TreeColumnAssociation is a TableColumnAssocation -* that works like a TreeAssociation, allowing any -* table to display hierarchical data in a tabular format. -* This class is mainly a convenience for connecting a -* TreeAssociation to a JTree to a TreeTableCellRenderer -* to a TableColumn.<br><br> -* -* Like TableColumnAssociation, you must call setTable() -* to specify the JTable to be used. (The corresponding -* table association will direct all column header sorting -* to the root node of the tree association.) -* -* You may also optionally call setTree() to specify a -* customized JTree to be used. If not specified, a -* slightly customized JTree will be used (see createTree() -* and configureColumn()). -* -* TreeColumnAssociation supports the following bindings, -* just as TableColumnAssociation does: -* <ul> -* <li>value: a property convertable to a string for -* display in the cells of the table column. This -* binding is equivalent to the titles binding of -* TreeAssociation.</li> -* -* <li>editable: a property convertable to a boolean -* that determines the editability of the corresponding -* cells in the column.</li> -* </ul> -* -* TreeColumnAssociation additionally supports the following -* bindings, just as TreeAssociation does, except that the -* value binding is used instead of the titles binding. -* <ul> -* <li>children: a property of a node value that returns -* zero, one or many objects, each of which will correspond -* to a child node for the corresponding node in the tree. -* If this aspect is not bound, the tree behaves like a list.</li> -* -* <li>isLeaf: a property of a node value that returns -* a value convertable to a boolean value. -* If the isLeaf aspect is not bound, the tree will force -* nodes to load their children to determine whether they -* are leaf nodes (in effect loading the grandchildren for -* any expanded node). -* </li> -* </ul> -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class TreeColumnAssociation extends TableColumnAssociation -{ - static final NSArray aspects = - new NSArray( new Object[] { - ValueAspect, EditableAspect, ChildrenAspect, IsLeafAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "table" - } ); - + * TreeColumnAssociation is a TableColumnAssocation that works like a + * TreeAssociation, allowing any table to display hierarchical data in a tabular + * format. This class is mainly a convenience for connecting a TreeAssociation + * to a JTree to a TreeTableCellRenderer to a TableColumn.<br> + * <br> + * + * Like TableColumnAssociation, you must call setTable() to specify the JTable + * to be used. (The corresponding table association will direct all column + * header sorting to the root node of the tree association.) + * + * You may also optionally call setTree() to specify a customized JTree to be + * used. If not specified, a slightly customized JTree will be used (see + * createTree() and configureColumn()). + * + * TreeColumnAssociation supports the following bindings, just as + * TableColumnAssociation does: + * <ul> + * <li>value: a property convertable to a string for display in the cells of the + * table column. This binding is equivalent to the titles binding of + * TreeAssociation.</li> + * + * <li>editable: a property convertable to a boolean that determines the + * editability of the corresponding cells in the column.</li> + * </ul> + * + * TreeColumnAssociation additionally supports the following bindings, just as + * TreeAssociation does, except that the value binding is used instead of the + * titles binding. + * <ul> + * <li>children: a property of a node value that returns zero, one or many + * objects, each of which will correspond to a child node for the corresponding + * node in the tree. If this aspect is not bound, the tree behaves like a + * list.</li> + * + * <li>isLeaf: a property of a node value that returns a value convertable to a + * boolean value. If the isLeaf aspect is not bound, the tree will force nodes + * to load their children to determine whether they are leaf nodes (in effect + * loading the grandchildren for any expanded node).</li> + * </ul> + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class TreeColumnAssociation extends TableColumnAssociation { + static final NSArray aspects = new NSArray( + new Object[] { ValueAspect, EditableAspect, ChildrenAspect, IsLeafAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "table" }); + EODisplayGroup childrenDisplayGroup, leafDisplayGroup; String childrenKey, leafKey; - - TreeModelAssociation treeAssociation; - JTree tree; - - /** - * Constructor specifying the object to be controlled by this - * association. Throws an exception if the object is not - * a TableColumn. - */ - public TreeColumnAssociation ( Object anObject ) - { - super( anObject ); - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect. <ul> - * <li>"A" attribute: the aspect can be bound to - * an attribute.</li> - * <li>"1" to-one: the aspect can be bound to a - * property that returns a single object.</li> - * <li>"M" to-one: the aspect can be bound to a - * property that returns multiple objects.</li> - * </ul> - * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Binds the specified aspect of this association to the - * specified key on the specified display group. - */ - public void bindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey ) - { - if ( ChildrenAspect.equals( anAspect ) ) - { - childrenDisplayGroup = aDisplayGroup; + + TreeModelAssociation treeAssociation; + JTree tree; + + /** + * Constructor specifying the object to be controlled by this association. + * Throws an exception if the object is not a TableColumn. + */ + public TreeColumnAssociation(Object anObject) { + super(anObject); + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + * <ul> + * <li>"A" attribute: the aspect can be bound to an attribute.</li> + * <li>"1" to-one: the aspect can be bound to a property that returns a single + * object.</li> + * <li>"M" to-one: the aspect can be bound to a property that returns multiple + * objects.</li> + * </ul> + * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Binds the specified aspect of this association to the specified key on the + * specified display group. + */ + public void bindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + if (ChildrenAspect.equals(anAspect)) { + childrenDisplayGroup = aDisplayGroup; childrenKey = aKey; } - if ( IsLeafAspect.equals( anAspect ) ) - { + if (IsLeafAspect.equals(anAspect)) { leafDisplayGroup = aDisplayGroup; leafKey = aKey; } - super.bindAspect( anAspect, aDisplayGroup, aKey ); + super.bindAspect(anAspect, aDisplayGroup, aKey); } - - /** - * Overridden to call createTree if necessary, - * call configureColumn, call createTreeAssociation - * if necessary, and then call to super. - */ - public void establishConnection () - { - if ( tree == null ) - { - tree = createTree(); - } - - configureColumn( (TableColumn) object() ); - - if ( treeAssociation == null ) - { - treeAssociation = createTreeAssociation( tree ); - } - - treeAssociation.bindAspect( TitlesAspect, valueDisplayGroup, valueKey ); - if ( childrenKey != null ) - { - treeAssociation.bindAspect( ChildrenAspect, childrenDisplayGroup, childrenKey ); - } - if ( leafKey != null ) - { - treeAssociation.bindAspect( IsLeafAspect, leafDisplayGroup, leafKey ); - } - - // ensure table association's source is tree asssociation's child group - getTableAssociation().bindAspect( - SourceAspect, treeAssociation.childrenDisplayGroup, "" ); - - treeAssociation.establishConnection(); - - table.setRowHeight( tree.getRowHeight() ); - - super.establishConnection(); - - // cause sort ordering to apply to root node of tree - if ( childrenKey != null ) - { - getTableAssociation().sortTarget = - (EODisplayGroup) treeAssociation.getRoot(); - } - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - super.breakConnection(); - treeAssociation.breakConnection(); - - // restore original source display group - getTableAssociation().sortTarget = null; - } - - /** - * Called by establishConnection if setTree was not called. - * This implementation returns a stock JTree. Override - * to provide your own customized JTree. Note that - * TreeTableCellRenderer will further customize this tree - * when configureColumn is called. - */ - protected JTree createTree() - { - return new JTree(); - } - - /** - * Called by establishConnection to create a tree association - * only if no tree association has already been created. - * This implementation returns a stock TreeAssociation. - * Override to return your own customized TreeAssociation. - */ - protected TreeModelAssociation createTreeAssociation( JTree aTree ) - { - return new TreeAssociation( aTree ); - } - - /** - * Called by establishConnection to configure the column - * with a TreeTableCellRenderer using the current JTree. - * Override to further customize the column, or customize - * your column yourself after the call to establishConnection. - */ - protected void configureColumn( TableColumn aColumn ) - { - aColumn.setCellRenderer( new TreeTableCellRenderer( tree ) ); - } - + /** - * Gets the JTree currently used for the column renderer. - * If not specified, returns null. - */ - public JTree getTree() - { - return tree; + * Overridden to call createTree if necessary, call configureColumn, call + * createTreeAssociation if necessary, and then call to super. + */ + public void establishConnection() { + if (tree == null) { + tree = createTree(); + } + + configureColumn((TableColumn) object()); + + if (treeAssociation == null) { + treeAssociation = createTreeAssociation(tree); + } + + treeAssociation.bindAspect(TitlesAspect, valueDisplayGroup, valueKey); + if (childrenKey != null) { + treeAssociation.bindAspect(ChildrenAspect, childrenDisplayGroup, childrenKey); + } + if (leafKey != null) { + treeAssociation.bindAspect(IsLeafAspect, leafDisplayGroup, leafKey); + } + + // ensure table association's source is tree asssociation's child group + getTableAssociation().bindAspect(SourceAspect, treeAssociation.childrenDisplayGroup, ""); + + treeAssociation.establishConnection(); + + table.setRowHeight(tree.getRowHeight()); + + super.establishConnection(); + + // cause sort ordering to apply to root node of tree + if (childrenKey != null) { + getTableAssociation().sortTarget = (EODisplayGroup) treeAssociation.getRoot(); + } + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + super.breakConnection(); + treeAssociation.breakConnection(); + + // restore original source display group + getTableAssociation().sortTarget = null; } /** - * Gets the TreeModelAssociation currently used for the tree. - * If not tree is not specified, returns null. - */ - public TreeModelAssociation getTreeModelAssociation() - { - if ( tree == null ) return null; - if (!( tree.getModel() instanceof TreeModelAssociation )) return null; - return (TreeModelAssociation) tree.getModel(); + * Called by establishConnection if setTree was not called. This implementation + * returns a stock JTree. Override to provide your own customized JTree. Note + * that TreeTableCellRenderer will further customize this tree when + * configureColumn is called. + */ + protected JTree createTree() { + return new JTree(); } /** - * Sets the JTree to be used for the column renderer. - * If not specified, createTree() will be called to create a JTree. - */ - public void setTree( JTree aTree ) - { - tree = aTree; + * Called by establishConnection to create a tree association only if no tree + * association has already been created. This implementation returns a stock + * TreeAssociation. Override to return your own customized TreeAssociation. + */ + protected TreeModelAssociation createTreeAssociation(JTree aTree) { + return new TreeAssociation(aTree); + } + + /** + * Called by establishConnection to configure the column with a + * TreeTableCellRenderer using the current JTree. Override to further customize + * the column, or customize your column yourself after the call to + * establishConnection. + */ + protected void configureColumn(TableColumn aColumn) { + aColumn.setCellRenderer(new TreeTableCellRenderer(tree)); + } + + /** + * Gets the JTree currently used for the column renderer. If not specified, + * returns null. + */ + public JTree getTree() { + return tree; + } + + /** + * Gets the TreeModelAssociation currently used for the tree. If not tree is not + * specified, returns null. + */ + public TreeModelAssociation getTreeModelAssociation() { + if (tree == null) + return null; + if (!(tree.getModel() instanceof TreeModelAssociation)) + return null; + return (TreeModelAssociation) tree.getModel(); + } + + /** + * Sets the JTree to be used for the column renderer. If not specified, + * createTree() will be called to create a JTree. + */ + public void setTree(JTree aTree) { + tree = aTree; } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.9 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.9 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.8 2002/05/03 21:31:50 mpowers - * Actually works better without selectionPaintedImmediately. + * Revision 1.8 2002/05/03 21:31:50 mpowers Actually works better without + * selectionPaintedImmediately. * - * Revision 1.7 2002/04/12 21:05:58 mpowers - * Now distinguishing changes in titles group even better. + * Revision 1.7 2002/04/12 21:05:58 mpowers Now distinguishing changes in titles + * group even better. * - * Revision 1.6 2002/03/08 23:18:48 mpowers - * Added accessor for tree association. + * Revision 1.6 2002/03/08 23:18:48 mpowers Added accessor for tree association. * - * Revision 1.5 2002/03/07 23:04:36 mpowers - * Refining TreeColumnAssociation. + * Revision 1.5 2002/03/07 23:04:36 mpowers Refining TreeColumnAssociation. * - * Revision 1.4 2002/03/06 13:04:16 mpowers - * Implemented cascading qualifiers in tree nodes. + * Revision 1.4 2002/03/06 13:04:16 mpowers Implemented cascading qualifiers in + * tree nodes. * - * Revision 1.3 2002/03/05 23:18:28 mpowers - * Added documentation. - * Added isSelectionPaintedImmediate and isSelectionTracking attributes - * to TableAssociation. - * Added getTableAssociation to TableColumnAssociation. + * Revision 1.3 2002/03/05 23:18:28 mpowers Added documentation. Added + * isSelectionPaintedImmediate and isSelectionTracking attributes to + * TableAssociation. Added getTableAssociation to TableColumnAssociation. * - * Revision 1.2 2002/03/04 22:48:22 mpowers - * Now working with table association to sort the root node. + * Revision 1.2 2002/03/04 22:48:22 mpowers Now working with table association + * to sort the root node. * - * Revision 1.1 2002/03/01 23:42:09 mpowers - * Implemented TreeColumnAssociation, and updated documentation. + * Revision 1.1 2002/03/01 23:42:09 mpowers Implemented TreeColumnAssociation, + * and updated documentation. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeModelAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeModelAssociation.java index 86bfa69..b0070d4 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeModelAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeModelAssociation.java @@ -48,1704 +48,1396 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* TreeModelAssociation binds a JTree or similar component -* that uses a TreeModel to a display group's -* list of displayable objects, each of which may have -* a list of child objects managed by another display -* group, and so on. TreeModelAssociation works exactly -* like a ListAssociation, with the additional capability -* to specify a "Children" aspect, that will allow child -* objects to be retrieved from a parent display group. -* -* <ul> -* -* <li>titles: a property convertable to a string for -* display in the nodes of the tree. The objects in -* the bound display group will be used to populate the -* initial root nodes of the tree (more accurately, -* children of the offscreen root node in the tree).</li> -* -* <li>children: a property of a node value that returns -* zero, one or many objects, each of which will correspond -* to a child node for the corresponding node in the tree. -* The data source of the bound display group is replaced -* a data source that populates the display group with -* the selection in the tree component as determined by -* calling fetchObjectsIntoChildrenGroup. -* If this aspect is not bound, the tree behaves like a list. -* <br><br> -* Binding this aspect with a null display group is the same -* as binding it with the titles display group. -* In this configuration the contents of the titles -* display group will be replaced with the selection in the -* tree component, as specified above, replacing the existing -* data source. -* <br><br> -* In that case, the display groups for the nodes in -* the tree will still use the original data source -* for resolving their children key, and programmatically -* setting the contents of the display group will still -* repopulate the root nodes of the tree. -* </li> -* -* <li>isLeaf: a property of a node value that returns -* a value convertable to a boolean value (aside from -* an actual boolean value, zeroes evaluate to true, -* as does any String containing "yes" or "true" or that -* is convertable to a number equal to zero; other values -* evaluate to false). -* <br><br> -* If the isLeaf aspect is not bound, -* the tree must force nodes to load their children to -* determine whether they are leaf nodes (in effect -* loading the grandchildren for any expanded node). -* If bound, child loading is deferred until the node -* is actually expanded. -* <br><br> -* For example, binding this value to a null -* display group and the key "false" will result in a -* deferred-loading tree that works much like Windows -* Explorer's network volume browser - all nodes appear -* with "pluses" until they are expanded. -* <br><br> -* Note that the display group is ignored: the property -* will be applied directly to the object corresponding -* to the node.</li> -* -* </ul> -* -* This class acts as the TreeModel for the controlled -* component: calling yourcomponent.getModel() will -* return this association. The tree model methods on -* this class are public and may be used to affect changes -* on the controlled components.<br><br> -* -* The titles display group's contents are inserted -* into a new display group that acts as the root node. -* After that point, changes in the titles display group -* will cause the tree model to reset itself, creating -* a new display group for the root node. -* <br><br> -* -* If a separate display group is bound to the children -* aspect, it will -* be used to hold the selected objects and their siblings -* and selection will be maintained there, and the titles -* display group selection will not be updated. -* Any editing or detail associations should in that case -* be attached to the children display group, not the titles -* group. <br><br> -* -* Each node in the tree is an EODisplayGroup that -* contains the child objects of the object it represents -* in the tree. These objects can be programmatically -* inserted, updated, or removed using DisplayGroup -* methods. Each node's takes its parent group's -* sortOrderings until a sort ordering is explicitly -* specified - setting a sort ordering to null will resume -* using the parent group's sort ordering.<br><br> -* -* Each node in the tree also implements MutableTreeNode. -* The value that a node represents is the titles property -* value of the object in the parent's displayed objects -* list at the index corresponding to the index of the node. -* Calling toString on a node returns the string representation -* of the titles property value, and setUserObject will update -* that value directly in the corresponding object. -* Moving a node from one parent to another will remove the -* actual object in the parent display group and insert it -* into the destination display group.<br><br> -* -* In short, any nodes obtained from this class' -* implementation of TreeModel may be cast as either -* EODisplayGroup or MutableTreeNode and maybe be -* programmatically manipulated in either manner. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class TreeModelAssociation extends EOAssociation - implements TreeModel, TreeSelectionListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - TitlesAspect, ChildrenAspect, IsLeafAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "model" - } ); - - private final static NSSelector getSelectionModel = - new NSSelector( "getSelectionModel" ); - private final static NSSelector setModel = - new NSSelector( "setModel", - new Class[] { TreeModel.class } ); - - EODisplayGroup titlesDisplayGroup, childrenDisplayGroup, leafDisplayGroup; - String titlesKey, childrenKey, leafKey; - DisplayGroupNode rootNode; - Vector listeners; - Object rootLabel; - - TreeSelectionModel selectionModel; - boolean selectionPaintedImmediately; - - boolean insertingChild; - boolean insertingAfter; - - EOObserverProxy recentChangesObserver; - - private boolean pleaseSelectRootNode; - - /** - * Constructor expecting a JTree or any other object - * that has void setModel(TreeModel) and TreeModel getSelectionModel() - * methods. This tree association will be used for the TreeModel. - * The root node will be labeled "Root". <br><br> - * - * As an alternate way to use a TreeModelAssociation, you may pass a - * TreeSelectionModel to the constructor and then manually set your - * component to use this class as its TreeModel. - */ - public TreeModelAssociation ( Object anObject ) - { - super( anObject ); - - titlesDisplayGroup = null; - titlesKey = null; - childrenDisplayGroup = null; - childrenKey = null; - leafDisplayGroup = null; - leafKey = null; - listeners = new Vector(); - - selectionPaintedImmediately = false; - - // after display group nodes process recent changes - recentChangesObserver = new EOObserverProxy( - this, new NSSelector( "processRecentChanges" ), - EODelayedObserver.ObserverPrioritySixth ); - EOObserverCenter.addObserver( recentChangesObserver, this ); - - insertingChild = true; - insertingAfter = true; - - pleaseSelectRootNode = false; - - rootLabel = "Root"; - rootNode = createNode( null, null ); - } - - /** - * Constructor expecting a JTree or similar component - * and specifying a label for the root node. - */ - public TreeModelAssociation( Object anObject, Object aRootLabel ) - { - this( anObject ); - rootLabel = aRootLabel; - rootNode.setUserObject( aRootLabel ); - } - - /** - * Gets the current root label. - */ - public Object rootLabel() - { - return rootLabel; - } - - /** - * Gets the current root label. - */ - public Object getRootLabel() - { - return rootLabel(); - } - - /** - * Sets the root label. - */ - public void setRootLabel( Object aLabel ) - { - rootLabel = aLabel; - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect. <ul> - * <li>"A" attribute: the aspect can be bound to - * an attribute.</li> - * <li>"1" to-one: the aspect can be bound to a - * property that returns a single object.</li> - * <li>"M" to-one: the aspect can be bound to a - * property that returns multiple objects.</li> - * </ul> - * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return setModel.implementedByObject( anObject ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return TitlesAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey ) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Binds the specified aspect of this association to the - * specified key on the specified display group. - */ - public void bindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey ) - { - if ( TitlesAspect.equals( anAspect ) ) - { - titlesDisplayGroup = aDisplayGroup; - titlesKey = aKey; - } - if ( ChildrenAspect.equals( anAspect ) ) - { - childrenDisplayGroup = aDisplayGroup; - childrenKey = aKey; - } - if ( IsLeafAspect.equals( anAspect ) ) - { - leafDisplayGroup = aDisplayGroup; - leafKey = aKey; - } - if ( childrenDisplayGroup == null ) - { - childrenDisplayGroup = titlesDisplayGroup; - } - super.bindAspect( anAspect, aDisplayGroup, aKey ); - } - - /** - * Establishes a connection between this association - * and the controlled object. Subclasses should begin - * listening for events from their controlled object here. - */ - public void establishConnection () - { - if ( titlesDisplayGroup == null ) - { - throw new WotonomyException( - "TreeModelAssociation: Titles aspect must be bound" ); - } - - // populate the root node - rootNode = createNode( titlesDisplayGroup, null ); - rootNode.setObjectArray( titlesDisplayGroup.displayedObjects() ); - rootNode.setSortOrderings( titlesDisplayGroup.sortOrderings() ); - - EODataSource dataSource = childrenDisplayGroup.dataSource(); - if ( dataSource == null ) dataSource = titlesDisplayGroup.dataSource(); - while ( dataSource instanceof DelegatingTreeDataSource ) - { // unwrap any existing delegating data sources - dataSource = ((DelegatingTreeDataSource)dataSource).delegateDataSource; - } - // create a new delegating data source - childrenDisplayGroup.setDataSource( - new DelegatingTreeDataSource( this, dataSource ) ); - - //TODO: find out why omitting this line causes weird selection behavior - childrenDisplayGroup.setSortOrderings( new NSArray() ); - - // check for alternate usage - if ( object() instanceof TreeSelectionModel ) - { - selectionModel = (TreeSelectionModel) object(); - } - else // use specified object - { - try - { - setModel.invoke( object(), new Object[] { this } ); - selectionModel = (TreeSelectionModel) - getSelectionModel.invoke( object(), new Object[] {} ); - } - catch ( Exception exc ) - { - throw new WotonomyException( exc ); - } - } - - addAsListener(); - super.establishConnection(); -/* - fireRootStructureChanged(); - -// titlesGroupChanged = true; -// subjectChanged(); - - // update the children group - removeAsListener(); - childrenDisplayGroup.fetch(); - addAsListener(); - - // update selection - selectFromDisplayGroup( titlesDisplayGroup ); -*/ - } - - protected void fireRootStructureChanged() - { - int count = rootNode.displayedObjects().count(); - int[] childIndices = new int[ count ]; - Object[] children = new Object[ count ]; - for ( int i = 0; i < count; i++ ) - { - childIndices[i] = i; - children[i] = rootNode.getChildNodeAt( i ); - } - - // must fire a tree structure changed with children, - // otherwise the tree gets weird selection behavior - fireTreeStructureChanged( this, new Object[] { rootNode }, - childIndices, children ); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - if ( childrenDisplayGroup != null ) - { - if ( childrenDisplayGroup.dataSource() instanceof DelegatingTreeDataSource ) - { - if ( titlesDisplayGroup == childrenDisplayGroup ) - { - titlesDisplayGroup.setDataSource( ((DelegatingTreeDataSource) - childrenDisplayGroup.dataSource()).delegateDataSource ); - } - else - { - childrenDisplayGroup.setDataSource( null ); - } - } - } - - removeAsListener(); - super.breakConnection(); - } - - protected void addAsListener() - { - isListening = true; - selectionModel.addTreeSelectionListener( this ); - } - - protected void removeAsListener() - { - isListening = false; - selectionModel.removeTreeSelectionListener( this ); - } - - protected boolean isListening = false; - private boolean pleaseIgnore = false; - protected boolean titlesGroupChanged = false; - protected boolean childrenGroupChanged = false; - - /** - * Overridden to better discriminate what is changed. - */ - public void objectWillChange( Object anObject ) - { - if ( ! isListening ) return; - - if ( anObject == titlesDisplayGroup ) - { - titlesGroupChanged = true; - } - if ( anObject == childrenDisplayGroup ) - { - childrenGroupChanged = true; - if ( childrenDisplayGroup.qualifier() != null ) - { - if ( ( rootNode.qualifier() == null ) || - ! childrenDisplayGroup.qualifier().equals( rootNode.qualifier() ) ) - { - // quietly move qualifier from children group to root node - rootNode.setQualifier( childrenDisplayGroup.qualifier() ); - childrenDisplayGroup.setQualifier( null ); - rootNode.updateDisplayedObjects(); - } - } - } - super.objectWillChange( anObject ); - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged () - { - // titles aspect - if ( titlesGroupChanged ) - { - if ( titlesDisplayGroup.contentsChanged() ) - { - NSArray displayedObjects = titlesDisplayGroup.displayedObjects(); - NSArray childrenObjects; - if ( titlesDisplayGroup != childrenDisplayGroup - || displayedObjects.count() - != (childrenObjects = objectsFetchedIntoChildrenGroup()).count() - || ! displayedObjects.containsAll( childrenObjects ) ) - { - populateFromDisplayGroup( displayedObjects ); - } - } - } - - if ( childrenDisplayGroup.selectionChanged() && !childrenDisplayGroup.contentsChanged() ) - { - selectFromDisplayGroup( childrenDisplayGroup ); - } - - titlesGroupChanged = false; - childrenGroupChanged = false; - } - - /** - * Called by subjectChanged() in response to an external change in the titles display group. - */ - void populateFromDisplayGroup( List displayedObjects ) - { - // trigger processRecentChanges - willChange(); - - // workaround: see below - int previousCount = rootNode.previouslyDisplayedObjects.length; - - // update the root node - rootNode.setObjectArray( displayedObjects ); - - //FIXME: workaround for what appears to be a bug in JTree: - // if root node is not visible and has no children, insert events are ignored - if ( previousCount == 0 ) - { - fireRootStructureChanged(); - } - } - - /** - * Package access so DisplayGroupNode can replace the selection after an update. - */ - void selectFromDisplayGroup( EODisplayGroup aDisplayGroup ) - { // System.out.println( "selectFromDisplayGroup: " + aDisplayGroup.selectedObjects() ); - - removeAsListener(); - - TreePath[] paths = selectionModel.getSelectionPaths(); - NSArray selectedObjects = aDisplayGroup.selectedObjects(); - - // assemble current selection list - List treeSelection = new LinkedList(); - if ( paths != null ) - { - for ( int i = 0; i < paths.length; i ++ ) - { - treeSelection.add( - ((DisplayGroupNode)paths[i].getLastPathComponent()).getUserObject() ); - } - } - - if ( ! ( selectedObjects.size() == treeSelection.size() - && treeSelection.containsAll( selectedObjects ) ) ) - { - selectionModel.clearSelection(); - - // workaround to select root node from valueChanged() - if ( pleaseSelectRootNode ) - { - selectionModel.addSelectionPath( new TreePath( this.getRoot() ) ); - pleaseSelectRootNode = false; - } - - //FIXME: display group is assumed to have only one instance of each object - for ( int i = 0; i < selectedObjects.count(); i++ ) - { - //FIXME: selects only the first instance for now - //selectionModel.addSelectionPaths( - // getPathsForObject( - // selectedObjects.objectAtIndex( i ) ) ); - selectionModel.addSelectionPath( - getPathForObject( - selectedObjects.objectAtIndex( i ) ) ); - } - } - - addAsListener(); - } - - /** - * Returns the first node found that represents the - * specified object, or null if not found. - * This implementation simply calls getPathForObject. - */ - public Object getNodeForObject( Object anObject ) - { - TreePath result = getPathForObject( anObject ); - if ( result != null ) - { - return result.getLastPathComponent(); - } - return null; - } - - /** - * Returns the object represented by the specified node - * which must be a display group node from this tree. - */ - public Object getObjectForNode( Object aNode ) - { - if ( aNode instanceof DisplayGroupNode ) - { - return ((DisplayGroupNode)aNode).getUserObject(); - } - - // not a display group node - throw new WotonomyException( - "Not a display group node: " + aNode ); - } - - /** - * Returns the tree path for the specified node, - * which must be a display group node from this tree. - */ - public TreePath getPathForNode( Object aNode ) - { - if ( aNode instanceof DisplayGroupNode ) - { - return ((DisplayGroupNode)aNode).treePath(); - } - - // not a display group node - throw new WotonomyException( - "Not a display group node: " + aNode ); - } - - /** - * Returns the first tree path for the node that represents - * the specified object, or null if the object does not exist in this tree. - * This implementation does a breadth-first search of the tree - * for the object, looking only at nodes that have been loaded. - * This means that if the object does not exist in the tree, - * the entire tree must be traversed. - */ - public TreePath getPathForObject( Object anObject ) - { - return getPathForObjectInPath( anObject, new TreePath( this.getRoot() ) ); - } - - /** - * Returns the tree path for the node that represents - * the specified object, - * or null if the object does not exist in this tree. - * This implementation does a breadth-first search of the tree - * for the object, looking only at nodes that have been loaded. - * This means that the entire tree is traversed. - */ - public TreePath[] getPathsForObject( Object anObject ) - { - return getPathsForObjectInPath( anObject, new TreePath( this.getRoot() ) ); - } - - /** - * A breadth-first search of the tree starting - * at the specified tree path, comparing by reference. - * Returns immediately with the first match. - */ - private TreePath getPathForObjectInPath( Object anObject, TreePath aPath ) - { - LinkedList queue = new LinkedList(); - - // add the specified path - queue.addLast( aPath ); - - return processQueue( anObject, queue, null ); - } - - /** - * A breadth-first search of the tree starting - * at the specified tree path, comparing by reference. - * The entire branch is searched before returning - * an array of all matches. - */ - private TreePath[] getPathsForObjectInPath( Object anObject, TreePath aPath ) - { - LinkedList queue = new LinkedList(); - - // add the specified path - queue.addLast( aPath ); - - List result = new LinkedList(); - processQueue( anObject, queue, result ); - TreePath[] paths = new TreePath[ result.size() ]; - for ( int i = 0; i < paths.length; i++ ) - { - paths[i] = (TreePath) result.get(i); - } - return paths; - } - - /** - * Processes the specified queue, appending results to aResult if it exists, - * or returning immediately with a TreePath is aResult is null. - */ - private TreePath processQueue( Object anObject, LinkedList aQueue, List aResult ) - { - TreePath path; - while ( ! aQueue.isEmpty() ) - { - path = (TreePath) aQueue.removeFirst(); - path = checkNode( anObject, path, aQueue ); - if ( path != null ) - { - if ( aResult != null ) - { - aResult.add( path ); - } - else - { - return path; - } - } - } - return null; - } - - /** - * Compares the specified object by reference each of the children of - * the node at the end of the specified tree path, adding nodes that - * do not match but have fetched object to the end of the specified queue. - * Returns the path of the first child node that matches the specified object, - * or null if no match was found. - */ - private TreePath checkNode( Object anObject, TreePath aPath, LinkedList aQueue ) - { - TreePath result = null; - Object child; - Object parent = aPath.getLastPathComponent(); - int count = getChildCount( parent ); - - for ( int i = 0; i < count; i++ ) - { - child = getChild( parent, i ); - - // add to queue if node has fetched children - if ( ((DisplayGroupNode)child).isFetched ) - { - aQueue.addLast( aPath.pathByAddingChild( child ) ); - } - - // compares by reference - if ( ((DisplayGroupNode)child).object() == anObject ) - { - // assumes same object cannot be in display group twice - result = aPath.pathByAddingChild( child ); + * TreeModelAssociation binds a JTree or similar component that uses a TreeModel + * to a display group's list of displayable objects, each of which may have a + * list of child objects managed by another display group, and so on. + * TreeModelAssociation works exactly like a ListAssociation, with the + * additional capability to specify a "Children" aspect, that will allow child + * objects to be retrieved from a parent display group. + * + * <ul> + * + * <li>titles: a property convertable to a string for display in the nodes of + * the tree. The objects in the bound display group will be used to populate the + * initial root nodes of the tree (more accurately, children of the offscreen + * root node in the tree).</li> + * + * <li>children: a property of a node value that returns zero, one or many + * objects, each of which will correspond to a child node for the corresponding + * node in the tree. The data source of the bound display group is replaced a + * data source that populates the display group with the selection in the tree + * component as determined by calling fetchObjectsIntoChildrenGroup. If this + * aspect is not bound, the tree behaves like a list. <br> + * <br> + * Binding this aspect with a null display group is the same as binding it with + * the titles display group. In this configuration the contents of the titles + * display group will be replaced with the selection in the tree component, as + * specified above, replacing the existing data source. <br> + * <br> + * In that case, the display groups for the nodes in the tree will still use the + * original data source for resolving their children key, and programmatically + * setting the contents of the display group will still repopulate the root + * nodes of the tree.</li> + * + * <li>isLeaf: a property of a node value that returns a value convertable to a + * boolean value (aside from an actual boolean value, zeroes evaluate to true, + * as does any String containing "yes" or "true" or that is convertable to a + * number equal to zero; other values evaluate to false). <br> + * <br> + * If the isLeaf aspect is not bound, the tree must force nodes to load their + * children to determine whether they are leaf nodes (in effect loading the + * grandchildren for any expanded node). If bound, child loading is deferred + * until the node is actually expanded. <br> + * <br> + * For example, binding this value to a null display group and the key "false" + * will result in a deferred-loading tree that works much like Windows + * Explorer's network volume browser - all nodes appear with "pluses" until they + * are expanded. <br> + * <br> + * Note that the display group is ignored: the property will be applied directly + * to the object corresponding to the node.</li> + * + * </ul> + * + * This class acts as the TreeModel for the controlled component: calling + * yourcomponent.getModel() will return this association. The tree model methods + * on this class are public and may be used to affect changes on the controlled + * components.<br> + * <br> + * + * The titles display group's contents are inserted into a new display group + * that acts as the root node. After that point, changes in the titles display + * group will cause the tree model to reset itself, creating a new display group + * for the root node. <br> + * <br> + * + * If a separate display group is bound to the children aspect, it will be used + * to hold the selected objects and their siblings and selection will be + * maintained there, and the titles display group selection will not be updated. + * Any editing or detail associations should in that case be attached to the + * children display group, not the titles group. <br> + * <br> + * + * Each node in the tree is an EODisplayGroup that contains the child objects of + * the object it represents in the tree. These objects can be programmatically + * inserted, updated, or removed using DisplayGroup methods. Each node's takes + * its parent group's sortOrderings until a sort ordering is explicitly + * specified - setting a sort ordering to null will resume using the parent + * group's sort ordering.<br> + * <br> + * + * Each node in the tree also implements MutableTreeNode. The value that a node + * represents is the titles property value of the object in the parent's + * displayed objects list at the index corresponding to the index of the node. + * Calling toString on a node returns the string representation of the titles + * property value, and setUserObject will update that value directly in the + * corresponding object. Moving a node from one parent to another will remove + * the actual object in the parent display group and insert it into the + * destination display group.<br> + * <br> + * + * In short, any nodes obtained from this class' implementation of TreeModel may + * be cast as either EODisplayGroup or MutableTreeNode and maybe be + * programmatically manipulated in either manner. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class TreeModelAssociation extends EOAssociation implements TreeModel, TreeSelectionListener { + static final NSArray aspects = new NSArray(new Object[] { TitlesAspect, ChildrenAspect, IsLeafAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "model" }); + + private final static NSSelector getSelectionModel = new NSSelector("getSelectionModel"); + private final static NSSelector setModel = new NSSelector("setModel", new Class[] { TreeModel.class }); + + EODisplayGroup titlesDisplayGroup, childrenDisplayGroup, leafDisplayGroup; + String titlesKey, childrenKey, leafKey; + DisplayGroupNode rootNode; + Vector listeners; + Object rootLabel; + + TreeSelectionModel selectionModel; + boolean selectionPaintedImmediately; + + boolean insertingChild; + boolean insertingAfter; + + EOObserverProxy recentChangesObserver; + + private boolean pleaseSelectRootNode; + + /** + * Constructor expecting a JTree or any other object that has void + * setModel(TreeModel) and TreeModel getSelectionModel() methods. This tree + * association will be used for the TreeModel. The root node will be labeled + * "Root". <br> + * <br> + * + * As an alternate way to use a TreeModelAssociation, you may pass a + * TreeSelectionModel to the constructor and then manually set your component to + * use this class as its TreeModel. + */ + public TreeModelAssociation(Object anObject) { + super(anObject); + + titlesDisplayGroup = null; + titlesKey = null; + childrenDisplayGroup = null; + childrenKey = null; + leafDisplayGroup = null; + leafKey = null; + listeners = new Vector(); + + selectionPaintedImmediately = false; + + // after display group nodes process recent changes + recentChangesObserver = new EOObserverProxy(this, new NSSelector("processRecentChanges"), + EODelayedObserver.ObserverPrioritySixth); + EOObserverCenter.addObserver(recentChangesObserver, this); + + insertingChild = true; + insertingAfter = true; + + pleaseSelectRootNode = false; + + rootLabel = "Root"; + rootNode = createNode(null, null); + } + + /** + * Constructor expecting a JTree or similar component and specifying a label for + * the root node. + */ + public TreeModelAssociation(Object anObject, Object aRootLabel) { + this(anObject); + rootLabel = aRootLabel; + rootNode.setUserObject(aRootLabel); + } + + /** + * Gets the current root label. + */ + public Object rootLabel() { + return rootLabel; + } + + /** + * Gets the current root label. + */ + public Object getRootLabel() { + return rootLabel(); + } + + /** + * Sets the root label. + */ + public void setRootLabel(Object aLabel) { + rootLabel = aLabel; + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + * <ul> + * <li>"A" attribute: the aspect can be bound to an attribute.</li> + * <li>"1" to-one: the aspect can be bound to a property that returns a single + * object.</li> + * <li>"M" to-one: the aspect can be bound to a property that returns multiple + * objects.</li> + * </ul> + * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return setModel.implementedByObject(anObject); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return TitlesAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Binds the specified aspect of this association to the specified key on the + * specified display group. + */ + public void bindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + if (TitlesAspect.equals(anAspect)) { + titlesDisplayGroup = aDisplayGroup; + titlesKey = aKey; + } + if (ChildrenAspect.equals(anAspect)) { + childrenDisplayGroup = aDisplayGroup; + childrenKey = aKey; + } + if (IsLeafAspect.equals(anAspect)) { + leafDisplayGroup = aDisplayGroup; + leafKey = aKey; + } + if (childrenDisplayGroup == null) { + childrenDisplayGroup = titlesDisplayGroup; + } + super.bindAspect(anAspect, aDisplayGroup, aKey); + } + + /** + * Establishes a connection between this association and the controlled object. + * Subclasses should begin listening for events from their controlled object + * here. + */ + public void establishConnection() { + if (titlesDisplayGroup == null) { + throw new WotonomyException("TreeModelAssociation: Titles aspect must be bound"); + } + + // populate the root node + rootNode = createNode(titlesDisplayGroup, null); + rootNode.setObjectArray(titlesDisplayGroup.displayedObjects()); + rootNode.setSortOrderings(titlesDisplayGroup.sortOrderings()); + + EODataSource dataSource = childrenDisplayGroup.dataSource(); + if (dataSource == null) + dataSource = titlesDisplayGroup.dataSource(); + while (dataSource instanceof DelegatingTreeDataSource) { // unwrap any existing delegating data sources + dataSource = ((DelegatingTreeDataSource) dataSource).delegateDataSource; + } + // create a new delegating data source + childrenDisplayGroup.setDataSource(new DelegatingTreeDataSource(this, dataSource)); + + // TODO: find out why omitting this line causes weird selection behavior + childrenDisplayGroup.setSortOrderings(new NSArray()); + + // check for alternate usage + if (object() instanceof TreeSelectionModel) { + selectionModel = (TreeSelectionModel) object(); + } else // use specified object + { + try { + setModel.invoke(object(), new Object[] { this }); + selectionModel = (TreeSelectionModel) getSelectionModel.invoke(object(), new Object[] {}); + } catch (Exception exc) { + throw new WotonomyException(exc); + } + } + + addAsListener(); + super.establishConnection(); + /* + * fireRootStructureChanged(); + * + * // titlesGroupChanged = true; // subjectChanged(); + * + * // update the children group removeAsListener(); + * childrenDisplayGroup.fetch(); addAsListener(); + * + * // update selection selectFromDisplayGroup( titlesDisplayGroup ); + */ + } + + protected void fireRootStructureChanged() { + int count = rootNode.displayedObjects().count(); + int[] childIndices = new int[count]; + Object[] children = new Object[count]; + for (int i = 0; i < count; i++) { + childIndices[i] = i; + children[i] = rootNode.getChildNodeAt(i); + } + + // must fire a tree structure changed with children, + // otherwise the tree gets weird selection behavior + fireTreeStructureChanged(this, new Object[] { rootNode }, childIndices, children); + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + if (childrenDisplayGroup != null) { + if (childrenDisplayGroup.dataSource() instanceof DelegatingTreeDataSource) { + if (titlesDisplayGroup == childrenDisplayGroup) { + titlesDisplayGroup.setDataSource( + ((DelegatingTreeDataSource) childrenDisplayGroup.dataSource()).delegateDataSource); + } else { + childrenDisplayGroup.setDataSource(null); + } + } + } + + removeAsListener(); + super.breakConnection(); + } + + protected void addAsListener() { + isListening = true; + selectionModel.addTreeSelectionListener(this); + } + + protected void removeAsListener() { + isListening = false; + selectionModel.removeTreeSelectionListener(this); + } + + protected boolean isListening = false; + private boolean pleaseIgnore = false; + protected boolean titlesGroupChanged = false; + protected boolean childrenGroupChanged = false; + + /** + * Overridden to better discriminate what is changed. + */ + public void objectWillChange(Object anObject) { + if (!isListening) + return; + + if (anObject == titlesDisplayGroup) { + titlesGroupChanged = true; + } + if (anObject == childrenDisplayGroup) { + childrenGroupChanged = true; + if (childrenDisplayGroup.qualifier() != null) { + if ((rootNode.qualifier() == null) || !childrenDisplayGroup.qualifier().equals(rootNode.qualifier())) { + // quietly move qualifier from children group to root node + rootNode.setQualifier(childrenDisplayGroup.qualifier()); + childrenDisplayGroup.setQualifier(null); + rootNode.updateDisplayedObjects(); + } + } + } + super.objectWillChange(anObject); + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { + // titles aspect + if (titlesGroupChanged) { + if (titlesDisplayGroup.contentsChanged()) { + NSArray displayedObjects = titlesDisplayGroup.displayedObjects(); + NSArray childrenObjects; + if (titlesDisplayGroup != childrenDisplayGroup + || displayedObjects.count() != (childrenObjects = objectsFetchedIntoChildrenGroup()).count() + || !displayedObjects.containsAll(childrenObjects)) { + populateFromDisplayGroup(displayedObjects); + } + } + } + + if (childrenDisplayGroup.selectionChanged() && !childrenDisplayGroup.contentsChanged()) { + selectFromDisplayGroup(childrenDisplayGroup); + } + + titlesGroupChanged = false; + childrenGroupChanged = false; + } + + /** + * Called by subjectChanged() in response to an external change in the titles + * display group. + */ + void populateFromDisplayGroup(List displayedObjects) { + // trigger processRecentChanges + willChange(); + + // workaround: see below + int previousCount = rootNode.previouslyDisplayedObjects.length; + + // update the root node + rootNode.setObjectArray(displayedObjects); + + // FIXME: workaround for what appears to be a bug in JTree: + // if root node is not visible and has no children, insert events are ignored + if (previousCount == 0) { + fireRootStructureChanged(); + } + } + + /** + * Package access so DisplayGroupNode can replace the selection after an update. + */ + void selectFromDisplayGroup(EODisplayGroup aDisplayGroup) { // System.out.println( "selectFromDisplayGroup: " + + // aDisplayGroup.selectedObjects() ); + + removeAsListener(); + + TreePath[] paths = selectionModel.getSelectionPaths(); + NSArray selectedObjects = aDisplayGroup.selectedObjects(); + + // assemble current selection list + List treeSelection = new LinkedList(); + if (paths != null) { + for (int i = 0; i < paths.length; i++) { + treeSelection.add(((DisplayGroupNode) paths[i].getLastPathComponent()).getUserObject()); + } + } + + if (!(selectedObjects.size() == treeSelection.size() && treeSelection.containsAll(selectedObjects))) { + selectionModel.clearSelection(); + + // workaround to select root node from valueChanged() + if (pleaseSelectRootNode) { + selectionModel.addSelectionPath(new TreePath(this.getRoot())); + pleaseSelectRootNode = false; + } + + // FIXME: display group is assumed to have only one instance of each object + for (int i = 0; i < selectedObjects.count(); i++) { + // FIXME: selects only the first instance for now + // selectionModel.addSelectionPaths( + // getPathsForObject( + // selectedObjects.objectAtIndex( i ) ) ); + selectionModel.addSelectionPath(getPathForObject(selectedObjects.objectAtIndex(i))); + } + } + + addAsListener(); + } + + /** + * Returns the first node found that represents the specified object, or null if + * not found. This implementation simply calls getPathForObject. + */ + public Object getNodeForObject(Object anObject) { + TreePath result = getPathForObject(anObject); + if (result != null) { + return result.getLastPathComponent(); + } + return null; + } + + /** + * Returns the object represented by the specified node which must be a display + * group node from this tree. + */ + public Object getObjectForNode(Object aNode) { + if (aNode instanceof DisplayGroupNode) { + return ((DisplayGroupNode) aNode).getUserObject(); + } + + // not a display group node + throw new WotonomyException("Not a display group node: " + aNode); + } + + /** + * Returns the tree path for the specified node, which must be a display group + * node from this tree. + */ + public TreePath getPathForNode(Object aNode) { + if (aNode instanceof DisplayGroupNode) { + return ((DisplayGroupNode) aNode).treePath(); + } + + // not a display group node + throw new WotonomyException("Not a display group node: " + aNode); + } + + /** + * Returns the first tree path for the node that represents the specified + * object, or null if the object does not exist in this tree. This + * implementation does a breadth-first search of the tree for the object, + * looking only at nodes that have been loaded. This means that if the object + * does not exist in the tree, the entire tree must be traversed. + */ + public TreePath getPathForObject(Object anObject) { + return getPathForObjectInPath(anObject, new TreePath(this.getRoot())); + } + + /** + * Returns the tree path for the node that represents the specified object, or + * null if the object does not exist in this tree. This implementation does a + * breadth-first search of the tree for the object, looking only at nodes that + * have been loaded. This means that the entire tree is traversed. + */ + public TreePath[] getPathsForObject(Object anObject) { + return getPathsForObjectInPath(anObject, new TreePath(this.getRoot())); + } + + /** + * A breadth-first search of the tree starting at the specified tree path, + * comparing by reference. Returns immediately with the first match. + */ + private TreePath getPathForObjectInPath(Object anObject, TreePath aPath) { + LinkedList queue = new LinkedList(); + + // add the specified path + queue.addLast(aPath); + + return processQueue(anObject, queue, null); + } + + /** + * A breadth-first search of the tree starting at the specified tree path, + * comparing by reference. The entire branch is searched before returning an + * array of all matches. + */ + private TreePath[] getPathsForObjectInPath(Object anObject, TreePath aPath) { + LinkedList queue = new LinkedList(); + + // add the specified path + queue.addLast(aPath); + + List result = new LinkedList(); + processQueue(anObject, queue, result); + TreePath[] paths = new TreePath[result.size()]; + for (int i = 0; i < paths.length; i++) { + paths[i] = (TreePath) result.get(i); + } + return paths; + } + + /** + * Processes the specified queue, appending results to aResult if it exists, or + * returning immediately with a TreePath is aResult is null. + */ + private TreePath processQueue(Object anObject, LinkedList aQueue, List aResult) { + TreePath path; + while (!aQueue.isEmpty()) { + path = (TreePath) aQueue.removeFirst(); + path = checkNode(anObject, path, aQueue); + if (path != null) { + if (aResult != null) { + aResult.add(path); + } else { + return path; + } + } + } + return null; + } + + /** + * Compares the specified object by reference each of the children of the node + * at the end of the specified tree path, adding nodes that do not match but + * have fetched object to the end of the specified queue. Returns the path of + * the first child node that matches the specified object, or null if no match + * was found. + */ + private TreePath checkNode(Object anObject, TreePath aPath, LinkedList aQueue) { + TreePath result = null; + Object child; + Object parent = aPath.getLastPathComponent(); + int count = getChildCount(parent); + + for (int i = 0; i < count; i++) { + child = getChild(parent, i); + + // add to queue if node has fetched children + if (((DisplayGroupNode) child).isFetched) { + aQueue.addLast(aPath.pathByAddingChild(child)); + } + + // compares by reference + if (((DisplayGroupNode) child).object() == anObject) { + // assumes same object cannot be in display group twice + result = aPath.pathByAddingChild(child); //System.out.println( "TRUE: " + ((DisplayGroupNode)child).object() + " == " + anObject ); - } + } // else // { //System.out.println( ((DisplayGroupNode)child).object() + " != " + anObject ); // } - } - return result; - } - - // interface TreeSelectionListener - - public void valueChanged(TreeSelectionEvent e) - { - if ( ! isListening ) return; - selectFromSelectionModel(); - } - - /** - * Determines whether the selection should be painted - * immediately after the user clicks and therefore - * before the children display group is updated. - * When the children group is bound to many associations - * or is bound to a master-detail association, updating - * the display group can take a perceptibly long time. - * This property defaults to false. - * @see #setSelectionPaintedImmediately - */ - public boolean isSelectionPaintedImmediately() - { - return selectionPaintedImmediately; - } - - /** - * Sets whether the selection should be painted immediately. - * Setting this property to true will let the tree paint - * first before the display group is updated. - * This means that any tree selection listers will - * also be notified before the display group is updated - * and will have to invokeLater if they want to see the - * updated display group. - */ - public void setSelectionPaintedImmediately( boolean isImmediate ) - { - selectionPaintedImmediately = isImmediate; - } - - /** - * Package access so DisplayGroupNode can replace the selection. - * Returns the display group containing the current selection: titles or children. - */ - EODisplayGroup selectFromSelectionModel() - { // System.out.print( "selectFromSelectionModel: " ); - removeAsListener(); - DisplayGroupNode node; - TreePath parentPath; - TreePath[] selectedPaths = selectionModel.getSelectionPaths(); - NSMutableArray selectionList = new NSMutableArray(); - if ( selectedPaths != null ) - { - for ( int i = 0; i < selectedPaths.length; i++ ) - { - // root node is zero - ignore root node - if ( ( selectedPaths[i].getLastPathComponent() == rootNode ) ) - { - // select root in selectFromDisplayGroup() - pleaseSelectRootNode = true; - } - else - { - node = (DisplayGroupNode) - selectedPaths[i].getLastPathComponent(); - Object o = node.object(); - - if ( selectionList.indexOfIdenticalObject(o) == NSArray.NotFound ) - { - selectionList.addObject( o ); - } - } - } - } - childrenDisplayGroup.fetch(); //note that we're not currently listening for changes - if ( ! childrenDisplayGroup.selectObjectsIdenticalTo( selectionList ) ) - { - addAsListener(); // because we don't have a listener stack - selectFromDisplayGroup( childrenDisplayGroup ); - removeAsListener(); - } - addAsListener(); - return childrenDisplayGroup; // titles is now children if children not explicitly set - } - - // interface TreeModel - - public Object getRoot() - { - return rootNode; - } - - public Object getChild(Object parent, int index) - { - // interestingly, this gets called by - // BasicTreeUI.paintVerticalPartOfLeg for - // the last child of each expanded tree node. - Object result = ((DisplayGroupNode)parent).getChildNodeAt( index ); + } + return result; + } + + // interface TreeSelectionListener + + public void valueChanged(TreeSelectionEvent e) { + if (!isListening) + return; + selectFromSelectionModel(); + } + + /** + * Determines whether the selection should be painted immediately after the user + * clicks and therefore before the children display group is updated. When the + * children group is bound to many associations or is bound to a master-detail + * association, updating the display group can take a perceptibly long time. + * This property defaults to false. + * + * @see #setSelectionPaintedImmediately + */ + public boolean isSelectionPaintedImmediately() { + return selectionPaintedImmediately; + } + + /** + * Sets whether the selection should be painted immediately. Setting this + * property to true will let the tree paint first before the display group is + * updated. This means that any tree selection listers will also be notified + * before the display group is updated and will have to invokeLater if they want + * to see the updated display group. + */ + public void setSelectionPaintedImmediately(boolean isImmediate) { + selectionPaintedImmediately = isImmediate; + } + + /** + * Package access so DisplayGroupNode can replace the selection. Returns the + * display group containing the current selection: titles or children. + */ + EODisplayGroup selectFromSelectionModel() { // System.out.print( "selectFromSelectionModel: " ); + removeAsListener(); + DisplayGroupNode node; + TreePath parentPath; + TreePath[] selectedPaths = selectionModel.getSelectionPaths(); + NSMutableArray selectionList = new NSMutableArray(); + if (selectedPaths != null) { + for (int i = 0; i < selectedPaths.length; i++) { + // root node is zero - ignore root node + if ((selectedPaths[i].getLastPathComponent() == rootNode)) { + // select root in selectFromDisplayGroup() + pleaseSelectRootNode = true; + } else { + node = (DisplayGroupNode) selectedPaths[i].getLastPathComponent(); + Object o = node.object(); + + if (selectionList.indexOfIdenticalObject(o) == NSArray.NotFound) { + selectionList.addObject(o); + } + } + } + } + childrenDisplayGroup.fetch(); // note that we're not currently listening for changes + if (!childrenDisplayGroup.selectObjectsIdenticalTo(selectionList)) { + addAsListener(); // because we don't have a listener stack + selectFromDisplayGroup(childrenDisplayGroup); + removeAsListener(); + } + addAsListener(); + return childrenDisplayGroup; // titles is now children if children not explicitly set + } + + // interface TreeModel + + public Object getRoot() { + return rootNode; + } + + public Object getChild(Object parent, int index) { + // interestingly, this gets called by + // BasicTreeUI.paintVerticalPartOfLeg for + // the last child of each expanded tree node. + Object result = ((DisplayGroupNode) parent).getChildNodeAt(index); //((DisplayGroupNode)parent).suppressRecentChangeProcessing(); -return result; + return result; // return ((DisplayGroupNode)parent).getChildNodeAt( index ); - } + } - public int getChildCount(Object parent) - { - int result = ((DisplayGroupNode)parent).getChildCount(); + public int getChildCount(Object parent) { + int result = ((DisplayGroupNode) parent).getChildCount(); //((DisplayGroupNode)parent).suppressRecentChangeProcessing(); -return result; + return result; // return ((DisplayGroupNode)parent).getChildCount(); - } + } - public boolean isLeaf(Object node) - { - boolean result = ((DisplayGroupNode)node).isLeaf(); + public boolean isLeaf(Object node) { + boolean result = ((DisplayGroupNode) node).isLeaf(); //((DisplayGroupNode)node).suppressRecentChangeProcessing(); -return result; + return result; // return ((DisplayGroupNode)node).isLeaf(); - } - - /** - * Returns whether this node is visible in the UI. - * This implementation returns true. - * <br><br> - * Subclasses should return false if they can - * determine that the node is not displayed or - * expanded or otherwise visible. Non-visible - * nodes will fetch only when they are shown. - */ - public boolean isVisible(Object node) - { - return true; - } - - public void valueForPathChanged(TreePath path, Object newValue) - { - ((DisplayGroupNode)path.getLastPathComponent()).setUserObject( newValue ); - } - - public int getIndexOfChild(Object parent, Object child) - { - int result = ((DisplayGroupNode)parent).getIndex( (DisplayGroupNode) child ); + } + + /** + * Returns whether this node is visible in the UI. This implementation returns + * true. <br> + * <br> + * Subclasses should return false if they can determine that the node is not + * displayed or expanded or otherwise visible. Non-visible nodes will fetch only + * when they are shown. + */ + public boolean isVisible(Object node) { + return true; + } + + public void valueForPathChanged(TreePath path, Object newValue) { + ((DisplayGroupNode) path.getLastPathComponent()).setUserObject(newValue); + } + + public int getIndexOfChild(Object parent, Object child) { + int result = ((DisplayGroupNode) parent).getIndex((DisplayGroupNode) child); //((DisplayGroupNode)parent).suppressRecentChangeProcessing(); -return result; + return result; // return ((DisplayGroupNode)parent).getIndex( (DisplayGroupNode) child ); - } - - public void addTreeModelListener(TreeModelListener aListener) - { - listeners.add( aListener ); - } - public void removeTreeModelListener(TreeModelListener aListener) - { - listeners.remove( aListener ); - } - - /** - * Fires a tree nodes changed event to all listeners. - * Provided as a convenience if you need to make manual - * changes to the tree model. - */ - public void fireTreeNodesChanged(Object source, - Object[] path, - int[] childIndices, - Object[] children) - { - - willChange(); // queue processRecentChanges - TreeModelEvent event = new TreeModelEvent( - source, path, childIndices, children ); + } + + public void addTreeModelListener(TreeModelListener aListener) { + listeners.add(aListener); + } + + public void removeTreeModelListener(TreeModelListener aListener) { + listeners.remove(aListener); + } + + /** + * Fires a tree nodes changed event to all listeners. Provided as a convenience + * if you need to make manual changes to the tree model. + */ + public void fireTreeNodesChanged(Object source, Object[] path, int[] childIndices, Object[] children) { + + willChange(); // queue processRecentChanges + TreeModelEvent event = new TreeModelEvent(source, path, childIndices, children); //System.out.println( "fireTreeNodesChanged: " + event ); - Enumeration it = listeners.elements(); - while ( it.hasMoreElements() ) - { - try - { - ((TreeModelListener)it.nextElement()).treeNodesChanged( event ); - } - catch ( Exception exc ) - { - System.out.println( "TreeModelAssociation.fireTreeNodesChanged: caught: " + exc ); - System.out.println( "Source:" + source ); - System.out.println( "Path:" ); - for ( int i = 0; i < path.length; i++ ) - { - System.out.print( path[i] + "-" ); - } - System.out.println(); - System.out.println( "Indices:" ); - for ( int i = 0; i < childIndices.length; i++ ) - { - System.out.print( childIndices[i] + "-" ); - } - System.out.println(); - System.out.println( "Children:" ); - for ( int i = 0; i < children.length; i++ ) - { - System.out.print( children[i] + "-" ); - } - System.out.println(); - exc.printStackTrace(); - } - } - } - - /** - * Fires a tree nodes inserted event to all listeners. - * Provided as a convenience if you need to make manual - * changes to the tree model. - */ - public void fireTreeNodesInserted(Object source, - Object[] path, - int[] childIndices, - Object[] children) - { - - willChange(); // queue processRecentChanges - TreeModelEvent event = new TreeModelEvent( - source, path, childIndices, children ); + Enumeration it = listeners.elements(); + while (it.hasMoreElements()) { + try { + ((TreeModelListener) it.nextElement()).treeNodesChanged(event); + } catch (Exception exc) { + System.out.println("TreeModelAssociation.fireTreeNodesChanged: caught: " + exc); + System.out.println("Source:" + source); + System.out.println("Path:"); + for (int i = 0; i < path.length; i++) { + System.out.print(path[i] + "-"); + } + System.out.println(); + System.out.println("Indices:"); + for (int i = 0; i < childIndices.length; i++) { + System.out.print(childIndices[i] + "-"); + } + System.out.println(); + System.out.println("Children:"); + for (int i = 0; i < children.length; i++) { + System.out.print(children[i] + "-"); + } + System.out.println(); + exc.printStackTrace(); + } + } + } + + /** + * Fires a tree nodes inserted event to all listeners. Provided as a convenience + * if you need to make manual changes to the tree model. + */ + public void fireTreeNodesInserted(Object source, Object[] path, int[] childIndices, Object[] children) { + + willChange(); // queue processRecentChanges + TreeModelEvent event = new TreeModelEvent(source, path, childIndices, children); //System.out.println( "fireTreeNodesInserted: " + event ); - Enumeration it = listeners.elements(); - while ( it.hasMoreElements() ) - { - try - { - ((TreeModelListener)it.nextElement()).treeNodesInserted( event ); - } - catch ( Exception exc ) - { - System.out.println( "TreeModelAssociation.fireTreeNodesInserted: caught: " + exc ); - } - } - } - - /** - * Fires a tree nodes removed event to all listeners. - * Provided as a convenience if you need to make manual - * changes to the tree model. - */ - public void fireTreeNodesRemoved(Object source, - Object[] path, - int[] childIndices, - Object[] children) - { - - willChange(); // queue processRecentChanges - TreeModelEvent event = new TreeModelEvent( - source, path, childIndices, children ); + Enumeration it = listeners.elements(); + while (it.hasMoreElements()) { + try { + ((TreeModelListener) it.nextElement()).treeNodesInserted(event); + } catch (Exception exc) { + System.out.println("TreeModelAssociation.fireTreeNodesInserted: caught: " + exc); + } + } + } + + /** + * Fires a tree nodes removed event to all listeners. Provided as a convenience + * if you need to make manual changes to the tree model. + */ + public void fireTreeNodesRemoved(Object source, Object[] path, int[] childIndices, Object[] children) { + + willChange(); // queue processRecentChanges + TreeModelEvent event = new TreeModelEvent(source, path, childIndices, children); //System.out.println( "fireTreeNodesRemoved: " + event ); - Enumeration it = listeners.elements(); - while ( it.hasMoreElements() ) - { - try - { - //NOTE: removing nodes causes tree to fire selection change event - // which confuses us if we're rearranging nodes (when sorting, for example). - boolean wasListening = isListening; - if ( wasListening ) isListening = false; - ((TreeModelListener)it.nextElement()).treeNodesRemoved( event ); - if ( wasListening ) isListening = true; - } - catch ( Exception exc ) - { - System.out.println( "TreeModelAssociation.fireTreeNodesRemoved: caught: " + exc ); - } - } - } - - /** - * Fires a tree structure changed event to all listeners. - * Provided as a convenience if you need to make manual - * changes to the tree model. - */ - public void fireTreeStructureChanged(Object source, - Object[] path, - int[] childIndices, - Object[] children) - { - - willChange(); // queue processRecentChanges - TreeModelEvent event = new TreeModelEvent( - source, path, childIndices, children ); + Enumeration it = listeners.elements(); + while (it.hasMoreElements()) { + try { + // NOTE: removing nodes causes tree to fire selection change event + // which confuses us if we're rearranging nodes (when sorting, for example). + boolean wasListening = isListening; + if (wasListening) + isListening = false; + ((TreeModelListener) it.nextElement()).treeNodesRemoved(event); + if (wasListening) + isListening = true; + } catch (Exception exc) { + System.out.println("TreeModelAssociation.fireTreeNodesRemoved: caught: " + exc); + } + } + } + + /** + * Fires a tree structure changed event to all listeners. Provided as a + * convenience if you need to make manual changes to the tree model. + */ + public void fireTreeStructureChanged(Object source, Object[] path, int[] childIndices, Object[] children) { + + willChange(); // queue processRecentChanges + TreeModelEvent event = new TreeModelEvent(source, path, childIndices, children); //System.out.println( "fireStructureChanged: " + event ); - Enumeration it = listeners.elements(); - while ( it.hasMoreElements() ) - { - ((TreeModelListener)it.nextElement()).treeStructureChanged( event ); - } - } - - /** - * Creates and returns a new display group node. - */ - public DisplayGroupNode createNode( EODisplayGroup aParentGroup, Object anObject ) - { - return new MutableDisplayGroupNode( this, aParentGroup, anObject ); - } - - /** - * Gets whether new objects programmatically inserted into the children - * display group should be inserted as a child of the first selected node. - * If false, new objects are inserted as siblings of the first selected node. - * Default value is true. - */ - public boolean isInsertingChild() - { - return insertingChild; - } - - /** - * Sets whether new objects programmatically inserted into the children - * display group should be inserted as a child of the first selected node. - * If false, new objects are inserted as siblings of the first selected node. - * Default value is true. - */ - public void setInsertingChild( boolean asChild ) - { - insertingChild = asChild; - } - - /** - * Determines where new objects programmatically inserted into the children - * display group should be inserted, based on the value of insertingChild. - * If insertingChild, isInsertingAfter causes objects to be inserted at - * the end of the selected node's child list; otherwise, objects are inserted - * at the beginning of the list. - * If inserting as a sibling, isInsertingAfter causes objects to be inserted - * before the selected node in the selected node's parent's child list; - * otherwise, objects are inserted after the selected node in the child list. - * Default value is true. - */ - public boolean isInsertingAfter() - { - return insertingAfter; - } - - /** - * Determines where new objects programmatically inserted into the children - * display group should be inserted, based on the value of insertingChild. - * If insertingChild, isInsertingAfter causes objects to be inserted at - * the end of the selected node's child list; otherwise, objects are inserted - * at the beginning of the list. - * If inserting as a sibling, isInsertingAfter causes objects to be inserted - * before the selected node in the selected node's parent's child list; - * otherwise, objects are inserted after the selected node in the child list. - * Default value is true. - */ - public void setInsertingAfter( boolean after ) - { - insertingAfter = after; - } - - /** - * Called to by the children group's data source when it receives - * an insertObject message, usually after an object has been inserted - * into the children display group. - * Return the object that should be passed to the titles display - * group's data source's implementation of insertObject, or return - * null to prevent that method from being called. <br><br> - * This implementation inserts the specified object into the tree - * as determined by calling isInsertingChild and isInsertingAfter, - * then returns the unmodified object. If there's no selection, or - * no selection model, the root node is assumed to be selected. - * And if the root node is selected, the new node will obviously be - * inserted as a child. Override to customize. - */ - protected Object objectInsertedIntoChildrenGroup( Object anObject ) - { - // determine selection - DisplayGroupNode selectedNode = (DisplayGroupNode) getRoot(); - if ( selectionModel != null ) - { - // get selected path - TreePath path = selectionModel.getSelectionPath(); - - // get selected node - if ( path != null ) - { - selectedNode = (DisplayGroupNode) path.getLastPathComponent(); - } - } - // determine location of insertion - int index = 0; - if ( ( isInsertingChild() ) || ( selectedNode == getRoot() ) ) - { - if ( isInsertingAfter() ) - { - index = selectedNode.getChildCount(); - } - } - else // inserting as sibling - { - DisplayGroupNode parentNode = selectedNode.getParentGroup(); - index = parentNode.getIndex( selectedNode ); - if ( isInsertingAfter() ) - { - index++; - } - selectedNode = parentNode; - } - - // insert and return - selectedNode.insertObjectAtIndex( anObject, index ); - return anObject; - } - - /** - * Called to by the children group's data source when it receives - * a deleteObject message, usually after an object has been deleted - * from the children display group. - * Return the object that should be passed to the titles display - * group's data source's implementation of deleteObject, or return - * null to prevent that method from being called. <br><br> - * This implementation deletes all instances of the selected object - * from the tree nodes that are currently loaded, and returns the - * unmodified object. Override to customize. - */ - protected Object objectDeletedFromChildrenGroup( Object anObject ) - { - TreePath[] paths = getPathsForObject( anObject ); - if ( paths != null ) - { - for ( int i = 0; i < paths.length; i++ ) - { - ((DisplayGroupNode)paths[i].getLastPathComponent()).removeFromParent(); - } - } - return anObject; - } - - /** - * Called to by the children group's data source to populate it - * with all selected nodes and their siblings. To customize, - * override this method, or specify a different data source for - * the children display group. - */ - protected NSArray objectsFetchedIntoChildrenGroup() - { - DisplayGroupNode node; - TreePath parentPath; - TreePath[] selectedPaths = selectionModel.getSelectionPaths(); - NSMutableArray objectList = new NSMutableArray(); - if ( selectedPaths != null ) - { - for ( int i = 0; i < selectedPaths.length; i++ ) - { - // root node is zero - ignore root node - if ( ( selectedPaths[i].getLastPathComponent() == rootNode ) ) - { - // select root in selectFromDisplayGroup() - pleaseSelectRootNode = true; - } - else - { - node = (DisplayGroupNode) - selectedPaths[i].getLastPathComponent(); - Object o = node.object(); - - // add all children of parent to object list - includes self - if ( node.parentGroup != null ) - { - Enumeration e = - node.parentGroup.displayedObjects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - // add only if not already in list - o = e.nextElement(); - if ( objectList.indexOfIdenticalObject(o) == NSArray.NotFound ) - { - objectList.addObject( o ); - } - } - } - else // no parent node - add the node by itself - { - // add only if not already in list - if ( objectList.indexOfIdenticalObject(o) == NSArray.NotFound ) - { - objectList.addObject( o ); - } - } - } - } - } - - // if no selection - if ( objectList.size() == 0 ) - { - // populate with children of root - objectList.addAll( rootNode.displayedObjects() ); - } - return objectList; - } - - /** - * Queues processRecentChanges to be run in the event queue. - */ - private void willChange() - { - EOObserverCenter.notifyObserversObjectWillChange( this ); - } - - /** - * Tells the children display group to refetch, so that it reflects - * any changes that were made in the node tree, - * and then updates the selection in the selection model. - * Triggered in response to willChange(). - */ - public void processRecentChanges() - { - Runnable update = new Runnable() - { - public void run() - { - removeAsListener(); // prevent data source refetch: see fetchObjects() - childrenDisplayGroup.fetch(); - addAsListener(); - selectFromDisplayGroup( childrenDisplayGroup ); - } - }; - if ( isListening ) - { - if ( selectionPaintedImmediately ) - { - // if painting selection immediately, run even later - // so that AWT's repaint event fires before we do. - SwingUtilities.invokeLater( update ); - } - else - { - // otherwise run now - update.run(); - } - } - } - - /** - * Delegates most behaviors to the specified data source, - * except fetchObjects, which calls fetchObjectsIntoChildrenGroup - * on the tree model association. If delegate is null, - * calls are passed to the superclass which is a PropertyDataSource. - */ - static class DelegatingTreeDataSource extends PropertyDataSource - { - TreeModelAssociation parentAssociation; - EODataSource delegateDataSource; - - public DelegatingTreeDataSource( - TreeModelAssociation aTreeModelAssociation, EODataSource aDataSource ) - { - parentAssociation = aTreeModelAssociation; - delegateDataSource = aDataSource; - } - - /** - * Calls to delegateDataSource if it exists, otherwise - * calls to super. - */ - public Object createObject() - { - if ( delegateDataSource != null ) - { - return delegateDataSource.createObject(); - } - return super.createObject(); - } - - /** - * Calls objectInsertedIntoChildrenGroup, and if not null - * calls to delegateDataSource.insertObject if it exists, - * and super.insertObjectAtIndex if not. - */ - public void insertObjectAtIndex( Object anObject, int anIndex ) - { - anObject = - parentAssociation.objectInsertedIntoChildrenGroup( - anObject ); - if ( anObject != null ) - { - if ( delegateDataSource != null ) - { - if ( delegateDataSource instanceof OrderedDataSource ) - { - ((OrderedDataSource)delegateDataSource).insertObjectAtIndex( anObject, anIndex ); - } - else - { - delegateDataSource.insertObject( anObject ); - } - } - else - { - super.insertObjectAtIndex( anObject, anIndex ); - } - } - } - - /** - * Calls objectDeletedIntoChildrenGroup, and if not null - * calls to delegateDataSource if it exists. - */ - public void deleteObject( Object anObject ) - { - anObject = - parentAssociation.objectDeletedFromChildrenGroup( - anObject ); - if ( anObject != null ) - { - if ( delegateDataSource != null ) - { - delegateDataSource.deleteObject( anObject ); - } - super.deleteObject( anObject ); - } - } - - /** - * Overridden to return the delegate's editing context, - * the titles display group's editing context, - * and failing that calling to super. - */ - public EOEditingContext editingContext () - { - EOEditingContext result = null; - if ( delegateDataSource != null ) - { - result = delegateDataSource.editingContext(); - } - if ( result == null ) - { - EODataSource parentDataSource = - parentAssociation.titlesDisplayGroup.dataSource(); - if ( parentDataSource != this && parentDataSource != null ) - { - result = parentAssociation.titlesDisplayGroup. - dataSource().editingContext(); - } - } - if ( result == null ) - { - result = super.editingContext(); - } - return result; - } - - /** - * Returns a List containing the objects in this - * data source. - */ - public NSArray fetchObjects () - { - // if titles group is doing double-duty as children group - if ( parentAssociation.titlesDisplayGroup == parentAssociation.childrenDisplayGroup ) - { - // if we're not initiating this fetch - if ( parentAssociation.isListening ) - { - // need to call to delegate to see if we should update values - if ( delegateDataSource != null ) - { + Enumeration it = listeners.elements(); + while (it.hasMoreElements()) { + ((TreeModelListener) it.nextElement()).treeStructureChanged(event); + } + } + + /** + * Creates and returns a new display group node. + */ + public DisplayGroupNode createNode(EODisplayGroup aParentGroup, Object anObject) { + return new MutableDisplayGroupNode(this, aParentGroup, anObject); + } + + /** + * Gets whether new objects programmatically inserted into the children display + * group should be inserted as a child of the first selected node. If false, new + * objects are inserted as siblings of the first selected node. Default value is + * true. + */ + public boolean isInsertingChild() { + return insertingChild; + } + + /** + * Sets whether new objects programmatically inserted into the children display + * group should be inserted as a child of the first selected node. If false, new + * objects are inserted as siblings of the first selected node. Default value is + * true. + */ + public void setInsertingChild(boolean asChild) { + insertingChild = asChild; + } + + /** + * Determines where new objects programmatically inserted into the children + * display group should be inserted, based on the value of insertingChild. If + * insertingChild, isInsertingAfter causes objects to be inserted at the end of + * the selected node's child list; otherwise, objects are inserted at the + * beginning of the list. If inserting as a sibling, isInsertingAfter causes + * objects to be inserted before the selected node in the selected node's + * parent's child list; otherwise, objects are inserted after the selected node + * in the child list. Default value is true. + */ + public boolean isInsertingAfter() { + return insertingAfter; + } + + /** + * Determines where new objects programmatically inserted into the children + * display group should be inserted, based on the value of insertingChild. If + * insertingChild, isInsertingAfter causes objects to be inserted at the end of + * the selected node's child list; otherwise, objects are inserted at the + * beginning of the list. If inserting as a sibling, isInsertingAfter causes + * objects to be inserted before the selected node in the selected node's + * parent's child list; otherwise, objects are inserted after the selected node + * in the child list. Default value is true. + */ + public void setInsertingAfter(boolean after) { + insertingAfter = after; + } + + /** + * Called to by the children group's data source when it receives an + * insertObject message, usually after an object has been inserted into the + * children display group. Return the object that should be passed to the titles + * display group's data source's implementation of insertObject, or return null + * to prevent that method from being called. <br> + * <br> + * This implementation inserts the specified object into the tree as determined + * by calling isInsertingChild and isInsertingAfter, then returns the unmodified + * object. If there's no selection, or no selection model, the root node is + * assumed to be selected. And if the root node is selected, the new node will + * obviously be inserted as a child. Override to customize. + */ + protected Object objectInsertedIntoChildrenGroup(Object anObject) { + // determine selection + DisplayGroupNode selectedNode = (DisplayGroupNode) getRoot(); + if (selectionModel != null) { + // get selected path + TreePath path = selectionModel.getSelectionPath(); + + // get selected node + if (path != null) { + selectedNode = (DisplayGroupNode) path.getLastPathComponent(); + } + } + // determine location of insertion + int index = 0; + if ((isInsertingChild()) || (selectedNode == getRoot())) { + if (isInsertingAfter()) { + index = selectedNode.getChildCount(); + } + } else // inserting as sibling + { + DisplayGroupNode parentNode = selectedNode.getParentGroup(); + index = parentNode.getIndex(selectedNode); + if (isInsertingAfter()) { + index++; + } + selectedNode = parentNode; + } + + // insert and return + selectedNode.insertObjectAtIndex(anObject, index); + return anObject; + } + + /** + * Called to by the children group's data source when it receives a deleteObject + * message, usually after an object has been deleted from the children display + * group. Return the object that should be passed to the titles display group's + * data source's implementation of deleteObject, or return null to prevent that + * method from being called. <br> + * <br> + * This implementation deletes all instances of the selected object from the + * tree nodes that are currently loaded, and returns the unmodified object. + * Override to customize. + */ + protected Object objectDeletedFromChildrenGroup(Object anObject) { + TreePath[] paths = getPathsForObject(anObject); + if (paths != null) { + for (int i = 0; i < paths.length; i++) { + ((DisplayGroupNode) paths[i].getLastPathComponent()).removeFromParent(); + } + } + return anObject; + } + + /** + * Called to by the children group's data source to populate it with all + * selected nodes and their siblings. To customize, override this method, or + * specify a different data source for the children display group. + */ + protected NSArray objectsFetchedIntoChildrenGroup() { + DisplayGroupNode node; + TreePath parentPath; + TreePath[] selectedPaths = selectionModel.getSelectionPaths(); + NSMutableArray objectList = new NSMutableArray(); + if (selectedPaths != null) { + for (int i = 0; i < selectedPaths.length; i++) { + // root node is zero - ignore root node + if ((selectedPaths[i].getLastPathComponent() == rootNode)) { + // select root in selectFromDisplayGroup() + pleaseSelectRootNode = true; + } else { + node = (DisplayGroupNode) selectedPaths[i].getLastPathComponent(); + Object o = node.object(); + + // add all children of parent to object list - includes self + if (node.parentGroup != null) { + Enumeration e = node.parentGroup.displayedObjects().objectEnumerator(); + while (e.hasMoreElements()) { + // add only if not already in list + o = e.nextElement(); + if (objectList.indexOfIdenticalObject(o) == NSArray.NotFound) { + objectList.addObject(o); + } + } + } else // no parent node - add the node by itself + { + // add only if not already in list + if (objectList.indexOfIdenticalObject(o) == NSArray.NotFound) { + objectList.addObject(o); + } + } + } + } + } + + // if no selection + if (objectList.size() == 0) { + // populate with children of root + objectList.addAll(rootNode.displayedObjects()); + } + return objectList; + } + + /** + * Queues processRecentChanges to be run in the event queue. + */ + private void willChange() { + EOObserverCenter.notifyObserversObjectWillChange(this); + } + + /** + * Tells the children display group to refetch, so that it reflects any changes + * that were made in the node tree, and then updates the selection in the + * selection model. Triggered in response to willChange(). + */ + public void processRecentChanges() { + Runnable update = new Runnable() { + public void run() { + removeAsListener(); // prevent data source refetch: see fetchObjects() + childrenDisplayGroup.fetch(); + addAsListener(); + selectFromDisplayGroup(childrenDisplayGroup); + } + }; + if (isListening) { + if (selectionPaintedImmediately) { + // if painting selection immediately, run even later + // so that AWT's repaint event fires before we do. + SwingUtilities.invokeLater(update); + } else { + // otherwise run now + update.run(); + } + } + } + + /** + * Delegates most behaviors to the specified data source, except fetchObjects, + * which calls fetchObjectsIntoChildrenGroup on the tree model association. If + * delegate is null, calls are passed to the superclass which is a + * PropertyDataSource. + */ + static class DelegatingTreeDataSource extends PropertyDataSource { + TreeModelAssociation parentAssociation; + EODataSource delegateDataSource; + + public DelegatingTreeDataSource(TreeModelAssociation aTreeModelAssociation, EODataSource aDataSource) { + parentAssociation = aTreeModelAssociation; + delegateDataSource = aDataSource; + } + + /** + * Calls to delegateDataSource if it exists, otherwise calls to super. + */ + public Object createObject() { + if (delegateDataSource != null) { + return delegateDataSource.createObject(); + } + return super.createObject(); + } + + /** + * Calls objectInsertedIntoChildrenGroup, and if not null calls to + * delegateDataSource.insertObject if it exists, and super.insertObjectAtIndex + * if not. + */ + public void insertObjectAtIndex(Object anObject, int anIndex) { + anObject = parentAssociation.objectInsertedIntoChildrenGroup(anObject); + if (anObject != null) { + if (delegateDataSource != null) { + if (delegateDataSource instanceof OrderedDataSource) { + ((OrderedDataSource) delegateDataSource).insertObjectAtIndex(anObject, anIndex); + } else { + delegateDataSource.insertObject(anObject); + } + } else { + super.insertObjectAtIndex(anObject, anIndex); + } + } + } + + /** + * Calls objectDeletedIntoChildrenGroup, and if not null calls to + * delegateDataSource if it exists. + */ + public void deleteObject(Object anObject) { + anObject = parentAssociation.objectDeletedFromChildrenGroup(anObject); + if (anObject != null) { + if (delegateDataSource != null) { + delegateDataSource.deleteObject(anObject); + } + super.deleteObject(anObject); + } + } + + /** + * Overridden to return the delegate's editing context, the titles display + * group's editing context, and failing that calling to super. + */ + public EOEditingContext editingContext() { + EOEditingContext result = null; + if (delegateDataSource != null) { + result = delegateDataSource.editingContext(); + } + if (result == null) { + EODataSource parentDataSource = parentAssociation.titlesDisplayGroup.dataSource(); + if (parentDataSource != this && parentDataSource != null) { + result = parentAssociation.titlesDisplayGroup.dataSource().editingContext(); + } + } + if (result == null) { + result = super.editingContext(); + } + return result; + } + + /** + * Returns a List containing the objects in this data source. + */ + public NSArray fetchObjects() { + // if titles group is doing double-duty as children group + if (parentAssociation.titlesDisplayGroup == parentAssociation.childrenDisplayGroup) { + // if we're not initiating this fetch + if (parentAssociation.isListening) { + // need to call to delegate to see if we should update values + if (delegateDataSource != null) { // System.out.println( "fetching from delegate (slow!)" ); - NSArray result = delegateDataSource.fetchObjects(); - NSArray rootObjects = parentAssociation.rootNode.displayedObjects(); - // if titles data source has different objects, return them - if ( rootObjects.count() != result.count() - || ! rootObjects.containsAll( result ) ) - { - // this will force the root node to repopulate in subjectChanged() + NSArray result = delegateDataSource.fetchObjects(); + NSArray rootObjects = parentAssociation.rootNode.displayedObjects(); + // if titles data source has different objects, return them + if (rootObjects.count() != result.count() || !rootObjects.containsAll(result)) { + // this will force the root node to repopulate in subjectChanged() //System.out.println( "fetchObjects: data source" ); - return result; - } - } - } - } - // otherwise: just repopulate the titles group + return result; + } + } + } + } + // otherwise: just repopulate the titles group //System.out.println( "fetchObjects: objectsFetchedIntoChildrenGroup" ); - return parentAssociation.objectsFetchedIntoChildrenGroup(); - } - - /** - * Returns a data source that is capable of - * manipulating objects of the type returned by - * applying the specified key to objects - * vended by this data source. - * @see #qualifyWithRelationshipKey - */ - public EODataSource - dataSourceQualifiedByKey ( String aKey ) - { - if ( delegateDataSource != null ) - { - return delegateDataSource.dataSourceQualifiedByKey( aKey ); - } - return null; - } - - /** - * Restricts this data source to vend those - * objects that are associated with the specified - * key on the specified object. - */ - public void - qualifyWithRelationshipKey ( - String aKey, Object anObject ) - { - if ( delegateDataSource != null ) - { - delegateDataSource.qualifyWithRelationshipKey( aKey, anObject ); - } - } - - /** - * Returns the value from the delegateDataSource, if it exists. - * Otherwise calls super. - */ - public EOClassDescription classDescriptionForObjects() - { - if ( delegateDataSource != null ) - { - return delegateDataSource.classDescriptionForObjects(); - } - return super.classDescriptionForObjects(); - } - - } - + return parentAssociation.objectsFetchedIntoChildrenGroup(); + } + + /** + * Returns a data source that is capable of manipulating objects of the type + * returned by applying the specified key to objects vended by this data source. + * + * @see #qualifyWithRelationshipKey + */ + public EODataSource dataSourceQualifiedByKey(String aKey) { + if (delegateDataSource != null) { + return delegateDataSource.dataSourceQualifiedByKey(aKey); + } + return null; + } + + /** + * Restricts this data source to vend those objects that are associated with the + * specified key on the specified object. + */ + public void qualifyWithRelationshipKey(String aKey, Object anObject) { + if (delegateDataSource != null) { + delegateDataSource.qualifyWithRelationshipKey(aKey, anObject); + } + } + + /** + * Returns the value from the delegateDataSource, if it exists. Otherwise calls + * super. + */ + public EOClassDescription classDescriptionForObjects() { + if (delegateDataSource != null) { + return delegateDataSource.classDescriptionForObjects(); + } + return super.classDescriptionForObjects(); + } + + } + } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.20 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.20 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.19 2002/05/03 21:41:18 mpowers - * No longer clearing the selection model when updating from display group: - * we now only modify if a change needs to be made. - * No longer listening for selection change during firing of delete events: - * delete events cause JTree's to update their selection model. - * Fix for paintsSelectionImmediately: TreeAssociation.processRecentChanges() - * must happen after the screen is painted, or the selection is not displayed. + * Revision 1.19 2002/05/03 21:41:18 mpowers No longer clearing the selection + * model when updating from display group: we now only modify if a change needs + * to be made. No longer listening for selection change during firing of delete + * events: delete events cause JTree's to update their selection model. Fix for + * paintsSelectionImmediately: TreeAssociation.processRecentChanges() must + * happen after the screen is painted, or the selection is not displayed. * - * Revision 1.18 2002/04/23 19:12:28 mpowers - * Reimplemented fireEventsForChanges. Fitter and happier. + * Revision 1.18 2002/04/23 19:12:28 mpowers Reimplemented fireEventsForChanges. + * Fitter and happier. * - * Revision 1.17 2002/04/19 21:18:46 mpowers - * Removed tree event coalescing, which was causing way too many problems. - * The fireChangeEvent algorithm is way faster than before, so we should - * still be better off than before. At least now, we don't have to track - * whether the view component has encountered a particular node. + * Revision 1.17 2002/04/19 21:18:46 mpowers Removed tree event coalescing, + * which was causing way too many problems. The fireChangeEvent algorithm is way + * faster than before, so we should still be better off than before. At least + * now, we don't have to track whether the view component has encountered a + * particular node. * - * Revision 1.16 2002/04/18 20:36:11 mpowers - * TreeModelAssociation now populates children group before selected objects. - * Got rid of the forceOnSync workaround for cancelled selection change. + * Revision 1.16 2002/04/18 20:36:11 mpowers TreeModelAssociation now populates + * children group before selected objects. Got rid of the forceOnSync workaround + * for cancelled selection change. * - * Revision 1.15 2002/04/15 21:52:50 mpowers - * Tightening up TreeModelAssociation and DisplayGroupNode. - * Now only firing root structure changed once. - * Now disposing of root's children. - * Better event coalescing. + * Revision 1.15 2002/04/15 21:52:50 mpowers Tightening up TreeModelAssociation + * and DisplayGroupNode. Now only firing root structure changed once. Now + * disposing of root's children. Better event coalescing. * - * Revision 1.14 2002/04/12 21:05:58 mpowers - * Now distinguishing changes in titles group even better. + * Revision 1.14 2002/04/12 21:05:58 mpowers Now distinguishing changes in + * titles group even better. * - * Revision 1.11 2002/04/10 21:20:04 mpowers - * Better handling for tree nodes when working with editing contexts. - * Better handling for invalidation. No longer broadcasting events - * when nodes have not been "registered" in the tree. + * Revision 1.11 2002/04/10 21:20:04 mpowers Better handling for tree nodes when + * working with editing contexts. Better handling for invalidation. No longer + * broadcasting events when nodes have not been "registered" in the tree. * - * Revision 1.10 2002/04/03 20:01:24 mpowers - * Removed printlns. + * Revision 1.10 2002/04/03 20:01:24 mpowers Removed printlns. * - * Revision 1.8 2002/03/11 03:16:28 mpowers - * Better handling of change events; coalescing changes to children group. + * Revision 1.8 2002/03/11 03:16:28 mpowers Better handling of change events; + * coalescing changes to children group. * - * Revision 1.7 2002/03/08 23:19:57 mpowers - * Refactoring of DelegatingTreeDataSource to facilitate binding of titles - * and children aspects to the same display group. + * Revision 1.7 2002/03/08 23:19:57 mpowers Refactoring of + * DelegatingTreeDataSource to facilitate binding of titles and children aspects + * to the same display group. * - * Revision 1.6 2002/03/07 23:04:36 mpowers - * Refining TreeColumnAssociation. + * Revision 1.6 2002/03/07 23:04:36 mpowers Refining TreeColumnAssociation. * - * Revision 1.5 2002/03/06 13:04:16 mpowers - * Implemented cascading qualifiers in tree nodes. + * Revision 1.5 2002/03/06 13:04:16 mpowers Implemented cascading qualifiers in + * tree nodes. * - * Revision 1.4 2002/03/04 22:47:48 mpowers - * Fixed sort ordering for titles group. Optimization for delegate selection. + * Revision 1.4 2002/03/04 22:47:48 mpowers Fixed sort ordering for titles + * group. Optimization for delegate selection. * - * Revision 1.3 2002/03/04 12:28:47 mpowers - * Revised case where children and titles are bound to same display group. + * Revision 1.3 2002/03/04 12:28:47 mpowers Revised case where children and + * titles are bound to same display group. * - * Revision 1.2 2002/03/01 23:42:09 mpowers - * Implemented TreeColumnAssociation, and updated documentation. + * Revision 1.2 2002/03/01 23:42:09 mpowers Implemented TreeColumnAssociation, + * and updated documentation. * - * Revision 1.1 2002/02/27 23:19:17 mpowers - * Refactoring of TreeAssociation to create TreeModelAssociation parent. + * Revision 1.1 2002/02/27 23:19:17 mpowers Refactoring of TreeAssociation to + * create TreeModelAssociation parent. * - * Revision 1.38 2002/02/18 03:46:08 mpowers - * Implemented TreeTableCellRenderer. + * Revision 1.38 2002/02/18 03:46:08 mpowers Implemented TreeTableCellRenderer. * - * Revision 1.37 2002/02/13 21:20:15 mpowers - * Updated comments. + * Revision 1.37 2002/02/13 21:20:15 mpowers Updated comments. * - * Revision 1.36 2001/11/21 15:13:25 mpowers - * Better repainting for selectionPaintedImmediately. - * Better handling for selection with multiple instances of the same - * object in the tree (from yjcheung). + * Revision 1.36 2001/11/21 15:13:25 mpowers Better repainting for + * selectionPaintedImmediately. Better handling for selection with multiple + * instances of the same object in the tree (from yjcheung). * - * Revision 1.35 2001/11/20 19:13:51 mpowers - * Finished implementation of children group's specialized data source. + * Revision 1.35 2001/11/20 19:13:51 mpowers Finished implementation of children + * group's specialized data source. * - * Revision 1.34 2001/11/19 16:30:37 mpowers - * Tree repaint strategy is now a preference: selectionPaintedImmediately. + * Revision 1.34 2001/11/19 16:30:37 mpowers Tree repaint strategy is now a + * preference: selectionPaintedImmediately. * - * Revision 1.33 2001/11/15 17:56:41 mpowers - * Initial implementation of data source for the children display group. + * Revision 1.33 2001/11/15 17:56:41 mpowers Initial implementation of data + * source for the children display group. * - * Revision 1.32 2001/11/14 00:05:54 mpowers - * Eliminated the run later in favor of repainting the component immediately. - * This makes things more predictable for users of the association that - * want to listen to mouse or selection events on the tree. + * Revision 1.32 2001/11/14 00:05:54 mpowers Eliminated the run later in favor + * of repainting the component immediately. This makes things more predictable + * for users of the association that want to listen to mouse or selection events + * on the tree. * - * Revision 1.31 2001/11/02 20:43:15 mpowers - * Fixes for delegate's shouldChangeSelection veto (from yjcheung). + * Revision 1.31 2001/11/02 20:43:15 mpowers Fixes for delegate's + * shouldChangeSelection veto (from yjcheung). * - * Revision 1.30 2001/10/29 20:42:56 mpowers - * On selection change, repainting tree before notifying display group; - * using NSRunLoop instead of SwingUtilities. + * Revision 1.30 2001/10/29 20:42:56 mpowers On selection change, repainting + * tree before notifying display group; using NSRunLoop instead of + * SwingUtilities. * - * Revision 1.29 2001/10/12 20:12:53 mpowers - * Better handling of selection change vetoing when changing selection - * to a node that is not the sibling of the originally selected node. + * Revision 1.29 2001/10/12 20:12:53 mpowers Better handling of selection change + * vetoing when changing selection to a node that is not the sibling of the + * originally selected node. * - * Revision 1.28 2001/09/14 13:40:26 mpowers - * User-initiated selection changes are now handled on the next event loop - * so that the component repaints the new selection before any potentially - * lengthy logic is triggered by the selection change. + * Revision 1.28 2001/09/14 13:40:26 mpowers User-initiated selection changes + * are now handled on the next event loop so that the component repaints the new + * selection before any potentially lengthy logic is triggered by the selection + * change. * - * Revision 1.27 2001/09/10 14:10:03 mpowers - * Tree now handles multiple instances of the same object. + * Revision 1.27 2001/09/10 14:10:03 mpowers Tree now handles multiple instances + * of the same object. * - * Revision 1.26 2001/07/18 13:03:32 mpowers - * TreeNodes now refetch only on demand. Previously, once a node had - * been fetched, it was always refetched after an invalidate, even if - * the node was not being displayed. + * Revision 1.26 2001/07/18 13:03:32 mpowers TreeNodes now refetch only on + * demand. Previously, once a node had been fetched, it was always refetched + * after an invalidate, even if the node was not being displayed. * - * Revision 1.25 2001/05/14 15:25:35 mpowers - * No longer copying titles group's data source to children group. + * Revision 1.25 2001/05/14 15:25:35 mpowers No longer copying titles group's + * data source to children group. * - * Revision 1.24 2001/05/08 18:47:34 mpowers - * Minor fixes for d3. + * Revision 1.24 2001/05/08 18:47:34 mpowers Minor fixes for d3. * - * Revision 1.23 2001/05/01 00:52:32 mpowers - * Implemented breadth-first traversal of tree for node. + * Revision 1.23 2001/05/01 00:52:32 mpowers Implemented breadth-first traversal + * of tree for node. * - * Revision 1.22 2001/04/26 01:15:19 mpowers - * Major clean-up of DisplayGroupNode: fitter, happier, more productive. + * Revision 1.22 2001/04/26 01:15:19 mpowers Major clean-up of DisplayGroupNode: + * fitter, happier, more productive. * - * Revision 1.21 2001/04/22 23:13:35 mpowers - * Minor bug. + * Revision 1.21 2001/04/22 23:13:35 mpowers Minor bug. * - * Revision 1.20 2001/04/22 23:05:33 mpowers - * Totally revised DisplayGroupNode so each object gets its own node - * (so the nodes are no longer fixed by index). + * Revision 1.20 2001/04/22 23:05:33 mpowers Totally revised DisplayGroupNode so + * each object gets its own node (so the nodes are no longer fixed by index). * - * Revision 1.19 2001/04/21 23:06:33 mpowers - * A major revisiting to support the revising of DisplayGroupNode. + * Revision 1.19 2001/04/21 23:06:33 mpowers A major revisiting to support the + * revising of DisplayGroupNode. * - * Revision 1.18 2001/04/03 20:36:01 mpowers - * Fixed refaulting/reverting/invalidating to be self-consistent. + * Revision 1.18 2001/04/03 20:36:01 mpowers Fixed + * refaulting/reverting/invalidating to be self-consistent. * - * Revision 1.17 2001/03/29 21:35:08 mpowers - * Now handling circular references in the graph. + * Revision 1.17 2001/03/29 21:35:08 mpowers Now handling circular references in + * the graph. * - * Revision 1.16 2001/03/22 21:25:42 mpowers - * Fixed some nasty issues with jtree's internal state and array bounds. + * Revision 1.16 2001/03/22 21:25:42 mpowers Fixed some nasty issues with + * jtree's internal state and array bounds. * - * Revision 1.15 2001/03/19 21:37:58 mpowers - * Improved refresh of titles display group. - * Fixed dangling selection problem after refresh. + * Revision 1.15 2001/03/19 21:37:58 mpowers Improved refresh of titles display + * group. Fixed dangling selection problem after refresh. * - * Revision 1.14 2001/03/09 22:08:57 mpowers - * Trying to handle the dangling reference problem after an update. + * Revision 1.14 2001/03/09 22:08:57 mpowers Trying to handle the dangling + * reference problem after an update. * - * Revision 1.13 2001/02/17 17:23:49 mpowers - * More changes to support compiling with jdk1.1 collections. + * Revision 1.13 2001/02/17 17:23:49 mpowers More changes to support compiling + * with jdk1.1 collections. * - * Revision 1.12 2001/01/25 02:16:25 mpowers - * TreeModelAssociation now returns DisplayGroupNode.getUserObject. + * Revision 1.12 2001/01/25 02:16:25 mpowers TreeModelAssociation now returns + * DisplayGroupNode.getUserObject. * - * Revision 1.11 2001/01/24 18:14:40 mpowers - * Fixed problem with leaving children aspect unspecified. + * Revision 1.11 2001/01/24 18:14:40 mpowers Fixed problem with leaving children + * aspect unspecified. * - * Revision 1.10 2001/01/24 17:49:15 mpowers - * Added getObjectForNode and getNodeForObject convenience methods. + * Revision 1.10 2001/01/24 17:49:15 mpowers Added getObjectForNode and + * getNodeForObject convenience methods. * - * Revision 1.9 2001/01/24 17:44:11 mpowers - * Renamed getPathForNode to getPathForObject to be more precise. - * And created a new getPathForNode method. + * Revision 1.9 2001/01/24 17:44:11 mpowers Renamed getPathForNode to + * getPathForObject to be more precise. And created a new getPathForNode method. * - * Revision 1.8 2001/01/24 17:20:29 mpowers - * Children display group now holds siblings of selected objects - * in addition to the selected objects. + * Revision 1.8 2001/01/24 17:20:29 mpowers Children display group now holds + * siblings of selected objects in addition to the selected objects. * - * Revision 1.5 2001/01/19 23:21:15 mpowers - * Fine tuning events broadcast from TreeModelAssociation. + * Revision 1.5 2001/01/19 23:21:15 mpowers Fine tuning events broadcast from + * TreeModelAssociation. * - * Revision 1.4 2001/01/18 21:27:29 mpowers - * Major rework of TreeModelAssociation. + * Revision 1.4 2001/01/18 21:27:29 mpowers Major rework of + * TreeModelAssociation. * - * Revision 1.2 2001/01/11 20:29:19 mpowers - * Expanded access to tree event firing methods. + * Revision 1.2 2001/01/11 20:29:19 mpowers Expanded access to tree event firing + * methods. * - * Revision 1.1.1.1 2000/12/21 15:49:18 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:49:18 mpowers Contributing wotonomy. * - * Revision 1.20 2000/12/20 16:25:42 michael - * Added log to all files. + * Revision 1.20 2000/12/20 16:25:42 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AbsoluteLayout.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AbsoluteLayout.java index 1fef587..579a595 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AbsoluteLayout.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AbsoluteLayout.java @@ -25,50 +25,43 @@ import java.awt.LayoutManager; import java.io.Serializable; /** - * AbsoluteLayout specifies that all components in the - * container will be placed according to their size - * and their location relative to the container's origin. <br><br> + * AbsoluteLayout specifies that all components in the container will be placed + * according to their size and their location relative to the container's + * origin. <br> + * <br> * - * You can achieve the same effect by setting a container's - * layout manager to null, but this class allows you to subclass - * it if you need specific control or functionality. + * You can achieve the same effect by setting a container's layout manager to + * null, but this class allows you to subclass it if you need specific control + * or functionality. * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 904 $ */ -public class AbsoluteLayout implements LayoutManager, Serializable -{ - public void addLayoutComponent(String name, - Component comp) - { - } +public class AbsoluteLayout implements LayoutManager, Serializable { + public void addLayoutComponent(String name, Component comp) { + } - public void removeLayoutComponent(Component comp) - { - } + public void removeLayoutComponent(Component comp) { + } - public Dimension preferredLayoutSize(Container parent) - { - return minimumLayoutSize( parent ); - } + public Dimension preferredLayoutSize(Container parent) { + return minimumLayoutSize(parent); + } - public Dimension minimumLayoutSize(Container parent) - { - int width = 0; - int height = 0; + public Dimension minimumLayoutSize(Container parent) { + int width = 0; + int height = 0; - Component[] c = parent.getComponents(); - for ( int i = 0; i < c.length; i++ ) - { - width = Math.max( width, c[i].getLocation().x + c[i].getBounds().width ); - height = Math.max( height, c[i].getLocation().y + c[i].getBounds().height ); - } + Component[] c = parent.getComponents(); + for (int i = 0; i < c.length; i++) { + width = Math.max(width, c[i].getLocation().x + c[i].getBounds().width); + height = Math.max(height, c[i].getLocation().y + c[i].getBounds().height); + } - return new Dimension( width, height ); - } + return new Dimension(width, height); + } - public void layoutContainer(Container parent) - { - } + public void layoutContainer(Container parent) { + } } diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AlphaTextField.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AlphaTextField.java index c36f5e2..9300d35 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AlphaTextField.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AlphaTextField.java @@ -19,317 +19,286 @@ License along with this library; if not, see http://www.gnu.org package net.wotonomy.ui.swing.components; /** -* AlphaTextField is a "smart" text field that restricts the user's input. The -* input can be restricted to alphabetic, alphanumeric, or all characters. The -* maximum number of characters can also be limited. -* The defaults for this component is alphabetic only string of unlimited length. -* -* @author rob@straylight.princeton.com -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -public class AlphaTextField extends SmartTextField -{ - -/******************************* -* CONSTANTS -*******************************/ - -/** -* Sets the input to alphabetic characters only. The characters "a-z" and "A-Z" -* are the only valid characters. All other characters will be ignored. -* @see #getAlphaType() -*/ - public static final int ALPHABETIC = 0; - -/** -* Sets the input to alphanumeric characters only. The characters "a-z", "A-Z" -* and "0-9" are the only valid characters. All other characters will be ignored. -* @see #getAlphaType() -*/ - public static final int ALPHANUMERIC = 1; - -/** -* Sets the input to alphanumeric characters and a few special characters only. -* The valid characters are "a-z", "A-Z", "0-9", space, "-", "_", "\", and ":". -* This is helpful for file names (with paths) as input strings. -* All other characters will be ignored. -* @see #getAlphaType() -*/ - public static final int ALPHANUMERIC_PLUS = 2; - -/** -* Sets the input to all characters without restriction. -* @see #getAlphaType() -*/ - public static final int ALL = 3; - - -/******************************* -* DATA MEMBERS -*******************************/ - - // The level of input restrictions, defaults to ALPHABETIC - private int alphaType; - - // The maximum length of the input string, defaults to 0, no maximum - private int stringLength; - - -/******************************* -* PUBLIC METHODS -*******************************/ - -/** -* The default constructor of this class. The default string of this text -* field is set to the empty string (""). The maximum length is set to 0, -* which specifies no limit. -*/ - public AlphaTextField() - { - this("", 0); - } - -/** -* Constructor of this class with the initial text of the text field specified. -* The maximum length is set to 0, which specifies no limit. -* @param text Initial text of the text field. -*/ - public AlphaTextField(String text) - { - this(text, 0); - } - -/** -* Constructor of this class with width (in columns) of the text field specified. -* The initial text is set to the empty string (""). The maximum length is set -* to 0, which specifies no limit. -* @param columns The width of the text field in characters. -*/ - public AlphaTextField(int columns) - { - this("", columns); - } - -/** -* Constructor of this class with width (in columns) and initial text of the -* text field specified. The maximum length is set to 0, which specifies no limit. -* @param text Initial text of the text field. -* @param columns The width of the text field in characters. -*/ - public AlphaTextField(String text, int columns) - { - super(text, columns); - } - -/** -* Constructor that allows the user to set the Alpha type of the text field -* and the maximum string length. -* @param anAlphaType The character restriction type. -* @param aLength The maximum number of characters allowed in the string. -*/ - public AlphaTextField(int anAlphaType, int aLength) - { - super( "", 0 ); - setAlphaType( anAlphaType ); - setStringLength( aLength ); - } - -/** -* Gets the current restriction type of this text field. -* @see #ALPHABETIC -* @see #ALPHANUMERIC -* @see #ALPHANUMERIC_PLUS -* @see #ALL -* @return The current restriction type as defined by the constansts of this class. -*/ - public int getAlphaType() - { - return alphaType; - } - -/** -* Sets the restriction type of this text field. -* @see #ALPHABETIC -* @see #ALPHANUMERIC -* @see #ALPHANUMERIC_PLUS -* @see #ALL -* @param newAlphaType The restriction of this text field. -*/ - public void setAlphaType(int newAlphaType) - { - switch (newAlphaType) - { - case ALPHABETIC: - case ALPHANUMERIC: - case ALPHANUMERIC_PLUS: - case ALL: - { - alphaType = newAlphaType; - break; - } - default: - { - alphaType = ALPHABETIC; - break; - } - } - } - -/** -* Sets the maximum string length of this text field. If the length is set to -* zero, then there is no limit. The default string length is zero. Negative -* sizes will set the length to zero. -* @param newStringLength The maximum length of the string that the user can input. -*/ - public void setStringLength(int newStringLength) - { - if (newStringLength < 0) - { - stringLength = 0; - } - else - { - stringLength = newStringLength; - } - } - -/** -* Gets the current length of the maximum string size the user can enter. -* @return The maximum length the string of the text field can be. -*/ - public int getStringLength() - { - return stringLength; - } - - -/******************************* -* PROTECTED METHODS -*******************************/ - - protected boolean isValidCharacter(char aChar) - { - // if its a non-printable character, then its ok - if ((aChar < ' ') || (aChar > '~')) - { - return true; - } - - // can only be a printable character now, check it for validation - return isValidCharacterType(aChar); - } - - protected boolean isValidString(String aString) - { - if (aString.length() > stringLength) - { - return false; - } - - for (int i = 0; i < aString.length(); ++i) - { - if (!(isValidCharacterType(aString.charAt(i)))) - { - return false; - } - } - - return true; - } - - protected void postProcessing() - { - // No need to do anything. - } - - -/******************************* -* PROTECTED METHODS -*******************************/ - - private boolean isValidCharacterType(char aChar) - { - switch (alphaType) - { - case ALPHABETIC: - { - if (!(isValidAlphabeticCharacter(aChar))) - { - return false; - } - break; - } - case ALPHANUMERIC: - { - if (!(isValidAlphanumericCharacter(aChar))) - { - return false; - } - break; - } - case ALPHANUMERIC_PLUS: - { - if (!(isValidAlphanumericPlusCharacter(aChar))) - { - return false; - } - break; - } - case ALL: - { - if (!(isValidAllCharacter(aChar))) - { - return false; - } - break; - } - default: - { - return false; - } - } - - return true; - } - - private boolean isValidAlphabeticCharacter(char aChar) - { - if (((aChar < 'A') || (aChar > 'Z')) && ((aChar < 'a') || (aChar > 'z'))) - { - return false; - } - return true; - } - - private boolean isValidAlphanumericCharacter(char aChar) - { - if (((aChar < 'A') || (aChar > 'Z')) && ((aChar < 'a') || (aChar > 'z')) && ((aChar < '0') || (aChar > '9'))) - { - return false; - } - return true; - } - - private boolean isValidAlphanumericPlusCharacter(char aChar) - { - if (((aChar < 'A') || (aChar > 'Z')) && ((aChar < 'a') || (aChar > 'z')) && ((aChar < '0') || (aChar > '9'))) - { - if ((aChar != ' ') && (aChar != '_') && (aChar != '-') && (aChar != ':') && (aChar != '\\')) - { - return false; - } - } - return true; - } - - private boolean isValidAllCharacter(char aChar) - { - if ((aChar < ' ') || (aChar > '~')) - { - return false; - } - return true; - } + * AlphaTextField is a "smart" text field that restricts the user's input. The + * input can be restricted to alphabetic, alphanumeric, or all characters. The + * maximum number of characters can also be limited. The defaults for this + * component is alphabetic only string of unlimited length. + * + * @author rob@straylight.princeton.com + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +public class AlphaTextField extends SmartTextField { + + /******************************* + * CONSTANTS + *******************************/ + + /** + * Sets the input to alphabetic characters only. The characters "a-z" and "A-Z" + * are the only valid characters. All other characters will be ignored. + * + * @see #getAlphaType() + */ + public static final int ALPHABETIC = 0; + + /** + * Sets the input to alphanumeric characters only. The characters "a-z", "A-Z" + * and "0-9" are the only valid characters. All other characters will be + * ignored. + * + * @see #getAlphaType() + */ + public static final int ALPHANUMERIC = 1; + + /** + * Sets the input to alphanumeric characters and a few special characters only. + * The valid characters are "a-z", "A-Z", "0-9", space, "-", "_", "\", and ":". + * This is helpful for file names (with paths) as input strings. All other + * characters will be ignored. + * + * @see #getAlphaType() + */ + public static final int ALPHANUMERIC_PLUS = 2; + + /** + * Sets the input to all characters without restriction. + * + * @see #getAlphaType() + */ + public static final int ALL = 3; + + /******************************* + * DATA MEMBERS + *******************************/ + + // The level of input restrictions, defaults to ALPHABETIC + private int alphaType; + + // The maximum length of the input string, defaults to 0, no maximum + private int stringLength; + + /******************************* + * PUBLIC METHODS + *******************************/ + + /** + * The default constructor of this class. The default string of this text field + * is set to the empty string (""). The maximum length is set to 0, which + * specifies no limit. + */ + public AlphaTextField() { + this("", 0); + } + + /** + * Constructor of this class with the initial text of the text field specified. + * The maximum length is set to 0, which specifies no limit. + * + * @param text Initial text of the text field. + */ + public AlphaTextField(String text) { + this(text, 0); + } + + /** + * Constructor of this class with width (in columns) of the text field + * specified. The initial text is set to the empty string (""). The maximum + * length is set to 0, which specifies no limit. + * + * @param columns The width of the text field in characters. + */ + public AlphaTextField(int columns) { + this("", columns); + } + + /** + * Constructor of this class with width (in columns) and initial text of the + * text field specified. The maximum length is set to 0, which specifies no + * limit. + * + * @param text Initial text of the text field. + * @param columns The width of the text field in characters. + */ + public AlphaTextField(String text, int columns) { + super(text, columns); + } + + /** + * Constructor that allows the user to set the Alpha type of the text field and + * the maximum string length. + * + * @param anAlphaType The character restriction type. + * @param aLength The maximum number of characters allowed in the string. + */ + public AlphaTextField(int anAlphaType, int aLength) { + super("", 0); + setAlphaType(anAlphaType); + setStringLength(aLength); + } + + /** + * Gets the current restriction type of this text field. + * + * @see #ALPHABETIC + * @see #ALPHANUMERIC + * @see #ALPHANUMERIC_PLUS + * @see #ALL + * @return The current restriction type as defined by the constansts of this + * class. + */ + public int getAlphaType() { + return alphaType; + } + + /** + * Sets the restriction type of this text field. + * + * @see #ALPHABETIC + * @see #ALPHANUMERIC + * @see #ALPHANUMERIC_PLUS + * @see #ALL + * @param newAlphaType The restriction of this text field. + */ + public void setAlphaType(int newAlphaType) { + switch (newAlphaType) { + case ALPHABETIC: + case ALPHANUMERIC: + case ALPHANUMERIC_PLUS: + case ALL: { + alphaType = newAlphaType; + break; + } + default: { + alphaType = ALPHABETIC; + break; + } + } + } + + /** + * Sets the maximum string length of this text field. If the length is set to + * zero, then there is no limit. The default string length is zero. Negative + * sizes will set the length to zero. + * + * @param newStringLength The maximum length of the string that the user can + * input. + */ + public void setStringLength(int newStringLength) { + if (newStringLength < 0) { + stringLength = 0; + } else { + stringLength = newStringLength; + } + } + + /** + * Gets the current length of the maximum string size the user can enter. + * + * @return The maximum length the string of the text field can be. + */ + public int getStringLength() { + return stringLength; + } + + /******************************* + * PROTECTED METHODS + *******************************/ + + protected boolean isValidCharacter(char aChar) { + // if its a non-printable character, then its ok + if ((aChar < ' ') || (aChar > '~')) { + return true; + } + + // can only be a printable character now, check it for validation + return isValidCharacterType(aChar); + } + + protected boolean isValidString(String aString) { + if (aString.length() > stringLength) { + return false; + } + + for (int i = 0; i < aString.length(); ++i) { + if (!(isValidCharacterType(aString.charAt(i)))) { + return false; + } + } + + return true; + } + + protected void postProcessing() { + // No need to do anything. + } + + /******************************* + * PROTECTED METHODS + *******************************/ + + private boolean isValidCharacterType(char aChar) { + switch (alphaType) { + case ALPHABETIC: { + if (!(isValidAlphabeticCharacter(aChar))) { + return false; + } + break; + } + case ALPHANUMERIC: { + if (!(isValidAlphanumericCharacter(aChar))) { + return false; + } + break; + } + case ALPHANUMERIC_PLUS: { + if (!(isValidAlphanumericPlusCharacter(aChar))) { + return false; + } + break; + } + case ALL: { + if (!(isValidAllCharacter(aChar))) { + return false; + } + break; + } + default: { + return false; + } + } + + return true; + } + + private boolean isValidAlphabeticCharacter(char aChar) { + if (((aChar < 'A') || (aChar > 'Z')) && ((aChar < 'a') || (aChar > 'z'))) { + return false; + } + return true; + } + + private boolean isValidAlphanumericCharacter(char aChar) { + if (((aChar < 'A') || (aChar > 'Z')) && ((aChar < 'a') || (aChar > 'z')) && ((aChar < '0') || (aChar > '9'))) { + return false; + } + return true; + } + + private boolean isValidAlphanumericPlusCharacter(char aChar) { + if (((aChar < 'A') || (aChar > 'Z')) && ((aChar < 'a') || (aChar > 'z')) && ((aChar < '0') || (aChar > '9'))) { + if ((aChar != ' ') && (aChar != '_') && (aChar != '-') && (aChar != ':') && (aChar != '\\')) { + return false; + } + } + return true; + } + + private boolean isValidAllCharacter(char aChar) { + if ((aChar < ' ') || (aChar > '~')) { + return false; + } + return true; + } } diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AlternatingRowCellRenderer.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AlternatingRowCellRenderer.java index 46d2693..fb4824c 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AlternatingRowCellRenderer.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AlternatingRowCellRenderer.java @@ -28,102 +28,79 @@ import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; /** -* A TableCellRenderer that wraps another TableCellRenderer -* and sets the background to the specified color for odd-numbered rows. -* This makes every other row appear to be a different color, -* which helps users distinguish rows of data in densely-packed -* tables. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ + * A TableCellRenderer that wraps another TableCellRenderer and sets the + * background to the specified color for odd-numbered rows. This makes every + * other row appear to be a different color, which helps users distinguish rows + * of data in densely-packed tables. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ public class AlternatingRowCellRenderer implements TableCellRenderer { - protected TableCellRenderer wrappedRenderer; - protected Color alternateColor; - - /** - * Default constructor uses a lighter shade of the system control color - * and wraps a DefaultTableCellRenderer. - */ - public AlternatingRowCellRenderer() - { - this( new DefaultTableCellRenderer() ); - } - - /** - * Uses the specified color for the background of the alternating rows, - * and wraps a DefaultTableCellRenderer. - */ - public AlternatingRowCellRenderer( - Color aColor ) - { - this( aColor, new DefaultTableCellRenderer() ); - } + protected TableCellRenderer wrappedRenderer; + protected Color alternateColor; - /** - * Uses the uses a lighter shade of the system control color - * for the background of the alternating rows, - * and wraps the specified TableCellRenderer. - */ - public AlternatingRowCellRenderer( - TableCellRenderer aRenderer ) - { - Color c = UIManager.getColor( "control" ); - c = new Color( // lighten this color just slightly - (int) ( c.getRed() + ( ( 255 - c.getRed() ) / 1.5 ) ), - (int) ( c.getGreen() + ( ( 255 - c.getGreen() ) / 1.5 ) ), - (int) ( c.getBlue() + ( ( 255 - c.getBlue() ) / 1.5 ) ) ); + /** + * Default constructor uses a lighter shade of the system control color and + * wraps a DefaultTableCellRenderer. + */ + public AlternatingRowCellRenderer() { + this(new DefaultTableCellRenderer()); + } - alternateColor = c; - wrappedRenderer = aRenderer; - } + /** + * Uses the specified color for the background of the alternating rows, and + * wraps a DefaultTableCellRenderer. + */ + public AlternatingRowCellRenderer(Color aColor) { + this(aColor, new DefaultTableCellRenderer()); + } - /** - * Uses the specified color for the background of the alternating rows, - * and wraps the specified TableCellRenderer. - */ - public AlternatingRowCellRenderer( - Color aColor, TableCellRenderer aRenderer ) - { - alternateColor = aColor; - wrappedRenderer = aRenderer; - } + /** + * Uses the uses a lighter shade of the system control color for the background + * of the alternating rows, and wraps the specified TableCellRenderer. + */ + public AlternatingRowCellRenderer(TableCellRenderer aRenderer) { + Color c = UIManager.getColor("control"); + c = new Color( // lighten this color just slightly + (int) (c.getRed() + ((255 - c.getRed()) / 1.5)), (int) (c.getGreen() + ((255 - c.getGreen()) / 1.5)), + (int) (c.getBlue() + ((255 - c.getBlue()) / 1.5))); - public Component getTableCellRendererComponent( - JTable table, Object value, - boolean isSelected, boolean hasFocus, - int row, int column) - { - Component result = wrappedRenderer.getTableCellRendererComponent( - table, value, isSelected, hasFocus, row, column ); - if ( ! isSelected ) - { - if ( row % 2 == 0 ) - { - if ( ! result.getBackground().equals( table.getBackground() ) ) - { - result.setBackground( table.getBackground() ); - } - } - else - { - if ( ! result.getBackground().equals( alternateColor ) ) - { - // jdk1.3's default renderer is opaque - if ( result instanceof JComponent ) - { - ((JComponent)result).setOpaque( true ); - } - - result.setBackground( alternateColor ); - } - } - } - return result; - } -} + alternateColor = c; + wrappedRenderer = aRenderer; + } + /** + * Uses the specified color for the background of the alternating rows, and + * wraps the specified TableCellRenderer. + */ + public AlternatingRowCellRenderer(Color aColor, TableCellRenderer aRenderer) { + alternateColor = aColor; + wrappedRenderer = aRenderer; + } + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, + int row, int column) { + Component result = wrappedRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, + column); + if (!isSelected) { + if (row % 2 == 0) { + if (!result.getBackground().equals(table.getBackground())) { + result.setBackground(table.getBackground()); + } + } else { + if (!result.getBackground().equals(alternateColor)) { + // jdk1.3's default renderer is opaque + if (result instanceof JComponent) { + ((JComponent) result).setOpaque(true); + } + result.setBackground(alternateColor); + } + } + } + return result; + } +} diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterFlowLayout.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterFlowLayout.java index 1c438b6..632fb59 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterFlowLayout.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterFlowLayout.java @@ -25,491 +25,505 @@ import java.awt.FlowLayout; import java.awt.Insets; /** - * BetterFlowLayout works just like FlowLayout, except that - * you can specify a vertical orientation in addition to the - * usual horizontal orientations. You can also specify that - * all the components be sized to the same height and/or width. - * By default, the behavior is identical to FlowLayout. + * BetterFlowLayout works just like FlowLayout, except that you can specify a + * vertical orientation in addition to the usual horizontal orientations. You + * can also specify that all the components be sized to the same height and/or + * width. By default, the behavior is identical to FlowLayout. * * @author michael@mpowers.net * @author $Author: cgruber $ - * @version $Revision: 904 $ - * $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ */ public class BetterFlowLayout extends FlowLayout { - /** - * This value indicates vertical orientation and - * that each column of components should be top-justified. - */ - public static final int TOP = 32; - - /** - * This value indicates vertical orientation and - * that each column of components should be centered. - */ - public static final int CENTER_VERTICAL = 16; - - /** - * This value indicates vertical orientation and - * that each column of components should be bottom-justified. - */ - public static final int BOTTOM = 8; - - /** - * Tracks orientation. - */ - protected boolean isHorizontal = true; - - /** - * Tracks component sizing of width. - */ - protected boolean isWidthUniform = false; - /** - * Tracks component sizing of height. - */ - protected boolean isHeightUniform = false; - - /** - * Constructs a new Flow Layout with a centered alignment and a - * default 5-unit horizontal and vertical gap. - */ - public BetterFlowLayout() { - this(CENTER, 5, 5); - } - - /** - * Constructs a new Flow Layout with the specified alignment and a - * default 5-unit horizontal and vertical gap. - * The value of the alignment argument must be one of - * <code>BetterFlowLayout.LEFT</code>, <code>BetterFlowLayout.RIGHT</code>, - * or <code>BetterFlowLayout.CENTER</code>. - * @param align the alignment value - */ - public BetterFlowLayout(int align) { - this(align, 5, 5); - } - - /** - * Creates a new flow layout manager with the indicated alignment - * and the indicated horizontal and vertical gaps. - * <p> - * The value of the alignment argument must be one of - * <code>BetterFlowLayout.LEFT</code>, <code>BetterFlowLayout.RIGHT</code>, - * or <code>BetterFlowLayout.CENTER</code>. - * @param align the alignment value. - * @param hgap the horizontal gap between components. - * @param vgap the vertical gap between components. - */ - public BetterFlowLayout(int align, int hgap, int vgap) { - setHgap(hgap); - setVgap(vgap); - setAlignment(align); - } - - /** - * Sets whether all components should have the same height. - * @param isUniform the new value. - * @see #isHeightUniform - */ - public void setHeightUniform(boolean isUniform) { - isHeightUniform = isUniform; - } - - /** - * Sets whether all components should have the same width. - * @param isUniform the new value. - * @see #isWidthUniform - */ - public void setWidthUniform(boolean isUniform) { - isWidthUniform = isUniform; - } - - /** - * Determines whether all components will have the same height. - * The uniform height will be the maximum of the preferred heights - * of all the components in the container. - * This value defaults to false. - * @return whether components will have the same height. - */ - public boolean isHeightUniform() { - return isHeightUniform; - } - - /** - * Determines whether all components will have the same width. - * The uniform height will be the maximum of the preferred widths - * of all the components in the container. - * This value defaults to false. - * @return whether components will have the same width. - */ - public boolean isWidthUniform() { - return isWidthUniform; - } - - /** - * Sets the alignment for this layout. - * Possible values for horizontal orientation are <code>LEFT</code>, - * <code>RIGHT</code>, and <code>CENTER</code>. - * Possible values for vertical orientation are <code>TOP</code>, - * <code>BOTTOM</code>, and <code>CENTER_VERTICAL</code>. - * @param align the alignment value. - * @see java.awt.FlowLayout#getAlignment - */ - public void setAlignment(int align) { - if ( ( align == TOP ) || ( align == BOTTOM ) || ( align == CENTER_VERTICAL ) ) - { - isHorizontal = false; - } - else - { - isHorizontal = true; - } - - super.setAlignment( align ); - } - - /** - * Returns the preferred dimensions for this layout given the components - * in the specified target container. - * @param target the component which needs to be laid out - * @return the preferred dimensions to lay out the - * subcomponents of the specified container. - * @see Container - * @see #minimumLayoutSize - * @see java.awt.Container#getPreferredSize - */ - public Dimension preferredLayoutSize(Container target) { - if ( isHorizontal ) { - return preferredLayoutSizeHorizontal( target ); - } else { - return preferredLayoutSizeVertical( target ); - } - } - - /** - * Returns the preferred dimensions for this layout given the components - * in the specified target container. - * @param target the component which needs to be laid out - * @return the preferred dimensions to lay out the - * subcomponents of the specified container. - * @see Container - * @see #minimumLayoutSize - * @see java.awt.Container#getPreferredSize - */ - public Dimension preferredLayoutSizeHorizontal(Container target) { - synchronized (target.getTreeLock()) { - Dimension dim = new Dimension(0, 0); - int nmembers = target.getComponentCount(); - int maxWidth = 0; - - for (int i = 0 ; i < nmembers ; i++) { - Component m = target.getComponent(i); - if (m.isVisible()) { - Dimension d = m.getPreferredSize(); - dim.height = Math.max(dim.height, d.height); - maxWidth = Math.max(maxWidth, d.width); - if (i > 0) { - dim.width += getHgap(); - } - dim.width += d.width; - } - } - if ( isWidthUniform ) - dim.width = ( maxWidth + getHgap() ) * nmembers - getHgap(); - Insets insets = target.getInsets(); - dim.width += insets.left + insets.right + getHgap()*2; - dim.height += insets.top + insets.bottom + getVgap()*2; - return dim; - } - } - - /** - * Returns the preferred dimensions for this layout given the components - * in the specified target container. - * @param target the component which needs to be laid out - * @return the preferred dimensions to lay out the - * subcomponents of the specified container. - * @see Container - * @see #minimumLayoutSize - * @see java.awt.Container#getPreferredSize - */ - public Dimension preferredLayoutSizeVertical(Container target) { - synchronized (target.getTreeLock()) { - Dimension dim = new Dimension(0, 0); - int nmembers = target.getComponentCount(); - int maxHeight = 0; - - for (int i = 0 ; i < nmembers ; i++) { - Component m = target.getComponent(i); - if (m.isVisible()) { - Dimension d = m.getPreferredSize(); - dim.width = Math.max(dim.width, d.width); - maxHeight = Math.max(maxHeight, d.height); - if (i > 0) { - dim.height += getVgap(); - } - dim.height += d.height; - } - } - if ( isHeightUniform ) - dim.height = ( maxHeight + getVgap() ) * nmembers - getVgap(); - Insets insets = target.getInsets(); - dim.width += insets.left + insets.right + getHgap()*2; - dim.height += insets.top + insets.bottom + getVgap()*2; - return dim; - } - } - - /** - * Returns the minimum dimensions needed to layout the components - * contained in the specified target container. - * @param target the component which needs to be laid out - * @return the minimum dimensions to lay out the - * subcomponents of the specified container. - * @see #preferredLayoutSize - * @see java.awt.Container - * @see java.awt.Container#doLayout - */ - public Dimension minimumLayoutSize(Container target) { - // preferred size is also the minimum size - if ( isHorizontal ) { - return preferredLayoutSizeHorizontal( target ); - } else { - return preferredLayoutSizeVertical( target ); - } - } - - /** - * Lays out the container. This method lets each component take - * its preferred size by reshaping the components in the - * target container in order to satisfy the constraints of - * this <code>BetterFlowLayout</code> object. - * @param target the specified component being laid out. - * @see Container - * @see java.awt.Container#doLayout - */ - public void layoutContainer(Container target) { - if ( isHorizontal ) { - layoutContainerHorizontal( target ); - } else { - layoutContainerVertical( target ); - } - } - - /** - * Lays out the container. This method lets each component take - * its preferred size by reshaping the components in the - * target container in order to satisfy the constraints of - * this <code>BetterFlowLayout</code> object. - * @param target the specified component being laid out. - * @see Container - * @see java.awt.Container#doLayout - */ - protected void layoutContainerHorizontal(Container target) { - synchronized (target.getTreeLock()) { - Insets insets = target.getInsets(); - int maxwidth = target.getSize().width - (insets.left + insets.right + getHgap()*2); - int nmembers = target.getComponentCount(); - int x = 0, y = insets.top + getVgap(); - int rowh = 0, start = 0; - - boolean ltr = true; // target.getComponentOrientation().isLeftToRight(); - Dimension uniform = getUniformDimension( target ); - - for (int i = 0 ; i < nmembers ; i++) { - Component m = target.getComponent(i); - if (m.isVisible()) { - Dimension d = m.getPreferredSize(); - if ( isWidthUniform ) - d.width = uniform.width; - if ( isHeightUniform ) - d.height = uniform.height; - m.setSize(d.width, d.height); - - if ((x == 0) || ((x + d.width) <= maxwidth)) { - if (x > 0) { - x += getHgap(); - } - x += d.width; - rowh = Math.max(rowh, d.height); - } else { - moveComponentsHorizontal(target, insets.left + getHgap(), y, maxwidth - x, rowh, start, i, ltr); - x = d.width; - y += getVgap() + rowh; - rowh = d.height; - start = i; - } - } - } - moveComponentsHorizontal(target, insets.left + getHgap(), y, maxwidth - x, rowh, start, nmembers, ltr); - } - } - - /** - * Centers the elements in the specified row, if there is any slack. - * @param target the component which needs to be moved - * @param x the x coordinate - * @param y the y coordinate - * @param width the width dimensions - * @param height the height dimensions - * @param rowStart the beginning of the row - * @param rowEnd the the ending of the row - */ - private void moveComponentsHorizontal(Container target, int x, int y, int width, int height, - int rowStart, int rowEnd, boolean ltr) { - synchronized (target.getTreeLock()) { - switch (getAlignment()) { - case LEFT: - x += ltr ? 0 : width; - break; - case CENTER: - x += width / 2; - break; - case RIGHT: - x += ltr ? width : 0; - break; + /** + * This value indicates vertical orientation and that each column of components + * should be top-justified. + */ + public static final int TOP = 32; + + /** + * This value indicates vertical orientation and that each column of components + * should be centered. + */ + public static final int CENTER_VERTICAL = 16; + + /** + * This value indicates vertical orientation and that each column of components + * should be bottom-justified. + */ + public static final int BOTTOM = 8; + + /** + * Tracks orientation. + */ + protected boolean isHorizontal = true; + + /** + * Tracks component sizing of width. + */ + protected boolean isWidthUniform = false; + /** + * Tracks component sizing of height. + */ + protected boolean isHeightUniform = false; + + /** + * Constructs a new Flow Layout with a centered alignment and a default 5-unit + * horizontal and vertical gap. + */ + public BetterFlowLayout() { + this(CENTER, 5, 5); + } + + /** + * Constructs a new Flow Layout with the specified alignment and a default + * 5-unit horizontal and vertical gap. The value of the alignment argument must + * be one of <code>BetterFlowLayout.LEFT</code>, + * <code>BetterFlowLayout.RIGHT</code>, or <code>BetterFlowLayout.CENTER</code>. + * + * @param align the alignment value + */ + public BetterFlowLayout(int align) { + this(align, 5, 5); + } + + /** + * Creates a new flow layout manager with the indicated alignment and the + * indicated horizontal and vertical gaps. + * <p> + * The value of the alignment argument must be one of + * <code>BetterFlowLayout.LEFT</code>, <code>BetterFlowLayout.RIGHT</code>, or + * <code>BetterFlowLayout.CENTER</code>. + * + * @param align the alignment value. + * @param hgap the horizontal gap between components. + * @param vgap the vertical gap between components. + */ + public BetterFlowLayout(int align, int hgap, int vgap) { + setHgap(hgap); + setVgap(vgap); + setAlignment(align); + } + + /** + * Sets whether all components should have the same height. + * + * @param isUniform the new value. + * @see #isHeightUniform + */ + public void setHeightUniform(boolean isUniform) { + isHeightUniform = isUniform; + } + + /** + * Sets whether all components should have the same width. + * + * @param isUniform the new value. + * @see #isWidthUniform + */ + public void setWidthUniform(boolean isUniform) { + isWidthUniform = isUniform; + } + + /** + * Determines whether all components will have the same height. The uniform + * height will be the maximum of the preferred heights of all the components in + * the container. This value defaults to false. + * + * @return whether components will have the same height. + */ + public boolean isHeightUniform() { + return isHeightUniform; + } + + /** + * Determines whether all components will have the same width. The uniform + * height will be the maximum of the preferred widths of all the components in + * the container. This value defaults to false. + * + * @return whether components will have the same width. + */ + public boolean isWidthUniform() { + return isWidthUniform; + } + + /** + * Sets the alignment for this layout. Possible values for horizontal + * orientation are <code>LEFT</code>, <code>RIGHT</code>, and + * <code>CENTER</code>. Possible values for vertical orientation are + * <code>TOP</code>, <code>BOTTOM</code>, and <code>CENTER_VERTICAL</code>. + * + * @param align the alignment value. + * @see java.awt.FlowLayout#getAlignment + */ + public void setAlignment(int align) { + if ((align == TOP) || (align == BOTTOM) || (align == CENTER_VERTICAL)) { + isHorizontal = false; + } else { + isHorizontal = true; + } + + super.setAlignment(align); + } + + /** + * Returns the preferred dimensions for this layout given the components in the + * specified target container. + * + * @param target the component which needs to be laid out + * @return the preferred dimensions to lay out the subcomponents of the + * specified container. + * @see Container + * @see #minimumLayoutSize + * @see java.awt.Container#getPreferredSize + */ + public Dimension preferredLayoutSize(Container target) { + if (isHorizontal) { + return preferredLayoutSizeHorizontal(target); + } else { + return preferredLayoutSizeVertical(target); + } + } + + /** + * Returns the preferred dimensions for this layout given the components in the + * specified target container. + * + * @param target the component which needs to be laid out + * @return the preferred dimensions to lay out the subcomponents of the + * specified container. + * @see Container + * @see #minimumLayoutSize + * @see java.awt.Container#getPreferredSize + */ + public Dimension preferredLayoutSizeHorizontal(Container target) { + synchronized (target.getTreeLock()) { + Dimension dim = new Dimension(0, 0); + int nmembers = target.getComponentCount(); + int maxWidth = 0; + + for (int i = 0; i < nmembers; i++) { + Component m = target.getComponent(i); + if (m.isVisible()) { + Dimension d = m.getPreferredSize(); + dim.height = Math.max(dim.height, d.height); + maxWidth = Math.max(maxWidth, d.width); + if (i > 0) { + dim.width += getHgap(); + } + dim.width += d.width; + } + } + if (isWidthUniform) + dim.width = (maxWidth + getHgap()) * nmembers - getHgap(); + Insets insets = target.getInsets(); + dim.width += insets.left + insets.right + getHgap() * 2; + dim.height += insets.top + insets.bottom + getVgap() * 2; + return dim; + } + } + + /** + * Returns the preferred dimensions for this layout given the components in the + * specified target container. + * + * @param target the component which needs to be laid out + * @return the preferred dimensions to lay out the subcomponents of the + * specified container. + * @see Container + * @see #minimumLayoutSize + * @see java.awt.Container#getPreferredSize + */ + public Dimension preferredLayoutSizeVertical(Container target) { + synchronized (target.getTreeLock()) { + Dimension dim = new Dimension(0, 0); + int nmembers = target.getComponentCount(); + int maxHeight = 0; + + for (int i = 0; i < nmembers; i++) { + Component m = target.getComponent(i); + if (m.isVisible()) { + Dimension d = m.getPreferredSize(); + dim.width = Math.max(dim.width, d.width); + maxHeight = Math.max(maxHeight, d.height); + if (i > 0) { + dim.height += getVgap(); + } + dim.height += d.height; + } + } + if (isHeightUniform) + dim.height = (maxHeight + getVgap()) * nmembers - getVgap(); + Insets insets = target.getInsets(); + dim.width += insets.left + insets.right + getHgap() * 2; + dim.height += insets.top + insets.bottom + getVgap() * 2; + return dim; + } + } + + /** + * Returns the minimum dimensions needed to layout the components contained in + * the specified target container. + * + * @param target the component which needs to be laid out + * @return the minimum dimensions to lay out the subcomponents of the specified + * container. + * @see #preferredLayoutSize + * @see java.awt.Container + * @see java.awt.Container#doLayout + */ + public Dimension minimumLayoutSize(Container target) { + // preferred size is also the minimum size + if (isHorizontal) { + return preferredLayoutSizeHorizontal(target); + } else { + return preferredLayoutSizeVertical(target); + } + } + + /** + * Lays out the container. This method lets each component take its preferred + * size by reshaping the components in the target container in order to satisfy + * the constraints of this <code>BetterFlowLayout</code> object. + * + * @param target the specified component being laid out. + * @see Container + * @see java.awt.Container#doLayout + */ + public void layoutContainer(Container target) { + if (isHorizontal) { + layoutContainerHorizontal(target); + } else { + layoutContainerVertical(target); + } + } + + /** + * Lays out the container. This method lets each component take its preferred + * size by reshaping the components in the target container in order to satisfy + * the constraints of this <code>BetterFlowLayout</code> object. + * + * @param target the specified component being laid out. + * @see Container + * @see java.awt.Container#doLayout + */ + protected void layoutContainerHorizontal(Container target) { + synchronized (target.getTreeLock()) { + Insets insets = target.getInsets(); + int maxwidth = target.getSize().width - (insets.left + insets.right + getHgap() * 2); + int nmembers = target.getComponentCount(); + int x = 0, y = insets.top + getVgap(); + int rowh = 0, start = 0; + + boolean ltr = true; // target.getComponentOrientation().isLeftToRight(); + Dimension uniform = getUniformDimension(target); + + for (int i = 0; i < nmembers; i++) { + Component m = target.getComponent(i); + if (m.isVisible()) { + Dimension d = m.getPreferredSize(); + if (isWidthUniform) + d.width = uniform.width; + if (isHeightUniform) + d.height = uniform.height; + m.setSize(d.width, d.height); + + if ((x == 0) || ((x + d.width) <= maxwidth)) { + if (x > 0) { + x += getHgap(); + } + x += d.width; + rowh = Math.max(rowh, d.height); + } else { + moveComponentsHorizontal(target, insets.left + getHgap(), y, maxwidth - x, rowh, start, i, ltr); + x = d.width; + y += getVgap() + rowh; + rowh = d.height; + start = i; + } + } + } + moveComponentsHorizontal(target, insets.left + getHgap(), y, maxwidth - x, rowh, start, nmembers, ltr); + } + } + + /** + * Centers the elements in the specified row, if there is any slack. + * + * @param target the component which needs to be moved + * @param x the x coordinate + * @param y the y coordinate + * @param width the width dimensions + * @param height the height dimensions + * @param rowStart the beginning of the row + * @param rowEnd the the ending of the row + */ + private void moveComponentsHorizontal(Container target, int x, int y, int width, int height, int rowStart, + int rowEnd, boolean ltr) { + synchronized (target.getTreeLock()) { + switch (getAlignment()) { + case LEFT: + x += ltr ? 0 : width; + break; + case CENTER: + x += width / 2; + break; + case RIGHT: + x += ltr ? width : 0; + break; //1.2 case LEADING: //1.2 break; //1.2 case TRAILING: //1.2 x += width; //1.2 break; + } + for (int i = rowStart; i < rowEnd; i++) { + Component m = target.getComponent(i); + if (m.isVisible()) { + if (ltr) { + m.setLocation(x, y + (height - m.getBounds().height) / 2); + } else { + m.setLocation(target.getBounds().width - x - m.getBounds().width, + y + (height - m.getBounds().height) / 2); + } + x += m.getBounds().width + getHgap(); + } + } + } } - for (int i = rowStart ; i < rowEnd ; i++) { - Component m = target.getComponent(i); - if (m.isVisible()) { - if (ltr) { - m.setLocation(x, y + (height - m.getBounds().height) / 2); - } else { - m.setLocation(target.getBounds().width - x - m.getBounds().width, y + (height - m.getBounds().height) / 2); - } - x += m.getBounds().width + getHgap(); - } + + /** + * Lays out the container. This method lets each component take its preferred + * size by reshaping the components in the target container in order to satisfy + * the constraints of this <code>BetterFlowLayout</code> object. + * + * @param target the specified component being laid out. + * @see Container + * @see java.awt.Container#doLayout + */ + protected void layoutContainerVertical(Container target) { + synchronized (target.getTreeLock()) { + + Insets insets = target.getInsets(); + int maxheight = target.getBounds().height - (insets.top + insets.bottom + getVgap() * 2); + int nmembers = target.getComponentCount(); + int y = 0, x = insets.left + getHgap(); + int colw = 0, start = 0; + + Dimension uniform = getUniformDimension(target); + for (int i = 0; i < nmembers; i++) { + Component m = target.getComponent(i); + if (m.isVisible()) { + Dimension d = m.getPreferredSize(); + if (isWidthUniform) + d.width = uniform.width; + if (isHeightUniform) + d.height = uniform.height; + m.setSize(d.width, d.height); + + if ((y == 0) || ((y + d.height) <= maxheight)) { + if (y > 0) { + y += getVgap(); + } + y += d.height; + colw = Math.max(colw, d.width); + } else { + moveComponentsVertical(target, x, insets.top + getVgap(), colw, maxheight - y, start, i); + y = d.height; + x += getHgap() + colw; + colw = d.width; + start = i; + } + } + } + moveComponentsVertical(target, x, insets.top + getVgap(), colw, maxheight - y, start, nmembers); + } } - } - } - - /** - * Lays out the container. This method lets each component take - * its preferred size by reshaping the components in the - * target container in order to satisfy the constraints of - * this <code>BetterFlowLayout</code> object. - * @param target the specified component being laid out. - * @see Container - * @see java.awt.Container#doLayout - */ - protected void layoutContainerVertical(Container target) { - synchronized (target.getTreeLock()) { - - Insets insets = target.getInsets(); - int maxheight = target.getBounds().height - (insets.top + insets.bottom + getVgap()*2); - int nmembers = target.getComponentCount(); - int y = 0, x = insets.left + getHgap(); - int colw = 0, start = 0; - - Dimension uniform = getUniformDimension( target ); - for (int i = 0 ; i < nmembers ; i++) { - Component m = target.getComponent(i); - if (m.isVisible()) { - Dimension d = m.getPreferredSize(); - if ( isWidthUniform ) - d.width = uniform.width; - if ( isHeightUniform ) - d.height = uniform.height; - m.setSize(d.width, d.height); - - if ((y == 0) || ((y + d.height) <= maxheight)) { - if (y > 0) { - y += getVgap(); - } - y += d.height; - colw = Math.max(colw, d.width); - } else { - moveComponentsVertical(target, x, insets.top + getVgap(), colw, maxheight - y, start, i ); - y = d.height; - x += getHgap() + colw; - colw = d.width; - start = i; - } - } - } - moveComponentsVertical(target, x, insets.top + getVgap(), colw, maxheight - y, start, nmembers ); - } - } - - /** - * Centers the elements in the specified row, if there is any slack. - * @param target the component which needs to be moved - * @param x the x coordinate - * @param y the y coordinate - * @param width the width dimensions - * @param height the height dimensions - * @param colStart the beginning of the column - * @param colEnd the the ending of the column - */ - private void moveComponentsVertical(Container target, int x, int y, int width, int height, - int colStart, int colEnd) { - synchronized (target.getTreeLock()) { - switch (getAlignment()) { - case TOP: - y += 0; - break; - case CENTER_VERTICAL: - y += ( height / 2 ); // - preferredLayoutSize( target ).height ) / 2 ); - break; - case BOTTOM: - y += height; - break; - } - for (int i = colStart ; i < colEnd ; i++) { - Component m = target.getComponent(i); - if (m.isVisible()) { - m.setLocation(x + (width - m.getBounds().width) / 2, y ); + + /** + * Centers the elements in the specified row, if there is any slack. + * + * @param target the component which needs to be moved + * @param x the x coordinate + * @param y the y coordinate + * @param width the width dimensions + * @param height the height dimensions + * @param colStart the beginning of the column + * @param colEnd the the ending of the column + */ + private void moveComponentsVertical(Container target, int x, int y, int width, int height, int colStart, + int colEnd) { + synchronized (target.getTreeLock()) { + switch (getAlignment()) { + case TOP: + y += 0; + break; + case CENTER_VERTICAL: + y += (height / 2); // - preferredLayoutSize( target ).height ) / 2 ); + break; + case BOTTOM: + y += height; + break; + } + for (int i = colStart; i < colEnd; i++) { + Component m = target.getComponent(i); + if (m.isVisible()) { + m.setLocation(x + (width - m.getBounds().width) / 2, y); // m.setLocation(x, y ); // m.setSize( width, m.getBounds().height ); //! - y += m.getBounds().height + getVgap(); - } - } - } - } - - /** - * Returns a dimension representing the maximum preferred - * height and width of all the components in the container. - * @param target the container to scan. - * @return a dimension containing the maximum values. - */ - protected Dimension getUniformDimension(Container target) { - Component m = null; - Dimension preferred = null; - int maxWidth = 0, maxHeight = 0; - int nmembers = target.getComponentCount(); - for ( int i = 0; i < nmembers; i++ ) { - m = target.getComponent( i ); - if ( m.isVisible() ) { - preferred = m.getPreferredSize(); - maxWidth = Math.max( maxWidth, preferred.width ); - maxHeight = Math.max( maxHeight, preferred.height ); - } - } - return new Dimension( maxWidth, maxHeight ); - } - - /** - * Returns a string representation of this <code>BetterFlowLayout</code> - * object and its values. - * @return a string representation of this layout. - */ - public String toString() { - String str = ""; - switch (getAlignment()) { - case TOP: str = ",align=top"; break; - case CENTER_VERTICAL: str = ",align=vertical"; break; - case BOTTOM: str = ",align=bottom"; break; - default: return super.toString(); + y += m.getBounds().height + getVgap(); + } + } + } + } + + /** + * Returns a dimension representing the maximum preferred height and width of + * all the components in the container. + * + * @param target the container to scan. + * @return a dimension containing the maximum values. + */ + protected Dimension getUniformDimension(Container target) { + Component m = null; + Dimension preferred = null; + int maxWidth = 0, maxHeight = 0; + int nmembers = target.getComponentCount(); + for (int i = 0; i < nmembers; i++) { + m = target.getComponent(i); + if (m.isVisible()) { + preferred = m.getPreferredSize(); + maxWidth = Math.max(maxWidth, preferred.width); + maxHeight = Math.max(maxHeight, preferred.height); + } + } + return new Dimension(maxWidth, maxHeight); } - return getClass().getName() + "[hgap=" + getHgap() + ",vgap=" + getVgap() + str + "]"; - } + /** + * Returns a string representation of this <code>BetterFlowLayout</code> object + * and its values. + * + * @return a string representation of this layout. + */ + public String toString() { + String str = ""; + switch (getAlignment()) { + case TOP: + str = ",align=top"; + break; + case CENTER_VERTICAL: + str = ",align=vertical"; + break; + case BOTTOM: + str = ",align=bottom"; + break; + default: + return super.toString(); + } + return getClass().getName() + "[hgap=" + getHgap() + ",vgap=" + getVgap() + str + "]"; + } } diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterRootLayout.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterRootLayout.java index 6e23ca1..238dd14 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterRootLayout.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterRootLayout.java @@ -28,247 +28,209 @@ import java.awt.Rectangle; import javax.swing.JPanel; import javax.swing.JRootPane; -/** -* A custom layout for a JRootPane that handles the the layout of a -* JRootPane's layeredPane, glassPane, and menuBar, and in addition -* handles four decorative components arranged in a border layout. -* Add the decorative components to the JRootPane using the directional -* constants; CENTER is reserved for the content pane and menu bar. -* -* @author michael@mpowers.net -* @version $Revision: 904 $ -*/ -public class BetterRootLayout extends BorderLayout -{ +/** + * A custom layout for a JRootPane that handles the the layout of a JRootPane's + * layeredPane, glassPane, and menuBar, and in addition handles four decorative + * components arranged in a border layout. Add the decorative components to the + * JRootPane using the directional constants; CENTER is reserved for the content + * pane and menu bar. + * + * @author michael@mpowers.net + * @version $Revision: 904 $ + */ +public class BetterRootLayout extends BorderLayout { /** * Returns the amount of space the layout would like to have. * * @param the Container for which this layout manager is being used * @return a Dimension object containing the layout's preferred size * @throws ClassCastException if parent is not a JRootPane - */ - public Dimension preferredLayoutSize(Container parent) - { + */ + public Dimension preferredLayoutSize(Container parent) { JRootPane rootPane = (JRootPane) parent; - + JPanel proxyPanel = new JPanel(); - proxyPanel.setLayout( new BorderLayout() ); - + proxyPanel.setLayout(new BorderLayout()); + JPanel contentProxy = null; - if(rootPane.getContentPane() != null) { + if (rootPane.getContentPane() != null) { contentProxy = new JPanel(); - contentProxy.setMinimumSize( - rootPane.getContentPane().getMinimumSize() ); - contentProxy.setMaximumSize( - rootPane.getContentPane().getMaximumSize() ); - contentProxy.setPreferredSize( - rootPane.getContentPane().getPreferredSize() ); - proxyPanel.add( contentProxy, CENTER ); + contentProxy.setMinimumSize(rootPane.getContentPane().getMinimumSize()); + contentProxy.setMaximumSize(rootPane.getContentPane().getMaximumSize()); + contentProxy.setPreferredSize(rootPane.getContentPane().getPreferredSize()); + proxyPanel.add(contentProxy, CENTER); } JPanel menuProxy = null; - if(rootPane.getJMenuBar() != null) { + if (rootPane.getJMenuBar() != null) { menuProxy = new JPanel(); - menuProxy.setMinimumSize( - rootPane.getJMenuBar().getMinimumSize() ); - menuProxy.setMaximumSize( - rootPane.getJMenuBar().getMaximumSize() ); - menuProxy.setPreferredSize( - rootPane.getJMenuBar().getPreferredSize() ); - proxyPanel.add( menuProxy, NORTH ); + menuProxy.setMinimumSize(rootPane.getJMenuBar().getMinimumSize()); + menuProxy.setMaximumSize(rootPane.getJMenuBar().getMaximumSize()); + menuProxy.setPreferredSize(rootPane.getJMenuBar().getPreferredSize()); + proxyPanel.add(menuProxy, NORTH); } - - this.addLayoutComponent( proxyPanel, CENTER ); - - Dimension result = super.preferredLayoutSize( parent ); - this.removeLayoutComponent( proxyPanel ); - + this.addLayoutComponent(proxyPanel, CENTER); + + Dimension result = super.preferredLayoutSize(parent); + + this.removeLayoutComponent(proxyPanel); + proxyPanel.removeAll(); - + return result; } - + /** * Returns the minimum amount of space the layout needs. * * @param the Container for which this layout manager is being used * @return a Dimension object containing the layout's minimum size * @throws ClassCastException if parent is not a JRootPane - */ - public Dimension minimumLayoutSize(Container parent) - { + */ + public Dimension minimumLayoutSize(Container parent) { JRootPane rootPane = (JRootPane) parent; - + JPanel proxyPanel = new JPanel(); - proxyPanel.setLayout( new BorderLayout() ); - + proxyPanel.setLayout(new BorderLayout()); + JPanel contentProxy = null; - if(rootPane.getContentPane() != null) { + if (rootPane.getContentPane() != null) { contentProxy = new JPanel(); - contentProxy.setMinimumSize( - rootPane.getContentPane().getMinimumSize() ); - contentProxy.setMaximumSize( - rootPane.getContentPane().getMaximumSize() ); - contentProxy.setPreferredSize( - rootPane.getContentPane().getPreferredSize() ); - proxyPanel.add( contentProxy, CENTER ); + contentProxy.setMinimumSize(rootPane.getContentPane().getMinimumSize()); + contentProxy.setMaximumSize(rootPane.getContentPane().getMaximumSize()); + contentProxy.setPreferredSize(rootPane.getContentPane().getPreferredSize()); + proxyPanel.add(contentProxy, CENTER); } JPanel menuProxy = null; - if(rootPane.getJMenuBar() != null) { + if (rootPane.getJMenuBar() != null) { menuProxy = new JPanel(); - menuProxy.setMinimumSize( - rootPane.getJMenuBar().getMinimumSize() ); - menuProxy.setMaximumSize( - rootPane.getJMenuBar().getMaximumSize() ); - menuProxy.setPreferredSize( - rootPane.getJMenuBar().getPreferredSize() ); - proxyPanel.add( menuProxy, NORTH ); + menuProxy.setMinimumSize(rootPane.getJMenuBar().getMinimumSize()); + menuProxy.setMaximumSize(rootPane.getJMenuBar().getMaximumSize()); + menuProxy.setPreferredSize(rootPane.getJMenuBar().getPreferredSize()); + proxyPanel.add(menuProxy, NORTH); } - - this.addLayoutComponent( proxyPanel, CENTER ); - - Dimension result = super.minimumLayoutSize( parent ); - this.removeLayoutComponent( proxyPanel ); - + this.addLayoutComponent(proxyPanel, CENTER); + + Dimension result = super.minimumLayoutSize(parent); + + this.removeLayoutComponent(proxyPanel); + proxyPanel.removeAll(); - + return result; } - + /** * Returns the maximum amount of space the layout can use. * * @param the Container for which this layout manager is being used * @return a Dimension object containing the layout's maximum size * @throws ClassCastException if parent is not a JRootPane - */ - public Dimension maximumLayoutSize(Container target) - { + */ + public Dimension maximumLayoutSize(Container target) { JRootPane rootPane = (JRootPane) target; - + JPanel proxyPanel = new JPanel(); - proxyPanel.setLayout( new BorderLayout() ); - + proxyPanel.setLayout(new BorderLayout()); + JPanel contentProxy = null; - if(rootPane.getContentPane() != null) { + if (rootPane.getContentPane() != null) { contentProxy = new JPanel(); - contentProxy.setMinimumSize( - rootPane.getContentPane().getMinimumSize() ); - contentProxy.setMaximumSize( - rootPane.getContentPane().getMaximumSize() ); - contentProxy.setPreferredSize( - rootPane.getContentPane().getPreferredSize() ); - proxyPanel.add( contentProxy, CENTER ); + contentProxy.setMinimumSize(rootPane.getContentPane().getMinimumSize()); + contentProxy.setMaximumSize(rootPane.getContentPane().getMaximumSize()); + contentProxy.setPreferredSize(rootPane.getContentPane().getPreferredSize()); + proxyPanel.add(contentProxy, CENTER); } JPanel menuProxy = null; - if(rootPane.getJMenuBar() != null) { + if (rootPane.getJMenuBar() != null) { menuProxy = new JPanel(); - menuProxy.setMinimumSize( - rootPane.getJMenuBar().getMinimumSize() ); - menuProxy.setMaximumSize( - rootPane.getJMenuBar().getMaximumSize() ); - menuProxy.setPreferredSize( - rootPane.getJMenuBar().getPreferredSize() ); - proxyPanel.add( menuProxy, NORTH ); + menuProxy.setMinimumSize(rootPane.getJMenuBar().getMinimumSize()); + menuProxy.setMaximumSize(rootPane.getJMenuBar().getMaximumSize()); + menuProxy.setPreferredSize(rootPane.getJMenuBar().getPreferredSize()); + proxyPanel.add(menuProxy, NORTH); } - - this.addLayoutComponent( proxyPanel, CENTER ); - - Dimension result = super.maximumLayoutSize( target ); - this.removeLayoutComponent( proxyPanel ); - + this.addLayoutComponent(proxyPanel, CENTER); + + Dimension result = super.maximumLayoutSize(target); + + this.removeLayoutComponent(proxyPanel); + proxyPanel.removeAll(); - + return result; } - + /** * Instructs the layout manager to perform the layout for the specified * container. * * @param the Container for which this layout manager is being used * @throws ClassCastException if parent is not a JRootPane - */ - public void layoutContainer(Container parent) - { + */ + public void layoutContainer(Container parent) { JRootPane rootPane = (JRootPane) parent; - + Rectangle b = parent.getBounds(); Insets i = rootPane.getInsets(); int w = b.width - i.right - i.left; int h = b.height - i.top - i.bottom; - + // layout panes - if(rootPane.getLayeredPane() != null) { + if (rootPane.getLayeredPane() != null) { rootPane.getLayeredPane().setBounds(i.left, i.top, w, h); } - if(rootPane.getGlassPane() != null) { + if (rootPane.getGlassPane() != null) { rootPane.getGlassPane().setBounds(i.left, i.top, w, h); } - + // handle proxy panel - + JPanel proxyPanel = new JPanel(); - proxyPanel.setLayout( new BorderLayout() ); - - this.addLayoutComponent( proxyPanel, CENTER ); - - super.layoutContainer( parent ); + proxyPanel.setLayout(new BorderLayout()); + + this.addLayoutComponent(proxyPanel, CENTER); + + super.layoutContainer(parent); // use proxy sizes to set sizes of layeredPane's children Rectangle proxyRect = proxyPanel.getBounds(); - if(rootPane.getJMenuBar() != null) { + if (rootPane.getJMenuBar() != null) { Rectangle menuRect = proxyPanel.getBounds(); menuRect.height = rootPane.getJMenuBar().getPreferredSize().height; - rootPane.getJMenuBar().setBounds( menuRect ); + rootPane.getJMenuBar().setBounds(menuRect); proxyRect.y += menuRect.height; proxyRect.height -= menuRect.height; } - if(rootPane.getContentPane() != null) { - rootPane.getContentPane().setBounds( proxyRect ); + if (rootPane.getContentPane() != null) { + rootPane.getContentPane().setBounds(proxyRect); } - this.removeLayoutComponent( proxyPanel ); - + this.removeLayoutComponent(proxyPanel); + proxyPanel.removeAll(); } - + /** - * Passes NORTH, SOUTH, EAST, WEST and CENTER to super implementation, - * and ignores all others. - */ - public void addLayoutComponent(Component comp, Object constraints) - { - if ( NORTH.equals( constraints ) ) - { - super.addLayoutComponent( comp, constraints ); - } - else - if ( SOUTH.equals( constraints ) ) - { - super.addLayoutComponent( comp, constraints ); - } - else - if ( EAST.equals( constraints ) ) - { - super.addLayoutComponent( comp, constraints ); + * Passes NORTH, SOUTH, EAST, WEST and CENTER to super implementation, and + * ignores all others. + */ + public void addLayoutComponent(Component comp, Object constraints) { + if (NORTH.equals(constraints)) { + super.addLayoutComponent(comp, constraints); + } else if (SOUTH.equals(constraints)) { + super.addLayoutComponent(comp, constraints); + } else if (EAST.equals(constraints)) { + super.addLayoutComponent(comp, constraints); + } else if (WEST.equals(constraints)) { + super.addLayoutComponent(comp, constraints); + } else if (CENTER.equals(constraints)) { + super.addLayoutComponent(comp, constraints); } - else - if ( WEST.equals( constraints ) ) - { - super.addLayoutComponent( comp, constraints ); - } - else - if ( CENTER.equals( constraints ) ) - { - super.addLayoutComponent( comp, constraints ); - } - + // otherwise, ignore } } - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterTableUI.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterTableUI.java index deb0eb6..cb1d48a 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterTableUI.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterTableUI.java @@ -24,100 +24,84 @@ import javax.swing.event.MouseInputListener; import javax.swing.plaf.basic.BasicTableUI; /** -* BetterTableUI allows a JTable to be disabled by -* listening for MouseEvents and then forwarding them -* to the usual MouseInputHandler only if the table -* is enabled. <BR><BR> -* -* This class also works around a bug where an editable -* table's selection is changed when clicking in an edit -* cell while the control key is down. This typically -* happened while users were copying/pasting data from -* cell to cell. <BR><BR> -* -* To use, call <code>JTable.setUI()</code> on any -* JTable with a BetterTableUI as the parameter. -* -* @author michael@mpowers.net -* @version $Revision: 904 $ -*/ -public class BetterTableUI extends BasicTableUI implements MouseInputListener -{ -/** -* The listener to get all mouse events when the table is enabled. -*/ - protected MouseInputListener delegateHandler; - -/** -* Overridden to set self as mouse listener and create delegate. -*/ - protected MouseInputListener createMouseInputListener() - { - // normal handler is a protected inner class of parent - delegateHandler = new MouseInputHandler(); - - return this; - } - - // interface MouseInputListener - - public void mouseClicked(MouseEvent event) - { - if ( (table!=null) && (table.isEnabled()) ) - { - delegateHandler.mouseClicked(event); - } - } - - public void mouseDragged(MouseEvent event) - { - if ( (table!=null) && (table.isEnabled()) ) - { - delegateHandler.mouseDragged(event); - } - } - - public void mouseEntered(MouseEvent event) - { - if ( (table!=null) && (table.isEnabled()) ) - { - delegateHandler.mouseEntered(event); - } - } - - public void mouseExited(MouseEvent event) - { - if ( (table!=null) && (table.isEnabled()) ) - { - delegateHandler.mouseExited(event); - } - } - - public void mouseMoved(MouseEvent event) - { - if ( (table!=null) && (table.isEnabled()) ) - { - delegateHandler.mouseMoved(event); - } - } - - public void mousePressed(MouseEvent event) - { - if ( (table!=null) && (table.isEnabled()) ) - { - // workaround bug - control key removes an existing selection - if ( table.isEditing() && event.isControlDown() ) return; - - delegateHandler.mousePressed(event); - } - } - - public void mouseReleased(MouseEvent event) - { - if ( (table!=null) && (table.isEnabled()) ) - { - delegateHandler.mouseReleased(event); - } - } + * BetterTableUI allows a JTable to be disabled by listening for MouseEvents and + * then forwarding them to the usual MouseInputHandler only if the table is + * enabled. <BR> + * <BR> + * + * This class also works around a bug where an editable table's selection is + * changed when clicking in an edit cell while the control key is down. This + * typically happened while users were copying/pasting data from cell to cell. + * <BR> + * <BR> + * + * To use, call <code>JTable.setUI()</code> on any JTable with a BetterTableUI + * as the parameter. + * + * @author michael@mpowers.net + * @version $Revision: 904 $ + */ +public class BetterTableUI extends BasicTableUI implements MouseInputListener { + /** + * The listener to get all mouse events when the table is enabled. + */ + protected MouseInputListener delegateHandler; + + /** + * Overridden to set self as mouse listener and create delegate. + */ + protected MouseInputListener createMouseInputListener() { + // normal handler is a protected inner class of parent + delegateHandler = new MouseInputHandler(); + + return this; + } + + // interface MouseInputListener + + public void mouseClicked(MouseEvent event) { + if ((table != null) && (table.isEnabled())) { + delegateHandler.mouseClicked(event); + } + } + + public void mouseDragged(MouseEvent event) { + if ((table != null) && (table.isEnabled())) { + delegateHandler.mouseDragged(event); + } + } + + public void mouseEntered(MouseEvent event) { + if ((table != null) && (table.isEnabled())) { + delegateHandler.mouseEntered(event); + } + } + + public void mouseExited(MouseEvent event) { + if ((table != null) && (table.isEnabled())) { + delegateHandler.mouseExited(event); + } + } + + public void mouseMoved(MouseEvent event) { + if ((table != null) && (table.isEnabled())) { + delegateHandler.mouseMoved(event); + } + } + + public void mousePressed(MouseEvent event) { + if ((table != null) && (table.isEnabled())) { + // workaround bug - control key removes an existing selection + if (table.isEditing() && event.isControlDown()) + return; + + delegateHandler.mousePressed(event); + } + } + + public void mouseReleased(MouseEvent event) { + if ((table != null) && (table.isEnabled())) { + delegateHandler.mouseReleased(event); + } + } } - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ButtonPanel.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ButtonPanel.java index 769e866..7d662ae 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ButtonPanel.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ButtonPanel.java @@ -46,565 +46,527 @@ import javax.swing.JPanel; import javax.swing.UIManager; /** -* ButtonPanel handles display and event broadcasting of standard buttons like -* OK/Cancel/Save/etc. The constructor takes a list or array of strings, each -* representing a button to appear on the panel from left to right. -* Any button click will send an action event to all listeners with the action -* command containing the corresponding string. Note action events are simply -* forwarded from the buttons themselves, so the source of the event will be -* the button, not the button panel. The button panel is the source of the -* STATE_CHANGED events that notify about changes to the panel itself.<BR><BR> -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class ButtonPanel extends JPanel implements ActionListener, MouseMotionListener -{ - // TODO: Button text should be read from resources. -/** -* Specifies a "OK" button. -* This is also the action command sent by the OK button. -*/ - public static final String OK = "OK"; -/** -* Specifies a "Save" button. -* This is also the action command sent by the Save button. -*/ - public static final String SAVE = "Save"; -/** -* Specifies a "Refresh" button. -* This is also the action command sent by the Refresh button. -*/ - public static final String REFRESH = "Refresh"; -/** -* Specifies a "Clear All" button. -* This is also the action command sent by the Clear All button. -*/ - public static final String CLEAR_ALL = "Clear All"; -/** -* Specifies a "Refresh" button. -* This is also the action command sent by the Cancel button. -*/ - public static final String CANCEL = "Cancel"; -/** -* Specifies a "Yes" button. -* This is also the action command sent by the Yes button. -*/ - public static final String YES = "Yes"; -/** -* Specifies a "No" button. -* This is also the action command sent by the No button. -*/ - public static final String NO = "No"; -/** -* Specifies an "Add" button. -* This is also the action command sent by the Add button. -*/ - public static final String ADD = "Add"; -/** -* Specifies a "Remove" button. -* This is also the action command sent by the Remove button. -*/ - public static final String REMOVE = "Remove"; -/** -* This is the action command to all listeners when the button state is changed. -*/ - public static final String STATE_CHANGED = "STATE_CHANGED"; - -/** -* This is the container to which buttons are added. -*/ - protected Container buttonContainer = null; // useful for subclasses -/** -* This is the list of all buttons on the panel. -*/ - protected Vector buttonList = null; -/** -* The insets for this panel, so they can be modified. -*/ - protected Insets insets = new Insets( 5, 5, 5, 5 ); - -/** -* This is the layout manager - which must be a FlowLayout or subclass. -*/ - protected FlowLayout buttonPanelLayout = null; - - // for action multicasting - protected ActionListener actionListener = null; - - -/** -* Constructs a ButtonPanel. Three buttons are created -* so the panel is filled when used in a GUI-builder environment. -*/ - public ButtonPanel() - { - buttonList = new Vector(); - initLayout(); - - // default labels for bean layout - setLabels( new String[] { "One", "Two", "Three" } ); - } - -/** -* This method is responsible for the initial layout of the panel. -* Subclasses can implement different layouts, but this method -* is responsible for initializing buttonContainer and buttonPanelLayout -* and setting the container to use the layout. -*/ - protected void initLayout() - { - this.setInsets( super.getInsets() ); - buttonContainer = this; - buttonPanelLayout = new BetterFlowLayout( BetterFlowLayout.RIGHT ); - buttonContainer.setLayout(buttonPanelLayout); - ((BetterFlowLayout)buttonPanelLayout).setWidthUniform( true ); - - // setBackground( Color.blue ); // useful for debugging - } - -/** -* Constructs a ButtonPanel using specified buttons. -* @param buttonList An array containing the strings to be used in labeling the buttons. -*/ - public ButtonPanel( String[] buttonList ) - { - this(); - setLabels( buttonList ); - } - -/** -* Constructs a ButtonPane using specified actions. For each action, a button -* is created, that when pressed the corresponding action is activated. The -* "name" of the action is used as the title of the button. -* @param actionList An array of actions to be used to create buttons with. -*/ - public ButtonPanel( Action[] actionList ) - { - this(); - setLabels( actionList ); - } - -/** -* Creates the buttons to appear on the panel. Any existing buttons -* are replaced. The labels are used as names and action commands -* in addition to labels. -* @param labels An array of strings to be used in labeling the buttons. -* If null, all buttons will be removed. -*/ - public void setLabels( String[] labels ) - { - if ( labels == null ) - { - labels = new String[] {}; - } - - buttonContainer.removeAll(); - this.buttonList = new Vector( labels.length ); - - String item = null; - Component button; - for ( int i = 0; i < labels.length; i++ ) - { - item = labels[i]; - if ( item != null ) - { - button = createComponentWithLabel( item.toString() ); - this.buttonList.addElement( item ); - addComponentToPanel( button ); - button.setEnabled( this.isEnabled() ); -/* - if ( i == 0 ) - { - JRootPane root = SwingUtilities.getRootPane( button ); - if ( root != null ) - root.setDefaultButton( button ); - } -*/ - } - else - { - throw new IllegalArgumentException( "ButtonPanel.setButtons: nulls are not allowed." ); - } - } - - this.revalidate(); - this.repaint(); - broadcastEvent( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, STATE_CHANGED ) ); - } - -/** -* -*/ - public void setLabels( Action[] actions ) - { - if ( actions == null ) - { - actions = new Action[] {}; - } - - buttonContainer.removeAll(); - this.buttonList = new Vector( actions.length ); - - Action action = null; - Component button; - for ( int i = 0; i < actions.length; i++ ) - { - action = actions[i]; - if ( action != null ) - { - String name = ( String )action.getValue( Action.NAME ); - button = createComponentWithLabel( name ); - this.buttonList.addElement( name ); - addComponentToPanel( button ); - button.setEnabled( this.isEnabled() ? action.isEnabled() : false ); - - // Add the action to the "button" if it knows about action listeners. - try - { - Method addActionListenerMethod = - button.getClass().getMethod( "addActionListener", new Class[] { ActionListener.class } ); - addActionListenerMethod.invoke( button, new Object[] { action } ); - } - catch ( NoSuchMethodException e ) { /* Do Nothing */ } - catch ( IllegalAccessException e ) { e.printStackTrace(); /* TODO: Do Something? */ } - catch ( InvocationTargetException e ) { e.printStackTrace(); /* TODO: Do Something? */ } - - // Create a new listener for property change events and have - // the action broadcast to that listener. - PropertyChangeListener pcListener = new ActionChangeListener( button ); - action.addPropertyChangeListener( pcListener ); - } - else - { - throw new IllegalArgumentException( "ButtonPanel.setButtons: nulls are not allowed." ); - } - } - - this.revalidate(); - this.repaint(); - broadcastEvent( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, STATE_CHANGED ) ); - } - - -/** -* Gets the labels of the buttons that appear on the panel, ordered from left to right. -* @return A new list containing strings used in labeling the buttons. -*/ - public String[] getLabels() - { - String[] labels = new String[ buttonList.size() ]; - int i = 0; - for ( Enumeration it = buttonList.elements(); it.hasMoreElements(); ) - { - labels[i++] = it.nextElement().toString(); - } - return labels; - } - -/** -* Gets the first component having the specified name. -* @return A component with the specified name, or null if none match. -*/ - public Component getButton( String aLabel ) - { - if ( aLabel == null ) return null; - - Component c = null; - int count = buttonContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = buttonContainer.getComponent( i ); - if ( aLabel.equals( c.getName() ) ) - { - return c; - } - } - return null; - } - -/** -* Creates a new component with the specified label. -* The label is also used for the component's name -* and action command, if any. -* (This implementation returns a JButton.) -* @param aLabel The label for the component that will be created. -* @return The newly created component. -*/ - protected Component createComponentWithLabel( String aLabel ) - { - String buttonLabel = aLabel; // TODO: get string from resource - JButton newButton = new JButton(); // might allow other types in future - newButton.setName( aLabel ); - newButton.setText( buttonLabel ); - newButton.setActionCommand( aLabel ); - newButton.addActionListener( this ); - return newButton; - } - -/** -* Adds a component to the right-most side of the layout. -* @param aComponent The component to be added to the layout. -*/ - protected void addComponentToPanel( Component aComponent ) - { - buttonContainer.add( aComponent ); - } - - -/** -* Changes the alignment of the buttons in the panel. Defaults to right-justified. -* @param alignment A valid alignment code, per BetterFlowLayout implementation. -* @see BetterFlowLayout -*/ - public void setAlignment( int alignment ) - { - buttonPanelLayout.setAlignment(alignment); - buttonContainer.doLayout(); - } -/** -* Gets the alignment of the buttons in the panel. -* @return An alignment code, per FlowLayout implementation. -* @see FlowLayout -*/ - public int getAlignment() - { - return buttonPanelLayout.getAlignment(); - } - -/** -* Changes the horizontal spacing between components in the panel. -* @param newHgap the new spacing, in pixels. May not be negative. -*/ - public void setHgap( int newHgap ) - { - if ( newHgap < 0 ) return; // may not be negative - buttonPanelLayout.setHgap( newHgap ); - } - -/** -* Gets the current horizontal spacing between components. -* @return the current horizontal spacing, in pixels. -*/ - public int getHgap() - { - return buttonPanelLayout.getHgap(); - } - -/** -* Changes the vertical spacing between components in the panel. -* @param newVgap the new spacing, in pixels. May not be negative. -*/ - public void setVgap( int newVgap ) - { - if ( newVgap < 0 ) return; // may not be negative - buttonPanelLayout.setVgap( newVgap ); - } - -/** -* Gets the current vertical spacing between components. -* @return the current vertical spacing, in pixels. -*/ - public int getVgap() - { - return buttonPanelLayout.getVgap(); - } - -/** -* Changes the insets for this panel. -* @param newInsets the new insets. -*/ - public void setInsets( Insets newInsets ) - { - insets = newInsets; - } - -/** -* Overridden to return the user-specified insets for this panel. -* @return the current insets for this panel. -*/ - public Insets getInsets() - { - return insets; - } - -/** -* Overridden to call setEnabled on all components on panel. -* @param isEnabled whether to enable the panel and all components on it. -*/ - public void setEnabled( boolean isEnabled ) - { - super.setEnabled( isEnabled ); - int count = buttonContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - buttonContainer.getComponent( i ).setEnabled( isEnabled ); - } - } - - // Action Multicast methods - -/** -* Adds an action listener to the list that will be -* notified by button events and changes in button state. -* @param l An action listener to be notified. -*/ - public void addActionListener(ActionListener l) - { - actionListener = AWTEventMulticaster.add(actionListener, l); - } -/** -* Removes an action listener from the list that will be -* notified by button events and changes in button state. -* @param l An action listener to be removed. -*/ - public void removeActionListener(ActionListener l) - { - actionListener = AWTEventMulticaster.remove(actionListener, l); - } -/** -* Notifies all registered action listeners of a pending Action Event. -* @param e An action event to be broadcast. -*/ - protected void broadcastEvent(ActionEvent e) - { - if (actionListener != null) - { - actionListener.actionPerformed(e); - } - } - - // interface ActionListener - -/** -* Called by buttons on panel and by other components that -* might be set to broadcast events to this listener. -* @param e An action event to be received. -*/ - public void actionPerformed(ActionEvent e) - { - broadcastEvent(e); - } - -/** -* A property change listener that listens specifically for property changes -* from action objects. This is the class that ties in the action to the -* button. This class is added to an action as a property change listener. -* The corresponding component is referenced by this class toe easily handle -* updates to the component caused by changes to the action. -*/ - public class ActionChangeListener implements PropertyChangeListener - { - /** The UI component that is affected by the action's changes. */ - Component theComponent; - - /** - * Constructs an ActionChangeListener with the given component being - * the recipient of the action's changes. - * @param The component to bind with the action. - */ - public ActionChangeListener( Component aComponent ) - { - super(); - theComponent = aComponent; - } - - /** - * Called whenever a property changes on the action object. - * @pram e The property change event generated by the action. - */ - public void propertyChange( PropertyChangeEvent e ) - { - String propertyName = e.getPropertyName(); - if ( propertyName.equals( Action.NAME ) ) - { - String name = ( String )e.getNewValue(); - if ( theComponent instanceof AbstractButton ) - { - AbstractButton button = ( AbstractButton )theComponent; - String oldName = button.getName(); - button.setText( name ); - button.setName( name ); - button.setActionCommand( name ); - - // Replace the old name of the component with the new name - // in the ButtonPanel's list of components. - buttonList.setElementAt( name, buttonList.indexOf( oldName ) ); - } - - // TODO: If component is not a button (or doesn't define the getText() - // then what should be done. - } - else if ( propertyName.equals( "enabled" ) ) - { - Boolean enabled = ( Boolean )e.getNewValue(); - theComponent.setEnabled( ButtonPanel.this.isEnabled() ? enabled.booleanValue() : false ); - } - - // TODO: Icon? - } - } - - - // for testing - - public static void main( String[] argv ) - { - try - { - UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() ); - } - catch (Exception exc) - { - - } - - JFrame dialog = new JFrame(); - BorderLayout bl = new BorderLayout( 20, 20 ); - - ButtonPanel panel = new ButtonPanel(); + * ButtonPanel handles display and event broadcasting of standard buttons like + * OK/Cancel/Save/etc. The constructor takes a list or array of strings, each + * representing a button to appear on the panel from left to right. Any button + * click will send an action event to all listeners with the action command + * containing the corresponding string. Note action events are simply forwarded + * from the buttons themselves, so the source of the event will be the button, + * not the button panel. The button panel is the source of the STATE_CHANGED + * events that notify about changes to the panel itself.<BR> + * <BR> + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class ButtonPanel extends JPanel implements ActionListener, MouseMotionListener { + // TODO: Button text should be read from resources. + /** + * Specifies a "OK" button. This is also the action command sent by the OK + * button. + */ + public static final String OK = "OK"; + /** + * Specifies a "Save" button. This is also the action command sent by the Save + * button. + */ + public static final String SAVE = "Save"; + /** + * Specifies a "Refresh" button. This is also the action command sent by the + * Refresh button. + */ + public static final String REFRESH = "Refresh"; + /** + * Specifies a "Clear All" button. This is also the action command sent by the + * Clear All button. + */ + public static final String CLEAR_ALL = "Clear All"; + /** + * Specifies a "Refresh" button. This is also the action command sent by the + * Cancel button. + */ + public static final String CANCEL = "Cancel"; + /** + * Specifies a "Yes" button. This is also the action command sent by the Yes + * button. + */ + public static final String YES = "Yes"; + /** + * Specifies a "No" button. This is also the action command sent by the No + * button. + */ + public static final String NO = "No"; + /** + * Specifies an "Add" button. This is also the action command sent by the Add + * button. + */ + public static final String ADD = "Add"; + /** + * Specifies a "Remove" button. This is also the action command sent by the + * Remove button. + */ + public static final String REMOVE = "Remove"; + /** + * This is the action command to all listeners when the button state is changed. + */ + public static final String STATE_CHANGED = "STATE_CHANGED"; + + /** + * This is the container to which buttons are added. + */ + protected Container buttonContainer = null; // useful for subclasses + /** + * This is the list of all buttons on the panel. + */ + protected Vector buttonList = null; + /** + * The insets for this panel, so they can be modified. + */ + protected Insets insets = new Insets(5, 5, 5, 5); + + /** + * This is the layout manager - which must be a FlowLayout or subclass. + */ + protected FlowLayout buttonPanelLayout = null; + + // for action multicasting + protected ActionListener actionListener = null; + + /** + * Constructs a ButtonPanel. Three buttons are created so the panel is filled + * when used in a GUI-builder environment. + */ + public ButtonPanel() { + buttonList = new Vector(); + initLayout(); + + // default labels for bean layout + setLabels(new String[] { "One", "Two", "Three" }); + } + + /** + * This method is responsible for the initial layout of the panel. Subclasses + * can implement different layouts, but this method is responsible for + * initializing buttonContainer and buttonPanelLayout and setting the container + * to use the layout. + */ + protected void initLayout() { + this.setInsets(super.getInsets()); + buttonContainer = this; + buttonPanelLayout = new BetterFlowLayout(BetterFlowLayout.RIGHT); + buttonContainer.setLayout(buttonPanelLayout); + ((BetterFlowLayout) buttonPanelLayout).setWidthUniform(true); + + // setBackground( Color.blue ); // useful for debugging + } + + /** + * Constructs a ButtonPanel using specified buttons. + * + * @param buttonList An array containing the strings to be used in labeling the + * buttons. + */ + public ButtonPanel(String[] buttonList) { + this(); + setLabels(buttonList); + } + + /** + * Constructs a ButtonPane using specified actions. For each action, a button is + * created, that when pressed the corresponding action is activated. The "name" + * of the action is used as the title of the button. + * + * @param actionList An array of actions to be used to create buttons with. + */ + public ButtonPanel(Action[] actionList) { + this(); + setLabels(actionList); + } + + /** + * Creates the buttons to appear on the panel. Any existing buttons are + * replaced. The labels are used as names and action commands in addition to + * labels. + * + * @param labels An array of strings to be used in labeling the buttons. If + * null, all buttons will be removed. + */ + public void setLabels(String[] labels) { + if (labels == null) { + labels = new String[] {}; + } + + buttonContainer.removeAll(); + this.buttonList = new Vector(labels.length); + + String item = null; + Component button; + for (int i = 0; i < labels.length; i++) { + item = labels[i]; + if (item != null) { + button = createComponentWithLabel(item.toString()); + this.buttonList.addElement(item); + addComponentToPanel(button); + button.setEnabled(this.isEnabled()); + /* + * if ( i == 0 ) { JRootPane root = SwingUtilities.getRootPane( button ); if ( + * root != null ) root.setDefaultButton( button ); } + */ + } else { + throw new IllegalArgumentException("ButtonPanel.setButtons: nulls are not allowed."); + } + } + + this.revalidate(); + this.repaint(); + broadcastEvent(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, STATE_CHANGED)); + } + + /** + * + */ + public void setLabels(Action[] actions) { + if (actions == null) { + actions = new Action[] {}; + } + + buttonContainer.removeAll(); + this.buttonList = new Vector(actions.length); + + Action action = null; + Component button; + for (int i = 0; i < actions.length; i++) { + action = actions[i]; + if (action != null) { + String name = (String) action.getValue(Action.NAME); + button = createComponentWithLabel(name); + this.buttonList.addElement(name); + addComponentToPanel(button); + button.setEnabled(this.isEnabled() ? action.isEnabled() : false); + + // Add the action to the "button" if it knows about action listeners. + try { + Method addActionListenerMethod = button.getClass().getMethod("addActionListener", + new Class[] { ActionListener.class }); + addActionListenerMethod.invoke(button, new Object[] { action }); + } catch (NoSuchMethodException e) { + /* Do Nothing */ } catch (IllegalAccessException e) { + e.printStackTrace(); + /* TODO: Do Something? */ } catch (InvocationTargetException e) { + e.printStackTrace(); + /* TODO: Do Something? */ } + + // Create a new listener for property change events and have + // the action broadcast to that listener. + PropertyChangeListener pcListener = new ActionChangeListener(button); + action.addPropertyChangeListener(pcListener); + } else { + throw new IllegalArgumentException("ButtonPanel.setButtons: nulls are not allowed."); + } + } + + this.revalidate(); + this.repaint(); + broadcastEvent(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, STATE_CHANGED)); + } + + /** + * Gets the labels of the buttons that appear on the panel, ordered from left to + * right. + * + * @return A new list containing strings used in labeling the buttons. + */ + public String[] getLabels() { + String[] labels = new String[buttonList.size()]; + int i = 0; + for (Enumeration it = buttonList.elements(); it.hasMoreElements();) { + labels[i++] = it.nextElement().toString(); + } + return labels; + } + + /** + * Gets the first component having the specified name. + * + * @return A component with the specified name, or null if none match. + */ + public Component getButton(String aLabel) { + if (aLabel == null) + return null; + + Component c = null; + int count = buttonContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = buttonContainer.getComponent(i); + if (aLabel.equals(c.getName())) { + return c; + } + } + return null; + } + + /** + * Creates a new component with the specified label. The label is also used for + * the component's name and action command, if any. (This implementation returns + * a JButton.) + * + * @param aLabel The label for the component that will be created. + * @return The newly created component. + */ + protected Component createComponentWithLabel(String aLabel) { + String buttonLabel = aLabel; // TODO: get string from resource + JButton newButton = new JButton(); // might allow other types in future + newButton.setName(aLabel); + newButton.setText(buttonLabel); + newButton.setActionCommand(aLabel); + newButton.addActionListener(this); + return newButton; + } + + /** + * Adds a component to the right-most side of the layout. + * + * @param aComponent The component to be added to the layout. + */ + protected void addComponentToPanel(Component aComponent) { + buttonContainer.add(aComponent); + } + + /** + * Changes the alignment of the buttons in the panel. Defaults to + * right-justified. + * + * @param alignment A valid alignment code, per BetterFlowLayout implementation. + * @see BetterFlowLayout + */ + public void setAlignment(int alignment) { + buttonPanelLayout.setAlignment(alignment); + buttonContainer.doLayout(); + } + + /** + * Gets the alignment of the buttons in the panel. + * + * @return An alignment code, per FlowLayout implementation. + * @see FlowLayout + */ + public int getAlignment() { + return buttonPanelLayout.getAlignment(); + } + + /** + * Changes the horizontal spacing between components in the panel. + * + * @param newHgap the new spacing, in pixels. May not be negative. + */ + public void setHgap(int newHgap) { + if (newHgap < 0) + return; // may not be negative + buttonPanelLayout.setHgap(newHgap); + } + + /** + * Gets the current horizontal spacing between components. + * + * @return the current horizontal spacing, in pixels. + */ + public int getHgap() { + return buttonPanelLayout.getHgap(); + } + + /** + * Changes the vertical spacing between components in the panel. + * + * @param newVgap the new spacing, in pixels. May not be negative. + */ + public void setVgap(int newVgap) { + if (newVgap < 0) + return; // may not be negative + buttonPanelLayout.setVgap(newVgap); + } + + /** + * Gets the current vertical spacing between components. + * + * @return the current vertical spacing, in pixels. + */ + public int getVgap() { + return buttonPanelLayout.getVgap(); + } + + /** + * Changes the insets for this panel. + * + * @param newInsets the new insets. + */ + public void setInsets(Insets newInsets) { + insets = newInsets; + } + + /** + * Overridden to return the user-specified insets for this panel. + * + * @return the current insets for this panel. + */ + public Insets getInsets() { + return insets; + } + + /** + * Overridden to call setEnabled on all components on panel. + * + * @param isEnabled whether to enable the panel and all components on it. + */ + public void setEnabled(boolean isEnabled) { + super.setEnabled(isEnabled); + int count = buttonContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + buttonContainer.getComponent(i).setEnabled(isEnabled); + } + } + + // Action Multicast methods + + /** + * Adds an action listener to the list that will be notified by button events + * and changes in button state. + * + * @param l An action listener to be notified. + */ + public void addActionListener(ActionListener l) { + actionListener = AWTEventMulticaster.add(actionListener, l); + } + + /** + * Removes an action listener from the list that will be notified by button + * events and changes in button state. + * + * @param l An action listener to be removed. + */ + public void removeActionListener(ActionListener l) { + actionListener = AWTEventMulticaster.remove(actionListener, l); + } + + /** + * Notifies all registered action listeners of a pending Action Event. + * + * @param e An action event to be broadcast. + */ + protected void broadcastEvent(ActionEvent e) { + if (actionListener != null) { + actionListener.actionPerformed(e); + } + } + + // interface ActionListener + + /** + * Called by buttons on panel and by other components that might be set to + * broadcast events to this listener. + * + * @param e An action event to be received. + */ + public void actionPerformed(ActionEvent e) { + broadcastEvent(e); + } + + /** + * A property change listener that listens specifically for property changes + * from action objects. This is the class that ties in the action to the button. + * This class is added to an action as a property change listener. The + * corresponding component is referenced by this class toe easily handle updates + * to the component caused by changes to the action. + */ + public class ActionChangeListener implements PropertyChangeListener { + /** The UI component that is affected by the action's changes. */ + Component theComponent; + + /** + * Constructs an ActionChangeListener with the given component being the + * recipient of the action's changes. + * + * @param The component to bind with the action. + */ + public ActionChangeListener(Component aComponent) { + super(); + theComponent = aComponent; + } + + /** + * Called whenever a property changes on the action object. + * + * @pram e The property change event generated by the action. + */ + public void propertyChange(PropertyChangeEvent e) { + String propertyName = e.getPropertyName(); + if (propertyName.equals(Action.NAME)) { + String name = (String) e.getNewValue(); + if (theComponent instanceof AbstractButton) { + AbstractButton button = (AbstractButton) theComponent; + String oldName = button.getName(); + button.setText(name); + button.setName(name); + button.setActionCommand(name); + + // Replace the old name of the component with the new name + // in the ButtonPanel's list of components. + buttonList.setElementAt(name, buttonList.indexOf(oldName)); + } + + // TODO: If component is not a button (or doesn't define the getText() + // then what should be done. + } else if (propertyName.equals("enabled")) { + Boolean enabled = (Boolean) e.getNewValue(); + theComponent.setEnabled(ButtonPanel.this.isEnabled() ? enabled.booleanValue() : false); + } + + // TODO: Icon? + } + } + + // for testing + + public static void main(String[] argv) { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception exc) { + + } + + JFrame dialog = new JFrame(); + BorderLayout bl = new BorderLayout(20, 20); + + ButtonPanel panel = new ButtonPanel(); // ButtonPanel panel = new ButtonPanel( new String[] { "OkayOkay", "CancelCancel" } ); - dialog.getContentPane().setLayout( bl ); - dialog.getContentPane().add( panel, BorderLayout.CENTER ); - dialog.setLocation( 50, 50 ); - // dialog.setSize( 450, 150 ); - - panel.setAlignment( BetterFlowLayout.CENTER_VERTICAL ); - panel.getButton( "One" ).setEnabled( false ); - - dialog.pack(); - dialog.setVisible( true ); - - try - { - BeanInfo info = Introspector.getBeanInfo( ButtonPanel.class ); - PropertyDescriptor[] props = info.getPropertyDescriptors(); - for ( int i = 0; i < props.length; i++ ) - { - System.out.println( props[i].getName() ); - } - } - catch (Exception exc) - { - System.out.println( exc ); - } + dialog.getContentPane().setLayout(bl); + dialog.getContentPane().add(panel, BorderLayout.CENTER); + dialog.setLocation(50, 50); + // dialog.setSize( 450, 150 ); + panel.setAlignment(BetterFlowLayout.CENTER_VERTICAL); + panel.getButton("One").setEnabled(false); + dialog.pack(); + dialog.setVisible(true); + try { + BeanInfo info = Introspector.getBeanInfo(ButtonPanel.class); + PropertyDescriptor[] props = info.getPropertyDescriptors(); + for (int i = 0; i < props.length; i++) { + System.out.println(props[i].getName()); + } + } catch (Exception exc) { + System.out.println(exc); + } - } - - public void mouseDragged(MouseEvent e) - { - } - - public void mouseMoved(MouseEvent e) - { - } + } + public void mouseDragged(MouseEvent e) { + } + public void mouseMoved(MouseEvent e) { + } } - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/CheckButtonPanel.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/CheckButtonPanel.java index 5e847ae..8116678 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/CheckButtonPanel.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/CheckButtonPanel.java @@ -27,246 +27,223 @@ import javax.swing.JCheckBox; import javax.swing.border.EmptyBorder; /** -* CheckButtonPanel is a simple extension of ButtonPanel. -* Differences are that it uses JCheckBoxes and the -* default alignment is vertical. The panel defaults to having -* no buttons selected. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class CheckButtonPanel extends ButtonPanel -{ -/** -* Constructs a CheckButtonPanel. Three buttons are created -* so the panel is filled when used in a GUI-builder environment. -*/ - public CheckButtonPanel() - { - super(); - } - -/** -* Constructs a ButtonPanel using specified buttons. -* @param buttonList An array containing the strings to be used in labeling the buttons. -*/ - public CheckButtonPanel( String[] buttonList ) - { - super( buttonList ); - } - -/** -* Overridden to set vertical-center alignment and zero vgap. -*/ - protected void initLayout() - { - super.initLayout(); - buttonPanelLayout.setAlignment( BetterFlowLayout.CENTER_VERTICAL ); - buttonPanelLayout.setVgap( 0 ); - } - -/** -* Overridden to return a JRadioButton. -* @param aLabel The label for the component that will be created. -* @return The newly created component. -*/ - protected Component createComponentWithLabel( String aLabel ) - { - String buttonLabel = aLabel; - JCheckBox newButton = new JCheckBox(); - newButton.setName( aLabel ); - newButton.setText( buttonLabel ); - newButton.setActionCommand( aLabel ); - newButton.addActionListener( this ); - - // reduce insets per java l&f guidelines (was 4 on each side) - newButton.setBorder( new EmptyBorder( 1, 4, 1, 4 ) ); - - return newButton; - } - -/** -* Sets the value of the button whose name matches the given text value. -* @param aName A String matching the name of one of the buttons. -* If null, empty, or not matching, nothing happens. -* @param aValue A value to set the button. -*/ - public void setValue(String aName, boolean aValue) - { - if ( aName != null ) - { - Component c = null; - int count = buttonContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = buttonContainer.getComponent( i ); - if ( c instanceof AbstractButton ) - { - if ( c.getName().equals( aName ) ) - { - ((AbstractButton)c).setSelected( aValue ); - c.repaint(); - return; - } - } - } - } - // null, empty, or not matching - exit. - System.out.println( "CheckButtonPanel.setValue: not found: " + aName ); - } - -/** -* Sets the state of the specified buttons to the specified value. -* @param aLabelArray An Array of Strings listing the buttons to be set. -* @param aValue The value to which the specified buttons will be set. -*/ - public void setValues(String[] aLabelArray, boolean aValue) - { - if ( aLabelArray != null ) - { - for ( int i = 0; i < aLabelArray.length; i++ ) - { - setValue( aLabelArray[i], aValue ); - } - } - } - -/** -* Convenience method to set all checkboxes on the panel. -* @param aValue The value to which all checkboxes on the panel will be set. -*/ - public void setAllValues(boolean aValue) - { - setValues( getLabels(), aValue ); - } - -/** -* Convenience method to check all boxes on the panel. -*/ - public void checkAll() - { - setAllValues( true ); - } - -/** -* Convenience method to clear all boxes on the panel. -*/ - public void clearAll() - { - setAllValues( false ); - } - -/** -* A convenience method to set only those buttons on the entire -* panel that should be checked. Buttons not in the list are unchecked. -* @param aLabelArray An Array of Strings listing the buttons to be set. -*/ - public void setCheckedValues(String[] aLabelArray) - { - setAllValues( false ); - setValues( aLabelArray, true ); - } - -/** -* Gets the labels of all checkboxes that are checked. -* @return A List of Strings containing the labels of the boxes that are checked. -*/ - public List getCheckedValueList() - { - Vector v = new Vector(); - Component c = null; - int count = buttonContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = buttonContainer.getComponent( i ); - if ( c instanceof AbstractButton ) - { - if ( ((AbstractButton)c).isSelected() ) - { - v.addElement(c.getName()); - } - } - } - return v; - } - -/** -* Gets the labels of all checkboxes that are checked. -* @return A String Array containing the labels of the boxes that are checked. -*/ - public String[] getCheckedValues() - { - List v = getCheckedValueList(); - String[] result = new String[ v.size() ]; - for ( int i = 0; i < v.size(); i++ ) - { - result[i] = (String) v.get(i); - } - return result; - } - -/** -* Gets the value of the specified button. -* @param aName A String matching the name of one of the buttons. -* @return True if the button is checked, False if it is not checked. -* NOTE: If the button is not found in the list, False is returned. -*/ - public boolean getValue( String aName ) - { - Component c = null; - int count = buttonContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = buttonContainer.getComponent( i ); - if ( ( c instanceof AbstractButton ) && ( ((AbstractButton)c).isSelected() ) ) - { - if ( ((AbstractButton)c).getText().equals( aName ) ) - { - return ((AbstractButton)c).isSelected(); - } - } - } - return false; - } - - // for testing - - public static void main( String[] argv ) - { - try - { - javax.swing.UIManager.setLookAndFeel( javax.swing.UIManager.getSystemLookAndFeelClassName() ); - } - catch (Exception exc) - { - - } - - javax.swing.JFrame dialog = new javax.swing.JFrame(); - java.awt.BorderLayout bl = new java.awt.BorderLayout( 20, 20 ); - - CheckButtonPanel panel = new CheckButtonPanel( new String[] { "One", "Two", "Three" } ); - - dialog.getContentPane().setLayout( bl ); - dialog.getContentPane().add( panel, java.awt.BorderLayout.CENTER ); - dialog.setLocation( 50, 50 ); - // dialog.setSize( 450, 150 ); - - panel.setAlignment( BetterFlowLayout.CENTER_VERTICAL ); - panel.getButton( "One" ).setEnabled( false ); - panel.setValues( new String[] { "One" }, true ); - panel.setValue( "Three", true ); + * CheckButtonPanel is a simple extension of ButtonPanel. Differences are that + * it uses JCheckBoxes and the default alignment is vertical. The panel defaults + * to having no buttons selected. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class CheckButtonPanel extends ButtonPanel { + /** + * Constructs a CheckButtonPanel. Three buttons are created so the panel is + * filled when used in a GUI-builder environment. + */ + public CheckButtonPanel() { + super(); + } + + /** + * Constructs a ButtonPanel using specified buttons. + * + * @param buttonList An array containing the strings to be used in labeling the + * buttons. + */ + public CheckButtonPanel(String[] buttonList) { + super(buttonList); + } + + /** + * Overridden to set vertical-center alignment and zero vgap. + */ + protected void initLayout() { + super.initLayout(); + buttonPanelLayout.setAlignment(BetterFlowLayout.CENTER_VERTICAL); + buttonPanelLayout.setVgap(0); + } + + /** + * Overridden to return a JRadioButton. + * + * @param aLabel The label for the component that will be created. + * @return The newly created component. + */ + protected Component createComponentWithLabel(String aLabel) { + String buttonLabel = aLabel; + JCheckBox newButton = new JCheckBox(); + newButton.setName(aLabel); + newButton.setText(buttonLabel); + newButton.setActionCommand(aLabel); + newButton.addActionListener(this); + + // reduce insets per java l&f guidelines (was 4 on each side) + newButton.setBorder(new EmptyBorder(1, 4, 1, 4)); + + return newButton; + } + + /** + * Sets the value of the button whose name matches the given text value. + * + * @param aName A String matching the name of one of the buttons. If null, + * empty, or not matching, nothing happens. + * @param aValue A value to set the button. + */ + public void setValue(String aName, boolean aValue) { + if (aName != null) { + Component c = null; + int count = buttonContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = buttonContainer.getComponent(i); + if (c instanceof AbstractButton) { + if (c.getName().equals(aName)) { + ((AbstractButton) c).setSelected(aValue); + c.repaint(); + return; + } + } + } + } + // null, empty, or not matching - exit. + System.out.println("CheckButtonPanel.setValue: not found: " + aName); + } + + /** + * Sets the state of the specified buttons to the specified value. + * + * @param aLabelArray An Array of Strings listing the buttons to be set. + * @param aValue The value to which the specified buttons will be set. + */ + public void setValues(String[] aLabelArray, boolean aValue) { + if (aLabelArray != null) { + for (int i = 0; i < aLabelArray.length; i++) { + setValue(aLabelArray[i], aValue); + } + } + } + + /** + * Convenience method to set all checkboxes on the panel. + * + * @param aValue The value to which all checkboxes on the panel will be set. + */ + public void setAllValues(boolean aValue) { + setValues(getLabels(), aValue); + } + + /** + * Convenience method to check all boxes on the panel. + */ + public void checkAll() { + setAllValues(true); + } + + /** + * Convenience method to clear all boxes on the panel. + */ + public void clearAll() { + setAllValues(false); + } + + /** + * A convenience method to set only those buttons on the entire panel that + * should be checked. Buttons not in the list are unchecked. + * + * @param aLabelArray An Array of Strings listing the buttons to be set. + */ + public void setCheckedValues(String[] aLabelArray) { + setAllValues(false); + setValues(aLabelArray, true); + } + + /** + * Gets the labels of all checkboxes that are checked. + * + * @return A List of Strings containing the labels of the boxes that are + * checked. + */ + public List getCheckedValueList() { + Vector v = new Vector(); + Component c = null; + int count = buttonContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = buttonContainer.getComponent(i); + if (c instanceof AbstractButton) { + if (((AbstractButton) c).isSelected()) { + v.addElement(c.getName()); + } + } + } + return v; + } + + /** + * Gets the labels of all checkboxes that are checked. + * + * @return A String Array containing the labels of the boxes that are checked. + */ + public String[] getCheckedValues() { + List v = getCheckedValueList(); + String[] result = new String[v.size()]; + for (int i = 0; i < v.size(); i++) { + result[i] = (String) v.get(i); + } + return result; + } + + /** + * Gets the value of the specified button. + * + * @param aName A String matching the name of one of the buttons. + * @return True if the button is checked, False if it is not checked. NOTE: If + * the button is not found in the list, False is returned. + */ + public boolean getValue(String aName) { + Component c = null; + int count = buttonContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = buttonContainer.getComponent(i); + if ((c instanceof AbstractButton) && (((AbstractButton) c).isSelected())) { + if (((AbstractButton) c).getText().equals(aName)) { + return ((AbstractButton) c).isSelected(); + } + } + } + return false; + } + + // for testing + + public static void main(String[] argv) { + try { + javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName()); + } catch (Exception exc) { + + } + + javax.swing.JFrame dialog = new javax.swing.JFrame(); + java.awt.BorderLayout bl = new java.awt.BorderLayout(20, 20); + + CheckButtonPanel panel = new CheckButtonPanel(new String[] { "One", "Two", "Three" }); + + dialog.getContentPane().setLayout(bl); + dialog.getContentPane().add(panel, java.awt.BorderLayout.CENTER); + dialog.setLocation(50, 50); + // dialog.setSize( 450, 150 ); + + panel.setAlignment(BetterFlowLayout.CENTER_VERTICAL); + panel.getButton("One").setEnabled(false); + panel.setValues(new String[] { "One" }, true); + panel.setValue("Three", true); // panel.setCheckedValues( new String[] { "Two" } ); - String[] values = panel.getCheckedValues(); - for ( int i = 0; i < values.length; i++ ) - { - System.out.println( values[i] ); - } + String[] values = panel.getCheckedValues(); + for (int i = 0; i < values.length; i++) { + System.out.println(values[i]); + } - dialog.pack(); - dialog.setVisible( true ); + dialog.pack(); + dialog.setVisible(true); - } + } } - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ColorCellEditor.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ColorCellEditor.java index a0a14ac..10feef7 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ColorCellEditor.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ColorCellEditor.java @@ -29,56 +29,42 @@ import javax.swing.JCheckBox; import javax.swing.JTable; /** -* A TableCellEditor that edits colors - it launches a color dialog when clicked. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ + * A TableCellEditor that edits colors - it launches a color dialog when + * clicked. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ class ColorCellEditor extends DefaultCellEditor { - Color currentColor = null; - - public ColorCellEditor(JButton b) - { - super(new JCheckBox()); // unfortunately, the constructor - // expects a check box, combo box, - // or text field. - editorComponent = b; - setClickCountToStart(1); // this is usually 1 or 2. - - // must do this so that editing stops when appropriate. - b.addActionListener(new ActionListener() - { - public void actionPerformed(ActionEvent e) - { - fireEditingStopped(); - } - } - ); - } - - protected void fireEditingStopped() - { - super.fireEditingStopped(); - } - - public Object getCellEditorValue() - { - return currentColor; - } - - public Component getTableCellEditorComponent(JTable table, - Object value, - boolean isSelected, - int row, - int column) - { - ((JButton)editorComponent).setText(value.toString()); - currentColor = (Color)value; - return editorComponent; - } + Color currentColor = null; + + public ColorCellEditor(JButton b) { + super(new JCheckBox()); // unfortunately, the constructor + // expects a check box, combo box, + // or text field. + editorComponent = b; + setClickCountToStart(1); // this is usually 1 or 2. + + // must do this so that editing stops when appropriate. + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + fireEditingStopped(); + } + }); + } + + protected void fireEditingStopped() { + super.fireEditingStopped(); + } + + public Object getCellEditorValue() { + return currentColor; + } + + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { + ((JButton) editorComponent).setText(value.toString()); + currentColor = (Color) value; + return editorComponent; + } } - - - - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ColorCellRenderer.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ColorCellRenderer.java index 0552183..2fc97bb 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ColorCellRenderer.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ColorCellRenderer.java @@ -28,54 +28,39 @@ import javax.swing.border.Border; import javax.swing.table.TableCellRenderer; /** -* A TableCellRenderer that renders colors. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ + * A TableCellRenderer that renders colors. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ public class ColorCellRenderer extends JLabel implements TableCellRenderer { - Border unselectedBorder = null; - Border selectedBorder = null; - boolean isBordered = true; + Border unselectedBorder = null; + Border selectedBorder = null; + boolean isBordered = true; - public ColorCellRenderer(boolean isBordered) - { - super(); - this.isBordered = isBordered; - setOpaque(true); // must do this for background to show up. - } + public ColorCellRenderer(boolean isBordered) { + super(); + this.isBordered = isBordered; + setOpaque(true); // must do this for background to show up. + } - public Component getTableCellRendererComponent( - JTable table, Object color, - boolean isSelected, boolean hasFocus, - int row, int column) - { - setBackground((Color)color); - if (isBordered) - { - if (isSelected) - { - if (selectedBorder == null) - { - selectedBorder = BorderFactory.createMatteBorder(2,5,2,5, - table.getSelectionBackground()); - } - setBorder(selectedBorder); - } - else - { - if (unselectedBorder == null) - { - unselectedBorder = BorderFactory.createMatteBorder(2,5,2,5, - table.getBackground()); - } - setBorder(unselectedBorder); - } - } - return this; - } + public Component getTableCellRendererComponent(JTable table, Object color, boolean isSelected, boolean hasFocus, + int row, int column) { + setBackground((Color) color); + if (isBordered) { + if (isSelected) { + if (selectedBorder == null) { + selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, table.getSelectionBackground()); + } + setBorder(selectedBorder); + } else { + if (unselectedBorder == null) { + unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, table.getBackground()); + } + setBorder(unselectedBorder); + } + } + return this; + } } - - - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ComboBoxCellRenderer.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ComboBoxCellRenderer.java index 2bf8dd6..67cb726 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ComboBoxCellRenderer.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ComboBoxCellRenderer.java @@ -26,32 +26,24 @@ import javax.swing.JTable; import javax.swing.table.TableCellRenderer; /** -* A TableCellRenderer that paints a JComboBox. Useful if -* you want to visibly display the JComboBox before the -* user clicks on the cell. -* -* @author bsafa@intersectsoft.com -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ + * A TableCellRenderer that paints a JComboBox. Useful if you want to visibly + * display the JComboBox before the user clicks on the cell. + * + * @author bsafa@intersectsoft.com + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ public class ComboBoxCellRenderer extends JComboBox implements TableCellRenderer { - public ComboBoxCellRenderer() - { - super(); - setOpaque(true); - } - - public Component getTableCellRendererComponent( - JTable table, Object value, - boolean isSelected, boolean hasFocus, - int row, int column) - { - setBackground(Color.white); - setSelectedItem(value); - return this; - } + public ComboBoxCellRenderer() { + super(); + setOpaque(true); + } + + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, + int row, int column) { + setBackground(Color.white); + setSelectedItem(value); + return this; + } } - - - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/DateTextField.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/DateTextField.java index 18ed035..8a3b08c 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/DateTextField.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/DateTextField.java @@ -30,601 +30,507 @@ import java.util.Vector; import javax.swing.JOptionPane; import javax.swing.JTextField; - -/** -* DateTextField is a "smart" text field that restricts the user's input. The -* input is restructed to a string representing a date format. -* -* @author rob@straylight.princeton.com -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class DateTextField extends JTextField -{ - -/******************************* -* CONSTANTS -*******************************/ - -/** -* Use the current date for this text field. -*/ - public static final int CURRENT_DATE = 0; - /** -* Use blanks for this text field. -*/ - public static final int BLANKS = 1; - -/** -* Use underscores for this text field. -*/ - public static final int UNDERSCORES = 2; - -/** -* Use just a 4-digit year for this text field. -*/ - public static final int YEAR = 3; - - private static final int BACKSPACE = 8; - private static final int DELETE = 127; - private static final int PASTE = 22; // Ctl-V - private static final int CUT = 24; // Ctl-X - - -/******************************* -* DATA MEMEBERS -*******************************/ - private int defaultType = CURRENT_DATE; - - private boolean warningMessageActive = false; - - -/******************************* -* PUBLIC METHODS -*******************************/ - -/** -* Default Constructor. -*/ - public DateTextField() - { - this(1, 1, 1999, 0); - - Calendar rightNow = Calendar.getInstance(); - - super.setText(createDateString(rightNow.get(Calendar.MONTH) + 1, - rightNow.get(Calendar.DATE), - rightNow.get(Calendar.YEAR))); - } - -/** -* Constructor. -* @param month Number of the month, January being 1. -* @param data The day of the month. -* @param year The year. -*/ - public DateTextField(int month, int date, int year) - { - this(month, date, year, 0); - } - -/** -* Constructor. -* @param columns Width of the text field (in characters). -*/ - public DateTextField(int columns) - { - this(1, 1, 1998, columns); - - Calendar rightNow = Calendar.getInstance(); - - super.setText(createDateString(rightNow.get(Calendar.MONTH) + 1, - rightNow.get(Calendar.DATE), - rightNow.get(Calendar.YEAR))); - } - -/** -* Constructor. -* @param month Number of the month, January being 1. -* @param data The day of the month. -* @param year The year. -* @param columns Width of the text field (in characters). -*/ - public DateTextField(int month, int date, int year, int columns) - { - super("", columns); - - super.setText(createDateString(month, date, year)); - - this.addFocusListener(new FocusAdapter() - { - public void focusLost(FocusEvent e) - { - if (!(e.isTemporary())) - { - validateDateString(e); - } - } - }); - } - -/** -* Sets the date type to display when the user has not entered any date yet. -* Default is the current date. -* @see #CURRENT_DATE -* @see #BLANKS -* @see #UNDERSCORES -* @param newDefaultType The type of date to display when there is no date data. -*/ - public void setDefaultType(int newDefaultType) - { - if (newDefaultType == BLANKS) - { - defaultType = BLANKS; - super.setText(" / / "); - } - else if (newDefaultType == UNDERSCORES) - { - defaultType = UNDERSCORES; - super.setText("__/__/____"); - } - else if (newDefaultType == YEAR) - { - defaultType = YEAR; - super.setText("0000"); - } - else - { - defaultType = CURRENT_DATE; - - Calendar rightNow = Calendar.getInstance(); - - super.setText(createDateString(rightNow.get(Calendar.MONTH) + 1, - rightNow.get(Calendar.DATE), - rightNow.get(Calendar.YEAR))); - } - } - -/** -* Returns the type of date to display when there is no user input. -* @see #CURRENT_DATE -* @see #BLANKS -* @see #UNDERSCORES -* @return The type of date to display when there is no date to display. -*/ - public int getDefaultType() - { - return defaultType; - } - -/** -* Sets the text field to the string representation of the specified date. -* @param aDate The date to set the text field to. -*/ - public void setDate(Date aDate) - { - Calendar aCalendar = Calendar.getInstance(); - - aCalendar.setTime(aDate); - - super.setText(createDateString(aCalendar.get(Calendar.MONTH) + 1, - aCalendar.get(Calendar.DATE), - aCalendar.get(Calendar.YEAR))); - } - -/** -* Sets the text field directly from a Date object. -* @param aDate The date to set the text field to. -*/ - public void setText( Date aDate ) - { - setDate( aDate ); - } - -/** -* Sets the text field to the date specified in the string. This is overridden -* from the parent class to insure a valid date is inputted. The format of the -* date expected is the type of date format this text field is currently set to. -* @param aString A string representing a date in this text field current format. -*/ - public void setText( String aString ) - { - Date testDate = null; - - if ( aString != null ) - { - ParsePosition position = new ParsePosition( 0 ); - - if ( defaultType == YEAR ) - { - SimpleDateFormat yearFormatter = new SimpleDateFormat( "yyyy" ); - testDate = yearFormatter.parse( aString, position ); - } - else - { - SimpleDateFormat fullDateFormatter = new SimpleDateFormat( "MM/dd/yyyy" ); - testDate = fullDateFormatter.parse( aString, position ); - } - } - - // The string is not a valid date, use default value for date then. - if ( testDate == null ) - { - Calendar aCalendar = Calendar.getInstance(); - - testDate = aCalendar.getTime(); - } - - setDate( testDate ); - } - -/** -* Returns the date as represented by the date string in the text field. -* @return The date in the text field. -*/ - public Date getDate() throws NumberFormatException - { - Calendar aCalendar = Calendar.getInstance(); - int year = 1980; - int month = 0; - int date = 1; - int[] tempArray = {1,3,5,7,8,10,12}; - Vector monthsWith31Days = new Vector(7); - - for (int i = 0; i < tempArray.length; ++i) - { - monthsWith31Days.addElement(new Integer(tempArray[i])); - } - - aCalendar.set(year, month, date, 12, 0, 0); - - try - { - String dateString = getText(); - NumberFormatException nfException = new NumberFormatException(new String("Invalid Date String: " + dateString)); - - if (defaultType == YEAR) - { - year = Integer.parseInt(dateString); - - aCalendar.set(year, 0, 1, 12, 0, 0); - - return aCalendar.getTime(); - } - - month = Integer.parseInt(dateString.substring(0, 2).trim()); - date = Integer.parseInt(dateString.substring(3, 5).trim()); - year = Integer.parseInt(dateString.substring(6).trim()); - - if ((month < 1) || (month > 12)) - { - throw nfException; - } - - if ((date < 1) || (date > 31)) - { - throw nfException; - } - - if ((date == 31) && (!(monthsWith31Days.contains(new Integer(month))))) - { - throw nfException; - } - - if ((date == 30) && (month == 2)) - { - throw nfException; - } - - if ((date == 29) && (month == 2)) - { - if ((year % 100) == 0) - { - if ((year % 400) != 0) - { - throw nfException; - } - } - else - { - if ((year % 4) != 0) - { - throw nfException; - } - } - } - } - catch (IndexOutOfBoundsException ioobe) - { - NumberFormatException nfException = new NumberFormatException(new String("Invalid Date String: " + getText())); - throw nfException; - } - catch (NumberFormatException nfe) - { - NumberFormatException nfException = new NumberFormatException(new String("Invalid Date String: " + getText())); - throw nfException; - } - - aCalendar.set(year, (month - 1), date, 12, 0, 0); - - return aCalendar.getTime(); - } - - public void processKeyEvent(KeyEvent e) - { - String currentString = ""; - String testString = ""; - char newChar = e.getKeyChar(); - int currentLength = 0; - int currentCaretPosition = 0; - int selectionStart = 0; - int selectionEnd = 0; - int modifierPosition = 0; - int modifierDirection = 1; - char modifierCharacter; - boolean backspace = false; - boolean delete = false; - boolean paste = false; - boolean cut = false; - boolean keyPressed = false; - - backspace = (newChar == BACKSPACE); - delete = (newChar == DELETE); - paste = (newChar == PASTE); - cut = (newChar == CUT); - - keyPressed = (e.paramString().startsWith("KEY_PRESSED")); - - if ((e.getKeyCode() == KeyEvent.VK_UNDEFINED) || ((backspace) || (delete) || (paste) || (cut))) // A "key-typed" event - { - if (isValidCharacter(newChar)) - { - if ((isPrintableCharacter(newChar)) || (backspace) || (delete)) - { - // Both the key "pressed" and key "released" events get passed - // in here for the delete and backspace key. Only processes - // these keys if the event is key "pressed". - if (((backspace) || (delete)) && (!(keyPressed))) - { - // Don't do anything, pass through to consumption. - } - else - { - // Analyze the current contents of the field - currentString = getText(); - currentLength = currentString.length(); - - char[] tempText = new char[currentLength]; - - currentCaretPosition = getCaretPosition(); - - selectionStart = getSelectionStart(); - selectionEnd = getSelectionEnd(); - - // if a range is selected, then get rid of it and place the caret - // at the begginning of the range and continue processing. - if (selectionStart != selectionEnd) - { - selectionEnd = selectionStart; - setSelectionEnd(selectionEnd); - - currentCaretPosition = selectionStart; - setCaretPosition(currentCaretPosition); - } - - if (currentCaretPosition <= currentLength) - { - // a number of delete or backspace was pressed, delete and - // backspace deletes a number and places a "space" there - - // if caret at start of string and the backspace pressed OR - // caret at end of string and delete or number pressed THEN - // don't do anything, otherwise process key stroke - if (((currentCaretPosition == 0) && (backspace)) || - ((currentCaretPosition == currentLength) && (!(backspace)))) - { - // Don't do any processing. - } - else - { - modifierPosition = currentCaretPosition; - if (backspace) - { - modifierDirection = -1; - modifierPosition += modifierDirection; - } - - // Overwrite the current position with the new character - // inputted or overwrite using a space or underscore if - // the backspace or delete key was pressed. - if (defaultType != YEAR) - { - modifierCharacter = - ((delete)||(backspace)) ? - ((defaultType == UNDERSCORES) ? '_' : ' ') : - newChar; - } - else - { - // We are dealing with a 4-digit year. Overwrite - // with new character or "0" if delete or backspace - // was pressed. - modifierCharacter = ((delete)||(backspace)) ? ('0') : newChar; - } - - if (currentString.charAt(modifierPosition) == '/') - { - modifierPosition += modifierDirection; - } - - for (int i = 0; i < currentLength; ++i) - { - if (i == modifierPosition) - { - tempText[i] = modifierCharacter; - } - else - { - tempText[i] = currentString.charAt(i); - } - } - - testString = new String(tempText); - if (isValidString(testString)) - { - super.setText(testString); - if (backspace) - { - setCaretPosition(modifierPosition); - } - else - { - setCaretPosition(modifierPosition + 1); - } - } - } - } - } - - e.consume(); - } - else if ((cut) || (paste)) - { - e.consume(); - } - // else its a non-printable character, let it pass through - } - else - { - e.consume(); - } - } - - super.processKeyEvent(e); - } - - private boolean isValidCharacter(char aChar) - { - if (((aChar >= '!') && (aChar <= '/')) || ((aChar >= ':') && (aChar <= '~'))) - { - return false; - } - return true; - } - - private boolean isPrintableCharacter(char inputChar) - { - if ((inputChar >= ' ') && (inputChar <= '~')) - { - return true; - } - return false; - } - - private boolean isValidDate(int month, int date, int year) - { - if ((month < 1) || (month > 12)) - { - return false; - } - - if ((date < 1) || (date > 31)) - { - return false; - } - - if ((year < 0) || (year > 9999)) - { - return false; - } - - return true; - } - - private boolean isValidString(String aString) - { - return true; - } - - private String createDateString(int month, int date, int year) - { - String dateString = ""; - - if (isValidDate(month, date, year)) - { - if (defaultType != YEAR) - { - if (month < 10) - { - dateString = "0"; - } - - dateString += String.valueOf(month); - dateString += "/"; - - if (date < 10) - { - dateString += "0"; - } - - dateString += String.valueOf(date); - dateString += "/"; - } - - if (year < 1000) - { - dateString += "0"; - if (year < 100) - { - dateString += "0"; - if (year < 10) - { - dateString += "0"; - } - } - } - - dateString += String.valueOf(year); - } - else - { - if (defaultType == YEAR) - { - dateString = "1999"; - } - else - { - dateString = "01/01/1999"; - } - } - - return dateString; - } - - private void validateDateString(FocusEvent e) - { - if (!(warningMessageActive)) - { - try - { - getDate(); - } - catch (NumberFormatException nfe) - { - System.out.println("Invalid Date String!!!"); - warningMessageActive = true; - JOptionPane.showMessageDialog(this, "Invald Date: " + getText(), "Warning", JOptionPane.WARNING_MESSAGE); - warningMessageActive = false; - if (defaultType == YEAR) - { - super.setText("1999"); - } - else - { - super.setText("01/01/1999"); - } - } - } - } + * DateTextField is a "smart" text field that restricts the user's input. The + * input is restructed to a string representing a date format. + * + * @author rob@straylight.princeton.com + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class DateTextField extends JTextField { + + /******************************* + * CONSTANTS + *******************************/ + + /** + * Use the current date for this text field. + */ + public static final int CURRENT_DATE = 0; + + /** + * Use blanks for this text field. + */ + public static final int BLANKS = 1; + + /** + * Use underscores for this text field. + */ + public static final int UNDERSCORES = 2; + + /** + * Use just a 4-digit year for this text field. + */ + public static final int YEAR = 3; + + private static final int BACKSPACE = 8; + private static final int DELETE = 127; + private static final int PASTE = 22; // Ctl-V + private static final int CUT = 24; // Ctl-X + + /******************************* + * DATA MEMEBERS + *******************************/ + private int defaultType = CURRENT_DATE; + + private boolean warningMessageActive = false; + + /******************************* + * PUBLIC METHODS + *******************************/ + + /** + * Default Constructor. + */ + public DateTextField() { + this(1, 1, 1999, 0); + + Calendar rightNow = Calendar.getInstance(); + + super.setText(createDateString(rightNow.get(Calendar.MONTH) + 1, rightNow.get(Calendar.DATE), + rightNow.get(Calendar.YEAR))); + } + + /** + * Constructor. + * + * @param month Number of the month, January being 1. + * @param data The day of the month. + * @param year The year. + */ + public DateTextField(int month, int date, int year) { + this(month, date, year, 0); + } + + /** + * Constructor. + * + * @param columns Width of the text field (in characters). + */ + public DateTextField(int columns) { + this(1, 1, 1998, columns); + + Calendar rightNow = Calendar.getInstance(); + + super.setText(createDateString(rightNow.get(Calendar.MONTH) + 1, rightNow.get(Calendar.DATE), + rightNow.get(Calendar.YEAR))); + } + + /** + * Constructor. + * + * @param month Number of the month, January being 1. + * @param data The day of the month. + * @param year The year. + * @param columns Width of the text field (in characters). + */ + public DateTextField(int month, int date, int year, int columns) { + super("", columns); + + super.setText(createDateString(month, date, year)); + + this.addFocusListener(new FocusAdapter() { + public void focusLost(FocusEvent e) { + if (!(e.isTemporary())) { + validateDateString(e); + } + } + }); + } + + /** + * Sets the date type to display when the user has not entered any date yet. + * Default is the current date. + * + * @see #CURRENT_DATE + * @see #BLANKS + * @see #UNDERSCORES + * @param newDefaultType The type of date to display when there is no date data. + */ + public void setDefaultType(int newDefaultType) { + if (newDefaultType == BLANKS) { + defaultType = BLANKS; + super.setText(" / / "); + } else if (newDefaultType == UNDERSCORES) { + defaultType = UNDERSCORES; + super.setText("__/__/____"); + } else if (newDefaultType == YEAR) { + defaultType = YEAR; + super.setText("0000"); + } else { + defaultType = CURRENT_DATE; + + Calendar rightNow = Calendar.getInstance(); + + super.setText(createDateString(rightNow.get(Calendar.MONTH) + 1, rightNow.get(Calendar.DATE), + rightNow.get(Calendar.YEAR))); + } + } + + /** + * Returns the type of date to display when there is no user input. + * + * @see #CURRENT_DATE + * @see #BLANKS + * @see #UNDERSCORES + * @return The type of date to display when there is no date to display. + */ + public int getDefaultType() { + return defaultType; + } + + /** + * Sets the text field to the string representation of the specified date. + * + * @param aDate The date to set the text field to. + */ + public void setDate(Date aDate) { + Calendar aCalendar = Calendar.getInstance(); + + aCalendar.setTime(aDate); + + super.setText(createDateString(aCalendar.get(Calendar.MONTH) + 1, aCalendar.get(Calendar.DATE), + aCalendar.get(Calendar.YEAR))); + } + + /** + * Sets the text field directly from a Date object. + * + * @param aDate The date to set the text field to. + */ + public void setText(Date aDate) { + setDate(aDate); + } + + /** + * Sets the text field to the date specified in the string. This is overridden + * from the parent class to insure a valid date is inputted. The format of the + * date expected is the type of date format this text field is currently set to. + * + * @param aString A string representing a date in this text field current + * format. + */ + public void setText(String aString) { + Date testDate = null; + + if (aString != null) { + ParsePosition position = new ParsePosition(0); + + if (defaultType == YEAR) { + SimpleDateFormat yearFormatter = new SimpleDateFormat("yyyy"); + testDate = yearFormatter.parse(aString, position); + } else { + SimpleDateFormat fullDateFormatter = new SimpleDateFormat("MM/dd/yyyy"); + testDate = fullDateFormatter.parse(aString, position); + } + } + + // The string is not a valid date, use default value for date then. + if (testDate == null) { + Calendar aCalendar = Calendar.getInstance(); + + testDate = aCalendar.getTime(); + } + + setDate(testDate); + } + + /** + * Returns the date as represented by the date string in the text field. + * + * @return The date in the text field. + */ + public Date getDate() throws NumberFormatException { + Calendar aCalendar = Calendar.getInstance(); + int year = 1980; + int month = 0; + int date = 1; + int[] tempArray = { 1, 3, 5, 7, 8, 10, 12 }; + Vector monthsWith31Days = new Vector(7); + + for (int i = 0; i < tempArray.length; ++i) { + monthsWith31Days.addElement(new Integer(tempArray[i])); + } + + aCalendar.set(year, month, date, 12, 0, 0); + + try { + String dateString = getText(); + NumberFormatException nfException = new NumberFormatException( + new String("Invalid Date String: " + dateString)); + + if (defaultType == YEAR) { + year = Integer.parseInt(dateString); + + aCalendar.set(year, 0, 1, 12, 0, 0); + + return aCalendar.getTime(); + } + + month = Integer.parseInt(dateString.substring(0, 2).trim()); + date = Integer.parseInt(dateString.substring(3, 5).trim()); + year = Integer.parseInt(dateString.substring(6).trim()); + + if ((month < 1) || (month > 12)) { + throw nfException; + } + + if ((date < 1) || (date > 31)) { + throw nfException; + } + + if ((date == 31) && (!(monthsWith31Days.contains(new Integer(month))))) { + throw nfException; + } + + if ((date == 30) && (month == 2)) { + throw nfException; + } + + if ((date == 29) && (month == 2)) { + if ((year % 100) == 0) { + if ((year % 400) != 0) { + throw nfException; + } + } else { + if ((year % 4) != 0) { + throw nfException; + } + } + } + } catch (IndexOutOfBoundsException ioobe) { + NumberFormatException nfException = new NumberFormatException( + new String("Invalid Date String: " + getText())); + throw nfException; + } catch (NumberFormatException nfe) { + NumberFormatException nfException = new NumberFormatException( + new String("Invalid Date String: " + getText())); + throw nfException; + } + + aCalendar.set(year, (month - 1), date, 12, 0, 0); + + return aCalendar.getTime(); + } + + public void processKeyEvent(KeyEvent e) { + String currentString = ""; + String testString = ""; + char newChar = e.getKeyChar(); + int currentLength = 0; + int currentCaretPosition = 0; + int selectionStart = 0; + int selectionEnd = 0; + int modifierPosition = 0; + int modifierDirection = 1; + char modifierCharacter; + boolean backspace = false; + boolean delete = false; + boolean paste = false; + boolean cut = false; + boolean keyPressed = false; + + backspace = (newChar == BACKSPACE); + delete = (newChar == DELETE); + paste = (newChar == PASTE); + cut = (newChar == CUT); + + keyPressed = (e.paramString().startsWith("KEY_PRESSED")); + + if ((e.getKeyCode() == KeyEvent.VK_UNDEFINED) || ((backspace) || (delete) || (paste) || (cut))) // A "key-typed" + // event + { + if (isValidCharacter(newChar)) { + if ((isPrintableCharacter(newChar)) || (backspace) || (delete)) { + // Both the key "pressed" and key "released" events get passed + // in here for the delete and backspace key. Only processes + // these keys if the event is key "pressed". + if (((backspace) || (delete)) && (!(keyPressed))) { + // Don't do anything, pass through to consumption. + } else { + // Analyze the current contents of the field + currentString = getText(); + currentLength = currentString.length(); + + char[] tempText = new char[currentLength]; + + currentCaretPosition = getCaretPosition(); + + selectionStart = getSelectionStart(); + selectionEnd = getSelectionEnd(); + + // if a range is selected, then get rid of it and place the caret + // at the begginning of the range and continue processing. + if (selectionStart != selectionEnd) { + selectionEnd = selectionStart; + setSelectionEnd(selectionEnd); + + currentCaretPosition = selectionStart; + setCaretPosition(currentCaretPosition); + } + + if (currentCaretPosition <= currentLength) { + // a number of delete or backspace was pressed, delete and + // backspace deletes a number and places a "space" there + + // if caret at start of string and the backspace pressed OR + // caret at end of string and delete or number pressed THEN + // don't do anything, otherwise process key stroke + if (((currentCaretPosition == 0) && (backspace)) + || ((currentCaretPosition == currentLength) && (!(backspace)))) { + // Don't do any processing. + } else { + modifierPosition = currentCaretPosition; + if (backspace) { + modifierDirection = -1; + modifierPosition += modifierDirection; + } + + // Overwrite the current position with the new character + // inputted or overwrite using a space or underscore if + // the backspace or delete key was pressed. + if (defaultType != YEAR) { + modifierCharacter = ((delete) || (backspace)) + ? ((defaultType == UNDERSCORES) ? '_' : ' ') + : newChar; + } else { + // We are dealing with a 4-digit year. Overwrite + // with new character or "0" if delete or backspace + // was pressed. + modifierCharacter = ((delete) || (backspace)) ? ('0') : newChar; + } + + if (currentString.charAt(modifierPosition) == '/') { + modifierPosition += modifierDirection; + } + + for (int i = 0; i < currentLength; ++i) { + if (i == modifierPosition) { + tempText[i] = modifierCharacter; + } else { + tempText[i] = currentString.charAt(i); + } + } + + testString = new String(tempText); + if (isValidString(testString)) { + super.setText(testString); + if (backspace) { + setCaretPosition(modifierPosition); + } else { + setCaretPosition(modifierPosition + 1); + } + } + } + } + } + + e.consume(); + } else if ((cut) || (paste)) { + e.consume(); + } + // else its a non-printable character, let it pass through + } else { + e.consume(); + } + } + + super.processKeyEvent(e); + } + + private boolean isValidCharacter(char aChar) { + if (((aChar >= '!') && (aChar <= '/')) || ((aChar >= ':') && (aChar <= '~'))) { + return false; + } + return true; + } + + private boolean isPrintableCharacter(char inputChar) { + if ((inputChar >= ' ') && (inputChar <= '~')) { + return true; + } + return false; + } + + private boolean isValidDate(int month, int date, int year) { + if ((month < 1) || (month > 12)) { + return false; + } + + if ((date < 1) || (date > 31)) { + return false; + } + + if ((year < 0) || (year > 9999)) { + return false; + } + + return true; + } + + private boolean isValidString(String aString) { + return true; + } + + private String createDateString(int month, int date, int year) { + String dateString = ""; + + if (isValidDate(month, date, year)) { + if (defaultType != YEAR) { + if (month < 10) { + dateString = "0"; + } + + dateString += String.valueOf(month); + dateString += "/"; + + if (date < 10) { + dateString += "0"; + } + + dateString += String.valueOf(date); + dateString += "/"; + } + + if (year < 1000) { + dateString += "0"; + if (year < 100) { + dateString += "0"; + if (year < 10) { + dateString += "0"; + } + } + } + + dateString += String.valueOf(year); + } else { + if (defaultType == YEAR) { + dateString = "1999"; + } else { + dateString = "01/01/1999"; + } + } + + return dateString; + } + + private void validateDateString(FocusEvent e) { + if (!(warningMessageActive)) { + try { + getDate(); + } catch (NumberFormatException nfe) { + System.out.println("Invalid Date String!!!"); + warningMessageActive = true; + JOptionPane.showMessageDialog(this, "Invald Date: " + getText(), "Warning", + JOptionPane.WARNING_MESSAGE); + warningMessageActive = false; + if (defaultType == YEAR) { + super.setText("1999"); + } else { + super.setText("01/01/1999"); + } + } + } + } } diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/FormattedCellRenderer.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/FormattedCellRenderer.java index b3e2a76..10b1b89 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/FormattedCellRenderer.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/FormattedCellRenderer.java @@ -27,101 +27,92 @@ import javax.swing.JTable; import javax.swing.table.DefaultTableCellRenderer; /** -* A cell renderer for dealing with formatted content. -* Subclasses can specify formats or colors or styles for specific values -* or locations in the table by overridding getFormatForContext(), -* getForegroundForContext() and/or getBackgroundForContext(). -* -* @author michael@mpowers.net -* @version $Revision: 904 $ -* $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ -*/ -public class FormattedCellRenderer extends DefaultTableCellRenderer -{ - protected Format currentFormat, defaultFormat; - protected Color defaultForeground, defaultBackground; - protected Font defaultFont; - -/** -* Default constructor with no specified format. -*/ - public FormattedCellRenderer() - { - this( (Format) null ); - } - -/** -* Constructor specifying a format for renderered content. -*/ - public FormattedCellRenderer( Format aFormat ) - { - currentFormat = null; - defaultFormat = aFormat; - defaultForeground = super.getForeground(); - defaultBackground = super.getForeground(); - } - -/** -* Returns the format currently in use to format cell content. -* @return The Format that is currently being used. -*/ - public Format getFormat() - { - return defaultFormat; - } - -/** -* Sets the format to be used to format cell content. -*/ - public void setFormat( Format aFormat ) - { - defaultFormat = aFormat; - } - -/** -* Overrides to retain the default foreground color, -* much the same as the DefaultCellRenderer does. -* We have to do this because DefaultCellRenderer's -* ivars are private. -*/ - public void setForeground(Color c) { - super.setForeground(c); - defaultForeground = c; - } - -/** -* Overrides to retain the default background color, -* much the same as the DefaultCellRenderer does. -* We have to do this because DefaultCellRenderer's -* ivars are private. -*/ - public void setBackground(Color c) { - super.setBackground(c); - defaultBackground = c; - } - -/** -* Overrides to retain the default font, -* much the same as the DefaultCellRenderer does. -* We have to do this because DefaultCellRenderer's -* ivars are private. -*/ - public void setFont(Font f) { - super.setFont(f); - defaultFont = f; - } - -/** -* Overridden to format the value with the appropriate Format. If the -* value cannot be formatted with the Format, the superclass method is called. -* @param value An Object to be formatted. -*/ - protected void setValue(Object value) - { - if ( currentFormat != null ) - { - try - { + * A cell renderer for dealing with formatted content. Subclasses can specify + * formats or colors or styles for specific values or locations in the table by + * overridding getFormatForContext(), getForegroundForContext() and/or + * getBackgroundForContext(). + * + * @author michael@mpowers.net + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ + */ +public class FormattedCellRenderer extends DefaultTableCellRenderer { + protected Format currentFormat, defaultFormat; + protected Color defaultForeground, defaultBackground; + protected Font defaultFont; + + /** + * Default constructor with no specified format. + */ + public FormattedCellRenderer() { + this((Format) null); + } + + /** + * Constructor specifying a format for renderered content. + */ + public FormattedCellRenderer(Format aFormat) { + currentFormat = null; + defaultFormat = aFormat; + defaultForeground = super.getForeground(); + defaultBackground = super.getForeground(); + } + + /** + * Returns the format currently in use to format cell content. + * + * @return The Format that is currently being used. + */ + public Format getFormat() { + return defaultFormat; + } + + /** + * Sets the format to be used to format cell content. + */ + public void setFormat(Format aFormat) { + defaultFormat = aFormat; + } + + /** + * Overrides to retain the default foreground color, much the same as the + * DefaultCellRenderer does. We have to do this because DefaultCellRenderer's + * ivars are private. + */ + public void setForeground(Color c) { + super.setForeground(c); + defaultForeground = c; + } + + /** + * Overrides to retain the default background color, much the same as the + * DefaultCellRenderer does. We have to do this because DefaultCellRenderer's + * ivars are private. + */ + public void setBackground(Color c) { + super.setBackground(c); + defaultBackground = c; + } + + /** + * Overrides to retain the default font, much the same as the + * DefaultCellRenderer does. We have to do this because DefaultCellRenderer's + * ivars are private. + */ + public void setFont(Font f) { + super.setFont(f); + defaultFont = f; + } + + /** + * Overridden to format the value with the appropriate Format. If the value + * cannot be formatted with the Format, the superclass method is called. + * + * @param value An Object to be formatted. + */ + protected void setValue(Object value) { + if (currentFormat != null) { + try { // if ( ( value instanceof Number ) && ( value.toString().indexOf( "E" ) != -1 ) ) // { @@ -132,153 +123,140 @@ public class FormattedCellRenderer extends DefaultTableCellRenderer // System.out.println( "FormattedCellRenderer.setValue: converted = '" + currentFormat.format( value ) + "'" ); // } - // WORKAROUND: This works around what may be a rounding bug in DecimalFormat. (PR 256/297) - currentFormat.format( ZERO ); - - // DEBUG: code to test for weird one/zero problem (PR 256/297) - String result = currentFormat.format( value ); -/* above workaround seems to be working - if ( result.equals( "1" ) ) - { - System.out.println( "FormattedCellRenderer.setValue: Could be the ONE/ZERO problem!" ); - System.out.println( "FormattedCellRenderer.setValue: format = '" + currentFormat.getClass() + "'" ); - System.out.println( "FormattedCellRenderer.setValue: original value = '" + value + "'" ); - System.out.println( "FormattedCellRenderer.setValue: result = '" + result + "'" ); - } -*/ - setText( result ); - + // WORKAROUND: This works around what may be a rounding bug in DecimalFormat. + // (PR 256/297) + currentFormat.format(ZERO); + + // DEBUG: code to test for weird one/zero problem (PR 256/297) + String result = currentFormat.format(value); + /* + * above workaround seems to be working if ( result.equals( "1" ) ) { + * System.out.println( + * "FormattedCellRenderer.setValue: Could be the ONE/ZERO problem!" ); + * System.out.println( "FormattedCellRenderer.setValue: format = '" + + * currentFormat.getClass() + "'" ); System.out.println( + * "FormattedCellRenderer.setValue: original value = '" + value + "'" ); + * System.out.println( "FormattedCellRenderer.setValue: result = '" + result + + * "'" ); } + */ + setText(result); // setText( currentFormat.format( value ) ); - return; - } - catch ( IllegalArgumentException exc ) - { - // fall back on superclass implementation - } - } - super.setValue( value ); - } - - // FIXME: remove this when possible - private static Double ZERO = new Double( 0.0 ); - -/** -* Overridden to call context delegate methods. -*/ - public Component getTableCellRendererComponent(JTable table, Object value, - boolean isSelected, boolean hasFocus, int row, int column) - { - Format format; - - // allow for context-sensitve formatting - format = getFormatForContext( table, value, isSelected, hasFocus, row, column ); - if ( format != null ) - { - currentFormat = format; - } - else - { - currentFormat = defaultFormat; - } - - Color color; - - // allow for context-sensitve foreground color - color = getForegroundForContext( table, value, isSelected, hasFocus, row, column ); - if ( color != null ) - { - super.setForeground( color ); - } - else - { - super.setForeground( defaultForeground ); - } - - // allow for context-sensitve background color - color = getBackgroundForContext( table, value, isSelected, hasFocus, row, column ); - if ( color != null ) - { - super.setBackground( color ); - } - else - { - super.setBackground( defaultBackground ); - } - - // have to call this here because super defaults to table's font - Component result = - super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column ); - // NOTE: DefaultTableCellRenderer returns itself. - - // allow for context-sensitve font - Font font = getFontForContext( table, value, isSelected, hasFocus, row, column ); - if ( font != null ) - { - result.setFont( font ); - } - else - { - result.setFont( defaultFont ); - } - - return result; - - } - -/** -* Override this method to provide a specific format for the -* specific cell to be rendered by this component. Any format -* returned by this method will take precedence of the format -* specified by setFormat(). <br><br> -* This default implementation returns null. -* @return A Format for this cell, or null to rely on the the -* format specified by setFormat(). -*/ - public Format getFormatForContext(JTable table, Object value, - boolean isSelected, boolean hasFocus, int row, int column) - { - return null; - } - -/** -* Override this method to provide a foreground color for the renderer. -* Because the table specifies colors for selected cells, -* these colors will only be used when renderering unselected cells. <br><br> -* This default implementation returns null. -* @return A Color for the foreground of the cell, or null to rely on -* the table's default color scheme. -*/ - public Color getForegroundForContext(JTable table, Object value, - boolean isSelected, boolean hasFocus, int row, int column) - { - return null; - } - -/** -* Override this method to provide a background color for the renderer. -* Because the table specifies colors for selected cells, -* these colors will only be used when renderering unselected cells. <br><br> -* This default implementation returns null. -* @return A Color for the background of the cell, or null to rely on -* the table's default color scheme. -*/ - public Color getBackgroundForContext(JTable table, Object value, - boolean isSelected, boolean hasFocus, int row, int column) - { - return null; - } - -/** -* Override this method to provide a font for the renderer.<br><br> -* This default implementation returns null. -* @return A Font for the cell, or null to rely on the table's default font. -*/ - public Font getFontForContext(JTable table, Object value, - boolean isSelected, boolean hasFocus, int row, int column) - { - return null; - } + return; + } catch (IllegalArgumentException exc) { + // fall back on superclass implementation + } + } + super.setValue(value); + } + + // FIXME: remove this when possible + private static Double ZERO = new Double(0.0); + + /** + * Overridden to call context delegate methods. + */ + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, + int row, int column) { + Format format; + + // allow for context-sensitve formatting + format = getFormatForContext(table, value, isSelected, hasFocus, row, column); + if (format != null) { + currentFormat = format; + } else { + currentFormat = defaultFormat; + } + + Color color; + + // allow for context-sensitve foreground color + color = getForegroundForContext(table, value, isSelected, hasFocus, row, column); + if (color != null) { + super.setForeground(color); + } else { + super.setForeground(defaultForeground); + } + + // allow for context-sensitve background color + color = getBackgroundForContext(table, value, isSelected, hasFocus, row, column); + if (color != null) { + super.setBackground(color); + } else { + super.setBackground(defaultBackground); + } + + // have to call this here because super defaults to table's font + Component result = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + // NOTE: DefaultTableCellRenderer returns itself. + + // allow for context-sensitve font + Font font = getFontForContext(table, value, isSelected, hasFocus, row, column); + if (font != null) { + result.setFont(font); + } else { + result.setFont(defaultFont); + } + + return result; + + } + + /** + * Override this method to provide a specific format for the specific cell to be + * rendered by this component. Any format returned by this method will take + * precedence of the format specified by setFormat(). <br> + * <br> + * This default implementation returns null. + * + * @return A Format for this cell, or null to rely on the the format specified + * by setFormat(). + */ + public Format getFormatForContext(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, + int column) { + return null; + } + + /** + * Override this method to provide a foreground color for the renderer. Because + * the table specifies colors for selected cells, these colors will only be used + * when renderering unselected cells. <br> + * <br> + * This default implementation returns null. + * + * @return A Color for the foreground of the cell, or null to rely on the + * table's default color scheme. + */ + public Color getForegroundForContext(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, + int column) { + return null; + } + + /** + * Override this method to provide a background color for the renderer. Because + * the table specifies colors for selected cells, these colors will only be used + * when renderering unselected cells. <br> + * <br> + * This default implementation returns null. + * + * @return A Color for the background of the cell, or null to rely on the + * table's default color scheme. + */ + public Color getBackgroundForContext(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, + int column) { + return null; + } + + /** + * Override this method to provide a font for the renderer.<br> + * <br> + * This default implementation returns null. + * + * @return A Font for the cell, or null to rely on the table's default font. + */ + public Font getFontForContext(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, + int column) { + return null; + } } - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/IconCellRenderer.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/IconCellRenderer.java index 8320d08..2d9531d 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/IconCellRenderer.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/IconCellRenderer.java @@ -54,792 +54,654 @@ import javax.swing.tree.TreeCellEditor; import javax.swing.tree.TreeCellRenderer; /** -* A cell renderer that displays icons in addition to text, -* and additionally is an editor in case you want to click -* the icon to trigger some kind of action. -* You probably should override both getStringForContext and -* getIconForContext to achieve your desired results. -* To receive mouse clicks, set the same instance of the -* renderer as the editor for the same component.<br><br> -* -* One notable addition is that this class is an action event -* broadcaster. ActionEvents are broadcast when the mouse is -* clicked on the button with an action event containing a -* user-configurable string that defaults to CLICKED. <br><br> -* -* The renderer itself can be used as a JComponent if -* you need something like a JLabel that allows you to click -* on the icon. You will want to call setIcon and setText -* to configure the component since the renderer method would -* not be called. (If you add an instance of the renderer -* to a container, you cannnot use the same instance as an -* editor in a table, tree, or list.) -* -* @author michael@mpowers.net -* @version $Revision: 904 $ -* $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ -*/ -public class IconCellRenderer extends JPanel - implements TableCellRenderer, TableCellEditor, - TreeCellRenderer, TreeCellEditor, ListCellRenderer, - Runnable, ActionListener, MouseListener -{ + * A cell renderer that displays icons in addition to text, and additionally is + * an editor in case you want to click the icon to trigger some kind of action. + * You probably should override both getStringForContext and getIconForContext + * to achieve your desired results. To receive mouse clicks, set the same + * instance of the renderer as the editor for the same component.<br> + * <br> + * + * One notable addition is that this class is an action event broadcaster. + * ActionEvents are broadcast when the mouse is clicked on the button with an + * action event containing a user-configurable string that defaults to CLICKED. + * <br> + * <br> + * + * The renderer itself can be used as a JComponent if you need something like a + * JLabel that allows you to click on the icon. You will want to call setIcon + * and setText to configure the component since the renderer method would not be + * called. (If you add an instance of the renderer to a container, you cannnot + * use the same instance as an editor in a table, tree, or list.) + * + * @author michael@mpowers.net + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ + */ +public class IconCellRenderer extends JPanel implements TableCellRenderer, TableCellEditor, TreeCellRenderer, + TreeCellEditor, ListCellRenderer, Runnable, ActionListener, MouseListener { public static final String CLICKED = "CLICKED"; - + /** - * The panel that is re-used to render everything. - * This is returned by getRendererForContext. - */ - protected JPanel rendererPanel; + * The panel that is re-used to render everything. This is returned by + * getRendererForContext. + */ + protected JPanel rendererPanel; protected JLabel rendererLabel; - protected JButton rendererButton; + protected JButton rendererButton; /** - * The panel that is used to receive mouse clicks. - * It must be a different component from rendererPanel. - * This is returned by getEditorForContext. - */ - protected JPanel editorPanel; + * The panel that is used to receive mouse clicks. It must be a different + * component from rendererPanel. This is returned by getEditorForContext. + */ + protected JPanel editorPanel; protected JLabel editorLabel; - protected JButton editorButton; - + protected JButton editorButton; + private Object lastKnownValue; - private JComponent lastKnownComponent; - + private JComponent lastKnownComponent; + // do as DefaultTableCellRenderer does private Border noFocusBorder; private Border treeFocusBorder; private Color unselectedForeground; private Color unselectedBackground; - + private Vector actionListeners; private String actionCommand; private Vector cellEditorListeners; - - private boolean editable; - private boolean clickable; - - /** - * Default constructor. - */ - public IconCellRenderer() - { - editable = true; - clickable = true; - - noFocusBorder = new EmptyBorder(1, 1, 1, 1); - treeFocusBorder = new LineBorder( - UIManager.getColor("Tree.selectionBorderColor") ); - setActionCommand( CLICKED ); - - rendererPanel = new JPanel(); - rendererPanel.setLayout( new GridBagLayout() ); - - editorPanel = this; - editorPanel.setLayout( new GridBagLayout() ); - - // set up constraints - GridBagConstraints imageConstraints = new GridBagConstraints(); - imageConstraints.gridx = 0; - GridBagConstraints labelConstraints = new GridBagConstraints(); - labelConstraints.fill = GridBagConstraints.HORIZONTAL; - labelConstraints.gridx = 1; - labelConstraints.weightx = 1.0; - labelConstraints.ipadx = 1; - labelConstraints.insets = new Insets( 0, 1, 0, 0 ); // sweat the pixel - - // make the editor panel go away when not in use - // and pass through all mouse events to container - - //this is not very useful since editorLabel and editorButton - //get all of the events - editorPanel.addMouseListener( this ); - + + private boolean editable; + private boolean clickable; + + /** + * Default constructor. + */ + public IconCellRenderer() { + editable = true; + clickable = true; + + noFocusBorder = new EmptyBorder(1, 1, 1, 1); + treeFocusBorder = new LineBorder(UIManager.getColor("Tree.selectionBorderColor")); + setActionCommand(CLICKED); + + rendererPanel = new JPanel(); + rendererPanel.setLayout(new GridBagLayout()); + + editorPanel = this; + editorPanel.setLayout(new GridBagLayout()); + + // set up constraints + GridBagConstraints imageConstraints = new GridBagConstraints(); + imageConstraints.gridx = 0; + GridBagConstraints labelConstraints = new GridBagConstraints(); + labelConstraints.fill = GridBagConstraints.HORIZONTAL; + labelConstraints.gridx = 1; + labelConstraints.weightx = 1.0; + labelConstraints.ipadx = 1; + labelConstraints.insets = new Insets(0, 1, 0, 0); // sweat the pixel + + // make the editor panel go away when not in use + // and pass through all mouse events to container + + // this is not very useful since editorLabel and editorButton + // get all of the events + editorPanel.addMouseListener(this); + rendererLabel = new JLabel(); - rendererLabel.setOpaque( false ); - rendererPanel.add( rendererLabel, labelConstraints ); + rendererLabel.setOpaque(false); + rendererPanel.add(rendererLabel, labelConstraints); editorLabel = new JLabel(); - editorLabel.setText( "" ); // default state - editorLabel.setOpaque( false ); - editorPanel.add( editorLabel, labelConstraints ); + editorLabel.setText(""); // default state + editorLabel.setOpaque(false); + editorPanel.add(editorLabel, labelConstraints); unselectedForeground = rendererLabel.getForeground(); unselectedBackground = rendererLabel.getBackground(); - - rendererButton = new JButton(); - rendererButton.setBorder( null ); - rendererButton.setBorderPainted( false ); - rendererButton.setContentAreaFilled( false ); - rendererButton.setFocusPainted( false ); - rendererButton.setMargin( new Insets( 0, 0, 0, 0 ) ); - rendererPanel.add( rendererButton, imageConstraints ); - - editorButton = new JButton(); - editorButton.setEnabled( clickable ); // default state - editorButton.setIcon( null ); // default state - editorButton.setBorder( null ); - editorButton.setBorderPainted( false ); - editorButton.setContentAreaFilled( false ); - editorButton.setFocusPainted( false ); - editorButton.setMargin( new Insets( 0, 0, 0, 0 ) ); - editorPanel.add( editorButton, imageConstraints ); - - editorButton.addActionListener( this ); - - //add these in order to dispatch the MouseEvents - //to the lastKnownComponent, and proper management of - //DnD operations - editorLabel.addMouseListener( this ); - editorButton.addMouseListener( this ); - } - -/** -* Returns the text string currently displayed in the editor component. -*/ - public String getText() - { - return editorLabel.getText(); - } - -/** -* Sets the text string displayed in the editor component. -* Default is an empty string. -*/ - public void setText( String aString ) - { - editorLabel.setText( aString ); - } -/** -* Returns the icon currently displayed in the editor component. -*/ - public Icon getIcon() - { - return editorButton.getIcon(); - } - -/** -* Sets the icon currently displayed in the editor component. -* Default is null. -*/ - public void setIcon( Icon anIcon ) - { - editorButton.setIcon( anIcon ); - if ( !isClickable() ) - { - editorButton.setDisabledIcon( anIcon ); - } - } + rendererButton = new JButton(); + rendererButton.setBorder(null); + rendererButton.setBorderPainted(false); + rendererButton.setContentAreaFilled(false); + rendererButton.setFocusPainted(false); + rendererButton.setMargin(new Insets(0, 0, 0, 0)); + rendererPanel.add(rendererButton, imageConstraints); + + editorButton = new JButton(); + editorButton.setEnabled(clickable); // default state + editorButton.setIcon(null); // default state + editorButton.setBorder(null); + editorButton.setBorderPainted(false); + editorButton.setContentAreaFilled(false); + editorButton.setFocusPainted(false); + editorButton.setMargin(new Insets(0, 0, 0, 0)); + editorPanel.add(editorButton, imageConstraints); + + editorButton.addActionListener(this); + + // add these in order to dispatch the MouseEvents + // to the lastKnownComponent, and proper management of + // DnD operations + editorLabel.addMouseListener(this); + editorButton.addMouseListener(this); + } -/** -* Returns whether the editor component's label text is editable. -*/ - public boolean isEditable() - { - return editable; - } - -/** -* Sets whether the editor component's label text is editable. -* Default is true. Editable text is not yet implemented. -*/ - public void setEditable( boolean isEditable ) - { - editable = isEditable; - } + /** + * Returns the text string currently displayed in the editor component. + */ + public String getText() { + return editorLabel.getText(); + } -/** -* Returns whether the editor component's icon is clickable. -*/ - public boolean isClickable() - { - return clickable; - } - -/** -* Sets whether the editor component's icon is clickable. -* Default is true. -*/ - public void setClickable( boolean isClickable ) - { - clickable = isClickable; - editorButton.setEnabled( clickable ); - } + /** + * Sets the text string displayed in the editor component. Default is an empty + * string. + */ + public void setText(String aString) { + editorLabel.setText(aString); + } -/** -* Returns the component from getRendererForContext. -*/ - public Component getListCellRendererComponent(JList list, - Object value, - int index, - boolean isSelected, - boolean cellHasFocus) - { - lastKnownComponent = list; - return getRendererForContext( - list, value, index, 0, isSelected, cellHasFocus, false, true ); - } + /** + * Returns the icon currently displayed in the editor component. + */ + public Icon getIcon() { + return editorButton.getIcon(); + } -/** -* Returns the component from getRendererForContext. -*/ - public Component getTableCellRendererComponent(JTable table, Object value, - boolean isSelected, boolean hasFocus, int row, int column) - { - lastKnownComponent = table; - return getRendererForContext( - table, value, row, column, isSelected, hasFocus, false, true ); - } + /** + * Sets the icon currently displayed in the editor component. Default is null. + */ + public void setIcon(Icon anIcon) { + editorButton.setIcon(anIcon); + if (!isClickable()) { + editorButton.setDisabledIcon(anIcon); + } + } -/** -* Returns the component from getRendererForContext. -*/ - public Component getTreeCellRendererComponent(JTree tree, - Object value, - boolean selected, - boolean expanded, - boolean leaf, - int row, - boolean hasFocus) - { - lastKnownComponent = tree; - return getRendererForContext( - tree, value, row, 0, selected, hasFocus, expanded, leaf ); - } - -/** -* Returns getEditorForContext with the same parameters with hasFocus true. -*/ - public Component getTableCellEditorComponent(JTable table, - Object value, boolean isSelected, int row, int column) - { + /** + * Returns whether the editor component's label text is editable. + */ + public boolean isEditable() { + return editable; + } + + /** + * Sets whether the editor component's label text is editable. Default is true. + * Editable text is not yet implemented. + */ + public void setEditable(boolean isEditable) { + editable = isEditable; + } + + /** + * Returns whether the editor component's icon is clickable. + */ + public boolean isClickable() { + return clickable; + } + + /** + * Sets whether the editor component's icon is clickable. Default is true. + */ + public void setClickable(boolean isClickable) { + clickable = isClickable; + editorButton.setEnabled(clickable); + } + + /** + * Returns the component from getRendererForContext. + */ + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, + boolean cellHasFocus) { + lastKnownComponent = list; + return getRendererForContext(list, value, index, 0, isSelected, cellHasFocus, false, true); + } + + /** + * Returns the component from getRendererForContext. + */ + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, + int row, int column) { + lastKnownComponent = table; + return getRendererForContext(table, value, row, column, isSelected, hasFocus, false, true); + } + + /** + * Returns the component from getRendererForContext. + */ + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, + boolean leaf, int row, boolean hasFocus) { + lastKnownComponent = tree; + return getRendererForContext(tree, value, row, 0, selected, hasFocus, expanded, leaf); + } + + /** + * Returns getEditorForContext with the same parameters with hasFocus true. + */ + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { lastKnownValue = value; - lastKnownComponent = table; - return getEditorForContext( - table, value, row, column, isSelected, true, false, true ); + lastKnownComponent = table; + return getEditorForContext(table, value, row, column, isSelected, true, false, true); } -/** -* Returns the component from getEditorForContext with hasFocus true. -*/ - public Component getTreeCellEditorComponent(JTree tree, - Object value, - boolean isSelected, - boolean expanded, - boolean leaf, - int row) - { - - - lastKnownValue = value; - lastKnownComponent = tree; - - return getEditorForContext( - tree, value, row, 0, isSelected, true, expanded, leaf ); - } - -/** -* This default implementation returns a JPanel that is configured by -* calling configureComponentForContext. -* @return An component that is used to render content. -*/ - public Component getRendererForContext( - JComponent container, Object value, - int row, int column, - boolean isSelected, boolean hasFocus, - boolean isExpanded, boolean isLeaf ) - { - - - configureComponentForContext( rendererPanel, rendererButton, rendererLabel, - container, value, row, column, - isSelected, hasFocus, isExpanded, isLeaf ); + /** + * Returns the component from getEditorForContext with hasFocus true. + */ + public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, + boolean leaf, int row) { + + lastKnownValue = value; + lastKnownComponent = tree; + + return getEditorForContext(tree, value, row, 0, isSelected, true, expanded, leaf); + } + + /** + * This default implementation returns a JPanel that is configured by calling + * configureComponentForContext. + * + * @return An component that is used to render content. + */ + public Component getRendererForContext(JComponent container, Object value, int row, int column, boolean isSelected, + boolean hasFocus, boolean isExpanded, boolean isLeaf) { + + configureComponentForContext(rendererPanel, rendererButton, rendererLabel, container, value, row, column, + isSelected, hasFocus, isExpanded, isLeaf); return rendererPanel; - } + } -/** -* This method returns a separate component that should be visually -* identical to the renderer component. We can't simply reuse the -* renderer component because the renderer is still used to paint -* the table while the editor component is displayed. Clicks are -* received on this component. -* This default implementation returns a JPanel that is configured by -* calling configureComponentForContext. -* @return A component used to receive clicks on the cell. -*/ - public Component getEditorForContext( - JComponent container, Object value, - int row, int column, - boolean isSelected, boolean hasFocus, - boolean isExpanded, boolean isLeaf ) - { - configureComponentForContext( editorPanel, editorButton, editorLabel, - container, value, row, column, - true, hasFocus, isExpanded, isLeaf ); // editor should always be selected + /** + * This method returns a separate component that should be visually identical to + * the renderer component. We can't simply reuse the renderer component because + * the renderer is still used to paint the table while the editor component is + * displayed. Clicks are received on this component. This default implementation + * returns a JPanel that is configured by calling configureComponentForContext. + * + * @return A component used to receive clicks on the cell. + */ + public Component getEditorForContext(JComponent container, Object value, int row, int column, boolean isSelected, + boolean hasFocus, boolean isExpanded, boolean isLeaf) { + configureComponentForContext(editorPanel, editorButton, editorLabel, container, value, row, column, true, + hasFocus, isExpanded, isLeaf); // editor should always be selected return editorPanel; - } + } -/** -* Called to configure components -*/ - protected void configureComponentForContext( - JPanel component, JButton iconButton, JLabel label, - JComponent container, Object value, - int row, int column, - boolean isSelected, boolean hasFocus, - boolean isExpanded, boolean isLeaf ) - { - if (hasFocus) - { - if ( container instanceof JTable ) - { - component.setBorder( - UIManager.getBorder("Table.focusCellHighlightBorder") ); - } - else - { - component.setBorder( noFocusBorder ); - } - - if ( container instanceof JTree ) // was: (false) - { - label.setBorder( treeFocusBorder ); - } - else - { - label.setBorder( noFocusBorder ); - } - } - else - { - label.setBorder(noFocusBorder); - component.setBorder(noFocusBorder); - } - - if (isSelected) - { - if ( container instanceof JTree ) - { - label.setOpaque( true ); - label.setForeground(UIManager.getColor("Tree.selectionForeground")); - label.setBackground(UIManager.getColor("Tree.selectionBackground")); - component.setBackground(container.getBackground()); - } - else if ( container instanceof JTable ) - { - label.setOpaque( false ); - label.setForeground( ((JTable)container).getSelectionForeground() ); - component.setBackground(((JTable)container).getSelectionBackground()); - } - else - { - label.setOpaque( false ); - label.setForeground(UIManager.getColor("Table.selectionForeground")); - component.setBackground(UIManager.getColor("Table.selectionBackground")); - } - } - else - { - label.setOpaque( false ); - label.setForeground(container.getForeground()); - component.setBackground(container.getBackground()); - } - - label.setFont(container.getFont()); - - Icon icon = getIconForContext( - container, value, row, column, isSelected, hasFocus, isExpanded, isLeaf ); - iconButton.setIcon( icon ); - if ( !isClickable() ) - { - iconButton.setDisabledIcon( icon ); - } - - String text = getStringForContext( - container, value, row, column, isSelected, hasFocus, isExpanded, isLeaf ); - - if ( ( text == null ) || ( "".equals( text ) ) ) - { - if ( ! label.getText().equals( "" ) ) - label.setText( "" ); + /** + * Called to configure components + */ + protected void configureComponentForContext(JPanel component, JButton iconButton, JLabel label, + JComponent container, Object value, int row, int column, boolean isSelected, boolean hasFocus, + boolean isExpanded, boolean isLeaf) { + if (hasFocus) { + if (container instanceof JTable) { + component.setBorder(UIManager.getBorder("Table.focusCellHighlightBorder")); + } else { + component.setBorder(noFocusBorder); + } + + if (container instanceof JTree) // was: (false) + { + label.setBorder(treeFocusBorder); + } else { + label.setBorder(noFocusBorder); + } + } else { + label.setBorder(noFocusBorder); + component.setBorder(noFocusBorder); } - else - { - if ( ! label.getText().equals( text ) ) - label.setText( text ); + + if (isSelected) { + if (container instanceof JTree) { + label.setOpaque(true); + label.setForeground(UIManager.getColor("Tree.selectionForeground")); + label.setBackground(UIManager.getColor("Tree.selectionBackground")); + component.setBackground(container.getBackground()); + } else if (container instanceof JTable) { + label.setOpaque(false); + label.setForeground(((JTable) container).getSelectionForeground()); + component.setBackground(((JTable) container).getSelectionBackground()); + } else { + label.setOpaque(false); + label.setForeground(UIManager.getColor("Table.selectionForeground")); + component.setBackground(UIManager.getColor("Table.selectionBackground")); + } + } else { + label.setOpaque(false); + label.setForeground(container.getForeground()); + component.setBackground(container.getBackground()); + } + + label.setFont(container.getFont()); + + Icon icon = getIconForContext(container, value, row, column, isSelected, hasFocus, isExpanded, isLeaf); + iconButton.setIcon(icon); + if (!isClickable()) { + iconButton.setDisabledIcon(icon); + } + + String text = getStringForContext(container, value, row, column, isSelected, hasFocus, isExpanded, isLeaf); + + if ((text == null) || ("".equals(text))) { + if (!label.getText().equals("")) + label.setText(""); + } else { + if (!label.getText().equals(text)) + label.setText(text); } } - -/** -* Override this method to provide an icon for the renderer. -* This default implementation returns null. -* @return An icon to be displayed in the cell, or null to omit the -* icon from the cell. -*/ - public Icon getIconForContext( - JComponent container, Object value, - int row, int column, - boolean isSelected, boolean hasFocus, - boolean isExpanded, boolean isLeaf ) - { - return null; - } -/** -* Override this method to provide a string for the renderer. -* This default implementation returns toString on the value parameter, -* or null if the value is null. -* @return A string to be displayed in the cell. -*/ - public String getStringForContext( - JComponent container, Object value, - int row, int column, - boolean isSelected, boolean hasFocus, - boolean isExpanded, boolean isLeaf ) - { - if ( value == null ) return null; - return value.toString(); - } - - /** - * Adds the specified listener to the list of listeners - * to be notified when the button receives a click. - */ - public void addActionListener( ActionListener aListener ) - { - if ( actionListeners == null ) - { - actionListeners = new Vector( 2 ); + /** + * Override this method to provide an icon for the renderer. This default + * implementation returns null. + * + * @return An icon to be displayed in the cell, or null to omit the icon from + * the cell. + */ + public Icon getIconForContext(JComponent container, Object value, int row, int column, boolean isSelected, + boolean hasFocus, boolean isExpanded, boolean isLeaf) { + return null; + } + + /** + * Override this method to provide a string for the renderer. This default + * implementation returns toString on the value parameter, or null if the value + * is null. + * + * @return A string to be displayed in the cell. + */ + public String getStringForContext(JComponent container, Object value, int row, int column, boolean isSelected, + boolean hasFocus, boolean isExpanded, boolean isLeaf) { + if (value == null) + return null; + return value.toString(); + } + + /** + * Adds the specified listener to the list of listeners to be notified when the + * button receives a click. + */ + public void addActionListener(ActionListener aListener) { + if (actionListeners == null) { + actionListeners = new Vector(2); } - actionListeners.add( aListener ); + actionListeners.add(aListener); } - + /** - * Removes the specified listener from the list of listeners - * to be notified when the button receives a click. - */ - public void removeActionListener( ActionListener aListener ) - { - actionListeners.remove( aListener ); + * Removes the specified listener from the list of listeners to be notified when + * the button receives a click. + */ + public void removeActionListener(ActionListener aListener) { + actionListeners.remove(aListener); } - + /** - * Broadcasts the specified action event to all listeners. - */ - protected void fireActionEvent( ActionEvent anActionEvent ) - { - if ( actionListeners == null ) return; + * Broadcasts the specified action event to all listeners. + */ + protected void fireActionEvent(ActionEvent anActionEvent) { + if (actionListeners == null) + return; // vector's enumeration is not fail-fast Enumeration e = actionListeners.elements(); - while ( e.hasMoreElements() ) - { - ((ActionListener)e.nextElement()).actionPerformed( anActionEvent ); + while (e.hasMoreElements()) { + ((ActionListener) e.nextElement()).actionPerformed(anActionEvent); } } - + /** - * Returns the action command broadcast when this icon - * receives a click. Defaults to CLICKED. - */ - public String getActionCommand() - { + * Returns the action command broadcast when this icon receives a click. + * Defaults to CLICKED. + */ + public String getActionCommand() { return actionCommand; } /** - * Sets the action command broadcast when this table - * receives a double click. - */ - public void setActionCommand( String anActionCommand ) - { - actionCommand = anActionCommand; + * Sets the action command broadcast when this table receives a double click. + */ + public void setActionCommand(String anActionCommand) { + actionCommand = anActionCommand; } - + // interface CellEditor /** - * Returns lastKnownValue, although this should not be called. - */ - public Object getCellEditorValue() - { + * Returns lastKnownValue, although this should not be called. + */ + public Object getCellEditorValue() { return lastKnownValue; } - + /** - * Returns true. - */ - public boolean isCellEditable(EventObject anEvent) - { - return true; + * Returns true. + */ + public boolean isCellEditable(EventObject anEvent) { + return true; } - + /** - * Returns true. - */ - public boolean shouldSelectCell(EventObject anEvent) - { + * Returns true. + */ + public boolean shouldSelectCell(EventObject anEvent) { return true; } - + /** - * Fires an editing stopped event and returns true. - */ - public boolean stopCellEditing() - { - ChangeEvent event = new ChangeEvent( this ); - if ( cellEditorListeners != null ) - { + * Fires an editing stopped event and returns true. + */ + public boolean stopCellEditing() { + ChangeEvent event = new ChangeEvent(this); + if (cellEditorListeners != null) { // vector's enumeration is not fail-fast Enumeration e = cellEditorListeners.elements(); - while ( e.hasMoreElements() ) - { - // broadcast editing cancelled since no value is edited - ((CellEditorListener)e.nextElement()).editingCanceled( event ); + while (e.hasMoreElements()) { + // broadcast editing cancelled since no value is edited + ((CellEditorListener) e.nextElement()).editingCanceled(event); } } - lastKnownComponent = null; + lastKnownComponent = null; return true; } - - /** - * Fires an editing cancelled event and returns true. - */ - public void cancelCellEditing() - { - //HACK: cancelCellEditing() causes for the dragGesture - //to be NOT recognized AT ALL since on the next MOUSE_PRESSED - //the cell editor first needs to startEditing() [if in the tree - //the CellEditorListener is a BasicTreeUI class] - //(before the drag gesture event can be recognized). - //Also the lastKnownComponent should not be set to null, - //none of the mouse events won't dispathced to the lastKnownComponent - //in that case. - - //Not calling it at all does seem to fix it, but what are the - //consequences??? - //Trying to workaround this might solve it, but it introduces having - //an extra listener (a MouseMotionListnener), which might be wasteful - //(i.e. only if a Mouse_dragged event has been initiated, but DragGesture - //hasn't been recognized, postpone calling this till finish the DnD event) - //But what if do DnD and not exited ??? The mouseExited() is not called - //anyway until the DnD event is done. - - ChangeEvent event = new ChangeEvent( this ); - if ( cellEditorListeners == null ) return; + + /** + * Fires an editing cancelled event and returns true. + */ + public void cancelCellEditing() { + // HACK: cancelCellEditing() causes for the dragGesture + // to be NOT recognized AT ALL since on the next MOUSE_PRESSED + // the cell editor first needs to startEditing() [if in the tree + // the CellEditorListener is a BasicTreeUI class] + // (before the drag gesture event can be recognized). + // Also the lastKnownComponent should not be set to null, + // none of the mouse events won't dispathced to the lastKnownComponent + // in that case. + + // Not calling it at all does seem to fix it, but what are the + // consequences??? + // Trying to workaround this might solve it, but it introduces having + // an extra listener (a MouseMotionListnener), which might be wasteful + // (i.e. only if a Mouse_dragged event has been initiated, but DragGesture + // hasn't been recognized, postpone calling this till finish the DnD event) + // But what if do DnD and not exited ??? The mouseExited() is not called + // anyway until the DnD event is done. + + ChangeEvent event = new ChangeEvent(this); + if (cellEditorListeners == null) + return; // vector's enumeration is not fail-fast Enumeration e = cellEditorListeners.elements(); - - while ( e.hasMoreElements() ) - { - ((CellEditorListener)e.nextElement()).editingCanceled( event ); - } - - //DO not nullify this - lastKnownComponent = null; - } - - /** - * Adds the specified listener to the list of listeners - * to be notified when the table receives a double click. - */ - public void addCellEditorListener( CellEditorListener aListener ) - { - if ( cellEditorListeners == null ) - { - cellEditorListeners = new Vector( 2 ); + + while (e.hasMoreElements()) { + ((CellEditorListener) e.nextElement()).editingCanceled(event); + } + + // DO not nullify this + lastKnownComponent = null; + } + + /** + * Adds the specified listener to the list of listeners to be notified when the + * table receives a double click. + */ + public void addCellEditorListener(CellEditorListener aListener) { + if (cellEditorListeners == null) { + cellEditorListeners = new Vector(2); } - cellEditorListeners.add( aListener ); + cellEditorListeners.add(aListener); } - + /** - * Removes the specified listener from the list of listeners - * to be notified when the table receives a double click. - */ - public void removeCellEditorListener( CellEditorListener aListener ) - { - cellEditorListeners.remove( aListener ); + * Removes the specified listener from the list of listeners to be notified when + * the table receives a double click. + */ + public void removeCellEditorListener(CellEditorListener aListener) { + cellEditorListeners.remove(aListener); } - + // interface ActionListener - - /** - * Puts ourself on the end of the event queue for - * firing our action event to all listeners. - */ - public void actionPerformed( ActionEvent evt ) - { - //commented out in order NOT to set lastKnownComponent to null, since - //if this object is inside a table or tree, relying on getCellEditorValue() - //to return the currently edited object - //cancelCellEditing(); - - SwingUtilities.invokeLater( this ); - } - + + /** + * Puts ourself on the end of the event queue for firing our action event to all + * listeners. + */ + public void actionPerformed(ActionEvent evt) { + // commented out in order NOT to set lastKnownComponent to null, since + // if this object is inside a table or tree, relying on getCellEditorValue() + // to return the currently edited object + // cancelCellEditing(); + + SwingUtilities.invokeLater(this); + } + // interface Runnable - + /** - * Fires the action event to all listeners. - * This is triggered by a click on the icon. - */ - public void run() - { - fireActionEvent( new ActionEvent( this, 0, getActionCommand() ) ); + * Fires the action event to all listeners. This is triggered by a click on the + * icon. + */ + public void run() { + fireActionEvent(new ActionEvent(this, 0, getActionCommand())); } // interface MouseListener - - /** - * Passes through editor mouse clicks to last known component. - * (left click only) - */ - public void mouseClicked(MouseEvent e) - { - if(lastKnownComponent != null){ - Object source = e.getSource(); - if(source != null) - { - if(source == editorPanel) - { - lastKnownComponent.dispatchEvent( - SwingUtilities.convertMouseEvent( - editorPanel, e, lastKnownComponent ) ); - - } - else if(source == editorLabel) - { - lastKnownComponent.dispatchEvent( - SwingUtilities.convertMouseEvent( - editorLabel, e, lastKnownComponent ) ); - } - - else if(source == editorButton) - { - lastKnownComponent.dispatchEvent( - SwingUtilities.convertMouseEvent( - editorButton, e, lastKnownComponent ) ); - } - } - } - } - - /** - * Passes through editor right-mouse (popup trigger) mouse events to last known component. - * Needed for possible displaying of popup menus on right click - */ - public void mousePressed(MouseEvent e) - { - if ( e.isPopupTrigger() ) - { - if(lastKnownComponent != null) - { - Object source = e.getSource(); - if(source != null) - { - if(source == editorPanel) - { - lastKnownComponent.dispatchEvent( - SwingUtilities.convertMouseEvent( - editorPanel, e, lastKnownComponent ) ); - } - else if(source == editorLabel) - { - lastKnownComponent.dispatchEvent( - SwingUtilities.convertMouseEvent( - editorLabel, e, lastKnownComponent ) ); - } - - else if(source == editorButton) - { - lastKnownComponent.dispatchEvent( - SwingUtilities.convertMouseEvent( - editorButton, e, lastKnownComponent ) ); - } - } - } - } - } - - /** - * Does nothing. - */ - public void mouseReleased(MouseEvent e) - { - if ( e.isPopupTrigger() ) - { - if(lastKnownComponent != null){ - - Object source = e.getSource(); - if(source != null) - { - if(source == editorPanel) - { - lastKnownComponent.dispatchEvent( - SwingUtilities.convertMouseEvent( - editorPanel, e, lastKnownComponent ) ); - } - - else if(source == editorLabel) - { - lastKnownComponent.dispatchEvent( - SwingUtilities.convertMouseEvent( - editorLabel, e, lastKnownComponent ) ); - } - - else if(source == editorButton) - { - lastKnownComponent.dispatchEvent( - SwingUtilities.convertMouseEvent( - editorButton, e, lastKnownComponent ) ); - } - } - } - } - } - - /** - * Does nothing. - */ - public void mouseEntered(MouseEvent e) - { - } - - /** - * Cancels cell editing. - */ - public void mouseExited(MouseEvent e) - { - Object source = e.getSource(); - if(source != null && source instanceof JComponent){ - //need to convert the Point from the source's coordinate system to editorPanel's coordinate system. - //(note that simple editorPanel.contains(e.getPoint()) fails if source is editorButton) - - Point convertedPoint = SwingUtilities.convertPoint((JComponent) source, e.getPoint(), editorPanel); - - //check if exited from editorButton, but still inside the editorPanel (works for editorLabel as well) - if(!editorPanel.contains(convertedPoint)){ - - //This was getting called before, but it interfers with the DnD operation - cancelCellEditing(); - } - } - } - - /* This might be redundant - public void cleanUp(){ - - //since cancelCellEditing() was never called call it now - cancelCellEditing(); - stopCellEditing(); - - editorButton.removeActionListener( this ); - editorPanel.removeMouseListener( this ); - editorLabel.removeMouseListener( this ); - editorButton.removeMouseListener( this ); - lastKnownComponent = null; - lastKnownValue = null; - } - */ + + /** + * Passes through editor mouse clicks to last known component. (left click only) + */ + public void mouseClicked(MouseEvent e) { + if (lastKnownComponent != null) { + Object source = e.getSource(); + if (source != null) { + if (source == editorPanel) { + lastKnownComponent + .dispatchEvent(SwingUtilities.convertMouseEvent(editorPanel, e, lastKnownComponent)); + + } else if (source == editorLabel) { + lastKnownComponent + .dispatchEvent(SwingUtilities.convertMouseEvent(editorLabel, e, lastKnownComponent)); + } + + else if (source == editorButton) { + lastKnownComponent + .dispatchEvent(SwingUtilities.convertMouseEvent(editorButton, e, lastKnownComponent)); + } + } + } + } + + /** + * Passes through editor right-mouse (popup trigger) mouse events to last known + * component. Needed for possible displaying of popup menus on right click + */ + public void mousePressed(MouseEvent e) { + if (e.isPopupTrigger()) { + if (lastKnownComponent != null) { + Object source = e.getSource(); + if (source != null) { + if (source == editorPanel) { + lastKnownComponent + .dispatchEvent(SwingUtilities.convertMouseEvent(editorPanel, e, lastKnownComponent)); + } else if (source == editorLabel) { + lastKnownComponent + .dispatchEvent(SwingUtilities.convertMouseEvent(editorLabel, e, lastKnownComponent)); + } + + else if (source == editorButton) { + lastKnownComponent + .dispatchEvent(SwingUtilities.convertMouseEvent(editorButton, e, lastKnownComponent)); + } + } + } + } + } + + /** + * Does nothing. + */ + public void mouseReleased(MouseEvent e) { + if (e.isPopupTrigger()) { + if (lastKnownComponent != null) { + + Object source = e.getSource(); + if (source != null) { + if (source == editorPanel) { + lastKnownComponent + .dispatchEvent(SwingUtilities.convertMouseEvent(editorPanel, e, lastKnownComponent)); + } + + else if (source == editorLabel) { + lastKnownComponent + .dispatchEvent(SwingUtilities.convertMouseEvent(editorLabel, e, lastKnownComponent)); + } + + else if (source == editorButton) { + lastKnownComponent + .dispatchEvent(SwingUtilities.convertMouseEvent(editorButton, e, lastKnownComponent)); + } + } + } + } + } + + /** + * Does nothing. + */ + public void mouseEntered(MouseEvent e) { + } + + /** + * Cancels cell editing. + */ + public void mouseExited(MouseEvent e) { + Object source = e.getSource(); + if (source != null && source instanceof JComponent) { + // need to convert the Point from the source's coordinate system to + // editorPanel's coordinate system. + // (note that simple editorPanel.contains(e.getPoint()) fails if source is + // editorButton) + + Point convertedPoint = SwingUtilities.convertPoint((JComponent) source, e.getPoint(), editorPanel); + + // check if exited from editorButton, but still inside the editorPanel (works + // for editorLabel as well) + if (!editorPanel.contains(convertedPoint)) { + + // This was getting called before, but it interfers with the DnD operation + cancelCellEditing(); + } + } + } + + /* + * This might be redundant public void cleanUp(){ + * + * //since cancelCellEditing() was never called call it now cancelCellEditing(); + * stopCellEditing(); + * + * editorButton.removeActionListener( this ); editorPanel.removeMouseListener( + * this ); editorLabel.removeMouseListener( this ); + * editorButton.removeMouseListener( this ); lastKnownComponent = null; + * lastKnownValue = null; } + */ } diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ImagePanel.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ImagePanel.java index cdaa218..4fa8e04 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ImagePanel.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ImagePanel.java @@ -26,79 +26,58 @@ import java.awt.image.ImageObserver; import javax.swing.JPanel; /** -* A JPanel that renders an image, tiling as necessary to -* fill the panel. -* The preferred size of the panel is the size of the image -* and will change until the image is fully loaded, so using -* a media tracker is recommended. -* -* @author michael@mpowers.net -* @version $Revision: 904 $ -*/ -public class ImagePanel extends JPanel implements ImageObserver -{ + * A JPanel that renders an image, tiling as necessary to fill the panel. The + * preferred size of the panel is the size of the image and will change until + * the image is fully loaded, so using a media tracker is recommended. + * + * @author michael@mpowers.net + * @version $Revision: 904 $ + */ +public class ImagePanel extends JPanel implements ImageObserver { protected Image image; protected int imageWidth, imageHeight; - - public ImagePanel() - { - this( null ); - } - - public ImagePanel( Image anImage ) - { - image = anImage; - if ( anImage != null ) - { - prepareImage( image, this ); + + public ImagePanel() { + this(null); + } + + public ImagePanel(Image anImage) { + image = anImage; + if (anImage != null) { + prepareImage(image, this); // these may return -1 - imageWidth = image.getWidth( this ); - imageHeight = image.getHeight( this ); - } - else - { + imageWidth = image.getWidth(this); + imageHeight = image.getHeight(this); + } else { imageWidth = 0; imageHeight = 0; } } - - protected void paintComponent(Graphics g) - { - if ( ( image != null ) && ( imageWidth > 0 ) && ( imageHeight > 0 ) ) - { + + protected void paintComponent(Graphics g) { + if ((image != null) && (imageWidth > 0) && (imageHeight > 0)) { int width = getWidth(); int height = getHeight(); - - for ( int x = 0; x < width; x += imageWidth ) - { - for ( int y = 0; y < height; y += imageHeight ) - { - g.drawImage( image, x, y, - imageWidth, imageHeight, - getBackground(), this ); + + for (int x = 0; x < width; x += imageWidth) { + for (int y = 0; y < height; y += imageHeight) { + g.drawImage(image, x, y, imageWidth, imageHeight, getBackground(), this); } } } } - - public boolean imageUpdate(Image img, - int infoflags, - int x, - int y, - int width, - int height) - { + + public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { imageWidth = width; imageHeight = height; - setPreferredSize( new Dimension( width, height ) ); - revalidate(); + setPreferredSize(new Dimension(width, height)); + revalidate(); repaint(); - - if ( ( infoflags & ImageObserver.ALLBITS ) == ImageObserver.ALLBITS ) - { - return false; + + if ((infoflags & ImageObserver.ALLBITS) == ImageObserver.ALLBITS) { + return false; } return true; } - + } diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/InfoPanel.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/InfoPanel.java index 55c1e36..a2c0182 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/InfoPanel.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/InfoPanel.java @@ -49,1645 +49,1521 @@ import javax.swing.JTextField; import javax.swing.SwingConstants; /** -* InfoPanel uses labels and textfields (or any other component - see below) -* to display a list of keys and values in a well-aligned and consistent manner, -* conforming to alignment and pixel spacing in the java look and feel -* <a href="http://java.sun.com/products/jlf/dg/higg.htm#55417">design guidelines</a>. -* <BR><BR> -* -* Each key is displayed in a label to the left of the component that contains -* the corresponding value. Each row is displayed starting at the top of the -* component's available area. Each row's height is the maximum preferred -* height of its components and the field itself gets as much of the width as -* it can, dependent on the length of the longest label. <BR><BR> -* -* The values in the fields can be editable, and the -* current value can be retrieved using the key - for this reason, unique keys -* are recommended. <BR><BR> -* -* As a convenience, push buttons may be placed across the -* bottom of the panel in a manner similar to ButtonPanel. <BR><BR> -* -* The panel forwards any ActionEvents generated by the components and -* buttons on it to all registered listeners. <BR><BR> -* -* Optionally, any component can be used instead of a textfield. -* However, <code>get/setValueForKey()</code> and <code>get/setEditable()</code> -* may not work for those components. Use <code>getComponentForKey()</code> to -* access them instead. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -* $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ -*/ -public class InfoPanel extends JPanel implements ActionListener -{ -/** -* Special label for an empty pair - a label and component -* that take up space but are hidden from view. This might -* be useful for achieving certain layouts. -*/ - public static final String HIDDEN = "(hidden)"; - - /** Cache for the introspectComponent method */ - private static Map _method_cache = - Collections.synchronizedMap( new HashMap(30) ); - - protected Container listContainer = null; - protected int hgap; // set in constructor - protected int vgap; // set in constructor - protected int margin; // set in constructor - protected int columns; // set in constructor - protected List fields = null; - protected List labels = null; - protected List fieldSpacers = null; - protected ButtonPanel buttonPanel = null; - protected boolean isEditable = true; - protected String prefix; - protected String postfix; + * InfoPanel uses labels and textfields (or any other component - see below) to + * display a list of keys and values in a well-aligned and consistent manner, + * conforming to alignment and pixel spacing in the java look and feel + * <a href="http://java.sun.com/products/jlf/dg/higg.htm#55417">design + * guidelines</a>. <BR> + * <BR> + * + * Each key is displayed in a label to the left of the component that contains + * the corresponding value. Each row is displayed starting at the top of the + * component's available area. Each row's height is the maximum preferred height + * of its components and the field itself gets as much of the width as it can, + * dependent on the length of the longest label. <BR> + * <BR> + * + * The values in the fields can be editable, and the current value can be + * retrieved using the key - for this reason, unique keys are recommended. <BR> + * <BR> + * + * As a convenience, push buttons may be placed across the bottom of the panel + * in a manner similar to ButtonPanel. <BR> + * <BR> + * + * The panel forwards any ActionEvents generated by the components and buttons + * on it to all registered listeners. <BR> + * <BR> + * + * Optionally, any component can be used instead of a textfield. However, + * <code>get/setValueForKey()</code> and <code>get/setEditable()</code> may not + * work for those components. Use <code>getComponentForKey()</code> to access + * them instead. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ + */ +public class InfoPanel extends JPanel implements ActionListener { + /** + * Special label for an empty pair - a label and component that take up space + * but are hidden from view. This might be useful for achieving certain layouts. + */ + public static final String HIDDEN = "(hidden)"; + + /** Cache for the introspectComponent method */ + private static Map _method_cache = Collections.synchronizedMap(new HashMap(30)); + + protected Container listContainer = null; + protected int hgap; // set in constructor + protected int vgap; // set in constructor + protected int margin; // set in constructor + protected int columns; // set in constructor + protected List fields = null; + protected List labels = null; + protected List fieldSpacers = null; + protected ButtonPanel buttonPanel = null; + protected boolean isEditable = true; + protected String prefix; + protected String postfix; protected int labelAnchor; protected int labelAlign; // protected Component marginStrut = null; - // for action multicasting - protected ActionListener actionListener = null; - -/** -* Constructs an empty InfoPanel. -*/ - public InfoPanel() - { - hgap = 12; // per java l&f guidelines - vgap = 6; // java l&f says 11 - columns = 1; // default columns - margin = 0; // default margin: none - prefix = ""; // default prefix: none - postfix = ":"; // per java l&f guidelines - fields = new ArrayList(); - labels = new ArrayList(); - labelAnchor = GridBagConstraints.NORTHWEST; - // per java l&f guidelines (CENTER is nicer) - labelAlign = SwingConstants.LEFT; - // per java l&f guidelines - - doInitialLayout(); - } + // for action multicasting + protected ActionListener actionListener = null; + + /** + * Constructs an empty InfoPanel. + */ + public InfoPanel() { + hgap = 12; // per java l&f guidelines + vgap = 6; // java l&f says 11 + columns = 1; // default columns + margin = 0; // default margin: none + prefix = ""; // default prefix: none + postfix = ":"; // per java l&f guidelines + fields = new ArrayList(); + labels = new ArrayList(); + labelAnchor = GridBagConstraints.NORTHWEST; + // per java l&f guidelines (CENTER is nicer) + labelAlign = SwingConstants.LEFT; + // per java l&f guidelines + + doInitialLayout(); + } -/** -* Constructs an InfoPanel with the specified labels -* each paired with a blank textfield. -* @param labelArray An Array containing the labels in the -* order in which they should appear from top to bottom. -* A null value produces an empty panel. -*/ - public InfoPanel( String[] labelArray ) - { - this(); - setLabels( labelArray ); - } + /** + * Constructs an InfoPanel with the specified labels each paired with a blank + * textfield. + * + * @param labelArray An Array containing the labels in the order in which they + * should appear from top to bottom. A null value produces an + * empty panel. + */ + public InfoPanel(String[] labelArray) { + this(); + setLabels(labelArray); + } -/** -* Creates a set of labels and empty textfields after first -* clearing all existing components on the panel. -* @param labelArray An Array containing the labels in the order -* in which they should appear from top to bottom. A null -* value will clear the panel. -*/ - public void setLabels( String[] labelArray ) - { - removeAll(); - if ( labelArray == null ) return; // null clears panel - for ( int i = 0; i < labelArray.length; i++ ) - { - addPair( labelArray[i], new JTextField() ); - } - } + /** + * Creates a set of labels and empty textfields after first clearing all + * existing components on the panel. + * + * @param labelArray An Array containing the labels in the order in which they + * should appear from top to bottom. A null value will clear + * the panel. + */ + public void setLabels(String[] labelArray) { + removeAll(); + if (labelArray == null) + return; // null clears panel + for (int i = 0; i < labelArray.length; i++) { + addPair(labelArray[i], new JTextField()); + } + } -/** -* Retrieves the labls for the components on the panel -* in the order in which they are displayed from top WIDTH bottom. -* These are the keys used to reference values or to reference -* the components directly. -* @return An Array of Strings containing the labels. -*/ - public String[] getLabels() - { - int length = fields.size(); - String[] labelArray = new String[ length ]; - for ( int i = 0; i < length; i++ ) - { - labelArray[i] = ((Component)fields.get(i)).getName(); - } - return labelArray; - } - -/** -* Retrieves the constant used to anchor the labels in place. -* The default value is GridBagConstraints.NORTHWEST. -*/ - public int getLabelAnchor() - { + /** + * Retrieves the labls for the components on the panel in the order in which + * they are displayed from top WIDTH bottom. These are the keys used to + * reference values or to reference the components directly. + * + * @return An Array of Strings containing the labels. + */ + public String[] getLabels() { + int length = fields.size(); + String[] labelArray = new String[length]; + for (int i = 0; i < length; i++) { + labelArray[i] = ((Component) fields.get(i)).getName(); + } + return labelArray; + } + + /** + * Retrieves the constant used to anchor the labels in place. The default value + * is GridBagConstraints.NORTHWEST. + */ + public int getLabelAnchor() { return labelAnchor; } -/** -* Sets the constant used to anchor the labels in place -* and reflows the layout. -* @param anAnchorConstant An anchor constant from -* GridBagConstraints. -*/ - public void setLabelAnchor( int anAnchorConstant ) - { - labelAnchor = anAnchorConstant; + /** + * Sets the constant used to anchor the labels in place and reflows the layout. + * + * @param anAnchorConstant An anchor constant from GridBagConstraints. + */ + public void setLabelAnchor(int anAnchorConstant) { + labelAnchor = anAnchorConstant; updateLabels(); } -/** -* Retrieves the constant used to align the labels in place. -* The default value is GridBagConstraints.CENTER. -*/ - public int getLabelAlignment() - { + /** + * Retrieves the constant used to align the labels in place. The default value + * is GridBagConstraints.CENTER. + */ + public int getLabelAlignment() { return labelAlign; } -/** -* Sets the constant used to align the labels in place -* and reflows the layout. -* @param anAlignmentConstant LEFT, CENTER, or RIGHT constants -* from SwingUtilities. -*/ - public void setLabelAlignment( int anAlignmentConstant ) - { - labelAlign = anAlignmentConstant; + /** + * Sets the constant used to align the labels in place and reflows the layout. + * + * @param anAlignmentConstant LEFT, CENTER, or RIGHT constants from + * SwingUtilities. + */ + public void setLabelAlignment(int anAlignmentConstant) { + labelAlign = anAlignmentConstant; updateLabels(); } - -/** -* Factory method for creating panel spacers. -* This implementation returns a JPanel with -* opaque set to false. Override to customize. -*/ - public JPanel createPanel() - { - JPanel result = new JPanel(); - result.setOpaque( false ); - return result; - } -/** -* This method is responsible for the initial layout of the panel. -* All labels and textfields will later added to listContainer. -* This method is responsible for initializing listContainer. -*/ - protected void doInitialLayout() - { - listContainer = createPanel(); - listContainer.setLayout( new BetterGridBagLayout() ); - this.setLayout( new BorderLayout() ); - this.add( listContainer, BorderLayout.NORTH ); + /** + * Factory method for creating panel spacers. This implementation returns a + * JPanel with opaque set to false. Override to customize. + */ + public JPanel createPanel() { + JPanel result = new JPanel(); + result.setOpaque(false); + return result; + } - //listContainer.setBackground( Color.blue ); // useful for testing - //this.setBackground( Color.red ); - } + /** + * This method is responsible for the initial layout of the panel. All labels + * and textfields will later added to listContainer. This method is responsible + * for initializing listContainer. + */ + protected void doInitialLayout() { + listContainer = createPanel(); + listContainer.setLayout(new BetterGridBagLayout()); + this.setLayout(new BorderLayout()); + this.add(listContainer, BorderLayout.NORTH); + + // listContainer.setBackground( Color.blue ); // useful for testing + // this.setBackground( Color.red ); + } -/** -* Changes the horizontal spacing between the label and the components in the panel. -* Note: Assumes listContainer uses a GridBagLayout. -* @param newHgap the new spacing, in pixels. May not be negative. -*/ - public void setHgap( int newHgap ) - { - if ( newHgap < 0 ) return; // may not be negative - this.hgap = newHgap; - updateGaps(); - this.revalidate(); - this.repaint(); + /** + * Changes the horizontal spacing between the label and the components in the + * panel. Note: Assumes listContainer uses a GridBagLayout. + * + * @param newHgap the new spacing, in pixels. May not be negative. + */ + public void setHgap(int newHgap) { + if (newHgap < 0) + return; // may not be negative + this.hgap = newHgap; + updateGaps(); + this.revalidate(); + this.repaint(); - } + } -/** -* Gets the current horizontal spacing between components. -* @return the current horizontal spacing, in pixels. -*/ - public int getHgap() - { - return this.hgap; - } + /** + * Gets the current horizontal spacing between components. + * + * @return the current horizontal spacing, in pixels. + */ + public int getHgap() { + return this.hgap; + } -/** -* Changes the vertical spacing between components in the panel. -* Note: Assumes listContainer uses a GridBagLayout. -* @param newVgap the new spacing, in pixels. May not be negative. -*/ - public void setVgap( int newVgap ) - { - if ( newVgap < 0 ) return; // may not be negative - this.vgap = newVgap; - updateGaps(); - this.revalidate(); - this.repaint(); + /** + * Changes the vertical spacing between components in the panel. Note: Assumes + * listContainer uses a GridBagLayout. + * + * @param newVgap the new spacing, in pixels. May not be negative. + */ + public void setVgap(int newVgap) { + if (newVgap < 0) + return; // may not be negative + this.vgap = newVgap; + updateGaps(); + this.revalidate(); + this.repaint(); - } + } -/** -* Gets the current vertical spacing between components. -* @return the current vertical spacing, in pixels. -*/ - public int getVgap() - { - return this.vgap; - } + /** + * Gets the current vertical spacing between components. + * + * @return the current vertical spacing, in pixels. + */ + public int getVgap() { + return this.vgap; + } -/** -* Sets the minimum width for the labels column. -* This left margin will grow if one of the labels -* is wider than this value. -* Note: assumes GridBagLayout. -* @param newMargin the new minimum margin in pixels. May not be negative. -*/ - public void setMargin( int newMargin ) - { - if ( newMargin < 0 ) return; // may not be negative - this.margin = newMargin; - - if ( listContainer.getLayout() instanceof GridBagLayout ) - { - GridBagLayout gridBag = (GridBagLayout) listContainer.getLayout(); - GridBagConstraints constraints = null; - Component c = null; - int count = listContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = listContainer.getComponent( i ); - constraints = gridBag.getConstraints( c ); - if ( constraints.gridy == 0 && constraints.gridx % 2 == 0 ) - { // if this is a label spacer - // replace it with an appropriately sized box - listContainer.remove( c ); - listContainer.add( Box.createHorizontalStrut( this.margin ), constraints ); - } - } - } - - this.revalidate(); - this.repaint(); - - } + /** + * Sets the minimum width for the labels column. This left margin will grow if + * one of the labels is wider than this value. Note: assumes GridBagLayout. + * + * @param newMargin the new minimum margin in pixels. May not be negative. + */ + public void setMargin(int newMargin) { + if (newMargin < 0) + return; // may not be negative + this.margin = newMargin; + + if (listContainer.getLayout() instanceof GridBagLayout) { + GridBagLayout gridBag = (GridBagLayout) listContainer.getLayout(); + GridBagConstraints constraints = null; + Component c = null; + int count = listContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = listContainer.getComponent(i); + constraints = gridBag.getConstraints(c); + if (constraints.gridy == 0 && constraints.gridx % 2 == 0) { // if this is a label spacer + // replace it with an appropriately sized + // box + listContainer.remove(c); + listContainer.add(Box.createHorizontalStrut(this.margin), constraints); + } + } + } -/** -* Gets the current minimum margin for the labels column. -* @return the current minimum margin in pixels. -*/ - public int getMargin() - { - return this.margin; - } + this.revalidate(); + this.repaint(); -/** -* Sets the number of columns for the panel. -* Label/Component pairs will start from the top left -* and fill in to the right before wrapping to the -* next row. The default number of columns is one. -* Note: assumes GridBagLayout. -* @param newColumns the new number of columns. May not be less than one. -*/ - public void setColumns( int newColumns ) - { - if ( newColumns < 1 ) return; // may not be less than one. - int oldColumns = this.columns; - this.columns = newColumns; - - if ( listContainer.getLayout() instanceof GridBagLayout ) - { - GridBagLayout gridBag = (GridBagLayout) listContainer.getLayout(); - int count = listContainer.getComponentCount(); - Component[] components = listContainer.getComponents(); - GridBagConstraints[] constraints = new GridBagConstraints[ components.length ]; - for ( int i = 0; i < components.length; i++ ) - { - constraints[i] = gridBag.getConstraints( components[i] ); - } - listContainer.removeAll(); - for ( int i = 0; i < components.length; i++ ) - { - if ( constraints[i].gridy != 0 ) - { // ignore first row which is reserved for spacers. - - // translate component to new position - // (columns*2 accounts for two grid columns for one "actual" column) - int index = ( constraints[i].gridy - 1 ) * oldColumns*2 + constraints[i].gridx; - constraints[i].gridy = ( index / (newColumns*2) ) + 1; - constraints[i].gridx = index % (newColumns*2) ; - listContainer.add( components[i], constraints[i] ); - } - } - createSpacers(); // replace the spacers - updateGaps(); - } - - this.revalidate(); - this.repaint(); - - } + } -/** -* Sets the vertical weight used for determining how to distribute additional -* vertical space in the component. -* @param aComponent Key that exists in the layout. -* @return weighty The weight of the component, or -1.0 if not found. -*/ - public double getVerticalWeightForKey( String key ) - { - Container c = getCompositeComponentForKey( key ); - if ( c == null ) return -1.0; - if ( ! ( listContainer.getLayout() instanceof GridBagLayout ) ) return -1.0; - GridBagLayout layout = (GridBagLayout) listContainer.getLayout(); - GridBagConstraints gbc = layout.getConstraints( c ); - return gbc.weighty; - } - -/** -* Sets the vertical weight used for determining how to distribute additional -* vertical space in the component. By default, all weights are zero, so each -* component gets its preferred height. If any weights are specified, then -* additional space is allocated to those components proportionately. -* @param aComponent Key that exists in the layout. -* @param weighty The new weight. -*/ - public void setVerticalWeightForKey( String key, double weighty ) - { - Container c = getCompositeComponentForKey( key ); - if ( c == null ) return; - if ( ! ( listContainer.getLayout() instanceof GridBagLayout ) ) return; - GridBagLayout layout = (GridBagLayout) listContainer.getLayout(); - GridBagConstraints gbc = layout.getConstraints( c ); - gbc.weighty = weighty; - layout.setConstraints( c, gbc ); - // handle adding on-the-fly - updateGaps(); - this.revalidate(); - this.repaint(); - } - -/** -* Gets the current number of columns. -* @return the current number of columns. -*/ - public int getColumns() - { - return this.columns; - } + /** + * Gets the current minimum margin for the labels column. + * + * @return the current minimum margin in pixels. + */ + public int getMargin() { + return this.margin; + } + + /** + * Sets the number of columns for the panel. Label/Component pairs will start + * from the top left and fill in to the right before wrapping to the next row. + * The default number of columns is one. Note: assumes GridBagLayout. + * + * @param newColumns the new number of columns. May not be less than one. + */ + public void setColumns(int newColumns) { + if (newColumns < 1) + return; // may not be less than one. + int oldColumns = this.columns; + this.columns = newColumns; + + if (listContainer.getLayout() instanceof GridBagLayout) { + GridBagLayout gridBag = (GridBagLayout) listContainer.getLayout(); + int count = listContainer.getComponentCount(); + Component[] components = listContainer.getComponents(); + GridBagConstraints[] constraints = new GridBagConstraints[components.length]; + for (int i = 0; i < components.length; i++) { + constraints[i] = gridBag.getConstraints(components[i]); + } + listContainer.removeAll(); + for (int i = 0; i < components.length; i++) { + if (constraints[i].gridy != 0) { // ignore first row which is reserved for spacers. + + // translate component to new position + // (columns*2 accounts for two grid columns for one "actual" column) + int index = (constraints[i].gridy - 1) * oldColumns * 2 + constraints[i].gridx; + constraints[i].gridy = (index / (newColumns * 2)) + 1; + constraints[i].gridx = index % (newColumns * 2); + listContainer.add(components[i], constraints[i]); + } + } + createSpacers(); // replace the spacers + updateGaps(); + } + + this.revalidate(); + this.repaint(); -/** -* Appends a label containing a key and the specified component -* to the bottom of the panel. Any registered action listeners -* will receive action events from the component - the key corresponding -* to the component will be used as the action command. -* @param key A string that will be displayed in a label, preferrably unique. -* @param component A component that will be placed next to the label. -* If null, a blank JPanel will be used. -*/ - public void addPair( String key, Component component ) - { - addRow( key, new Component[] { component } ); } - -/** -* Appends a label containing a key and the specified component -* to the bottom of the panel. Any registered action listeners -* will receive action events from the component - the key corresponding -* to the component will be used as the action command. -* @param key A string that will be displayed in a label, preferrably unique. -* @param component A component that will be placed next to the label. -* If null, a blank JPanel will appear. -*/ - public void addRow( String key, Component component ) - { - addRow( key, new Component[] { component } ); + + /** + * Sets the vertical weight used for determining how to distribute additional + * vertical space in the component. + * + * @param aComponent Key that exists in the layout. + * @return weighty The weight of the component, or -1.0 if not found. + */ + public double getVerticalWeightForKey(String key) { + Container c = getCompositeComponentForKey(key); + if (c == null) + return -1.0; + if (!(listContainer.getLayout() instanceof GridBagLayout)) + return -1.0; + GridBagLayout layout = (GridBagLayout) listContainer.getLayout(); + GridBagConstraints gbc = layout.getConstraints(c); + return gbc.weighty; } - -/** -* Appends a label containing a key and the specified components -* to the bottom of the panel. Any registered action listeners -* will receive action events from the component - the key corresponding -* to the component will be used as the action command. -* @param key A string that will be displayed in a label, preferrably unique. -* @param components An array of components that will be placed next to the label. -* Any nulls in the list will be replaced with blank JPanels. -*/ - public void addRow( - String key, Component[] components ) - { - addCompositeComponent( key, makeCompositeComponent( key, components ) ); + + /** + * Sets the vertical weight used for determining how to distribute additional + * vertical space in the component. By default, all weights are zero, so each + * component gets its preferred height. If any weights are specified, then + * additional space is allocated to those components proportionately. + * + * @param aComponent Key that exists in the layout. + * @param weighty The new weight. + */ + public void setVerticalWeightForKey(String key, double weighty) { + Container c = getCompositeComponentForKey(key); + if (c == null) + return; + if (!(listContainer.getLayout() instanceof GridBagLayout)) + return; + GridBagLayout layout = (GridBagLayout) listContainer.getLayout(); + GridBagConstraints gbc = layout.getConstraints(c); + gbc.weighty = weighty; + layout.setConstraints(c, gbc); + // handle adding on-the-fly + updateGaps(); + this.revalidate(); + this.repaint(); } -/** -* Appends a label containing a key and the specified components -* to the bottom of the panel. Any registered action listeners -* will receive action events from the components - the key corresponding -* to the component will be used as the action command. -* @param key A string that will be displayed in a label, preferrably unique. -* @param west A component that will appear to the left of the other components, -* as wide as its preferred width and as tall as the tallest of the other components. -* A null will be replaced with a blank JPanel. -* @param center A component that will appear between the other components, -* taking up available space. -* A null will be replaced with a blank JPanel. -* @param east A component that will appear to the right of the other components, -* as wide as its preferred width and as tall as the tallest of the other components. -* A null will be replaced with a blank JPanel. -*/ - public void addRow( - String key, Component west, Component center, Component east ) - { - addCompositeComponent( key, - makeCompositeComponent( key, - west, center, east ) ); + /** + * Gets the current number of columns. + * + * @return the current number of columns. + */ + public int getColumns() { + return this.columns; } -/** -* Appends a label containing a key and the specified components -* to the bottom of the panel. Any registered action listeners -* will receive action events from the components - the key corresponding -* to the component will be used as the action command. -* @param key A string that will be displayed in a label, preferrably unique. -* @param west A component that will appear to the left of the other components, -* as wide as its preferred width and as tall as the tallest of the other components. -* A null will be replaced with a blank JPanel. -* @param north A component that will appear above all the other components, -* as tall as its preferred height and as wide as the info panel itself. -* @param center A component that will appear between the other components, -* taking up available space. A null will be replaced with a blank JPanel. -* @param south A component that will appear below all the other components, -* as tall as its preferred height and as wide as the info panel itself. -* @param east A component that will appear to the right of the other components, -* as wide as its preferred width and as tall as the tallest of the other components. -* A null will be replaced with a blank JPanel. -*/ - public void addRow( - String key, Component west, Component north, - Component center, Component south, Component east ) - { - addCompositeComponent( key, - makeCompositeComponent( key, - west, north, center, south, east ) ); + /** + * Appends a label containing a key and the specified component to the bottom of + * the panel. Any registered action listeners will receive action events from + * the component - the key corresponding to the component will be used as the + * action command. + * + * @param key A string that will be displayed in a label, preferrably + * unique. + * @param component A component that will be placed next to the label. If null, + * a blank JPanel will be used. + */ + public void addPair(String key, Component component) { + addRow(key, new Component[] { component }); } -/** -* Produces a container that contains the specified components, -* using GridLayout. Nulls are ignored. -* This implementation returns a JPanel. -*/ - protected Container makeCompositeComponent( - String key, Component[] components ) - { + /** + * Appends a label containing a key and the specified component to the bottom of + * the panel. Any registered action listeners will receive action events from + * the component - the key corresponding to the component will be used as the + * action command. + * + * @param key A string that will be displayed in a label, preferrably + * unique. + * @param component A component that will be placed next to the label. If null, + * a blank JPanel will appear. + */ + public void addRow(String key, Component component) { + addRow(key, new Component[] { component }); + } + + /** + * Appends a label containing a key and the specified components to the bottom + * of the panel. Any registered action listeners will receive action events from + * the component - the key corresponding to the component will be used as the + * action command. + * + * @param key A string that will be displayed in a label, preferrably + * unique. + * @param components An array of components that will be placed next to the + * label. Any nulls in the list will be replaced with blank + * JPanels. + */ + public void addRow(String key, Component[] components) { + addCompositeComponent(key, makeCompositeComponent(key, components)); + } + + /** + * Appends a label containing a key and the specified components to the bottom + * of the panel. Any registered action listeners will receive action events from + * the components - the key corresponding to the component will be used as the + * action command. + * + * @param key A string that will be displayed in a label, preferrably unique. + * @param west A component that will appear to the left of the other + * components, as wide as its preferred width and as tall as the + * tallest of the other components. A null will be replaced with a + * blank JPanel. + * @param center A component that will appear between the other components, + * taking up available space. A null will be replaced with a blank + * JPanel. + * @param east A component that will appear to the right of the other + * components, as wide as its preferred width and as tall as the + * tallest of the other components. A null will be replaced with a + * blank JPanel. + */ + public void addRow(String key, Component west, Component center, Component east) { + addCompositeComponent(key, makeCompositeComponent(key, west, center, east)); + } + + /** + * Appends a label containing a key and the specified components to the bottom + * of the panel. Any registered action listeners will receive action events from + * the components - the key corresponding to the component will be used as the + * action command. + * + * @param key A string that will be displayed in a label, preferrably unique. + * @param west A component that will appear to the left of the other + * components, as wide as its preferred width and as tall as the + * tallest of the other components. A null will be replaced with a + * blank JPanel. + * @param north A component that will appear above all the other components, as + * tall as its preferred height and as wide as the info panel + * itself. + * @param center A component that will appear between the other components, + * taking up available space. A null will be replaced with a blank + * JPanel. + * @param south A component that will appear below all the other components, as + * tall as its preferred height and as wide as the info panel + * itself. + * @param east A component that will appear to the right of the other + * components, as wide as its preferred width and as tall as the + * tallest of the other components. A null will be replaced with a + * blank JPanel. + */ + public void addRow(String key, Component west, Component north, Component center, Component south, Component east) { + addCompositeComponent(key, makeCompositeComponent(key, west, north, center, south, east)); + } + + /** + * Produces a container that contains the specified components, using + * GridLayout. Nulls are ignored. This implementation returns a JPanel. + */ + protected Container makeCompositeComponent(String key, Component[] components) { JPanel panel = createPanel(); - if ( components.length != 0 ) - { - panel.setLayout( new GridLayout( 1, components.length, hgap, vgap ) ); + if (components.length != 0) { + panel.setLayout(new GridLayout(1, components.length, hgap, vgap)); Component c; - for ( int i = 0; i < components.length; i++ ) - { + for (int i = 0; i < components.length; i++) { c = components[i]; - if ( c != null ) - { - introspectComponent( c, key ); - panel.add( c ); + if (c != null) { + introspectComponent(c, key); + panel.add(c); } } } return panel; } -/** -* Produces a container that contains the specified components, -* using BorderLayout. Nulls are ignored. -* This implementation returns a JPanel. -*/ - protected Container makeCompositeComponent( - String key, Component west, Component center, Component east ) - { + /** + * Produces a container that contains the specified components, using + * BorderLayout. Nulls are ignored. This implementation returns a JPanel. + */ + protected Container makeCompositeComponent(String key, Component west, Component center, Component east) { JPanel panel = createPanel(); - panel.setLayout( new BorderLayout( hgap, vgap ) ); + panel.setLayout(new BorderLayout(hgap, vgap)); - if ( west != null ) - { - introspectComponent( west, key ); - panel.add( west, BorderLayout.WEST ); + if (west != null) { + introspectComponent(west, key); + panel.add(west, BorderLayout.WEST); } - - if ( center != null ) - { - introspectComponent( center, key ); - panel.add( center, BorderLayout.CENTER ); + + if (center != null) { + introspectComponent(center, key); + panel.add(center, BorderLayout.CENTER); } - - if ( east != null ) - { - introspectComponent( east, key ); - panel.add( east, BorderLayout.EAST ); + + if (east != null) { + introspectComponent(east, key); + panel.add(east, BorderLayout.EAST); } - + return panel; } -/** -* Produces a container that contains the specified components, -* using BorderLayout. Nulls are ignored. -* This implementation returns a JPanel. -*/ - protected Container makeCompositeComponent( - String key, Component west, Component north, - Component center, Component south, Component east ) - { + /** + * Produces a container that contains the specified components, using + * BorderLayout. Nulls are ignored. This implementation returns a JPanel. + */ + protected Container makeCompositeComponent(String key, Component west, Component north, Component center, + Component south, Component east) { JPanel panel = createPanel(); - panel.setLayout( new BorderLayout( hgap, vgap ) ); + panel.setLayout(new BorderLayout(hgap, vgap)); - if ( west != null ) - { - introspectComponent( west, key ); - panel.add( west, BorderLayout.WEST ); + if (west != null) { + introspectComponent(west, key); + panel.add(west, BorderLayout.WEST); } - - if ( north != null ) - { - introspectComponent( north, key ); - panel.add( north, BorderLayout.WEST ); + + if (north != null) { + introspectComponent(north, key); + panel.add(north, BorderLayout.WEST); } - - if ( center != null ) - { - introspectComponent( center, key ); - panel.add( center, BorderLayout.CENTER ); + + if (center != null) { + introspectComponent(center, key); + panel.add(center, BorderLayout.CENTER); } - - if ( south != null ) - { - introspectComponent( south, key ); - panel.add( south, BorderLayout.CENTER ); + + if (south != null) { + introspectComponent(south, key); + panel.add(south, BorderLayout.CENTER); } - - if ( east != null ) - { - introspectComponent( east, key ); - panel.add( east, BorderLayout.EAST ); + + if (east != null) { + introspectComponent(east, key); + panel.add(east, BorderLayout.EAST); } - + return panel; } -/** -* Override to return a specific component to be used -* as a label. This implementation calls createLabel(). -*/ - protected Component createLabelForKey( String aKey ) - { - return createLabel(); + /** + * Override to return a specific component to be used as a label. This + * implementation calls createLabel(). + */ + protected Component createLabelForKey(String aKey) { + return createLabel(); } -/** -* Provided for backwards compatibility, and called by -* the default implementation of createLabelForKey. -* This implementation returns a JLabel. -*/ - protected JLabel createLabel() - { - return new JLabel(); + /** + * Provided for backwards compatibility, and called by the default + * implementation of createLabelForKey. This implementation returns a JLabel. + */ + protected JLabel createLabel() { + return new JLabel(); } -/** -* Appends a label containing a key and the specified component -* to the bottom of the panel. Any registered action listeners -* will receive action events from the component - the key corresponding -* to the component will be used as the action command. -* @param key A string that will be displayed in a label, preferrably unique. -* @param component A component that will be placed next to the label. -* If null, a stock JTextField will be used. -*/ - protected void addCompositeComponent( String key, Component component ) - { - if ( key == null ) - { - key = ""; - } - Component label = createLabelForKey( key ); - Component field = component; - if ( field == null ) - { - field = new JTextField( 15 ); // default to 15 columns - } - field.setName( key ); // for association and reference - label.setName( key ); // ditto - if ( label instanceof JLabel ) - { - ((JLabel)label).setHorizontalAlignment( labelAlign ); - ((JLabel)label).setLabelFor( field ); // for accessibility - } - if ( "".equals( key ) ) - { - setText( label, "" ); - } - else - { - setText( label, prefix + key + postfix ); - } - field.setEnabled( this.isEditable ); // was: setEditable - - GridBagConstraints gbc = new GridBagConstraints(); - - if ( listContainer.getComponentCount() == 0 ) - { // we've just initialized or called removeAll - createSpacers(); - } - - gbc.gridx = ( fields.size() % this.columns ) * 2; - gbc.gridy = ( fields.size() / this.columns ) + 1; // spacer is at index zero - gbc.weightx = 0.0; - gbc.weighty = 0.0; - gbc.anchor = this.labelAnchor; - gbc.fill = GridBagConstraints.HORIZONTAL; - listContainer.add( label, gbc ); + /** + * Appends a label containing a key and the specified component to the bottom of + * the panel. Any registered action listeners will receive action events from + * the component - the key corresponding to the component will be used as the + * action command. + * + * @param key A string that will be displayed in a label, preferrably + * unique. + * @param component A component that will be placed next to the label. If null, + * a stock JTextField will be used. + */ + protected void addCompositeComponent(String key, Component component) { + if (key == null) { + key = ""; + } + Component label = createLabelForKey(key); + Component field = component; + if (field == null) { + field = new JTextField(15); // default to 15 columns + } + field.setName(key); // for association and reference + label.setName(key); // ditto + if (label instanceof JLabel) { + ((JLabel) label).setHorizontalAlignment(labelAlign); + ((JLabel) label).setLabelFor(field); // for accessibility + } + if ("".equals(key)) { + setText(label, ""); + } else { + setText(label, prefix + key + postfix); + } + field.setEnabled(this.isEditable); // was: setEditable + + GridBagConstraints gbc = new GridBagConstraints(); + + if (listContainer.getComponentCount() == 0) { // we've just initialized or called removeAll + createSpacers(); + } + + gbc.gridx = (fields.size() % this.columns) * 2; + gbc.gridy = (fields.size() / this.columns) + 1; // spacer is at index zero + gbc.weightx = 0.0; + gbc.weighty = 0.0; + gbc.anchor = this.labelAnchor; + gbc.fill = GridBagConstraints.HORIZONTAL; + listContainer.add(label, gbc); gbc.fill = GridBagConstraints.BOTH; - gbc.gridx = gbc.gridx + 1; - //FIXME: components default to the labelAnchor - should be different? - gbc.weightx = 1.0; - gbc.weighty = 0.0; - - listContainer.add( field, gbc ); - - if ( key.equals( HIDDEN ) ) - { // these components are not to be shown - setText( label, " " ); - field.setVisible( false ); - } - - fields.add( field ); // using list not map to allow for duplicate keys - labels.add( label ); // ditto - - // handle adding on-the-fly - updateGaps(); - this.revalidate(); - this.repaint(); - } + gbc.gridx = gbc.gridx + 1; + // FIXME: components default to the labelAnchor - should be different? + gbc.weightx = 1.0; + gbc.weighty = 0.0; -/** -* Introspects a component to set the action command and to add the -* InfoPanel to its list of ActionListeners. -* @param aComponent The Component to be introspected. -* @param aKey The action command to be set. -*/ - protected void introspectComponent( Component aComponent, String aKey ) - { - // try to set properties of whatever component this might be - try { - Method [] methods = - (Method []) _method_cache.get( aComponent.getClass() ); - if (methods == null) { - Class componentClass = aComponent.getClass(); - BeanInfo info = - Introspector.getBeanInfo( componentClass ); - - MethodDescriptor[] descriptors = - info.getMethodDescriptors(); - Method setMethod = null; - Method addMethod = null; - for ( int i = 0; - ((setMethod == null || addMethod == null) && - i < descriptors.length); - i++ ) - { - Method m = descriptors[i].getMethod(); - String name = m.getName (); - if ( setMethod == null && - name.equals( "setActionCommand" ) ) - { - setMethod = m; - } - else if ( addMethod == null && - name.equals( "addActionListener" ) ) - { - addMethod = m; - } - } - - methods = new Method [] {setMethod, addMethod}; - _method_cache.put (componentClass, methods); - } - if (methods [0] != null) { - methods [0].invoke( aComponent, new Object[] { aKey } ); - } - if (methods [1] != null) { - methods [1].invoke( aComponent, new Object[] { this } ); - listenedToComponents.add( aComponent ); - } - } - catch ( Exception exc ) - { // error occured while introspecting... move along. - System.out.println( "InfoPanel.introspectComponent: " + exc ); - } - } - -/** -* Called to populate a label component with the specified text. -* This implementation attempts to call setText(String) on the component. -* Override to customize. -*/ - protected void setText( Component c, String text ) - { - try - { - Method m = c.getClass().getMethod( "setText", new Class[] { String.class } ); - if ( m != null ) - { - m.invoke( c, new Object[] { text } ); - } - } - catch ( Exception exc ) - { - // no such method: ignore - } - } + listContainer.add(field, gbc); -/** -* Creates spacer components on the reserved first grid row -* for each column of labels and fields. -* This allows us to set the margin for those label columns, -* and set the preferred width of the field columns. -* A list containing the field spacers should be assigned to -* the fieldSpacers instance variable. -*/ - private void createSpacers() - { - if ( listContainer.getLayout() instanceof GridBagLayout ) - { - // insert spacers for labels column - GridBagLayout gridBag = (GridBagLayout) listContainer.getLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.gridy = 0; - constraints.fill = GridBagConstraints.HORIZONTAL; - - fieldSpacers = new LinkedList(); - Component fieldSpacer; - for ( int i = 0; i < this.columns; i++ ) - { - constraints.gridx = i * 2; - listContainer.add( Box.createHorizontalStrut( this.margin ), constraints ); - - constraints.gridx = i * 2 + 1; - fieldSpacer = Box.createHorizontalStrut( 0 ); - fieldSpacers.add( fieldSpacer ); - listContainer.add( fieldSpacer, constraints ); - } - } - } + if (key.equals(HIDDEN)) { // these components are not to be shown + setText(label, " "); + field.setVisible(false); + } -/** -* Updates the insets for all components. -*/ - protected void updateGaps() - { - if ( listContainer.getLayout() instanceof GridBagLayout ) - { - GridBagLayout layout = (GridBagLayout) listContainer.getLayout(); - Component c = null; - GridBagConstraints gbc = null; - double totalWeightY = 0.0; - int count = listContainer.getComponentCount(); - int i; - for ( i = 0; i < count; i++ ) - { - c = listContainer.getComponent( i ); - gbc = layout.getConstraints( c ); - totalWeightY += gbc.weighty; - if ( (gbc.gridx + 1) % ( this.columns * 2 ) == 0 ) - { // if last component in row - gbc.insets = new Insets( 0, 0, this.vgap, 0 ); - } - else - { - if ( gbc.gridx % 2 == 0 ) - { // is a label column - NOTE: uses eleven pixels before component, per l&f guide - gbc.insets = new Insets( 0, 0, this.vgap, 11 ); - } - else - { // is a component column - if ( gbc.gridy != 0 ) - { - if ( c instanceof JPanel ) ((JPanel)c).setPreferredSize( null ); - gbc.insets = new Insets( 0, 0, this.vgap, this.hgap ); - } - } - } - layout.setConstraints( c, gbc ); - } - - //hack: gridbag clumps components in center if weighty is zero - // if sum of weighty is zero, top-justify the list container - this.remove( listContainer ); - if ( totalWeightY == 0.0 ) - { - this.add( listContainer, BorderLayout.NORTH ); - } - else // put list container in center so it will grow - { - this.add( listContainer, BorderLayout.CENTER ); - } - } - } + fields.add(field); // using list not map to allow for duplicate keys + labels.add(label); // ditto -/** -* Updates the label alignment. -*/ - protected void updateLabels() - { - if ( listContainer.getLayout() instanceof GridBagLayout ) - { - GridBagLayout layout = (GridBagLayout) listContainer.getLayout(); - Component c = null; - GridBagConstraints gbc = null; + // handle adding on-the-fly + updateGaps(); + this.revalidate(); + this.repaint(); + } + + /** + * Introspects a component to set the action command and to add the InfoPanel to + * its list of ActionListeners. + * + * @param aComponent The Component to be introspected. + * @param aKey The action command to be set. + */ + protected void introspectComponent(Component aComponent, String aKey) { + // try to set properties of whatever component this might be + try { + Method[] methods = (Method[]) _method_cache.get(aComponent.getClass()); + if (methods == null) { + Class componentClass = aComponent.getClass(); + BeanInfo info = Introspector.getBeanInfo(componentClass); + + MethodDescriptor[] descriptors = info.getMethodDescriptors(); + Method setMethod = null; + Method addMethod = null; + for (int i = 0; ((setMethod == null || addMethod == null) && i < descriptors.length); i++) { + Method m = descriptors[i].getMethod(); + String name = m.getName(); + if (setMethod == null && name.equals("setActionCommand")) { + setMethod = m; + } else if (addMethod == null && name.equals("addActionListener")) { + addMethod = m; + } + } + + methods = new Method[] { setMethod, addMethod }; + _method_cache.put(componentClass, methods); + } + if (methods[0] != null) { + methods[0].invoke(aComponent, new Object[] { aKey }); + } + if (methods[1] != null) { + methods[1].invoke(aComponent, new Object[] { this }); + listenedToComponents.add(aComponent); + } + } catch (Exception exc) { // error occured while introspecting... move along. + System.out.println("InfoPanel.introspectComponent: " + exc); + } + } + + /** + * Called to populate a label component with the specified text. This + * implementation attempts to call setText(String) on the component. Override to + * customize. + */ + protected void setText(Component c, String text) { + try { + Method m = c.getClass().getMethod("setText", new Class[] { String.class }); + if (m != null) { + m.invoke(c, new Object[] { text }); + } + } catch (Exception exc) { + // no such method: ignore + } + } + + /** + * Creates spacer components on the reserved first grid row for each column of + * labels and fields. This allows us to set the margin for those label columns, + * and set the preferred width of the field columns. A list containing the field + * spacers should be assigned to the fieldSpacers instance variable. + */ + private void createSpacers() { + if (listContainer.getLayout() instanceof GridBagLayout) { + // insert spacers for labels column + GridBagLayout gridBag = (GridBagLayout) listContainer.getLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.gridy = 0; + constraints.fill = GridBagConstraints.HORIZONTAL; + + fieldSpacers = new LinkedList(); + Component fieldSpacer; + for (int i = 0; i < this.columns; i++) { + constraints.gridx = i * 2; + listContainer.add(Box.createHorizontalStrut(this.margin), constraints); + + constraints.gridx = i * 2 + 1; + fieldSpacer = Box.createHorizontalStrut(0); + fieldSpacers.add(fieldSpacer); + listContainer.add(fieldSpacer, constraints); + } + } + } + + /** + * Updates the insets for all components. + */ + protected void updateGaps() { + if (listContainer.getLayout() instanceof GridBagLayout) { + GridBagLayout layout = (GridBagLayout) listContainer.getLayout(); + Component c = null; + GridBagConstraints gbc = null; + double totalWeightY = 0.0; + int count = listContainer.getComponentCount(); + int i; + for (i = 0; i < count; i++) { + c = listContainer.getComponent(i); + gbc = layout.getConstraints(c); + totalWeightY += gbc.weighty; + if ((gbc.gridx + 1) % (this.columns * 2) == 0) { // if last component in row + gbc.insets = new Insets(0, 0, this.vgap, 0); + } else { + if (gbc.gridx % 2 == 0) { // is a label column - NOTE: uses eleven pixels before component, per l&f + // guide + gbc.insets = new Insets(0, 0, this.vgap, 11); + } else { // is a component column + if (gbc.gridy != 0) { + if (c instanceof JPanel) + ((JPanel) c).setPreferredSize(null); + gbc.insets = new Insets(0, 0, this.vgap, this.hgap); + } + } + } + layout.setConstraints(c, gbc); + } + + // hack: gridbag clumps components in center if weighty is zero + // if sum of weighty is zero, top-justify the list container + this.remove(listContainer); + if (totalWeightY == 0.0) { + this.add(listContainer, BorderLayout.NORTH); + } else // put list container in center so it will grow + { + this.add(listContainer, BorderLayout.CENTER); + } + } + } + + /** + * Updates the label alignment. + */ + protected void updateLabels() { + if (listContainer.getLayout() instanceof GridBagLayout) { + GridBagLayout layout = (GridBagLayout) listContainer.getLayout(); + Component c = null; + GridBagConstraints gbc = null; Iterator it = labels.iterator(); - while ( it.hasNext() ) - { - c = (Component) it.next(); - if ( c instanceof JLabel ) - { - ((JLabel)c).setHorizontalAlignment( labelAlign ); - } - gbc = layout.getConstraints( c ); - gbc.anchor = this.labelAnchor; - layout.setConstraints( c, gbc ); - } - } - } + while (it.hasNext()) { + c = (Component) it.next(); + if (c instanceof JLabel) { + ((JLabel) c).setHorizontalAlignment(labelAlign); + } + gbc = layout.getConstraints(c); + gbc.anchor = this.labelAnchor; + layout.setConstraints(c, gbc); + } + } + } -/** -* Convenience method that uses a stock JTextField. -* @param key A string that will be displayed in a label, preferrably unique. -* @param value A string that will be displayed in a textfield. -*/ - public void addPair( String key, String value ) - { - addPair( key, value, null ); - } + /** + * Convenience method that uses a stock JTextField. + * + * @param key A string that will be displayed in a label, preferrably unique. + * @param value A string that will be displayed in a textfield. + */ + public void addPair(String key, String value) { + addPair(key, value, null); + } -/** -* Convenience method that uses the specified JTextField or subclass -* and sets it to the specified value. -* @param key A string that will be displayed in a label, preferrably unique. -* @param value A string that will be displayed in a textfield. -* @param textField A JTextField or subclass that will be used to display the value. -* If null, a stock JTextField will be used. -*/ - public void addPair( String key, String value, JTextField textField ) - { - if ( value == null ) - { - value = ""; - } - JTextField field = textField; - if ( field == null ) - { - field = new JTextField( 15 ); // default to 15 columns - } - else - { - field = textField; - } - field.setText( value ); - - addPair( key, (Component) field ); - } + /** + * Convenience method that uses the specified JTextField or subclass and sets it + * to the specified value. + * + * @param key A string that will be displayed in a label, preferrably + * unique. + * @param value A string that will be displayed in a textfield. + * @param textField A JTextField or subclass that will be used to display the + * value. If null, a stock JTextField will be used. + */ + public void addPair(String key, String value, JTextField textField) { + if (value == null) { + value = ""; + } + JTextField field = textField; + if (field == null) { + field = new JTextField(15); // default to 15 columns + } else { + field = textField; + } + field.setText(value); -/** -* Removes all components from the list. Buttons, if any, -* will remain unchanged - use setButtons( null ) to remove -* them. NOTE: does not call super.removeAll(). -*/ - public void removeAll() - { - Object component; - Method method; - Class[] paramClasses = new Class[] { ActionListener.class }; - Object[] paramObjects = new Object[] { this }; - - Iterator iterator = listenedToComponents.iterator(); - while ( iterator.hasNext() ) - { - component = iterator.next(); - try - { - method = component.getClass().getMethod( "removeActionListener", paramClasses ); - if ( method != null ) - { - method.invoke( component, paramObjects ); - } - } - catch ( Exception exception ) - { - // No removeActionListener() method, move along. - } - } - - listenedToComponents.clear(); - - listContainer.removeAll(); - fields.clear(); - labels.clear(); - this.revalidate(); - this.repaint(); - - //FIXME: It is very confusing that this - // implementation does not call super.removeAll(). - } + addPair(key, (Component) field); + } -/** -* Adds one or buttons to the bottom of the panel with the specified labels -* from left to right. Any action listeners will receive action events -* from clicks on these buttons - the supplied label will be used as the action command. -* @param buttons A string array containing the strings to be used for the button labels -* and action commands. A null value will remove the button panel. -* @see ButtonPanel -*/ - public void setButtons( String[] buttons ) - { - if ( buttonPanel == null ) - { - buttonPanel = new ButtonPanel(); - buttonPanel.setInsets( new Insets( 6, 0, 0, 0 ) ); - // button panel has a 11-pixel top inset - // and java l&f guide says 17-pixels before command buttons - buttonPanel.addActionListener( this ); - this.add( buttonPanel, BorderLayout.SOUTH ); - } - if ( buttons == null ) - { - this.remove( buttonPanel ); - buttonPanel = null; - } - else - { - buttonPanel.setLabels( buttons ); - } - - this.revalidate(); - this.repaint(); - } - protected Collection listenedToComponents = new LinkedList(); + /** + * Removes all components from the list. Buttons, if any, will remain unchanged + * - use setButtons( null ) to remove them. NOTE: does not call + * super.removeAll(). + */ + public void removeAll() { + Object component; + Method method; + Class[] paramClasses = new Class[] { ActionListener.class }; + Object[] paramObjects = new Object[] { this }; + + Iterator iterator = listenedToComponents.iterator(); + while (iterator.hasNext()) { + component = iterator.next(); + try { + method = component.getClass().getMethod("removeActionListener", paramClasses); + if (method != null) { + method.invoke(component, paramObjects); + } + } catch (Exception exception) { + // No removeActionListener() method, move along. + } + } -/** -* Retrieves the names of the buttons that are displayed, if any. -* @return A string array containing the strings used for the button labels -* and action commands, or null if no buttons have been created. -* @see ButtonPanel -*/ - public String[] getButtons() - { - if ( buttonPanel == null ) - { - return null; // none created - } + listenedToComponents.clear(); - return buttonPanel.getLabels(); - } + listContainer.removeAll(); + fields.clear(); + labels.clear(); + this.revalidate(); + this.repaint(); -/** -* Retrieves the actual button panel, if any. -* @return A button panel, or null if none has been created. -* @see ButtonPanel -*/ - public ButtonPanel getButtonPanel() - { - return buttonPanel; - } + // FIXME: It is very confusing that this + // implementation does not call super.removeAll(). + } + /** + * Adds one or buttons to the bottom of the panel with the specified labels from + * left to right. Any action listeners will receive action events from clicks on + * these buttons - the supplied label will be used as the action command. + * + * @param buttons A string array containing the strings to be used for the + * button labels and action commands. A null value will remove + * the button panel. + * @see ButtonPanel + */ + public void setButtons(String[] buttons) { + if (buttonPanel == null) { + buttonPanel = new ButtonPanel(); + buttonPanel.setInsets(new Insets(6, 0, 0, 0)); + // button panel has a 11-pixel top inset + // and java l&f guide says 17-pixels before command buttons + buttonPanel.addActionListener(this); + this.add(buttonPanel, BorderLayout.SOUTH); + } + if (buttons == null) { + this.remove(buttonPanel); + buttonPanel = null; + } else { + buttonPanel.setLabels(buttons); + } -/** -* Sets whether the values displayed in the panel should be editable. Defaults to true. -* @param isEditable Whether the values should be editable. -*/ - public void setEditable( boolean isEditable ) - { - this.isEditable = isEditable; - Iterator enumeration = fields.iterator(); - while ( enumeration.hasNext() ) - { - ( (Component) enumeration.next() ).setEnabled( isEditable ); - } - } + this.revalidate(); + this.repaint(); + } -/** -* Gets whether the values displayed in the panel are editable. -* @return Whether the values should be editable. -*/ - public boolean isEditable() - { - return this.isEditable; - } + protected Collection listenedToComponents = new LinkedList(); + + /** + * Retrieves the names of the buttons that are displayed, if any. + * + * @return A string array containing the strings used for the button labels and + * action commands, or null if no buttons have been created. + * @see ButtonPanel + */ + public String[] getButtons() { + if (buttonPanel == null) { + return null; // none created + } -/** -* Sets the field associated with the key to the specified value. -* Note: If the component does not respond to setText() or setString() -* or setValue() the value will not be set. JTextFields and the like will work. -* @param key A string representing the key associated with the field. Nulls are converted to an empty string. -* @param value A object to be displayed in the specified field. Nulls are converted to an empty string. -*/ - public void setValueForKey( String key, Object value ) - { - setValueForKey( key, value, 0 ); + return buttonPanel.getLabels(); } - -/** -* Sets the field associated with the key to the specified value. -* Note: If the component does not respond to setText() or setString() -* or setValue() the value will not be set. JTextFields and the like will work. -* @param key A string representing the key associated with the field. Nulls are converted to an empty string. -* @param value A object to be displayed in the specified field. Nulls are converted to an empty string. -*/ - public void setValueForKey( String key, Object value, int index ) - { - if ( key == null ) - { - key = ""; - } - - Container field = null; - for ( int i = 0; i < fields.size(); i++ ) - { - field = (Container) fields.get(i); - if ( key.equals( field.getName() ) ) - { - setValueForIndex( index, i, value ); + + /** + * Retrieves the actual button panel, if any. + * + * @return A button panel, or null if none has been created. + * @see ButtonPanel + */ + public ButtonPanel getButtonPanel() { + return buttonPanel; + } + + /** + * Sets whether the values displayed in the panel should be editable. Defaults + * to true. + * + * @param isEditable Whether the values should be editable. + */ + public void setEditable(boolean isEditable) { + this.isEditable = isEditable; + Iterator enumeration = fields.iterator(); + while (enumeration.hasNext()) { + ((Component) enumeration.next()).setEnabled(isEditable); + } + } + + /** + * Gets whether the values displayed in the panel are editable. + * + * @return Whether the values should be editable. + */ + public boolean isEditable() { + return this.isEditable; + } + + /** + * Sets the field associated with the key to the specified value. Note: If the + * component does not respond to setText() or setString() or setValue() the + * value will not be set. JTextFields and the like will work. + * + * @param key A string representing the key associated with the field. Nulls + * are converted to an empty string. + * @param value A object to be displayed in the specified field. Nulls are + * converted to an empty string. + */ + public void setValueForKey(String key, Object value) { + setValueForKey(key, value, 0); + } + + /** + * Sets the field associated with the key to the specified value. Note: If the + * component does not respond to setText() or setString() or setValue() the + * value will not be set. JTextFields and the like will work. + * + * @param key A string representing the key associated with the field. Nulls + * are converted to an empty string. + * @param value A object to be displayed in the specified field. Nulls are + * converted to an empty string. + */ + public void setValueForKey(String key, Object value, int index) { + if (key == null) { + key = ""; + } + + Container field = null; + for (int i = 0; i < fields.size(); i++) { + field = (Container) fields.get(i); + if (key.equals(field.getName())) { + setValueForIndex(index, i, value); return; - } + } } - // else not found - ignore - } - -/** -* Sets the first field at the specified row index to the specified value. -* Note: If the component does not respond to setText() or setString() -* or setValue() the value will not be set. JTextFields and the like will work. -* @param row The row index of the component. -* @param value A object to be displayed in the specified field. -* Nulls are converted to an empty string. -*/ - public void setValueForIndex( int row, Object value ) - { - setValueForIndex( row, 0, value ); + // else not found - ignore } - -/** -* Sets the field at the specified row index and column index to the specified value. -* Note: If the component does not respond to setText() or setString() -* or setValue() the value will not be set. JTextFields and the like will work. -* @param row The row index of the component. -* @param index The column index of the component. -* @param value A object to be displayed in the specified field. -* Nulls are converted to an empty string. -*/ - public void setValueForIndex( int row, int col, Object value ) - { - Container field = (Container) fields.get( row ); - Component c = field.getComponent( col ); - setValueForComponent( c, value ); + + /** + * Sets the first field at the specified row index to the specified value. Note: + * If the component does not respond to setText() or setString() or setValue() + * the value will not be set. JTextFields and the like will work. + * + * @param row The row index of the component. + * @param value A object to be displayed in the specified field. Nulls are + * converted to an empty string. + */ + public void setValueForIndex(int row, Object value) { + setValueForIndex(row, 0, value); } - + /** + * Sets the field at the specified row index and column index to the specified + * value. Note: If the component does not respond to setText() or setString() or + * setValue() the value will not be set. JTextFields and the like will work. + * + * @param row The row index of the component. + * @param index The column index of the component. + * @param value A object to be displayed in the specified field. Nulls are + * converted to an empty string. + */ + public void setValueForIndex(int row, int col, Object value) { + Container field = (Container) fields.get(row); + Component c = field.getComponent(col); + setValueForComponent(c, value); + } -/** -* Sets the value in the field at the specified index. -* Note: If the component does not respond to setText() or setString() -* or setValue() this method will return null. JTextFields and the like will work. -* @param A valid index. -* @param value A object to be displayed in the specified field. -*/ - protected void setValueForComponent( Component aComponent, Object value ) - { - // try to set a text or string property - try { - BeanInfo info = Introspector.getBeanInfo( aComponent.getClass() ); - MethodDescriptor[] methods = info.getMethodDescriptors(); - for ( int i = 0; i < methods.length; i++ ) - { - Method m = methods[i].getMethod(); - Class[] paramTypes = m.getParameterTypes(); - if ( paramTypes.length == 1 ) - { - if ( m.getName().equals( "setText" ) ) - { - if ( paramTypes[0].getName().equals( String.class.getName() ) ) - { - m.invoke( aComponent, new Object[] { value } ); - } - } - if ( m.getName().equals( "setString" ) ) - { - if ( paramTypes[0].getName().equals( String.class.getName() ) ) - { - m.invoke( aComponent, new Object[] { value } ); - } - } - if ( m.getName().equals( "setValue" ) ) - { - if ( paramTypes[0].getName().equals( Object.class.getName() ) ) - { - m.invoke( aComponent, new Object[] { value } ); - } - } - } - } - } - catch ( Exception exc ) - { // error occured while introspecting... move along. - // FIXME: should log error in ErrorManager - System.out.println( "InfoPanel.setValueForComponent: " + exc ); - } - } + /** + * Sets the value in the field at the specified index. Note: If the component + * does not respond to setText() or setString() or setValue() this method will + * return null. JTextFields and the like will work. + * + * @param A valid index. + * @param value A object to be displayed in the specified field. + */ + protected void setValueForComponent(Component aComponent, Object value) { + // try to set a text or string property + try { + BeanInfo info = Introspector.getBeanInfo(aComponent.getClass()); + MethodDescriptor[] methods = info.getMethodDescriptors(); + for (int i = 0; i < methods.length; i++) { + Method m = methods[i].getMethod(); + Class[] paramTypes = m.getParameterTypes(); + if (paramTypes.length == 1) { + if (m.getName().equals("setText")) { + if (paramTypes[0].getName().equals(String.class.getName())) { + m.invoke(aComponent, new Object[] { value }); + } + } + if (m.getName().equals("setString")) { + if (paramTypes[0].getName().equals(String.class.getName())) { + m.invoke(aComponent, new Object[] { value }); + } + } + if (m.getName().equals("setValue")) { + if (paramTypes[0].getName().equals(Object.class.getName())) { + m.invoke(aComponent, new Object[] { value }); + } + } + } + } + } catch (Exception exc) { // error occured while introspecting... move along. + // FIXME: should log error in ErrorManager + System.out.println("InfoPanel.setValueForComponent: " + exc); + } + } -/** -* Gets the value in the field at the specified index. -* Note: If the component does not respond to getText() or getString() -* or getSelectedItem() this method will return null. JTextFields and the like will work. -* @param A valid index. -* @return An object representing the value in the field at the specified index, -* or null if the component does not have a text property or if the index is out of bounds. -*/ - public Object getValueForIndex( int anIndex ) - { - return getValueForIndex( anIndex, 0 ); - } + /** + * Gets the value in the field at the specified index. Note: If the component + * does not respond to getText() or getString() or getSelectedItem() this method + * will return null. JTextFields and the like will work. + * + * @param A valid index. + * @return An object representing the value in the field at the specified index, + * or null if the component does not have a text property or if the + * index is out of bounds. + */ + public Object getValueForIndex(int anIndex) { + return getValueForIndex(anIndex, 0); + } -/** -* Gets the value in the field at the specified row and column. -* Note: If the component does not respond to getText() or getString() -* or getSelectedItem() this method will return null. JTextFields and the like will work. -* @param A valid index. -* @return An object representing the value in the field at the specified index, -* or null if the component does not have a text property or if the index is out of bounds. -*/ - public Object getValueForIndex( int row, int col ) - { - if ( ( row >= fields.size() ) || ( row < 0 ) ) - { // out of bounds - return null; - } - - Container field = (Container) fields.get( row ); - Component c = field.getComponent( col ); - return getValueForComponent( c ); - } + /** + * Gets the value in the field at the specified row and column. Note: If the + * component does not respond to getText() or getString() or getSelectedItem() + * this method will return null. JTextFields and the like will work. + * + * @param A valid index. + * @return An object representing the value in the field at the specified index, + * or null if the component does not have a text property or if the + * index is out of bounds. + */ + public Object getValueForIndex(int row, int col) { + if ((row >= fields.size()) || (row < 0)) { // out of bounds + return null; + } -/** -* Gets the value in the field associated with the key. -* Note: If the component does not respond to getText() or getString() -* or getSelectedItem() this method will return null. JTextFields and the like will work. -* @param key An string representing the key associated with the field. Nulls are converted to an empty string. -* @return An object representing the value in the field associated with the key, -* or null if the key does not exist or if the component does not have a text property. -*/ - public Object getValueForKey( String key ) - { - return getValueForKey( key, 0 ); + Container field = (Container) fields.get(row); + Component c = field.getComponent(col); + return getValueForComponent(c); } -/** -* Gets the value in the field associated with the key. -* Note: If the component does not respond to getText() or getString() -* or getSelectedItem() this method will return null. JTextFields and the like will work. -* @param key An string representing the key associated with the field. Nulls are converted to an empty string. -* @return An object representing the value in the field associated with the key, -* or null if the key does not exist or if the component does not have a text property. -*/ - public Object getValueForKey( String key, int index ) - { - if ( key == null ) - { - key = ""; - } - - Container field = null; - Iterator enumeration = fields.iterator(); - while ( enumeration.hasNext() ) - { // finds first value in list with specified key - field = (Container) enumeration.next(); - if ( key.equals( field.getName() ) ) - { - Component c = field.getComponent( index ); - if ( c != null ) - { - return getValueForComponent( c ); + /** + * Gets the value in the field associated with the key. Note: If the component + * does not respond to getText() or getString() or getSelectedItem() this method + * will return null. JTextFields and the like will work. + * + * @param key An string representing the key associated with the field. Nulls + * are converted to an empty string. + * @return An object representing the value in the field associated with the + * key, or null if the key does not exist or if the component does not + * have a text property. + */ + public Object getValueForKey(String key) { + return getValueForKey(key, 0); + } + + /** + * Gets the value in the field associated with the key. Note: If the component + * does not respond to getText() or getString() or getSelectedItem() this method + * will return null. JTextFields and the like will work. + * + * @param key An string representing the key associated with the field. Nulls + * are converted to an empty string. + * @return An object representing the value in the field associated with the + * key, or null if the key does not exist or if the component does not + * have a text property. + */ + public Object getValueForKey(String key, int index) { + if (key == null) { + key = ""; + } + + Container field = null; + Iterator enumeration = fields.iterator(); + while (enumeration.hasNext()) { // finds first value in list with specified key + field = (Container) enumeration.next(); + if (key.equals(field.getName())) { + Component c = field.getComponent(index); + if (c != null) { + return getValueForComponent(c); } - } - } - // else not found - return null; - } + } + } + // else not found + return null; + } -/** -* Gets the value in the specified component. -* Note: If the component does not respond to getText() or getString() -* or getSelectedItem() this method will return null. JTextFields and the like will work. -* @param aComponent The specified component. -* @return An object representing the value in the component. -* or null if the component does not have a text property. -*/ - protected Object getValueForComponent( Component aComponent ) - { - // try to get a text or string property - try - { - BeanInfo info = Introspector.getBeanInfo( aComponent.getClass() ); - MethodDescriptor[] methods = info.getMethodDescriptors(); - for ( int i = 0; i < methods.length; i++ ) - { - Method m = methods[i].getMethod(); - Class[] paramTypes = m.getParameterTypes(); - if ( m.getName().equals( "getText" ) ) - { - if ( paramTypes.length == 0 ) - { - return m.invoke( aComponent, new Object[] {} ); - } - } - if ( m.getName().equals( "getString" ) ) - { - if ( paramTypes.length == 0 ) - { - return m.invoke( aComponent, new Object[] {} ); - } - } - if ( m.getName().equals( "getSelectedItem" ) ) - { - if ( paramTypes.length == 0 ) - { - return m.invoke( aComponent, new Object[] {} ); - } - } - // TODO: should also handle variants of setValue() - } - } - catch ( Exception exc ) - { // error occured while introspecting... move along. - System.out.println( "InfoPanel.getValueFromComponent: " + exc ); - } - - // not found - return null; - } + /** + * Gets the value in the specified component. Note: If the component does not + * respond to getText() or getString() or getSelectedItem() this method will + * return null. JTextFields and the like will work. + * + * @param aComponent The specified component. + * @return An object representing the value in the component. or null if the + * component does not have a text property. + */ + protected Object getValueForComponent(Component aComponent) { + // try to get a text or string property + try { + BeanInfo info = Introspector.getBeanInfo(aComponent.getClass()); + MethodDescriptor[] methods = info.getMethodDescriptors(); + for (int i = 0; i < methods.length; i++) { + Method m = methods[i].getMethod(); + Class[] paramTypes = m.getParameterTypes(); + if (m.getName().equals("getText")) { + if (paramTypes.length == 0) { + return m.invoke(aComponent, new Object[] {}); + } + } + if (m.getName().equals("getString")) { + if (paramTypes.length == 0) { + return m.invoke(aComponent, new Object[] {}); + } + } + if (m.getName().equals("getSelectedItem")) { + if (paramTypes.length == 0) { + return m.invoke(aComponent, new Object[] {}); + } + } + // TODO: should also handle variants of setValue() + } + } catch (Exception exc) { // error occured while introspecting... move along. + System.out.println("InfoPanel.getValueFromComponent: " + exc); + } -/** -* Gets the component associated with the key as a JTextField, for backwards compatibility. -* @param key A string representing the key associated with the component. Nulls are converted to an empty string. -* @return A JTextField that contains the value associated with the key, -* or null if the key does not exist or if the component is not a JTextField. -*/ - public JTextField getFieldForKey( String key ) - { - Component c = getComponentForKey( key ); - if ( c instanceof JTextField ) - { - return (JTextField) c; - } - return null; - } + // not found + return null; + } -/** -* Gets the component associated with the key. If more than one component is associated -* with the key, returns the first such component. -* @param key A string representing the key associated with the component. -* Nulls are converted to an empty string. -* @return A component that contains the value associated with the key, -* or null if the key does not exist. -*/ - public Component getComponentForKey( String key ) - { - return getComponentForKey( key, 0 ); + /** + * Gets the component associated with the key as a JTextField, for backwards + * compatibility. + * + * @param key A string representing the key associated with the component. Nulls + * are converted to an empty string. + * @return A JTextField that contains the value associated with the key, or null + * if the key does not exist or if the component is not a JTextField. + */ + public JTextField getFieldForKey(String key) { + Component c = getComponentForKey(key); + if (c instanceof JTextField) { + return (JTextField) c; + } + return null; } -/** -* Gets the component associated with the key and index. -* @param key A string representing the key associated with the component. -* Nulls are converted to an empty string. -* @return A component that contains the value associated with the key, -* or null if the key does not exist. -*/ - public Component getComponentForKey( String key, int index ) - { - Container c = getCompositeComponentForKey( key ); - if ( c == null ) return null; - return c.getComponent( index ); - } + /** + * Gets the component associated with the key. If more than one component is + * associated with the key, returns the first such component. + * + * @param key A string representing the key associated with the component. Nulls + * are converted to an empty string. + * @return A component that contains the value associated with the key, or null + * if the key does not exist. + */ + public Component getComponentForKey(String key) { + return getComponentForKey(key, 0); + } -/** -* Gets the component at the specified row. If more than one component exists -* on that row, returns the first such component. -* @return A component or null if the row does not exist. -*/ - public Object getComponentForIndex( int row ) - { - return getComponentForIndex( row, 0 ); - } + /** + * Gets the component associated with the key and index. + * + * @param key A string representing the key associated with the component. Nulls + * are converted to an empty string. + * @return A component that contains the value associated with the key, or null + * if the key does not exist. + */ + public Component getComponentForKey(String key, int index) { + Container c = getCompositeComponentForKey(key); + if (c == null) + return null; + return c.getComponent(index); + } -/** -* Gets the component at the specified row and column. -* @return A component or null if the index is out of bounds. -*/ - public Object getComponentForIndex( int row, int col ) - { - if ( ( row > fields.size() ) || ( row < 0 ) ) - { // out of bounds - return null; - } - - Container field = (Container) fields.get( row ); - return field.getComponent( col ); - } + /** + * Gets the component at the specified row. If more than one component exists on + * that row, returns the first such component. + * + * @return A component or null if the row does not exist. + */ + public Object getComponentForIndex(int row) { + return getComponentForIndex(row, 0); + } -/** -* Gets the container associated with the key. -* @param key A string representing the key associated with the component. -* Nulls are converted to an empty string. -* @return A component that contains the value associated with the key, -* or null if the key does not exist. -*/ - protected Container getCompositeComponentForKey( String key ) - { - if ( key == null ) - { - key = ""; - } - - JPanel field = null; - Iterator enumeration = fields.iterator(); - while ( enumeration.hasNext() ) - { // finds first value in list with specified key - field = (JPanel) enumeration.next(); - if ( key.equals( field.getName() ) ) - { - return field; - } - } - - // else not found - return null; - } + /** + * Gets the component at the specified row and column. + * + * @return A component or null if the index is out of bounds. + */ + public Object getComponentForIndex(int row, int col) { + if ((row > fields.size()) || (row < 0)) { // out of bounds + return null; + } -/** -* Provided for backwards compatibility: calls getLabelComponentForKey. -* @param key A string representing the key associated with the compoent. -* Nulls are converted to an empty string. -* @return Component label object associated with the key, or null if the key does not exist -* or if the label component is not an instance of JLabel. -*/ - public JLabel getLabelForKey( String key ) - { - Component result = getLabelComponentForKey( key ); - if ( result instanceof JLabel ) return (JLabel) result; - return null; - } - -/** -* Get the label component associated with the key. -* @param key A string representing the key associated with the compoent. -* Nulls are converted to an empty string. -* @return Component label object associated with the key, or null if the key does not exist. -*/ - public Component getLabelComponentForKey( String key ) - { - if ( key == null ) - { - key = ""; - } - - Component label = null; - Iterator enumeration = labels.iterator(); - while ( enumeration.hasNext() ) - { // finds first value in list with specified key - label = (Component) enumeration.next(); - if ( key.equals( label.getName() ) ) - { - return label; - } - } - - // else not found - return null; - } + Container field = (Container) fields.get(row); + return field.getComponent(col); + } -/** -* Replaces the first component associated with the key. Any value in the existing -* component will be copied to the new component. -* @param key A string representing the key to be associated with the component. -* Nulls are converted to an empty string. -* @param c A component to be placed next to the label corresponding to the key. -* Nulls are converted to a JTextField. -*/ - public void setComponentForKey( String key, Component c ) - { - setComponentForKey( key, c, 0 ); + /** + * Gets the container associated with the key. + * + * @param key A string representing the key associated with the component. Nulls + * are converted to an empty string. + * @return A component that contains the value associated with the key, or null + * if the key does not exist. + */ + protected Container getCompositeComponentForKey(String key) { + if (key == null) { + key = ""; + } + + JPanel field = null; + Iterator enumeration = fields.iterator(); + while (enumeration.hasNext()) { // finds first value in list with specified key + field = (JPanel) enumeration.next(); + if (key.equals(field.getName())) { + return field; + } + } + + // else not found + return null; } - -/** -* Replaces the component associated with the key. Any value in the existing -* component will be copied to the new component. -* @param key A string representing the key to be associated with the component. -* Nulls are converted to an empty string. -* @param c A component to be placed next to the label corresponding to the key. -* Nulls are converted to a JTextField. -*/ - public void setComponentForKey( String key, Component c, int index ) - { - if ( c == null ) - { - c = new JTextField( 15 ); - } - if ( key == null ) - { - key = ""; - } - - Container container = this.getCompositeComponentForKey( key ); - Component field = container.getComponent( index ); - Object value = this.getValueForKey( key, index ); - if ( field != null ) - { - container.remove( index ); - container.add( c, index ); - c.setEnabled( this.isEditable ); - introspectComponent( c, key ); - setValueForComponent( c, value ); - } - } -/** -* Replaces the first component in the specified row. Any value in the existing -* component will be copied to the new component. -* @param row A valid index. -* @param c A component to be placed next to the label corresponding to the key. -*/ - public void setComponentForIndex( int row, Component c ) - { - setComponentForIndex( row, 0, c ); + /** + * Provided for backwards compatibility: calls getLabelComponentForKey. + * + * @param key A string representing the key associated with the compoent. Nulls + * are converted to an empty string. + * @return Component label object associated with the key, or null if the key + * does not exist or if the label component is not an instance of + * JLabel. + */ + public JLabel getLabelForKey(String key) { + Component result = getLabelComponentForKey(key); + if (result instanceof JLabel) + return (JLabel) result; + return null; } - -/** -* Replaces the component associated with the key. Any value in the existing -* component will be copied to the new component. -* @param row A valid index. -* @param c A component to be placed next to the label corresponding to the key. -*/ - public void setComponentForIndex( int row, int col, Component c ) - { - setComponentForKey( getLabels()[row], c, col ); - } -/** -* Sets the string that appears before each label's text on the panel. -* @param aString A String to be used as the label prefix. -*/ - public void setLabelPrefix( String aString ) - { - prefix = aString; - setLabels( getLabels() ); // force refresh - } + /** + * Get the label component associated with the key. + * + * @param key A string representing the key associated with the compoent. Nulls + * are converted to an empty string. + * @return Component label object associated with the key, or null if the key + * does not exist. + */ + public Component getLabelComponentForKey(String key) { + if (key == null) { + key = ""; + } -/** -* Gets the string that appears before each label's text on the panel. -* Defaults to "", an empty string. -* @return A String that is currently used as the label prefix. -*/ - public String getLabelPrefix() - { - return prefix; - } + Component label = null; + Iterator enumeration = labels.iterator(); + while (enumeration.hasNext()) { // finds first value in list with specified key + label = (Component) enumeration.next(); + if (key.equals(label.getName())) { + return label; + } + } -/** -* Sets the string that appears after each label's text on the panel. -* Defaults to ": ", a colon followed by a space. -* @param aString A String to be used as the label postfix. -*/ - public void setLabelPostfix( String aString ) - { - postfix = aString; - setLabels( getLabels() ); // force refresh - } + // else not found + return null; + } -/** -* Gets the string that appears after each label's text on the panel. -* @return A String that is currently used as the label postfix. -*/ - public String getLabelPostfix() - { - return postfix; - } + /** + * Replaces the first component associated with the key. Any value in the + * existing component will be copied to the new component. + * + * @param key A string representing the key to be associated with the component. + * Nulls are converted to an empty string. + * @param c A component to be placed next to the label corresponding to the + * key. Nulls are converted to a JTextField. + */ + public void setComponentForKey(String key, Component c) { + setComponentForKey(key, c, 0); + } -/** -* Adds an action listener to the list that will be -* notified by events occurring in the panel. -* @param l An action listener to be notified. -*/ - public void addActionListener(ActionListener l) - { - actionListener = AWTEventMulticaster.add(actionListener, l); - } -/** -* Removes an action listener from the list that will be -* notified by events occurring in the panel. -* @param l An action listener to be removed. -*/ - public void removeActionListener(ActionListener l) - { - actionListener = AWTEventMulticaster.remove(actionListener, l); - } -/** -* Notifies all registered action listeners of a pending Action Event. -* @param e An action event to be broadcast. -*/ - protected void broadcastEvent(ActionEvent e) - { - if (actionListener != null) - { - actionListener.actionPerformed(e); - } - } + /** + * Replaces the component associated with the key. Any value in the existing + * component will be copied to the new component. + * + * @param key A string representing the key to be associated with the component. + * Nulls are converted to an empty string. + * @param c A component to be placed next to the label corresponding to the + * key. Nulls are converted to a JTextField. + */ + public void setComponentForKey(String key, Component c, int index) { + if (c == null) { + c = new JTextField(15); + } + if (key == null) { + key = ""; + } - // interface ActionListener + Container container = this.getCompositeComponentForKey(key); + Component field = container.getComponent(index); + Object value = this.getValueForKey(key, index); + if (field != null) { + container.remove(index); + container.add(c, index); + c.setEnabled(this.isEditable); + introspectComponent(c, key); + setValueForComponent(c, value); + } + } -/** -* Called by buttons on panel and by other components that -* might be set to broadcast events to this listener. -* Simply forwards the action event unchanged. -* @param e An action event to be received. -*/ - public void actionPerformed(ActionEvent e) - { + /** + * Replaces the first component in the specified row. Any value in the existing + * component will be copied to the new component. + * + * @param row A valid index. + * @param c A component to be placed next to the label corresponding to the + * key. + */ + public void setComponentForIndex(int row, Component c) { + setComponentForIndex(row, 0, c); + } + + /** + * Replaces the component associated with the key. Any value in the existing + * component will be copied to the new component. + * + * @param row A valid index. + * @param c A component to be placed next to the label corresponding to the + * key. + */ + public void setComponentForIndex(int row, int col, Component c) { + setComponentForKey(getLabels()[row], c, col); + } + + /** + * Sets the string that appears before each label's text on the panel. + * + * @param aString A String to be used as the label prefix. + */ + public void setLabelPrefix(String aString) { + prefix = aString; + setLabels(getLabels()); // force refresh + } + + /** + * Gets the string that appears before each label's text on the panel. Defaults + * to "", an empty string. + * + * @return A String that is currently used as the label prefix. + */ + public String getLabelPrefix() { + return prefix; + } + + /** + * Sets the string that appears after each label's text on the panel. Defaults + * to ": ", a colon followed by a space. + * + * @param aString A String to be used as the label postfix. + */ + public void setLabelPostfix(String aString) { + postfix = aString; + setLabels(getLabels()); // force refresh + } + + /** + * Gets the string that appears after each label's text on the panel. + * + * @return A String that is currently used as the label postfix. + */ + public String getLabelPostfix() { + return postfix; + } + + /** + * Adds an action listener to the list that will be notified by events occurring + * in the panel. + * + * @param l An action listener to be notified. + */ + public void addActionListener(ActionListener l) { + actionListener = AWTEventMulticaster.add(actionListener, l); + } + + /** + * Removes an action listener from the list that will be notified by events + * occurring in the panel. + * + * @param l An action listener to be removed. + */ + public void removeActionListener(ActionListener l) { + actionListener = AWTEventMulticaster.remove(actionListener, l); + } + + /** + * Notifies all registered action listeners of a pending Action Event. + * + * @param e An action event to be broadcast. + */ + protected void broadcastEvent(ActionEvent e) { + if (actionListener != null) { + actionListener.actionPerformed(e); + } + } + + // interface ActionListener + + /** + * Called by buttons on panel and by other components that might be set to + * broadcast events to this listener. Simply forwards the action event + * unchanged. + * + * @param e An action event to be received. + */ + public void actionPerformed(ActionEvent e) { // if ( e.getSource() instanceof AbstractButton ) // { - broadcastEvent(e); + broadcastEvent(e); // } - } - - /** - * GridBagLayout allocates weightx only after considering - * the preferred width of the components in a column. - * We'd prefer that preferred width wasn't considered, - * so that the layout worked more like a html-table. - * GridBagLayout is poorly factored for subclassing, - * so this code is going to get a little bit ugly. - * Really, what good is a protected method that returns - * a private class? Would have liked to just override - * getLayoutInfo and be done with it. - */ - private class BetterGridBagLayout extends GridBagLayout - { - public Dimension preferredLayoutSize(Container parent) - { - preprocess(); - return super.preferredLayoutSize( parent ); - } - - public Dimension minimumLayoutSize(Container parent) - { - preprocess(); - return super.minimumLayoutSize( parent ); - } - - - public void layoutContainer(Container parent) - { - preprocess(); - super.layoutContainer( parent ); - } - - protected void preprocess() - { - if ( fieldSpacers == null ) return; - Iterator i; - - // find the field with the widest preferred size - Component c; - int maxWidth = 0; - i = fields.iterator(); - while ( i.hasNext() ) - { - c = (Component) i.next(); - maxWidth = Math.max( maxWidth, - Math.max( c.getPreferredSize().width, c.getMinimumSize().width ) ); - } - - // set each column's spacers to that preferred size - Dimension min = new Dimension( 0, 0 ); - Dimension pref = new Dimension( maxWidth, 0 ); - i = fieldSpacers.iterator(); - while ( i.hasNext() ) - { - ((Box.Filler)i.next()).changeShape( min, pref, pref ); - } - } - } -} + } + + /** + * GridBagLayout allocates weightx only after considering the preferred width of + * the components in a column. We'd prefer that preferred width wasn't + * considered, so that the layout worked more like a html-table. GridBagLayout + * is poorly factored for subclassing, so this code is going to get a little bit + * ugly. Really, what good is a protected method that returns a private class? + * Would have liked to just override getLayoutInfo and be done with it. + */ + private class BetterGridBagLayout extends GridBagLayout { + public Dimension preferredLayoutSize(Container parent) { + preprocess(); + return super.preferredLayoutSize(parent); + } + + public Dimension minimumLayoutSize(Container parent) { + preprocess(); + return super.minimumLayoutSize(parent); + } + + public void layoutContainer(Container parent) { + preprocess(); + super.layoutContainer(parent); + } + + protected void preprocess() { + if (fieldSpacers == null) + return; + Iterator i; + + // find the field with the widest preferred size + Component c; + int maxWidth = 0; + i = fields.iterator(); + while (i.hasNext()) { + c = (Component) i.next(); + maxWidth = Math.max(maxWidth, Math.max(c.getPreferredSize().width, c.getMinimumSize().width)); + } + // set each column's spacers to that preferred size + Dimension min = new Dimension(0, 0); + Dimension pref = new Dimension(maxWidth, 0); + i = fieldSpacers.iterator(); + while (i.hasNext()) { + ((Box.Filler) i.next()).changeShape(min, pref, pref); + } + } + } +} diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/KeyDelayTimer.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/KeyDelayTimer.java index b73c74d..31bdb70 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/KeyDelayTimer.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/KeyDelayTimer.java @@ -28,161 +28,150 @@ import java.awt.event.KeyListener; import javax.swing.Timer; /** -* KeyDelayTimer is a utility that listens for KeyEvents from one -* or more components. After receiving a KeyEvents the timer will -* broadcast an action event if a specified time interval passes without -* a subsequent KeyEvent.<BR><BR> -* -* This utility is useful for implementing any kind of auto-complete -* feature in a user interface. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -* $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ -*/ -public class KeyDelayTimer implements ActionListener, KeyListener -{ - // delay timer for keypress-sensitve events - protected Timer keyTimer = null; - protected Component lastFieldTouched = null; - protected long timeLastFieldTouched = 0; - protected int interval = 400; // adjust as needed - - // for action multicasting - protected ActionListener actionListener = null; - -/** -* Default constructor. -*/ - public KeyDelayTimer() - { - keyTimer = new Timer( interval, this ); - } - -/** -* Convenience constructor. -* @param listener An action listener to be notified of delay events. -*/ - public KeyDelayTimer( ActionListener listener ) - { - this(); - addActionListener( listener ); - } - -/** -* Returns the last component that generated a KeyEvent. -* @return The component that sent the most recent KeyEvent. -*/ - public Component getComponent() - { - return lastFieldTouched; - } - -/** -* Returns the number of milliseconds before an ActionEvent is generated. -* The default is 400. -* @return The current delay interval in milliseconds. -*/ - public int getInterval() - { - return interval; - } - -/** -* Sets the number of milliseconds before an ActionEvent will be generated -* after a KeyEvent is received. -* @param millis The new delay interval in milliseconds. -*/ - public void setInterval( int millis ) - { - interval = millis; - keyTimer.setDelay( interval / 2 ); - } - - // interface KeyListener - - public void keyTyped(KeyEvent e) - { - } - public void keyPressed(KeyEvent e) - { - } - -/** -* Receives key events from one or more components. -* Records the component and the time this event was received, -* then starts the timer. -* @param e The key event in question. -*/ - public void keyReleased(KeyEvent e) - { // handles keystrokes in the textfields (except ENTER and ESCAPE) - if ( ( Character.isLetterOrDigit( e.getKeyChar() ) ) - || ( e.getKeyCode() == KeyEvent.VK_SPACE ) - || ( e.getKeyCode() == KeyEvent.VK_DELETE ) - || ( e.getKeyCode() == KeyEvent.VK_BACK_SPACE ) ) - { - this.lastFieldTouched = e.getComponent(); - this.timeLastFieldTouched = System.currentTimeMillis(); - this.keyTimer.start(); - return; - } - } - - // interface ActionListener - -/** -* Receives ActionEvents from the internal timer. -* If the interval has passed without another KeyEvent, -* an ActionEvent is broadcast, with the name of this class -* as the ActionCommand, and the internal timer is stopped. -* @param e The action event in question. -*/ - public void actionPerformed(ActionEvent e) - { - if ( e.getSource() == keyTimer ) - { - if ( System.currentTimeMillis() - this.timeLastFieldTouched > interval ) - { - this.keyTimer.stop(); - broadcastEvent( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, this.getClass().getName() ) ); - } - return; - } - } - - // Action Multicast methods - -/** -* Adds an action listener to the list that will be -* notified by button events and changes in button state. -* @param l An action listener to be notified. -*/ - public void addActionListener(ActionListener l) - { - actionListener = AWTEventMulticaster.add(actionListener, l); - } -/** -* Removes an action listener from the list that will be -* notified by button events and changes in button state. -* @param l An action listener to be removed. -*/ - public void removeActionListener(ActionListener l) - { - actionListener = AWTEventMulticaster.remove(actionListener, l); - } -/** -* Notifies all registered action listeners of a pending Action Event. -* @param e An action event to be broadcast. -*/ - protected void broadcastEvent(ActionEvent e) - { - if (actionListener != null) - { - actionListener.actionPerformed(e); - } - } + * KeyDelayTimer is a utility that listens for KeyEvents from one or more + * components. After receiving a KeyEvents the timer will broadcast an action + * event if a specified time interval passes without a subsequent KeyEvent.<BR> + * <BR> + * + * This utility is useful for implementing any kind of auto-complete feature in + * a user interface. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ + */ +public class KeyDelayTimer implements ActionListener, KeyListener { + // delay timer for keypress-sensitve events + protected Timer keyTimer = null; + protected Component lastFieldTouched = null; + protected long timeLastFieldTouched = 0; + protected int interval = 400; // adjust as needed + + // for action multicasting + protected ActionListener actionListener = null; + + /** + * Default constructor. + */ + public KeyDelayTimer() { + keyTimer = new Timer(interval, this); + } + + /** + * Convenience constructor. + * + * @param listener An action listener to be notified of delay events. + */ + public KeyDelayTimer(ActionListener listener) { + this(); + addActionListener(listener); + } + + /** + * Returns the last component that generated a KeyEvent. + * + * @return The component that sent the most recent KeyEvent. + */ + public Component getComponent() { + return lastFieldTouched; + } + + /** + * Returns the number of milliseconds before an ActionEvent is generated. The + * default is 400. + * + * @return The current delay interval in milliseconds. + */ + public int getInterval() { + return interval; + } + + /** + * Sets the number of milliseconds before an ActionEvent will be generated after + * a KeyEvent is received. + * + * @param millis The new delay interval in milliseconds. + */ + public void setInterval(int millis) { + interval = millis; + keyTimer.setDelay(interval / 2); + } + + // interface KeyListener + + public void keyTyped(KeyEvent e) { + } + + public void keyPressed(KeyEvent e) { + } + + /** + * Receives key events from one or more components. Records the component and + * the time this event was received, then starts the timer. + * + * @param e The key event in question. + */ + public void keyReleased(KeyEvent e) { // handles keystrokes in the textfields (except ENTER and ESCAPE) + if ((Character.isLetterOrDigit(e.getKeyChar())) || (e.getKeyCode() == KeyEvent.VK_SPACE) + || (e.getKeyCode() == KeyEvent.VK_DELETE) || (e.getKeyCode() == KeyEvent.VK_BACK_SPACE)) { + this.lastFieldTouched = e.getComponent(); + this.timeLastFieldTouched = System.currentTimeMillis(); + this.keyTimer.start(); + return; + } + } + + // interface ActionListener + + /** + * Receives ActionEvents from the internal timer. If the interval has passed + * without another KeyEvent, an ActionEvent is broadcast, with the name of this + * class as the ActionCommand, and the internal timer is stopped. + * + * @param e The action event in question. + */ + public void actionPerformed(ActionEvent e) { + if (e.getSource() == keyTimer) { + if (System.currentTimeMillis() - this.timeLastFieldTouched > interval) { + this.keyTimer.stop(); + broadcastEvent(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, this.getClass().getName())); + } + return; + } + } + + // Action Multicast methods + + /** + * Adds an action listener to the list that will be notified by button events + * and changes in button state. + * + * @param l An action listener to be notified. + */ + public void addActionListener(ActionListener l) { + actionListener = AWTEventMulticaster.add(actionListener, l); + } + + /** + * Removes an action listener from the list that will be notified by button + * events and changes in button state. + * + * @param l An action listener to be removed. + */ + public void removeActionListener(ActionListener l) { + actionListener = AWTEventMulticaster.remove(actionListener, l); + } + + /** + * Notifies all registered action listeners of a pending Action Event. + * + * @param e An action event to be broadcast. + */ + protected void broadcastEvent(ActionEvent e) { + if (actionListener != null) { + actionListener.actionPerformed(e); + } + } } - - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/KeyableCellEditor.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/KeyableCellEditor.java index 95b8a19..f64e607 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/KeyableCellEditor.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/KeyableCellEditor.java @@ -40,311 +40,265 @@ import javax.swing.event.ChangeEvent; import javax.swing.table.TableCellEditor; /** -* A table cell editor customized for keyboard navigation, much like -* working with a spreadsheet. The default cell editor unfortunately -* does none of these things: -* <ul> -* <li> Selects text on start of editing. -* <li> Up and down keys move edit cell up and down. -* <li> Right and left keys move cell when selection caret is at end of text. -* <li> Escape cancels editing. -* <li> Enter commits edit. -* <li> Edits are properly committed on lost focus. -* <li> Tab and shift-tab work as expected. -* <li> Cell selection moves with the edit cell. -* </ul> -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -* $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ -*/ -public class KeyableCellEditor implements TableCellEditor, FocusListener, - KeyListener, Serializable -{ - List listeners; - JTextField textField; - Object lastValue; - Format currentFormat; - - JTable table; - -/** -* Default constructor - a standard JTextField will be used for editing. -*/ - public KeyableCellEditor() - { - this( (JTextField) null ); - } - -/** -* Constructor specifying a type of JTextField to be used for editing. -* The JTextField will have its border replaced with a black line border. -* @param aTextField A JTextField or subclass for editing values. -*/ - public KeyableCellEditor( JTextField aTextField ) - { - listeners = new Vector(); - lastValue = null; - - // default to stock JTextField - textField = aTextField; - if ( textField == null ) - { - textField = new JTextField(); - } - - textField.setBorder(new LineBorder(Color.black)); - - // handle arrow keys while caret is showing - textField.addKeyListener( this ); - - // handle lost focus - textField.addFocusListener( this ); - } - - public Component getTableCellEditorComponent(JTable table, - Object value, - boolean isSelected, - int row, - int column) - { - this.table = table; - table.removeKeyListener( this ); // if any - table.addKeyListener( this ); - return getEditorComponent( value ); - } - - protected Component getEditorComponent( Object value ) - { - if ( value != null ) - { - textField.setText( value.toString() ); - } - else - { - textField.setText( "" ); - } - - if ( value instanceof Number ) - { - textField.setHorizontalAlignment(JTextField.RIGHT); - } - else - { - textField.setHorizontalAlignment(JTextField.LEFT); - } - - // remember original value - lastValue = value; - - // select all text and get focus - textField.selectAll(); - textField.requestFocus(); - - return textField; - } - - public Object getCellEditorValue() - { - return lastValue; - } - - public boolean isCellEditable(EventObject anEvent) - { - // key events should replace the selection - // NOTE: For whatever reason, key events trigger result in a null parameter - if ( anEvent == null ) - { - textField.setText(""); - textField.requestFocus(); - return true; - } - - return true; - } - - public boolean shouldSelectCell(EventObject anEvent) - { // System.out.println( "KeyableCellEditor.shouldSelectCell: " + anEvent ); - - // key events should replace the selection - // NOTE: For whatever reason, key events are not generated - if ( anEvent instanceof KeyEvent ) - { - textField.setText(""); - textField.requestFocus(); - return true; - } - - // otherwise, select all text and continue - textField.selectAll(); - textField.requestFocus(); - - return true; - } - - public boolean stopCellEditing() - { - lastValue = textField.getText(); - fireEditingStopped(); - table.removeKeyListener( this ); // if any - return true; - } - - public void cancelCellEditing() - { - fireEditingCanceled(); - table.removeKeyListener( this ); // if any - } - - public void addCellEditorListener(CellEditorListener l) - { - listeners.add( l ); - } - - public void removeCellEditorListener(CellEditorListener l) - { - listeners.remove( l ); - } - - protected void fireEditingCanceled() - { - ChangeEvent event = new ChangeEvent( this ); - Iterator it = new ArrayList( listeners ).iterator(); // copy to prevent modification exception - while ( it.hasNext() ) - { - ((CellEditorListener)it.next()).editingCanceled( event ); - } - } - - protected void fireEditingStopped() - { - ChangeEvent event = new ChangeEvent( this ); - Iterator it = new ArrayList( listeners ).iterator(); // copy to prevent modification exception - while ( it.hasNext() ) - { - ((CellEditorListener)it.next()).editingStopped( event ); - } - } - - protected void onEnterKey() - { - stopCellEditing(); - } - - protected void onEscapeKey() - { - cancelCellEditing(); - } - - protected void moveEditCell( int dRow, int dCol ) - { - if ( table == null ) return; - int row = table.getSelectedRow() + dRow; - int col = table.getSelectedColumn() + dCol; - - row = Math.max( 0, row ); - row = Math.min( row, table.getRowCount() - 1 ); - col = Math.max( 0, col ); - col = Math.min( col, table.getColumnCount() - 1 ); - - stopCellEditing(); - table.setRowSelectionInterval( row, row ); - table.setColumnSelectionInterval( col, col ); - table.editCellAt( row, col ); - textField.selectAll(); - textField.requestFocus(); - } - - // interface KeyListener - - public void keyTyped(KeyEvent e) - { // System.out.println( "KeyableCellEditor.keyTyped: " + KeyEvent.getKeyText( e.getKeyCode() ) ); - } - - public void keyPressed(KeyEvent e) - { // System.out.println( "KeyableCellEditor.keyPressed: " + KeyEvent.getKeyText( e.getKeyCode() ) ); - - // catch LEFT and RIGHT here before JTextField consumes them - - int keyCode = e.getKeyCode(); - if ( keyCode == KeyEvent.VK_LEFT ) - { - if ( textField.getSelectionStart() == 0 ) - { - moveEditCell( 0, -1 ); - e.consume(); - return; - } - } - if ( keyCode == KeyEvent.VK_RIGHT ) - { - if ( textField.getSelectionEnd() == textField.getText().length() ) - { - moveEditCell( 0, 1 ); - e.consume(); - return; - } - } - if ( keyCode == KeyEvent.VK_UP ) - { - moveEditCell( -1, 0 ); - e.consume(); - return; - } - if ( keyCode == KeyEvent.VK_DOWN ) - { - moveEditCell( 1, 0 ); - e.consume(); - return; - } - } - - public void keyReleased(KeyEvent e) - { // System.out.println( "KeyableCellEditor.keyReleased: " + KeyEvent.getKeyText( e.getKeyCode() ) ); - - // catch ENTER here to allow JTextField to process it as well - - int keyCode = e.getKeyCode(); - if ( keyCode == KeyEvent.VK_ENTER ) - { - onEnterKey(); - return; - } - if ( keyCode == KeyEvent.VK_ESCAPE ) - { - onEscapeKey(); - return; - } - - // tabs are apparently only received on key release - if ( keyCode == KeyEvent.VK_TAB ) - { - if ( e.isShiftDown() ) - { - moveEditCell( 0, -1 ); - } - else - { - moveEditCell( 0, 1 ); - } - e.consume(); - return; - } - - } - - // interface FocusListener - - public void focusGained(FocusEvent e) - { // System.out.println( "focusGained: " ); - } - - public void focusLost(FocusEvent e) - { // System.out.println( "focusLost: " ); - stopCellEditing(); - } + * A table cell editor customized for keyboard navigation, much like working + * with a spreadsheet. The default cell editor unfortunately does none of these + * things: + * <ul> + * <li>Selects text on start of editing. + * <li>Up and down keys move edit cell up and down. + * <li>Right and left keys move cell when selection caret is at end of text. + * <li>Escape cancels editing. + * <li>Enter commits edit. + * <li>Edits are properly committed on lost focus. + * <li>Tab and shift-tab work as expected. + * <li>Cell selection moves with the edit cell. + * </ul> + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ + */ +public class KeyableCellEditor implements TableCellEditor, FocusListener, KeyListener, Serializable { + List listeners; + JTextField textField; + Object lastValue; + Format currentFormat; + + JTable table; + + /** + * Default constructor - a standard JTextField will be used for editing. + */ + public KeyableCellEditor() { + this((JTextField) null); + } + + /** + * Constructor specifying a type of JTextField to be used for editing. The + * JTextField will have its border replaced with a black line border. + * + * @param aTextField A JTextField or subclass for editing values. + */ + public KeyableCellEditor(JTextField aTextField) { + listeners = new Vector(); + lastValue = null; + + // default to stock JTextField + textField = aTextField; + if (textField == null) { + textField = new JTextField(); + } + + textField.setBorder(new LineBorder(Color.black)); + + // handle arrow keys while caret is showing + textField.addKeyListener(this); + + // handle lost focus + textField.addFocusListener(this); + } + + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { + this.table = table; + table.removeKeyListener(this); // if any + table.addKeyListener(this); + return getEditorComponent(value); + } + + protected Component getEditorComponent(Object value) { + if (value != null) { + textField.setText(value.toString()); + } else { + textField.setText(""); + } + + if (value instanceof Number) { + textField.setHorizontalAlignment(JTextField.RIGHT); + } else { + textField.setHorizontalAlignment(JTextField.LEFT); + } + + // remember original value + lastValue = value; + + // select all text and get focus + textField.selectAll(); + textField.requestFocus(); + + return textField; + } + + public Object getCellEditorValue() { + return lastValue; + } + + public boolean isCellEditable(EventObject anEvent) { + // key events should replace the selection + // NOTE: For whatever reason, key events trigger result in a null parameter + if (anEvent == null) { + textField.setText(""); + textField.requestFocus(); + return true; + } + + return true; + } + + public boolean shouldSelectCell(EventObject anEvent) { // System.out.println( "KeyableCellEditor.shouldSelectCell: " + // + anEvent ); + + // key events should replace the selection + // NOTE: For whatever reason, key events are not generated + if (anEvent instanceof KeyEvent) { + textField.setText(""); + textField.requestFocus(); + return true; + } + + // otherwise, select all text and continue + textField.selectAll(); + textField.requestFocus(); + + return true; + } + + public boolean stopCellEditing() { + lastValue = textField.getText(); + fireEditingStopped(); + table.removeKeyListener(this); // if any + return true; + } + + public void cancelCellEditing() { + fireEditingCanceled(); + table.removeKeyListener(this); // if any + } + + public void addCellEditorListener(CellEditorListener l) { + listeners.add(l); + } + + public void removeCellEditorListener(CellEditorListener l) { + listeners.remove(l); + } + + protected void fireEditingCanceled() { + ChangeEvent event = new ChangeEvent(this); + Iterator it = new ArrayList(listeners).iterator(); // copy to prevent modification exception + while (it.hasNext()) { + ((CellEditorListener) it.next()).editingCanceled(event); + } + } + + protected void fireEditingStopped() { + ChangeEvent event = new ChangeEvent(this); + Iterator it = new ArrayList(listeners).iterator(); // copy to prevent modification exception + while (it.hasNext()) { + ((CellEditorListener) it.next()).editingStopped(event); + } + } + + protected void onEnterKey() { + stopCellEditing(); + } + + protected void onEscapeKey() { + cancelCellEditing(); + } + + protected void moveEditCell(int dRow, int dCol) { + if (table == null) + return; + int row = table.getSelectedRow() + dRow; + int col = table.getSelectedColumn() + dCol; + + row = Math.max(0, row); + row = Math.min(row, table.getRowCount() - 1); + col = Math.max(0, col); + col = Math.min(col, table.getColumnCount() - 1); + + stopCellEditing(); + table.setRowSelectionInterval(row, row); + table.setColumnSelectionInterval(col, col); + table.editCellAt(row, col); + textField.selectAll(); + textField.requestFocus(); + } + + // interface KeyListener + + public void keyTyped(KeyEvent e) { // System.out.println( "KeyableCellEditor.keyTyped: " + KeyEvent.getKeyText( + // e.getKeyCode() ) ); + } + + public void keyPressed(KeyEvent e) { // System.out.println( "KeyableCellEditor.keyPressed: " + KeyEvent.getKeyText( + // e.getKeyCode() ) ); + + // catch LEFT and RIGHT here before JTextField consumes them + + int keyCode = e.getKeyCode(); + if (keyCode == KeyEvent.VK_LEFT) { + if (textField.getSelectionStart() == 0) { + moveEditCell(0, -1); + e.consume(); + return; + } + } + if (keyCode == KeyEvent.VK_RIGHT) { + if (textField.getSelectionEnd() == textField.getText().length()) { + moveEditCell(0, 1); + e.consume(); + return; + } + } + if (keyCode == KeyEvent.VK_UP) { + moveEditCell(-1, 0); + e.consume(); + return; + } + if (keyCode == KeyEvent.VK_DOWN) { + moveEditCell(1, 0); + e.consume(); + return; + } + } + + public void keyReleased(KeyEvent e) { // System.out.println( "KeyableCellEditor.keyReleased: " + + // KeyEvent.getKeyText( e.getKeyCode() ) ); + + // catch ENTER here to allow JTextField to process it as well + + int keyCode = e.getKeyCode(); + if (keyCode == KeyEvent.VK_ENTER) { + onEnterKey(); + return; + } + if (keyCode == KeyEvent.VK_ESCAPE) { + onEscapeKey(); + return; + } + + // tabs are apparently only received on key release + if (keyCode == KeyEvent.VK_TAB) { + if (e.isShiftDown()) { + moveEditCell(0, -1); + } else { + moveEditCell(0, 1); + } + e.consume(); + return; + } + + } + + // interface FocusListener + + public void focusGained(FocusEvent e) { // System.out.println( "focusGained: " ); + } + + public void focusLost(FocusEvent e) { // System.out.println( "focusLost: " ); + stopCellEditing(); + } } - - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/LineWrappingRenderer.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/LineWrappingRenderer.java index 4a7f07e..326d825 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/LineWrappingRenderer.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/LineWrappingRenderer.java @@ -31,124 +31,111 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; /** -* A list cell renderer that wraps its text to subsequent lines -* depending on the length of text string and the width of the -* parent list. -* -* This renderer depends on listening to the parent list's viewport -* and fixing the list's width to match the viewport's size. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @date $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ -* @revision $Revision: 904 $ -*/ -public class LineWrappingRenderer extends MultiLineLabel - implements ListCellRenderer, ChangeListener -{ - protected static Border noFocusBorder; - - protected JList list; - protected JViewport viewport; - protected int preferredWidth; - -/** -* Required constructor. The renderer keeps a reference to -* the list in which it is used and its viewport. This list -* is the only list that may use this renderer. The renderer -* will use the current size of the list to determine where -* lines will initially break. -* @param containerList The list that will be using this renderer. -*/ - public LineWrappingRenderer( JList containerList ) - { - super(); - setLineWrap(true); - noFocusBorder = new EmptyBorder(1, 1, 1, 1); - - list = containerList; - preferredWidth = 400; - if ( list.getParent() instanceof JViewport ) - { - viewport = (JViewport) list.getParent(); - viewport.addChangeListener( this ); - int newWidth = viewport.getExtentSize().width; - if ( newWidth > 0 ) preferredWidth = newWidth; - } - else - { - // should function adequately in absence of a viewport - // System.err.println( "LineWrappingRenderer.init: list.getParent = " + list.getParent() ); - } - } - -/** -* Returns the preferred size of the label, with width -* constrained to the current width. -* @return the size -*/ - public Dimension getPreferredSize() - { - int width = getWidth(); - if ( width != preferredWidth ) - { - // if component has not yet been placed within the list - if ( width < list.getWidth() / 2 ) width = list.getWidth(); - preferredWidth = width; - } - return new Dimension( preferredWidth, super.getPreferredSize().height ); - } - -/** -* Returns this component with the width set to the -* width of the specified JList. -* @return this component. -*/ - public Component getListCellRendererComponent ( - JList list, - Object value, - int index, - boolean isSelected, - boolean cellHasFocus ) - { // System.out.println( "LineWrappingRenderer.getListCellRendererComponent:" ); - - if ( list != this.list ) - { - System.err.println( "LineWrappingRenderer.getListCellRendererComponent: " + - "warning: the list using the renderer is not the list specified in the constructor." ); - } - - if (isSelected) - { - setBackground(this.list.getSelectionBackground()); - setForeground(this.list.getSelectionForeground()); - } - else - { - setBackground(this.list.getBackground()); - setForeground(this.list.getForeground()); - } - - setText((value == null) ? "" : value.toString()); - - setEnabled(this.list.isEnabled()); - setFont(this.list.getFont()); - setBorder( (cellHasFocus) ? UIManager.getBorder("List.focusCellHighlightBorder") : noFocusBorder ); - - return this; - } - -/** -* Overridden to respond to viewport changes. -*/ - public void stateChanged(ChangeEvent e) - { - int newWidth = viewport.getExtentSize().width; - if ( newWidth > 0 ) preferredWidth = newWidth; - - // set fixed width on list - list.setFixedCellWidth( preferredWidth ); - setSize( preferredWidth, super.getSize().height ); - } + * A list cell renderer that wraps its text to subsequent lines depending on the + * length of text string and the width of the parent list. + * + * This renderer depends on listening to the parent list's viewport and fixing + * the list's width to match the viewport's size. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @date $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ + * @revision $Revision: 904 $ + */ +public class LineWrappingRenderer extends MultiLineLabel implements ListCellRenderer, ChangeListener { + protected static Border noFocusBorder; + + protected JList list; + protected JViewport viewport; + protected int preferredWidth; + + /** + * Required constructor. The renderer keeps a reference to the list in which it + * is used and its viewport. This list is the only list that may use this + * renderer. The renderer will use the current size of the list to determine + * where lines will initially break. + * + * @param containerList The list that will be using this renderer. + */ + public LineWrappingRenderer(JList containerList) { + super(); + setLineWrap(true); + noFocusBorder = new EmptyBorder(1, 1, 1, 1); + + list = containerList; + preferredWidth = 400; + if (list.getParent() instanceof JViewport) { + viewport = (JViewport) list.getParent(); + viewport.addChangeListener(this); + int newWidth = viewport.getExtentSize().width; + if (newWidth > 0) + preferredWidth = newWidth; + } else { + // should function adequately in absence of a viewport + // System.err.println( "LineWrappingRenderer.init: list.getParent = " + + // list.getParent() ); + } + } + + /** + * Returns the preferred size of the label, with width constrained to the + * current width. + * + * @return the size + */ + public Dimension getPreferredSize() { + int width = getWidth(); + if (width != preferredWidth) { + // if component has not yet been placed within the list + if (width < list.getWidth() / 2) + width = list.getWidth(); + preferredWidth = width; + } + return new Dimension(preferredWidth, super.getPreferredSize().height); + } + + /** + * Returns this component with the width set to the width of the specified + * JList. + * + * @return this component. + */ + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, + boolean cellHasFocus) { // System.out.println( "LineWrappingRenderer.getListCellRendererComponent:" ); + + if (list != this.list) { + System.err.println("LineWrappingRenderer.getListCellRendererComponent: " + + "warning: the list using the renderer is not the list specified in the constructor."); + } + + if (isSelected) { + setBackground(this.list.getSelectionBackground()); + setForeground(this.list.getSelectionForeground()); + } else { + setBackground(this.list.getBackground()); + setForeground(this.list.getForeground()); + } + + setText((value == null) ? "" : value.toString()); + + setEnabled(this.list.isEnabled()); + setFont(this.list.getFont()); + setBorder((cellHasFocus) ? UIManager.getBorder("List.focusCellHighlightBorder") : noFocusBorder); + + return this; + } + + /** + * Overridden to respond to viewport changes. + */ + public void stateChanged(ChangeEvent e) { + int newWidth = viewport.getExtentSize().width; + if (newWidth > 0) + preferredWidth = newWidth; + + // set fixed width on list + list.setFixedCellWidth(preferredWidth); + setSize(preferredWidth, super.getSize().height); + } } diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/MultiLineLabel.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/MultiLineLabel.java index b5f8a9b..ce362c9 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/MultiLineLabel.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/MultiLineLabel.java @@ -23,113 +23,100 @@ import javax.swing.LookAndFeel; import javax.swing.text.Highlighter; /** -* A custom JTextArea that looks and feels like a JLabel, but supports -* line wrapping. This works a lot more like the IFC label component. -* NOTE: doesn't support icons (yet). -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @date $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ -* @revision $Revision: 904 $ -*/ -public class MultiLineLabel extends JTextArea -{ + * A custom JTextArea that looks and feels like a JLabel, but supports line + * wrapping. This works a lot more like the IFC label component. NOTE: doesn't + * support icons (yet). + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @date $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ + * @revision $Revision: 904 $ + */ +public class MultiLineLabel extends JTextArea { /** - * Saves a reference to the original highlighter - * to enable/disable text selection. - */ + * Saves a reference to the original highlighter to enable/disable text + * selection. + */ protected Highlighter originalHighlighter; - -/* -* Creates a MultiLineLabel instance with an empty string for the title. -*/ - public MultiLineLabel() - { - super(); - // turn on wrapping and disable editing and highlighting + /* + * Creates a MultiLineLabel instance with an empty string for the title. + */ + public MultiLineLabel() { + super(); - setLineWrap(true); - setWrapStyleWord(true); - setEditable(false); - setSelectable( false ); - } + // turn on wrapping and disable editing and highlighting -/* -* Creates a MultiLineLabel instance with the specified text. -* @param text The specified text. -*/ - public MultiLineLabel( String text ) - { - super( text ); + setLineWrap(true); + setWrapStyleWord(true); + setEditable(false); + setSelectable(false); + } - // turn on wrapping and disable editing and highlighting + /* + * Creates a MultiLineLabel instance with the specified text. + * + * @param text The specified text. + */ + public MultiLineLabel(String text) { + super(text); - setLineWrap(true); - setWrapStyleWord(true); - setEditable(false); - setSelectable( false ); - } + // turn on wrapping and disable editing and highlighting -/* -* Overridden to look like a label. -* @param text The specified text. -*/ - public void updateUI() - { - // got the implementation idea from usenet + setLineWrap(true); + setWrapStyleWord(true); + setEditable(false); + setSelectable(false); + } - super.updateUI(); + /* + * Overridden to look like a label. + * + * @param text The specified text. + */ + public void updateUI() { + // got the implementation idea from usenet - // turn on wrapping and disable editing and highlighting + super.updateUI(); - setLineWrap(true); - setWrapStyleWord(true); - setEditable(false); - setSelectable( false ); + // turn on wrapping and disable editing and highlighting - // Set the text area's border, colors and font to - // that of a label + setLineWrap(true); + setWrapStyleWord(true); + setEditable(false); + setSelectable(false); - LookAndFeel.installBorder(this, "Label.border"); + // Set the text area's border, colors and font to + // that of a label - LookAndFeel.installColorsAndFont(this, - "Label.background", - "Label.foreground", - "Label.font"); - } + LookAndFeel.installBorder(this, "Label.border"); -/** -* Sets whether text is selectable. -* Default is non-selectable text. -*/ - public void setSelectable( boolean selectable ) - { - if ( selectable ) - { - setHighlighter( originalHighlighter ); - } - else - { + LookAndFeel.installColorsAndFont(this, "Label.background", "Label.foreground", "Label.font"); + } + + /** + * Sets whether text is selectable. Default is non-selectable text. + */ + public void setSelectable(boolean selectable) { + if (selectable) { + setHighlighter(originalHighlighter); + } else { originalHighlighter = getHighlighter(); - setHighlighter( null ); + setHighlighter(null); } } - -/** -* Gets whether text is selectable. -* Default is non-selectable text. -*/ - public boolean isSelectable() - { - return ( getHighlighter() != null ); + + /** + * Gets whether text is selectable. Default is non-selectable text. + */ + public boolean isSelectable() { + return (getHighlighter() != null); } -/** -* Overridden to return false. -*/ - public boolean isFocusTraversable() - { + /** + * Overridden to return false. + */ + public boolean isFocusTraversable() { return false; - } + } } diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/NumericTextField.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/NumericTextField.java index b3d2d03..dee8f27 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/NumericTextField.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/NumericTextField.java @@ -19,416 +19,346 @@ License along with this library; if not, see http://www.gnu.org package net.wotonomy.ui.swing.components; /** -* NumericTextField is a "smart" text field that restricts the user's input. The -* input is restructed to numeric only wich can be of two types: integer and real -* numbers. A range can also be placed on the text field. The default type is -* integer, with the being (Integer.MIN_VALUE, Integer.MAX_VALUE). -* -* @author rob@straylight.princeton.com -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -public class NumericTextField extends SmartTextField -{ - -/******************************* -* CONSTANTS -*******************************/ - -/** -* Restrict the text input to integers (whole numbers) only. -*/ - public final static int INTEGER = 0; - -/** -* Restrict the text input to floating-point numbers only. -*/ - public final static int FLOAT = 1; - - private Number maximumValue = null; - private Number minimumValue = null; - - private boolean sign = false; - private int newCaretPosition = 0; - private int valueType = INTEGER; - - -/******************************* -* PUBLIC METHODS -*******************************/ - -/** -* Default constructor. -*/ - public NumericTextField() - { - this("", 0); - } - -/** -* Constructor. -* @param text The initial string the text field is set to. -*/ - public NumericTextField(String text) - { - this(text, 0); - } - -/** -* Constructor. -* @param columns Width of the text field (in characters). -*/ - public NumericTextField(int columns) - { - this("", columns); - } - -/** -* Constructor. -* @param text The initial string the text field is set to. -* @param columns Width of the text field (in characters). -*/ - public NumericTextField(String text, int columns) - { - super(text, columns); - } - -/** -* Sets the upper limit of the range of numbers to accept. -* @param newMaximumValue The maximum number accepted by the text field. -*/ - public void setMaximumValue(double newMaximumValue) - { - if (newMaximumValue >= 0) - { - maximumValue = new Double( newMaximumValue ); - } - else - { - maximumValue = null; - } - } - -/** -* Returns the upper limit of the range of numbers to accept. -* @return The maximum number accepted by this text field. -*/ - public double getMaximumValue() - { - if ( valueType == INTEGER ) - { - return (maximumValue == null) ? (double) Integer.MAX_VALUE : maximumValue.doubleValue(); - } - else - { - return (maximumValue == null) ? Double.MAX_VALUE : maximumValue.doubleValue(); - } - } - -/** -* Sets the lower limit of the range of numbers to accept. -* @param newMinimumValue The minimum number accepted by the text field. -*/ - public void setMinimumValue(double newMinimumValue) - { - if (newMinimumValue <= 0) - { - minimumValue = new Double( newMinimumValue ); - } - else - { - minimumValue = null; - } - } - -/** -* Returns the lower limit of the range of numbers to accept. -* @return The minimum number accepted by this text field. -*/ - public double getMinimumValue() - { - if ( valueType == INTEGER ) - { - return (minimumValue == null) ? (double) Integer.MIN_VALUE : minimumValue.doubleValue(); - } - else - { - return (minimumValue == null) ? -1.0*Double.MAX_VALUE : minimumValue.doubleValue(); - // NOTE: Double.MIN_VALUE returns the smallest positive value - oooops. - } - } - -/** -* Sets which type of number this text field can accept. -* @see #INTEGER -* @see #FLOAT -* @param newValueType The type of number to accept. -*/ - public void setValueType(int newValueType) - { - if ((newValueType != INTEGER) && (newValueType != FLOAT)) - { - valueType = INTEGER; - } - else - { - valueType = newValueType; - } - } - -/** -* Returns which type of number this text field accepts. The default is -* integer. -* @see #INTEGER -* @see #FLOAT -* @return The type of number to accept. -*/ - public int getValueType() - { - return valueType; - } - -/** -* Returns the integer numeric value of the string in the text field. The type -* can be either integer of float. -* @return The current value in the text field. -*/ - public int getIntValue() - { - int value = 0; - - try - { - value = Integer.parseInt(getText()); - } - catch (NumberFormatException e) - { - try - { - Double dValue = Double.valueOf(getText()); - value = dValue.intValue(); - } - catch (NumberFormatException ignored) {} - } - - return value; - } - -/** -* Sets the text field to integer value specified. -* @param aValue An integer value to display in the text field. -*/ - public void setIntValue(int aValue) - { - setText(Integer.toString(aValue)); - } - -/** -* Returns the real number numeric value of the string in the text field. The type -* can be either integer of float. -* @return The current value in the text field. -*/ - public double getDoubleValue() - { - Double value = new Double(0); - - try - { - value = Double.valueOf(getText()); - } - catch (NumberFormatException ignored) {} - - return value.doubleValue(); - } - -/** -* Sets the text field to the double value specified. If the text field type is -* FLOAT then the the number is display as a real number. If the text field -* type is INTEGER then the number is converted to a whole number for displaying. -* @param aValue A double value to display in the text field. -*/ - public void setDoubleValue(double aValue) - { - Double temp = new Double(aValue); - - if (valueType == FLOAT) - { - setText(temp.toString()); - } - else - { - setText(Integer.toString(temp.intValue())); - } - } - -/******************************* -* PROTECTED METHODS -*******************************/ - - protected boolean isValidCharacter(char aChar) - { - if (((aChar >= ' ') && (aChar <= '/')) || ((aChar >= ':') && (aChar <= '~'))) - { - if (aChar == '.') - { - if ( valueType == FLOAT ) - { - return true; - } - else - { - return false; - } - } - else if (aChar == '-') - { - if ( getMinimumValue() < 0 ) - { - return true; - } - else - { - return false; - } - } - else if (aChar == '+') - { - if ( getMaximumValue() >= 0 ) - { - return true; - } - else - { - return false; - } - } - return false; - } - return true; - } - - protected boolean isValidString(String aString) - { - int iValue = 0; - double dValue = 0.0; - - String tempString = new String(scanForSignChar(aString)); - - if ( valueType == INTEGER ) - { - try - { - iValue = Integer.parseInt(tempString); - } - catch (NumberFormatException e1) - { - if ((tempString.compareTo("-") == 0) && (getMinimumValue() < 0.0)) - { - iValue = 0; - } - else - { - return false; - } - } - if ((((double)iValue) < getMinimumValue()) || (((double)iValue) > getMaximumValue())) - { - return false; - } - } - else - { - // Double.valueOf requires a zero before the decimal point - if ( tempString.startsWith( "." ) ) - { - tempString = "0" + tempString; - } - try - { - dValue = Double.valueOf(tempString).doubleValue(); - } - catch (NumberFormatException e2) - { - if ((tempString.compareTo("-") == 0) && (getMinimumValue() < 0.0)) - { - dValue = 0.0; - } - else - { - return false; - } - } - - if ((dValue < getMinimumValue()) || (dValue > getMaximumValue())) - { - return false; - } - } - - return true; - } - - protected void postProcessing() - { - if (sign) - { - setText(scanForSignChar(getText())); - setCaretPosition(newCaretPosition); - } - sign = false; - } - - -/******************************* -* PRIVATE METHODS -*******************************/ - - private String scanForSignChar(String aString) - { - String newString = ""; - boolean positive = false; - boolean negative = false; - int oldCaretPosition = getCaretPosition(); - int charactersAdded = 0; - - newCaretPosition = 0; - - if (aString.length() <= 0) - { - return aString; - } - - for (int i = 0; i < aString.length(); ++i) - { - switch (aString.charAt(i)) - { - case '+': positive = true; - break; - case '-': negative = true; - break; - default: newString += aString.charAt(i); - charactersAdded++; - break; - } - - if ((i + 1) == oldCaretPosition) - { - newCaretPosition = charactersAdded; - } - } - - if ((!(positive)) && (negative)) - { - newString = "-" + newString; - newCaretPosition++; - } - - if (positive || negative) - { - sign = true; - } - - return newString; - } + * NumericTextField is a "smart" text field that restricts the user's input. The + * input is restructed to numeric only wich can be of two types: integer and + * real numbers. A range can also be placed on the text field. The default type + * is integer, with the being (Integer.MIN_VALUE, Integer.MAX_VALUE). + * + * @author rob@straylight.princeton.com + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +public class NumericTextField extends SmartTextField { + + /******************************* + * CONSTANTS + *******************************/ + + /** + * Restrict the text input to integers (whole numbers) only. + */ + public final static int INTEGER = 0; + + /** + * Restrict the text input to floating-point numbers only. + */ + public final static int FLOAT = 1; + + private Number maximumValue = null; + private Number minimumValue = null; + + private boolean sign = false; + private int newCaretPosition = 0; + private int valueType = INTEGER; + + /******************************* + * PUBLIC METHODS + *******************************/ + + /** + * Default constructor. + */ + public NumericTextField() { + this("", 0); + } + + /** + * Constructor. + * + * @param text The initial string the text field is set to. + */ + public NumericTextField(String text) { + this(text, 0); + } + + /** + * Constructor. + * + * @param columns Width of the text field (in characters). + */ + public NumericTextField(int columns) { + this("", columns); + } + + /** + * Constructor. + * + * @param text The initial string the text field is set to. + * @param columns Width of the text field (in characters). + */ + public NumericTextField(String text, int columns) { + super(text, columns); + } + + /** + * Sets the upper limit of the range of numbers to accept. + * + * @param newMaximumValue The maximum number accepted by the text field. + */ + public void setMaximumValue(double newMaximumValue) { + if (newMaximumValue >= 0) { + maximumValue = new Double(newMaximumValue); + } else { + maximumValue = null; + } + } + + /** + * Returns the upper limit of the range of numbers to accept. + * + * @return The maximum number accepted by this text field. + */ + public double getMaximumValue() { + if (valueType == INTEGER) { + return (maximumValue == null) ? (double) Integer.MAX_VALUE : maximumValue.doubleValue(); + } else { + return (maximumValue == null) ? Double.MAX_VALUE : maximumValue.doubleValue(); + } + } + + /** + * Sets the lower limit of the range of numbers to accept. + * + * @param newMinimumValue The minimum number accepted by the text field. + */ + public void setMinimumValue(double newMinimumValue) { + if (newMinimumValue <= 0) { + minimumValue = new Double(newMinimumValue); + } else { + minimumValue = null; + } + } + + /** + * Returns the lower limit of the range of numbers to accept. + * + * @return The minimum number accepted by this text field. + */ + public double getMinimumValue() { + if (valueType == INTEGER) { + return (minimumValue == null) ? (double) Integer.MIN_VALUE : minimumValue.doubleValue(); + } else { + return (minimumValue == null) ? -1.0 * Double.MAX_VALUE : minimumValue.doubleValue(); + // NOTE: Double.MIN_VALUE returns the smallest positive value - oooops. + } + } + + /** + * Sets which type of number this text field can accept. + * + * @see #INTEGER + * @see #FLOAT + * @param newValueType The type of number to accept. + */ + public void setValueType(int newValueType) { + if ((newValueType != INTEGER) && (newValueType != FLOAT)) { + valueType = INTEGER; + } else { + valueType = newValueType; + } + } + + /** + * Returns which type of number this text field accepts. The default is integer. + * + * @see #INTEGER + * @see #FLOAT + * @return The type of number to accept. + */ + public int getValueType() { + return valueType; + } + + /** + * Returns the integer numeric value of the string in the text field. The type + * can be either integer of float. + * + * @return The current value in the text field. + */ + public int getIntValue() { + int value = 0; + + try { + value = Integer.parseInt(getText()); + } catch (NumberFormatException e) { + try { + Double dValue = Double.valueOf(getText()); + value = dValue.intValue(); + } catch (NumberFormatException ignored) { + } + } + + return value; + } + + /** + * Sets the text field to integer value specified. + * + * @param aValue An integer value to display in the text field. + */ + public void setIntValue(int aValue) { + setText(Integer.toString(aValue)); + } + + /** + * Returns the real number numeric value of the string in the text field. The + * type can be either integer of float. + * + * @return The current value in the text field. + */ + public double getDoubleValue() { + Double value = new Double(0); + + try { + value = Double.valueOf(getText()); + } catch (NumberFormatException ignored) { + } + + return value.doubleValue(); + } + + /** + * Sets the text field to the double value specified. If the text field type is + * FLOAT then the the number is display as a real number. If the text field type + * is INTEGER then the number is converted to a whole number for displaying. + * + * @param aValue A double value to display in the text field. + */ + public void setDoubleValue(double aValue) { + Double temp = new Double(aValue); + + if (valueType == FLOAT) { + setText(temp.toString()); + } else { + setText(Integer.toString(temp.intValue())); + } + } + + /******************************* + * PROTECTED METHODS + *******************************/ + + protected boolean isValidCharacter(char aChar) { + if (((aChar >= ' ') && (aChar <= '/')) || ((aChar >= ':') && (aChar <= '~'))) { + if (aChar == '.') { + if (valueType == FLOAT) { + return true; + } else { + return false; + } + } else if (aChar == '-') { + if (getMinimumValue() < 0) { + return true; + } else { + return false; + } + } else if (aChar == '+') { + if (getMaximumValue() >= 0) { + return true; + } else { + return false; + } + } + return false; + } + return true; + } + + protected boolean isValidString(String aString) { + int iValue = 0; + double dValue = 0.0; + + String tempString = new String(scanForSignChar(aString)); + + if (valueType == INTEGER) { + try { + iValue = Integer.parseInt(tempString); + } catch (NumberFormatException e1) { + if ((tempString.compareTo("-") == 0) && (getMinimumValue() < 0.0)) { + iValue = 0; + } else { + return false; + } + } + if ((((double) iValue) < getMinimumValue()) || (((double) iValue) > getMaximumValue())) { + return false; + } + } else { + // Double.valueOf requires a zero before the decimal point + if (tempString.startsWith(".")) { + tempString = "0" + tempString; + } + try { + dValue = Double.valueOf(tempString).doubleValue(); + } catch (NumberFormatException e2) { + if ((tempString.compareTo("-") == 0) && (getMinimumValue() < 0.0)) { + dValue = 0.0; + } else { + return false; + } + } + + if ((dValue < getMinimumValue()) || (dValue > getMaximumValue())) { + return false; + } + } + + return true; + } + + protected void postProcessing() { + if (sign) { + setText(scanForSignChar(getText())); + setCaretPosition(newCaretPosition); + } + sign = false; + } + + /******************************* + * PRIVATE METHODS + *******************************/ + + private String scanForSignChar(String aString) { + String newString = ""; + boolean positive = false; + boolean negative = false; + int oldCaretPosition = getCaretPosition(); + int charactersAdded = 0; + + newCaretPosition = 0; + + if (aString.length() <= 0) { + return aString; + } + + for (int i = 0; i < aString.length(); ++i) { + switch (aString.charAt(i)) { + case '+': + positive = true; + break; + case '-': + negative = true; + break; + default: + newString += aString.charAt(i); + charactersAdded++; + break; + } + + if ((i + 1) == oldCaretPosition) { + newCaretPosition = charactersAdded; + } + } + + if ((!(positive)) && (negative)) { + newString = "-" + newString; + newCaretPosition++; + } + + if (positive || negative) { + sign = true; + } + + return newString; + } } - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/PropertyEditorTable.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/PropertyEditorTable.java index 9db2834..996f8e0 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/PropertyEditorTable.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/PropertyEditorTable.java @@ -47,233 +47,235 @@ import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; /** -* PropertyEditorTable is a table designed to display and edit the properties -* of an object. Because JTable assumes all cells in a column display -* the same data type, we have to subclass to determine the class -* based on the cell contents. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ + * PropertyEditorTable is a table designed to display and edit the properties of + * an object. Because JTable assumes all cells in a column display the same data + * type, we have to subclass to determine the class based on the cell contents. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ public class PropertyEditorTable extends JTable { // // Constructors // - /** - * Constructs a default JTable which is initialized with a default - * data model, a default column model, and a default selection - * model. - * - * @see #createDefaultDataModel - * @see #createDefaultColumnModel - * @see #createDefaultSelectionModel - */ - public PropertyEditorTable() { - super(null, null, null); - } - - /** - * Constructs a JTable which is initialized with <i>dm</i> as the - * data model, a default column model, and a default selection - * model. - * - * @param dm The data model for the table - * @see #createDefaultColumnModel - * @see #createDefaultSelectionModel - */ - public PropertyEditorTable(TableModel dm) { - super(dm, null, null); - } - - /** - * Constructs a JTable which is initialized with <i>dm</i> as the - * data model, <i>cm</i> as the column model, and a default selection - * model. - * - * @param dm The data model for the table - * @param cm The column model for the table - * @see #createDefaultSelectionModel - */ - public PropertyEditorTable(TableModel dm, TableColumnModel cm) { - super(dm, cm, null); - } - - /** - * Constructs a JTable which is initialized with <i>dm</i> as the - * data model, <i>cm</i> as the column model, and <i>sm</i> as the - * selection model. If any of the parameters are <b>null</b> this - * method will initialize the table with the corresponding - * default model. The <i>autoCreateColumnsFromModel</i> flag is set - * to false if <i>cm</i> is non-null, otherwise it is set to true - * and the column model is populated with suitable TableColumns - * for the columns in <i>dm</i>. - * - * @param dm The data model for the table - * @param cm The column model for the table - * @param sm The row selection model for the table - * @see #createDefaultDataModel - * @see #createDefaultColumnModel - * @see #createDefaultSelectionModel - */ - public PropertyEditorTable(TableModel dm, TableColumnModel cm, ListSelectionModel sm) { - super( dm, cm, sm ); + /** + * Constructs a default JTable which is initialized with a default data model, a + * default column model, and a default selection model. + * + * @see #createDefaultDataModel + * @see #createDefaultColumnModel + * @see #createDefaultSelectionModel + */ + public PropertyEditorTable() { + super(null, null, null); } - /** - * Constructs a JTable with <i>numRows</i> and <i>numColumns</i> of - * empty cells using the DefaultTableModel. The columns will have - * names of the form "A", "B", "C", etc. - * - * @param numRows The number of rows the table holds - * @param numColumns The number of columns the table holds - */ - public PropertyEditorTable(int numRows, int numColumns) { - super( numRows, numColumns ); - } - - /** - * Constructs a JTable to display the values in the Vector of Vectors, - * <i>rowData</i>, with column names, <i>columnNames</i>. - * The Vectors contained in <i>rowData</i> should contain the values - * for that row. In other words, the value of the cell at row 1, - * column 5 can be obtained with the following code: - * <p> - * <pre>((Vector)rowData.elementAt(1)).elementAt(5);</pre> - * <p> - * All rows must be of the same length as <i>columnNames</i>. - * <p> - * @param rowData The data for the new table - * @param columnNames Names of each column - */ - public PropertyEditorTable(final Vector rowData, final Vector columnNames) { - super( rowData, columnNames ); - } - - /** - * Constructs a JTable to display the values in the two dimensional array, - * <i>rowData</i>, with column names, <i>columnNames</i>. - * <i>rowData</i> is an Array of rows, so the value of the cell at row 1, - * column 5 can be obtained with the following code: - * <p> - * <pre> rowData[1][5]; </pre> - * <p> - * All rows must be of the same length as <i>columnNames</i>. - * <p> - * @param rowData The data for the new table - * @param columnNames Names of each column - */ - public PropertyEditorTable(final Object[][] rowData, final Object[] columnNames) { - super( rowData, columnNames ); + /** + * Constructs a JTable which is initialized with <i>dm</i> as the data model, a + * default column model, and a default selection model. + * + * @param dm The data model for the table + * @see #createDefaultColumnModel + * @see #createDefaultSelectionModel + */ + public PropertyEditorTable(TableModel dm) { + super(dm, null, null); } - /** - * Returns the type of the column at the specified view position. - * - * @return the type of the column at position <I>column</I> in the view - * where the first column is column 0. + /** + * Constructs a JTable which is initialized with <i>dm</i> as the data model, + * <i>cm</i> as the column model, and a default selection model. * - * Modified mln: now a wrapper for getCellClass() + * @param dm The data model for the table + * @param cm The column model for the table + * @see #createDefaultSelectionModel + */ + public PropertyEditorTable(TableModel dm, TableColumnModel cm) { + super(dm, cm, null); + } + + /** + * Constructs a JTable which is initialized with <i>dm</i> as the data model, + * <i>cm</i> as the column model, and <i>sm</i> as the selection model. If any + * of the parameters are <b>null</b> this method will initialize the table with + * the corresponding default model. The <i>autoCreateColumnsFromModel</i> flag + * is set to false if <i>cm</i> is non-null, otherwise it is set to true and the + * column model is populated with suitable TableColumns for the columns in + * <i>dm</i>. * - */ - public Class getColumnClass(int column) { - return getCellClass( 0, column ); - } - - /** - * Returns the type of the cell at the specified view position. - * - * @return the type of the cell at position <I>row</I>, <I>column</I> in the view - * where the first column is column 0. + * @param dm The data model for the table + * @param cm The column model for the table + * @param sm The row selection model for the table + * @see #createDefaultDataModel + * @see #createDefaultColumnModel + * @see #createDefaultSelectionModel + */ + public PropertyEditorTable(TableModel dm, TableColumnModel cm, ListSelectionModel sm) { + super(dm, cm, sm); + } + + /** + * Constructs a JTable with <i>numRows</i> and <i>numColumns</i> of empty cells + * using the DefaultTableModel. The columns will have names of the form "A", + * "B", "C", etc. * - * Modified mln: new methods + * @param numRows The number of rows the table holds + * @param numColumns The number of columns the table holds + */ + public PropertyEditorTable(int numRows, int numColumns) { + super(numRows, numColumns); + } + + /** + * Constructs a JTable to display the values in the Vector of Vectors, + * <i>rowData</i>, with column names, <i>columnNames</i>. The Vectors contained + * in <i>rowData</i> should contain the values for that row. In other words, the + * value of the cell at row 1, column 5 can be obtained with the following code: + * <p> + * + * <pre> + * ((Vector) rowData.elementAt(1)).elementAt(5); + * </pre> + * <p> + * All rows must be of the same length as <i>columnNames</i>. + * <p> + * + * @param rowData The data for the new table + * @param columnNames Names of each column + */ + public PropertyEditorTable(final Vector rowData, final Vector columnNames) { + super(rowData, columnNames); + } + + /** + * Constructs a JTable to display the values in the two dimensional array, + * <i>rowData</i>, with column names, <i>columnNames</i>. <i>rowData</i> is an + * Array of rows, so the value of the cell at row 1, column 5 can be obtained + * with the following code: + * <p> + * + * <pre> + * rowData[1][5]; + * </pre> + * <p> + * All rows must be of the same length as <i>columnNames</i>. + * <p> + * + * @param rowData The data for the new table + * @param columnNames Names of each column + */ + public PropertyEditorTable(final Object[][] rowData, final Object[] columnNames) { + super(rowData, columnNames); + } + + /** + * Returns the type of the column at the specified view position. + * + * @return the type of the column at position <I>column</I> in the view where + * the first column is column 0. + * + * Modified mln: now a wrapper for getCellClass() + * + */ + public Class getColumnClass(int column) { + return getCellClass(0, column); + } + + /** + * Returns the type of the cell at the specified view position. + * + * @return the type of the cell at position <I>row</I>, <I>column</I> in the + * view where the first column is column 0. * - */ - public Class getCellClass(int row, int column) { + * Modified mln: new methods + * + */ + public Class getCellClass(int row, int column) { TableModel model = getModel(); - if ( model instanceof PropertyEditorTableModel ) - return ((PropertyEditorTableModel)model).getCellClass( row, column ); - else + if (model instanceof PropertyEditorTableModel) + return ((PropertyEditorTableModel) model).getCellClass(row, column); + else return model.getColumnClass(convertColumnIndexToModel(column)); - } - - /** - * Return an appropriate renderer for the cell specified by this this row and - * column. If the TableColumn for this column has a non-null renderer, return that. - * If not, find the class of the data in this column (using getColumnClass()) - * and return the default renderer for this type of data. - * - * @param row the row of the cell to render, where 0 is the first - * @param column the column of the cell to render, where 0 is the first + } + + /** + * Return an appropriate renderer for the cell specified by this this row and + * column. If the TableColumn for this column has a non-null renderer, return + * that. If not, find the class of the data in this column (using + * getColumnClass()) and return the default renderer for this type of data. + * + * @param row the row of the cell to render, where 0 is the first + * @param column the column of the cell to render, where 0 is the first + * + * Modified mln: calls getCellClass if there's no column model * - * Modified mln: calls getCellClass if there's no column model + */ + public TableCellRenderer getCellRenderer(int row, int column) { + TableColumn tableColumn = getColumnModel().getColumn(column); + TableCellRenderer renderer = tableColumn.getCellRenderer(); + if (renderer == null) { + renderer = getDefaultRenderer(getCellClass(row, column)); + } + return renderer; + } + + /** + * Return an appropriate editor for the cell specified by this this row and + * column. If the TableColumn for this column has a non-null editor, return + * that. If not, find the class of the data in this column (using + * getColumnClass()) and return the default editor for this type of data. * - */ - public TableCellRenderer getCellRenderer(int row, int column) { - TableColumn tableColumn = getColumnModel().getColumn(column); - TableCellRenderer renderer = tableColumn.getCellRenderer(); - if (renderer == null) { - renderer = getDefaultRenderer(getCellClass(row, column)); - } - return renderer; - } - - - /** - * Return an appropriate editor for the cell specified by this this row and - * column. If the TableColumn for this column has a non-null editor, return that. - * If not, find the class of the data in this column (using getColumnClass()) - * and return the default editor for this type of data. - * - * @param row the row of the cell to edit, where 0 is the first - * @param column the column of the cell to edit, where 0 is the first + * @param row the row of the cell to edit, where 0 is the first + * @param column the column of the cell to edit, where 0 is the first * - * Modified mp: calls getCellClass if there's no column model + * Modified mp: calls getCellClass if there's no column model * - */ - public TableCellEditor getCellEditor(int row, int column) { - TableColumn tableColumn = getColumnModel().getColumn(column); - TableCellEditor editor = tableColumn.getCellEditor(); - if (editor == null) { - editor = getDefaultEditor(getCellClass(row, column)); - } - return editor; - } + */ + public TableCellEditor getCellEditor(int row, int column) { + TableColumn tableColumn = getColumnModel().getColumn(column); + TableCellEditor editor = tableColumn.getCellEditor(); + if (editor == null) { + editor = getDefaultEditor(getCellClass(row, column)); + } + return editor; + } protected void createDefaultRenderers() { super.createDefaultRenderers(); -/* // copying this code here as a sample of creating a renderer - // Dates - DefaultTableCellRenderer dateRenderer = new DefaultTableCellRenderer() { - DateFormat formatter = DateFormat.getDateInstance(); - public void setValue(Object value) { - setText((value == null) ? "" : formatter.format(value)); } - }; - dateRenderer.setHorizontalAlignment(JLabel.RIGHT); - setDefaultRenderer(Date.class, dateRenderer); -*/ - - DefaultTableCellRenderer fontRenderer = new DefaultTableCellRenderer() { - public void setValue(Object value) { - setText( getFontDescription( (Font) value ) ); + /* + * // copying this code here as a sample of creating a renderer // Dates + * DefaultTableCellRenderer dateRenderer = new DefaultTableCellRenderer() { + * DateFormat formatter = DateFormat.getDateInstance(); public void + * setValue(Object value) { setText((value == null) ? "" : + * formatter.format(value)); } }; + * dateRenderer.setHorizontalAlignment(JLabel.RIGHT); + * setDefaultRenderer(Date.class, dateRenderer); + */ + + DefaultTableCellRenderer fontRenderer = new DefaultTableCellRenderer() { + public void setValue(Object value) { + setText(getFontDescription((Font) value)); } }; - fontRenderer.setHorizontalAlignment(JLabel.RIGHT); - setDefaultRenderer(Font.class, fontRenderer); + fontRenderer.setHorizontalAlignment(JLabel.RIGHT); + setDefaultRenderer(Font.class, fontRenderer); - setUpColorRenderer( this ); - setUpMethodRenderer( this ); - } + setUpColorRenderer(this); + setUpMethodRenderer(this); + } - protected String getFontDescription( Font f ) { + protected String getFontDescription(Font f) { String s; - if ( f != null ) { + if (f != null) { s = f.getName(); - if ( f.isBold() ) s += " Bold"; - if ( f.isItalic() ) s += " Italic"; + if (f.isBold()) + s += " Bold"; + if (f.isItalic()) + s += " Italic"; s += " " + f.getSize(); } else { s = ""; @@ -281,292 +283,263 @@ public class PropertyEditorTable extends JTable { return s; } - protected void createDefaultEditors() { + protected void createDefaultEditors() { super.createDefaultEditors(); -/* // copying this code here as a sample of creating an editor - // Numbers - JTextField rightAlignedTextField = new JTextField(); - rightAlignedTextField.setHorizontalAlignment(JTextField.RIGHT); - rightAlignedTextField.setBorder(new LineBorder(Color.black)); - setDefaultEditor(Number.class, new DefaultCellEditor(rightAlignedTextField)); - - // Booleans - JCheckBox centeredCheckBox = new JCheckBox(); - centeredCheckBox.setHorizontalAlignment(JCheckBox.CENTER); - setDefaultEditor(Boolean.class, new DefaultCellEditor(centeredCheckBox)); -*/ - setUpColorEditor( this ); - setUpMethodEditor( this ); + /* + * // copying this code here as a sample of creating an editor // Numbers + * JTextField rightAlignedTextField = new JTextField(); + * rightAlignedTextField.setHorizontalAlignment(JTextField.RIGHT); + * rightAlignedTextField.setBorder(new LineBorder(Color.black)); + * setDefaultEditor(Number.class, new DefaultCellEditor(rightAlignedTextField)); + * + * // Booleans JCheckBox centeredCheckBox = new JCheckBox(); + * centeredCheckBox.setHorizontalAlignment(JCheckBox.CENTER); + * setDefaultEditor(Boolean.class, new DefaultCellEditor(centeredCheckBox)); + */ + setUpColorEditor(this); + setUpMethodEditor(this); } - - // following code lifted from: + // following code lifted from: // http://java.sun.com/docs/books/tutorial/ui/swing/example-swing/TableDialogEditDemo.java - class ColorRenderer extends JLabel - implements TableCellRenderer { - Border unselectedBorder = null; - Border selectedBorder = null; - boolean isBordered = true; - - public ColorRenderer(boolean isBordered) { - super(); - this.isBordered = isBordered; - this.setOpaque(true); //MUST do this for background to show up. - } - - public Component getTableCellRendererComponent( - JTable table, Object color, - boolean isSelected, boolean hasFocus, - int row, int column) { - this.setBackground((Color)color); - if (isBordered) { - if (isSelected) { - if (selectedBorder == null) { - selectedBorder = BorderFactory.createMatteBorder(2,5,2,5, - table.getSelectionBackground()); - } - this.setBorder(selectedBorder); - } else { - if (unselectedBorder == null) { - unselectedBorder = BorderFactory.createMatteBorder(2,5,2,5, - table.getBackground()); - } - this.setBorder(unselectedBorder); - } - } - return this; - } - } - - private void setUpColorRenderer(JTable table) { - table.setDefaultRenderer(Color.class, - new ColorRenderer(true)); - } - - //Set up the editor for the Color cells. - private void setUpColorEditor(JTable table) { - //First, set up the button that brings up the dialog. - final JButton button = new JButton("") { - public void setText(String s) { - //Button never shows text -- only color. - } - }; - button.setBackground(Color.white); - button.setBorderPainted(false); - button.setMargin(new Insets(0,0,0,0)); - - //Now create an editor to encapsulate the button, and - //set it up as the editor for all Color cells. - final ColorEditor colorEditor = new ColorEditor(button); - table.setDefaultEditor(Color.class, colorEditor); - - //Set up the dialog that the button brings up. - final JColorChooser colorChooser = new JColorChooser(); - //XXX: PENDING: add the following when setPreviewPanel - //XXX: starts working. - //JComponent preview = new ColorRenderer(false); - //preview.setPreferredSize(new Dimension(50, 10)); - //colorChooser.setPreviewPanel(preview); - ActionListener okListener = new ActionListener() { - public void actionPerformed(ActionEvent e) { - colorEditor.currentColor = colorChooser.getColor(); - } - }; - final JDialog dialog = JColorChooser.createDialog(button, - "Pick a Color", - true, - colorChooser, - okListener, - null); //XXXDoublecheck this is OK - - //Here's the code that brings up the dialog. - button.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - button.setBackground(colorEditor.currentColor); - colorChooser.setColor(colorEditor.currentColor); - //Without the following line, the dialog comes up - //in the middle of the screen. - //dialog.setLocationRelativeTo(button); - dialog.show(); - } - }); - } - - /* - * The editor button that brings up the dialog. - * We extend DefaultCellEditor for convenience, - * even though it mean we have to create a dummy - * check box. Another approach would be to copy - * the implementation of TableCellEditor methods - * from the source code for DefaultCellEditor. - */ - class ColorEditor extends DefaultCellEditor { - Color currentColor = null; - - public ColorEditor(JButton b) { - super(new JCheckBox()); //Unfortunately, the constructor - //expects a check box, combo box, - //or text field. - editorComponent = b; - setClickCountToStart(1); //This is usually 1 or 2. - - //Must do this so that editing stops when appropriate. - b.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - fireEditingStopped(); - } - }); - } - - protected void fireEditingStopped() { - super.fireEditingStopped(); - } - - public Object getCellEditorValue() { - return currentColor; - } - - public Component getTableCellEditorComponent(JTable table, - Object value, - boolean isSelected, - int row, - int column) { - ((JButton)editorComponent).setText(value.toString()); - currentColor = (Color)value; - return editorComponent; - } - } - - class MethodRenderer extends JLabel - implements TableCellRenderer { - - Method theMethod = null; - JTable theTable = null; - - public MethodRenderer() { - super(); - } - - public Component getTableCellRendererComponent( - JTable table, Object method, - boolean isSelected, boolean hasFocus, - int row, int column) { - theMethod = (Method) method; - theTable = table; - setText( " " + theMethod.getReturnType().getName() ); - return this; - } - } - - private void setUpMethodRenderer(JTable table) { - table.setDefaultRenderer(Method.class, - new MethodRenderer()); - } - - /* - * We extend DefaultCellEditor for convenience, - * as with ColorEditor. - */ - class MethodEditor extends DefaultCellEditor { - Method theMethod = null; - JTable theTable = null; - - public MethodEditor(JButton b) { - super(new JCheckBox()); //Unfortunately, the constructor - //expects a check box, combo box, - //or text field. - editorComponent = b; - setClickCountToStart(1); //This is usually 1 or 2. - - //Must do this so that editing stops when appropriate. - b.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - fireEditingStopped(); - } - }); - } - - protected void fireEditingStopped() { - super.fireEditingStopped(); - } - - public Object getCellEditorValue() { - return theMethod; - } - - public Component getTableCellEditorComponent(JTable table, - Object value, - boolean isSelected, - int row, - int column) { - ((JButton)editorComponent).setText(value.toString()); - theMethod = (Method)value; - theTable = table; - return editorComponent; - } - } - - //Set up the editor for the Method cells. - private void setUpMethodEditor(PropertyEditorTable table) { - //First, set up the button that brings up the dialog. - final JButton button = new JButton("invoking method") { - public void setText(String s) { - //Button never shows text -- only color. - } - }; - button.setBackground(Color.white); - button.setBorderPainted(false); - button.setMargin(new Insets(0,0,0,0)); - - //Now create an editor to encapsulate the button, and - //set it up as the editor for all Color cells. - final MethodEditor methodEditor = new MethodEditor(button); - table.setDefaultEditor(Method.class, methodEditor); - - // handle the button-click - final PropertyEditorTable theTable = table; - button.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - - Component parent = SwingUtilities.getRoot( theTable ); - if ( parent == null ) parent = theTable; - - Cursor oldCursor = parent.getCursor(); - parent.setCursor( Cursor.getPredefinedCursor( Cursor.WAIT_CURSOR ) ); - - Object result = null; - Object inspectedObject = ((PropertyEditorTableModel) - methodEditor.theTable.getModel()).inspectedObject; - try - { - methodEditor.theMethod.setAccessible( true ); - result = methodEditor.theMethod.invoke( - inspectedObject, (Object[])null ); - } - catch ( Exception exc ) - { - System.err.println( "PropertyEditorTable.MethodRenderer.actionPerformed: " + - "Error occurred: " + exc ); - } - theTable.methodInvoked( inspectedObject, methodEditor.theMethod, result ); - - parent.setCursor( oldCursor ); - } - }); - } + class ColorRenderer extends JLabel implements TableCellRenderer { + Border unselectedBorder = null; + Border selectedBorder = null; + boolean isBordered = true; -/** -* Called by the method cell editor when a method is invoked. -* @param anObject The object upon which the method was invoked. -* @param aMethod The method that was invoked. -* @param aResult The result of the method invocation; may be null. -*/ - public void methodInvoked( Object anObject, Method aMethod, Object aResult ) - { - System.out.println( aMethod.getName() + ": " + aResult ); - } -} + public ColorRenderer(boolean isBordered) { + super(); + this.isBordered = isBordered; + this.setOpaque(true); // MUST do this for background to show up. + } + public Component getTableCellRendererComponent(JTable table, Object color, boolean isSelected, boolean hasFocus, + int row, int column) { + this.setBackground((Color) color); + if (isBordered) { + if (isSelected) { + if (selectedBorder == null) { + selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, table.getSelectionBackground()); + } + this.setBorder(selectedBorder); + } else { + if (unselectedBorder == null) { + unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, table.getBackground()); + } + this.setBorder(unselectedBorder); + } + } + return this; + } + } + + private void setUpColorRenderer(JTable table) { + table.setDefaultRenderer(Color.class, new ColorRenderer(true)); + } + // Set up the editor for the Color cells. + private void setUpColorEditor(JTable table) { + // First, set up the button that brings up the dialog. + final JButton button = new JButton("") { + public void setText(String s) { + // Button never shows text -- only color. + } + }; + button.setBackground(Color.white); + button.setBorderPainted(false); + button.setMargin(new Insets(0, 0, 0, 0)); + + // Now create an editor to encapsulate the button, and + // set it up as the editor for all Color cells. + final ColorEditor colorEditor = new ColorEditor(button); + table.setDefaultEditor(Color.class, colorEditor); + + // Set up the dialog that the button brings up. + final JColorChooser colorChooser = new JColorChooser(); + // XXX: PENDING: add the following when setPreviewPanel + // XXX: starts working. + // JComponent preview = new ColorRenderer(false); + // preview.setPreferredSize(new Dimension(50, 10)); + // colorChooser.setPreviewPanel(preview); + ActionListener okListener = new ActionListener() { + public void actionPerformed(ActionEvent e) { + colorEditor.currentColor = colorChooser.getColor(); + } + }; + final JDialog dialog = JColorChooser.createDialog(button, "Pick a Color", true, colorChooser, okListener, null); // XXXDoublecheck + // this + // is + // OK + + // Here's the code that brings up the dialog. + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + button.setBackground(colorEditor.currentColor); + colorChooser.setColor(colorEditor.currentColor); + // Without the following line, the dialog comes up + // in the middle of the screen. + // dialog.setLocationRelativeTo(button); + dialog.show(); + } + }); + } + + /* + * The editor button that brings up the dialog. We extend DefaultCellEditor for + * convenience, even though it mean we have to create a dummy check box. Another + * approach would be to copy the implementation of TableCellEditor methods from + * the source code for DefaultCellEditor. + */ + class ColorEditor extends DefaultCellEditor { + Color currentColor = null; + + public ColorEditor(JButton b) { + super(new JCheckBox()); // Unfortunately, the constructor + // expects a check box, combo box, + // or text field. + editorComponent = b; + setClickCountToStart(1); // This is usually 1 or 2. + + // Must do this so that editing stops when appropriate. + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + fireEditingStopped(); + } + }); + } + + protected void fireEditingStopped() { + super.fireEditingStopped(); + } + + public Object getCellEditorValue() { + return currentColor; + } + + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, + int column) { + ((JButton) editorComponent).setText(value.toString()); + currentColor = (Color) value; + return editorComponent; + } + } + + class MethodRenderer extends JLabel implements TableCellRenderer { + + Method theMethod = null; + JTable theTable = null; + + public MethodRenderer() { + super(); + } + + public Component getTableCellRendererComponent(JTable table, Object method, boolean isSelected, + boolean hasFocus, int row, int column) { + theMethod = (Method) method; + theTable = table; + setText(" " + theMethod.getReturnType().getName()); + return this; + } + } + + private void setUpMethodRenderer(JTable table) { + table.setDefaultRenderer(Method.class, new MethodRenderer()); + } + + /* + * We extend DefaultCellEditor for convenience, as with ColorEditor. + */ + class MethodEditor extends DefaultCellEditor { + Method theMethod = null; + JTable theTable = null; + + public MethodEditor(JButton b) { + super(new JCheckBox()); // Unfortunately, the constructor + // expects a check box, combo box, + // or text field. + editorComponent = b; + setClickCountToStart(1); // This is usually 1 or 2. + + // Must do this so that editing stops when appropriate. + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + fireEditingStopped(); + } + }); + } + + protected void fireEditingStopped() { + super.fireEditingStopped(); + } + + public Object getCellEditorValue() { + return theMethod; + } + + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, + int column) { + ((JButton) editorComponent).setText(value.toString()); + theMethod = (Method) value; + theTable = table; + return editorComponent; + } + } + + // Set up the editor for the Method cells. + private void setUpMethodEditor(PropertyEditorTable table) { + // First, set up the button that brings up the dialog. + final JButton button = new JButton("invoking method") { + public void setText(String s) { + // Button never shows text -- only color. + } + }; + button.setBackground(Color.white); + button.setBorderPainted(false); + button.setMargin(new Insets(0, 0, 0, 0)); + + // Now create an editor to encapsulate the button, and + // set it up as the editor for all Color cells. + final MethodEditor methodEditor = new MethodEditor(button); + table.setDefaultEditor(Method.class, methodEditor); + + // handle the button-click + final PropertyEditorTable theTable = table; + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + + Component parent = SwingUtilities.getRoot(theTable); + if (parent == null) + parent = theTable; + + Cursor oldCursor = parent.getCursor(); + parent.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + + Object result = null; + Object inspectedObject = ((PropertyEditorTableModel) methodEditor.theTable.getModel()).inspectedObject; + try { + methodEditor.theMethod.setAccessible(true); + result = methodEditor.theMethod.invoke(inspectedObject, (Object[]) null); + } catch (Exception exc) { + System.err + .println("PropertyEditorTable.MethodRenderer.actionPerformed: " + "Error occurred: " + exc); + } + theTable.methodInvoked(inspectedObject, methodEditor.theMethod, result); + + parent.setCursor(oldCursor); + } + }); + } + + /** + * Called by the method cell editor when a method is invoked. + * + * @param anObject The object upon which the method was invoked. + * @param aMethod The method that was invoked. + * @param aResult The result of the method invocation; may be null. + */ + public void methodInvoked(Object anObject, Method aMethod, Object aResult) { + System.out.println(aMethod.getName() + ": " + aResult); + } +} diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/PropertyEditorTableModel.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/PropertyEditorTableModel.java index f6a2a8d..f6f80f2 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/PropertyEditorTableModel.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/PropertyEditorTableModel.java @@ -31,30 +31,28 @@ import javax.swing.Timer; import javax.swing.table.AbstractTableModel; /** -* PropertyEditorTableModel introspects an object to facilitate -* editing it in a PropertyTable. -* -* Because the model always reflects the current state of the -* inspected object, it is useful to have a table update at -* automated intervals. By default, this feature is turned off. -* If you turn it on, you'll want to remember to turn it off -* when you're done with the table model. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class PropertyEditorTableModel extends AbstractTableModel implements ActionListener -{ + * PropertyEditorTableModel introspects an object to facilitate editing it in a + * PropertyTable. + * + * Because the model always reflects the current state of the inspected object, + * it is useful to have a table update at automated intervals. By default, this + * feature is turned off. If you turn it on, you'll want to remember to turn it + * off when you're done with the table model. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class PropertyEditorTableModel extends AbstractTableModel implements ActionListener { protected Object inspectedObject = null; final String[] columnNames = { "Property", "Value" }; - static final String METHOD_TAG = " "; + static final String METHOD_TAG = " "; Vector properties = new Vector(); Hashtable methods = new Hashtable(0); - public void setObject( Object o ) { + public void setObject(Object o) { inspectedObject = o; Class c = o.getClass(); Method[] m = c.getMethods(); @@ -62,67 +60,56 @@ public class PropertyEditorTableModel extends AbstractTableModel implements Acti properties = new Vector(); methods = new Hashtable(m.length, 1F); String name, propertyName; - for ( int i = 0; i < m.length; i++ ) { + for (int i = 0; i < m.length; i++) { // if ( m[i].getName().startsWith( "get" ) ) // System.out.println( m[i].getName() + ": " + m[i].getReturnType().getName() ); // get methods - if ( - ( ( m[i].getName().startsWith( "get" ) ) || ( m[i].getName().startsWith( "is" ) ) ) - && ( m[i].getParameterTypes().length == 0 ) - ) { - name = m[i].getName(); - if ( m[i].getName().startsWith( "is" ) ) { - propertyName = name.substring( 2, name.length() ); - // probably should only add "is" methods if accompanied by "set" method - } else { // "get" - propertyName = name.substring( 3, name.length() ); - } - if ( - ( m[i].getReturnType().getName().equals( String.class.getName() ) ) - || ( m[i].getReturnType().getName().equals( "boolean" ) ) - || ( m[i].getReturnType().getName().equals( "int" ) ) - || ( m[i].getReturnType().getName().equals( "float" ) ) - || ( m[i].getReturnType().getName().equals( "char" ) ) - || ( m[i].getReturnType().getName().equals( Color.class.getName() ) ) - || ( m[i].getReturnType().getName().equals( Font.class.getName() ) ) - ) { - properties.addElement( propertyName ); // put property - methods.put( name, m[i] ); // put method + if (((m[i].getName().startsWith("get")) || (m[i].getName().startsWith("is"))) + && (m[i].getParameterTypes().length == 0)) { + name = m[i].getName(); + if (m[i].getName().startsWith("is")) { + propertyName = name.substring(2, name.length()); + // probably should only add "is" methods if accompanied by "set" method + } else { // "get" + propertyName = name.substring(3, name.length()); } - else - { - // handle unknown types as invokable methods: - properties.addElement( propertyName + METHOD_TAG ); - methods.put( propertyName + METHOD_TAG, m[i] ); - } - } - else + if ((m[i].getReturnType().getName().equals(String.class.getName())) + || (m[i].getReturnType().getName().equals("boolean")) + || (m[i].getReturnType().getName().equals("int")) + || (m[i].getReturnType().getName().equals("float")) + || (m[i].getReturnType().getName().equals("char")) + || (m[i].getReturnType().getName().equals(Color.class.getName())) + || (m[i].getReturnType().getName().equals(Font.class.getName()))) { + properties.addElement(propertyName); // put property + methods.put(name, m[i]); // put method + } else { + // handle unknown types as invokable methods: + properties.addElement(propertyName + METHOD_TAG); + methods.put(propertyName + METHOD_TAG, m[i]); + } + } else // set methods - if ( ( m[i].getName().startsWith( "set" ) ) && - ( m[i].getParameterTypes().length == 1 ) ) { + if ((m[i].getName().startsWith("set")) && (m[i].getParameterTypes().length == 1)) { name = m[i].getName(); - if ( - ( m[i].getParameterTypes()[0].getName().equals( String.class.getName() ) ) - || ( m[i].getParameterTypes()[0].getName().equals( "boolean" ) ) - || ( m[i].getParameterTypes()[0].getName().equals( "int" ) ) - || ( m[i].getParameterTypes()[0].getName().equals( "float" ) ) - || ( m[i].getParameterTypes()[0].getName().equals( Color.class.getName() ) ) + if ((m[i].getParameterTypes()[0].getName().equals(String.class.getName())) + || (m[i].getParameterTypes()[0].getName().equals("boolean")) + || (m[i].getParameterTypes()[0].getName().equals("int")) + || (m[i].getParameterTypes()[0].getName().equals("float")) + || (m[i].getParameterTypes()[0].getName().equals(Color.class.getName())) // || ( m[i].getParameterTypes()[0].getName().equals( Font.class.getName() ) ) ) { // System.out.println( "Accepted: " + name + ": " + m[i].getParameterTypes()[0].getName() ); - methods.put( name, m[i] ); // set method + methods.put(name, m[i]); // set method } else { // System.out.println( "Rejected: " + name + ": " + m[i].getParameterTypes()[0].getName() ); } + } else + // zero-parameter methods to be invoked on click + if (m[i].getParameterTypes().length == 0) { + properties.addElement(m[i].getName() + METHOD_TAG); + methods.put(m[i].getName() + METHOD_TAG, m[i]); } - else - // zero-parameter methods to be invoked on click - if ( m[i].getParameterTypes().length == 0 ) - { - properties.addElement( m[i].getName() + METHOD_TAG ); - methods.put( m[i].getName() + METHOD_TAG, m[i] ); - } } @@ -131,288 +118,259 @@ public class PropertyEditorTableModel extends AbstractTableModel implements Acti } public int getColumnCount() { - return columnNames.length; + return columnNames.length; } public int getRowCount() { - return properties.size(); + return properties.size(); } public String getColumnName(int col) { - return columnNames[col]; + return columnNames[col]; } public Object getValueAt(int row, int col) { - if ( col == 0 ) - return properties.elementAt( row ); - else - { + if (col == 0) + return properties.elementAt(row); + else { Method m = null; - m = (Method) methods.get( "get" + ( (String) properties.elementAt( row ) ) ) ; - if ( m == null ) // try "is" - m = (Method) methods.get( "is" + ( (String) properties.elementAt( row ) ) ) ; - if ( m == null ) { // try entire method name - m = (Method) methods.get( (String) properties.elementAt( row ) ) ; - if ( m != null ) return m; - } + m = (Method) methods.get("get" + ((String) properties.elementAt(row))); + if (m == null) // try "is" + m = (Method) methods.get("is" + ((String) properties.elementAt(row))); + if (m == null) { // try entire method name + m = (Method) methods.get((String) properties.elementAt(row)); + if (m != null) + return m; + } try { - return m.invoke( inspectedObject, (Object[])null ); - } catch ( Exception exc ) { - System.out.println( "InspectorFrame.tableModel.getValueAt: error occured while reflecting: " ); - System.out.println( exc ); + return m.invoke(inspectedObject, (Object[]) null); + } catch (Exception exc) { + System.out.println("InspectorFrame.tableModel.getValueAt: error occured while reflecting: "); + System.out.println(exc); } return null; } } - public Class getColumnClass( int col ) { + public Class getColumnClass(int col) { // System.out.println( "getColumnClass" ); -/* try { - throw new Exception(); - } catch ( Exception exc ) { - exc.printStackTrace( System.out ); - } -*/ return new String().getClass(); + /* + * try { throw new Exception(); } catch ( Exception exc ) { exc.printStackTrace( + * System.out ); } + */ return new String().getClass(); } public Class getCellClass(int row, int col) { // System.out.println( "getCellClass" ); -/* - - Class c; - Method m = (Method) methods.get( "set" + ( (String) properties.elementAt( row ) ) ) ; - if ( m == null ) - c = new Object().getClass(); - else { - c = m.getParameterTypes()[0]; - - // special case for boolean - if ( c.getName().equals( "boolean" ) ) - c = new Boolean(true).getClass(); - - // special case for int - if ( c.getName().equals( "int" ) ) - c = new Integer(0).getClass(); - } - System.out.println( row + ": " + c.getName() ); - return c; -*/ + /* + * + * Class c; Method m = (Method) methods.get( "set" + ( (String) + * properties.elementAt( row ) ) ) ; if ( m == null ) c = new + * Object().getClass(); else { c = m.getParameterTypes()[0]; + * + * // special case for boolean if ( c.getName().equals( "boolean" ) ) c = new + * Boolean(true).getClass(); + * + * // special case for int if ( c.getName().equals( "int" ) ) c = new + * Integer(0).getClass(); } System.out.println( row + ": " + c.getName() ); + * return c; + */ // return new String().getClass(); - Object o = getValueAt( row, col ); - if ( o == null ) o = "null"; + Object o = getValueAt(row, col); + if (o == null) + o = "null"; return o.getClass(); } /* - * Don't need to implement this method unless your table's - * editable. - */ + * Don't need to implement this method unless your table's editable. + */ public boolean isCellEditable(int row, int col) { - //Note that the data/cell address is constant, - //no matter where the cell appears onscreen. - if (col < 1) { - return false; - } else { - // handle method invocation - if ( ((String)properties.elementAt(row)).endsWith(METHOD_TAG) ) return true; - // handle read-only properties - Method m = (Method) methods.get( "set" + ( (String) properties.elementAt( row ) ) ) ; - if ( m == null ) + // Note that the data/cell address is constant, + // no matter where the cell appears onscreen. + if (col < 1) { return false; - else - return true; - } + } else { + // handle method invocation + if (((String) properties.elementAt(row)).endsWith(METHOD_TAG)) + return true; + // handle read-only properties + Method m = (Method) methods.get("set" + ((String) properties.elementAt(row))); + if (m == null) + return false; + else + return true; + } } /* - * Don't need to implement this method unless your table's - * data can change. - */ + * Don't need to implement this method unless your table's data can change. + */ public void setValueAt(Object value, int row, int col) { - // test for inspected object - if ( inspectedObject == null ) return; - // handle method invocation - no need to update values - if ( ((String)properties.elementAt(row)).endsWith( METHOD_TAG ) ) - { - fireTableDataChanged(); - return; - }; - - // handle writable properties - Method m = (Method) methods.get( "set" + ( (String) properties.elementAt( row ) ) ) ; + // test for inspected object + if (inspectedObject == null) + return; + // handle method invocation - no need to update values + if (((String) properties.elementAt(row)).endsWith(METHOD_TAG)) { + fireTableDataChanged(); + return; + } + ; + + // handle writable properties + Method m = (Method) methods.get("set" + ((String) properties.elementAt(row))); String parameterType = m.getParameterTypes()[0].getName(); // ugly cast code - if ( - ( parameterType.equals( "int" ) ) - || ( parameterType.equals( "java.lang.Integer" ) ) - ) - { - try { - value = new Integer((String)value); - } catch (NumberFormatException e) { - System.out.println("PropertyEditorTableModel.setValueAt: User attempted to enter non-integer" - + " value (" + value - + ") into an integer-only column."); - } + if ((parameterType.equals("int")) || (parameterType.equals("java.lang.Integer"))) { + try { + value = new Integer((String) value); + } catch (NumberFormatException e) { + System.out.println("PropertyEditorTableModel.setValueAt: User attempted to enter non-integer" + + " value (" + value + ") into an integer-only column."); + } } Object[] parameters = { value }; try { - m.invoke( inspectedObject, parameters ); - if ( inspectedObject instanceof Component ) { - Component c = (Component)inspectedObject; - if ( c.getParent() != null ) + m.invoke(inspectedObject, parameters); + if (inspectedObject instanceof Component) { + Component c = (Component) inspectedObject; + if (c.getParent() != null) c.getParent().repaint(); } - } catch ( Exception exc ) { - System.out.println( "PropertyEditorTableModel.setValueAt: error occured while reflecting: " ); - System.out.println( exc ); + } catch (Exception exc) { + System.out.println("PropertyEditorTableModel.setValueAt: error occured while reflecting: "); + System.out.println(exc); } fireTableDataChanged(); } + protected void sort(Vector v) { + quickSort(v, 0, v.size() - 1); + } - protected void sort(Vector v){ - quickSort(v, 0, v.size()-1); - } - - - // Liberated from the BasicDirectoryModel which was... - // Liberated from the 1.1 SortDemo - - // This is a generic version of C.A.R Hoare's Quick Sort - // algorithm. This will handle arrays that are already - // sorted, and arrays with duplicate keys.<BR> - // - // If you think of a one dimensional array as going from - // the lowest index on the left to the highest index on the right - // then the parameters to this function are lowest index or - // left and highest index or right. The first time you call - // this function it will be with the parameters 0, a.length - 1. - // - // @param a an integer array - // @param lo0 left boundary of array partition - // @param hi0 right boundary of array partition - private void quickSort(Vector v, int lo0, int hi0) { - int lo = lo0; - int hi = hi0; - String mid; - - if (hi0 > lo0) { - // Arbitrarily establishing partition element as the midpoint of - // the array. - mid = (String) v.elementAt((lo0 + hi0) / 2); - - // loop through the array until indices cross - while(lo <= hi) { - // find the first element that is greater than or equal to - // the partition element starting from the left Index. - // - // Nasty to have to cast here. Would it be quicker - // to copy the vectors into arrays and sort the arrays? - while((lo < hi0) && lt((String)v.elementAt(lo), mid)) { - ++lo; - } + // Liberated from the BasicDirectoryModel which was... + // Liberated from the 1.1 SortDemo + + // This is a generic version of C.A.R Hoare's Quick Sort + // algorithm. This will handle arrays that are already + // sorted, and arrays with duplicate keys.<BR> + // + // If you think of a one dimensional array as going from + // the lowest index on the left to the highest index on the right + // then the parameters to this function are lowest index or + // left and highest index or right. The first time you call + // this function it will be with the parameters 0, a.length - 1. + // + // @param a an integer array + // @param lo0 left boundary of array partition + // @param hi0 right boundary of array partition + private void quickSort(Vector v, int lo0, int hi0) { + int lo = lo0; + int hi = hi0; + String mid; + + if (hi0 > lo0) { + // Arbitrarily establishing partition element as the midpoint of + // the array. + mid = (String) v.elementAt((lo0 + hi0) / 2); + + // loop through the array until indices cross + while (lo <= hi) { + // find the first element that is greater than or equal to + // the partition element starting from the left Index. + // + // Nasty to have to cast here. Would it be quicker + // to copy the vectors into arrays and sort the arrays? + while ((lo < hi0) && lt((String) v.elementAt(lo), mid)) { + ++lo; + } + + // find an element that is smaller than or equal to + // the partition element starting from the right Index. + while ((hi > lo0) && lt(mid, (String) v.elementAt(hi))) { + --hi; + } + + // if the indexes have not crossed, swap + if (lo <= hi) { + swap(v, lo, hi); + ++lo; + --hi; + } + } + + // If the right index has not reached the left side of array + // must now sort the left partition. + if (lo0 < hi) { + quickSort(v, lo0, hi); + } + + // If the left index has not reached the right side of array + // must now sort the right partition. + if (lo < hi0) { + quickSort(v, lo, hi0); + } - // find an element that is smaller than or equal to - // the partition element starting from the right Index. - while((hi > lo0) && lt(mid, (String)v.elementAt(hi))) { - --hi; } + } + + private void swap(Vector a, int i, int j) { + Object T = a.elementAt(i); + a.setElementAt(a.elementAt(j), i); + a.setElementAt(T, j); + } + + protected boolean lt(String a, String b) { + return a.compareTo(b) < 0; + } + + // automated updates - // if the indexes have not crossed, swap - if(lo <= hi) { - swap(v, lo, hi); - ++lo; - --hi; + private boolean autoUpdating = false; + private int updateInterval = 2000; // one-second delay on average + protected Timer timer = null; + + public boolean isAutoUpdating() { + return autoUpdating; + } + + public void setAutoUpdating(boolean shouldAutoUpdate) { + if (shouldAutoUpdate) { + if (timer == null) { + timer = new Timer(updateInterval, this); + timer.setRepeats(true); + timer.start(); + } + } else { + if (timer != null) { + timer.stop(); + timer = null; + } } - } + autoUpdating = shouldAutoUpdate; + } + + public int getUpdateInterval() { + return updateInterval; + } - // If the right index has not reached the left side of array - // must now sort the left partition. - if(lo0 < hi) { - quickSort(v, lo0, hi); - } + public void setUpdateInterval(int anInterval) { + if (timer != null) { + timer.setDelay(anInterval); + } - // If the left index has not reached the right side of array - // must now sort the right partition. - if(lo < hi0) { - quickSort(v, lo, hi0); - } + updateInterval = anInterval; + } + public void actionPerformed(ActionEvent evt) { + if (evt.getSource() == timer) { + fireTableDataChanged(); + } } - } - - private void swap(Vector a, int i, int j) { - Object T = a.elementAt(i); - a.setElementAt(a.elementAt(j), i); - a.setElementAt(T, j); - } - - protected boolean lt(String a, String b) { - return a.compareTo(b) < 0; - } - - // automated updates - - private boolean autoUpdating = false; - private int updateInterval = 2000; // one-second delay on average - protected Timer timer = null; - - public boolean isAutoUpdating() - { - return autoUpdating; - } - - public void setAutoUpdating( boolean shouldAutoUpdate ) - { - if ( shouldAutoUpdate ) - { - if ( timer == null ) - { - timer = new Timer( updateInterval, this ); - timer.setRepeats( true ); - timer.start(); - } - } - else - { - if ( timer != null ) - { - timer.stop(); - timer = null; - } - } - - autoUpdating = shouldAutoUpdate; - } - - public int getUpdateInterval() - { - return updateInterval; - } - - public void setUpdateInterval( int anInterval ) - { - if ( timer != null ) - { - timer.setDelay( anInterval ); - } - - updateInterval = anInterval; - } - - public void actionPerformed( ActionEvent evt ) - { - if ( evt.getSource() == timer ) - { - fireTableDataChanged(); - } - } } - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/RadioButtonPanel.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/RadioButtonPanel.java index 2956c71..62cbc2c 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/RadioButtonPanel.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/RadioButtonPanel.java @@ -26,149 +26,139 @@ import javax.swing.JRadioButton; import javax.swing.border.EmptyBorder; /** -* RadioButtonPanel is a simple extension of ButtonPanel. -* Differences are that it uses radio buttons and the -* default alignment is vertical. The radio buttons are -* placed in a ButtonGroup and the panel defaults to having -* no buttons selected. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -* $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ -*/ -public class RadioButtonPanel extends ButtonPanel -{ -/** -* A ButtonGroup to help manage button state. -*/ - protected ButtonGroup buttonGroup; - -/** -* ButtonGroup does not make it easy to unselect all buttons. -* The preferred way to do it is actually to create a hidden button. -*/ - protected JRadioButton hiddenButton; - -/** -* Constructs a RadioButtonPanel. Three buttons are created -* so the panel is filled when used in a GUI-builder environment. -*/ - public RadioButtonPanel() - { - super(); - } - -/** -* Constructs a ButtonPanel using specified buttons. -* @param buttonList An array containing the strings to be used in labeling the buttons. -*/ - public RadioButtonPanel( String[] buttonList ) - { - super( buttonList ); - } - -/** -* Overridden to set vertical-center alignment and 2-pixel vgap. -*/ - protected void initLayout() - { - super.initLayout(); - buttonPanelLayout.setAlignment( BetterFlowLayout.CENTER_VERTICAL ); - buttonPanelLayout.setVgap( 2 ); // looks nicer than java l&f recommendation (imho) - } - -/** -* Overridden to return a JRadioButton. -* @param aLabel The label for the component that will be created. -* @return The newly created component. -*/ - protected Component createComponentWithLabel( String aLabel ) - { - String buttonLabel = aLabel; - JRadioButton newButton = new JRadioButton(); - newButton.setName( aLabel ); - newButton.setText( buttonLabel ); - newButton.setActionCommand( aLabel ); - newButton.addActionListener( this ); - - // reduce insets per java l&f guidelines (was 4 on each side) - newButton.setBorder( new EmptyBorder( 0, 4, 0, 4 ) ); - - if ( buttonGroup == null ) - { - buttonGroup = new ButtonGroup(); - - // cheesy hack to allow a buttongroup to have no items selected. - // note that the button is not added to container or buttonList. - hiddenButton = new JRadioButton( "Hidden Button" ); - buttonGroup.add( hiddenButton ); - } - buttonGroup.add( newButton ); - - return newButton; - } - -/** -* Selects the button whose name matches the given text value. -* @param newText A String matching the name of one of the buttons. -* If null, empty, or not matching, all buttons are deselected. -*/ - public void setValue(String aName) - { - if ( aName != null ) - { - Component c = null; - int count = buttonContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = buttonContainer.getComponent( i ); - if ( c instanceof AbstractButton ) - { - if ( c.getName().equals( aName ) ) - { - ((AbstractButton)c).setSelected( true ); - return; - } - } - } - } - - // null, empty, or not matching - deselect all - hiddenButton.setSelected( true ); - } - -/** -* Gets the name of the currently selected button. -* @return A string matching the name of the currently selected button, -* or null of no button is selected. -*/ - public String getValue() - { - String result = null; - Component c = null; - int count = buttonContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = buttonContainer.getComponent( i ); - if ( ( c instanceof AbstractButton ) && ( ((AbstractButton)c).isSelected() ) ) - { - return c.getName(); - } - } - return result; - } - -/** -* Tests whether the specified value is checked. -* @param aValue A value to be tested. -* @return True if the specified value is checked, otherwise false. -*/ - public boolean getValue( String aValue ) - { - if ( aValue == null ) return false; - return aValue.equals( getValue() ); - } + * RadioButtonPanel is a simple extension of ButtonPanel. Differences are that + * it uses radio buttons and the default alignment is vertical. The radio + * buttons are placed in a ButtonGroup and the panel defaults to having no + * buttons selected. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ + */ +public class RadioButtonPanel extends ButtonPanel { + /** + * A ButtonGroup to help manage button state. + */ + protected ButtonGroup buttonGroup; + + /** + * ButtonGroup does not make it easy to unselect all buttons. The preferred way + * to do it is actually to create a hidden button. + */ + protected JRadioButton hiddenButton; + + /** + * Constructs a RadioButtonPanel. Three buttons are created so the panel is + * filled when used in a GUI-builder environment. + */ + public RadioButtonPanel() { + super(); + } + + /** + * Constructs a ButtonPanel using specified buttons. + * + * @param buttonList An array containing the strings to be used in labeling the + * buttons. + */ + public RadioButtonPanel(String[] buttonList) { + super(buttonList); + } + + /** + * Overridden to set vertical-center alignment and 2-pixel vgap. + */ + protected void initLayout() { + super.initLayout(); + buttonPanelLayout.setAlignment(BetterFlowLayout.CENTER_VERTICAL); + buttonPanelLayout.setVgap(2); // looks nicer than java l&f recommendation (imho) + } + + /** + * Overridden to return a JRadioButton. + * + * @param aLabel The label for the component that will be created. + * @return The newly created component. + */ + protected Component createComponentWithLabel(String aLabel) { + String buttonLabel = aLabel; + JRadioButton newButton = new JRadioButton(); + newButton.setName(aLabel); + newButton.setText(buttonLabel); + newButton.setActionCommand(aLabel); + newButton.addActionListener(this); + + // reduce insets per java l&f guidelines (was 4 on each side) + newButton.setBorder(new EmptyBorder(0, 4, 0, 4)); + + if (buttonGroup == null) { + buttonGroup = new ButtonGroup(); + + // cheesy hack to allow a buttongroup to have no items selected. + // note that the button is not added to container or buttonList. + hiddenButton = new JRadioButton("Hidden Button"); + buttonGroup.add(hiddenButton); + } + buttonGroup.add(newButton); + + return newButton; + } + + /** + * Selects the button whose name matches the given text value. + * + * @param newText A String matching the name of one of the buttons. If null, + * empty, or not matching, all buttons are deselected. + */ + public void setValue(String aName) { + if (aName != null) { + Component c = null; + int count = buttonContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = buttonContainer.getComponent(i); + if (c instanceof AbstractButton) { + if (c.getName().equals(aName)) { + ((AbstractButton) c).setSelected(true); + return; + } + } + } + } + + // null, empty, or not matching - deselect all + hiddenButton.setSelected(true); + } + + /** + * Gets the name of the currently selected button. + * + * @return A string matching the name of the currently selected button, or null + * of no button is selected. + */ + public String getValue() { + String result = null; + Component c = null; + int count = buttonContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = buttonContainer.getComponent(i); + if ((c instanceof AbstractButton) && (((AbstractButton) c).isSelected())) { + return c.getName(); + } + } + return result; + } + + /** + * Tests whether the specified value is checked. + * + * @param aValue A value to be tested. + * @return True if the specified value is checked, otherwise false. + */ + public boolean getValue(String aValue) { + if (aValue == null) + return false; + return aValue.equals(getValue()); + } } - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/SmartPasswordField.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/SmartPasswordField.java index 6914cf6..55ba2f7 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/SmartPasswordField.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/SmartPasswordField.java @@ -26,249 +26,227 @@ import java.awt.event.KeyEvent; import javax.swing.JPasswordField; /** - * SmartPasswordField is an extention of JPasswordField. It does everything - * a JPassword does, as well as limit the number of characters. The user - * of this class can specify that a password can only have a maximum of - * 10 characters for instance. + * SmartPasswordField is an extention of JPasswordField. It does everything a + * JPassword does, as well as limit the number of characters. The user of this + * class can specify that a password can only have a maximum of 10 characters + * for instance. * * @author rob@straylight.princeton.com * @author $Author: cgruber $ * @version $Revision: 904 $ */ -public class SmartPasswordField extends JPasswordField -{ - -/******************************* -* CONSTANTS -*******************************/ - private static final int BACKSPACE = 8; - private static final int DELETE = 127; - private static final int SPACE = 32; - private static final int DASH = 45; - private static final int UNDERSCORE = 95; - private static final int PERIOD = 46; - private static final int PASTE = 22; // Ctl-V - - private int passwordLength = Integer.MAX_VALUE; - -/******************************* -* PUBLIC METHODS -*******************************/ - -/** -* Default constructor. -*/ - public SmartPasswordField() - { - super(); - } - -/** -* This constructor allows the user to set the maximum length of the password. -* @param aLength The maximum length of the password. -*/ - public SmartPasswordField( int aLength ) - { - this(); - setPasswordLength( aLength ); - } - -/** -* Sets the maximum lenght of the password. The value must be 0 or greater. -* If the length specified is less than 0, then no action occurs. -* @param aLength The maximum lenght of the password. -*/ - public void setPasswordLength( int aLength ) - { - if ( aLength >= 0 ) - { - passwordLength = aLength; - } - } - -/** -* Returns the current maximum length of the password. -* @return The current maximum length of the password. -*/ - public int getPasswordLength() - { - return passwordLength; - } - -/** -* This method processes a key event. This event is generated by input from the -* keyboard when this text field has the focus. This method is called for every -* key that is pressed and released on the keyboard. This includes modifier -* keys like the shift and alt keys. This class looks at the key and determines -* if the key is valid input given the restrictions of this class. <BR> <BR> -* @param e A key event generated by a keyboard action. -*/ - public void processKeyEvent(KeyEvent e) - { - String currentText = ""; - String testString = ""; - char newChar = e.getKeyChar(); - int currentLength = 0; - int selectionStart = 0; - int selectionEnd = 0; - int endOfHead = 0; - int startOfTail = 0; - boolean backspace = false; - boolean delete = false; - boolean paste = false; - boolean insertionPoint = false; - boolean selectionAtStart = false; - boolean selectionAtEnd = false; - - backspace = (newChar == BACKSPACE); - delete = (newChar == DELETE); - paste = (newChar == PASTE); - - if ((e.getKeyCode() == KeyEvent.VK_UNDEFINED) || ((backspace) || (delete) || (paste))) // A "key-typed" event - { - if (isValidCharacter(newChar)) - { - - if ((isPrintableCharacter(newChar)) || (backspace) || (delete) || (paste)) - { - // Analyze the current contents of the field - currentText = new String( getPassword() ); - currentLength = currentText.length(); - - selectionStart = getSelectionStart(); - selectionEnd = getSelectionEnd(); - - insertionPoint = (selectionStart == selectionEnd); - selectionAtStart = (selectionStart == 0); - selectionAtEnd = (selectionEnd >= currentLength); - if (selectionEnd > currentLength) - { - setSelectionEnd(currentLength); - } - - // Generate new string - if (selectionStart > 0) // Create head of test string - { - endOfHead = selectionStart; - if (insertionPoint && backspace) - { - endOfHead -= 1; - } - testString += currentText.substring(0, endOfHead); - } - - if (!(backspace || delete || paste)) // Add the new character - { - testString += newChar; - } - - if (paste) // Add the string from the clipboard - { - Transferable data = getToolkit().getSystemClipboard().getContents(this); - if (data != null) - { - try - { - String clipString = (String)data.getTransferData(DataFlavor.stringFlavor); - testString += clipString; - } - catch (java.io.IOException ioe) - { - // Do nothing - } - catch (UnsupportedFlavorException ufe) - { - // Do nothing - } - } - } - - if (selectionEnd < currentLength) // Add the tail of the string - { - startOfTail = selectionEnd; - if (insertionPoint && delete) - { - startOfTail += 1; - } - testString += currentText.substring(startOfTail); - } - - } - - if (testString.compareTo("") != 0) // Null string is OK - { - if (!(isValidString(testString))) - { - e.consume(); - } - } - } - else - { - e.consume(); - } - } - super.processKeyEvent(e); - - postProcessing(); - } - - -/******************************* -* PROTECTED METHODS -*******************************/ - -/** -* Returns whether the inputted character is valid or not. In this case all -* characters are valid input. -* @param aChar A character to perform the validity test with. -* @return True if the character is valid for this subclassed text field. <BR> -* False is the character is not valid. -*/ - protected boolean isValidCharacter(char aChar) - { - return true; - } - -/** -* Returns whether a string is valid for this text field. As the user types from -* the keyboard, this method is called to determine if the new string in the text -* field is valid based upon the restriction of this class. The length of the -* new string is checked against the maximum password length. -* @param aString The string to perform the validity check with. -* @return True if the length of the string is less than or equal to the maximum length. -* False if the character is not valud. -*/ - protected boolean isValidString(String aString) - { - if ( aString.length() > passwordLength ) - { - return false; - } - - return true; - } - -/** -* This class does not need any post processing. -*/ - protected void postProcessing() - { - /* Do Nothing */ - } - - -/******************************* -* PRIVATE METHODS -*******************************/ - - private boolean isPrintableCharacter(char inputChar) - { - if ((inputChar >= ' ') && (inputChar <= '~')) - { - return true; - } - return false; - } +public class SmartPasswordField extends JPasswordField { + + /******************************* + * CONSTANTS + *******************************/ + private static final int BACKSPACE = 8; + private static final int DELETE = 127; + private static final int SPACE = 32; + private static final int DASH = 45; + private static final int UNDERSCORE = 95; + private static final int PERIOD = 46; + private static final int PASTE = 22; // Ctl-V + + private int passwordLength = Integer.MAX_VALUE; + + /******************************* + * PUBLIC METHODS + *******************************/ + + /** + * Default constructor. + */ + public SmartPasswordField() { + super(); + } + + /** + * This constructor allows the user to set the maximum length of the password. + * + * @param aLength The maximum length of the password. + */ + public SmartPasswordField(int aLength) { + this(); + setPasswordLength(aLength); + } + + /** + * Sets the maximum lenght of the password. The value must be 0 or greater. If + * the length specified is less than 0, then no action occurs. + * + * @param aLength The maximum lenght of the password. + */ + public void setPasswordLength(int aLength) { + if (aLength >= 0) { + passwordLength = aLength; + } + } + + /** + * Returns the current maximum length of the password. + * + * @return The current maximum length of the password. + */ + public int getPasswordLength() { + return passwordLength; + } + + /** + * This method processes a key event. This event is generated by input from the + * keyboard when this text field has the focus. This method is called for every + * key that is pressed and released on the keyboard. This includes modifier keys + * like the shift and alt keys. This class looks at the key and determines if + * the key is valid input given the restrictions of this class. <BR> + * <BR> + * + * @param e A key event generated by a keyboard action. + */ + public void processKeyEvent(KeyEvent e) { + String currentText = ""; + String testString = ""; + char newChar = e.getKeyChar(); + int currentLength = 0; + int selectionStart = 0; + int selectionEnd = 0; + int endOfHead = 0; + int startOfTail = 0; + boolean backspace = false; + boolean delete = false; + boolean paste = false; + boolean insertionPoint = false; + boolean selectionAtStart = false; + boolean selectionAtEnd = false; + + backspace = (newChar == BACKSPACE); + delete = (newChar == DELETE); + paste = (newChar == PASTE); + + if ((e.getKeyCode() == KeyEvent.VK_UNDEFINED) || ((backspace) || (delete) || (paste))) // A "key-typed" event + { + if (isValidCharacter(newChar)) { + + if ((isPrintableCharacter(newChar)) || (backspace) || (delete) || (paste)) { + // Analyze the current contents of the field + currentText = new String(getPassword()); + currentLength = currentText.length(); + + selectionStart = getSelectionStart(); + selectionEnd = getSelectionEnd(); + + insertionPoint = (selectionStart == selectionEnd); + selectionAtStart = (selectionStart == 0); + selectionAtEnd = (selectionEnd >= currentLength); + if (selectionEnd > currentLength) { + setSelectionEnd(currentLength); + } + + // Generate new string + if (selectionStart > 0) // Create head of test string + { + endOfHead = selectionStart; + if (insertionPoint && backspace) { + endOfHead -= 1; + } + testString += currentText.substring(0, endOfHead); + } + + if (!(backspace || delete || paste)) // Add the new character + { + testString += newChar; + } + + if (paste) // Add the string from the clipboard + { + Transferable data = getToolkit().getSystemClipboard().getContents(this); + if (data != null) { + try { + String clipString = (String) data.getTransferData(DataFlavor.stringFlavor); + testString += clipString; + } catch (java.io.IOException ioe) { + // Do nothing + } catch (UnsupportedFlavorException ufe) { + // Do nothing + } + } + } + + if (selectionEnd < currentLength) // Add the tail of the string + { + startOfTail = selectionEnd; + if (insertionPoint && delete) { + startOfTail += 1; + } + testString += currentText.substring(startOfTail); + } + + } + + if (testString.compareTo("") != 0) // Null string is OK + { + if (!(isValidString(testString))) { + e.consume(); + } + } + } else { + e.consume(); + } + } + super.processKeyEvent(e); + + postProcessing(); + } + + /******************************* + * PROTECTED METHODS + *******************************/ + + /** + * Returns whether the inputted character is valid or not. In this case all + * characters are valid input. + * + * @param aChar A character to perform the validity test with. + * @return True if the character is valid for this subclassed text field. <BR> + * False is the character is not valid. + */ + protected boolean isValidCharacter(char aChar) { + return true; + } + + /** + * Returns whether a string is valid for this text field. As the user types from + * the keyboard, this method is called to determine if the new string in the + * text field is valid based upon the restriction of this class. The length of + * the new string is checked against the maximum password length. + * + * @param aString The string to perform the validity check with. + * @return True if the length of the string is less than or equal to the maximum + * length. False if the character is not valud. + */ + protected boolean isValidString(String aString) { + if (aString.length() > passwordLength) { + return false; + } + + return true; + } + + /** + * This class does not need any post processing. + */ + protected void postProcessing() { + /* Do Nothing */ + } + + /******************************* + * PRIVATE METHODS + *******************************/ + + private boolean isPrintableCharacter(char inputChar) { + if ((inputChar >= ' ') && (inputChar <= '~')) { + return true; + } + return false; + } } diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/SmartTextField.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/SmartTextField.java index cee37e1..5092179 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/SmartTextField.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/SmartTextField.java @@ -27,218 +27,204 @@ import javax.swing.JTextField; /** * SmartTextField is an abstract class for that allows the text field to - * intelligently analyze the user's input in real-time. As the user enters + * intelligently analyze the user's input in real-time. As the user enters * keystrokes, the generated string is analyzed to determine if the new string * is valid based on the criteria of the concrete classes that extend this - * class. An invalid keystroke is rejected and not displayed in the text - * field. This class can be extended to to create smart text fields that only - * accept integers or floating points number or alphabetic strings of maximum - * length. These are several examples. + * class. An invalid keystroke is rejected and not displayed in the text field. + * This class can be extended to to create smart text fields that only accept + * integers or floating points number or alphabetic strings of maximum length. + * These are several examples. * * @author rob@straylight.princeton.com * @author $Author: cgruber $ * @version $Revision: 904 $ */ -public abstract class SmartTextField extends JTextField -{ - -/******************************* -* CONSTANTS -*******************************/ - private static final int BACKSPACE = 8; - private static final int DELETE = 127; - private static final int SPACE = 32; - private static final int DASH = 45; - private static final int UNDERSCORE = 95; - private static final int PERIOD = 46; - private static final int PASTE = 22; // Ctl-V - - -/******************************* -* PUBLIC METHODS -*******************************/ - -/** -* This method processes a key event. This event is generated by input from the -* keyboard when this text field has the focus. This method is called for every -* key that is pressed and released on the keyboard. This includes modifier -* keys like the shift and alt keys. This class looks at the key and determines -* if the key is valid input given the restrictions of the concrete sub-classes. <BR> <BR> -* Example - A smart text field only allows alphabetic characters. If the key -* pressed is a "2" then this method will determine that the key is invalid and -* "consume" the key event. <BR> <BR> -* Note - Every printable character has a "TYPED" key event. Currentlt under -* Java 1.2.1 this does not happen. Bug 4186905 relating this bug has been -* fixed and is awaiting release. -* @param e A key event generated by a keyboard action. -*/ - public void processKeyEvent(KeyEvent e) - { - String currentText = ""; - String testString = ""; - char newChar = e.getKeyChar(); - int currentLength = 0; - int selectionStart = 0; - int selectionEnd = 0; - int endOfHead = 0; - int startOfTail = 0; - boolean backspace = false; - boolean delete = false; - boolean paste = false; - boolean insertionPoint = false; - boolean selectionAtStart = false; - boolean selectionAtEnd = false; - - backspace = (newChar == BACKSPACE); - delete = (newChar == DELETE); - paste = (newChar == PASTE); - - if ((e.getKeyCode() == KeyEvent.VK_UNDEFINED) || ((backspace) || (delete) || (paste))) // A "key-typed" event - { - if (isValidCharacter(newChar)) - { - - if ((isPrintableCharacter(newChar)) || (backspace) || (delete) || (paste)) - { - // Analyze the current contents of the field - currentText = getText(); - currentLength = currentText.length(); - - selectionStart = getSelectionStart(); - selectionEnd = getSelectionEnd(); - - insertionPoint = (selectionStart == selectionEnd); - selectionAtStart = (selectionStart == 0); - selectionAtEnd = (selectionEnd >= currentLength); - if (selectionEnd > currentLength) - { - setSelectionEnd(currentLength); - } - - // Generate new string - if (selectionStart > 0) // Create head of test string - { - endOfHead = selectionStart; - if (insertionPoint && backspace) - { - endOfHead -= 1; - } - testString += currentText.substring(0, endOfHead); - } - - if (!(backspace || delete || paste)) // Add the new character - { - testString += newChar; - } - - if (paste) // Add the string from the clipboard - { - Transferable data = getToolkit().getSystemClipboard().getContents(this); - if (data != null) - { - try - { - String clipString = (String)data.getTransferData(DataFlavor.stringFlavor); - testString += clipString; - } - catch (java.io.IOException ioe) - { - // Do nothing - } - catch (UnsupportedFlavorException ufe) - { - // Do nothing - } - } - } - - if (selectionEnd < currentLength) // Add the tail of the string - { - startOfTail = selectionEnd; - if (insertionPoint && delete) - { - startOfTail += 1; - } - testString += currentText.substring(startOfTail); - } - - } - - if (testString.compareTo("") != 0) // Null string is OK - { - if (!(isValidString(testString))) - { - e.consume(); - } - } - } - else - { - e.consume(); - } - } - super.processKeyEvent(e); - - postProcessing(); - } - - -/******************************* -* PROTECTED METHODS -*******************************/ - -/** -* Default constructor for this class. The initial text of the smart text field -* can be specified as well as the size (in characters) of the text field. -* @param text The initial string that is displayed in the text field. -* @param columns THe width of the text field in characters. -*/ - protected SmartTextField(String text, int columns) - { - super(text, columns); - } - -/** -* Returns whether a character is valid for this text field. As the user types -* from the keyboard, this method is called to determine if the character is a -* valid character based in the restrictions of the subclass. -* @param aChar A character to perform the validity test with. -* @return True if the character is valid for this subclassed text field. <BR> -* False is the character is not valid. -*/ - abstract protected boolean isValidCharacter(char aChar); - -/** -* Returns whether a string is valid for this text field. As the user types from -* the keyboard, this method is called to determine if the new string in the text -* field is valid based upon the restriction of the subclass. This is done after -* the character has been determined to be valid since there can be restrictions -* placed on the text string as a whole, such a maximum length or date format. -* @param aString The string to perform the validity check with. -* @return True if the string is valid for this subclassed text field. <BR> -* False if the character is not valud. -*/ - abstract protected boolean isValidString(String aString); - -/** -* This method is used by the any subclass that need to complete any processing -* of the text string in the text field after all the requirement checks have -* been performed. -*/ - abstract protected void postProcessing(); - - -/******************************* -* PRIVATE METHODS -*******************************/ - - private boolean isPrintableCharacter(char inputChar) - { - if ((inputChar >= ' ') && (inputChar <= '~')) - { - return true; - } - return false; - } +public abstract class SmartTextField extends JTextField { + + /******************************* + * CONSTANTS + *******************************/ + private static final int BACKSPACE = 8; + private static final int DELETE = 127; + private static final int SPACE = 32; + private static final int DASH = 45; + private static final int UNDERSCORE = 95; + private static final int PERIOD = 46; + private static final int PASTE = 22; // Ctl-V + + /******************************* + * PUBLIC METHODS + *******************************/ + + /** + * This method processes a key event. This event is generated by input from the + * keyboard when this text field has the focus. This method is called for every + * key that is pressed and released on the keyboard. This includes modifier keys + * like the shift and alt keys. This class looks at the key and determines if + * the key is valid input given the restrictions of the concrete sub-classes. + * <BR> + * <BR> + * Example - A smart text field only allows alphabetic characters. If the key + * pressed is a "2" then this method will determine that the key is invalid and + * "consume" the key event. <BR> + * <BR> + * Note - Every printable character has a "TYPED" key event. Currentlt under + * Java 1.2.1 this does not happen. Bug 4186905 relating this bug has been fixed + * and is awaiting release. + * + * @param e A key event generated by a keyboard action. + */ + public void processKeyEvent(KeyEvent e) { + String currentText = ""; + String testString = ""; + char newChar = e.getKeyChar(); + int currentLength = 0; + int selectionStart = 0; + int selectionEnd = 0; + int endOfHead = 0; + int startOfTail = 0; + boolean backspace = false; + boolean delete = false; + boolean paste = false; + boolean insertionPoint = false; + boolean selectionAtStart = false; + boolean selectionAtEnd = false; + + backspace = (newChar == BACKSPACE); + delete = (newChar == DELETE); + paste = (newChar == PASTE); + + if ((e.getKeyCode() == KeyEvent.VK_UNDEFINED) || ((backspace) || (delete) || (paste))) // A "key-typed" event + { + if (isValidCharacter(newChar)) { + + if ((isPrintableCharacter(newChar)) || (backspace) || (delete) || (paste)) { + // Analyze the current contents of the field + currentText = getText(); + currentLength = currentText.length(); + + selectionStart = getSelectionStart(); + selectionEnd = getSelectionEnd(); + + insertionPoint = (selectionStart == selectionEnd); + selectionAtStart = (selectionStart == 0); + selectionAtEnd = (selectionEnd >= currentLength); + if (selectionEnd > currentLength) { + setSelectionEnd(currentLength); + } + + // Generate new string + if (selectionStart > 0) // Create head of test string + { + endOfHead = selectionStart; + if (insertionPoint && backspace) { + endOfHead -= 1; + } + testString += currentText.substring(0, endOfHead); + } + + if (!(backspace || delete || paste)) // Add the new character + { + testString += newChar; + } + + if (paste) // Add the string from the clipboard + { + Transferable data = getToolkit().getSystemClipboard().getContents(this); + if (data != null) { + try { + String clipString = (String) data.getTransferData(DataFlavor.stringFlavor); + testString += clipString; + } catch (java.io.IOException ioe) { + // Do nothing + } catch (UnsupportedFlavorException ufe) { + // Do nothing + } + } + } + + if (selectionEnd < currentLength) // Add the tail of the string + { + startOfTail = selectionEnd; + if (insertionPoint && delete) { + startOfTail += 1; + } + testString += currentText.substring(startOfTail); + } + + } + + if (testString.compareTo("") != 0) // Null string is OK + { + if (!(isValidString(testString))) { + e.consume(); + } + } + } else { + e.consume(); + } + } + super.processKeyEvent(e); + + postProcessing(); + } + + /******************************* + * PROTECTED METHODS + *******************************/ + + /** + * Default constructor for this class. The initial text of the smart text field + * can be specified as well as the size (in characters) of the text field. + * + * @param text The initial string that is displayed in the text field. + * @param columns THe width of the text field in characters. + */ + protected SmartTextField(String text, int columns) { + super(text, columns); + } + + /** + * Returns whether a character is valid for this text field. As the user types + * from the keyboard, this method is called to determine if the character is a + * valid character based in the restrictions of the subclass. + * + * @param aChar A character to perform the validity test with. + * @return True if the character is valid for this subclassed text field. <BR> + * False is the character is not valid. + */ + abstract protected boolean isValidCharacter(char aChar); + + /** + * Returns whether a string is valid for this text field. As the user types from + * the keyboard, this method is called to determine if the new string in the + * text field is valid based upon the restriction of the subclass. This is done + * after the character has been determined to be valid since there can be + * restrictions placed on the text string as a whole, such a maximum length or + * date format. + * + * @param aString The string to perform the validity check with. + * @return True if the string is valid for this subclassed text field. <BR> + * False if the character is not valud. + */ + abstract protected boolean isValidString(String aString); + + /** + * This method is used by the any subclass that need to complete any processing + * of the text string in the text field after all the requirement checks have + * been performed. + */ + abstract protected void postProcessing(); + + /******************************* + * PRIVATE METHODS + *******************************/ + + private boolean isPrintableCharacter(char inputChar) { + if ((inputChar >= ' ') && (inputChar <= '~')) { + return true; + } + return false; + } } diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/StatusButtonPanel.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/StatusButtonPanel.java index 3d9a85b..ba9f361 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/StatusButtonPanel.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/StatusButtonPanel.java @@ -34,243 +34,228 @@ import javax.swing.UIManager; import javax.swing.border.EmptyBorder; /** -* StatusButtonPanel extends ButtonPanel to provide a space -* to display status messages in a consistent manner.<BR><BR> -* Messages are erased after a certain predefined interval, -* defaulting to 10 seconds. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class StatusButtonPanel extends ButtonPanel -{ -/** -* This is the action command to all listeners when the status text is changed. -*/ - public static final String STATUS_CHANGED = "STATUS_CHANGED"; - - // note: weirdness happens if you initialize - // this variable. Because it is set by initLayout - // and initLayout is called by the superclass constructor, - // this variable would get initialized after initLayout - // is called... - protected Component statusComponent; // = null; - - protected Timer timer = null; - protected int interval = 10000; // adjust as needed - -/** -* Constructs a StatusButtonPanel. Three buttons are created -* so the panel is filled when used in a GUI-builder environment. -*/ - public StatusButtonPanel() - { - super(); - setupTimer(); - } - -/** -* Constructs a StatusButtonPanel using specified buttons. -* @param buttonList An array containing the strings to be used in labeling the buttons. -*/ - public StatusButtonPanel( String[] buttonList ) - { - super( buttonList ); - setupTimer(); - } - -/** -* Initializes the timer instance variable. -*/ - protected void setupTimer() - { - timer = new Timer( interval, this ); - timer.addActionListener( this ); - timer.setRepeats( false ); - timer.start(); - } - -/** -* Returns the number of milliseconds before the status message is cleared. -* The default is 10000. -* @return The current delay interval in milliseconds. -*/ - public int getDelayInterval() - { - return interval; - } - -/** -* Sets the number of milliseconds before the status message is cleared. -* @param millis The new delay interval in milliseconds. -*/ - public void setDelayInterval( int millis ) - { - interval = millis; - timer.setDelay( interval ); - } - -/** -* Returns the visual component used to display the status. -* @return A component used for displaying status. -*/ - public Component getStatusComponent() - { - return statusComponent; - - } -/** -* Receives ActionEvents from the internal timer. -* @param e The action event in question. -*/ - public void actionPerformed(ActionEvent e) - { - if ( e.getSource() == timer ) - { - setText( "" ); - return; - } - - // otherwise continue with superclass implementation - super.actionPerformed( e ); - } - -/** -* This method is responsible for the initial layout of the panel. -* Subclasses can implement different layouts, but this method -* is responsible for initializing buttonPanelLayout to a valid -* layout manager and setting this panel to use it. This method -* must should initialize statusComponent to a component that ideally -* has get/setText methods, although this is not required. -*/ - protected void initLayout() - { - - statusComponent = new JTextField(); - JTextField textField = (JTextField) statusComponent; - textField.setColumns( 20 ); - textField.setBackground( getBackground() ); - textField.setEditable( false ); + * StatusButtonPanel extends ButtonPanel to provide a space to display status + * messages in a consistent manner.<BR> + * <BR> + * Messages are erased after a certain predefined interval, defaulting to 10 + * seconds. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class StatusButtonPanel extends ButtonPanel { + /** + * This is the action command to all listeners when the status text is changed. + */ + public static final String STATUS_CHANGED = "STATUS_CHANGED"; + + // note: weirdness happens if you initialize + // this variable. Because it is set by initLayout + // and initLayout is called by the superclass constructor, + // this variable would get initialized after initLayout + // is called... + protected Component statusComponent; // = null; + + protected Timer timer = null; + protected int interval = 10000; // adjust as needed + + /** + * Constructs a StatusButtonPanel. Three buttons are created so the panel is + * filled when used in a GUI-builder environment. + */ + public StatusButtonPanel() { + super(); + setupTimer(); + } + + /** + * Constructs a StatusButtonPanel using specified buttons. + * + * @param buttonList An array containing the strings to be used in labeling the + * buttons. + */ + public StatusButtonPanel(String[] buttonList) { + super(buttonList); + setupTimer(); + } + + /** + * Initializes the timer instance variable. + */ + protected void setupTimer() { + timer = new Timer(interval, this); + timer.addActionListener(this); + timer.setRepeats(false); + timer.start(); + } + + /** + * Returns the number of milliseconds before the status message is cleared. The + * default is 10000. + * + * @return The current delay interval in milliseconds. + */ + public int getDelayInterval() { + return interval; + } + + /** + * Sets the number of milliseconds before the status message is cleared. + * + * @param millis The new delay interval in milliseconds. + */ + public void setDelayInterval(int millis) { + interval = millis; + timer.setDelay(interval); + } + + /** + * Returns the visual component used to display the status. + * + * @return A component used for displaying status. + */ + public Component getStatusComponent() { + return statusComponent; + + } + + /** + * Receives ActionEvents from the internal timer. + * + * @param e The action event in question. + */ + public void actionPerformed(ActionEvent e) { + if (e.getSource() == timer) { + setText(""); + return; + } + + // otherwise continue with superclass implementation + super.actionPerformed(e); + } + + /** + * This method is responsible for the initial layout of the panel. Subclasses + * can implement different layouts, but this method is responsible for + * initializing buttonPanelLayout to a valid layout manager and setting this + * panel to use it. This method must should initialize statusComponent to a + * component that ideally has get/setText methods, although this is not + * required. + */ + protected void initLayout() { + + statusComponent = new JTextField(); + JTextField textField = (JTextField) statusComponent; + textField.setColumns(20); + textField.setBackground(getBackground()); + textField.setEditable(false); // statusComponent = new PickListPanel(); // for testing - this.setLayout( new GridBagLayout() ); - - GridBagConstraints gbc = - new GridBagConstraints(); - gbc.gridx = GridBagConstraints.RELATIVE; - gbc.gridy = GridBagConstraints.RELATIVE; - gbc.gridwidth = 1; - gbc.gridheight = 1; - gbc.weightx = 1.0; - gbc.weighty = 0.0; - gbc.anchor = GridBagConstraints.CENTER; - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.insets = new Insets(0, 5, 0, 10); - gbc.ipadx = 0; - gbc.ipady = 0; + this.setLayout(new GridBagLayout()); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = GridBagConstraints.RELATIVE; + gbc.gridy = GridBagConstraints.RELATIVE; + gbc.gridwidth = 1; + gbc.gridheight = 1; + gbc.weightx = 1.0; + gbc.weighty = 0.0; + gbc.anchor = GridBagConstraints.CENTER; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.insets = new Insets(0, 5, 0, 10); + gbc.ipadx = 0; + gbc.ipady = 0; //1.2 new GridBagConstraints(GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE, 1, 1, 1.0, 0.0, //1.2 GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 0, 10), 0, 0 ); - this.add( statusComponent, gbc ); - - buttonContainer = new JPanel(); - buttonPanelLayout = new BetterFlowLayout(); - buttonContainer.setLayout(buttonPanelLayout); - buttonPanelLayout.setAlignment( BetterFlowLayout.RIGHT ); - ((BetterFlowLayout)buttonPanelLayout).setWidthUniform( true ); - gbc.weightx = 0.0; - gbc.insets = new Insets( 0, 0, 0, 0 ); - this.add( buttonContainer, gbc ); - } - -/** -* Sets the text to appear in the status area. -* @param newText A string to appear in the status area. Nulls are allowed. -*/ - public void setText(String newText) - { - // TODO: should use property introspection instead - - // use reflection to call the "setText" method, if any. - try - { - Class c = statusComponent.getClass(); - Method m = c.getMethod( "setText", new Class[] { new String().getClass() } ); - m.invoke( statusComponent, new Object[] { newText } ); - broadcastEvent( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, STATUS_CHANGED ) ); - statusComponent.paint( statusComponent.getGraphics() ); - } - catch ( Exception exc ) - { - // "setText" method does not exist; do nothing. - } - - // if non-empty string, start the timer - if ( ! "".equals( newText ) ) - { - timer.restart(); - } - } - -/** -* Gets the text in the status area. -* @return The string being displayed in the status area. -*/ - public String getText() - { - // TODO: should use property introspection instead - - String value = ""; - // use reflection to call the "setText" method, if any. - try - { - Class c = statusComponent.getClass(); - Method m = c.getMethod( "getText", (Class[])null ); - value = (String) m.invoke( statusComponent, (Object[])null ); - } - catch ( Exception exc ) - { - // "getText" method does not exist; do nothing. - } - return value; - } - - // for testing - - public static void main( String[] argv ) - { - try - { - UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() ); - } - catch (Exception exc) - { - - } - - JFrame dialog = new JFrame(); - BorderLayout bl = new BorderLayout( 20, 20 ); + this.add(statusComponent, gbc); + + buttonContainer = new JPanel(); + buttonPanelLayout = new BetterFlowLayout(); + buttonContainer.setLayout(buttonPanelLayout); + buttonPanelLayout.setAlignment(BetterFlowLayout.RIGHT); + ((BetterFlowLayout) buttonPanelLayout).setWidthUniform(true); + gbc.weightx = 0.0; + gbc.insets = new Insets(0, 0, 0, 0); + this.add(buttonContainer, gbc); + } + + /** + * Sets the text to appear in the status area. + * + * @param newText A string to appear in the status area. Nulls are allowed. + */ + public void setText(String newText) { + // TODO: should use property introspection instead + + // use reflection to call the "setText" method, if any. + try { + Class c = statusComponent.getClass(); + Method m = c.getMethod("setText", new Class[] { new String().getClass() }); + m.invoke(statusComponent, new Object[] { newText }); + broadcastEvent(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, STATUS_CHANGED)); + statusComponent.paint(statusComponent.getGraphics()); + } catch (Exception exc) { + // "setText" method does not exist; do nothing. + } + + // if non-empty string, start the timer + if (!"".equals(newText)) { + timer.restart(); + } + } + + /** + * Gets the text in the status area. + * + * @return The string being displayed in the status area. + */ + public String getText() { + // TODO: should use property introspection instead + + String value = ""; + // use reflection to call the "setText" method, if any. + try { + Class c = statusComponent.getClass(); + Method m = c.getMethod("getText", (Class[]) null); + value = (String) m.invoke(statusComponent, (Object[]) null); + } catch (Exception exc) { + // "getText" method does not exist; do nothing. + } + return value; + } + + // for testing + + public static void main(String[] argv) { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception exc) { + + } + + JFrame dialog = new JFrame(); + BorderLayout bl = new BorderLayout(20, 20); // StatusButtonPanel panel = new StatusButtonPanel(); // System.out.println( panel.statusComponent ); - StatusButtonPanel panel = new StatusButtonPanel( new String[] { "Okay", "Cancel" } ); + StatusButtonPanel panel = new StatusButtonPanel(new String[] { "Okay", "Cancel" }); - dialog.getContentPane().setLayout( bl ); - dialog.getContentPane().add( panel, BorderLayout.SOUTH ); - dialog.setLocation( 50, 50 ); - // dialog.setSize( 450, 150 ); - dialog.pack(); - dialog.setVisible( true ); + dialog.getContentPane().setLayout(bl); + dialog.getContentPane().add(panel, BorderLayout.SOUTH); + dialog.setLocation(50, 50); + // dialog.setSize( 450, 150 ); + dialog.pack(); + dialog.setVisible(true); - panel.setBorder( new EmptyBorder( 5, 5, 5, 5 ) ); - panel.setAlignment( BetterFlowLayout.RIGHT ); + panel.setBorder(new EmptyBorder(5, 5, 5, 5)); + panel.setAlignment(BetterFlowLayout.RIGHT); // panel.getButton( "One" ).setEnabled( false ); - panel.setText( "File saved." ); - System.out.println( panel.getText() ); - } + panel.setText("File saved."); + System.out.println(panel.getText()); + } } - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TintedImageFilter.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TintedImageFilter.java index a51ed16..b15a660 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TintedImageFilter.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TintedImageFilter.java @@ -22,79 +22,75 @@ import java.awt.Color; import java.awt.image.RGBImageFilter; /** - * TintedImageFilter tints all gray pixels half-way towards - * the value passed into the constructor. This "tints" a - * mostly grayscale image. This has proven useful for tinting - * user interface decorative images towards one of the SystemColor - * constants to better mesh with a platform look and feel. + * TintedImageFilter tints all gray pixels half-way towards the value passed + * into the constructor. This "tints" a mostly grayscale image. This has proven + * useful for tinting user interface decorative images towards one of the + * SystemColor constants to better mesh with a platform look and feel. * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 893 $ */ - public class TintedImageFilter extends RGBImageFilter - { - double redOffset, greenOffset, blueOffset; - - public TintedImageFilter( Color aColor ) - { - canFilterIndexColorModel = true; - redOffset = getOffset( aColor.getRed() ); - greenOffset = getOffset( aColor.getGreen() ); - blueOffset = getOffset( aColor.getBlue() ); - } - - /** - * Calculates the offset used to modify color - * values. This method returns half the difference - * between the specified color level and 192. - */ - protected double getOffset( int colorValue ) - { - return ( colorValue - 192 ) / 2; - } +public class TintedImageFilter extends RGBImageFilter { + double redOffset, greenOffset, blueOffset; - public int filterRGB(int x, int y, int rgb) - { + public TintedImageFilter(Color aColor) { + canFilterIndexColorModel = true; + redOffset = getOffset(aColor.getRed()); + greenOffset = getOffset(aColor.getGreen()); + blueOffset = getOffset(aColor.getBlue()); + } + + /** + * Calculates the offset used to modify color values. This method returns half + * the difference between the specified color level and 192. + */ + protected double getOffset(int colorValue) { + return (colorValue - 192) / 2; + } + + public int filterRGB(int x, int y, int rgb) { + + int red = (rgb & 0xff0000) >> 16; + int green = (rgb & 0x00ff00) >> 8; + int blue = (rgb & 0x0000ff); + + // if roughly black + if (red + green + blue < 30) + return rgb; + + // if roughly gray + if ((Math.abs(red - green) < 10) && (Math.abs(red - blue) < 10)) { + red += redOffset; + if (red < 0) + red = 0; + if (red > 255) + red = 255; + green += greenOffset; + if (green < 0) + green = 0; + if (green > 255) + green = 255; + blue += blueOffset; + if (blue < 0) + blue = 0; + if (blue > 255) + blue = 255; + + return new Color(red, green, blue).getRGB(); + } + + return rgb; + } +} - int red = ( rgb & 0xff0000 ) >> 16; - int green = ( rgb & 0x00ff00 ) >> 8; - int blue = ( rgb & 0x0000ff ); - - // if roughly black - if ( red + green + blue < 30 ) return rgb; - - // if roughly gray - if ( ( Math.abs( red - green ) < 10 ) - && ( Math.abs( red - blue ) < 10 ) ) - { - red += redOffset; - if ( red < 0 ) red = 0; - if ( red > 255 ) red = 255; - green += greenOffset; - if ( green < 0 ) green = 0; - if ( green > 255 ) green = 255; - blue += blueOffset; - if ( blue < 0 ) blue = 0; - if ( blue > 255 ) blue = 255; - - return new Color( red, green, blue ).getRGB(); - } - - return rgb; - } - } - /* - * $Log$ - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.2 2001/01/18 21:27:04 mpowers - * Made the tinting a little darker. + * Revision 1.2 2001/01/18 21:27:04 mpowers Made the tinting a little darker. * - * Revision 1.1 2001/01/12 17:36:27 mpowers - * Contributing TintedImageFilter. + * Revision 1.1 2001/01/12 17:36:27 mpowers Contributing TintedImageFilter. * * */ diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TreeChooser.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TreeChooser.java index f0bb6c2..f5ab50c 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TreeChooser.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TreeChooser.java @@ -66,662 +66,538 @@ import javax.swing.tree.TreeSelectionModel; import net.wotonomy.foundation.internal.WotonomyException; /** -* TreeChooser is a FileChooser-like panel that -* uses a TreeModel as a data source. It basically -* provides an alternative to JTree for rendering -* and manipulating tree-like data. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class TreeChooser extends JPanel - implements ActionListener, ListSelectionListener, - TreeSelectionListener, TreeModelListener, ListCellRenderer -{ - /** - * The TreeChooser responds to this action command - * by calling displayPrevious(). - */ + * TreeChooser is a FileChooser-like panel that uses a TreeModel as a data + * source. It basically provides an alternative to JTree for rendering and + * manipulating tree-like data. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class TreeChooser extends JPanel + implements ActionListener, ListSelectionListener, TreeSelectionListener, TreeModelListener, ListCellRenderer { + /** + * The TreeChooser responds to this action command by calling displayPrevious(). + */ public static final String BACK = "Back"; - /** - * The TreeChooser responds to this action command - * by calling displayHome(). - */ + /** + * The TreeChooser responds to this action command by calling displayHome(). + */ public static final String HOME = "Home"; - /** - * The TreeChooser responds to this action command - * by calling displayParent(). - */ + /** + * The TreeChooser responds to this action command by calling displayParent(). + */ public static final String UP = "Up"; - /** - * The TreeChooser responds to this action command - * by attempting to navigate to the first node in - * the current selection and display that node's children. - */ + /** + * The TreeChooser responds to this action command by attempting to navigate to + * the first node in the current selection and display that node's children. + */ public static final String SELECT = "Select"; protected JList contents; - protected JComboBox pathCombo; + protected JComboBox pathCombo; protected JToolBar toolBar; - + protected TreeModel model; protected TreeSelectionModel selectionModel; - protected TreeCellRenderer renderer; - protected TreePath displayPath; - protected Stack pathStack; - protected int pathIndent; - - private ChooserComboBoxModel comboBoxModel; - private JTree bogusJTree; // needed for tree cell renderer - private Dimension preferredSize; - - public TreeChooser() - { - preferredSize = new Dimension( 300, 200 ); - model = new DefaultTreeModel( new DefaultMutableTreeNode( "Root" ) ); - displayPath = new TreePath( model.getRoot() ); - selectionModel = new DefaultTreeSelectionModel(); - renderer = new DefaultTreeCellRenderer(); - pathStack = new Stack(); - pathIndent = 0; // 16; - comboBoxModel = new ChooserComboBoxModel( this ); - - bogusJTree = new JTree(); - bogusJTree.setModel( model ); + protected TreeCellRenderer renderer; + protected TreePath displayPath; + protected Stack pathStack; + protected int pathIndent; + + private ChooserComboBoxModel comboBoxModel; + private JTree bogusJTree; // needed for tree cell renderer + private Dimension preferredSize; + + public TreeChooser() { + preferredSize = new Dimension(300, 200); + model = new DefaultTreeModel(new DefaultMutableTreeNode("Root")); + displayPath = new TreePath(model.getRoot()); + selectionModel = new DefaultTreeSelectionModel(); + renderer = new DefaultTreeCellRenderer(); + pathStack = new Stack(); + pathIndent = 0; // 16; + comboBoxModel = new ChooserComboBoxModel(this); + + bogusJTree = new JTree(); + bogusJTree.setModel(model); init(); - displayHome(); - - stopListening(); // clear existing listeners - startListening(); - } - - public Dimension getPreferredSize() - { - return preferredSize; - } - - protected void init() - { - this.setLayout( new BorderLayout( 10, 10 ) ); - + displayHome(); + + stopListening(); // clear existing listeners + startListening(); + } + + public Dimension getPreferredSize() { + return preferredSize; + } + + protected void init() { + this.setLayout(new BorderLayout(10, 10)); + contents = initList(); - contents.getSelectionModel().setSelectionMode( - ListSelectionModel.MULTIPLE_INTERVAL_SELECTION ); - // synchs with DefaultTreeSelectionModel - - JScrollPane scrollPane = new JScrollPane( contents ); - scrollPane.setPreferredSize( new Dimension( 200, 150 ) ); - this.add( scrollPane, BorderLayout.CENTER ); - - Component previewPane = initPreviewPane(); - if ( previewPane != null ) - { - this.add( previewPane, BorderLayout.EAST ); - } - + contents.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + // synchs with DefaultTreeSelectionModel + + JScrollPane scrollPane = new JScrollPane(contents); + scrollPane.setPreferredSize(new Dimension(200, 150)); + this.add(scrollPane, BorderLayout.CENTER); + + Component previewPane = initPreviewPane(); + if (previewPane != null) { + this.add(previewPane, BorderLayout.EAST); + } + JPanel navigationPanel = new JPanel(); - navigationPanel.setLayout( new BorderLayout( 10, 10 ) ); - this.add( navigationPanel, BorderLayout.NORTH ); - + navigationPanel.setLayout(new BorderLayout(10, 10)); + this.add(navigationPanel, BorderLayout.NORTH); + pathCombo = initComboBox(); - if ( pathCombo != null ) - { - pathCombo.setModel( comboBoxModel ); + if (pathCombo != null) { + pathCombo.setModel(comboBoxModel); // put combo in a grid bag to handle varying - // heights of JToolBars across platforms + // heights of JToolBars across platforms JPanel panel = new JPanel(); - panel.setLayout( new GridBagLayout() ); + panel.setLayout(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0; - panel.add( pathCombo, gbc ); - navigationPanel.add( panel, BorderLayout.CENTER ); - } - + panel.add(pathCombo, gbc); + navigationPanel.add(panel, BorderLayout.CENTER); + } + Component toolBar = initToolBar(); - if ( toolBar != null ) - { - navigationPanel.add( toolBar, BorderLayout.EAST ); - } - - } - - /** - * Creates tool bar or return null if no tool bar is desired. - * This implementation returns a JToolBar containing buttons - * for BACK, UP, and HOME. - */ - protected Component initToolBar() - { + if (toolBar != null) { + navigationPanel.add(toolBar, BorderLayout.EAST); + } + + } + + /** + * Creates tool bar or return null if no tool bar is desired. This + * implementation returns a JToolBar containing buttons for BACK, UP, and HOME. + */ + protected Component initToolBar() { JToolBar toolBar = new JToolBar(); - toolBar.setFloatable( false ); - JButton button; - button = new JButton( UIManager.getIcon("FileChooser.upFolderIcon") ); - button.setActionCommand( UP ); - button.addActionListener( this ); - toolBar.add( button ); - button = new JButton( UIManager.getIcon("FileChooser.homeFolderIcon") ); - button.setActionCommand( HOME ); - button.addActionListener( this ); - toolBar.add( button ); -/* - button = new JButton( UIManager.getIcon("FileChooser.newFolderIcon") ); - button.setActionCommand( BACK ); - button.addActionListener( this ); - toolBar.add( button ); -*/ - return toolBar; - } - - /** - * Creates the component that is used to display a preview of the - * selected item(s) in the content area. This component would listen - * to the selection model to update itself when the selected items change. - * Return null to omit this component. - * This implementation returns null. - */ - protected Component initPreviewPane() - { - return null; - } - - /** - * Creates the JComboBox that is used to render the path leading to - * the displayed contents. Return null to omit this combo box. - * This implementation returns a stock JComboBox that uses this - * class as its cell renderer. - */ - protected JComboBox initComboBox() - { - JComboBox comboBox = new JComboBox(); - comboBox.setRenderer( this ); - return comboBox; - } - - /** - * Creates the JList that is used to render the path leading to - * the displayed contents. This method may not return null. - * This implementation returns a stock JList that uses this - * class as its cell renderer and fires a SELECT action event - * on double click. - */ - protected JList initList() - { - JList list = new JList(); - list.setCellRenderer( this ); - list.addMouseListener( new MouseAdapter() - { - public void mouseClicked( MouseEvent evt ) - { - if ( evt.getClickCount() > 1 ) - { - actionPerformed( new ActionEvent( this, 0, SELECT ) ); - } - } - }); - return list; - } - - /** - * Begins listening to the specified tree model - * and tree selection model. - */ - protected void startListening() - { - model.addTreeModelListener( this ); - selectionModel.addTreeSelectionListener( this ); - contents.addListSelectionListener( this ); - } - - /** - * Stops listening to the specified tree model - * and tree selection model. - */ - protected void stopListening() - { - model.removeTreeModelListener( this ); - selectionModel.removeTreeSelectionListener( this ); - contents.removeListSelectionListener( this ); - } - - /** - * Returns the TreeModel used by the TreeChooser. - */ - public TreeModel getModel() - { - return model; - } - - /** - * Sets the TreeModel used by the TreeChooser. - */ - public void setModel( TreeModel aTreeModel ) - { - stopListening(); - model = aTreeModel; - bogusJTree.setModel( aTreeModel ); - pathStack.removeAllElements(); - startListening(); - displayHome(); - } - - /** - * Returns the TreeSelectionModel used by the TreeChooser. - */ - public TreeSelectionModel getSelectionModel() - { - return selectionModel; - } - - /** - * Sets the TreeSelectionModel used by the TreeChooser. - */ - public void setSelectionModel( TreeSelectionModel aSelectionModel ) - { - selectionModel = aSelectionModel; - if ( aSelectionModel.getSelectionMode() == - TreeSelectionModel.SINGLE_TREE_SELECTION ) - { - contents.getSelectionModel().setSelectionMode( - ListSelectionModel.SINGLE_SELECTION ); - } - else - { - contents.getSelectionModel().setSelectionMode( - ListSelectionModel.MULTIPLE_INTERVAL_SELECTION ); - } - updateSelection(); - } - - /** - * Returns the TreeCellRenderer used by the TreeChooser. - */ - public TreeCellRenderer getRenderer() - { - return renderer; - } - - /** - * Sets the TreeCellRenderer used by the TreeChooser. - */ - public void setRenderer( TreeCellRenderer aRenderer ) - { - renderer = aRenderer; - updateContents(); - } - - /** - * Displays the "home" directory. - * This implementation displays the root node's children. - */ - public void displayHome() - { - setDisplayPath( null ); - } - - /** - * Displays the parent path of the currently displayed path. - */ - public void displayParent() - { - setDisplayPath( displayPath.getParentPath() ); - } - - /** - * Displays the last displayed path before the current one, - * emulating the behavior of a "back" button. - */ - public void displayPrevious() - { - if ( pathStack.empty() ) - { - displayHome(); - } - else - { - setDisplayPathDirect( (TreePath) pathStack.pop() ); - updateContents(); - } - } - - /** - * Pushes the previous item onto the stack, sets - * the display path, and then updates the contents. - * If aPath is null, the root node's children are displayed. - */ - public void setDisplayPath( TreePath aPath ) - { - if ( aPath == null ) - { - aPath = new TreePath( getModel().getRoot() ); - } - if ( ! displayPath.equals ( aPath ) ) - { - pathStack.push( displayPath ); - setDisplayPathDirect( aPath ); - } - updateContents(); - } - - /** - * Sets the displayPath field and does not - * update the stack nor update the contents. - */ - protected void setDisplayPathDirect( TreePath aPath ) - { - displayPath = aPath; - } - - /** - * Gets the currently displayed path. - */ - public TreePath getDisplayPath() - { - return displayPath; - } - - /** - * Called when selected path changes or when model indicates - * that the displayed path has changed. - */ - protected void updateContents() - { - stopListening(); - - // update combo box - comboBoxModel.fireContentsChanged(); - - // update list contents - Object displayedObject = displayPath.getLastPathComponent(); -/* -//FIXME: this display group doesn't seem to be getting the sort orderings from parent -if ( displayedObject instanceof net.wotonomy.ui.EODisplayGroup ) -System.out.println( ((net.wotonomy.ui.EODisplayGroup)displayedObject).displayedObjects() ); -*/ - int count = model.getChildCount( displayedObject ); - Object[] children = new Object[ count ]; - for ( int i = 0; i < count; i++ ) - { - children[i] = model.getChild( displayedObject, i ); - } - contents.setListData( children ); - - startListening(); - - // synchronize the selection - updateSelection(); - } - - /** - * Updates the selection in the list to reflect the - * selection in the tree selection model. - */ - public void updateSelection() - { - int index; - Object last = displayPath.getLastPathComponent(); - TreePath[] selectionPaths = selectionModel.getSelectionPaths(); - if ( selectionPaths != null ) - { - List selectedIndices = new LinkedList(); - for ( int i = 0; i < selectionPaths.length; i++ ) - { - if ( displayPath.equals( selectionPaths[i].getParentPath() ) ) - { - index = getModel().getIndexOfChild( - last, selectionPaths[i].getLastPathComponent() ); - if ( index != -1 ) - { - selectedIndices.add( new Integer( index ) ); - } - else // should never happen - { - throw new WotonomyException( - "Could not find child of displayed node." ); - } - } - } - int[] selected = new int[ selectedIndices.size() ]; - for ( int i = 0; i < selected.length; i++ ) - { - selected[i] = ((Integer)selectedIndices.get(i)).intValue(); - } - stopListening(); - contents.setSelectedIndices( selected ); - startListening(); - } - } - - // interface TreeModelListener - - public void treeNodesChanged( TreeModelEvent evt ) - { -/* - if ( displayPath.getLastPathComponent().toString().equals( - evt.getTreePath().getLastPathComponent().toString() ) ) - { -System.out.println( "TreeChooser.treeNodesChanged: " + count++ ); -*/ - updateContents(); -/* - } - else - { - System.out.println( evt.getTreePath() + " != " + displayPath ); - } -*/ - } - - public void treeNodesInserted( TreeModelEvent evt ) - { + toolBar.setFloatable(false); + JButton button; + button = new JButton(UIManager.getIcon("FileChooser.upFolderIcon")); + button.setActionCommand(UP); + button.addActionListener(this); + toolBar.add(button); + button = new JButton(UIManager.getIcon("FileChooser.homeFolderIcon")); + button.setActionCommand(HOME); + button.addActionListener(this); + toolBar.add(button); + /* + * button = new JButton( UIManager.getIcon("FileChooser.newFolderIcon") ); + * button.setActionCommand( BACK ); button.addActionListener( this ); + * toolBar.add( button ); + */ + return toolBar; + } + + /** + * Creates the component that is used to display a preview of the selected + * item(s) in the content area. This component would listen to the selection + * model to update itself when the selected items change. Return null to omit + * this component. This implementation returns null. + */ + protected Component initPreviewPane() { + return null; + } + + /** + * Creates the JComboBox that is used to render the path leading to the + * displayed contents. Return null to omit this combo box. This implementation + * returns a stock JComboBox that uses this class as its cell renderer. + */ + protected JComboBox initComboBox() { + JComboBox comboBox = new JComboBox(); + comboBox.setRenderer(this); + return comboBox; + } + + /** + * Creates the JList that is used to render the path leading to the displayed + * contents. This method may not return null. This implementation returns a + * stock JList that uses this class as its cell renderer and fires a SELECT + * action event on double click. + */ + protected JList initList() { + JList list = new JList(); + list.setCellRenderer(this); + list.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent evt) { + if (evt.getClickCount() > 1) { + actionPerformed(new ActionEvent(this, 0, SELECT)); + } + } + }); + return list; + } + + /** + * Begins listening to the specified tree model and tree selection model. + */ + protected void startListening() { + model.addTreeModelListener(this); + selectionModel.addTreeSelectionListener(this); + contents.addListSelectionListener(this); + } + + /** + * Stops listening to the specified tree model and tree selection model. + */ + protected void stopListening() { + model.removeTreeModelListener(this); + selectionModel.removeTreeSelectionListener(this); + contents.removeListSelectionListener(this); + } + + /** + * Returns the TreeModel used by the TreeChooser. + */ + public TreeModel getModel() { + return model; + } + + /** + * Sets the TreeModel used by the TreeChooser. + */ + public void setModel(TreeModel aTreeModel) { + stopListening(); + model = aTreeModel; + bogusJTree.setModel(aTreeModel); + pathStack.removeAllElements(); + startListening(); + displayHome(); + } + + /** + * Returns the TreeSelectionModel used by the TreeChooser. + */ + public TreeSelectionModel getSelectionModel() { + return selectionModel; + } + + /** + * Sets the TreeSelectionModel used by the TreeChooser. + */ + public void setSelectionModel(TreeSelectionModel aSelectionModel) { + selectionModel = aSelectionModel; + if (aSelectionModel.getSelectionMode() == TreeSelectionModel.SINGLE_TREE_SELECTION) { + contents.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + } else { + contents.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + } + updateSelection(); + } + + /** + * Returns the TreeCellRenderer used by the TreeChooser. + */ + public TreeCellRenderer getRenderer() { + return renderer; + } + + /** + * Sets the TreeCellRenderer used by the TreeChooser. + */ + public void setRenderer(TreeCellRenderer aRenderer) { + renderer = aRenderer; + updateContents(); + } + + /** + * Displays the "home" directory. This implementation displays the root node's + * children. + */ + public void displayHome() { + setDisplayPath(null); + } + + /** + * Displays the parent path of the currently displayed path. + */ + public void displayParent() { + setDisplayPath(displayPath.getParentPath()); + } + + /** + * Displays the last displayed path before the current one, emulating the + * behavior of a "back" button. + */ + public void displayPrevious() { + if (pathStack.empty()) { + displayHome(); + } else { + setDisplayPathDirect((TreePath) pathStack.pop()); + updateContents(); + } + } + + /** + * Pushes the previous item onto the stack, sets the display path, and then + * updates the contents. If aPath is null, the root node's children are + * displayed. + */ + public void setDisplayPath(TreePath aPath) { + if (aPath == null) { + aPath = new TreePath(getModel().getRoot()); + } + if (!displayPath.equals(aPath)) { + pathStack.push(displayPath); + setDisplayPathDirect(aPath); + } + updateContents(); + } + + /** + * Sets the displayPath field and does not update the stack nor update the + * contents. + */ + protected void setDisplayPathDirect(TreePath aPath) { + displayPath = aPath; + } + + /** + * Gets the currently displayed path. + */ + public TreePath getDisplayPath() { + return displayPath; + } + + /** + * Called when selected path changes or when model indicates that the displayed + * path has changed. + */ + protected void updateContents() { + stopListening(); + + // update combo box + comboBoxModel.fireContentsChanged(); + + // update list contents + Object displayedObject = displayPath.getLastPathComponent(); + /* + * //FIXME: this display group doesn't seem to be getting the sort orderings + * from parent if ( displayedObject instanceof net.wotonomy.ui.EODisplayGroup ) + * System.out.println( + * ((net.wotonomy.ui.EODisplayGroup)displayedObject).displayedObjects() ); + */ + int count = model.getChildCount(displayedObject); + Object[] children = new Object[count]; + for (int i = 0; i < count; i++) { + children[i] = model.getChild(displayedObject, i); + } + contents.setListData(children); + + startListening(); + + // synchronize the selection + updateSelection(); + } + + /** + * Updates the selection in the list to reflect the selection in the tree + * selection model. + */ + public void updateSelection() { + int index; + Object last = displayPath.getLastPathComponent(); + TreePath[] selectionPaths = selectionModel.getSelectionPaths(); + if (selectionPaths != null) { + List selectedIndices = new LinkedList(); + for (int i = 0; i < selectionPaths.length; i++) { + if (displayPath.equals(selectionPaths[i].getParentPath())) { + index = getModel().getIndexOfChild(last, selectionPaths[i].getLastPathComponent()); + if (index != -1) { + selectedIndices.add(new Integer(index)); + } else // should never happen + { + throw new WotonomyException("Could not find child of displayed node."); + } + } + } + int[] selected = new int[selectedIndices.size()]; + for (int i = 0; i < selected.length; i++) { + selected[i] = ((Integer) selectedIndices.get(i)).intValue(); + } + stopListening(); + contents.setSelectedIndices(selected); + startListening(); + } + } + + // interface TreeModelListener + + public void treeNodesChanged(TreeModelEvent evt) { + /* + * if ( displayPath.getLastPathComponent().toString().equals( + * evt.getTreePath().getLastPathComponent().toString() ) ) { System.out.println( + * "TreeChooser.treeNodesChanged: " + count++ ); + */ + updateContents(); + /* + * } else { System.out.println( evt.getTreePath() + " != " + displayPath ); } + */ + } + + public void treeNodesInserted(TreeModelEvent evt) { // updateContents(); - } - - public void treeNodesRemoved( TreeModelEvent evt ) - { + } + + public void treeNodesRemoved(TreeModelEvent evt) { // updateContents(); - } - - public void treeStructureChanged( TreeModelEvent evt ) - { - if ( ( evt.getTreePath().equals( displayPath ) ) - || ( evt.getTreePath().isDescendant( displayPath ) ) ) - { + } + + public void treeStructureChanged(TreeModelEvent evt) { + if ((evt.getTreePath().equals(displayPath)) || (evt.getTreePath().isDescendant(displayPath))) { // setDisplayPath( evt.getTreePath() ); - } - - displayHome(); - } - - // interface TreeSelectionListener - - /** - * Called when the tree selection model's value changes. - * This is presumably an external change, so this calls - * updateSelection. - */ - public void valueChanged( TreeSelectionEvent evt ) - { - updateSelection(); - } - - // interface ListSelectionListener - - /** - * Called when user changes the selection in the list. - * This implementation updates the tree selection model - * with the corresponding selection. - */ - public void valueChanged( ListSelectionEvent evt ) - { - if ( ! evt.getValueIsAdjusting() ) - { - Object last = displayPath.getLastPathComponent(); - int[] selection = contents.getSelectedIndices(); - TreePath[] selectionPaths = new TreePath[ selection.length ]; - for ( int i = 0; i < selection.length; i++ ) - { - selectionPaths[i] = displayPath.pathByAddingChild( - getModel().getChild( last, selection[i] ) ); - } - selectionModel.setSelectionPaths( selectionPaths ); - } - - } - - // interface ListCellRenderer - - /** - * This method returns the component returned by the tree cell renderer. - */ - public Component getListCellRendererComponent( - JList list, - Object value, - int index, - boolean isSelected, - boolean cellHasFocus ) - { - boolean isLeaf = ( model.isLeaf( value ) ); - - bogusJTree.setForeground( list.getForeground() ); - bogusJTree.setBackground( list.getBackground() ); - - JComponent result = (JComponent) renderer.getTreeCellRendererComponent( - bogusJTree, value, isSelected, (list != contents), - isLeaf, index, cellHasFocus ); -/* - if ( ( list != contents ) && ( index > -1 ) ) - { - result.setBorder( - BorderFactory.createEmptyBorder( 0, index*pathIndent, 0, 0 ) ); - } - else - { - result.setBorder( - BorderFactory.createEmptyBorder() ); - } -*/ - return result; - } - - // interface ActionListener - - public void actionPerformed( ActionEvent evt ) - { - String command = evt.getActionCommand(); - - if ( HOME.equals( command ) ) - { - displayHome(); - } - else - if ( UP.equals( command ) ) - { - displayParent(); - } - else - if ( BACK.equals( command ) ) - { - displayPrevious(); - } - else - if ( SELECT.equals( command ) ) - { - Cursor oldCursor = getCursor(); - setCursor( Cursor.getPredefinedCursor( Cursor.WAIT_CURSOR ) ); - - int index = contents.getSelectedIndex(); - // if selection - if ( index != -1 ) - { - Object parent = displayPath.getLastPathComponent(); - Object child = getModel().getChild( parent, index ); - // if selected item is not a leaf - if ( getModel().getChildCount( child ) > 0 ) - { - // navigate to selected item - setDisplayPath( displayPath.pathByAddingChild( child ) ); - } - } - - setCursor( oldCursor ); - } - - } - - private class ChooserComboBoxModel implements ComboBoxModel - { - TreeChooser treeChooser; - Vector listeners; - - ChooserComboBoxModel( TreeChooser aTreeChooser ) - { - treeChooser = aTreeChooser; - listeners = new Vector(); - } - - public int getSize() - { - return treeChooser.displayPath.getPathCount(); - } - - public Object getElementAt(int index) - { - return treeChooser.displayPath.getPathComponent( index ); - } - - public Object getSelectedItem() - { - return treeChooser.displayPath.getLastPathComponent(); - } - - public void setSelectedItem(Object anItem) - { - if ( ! ( - treeChooser.displayPath.getLastPathComponent().equals( anItem ) ) ) - { - Object[] items = treeChooser.displayPath.getPath(); - TreePath path = new TreePath( getModel().getRoot() ); - for ( int i = 1; i < items.length; i++ ) - { - if ( path.getLastPathComponent() == anItem ) - { - treeChooser.setDisplayPath( path ); - return; - } - path = path.pathByAddingChild( items[i] ); - } - } - } - - public void addListDataListener(ListDataListener l) - { - listeners.add( l ); - } - - public void removeListDataListener(ListDataListener l) - { - listeners.remove( l ); - } - - public void fireContentsChanged() - { - Enumeration e = listeners.elements(); - while ( e.hasMoreElements() ) - { - ((ListDataListener)e.nextElement()).contentsChanged( - new ListDataEvent( - this, ListDataEvent.CONTENTS_CHANGED, 0, getSize() ) ); - } - } - } - -} + } + + displayHome(); + } + + // interface TreeSelectionListener + + /** + * Called when the tree selection model's value changes. This is presumably an + * external change, so this calls updateSelection. + */ + public void valueChanged(TreeSelectionEvent evt) { + updateSelection(); + } + // interface ListSelectionListener + /** + * Called when user changes the selection in the list. This implementation + * updates the tree selection model with the corresponding selection. + */ + public void valueChanged(ListSelectionEvent evt) { + if (!evt.getValueIsAdjusting()) { + Object last = displayPath.getLastPathComponent(); + int[] selection = contents.getSelectedIndices(); + TreePath[] selectionPaths = new TreePath[selection.length]; + for (int i = 0; i < selection.length; i++) { + selectionPaths[i] = displayPath.pathByAddingChild(getModel().getChild(last, selection[i])); + } + selectionModel.setSelectionPaths(selectionPaths); + } + + } + + // interface ListCellRenderer + + /** + * This method returns the component returned by the tree cell renderer. + */ + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, + boolean cellHasFocus) { + boolean isLeaf = (model.isLeaf(value)); + + bogusJTree.setForeground(list.getForeground()); + bogusJTree.setBackground(list.getBackground()); + + JComponent result = (JComponent) renderer.getTreeCellRendererComponent(bogusJTree, value, isSelected, + (list != contents), isLeaf, index, cellHasFocus); + /* + * if ( ( list != contents ) && ( index > -1 ) ) { result.setBorder( + * BorderFactory.createEmptyBorder( 0, index*pathIndent, 0, 0 ) ); } else { + * result.setBorder( BorderFactory.createEmptyBorder() ); } + */ + return result; + } + + // interface ActionListener + + public void actionPerformed(ActionEvent evt) { + String command = evt.getActionCommand(); + + if (HOME.equals(command)) { + displayHome(); + } else if (UP.equals(command)) { + displayParent(); + } else if (BACK.equals(command)) { + displayPrevious(); + } else if (SELECT.equals(command)) { + Cursor oldCursor = getCursor(); + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + + int index = contents.getSelectedIndex(); + // if selection + if (index != -1) { + Object parent = displayPath.getLastPathComponent(); + Object child = getModel().getChild(parent, index); + // if selected item is not a leaf + if (getModel().getChildCount(child) > 0) { + // navigate to selected item + setDisplayPath(displayPath.pathByAddingChild(child)); + } + } + + setCursor(oldCursor); + } + + } + + private class ChooserComboBoxModel implements ComboBoxModel { + TreeChooser treeChooser; + Vector listeners; + + ChooserComboBoxModel(TreeChooser aTreeChooser) { + treeChooser = aTreeChooser; + listeners = new Vector(); + } + + public int getSize() { + return treeChooser.displayPath.getPathCount(); + } + + public Object getElementAt(int index) { + return treeChooser.displayPath.getPathComponent(index); + } + + public Object getSelectedItem() { + return treeChooser.displayPath.getLastPathComponent(); + } + + public void setSelectedItem(Object anItem) { + if (!(treeChooser.displayPath.getLastPathComponent().equals(anItem))) { + Object[] items = treeChooser.displayPath.getPath(); + TreePath path = new TreePath(getModel().getRoot()); + for (int i = 1; i < items.length; i++) { + if (path.getLastPathComponent() == anItem) { + treeChooser.setDisplayPath(path); + return; + } + path = path.pathByAddingChild(items[i]); + } + } + } + + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + + public void fireContentsChanged() { + Enumeration e = listeners.elements(); + while (e.hasMoreElements()) { + ((ListDataListener) e.nextElement()) + .contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, getSize())); + } + } + } + +} diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TreeTableCellRenderer.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TreeTableCellRenderer.java index fbf3791..c6e1a99 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TreeTableCellRenderer.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TreeTableCellRenderer.java @@ -32,193 +32,158 @@ import javax.swing.JViewport; import javax.swing.table.TableCellRenderer; /** -* A TableCellRenderer that paints a portion of a JTree. -* Extends JViewport to take advantage of buffering and -* fast blitting (avoids repeated clipping and repainting). -* Defaults opaque to false: to see selection background -* painted, call setOpaque( true ). -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ + * A TableCellRenderer that paints a portion of a JTree. Extends JViewport to + * take advantage of buffering and fast blitting (avoids repeated clipping and + * repainting). Defaults opaque to false: to see selection background painted, + * call setOpaque( true ). + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ public class TreeTableCellRenderer extends JViewport implements TableCellRenderer, MouseListener { - JTree tree; - Component emptyComponent; - JTable delegateTable; - int lastKnownColumn; - - /** - * Constructor takes a JTree and modifies it by setting - * rootVisible to false, showsRootHandles to true, - * opaque to false, and border to null. - */ - public TreeTableCellRenderer( JTree aTree ) - { - setView( aTree ); - setBorder( null ); - tree = aTree; - tree.setRootVisible( false ); - tree.setShowsRootHandles( true ); - tree.setBorder( null ); - tree.setOpaque( false ); - - Object renderer = tree.getCellRenderer(); - if ( renderer instanceof JComponent ) - { - ((JComponent)renderer).setOpaque( false ); - } - Object editor = tree.getCellEditor(); - if ( editor instanceof JComponent ) - { - ((JComponent)editor).setOpaque( false ); - } - - this.setOpaque( false ); - emptyComponent = new JLabel(); - } - - public Component getTableCellRendererComponent( - JTable table, Object value, - boolean isSelected, boolean hasFocus, - int row, int column) - { - if ( isSelected ) - { - setForeground( table.getSelectionForeground() ); - setBackground( table.getSelectionBackground() ); - } - else - { - setForeground( table.getForeground() ); - setBackground( table.getBackground() ); - } - - lastKnownColumn = column; - if ( delegateTable != table ) - { - if ( delegateTable != null ) - { - delegateTable.removeMouseListener( this ); - } - table.addMouseListener( this ); - delegateTable = table; - } - - Rectangle rect = tree.getRowBounds( row ); - if ( rect != null ) - { - setViewPosition( new Point( 0 /*rect.x*/, rect.y ) ); - - //FIXME: this causes problems for some LAFs (like Metal): - // in particular, the table height seems to get stuck. - //if ( table.getRowHeight( row ) != rect.height ) - //{ - // table.setRowHeight( row, rect.height ); - //} - return this; - } - else - { - return emptyComponent; - } - } - - public void mouseClicked(MouseEvent e) - { - delegateToTree( e ); - } - - public void mousePressed(MouseEvent e) - { - delegateToTree( e ); - } - - public void mouseReleased(MouseEvent e) - { - delegateToTree( e ); - } - - public void mouseEntered(MouseEvent e) - { - delegateToTree( e ); - } - - public void mouseExited(MouseEvent e) - { - delegateToTree( e ); - } - - protected void delegateToTree(MouseEvent e) - { - int col = delegateTable.getColumnModel().getColumnIndexAtX( e.getX() ); - if ( col == lastKnownColumn ) - { - Rectangle nodeRect = tree.getRowBounds( 0 ); - Rectangle cellRect = delegateTable.getCellRect( -1, col, false ); - if ( nodeRect != null ) - { - e.translatePoint( -cellRect.x, nodeRect.y ); - tree.dispatchEvent( // e ); - new MouseEvent( tree, e.getID(), e.getWhen(), e.getModifiers(), - e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger() ) ); - } - } - } - - public void repaint() - { - //if ( delegateTable != null ) delegateTable.repaint(); - - // not calling super.repaint() does not seem to cause - // any problems so we're not doing it. - } + JTree tree; + Component emptyComponent; + JTable delegateTable; + int lastKnownColumn; + + /** + * Constructor takes a JTree and modifies it by setting rootVisible to false, + * showsRootHandles to true, opaque to false, and border to null. + */ + public TreeTableCellRenderer(JTree aTree) { + setView(aTree); + setBorder(null); + tree = aTree; + tree.setRootVisible(false); + tree.setShowsRootHandles(true); + tree.setBorder(null); + tree.setOpaque(false); + + Object renderer = tree.getCellRenderer(); + if (renderer instanceof JComponent) { + ((JComponent) renderer).setOpaque(false); + } + Object editor = tree.getCellEditor(); + if (editor instanceof JComponent) { + ((JComponent) editor).setOpaque(false); + } + + this.setOpaque(false); + emptyComponent = new JLabel(); + } + + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, + int row, int column) { + if (isSelected) { + setForeground(table.getSelectionForeground()); + setBackground(table.getSelectionBackground()); + } else { + setForeground(table.getForeground()); + setBackground(table.getBackground()); + } + + lastKnownColumn = column; + if (delegateTable != table) { + if (delegateTable != null) { + delegateTable.removeMouseListener(this); + } + table.addMouseListener(this); + delegateTable = table; + } + + Rectangle rect = tree.getRowBounds(row); + if (rect != null) { + setViewPosition(new Point(0 /* rect.x */, rect.y)); + + // FIXME: this causes problems for some LAFs (like Metal): + // in particular, the table height seems to get stuck. + // if ( table.getRowHeight( row ) != rect.height ) + // { + // table.setRowHeight( row, rect.height ); + // } + return this; + } else { + return emptyComponent; + } + } + + public void mouseClicked(MouseEvent e) { + delegateToTree(e); + } + + public void mousePressed(MouseEvent e) { + delegateToTree(e); + } + + public void mouseReleased(MouseEvent e) { + delegateToTree(e); + } + + public void mouseEntered(MouseEvent e) { + delegateToTree(e); + } + + public void mouseExited(MouseEvent e) { + delegateToTree(e); + } + + protected void delegateToTree(MouseEvent e) { + int col = delegateTable.getColumnModel().getColumnIndexAtX(e.getX()); + if (col == lastKnownColumn) { + Rectangle nodeRect = tree.getRowBounds(0); + Rectangle cellRect = delegateTable.getCellRect(-1, col, false); + if (nodeRect != null) { + e.translatePoint(-cellRect.x, nodeRect.y); + tree.dispatchEvent( // e ); + new MouseEvent(tree, e.getID(), e.getWhen(), e.getModifiers(), e.getX(), e.getY(), + e.getClickCount(), e.isPopupTrigger())); + } + } + } + + public void repaint() { + // if ( delegateTable != null ) delegateTable.repaint(); + + // not calling super.repaint() does not seem to cause + // any problems so we're not doing it. + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.11 2003/08/06 23:07:53 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.11 2003/08/06 23:07:53 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.10 2002/04/12 20:07:35 mpowers - * Fixed cool/annoying view position. + * Revision 1.10 2002/04/12 20:07:35 mpowers Fixed cool/annoying view position. * - * Revision 1.9 2002/04/09 18:12:21 mpowers - * Fixes for 1.4. + * Revision 1.9 2002/04/09 18:12:21 mpowers Fixes for 1.4. * - * Revision 1.8 2002/03/22 22:39:24 mpowers - * Can now move column to any position in the table. + * Revision 1.8 2002/03/22 22:39:24 mpowers Can now move column to any position + * in the table. * - * Revision 1.7 2002/03/11 03:13:22 mpowers - * Adjusting for viewport position; no longer responding to repaint(). + * Revision 1.7 2002/03/11 03:13:22 mpowers Adjusting for viewport position; no + * longer responding to repaint(). * - * Revision 1.6 2002/03/07 23:04:36 mpowers - * Refining TreeColumnAssociation. + * Revision 1.6 2002/03/07 23:04:36 mpowers Refining TreeColumnAssociation. * - * Revision 1.5 2002/03/05 23:18:28 mpowers - * Added documentation. - * Added isSelectionPaintedImmediate and isSelectionTracking attributes - * to TableAssociation. - * Added getTableAssociation to TableColumnAssociation. + * Revision 1.5 2002/03/05 23:18:28 mpowers Added documentation. Added + * isSelectionPaintedImmediate and isSelectionTracking attributes to + * TableAssociation. Added getTableAssociation to TableColumnAssociation. * - * Revision 1.3 2002/02/27 23:19:17 mpowers - * Refactoring of TreeAssociation to create TreeModelAssociation parent. + * Revision 1.3 2002/02/27 23:19:17 mpowers Refactoring of TreeAssociation to + * create TreeModelAssociation parent. * - * Revision 1.2 2002/02/18 23:13:55 mpowers - * Only setting row height when needed. + * Revision 1.2 2002/02/18 23:13:55 mpowers Only setting row height when needed. * - * Revision 1.1 2002/02/18 03:46:08 mpowers - * Implemented TreeTableCellRenderer. + * Revision 1.1 2002/02/18 03:46:08 mpowers Implemented TreeTableCellRenderer. * * */ - - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ClassGrabber.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ClassGrabber.java index 4412dbc..0e95d2d 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ClassGrabber.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ClassGrabber.java @@ -25,79 +25,67 @@ import java.io.InputStream; import java.util.Hashtable; /** - * ClassGrabber is a class loader used by WindowGrabber. - * It simply loads classes by filename and nothing more. - * It exists mainly because the java 1.1 class loading - * framework doesn't easily allow the creation of class - * loaders nor the loading of arbitrary classes. + * ClassGrabber is a class loader used by WindowGrabber. It simply loads classes + * by filename and nothing more. It exists mainly because the java 1.1 class + * loading framework doesn't easily allow the creation of class loaders nor the + * loading of arbitrary classes. * * @author michael@mpowers.net - * @version $Revision: 904 $ - * $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ */ -public class ClassGrabber extends ClassLoader -{ +public class ClassGrabber extends ClassLoader { Hashtable classMap = new Hashtable(); - public ClassGrabber() - { + public ClassGrabber() { super(); } - protected Class loadClass(String name, boolean resolve) - throws ClassNotFoundException - { - Class c = (Class) classMap.get( name ); + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + Class c = (Class) classMap.get(name); - if ( c != null ) return c; + if (c != null) + return c; - try - { - c = findSystemClass( name ); - } - catch ( Exception exc1 ) - { + try { + c = findSystemClass(name); + } catch (Exception exc1) { // System.err.print( "findSystemClass: " + name + ": " ); // System.err.println( exc1 ); } - if ( c != null ) return c; + if (c != null) + return c; - try - { - c = findLoadedClass( name ); - } - catch ( Exception exc1 ) - { + try { + c = findLoadedClass(name); + } catch (Exception exc1) { // System.err.print( "findLoadedClass: " + name + ": " ); // System.err.println( exc1 ); } - - if ( c != null ) return c; - - try - { - InputStream input = new BufferedInputStream( new FileInputStream( name ) ); - ByteArrayOutputStream output = new ByteArrayOutputStream( 200 ); + + if (c != null) + return c; + + try { + InputStream input = new BufferedInputStream(new FileInputStream(name)); + ByteArrayOutputStream output = new ByteArrayOutputStream(200); int ch; - while ( ( ch = input.read() ) != -1 ) - { - output.write( ch ); + while ((ch = input.read()) != -1) { + output.write(ch); } byte[] data = output.toByteArray(); - c = defineClass( null, data, 0, data.length ); - } - catch ( Exception exc ) - { - System.err.print( "getResource: " + name + ": " ); - System.err.println( exc ); - c = null; + c = defineClass(null, data, 0, data.length); + } catch (Exception exc) { + System.err.print("getResource: " + name + ": "); + System.err.println(exc); + c = null; } - if ( c != null ) - { - classMap.put( name, c ); - if ( resolve ) resolveClass( c ); + if (c != null) { + classMap.put(name, c); + if (resolve) + resolveClass(c); } return c; @@ -105,22 +93,18 @@ public class ClassGrabber extends ClassLoader } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.2 2003/08/06 23:07:53 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.2 2003/08/06 23:07:53 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.1.1.1 2000/12/21 15:51:18 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:51:18 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:45 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:45 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ComponentHighlighter.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ComponentHighlighter.java index c63157d..08e6216 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ComponentHighlighter.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ComponentHighlighter.java @@ -36,125 +36,115 @@ import javax.swing.SwingUtilities; import javax.swing.Timer; /** -* Visually highlights a component with the specified image for a brief period. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -* $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ -*/ -public class ComponentHighlighter implements ActionListener -{ - // lots of state to track - JRootPane rootPane; - JComponent component; - Component oldGlassPane; - JLabel imageLabel; - Timer timer; - JPanel glassPane; - -/** -* Alternate "Fire-and-forget" constructor loads an image from a URL. -* @param aComponent A Component that will be highlighted. -* @param aURL A URL pointing to an image. -*/ - public ComponentHighlighter( JComponent aComponent, URL aURL ) - { - if ( aURL == null ) return; - init( aComponent, Toolkit.getDefaultToolkit().getImage( aURL ) ); - } - -/** -* "Fire-and-forget" constructor. -* @param aComponent A Component that will be highlighted. -* @param anImage An image, preferably an animated GIF with transparency, -* that will slide along the length of the component. -*/ - public ComponentHighlighter( JComponent aComponent, Image anImage ) - { - init( aComponent, anImage ); - } - - protected void init( JComponent aComponent, Image anImage ) - { - if ( ( aComponent == null ) || ( anImage == null ) ) return; - - component = aComponent; - rootPane = SwingUtilities.getRootPane( component ); - oldGlassPane = rootPane.getGlassPane(); + * Visually highlights a component with the specified image for a brief period. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ + */ +public class ComponentHighlighter implements ActionListener { + // lots of state to track + JRootPane rootPane; + JComponent component; + Component oldGlassPane; + JLabel imageLabel; + Timer timer; + JPanel glassPane; + + /** + * Alternate "Fire-and-forget" constructor loads an image from a URL. + * + * @param aComponent A Component that will be highlighted. + * @param aURL A URL pointing to an image. + */ + public ComponentHighlighter(JComponent aComponent, URL aURL) { + if (aURL == null) + return; + init(aComponent, Toolkit.getDefaultToolkit().getImage(aURL)); + } + + /** + * "Fire-and-forget" constructor. + * + * @param aComponent A Component that will be highlighted. + * @param anImage An image, preferably an animated GIF with transparency, + * that will slide along the length of the component. + */ + public ComponentHighlighter(JComponent aComponent, Image anImage) { + init(aComponent, anImage); + } + + protected void init(JComponent aComponent, Image anImage) { + if ((aComponent == null) || (anImage == null)) + return; + + component = aComponent; + rootPane = SwingUtilities.getRootPane(component); + oldGlassPane = rootPane.getGlassPane(); glassPane = new JPanel(); - rootPane.setGlassPane( glassPane ); - glassPane.setVisible( true ); - glassPane.setOpaque( false ); - glassPane.setLayout( null ); - - ImageIcon icon = new ImageIcon( anImage ); - - imageLabel = new JLabel(); - imageLabel.setIconTextGap( 0 ); - imageLabel.setIcon( icon ); - imageLabel.setSize( icon.getIconWidth(), icon.getIconHeight() ); - glassPane.add( imageLabel ); - - Rectangle bounds = component.getBounds(); - if ( component.getParent() instanceof Component ) - { - bounds = SwingUtilities.convertRectangle( (Container) component.getParent(), - bounds, rootPane.getContentPane() ); - } - imageLabel.setLocation( - bounds.x, bounds.y + bounds.height - imageLabel.getBounds().height ); - - glassPane.revalidate(); - glassPane.repaint(); - - component.transferFocus(); // halts a caret, if necessary - - timer = new Timer( 80, this ); - timer.setRepeats( true ); - timer.start(); - } - - public void actionPerformed( ActionEvent evt ) - { - Rectangle bounds = imageLabel.getBounds(); - Rectangle target = component.getBounds(); - if ( component.getParent() instanceof Component ) - { - target = SwingUtilities.convertRectangle( (Container) component.getParent(), - target, rootPane.getContentPane() ); - } - - if ( bounds.x + bounds.width > target.x + target.width ) - { // clean up and end - timer.stop(); - rootPane.setGlassPane( oldGlassPane ); - component.requestFocus(); - return; - } - - // else, slide to the right and continue - imageLabel.setLocation( - bounds.x + Math.max( bounds.width / 12, 1 ), bounds.y ); - imageLabel.repaint(); - } + rootPane.setGlassPane(glassPane); + glassPane.setVisible(true); + glassPane.setOpaque(false); + glassPane.setLayout(null); + + ImageIcon icon = new ImageIcon(anImage); + + imageLabel = new JLabel(); + imageLabel.setIconTextGap(0); + imageLabel.setIcon(icon); + imageLabel.setSize(icon.getIconWidth(), icon.getIconHeight()); + glassPane.add(imageLabel); + + Rectangle bounds = component.getBounds(); + if (component.getParent() instanceof Component) { + bounds = SwingUtilities.convertRectangle((Container) component.getParent(), bounds, + rootPane.getContentPane()); + } + imageLabel.setLocation(bounds.x, bounds.y + bounds.height - imageLabel.getBounds().height); + + glassPane.revalidate(); + glassPane.repaint(); + + component.transferFocus(); // halts a caret, if necessary + + timer = new Timer(80, this); + timer.setRepeats(true); + timer.start(); + } + + public void actionPerformed(ActionEvent evt) { + Rectangle bounds = imageLabel.getBounds(); + Rectangle target = component.getBounds(); + if (component.getParent() instanceof Component) { + target = SwingUtilities.convertRectangle((Container) component.getParent(), target, + rootPane.getContentPane()); + } + + if (bounds.x + bounds.width > target.x + target.width) { // clean up and end + timer.stop(); + rootPane.setGlassPane(oldGlassPane); + component.requestFocus(); + return; + } + + // else, slide to the right and continue + imageLabel.setLocation(bounds.x + Math.max(bounds.width / 12, 1), bounds.y); + imageLabel.repaint(); + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1.1.1 2000/12/21 15:51:18 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:51:18 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:45 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:45 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/GIFEncoder.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/GIFEncoder.java index 82fd897..9ff7496 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/GIFEncoder.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/GIFEncoder.java @@ -12,509 +12,445 @@ import java.io.IOException; import java.io.OutputStream; /** - * GIFEncoder is a class which takes an image and saves it to a stream - * using the GIF file format. A GIFEncoder is constructed with either - * an AWT Image (which must be fully loaded) or a set of RGB arrays. - * The image can be written out with a call to <CODE>write</CODE>.<br><br> + * GIFEncoder is a class which takes an image and saves it to a stream using the + * GIF file format. A GIFEncoder is constructed with either an AWT Image (which + * must be fully loaded) or a set of RGB arrays. The image can be written out + * with a call to <CODE>write</CODE>.<br> + * <br> * * Three caveats: * <UL> - * <LI>GIFEncoder will convert the image to indexed color upon - * construction, and this is not fast. + * <LI>GIFEncoder will convert the image to indexed color upon construction, and + * this is not fast. * - * <LI>The image cannot have more than 256 colors, since GIF is an 8 - * bit format. + * <LI>The image cannot have more than 256 colors, since GIF is an 8 bit format. * - * <LI>Since the image must be completely loaded into memory, - * there may be problems with large images. + * <LI>Since the image must be completely loaded into memory, there may be + * problems with large images. * </UL> * - * This implementation is heavily based on code made available by - * Adam Doppelt, which was based upon gifsave.c, which was written - * and released by Sverre H. Huseby. + * This implementation is heavily based on code made available by Adam Doppelt, + * which was based upon gifsave.c, which was written and released by Sverre H. + * Huseby. * * @author amd@brown.edu * @author sverrehu@ifi.uio.no * @author michael@mpowers.net */ -public class GIFEncoder -{ - short width_, height_; - int numColors_; - byte pixels_[], colors_[]; - - ScreenDescriptor sd_; - ImageDescriptor id_; - -/** - * Construct a GIFEncoder. The constructor will convert the image to - * an indexed color array. This may take some time. If more than 256 - * colors are encountered, all subsequent colors are mapped to the first - * color encountered. - * @param image The image to encode. The image must be completely loaded. - * @exception AWTException Will be thrown if the pixel grab fails. This - * can happen if Java runs out of memory. - * */ - public GIFEncoder(Image image) throws AWTException - { - width_ = (short)image.getWidth(null); - height_ = (short)image.getHeight(null); - - int values[] = new int[width_ * height_]; - PixelGrabber grabber = new PixelGrabber( - image, 0, 0, width_, height_, values, 0, width_); - - try - { - if(grabber.grabPixels() != true) - throw new AWTException("Grabber returned false: " + - grabber.status()); - } - catch (InterruptedException e) - { - } - - byte r[][] = new byte[width_][height_]; - byte g[][] = new byte[width_][height_]; - byte b[][] = new byte[width_][height_]; - int index = 0; - for (int y = 0; y < height_; ++y) - { - for (int x = 0; x < width_; ++x) - { - r[x][y] = (byte)((values[index] >> 16) & 0xFF); - g[x][y] = (byte)((values[index] >> 8) & 0xFF); - b[x][y] = (byte)((values[index]) & 0xFF); - ++index; - } - } - toIndexedColor(r, g, b); - } - -/** - * Construct a GIFEncoder. The constructor will convert the image to - * an indexed color array. This may take some time. <br><br> - * Each array stores intensity values for the image. In other words, - * r[x][y] refers to the red intensity of the pixel at column x, row y. - * @param r An array containing the red intensity values. - * @param g An array containing the green intensity values. - * @param b An array containing the blue intensity values. - * - * @exception AWTException Will be thrown if the image contains more than - * 256 colors. - * */ - public GIFEncoder(byte r[][], byte g[][], byte b[][]) throws AWTException - { - width_ = (short)(r.length); - height_ = (short)(r[0].length); - - toIndexedColor(r, g, b); - } - -/** - * Writes the image out to a stream in the GIF file format. This will - * be a single GIF87a image, non-interlaced, with no background color. - * <B>This may take some time.</B><P> - * - * @param output The stream to output to. This should probably be a - * buffered stream. - * - * @exception IOException Will be thrown if a write operation fails. - * */ - public void write(OutputStream output) throws IOException - { - BitUtils.writeString(output, "GIF87a"); - ScreenDescriptor sd = new ScreenDescriptor(width_, height_, - numColors_); - sd.write(output); - - output.write(colors_, 0, colors_.length); - - ImageDescriptor id = new ImageDescriptor(width_, height_, ','); - id.write(output); - - byte codesize = BitUtils.bitsNeeded(numColors_); - if (codesize == 1) - ++codesize; - output.write(codesize); - - LZWCompressor.LZWCompress(output, codesize, pixels_); - output.write(0); - id = new ImageDescriptor((byte)0, (byte)0, ';'); - id.write(output); - output.flush(); - } - - void toIndexedColor(byte r[][], byte g[][], - byte b[][]) throws AWTException - { - pixels_ = new byte[width_ * height_]; - colors_ = new byte[256 * 3]; - int colornum = 0; - for (int x = 0; x < width_; ++x) - { - for (int y = 0; y < height_; ++y) - { - int search; - for (search = 0; search < colornum; ++search) - if (colors_[search * 3] == r[x][y] && - colors_[search * 3 + 1] == g[x][y] && - colors_[search * 3 + 2] == b[x][y]) - break; - - if (search > 255) - search = 0; - //throw new AWTException("Too many colors."); - - pixels_[y * width_ + x] = (byte)search; - - if (search == colornum) { - colors_[search * 3] = r[x][y]; - colors_[search * 3 + 1] = g[x][y]; - colors_[search * 3 + 2] = b[x][y]; - ++colornum; - } - } - } - - numColors_ = 1 << BitUtils.bitsNeeded(colornum); - byte copy[] = new byte[numColors_ * 3]; - System.arraycopy(colors_, 0, copy, 0, numColors_ * 3); - colors_ = copy; - } +public class GIFEncoder { + short width_, height_; + int numColors_; + byte pixels_[], colors_[]; + + ScreenDescriptor sd_; + ImageDescriptor id_; + + /** + * Construct a GIFEncoder. The constructor will convert the image to an indexed + * color array. This may take some time. If more than 256 colors are + * encountered, all subsequent colors are mapped to the first color encountered. + * + * @param image The image to encode. The image must be completely loaded. + * @exception AWTException Will be thrown if the pixel grab fails. This can + * happen if Java runs out of memory. + */ + public GIFEncoder(Image image) throws AWTException { + width_ = (short) image.getWidth(null); + height_ = (short) image.getHeight(null); + + int values[] = new int[width_ * height_]; + PixelGrabber grabber = new PixelGrabber(image, 0, 0, width_, height_, values, 0, width_); + + try { + if (grabber.grabPixels() != true) + throw new AWTException("Grabber returned false: " + grabber.status()); + } catch (InterruptedException e) { + } + + byte r[][] = new byte[width_][height_]; + byte g[][] = new byte[width_][height_]; + byte b[][] = new byte[width_][height_]; + int index = 0; + for (int y = 0; y < height_; ++y) { + for (int x = 0; x < width_; ++x) { + r[x][y] = (byte) ((values[index] >> 16) & 0xFF); + g[x][y] = (byte) ((values[index] >> 8) & 0xFF); + b[x][y] = (byte) ((values[index]) & 0xFF); + ++index; + } + } + toIndexedColor(r, g, b); + } + + /** + * Construct a GIFEncoder. The constructor will convert the image to an indexed + * color array. This may take some time. <br> + * <br> + * Each array stores intensity values for the image. In other words, r[x][y] + * refers to the red intensity of the pixel at column x, row y. + * + * @param r An array containing the red intensity values. + * @param g An array containing the green intensity values. + * @param b An array containing the blue intensity values. + * + * @exception AWTException Will be thrown if the image contains more than 256 + * colors. + */ + public GIFEncoder(byte r[][], byte g[][], byte b[][]) throws AWTException { + width_ = (short) (r.length); + height_ = (short) (r[0].length); + + toIndexedColor(r, g, b); + } + + /** + * Writes the image out to a stream in the GIF file format. This will be a + * single GIF87a image, non-interlaced, with no background color. <B>This may + * take some time.</B> + * <P> + * + * @param output The stream to output to. This should probably be a buffered + * stream. + * + * @exception IOException Will be thrown if a write operation fails. + */ + public void write(OutputStream output) throws IOException { + BitUtils.writeString(output, "GIF87a"); + ScreenDescriptor sd = new ScreenDescriptor(width_, height_, numColors_); + sd.write(output); + + output.write(colors_, 0, colors_.length); + + ImageDescriptor id = new ImageDescriptor(width_, height_, ','); + id.write(output); + + byte codesize = BitUtils.bitsNeeded(numColors_); + if (codesize == 1) + ++codesize; + output.write(codesize); + + LZWCompressor.LZWCompress(output, codesize, pixels_); + output.write(0); + id = new ImageDescriptor((byte) 0, (byte) 0, ';'); + id.write(output); + output.flush(); + } + + void toIndexedColor(byte r[][], byte g[][], byte b[][]) throws AWTException { + pixels_ = new byte[width_ * height_]; + colors_ = new byte[256 * 3]; + int colornum = 0; + for (int x = 0; x < width_; ++x) { + for (int y = 0; y < height_; ++y) { + int search; + for (search = 0; search < colornum; ++search) + if (colors_[search * 3] == r[x][y] && colors_[search * 3 + 1] == g[x][y] + && colors_[search * 3 + 2] == b[x][y]) + break; + + if (search > 255) + search = 0; + // throw new AWTException("Too many colors."); + + pixels_[y * width_ + x] = (byte) search; + + if (search == colornum) { + colors_[search * 3] = r[x][y]; + colors_[search * 3 + 1] = g[x][y]; + colors_[search * 3 + 2] = b[x][y]; + ++colornum; + } + } + } + + numColors_ = 1 << BitUtils.bitsNeeded(colornum); + byte copy[] = new byte[numColors_ * 3]; + System.arraycopy(colors_, 0, copy, 0, numColors_ * 3); + colors_ = copy; + } } -class BitFile -{ - OutputStream output_; - byte buffer_[]; - int index_, bitsLeft_; - - public BitFile(OutputStream output) - { - output_ = output; - buffer_ = new byte[256]; - index_ = 0; - bitsLeft_ = 8; - } - - public void flush() throws IOException - { - int numBytes = index_ + (bitsLeft_ == 8 ? 0 : 1); - if (numBytes > 0) - { - output_.write(numBytes); - output_.write(buffer_, 0, numBytes); - buffer_[0] = 0; - index_ = 0; - bitsLeft_ = 8; - } - } - - public void writeBits(int bits, int numbits) throws IOException { - int bitsWritten = 0; - int numBytes = 255; - do - { - if ((index_ == 254 && bitsLeft_ == 0) || index_ > 254) - { - output_.write(numBytes); - output_.write(buffer_, 0, numBytes); - - buffer_[0] = 0; - index_ = 0; - bitsLeft_ = 8; - } - - if (numbits <= bitsLeft_) - { - buffer_[index_] |= (bits & ((1 << numbits) - 1)) << - (8 - bitsLeft_); - bitsWritten += numbits; - bitsLeft_ -= numbits; - numbits = 0; - } - else - { - buffer_[index_] |= (bits & ((1 << bitsLeft_) - 1)) << - (8 - bitsLeft_); - bitsWritten += bitsLeft_; - bits >>= bitsLeft_; - numbits -= bitsLeft_; - buffer_[++index_] = 0; - bitsLeft_ = 8; - } - - } - while (numbits != 0); - } +class BitFile { + OutputStream output_; + byte buffer_[]; + int index_, bitsLeft_; + + public BitFile(OutputStream output) { + output_ = output; + buffer_ = new byte[256]; + index_ = 0; + bitsLeft_ = 8; + } + + public void flush() throws IOException { + int numBytes = index_ + (bitsLeft_ == 8 ? 0 : 1); + if (numBytes > 0) { + output_.write(numBytes); + output_.write(buffer_, 0, numBytes); + buffer_[0] = 0; + index_ = 0; + bitsLeft_ = 8; + } + } + + public void writeBits(int bits, int numbits) throws IOException { + int bitsWritten = 0; + int numBytes = 255; + do { + if ((index_ == 254 && bitsLeft_ == 0) || index_ > 254) { + output_.write(numBytes); + output_.write(buffer_, 0, numBytes); + + buffer_[0] = 0; + index_ = 0; + bitsLeft_ = 8; + } + + if (numbits <= bitsLeft_) { + buffer_[index_] |= (bits & ((1 << numbits) - 1)) << (8 - bitsLeft_); + bitsWritten += numbits; + bitsLeft_ -= numbits; + numbits = 0; + } else { + buffer_[index_] |= (bits & ((1 << bitsLeft_) - 1)) << (8 - bitsLeft_); + bitsWritten += bitsLeft_; + bits >>= bitsLeft_; + numbits -= bitsLeft_; + buffer_[++index_] = 0; + bitsLeft_ = 8; + } + + } while (numbits != 0); + } } -class LZWStringTable -{ - private final static int RES_CODES = 2; - private final static short HASH_FREE = (short)0xFFFF; - private final static short NEXT_FIRST = (short)0xFFFF; - private final static int MAXBITS = 12; - private final static int MAXSTR = (1 << MAXBITS); - private final static short HASHSIZE = 9973; - private final static short HASHSTEP = 2039; - - byte strChr_[]; - short strNxt_[]; - short strHsh_[]; - short numStrings_; - - public LZWStringTable() - { - strChr_ = new byte[MAXSTR]; - strNxt_ = new short[MAXSTR]; - strHsh_ = new short[HASHSIZE]; - } - - public int addCharString(short index, byte b) - { - int hshidx; - - if (numStrings_ >= MAXSTR) - return 0xFFFF; - - hshidx = Hash(index, b); - while (strHsh_[hshidx] != HASH_FREE) - hshidx = (hshidx + HASHSTEP) % HASHSIZE; - - strHsh_[hshidx] = numStrings_; - strChr_[numStrings_] = b; - strNxt_[numStrings_] = (index != HASH_FREE) ? index : NEXT_FIRST; - - return numStrings_++; - } - - public short findCharString(short index, byte b) - { - int hshidx, nxtidx; - - if (index == HASH_FREE) - return b; - - hshidx = Hash(index, b); - while ((nxtidx = strHsh_[hshidx]) != HASH_FREE) - { - if (strNxt_[nxtidx] == index && strChr_[nxtidx] == b) - return (short)nxtidx; - hshidx = (hshidx + HASHSTEP) % HASHSIZE; - } - - return (short)0xFFFF; - } - - public void clearTable(int codesize) - { - numStrings_ = 0; - - for (int q = 0; q < HASHSIZE; q++) - { - strHsh_[q] = HASH_FREE; - } - - int w = (1 << codesize) + RES_CODES; - for (int q = 0; q < w; q++) - { - addCharString((short)0xFFFF, (byte)q); - } - } - - static public int Hash(short index, byte lastbyte) - { - return ((int)((short)(lastbyte << 8) ^ index) & 0xFFFF) % HASHSIZE; - } +class LZWStringTable { + private final static int RES_CODES = 2; + private final static short HASH_FREE = (short) 0xFFFF; + private final static short NEXT_FIRST = (short) 0xFFFF; + private final static int MAXBITS = 12; + private final static int MAXSTR = (1 << MAXBITS); + private final static short HASHSIZE = 9973; + private final static short HASHSTEP = 2039; + + byte strChr_[]; + short strNxt_[]; + short strHsh_[]; + short numStrings_; + + public LZWStringTable() { + strChr_ = new byte[MAXSTR]; + strNxt_ = new short[MAXSTR]; + strHsh_ = new short[HASHSIZE]; + } + + public int addCharString(short index, byte b) { + int hshidx; + + if (numStrings_ >= MAXSTR) + return 0xFFFF; + + hshidx = Hash(index, b); + while (strHsh_[hshidx] != HASH_FREE) + hshidx = (hshidx + HASHSTEP) % HASHSIZE; + + strHsh_[hshidx] = numStrings_; + strChr_[numStrings_] = b; + strNxt_[numStrings_] = (index != HASH_FREE) ? index : NEXT_FIRST; + + return numStrings_++; + } + + public short findCharString(short index, byte b) { + int hshidx, nxtidx; + + if (index == HASH_FREE) + return b; + + hshidx = Hash(index, b); + while ((nxtidx = strHsh_[hshidx]) != HASH_FREE) { + if (strNxt_[nxtidx] == index && strChr_[nxtidx] == b) + return (short) nxtidx; + hshidx = (hshidx + HASHSTEP) % HASHSIZE; + } + + return (short) 0xFFFF; + } + + public void clearTable(int codesize) { + numStrings_ = 0; + + for (int q = 0; q < HASHSIZE; q++) { + strHsh_[q] = HASH_FREE; + } + + int w = (1 << codesize) + RES_CODES; + for (int q = 0; q < w; q++) { + addCharString((short) 0xFFFF, (byte) q); + } + } + + static public int Hash(short index, byte lastbyte) { + return ((int) ((short) (lastbyte << 8) ^ index) & 0xFFFF) % HASHSIZE; + } } class LZWCompressor { - public static void LZWCompress(OutputStream output, int codesize, - byte toCompress[]) throws IOException - { - byte c; - short index; - int clearcode, endofinfo, numbits, limit, errcode; - short prefix = (short)0xFFFF; - - BitFile bitFile = new BitFile(output); - LZWStringTable strings = new LZWStringTable(); - - clearcode = 1 << codesize; - endofinfo = clearcode + 1; - - numbits = codesize + 1; - limit = (1 << numbits) - 1; - strings.clearTable(codesize); - bitFile.writeBits(clearcode, numbits); - - for (int loop = 0; loop < toCompress.length; ++loop) - { - c = toCompress[loop]; - if ((index = strings.findCharString(prefix, c)) != -1) - { - prefix = index; - } - else - { - bitFile.writeBits(prefix, numbits); - if (strings.addCharString(prefix, c) > limit) { - if (++numbits > 12) { - bitFile.writeBits(clearcode, numbits - 1); - strings.clearTable(codesize); - numbits = codesize + 1; - } - limit = (1 << numbits) - 1; - } - - prefix = (short)((short)c & 0xFF); - } - } - - if (prefix != -1) - bitFile.writeBits(prefix, numbits); - - bitFile.writeBits(endofinfo, numbits); - bitFile.flush(); - } + public static void LZWCompress(OutputStream output, int codesize, byte toCompress[]) throws IOException { + byte c; + short index; + int clearcode, endofinfo, numbits, limit, errcode; + short prefix = (short) 0xFFFF; + + BitFile bitFile = new BitFile(output); + LZWStringTable strings = new LZWStringTable(); + + clearcode = 1 << codesize; + endofinfo = clearcode + 1; + + numbits = codesize + 1; + limit = (1 << numbits) - 1; + strings.clearTable(codesize); + bitFile.writeBits(clearcode, numbits); + + for (int loop = 0; loop < toCompress.length; ++loop) { + c = toCompress[loop]; + if ((index = strings.findCharString(prefix, c)) != -1) { + prefix = index; + } else { + bitFile.writeBits(prefix, numbits); + if (strings.addCharString(prefix, c) > limit) { + if (++numbits > 12) { + bitFile.writeBits(clearcode, numbits - 1); + strings.clearTable(codesize); + numbits = codesize + 1; + } + limit = (1 << numbits) - 1; + } + + prefix = (short) ((short) c & 0xFF); + } + } + + if (prefix != -1) + bitFile.writeBits(prefix, numbits); + + bitFile.writeBits(endofinfo, numbits); + bitFile.flush(); + } } -class ScreenDescriptor -{ - public short localScreenWidth_, localScreenHeight_; - private byte byte_; - public byte backgroundColorIndex_, pixelAspectRatio_; - - public ScreenDescriptor(short width, short height, int numColors) - { - localScreenWidth_ = width; - localScreenHeight_ = height; - setGlobalColorTableSize((byte)(BitUtils.bitsNeeded(numColors) - 1)); - setGlobalColorTableFlag((byte)1); - setSortFlag((byte)0); - setColorResolution((byte)7); - backgroundColorIndex_ = 0; - pixelAspectRatio_ = 0; - } - - public void write(OutputStream output) throws IOException - { - BitUtils.writeWord(output, localScreenWidth_); - BitUtils.writeWord(output, localScreenHeight_); - output.write(byte_); - output.write(backgroundColorIndex_); - output.write(pixelAspectRatio_); - } - - public void setGlobalColorTableSize(byte num) - { - byte_ |= (num & 7); - } - - public void setSortFlag(byte num) - { - byte_ |= (num & 1) << 3; - } - - public void setColorResolution(byte num) - { - byte_ |= (num & 7) << 4; - } - - public void setGlobalColorTableFlag(byte num) - { - byte_ |= (num & 1) << 7; - } +class ScreenDescriptor { + public short localScreenWidth_, localScreenHeight_; + private byte byte_; + public byte backgroundColorIndex_, pixelAspectRatio_; + + public ScreenDescriptor(short width, short height, int numColors) { + localScreenWidth_ = width; + localScreenHeight_ = height; + setGlobalColorTableSize((byte) (BitUtils.bitsNeeded(numColors) - 1)); + setGlobalColorTableFlag((byte) 1); + setSortFlag((byte) 0); + setColorResolution((byte) 7); + backgroundColorIndex_ = 0; + pixelAspectRatio_ = 0; + } + + public void write(OutputStream output) throws IOException { + BitUtils.writeWord(output, localScreenWidth_); + BitUtils.writeWord(output, localScreenHeight_); + output.write(byte_); + output.write(backgroundColorIndex_); + output.write(pixelAspectRatio_); + } + + public void setGlobalColorTableSize(byte num) { + byte_ |= (num & 7); + } + + public void setSortFlag(byte num) { + byte_ |= (num & 1) << 3; + } + + public void setColorResolution(byte num) { + byte_ |= (num & 7) << 4; + } + + public void setGlobalColorTableFlag(byte num) { + byte_ |= (num & 1) << 7; + } } -class ImageDescriptor -{ - public byte separator_; - public short leftPosition_, topPosition_, width_, height_; - private byte byte_; - - public ImageDescriptor(short width, short height, char separator) - { - separator_ = (byte)separator; - leftPosition_ = 0; - topPosition_ = 0; - width_ = width; - height_ = height; - setLocalColorTableSize((byte)0); - setReserved((byte)0); - setSortFlag((byte)0); - setInterlaceFlag((byte)0); - setLocalColorTableFlag((byte)0); - } - - public void write(OutputStream output) throws IOException - { - output.write(separator_); - BitUtils.writeWord(output, leftPosition_); - BitUtils.writeWord(output, topPosition_); - BitUtils.writeWord(output, width_); - BitUtils.writeWord(output, height_); - output.write(byte_); - } - - public void setLocalColorTableSize(byte num) - { - byte_ |= (num & 7); - } - - public void setReserved(byte num) - { - byte_ |= (num & 3) << 3; - } - - public void setSortFlag(byte num) - { - byte_ |= (num & 1) << 5; - } - - public void setInterlaceFlag(byte num) - { - byte_ |= (num & 1) << 6; - } - - public void setLocalColorTableFlag(byte num) - { - byte_ |= (num & 1) << 7; - } +class ImageDescriptor { + public byte separator_; + public short leftPosition_, topPosition_, width_, height_; + private byte byte_; + + public ImageDescriptor(short width, short height, char separator) { + separator_ = (byte) separator; + leftPosition_ = 0; + topPosition_ = 0; + width_ = width; + height_ = height; + setLocalColorTableSize((byte) 0); + setReserved((byte) 0); + setSortFlag((byte) 0); + setInterlaceFlag((byte) 0); + setLocalColorTableFlag((byte) 0); + } + + public void write(OutputStream output) throws IOException { + output.write(separator_); + BitUtils.writeWord(output, leftPosition_); + BitUtils.writeWord(output, topPosition_); + BitUtils.writeWord(output, width_); + BitUtils.writeWord(output, height_); + output.write(byte_); + } + + public void setLocalColorTableSize(byte num) { + byte_ |= (num & 7); + } + + public void setReserved(byte num) { + byte_ |= (num & 3) << 3; + } + + public void setSortFlag(byte num) { + byte_ |= (num & 1) << 5; + } + + public void setInterlaceFlag(byte num) { + byte_ |= (num & 1) << 6; + } + + public void setLocalColorTableFlag(byte num) { + byte_ |= (num & 1) << 7; + } } -class BitUtils -{ - public static byte bitsNeeded(int n) - { - byte ret = 1; - - if (n-- == 0) - return 0; - - while ((n >>= 1) != 0) - ++ret; - - return ret; - } - - public static void writeWord(OutputStream output, - short w) throws IOException - { - output.write(w & 0xFF); - output.write((w >> 8) & 0xFF); - } - - static void writeString(OutputStream output, - String string) throws IOException - { - for (int loop = 0; loop < string.length(); ++loop) - output.write((byte)(string.charAt(loop))); - } -} +class BitUtils { + public static byte bitsNeeded(int n) { + byte ret = 1; + if (n-- == 0) + return 0; + while ((n >>= 1) != 0) + ++ret; + + return ret; + } + + public static void writeWord(OutputStream output, short w) throws IOException { + output.write(w & 0xFF); + output.write((w >> 8) & 0xFF); + } + + static void writeString(OutputStream output, String string) throws IOException { + for (int loop = 0; loop < string.length(); ++loop) + output.write((byte) (string.charAt(loop))); + } +} diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ObjectInspector.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ObjectInspector.java index 6c8d7ee..ad94ddd 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ObjectInspector.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ObjectInspector.java @@ -44,183 +44,165 @@ import net.wotonomy.ui.swing.components.PropertyEditorTable; import net.wotonomy.ui.swing.components.PropertyEditorTableModel; /** -* The ObjectInspector displays a JFrame containing -* a PropertyEditorTable that displays an object. <br><br> -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ + * The ObjectInspector displays a JFrame containing a PropertyEditorTable that + * displays an object. <br> + * <br> + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ -public class ObjectInspector implements ActionListener, MouseListener -{ - protected JTable table = null; +public class ObjectInspector implements ActionListener, MouseListener { + protected JTable table = null; - // key command to copy contents to clipboard - static public final String COPY = "COPY"; + // key command to copy contents to clipboard + static public final String COPY = "COPY"; /** - * Displays the specified object in a frame. - */ - public ObjectInspector( Object anObject ) - { - initLayout( anObject ); - } - - protected void initLayout( Object aTargetObject ) - { - PropertyEditorTableModel model = - new PropertyEditorTableModel(); - model.setObject( aTargetObject ); - table = new PropertyEditorTable() - { - public void methodInvoked( Object anObject, Method aMethod, Object aResult ) - { - if - ( ( aResult == null ) - || ( aResult instanceof Number ) - || ( aResult instanceof Boolean ) - || ( aResult instanceof String ) ) - { - System.out.println( aMethod.getName() + ": " + aResult ); - } - else - { - new ObjectInspector( aResult ); + * Displays the specified object in a frame. + */ + public ObjectInspector(Object anObject) { + initLayout(anObject); + } + + protected void initLayout(Object aTargetObject) { + PropertyEditorTableModel model = new PropertyEditorTableModel(); + model.setObject(aTargetObject); + table = new PropertyEditorTable() { + public void methodInvoked(Object anObject, Method aMethod, Object aResult) { + if ((aResult == null) || (aResult instanceof Number) || (aResult instanceof Boolean) + || (aResult instanceof String)) { + System.out.println(aMethod.getName() + ": " + aResult); + } else { + new ObjectInspector(aResult); } } }; - table.setModel( model ); - table.addMouseListener( this ); // listen for double-clicks - - // set up keyboard events for cut-copy: Ctrl-C, Ctrl-X - table.registerKeyboardAction( this, COPY, - KeyStroke.getKeyStroke( KeyEvent.VK_C, - Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() ), - JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); - table.registerKeyboardAction( this, COPY, - KeyStroke.getKeyStroke( KeyEvent.VK_X, - Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() ), - JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); - - JPanel panel = new JPanel(); - panel.setBorder( new EmptyBorder( new Insets( 10, 10, 10, 10 ) ) ); - panel.setLayout( new BorderLayout( 10, 10 ) ); - - JScrollPane scrollPane = new JScrollPane( table ); - scrollPane.setPreferredSize( new Dimension( 325, 350 ) ); - panel.add( scrollPane, BorderLayout.CENTER ); - - JFrame window = new JFrame(); - window.setTitle( aTargetObject.getClass().getName() ); - window.getContentPane().add( panel ); - - window.pack(); - WindowUtilities.cascade( window ); - window.show(); - } - - // interface MouseListener - - /** - * Double click to call invokeFileFromString. - */ - - public void mouseClicked(MouseEvent e) - { - if ( e.getSource() == table ) - { - if ( e.getClickCount() > 1 ) - { - int row = table.rowAtPoint( e.getPoint() ); - int col = table.columnAtPoint( e.getPoint() ); - - if ( ( row == -1 ) || ( col != 0 ) ) return; - - /* do something here */ - } - } - } - - public void mouseReleased(MouseEvent e) {} - public void mousePressed(MouseEvent e) {} - public void mouseEntered(MouseEvent e) {} - public void mouseExited(MouseEvent e) {} - - - // interface ActionEventListener - for listening to key commands - - public void actionPerformed(ActionEvent evt) - { - if ( COPY.equals( evt.getActionCommand() ) ) - { - copyToClipboard(); - return; - } - } + table.setModel(model); + table.addMouseListener(this); // listen for double-clicks + + // set up keyboard events for cut-copy: Ctrl-C, Ctrl-X + table.registerKeyboardAction(this, COPY, + KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + table.registerKeyboardAction(this, COPY, + KeyStroke.getKeyStroke(KeyEvent.VK_X, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + + JPanel panel = new JPanel(); + panel.setBorder(new EmptyBorder(new Insets(10, 10, 10, 10))); + panel.setLayout(new BorderLayout(10, 10)); + + JScrollPane scrollPane = new JScrollPane(table); + scrollPane.setPreferredSize(new Dimension(325, 350)); + panel.add(scrollPane, BorderLayout.CENTER); + + JFrame window = new JFrame(); + window.setTitle(aTargetObject.getClass().getName()); + window.getContentPane().add(panel); + + window.pack(); + WindowUtilities.cascade(window); + window.show(); + } + + // interface MouseListener -/** -* Copies the contents of the table to the clipboard as a tab-delimited string. -*/ - public void copyToClipboard() - { - Toolkit toolkit = Toolkit.getDefaultToolkit(); - Clipboard clipboard = toolkit.getSystemClipboard(); - StringSelection selection = - new StringSelection( getTabDelimitedString() ); - clipboard.setContents( selection, selection ); - } - /** - * Converts the contents of the table to a tab-delimited string. - * @return A String containing the text contents of the table. - */ - public String getTabDelimitedString() - { - StringBuffer result = new StringBuffer(64); - - TableModel model = table.getModel(); - int cols = model.getColumnCount(); - int rows = model.getRowCount(); - - Object o = null; - for ( int y = 0; y < rows; y++ ) - { - for ( int x = 0; x < cols; x++ ) - { - o = model.getValueAt( y, x ); - if ( o == null ) o = ""; - result.append( o ); - result.append( '\t' ); - } - result.append( '\n' ); - } - - return result.toString(); - } + * Double click to call invokeFileFromString. + */ + + public void mouseClicked(MouseEvent e) { + if (e.getSource() == table) { + if (e.getClickCount() > 1) { + int row = table.rowAtPoint(e.getPoint()); + int col = table.columnAtPoint(e.getPoint()); + + if ((row == -1) || (col != 0)) + return; + + /* do something here */ + } + } + } + + public void mouseReleased(MouseEvent e) { + } + + public void mousePressed(MouseEvent e) { + } + + public void mouseEntered(MouseEvent e) { + } + + public void mouseExited(MouseEvent e) { + } + + // interface ActionEventListener - for listening to key commands + + public void actionPerformed(ActionEvent evt) { + if (COPY.equals(evt.getActionCommand())) { + copyToClipboard(); + return; + } + } + + /** + * Copies the contents of the table to the clipboard as a tab-delimited string. + */ + public void copyToClipboard() { + Toolkit toolkit = Toolkit.getDefaultToolkit(); + Clipboard clipboard = toolkit.getSystemClipboard(); + StringSelection selection = new StringSelection(getTabDelimitedString()); + clipboard.setContents(selection, selection); + } + + /** + * Converts the contents of the table to a tab-delimited string. + * + * @return A String containing the text contents of the table. + */ + public String getTabDelimitedString() { + StringBuffer result = new StringBuffer(64); + + TableModel model = table.getModel(); + int cols = model.getColumnCount(); + int rows = model.getRowCount(); + + Object o = null; + for (int y = 0; y < rows; y++) { + for (int x = 0; x < cols; x++) { + o = model.getValueAt(y, x); + if (o == null) + o = ""; + result.append(o); + result.append('\t'); + } + result.append('\n'); + } + + return result.toString(); + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.3 2003/08/06 23:07:53 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.3 2003/08/06 23:07:53 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.2 2002/11/16 16:33:31 mpowers - * Now using platform-specific accelerator key for shortcuts. + * Revision 1.2 2002/11/16 16:33:31 mpowers Now using platform-specific + * accelerator key for shortcuts. * - * Revision 1.1.1.1 2000/12/21 15:51:27 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:51:27 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:45 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:45 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/PositionComparator.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/PositionComparator.java index f5fe3e4..7f2adb8 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/PositionComparator.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/PositionComparator.java @@ -27,63 +27,55 @@ import java.util.Comparator; import javax.swing.SwingUtilities; /** -* A Comparator that will sort components in a common container -* based first on their y-coordinate and then on their x-coordinate, -* producing a list sorted from top to bottom and left to right. -* If all components are not in the same container, the resulting -* sort is undefined. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class PositionComparator implements Comparator, Serializable -{ - private Container rootContainer; - private transient Component c1, c2; - private transient Point p1, p2; + * A Comparator that will sort components in a common container based first on + * their y-coordinate and then on their x-coordinate, producing a list sorted + * from top to bottom and left to right. If all components are not in the same + * container, the resulting sort is undefined. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class PositionComparator implements Comparator, Serializable { + private Container rootContainer; + private transient Component c1, c2; + private transient Point p1, p2; -/** -* Standard constructor to configure the comparator. -* @param aContainer The common container for all the objects to be compared. -*/ - public PositionComparator( Container aContainer ) - { - rootContainer = aContainer; - } + /** + * Standard constructor to configure the comparator. + * + * @param aContainer The common container for all the objects to be compared. + */ + public PositionComparator(Container aContainer) { + rootContainer = aContainer; + } - // interface Comparable + // interface Comparable - public int compare(Object o1, Object o2) - { - c1 = (Component) o1; - c2 = (Component) o2; + public int compare(Object o1, Object o2) { + c1 = (Component) o1; + c2 = (Component) o2; - p1 = SwingUtilities.convertPoint( c1.getParent(), c1.getLocation(), rootContainer ); - p2 = SwingUtilities.convertPoint( c2.getParent(), c2.getLocation(), rootContainer ); + p1 = SwingUtilities.convertPoint(c1.getParent(), c1.getLocation(), rootContainer); + p2 = SwingUtilities.convertPoint(c2.getParent(), c2.getLocation(), rootContainer); - if ( p1.y != p2.y ) - { - return p1.y - p2.y; - } - return p1.x - p2.x; - } + if (p1.y != p2.y) { + return p1.y - p2.y; + } + return p1.x - p2.x; + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1.1.1 2000/12/21 15:51:27 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:51:27 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:45 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:45 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/StackTraceInspector.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/StackTraceInspector.java index 7e61411..ae4bb2d 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/StackTraceInspector.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/StackTraceInspector.java @@ -50,408 +50,364 @@ import javax.swing.table.TableModel; import net.wotonomy.ui.swing.components.MultiLineLabel; /** -* The StackTraceInspector displays a JFrame containing -* stack trace information for a Throwable. <br><br> -* -* There are also a few static methods for obtaining -* information about the current stack, which is useful -* for determining who's calling you at runtime. -* -* @author michael@mpowers.net -* @version $Revision: 904 $ -*/ + * The StackTraceInspector displays a JFrame containing stack trace information + * for a Throwable. <br> + * <br> + * + * There are also a few static methods for obtaining information about the + * current stack, which is useful for determining who's calling you at runtime. + * + * @author michael@mpowers.net + * @version $Revision: 904 $ + */ -public class StackTraceInspector - implements TableModel, MouseListener, ActionListener -{ - protected JTable table = null; - protected List tableModelListeners = null; - protected List methodNames = new ArrayList(); +public class StackTraceInspector implements TableModel, MouseListener, ActionListener { + protected JTable table = null; + protected List tableModelListeners = null; + protected List methodNames = new ArrayList(); + + // key command to copy contents to clipboard + static public final String COPY = "COPY"; + + /** + * Displays the current stack trace at the time of instantiation in a table on a + * frame. + */ + public StackTraceInspector() { + initLayout(parseStackTrace(new RuntimeException()), null); + } + + /** + * Displays the current stack trace at the time of instantiation in a table on a + * frame annotated with the specified message. + */ + public StackTraceInspector(String aMessage) { + initLayout(parseStackTrace(new RuntimeException()), aMessage); + } + + /** + * Displays the stack trace for the given throwable in a table on a frame. + * + * @param aThrowable A Throwable whose stack will be examined. + */ + public StackTraceInspector(Throwable aThrowable) { + initLayout(parseStackTrace(aThrowable), aThrowable.getClass() + ": " + aThrowable.getMessage()); + } + + /** + * Simply displays the list items in a dialog. Presumably (but not necessarily) + * called from the other constructors. (I guess if you just want a frame with + * strings in table, you can call this.) + * + * @param aStringList A List containing Strings. + */ + public StackTraceInspector(List aStringList) { + initLayout(aStringList, null); + } + + protected void initLayout(List items, String message) { + methodNames = new ArrayList(items); + table = new JTable(this); // this class is the table model + table.addMouseListener(this); // listen for double-clicks + + // set up keyboard events for cut-copy: Ctrl-C, Ctrl-X + table.registerKeyboardAction(this, COPY, + KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + table.registerKeyboardAction(this, COPY, + KeyStroke.getKeyStroke(KeyEvent.VK_X, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + + JPanel panel = new JPanel(); + panel.setBorder(new EmptyBorder(new Insets(10, 10, 10, 10))); + panel.setLayout(new BorderLayout(10, 10)); + + if (message != null) { + panel.add(new MultiLineLabel(message), BorderLayout.NORTH); + } - // key command to copy contents to clipboard - static public final String COPY = "COPY"; + JScrollPane scrollPane = new JScrollPane(table); + scrollPane.setPreferredSize(new Dimension(325, 350)); + panel.add(scrollPane, BorderLayout.CENTER); -/** -* Displays the current stack trace at the time -* of instantiation in a table on a frame. -*/ - public StackTraceInspector() - { - initLayout( parseStackTrace( new RuntimeException() ), null ); - } + JFrame window = new JFrame(); + window.setTitle("Stack Trace Inspector"); + window.getContentPane().add(panel); -/** -* Displays the current stack trace at the time -* of instantiation in a table on a frame -* annotated with the specified message. -*/ - public StackTraceInspector( String aMessage ) - { - initLayout( parseStackTrace( new RuntimeException() ), aMessage ); - } + window.pack(); + WindowUtilities.cascade(window); + window.show(); + } -/** -* Displays the stack trace for the given throwable -* in a table on a frame. -* @param aThrowable A Throwable whose stack will be examined. -*/ - public StackTraceInspector( Throwable aThrowable ) - { - initLayout( parseStackTrace( aThrowable ), - aThrowable.getClass() + ": " + aThrowable.getMessage() ); - } + // interface TableModel -/** -* Simply displays the list items in a dialog. -* Presumably (but not necessarily) called from -* the other constructors. (I guess if you just -* want a frame with strings in table, you can -* call this.) -* @param aStringList A List containing Strings. -*/ - public StackTraceInspector( List aStringList ) - { - initLayout( aStringList, null ); - } - - protected void initLayout( List items, String message ) - { - methodNames = new ArrayList( items ); - table = new JTable( this ); // this class is the table model - table.addMouseListener( this ); // listen for double-clicks - - // set up keyboard events for cut-copy: Ctrl-C, Ctrl-X - table.registerKeyboardAction( this, COPY, - KeyStroke.getKeyStroke( KeyEvent.VK_C, - Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() ), - JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); - table.registerKeyboardAction( this, COPY, - KeyStroke.getKeyStroke( KeyEvent.VK_X, - Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() ), - JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); - - JPanel panel = new JPanel(); - panel.setBorder( new EmptyBorder( new Insets( 10, 10, 10, 10 ) ) ); - panel.setLayout( new BorderLayout( 10, 10 ) ); - - if ( message != null ) - { - panel.add( new MultiLineLabel( message ), BorderLayout.NORTH ); + public int getRowCount() { + return methodNames.size(); + } + + public int getColumnCount() { + return 1; + } + + public String getColumnName(int columnIndex) { + switch (columnIndex) { + case 0: + return "Methods"; + case 1: + return "Property"; } + System.out.println("StackTraceInspector.getColumnName: unknown column: " + columnIndex); + return ""; + } + + public Class getColumnClass(int columnIndex) { + switch (columnIndex) { + case 0: + return String.class; + case 1: + return String.class; + } + System.out.println("StackTraceInspector.getColumnClass: unknown column: " + columnIndex); + return Object.class; + } - JScrollPane scrollPane = new JScrollPane( table ); - scrollPane.setPreferredSize( new Dimension( 325, 350 ) ); - panel.add( scrollPane, BorderLayout.CENTER ); - - JFrame window = new JFrame(); - window.setTitle( "Stack Trace Inspector" ); - window.getContentPane().add( panel ); - - window.pack(); - WindowUtilities.cascade( window ); - window.show(); - } - - // interface TableModel - - public int getRowCount() - { - return methodNames.size(); - } - - public int getColumnCount() - { - return 1; - } - - public String getColumnName(int columnIndex) - { - switch ( columnIndex ) - { - case 0: - return "Methods"; - case 1: - return "Property"; - } - System.out.println( "StackTraceInspector.getColumnName: unknown column: " + columnIndex ); - return ""; - } - - public Class getColumnClass(int columnIndex) - { - switch ( columnIndex ) - { - case 0: - return String.class; - case 1: - return String.class; - } - System.out.println( "StackTraceInspector.getColumnClass: unknown column: " + columnIndex ); - return Object.class; - } - - public boolean isCellEditable(int rowIndex, - int columnIndex) - { - return false; - } - - public Object getValueAt(int rowIndex, - int columnIndex) - { - return methodNames.get( rowIndex ); - } - - public void setValueAt(Object aValue, - int rowIndex, - int columnIndex) - { - } - - public void addTableModelListener(TableModelListener l) - { - if ( tableModelListeners == null ) - { - tableModelListeners = new ArrayList(); - } - tableModelListeners.add( l ); - } - - public void removeTableModelListener(TableModelListener l) - { - if ( tableModelListeners != null ) - { - tableModelListeners.remove( l ); - } - } - - // interface MouseListener - - /** - * Double click to call invokeFileFromString. - */ - - public void mouseClicked(MouseEvent e) - { - if ( e.getSource() == table ) - { - if ( e.getClickCount() > 1 ) - { - int row = table.rowAtPoint( e.getPoint() ); - int col = table.columnAtPoint( e.getPoint() ); - - if ( ( row == -1 ) || ( col != 0 ) ) return; - - invokeFileFromString( methodNames.get( row ).toString() ); - } - } - } - - public void mouseReleased(MouseEvent e) {} - public void mousePressed(MouseEvent e) {} - public void mouseEntered(MouseEvent e) {} - public void mouseExited(MouseEvent e) {} - - - // interface ActionEventListener - for listening to key commands - - public void actionPerformed(ActionEvent evt) - { - if ( COPY.equals( evt.getActionCommand() ) ) - { - copyToClipboard(); - return; - } - } + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } -/** -* Copies the contents of the table to the clipboard as a tab-delimited string. -*/ - public void copyToClipboard() - { - Toolkit toolkit = Toolkit.getDefaultToolkit(); - Clipboard clipboard = toolkit.getSystemClipboard(); - StringSelection selection = - new StringSelection( getSelectedStackString() ); - clipboard.setContents( selection, selection ); - } + public Object getValueAt(int rowIndex, int columnIndex) { + return methodNames.get(rowIndex); + } -/** -* Converts the selected contents of the table to a string. -* @return A String containing the text contents of the table. -*/ - public String getSelectedStackString() - { - StringBuffer result = new StringBuffer(64); + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + } - TableModel model = table.getModel(); + public void addTableModelListener(TableModelListener l) { + if (tableModelListeners == null) { + tableModelListeners = new ArrayList(); + } + tableModelListeners.add(l); + } - Object o; - int[] selectedRows = table.getSelectedRows(); - for ( int i = 0; i < selectedRows.length; i++ ) - { - o = model.getValueAt( selectedRows[i], 0 ); - if ( o == null ) o = ""; - result.append( o ); - result.append( '\n' ); - } + public void removeTableModelListener(TableModelListener l) { + if (tableModelListeners != null) { + tableModelListeners.remove(l); + } + } - return result.toString(); - } + // interface MouseListener + /** + * Double click to call invokeFileFromString. + */ - // static methods + public void mouseClicked(MouseEvent e) { + if (e.getSource() == table) { + if (e.getClickCount() > 1) { + int row = table.rowAtPoint(e.getPoint()); + int col = table.columnAtPoint(e.getPoint()); -/** -* Obtains a list of strings representing the stack trace -* associated with this throwable starting with the most recent call. -* @param aThrowable A Throwable whose stack trace is parsed. -* @return a List containing the method names as Strings. -*/ - static public List parseStackTrace( Throwable aThrowable ) - { - String trace = null; + if ((row == -1) || (col != 0)) + return; - // create new stream - ByteArrayOutputStream os = new ByteArrayOutputStream( 256 ); - PrintStream newErr = new PrintStream( os ); - aThrowable.printStackTrace( newErr ); // prints to System.err + invokeFileFromString(methodNames.get(row).toString()); + } + } + } - // convert to string - trace = os.toString(); + public void mouseReleased(MouseEvent e) { + } - List result = new ArrayList(); + public void mousePressed(MouseEvent e) { + } - // populate list with parsed trace, starting from top - String token; - StringTokenizer tokens = new StringTokenizer( trace, "\n" ); - tokens.nextToken(); // strip off description of throwable - while ( tokens.hasMoreTokens() ) - { - token = tokens.nextToken(); - if ( token.indexOf( StackTraceInspector.class.getName() ) == -1 ) - { // add only those methods not from this class + public void mouseEntered(MouseEvent e) { + } - // strip whitespace, "at " from front, and \r from end - token.trim(); - token = token.substring( 4, token.length() - 1 ); + public void mouseExited(MouseEvent e) { + } - result.add( token ); - } - } + // interface ActionEventListener - for listening to key commands - return result; - } + public void actionPerformed(ActionEvent evt) { + if (COPY.equals(evt.getActionCommand())) { + copyToClipboard(); + return; + } + } + + /** + * Copies the contents of the table to the clipboard as a tab-delimited string. + */ + public void copyToClipboard() { + Toolkit toolkit = Toolkit.getDefaultToolkit(); + Clipboard clipboard = toolkit.getSystemClipboard(); + StringSelection selection = new StringSelection(getSelectedStackString()); + clipboard.setContents(selection, selection); + } + + /** + * Converts the selected contents of the table to a string. + * + * @return A String containing the text contents of the table. + */ + public String getSelectedStackString() { + StringBuffer result = new StringBuffer(64); + + TableModel model = table.getModel(); + + Object o; + int[] selectedRows = table.getSelectedRows(); + for (int i = 0; i < selectedRows.length; i++) { + o = model.getValueAt(selectedRows[i], 0); + if (o == null) + o = ""; + result.append(o); + result.append('\n'); + } -/** -* Convenience method that obtains a String representing -* the caller's caller. -* @return a String representing a method in stack trace format. -*/ - static public String getMyCaller() - { - List trace = parseStackTrace( new RuntimeException() ); - if ( trace.size() > 1 ) - { - return trace.get( 1 ).toString(); - } + return result.toString(); + } + + // static methods + + /** + * Obtains a list of strings representing the stack trace associated with this + * throwable starting with the most recent call. + * + * @param aThrowable A Throwable whose stack trace is parsed. + * @return a List containing the method names as Strings. + */ + static public List parseStackTrace(Throwable aThrowable) { + String trace = null; + + // create new stream + ByteArrayOutputStream os = new ByteArrayOutputStream(256); + PrintStream newErr = new PrintStream(os); + aThrowable.printStackTrace(newErr); // prints to System.err + + // convert to string + trace = os.toString(); + + List result = new ArrayList(); + + // populate list with parsed trace, starting from top + String token; + StringTokenizer tokens = new StringTokenizer(trace, "\n"); + tokens.nextToken(); // strip off description of throwable + while (tokens.hasMoreTokens()) { + token = tokens.nextToken(); + if (token.indexOf(StackTraceInspector.class.getName()) == -1) { // add only those methods not from this + // class + + // strip whitespace, "at " from front, and \r from end + token.trim(); + token = token.substring(4, token.length() - 1); + + result.add(token); + } + } - return null; - } + return result; + } + + /** + * Convenience method that obtains a String representing the caller's caller. + * + * @return a String representing a method in stack trace format. + */ + static public String getMyCaller() { + List trace = parseStackTrace(new RuntimeException()); + if (trace.size() > 1) { + return trace.get(1).toString(); + } -/** -* Prints a stack trace up to the first method whose fully -* qualified class name begins with "java" to System.out. -*/ - static public void printShortStackTrace() - { - String s; - Iterator i = parseStackTrace( new RuntimeException() ).iterator(); - while ( i.hasNext() ) - { - System.out.println( " " + ( s = i.next().toString() ) ); - if ( s.startsWith( "java" ) ) break; - } - } - - protected void invokeFileFromString( String aString ) - { - // strip off parentheses, if any - int openParam = aString.indexOf( "(" ); - if ( openParam != -1 ) - { - aString = aString.substring( 0, openParam ); - } - - // separate class name from method name - int lastDot = aString.lastIndexOf( "." ); - if ( lastDot == -1 ) return; - String className = aString.substring( 0, lastDot ); - String methodName = aString.substring( lastDot + 1 ); - - // convert "."s to file separator characters - StringBuffer buf = new StringBuffer(); - StringTokenizer tokens = new StringTokenizer( className, "." ); - while ( true ) - { - buf.append( tokens.nextToken() ); - if ( ! tokens.hasMoreTokens() ) break; - buf.append( File.separator ); - } - String path = buf.toString(); - java.net.URL url = ClassLoader.getSystemResource( path + ".java" ); - if ( url == null ) return; // do nothing - - String name = url.getFile(); - - // try to launch the document - try - { - // NOTE: This is Windows-dependent! - String args[] = new String[] { - "cmd", "/c", "\"start \"\" \"" + name.substring(1) + "\"\"" }; - // this translates to: cmd /c "start "" "path"" - // apparently an array is more reliable for calling exec(). - // all the extra quotes are to handle paths with spaces. - // trims off the first "/" before the drive letter. - // needed a dummy title for the console or it wouldn't work. - Runtime.getRuntime().exec( args ); - } - catch ( Exception exc ) - { - System.out.println( "DocumentLinkPanel.invokeDocument: " + exc ); - } - return; - } + return null; + } + + /** + * Prints a stack trace up to the first method whose fully qualified class name + * begins with "java" to System.out. + */ + static public void printShortStackTrace() { + String s; + Iterator i = parseStackTrace(new RuntimeException()).iterator(); + while (i.hasNext()) { + System.out.println(" " + (s = i.next().toString())); + if (s.startsWith("java")) + break; + } + } + + protected void invokeFileFromString(String aString) { + // strip off parentheses, if any + int openParam = aString.indexOf("("); + if (openParam != -1) { + aString = aString.substring(0, openParam); + } + + // separate class name from method name + int lastDot = aString.lastIndexOf("."); + if (lastDot == -1) + return; + String className = aString.substring(0, lastDot); + String methodName = aString.substring(lastDot + 1); + + // convert "."s to file separator characters + StringBuffer buf = new StringBuffer(); + StringTokenizer tokens = new StringTokenizer(className, "."); + while (true) { + buf.append(tokens.nextToken()); + if (!tokens.hasMoreTokens()) + break; + buf.append(File.separator); + } + String path = buf.toString(); + java.net.URL url = ClassLoader.getSystemResource(path + ".java"); + if (url == null) + return; // do nothing + + String name = url.getFile(); + + // try to launch the document + try { + // NOTE: This is Windows-dependent! + String args[] = new String[] { "cmd", "/c", "\"start \"\" \"" + name.substring(1) + "\"\"" }; + // this translates to: cmd /c "start "" "path"" + // apparently an array is more reliable for calling exec(). + // all the extra quotes are to handle paths with spaces. + // trims off the first "/" before the drive letter. + // needed a dummy title for the console or it wouldn't work. + Runtime.getRuntime().exec(args); + } catch (Exception exc) { + System.out.println("DocumentLinkPanel.invokeDocument: " + exc); + } + return; + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.5 2003/08/06 23:07:53 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.5 2003/08/06 23:07:53 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.4 2002/11/16 16:33:31 mpowers - * Now using platform-specific accelerator key for shortcuts. + * Revision 1.4 2002/11/16 16:33:31 mpowers Now using platform-specific + * accelerator key for shortcuts. * - * Revision 1.3 2001/07/18 21:53:33 mpowers - * Added a string argument for display as a message. + * Revision 1.3 2001/07/18 21:53:33 mpowers Added a string argument for display + * as a message. * - * Revision 1.2 2001/07/17 14:01:43 mpowers - * Added short stack trace method. + * Revision 1.2 2001/07/17 14:01:43 mpowers Added short stack trace method. * - * Revision 1.1.1.1 2000/12/21 15:51:34 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:51:34 mpowers Contributing wotonomy. * - * Revision 1.5 2000/12/20 16:25:45 michael - * Added log to all files. + * Revision 1.5 2000/12/20 16:25:45 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/TextInputRangeChecker.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/TextInputRangeChecker.java index 36dbacf..20f37c1 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/TextInputRangeChecker.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/TextInputRangeChecker.java @@ -30,339 +30,298 @@ import javax.swing.SwingUtilities; import javax.swing.text.JTextComponent; /** -* This class will actively check the inputs of 2 numbers in seperate text -* components. The number in the text components represent an upper and lower -* bound to some range. This class checks to make sure the user inputs values -* in the lower bound text field that are less than the value of the upper -* bound and vice versa for the upper bound text field. This class will also -* check to make sure the bounds fall within a given range if specified. -* -* The checks are automatically performed when the focus is lost on either -* component. If the inputs are correct then no event occurs. If the inputs -* are not correct, then a dialog message is displayed stating the reason why -* the bounds are invalid, and the original correct value is restored into the -* text components. -* -* @author rglista -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class TextInputRangeChecker implements FocusListener -{ - protected static final int NONE = 0; - protected static final int LOWER = 1; - protected static final int UPPER = 2; - - private JTextComponent lowerComponent; - private JTextComponent upperComponent; - private double maxRange; - private double lowerNumber; - private double upperNumber; - private Collection focusListeners; - - private String invalidLowerMessage; - private String invalidUpperMessage; - private String invalidEitherMessage; - private String invalidRangeMessage; - - -/** -* Constructor with some of the settable parameters. No range checking is used. -* @param aLowerTextComponent A text component for the lower bound. -* @param anUpperTextComponent A text component for the upper bound. -*/ - public TextInputRangeChecker( JTextComponent aLowerTextComponent, - JTextComponent anUpperTextComponent ) - { - this( aLowerTextComponent, anUpperTextComponent, null, null, 0.0 ); - } - -/** -* Constructor with some of the settable parameters. No range checking is -* used. -* @param aLowerTextComponent A text component for the lower bound. -* @param anUpperTextComponent A text component for the upper bound. -* @param lowerTextName The name of the lower bound, eg - start year. -* @param upperTextName The name of the upper bound, eg - end year. -* is used. -*/ - public TextInputRangeChecker( JTextComponent aLowerTextComponent, - JTextComponent anUpperTextComponent, - String lowerTextName, String upperTextName ) - { - this( aLowerTextComponent, anUpperTextComponent, lowerTextName, upperTextName, 0.0 ); - } - -/** -* Constructor with some of the settable parameters. -* @param aLowerTextComponent A text component for the lower bound. -* @param anUpperTextComponent A text component for the upper bound. -* @param aMaxRange The range the bounds muist fall between, if 0 then no range -* is used. -*/ - public TextInputRangeChecker( JTextComponent aLowerTextComponent, - JTextComponent anUpperTextComponent, - double aMaxRange ) - { - this( aLowerTextComponent, anUpperTextComponent, null, null, aMaxRange ); - } - -/** -* Constructor with all the settable parameters. -* @param aLowerTextComponent A text component for the lower bound. -* @param anUpperTextComponent A text component for the upper bound. -* @param lowerTextName The name of the lower bound, eg - start year. -* @param upperTextName The name of the upper bound, eg - end year. -* @param aMaxRange The range the bounds muist fall between, if 0 then no range -* is used. -*/ - public TextInputRangeChecker( JTextComponent aLowerTextComponent, - JTextComponent anUpperTextComponent, - String lowerTextName, String upperTextName, - double aMaxRange ) - { - lowerComponent = aLowerTextComponent; - upperComponent = anUpperTextComponent; - maxRange = aMaxRange; - - focusListeners = new ArrayList( 1 ); // For most cases, there will be only 1 listener. - - lowerComponent.addFocusListener( this ); - upperComponent.addFocusListener( this ); - - lowerNumber = getNumber( lowerComponent ); - upperNumber = getNumber( upperComponent ); - - if ( ( lowerTextName != null ) && ( upperTextName != null ) ) - { - invalidLowerMessage = "The " + lowerTextName + " must be less than or equal to the " + upperTextName + "."; - invalidUpperMessage = "The " + upperTextName + " must be greater than or equal to the " + lowerTextName + "."; - invalidEitherMessage = "The " + lowerTextName + " and/or the " + upperTextName + " are not correct."; - invalidRangeMessage = "The maximum range for the " + lowerTextName + " and " + upperTextName + " is " + maxRange + "."; - } - else - { - invalidLowerMessage = "The lower bound must be less than or equal to the upper bound."; - invalidUpperMessage = "The upper bound must be greater than or equal to the lower bound."; - invalidEitherMessage = "The upper and/or lower bounds are not correct."; - invalidRangeMessage = "The maximum range is " + maxRange + "."; - } - } - -/** -* Allows the caller to perform the validation of the bounds programatically. -* The lower bound is compared to the upper bound and range checking is performed. -* If the lower bound is greater than the upper bound, or the range between the -* bounds is greater than the max range, then validation fails. -* @return TRUE is validation is successfull, FALSE if it fails. -*/ - public boolean performCheck() - { - return validate( null ); - } - -/** -* Adds the listener to the lists of focus listener maintened by this object. -* When one of the 2 text components receives a focus event, this object will -* fire that focus event to any of its listeners. This is useful when the -* calling object wants to be notified of the components focus events, but wants -* to ensure that the validation has occured first. -* <br><br> -* NOTE: The focus is only fired if the validation was successful. This might -* have to be changed. -* @param aListener A Focus Listener to receive Focus Events. -*/ - public void addFocusListener( FocusListener aListener ) - { - focusListeners.add( aListener ); - } - -/** -* Returns the last valid value of the lower bound. If this is called while -* the user is updating the text component but before the focus is lost, the -* value returned will be the original value before the user started updating -* the bound. -* @return The last valid value of the lower bound. -*/ - public double getLastValidatedLowerNumber() - { - return lowerNumber; - } - -/** -* Returns the last valid value of the upper bound. If this is called while -* the user is updating the text component but before the focus is lost, the -* value returned will be the original value before the user started updating -* the bound. -* @return The last valid value of the upper bound. -*/ - public double getLastValidatedUpperNumber() - { - return upperNumber; - } - -/** -* Method used to be notified when one of the text components has gained its -* focus. -*/ - public void focusGained( FocusEvent e ) - { - lowerNumber = getNumber( lowerComponent ); - upperNumber = getNumber( upperComponent ); - } - -/** -* Method used to be notified when one of the text components has lost its -* focus. Automatic validation occurs here. -*/ - public void focusLost( FocusEvent e ) - { - if ( e.isTemporary() ) - { - return; - } - - if ( validate( e.getSource() ) ) - { - fireFocusEvent( e ); - } - } - -/** -* Fires a focus lost event if the validation was successfull. -*/ - protected void fireFocusEvent( FocusEvent e ) - { - for ( Iterator it = focusListeners.iterator(); it.hasNext(); ) - { - ( ( FocusListener )it.next() ).focusLost( e ); - } - } - -/** -* Validates the bounds inputed by the user. -* @param aComponent The component to use to display a dialog window, if neccessray. -* If null, then the parent window of the text componets will be used. -* @return TRUE if validation was successful, FALSE otherwise. -*/ - protected boolean validate( Object aComponent ) - { - int componentUsed = NONE; - if ( aComponent == lowerComponent ) - { - componentUsed = LOWER; - } - else if ( aComponent == upperComponent ) - { - componentUsed = UPPER; - } - - double lower = getNumber( lowerComponent ); - double upper = getNumber( upperComponent ); - - if ( lower > upper ) - { - if ( componentUsed == LOWER ) - { - lowerComponent.setText( Double.toString( lowerNumber ) ); - displayMessage( invalidLowerMessage, lowerComponent ); - } - else if ( componentUsed == UPPER ) - { - upperComponent.setText( Double.toString( upperNumber ) ); - displayMessage( invalidUpperMessage, upperComponent ); - } - else - { - upperComponent.setText( Double.toString( upperNumber ) ); - lowerComponent.setText( Double.toString( lowerNumber ) ); - displayMessage( invalidEitherMessage, lowerComponent.getTopLevelAncestor() ); - } - - return false; - } - - if ( maxRange != 0.0 ) - { - if ( ( upper - lower ) > maxRange ) - { - if ( componentUsed == LOWER ) - { - lowerComponent.setText( Double.toString( lowerNumber ) ); - displayMessage( invalidRangeMessage, lowerComponent ); - } - else if ( componentUsed == UPPER ) - { - upperComponent.setText( Double.toString( upperNumber ) ); - displayMessage( invalidRangeMessage, upperComponent ); - } - else - { - upperComponent.setText( Double.toString( upperNumber ) ); - lowerComponent.setText( Double.toString( lowerNumber ) ); - displayMessage( invalidRangeMessage, lowerComponent.getTopLevelAncestor() ); - } - - return false; - } - } - - lowerNumber = lower; - upperNumber = upper; - return true; - } - -/** -* Creates a JOptionPane to display the reason why the bounds failed validation. -*/ - protected void displayMessage( final String message, final Component parent ) - { - SwingUtilities.invokeLater( new Runnable() - { - public void run() - { - JOptionPane.showMessageDialog( parent, message, "Data Entry Error", - JOptionPane.ERROR_MESSAGE ); - } - } ); - } - -/** -* Gets the number represented in the text component. If the text does not -* represent a number, then zero is returned. -*/ - protected double getNumber( JTextComponent aComponent ) - { - try - { - return Double.valueOf( aComponent.getText() ).doubleValue(); + * This class will actively check the inputs of 2 numbers in seperate text + * components. The number in the text components represent an upper and lower + * bound to some range. This class checks to make sure the user inputs values in + * the lower bound text field that are less than the value of the upper bound + * and vice versa for the upper bound text field. This class will also check to + * make sure the bounds fall within a given range if specified. + * + * The checks are automatically performed when the focus is lost on either + * component. If the inputs are correct then no event occurs. If the inputs are + * not correct, then a dialog message is displayed stating the reason why the + * bounds are invalid, and the original correct value is restored into the text + * components. + * + * @author rglista + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class TextInputRangeChecker implements FocusListener { + protected static final int NONE = 0; + protected static final int LOWER = 1; + protected static final int UPPER = 2; + + private JTextComponent lowerComponent; + private JTextComponent upperComponent; + private double maxRange; + private double lowerNumber; + private double upperNumber; + private Collection focusListeners; + + private String invalidLowerMessage; + private String invalidUpperMessage; + private String invalidEitherMessage; + private String invalidRangeMessage; + + /** + * Constructor with some of the settable parameters. No range checking is used. + * + * @param aLowerTextComponent A text component for the lower bound. + * @param anUpperTextComponent A text component for the upper bound. + */ + public TextInputRangeChecker(JTextComponent aLowerTextComponent, JTextComponent anUpperTextComponent) { + this(aLowerTextComponent, anUpperTextComponent, null, null, 0.0); + } + + /** + * Constructor with some of the settable parameters. No range checking is used. + * + * @param aLowerTextComponent A text component for the lower bound. + * @param anUpperTextComponent A text component for the upper bound. + * @param lowerTextName The name of the lower bound, eg - start year. + * @param upperTextName The name of the upper bound, eg - end year. is + * used. + */ + public TextInputRangeChecker(JTextComponent aLowerTextComponent, JTextComponent anUpperTextComponent, + String lowerTextName, String upperTextName) { + this(aLowerTextComponent, anUpperTextComponent, lowerTextName, upperTextName, 0.0); + } + + /** + * Constructor with some of the settable parameters. + * + * @param aLowerTextComponent A text component for the lower bound. + * @param anUpperTextComponent A text component for the upper bound. + * @param aMaxRange The range the bounds muist fall between, if 0 + * then no range is used. + */ + public TextInputRangeChecker(JTextComponent aLowerTextComponent, JTextComponent anUpperTextComponent, + double aMaxRange) { + this(aLowerTextComponent, anUpperTextComponent, null, null, aMaxRange); + } + + /** + * Constructor with all the settable parameters. + * + * @param aLowerTextComponent A text component for the lower bound. + * @param anUpperTextComponent A text component for the upper bound. + * @param lowerTextName The name of the lower bound, eg - start year. + * @param upperTextName The name of the upper bound, eg - end year. + * @param aMaxRange The range the bounds muist fall between, if 0 + * then no range is used. + */ + public TextInputRangeChecker(JTextComponent aLowerTextComponent, JTextComponent anUpperTextComponent, + String lowerTextName, String upperTextName, double aMaxRange) { + lowerComponent = aLowerTextComponent; + upperComponent = anUpperTextComponent; + maxRange = aMaxRange; + + focusListeners = new ArrayList(1); // For most cases, there will be only 1 listener. + + lowerComponent.addFocusListener(this); + upperComponent.addFocusListener(this); + + lowerNumber = getNumber(lowerComponent); + upperNumber = getNumber(upperComponent); + + if ((lowerTextName != null) && (upperTextName != null)) { + invalidLowerMessage = "The " + lowerTextName + " must be less than or equal to the " + upperTextName + "."; + invalidUpperMessage = "The " + upperTextName + " must be greater than or equal to the " + lowerTextName + + "."; + invalidEitherMessage = "The " + lowerTextName + " and/or the " + upperTextName + " are not correct."; + invalidRangeMessage = "The maximum range for the " + lowerTextName + " and " + upperTextName + " is " + + maxRange + "."; + } else { + invalidLowerMessage = "The lower bound must be less than or equal to the upper bound."; + invalidUpperMessage = "The upper bound must be greater than or equal to the lower bound."; + invalidEitherMessage = "The upper and/or lower bounds are not correct."; + invalidRangeMessage = "The maximum range is " + maxRange + "."; + } + } + + /** + * Allows the caller to perform the validation of the bounds programatically. + * The lower bound is compared to the upper bound and range checking is + * performed. If the lower bound is greater than the upper bound, or the range + * between the bounds is greater than the max range, then validation fails. + * + * @return TRUE is validation is successfull, FALSE if it fails. + */ + public boolean performCheck() { + return validate(null); + } + + /** + * Adds the listener to the lists of focus listener maintened by this object. + * When one of the 2 text components receives a focus event, this object will + * fire that focus event to any of its listeners. This is useful when the + * calling object wants to be notified of the components focus events, but wants + * to ensure that the validation has occured first. <br> + * <br> + * NOTE: The focus is only fired if the validation was successful. This might + * have to be changed. + * + * @param aListener A Focus Listener to receive Focus Events. + */ + public void addFocusListener(FocusListener aListener) { + focusListeners.add(aListener); + } + + /** + * Returns the last valid value of the lower bound. If this is called while the + * user is updating the text component but before the focus is lost, the value + * returned will be the original value before the user started updating the + * bound. + * + * @return The last valid value of the lower bound. + */ + public double getLastValidatedLowerNumber() { + return lowerNumber; + } + + /** + * Returns the last valid value of the upper bound. If this is called while the + * user is updating the text component but before the focus is lost, the value + * returned will be the original value before the user started updating the + * bound. + * + * @return The last valid value of the upper bound. + */ + public double getLastValidatedUpperNumber() { + return upperNumber; + } + + /** + * Method used to be notified when one of the text components has gained its + * focus. + */ + public void focusGained(FocusEvent e) { + lowerNumber = getNumber(lowerComponent); + upperNumber = getNumber(upperComponent); + } + + /** + * Method used to be notified when one of the text components has lost its + * focus. Automatic validation occurs here. + */ + public void focusLost(FocusEvent e) { + if (e.isTemporary()) { + return; + } + + if (validate(e.getSource())) { + fireFocusEvent(e); + } + } + + /** + * Fires a focus lost event if the validation was successfull. + */ + protected void fireFocusEvent(FocusEvent e) { + for (Iterator it = focusListeners.iterator(); it.hasNext();) { + ((FocusListener) it.next()).focusLost(e); + } + } + + /** + * Validates the bounds inputed by the user. + * + * @param aComponent The component to use to display a dialog window, if + * neccessray. If null, then the parent window of the text + * componets will be used. + * @return TRUE if validation was successful, FALSE otherwise. + */ + protected boolean validate(Object aComponent) { + int componentUsed = NONE; + if (aComponent == lowerComponent) { + componentUsed = LOWER; + } else if (aComponent == upperComponent) { + componentUsed = UPPER; + } + + double lower = getNumber(lowerComponent); + double upper = getNumber(upperComponent); + + if (lower > upper) { + if (componentUsed == LOWER) { + lowerComponent.setText(Double.toString(lowerNumber)); + displayMessage(invalidLowerMessage, lowerComponent); + } else if (componentUsed == UPPER) { + upperComponent.setText(Double.toString(upperNumber)); + displayMessage(invalidUpperMessage, upperComponent); + } else { + upperComponent.setText(Double.toString(upperNumber)); + lowerComponent.setText(Double.toString(lowerNumber)); + displayMessage(invalidEitherMessage, lowerComponent.getTopLevelAncestor()); + } + + return false; + } + + if (maxRange != 0.0) { + if ((upper - lower) > maxRange) { + if (componentUsed == LOWER) { + lowerComponent.setText(Double.toString(lowerNumber)); + displayMessage(invalidRangeMessage, lowerComponent); + } else if (componentUsed == UPPER) { + upperComponent.setText(Double.toString(upperNumber)); + displayMessage(invalidRangeMessage, upperComponent); + } else { + upperComponent.setText(Double.toString(upperNumber)); + lowerComponent.setText(Double.toString(lowerNumber)); + displayMessage(invalidRangeMessage, lowerComponent.getTopLevelAncestor()); + } + + return false; + } + } + + lowerNumber = lower; + upperNumber = upper; + return true; + } + + /** + * Creates a JOptionPane to display the reason why the bounds failed validation. + */ + protected void displayMessage(final String message, final Component parent) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + JOptionPane.showMessageDialog(parent, message, "Data Entry Error", JOptionPane.ERROR_MESSAGE); + } + }); + } + + /** + * Gets the number represented in the text component. If the text does not + * represent a number, then zero is returned. + */ + protected double getNumber(JTextComponent aComponent) { + try { + return Double.valueOf(aComponent.getText()).doubleValue(); //1.2 return Double.parseDouble( aComponent.getText() ); - } - catch ( NumberFormatException e ) - { - System.out.println("[GUI] TextInputRangeChecker.getNumber(): The text is NOT a number: " + aComponent.getText() ); - return 0.0; - } - } + } catch (NumberFormatException e) { + System.out.println( + "[GUI] TextInputRangeChecker.getNumber(): The text is NOT a number: " + aComponent.getText()); + return 0.0; + } + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.2 2003/08/06 23:07:53 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.2 2003/08/06 23:07:53 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.1.1.1 2000/12/21 15:51:49 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:51:49 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:46 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:46 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/WindowGrabber.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/WindowGrabber.java index c360105..4ac8374 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/WindowGrabber.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/WindowGrabber.java @@ -31,173 +31,140 @@ import javax.swing.JFrame; import javax.swing.JWindow; /** - * WindowGrabber is a collection of static utility methods - * for taking screen shots of lightweight containers. All - * components that are not native peers will be drawn to a - * bitmap that will be run-length compressed (LZW encoding, GIF format) - * and written to the specified file or output stream. <br><br> + * WindowGrabber is a collection of static utility methods for taking screen + * shots of lightweight containers. All components that are not native peers + * will be drawn to a bitmap that will be run-length compressed (LZW encoding, + * GIF format) and written to the specified file or output stream. <br> + * <br> * - * Any Swing/JFC or IFC window is a good candidate for use with these - * methods. The component is not expected to contain more than 256 colors - * (the maximum that GIF allows). While there is a color depth limitation, - * the compression is lossless, with no blurs or bleeding color. + * Any Swing/JFC or IFC window is a good candidate for use with these methods. + * The component is not expected to contain more than 256 colors (the maximum + * that GIF allows). While there is a color depth limitation, the compression is + * lossless, with no blurs or bleeding color. * * @author michael@mpowers.net - * @version $Revision: 904 $ - * $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ */ -public class WindowGrabber -{ -/** - * Captures the screen contents of the specified component, - * and write it to a file with the specified name. - * - * @param aComponent a lightweight component or container. - * @param aFileName the name of the file to write, optionally preceded by path. - * @return True if the file was successfully written, false if there was an error. - * Errors are written to the standard error stream. - */ - static public boolean grab( Component aComponent, String aFileName ) - { +public class WindowGrabber { + /** + * Captures the screen contents of the specified component, and write it to a + * file with the specified name. + * + * @param aComponent a lightweight component or container. + * @param aFileName the name of the file to write, optionally preceded by path. + * @return True if the file was successfully written, false if there was an + * error. Errors are written to the standard error stream. + */ + static public boolean grab(Component aComponent, String aFileName) { OutputStream output = null; - try - { - output = new FileOutputStream( aFileName ); - } - catch ( Exception exc ) - { - System.err.println( exc ); + try { + output = new FileOutputStream(aFileName); + } catch (Exception exc) { + System.err.println(exc); return false; } - return grab( aComponent, output ); + return grab(aComponent, output); } -/** - * Captures the screen contents of the specified component, - * and write it to a file with the specified name. - * - * @param aComponent a lightweight component or container. - * @param anOutputStream an output stream to which the image will be written. - * @return True if the image was successfully written, false if there was an error. - * Errors are written to the standard error stream. - */ - static public boolean grab( Component aComponent, OutputStream anOutputStream ) - { - Image img = aComponent.createImage( - aComponent.getSize().width, aComponent.getSize().height ); - aComponent.paintAll( img.getGraphics() ); + /** + * Captures the screen contents of the specified component, and write it to a + * file with the specified name. + * + * @param aComponent a lightweight component or container. + * @param anOutputStream an output stream to which the image will be written. + * @return True if the image was successfully written, false if there was an + * error. Errors are written to the standard error stream. + */ + static public boolean grab(Component aComponent, OutputStream anOutputStream) { + Image img = aComponent.createImage(aComponent.getSize().width, aComponent.getSize().height); + aComponent.paintAll(img.getGraphics()); try { - GIFEncoder encoder = new GIFEncoder( img ); - encoder.write( anOutputStream ); + GIFEncoder encoder = new GIFEncoder(img); + encoder.write(anOutputStream); anOutputStream.flush(); - } catch ( Exception exc ) { - System.err.println( exc ); + } catch (Exception exc) { + System.err.println(exc); return false; } return true; } - protected static void processFilenames( String path, String[] filenames ) - { + protected static void processFilenames(String path, String[] filenames) { ClassLoader loader = new ClassGrabber(); File f; - for ( int i = 0; i < filenames.length; i++ ) - { - try - { - f = new File( path + filenames[i] ); - if ( f.isDirectory() ) - { - processFilenames ( - path + filenames[i] + "/", - f.list( new FilenameFilter() - { - public boolean accept(File dir, String name) - { - return name.endsWith( ".class" ); + for (int i = 0; i < filenames.length; i++) { + try { + f = new File(path + filenames[i]); + if (f.isDirectory()) { + processFilenames(path + filenames[i] + "/", f.list(new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.endsWith(".class"); + } + })); + } else { + System.out.println("Loading " + filenames[i] + ": "); + Class c = loader.loadClass(path + filenames[i]); + + System.out.println(c); + + if (JWindow.class.isAssignableFrom(c) || JDialog.class.isAssignableFrom(c) + || JFrame.class.isAssignableFrom(c)) { + try { + Window w = (Window) c.newInstance(); + if (w.getBounds().width * w.getBounds().height == 0) { // if size not specified or set, pack + // it + w.pack(); } - }) - ); - } - else - { - System.out.println( "Loading " + filenames[i] + ": " ); - Class c = loader.loadClass( path + filenames[i] ); - - System.out.println( c ); - - if ( JWindow.class.isAssignableFrom( c ) || - JDialog.class.isAssignableFrom( c ) || - JFrame.class.isAssignableFrom( c ) ) - { - try - { - Window w = (Window) c.newInstance(); - if ( w.getBounds().width * w.getBounds().height == 0 ) - { // if size not specified or set, pack it - w.pack(); - } - String gifName = // replace .class with .gif - filenames[i].substring( - 0, filenames[i].length() - 5 ) + "gif"; - w.addNotify(); - w.repaint(); - grab( w, gifName ); - System.out.println( "wrote: " + gifName ); - } - catch ( Exception exc ) - { - System.err.println( "WindowGrab failed for " + filenames[i] + ": " ); - exc.printStackTrace(); - } - - } - else - { - System.out.println( "not a JWindow, JDialog, or JFrame; ignored." ); - } + String gifName = // replace .class with .gif + filenames[i].substring(0, filenames[i].length() - 5) + "gif"; + w.addNotify(); + w.repaint(); + grab(w, gifName); + System.out.println("wrote: " + gifName); + } catch (Exception exc) { + System.err.println("WindowGrab failed for " + filenames[i] + ": "); + exc.printStackTrace(); + } + + } else { + System.out.println("not a JWindow, JDialog, or JFrame; ignored."); + } } - } - catch ( Exception exc ) - { - System.err.println( filenames[i] + ": " + exc ); + } catch (Exception exc) { + System.err.println(filenames[i] + ": " + exc); } } } -/** - * Captures images of any Swing window component classes specified - * as parameters. If created, image file will have the same name - * as the corresponding class file, but with a ".gif" extension. - * - * @param argv a list of filenames or directory names. - */ - public static void main( String[] argv ) - { - processFilenames( "", argv ); - System.exit( 0 ); + /** + * Captures images of any Swing window component classes specified as + * parameters. If created, image file will have the same name as the + * corresponding class file, but with a ".gif" extension. + * + * @param argv a list of filenames or directory names. + */ + public static void main(String[] argv) { + processFilenames("", argv); + System.exit(0); } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1.1.1 2000/12/21 15:51:49 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:51:49 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:46 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:46 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/WindowUtilities.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/WindowUtilities.java index a36ba12..e9d3329 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/WindowUtilities.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/WindowUtilities.java @@ -34,488 +34,440 @@ import java.util.List; import java.util.Map; /** -* A collection of window-related utilities. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -* $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ -* -*/ -public class WindowUtilities -{ - -/** -* Place frame at center (vertically and horizontally) of screen. -*/ - public static final int CENTER = 0; - -/** -* Center dialog on frame area of parent, if any. -*/ - public static final int CENTER_PARENT = 1; - -/** -* Place lower and to the right of the last window -* placed in this manner. Will wrap to top and left -* of screen as necessary. -*/ - public static final int CASCADE = 10; - - // cascade state - private static int lastX = 0; - private static int lastY = 0; - private static int incrementX = 20; - private static int incrementY = 20; - -/** -* Place the window in the center of the screen. -* Note: don't forget to first set the size of your window. -* This is a convenience method and simply calls place() with -* the CENTER parameter. -* @param aWindow The window to be centered. -* @see #place -*/ - public static void center( Window aWindow ) - { - place( aWindow, CENTER ); - } - -/** -* Place lower and to the right of the last window -* placed in this manner. Will wrap to top and left -* of screen as necessary. -* This is a convenience method and simply calls place() with -* the CASCADE parameter. -* @param aWindow The window to be cascaded. -* @see #place -*/ - public static void cascade( Window aWindow ) - { - place( aWindow, CASCADE ); - } - -/** -* Place the window in the specified location. -* Note: don't forget to first set the size of your window. -* @param aWindow The window to be placed. -* @param location Where on screen to place the frame. -*/ - public static void place( Window aWindow, int location) - { - Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); - Dimension mySize = aWindow.getSize(); - int x = (aWindow.getLocation()).x; - int y = (aWindow.getLocation()).y; - float aspectRatio = (float)screenSize.height/(float)screenSize.width; - - // hack to make windows appear on left monitor if dual monitor - // if aspect ratio is less than 0.6, assume dual monitor - if (aspectRatio < 0.6) - { - screenSize.width = screenSize.width/2; - } - - switch (location) - { - case CENTER_PARENT: - if ( ( ! ( aWindow instanceof Dialog ) ) - || ( ((Dialog)aWindow).getParent() == null ) ) + * A collection of window-related utilities. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ + * + */ +public class WindowUtilities { + + /** + * Place frame at center (vertically and horizontally) of screen. + */ + public static final int CENTER = 0; + + /** + * Center dialog on frame area of parent, if any. + */ + public static final int CENTER_PARENT = 1; + + /** + * Place lower and to the right of the last window placed in this manner. Will + * wrap to top and left of screen as necessary. + */ + public static final int CASCADE = 10; + + // cascade state + private static int lastX = 0; + private static int lastY = 0; + private static int incrementX = 20; + private static int incrementY = 20; + + /** + * Place the window in the center of the screen. Note: don't forget to first set + * the size of your window. This is a convenience method and simply calls + * place() with the CENTER parameter. + * + * @param aWindow The window to be centered. + * @see #place + */ + public static void center(Window aWindow) { + place(aWindow, CENTER); + } + + /** + * Place lower and to the right of the last window placed in this manner. Will + * wrap to top and left of screen as necessary. This is a convenience method and + * simply calls place() with the CASCADE parameter. + * + * @param aWindow The window to be cascaded. + * @see #place + */ + public static void cascade(Window aWindow) { + place(aWindow, CASCADE); + } + + /** + * Place the window in the specified location. Note: don't forget to first set + * the size of your window. + * + * @param aWindow The window to be placed. + * @param location Where on screen to place the frame. + */ + public static void place(Window aWindow, int location) { + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + Dimension mySize = aWindow.getSize(); + int x = (aWindow.getLocation()).x; + int y = (aWindow.getLocation()).y; + float aspectRatio = (float) screenSize.height / (float) screenSize.width; + + // hack to make windows appear on left monitor if dual monitor + // if aspect ratio is less than 0.6, assume dual monitor + if (aspectRatio < 0.6) { + screenSize.width = screenSize.width / 2; + } + + switch (location) { + case CENTER_PARENT: + if ((!(aWindow instanceof Dialog)) || (((Dialog) aWindow).getParent() == null)) //1.2 || ( ((Dialog)aWindow).getOwner() == null ) ) - { - place( aWindow, CENTER ); - return; - } - Point parentLocation = (((Dialog)aWindow).getParent()).getLocation(); + { + place(aWindow, CENTER); + return; + } + Point parentLocation = (((Dialog) aWindow).getParent()).getLocation(); //1.2 (((Dialog)aWindow).getOwner()).getLocation(); - Dimension parentSize = (((Dialog)aWindow).getParent()).getSize(); + Dimension parentSize = (((Dialog) aWindow).getParent()).getSize(); //1.2 Dimension parentSize = (((Dialog)aWindow).getOwner()).getSize(); - if (parentSize.width > mySize.width) - { - x = ((parentSize.width - mySize.width)/2) + parentLocation.x; - } - if (parentSize.height > mySize.height) - { - y = ((parentSize.height - mySize.height)/2) + parentLocation.y; - } - break; - case CENTER: - if (screenSize.width > mySize.width) - { - x = (screenSize.width - mySize.width)/2; - } - if (screenSize.height > mySize.height) - { - y = (screenSize.height - mySize.height)/2; - } - break; - case CASCADE: - x = lastX + incrementX; - if ( x + mySize.width > screenSize.width ) - { - x = incrementX; - } - y = lastY + incrementY; - if ( y + mySize.height > screenSize.height ) - { - y = incrementY; - } - lastX = x; - lastY = y; - break; - default: - // don't move the frame - Point p = aWindow.getLocation(); - x = p.x; - y = p.y; - break; - } - aWindow.setLocation(x,y); - } - -/** - * Returns the first parent Window of the specified component. - * - * @param c the Component whose parent will be found. - * @return the Window that contains the component, - * or null if the component does not have a valid Frame parent. - */ - public static Window getWindowForComponent(Component c) - { - for(Component p = c; p != null; p = p.getParent()) { - if (p instanceof Window) { - return (Window) p; - } - } - return null; - } - - - -/** -* Prints out a list of all components to System.out. -* @param aContainer the Container whose components will be listed. -*/ - public static void dumpComponents( Container aContainer ) - { - dumpComponents( aContainer, "" ); - } - - protected static void dumpComponents( Container aContainer, String padding ) - { - Component c = null; - int count = aContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = aContainer.getComponent( i ); - if ( c instanceof javax.swing.JComponent ) - { - System.out.println( padding + c.getClass() + ": " - + ((javax.swing.JComponent)c).getAccessibleContext().getAccessibleName() ); - } - else - { - System.out.println( padding + c.getClass() + ": " + c.getName() ); - } - if ( c instanceof Container ) - { - dumpComponents( (Container) c, padding + " " ); - } - } - } - -/** -* Gets a list of all children of a specified container, sorted by position. -* Components are sorted from top to bottom and then left to right. -* @param aContainer The container whose children are to be returned. -* @result A List containing the sorted components. -*/ - public static List getSortedChildComponents( Container aContainer ) - { - List result = new ArrayList( getAllChildComponents( aContainer ) ); - Collections.sort( result, new PositionComparator( aContainer ) ); - return result; - } - - public static void dumpSortedChildComponents( Container aContainer ) - { - Component c = null; - Iterator it = getSortedChildComponents( aContainer ).iterator(); - while ( it.hasNext() ) - { - c = (Component) it.next(); - System.out.println( c.getLocation() + " : " + c.getClass() ); - } - } - - public static void dumpNamedChildComponents( Container aContainer ) - { - Iterator it = getUniqueNameMap( getSortedChildComponents( aContainer ) ).values().iterator(); - while ( it.hasNext() ) - { - System.out.println( it.next() ); - } - } - -/** -* Generates a unique name for each object in a list and returns -* a map that maps the objects to the names. The name is based -* on the class of the object in the object list. -* @param anObjectList A List of objects. -* @return A Map that maps the objects in the list to the generated names. -*/ - public static Map getUniqueNameMap( List anObjectList ) - { - Map namesToObjects = new HashMap(anObjectList.size(), 1F); - Map objectsToNames = new HashMap(anObjectList.size(), 1F); - - Object o = null; - String name = null; - int lastIndex = 0; - Iterator it = anObjectList.iterator(); - while ( it.hasNext() ) - { - o = it.next(); - name = o.getClass().getName(); - lastIndex = name.lastIndexOf( "." ); - if ( lastIndex != -1 ) - { - name = name.substring( lastIndex+1 ); - } - name = incrementString( name ); - while ( namesToObjects.get( name ) != null ) - { - name = incrementString( name ); - } - namesToObjects.put( name, o ); - objectsToNames.put( o, name ); - } - - return objectsToNames; - } - -/** -* Numerically increments a string. For example, "hello" becomes "hello1" -* and "hello1" becomes "hello2" while "hello999" becomes "hello1000". -* @param aString a String to be incremented. -* @return The incremented String. -*/ - public static String incrementString( String aString ) - { - int i = aString.length()-1; - while ( ( i >= 0 ) && ( Character.isDigit( aString.charAt( i ) ) ) ) - { - i--; - } - - if ( i == aString.length()-1 ) - { // no numerics at end of string, increment manually - return aString + "1"; - } - - String alpha = aString.substring( 0, i+1 ); - String numeric = aString.substring( i+1 ); - numeric = Integer.toString( Integer.parseInt( numeric ) + 1 ); - - return alpha + numeric; - - } - -/** -* Gets all children of the specified container. -* @param aContainer the Container to be searched. -* @return A Collection containing all of the child components of the container. -*/ - public static Collection getAllChildComponents( Container aContainer ) - { - Collection result = new ArrayList(); - addAllChildComponents( aContainer, result ); - return result; - } - -/** -* Adds all children of the specified container to the specified collection. -* @param aContainer the Container to be searched. -* @param aCollection the Collection to which the child components will be added. -*/ - protected static void addAllChildComponents( Container aContainer, Collection aCollection ) - { - Component c = null; - int count = aContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = aContainer.getComponent( i ); - aCollection.add( c ); - if ( c instanceof Container ) - { - addAllChildComponents( (Container) c, aCollection ); - } - } - } - -/** -* Sets each child component's tooltip to show the name generated from -* getComponentNameMap(). -* (We're doing this so the tooltip authors can know how to reference -* the components.) -* @param aContainer the Container whose components will be labeled. -*/ - public static void labelComponents( Container aContainer ) - { - Map nameToComponent = getNameToComponentMap( aContainer ); - Map nameToName = new HashMap(nameToComponent.size(), 1F); - Iterator it = nameToComponent.keySet().iterator(); - String key; - while ( it.hasNext() ) - { - key = it.next().toString(); - nameToName.put( key, key ); - } - labelComponents( aContainer, nameToName ); - } - -/** -* Sets each child component's tooltip to show a given string retrieved -* from a map using the component's generated name as a key. -* @param aContainer the Container whose components will be labeled. -* @param aNameMap a Map of generated names to string values. -*/ - public static void labelComponents( Container aContainer, Map aNameMap ) - { - if ( aNameMap == null ) return; - - String key; - Object o ; - Iterator it = aNameMap.keySet().iterator(); - Map nameToComponent = getNameToComponentMap( aContainer ); - while ( it.hasNext() ) - { - key = it.next().toString(); - o = nameToComponent.get( key ); - if ( o instanceof javax.swing.JComponent ) - { - ((javax.swing.JComponent)o).setToolTipText( aNameMap.get( key ).toString() ); - } - } - } - -/** -* Generates a deterministically unique name for each component in a -* container. The name is based on the name of the class of the component -* followed by a number. Each class of component is numbered based on it's -* position in the container, sorted from top to bottom and left to right. -* @param aContainer the Container whose components will named. -* @return a Map that maps each component to its name. -*/ - public static Map getComponentToNameMap( Container aContainer ) - { - return getUniqueNameMap( getSortedChildComponents( aContainer ) ); - } - -/** -* Maps a deterministically unique name to a component in a -* container. The name is based on the name of the class of the component -* followed by a number. Each class of component is numbered based on it's -* position in the container, sorted from top to bottom and left to right. -* @param aContainer the Container whose components will named. -* @return a Map that maps each component to its name. -*/ - public static Map getNameToComponentMap( Container aContainer ) - { - Map componentToName = getComponentToNameMap( aContainer ); - Map result = new HashMap(componentToName.size(), 1F); - Iterator it = componentToName.keySet().iterator(); - Object key; - while ( it.hasNext() ) - { - key = it.next(); - result.put( componentToName.get( key ), key ); - } - return result; - } - -/** -* Sets the tooltips of all components in a container to the -* respective names of those components. (We're using this -* so the tooltip authors can know how to reference the components.) -* @param aContainer the Container whose components will be labeled. -*/ - public static void nameComponents( Container aContainer ) - { - nameComponents( aContainer, "" ); - } - - protected static void nameComponents( Container aContainer, String path ) - { - Component c = null; - String className = null; - int index = 0; - int count = aContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = aContainer.getComponent( i ); - className = c.getClass().getName(); - className = className.substring( className.lastIndexOf( '.' ) + 1 ); - System.out.println( path + className ); - if ( c instanceof javax.swing.JComponent ) - { - // ((javax.swing.JComponent)c).setToolTipText( path + className + " (" + c.getName() + ")" ); - ((javax.swing.JComponent)c).setToolTipText( c.getName() ); - } - if ( c instanceof Container ) - { - nameComponents( (Container) c, path + className + "." ); - } - } - } - -/** -* Sets the enabled state of a container and all of its components. -* @param aContainer the Container whose components will be enabled. -* @param isEnabled True if enabled, false id disabled. -*/ - public static void enableComponents( Container aContainer, boolean isEnabled ) - { - Component c = null; - String className = null; - int index = 0; - int count = aContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = aContainer.getComponent( i ); - if ( c instanceof Container ) - { - enableComponents( (Container) c, isEnabled ); - } - else - { - c.setEnabled( isEnabled ); - } - } - aContainer.setEnabled( isEnabled ); - } + if (parentSize.width > mySize.width) { + x = ((parentSize.width - mySize.width) / 2) + parentLocation.x; + } + if (parentSize.height > mySize.height) { + y = ((parentSize.height - mySize.height) / 2) + parentLocation.y; + } + break; + case CENTER: + if (screenSize.width > mySize.width) { + x = (screenSize.width - mySize.width) / 2; + } + if (screenSize.height > mySize.height) { + y = (screenSize.height - mySize.height) / 2; + } + break; + case CASCADE: + x = lastX + incrementX; + if (x + mySize.width > screenSize.width) { + x = incrementX; + } + y = lastY + incrementY; + if (y + mySize.height > screenSize.height) { + y = incrementY; + } + lastX = x; + lastY = y; + break; + default: + // don't move the frame + Point p = aWindow.getLocation(); + x = p.x; + y = p.y; + break; + } + aWindow.setLocation(x, y); + } + + /** + * Returns the first parent Window of the specified component. + * + * @param c the Component whose parent will be found. + * @return the Window that contains the component, or null if the component does + * not have a valid Frame parent. + */ + public static Window getWindowForComponent(Component c) { + for (Component p = c; p != null; p = p.getParent()) { + if (p instanceof Window) { + return (Window) p; + } + } + return null; + } + + /** + * Prints out a list of all components to System.out. + * + * @param aContainer the Container whose components will be listed. + */ + public static void dumpComponents(Container aContainer) { + dumpComponents(aContainer, ""); + } + + protected static void dumpComponents(Container aContainer, String padding) { + Component c = null; + int count = aContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = aContainer.getComponent(i); + if (c instanceof javax.swing.JComponent) { + System.out.println(padding + c.getClass() + ": " + + ((javax.swing.JComponent) c).getAccessibleContext().getAccessibleName()); + } else { + System.out.println(padding + c.getClass() + ": " + c.getName()); + } + if (c instanceof Container) { + dumpComponents((Container) c, padding + " "); + } + } + } + + /** + * Gets a list of all children of a specified container, sorted by position. + * Components are sorted from top to bottom and then left to right. + * + * @param aContainer The container whose children are to be returned. + * @result A List containing the sorted components. + */ + public static List getSortedChildComponents(Container aContainer) { + List result = new ArrayList(getAllChildComponents(aContainer)); + Collections.sort(result, new PositionComparator(aContainer)); + return result; + } + + public static void dumpSortedChildComponents(Container aContainer) { + Component c = null; + Iterator it = getSortedChildComponents(aContainer).iterator(); + while (it.hasNext()) { + c = (Component) it.next(); + System.out.println(c.getLocation() + " : " + c.getClass()); + } + } + + public static void dumpNamedChildComponents(Container aContainer) { + Iterator it = getUniqueNameMap(getSortedChildComponents(aContainer)).values().iterator(); + while (it.hasNext()) { + System.out.println(it.next()); + } + } + + /** + * Generates a unique name for each object in a list and returns a map that maps + * the objects to the names. The name is based on the class of the object in the + * object list. + * + * @param anObjectList A List of objects. + * @return A Map that maps the objects in the list to the generated names. + */ + public static Map getUniqueNameMap(List anObjectList) { + Map namesToObjects = new HashMap(anObjectList.size(), 1F); + Map objectsToNames = new HashMap(anObjectList.size(), 1F); + + Object o = null; + String name = null; + int lastIndex = 0; + Iterator it = anObjectList.iterator(); + while (it.hasNext()) { + o = it.next(); + name = o.getClass().getName(); + lastIndex = name.lastIndexOf("."); + if (lastIndex != -1) { + name = name.substring(lastIndex + 1); + } + name = incrementString(name); + while (namesToObjects.get(name) != null) { + name = incrementString(name); + } + namesToObjects.put(name, o); + objectsToNames.put(o, name); + } + + return objectsToNames; + } + + /** + * Numerically increments a string. For example, "hello" becomes "hello1" and + * "hello1" becomes "hello2" while "hello999" becomes "hello1000". + * + * @param aString a String to be incremented. + * @return The incremented String. + */ + public static String incrementString(String aString) { + int i = aString.length() - 1; + while ((i >= 0) && (Character.isDigit(aString.charAt(i)))) { + i--; + } + + if (i == aString.length() - 1) { // no numerics at end of string, increment manually + return aString + "1"; + } + + String alpha = aString.substring(0, i + 1); + String numeric = aString.substring(i + 1); + numeric = Integer.toString(Integer.parseInt(numeric) + 1); + + return alpha + numeric; + + } + + /** + * Gets all children of the specified container. + * + * @param aContainer the Container to be searched. + * @return A Collection containing all of the child components of the container. + */ + public static Collection getAllChildComponents(Container aContainer) { + Collection result = new ArrayList(); + addAllChildComponents(aContainer, result); + return result; + } + + /** + * Adds all children of the specified container to the specified collection. + * + * @param aContainer the Container to be searched. + * @param aCollection the Collection to which the child components will be + * added. + */ + protected static void addAllChildComponents(Container aContainer, Collection aCollection) { + Component c = null; + int count = aContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = aContainer.getComponent(i); + aCollection.add(c); + if (c instanceof Container) { + addAllChildComponents((Container) c, aCollection); + } + } + } + + /** + * Sets each child component's tooltip to show the name generated from + * getComponentNameMap(). (We're doing this so the tooltip authors can know how + * to reference the components.) + * + * @param aContainer the Container whose components will be labeled. + */ + public static void labelComponents(Container aContainer) { + Map nameToComponent = getNameToComponentMap(aContainer); + Map nameToName = new HashMap(nameToComponent.size(), 1F); + Iterator it = nameToComponent.keySet().iterator(); + String key; + while (it.hasNext()) { + key = it.next().toString(); + nameToName.put(key, key); + } + labelComponents(aContainer, nameToName); + } + + /** + * Sets each child component's tooltip to show a given string retrieved from a + * map using the component's generated name as a key. + * + * @param aContainer the Container whose components will be labeled. + * @param aNameMap a Map of generated names to string values. + */ + public static void labelComponents(Container aContainer, Map aNameMap) { + if (aNameMap == null) + return; + + String key; + Object o; + Iterator it = aNameMap.keySet().iterator(); + Map nameToComponent = getNameToComponentMap(aContainer); + while (it.hasNext()) { + key = it.next().toString(); + o = nameToComponent.get(key); + if (o instanceof javax.swing.JComponent) { + ((javax.swing.JComponent) o).setToolTipText(aNameMap.get(key).toString()); + } + } + } + + /** + * Generates a deterministically unique name for each component in a container. + * The name is based on the name of the class of the component followed by a + * number. Each class of component is numbered based on it's position in the + * container, sorted from top to bottom and left to right. + * + * @param aContainer the Container whose components will named. + * @return a Map that maps each component to its name. + */ + public static Map getComponentToNameMap(Container aContainer) { + return getUniqueNameMap(getSortedChildComponents(aContainer)); + } + + /** + * Maps a deterministically unique name to a component in a container. The name + * is based on the name of the class of the component followed by a number. Each + * class of component is numbered based on it's position in the container, + * sorted from top to bottom and left to right. + * + * @param aContainer the Container whose components will named. + * @return a Map that maps each component to its name. + */ + public static Map getNameToComponentMap(Container aContainer) { + Map componentToName = getComponentToNameMap(aContainer); + Map result = new HashMap(componentToName.size(), 1F); + Iterator it = componentToName.keySet().iterator(); + Object key; + while (it.hasNext()) { + key = it.next(); + result.put(componentToName.get(key), key); + } + return result; + } + + /** + * Sets the tooltips of all components in a container to the respective names of + * those components. (We're using this so the tooltip authors can know how to + * reference the components.) + * + * @param aContainer the Container whose components will be labeled. + */ + public static void nameComponents(Container aContainer) { + nameComponents(aContainer, ""); + } + + protected static void nameComponents(Container aContainer, String path) { + Component c = null; + String className = null; + int index = 0; + int count = aContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = aContainer.getComponent(i); + className = c.getClass().getName(); + className = className.substring(className.lastIndexOf('.') + 1); + System.out.println(path + className); + if (c instanceof javax.swing.JComponent) { + // ((javax.swing.JComponent)c).setToolTipText( path + className + " (" + + // c.getName() + ")" ); + ((javax.swing.JComponent) c).setToolTipText(c.getName()); + } + if (c instanceof Container) { + nameComponents((Container) c, path + className + "."); + } + } + } + + /** + * Sets the enabled state of a container and all of its components. + * + * @param aContainer the Container whose components will be enabled. + * @param isEnabled True if enabled, false id disabled. + */ + public static void enableComponents(Container aContainer, boolean isEnabled) { + Component c = null; + String className = null; + int index = 0; + int count = aContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = aContainer.getComponent(i); + if (c instanceof Container) { + enableComponents((Container) c, isEnabled); + } else { + c.setEnabled(isEnabled); + } + } + aContainer.setEnabled(isEnabled); + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.2 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.2 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.1.1.1 2000/12/21 15:51:55 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:51:55 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:46 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:46 michael Added log to all files. * * */ - |
