Coverage Report - net.wotonomy.ui.swing.TimedTextAssociation
 
Classes in this File Line Coverage Branch Coverage Complexity
TimedTextAssociation
0% 
0% 
4.103
 
 1  
 /*
 2  
 Wotonomy: OpenStep design patterns for pure Java applications.
 3  
 Copyright (C) 2000 Intersect Software Corporation
 4  
 
 5  
 This library is free software; you can redistribute it and/or
 6  
 modify it under the terms of the GNU Lesser General Public
 7  
 License as published by the Free Software Foundation; either
 8  
 version 2.1 of the License, or (at your option) any later version.
 9  
 
 10  
 This library is distributed in the hope that it will be useful,
 11  
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 Lesser General Public License for more details.
 14  
 
 15  
 You should have received a copy of the GNU Lesser General Public
 16  
 License along with this library; if not, see http://www.gnu.org
 17  
 */
 18  
 
 19  
 package net.wotonomy.ui.swing;
 20  
 
 21  
 import java.awt.Component;
 22  
 import java.awt.event.ActionEvent;
 23  
 import java.awt.event.ActionListener;
 24  
 import java.awt.event.FocusEvent;
 25  
 import java.awt.event.FocusListener;
 26  
 import java.net.URL;
 27  
 import java.text.Format;
 28  
 import java.text.ParseException;
 29  
 import java.util.Enumeration;
 30  
 import java.util.Iterator;
 31  
 
 32  
 import javax.swing.Icon;
 33  
 import javax.swing.ImageIcon;
 34  
 import javax.swing.JOptionPane;
 35  
 import javax.swing.JTextArea;
 36  
 import javax.swing.LookAndFeel;
 37  
 import javax.swing.Timer;
 38  
 import javax.swing.event.DocumentEvent;
 39  
 import javax.swing.event.DocumentListener;
 40  
 import javax.swing.text.Document;
 41  
 import javax.swing.text.JTextComponent;
 42  
 
 43  
 import net.wotonomy.foundation.NSArray;
 44  
 import net.wotonomy.foundation.NSSelector;
 45  
 import net.wotonomy.foundation.internal.ValueConverter;
 46  
 import net.wotonomy.foundation.internal.WotonomyException;
 47  
 import net.wotonomy.ui.EOAssociation;
 48  
 import net.wotonomy.ui.EODisplayGroup;
 49  
 
 50  
 /**
 51  
 * TimedTextAssociation works like TextAssociation,
 52  
 * but instead of using a delayed event to update the
 53  
 * model, it uses a timer so that the model is only
 54  
 * updated if the user pauses typing for some short interval.
 55  
 * This is useful when the update and/or re-read of the model
 56  
 * is a costly operation.
 57  
 * Bindings are:
 58  
 * <ul>
 59  
 * <li>value: a property convertable to/from a string</li>
 60  
 * <li>editable: a boolean property that determines whether
 61  
 * the user can edit the text in the field</li>
 62  
 * <li>enabled: a boolean property that determines whether
 63  
 * the user can select the text in the field</li>
 64  
 * <li>label: a boolean property that determines whether
 65  
 * field should appear as a read-only, selectable label</li>
 66  
 * <li>icon: a property that returns a Swing icon, for use
 67  
 * with JLabels and other components with setIcon() methods.
 68  
 * If bound to a static string, the string will be used to
 69  
 * load an image resource from the selected object's class.</li>
 70  
 * </ul>
 71  
 *
 72  
 * @author michael@mpowers.net
 73  
 * @author $Author: cgruber $
 74  
 * @version $Revision: 904 $
 75  
 */
 76  0
 public class TimedTextAssociation extends EOAssociation
 77  
         implements FocusListener, ActionListener, DocumentListener
 78  
 {
 79  
     //TODO: need to refactor this so that it can subclass text association.
 80  
     //This implementation is basically a branch from the v1.20 TextAssociation.
 81  
     
 82  0
     static final NSArray aspects =
 83  0
         new NSArray( new Object[] {
 84  0
             ValueAspect, EnabledAspect, EditableAspect, LabelAspect, IconAspect
 85  
         } );
 86  0
     static final NSArray aspectSignatures =
 87  0
         new NSArray( new Object[] {
 88  0
             AttributeToOneAspectSignature,
 89  0
                         AttributeToOneAspectSignature,
 90  0
                         AttributeToOneAspectSignature,
 91  0
                         AttributeToOneAspectSignature
 92  
         } );
 93  0
     static final NSArray objectKeysTaken =
 94  0
         new NSArray( new Object[] {
 95  0
             "text", "enabled", "editable"
 96  
         } );
 97  
 
 98  0
         private final static NSSelector getText =
 99  0
                 new NSSelector( "getText" );
 100  0
         private final static NSSelector setText =
 101  0
                 new NSSelector( "setText",
 102  0
                 new Class[] { String.class } );
 103  0
         private final static NSSelector getDocument =
 104  0
                 new NSSelector( "getDocument" );
 105  0
         private final static NSSelector setIcon =
 106  0
                 new NSSelector( "setIcon",
 107  0
                 new Class[] { Icon.class } );
 108  0
         private final static NSSelector addActionListener =
 109  0
                 new NSSelector( "addActionListener",
 110  0
                 new Class[] { ActionListener.class } );
 111  0
         private final static NSSelector removeActionListener =
 112  0
                 new NSSelector( "removeActionListener",
 113  0
                 new Class[] { ActionListener.class } );
 114  0
         private final static NSSelector addFocusListener =
 115  0
                 new NSSelector( "addFocusListener",
 116  0
                 new Class[] { FocusListener.class } );
 117  0
         private final static NSSelector removeFocusListener =
 118  0
                 new NSSelector( "removeFocusListener",
 119  0
                 new Class[] { FocusListener.class } );
 120  
 
 121  
         // null handling
 122  
         protected boolean wasNull;
 123  
         protected static final String EMPTY_STRING = "";
 124  
 
 125  
         // dirty handling
 126  
         protected boolean needsUpdate;
 127  
         protected boolean hasDocument;
 128  
     protected boolean isListening;
 129  
 
 130  
         // formatting
 131  
         protected Format format;
 132  
 
 133  
     // cache the value aspect
 134  
     private EODisplayGroup valueDisplayGroup;
 135  
     private String valueKey;
 136  
 
 137  
     // coalescing document events
 138  
     protected boolean autoUpdating;
 139  0
     protected int interval = 400; // adjust as needed
 140  
     protected Timer keyTimer;
 141  
 
 142  
     // NOTE: a new key timer is created for each use and
 143  
     // is disposed when the timer is stopped.
 144  
     // Swing's Timer class is kept in a static list of timers
 145  
     // and each retains a strong reference to their listeners.
 146  
     // This caused a memory leak as associations typically
 147  
     // refer to their controlled component which is referred
 148  
     // to by its parents and so on until no application window
 149  
     // will ever get garbage collected.  yikes.
 150  
 
 151  
     /**
 152  
     * Constructor specifying the object to be controlled by this
 153  
     * association.  Does not establish connection.
 154  
     */
 155  
     public TimedTextAssociation ( Object anObject )
 156  
     {
 157  0
         super( anObject );
 158  0
                 wasNull = false;
 159  0
                 needsUpdate = false;
 160  0
                 hasDocument = false;
 161  0
         isListening = true;
 162  0
         valueDisplayGroup = null;
 163  0
         valueKey = null;
 164  
 
 165  0
         autoUpdating = true;
 166  0
         keyTimer = null;
 167  0
     }
 168  
 
 169  
     /**
 170  
     * Returns a List of aspect signatures whose contents
 171  
     * correspond with the aspects list.  Each element is
 172  
     * a string whose characters represent a capability of
 173  
     * the corresponding aspect. <ul>
 174  
     * <li>"A" attribute: the aspect can be bound to
 175  
     * an attribute.</li>
 176  
     * <li>"1" to-one: the aspect can be bound to a
 177  
     * property that returns a single object.</li>
 178  
     * <li>"M" to-one: the aspect can be bound to a
 179  
     * property that returns multiple objects.</li>
 180  
     * </ul>
 181  
     * An empty signature "" means that the aspect can
 182  
     * bind without needing a key.
 183  
     * This implementation returns "A1M" for each
 184  
     * element in the aspects array.
 185  
     */
 186  
     public static NSArray aspectSignatures ()
 187  
     {
 188  0
         return aspectSignatures;
 189  
     }
 190  
 
 191  
     /**
 192  
     * Returns a List that describes the aspects supported
 193  
     * by this class.  Each element in the list is the string
 194  
     * name of the aspect.  This implementation returns an
 195  
     * empty list.
 196  
     */
 197  
     public static NSArray aspects ()
 198  
     {
 199  0
         return aspects;
 200  
     }
 201  
 
 202  
     /**
 203  
     * Returns a List of EOAssociation subclasses that,
 204  
     * for the objects that are usable for this association,
 205  
     * are less suitable than this association.
 206  
     */
 207  
     public static NSArray associationClassesSuperseded ()
 208  
     {
 209  0
         return new NSArray();
 210  
     }
 211  
 
 212  
     /**
 213  
     * Returns whether this class can control the specified
 214  
     * object.
 215  
     */
 216  
     public static boolean isUsableWithObject ( Object anObject )
 217  
     {
 218  0
         return setText.implementedByObject( anObject );
 219  
     }
 220  
 
 221  
     /**
 222  
     * Returns a List of properties of the controlled object
 223  
     * that are controlled by this class.  For example,
 224  
     * "stringValue", or "selected".
 225  
     */
 226  
     public static NSArray objectKeysTaken ()
 227  
     {
 228  0
         return objectKeysTaken;
 229  
     }
 230  
 
 231  
     /**
 232  
     * Returns the aspect that is considered primary
 233  
     * or default.  This is typically "value" or somesuch.
 234  
     */
 235  
     public static String primaryAspect ()
 236  
     {
 237  0
         return ValueAspect;
 238  
     }
 239  
 
 240  
     /**
 241  
     * Returns whether this association can bind to the
 242  
     * specified display group on the specified key for
 243  
     * the specified aspect.
 244  
     */
 245  
     public boolean canBindAspect (
 246  
         String anAspect, EODisplayGroup aDisplayGroup, String aKey)
 247  
     {
 248  0
         return ( aspects.containsObject( anAspect ) );
 249  
     }
 250  
 
 251  
     /**
 252  
     * Binds the specified aspect of this association to the
 253  
     * specified key on the specified display group.
 254  
     */
 255  
     public void bindAspect (
 256  
         String anAspect, EODisplayGroup aDisplayGroup, String aKey )
 257  
         {
 258  0
                 if ( ValueAspect.equals( anAspect ) )
 259  
                 {
 260  0
                         valueDisplayGroup = aDisplayGroup;
 261  0
                         valueKey = aKey;
 262  
                 }
 263  0
                 super.bindAspect( anAspect, aDisplayGroup, aKey );
 264  0
         }
 265  
 
 266  
     /**
 267  
     * Establishes a connection between this association
 268  
     * and the controlled object.  This implementation
 269  
         * attempts to add this class as an ActionListener
 270  
         * and as a FocusListener to the specified object.
 271  
     */
 272  
     public void establishConnection ()
 273  
     {
 274  0
                 Object component = object();
 275  
                 try
 276  
                 {
 277  0
                         if ( addActionListener.implementedByObject( component ) )
 278  
                         {
 279  0
                                 addActionListener.invoke( component, this );
 280  
                         }
 281  0
                         if ( addFocusListener.implementedByObject( component ) )
 282  
                         {
 283  0
                                 addFocusListener.invoke( component, this );
 284  
                         }
 285  0
             hasDocument = false;
 286  0
                         if ( getDocument.implementedByObject( component ) )
 287  
                         {
 288  0
                                 Object document = getDocument.invoke( component );
 289  0
                 if ( document instanceof Document )
 290  
                 {
 291  0
                     ((Document)document).addDocumentListener( this );
 292  0
                     hasDocument = true;
 293  
                 }
 294  
                         }
 295  
                 }
 296  0
                 catch ( Exception exc )
 297  
                 {
 298  0
                         throw new WotonomyException(
 299  0
                                 "Error while establishing connection", exc );
 300  0
                 }
 301  
 
 302  0
         super.establishConnection();
 303  
 
 304  
                 // forces update from bindings
 305  0
                 subjectChanged();
 306  0
     }
 307  
 
 308  
     /**
 309  
     * Breaks the connection between this association and
 310  
     * its object.  Override to stop listening for events
 311  
     * from the object.
 312  
     */
 313  
     public void breakConnection ()
 314  
     {
 315  0
                 Object component = object();
 316  
                 try
 317  
                 {
 318  0
                         if ( removeActionListener.implementedByObject( component ) )
 319  
                         {
 320  0
                                 removeActionListener.invoke( component, this );
 321  
                         }
 322  0
                         if ( removeFocusListener.implementedByObject( component ) )
 323  
                         {
 324  0
                                 removeFocusListener.invoke( component, this );
 325  
                         }
 326  0
                         if ( getDocument.implementedByObject( component ) )
 327  
                         {
 328  0
                                 Object document = getDocument.invoke( component );
 329  0
                 if ( document instanceof Document )
 330  
                 {
 331  0
                     ((Document)document).removeDocumentListener( this );
 332  
                 }
 333  
                         }
 334  
                 }
 335  0
                 catch ( Exception exc )
 336  
                 {
 337  0
                         throw new WotonomyException(
 338  0
                                 "Error while breaking connection", exc );
 339  0
                 }
 340  0
         super.breakConnection();
 341  0
     }
 342  
 
 343  
     /**
 344  
     * Called when either the selection or the contents
 345  
     * of an associated display group have changed.
 346  
     */
 347  
     public void subjectChanged ()
 348  
     {
 349  0
                 Object component = object();
 350  
                 EODisplayGroup displayGroup;
 351  
                 String key;
 352  
                 Object value;
 353  
 
 354  
                 // value aspect
 355  0
                 displayGroup = valueDisplayGroup;
 356  0
                 if ( displayGroup != null )
 357  
                 {
 358  0
             if ( component instanceof Component )
 359  
             {
 360  0
                 ((Component)component).setEnabled( 
 361  0
                     displayGroup.enabledToSetSelectedObjectValueForKey( valueKey ) );
 362  
             }
 363  
             
 364  0
                         key = valueKey;
 365  
 
 366  0
                         if ( displayGroup.selectedObjects().size() > 1 )
 367  
                         {
 368  
                             // if there're more than one object selected, set
 369  
                             // the value to blank for all of them.
 370  
                             Object previousValue;
 371  
 
 372  0
                             Iterator indexIterator = displayGroup.selectionIndexes().
 373  0
                                                       iterator();
 374  
 
 375  
                             // get value for the first selected object.
 376  0
                             int initialIndex = ( (Integer)indexIterator.next() ).intValue();
 377  0
                             previousValue = displayGroup.valueForObjectAtIndex(
 378  0
                                                       initialIndex, key );
 379  0
                             value = null;
 380  
 
 381  
                             // go through the rest of the selected objects, compare each
 382  
                             // value with the previous one. continue comparing if two
 383  
                             // values are equal, break the while loop if they're different.
 384  
                             // the final value will be the common value of all selected objects
 385  
                             // if there is one, or be blank if there is not.
 386  0
                             while ( indexIterator.hasNext() )
 387  
                             {
 388  0
                                 int index = ( (Integer)indexIterator.next() ).intValue();
 389  0
                                 Object currentValue = displayGroup.valueForObjectAtIndex(
 390  0
                                                       index, key );
 391  0
                                 if ( currentValue != null && !currentValue.equals( previousValue ) )
 392  
                                 {
 393  0
                                     value = null;
 394  0
                                     break;
 395  
                                 }
 396  
                                 else
 397  
                                 {
 398  
                                     // currentValue is the same as the previous one
 399  0
                                     value = currentValue;
 400  
                                 }
 401  
 
 402  0
                             } // end while
 403  
 
 404  0
                         } else {
 405  
 
 406  
                             // if there's only one object selected.
 407  0
                             value = displayGroup.selectedObjectValueForKey( key );
 408  
                        } // end checking the size of selected objects in displayGroup
 409  
 
 410  
                         // convert value to string
 411  0
                         if ( value == null )
 412  
                         {
 413  0
                                 wasNull = true;
 414  0
                                 value = EMPTY_STRING;
 415  0
                         }
 416  
                         else
 417  
                         {
 418  0
                                 wasNull = false;
 419  0
                                 if ( format() != null )
 420  
                                 {
 421  
                                         try
 422  
                                         {
 423  0
                                                 value = format().format( value );
 424  
                                         }
 425  0
                                         catch ( IllegalArgumentException exc )
 426  
                                         {
 427  0
                                                 value = value.toString();
 428  0
                                         }
 429  
                                 }
 430  
                         }
 431  
 
 432  
 
 433  
                         try
 434  
                         {
 435  0
                             if ( ! value.toString().equals( getText.invoke( component ) ) )
 436  
                             {
 437  
                                 // No need to listen for any events that might get fired
 438  
                                 // while setting the text since we are the one setting it.
 439  0
                                 boolean wasListening = isListening;
 440  0
                                 isListening = false;
 441  
 
 442  
                                 // setText is an expensive operation
 443  0
                                 setText.invoke( component, value.toString() );
 444  
 
 445  0
                                 isListening = wasListening;
 446  0
                                 needsUpdate = false;
 447  
                             }
 448  
                         }
 449  0
                         catch ( Exception exc )
 450  
                         {
 451  0
                                 throw new WotonomyException(
 452  0
                                         "Error while updating component connection", exc );
 453  0
                         }
 454  
                 }
 455  
 
 456  
                 // icon aspect
 457  0
                 displayGroup = displayGroupForAspect( IconAspect );
 458  0
                 key = displayGroupKeyForAspect( IconAspect );
 459  0
                 if ( key != null )
 460  
                 {
 461  0
                         if ( displayGroup != null )
 462  
                         {
 463  0
                                 value =
 464  0
                                         displayGroup.selectedObjectValueForKey( key );
 465  0
                         }
 466  
                         else
 467  
                         {
 468  
                             // treat bound key without display group
 469  
                             // as a resource to be loaded from the selected class.
 470  0
                             value = null;
 471  0
                             Object o = displayGroup.selectedObject();
 472  0
                             if ( o != null )
 473  
                             {
 474  0
                                 URL url = o.getClass().getResource( key );
 475  0
                                 if ( url != null )
 476  
                                 {
 477  0
                                     value = new ImageIcon( url );
 478  
                                 }
 479  
                             }
 480  
                         }
 481  
 
 482  
                         try
 483  
                         {
 484  0
                 setIcon.invoke( component, value );
 485  
                         }
 486  0
                         catch ( Exception exc )
 487  
                         {
 488  0
                                 throw new WotonomyException(
 489  0
                                         "Error while updating component connection", exc );
 490  0
                         }
 491  
                 }
 492  
 
 493  
                 // enabled aspect
 494  0
                 displayGroup = displayGroupForAspect( EnabledAspect );
 495  0
                 key = displayGroupKeyForAspect( EnabledAspect );
 496  0
                 if ( ( key != null )
 497  0
                 && ( component instanceof Component ) )
 498  
                 {
 499  0
                         if ( displayGroup != null )
 500  
                         {
 501  0
                                 value =
 502  0
                                         displayGroup.selectedObjectValueForKey( key );
 503  0
                         }
 504  
                         else
 505  
                         {
 506  
                                 // treat bound key without display group as a value
 507  0
                                 value = key;
 508  
                         }
 509  0
             Boolean converted = null;
 510  0
             if ( value != null ) 
 511  
             {
 512  0
                 converted = (Boolean)
 513  0
                     ValueConverter.convertObjectToClass(
 514  0
                         value, Boolean.class );
 515  
             }
 516  0
             if ( converted == null ) converted = Boolean.FALSE;
 517  0
             if ( ((Component)component).isEnabled() != converted.booleanValue() )
 518  
             {
 519  0
                 ((Component)component).setEnabled( converted.booleanValue() );
 520  
             }
 521  
                 }
 522  
 
 523  
                 // editable aspect
 524  0
                 displayGroup = displayGroupForAspect( EditableAspect );
 525  0
                 key = displayGroupKeyForAspect( EditableAspect );
 526  0
                 if ( ( key != null )
 527  0
                 && ( component instanceof JTextComponent ) )
 528  
                 {
 529  0
                         if ( displayGroup != null )
 530  
                         {
 531  0
                                 value =
 532  0
                                         displayGroup.selectedObjectValueForKey( key );
 533  0
                         }
 534  
                         else
 535  
                         {
 536  
                                 // treat bound key without display group as a value
 537  0
                                 value = key;
 538  
                         }
 539  0
                         Boolean converted = (Boolean)
 540  0
                                 ValueConverter.convertObjectToClass(
 541  0
                                         value, Boolean.class );
 542  
 
 543  0
                         if ( converted != null )
 544  
                         {
 545  0
                                 if ( converted.booleanValue() != ((JTextComponent)component).isEditable() )
 546  
                                 {
 547  0
                                         ((JTextComponent)component).setEditable( converted.booleanValue() );
 548  
                                 }
 549  
                         }
 550  
                 }
 551  
 
 552  
                 // label aspect
 553  0
                 displayGroup = displayGroupForAspect( LabelAspect );
 554  0
                 key = displayGroupKeyForAspect( LabelAspect );
 555  
 
 556  0
                 if ( ( key != null )
 557  0
                 && ( component instanceof JTextComponent ) )
 558  
                 {
 559  0
                         if ( displayGroup != null )
 560  
                         {
 561  0
                                 value =
 562  0
                                         displayGroup.selectedObjectValueForKey( key );
 563  0
                         }
 564  
                         else
 565  
                         {
 566  
                                 // treat bound key without display group as a value
 567  0
                                 value = key;
 568  
                         }
 569  0
                         Boolean converted = (Boolean)
 570  0
                                 ValueConverter.convertObjectToClass(
 571  0
                                         value, Boolean.class );
 572  
 
 573  0
                         if ( converted != null )
 574  
                         {
 575  0
                                 if ( converted.booleanValue() )
 576  
                                 {
 577  0
                                         if ( component instanceof JTextComponent )
 578  
                                         {
 579  0
                                             if ( component instanceof JTextArea )
 580  
                                             {
 581  0
                                                 areaToLabel( (JTextArea) component );
 582  0
                                             }
 583  
                                             else
 584  
                                             {
 585  0
                                                 fieldToLabel( (JTextComponent) component );
 586  
                                             }
 587  0
                                         }
 588  
                                 }
 589  
                                 else
 590  
                                 {
 591  0
                                         if ( component instanceof JTextComponent )
 592  
                                         {
 593  0
                                             if ( component instanceof JTextArea )
 594  
                                             {
 595  0
                                                 labelToArea( (JTextArea) component );
 596  0
                                             }
 597  
                                             else
 598  
                                             {
 599  0
                                                 labelToField( (JTextComponent ) component );
 600  
                                             }
 601  
                                         }
 602  
                                 }
 603  
                         }
 604  
                 }
 605  
 
 606  0
     }
 607  
 
 608  
     private void fieldToLabel( JTextComponent aTextField )
 609  
     {
 610  
         // turn on wrapping and disable editing and highlighting
 611  
 
 612  0
         aTextField.setEditable(false);
 613  0
         aTextField.setOpaque(false);
 614  
 
 615  
         // Set the border, colors and font to that of a label
 616  
 
 617  0
         LookAndFeel.installBorder(aTextField, "Label.border");
 618  
 
 619  0
         LookAndFeel.installColorsAndFont(aTextField,
 620  0
             "Label.background",
 621  0
             "Label.foreground",
 622  0
             "Label.font");
 623  0
     }
 624  
 
 625  
     private void labelToField( JTextComponent aTextField )
 626  
     {
 627  
         // turn on wrapping and disable editing and highlighting
 628  
 
 629  0
         aTextField.setEditable(true);
 630  0
         aTextField.setOpaque(true);
 631  
 
 632  
         // Set the border, colors and font to that of a label
 633  
 
 634  0
         LookAndFeel.installBorder(aTextField, "TextField.border");
 635  
 
 636  0
         LookAndFeel.installColorsAndFont(aTextField,
 637  0
             "TextField.background",
 638  0
             "TextField.foreground",
 639  0
             "TextField.font");
 640  0
     }
 641  
 
 642  
         private void areaToLabel( JTextArea aTextArea )
 643  
         {
 644  
             // turn on wrapping and disable editing and highlighting
 645  
 
 646  0
             aTextArea.setLineWrap(true);
 647  0
             aTextArea.setWrapStyleWord(true);
 648  0
             aTextArea.setEditable(false);
 649  
 
 650  
             // Set the text area's border, colors and font to
 651  
             // that of a label
 652  
 
 653  0
             LookAndFeel.installBorder(aTextArea, "Label.border");
 654  
 
 655  0
             LookAndFeel.installColorsAndFont(aTextArea,
 656  0
                 "Label.background",
 657  0
                 "Label.foreground",
 658  0
                 "Label.font");
 659  
 
 660  0
         }
 661  
 
 662  
         private void labelToArea( JTextArea aTextArea )
 663  
         {
 664  
             // turn on wrapping and disable editing and highlighting
 665  
 
 666  0
             aTextArea.setEditable(true);
 667  
 
 668  
             // Set the border, colors and font to that of a label
 669  
 
 670  0
             LookAndFeel.installBorder(aTextArea, "TextArea.border");
 671  
 
 672  0
             LookAndFeel.installColorsAndFont(aTextArea,
 673  0
                 "TextArea.background",
 674  0
                 "TextArea.foreground",
 675  0
                 "TextArea.font");
 676  0
         }
 677  
 
 678  
 
 679  
     /**
 680  
     * Forces this association to cause the object to
 681  
     * stop editing and validate the user's input.
 682  
     * @return false if there were problems validating,
 683  
     * or true to continue.
 684  
     */
 685  
     public boolean endEditing ()
 686  
     {
 687  0
         if ( keyTimer != null )
 688  
         {
 689  0
             keyTimer.stop();
 690  0
             keyTimer.removeActionListener( this );
 691  0
             keyTimer = null;
 692  
         }
 693  0
                 return writeValueToDisplayGroup();
 694  
     }
 695  
 
 696  
         /**
 697  
         * Writes the value currently in the component
 698  
         * to the selected object in the display group
 699  
         * bound to the value aspect.
 700  
     * @return false if there were problems validating,
 701  
     * or true to continue.
 702  
         */
 703  
         protected boolean writeValueToDisplayGroup()
 704  
         {
 705  0
             boolean returnValue = true;
 706  0
             if ( hasDocument && !needsUpdate ) return true;
 707  
 
 708  0
             EODisplayGroup displayGroup = valueDisplayGroup;
 709  0
             if ( displayGroup != null )
 710  
             {
 711  0
                 String key = valueKey;
 712  0
                 Object component = object();
 713  0
                 Object value = null;
 714  
                 try
 715  
                 {
 716  
                         //if ( getText.implementedByObject( component ) )
 717  
                         //{
 718  0
                                 value = getText.invoke( component );
 719  
                         //}
 720  
                 }
 721  0
                 catch ( Exception exc )
 722  
                 {
 723  0
                         throw new WotonomyException(
 724  0
                                 "Error updating display group", exc );
 725  0
                 }
 726  
 
 727  0
                 if ( ( wasNull ) && ( EMPTY_STRING.equals( value ) ) )
 728  
                 {
 729  0
                         value = null;
 730  0
                 }
 731  
                 else
 732  0
                 if ( format() != null )
 733  
                 {
 734  
                     try
 735  
                     {
 736  0
                         value = format().parseObject( value.toString() );
 737  
                     }
 738  0
                     catch ( ParseException exc )
 739  
                     {
 740  0
                         String message = exc.getMessage();
 741  
                         //"That format was not recognized.";
 742  0
                         if ( displayGroup.associationFailedToValidateValue(
 743  0
                                 this, value.toString(), key, exc, message ) )
 744  
                         {
 745  0
                             boolean wasListening = isListening;
 746  0
                             isListening = false;
 747  0
                             JOptionPane.showMessageDialog(
 748  0
                                 (Component)component, message );
 749  0
                             isListening = wasListening;
 750  
                         }
 751  0
                         needsUpdate = false;
 752  0
                         return false;
 753  0
                     }
 754  
                 }
 755  
 
 756  0
                 needsUpdate = false;
 757  
 
 758  
                 // only update if the value is different from the one in the display group
 759  0
                 Object existingValue = displayGroup.selectedObjectValueForKey( key );
 760  0
                 if ( displayGroup.selectedObjects().size() == 1 )
 761  
                 {
 762  0
                     if ( existingValue == value ) return true;
 763  0
                     if ( ( existingValue != null ) && ( existingValue.equals( value ) ) ) return true;
 764  0
                     if ( ( value != null ) && ( value.equals( existingValue ) ) ) return true;
 765  
                 }
 766  
 
 767  
                 // we might lose focus if display group displays a validation message
 768  0
                 boolean wasListening = isListening;
 769  0
                 isListening = false;
 770  
 
 771  0
                 Iterator selectedIterator = displayGroup.selectionIndexes().iterator();
 772  0
                 while ( selectedIterator.hasNext() )
 773  
                 {
 774  0
                     int index = ( (Integer)selectedIterator.next() ).intValue();
 775  
 
 776  0
                     if ( displayGroup.setValueForObjectAtIndex( value, index, key ) )
 777  
                     {
 778  0
                         isListening = wasListening;
 779  0
                         needsUpdate = false;
 780  0
                     }
 781  
                     else
 782  
                     {
 783  0
                         isListening = wasListening;
 784  0
                         needsUpdate = false;
 785  0
                         returnValue = false;
 786  
                     }
 787  0
                 }
 788  
 
 789  
             }
 790  0
             return returnValue;
 791  
         }
 792  
 
 793  
         /**
 794  
         * Sets the Format that is used to convert values from the display
 795  
         * group to and from text that is displayed in the component.
 796  
     * Having a formatter disables auto-updating.
 797  
         */
 798  
         public void setFormat( Format aFormat )
 799  
         {
 800  0
                 format = aFormat;
 801  0
         }
 802  
 
 803  
         /**
 804  
         * Gets the Format that is used to convert values from the display
 805  
         * group to and from text that is displayed in the component.
 806  
         */
 807  
         public Format format()
 808  
         {
 809  0
                 return format;
 810  
         }
 811  
 
 812  
     // interface ActionListener
 813  
 
 814  
         /**
 815  
         * Updates object on action performed.
 816  
         */
 817  
         public void actionPerformed( ActionEvent evt )
 818  
         {
 819  0
         if ( keyTimer != null )
 820  
         {
 821  0
             keyTimer.stop();
 822  0
             keyTimer.removeActionListener( this );
 823  0
             keyTimer = null;
 824  
         }
 825  0
         if ( ! isListening ) return;
 826  0
         if ( needsUpdate )
 827  
         {
 828  0
             writeValueToDisplayGroup();
 829  
         }
 830  0
         }
 831  
 
 832  
     // interface FocusListener
 833  
 
 834  
         /**
 835  
         * Notifies of beginning of edit.
 836  
         */
 837  
     public void focusGained(FocusEvent evt)
 838  
     {
 839  0
         if ( ! isListening ) return;
 840  
                 Object o;
 841  
                 EODisplayGroup displayGroup;
 842  0
         Enumeration e = aspects().objectEnumerator();
 843  0
                 while ( e.hasMoreElements() )
 844  
                 {
 845  0
                         displayGroup =
 846  0
                                 displayGroupForAspect( e.nextElement().toString() );
 847  0
                         if ( displayGroup != null )
 848  
                         {
 849  0
                                 displayGroup.associationDidBeginEditing( this );
 850  0
                         }
 851  
                 }
 852  0
     }
 853  
 
 854  
         /**
 855  
         * Updates object on focus lost and notifies of end of edit.
 856  
         */
 857  
     public void focusLost(FocusEvent evt)
 858  
     {
 859  0
         if ( ! isListening ) return;
 860  0
                 if ( endEditing() )
 861  
                 {
 862  
                         Object o;
 863  
                         EODisplayGroup displayGroup;
 864  0
                         Enumeration e = aspects().objectEnumerator();
 865  0
                         while ( e.hasMoreElements() )
 866  
                         {
 867  0
                                 displayGroup =
 868  0
                                         displayGroupForAspect( e.nextElement().toString() );
 869  0
                                 if ( displayGroup != null )
 870  
                                 {
 871  0
                                         displayGroup.associationDidEndEditing( this );
 872  0
                                 }
 873  
                         }
 874  
                 }
 875  
                 else
 876  
                 {
 877  
                         // probably should notify of a validation error here,
 878  
                 }
 879  0
     }
 880  
 
 881  
     /**
 882  
     * Returns whether the data model is updated for every change
 883  
     * in the controlled component.  If false, the data is only
 884  
     * updated on focus lost or the enter key.  Default is true.
 885  
     */
 886  
     public boolean isAutoUpdating()
 887  
     {
 888  0
         if ( format() != null ) return false;
 889  0
         return autoUpdating;
 890  
     }
 891  
 
 892  
     /**
 893  
     * Sets whether the data model is updated for every change
 894  
     * in the controlled component.
 895  
     */
 896  
     public void setAutoUpdating( boolean isAutoUpdating )
 897  
     {
 898  0
         autoUpdating = isAutoUpdating;
 899  0
     }
 900  
 
 901  
     /**
 902  
     * Triggers the key timer to start.
 903  
     */
 904  
     protected void queueUpdate()
 905  
     {
 906  0
         if ( isAutoUpdating() )
 907  
         {
 908  0
             if ( keyTimer == null )
 909  
             {
 910  0
                 keyTimer = new Timer( interval, this );
 911  
             }
 912  0
             keyTimer.restart();
 913  
         }
 914  0
     }
 915  
 
 916  
    // interface DocumentListener
 917  
 
 918  
    public void insertUpdate(DocumentEvent e)
 919  
    {
 920  0
        if ( ! isListening ) return;
 921  0
            needsUpdate = true;
 922  0
        queueUpdate();
 923  0
    }
 924  
 
 925  
    public void removeUpdate(DocumentEvent e)
 926  
    {
 927  0
        if ( ! isListening ) return;
 928  0
            needsUpdate = true;
 929  0
        queueUpdate();
 930  0
    }
 931  
 
 932  
    public void changedUpdate(DocumentEvent e)
 933  
    {
 934  0
        if ( ! isListening ) return;
 935  0
        needsUpdate = true;
 936  0
        queueUpdate();
 937  0
    }
 938  
 
 939  
 }
 940  
 
 941  
 /*
 942  
  * $Log$
 943  
  * Revision 1.2  2006/02/18 23:19:05  cgruber
 944  
  * Update imports and maven dependencies.
 945  
  *
 946  
  * Revision 1.1  2006/02/16 13:22:22  cgruber
 947  
  * Check in all sources in eclipse-friendly maven-enabled packages.
 948  
  *
 949  
  * Revision 1.3  2004/01/28 18:34:57  mpowers
 950  
  * Better handling for enabling.
 951  
  * Now respecting enabledToSetSelectedObjectValueForKey from display group.
 952  
  *
 953  
  * Revision 1.2  2003/08/06 23:07:52  chochos
 954  
  * general code cleanup (mostly, removing unused imports)
 955  
  *
 956  
  * Revision 1.1  2001/12/20 18:57:24  mpowers
 957  
  * (Re-)Contributing TimedTextAssociation.  Just like TA, except uses timers.
 958  
  *
 959  
  * Revision 1.20  2001/10/26 19:58:06  mpowers
 960  
  * Better handling for non-string types.  We were testing with equals with the
 961  
  * new value against the existing value in the component.  Now we convert
 962  
  * the new value to a string before comparing.  Fixes case for properties
 963  
  * of non-String types, like StringBuffer.
 964  
  *
 965  
  * Revision 1.19  2001/09/30 21:57:14  mpowers
 966  
  * Timers were not getting cleaned up if breakConnection was called
 967  
  * before the timer got a chance to fire.
 968  
  *
 969  
  * Revision 1.18  2001/08/22 15:42:26  mpowers
 970  
  * Added support for JTextComponent label-izing.
 971  
  *
 972  
  * Revision 1.17  2001/07/30 16:32:55  mpowers
 973  
  * Implemented support for bulk-editing.  Detail associations will now
 974  
  * apply changes to all selected objects.
 975  
  *
 976  
  * Revision 1.16  2001/07/17 19:53:37  mpowers
 977  
  * Made some private fields protected for benefit of subclassers.
 978  
  *
 979  
  * Revision 1.15  2001/06/30 14:59:36  mpowers
 980  
  * LabelAspect now sets the text field's opaque setting.
 981  
  *
 982  
  * Revision 1.14  2001/06/29 14:54:08  mpowers
 983  
  * Another fix for timers - timers were definitely causing a memory leak.
 984  
  *
 985  
  * Revision 1.13  2001/06/26 21:37:19  mpowers
 986  
  * Fixed a null pointer in the new key timer scheme.
 987  
  *
 988  
  * Revision 1.12  2001/06/25 14:46:03  mpowers
 989  
  * Fixed a memory leak involving the use of timers.
 990  
  *
 991  
  * Revision 1.11  2001/06/01 19:14:59  mpowers
 992  
  * Text association's enabled aspect is now more discriminating.
 993  
  *
 994  
  * Revision 1.10  2001/05/18 21:07:24  mpowers
 995  
  * Changed the way we handle failure to update object value.
 996  
  *
 997  
  * Revision 1.9  2001/03/13 21:39:58  mpowers
 998  
  * Improved validation handling.
 999  
  *
 1000  
  * Revision 1.8  2001/03/12 12:49:10  mpowers
 1001  
  * Improved validation handling.
 1002  
  * Having a formatter disables auto-updating.
 1003  
  *
 1004  
  * Revision 1.7  2001/03/09 22:08:13  mpowers
 1005  
  * Now handling any objects that have a valid Document.
 1006  
  * No longer checking enabled before updating the enabled state.
 1007  
  *
 1008  
  * Revision 1.6  2001/03/07 19:57:32  mpowers
 1009  
  * Fixed paste error in IconAspect.
 1010  
  *
 1011  
  * Revision 1.4  2001/02/17 16:52:05  mpowers
 1012  
  * Changes in imports to support building with jdk1.1 collections.
 1013  
  *
 1014  
  * Revision 1.3  2001/01/31 19:12:33  mpowers
 1015  
  * Implemented auto-updating in TextComponent.
 1016  
  *
 1017  
  * Revision 1.2  2001/01/10 15:53:58  mpowers
 1018  
  * Preventing a null pointer exception if getText were to return null,
 1019  
  * which doesn't happen for JTextFields but might happen for other objects.
 1020  
  *
 1021  
  * Revision 1.1.1.1  2000/12/21 15:49:08  mpowers
 1022  
  * Contributing wotonomy.
 1023  
  *
 1024  
  * Revision 1.13  2000/12/20 16:25:41  michael
 1025  
  * Added log to all files.
 1026  
  *
 1027  
  *
 1028  
  */
 1029