diff options
Diffstat (limited to 'projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TimedTextAssociation.java')
| -rw-r--r-- | projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TimedTextAssociation.java | 1029 |
1 files changed, 1029 insertions, 0 deletions
diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TimedTextAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TimedTextAssociation.java new file mode 100644 index 0000000..49879e9 --- /dev/null +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TimedTextAssociation.java @@ -0,0 +1,1029 @@ +/* +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.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.Timer; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.Document; +import javax.swing.text.JTextComponent; + +import net.wotonomy.foundation.NSArray; +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; + +/** +* TimedTextAssociation works like TextAssociation, +* but instead of using a delayed event to update the +* model, it uses a timer so that the model is only +* updated if the user pauses typing for some short interval. +* This is useful when the update and/or re-read of the model +* is a costly operation. +* Bindings are: +* <ul> +* <li>value: a property convertable to/from a string</li> +* <li>editable: a boolean property that determines whether +* the user can edit the text in the field</li> +* <li>enabled: a boolean property that determines whether +* the user can select the text in the field</li> +* <li>label: a boolean property that determines whether +* field should appear as a read-only, selectable label</li> +* <li>icon: a property that returns a Swing icon, for use +* with JLabels and other components with setIcon() methods. +* If bound to a static string, the string will be used to +* load an image resource from the selected object's class.</li> +* </ul> +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 904 $ +*/ +public class TimedTextAssociation extends EOAssociation + implements FocusListener, ActionListener, DocumentListener +{ + //TODO: need to refactor this so that it can subclass text association. + //This implementation is basically a branch from the v1.20 TextAssociation. + + static final NSArray aspects = + new NSArray( new Object[] { + ValueAspect, EnabledAspect, EditableAspect, LabelAspect, IconAspect + } ); + static final NSArray aspectSignatures = + new NSArray( new Object[] { + AttributeToOneAspectSignature, + AttributeToOneAspectSignature, + AttributeToOneAspectSignature, + AttributeToOneAspectSignature + } ); + static final NSArray objectKeysTaken = + new NSArray( new Object[] { + "text", "enabled", "editable" + } ); + + private final static NSSelector getText = + new NSSelector( "getText" ); + private final static NSSelector setText = + new NSSelector( "setText", + new Class[] { String.class } ); + private final static NSSelector getDocument = + new NSSelector( "getDocument" ); + private final static NSSelector setIcon = + new NSSelector( "setIcon", + new Class[] { Icon.class } ); + private final static NSSelector addActionListener = + new NSSelector( "addActionListener", + new Class[] { ActionListener.class } ); + private final static NSSelector removeActionListener = + new NSSelector( "removeActionListener", + new Class[] { ActionListener.class } ); + private final static NSSelector addFocusListener = + new NSSelector( "addFocusListener", + new Class[] { FocusListener.class } ); + private final static NSSelector removeFocusListener = + new NSSelector( "removeFocusListener", + new Class[] { FocusListener.class } ); + + // null handling + protected boolean wasNull; + protected static final String EMPTY_STRING = ""; + + // dirty handling + protected boolean needsUpdate; + protected boolean hasDocument; + protected boolean isListening; + + // formatting + protected Format format; + + // cache the value aspect + private EODisplayGroup valueDisplayGroup; + private String valueKey; + + // coalescing document events + protected boolean autoUpdating; + protected int interval = 400; // adjust as needed + protected Timer keyTimer; + + // NOTE: a new key timer is created for each use and + // is disposed when the timer is stopped. + // Swing's Timer class is kept in a static list of timers + // and each retains a strong reference to their listeners. + // This caused a memory leak as associations typically + // refer to their controlled component which is referred + // to by its parents and so on until no application window + // will ever get garbage collected. yikes. + + /** + * Constructor specifying the object to be controlled by this + * association. Does not establish connection. + */ + public TimedTextAssociation ( Object anObject ) + { + super( anObject ); + wasNull = false; + needsUpdate = false; + hasDocument = false; + isListening = true; + valueDisplayGroup = null; + valueKey = null; + + autoUpdating = true; + keyTimer = null; + } + + /** + * Returns a List of aspect signatures whose contents + * correspond with the aspects list. Each element is + * a string whose characters represent a capability of + * the corresponding aspect. <ul> + * <li>"A" attribute: the aspect can be bound to + * an attribute.</li> + * <li>"1" to-one: the aspect can be bound to a + * property that returns a single object.</li> + * <li>"M" to-one: the aspect can be bound to a + * property that returns multiple objects.</li> + * </ul> + * An empty signature "" means that the aspect can + * bind without needing a key. + * This implementation returns "A1M" for each + * element in the aspects array. + */ + public static NSArray aspectSignatures () + { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported + * by this class. Each element in the list is the string + * name of the aspect. This implementation returns an + * empty list. + */ + public static NSArray aspects () + { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, + * for the objects that are usable for this association, + * are less suitable than this association. + */ + public static NSArray associationClassesSuperseded () + { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified + * object. + */ + public static boolean isUsableWithObject ( Object anObject ) + { + return setText.implementedByObject( anObject ); + } + + /** + * Returns a List of properties of the controlled object + * that are controlled by this class. For example, + * "stringValue", or "selected". + */ + public static NSArray objectKeysTaken () + { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary + * or default. This is typically "value" or somesuch. + */ + public static String primaryAspect () + { + return ValueAspect; + } + + /** + * Returns whether this association can bind to the + * specified display group on the specified key for + * the specified aspect. + */ + public boolean canBindAspect ( + String anAspect, EODisplayGroup aDisplayGroup, String aKey) + { + return ( aspects.containsObject( anAspect ) ); + } + + /** + * Binds the specified aspect of this association to the + * specified key on the specified display group. + */ + public void bindAspect ( + String anAspect, EODisplayGroup aDisplayGroup, String aKey ) + { + if ( ValueAspect.equals( anAspect ) ) + { + 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(); + } + + /** + * Called when either the selection or the contents + * of an associated display group have changed. + */ + public void subjectChanged () + { + Object component = object(); + EODisplayGroup displayGroup; + String key; + Object value; + + // value aspect + displayGroup = valueDisplayGroup; + if ( displayGroup != null ) + { + if ( component instanceof Component ) + { + ((Component)component).setEnabled( + displayGroup.enabledToSetSelectedObjectValueForKey( valueKey ) ); + } + + key = valueKey; + + if ( displayGroup.selectedObjects().size() > 1 ) + { + // if there're more than one object selected, set + // the value to blank for all of them. + Object previousValue; + + Iterator indexIterator = displayGroup.selectionIndexes(). + iterator(); + + // get value for the first selected object. + int initialIndex = ( (Integer)indexIterator.next() ).intValue(); + previousValue = displayGroup.valueForObjectAtIndex( + initialIndex, key ); + value = null; + + // go through the rest of the selected objects, compare each + // value with the previous one. continue comparing if two + // values are equal, break the while loop if they're different. + // the final value will be the common value of all selected objects + // if there is one, or be blank if there is not. + while ( indexIterator.hasNext() ) + { + int index = ( (Integer)indexIterator.next() ).intValue(); + Object currentValue = displayGroup.valueForObjectAtIndex( + index, key ); + if ( currentValue != null && !currentValue.equals( previousValue ) ) + { + value = null; + break; + } + else + { + // currentValue is the same as the previous one + value = currentValue; + } + + } // end while + + } else { + + // if there's only one object selected. + value = displayGroup.selectedObjectValueForKey( key ); + } // end checking the size of selected objects in displayGroup + + // convert value to string + if ( value == null ) + { + wasNull = true; + value = EMPTY_STRING; + } + else + { + wasNull = false; + if ( format() != null ) + { + try + { + value = format().format( value ); + } + catch ( IllegalArgumentException exc ) + { + value = value.toString(); + } + } + } + + + try + { + if ( ! value.toString().equals( getText.invoke( component ) ) ) + { + // No need to listen for any events that might get fired + // while setting the text since we are the one setting it. + boolean wasListening = isListening; + isListening = false; + + // setText is an expensive operation + setText.invoke( component, value.toString() ); + + isListening = wasListening; + needsUpdate = false; + } + } + 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() ); + } + } + } + + // 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"); + + 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"); + + 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 () + { + if ( keyTimer != null ) + { + keyTimer.stop(); + keyTimer.removeActionListener( this ); + keyTimer = null; + } + return writeValueToDisplayGroup(); + } + + /** + * Writes the value currently in the component + * to the selected object in the display group + * bound to the value aspect. + * @return false if there were problems validating, + * or true to continue. + */ + protected boolean writeValueToDisplayGroup() + { + boolean returnValue = true; + if ( hasDocument && !needsUpdate ) return true; + + EODisplayGroup displayGroup = valueDisplayGroup; + if ( displayGroup != null ) + { + String key = valueKey; + Object component = object(); + Object value = null; + try + { + //if ( getText.implementedByObject( component ) ) + //{ + value = getText.invoke( component ); + //} + } + catch ( Exception exc ) + { + throw new WotonomyException( + "Error updating display group", exc ); + } + + if ( ( wasNull ) && ( EMPTY_STRING.equals( value ) ) ) + { + value = null; + } + else + if ( format() != null ) + { + try + { + value = format().parseObject( value.toString() ); + } + catch ( ParseException exc ) + { + String message = exc.getMessage(); + //"That format was not recognized."; + if ( displayGroup.associationFailedToValidateValue( + this, value.toString(), key, exc, message ) ) + { + boolean wasListening = isListening; + isListening = false; + JOptionPane.showMessageDialog( + (Component)component, message ); + isListening = wasListening; + } + needsUpdate = false; + return false; + } + } + + needsUpdate = false; + + // only update if the value is different from the one in the display group + Object existingValue = displayGroup.selectedObjectValueForKey( key ); + if ( displayGroup.selectedObjects().size() == 1 ) + { + if ( existingValue == value ) return true; + if ( ( existingValue != null ) && ( existingValue.equals( value ) ) ) return true; + if ( ( value != null ) && ( value.equals( existingValue ) ) ) return true; + } + + // we might lose focus if display group displays a validation message + boolean wasListening = isListening; + isListening = false; + + Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); + while ( selectedIterator.hasNext() ) + { + int index = ( (Integer)selectedIterator.next() ).intValue(); + + if ( displayGroup.setValueForObjectAtIndex( value, index, key ) ) + { + isListening = wasListening; + needsUpdate = false; + } + else + { + isListening = wasListening; + needsUpdate = false; + returnValue = false; + } + } + + } + return returnValue; + } + + /** + * Sets the Format that is used to convert values from the display + * group to and from text that is displayed in the component. + * Having a formatter disables auto-updating. + */ + public void setFormat( Format aFormat ) + { + format = aFormat; + } + + /** + * Gets the Format that is used to convert values from the display + * group to and from text that is displayed in the component. + */ + public Format format() + { + return format; + } + + // interface ActionListener + + /** + * Updates object on action performed. + */ + public void actionPerformed( ActionEvent evt ) + { + if ( keyTimer != null ) + { + keyTimer.stop(); + keyTimer.removeActionListener( this ); + keyTimer = null; + } + if ( ! isListening ) return; + if ( needsUpdate ) + { + writeValueToDisplayGroup(); + } + } + + // interface FocusListener + + /** + * Notifies of beginning of edit. + */ + public void focusGained(FocusEvent evt) + { + if ( ! isListening ) return; + Object o; + EODisplayGroup displayGroup; + Enumeration e = aspects().objectEnumerator(); + while ( e.hasMoreElements() ) + { + displayGroup = + displayGroupForAspect( e.nextElement().toString() ); + if ( displayGroup != null ) + { + displayGroup.associationDidBeginEditing( this ); + } + } + } + + /** + * 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, + } + } + + /** + * Returns whether the data model is updated for every change + * in the controlled component. If false, the data is only + * updated on focus lost or the enter key. Default is true. + */ + public boolean isAutoUpdating() + { + if ( format() != null ) return false; + return autoUpdating; + } + + /** + * Sets whether the data model is updated for every change + * in the controlled component. + */ + public void setAutoUpdating( boolean isAutoUpdating ) + { + autoUpdating = isAutoUpdating; + } + + /** + * Triggers the key timer to start. + */ + protected void queueUpdate() + { + if ( isAutoUpdating() ) + { + if ( keyTimer == null ) + { + keyTimer = new Timer( interval, this ); + } + keyTimer.restart(); + } + } + + // interface DocumentListener + + public void insertUpdate(DocumentEvent e) + { + if ( ! isListening ) return; + needsUpdate = true; + queueUpdate(); + } + + public void removeUpdate(DocumentEvent e) + { + if ( ! isListening ) return; + needsUpdate = true; + queueUpdate(); + } + + public void changedUpdate(DocumentEvent e) + { + if ( ! isListening ) return; + needsUpdate = true; + queueUpdate(); + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/18 23:19:05 cgruber + * Update imports and maven dependencies. + * + * Revision 1.1 2006/02/16 13:22:22 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.3 2004/01/28 18:34:57 mpowers + * Better handling for enabling. + * Now respecting enabledToSetSelectedObjectValueForKey from display group. + * + * Revision 1.2 2003/08/06 23:07:52 chochos + * general code cleanup (mostly, removing unused imports) + * + * Revision 1.1 2001/12/20 18:57:24 mpowers + * (Re-)Contributing TimedTextAssociation. Just like TA, except uses timers. + * + * Revision 1.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. + * + * + */ + |
