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.util.Enumeration;
22  
23  import javax.swing.DefaultListModel;
24  import javax.swing.JList;
25  import javax.swing.SwingUtilities;
26  import javax.swing.event.ListDataEvent;
27  import javax.swing.event.ListDataListener;
28  import javax.swing.event.ListSelectionEvent;
29  import javax.swing.event.ListSelectionListener;
30  
31  import net.wotonomy.foundation.NSArray;
32  import net.wotonomy.foundation.NSMutableArray;
33  import net.wotonomy.ui.EOAssociation;
34  import net.wotonomy.ui.EODisplayGroup;
35  
36  /***
37  * TextAssociation binds a JList to a display group's
38  * list of displayable objects.  Bindings are: 
39  * <ul>
40  * <li>titles: a property convertable to a string for
41  * display in the cells of the list</li> 
42  * </ul>
43  *
44  * @author michael@mpowers.net
45  * @author $Author: cgruber $
46  * @version $Revision: 904 $
47  */
48  public class ListAssociation extends EOAssociation
49                            implements ListSelectionListener,
50                                       ListDataListener
51  {
52      static final NSArray aspects = 
53          new NSArray( new Object[] {
54              TitlesAspect
55          } );
56      static final NSArray aspectSignatures = 
57          new NSArray( new Object[] {
58              AttributeToOneAspectSignature
59          } );
60      static final NSArray objectKeysTaken = 
61          new NSArray( new Object[] {
62              "items" 
63          } );
64  		
65      protected DefaultListModel model;
66  
67      /***
68      * Constructor expecting a JList. Throws an
69  	* exception if it doesn't receive one.
70  	* Note: This sets the JList's model to a DefaultListModel.
71      */
72      public ListAssociation ( Object anObject )
73      {
74          super( anObject );
75          model = new DefaultListModel();
76          ((JList)anObject).setModel( model );
77      }
78      
79      /***
80      * Returns a List of aspect signatures whose contents
81      * correspond with the aspects list.  Each element is 
82      * a string whose characters represent a capability of
83      * the corresponding aspect. <ul>
84      * <li>"A" attribute: the aspect can be bound to
85      * an attribute.</li>
86      * <li>"1" to-one: the aspect can be bound to a
87      * property that returns a single object.</li>
88      * <li>"M" to-one: the aspect can be bound to a
89      * property that returns multiple objects.</li>
90      * </ul> 
91      * An empty signature "" means that the aspect can
92      * bind without needing a key.
93      * This implementation returns "A1M" for each
94      * element in the aspects array.
95      */
96      public static NSArray aspectSignatures ()
97      {
98          return aspectSignatures;
99      }
100     
101     /***
102     * Returns a List that describes the aspects supported
103     * by this class.  Each element in the list is the string
104     * name of the aspect.  This implementation returns an
105     * empty list.
106     */
107     public static NSArray aspects ()
108     {
109         return aspects;
110     }
111     
112     /***
113     * Returns a List of EOAssociation subclasses that,
114     * for the objects that are usable for this association,
115     * are less suitable than this association.
116     */
117     public static NSArray associationClassesSuperseded ()
118     {
119         return new NSArray();
120     }
121     
122     /***
123     * Returns whether this class can control the specified 
124     * object. 
125     */
126     public static boolean isUsableWithObject ( Object anObject )
127     {
128         return ( anObject instanceof JList );
129     }
130     
131     /***
132     * Returns a List of properties of the controlled object
133     * that are controlled by this class.  For example,
134     * "stringValue", or "selected".
135     */
136     public static NSArray objectKeysTaken ()
137     {
138         return objectKeysTaken;
139     }
140     
141     /***
142     * Returns the aspect that is considered primary
143     * or default.  This is typically "value" or somesuch.
144     */
145     public static String primaryAspect ()
146     {
147         return TitlesAspect;
148     }
149         
150     /***
151     * Returns whether this association can bind to the
152     * specified display group on the specified key for
153     * the specified aspect.  
154     */
155     public boolean canBindAspect ( 
156         String anAspect, EODisplayGroup aDisplayGroup, String aKey)
157     {
158         return ( aspects.containsObject( anAspect ) );
159     }
160     
161     /***
162     * Establishes a connection between this association
163     * and the controlled object.  Subclasses should begin
164     * listening for events from their controlled object here.
165     */
166     public void establishConnection ()
167     {
168         addAsListener();
169         super.establishConnection();
170 		populateFromDisplayGroup(); 
171         selectFromDisplayGroup();
172     }
173     
174     /***
175     * Breaks the connection between this association and 
176     * its object.  Override to stop listening for events
177     * from the object.
178     */
179     public void breakConnection ()
180     {
181         removeAsListener();
182         super.breakConnection();
183     }
184 
185     protected void addAsListener()
186     { // System.out.println( "ListAssociation.addAsListener: " + ++count );
187         component().getModel().addListDataListener( this );
188         component().addListSelectionListener( this );
189     }
190 
191     protected void removeAsListener()
192     { // System.out.println( "ListAssociation.removeAsListener: " + --count );
193         component().getModel().removeListDataListener( this );
194         component().removeListSelectionListener( this );
195     }
196 
197     /***
198     * Called when either the selection or the contents 
199     * of an associated display group have changed.
200     */
201     public void subjectChanged ()
202     {
203 		EODisplayGroup displayGroup;
204 	
205 		// titles aspect
206 		displayGroup = displayGroupForAspect( TitlesAspect );
207 		if ( displayGroup != null )
208 		{
209 //System.out.println( "subjectChanged: " + 
210 //displayGroup.contentsChanged() + " : " + displayGroup.selectionChanged() 
211 //+ " : " + displayGroup.updatedObjectIndex() );
212 //new net.wotonomy.ui.swing.util.StackTraceInspector();
213 			if ( displayGroup.contentsChanged() )
214 			{
215 				populateFromDisplayGroup();
216 			}
217 
218 			if ( displayGroup.selectionChanged() )
219 			{
220 				selectFromDisplayGroup();
221 			}
222 		}
223 
224     }
225 	
226 	private void populateFromDisplayGroup()
227 	{
228 		JList component = component();
229 		EODisplayGroup displayGroup = displayGroupForAspect( TitlesAspect );
230 		String key = displayGroupKeyForAspect( TitlesAspect );
231 			
232 		removeAsListener();
233 		
234 		// remember selection
235 		int[] selectedIndices = component().getSelectedIndices();
236 		
237 		// clear the model
238 		model.removeAllElements();
239 
240 		// populate the model
241 		Object value;
242 		int size = displayGroup.displayedObjects().count();
243 //System.out.println( "populateFromDisplayGroup: " + size );
244 		for ( int i = 0; i < size; i++ )
245 		{
246 			value = displayGroup.valueForObjectAtIndex( i, key );
247 			if ( value == null ) value = "[null]";
248 			model.addElement( value );
249 		}
250 	
251 		// select the same indexes
252 		for ( int i = 0; i < selectedIndices.length; i++ )
253 		{
254 			component.addSelectionInterval(
255 				selectedIndices[i], selectedIndices[i] ); // adds one row
256 		}
257 
258 		addAsListener();
259 	}
260             
261 	private void selectFromDisplayGroup()
262 	{
263 		JList component = component();
264 		EODisplayGroup displayGroup = displayGroupForAspect( TitlesAspect );
265 
266 		removeAsListener();
267 		
268 		int index;
269 		component.clearSelection();
270 		Enumeration e = 
271 			displayGroup.selectionIndexes().objectEnumerator();
272 
273 		while ( e.hasMoreElements() )
274 		{   // add selections one-by-one to support non-contiguous
275 			index = ((Number)e.nextElement()).intValue();
276 			component.addSelectionInterval(
277 				index, index ); // adds one row
278 		}
279 		
280 		addAsListener();
281 	}
282 	
283 	// interface ListSelectionListener
284 
285     public void valueChanged(ListSelectionEvent e)
286     {
287         final EODisplayGroup displayGroup = 
288 			displayGroupForAspect( TitlesAspect );
289         if ( ( displayGroup != null ) && ( ! e.getValueIsAdjusting() ) )
290         {
291             int[] selectedIndices = component().getSelectedIndices();
292 			final NSMutableArray indexList = new NSMutableArray();
293 			for ( int i = 0; i < selectedIndices.length; i++ )
294 			{
295 				indexList.addObject( new Integer( selectedIndices[i] ) );
296 			}
297             
298             // invoke later so the component is repainted before
299             //   any potentially lengthy second-order effects happen:
300             //   this improves user-perceived responsiveness of big apps
301             SwingUtilities.invokeLater( new Runnable() {
302                 public void run()
303                 {
304         			displayGroup.setSelectionIndexes( indexList );
305                 }
306              });
307         }
308     }
309 
310     // interface ListDataListener
311 
312     public void intervalAdded(ListDataEvent e)
313     {
314         // System.out.println( "intervalAdded" );
315         contentsChanged(e);
316     }
317     public void intervalRemoved(ListDataEvent e)
318     {
319         // System.out.println( "intervalRemoved" );
320         contentsChanged(e);
321     }
322     public void contentsChanged(ListDataEvent e)
323     {
324         // System.out.println( "contentsChanged" );
325 		
326         // if we were editing a property,
327 		// we'd notify our display group now.
328     }
329 	
330 	// convenience
331 
332     private JList component()
333     {
334         return (JList) object();
335     }
336 }
337 
338 /*
339  * $Log$
340  * Revision 1.2  2006/02/18 23:19:05  cgruber
341  * Update imports and maven dependencies.
342  *
343  * Revision 1.1  2006/02/16 13:22:22  cgruber
344  * Check in all sources in eclipse-friendly maven-enabled packages.
345  *
346  * Revision 1.5  2003/08/06 23:07:52  chochos
347  * general code cleanup (mostly, removing unused imports)
348  *
349  * Revision 1.4  2002/05/15 14:05:55  mpowers
350  * Now appropriately selectingFromDisplayGroup on establishConnection.
351  *
352  * Revision 1.3  2001/09/14 13:40:26  mpowers
353  * User-initiated selection changes are now handled on the next event loop
354  * so that the component repaints the new selection before any potentially
355  * lengthy logic is triggered by the selection change.
356  *
357  * Revision 1.2  2001/02/17 16:52:05  mpowers
358  * Changes in imports to support building with jdk1.1 collections.
359  *
360  * Revision 1.1.1.1  2000/12/21 15:48:49  mpowers
361  * Contributing wotonomy.
362  *
363  * Revision 1.5  2000/12/20 16:25:41  michael
364  * Added log to all files.
365  *
366  *
367  */
368