diff options
Diffstat (limited to 'projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TextAssociation.java')
| -rw-r--r-- | projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TextAssociation.java | 1980 |
1 files changed, 872 insertions, 1108 deletions
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. * * */ - |
