diff options
| author | Benjamin Culkin <scorpress@gmail.com> | 2024-05-19 17:56:33 -0400 |
|---|---|---|
| committer | Benjamin Culkin <scorpress@gmail.com> | 2024-05-19 17:56:33 -0400 |
| commit | aedc34d55462a75e329bbf342251ff6504cd117e (patch) | |
| tree | bcc8f1f2352582717b484df302aeea6696b8f000 /projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TextAssociation.java | |
Initial import from SVN
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 | 1212 |
1 files changed, 1212 insertions, 0 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 new file mode 100644 index 0000000..6aa27c3 --- /dev/null +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TextAssociation.java @@ -0,0 +1,1212 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2000 Intersect Software Corporation + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org +*/ + +package net.wotonomy.ui.swing; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.text.Format; +import java.text.ParseException; +import java.util.Enumeration; +import java.util.Iterator; + +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JOptionPane; +import javax.swing.JTextArea; +import javax.swing.LookAndFeel; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.AbstractDocument; +import javax.swing.text.DefaultStyledDocument; +import javax.swing.text.Document; +import javax.swing.text.JTextComponent; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSNotification; +import net.wotonomy.foundation.NSNotificationCenter; +import net.wotonomy.foundation.NSNotificationQueue; +import net.wotonomy.foundation.NSSelector; +import net.wotonomy.foundation.internal.ValueConverter; +import net.wotonomy.foundation.internal.WotonomyException; +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 ); + } + +} + +/* + * $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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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. + * + * + */ + |
