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.util.Calendar;
27 import java.util.Date;
28 import java.util.Enumeration;
29 import java.util.Iterator;
30
31 import net.wotonomy.foundation.NSArray;
32 import net.wotonomy.foundation.NSSelector;
33 import net.wotonomy.foundation.internal.ValueConverter;
34 import net.wotonomy.foundation.internal.WotonomyException;
35 import net.wotonomy.ui.EOAssociation;
36 import net.wotonomy.ui.EODisplayGroup;
37
38 /***
39 * DateAssociation binds any component that has a set and get Date methods and
40 * fire actions events when the date has been changed. Bindings are:
41 * <ul>
42 * <li>value: a property convertable to/from a date</li>
43 * <li>editable: a boolean property that determines whether
44 * the user can edit the date in the component</li>
45 * <li>enabled: a boolean property that determines whether
46 * the component is enabled or disabled</li>
47 * </ul>
48 *
49 * @author rob@yahoo.com
50 * @version $Revision: 904 $
51 */
52 public class DateAssociation extends EOAssociation
53 implements ActionListener, FocusListener
54 {
55 static final NSArray aspects =
56 new NSArray( new Object[] {
57 ValueAspect, EnabledAspect, EditableAspect
58 } );
59 static final NSArray aspectSignatures =
60 new NSArray( new Object[] {
61 AttributeToOneAspectSignature,
62 AttributeToOneAspectSignature,
63 AttributeToOneAspectSignature
64 } );
65 static final NSArray objectKeysTaken =
66 new NSArray( new Object[] {
67 "date", "enabled", "editable"
68 } );
69
70 private final static NSSelector getDate =
71 new NSSelector( "getDate" );
72 private final static NSSelector setDate =
73 new NSSelector( "setDate",
74 new Class[] { Date.class } );
75 private final static NSSelector addActionListener =
76 new NSSelector( "addActionListener",
77 new Class[] { ActionListener.class } );
78 private final static NSSelector removeActionListener =
79 new NSSelector( "removeActionListener",
80 new Class[] { ActionListener.class } );
81 private final static NSSelector addFocusListener =
82 new NSSelector( "addFocusListener",
83 new Class[] { FocusListener.class } );
84 private final static NSSelector removeFocusListener =
85 new NSSelector( "removeFocusListener",
86 new Class[] { FocusListener.class } );
87 private final static NSSelector setEditable =
88 new NSSelector( "setEditable",
89 new Class[] { boolean.class } );
90
91
92 private boolean needsUpdate;
93 private Date nullValue;
94
95 /***
96 * Constructor specifying the object to be controlled by this
97 * association. Does not establish connection.
98 */
99 public DateAssociation ( Object anObject )
100 {
101 super( anObject );
102 needsUpdate = false;
103 nullValue = null;
104 }
105
106 /***
107 * Returns a List of aspect signatures whose contents
108 * correspond with the aspects list. Each element is
109 * a string whose characters represent a capability of
110 * the corresponding aspect. <ul>
111 * <li>"A" attribute: the aspect can be bound to
112 * an attribute.</li>
113 * <li>"1" to-one: the aspect can be bound to a
114 * property that returns a single object.</li>
115 * <li>"M" to-one: the aspect can be bound to a
116 * property that returns multiple objects.</li>
117 * </ul>
118 * An empty signature "" means that the aspect can
119 * bind without needing a key.
120 * This implementation returns "A1M" for each
121 * element in the aspects array.
122 */
123 public static NSArray aspectSignatures ()
124 {
125 return aspectSignatures;
126 }
127
128 /***
129 * Returns a List that describes the aspects supported
130 * by this class. Each element in the list is the string
131 * name of the aspect. This implementation returns an
132 * empty list.
133 */
134 public static NSArray aspects ()
135 {
136 return aspects;
137 }
138
139 /***
140 * Returns a List of EOAssociation subclasses that,
141 * for the objects that are usable for this association,
142 * are less suitable than this association.
143 */
144 public static NSArray associationClassesSuperseded ()
145 {
146 return new NSArray();
147 }
148
149 /***
150 * Returns whether this class can control the specified
151 * object.
152 */
153 public static boolean isUsableWithObject ( Object anObject )
154 {
155 return setDate.implementedByObject( anObject );
156 }
157
158 /***
159 * Returns a List of properties of the controlled object
160 * that are controlled by this class. For example,
161 * "stringValue", or "selected".
162 */
163 public static NSArray objectKeysTaken ()
164 {
165 return objectKeysTaken;
166 }
167
168 /***
169 * Returns the aspect that is considered primary
170 * or default. This is typically "value" or somesuch.
171 */
172 public static String primaryAspect ()
173 {
174 return ValueAspect;
175 }
176
177 /***
178 * Returns whether this association can bind to the
179 * specified display group on the specified key for
180 * the specified aspect.
181 */
182 public boolean canBindAspect (
183 String anAspect, EODisplayGroup aDisplayGroup, String aKey)
184 {
185 return ( aspects.containsObject( anAspect ) );
186 }
187
188 /***
189 * Establishes a connection between this association
190 * and the controlled object. This implementation
191 * attempts to add this class as an ActionListener
192 * and Focus Listener to the specified object.
193 */
194 public void establishConnection ()
195 {
196 Object component = object();
197 try
198 {
199 if ( addActionListener.implementedByObject( component ) )
200 {
201 addActionListener.invoke( component, this );
202 }
203 if ( addFocusListener.implementedByObject( component ) )
204 {
205 addFocusListener.invoke( component, this );
206 }
207 }
208 catch ( Exception exc )
209 {
210 throw new WotonomyException(
211 "Error while establishing connection", exc );
212 }
213
214 super.establishConnection();
215
216
217 subjectChanged();
218 }
219
220 /***
221 * Breaks the connection between this association and
222 * its object. Override to stop listening for events
223 * from the object.
224 */
225 public void breakConnection ()
226 {
227 Object component = object();
228 try
229 {
230 if ( removeActionListener.implementedByObject( component ) )
231 {
232 removeActionListener.invoke( component, this );
233 }
234 if ( removeFocusListener.implementedByObject( component ) )
235 {
236 removeFocusListener.invoke( component, this );
237 }
238 }
239 catch ( Exception exc )
240 {
241 throw new WotonomyException(
242 "Error while breaking connection", exc );
243 }
244 super.breakConnection();
245 }
246
247 /***
248 * Called when either the selection or the contents
249 * of an associated display group have changed.
250 */
251 public void subjectChanged ()
252 {
253 Object component = object();
254 EODisplayGroup displayGroup;
255 String key;
256 Object value;
257
258
259 displayGroup = displayGroupForAspect( ValueAspect );
260 if ( displayGroup != null )
261 {
262 key = displayGroupKeyForAspect( ValueAspect );
263 if ( component instanceof Component )
264 {
265 ((Component)component).setEnabled(
266 displayGroup.enabledToSetSelectedObjectValueForKey( key ) );
267 }
268 if ( displayGroup.selectedObjects().size() > 1 )
269 {
270
271
272 Object previousValue;
273
274 Iterator indexIterator = displayGroup.selectionIndexes().
275 iterator();
276
277
278 int initialIndex = ( (Integer)indexIterator.next() ).intValue();
279 previousValue = displayGroup.valueForObjectAtIndex(
280 initialIndex, key );
281 value = null;
282
283
284
285
286
287
288 while ( indexIterator.hasNext() )
289 {
290 int index = ( (Integer)indexIterator.next() ).intValue();
291 Object currentValue = displayGroup.valueForObjectAtIndex(
292 index, key );
293 if ( currentValue != null && !currentValue.equals( previousValue ) )
294 {
295 value = null;
296 break;
297 }
298 else
299 {
300
301 value = currentValue;
302 }
303
304 }
305
306 } else {
307
308 value = displayGroup.selectedObjectValueForKey( key );
309 }
310
311
312 try
313 {
314 Date dateValue = null;
315
316
317 if ( value instanceof Date )
318 {
319 dateValue = (Date) value;
320 }
321
322 if ( ( dateValue == null ) && ( value instanceof Calendar ) )
323 {
324 dateValue = ( ( Calendar )value ).getTime();
325 }
326
327 if ( dateValue == null )
328 {
329
330 nullValue = new Date();
331 dateValue = nullValue;
332 }
333 else
334 {
335 nullValue = null;
336 }
337
338 if ( !dateValue.equals( getDate.invoke( component ) ) )
339 {
340 setDate.invoke( component, dateValue );
341 needsUpdate = false;
342 }
343
344 }
345 catch ( Exception exc )
346 {
347 throw new WotonomyException(
348 "Error while updating component connection", exc );
349 }
350 }
351
352
353 displayGroup = displayGroupForAspect( EnabledAspect );
354 key = displayGroupKeyForAspect( EnabledAspect );
355 if ( ( ( displayGroup != null ) || ( key != null ) )
356 && ( component instanceof Component ) )
357 {
358 if ( displayGroup != null )
359 {
360 value =
361 displayGroup.selectedObjectValueForKey( key );
362 }
363 else
364 {
365
366 value = key;
367 }
368 Boolean converted = null;
369 if ( value != null )
370 {
371 converted = (Boolean)
372 ValueConverter.convertObjectToClass(
373 value, Boolean.class );
374 }
375 if ( converted == null ) converted = Boolean.FALSE;
376 if ( converted.booleanValue() != ((Component)component).isEnabled() )
377 {
378 ((Component)component).setEnabled( converted.booleanValue() );
379 }
380 }
381
382
383 displayGroup = displayGroupForAspect( EditableAspect );
384 key = displayGroupKeyForAspect( EditableAspect );
385 if ( ( ( displayGroup != null ) || ( key != null ) )
386 && ( setEditable.implementedByObject( component ) ) )
387 {
388 try
389 {
390 if ( displayGroup != null )
391 {
392 value =
393 displayGroup.selectedObjectValueForKey( key );
394 }
395 else
396 {
397
398 value = key;
399 }
400 Boolean converted = (Boolean)
401 ValueConverter.convertObjectToClass(
402 value, Boolean.class );
403
404 if ( converted != null )
405 {
406 setEditable.invoke( component, converted );
407 }
408 }
409 catch ( Exception exc )
410 {
411 throw new WotonomyException(
412 "Error while updating component connection (editable aspect)", exc );
413 }
414 }
415 }
416
417 /***
418 * Forces this association to cause the object to
419 * stop editing and validate the user's input.
420 * @return false if there were problems validating,
421 * or true to continue.
422 */
423 public boolean endEditing ()
424 {
425 return writeValueToDisplayGroup();
426 }
427
428 /***
429 * Writes the value currently in the component
430 * to the selected object in the display group
431 * bound to the value aspect.
432 * @return false if there were problems validating,
433 * or true to continue.
434 */
435 protected boolean writeValueToDisplayGroup()
436 {
437 if ( !needsUpdate ) return true;
438
439 EODisplayGroup displayGroup =
440 displayGroupForAspect( ValueAspect );
441 if ( displayGroup != null )
442 {
443 String key = displayGroupKeyForAspect( ValueAspect );
444 Object component = object();
445 Object value = null;
446 try
447 {
448 if ( getDate.implementedByObject( component ) )
449 {
450 value = getDate.invoke( component );
451 }
452 if ( nullValue != null )
453 {
454 if ( nullValue.equals( value ) )
455 {
456 value = null;
457 }
458 }
459 }
460 catch ( Exception exc )
461 {
462 throw new WotonomyException(
463 "Error updating display group", exc );
464 }
465
466 needsUpdate = false;
467
468 boolean returnValue = true;
469 Iterator selectedIterator = displayGroup.selectionIndexes().iterator();
470 while ( selectedIterator.hasNext() )
471 {
472 int index = ( (Integer)selectedIterator.next() ).intValue();
473
474 if ( !displayGroup.setValueForObjectAtIndex( value, index, key ) )
475 {
476 returnValue = false;
477 }
478 }
479 return returnValue;
480
481 }
482 return false;
483 }
484
485
486
487 /***
488 * Updates object on action performed.
489 */
490 public void actionPerformed( ActionEvent evt )
491 {
492 needsUpdate = true;
493 writeValueToDisplayGroup();
494 }
495
496
497
498 /***
499 * Notifies of beginning of edit.
500 */
501 public void focusGained(FocusEvent evt)
502 {
503 Object o;
504 EODisplayGroup displayGroup;
505 Enumeration e = aspects().objectEnumerator();
506 while ( e.hasMoreElements() )
507 {
508 displayGroup =
509 displayGroupForAspect( e.nextElement().toString() );
510 if ( displayGroup != null )
511 {
512 displayGroup.associationDidBeginEditing( this );
513 }
514 }
515 }
516
517 /***
518 * Updates object on focus lost and notifies of end of edit.
519 */
520 public void focusLost(FocusEvent evt)
521 {
522 if ( endEditing() )
523 {
524 Object o;
525 EODisplayGroup displayGroup;
526 Enumeration e = aspects().objectEnumerator();
527 while ( e.hasMoreElements() )
528 {
529 displayGroup =
530 displayGroupForAspect( e.nextElement().toString() );
531 if ( displayGroup != null )
532 {
533 displayGroup.associationDidEndEditing( this );
534 }
535 }
536 }
537 else
538 {
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573 }
574 }
575 }
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613