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.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 {
187 component().getModel().addListDataListener( this );
188 component().addListSelectionListener( this );
189 }
190
191 protected void removeAsListener()
192 {
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
206 displayGroup = displayGroupForAspect( TitlesAspect );
207 if ( displayGroup != null )
208 {
209
210
211
212
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
235 int[] selectedIndices = component().getSelectedIndices();
236
237
238 model.removeAllElements();
239
240
241 Object value;
242 int size = displayGroup.displayedObjects().count();
243
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
252 for ( int i = 0; i < selectedIndices.length; i++ )
253 {
254 component.addSelectionInterval(
255 selectedIndices[i], selectedIndices[i] );
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 {
275 index = ((Number)e.nextElement()).intValue();
276 component.addSelectionInterval(
277 index, index );
278 }
279
280 addAsListener();
281 }
282
283
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
299
300
301 SwingUtilities.invokeLater( new Runnable() {
302 public void run()
303 {
304 displayGroup.setSelectionIndexes( indexList );
305 }
306 });
307 }
308 }
309
310
311
312 public void intervalAdded(ListDataEvent e)
313 {
314
315 contentsChanged(e);
316 }
317 public void intervalRemoved(ListDataEvent e)
318 {
319
320 contentsChanged(e);
321 }
322 public void contentsChanged(ListDataEvent e)
323 {
324
325
326
327
328 }
329
330
331
332 private JList component()
333 {
334 return (JList) object();
335 }
336 }
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368