View Javadoc

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.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 		// prepopulate titles
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 		// titles aspect
226 		displayGroup = displayGroupForAspect( TitlesAspect );
227 		if ( displayGroup != null )
228 		{
229 			// if backing group has changed
230 			if ( displayGroup.contentsChanged() ) 
231 			{
232 				key = displayGroupKeyForAspect( TitlesAspect );
233 				populateTitles( displayGroup, key );
234 			}
235 		}
236 
237         // value aspect
238         populateValue();
239         
240 		// enabled aspect
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 		// value aspect
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 			// objects aspect
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 		// selected title aspect
371 		displayGroup = displayGroupForAspect( ValueAspect );
372 		if ( displayGroup != null )
373 		{
374 			key = displayGroupKeyForAspect( ValueAspect );
375 			Object value = null;
376 
377 			// selected object aspect, if any
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 // just use the selected item
403 			{
404 				value = component.getValue();	
405 			}
406 
407 			return displayGroup.setSelectedObjectValue( value, key );
408 		}
409 		
410 		return false;
411 	}
412         
413     // interface ActionListener
414 	
415 	/***
416 	* Updates object on action performed.
417 	*/
418 	public void actionPerformed( ActionEvent evt )
419 	{
420 		writeValueToDisplayGroup();
421 	}
422     	
423 	// convenience
424 
425 	private RadioButtonPanel component() 
426 	{
427 		return (RadioButtonPanel) object();
428 	}
429 }
430 
431 /*
432  * $Log$
433  * Revision 1.2  2006/02/18 23:19:05  cgruber
434  * Update imports and maven dependencies.
435  *
436  * Revision 1.1  2006/02/16 13:22:22  cgruber
437  * Check in all sources in eclipse-friendly maven-enabled packages.
438  *
439  * Revision 1.4  2004/01/28 18:34:57  mpowers
440  * Better handling for enabling.
441  * Now respecting enabledToSetSelectedObjectValueForKey from display group.
442  *
443  * Revision 1.3  2003/08/06 23:07:52  chochos
444  * general code cleanup (mostly, removing unused imports)
445  *
446  * Revision 1.2  2001/02/16 17:48:07  mpowers
447  * Populating titles or data not longer marks target object as changed.
448  *
449  * Revision 1.1.1.1  2000/12/21 15:48:52  mpowers
450  * Contributing wotonomy.
451  *
452  * Revision 1.3  2000/12/20 16:25:41  michael
453  * Added log to all files.
454  *
455  *
456  */
457