Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
TableAssociation |
|
| 2.268292682926829;2.268 |
1 | 0 | /* |
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.EventQueue; |
|
22 | import java.awt.Graphics; |
|
23 | import java.awt.Rectangle; |
|
24 | import java.awt.Toolkit; |
|
25 | import java.awt.datatransfer.Clipboard; |
|
26 | import java.awt.datatransfer.StringSelection; |
|
27 | import java.awt.event.ActionEvent; |
|
28 | import java.awt.event.ActionListener; |
|
29 | import java.awt.event.FocusEvent; |
|
30 | import java.awt.event.FocusListener; |
|
31 | import java.awt.event.KeyEvent; |
|
32 | import java.awt.event.MouseAdapter; |
|
33 | import java.awt.event.MouseEvent; |
|
34 | import java.util.Enumeration; |
|
35 | ||
36 | import javax.swing.CellEditor; |
|
37 | import javax.swing.JComponent; |
|
38 | import javax.swing.JTable; |
|
39 | import javax.swing.KeyStroke; |
|
40 | import javax.swing.event.ListSelectionEvent; |
|
41 | import javax.swing.event.ListSelectionListener; |
|
42 | import javax.swing.table.AbstractTableModel; |
|
43 | import javax.swing.table.JTableHeader; |
|
44 | import javax.swing.table.TableColumn; |
|
45 | import javax.swing.table.TableModel; |
|
46 | ||
47 | import net.wotonomy.control.EOSortOrdering; |
|
48 | import net.wotonomy.foundation.NSArray; |
|
49 | import net.wotonomy.foundation.NSMutableArray; |
|
50 | import net.wotonomy.foundation.internal.WotonomyException; |
|
51 | import net.wotonomy.ui.EOAssociation; |
|
52 | import net.wotonomy.ui.EODisplayGroup; |
|
53 | ||
54 | /** |
|
55 | * TableAssociation binds one or more TableColumnAssociations |
|
56 | * to a display group. You should not instantiate this class |
|
57 | * directly; use TableColumnAssociation.setTable() instead. |
|
58 | * Note that TableAssociation inserts itself as the controlled |
|
59 | * JTable's TableModel. |
|
60 | * |
|
61 | * Bindings are: |
|
62 | * <ul> |
|
63 | * <li>source: a property convertable to a string for |
|
64 | * display in the cells of the table column</li> |
|
65 | * <li>enabled: a property convertable to a string for |
|
66 | * display in the cells of the table column. |
|
67 | * Note that you can bind this aspect to a key equal to |
|
68 | * "true" or "false" and leave the display group null.</li> |
|
69 | * </ul> |
|
70 | * |
|
71 | * @author michael@mpowers.net |
|
72 | * @author $Author: cgruber $ |
|
73 | * @version $Revision: 904 $ |
|
74 | */ |
|
75 | 0 | public class TableAssociation extends EOAssociation |
76 | implements ActionListener, ListSelectionListener, FocusListener |
|
77 | { |
|
78 | 0 | static final NSArray aspects = |
79 | 0 | new NSArray( new Object[] { |
80 | 0 | SourceAspect, EnabledAspect |
81 | } ); |
|
82 | 0 | static final NSArray aspectSignatures = |
83 | 0 | new NSArray( new Object[] { |
84 | 0 | AttributeToOneAspectSignature |
85 | } ); |
|
86 | 0 | static final NSArray objectKeysTaken = |
87 | 0 | new NSArray( new Object[] { |
88 | 0 | "tableModel", "tableHeader" |
89 | } ); |
|
90 | ||
91 | // key command to copy contents to clipboard |
|
92 | static public final String COPY = "COPY"; |
|
93 | ||
94 | EODisplayGroup source; |
|
95 | EODisplayGroup sortTarget; // used by TreeColumnAssociation |
|
96 | NSMutableArray columns; |
|
97 | JTableHeader tableHeader; |
|
98 | ||
99 | boolean pleaseIgnore; |
|
100 | boolean selectionPaintedImmediately; |
|
101 | boolean selectionTracking; |
|
102 | ||
103 | /** |
|
104 | * Constructor specifying the object to be controlled by this |
|
105 | * association. Throws an exception if the object is not |
|
106 | * a TableColumn. setTable() must be called before |
|
107 | * establishing the connection. |
|
108 | */ |
|
109 | public TableAssociation ( Object anObject ) |
|
110 | { |
|
111 | 0 | super( anObject ); |
112 | 0 | source = null; |
113 | 0 | columns = new NSMutableArray(); |
114 | 0 | JTable aTable = ((JTable)anObject); |
115 | 0 | aTable.addFocusListener( this ); |
116 | 0 | aTable.setModel( |
117 | 0 | new TableAssociationModel( this ) ); |
118 | // set up keyboard events for cut-copy: Ctrl-C, Ctrl-X |
|
119 | ||
120 | // why did sun make this harder to use? |
|
121 | //aTable.getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ).put( |
|
122 | // KeyStroke.getKeyStroke( KeyEvent.VK_C, java.awt.Event.CTRL_MASK ), COPY); |
|
123 | //aTable.getActionMap().put(COPY, this); |
|
124 | ||
125 | 0 | aTable.registerKeyboardAction( this, COPY, |
126 | 0 | KeyStroke.getKeyStroke( KeyEvent.VK_C, |
127 | 0 | Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() ), |
128 | 0 | JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); |
129 | 0 | aTable.registerKeyboardAction( this, COPY, |
130 | 0 | KeyStroke.getKeyStroke( KeyEvent.VK_X, |
131 | 0 | Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() ), |
132 | 0 | JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); |
133 | 0 | tableHeader = new SortedTableHeader(); |
134 | 0 | tableHeader.setColumnModel( aTable.getColumnModel() ); |
135 | 0 | aTable.setTableHeader( tableHeader ); |
136 | 0 | tableHeader.addMouseListener( new TableHeaderListener() ); |
137 | 0 | pleaseIgnore = false; |
138 | 0 | selectionPaintedImmediately = false; |
139 | 0 | selectionTracking = false; |
140 | 0 | } |
141 | ||
142 | /** |
|
143 | * Returns a List of aspect signatures whose contents |
|
144 | * correspond with the aspects list. Each element is |
|
145 | * a string whose characters represent a capability of |
|
146 | * the corresponding aspect. <ul> |
|
147 | * <li>"A" attribute: the aspect can be bound to |
|
148 | * an attribute.</li> |
|
149 | * <li>"1" to-one: the aspect can be bound to a |
|
150 | * property that returns a single object.</li> |
|
151 | * <li>"M" to-one: the aspect can be bound to a |
|
152 | * property that returns multiple objects.</li> |
|
153 | * </ul> |
|
154 | * An empty signature "" means that the aspect can |
|
155 | * bind without needing a key. |
|
156 | * This implementation returns "A1M" for each |
|
157 | * element in the aspects array. |
|
158 | */ |
|
159 | public static NSArray aspectSignatures () |
|
160 | { |
|
161 | 0 | return aspectSignatures; |
162 | } |
|
163 | ||
164 | /** |
|
165 | * Returns a List that describes the aspects supported |
|
166 | * by this class. Each element in the list is the string |
|
167 | * name of the aspect. This implementation returns an |
|
168 | * empty list. |
|
169 | */ |
|
170 | public static NSArray aspects () |
|
171 | { |
|
172 | 0 | return aspects; |
173 | } |
|
174 | ||
175 | /** |
|
176 | * Returns a List of EOAssociation subclasses that, |
|
177 | * for the objects that are usable for this association, |
|
178 | * are less suitable than this association. |
|
179 | */ |
|
180 | public static NSArray associationClassesSuperseded () |
|
181 | { |
|
182 | 0 | return new NSArray(); |
183 | } |
|
184 | ||
185 | /** |
|
186 | * Returns whether this class can control the specified |
|
187 | * object. |
|
188 | */ |
|
189 | public static boolean isUsableWithObject ( Object anObject ) |
|
190 | { |
|
191 | 0 | return ( anObject instanceof JTable ); |
192 | } |
|
193 | ||
194 | /** |
|
195 | * Returns a List of properties of the controlled object |
|
196 | * that are controlled by this class. For example, |
|
197 | * "stringValue", or "selected". |
|
198 | */ |
|
199 | public static NSArray objectKeysTaken () |
|
200 | { |
|
201 | 0 | return objectKeysTaken; |
202 | } |
|
203 | ||
204 | /** |
|
205 | * Returns the aspect that is considered primary |
|
206 | * or default. This is typically "value" or somesuch. |
|
207 | */ |
|
208 | public static String primaryAspect () |
|
209 | { |
|
210 | 0 | return ValueAspect; |
211 | } |
|
212 | ||
213 | /** |
|
214 | * Returns whether this association can bind to the |
|
215 | * specified display group on the specified key for |
|
216 | * the specified aspect. |
|
217 | */ |
|
218 | public boolean canBindAspect ( |
|
219 | String anAspect, EODisplayGroup aDisplayGroup, String aKey) |
|
220 | { |
|
221 | 0 | return ( aspects.containsObject( anAspect ) ); |
222 | } |
|
223 | ||
224 | /** |
|
225 | * Binds the specified aspect of this association to the |
|
226 | * specified key on the specified display group. |
|
227 | */ |
|
228 | public void bindAspect ( |
|
229 | String anAspect, EODisplayGroup aDisplayGroup, String aKey ) |
|
230 | { |
|
231 | 0 | if ( SourceAspect.equals( anAspect ) ) |
232 | { |
|
233 | 0 | source = aDisplayGroup; |
234 | } |
|
235 | 0 | super.bindAspect( anAspect, aDisplayGroup, aKey ); |
236 | 0 | } |
237 | ||
238 | /** |
|
239 | * Establishes a connection between this association |
|
240 | * and the controlled object. Subclasses should begin |
|
241 | * listening for events from their controlled object here. |
|
242 | */ |
|
243 | public void establishConnection () |
|
244 | { |
|
245 | 0 | if ( source == null ) |
246 | { |
|
247 | 0 | throw new WotonomyException( "No display group: " + |
248 | "please ensure that the TableColumnAssociation " + |
|
249 | "has a display group bound to the ValueAspect" ); |
|
250 | } |
|
251 | 0 | super.establishConnection(); |
252 | 0 | selectFromDisplayGroup(); |
253 | 0 | addAsListener(); |
254 | 0 | } |
255 | ||
256 | /** |
|
257 | * Breaks the connection between this association and |
|
258 | * its object. Override to stop listening for events |
|
259 | * from the object. |
|
260 | */ |
|
261 | public void breakConnection () |
|
262 | { |
|
263 | 0 | removeAsListener(); |
264 | 0 | super.breakConnection(); |
265 | 0 | } |
266 | ||
267 | protected void addAsListener() |
|
268 | { |
|
269 | 0 | component().getSelectionModel() |
270 | 0 | .addListSelectionListener( this ); |
271 | 0 | } |
272 | ||
273 | protected void removeAsListener() |
|
274 | { |
|
275 | 0 | component().getSelectionModel() |
276 | 0 | .removeListSelectionListener( this ); |
277 | 0 | } |
278 | ||
279 | /** |
|
280 | * Forces this association to cause the object to |
|
281 | * stop editing and validate the user's input. |
|
282 | * @return false if there were problems validating, |
|
283 | * or true to continue. |
|
284 | */ |
|
285 | public boolean endEditing () |
|
286 | { |
|
287 | // stop any cell editing |
|
288 | 0 | CellEditor editor = component().getCellEditor(); |
289 | 0 | if ( editor != null ) |
290 | { |
|
291 | 0 | return editor.stopCellEditing(); |
292 | } |
|
293 | 0 | return true; |
294 | } |
|
295 | ||
296 | /** |
|
297 | * Called when either the selection or the contents |
|
298 | * of an associated display group have changed. |
|
299 | */ |
|
300 | public void subjectChanged () |
|
301 | { |
|
302 | 0 | if ( source != null ) |
303 | { |
|
304 | 0 | if ( source.contentsChanged() ) |
305 | { |
|
306 | 0 | removeAsListener(); |
307 | 0 | ((TableAssociationModel)component().getModel()). |
308 | 0 | fireTableDataChanged(); |
309 | 0 | selectFromDisplayGroup(); |
310 | 0 | addAsListener(); |
311 | ||
312 | // if we caused this change, do nothing |
|
313 | 0 | if ( pleaseIgnore ) |
314 | { |
|
315 | 0 | pleaseIgnore = false; |
316 | 0 | } |
317 | else // otherwise, update the sort indicator |
|
318 | { |
|
319 | 0 | tableHeader.repaint(); |
320 | ||
321 | // cancel any cell editing |
|
322 | 0 | CellEditor editor = component().getCellEditor(); |
323 | 0 | if ( editor != null ) |
324 | { |
|
325 | 0 | editor.cancelCellEditing(); |
326 | } |
|
327 | } |
|
328 | 0 | } |
329 | else |
|
330 | 0 | if ( source.selectionChanged() ) |
331 | { |
|
332 | 0 | removeAsListener(); |
333 | 0 | selectFromDisplayGroup(); |
334 | 0 | addAsListener(); |
335 | } |
|
336 | } |
|
337 | ||
338 | 0 | } |
339 | ||
340 | private void selectFromDisplayGroup() |
|
341 | { |
|
342 | 0 | JTable component = component(); |
343 | ||
344 | int index; |
|
345 | 0 | component.getSelectionModel().clearSelection(); |
346 | 0 | Enumeration e = |
347 | 0 | source.selectionIndexes().objectEnumerator(); |
348 | ||
349 | 0 | while ( e.hasMoreElements() ) |
350 | { // add selections one-by-one to support non-contiguous |
|
351 | 0 | index = ((Number)e.nextElement()).intValue(); |
352 | 0 | component.getSelectionModel().addSelectionInterval( |
353 | 0 | index, index ); // adds one row |
354 | 0 | } |
355 | 0 | } |
356 | ||
357 | // interface ListSelectionListener |
|
358 | ||
359 | public void valueChanged(ListSelectionEvent e) |
|
360 | { |
|
361 | 0 | if ( source != null ) |
362 | { |
|
363 | 0 | if ( selectionTracking || !e.getValueIsAdjusting() ) |
364 | { |
|
365 | 0 | int[] selectedIndices = component().getSelectedRows(); |
366 | 0 | final NSMutableArray indexList = new NSMutableArray(); |
367 | 0 | for ( int i = 0; i < selectedIndices.length; i++ ) |
368 | { |
|
369 | 0 | indexList.addObject( new Integer( selectedIndices[i] ) ); |
370 | } |
|
371 | ||
372 | // invoke later so the component is repainted before |
|
373 | // any potentially lengthy second-order effects happen: |
|
374 | // this improves user-perceived responsiveness of big apps |
|
375 | 0 | Runnable select = new Runnable() |
376 | { |
|
377 | 0 | public void run() |
378 | { |
|
379 | 0 | pleaseIgnore = true; |
380 | 0 | source.setSelectionIndexes( indexList ); |
381 | 0 | } |
382 | }; |
|
383 | 0 | if ( selectionPaintedImmediately ) |
384 | { |
|
385 | 0 | EventQueue.invokeLater( select ); |
386 | 0 | } |
387 | else |
|
388 | { |
|
389 | 0 | select.run(); |
390 | } |
|
391 | } |
|
392 | } |
|
393 | 0 | } |
394 | ||
395 | /** |
|
396 | * Determines whether the selection should be painted |
|
397 | * immediately after the user clicks and therefore |
|
398 | * before the children display group is updated. |
|
399 | * When the children group is bound to many associations |
|
400 | * or is bound to a master-detail association, updating |
|
401 | * the display group can take a perceptibly long time. |
|
402 | * This property defaults to false. |
|
403 | * @see #setSelectionPaintedImmediately |
|
404 | */ |
|
405 | public boolean isSelectionPaintedImmediately() |
|
406 | { |
|
407 | 0 | return selectionPaintedImmediately; |
408 | } |
|
409 | ||
410 | /** |
|
411 | * Sets whether the selection should be painted immediately. |
|
412 | * Setting this property to true will let the table paint |
|
413 | * first before the display group is updated. |
|
414 | */ |
|
415 | public void setSelectionPaintedImmediately( boolean isImmediate ) |
|
416 | { |
|
417 | 0 | selectionPaintedImmediately = isImmediate; |
418 | 0 | } |
419 | ||
420 | /** |
|
421 | * Determines whether the selection is actively tracking |
|
422 | * the selection as the user moves the mouse. |
|
423 | * If true, selection will not be updated while the |
|
424 | * list selection event returns true for isValueAdjusting(). |
|
425 | * This property defaults to false. |
|
426 | * @see #setSelectionTracking |
|
427 | */ |
|
428 | public boolean isSelectionTracking() |
|
429 | { |
|
430 | 0 | return selectionTracking; |
431 | } |
|
432 | ||
433 | /** |
|
434 | * Sets whether the selection is actively tracking |
|
435 | * the selection as the user moves the mouse. |
|
436 | * Setting this property to true will update the display |
|
437 | * group with each change to the selection. |
|
438 | * This means that any tree selection listers will |
|
439 | * also be notified before the display group is updated |
|
440 | * and will have to invokeLater if they want to see the |
|
441 | * updated display group. |
|
442 | */ |
|
443 | public void setSelectionTracking( boolean isTracking ) |
|
444 | { |
|
445 | 0 | selectionTracking = isTracking; |
446 | 0 | } |
447 | ||
448 | // convenience |
|
449 | 0 | private JTable component() |
450 | { |
|
451 | 0 | return (JTable) object(); |
452 | } |
|
453 | ||
454 | /** |
|
455 | * Copies the contents of the table to the clipboard as a tab-delimited string. |
|
456 | */ |
|
457 | public void copyToClipboard() |
|
458 | { |
|
459 | 0 | Toolkit toolkit = Toolkit.getDefaultToolkit(); |
460 | 0 | Clipboard clipboard = toolkit.getSystemClipboard(); |
461 | 0 | StringSelection selection = |
462 | 0 | new StringSelection( getTabDelimitedString() ); |
463 | 0 | clipboard.setContents( selection, selection ); |
464 | 0 | } |
465 | ||
466 | /** |
|
467 | * Converts the contents of the table to a tab-delimited string. |
|
468 | * @return A String containing the text contents of the table. |
|
469 | */ |
|
470 | public String getTabDelimitedString() |
|
471 | { |
|
472 | 0 | StringBuffer result = new StringBuffer(64); |
473 | ||
474 | 0 | TableModel model = component().getModel(); |
475 | 0 | int cols = model.getColumnCount(); |
476 | 0 | int rows = model.getRowCount(); |
477 | ||
478 | 0 | Object o = null; |
479 | 0 | for ( int y = 0; y < rows; y++ ) |
480 | { |
|
481 | 0 | for ( int x = 0; x < cols; x++ ) |
482 | { |
|
483 | 0 | o = model.getValueAt( y, x ); |
484 | 0 | if ( o == null ) o = ""; |
485 | 0 | result.append( o ); |
486 | 0 | result.append( '\t' ); |
487 | } |
|
488 | 0 | result.append( '\n' ); |
489 | } |
|
490 | ||
491 | 0 | return result.toString(); |
492 | } |
|
493 | ||
494 | // interface ActionEventListener |
|
495 | ||
496 | public void actionPerformed(ActionEvent evt) |
|
497 | { |
|
498 | 0 | String cmd = evt.getActionCommand(); |
499 | ||
500 | 0 | if ( COPY.equals( cmd ) ) |
501 | { |
|
502 | 0 | copyToClipboard(); |
503 | 0 | return; |
504 | } |
|
505 | 0 | } |
506 | ||
507 | /** |
|
508 | * Used to render the little triangle over the sorted column(s). |
|
509 | */ |
|
510 | 0 | class SortedTableHeader extends JTableHeader |
511 | { |
|
512 | public void paint(Graphics g) |
|
513 | { |
|
514 | 0 | super.paint( g ); |
515 | Rectangle r; |
|
516 | TableColumnAssociation association; |
|
517 | 0 | int size = columns.size(); |
518 | NSArray orderings; |
|
519 | 0 | if ( sortTarget != null ) |
520 | { |
|
521 | 0 | orderings = sortTarget.sortOrderings(); |
522 | 0 | } |
523 | else |
|
524 | { |
|
525 | 0 | orderings = source.sortOrderings(); |
526 | } |
|
527 | 0 | for ( int i = 0; i < size; i++ ) |
528 | { |
|
529 | 0 | r = getHeaderRect( component().convertColumnIndexToView( i ) ); |
530 | 0 | association = (TableColumnAssociation) columns.objectAtIndex( i ); |
531 | 0 | association.drawSortIndicator( r, g, orderings ); |
532 | } |
|
533 | 0 | } |
534 | } |
|
535 | ||
536 | /** |
|
537 | * Used to listen for clicks on the table header. |
|
538 | */ |
|
539 | 0 | class TableHeaderListener extends MouseAdapter |
540 | { |
|
541 | public void mouseClicked( MouseEvent evt ) |
|
542 | { |
|
543 | 0 | EODisplayGroup displayGroup = sortTarget; |
544 | 0 | if ( displayGroup == null ) displayGroup = source; |
545 | ||
546 | 0 | if ( evt.getClickCount() > 0 ) |
547 | { |
|
548 | 0 | int columnClicked = tableHeader.columnAtPoint( evt.getPoint() ); |
549 | 0 | if ( columnClicked != -1 ) |
550 | { |
|
551 | 0 | columnClicked = component().convertColumnIndexToModel( columnClicked ); |
552 | 0 | TableColumnAssociation association = (TableColumnAssociation) |
553 | 0 | columns.objectAtIndex( columnClicked ); |
554 | 0 | if ( association.isSortable() ) |
555 | { |
|
556 | 0 | NSMutableArray newOrder = |
557 | 0 | new NSMutableArray( displayGroup.sortOrderings() ); |
558 | ||
559 | // click once = asc, twice = desc, thrice = clear |
|
560 | EOSortOrdering sortOrdering; |
|
561 | 0 | int index = association.getIndexOfMatchingOrdering( newOrder ); |
562 | 0 | if ( index == -1 ) sortOrdering = null; |
563 | 0 | else if ( index == 1 ) sortOrdering = association.getSortOrdering( false ); |
564 | 0 | else sortOrdering = association.getSortOrdering( true ); |
565 | ||
566 | 0 | pleaseIgnore = true; |
567 | 0 | tableHeader.repaint(); |
568 | ||
569 | // stop any cell editing |
|
570 | 0 | CellEditor editor = component().getCellEditor(); |
571 | 0 | if ( editor != null ) |
572 | { |
|
573 | 0 | editor.stopCellEditing(); |
574 | } |
|
575 | ||
576 | // remove existing key |
|
577 | 0 | if ( index != 0 ) |
578 | { |
|
579 | 0 | newOrder.removeObjectAtIndex( Math.abs( index ) - 1 ); |
580 | } |
|
581 | ||
582 | // put new key at front of list |
|
583 | 0 | if ( sortOrdering != null ) |
584 | { |
|
585 | 0 | newOrder.insertObjectAtIndex( sortOrdering, 0 ); |
586 | } |
|
587 | ||
588 | 0 | displayGroup.setSortOrderings( newOrder ); |
589 | 0 | displayGroup.updateDisplayedObjects(); |
590 | } |
|
591 | } |
|
592 | } |
|
593 | 0 | } |
594 | } |
|
595 | ||
596 | /** |
|
597 | * Notifies of beginning of edit. |
|
598 | */ |
|
599 | public void focusGained(FocusEvent evt) |
|
600 | { |
|
601 | Object o; |
|
602 | EODisplayGroup displayGroup; |
|
603 | 0 | Enumeration e = aspects().objectEnumerator(); |
604 | 0 | while ( e.hasMoreElements() ) |
605 | { |
|
606 | 0 | displayGroup = |
607 | 0 | displayGroupForAspect( e.nextElement().toString() ); |
608 | 0 | if ( displayGroup != null ) |
609 | { |
|
610 | 0 | displayGroup.associationDidBeginEditing( this ); |
611 | 0 | } |
612 | } |
|
613 | 0 | } |
614 | ||
615 | /** |
|
616 | * Updates object on focus lost and notifies of end of edit. |
|
617 | */ |
|
618 | public void focusLost(FocusEvent evt) |
|
619 | { |
|
620 | 0 | if ( ! component().isEditing() ) |
621 | { |
|
622 | Object o; |
|
623 | EODisplayGroup displayGroup; |
|
624 | 0 | Enumeration e = aspects().objectEnumerator(); |
625 | 0 | while ( e.hasMoreElements() ) |
626 | { |
|
627 | 0 | displayGroup = |
628 | 0 | displayGroupForAspect( e.nextElement().toString() ); |
629 | 0 | if ( displayGroup != null ) |
630 | { |
|
631 | 0 | displayGroup.associationDidEndEditing( this ); |
632 | 0 | } |
633 | } |
|
634 | } |
|
635 | 0 | } |
636 | ||
637 | /** |
|
638 | * Used as the model for the controlled table. |
|
639 | * Package access so TableColumnAssociation can recognize |
|
640 | * it and use the addColumnAssociation() method. |
|
641 | * Extends AbstractTableModel just so we get event |
|
642 | * broadcasting for free. |
|
643 | */ |
|
644 | 0 | static class TableAssociationModel extends AbstractTableModel |
645 | { |
|
646 | private TableAssociation parent; |
|
647 | ||
648 | 0 | private TableAssociationModel( TableAssociation aParent ) |
649 | 0 | { |
650 | 0 | parent = aParent; |
651 | 0 | } |
652 | ||
653 | public TableAssociation getAssociation() |
|
654 | { |
|
655 | 0 | return parent; |
656 | } |
|
657 | ||
658 | /** |
|
659 | * Adds the column to the list of ColumnAssociations, |
|
660 | * and adds the corresponding column to the table |
|
661 | * at the next available index, setting the value of |
|
662 | * the column's model index accordingly. |
|
663 | * Establishes connection if no columns are currently |
|
664 | * associated. |
|
665 | */ |
|
666 | public void addColumnAssociation( |
|
667 | TableColumnAssociation aColumnAssociation ) |
|
668 | { |
|
669 | // establish connection if necessary |
|
670 | 0 | if ( parent.columns.size() == 0 ) |
671 | { |
|
672 | 0 | parent.establishConnection(); |
673 | } |
|
674 | ||
675 | 0 | int newIndex = parent.columns.count(); |
676 | 0 | parent.columns.add( aColumnAssociation ); |
677 | ||
678 | // add column to table |
|
679 | 0 | TableColumn column = (TableColumn) aColumnAssociation.object(); |
680 | 0 | column.setModelIndex( newIndex ); |
681 | 0 | parent.component().addColumn( column ); |
682 | ||
683 | 0 | } |
684 | ||
685 | /** |
|
686 | * Removes the column from the list of ColumnAssociations, |
|
687 | * and removes the corresponding column from the table. |
|
688 | * Breaks connection if no more columns are associated. |
|
689 | */ |
|
690 | public void removeColumnAssociation( |
|
691 | TableColumnAssociation aColumnAssociation ) |
|
692 | { |
|
693 | 0 | int index = parent.columns.indexOfIdenticalObject( aColumnAssociation ); |
694 | 0 | if ( index == NSArray.NotFound ) return; |
695 | ||
696 | 0 | parent.columns.removeObjectAtIndex( index ); |
697 | ||
698 | // remove column from table |
|
699 | 0 | TableColumn column = (TableColumn) aColumnAssociation.object(); |
700 | 0 | parent.component().removeColumn( column ); |
701 | ||
702 | // break connection if necessary |
|
703 | 0 | if ( parent.columns.size() == 0 ) |
704 | { |
|
705 | 0 | parent.breakConnection(); |
706 | } |
|
707 | 0 | } |
708 | ||
709 | public int getRowCount() |
|
710 | { |
|
711 | 0 | if ( parent.source == null ) return 0; |
712 | 0 | return parent.source.displayedObjects().count(); |
713 | } |
|
714 | ||
715 | public int getColumnCount() |
|
716 | { |
|
717 | 0 | return parent.columns.count(); |
718 | } |
|
719 | ||
720 | /** |
|
721 | * Attempts to retrieve the header value from the specified column, |
|
722 | * or returns " " if the value is null. |
|
723 | */ |
|
724 | public String getColumnName(int columnIndex) |
|
725 | { |
|
726 | 0 | TableColumnAssociation association = (TableColumnAssociation) |
727 | 0 | parent.columns.objectAtIndex( columnIndex ); |
728 | 0 | Object value = ((TableColumn)association.object()).getHeaderValue(); |
729 | 0 | if ( value != null ) return value.toString(); |
730 | 0 | return " "; |
731 | } |
|
732 | ||
733 | /** |
|
734 | * Returns the class of the first item in the |
|
735 | * display group bound to the column. |
|
736 | */ |
|
737 | public Class getColumnClass(int columnIndex) |
|
738 | { |
|
739 | Object value; |
|
740 | 0 | int count = getRowCount(); |
741 | 0 | for( int i = 0; i < count; i++ ) |
742 | { //First row in column is null find one that is not. |
|
743 | 0 | value = ((TableColumnAssociation)parent.columns. |
744 | 0 | objectAtIndex( columnIndex ) ).valueAtIndex( i ); |
745 | 0 | if ( value != null ) return value.getClass(); |
746 | } |
|
747 | 0 | return Object.class; |
748 | } |
|
749 | ||
750 | /** |
|
751 | * Calls the column association's isEditableAtRow method. |
|
752 | */ |
|
753 | public boolean isCellEditable(int rowIndex, |
|
754 | int columnIndex) |
|
755 | { |
|
756 | 0 | return |
757 | 0 | ((TableColumnAssociation)parent.columns.objectAtIndex( |
758 | 0 | columnIndex ) ).isEditableAtRow( rowIndex ); |
759 | } |
|
760 | ||
761 | /** |
|
762 | * Calls the column association's valueAtIndex method. |
|
763 | */ |
|
764 | public Object getValueAt(int rowIndex, |
|
765 | int columnIndex) |
|
766 | { |
|
767 | 0 | return |
768 | 0 | ((TableColumnAssociation)parent.columns.objectAtIndex( |
769 | 0 | columnIndex ) ).valueAtIndex( rowIndex ); |
770 | } |
|
771 | ||
772 | /** |
|
773 | * Calls the column association's setValueAtIndex method. |
|
774 | */ |
|
775 | public void setValueAt(Object aValue, |
|
776 | int rowIndex, |
|
777 | int columnIndex) |
|
778 | { |
|
779 | 0 | Object existingValue = getValueAt( rowIndex, columnIndex ); |
780 | ||
781 | // don't update display group if value is the same as before |
|
782 | 0 | if ( aValue == existingValue ) return; // handles null case |
783 | 0 | if ( existingValue != null ) // both are not null |
784 | { |
|
785 | 0 | Object newValue = aValue; |
786 | ||
787 | // if different types |
|
788 | 0 | if ( newValue.getClass() != existingValue.getClass() ) |
789 | { |
|
790 | // convert to string since most editors do anyway |
|
791 | 0 | newValue = newValue.toString(); |
792 | 0 | existingValue = existingValue.toString(); |
793 | } |
|
794 | 0 | if ( newValue.equals( existingValue ) ) |
795 | { |
|
796 | // same value - do nothing |
|
797 | 0 | return; |
798 | } |
|
799 | } |
|
800 | ||
801 | 0 | ((TableColumnAssociation)parent.columns.objectAtIndex( |
802 | 0 | columnIndex ) ).setValueAtIndex( aValue, rowIndex ); |
803 | 0 | } |
804 | ||
805 | } |
|
806 | ||
807 | } |
|
808 | ||
809 | /* |
|
810 | * $Log$ |
|
811 | * Revision 1.2 2006/02/18 23:19:05 cgruber |
|
812 | * Update imports and maven dependencies. |
|
813 | * |
|
814 | * Revision 1.1 2006/02/16 13:22:22 cgruber |
|
815 | * Check in all sources in eclipse-friendly maven-enabled packages. |
|
816 | * |
|
817 | * Revision 1.31 2003/08/06 23:07:52 chochos |
|
818 | * general code cleanup (mostly, removing unused imports) |
|
819 | * |
|
820 | * Revision 1.30 2002/11/16 16:33:31 mpowers |
|
821 | * Now using platform-specific accelerator key for shortcuts. |
|
822 | * |
|
823 | * Revision 1.29 2002/05/24 14:41:29 mpowers |
|
824 | * Throwing an exception for clarity. |
|
825 | * |
|
826 | * Revision 1.28 2002/04/10 21:20:04 mpowers |
|
827 | * Better handling for tree nodes when working with editing contexts. |
|
828 | * Better handling for invalidation. No longer broadcasting events |
|
829 | * when nodes have not been "registered" in the tree. |
|
830 | * |
|
831 | * Revision 1.27 2002/03/05 23:18:28 mpowers |
|
832 | * Added documentation. |
|
833 | * Added isSelectionPaintedImmediate and isSelectionTracking attributes |
|
834 | * to TableAssociation. |
|
835 | * Added getTableAssociation to TableColumnAssociation. |
|
836 | * |
|
837 | * Revision 1.25 2002/03/04 22:49:53 mpowers |
|
838 | * Now working with TreeColumnAssociation to delegate sorting behavior. |
|
839 | * |
|
840 | * Revision 1.23 2002/03/04 03:58:17 mpowers |
|
841 | * Refined table header click behavior. |
|
842 | * |
|
843 | * Revision 1.22 2002/03/01 23:41:39 mpowers |
|
844 | * Added facility to get table association from model. |
|
845 | * |
|
846 | * Revision 1.21 2002/03/01 20:41:22 mpowers |
|
847 | * Cleaned up unused code. |
|
848 | * |
|
849 | * Revision 1.20 2002/03/01 15:42:00 mpowers |
|
850 | * Table column headers now always show their sort indicator. |
|
851 | * A third table-column click clears the sort for that column. |
|
852 | * |
|
853 | * Revision 1.19 2002/02/28 23:01:39 mpowers |
|
854 | * TableColumnAssociations add and remove themselves from the TableAssociation |
|
855 | * when their connection is established and broken respectively. |
|
856 | * TableAssociations now break connection if they have no column associations. |
|
857 | * |
|
858 | * Revision 1.18 2002/02/28 22:45:27 mpowers |
|
859 | * Now registers as editing association when table gets focus, so that |
|
860 | * endEditing can appropriate commit any table cell editors. |
|
861 | * |
|
862 | * Revision 1.17 2001/10/26 20:01:50 mpowers |
|
863 | * We're again cancelling instead of stopping editing on subjectChanged. |
|
864 | * I believe that the introduction of NSRunLoop has allowed us to return |
|
865 | * to the correct behavior. |
|
866 | * |
|
867 | * Revision 1.16 2001/09/14 13:40:26 mpowers |
|
868 | * User-initiated selection changes are now handled on the next event loop |
|
869 | * so that the component repaints the new selection before any potentially |
|
870 | * lengthy logic is triggered by the selection change. |
|
871 | * |
|
872 | * Revision 1.15 2001/07/25 15:03:01 mpowers |
|
873 | * getColumnName now checks for a column header value before defaulting " ". |
|
874 | * |
|
875 | * Revision 1.14 2001/06/05 19:09:25 mpowers |
|
876 | * Fixed broken build. |
|
877 | * |
|
878 | * Revision 1.13 2001/06/05 19:08:12 mpowers |
|
879 | * Better determination of column class. Previously, if the first object |
|
880 | * was null, Object.class was used. Now we get the first non-null object. |
|
881 | * |
|
882 | * Revision 1.12 2001/05/02 17:39:01 mpowers |
|
883 | * Now selects from display group after initial population. |
|
884 | * |
|
885 | * Revision 1.11 2001/03/29 23:05:22 mpowers |
|
886 | * Fixes for editing table cells. |
|
887 | * |
|
888 | * Revision 1.10 2001/03/09 22:09:22 mpowers |
|
889 | * Now better handling jdk1.1 for rendering the column header. |
|
890 | * |
|
891 | * Revision 1.9 2001/02/27 02:10:38 mpowers |
|
892 | * No longer updating values to the display group if the value |
|
893 | * has not changed. |
|
894 | * |
|
895 | * Revision 1.8 2001/02/17 16:52:05 mpowers |
|
896 | * Changes in imports to support building with jdk1.1 collections. |
|
897 | * |
|
898 | * Revision 1.7 2001/02/16 17:47:17 mpowers |
|
899 | * Now cancelling any cell editing on subjectChanged(). |
|
900 | * |
|
901 | * Revision 1.6 2001/01/12 19:11:56 mpowers |
|
902 | * Fixed table column click sorting. |
|
903 | * |
|
904 | * Revision 1.5 2001/01/12 17:20:30 mpowers |
|
905 | * Moved EOSortOrdering creation to ColumnAssociation. |
|
906 | * |
|
907 | * Revision 1.4 2001/01/11 21:55:57 mpowers |
|
908 | * Implemented sort indicator for table column headers. |
|
909 | * |
|
910 | * Revision 1.3 2001/01/11 20:34:26 mpowers |
|
911 | * Implemented EOSortOrdering and added support in framework. |
|
912 | * Added header-click to sort table columns. |
|
913 | * |
|
914 | * Revision 1.2 2001/01/08 20:40:51 mpowers |
|
915 | * JTables in 1.3 clear their selection when the data model changes, |
|
916 | * which sends a list selection event. We now reset the selection again |
|
917 | * after the data changes so we're compatible across 1.2 and 1.3. |
|
918 | * |
|
919 | * Revision 1.1.1.1 2000/12/21 15:49:00 mpowers |
|
920 | * Contributing wotonomy. |
|
921 | * |
|
922 | * Revision 1.7 2000/12/20 16:25:41 michael |
|
923 | * Added log to all files. |
|
924 | * |
|
925 | * |
|
926 | */ |
|
927 |