Coverage Report - net.wotonomy.ui.swing.TableAssociation
 
Classes in this File Line Coverage Branch Coverage Complexity
TableAssociation
0% 
0% 
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