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.event.ActionEvent;
22 import java.awt.event.ActionListener;
23
24 import net.wotonomy.foundation.NSArray;
25 import net.wotonomy.foundation.internal.ValueConverter;
26 import net.wotonomy.ui.EOAssociation;
27 import net.wotonomy.ui.EODisplayGroup;
28 import net.wotonomy.ui.swing.components.RadioButtonPanel;
29
30 /***
31 * RadioPanelAssociation binds RadioButtonPanels to
32 * display groups. It works exactly like a
33 * ComboBoxAssociation. Bindings are:
34 * <ul>
35 *
36 * <li>value: a property of the selected object in the
37 * display group that will be bind to the item the user
38 * selects or the text that the user enters in the field.</li>
39 *
40 * <li>titles: a property of the objects in the bound
41 * display group that will appear in the list. If the
42 * objects aspect is not bound, this property is also
43 * used to populate the value binding.</li>
44 *
45 * <li>objects: optional - if specified, when the user
46 * selects an title in the list, the property of the
47 * object at the corresponding index of the bound display
48 * group will be used to populate the value binding.</li>
49 *
50 * <li>enabled: a boolean property of the selected object in the
51 * display group that determines whether
52 * the user can edit the field.</li>
53 *
54 * </ul>
55 *
56 * @author michael@mpowers.net
57 * @author $Author: cgruber $
58 * @version $Revision: 904 $
59 */
60 public class RadioPanelAssociation extends EOAssociation
61 implements ActionListener
62 {
63 static final NSArray aspects =
64 new NSArray( new Object[] {
65 TitlesAspect, ValueAspect,
66 ObjectsAspect, EnabledAspect
67 } );
68 static final NSArray aspectSignatures =
69 new NSArray( new Object[] {
70 AttributeToOneAspectSignature,
71 AttributeToOneAspectSignature,
72 AttributeToOneAspectSignature,
73 AttributeToOneAspectSignature
74 } );
75 static final NSArray objectKeysTaken =
76 new NSArray( new Object[] {
77 "text"
78 } );
79
80 /***
81 * Constructor specifying the object to be controlled by this
82 * association. Does not establish connection.
83 */
84 public RadioPanelAssociation ( Object anObject )
85 {
86 super( anObject );
87 }
88
89 /***
90 * Returns a List of aspect signatures whose contents
91 * correspond with the aspects list. Each element is
92 * a string whose characters represent a capability of
93 * the corresponding aspect. <ul>
94 * <li>"A" attribute: the aspect can be bound to
95 * an attribute.</li>
96 * <li>"1" to-one: the aspect can be bound to a
97 * property that returns a single object.</li>
98 * <li>"M" to-one: the aspect can be bound to a
99 * property that returns multiple objects.</li>
100 * </ul>
101 * An empty signature "" means that the aspect can
102 * bind without needing a key.
103 * This implementation returns "A1M" for each
104 * element in the aspects array.
105 */
106 public static NSArray aspectSignatures ()
107 {
108 return aspectSignatures;
109 }
110
111 /***
112 * Returns a List that describes the aspects supported
113 * by this class. Each element in the list is the string
114 * name of the aspect. This implementation returns an
115 * empty list.
116 */
117 public static NSArray aspects ()
118 {
119 return aspects;
120 }
121
122 /***
123 * Returns a List of EOAssociation subclasses that,
124 * for the objects that are usable for this association,
125 * are less suitable than this association.
126 */
127 public static NSArray associationClassesSuperseded ()
128 {
129 return new NSArray();
130 }
131
132 /***
133 * Returns whether this class can control the specified
134 * object.
135 */
136 public static boolean isUsableWithObject ( Object anObject )
137 {
138 return ( anObject instanceof RadioButtonPanel );
139 }
140
141 /***
142 * Returns a List of properties of the controlled object
143 * that are controlled by this class. For example,
144 * "stringValue", or "selected".
145 */
146 public static NSArray objectKeysTaken ()
147 {
148 return objectKeysTaken;
149 }
150
151 /***
152 * Returns the aspect that is considered primary
153 * or default. This is typically "value" or somesuch.
154 */
155 public static String primaryAspect ()
156 {
157 return ValueAspect;
158 }
159
160 /***
161 * Returns whether this association can bind to the
162 * specified display group on the specified key for
163 * the specified aspect.
164 */
165 public boolean canBindAspect (
166 String anAspect, EODisplayGroup aDisplayGroup, String aKey)
167 {
168 return ( aspects.containsObject( anAspect ) );
169 }
170
171 /***
172 * Establishes a connection between this association
173 * and the controlled object. Subclasses should begin
174 * listening for events from their controlled object here.
175 */
176 public void establishConnection ()
177 {
178 super.establishConnection();
179
180
181 EODisplayGroup displayGroup =
182 displayGroupForAspect( TitlesAspect );
183 if ( displayGroup != null )
184 {
185 String key = displayGroupKeyForAspect( TitlesAspect );
186 populateTitles( displayGroup, key );
187 }
188 populateValue();
189 addAsListener();
190 }
191
192 protected void addAsListener()
193 {
194 component().addActionListener( this );
195 }
196
197 /***
198 * Breaks the connection between this association and
199 * its object. Override to stop listening for events
200 * from the object.
201 */
202 public void breakConnection ()
203 {
204 removeAsListener();
205 super.breakConnection();
206 }
207
208 protected void removeAsListener()
209 {
210 component().removeActionListener( this );
211 }
212
213 /***
214 * Called when either the selection or the contents
215 * of an associated display group have changed.
216 */
217 public void subjectChanged ()
218 {
219 removeAsListener();
220
221 RadioButtonPanel component = component();
222 EODisplayGroup displayGroup;
223 String key;
224
225
226 displayGroup = displayGroupForAspect( TitlesAspect );
227 if ( displayGroup != null )
228 {
229
230 if ( displayGroup.contentsChanged() )
231 {
232 key = displayGroupKeyForAspect( TitlesAspect );
233 populateTitles( displayGroup, key );
234 }
235 }
236
237
238 populateValue();
239
240
241 displayGroup = displayGroupForAspect( EnabledAspect );
242 if ( displayGroup != null )
243 {
244 key = displayGroupKeyForAspect( EnabledAspect );
245 Object value =
246 displayGroup.selectedObjectValueForKey( key );
247 Boolean converted = null;
248 if ( value != null )
249 {
250 converted = (Boolean)
251 ValueConverter.convertObjectToClass(
252 value, Boolean.class );
253 }
254 if ( converted == null ) converted = Boolean.FALSE;
255 if ( converted.booleanValue() != component.isEnabled() )
256 {
257 component.setEnabled( converted.booleanValue() );
258 }
259 }
260
261 addAsListener();
262 }
263
264 /***
265 * Called to repopulate the title list from the
266 * specified display group.
267 */
268 protected void populateTitles(
269 EODisplayGroup displayGroup, String key )
270 {
271 Object value;
272 int count = displayGroup.displayedObjects().count();
273 String[] titles = new String[ count ];
274 for ( int i = 0; i < count; i++ )
275 {
276 value = displayGroup.valueForObjectAtIndex( i, key );
277 if ( value != null )
278 {
279 titles[i] = value.toString();
280 }
281 else
282 {
283 titles[i] = "";
284 }
285 }
286 component().setLabels( titles );
287 }
288
289 /***
290 * Called to populate the value from the display group.
291 */
292 protected void populateValue()
293 {
294 RadioButtonPanel component = component();
295 EODisplayGroup displayGroup;
296 String key;
297
298
299 displayGroup = displayGroupForAspect( ValueAspect );
300 if ( displayGroup != null )
301 {
302 key = displayGroupKeyForAspect( ValueAspect );
303 component.setEnabled(
304 displayGroup.enabledToSetSelectedObjectValueForKey( key ) );
305
306 Object value = displayGroup.selectedObjectValueForKey( key );
307
308
309 EODisplayGroup objectsDisplayGroup =
310 displayGroupForAspect( ObjectsAspect );
311 if ( ( objectsDisplayGroup != null ) && ( value != null ) )
312 {
313 String objectKey = displayGroupKeyForAspect( ObjectsAspect );
314 Object match;
315 int index = NSArray.NotFound;
316 int count = objectsDisplayGroup.displayedObjects().count();
317 for ( int i = 0; i < count; i++ )
318 {
319 match = objectsDisplayGroup.valueForObjectAtIndex( i, objectKey );
320 if ( value.equals( match ) )
321 {
322 index = i;
323 }
324 }
325 if ( index == NSArray.NotFound )
326 {
327 if ( component.getValue() != null )
328 {
329 component.setValue( null );
330 }
331 }
332 else
333 {
334 String[] titles = component().getLabels();
335 component.setValue( titles[ index ] );
336 }
337 }
338 else
339 {
340 if ( value != null ) value = value.toString();
341 component.setValue( (String) value );
342 }
343 }
344 }
345
346 /***
347 * Forces this association to cause the object to
348 * stop editing and validate the user's input.
349 * @return false if there were problems validating,
350 * or true to continue.
351 */
352 public boolean endEditing ()
353 {
354 return writeValueToDisplayGroup();
355 }
356
357 /***
358 * Writes the value currently in the component
359 * to the selected object in the display group
360 * bound to the value aspect.
361 * @return false if there were problems validating,
362 * or true to continue.
363 */
364 protected boolean writeValueToDisplayGroup()
365 {
366 RadioButtonPanel component = component();
367 EODisplayGroup displayGroup;
368 String key;
369
370
371 displayGroup = displayGroupForAspect( ValueAspect );
372 if ( displayGroup != null )
373 {
374 key = displayGroupKeyForAspect( ValueAspect );
375 Object value = null;
376
377
378 EODisplayGroup objectsGroup =
379 displayGroupForAspect( ObjectsAspect );
380 if ( objectsGroup != null )
381 {
382 String objectKey = displayGroupKeyForAspect( ObjectsAspect );
383 String selectedValue = component.getValue();
384 if ( selectedValue != null )
385 {
386 String[] titles = component.getLabels();
387 int index = -1;
388 for ( int i = 0; i < titles.length; i++ )
389 {
390 if ( selectedValue.equals( titles[i] ) )
391 {
392 index = i;
393 }
394 }
395 if ( index != -1 )
396 {
397 value = objectsGroup
398 .valueForObjectAtIndex( index, objectKey );
399 }
400 }
401 }
402 else
403 {
404 value = component.getValue();
405 }
406
407 return displayGroup.setSelectedObjectValue( value, key );
408 }
409
410 return false;
411 }
412
413
414
415 /***
416 * Updates object on action performed.
417 */
418 public void actionPerformed( ActionEvent evt )
419 {
420 writeValueToDisplayGroup();
421 }
422
423
424
425 private RadioButtonPanel component()
426 {
427 return (RadioButtonPanel) object();
428 }
429 }
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457