1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.lang.reflect.InvocationTargetException;
27 import java.net.URL;
28 import java.text.Format;
29 import java.text.ParseException;
30 import java.util.Enumeration;
31 import java.util.Iterator;
32
33 import javax.swing.Icon;
34 import javax.swing.ImageIcon;
35 import javax.swing.JOptionPane;
36 import javax.swing.JTextArea;
37 import javax.swing.LookAndFeel;
38 import javax.swing.event.DocumentEvent;
39 import javax.swing.event.DocumentListener;
40 import javax.swing.text.AbstractDocument;
41 import javax.swing.text.DefaultStyledDocument;
42 import javax.swing.text.Document;
43 import javax.swing.text.JTextComponent;
44
45 import net.wotonomy.foundation.NSArray;
46 import net.wotonomy.foundation.NSDictionary;
47 import net.wotonomy.foundation.NSNotification;
48 import net.wotonomy.foundation.NSNotificationCenter;
49 import net.wotonomy.foundation.NSNotificationQueue;
50 import net.wotonomy.foundation.NSSelector;
51 import net.wotonomy.foundation.internal.ValueConverter;
52 import net.wotonomy.foundation.internal.WotonomyException;
53 import net.wotonomy.ui.EOAssociation;
54 import net.wotonomy.ui.EODisplayGroup;
55
56 /***
57 * TextAssociation binds JTextComponents and other objects
58 * with getText() and setText() methods to a display group.
59 * Note that JLabels are supported with both the Text and
60 * Icon aspects.
61 * Bindings are:
62 * <ul>
63 * <li>value: a property convertable to/from a string</li>
64 * <li>editable: a boolean property that determines whether
65 * the user can edit the text in the field</li>
66 * <li>enabled: a boolean property that determines whether
67 * the user can select the text in the field</li>
68 * <li>visible: a boolean property that determines whether
69 * the field is visible</li>
70 * <li>label: a boolean property that determines whether
71 * field should appear as a read-only, selectable label</li>
72 * <li>icon: a property that returns a Swing icon, for use
73 * with JLabels and other components with setIcon() methods.
74 * If bound to a static string, the string will be used to
75 * load an image resource from the selected object's class.</li>
76 * </ul>
77 *
78 * @author michael@mpowers.net
79 * @author $Author: cgruber $
80 * @version $Revision: 904 $
81 */
82 public class TextAssociation extends EOAssociation
83 implements FocusListener, ActionListener, DocumentListener
84 {
85 static final NSArray aspects =
86 new NSArray( new Object[] {
87 ValueAspect, EnabledAspect, EditableAspect, VisibleAspect, LabelAspect, IconAspect
88 } );
89 static final NSArray aspectSignatures =
90 new NSArray( new Object[] {
91 AttributeToOneAspectSignature,
92 AttributeToOneAspectSignature,
93 AttributeToOneAspectSignature,
94 AttributeToOneAspectSignature,
95 AttributeToOneAspectSignature,
96 AttributeToOneAspectSignature
97 } );
98 static final NSArray objectKeysTaken =
99 new NSArray( new Object[] {
100 "text", "enabled", "editable", "visible"
101 } );
102
103 private final static NSSelector getText =
104 new NSSelector( "getText" );
105 private final static NSSelector setText =
106 new NSSelector( "setText",
107 new Class[] { String.class } );
108 private final static NSSelector getDocument =
109 new NSSelector( "getDocument" );
110 private final static NSSelector setIcon =
111 new NSSelector( "setIcon",
112 new Class[] { Icon.class } );
113 private final static NSSelector addActionListener =
114 new NSSelector( "addActionListener",
115 new Class[] { ActionListener.class } );
116 private final static NSSelector removeActionListener =
117 new NSSelector( "removeActionListener",
118 new Class[] { ActionListener.class } );
119 private final static NSSelector addFocusListener =
120 new NSSelector( "addFocusListener",
121 new Class[] { FocusListener.class } );
122 private final static NSSelector removeFocusListener =
123 new NSSelector( "removeFocusListener",
124 new Class[] { FocusListener.class } );
125
126
127 protected boolean wasNull;
128 protected static final String EMPTY_STRING = "";
129
130
131 protected boolean needsUpdate;
132 protected boolean hasDocument;
133 protected boolean isListening;
134
135
136 protected Format format;
137
138
139 protected boolean activeUpdate;
140
141
142 protected Class lastKnownType;
143
144
145 private EODisplayGroup valueDisplayGroup;
146 private String valueKey;
147
148
149 private boolean pleaseIgnoreNextChange = false;
150 private boolean pleaseAcceptNextChange = false;
151 private boolean externallyChanged = true;
152
153
154 /***
155 * Constructor specifying the object to be controlled by this
156 * association. Does not establish connection.
157 */
158 public TextAssociation ( Object anObject )
159 {
160 super( anObject );
161 wasNull = false;
162 needsUpdate = false;
163 activeUpdate = true;
164 hasDocument = false;
165 isListening = true;
166 valueDisplayGroup = null;
167 valueKey = null;
168 format = null;
169 lastKnownType = null;
170
171
172 NSSelector handleNotification =
173 new NSSelector( "handleNotification",
174 new Class[] { NSNotification.class } );
175 NSNotificationCenter.defaultCenter().addObserver(
176 this, handleNotification, null, this );
177 }
178
179 /***
180 * Returns a List of aspect signatures whose contents
181 * correspond with the aspects list. Each element is
182 * a string whose characters represent a capability of
183 * the corresponding aspect. <ul>
184 * <li>"A" attribute: the aspect can be bound to
185 * an attribute.</li>
186 * <li>"1" to-one: the aspect can be bound to a
187 * property that returns a single object.</li>
188 * <li>"M" to-one: the aspect can be bound to a
189 * property that returns multiple objects.</li>
190 * </ul>
191 * An empty signature "" means that the aspect can
192 * bind without needing a key.
193 * This implementation returns "A1M" for each
194 * element in the aspects array.
195 */
196 public static NSArray aspectSignatures ()
197 {
198 return aspectSignatures;
199 }
200
201 /***
202 * Returns a List that describes the aspects supported
203 * by this class. Each element in the list is the string
204 * name of the aspect. This implementation returns an
205 * empty list.
206 */
207 public static NSArray aspects ()
208 {
209 return aspects;
210 }
211
212 /***
213 * Returns a List of EOAssociation subclasses that,
214 * for the objects that are usable for this association,
215 * are less suitable than this association.
216 */
217 public static NSArray associationClassesSuperseded ()
218 {
219 return new NSArray();
220 }
221
222 /***
223 * Returns whether this class can control the specified
224 * object.
225 */
226 public static boolean isUsableWithObject ( Object anObject )
227 {
228 return setText.implementedByObject( anObject );
229 }
230
231 /***
232 * Returns a List of properties of the controlled object
233 * that are controlled by this class. For example,
234 * "stringValue", or "selected".
235 */
236 public static NSArray objectKeysTaken ()
237 {
238 return objectKeysTaken;
239 }
240
241 /***
242 * Returns the aspect that is considered primary
243 * or default. This is typically "value" or somesuch.
244 */
245 public static String primaryAspect ()
246 {
247 return ValueAspect;
248 }
249
250 /***
251 * Returns whether this association can bind to the
252 * specified display group on the specified key for
253 * the specified aspect.
254 */
255 public boolean canBindAspect (
256 String anAspect, EODisplayGroup aDisplayGroup, String aKey)
257 {
258 return ( aspects.containsObject( anAspect ) );
259 }
260
261 /***
262 * Binds the specified aspect of this association to the
263 * specified key on the specified display group.
264 */
265 public void bindAspect (
266 String anAspect, EODisplayGroup aDisplayGroup, String aKey )
267 {
268 if ( ValueAspect.equals( anAspect ) )
269 {
270 valueDisplayGroup = aDisplayGroup;
271 valueKey = aKey;
272 }
273 super.bindAspect( anAspect, aDisplayGroup, aKey );
274 }
275
276 /***
277 * Establishes a connection between this association
278 * and the controlled object. This implementation
279 * attempts to add this class as an ActionListener
280 * and as a FocusListener to the specified object.
281 */
282 public void establishConnection ()
283 {
284 Object component = object();
285 try
286 {
287 if ( addActionListener.implementedByObject( component ) )
288 {
289 addActionListener.invoke( component, this );
290 }
291 if ( addFocusListener.implementedByObject( component ) )
292 {
293 addFocusListener.invoke( component, this );
294 }
295 hasDocument = false;
296 if ( getDocument.implementedByObject( component ) )
297 {
298 Object document = getDocument.invoke( component );
299 if ( document instanceof Document )
300 {
301 ((Document)document).addDocumentListener( this );
302 hasDocument = true;
303 }
304 }
305 }
306 catch ( Exception exc )
307 {
308 throw new WotonomyException(
309 "Error while establishing connection", exc );
310 }
311
312 super.establishConnection();
313
314
315 subjectChanged();
316 }
317
318 /***
319 * Breaks the connection between this association and
320 * its object. Override to stop listening for events
321 * from the object.
322 */
323 public void breakConnection ()
324 {
325 Object component = object();
326 try
327 {
328 if ( removeActionListener.implementedByObject( component ) )
329 {
330 removeActionListener.invoke( component, this );
331 }
332 if ( removeFocusListener.implementedByObject( component ) )
333 {
334 removeFocusListener.invoke( component, this );
335 }
336 if ( getDocument.implementedByObject( component ) )
337 {
338 Object document = getDocument.invoke( component );
339 if ( document instanceof Document )
340 {
341 ((Document)document).removeDocumentListener( this );
342 }
343 }
344 }
345 catch ( Exception exc )
346 {
347 throw new WotonomyException(
348 "Error while breaking connection", exc );
349 }
350 super.breakConnection();
351 }
352
353 public void objectWillChange( Object anObject )
354 {
355 super.objectWillChange( anObject );
356 externallyChanged = true;
357 }
358
359 /***
360 * Called when either the selection or the contents
361 * of an associated display group have changed.
362 */
363 public void subjectChanged()
364 {
365 if ( pleaseIgnoreNextChange )
366 {
367 pleaseIgnoreNextChange = false;
368 externallyChanged = false;
369 return;
370 }
371
372 externallyChanged = true;
373
374 Object component = object();
375 EODisplayGroup displayGroup;
376 String key;
377 Object value;
378
379
380 displayGroup = valueDisplayGroup;
381 if ( displayGroup != null )
382 {
383 if ( component instanceof Component )
384 {
385 ((Component)component).setEnabled(
386 displayGroup.enabledToSetSelectedObjectValueForKey( valueKey ) );
387 }
388
389
390 if ( activeUpdate || displayGroup.editingAssociation() != this || pleaseAcceptNextChange )
391 {
392 pleaseAcceptNextChange = false;
393 key = valueKey;
394
395 if ( displayGroup.selectedObjects().size() > 1 )
396 {
397
398
399 Object previousValue;
400
401 Iterator indexIterator = displayGroup.selectionIndexes().
402 iterator();
403
404
405 int initialIndex = ( (Integer)indexIterator.next() ).intValue();
406 previousValue = displayGroup.valueForObjectAtIndex(
407 initialIndex, key );
408 value = previousValue;
409
410
411
412
413
414
415 while ( indexIterator.hasNext() )
416 {
417 int index = ( (Integer)indexIterator.next() ).intValue();
418 Object currentValue = displayGroup.valueForObjectAtIndex(
419 index, key );
420 if ( currentValue != null && previousValue != null
421 && !currentValue.toString().equals( previousValue.toString() ) ) {
422 value = null;
423 break;
424 }
425
426 }
427
428 } else {
429
430
431 value = displayGroup.selectedObjectValueForKey( key );
432 }
433
434
435 if ( value == null )
436 {
437 wasNull = true;
438 value = EMPTY_STRING;
439 lastKnownType = null;
440 }
441 else
442 {
443 wasNull = false;
444 lastKnownType = value.getClass();
445 if ( format() != null )
446 {
447 try
448 {
449 value = format().format( value );
450 }
451 catch ( IllegalArgumentException exc )
452 {
453 value = value.toString();
454 }
455 }
456 }
457
458
459 try
460 {
461 if ( needToReadValueFromDisplayGroup( value.toString(), getText ) )
462 {
463
464
465 boolean wasListening = isListening;
466 isListening = false;
467
468
469 setText.invoke( component, value.toString() );
470
471 isListening = wasListening;
472 needsUpdate = false;
473 }
474 }
475 catch ( Exception exc )
476 {
477 throw new WotonomyException(
478 "Error while updating component connection", exc );
479 }
480 }
481 }
482
483
484 displayGroup = displayGroupForAspect( IconAspect );
485 key = displayGroupKeyForAspect( IconAspect );
486 if ( key != null )
487 {
488 if ( displayGroup != null )
489 {
490 value =
491 displayGroup.selectedObjectValueForKey( key );
492 }
493 else
494 {
495
496
497 value = null;
498 Object o = displayGroup.selectedObject();
499 if ( o != null )
500 {
501 URL url = o.getClass().getResource( key );
502 if ( url != null )
503 {
504 value = new ImageIcon( url );
505 }
506 }
507 }
508
509 try
510 {
511 setIcon.invoke( component, value );
512 }
513 catch ( Exception exc )
514 {
515 throw new WotonomyException(
516 "Error while updating component connection", exc );
517 }
518 }
519
520
521 displayGroup = displayGroupForAspect( EnabledAspect );
522 key = displayGroupKeyForAspect( EnabledAspect );
523 if ( ( key != null )
524 && ( component instanceof Component ) )
525 {
526 if ( displayGroup != null )
527 {
528 value =
529 displayGroup.selectedObjectValueForKey( key );
530 }
531 else
532 {
533
534 value = key;
535 }
536 Boolean converted = null;
537 if ( value != null )
538 {
539 converted = (Boolean)
540 ValueConverter.convertObjectToClass(
541 value, Boolean.class );
542 }
543 if ( converted == null ) converted = Boolean.FALSE;
544 if ( ((Component)component).isEnabled() != converted.booleanValue() )
545 {
546 ((Component)component).setEnabled( converted.booleanValue() );
547 }
548 }
549
550
551 displayGroup = displayGroupForAspect( EditableAspect );
552 key = displayGroupKeyForAspect( EditableAspect );
553 if ( ( key != null )
554 && ( component instanceof JTextComponent ) )
555 {
556 if ( displayGroup != null )
557 {
558 value =
559 displayGroup.selectedObjectValueForKey( key );
560 }
561 else
562 {
563
564 value = key;
565 }
566 Boolean converted = (Boolean)
567 ValueConverter.convertObjectToClass(
568 value, Boolean.class );
569
570 if ( converted != null )
571 {
572 if ( converted.booleanValue() != ((JTextComponent)component).isEditable() )
573 {
574 ((JTextComponent)component).setEditable( converted.booleanValue() );
575 }
576 }
577 }
578
579
580 displayGroup = displayGroupForAspect( VisibleAspect );
581 key = displayGroupKeyForAspect( VisibleAspect );
582 if ( ( key != null )
583 && ( component instanceof Component ) )
584 {
585 if ( displayGroup != null )
586 {
587 value =
588 displayGroup.selectedObjectValueForKey( key );
589 }
590 else
591 {
592
593 value = key;
594 }
595 Boolean converted = (Boolean)
596 ValueConverter.convertObjectToClass(
597 value, Boolean.class );
598
599 if ( converted != null )
600 {
601 if ( converted.booleanValue() != ((Component)component).isVisible() )
602 {
603 ((Component)component).setVisible( converted.booleanValue() );
604 }
605 }
606 }
607
608
609 displayGroup = displayGroupForAspect( LabelAspect );
610 key = displayGroupKeyForAspect( LabelAspect );
611
612 if ( ( key != null )
613 && ( component instanceof JTextComponent ) )
614 {
615 if ( displayGroup != null )
616 {
617 value =
618 displayGroup.selectedObjectValueForKey( key );
619 }
620 else
621 {
622
623 value = key;
624 }
625 Boolean converted = (Boolean)
626 ValueConverter.convertObjectToClass(
627 value, Boolean.class );
628
629 if ( converted != null )
630 {
631 if ( converted.booleanValue() )
632 {
633 if ( component instanceof JTextComponent )
634 {
635 if ( component instanceof JTextArea )
636 {
637 areaToLabel( (JTextArea) component );
638 }
639 else
640 {
641 fieldToLabel( (JTextComponent) component );
642 }
643 }
644 }
645 else
646 {
647 if ( component instanceof JTextComponent )
648 {
649 if ( component instanceof JTextArea )
650 {
651 labelToArea( (JTextArea) component );
652 }
653 else
654 {
655 labelToField( (JTextComponent ) component );
656 }
657 }
658 }
659 }
660 }
661 }
662
663 private void fieldToLabel( JTextComponent aTextField )
664 {
665
666
667 aTextField.setEditable(false);
668 aTextField.setOpaque(false);
669
670
671
672
673 aTextField.setBorder( null );
674
675 LookAndFeel.installColorsAndFont(aTextField,
676 "Label.background",
677 "Label.foreground",
678 "Label.font");
679 }
680
681 private void labelToField( JTextComponent aTextField )
682 {
683
684
685 aTextField.setEditable(true);
686 aTextField.setOpaque(true);
687
688
689
690 LookAndFeel.installBorder(aTextField, "TextField.border");
691
692 LookAndFeel.installColorsAndFont(aTextField,
693 "TextField.background",
694 "TextField.foreground",
695 "TextField.font");
696 }
697
698 private void areaToLabel( JTextArea aTextArea )
699 {
700
701
702 aTextArea.setLineWrap(true);
703 aTextArea.setWrapStyleWord(true);
704 aTextArea.setEditable(false);
705
706
707
708
709
710 aTextArea.setBorder( null );
711
712 LookAndFeel.installColorsAndFont(aTextArea,
713 "Label.background",
714 "Label.foreground",
715 "Label.font");
716
717 }
718
719 private void labelToArea( JTextArea aTextArea )
720 {
721
722
723 aTextArea.setEditable(true);
724
725
726
727 LookAndFeel.installBorder(aTextArea, "TextArea.border");
728
729 LookAndFeel.installColorsAndFont(aTextArea,
730 "TextArea.background",
731 "TextArea.foreground",
732 "TextArea.font");
733 }
734
735
736 /***
737 * Forces this association to cause the object to
738 * stop editing and validate the user's input.
739 * @return false if there were problems validating,
740 * or true to continue.
741 */
742 public boolean endEditing()
743 {
744 pleaseAcceptNextChange = true;
745 pleaseIgnoreNextChange = false;
746 return writeValueToDisplayGroup();
747 }
748
749 /***
750 * Writes the value currently in the component
751 * to the selected object in the display group
752 * bound to the value aspect.
753 * @return false if there were problems validating,
754 * or true to continue.
755 */
756 protected boolean writeValueToDisplayGroup()
757 {
758 boolean returnValue = true;
759 if ( hasDocument && !needsUpdate ) return true;
760
761 EODisplayGroup displayGroup = valueDisplayGroup;
762 if ( displayGroup != null )
763 {
764 String key = valueKey;
765 Object component = object();
766 Object value = null;
767 try
768 {
769
770
771 value = getText.invoke( component );
772
773 }
774 catch ( Exception exc )
775 {
776 throw new WotonomyException(
777 "Error updating display group", exc );
778 }
779
780 if ( ( wasNull ) && ( EMPTY_STRING.equals( value ) ) )
781 {
782 value = null;
783 }
784 else
785 if ( format() != null )
786 {
787 try
788 {
789 value = format().parseObject( value.toString() );
790 }
791 catch ( ParseException exc )
792 {
793 String message = exc.getMessage();
794
795 if ( displayGroup.associationFailedToValidateValue(
796 this, value.toString(), key, exc, message ) )
797 {
798 boolean wasListening = isListening;
799 isListening = false;
800 JOptionPane.showMessageDialog(
801 (Component)component, message );
802 isListening = wasListening;
803 }
804 needsUpdate = false;
805 return false;
806 }
807 }
808
809 if ( ( lastKnownType != null ) && ( value != null ) )
810 {
811
812 Class type = value.getClass();
813 if ( ( type != null ) && ( type != lastKnownType ) )
814 {
815 Object converted =
816 ValueConverter.convertObjectToClass(
817 value, lastKnownType );
818 if ( converted != null )
819 {
820 value = converted;
821 }
822
823 }
824 }
825
826 needsUpdate = false;
827
828
829 if ( ! needToWriteValueToDisplayGroup( value, displayGroup ) ) return true;
830
831
832 boolean wasListening = isListening;
833 isListening = false;
834
835 Iterator selectedIterator = displayGroup.selectionIndexes().iterator();
836 while ( selectedIterator.hasNext() )
837 {
838 int index = ( (Integer)selectedIterator.next() ).intValue();
839
840 if ( displayGroup.setValueForObjectAtIndex( value, index, key ) )
841 {
842 needsUpdate = false;
843 }
844 else
845 {
846 needsUpdate = false;
847 returnValue = false;
848 }
849 }
850 isListening = wasListening;
851
852 }
853 return returnValue;
854 }
855
856 /***
857 * Called to determine whether the display group needs to be
858 * updated. This implementation reads the value from the display
859 * group and only returns true if the specified value is different.
860 * This is done as an optimization since writes are more expensive
861 * than reads. Override to customize this behavior.
862 */
863 protected boolean needToWriteValueToDisplayGroup(
864 Object aValue, EODisplayGroup aDisplayGroup )
865 {
866 Object existingValue = aDisplayGroup.selectedObjectValueForKey( valueKey );
867 if ( aDisplayGroup.selectedObjects().size() == 1 )
868 {
869 if ( existingValue == aValue ) return false;
870 if ( ( existingValue != null ) && ( existingValue.equals( aValue ) ) ) return false;
871 if ( ( aValue != null ) && ( aValue.equals( existingValue ) ) ) return false;
872 }
873 return true;
874 }
875
876 /***
877 * Called to determine whether the controlled component needs to be
878 * updated. This implementation reads the value from the selector
879 * and only returns true if the specified value is different.
880 * This is done as an optimization since updating the component
881 * can be an expensive operation. Override to customize this behavior.
882 */
883 protected boolean needToReadValueFromDisplayGroup(
884 Object aValue, NSSelector aSelector )
885 throws IllegalAccessException, InvocationTargetException, NoSuchMethodException
886 {
887 return !aValue.toString().equals( aSelector.invoke( object() ) );
888 }
889
890 /***
891 * Sets the Format that is used to convert values from the display
892 * group to and from text that is displayed in the component.
893 */
894 public void setFormat( Format aFormat )
895 {
896 format = aFormat;
897 }
898
899 /***
900 * Gets the Format that is used to convert values from the display
901 * group to and from text that is displayed in the component.
902 */
903 public Format format()
904 {
905 return format;
906 }
907
908 /***
909 * Returns whether the text association is configured to actively
910 * update the model in response to changes in the component.
911 */
912 public boolean isActiveUpdate()
913 {
914 return activeUpdate;
915 }
916
917 /***
918 * Sets whether the text association should actively
919 * update the model in response to changes in the component.
920 * Default is true. False indicates that the model will be updated
921 * only when the component loses focus or fires an action event.
922 */
923 public void setActiveUpdate( boolean isActiveUpdate )
924 {
925 activeUpdate = isActiveUpdate;
926 }
927
928
929
930 /***
931 * Updates object on action performed.
932 */
933 public void actionPerformed( ActionEvent evt )
934 {
935 if ( ! isListening ) return;
936 if ( needsUpdate )
937 {
938 pleaseAcceptNextChange = true;
939 writeValueToDisplayGroup();
940 }
941 }
942
943
944
945 /***
946 * Notifies of beginning of edit.
947 */
948 public void focusGained(FocusEvent evt)
949 {
950 if ( ! isListening ) return;
951
952 pleaseAcceptNextChange = true;
953 externallyChanged = true;
954
955 Object o;
956 EODisplayGroup displayGroup;
957 Enumeration e = aspects().objectEnumerator();
958 while ( e.hasMoreElements() )
959 {
960 displayGroup =
961 displayGroupForAspect( e.nextElement().toString() );
962 if ( displayGroup != null )
963 {
964 displayGroup.associationDidBeginEditing( this );
965 }
966 }
967 }
968
969 /***
970 * Updates object on focus lost and notifies of end of edit.
971 */
972 public void focusLost(FocusEvent evt)
973 {
974 if ( ! isListening ) return;
975 if ( endEditing() )
976 {
977 Object o;
978 EODisplayGroup displayGroup;
979 Enumeration e = aspects().objectEnumerator();
980 while ( e.hasMoreElements() )
981 {
982 displayGroup =
983 displayGroupForAspect( e.nextElement().toString() );
984 if ( displayGroup != null )
985 {
986 displayGroup.associationDidEndEditing( this );
987 }
988 }
989 }
990 else
991 {
992
993 }
994 }
995
996 /***
997 * Queues a notification to PostWhenIdle.
998 */
999 protected void queueUpdate(DocumentEvent e)
1000 {
1001 if ( e.getDocument() instanceof DefaultStyledDocument )
1002 {
1003 if ( e instanceof AbstractDocument.DefaultDocumentEvent )
1004 {
1005 int docLength = e.getDocument().getLength();
1006
1007 if ( ( e.getType().equals( DocumentEvent.EventType.CHANGE ) ) )
1008 {
1009 if ( e.getOffset() == 0 && e.getLength() == docLength )
1010 {
1011
1012
1013
1014
1015
1016 return;
1017 }
1018 }
1019 }
1020 }
1021
1022 NSNotificationQueue.defaultQueue().enqueueNotification(
1023 new NSNotification( "TextAssociation.DocumentChanged", this,
1024 new NSDictionary( new Object[] { "event" }, new Object[] { e } ) ),
1025 NSNotificationQueue.PostWhenIdle );
1026 }
1027
1028 /***
1029 * Handles idle notification.
1030 */
1031 public void handleNotification( NSNotification aNotification )
1032 {
1033 if ( activeUpdate )
1034 {
1035 writeValueToDisplayGroup();
1036 }
1037 }
1038
1039
1040
1041 public void insertUpdate(DocumentEvent e)
1042 {
1043 if ( ! isListening ) return;
1044 needsUpdate = true;
1045 queueUpdate( e );
1046 }
1047
1048 public void removeUpdate(DocumentEvent e)
1049 {
1050 if ( ! isListening ) return;
1051 needsUpdate = true;
1052 queueUpdate( e );
1053 }
1054
1055 public void changedUpdate(DocumentEvent e)
1056 {
1057 if ( ! isListening ) return;
1058 needsUpdate = true;
1059 queueUpdate( e );
1060 }
1061
1062 }
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212