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.Component;
22  import java.util.Iterator;
23  
24  import javax.swing.ButtonModel;
25  import javax.swing.event.ChangeEvent;
26  import javax.swing.event.ChangeListener;
27  
28  import net.wotonomy.foundation.NSArray;
29  import net.wotonomy.foundation.NSSelector;
30  import net.wotonomy.foundation.internal.ValueConverter;
31  import net.wotonomy.foundation.internal.WotonomyException;
32  import net.wotonomy.ui.EOAssociation;
33  import net.wotonomy.ui.EODisplayGroup;
34  
35  /***
36  * ButtonAssociation binds any component that uses a ButtonModel
37  * (all Swing button classes) to a display group.  This association
38  * should be used to handle individual JRadioButtons and JCheckBoxes.
39  * Bindings are:
40  * <ul>
41  * <li>value: a boolean property that determines the
42  * selected state of the button model.  This will set
43  * the value for radio buttons and check boxes.</li>
44  * <li>enabled: a boolean property that determines the
45  * enabled state of the button model.</li>
46  * <li>visible: a boolean property that determines the
47  * visible state of the button model.</li>
48  * </ul>
49  *
50  * @author michael@mpowers.net
51  * @author $Author: cgruber $
52  * @version $Revision: 904 $
53  */
54  public class ButtonAssociation extends EOAssociation
55      implements ChangeListener
56  {
57      static final NSArray aspects =
58          new NSArray( new Object[] {
59              ValueAspect, EnabledAspect, VisibleAspect
60          } );
61      static final NSArray aspectSignatures =
62          new NSArray( new Object[] {
63              AttributeToOneAspectSignature,
64              AttributeToOneAspectSignature,
65              AttributeToOneAspectSignature
66          } );
67      static final NSArray objectKeysTaken =
68          new NSArray( new Object[] {
69              "model.selected"
70          } );
71  
72      static NSSelector getModel =
73          new NSSelector( "getModel", new Class[] {} );
74  
75      protected ButtonModel buttonModel;
76      protected boolean lastKnownValue;
77  
78      /***
79      * Constructor specifying the object to be controlled by this
80      * association.  Does not establish connection.
81      * This implementation expects a ButtonModel or a class
82      * that has a "getModel" method that returns a ButtonModel.
83      */
84      public ButtonAssociation ( Object anObject )
85      {
86          super( anObject );
87  
88          if ( anObject instanceof ButtonModel )
89          {
90              buttonModel = (ButtonModel) anObject;
91          }
92          else
93          {
94              try
95              {
96                  buttonModel = (ButtonModel) getModel.invoke( anObject );
97              }
98              catch ( Exception exc )
99              {
100                 throw new WotonomyException( "EOButtonAssociation: " +
101                     "could not retrieve a button model from object:" + anObject );
102             }
103         }
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     * This implementation expects a ButtonModel or a class
153     * that has a "getModel" method that returns a ButtonModel.
154     */
155     public static boolean isUsableWithObject ( Object anObject )
156     {
157         return
158            ( anObject instanceof ButtonModel )
159         || ( getModel.implementedByObject( anObject ) );
160     }
161 
162     /***
163     * Returns a List of properties of the controlled object
164     * that are controlled by this class.  For example,
165     * "stringValue", or "selected".
166     */
167     public static NSArray objectKeysTaken ()
168     {
169         return objectKeysTaken;
170     }
171 
172     /***
173     * Returns the aspect that is considered primary
174     * or default.
175     */
176     public static String primaryAspect ()
177     {
178         return ValueAspect;
179     }
180 
181     /***
182     * Returns whether this association can bind to the
183     * specified display group on the specified key for
184     * the specified aspect.
185     */
186     public boolean canBindAspect (
187         String anAspect, EODisplayGroup aDisplayGroup, String aKey)
188     {
189         return ( aspects.containsObject( anAspect ) );
190     }
191 
192     /***
193     * Establishes a connection between this association
194     * and the controlled object.  Subclasses should begin
195     * listening for events from their controlled object here.
196     */
197     public void establishConnection ()
198     {
199         buttonModel.addChangeListener( this );
200         super.establishConnection();
201         subjectChanged();
202     }
203 
204     /***
205     * Breaks the connection between this association and
206     * its object.  Override to stop listening for events
207     * from the object.
208     */
209     public void breakConnection ()
210     {
211         buttonModel.removeChangeListener( this );
212         super.breakConnection();
213     }
214 
215     /***
216     * Called when either the selection or the contents
217     * of an associated display group have changed.
218     * This implementation does nothing.
219     */
220     public void subjectChanged ()
221     {
222         Object component = object();
223         EODisplayGroup displayGroup;
224         String key;
225 
226         // value aspect
227         displayGroup = displayGroupForAspect( ValueAspect );
228 
229         if ( displayGroup != null )
230         {
231             key = displayGroupKeyForAspect( ValueAspect );
232             if ( component instanceof Component )
233             {
234                 ((Component)component).setEnabled( 
235                         displayGroup.enabledToSetSelectedObjectValueForKey( key ) );
236             }
237             
238             Object value;
239             if ( displayGroup.selectedObjects().size() > 1 )
240             {
241                 // if there're more than one object selected, set
242                 // the value to blank for all of them.
243                 Object previousValue;
244 
245                 Iterator indexIterator = displayGroup.selectionIndexes().
246                                           iterator();
247 
248                 // get value for the first selected object.
249                 int initialIndex = ( (Integer)indexIterator.next() ).intValue();
250                 previousValue = displayGroup.valueForObjectAtIndex(
251                                           initialIndex, key );
252                 value = null;
253 
254                 // go through the rest of the selected objects, compare each
255                 // value with the previous one. continue comparing if two
256                 // values are equal, break the while loop if they're different.
257                 // the final value will be the common value of all selected objects
258                 // if there is one, or be blank if there is not.
259                 while ( indexIterator.hasNext() )
260                 {
261                     int index = ( (Integer)indexIterator.next() ).intValue();
262                     Object currentValue = displayGroup.valueForObjectAtIndex(
263                                           index, key );
264                     if ( currentValue != null && !currentValue.equals( previousValue ) )
265                     {
266                         value = null;
267                         break;
268                     }
269                     else
270                     {
271                         // currentValue is the same as the previous one
272                         value = currentValue;
273                     }
274 
275                 } // end while
276 
277             } 
278             else // displayGroup has only one object 
279             {
280                 value =
281                     displayGroup.selectedObjectValueForKey( key );
282             } // end checking size of displayGroup
283 
284             buttonModel.setArmed( false );
285             buttonModel.setPressed( false );
286             
287             if ( value != null )
288             {
289                 Boolean converted = (Boolean)
290                     ValueConverter.convertObjectToClass(
291                         value, Boolean.class );
292                 if ( converted != null )
293                 {
294                     lastKnownValue = converted.booleanValue();
295                     if ( converted.booleanValue() !=
296                         buttonModel.isSelected() )
297                     {
298                         buttonModel.removeChangeListener( this );
299                         buttonModel.setSelected(
300                             converted.booleanValue() );
301                         buttonModel.addChangeListener( this );
302                     }
303                 } // end checking converted  == null
304             }
305             else
306             {
307                 buttonModel.setArmed( true );
308                 buttonModel.setPressed( true );
309             }
310         }
311 
312         // enabled aspect
313         displayGroup = displayGroupForAspect( EnabledAspect );
314         if ( displayGroup != null )
315         {
316             key = displayGroupKeyForAspect( EnabledAspect );
317             Object value =
318                 displayGroup.selectedObjectValueForKey( key );
319             Boolean converted = null;
320             if ( value != null ) 
321             {
322                 converted = (Boolean)
323                     ValueConverter.convertObjectToClass(
324                         value, Boolean.class );
325             }
326             if ( converted == null ) converted = Boolean.FALSE;
327             if ( converted.booleanValue() !=
328                 buttonModel.isEnabled() )
329             {
330                 buttonModel.removeChangeListener( this );
331                 buttonModel.setEnabled(
332                     converted.booleanValue() );
333                 buttonModel.addChangeListener( this );
334             }
335         }
336 
337         // visible aspect
338         displayGroup = displayGroupForAspect( VisibleAspect );
339         if ( displayGroup != null )
340         {
341             key = displayGroupKeyForAspect( VisibleAspect );
342             Object value =
343                 displayGroup.selectedObjectValueForKey( key );
344             Boolean converted = (Boolean)
345                 ValueConverter.convertObjectToClass(
346                     value, Boolean.class );
347             if ( converted != null )
348             {
349                 if ( converted.booleanValue() !=
350                     ((Component)component).isVisible() )
351                 {
352                     ((Component)component).setVisible(
353                         converted.booleanValue() );
354                 }
355             }
356         }
357     }
358 
359     /***
360     * Writes the value currently in the component
361     * to the selected object in the display group
362     * bound to the value aspect.
363     * @return false if there were problems validating,
364     * or true to continue.
365     */
366     protected boolean writeValueToDisplayGroup()
367     {
368         EODisplayGroup displayGroup =
369             displayGroupForAspect( ValueAspect );
370         if ( displayGroup != null )
371         {
372             boolean returnValue = true;
373             String key = displayGroupKeyForAspect( ValueAspect );
374             Object value = new Boolean( buttonModel.isSelected() );
375 
376             Iterator selectedIterator = displayGroup.selectionIndexes().iterator();
377             while ( selectedIterator.hasNext() )
378             {
379                 int index = ( (Integer)selectedIterator.next() ).intValue();
380 
381                 if ( !displayGroup.setValueForObjectAtIndex( value, index, key ) )
382                 {
383                     returnValue = false;
384                 }
385             }
386             return returnValue;
387 
388         }
389         return false;
390     }
391     // interface ChangeListener
392 
393     public void stateChanged(ChangeEvent e)
394     {
395         if ( buttonModel.isSelected() != lastKnownValue )
396         {
397             lastKnownValue = buttonModel.isSelected();
398             writeValueToDisplayGroup();
399         }
400     }
401 
402 }
403 
404 /*
405  * $Log$
406  * Revision 1.2  2006/02/18 23:19:05  cgruber
407  * Update imports and maven dependencies.
408  *
409  * Revision 1.1  2006/02/16 13:22:22  cgruber
410  * Check in all sources in eclipse-friendly maven-enabled packages.
411  *
412  * Revision 1.8  2004/01/28 18:34:57  mpowers
413  * Better handling for enabling.
414  * Now respecting enabledToSetSelectedObjectValueForKey from display group.
415  *
416  * Revision 1.7  2003/08/06 23:07:52  chochos
417  * general code cleanup (mostly, removing unused imports)
418  *
419  * Revision 1.6  2001/07/30 16:32:55  mpowers
420  * Implemented support for bulk-editing.  Detail associations will now
421  * apply changes to all selected objects.
422  *
423  * Revision 1.5  2001/06/29 22:28:19  mpowers
424  * Tabs to spaces.
425  *
426  * Revision 1.4  2001/06/29 22:17:31  mpowers
427  * Now updating the component on establishConnection.
428  *
429  * Revision 1.3  2001/02/27 02:10:38  mpowers
430  * No longer updating values to the display group if the value
431  * has not changed.
432  *
433  * Revision 1.2  2001/02/21 20:33:01  mpowers
434  * Fixed bug with change listener.
435  *
436  * Revision 1.1.1.1  2000/12/21 15:48:38  mpowers
437  * Contributing wotonomy.
438  *
439  * Revision 1.3  2000/12/20 16:25:40  michael
440  * Added log to all files.
441  *
442  *
443  */
444