summaryrefslogtreecommitdiff
path: root/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableAssociation.java
diff options
context:
space:
mode:
Diffstat (limited to 'projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableAssociation.java')
-rw-r--r--projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableAssociation.java1370
1 files changed, 610 insertions, 760 deletions
diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableAssociation.java
index bc02f7d..edba674 100644
--- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableAssociation.java
+++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableAssociation.java
@@ -52,754 +52,619 @@ import net.wotonomy.ui.EOAssociation;
import net.wotonomy.ui.EODisplayGroup;
/**
-* TableAssociation binds one or more TableColumnAssociations
-* to a display group. You should not instantiate this class
-* directly; use TableColumnAssociation.setTable() instead.
-* Note that TableAssociation inserts itself as the controlled
-* JTable's TableModel.
-*
-* Bindings are:
-* <ul>
-* <li>source: a property convertable to a string for
-* display in the cells of the table column</li>
-* <li>enabled: a property convertable to a string for
-* display in the cells of the table column.
-* Note that you can bind this aspect to a key equal to
-* "true" or "false" and leave the display group null.</li>
-* </ul>
-*
-* @author michael@mpowers.net
-* @author $Author: cgruber $
-* @version $Revision: 904 $
-*/
-public class TableAssociation extends EOAssociation
- implements ActionListener, ListSelectionListener, FocusListener
-{
- static final NSArray aspects =
- new NSArray( new Object[] {
- SourceAspect, EnabledAspect
- } );
- static final NSArray aspectSignatures =
- new NSArray( new Object[] {
- AttributeToOneAspectSignature
- } );
- static final NSArray objectKeysTaken =
- new NSArray( new Object[] {
- "tableModel", "tableHeader"
- } );
-
- // key command to copy contents to clipboard
- static public final String COPY = "COPY";
+ * TableAssociation binds one or more TableColumnAssociations to a display
+ * group. You should not instantiate this class directly; use
+ * TableColumnAssociation.setTable() instead. Note that TableAssociation inserts
+ * itself as the controlled JTable's TableModel.
+ *
+ * Bindings are:
+ * <ul>
+ * <li>source: a property convertable to a string for display in the cells of
+ * the table column</li>
+ * <li>enabled: a property convertable to a string for display in the cells of
+ * the table column. Note that you can bind this aspect to a key equal to "true"
+ * or "false" and leave the display group null.</li>
+ * </ul>
+ *
+ * @author michael@mpowers.net
+ * @author $Author: cgruber $
+ * @version $Revision: 904 $
+ */
+public class TableAssociation extends EOAssociation implements ActionListener, ListSelectionListener, FocusListener {
+ static final NSArray aspects = new NSArray(new Object[] { SourceAspect, EnabledAspect });
+ static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature });
+ static final NSArray objectKeysTaken = new NSArray(new Object[] { "tableModel", "tableHeader" });
+
+ // key command to copy contents to clipboard
+ static public final String COPY = "COPY";
EODisplayGroup source;
- EODisplayGroup sortTarget; // used by TreeColumnAssociation
+ EODisplayGroup sortTarget; // used by TreeColumnAssociation
NSMutableArray columns;
- JTableHeader tableHeader;
-
- boolean pleaseIgnore;
- boolean selectionPaintedImmediately;
- boolean selectionTracking;
-
- /**
- * Constructor specifying the object to be controlled by this
- * association. Throws an exception if the object is not
- * a TableColumn. setTable() must be called before
- * establishing the connection.
- */
- public TableAssociation ( Object anObject )
- {
- super( anObject );
+ JTableHeader tableHeader;
+
+ boolean pleaseIgnore;
+ boolean selectionPaintedImmediately;
+ boolean selectionTracking;
+
+ /**
+ * Constructor specifying the object to be controlled by this association.
+ * Throws an exception if the object is not a TableColumn. setTable() must be
+ * called before establishing the connection.
+ */
+ public TableAssociation(Object anObject) {
+ super(anObject);
source = null;
columns = new NSMutableArray();
- JTable aTable = ((JTable)anObject);
- aTable.addFocusListener( this );
- aTable.setModel(
- new TableAssociationModel( this ) );
- // set up keyboard events for cut-copy: Ctrl-C, Ctrl-X
-
- // why did sun make this harder to use?
- //aTable.getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ).put(
- // KeyStroke.getKeyStroke( KeyEvent.VK_C, java.awt.Event.CTRL_MASK ), COPY);
- //aTable.getActionMap().put(COPY, this);
-
- aTable.registerKeyboardAction( this, COPY,
- KeyStroke.getKeyStroke( KeyEvent.VK_C,
- Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() ),
- JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
- aTable.registerKeyboardAction( this, COPY,
- KeyStroke.getKeyStroke( KeyEvent.VK_X,
- Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() ),
- JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
- tableHeader = new SortedTableHeader();
- tableHeader.setColumnModel( aTable.getColumnModel() );
- aTable.setTableHeader( tableHeader );
- tableHeader.addMouseListener( new TableHeaderListener() );
- pleaseIgnore = false;
- selectionPaintedImmediately = false;
- selectionTracking = false;
- }
-
- /**
- * Returns a List of aspect signatures whose contents
- * correspond with the aspects list. Each element is
- * a string whose characters represent a capability of
- * the corresponding aspect. <ul>
- * <li>"A" attribute: the aspect can be bound to
- * an attribute.</li>
- * <li>"1" to-one: the aspect can be bound to a
- * property that returns a single object.</li>
- * <li>"M" to-one: the aspect can be bound to a
- * property that returns multiple objects.</li>
- * </ul>
- * An empty signature "" means that the aspect can
- * bind without needing a key.
- * This implementation returns "A1M" for each
- * element in the aspects array.
- */
- public static NSArray aspectSignatures ()
- {
- return aspectSignatures;
- }
-
- /**
- * Returns a List that describes the aspects supported
- * by this class. Each element in the list is the string
- * name of the aspect. This implementation returns an
- * empty list.
- */
- public static NSArray aspects ()
- {
- return aspects;
- }
-
- /**
- * Returns a List of EOAssociation subclasses that,
- * for the objects that are usable for this association,
- * are less suitable than this association.
- */
- public static NSArray associationClassesSuperseded ()
- {
- return new NSArray();
- }
-
- /**
- * Returns whether this class can control the specified
- * object.
- */
- public static boolean isUsableWithObject ( Object anObject )
- {
- return ( anObject instanceof JTable );
- }
-
- /**
- * Returns a List of properties of the controlled object
- * that are controlled by this class. For example,
- * "stringValue", or "selected".
- */
- public static NSArray objectKeysTaken ()
- {
- return objectKeysTaken;
- }
-
- /**
- * Returns the aspect that is considered primary
- * or default. This is typically "value" or somesuch.
- */
- public static String primaryAspect ()
- {
- return ValueAspect;
- }
-
- /**
- * Returns whether this association can bind to the
- * specified display group on the specified key for
- * the specified aspect.
- */
- public boolean canBindAspect (
- String anAspect, EODisplayGroup aDisplayGroup, String aKey)
- {
- return ( aspects.containsObject( anAspect ) );
- }
-
- /**
- * Binds the specified aspect of this association to the
- * specified key on the specified display group.
- */
- public void bindAspect (
- String anAspect, EODisplayGroup aDisplayGroup, String aKey )
- {
- if ( SourceAspect.equals( anAspect ) )
- {
+ JTable aTable = ((JTable) anObject);
+ aTable.addFocusListener(this);
+ aTable.setModel(new TableAssociationModel(this));
+ // set up keyboard events for cut-copy: Ctrl-C, Ctrl-X
+
+ // why did sun make this harder to use?
+ // aTable.getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ).put(
+ // KeyStroke.getKeyStroke( KeyEvent.VK_C, java.awt.Event.CTRL_MASK ), COPY);
+ // aTable.getActionMap().put(COPY, this);
+
+ aTable.registerKeyboardAction(this, COPY,
+ KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
+ JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+ aTable.registerKeyboardAction(this, COPY,
+ KeyStroke.getKeyStroke(KeyEvent.VK_X, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
+ JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+ tableHeader = new SortedTableHeader();
+ tableHeader.setColumnModel(aTable.getColumnModel());
+ aTable.setTableHeader(tableHeader);
+ tableHeader.addMouseListener(new TableHeaderListener());
+ pleaseIgnore = false;
+ selectionPaintedImmediately = false;
+ selectionTracking = false;
+ }
+
+ /**
+ * Returns a List of aspect signatures whose contents correspond with the
+ * aspects list. Each element is a string whose characters represent a
+ * capability of the corresponding aspect.
+ * <ul>
+ * <li>"A" attribute: the aspect can be bound to an attribute.</li>
+ * <li>"1" to-one: the aspect can be bound to a property that returns a single
+ * object.</li>
+ * <li>"M" to-one: the aspect can be bound to a property that returns multiple
+ * objects.</li>
+ * </ul>
+ * An empty signature "" means that the aspect can bind without needing a key.
+ * This implementation returns "A1M" for each element in the aspects array.
+ */
+ public static NSArray aspectSignatures() {
+ return aspectSignatures;
+ }
+
+ /**
+ * Returns a List that describes the aspects supported by this class. Each
+ * element in the list is the string name of the aspect. This implementation
+ * returns an empty list.
+ */
+ public static NSArray aspects() {
+ return aspects;
+ }
+
+ /**
+ * Returns a List of EOAssociation subclasses that, for the objects that are
+ * usable for this association, are less suitable than this association.
+ */
+ public static NSArray associationClassesSuperseded() {
+ return new NSArray();
+ }
+
+ /**
+ * Returns whether this class can control the specified object.
+ */
+ public static boolean isUsableWithObject(Object anObject) {
+ return (anObject instanceof JTable);
+ }
+
+ /**
+ * Returns a List of properties of the controlled object that are controlled by
+ * this class. For example, "stringValue", or "selected".
+ */
+ public static NSArray objectKeysTaken() {
+ return objectKeysTaken;
+ }
+
+ /**
+ * Returns the aspect that is considered primary or default. This is typically
+ * "value" or somesuch.
+ */
+ public static String primaryAspect() {
+ return ValueAspect;
+ }
+
+ /**
+ * Returns whether this association can bind to the specified display group on
+ * the specified key for the specified aspect.
+ */
+ public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) {
+ return (aspects.containsObject(anAspect));
+ }
+
+ /**
+ * Binds the specified aspect of this association to the specified key on the
+ * specified display group.
+ */
+ public void bindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) {
+ if (SourceAspect.equals(anAspect)) {
source = aDisplayGroup;
}
- super.bindAspect( anAspect, aDisplayGroup, aKey );
+ super.bindAspect(anAspect, aDisplayGroup, aKey);
}
- /**
- * Establishes a connection between this association
- * and the controlled object. Subclasses should begin
- * listening for events from their controlled object here.
- */
- public void establishConnection ()
- {
- if ( source == null )
- {
- throw new WotonomyException( "No display group: " +
- "please ensure that the TableColumnAssociation " +
- "has a display group bound to the ValueAspect" );
- }
- super.establishConnection();
- selectFromDisplayGroup();
- addAsListener();
- }
-
- /**
- * Breaks the connection between this association and
- * its object. Override to stop listening for events
- * from the object.
- */
- public void breakConnection ()
- {
- removeAsListener();
- super.breakConnection();
- }
-
- protected void addAsListener()
- {
- component().getSelectionModel()
- .addListSelectionListener( this );
- }
-
- protected void removeAsListener()
- {
- component().getSelectionModel()
- .removeListSelectionListener( this );
- }
-
- /**
- * Forces this association to cause the object to
- * stop editing and validate the user's input.
- * @return false if there were problems validating,
- * or true to continue.
- */
- public boolean endEditing ()
- {
- // stop any cell editing
- CellEditor editor = component().getCellEditor();
- if ( editor != null )
- {
- return editor.stopCellEditing();
- }
- return true;
- }
-
- /**
- * Called when either the selection or the contents
- * of an associated display group have changed.
- */
- public void subjectChanged ()
- {
- if ( source != null )
- {
- if ( source.contentsChanged() )
- {
- removeAsListener();
- ((TableAssociationModel)component().getModel()).
- fireTableDataChanged();
+ /**
+ * Establishes a connection between this association and the controlled object.
+ * Subclasses should begin listening for events from their controlled object
+ * here.
+ */
+ public void establishConnection() {
+ if (source == null) {
+ throw new WotonomyException("No display group: " + "please ensure that the TableColumnAssociation "
+ + "has a display group bound to the ValueAspect");
+ }
+ super.establishConnection();
+ selectFromDisplayGroup();
+ addAsListener();
+ }
+
+ /**
+ * Breaks the connection between this association and its object. Override to
+ * stop listening for events from the object.
+ */
+ public void breakConnection() {
+ removeAsListener();
+ super.breakConnection();
+ }
+
+ protected void addAsListener() {
+ component().getSelectionModel().addListSelectionListener(this);
+ }
+
+ protected void removeAsListener() {
+ component().getSelectionModel().removeListSelectionListener(this);
+ }
+
+ /**
+ * Forces this association to cause the object to stop editing and validate the
+ * user's input.
+ *
+ * @return false if there were problems validating, or true to continue.
+ */
+ public boolean endEditing() {
+ // stop any cell editing
+ CellEditor editor = component().getCellEditor();
+ if (editor != null) {
+ return editor.stopCellEditing();
+ }
+ return true;
+ }
+
+ /**
+ * Called when either the selection or the contents of an associated display
+ * group have changed.
+ */
+ public void subjectChanged() {
+ if (source != null) {
+ if (source.contentsChanged()) {
+ removeAsListener();
+ ((TableAssociationModel) component().getModel()).fireTableDataChanged();
selectFromDisplayGroup();
- addAsListener();
-
- // if we caused this change, do nothing
- if ( pleaseIgnore )
- {
- pleaseIgnore = false;
- }
- else // otherwise, update the sort indicator
- {
- tableHeader.repaint();
-
- // cancel any cell editing
- CellEditor editor = component().getCellEditor();
- if ( editor != null )
- {
- editor.cancelCellEditing();
- }
- }
- }
- else
- if ( source.selectionChanged() )
- {
- removeAsListener();
+ addAsListener();
+
+ // if we caused this change, do nothing
+ if (pleaseIgnore) {
+ pleaseIgnore = false;
+ } else // otherwise, update the sort indicator
+ {
+ tableHeader.repaint();
+
+ // cancel any cell editing
+ CellEditor editor = component().getCellEditor();
+ if (editor != null) {
+ editor.cancelCellEditing();
+ }
+ }
+ } else if (source.selectionChanged()) {
+ removeAsListener();
selectFromDisplayGroup();
- addAsListener();
+ addAsListener();
}
}
- }
+ }
- private void selectFromDisplayGroup()
- {
+ private void selectFromDisplayGroup() {
JTable component = component();
int index;
component.getSelectionModel().clearSelection();
- Enumeration e =
- source.selectionIndexes().objectEnumerator();
-
- while ( e.hasMoreElements() )
- { // add selections one-by-one to support non-contiguous
- index = ((Number)e.nextElement()).intValue();
- component.getSelectionModel().addSelectionInterval(
- index, index ); // adds one row
+ Enumeration e = source.selectionIndexes().objectEnumerator();
+
+ while (e.hasMoreElements()) { // add selections one-by-one to support non-contiguous
+ index = ((Number) e.nextElement()).intValue();
+ component.getSelectionModel().addSelectionInterval(index, index); // adds one row
}
}
// interface ListSelectionListener
- public void valueChanged(ListSelectionEvent e)
- {
- if ( source != null )
- {
- if ( selectionTracking || !e.getValueIsAdjusting() )
- {
- int[] selectedIndices = component().getSelectedRows();
- final NSMutableArray indexList = new NSMutableArray();
- for ( int i = 0; i < selectedIndices.length; i++ )
- {
- indexList.addObject( new Integer( selectedIndices[i] ) );
- }
-
- // invoke later so the component is repainted before
- // any potentially lengthy second-order effects happen:
- // this improves user-perceived responsiveness of big apps
- Runnable select = new Runnable()
- {
- public void run()
- {
- pleaseIgnore = true;
- source.setSelectionIndexes( indexList );
- }
- };
- if ( selectionPaintedImmediately )
- {
- EventQueue.invokeLater( select );
- }
- else
- {
- select.run();
- }
- }
- }
- }
-
- /**
- * Determines whether the selection should be painted
- * immediately after the user clicks and therefore
- * before the children display group is updated.
- * When the children group is bound to many associations
- * or is bound to a master-detail association, updating
- * the display group can take a perceptibly long time.
- * This property defaults to false.
- * @see #setSelectionPaintedImmediately
- */
- public boolean isSelectionPaintedImmediately()
- {
- return selectionPaintedImmediately;
- }
-
- /**
- * Sets whether the selection should be painted immediately.
- * Setting this property to true will let the table paint
- * first before the display group is updated.
- */
- public void setSelectionPaintedImmediately( boolean isImmediate )
- {
- selectionPaintedImmediately = isImmediate;
- }
-
- /**
- * Determines whether the selection is actively tracking
- * the selection as the user moves the mouse.
- * If true, selection will not be updated while the
- * list selection event returns true for isValueAdjusting().
- * This property defaults to false.
- * @see #setSelectionTracking
- */
- public boolean isSelectionTracking()
- {
- return selectionTracking;
- }
-
- /**
- * Sets whether the selection is actively tracking
- * the selection as the user moves the mouse.
- * Setting this property to true will update the display
- * group with each change to the selection.
- * This means that any tree selection listers will
- * also be notified before the display group is updated
- * and will have to invokeLater if they want to see the
- * updated display group.
- */
- public void setSelectionTracking( boolean isTracking )
- {
- selectionTracking = isTracking;
- }
-
- // convenience
- private JTable component()
- {
- return (JTable) object();
- }
+ public void valueChanged(ListSelectionEvent e) {
+ if (source != null) {
+ if (selectionTracking || !e.getValueIsAdjusting()) {
+ int[] selectedIndices = component().getSelectedRows();
+ final NSMutableArray indexList = new NSMutableArray();
+ for (int i = 0; i < selectedIndices.length; i++) {
+ indexList.addObject(new Integer(selectedIndices[i]));
+ }
+
+ // invoke later so the component is repainted before
+ // any potentially lengthy second-order effects happen:
+ // this improves user-perceived responsiveness of big apps
+ Runnable select = new Runnable() {
+ public void run() {
+ pleaseIgnore = true;
+ source.setSelectionIndexes(indexList);
+ }
+ };
+ if (selectionPaintedImmediately) {
+ EventQueue.invokeLater(select);
+ } else {
+ select.run();
+ }
+ }
+ }
+ }
/**
- * Copies the contents of the table to the clipboard as a tab-delimited string.
- */
- public void copyToClipboard()
- {
- Toolkit toolkit = Toolkit.getDefaultToolkit();
- Clipboard clipboard = toolkit.getSystemClipboard();
- StringSelection selection =
- new StringSelection( getTabDelimitedString() );
- clipboard.setContents( selection, selection );
- }
+ * Determines whether the selection should be painted immediately after the user
+ * clicks and therefore before the children display group is updated. When the
+ * children group is bound to many associations or is bound to a master-detail
+ * association, updating the display group can take a perceptibly long time.
+ * This property defaults to false.
+ *
+ * @see #setSelectionPaintedImmediately
+ */
+ public boolean isSelectionPaintedImmediately() {
+ return selectionPaintedImmediately;
+ }
/**
- * Converts the contents of the table to a tab-delimited string.
- * @return A String containing the text contents of the table.
- */
- public String getTabDelimitedString()
- {
- StringBuffer result = new StringBuffer(64);
-
- TableModel model = component().getModel();
- int cols = model.getColumnCount();
- int rows = model.getRowCount();
-
- Object o = null;
- for ( int y = 0; y < rows; y++ )
- {
- for ( int x = 0; x < cols; x++ )
- {
- o = model.getValueAt( y, x );
- if ( o == null ) o = "";
- result.append( o );
- result.append( '\t' );
- }
- result.append( '\n' );
- }
-
- return result.toString();
- }
-
- // interface ActionEventListener
-
- public void actionPerformed(ActionEvent evt)
- {
- String cmd = evt.getActionCommand();
-
- if ( COPY.equals( cmd ) )
- {
- copyToClipboard();
- return;
- }
- }
-
- /**
- * Used to render the little triangle over the sorted column(s).
- */
- class SortedTableHeader extends JTableHeader
- {
- public void paint(Graphics g)
- {
- super.paint( g );
- Rectangle r;
- TableColumnAssociation association;
- int size = columns.size();
- NSArray orderings;
- if ( sortTarget != null )
- {
- orderings = sortTarget.sortOrderings();
- }
- else
- {
- orderings = source.sortOrderings();
- }
- for ( int i = 0; i < size; i++ )
- {
- r = getHeaderRect( component().convertColumnIndexToView( i ) );
- association = (TableColumnAssociation) columns.objectAtIndex( i );
- association.drawSortIndicator( r, g, orderings );
- }
- }
- }
-
- /**
- * Used to listen for clicks on the table header.
- */
- class TableHeaderListener extends MouseAdapter
- {
- public void mouseClicked( MouseEvent evt )
- {
- EODisplayGroup displayGroup = sortTarget;
- if ( displayGroup == null ) displayGroup = source;
-
- if ( evt.getClickCount() > 0 )
- {
- int columnClicked = tableHeader.columnAtPoint( evt.getPoint() );
- if ( columnClicked != -1 )
- {
- columnClicked = component().convertColumnIndexToModel( columnClicked );
- TableColumnAssociation association = (TableColumnAssociation)
- columns.objectAtIndex( columnClicked );
- if ( association.isSortable() )
- {
- NSMutableArray newOrder =
- new NSMutableArray( displayGroup.sortOrderings() );
-
- // click once = asc, twice = desc, thrice = clear
- EOSortOrdering sortOrdering;
- int index = association.getIndexOfMatchingOrdering( newOrder );
- if ( index == -1 ) sortOrdering = null;
- else if ( index == 1 ) sortOrdering = association.getSortOrdering( false );
- else sortOrdering = association.getSortOrdering( true );
-
- pleaseIgnore = true;
- tableHeader.repaint();
-
- // stop any cell editing
- CellEditor editor = component().getCellEditor();
- if ( editor != null )
- {
- editor.stopCellEditing();
- }
-
- // remove existing key
- if ( index != 0 )
- {
- newOrder.removeObjectAtIndex( Math.abs( index ) - 1 );
- }
-
- // put new key at front of list
- if ( sortOrdering != null )
- {
- newOrder.insertObjectAtIndex( sortOrdering, 0 );
- }
-
- displayGroup.setSortOrderings( newOrder );
- displayGroup.updateDisplayedObjects();
- }
- }
- }
- }
- }
+ * Sets whether the selection should be painted immediately. Setting this
+ * property to true will let the table paint first before the display group is
+ * updated.
+ */
+ public void setSelectionPaintedImmediately(boolean isImmediate) {
+ selectionPaintedImmediately = isImmediate;
+ }
/**
- * Notifies of beginning of edit.
- */
- public void focusGained(FocusEvent evt)
- {
+ * Determines whether the selection is actively tracking the selection as the
+ * user moves the mouse. If true, selection will not be updated while the list
+ * selection event returns true for isValueAdjusting(). This property defaults
+ * to false.
+ *
+ * @see #setSelectionTracking
+ */
+ public boolean isSelectionTracking() {
+ return selectionTracking;
+ }
+
+ /**
+ * Sets whether the selection is actively tracking the selection as the user
+ * moves the mouse. Setting this property to true will update the display group
+ * with each change to the selection. This means that any tree selection listers
+ * will also be notified before the display group is updated and will have to
+ * invokeLater if they want to see the updated display group.
+ */
+ public void setSelectionTracking(boolean isTracking) {
+ selectionTracking = isTracking;
+ }
+
+ // convenience
+ private JTable component() {
+ return (JTable) object();
+ }
+
+ /**
+ * Copies the contents of the table to the clipboard as a tab-delimited string.
+ */
+ public void copyToClipboard() {
+ Toolkit toolkit = Toolkit.getDefaultToolkit();
+ Clipboard clipboard = toolkit.getSystemClipboard();
+ StringSelection selection = new StringSelection(getTabDelimitedString());
+ clipboard.setContents(selection, selection);
+ }
+
+ /**
+ * Converts the contents of the table to a tab-delimited string.
+ *
+ * @return A String containing the text contents of the table.
+ */
+ public String getTabDelimitedString() {
+ StringBuffer result = new StringBuffer(64);
+
+ TableModel model = component().getModel();
+ int cols = model.getColumnCount();
+ int rows = model.getRowCount();
+
+ Object o = null;
+ for (int y = 0; y < rows; y++) {
+ for (int x = 0; x < cols; x++) {
+ o = model.getValueAt(y, x);
+ if (o == null)
+ o = "";
+ result.append(o);
+ result.append('\t');
+ }
+ result.append('\n');
+ }
+
+ return result.toString();
+ }
+
+ // interface ActionEventListener
+
+ public void actionPerformed(ActionEvent evt) {
+ String cmd = evt.getActionCommand();
+
+ if (COPY.equals(cmd)) {
+ copyToClipboard();
+ return;
+ }
+ }
+
+ /**
+ * Used to render the little triangle over the sorted column(s).
+ */
+ class SortedTableHeader extends JTableHeader {
+ public void paint(Graphics g) {
+ super.paint(g);
+ Rectangle r;
+ TableColumnAssociation association;
+ int size = columns.size();
+ NSArray orderings;
+ if (sortTarget != null) {
+ orderings = sortTarget.sortOrderings();
+ } else {
+ orderings = source.sortOrderings();
+ }
+ for (int i = 0; i < size; i++) {
+ r = getHeaderRect(component().convertColumnIndexToView(i));
+ association = (TableColumnAssociation) columns.objectAtIndex(i);
+ association.drawSortIndicator(r, g, orderings);
+ }
+ }
+ }
+
+ /**
+ * Used to listen for clicks on the table header.
+ */
+ class TableHeaderListener extends MouseAdapter {
+ public void mouseClicked(MouseEvent evt) {
+ EODisplayGroup displayGroup = sortTarget;
+ if (displayGroup == null)
+ displayGroup = source;
+
+ if (evt.getClickCount() > 0) {
+ int columnClicked = tableHeader.columnAtPoint(evt.getPoint());
+ if (columnClicked != -1) {
+ columnClicked = component().convertColumnIndexToModel(columnClicked);
+ TableColumnAssociation association = (TableColumnAssociation) columns.objectAtIndex(columnClicked);
+ if (association.isSortable()) {
+ NSMutableArray newOrder = new NSMutableArray(displayGroup.sortOrderings());
+
+ // click once = asc, twice = desc, thrice = clear
+ EOSortOrdering sortOrdering;
+ int index = association.getIndexOfMatchingOrdering(newOrder);
+ if (index == -1)
+ sortOrdering = null;
+ else if (index == 1)
+ sortOrdering = association.getSortOrdering(false);
+ else
+ sortOrdering = association.getSortOrdering(true);
+
+ pleaseIgnore = true;
+ tableHeader.repaint();
+
+ // stop any cell editing
+ CellEditor editor = component().getCellEditor();
+ if (editor != null) {
+ editor.stopCellEditing();
+ }
+
+ // remove existing key
+ if (index != 0) {
+ newOrder.removeObjectAtIndex(Math.abs(index) - 1);
+ }
+
+ // put new key at front of list
+ if (sortOrdering != null) {
+ newOrder.insertObjectAtIndex(sortOrdering, 0);
+ }
+
+ displayGroup.setSortOrderings(newOrder);
+ displayGroup.updateDisplayedObjects();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Notifies of beginning of edit.
+ */
+ public void focusGained(FocusEvent evt) {
Object o;
EODisplayGroup displayGroup;
- Enumeration e = aspects().objectEnumerator();
- while ( e.hasMoreElements() )
- {
- displayGroup =
- displayGroupForAspect( e.nextElement().toString() );
- if ( displayGroup != null )
- {
- displayGroup.associationDidBeginEditing( this );
+ Enumeration e = aspects().objectEnumerator();
+ while (e.hasMoreElements()) {
+ displayGroup = displayGroupForAspect(e.nextElement().toString());
+ if (displayGroup != null) {
+ displayGroup.associationDidBeginEditing(this);
}
}
- }
+ }
/**
- * Updates object on focus lost and notifies of end of edit.
- */
- public void focusLost(FocusEvent evt)
- {
- if ( ! component().isEditing() )
- {
- Object o;
- EODisplayGroup displayGroup;
- Enumeration e = aspects().objectEnumerator();
- while ( e.hasMoreElements() )
- {
- displayGroup =
- displayGroupForAspect( e.nextElement().toString() );
- if ( displayGroup != null )
- {
- displayGroup.associationDidEndEditing( this );
- }
- }
- }
- }
+ * Updates object on focus lost and notifies of end of edit.
+ */
+ public void focusLost(FocusEvent evt) {
+ if (!component().isEditing()) {
+ Object o;
+ EODisplayGroup displayGroup;
+ Enumeration e = aspects().objectEnumerator();
+ while (e.hasMoreElements()) {
+ displayGroup = displayGroupForAspect(e.nextElement().toString());
+ if (displayGroup != null) {
+ displayGroup.associationDidEndEditing(this);
+ }
+ }
+ }
+ }
/**
- * Used as the model for the controlled table.
- * Package access so TableColumnAssociation can recognize
- * it and use the addColumnAssociation() method.
- * Extends AbstractTableModel just so we get event
- * broadcasting for free.
- */
- static class TableAssociationModel extends AbstractTableModel
- {
+ * Used as the model for the controlled table. Package access so
+ * TableColumnAssociation can recognize it and use the addColumnAssociation()
+ * method. Extends AbstractTableModel just so we get event broadcasting for
+ * free.
+ */
+ static class TableAssociationModel extends AbstractTableModel {
private TableAssociation parent;
- private TableAssociationModel( TableAssociation aParent )
- {
+ private TableAssociationModel(TableAssociation aParent) {
parent = aParent;
}
-
- public TableAssociation getAssociation()
- {
- return parent;
- }
+
+ public TableAssociation getAssociation() {
+ return parent;
+ }
/**
- * Adds the column to the list of ColumnAssociations,
- * and adds the corresponding column to the table
- * at the next available index, setting the value of
- * the column's model index accordingly.
- * Establishes connection if no columns are currently
- * associated.
- */
- public void addColumnAssociation(
- TableColumnAssociation aColumnAssociation )
- {
- // establish connection if necessary
- if ( parent.columns.size() == 0 )
- {
- parent.establishConnection();
- }
-
+ * Adds the column to the list of ColumnAssociations, and adds the corresponding
+ * column to the table at the next available index, setting the value of the
+ * column's model index accordingly. Establishes connection if no columns are
+ * currently associated.
+ */
+ public void addColumnAssociation(TableColumnAssociation aColumnAssociation) {
+ // establish connection if necessary
+ if (parent.columns.size() == 0) {
+ parent.establishConnection();
+ }
+
int newIndex = parent.columns.count();
- parent.columns.add( aColumnAssociation );
+ parent.columns.add(aColumnAssociation);
// add column to table
TableColumn column = (TableColumn) aColumnAssociation.object();
- column.setModelIndex( newIndex );
- parent.component().addColumn( column );
-
+ column.setModelIndex(newIndex);
+ parent.component().addColumn(column);
+
}
/**
- * Removes the column from the list of ColumnAssociations,
- * and removes the corresponding column from the table.
- * Breaks connection if no more columns are associated.
- */
- public void removeColumnAssociation(
- TableColumnAssociation aColumnAssociation )
- {
- int index = parent.columns.indexOfIdenticalObject( aColumnAssociation );
- if ( index == NSArray.NotFound ) return;
-
- parent.columns.removeObjectAtIndex( index );
+ * Removes the column from the list of ColumnAssociations, and removes the
+ * corresponding column from the table. Breaks connection if no more columns are
+ * associated.
+ */
+ public void removeColumnAssociation(TableColumnAssociation aColumnAssociation) {
+ int index = parent.columns.indexOfIdenticalObject(aColumnAssociation);
+ if (index == NSArray.NotFound)
+ return;
+
+ parent.columns.removeObjectAtIndex(index);
// remove column from table
TableColumn column = (TableColumn) aColumnAssociation.object();
- parent.component().removeColumn( column );
-
- // break connection if necessary
- if ( parent.columns.size() == 0 )
- {
- parent.breakConnection();
- }
+ parent.component().removeColumn(column);
+
+ // break connection if necessary
+ if (parent.columns.size() == 0) {
+ parent.breakConnection();
+ }
}
- public int getRowCount()
- {
- if ( parent.source == null ) return 0;
+ public int getRowCount() {
+ if (parent.source == null)
+ return 0;
return parent.source.displayedObjects().count();
}
- public int getColumnCount()
- {
+ public int getColumnCount() {
return parent.columns.count();
}
/**
- * Attempts to retrieve the header value from the specified column,
- * or returns " " if the value is null.
- */
- public String getColumnName(int columnIndex)
- {
- TableColumnAssociation association = (TableColumnAssociation)
- parent.columns.objectAtIndex( columnIndex );
- Object value = ((TableColumn)association.object()).getHeaderValue();
- if ( value != null ) return value.toString();
- return " ";
+ * Attempts to retrieve the header value from the specified column, or returns "
+ * " if the value is null.
+ */
+ public String getColumnName(int columnIndex) {
+ TableColumnAssociation association = (TableColumnAssociation) parent.columns.objectAtIndex(columnIndex);
+ Object value = ((TableColumn) association.object()).getHeaderValue();
+ if (value != null)
+ return value.toString();
+ return " ";
}
/**
- * Returns the class of the first item in the
- * display group bound to the column.
- */
- public Class getColumnClass(int columnIndex)
- {
- Object value;
- int count = getRowCount();
- for( int i = 0; i < count; i++ )
- { //First row in column is null find one that is not.
- value = ((TableColumnAssociation)parent.columns.
- objectAtIndex( columnIndex ) ).valueAtIndex( i );
- if ( value != null ) return value.getClass();
- }
- return Object.class;
+ * Returns the class of the first item in the display group bound to the column.
+ */
+ public Class getColumnClass(int columnIndex) {
+ Object value;
+ int count = getRowCount();
+ for (int i = 0; i < count; i++) { // First row in column is null find one that is not.
+ value = ((TableColumnAssociation) parent.columns.objectAtIndex(columnIndex)).valueAtIndex(i);
+ if (value != null)
+ return value.getClass();
+ }
+ return Object.class;
}
- /**
- * Calls the column association's isEditableAtRow method.
- */
- public boolean isCellEditable(int rowIndex,
- int columnIndex)
- {
- return
- ((TableColumnAssociation)parent.columns.objectAtIndex(
- columnIndex ) ).isEditableAtRow( rowIndex );
+ /**
+ * Calls the column association's isEditableAtRow method.
+ */
+ public boolean isCellEditable(int rowIndex, int columnIndex) {
+ return ((TableColumnAssociation) parent.columns.objectAtIndex(columnIndex)).isEditableAtRow(rowIndex);
}
- /**
- * Calls the column association's valueAtIndex method.
- */
- public Object getValueAt(int rowIndex,
- int columnIndex)
- {
- return
- ((TableColumnAssociation)parent.columns.objectAtIndex(
- columnIndex ) ).valueAtIndex( rowIndex );
+ /**
+ * Calls the column association's valueAtIndex method.
+ */
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ return ((TableColumnAssociation) parent.columns.objectAtIndex(columnIndex)).valueAtIndex(rowIndex);
}
- /**
- * Calls the column association's setValueAtIndex method.
- */
- public void setValueAt(Object aValue,
- int rowIndex,
- int columnIndex)
- {
- Object existingValue = getValueAt( rowIndex, columnIndex );
-
- // don't update display group if value is the same as before
- if ( aValue == existingValue ) return; // handles null case
- if ( existingValue != null ) // both are not null
- {
- Object newValue = aValue;
-
- // if different types
- if ( newValue.getClass() != existingValue.getClass() )
- {
- // convert to string since most editors do anyway
- newValue = newValue.toString();
- existingValue = existingValue.toString();
- }
- if ( newValue.equals( existingValue ) )
- {
- // same value - do nothing
- return;
- }
- }
-
- ((TableColumnAssociation)parent.columns.objectAtIndex(
- columnIndex ) ).setValueAtIndex( aValue, rowIndex );
+ /**
+ * Calls the column association's setValueAtIndex method.
+ */
+ public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
+ Object existingValue = getValueAt(rowIndex, columnIndex);
+
+ // don't update display group if value is the same as before
+ if (aValue == existingValue)
+ return; // handles null case
+ if (existingValue != null) // both are not null
+ {
+ Object newValue = aValue;
+
+ // if different types
+ if (newValue.getClass() != existingValue.getClass()) {
+ // convert to string since most editors do anyway
+ newValue = newValue.toString();
+ existingValue = existingValue.toString();
+ }
+ if (newValue.equals(existingValue)) {
+ // same value - do nothing
+ return;
+ }
+ }
+
+ ((TableColumnAssociation) parent.columns.objectAtIndex(columnIndex)).setValueAtIndex(aValue, rowIndex);
}
}
@@ -807,121 +672,106 @@ public class TableAssociation extends EOAssociation
}
/*
- * $Log$
- * Revision 1.2 2006/02/18 23:19:05 cgruber
- * Update imports and maven dependencies.
+ * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven
+ * dependencies.
*
- * Revision 1.1 2006/02/16 13:22:22 cgruber
- * Check in all sources in eclipse-friendly maven-enabled packages.
+ * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in
+ * eclipse-friendly maven-enabled packages.
*
- * Revision 1.31 2003/08/06 23:07:52 chochos
- * general code cleanup (mostly, removing unused imports)
+ * Revision 1.31 2003/08/06 23:07:52 chochos general code cleanup (mostly,
+ * removing unused imports)
*
- * Revision 1.30 2002/11/16 16:33:31 mpowers
- * Now using platform-specific accelerator key for shortcuts.
+ * Revision 1.30 2002/11/16 16:33:31 mpowers Now using platform-specific
+ * accelerator key for shortcuts.
*
- * Revision 1.29 2002/05/24 14:41:29 mpowers
- * Throwing an exception for clarity.
+ * Revision 1.29 2002/05/24 14:41:29 mpowers Throwing an exception for clarity.
*
- * Revision 1.28 2002/04/10 21:20:04 mpowers
- * Better handling for tree nodes when working with editing contexts.
- * Better handling for invalidation. No longer broadcasting events
- * when nodes have not been "registered" in the tree.
+ * Revision 1.28 2002/04/10 21:20:04 mpowers Better handling for tree nodes when
+ * working with editing contexts. Better handling for invalidation. No longer
+ * broadcasting events when nodes have not been "registered" in the tree.
*
- * Revision 1.27 2002/03/05 23:18:28 mpowers
- * Added documentation.
- * Added isSelectionPaintedImmediate and isSelectionTracking attributes
- * to TableAssociation.
- * Added getTableAssociation to TableColumnAssociation.
+ * Revision 1.27 2002/03/05 23:18:28 mpowers Added documentation. Added
+ * isSelectionPaintedImmediate and isSelectionTracking attributes to
+ * TableAssociation. Added getTableAssociation to TableColumnAssociation.
*
- * Revision 1.25 2002/03/04 22:49:53 mpowers
- * Now working with TreeColumnAssociation to delegate sorting behavior.
+ * Revision 1.25 2002/03/04 22:49:53 mpowers Now working with
+ * TreeColumnAssociation to delegate sorting behavior.
*
- * Revision 1.23 2002/03/04 03:58:17 mpowers
- * Refined table header click behavior.
+ * Revision 1.23 2002/03/04 03:58:17 mpowers Refined table header click
+ * behavior.
*
- * Revision 1.22 2002/03/01 23:41:39 mpowers
- * Added facility to get table association from model.
+ * Revision 1.22 2002/03/01 23:41:39 mpowers Added facility to get table
+ * association from model.
*
- * Revision 1.21 2002/03/01 20:41:22 mpowers
- * Cleaned up unused code.
+ * Revision 1.21 2002/03/01 20:41:22 mpowers Cleaned up unused code.
*
- * Revision 1.20 2002/03/01 15:42:00 mpowers
- * Table column headers now always show their sort indicator.
- * A third table-column click clears the sort for that column.
+ * Revision 1.20 2002/03/01 15:42:00 mpowers Table column headers now always
+ * show their sort indicator. A third table-column click clears the sort for
+ * that column.
*
- * Revision 1.19 2002/02/28 23:01:39 mpowers
- * TableColumnAssociations add and remove themselves from the TableAssociation
- * when their connection is established and broken respectively.
- * TableAssociations now break connection if they have no column associations.
+ * Revision 1.19 2002/02/28 23:01:39 mpowers TableColumnAssociations add and
+ * remove themselves from the TableAssociation when their connection is
+ * established and broken respectively. TableAssociations now break connection
+ * if they have no column associations.
*
- * Revision 1.18 2002/02/28 22:45:27 mpowers
- * Now registers as editing association when table gets focus, so that
- * endEditing can appropriate commit any table cell editors.
+ * Revision 1.18 2002/02/28 22:45:27 mpowers Now registers as editing
+ * association when table gets focus, so that endEditing can appropriate commit
+ * any table cell editors.
*
- * Revision 1.17 2001/10/26 20:01:50 mpowers
- * We're again cancelling instead of stopping editing on subjectChanged.
- * I believe that the introduction of NSRunLoop has allowed us to return
- * to the correct behavior.
+ * Revision 1.17 2001/10/26 20:01:50 mpowers We're again cancelling instead of
+ * stopping editing on subjectChanged. I believe that the introduction of
+ * NSRunLoop has allowed us to return to the correct behavior.
*
- * Revision 1.16 2001/09/14 13:40:26 mpowers
- * User-initiated selection changes are now handled on the next event loop
- * so that the component repaints the new selection before any potentially
- * lengthy logic is triggered by the selection change.
+ * Revision 1.16 2001/09/14 13:40:26 mpowers User-initiated selection changes
+ * are now handled on the next event loop so that the component repaints the new
+ * selection before any potentially lengthy logic is triggered by the selection
+ * change.
*
- * Revision 1.15 2001/07/25 15:03:01 mpowers
- * getColumnName now checks for a column header value before defaulting " ".
+ * Revision 1.15 2001/07/25 15:03:01 mpowers getColumnName now checks for a
+ * column header value before defaulting " ".
*
- * Revision 1.14 2001/06/05 19:09:25 mpowers
- * Fixed broken build.
+ * Revision 1.14 2001/06/05 19:09:25 mpowers Fixed broken build.
*
- * Revision 1.13 2001/06/05 19:08:12 mpowers
- * Better determination of column class. Previously, if the first object
- * was null, Object.class was used. Now we get the first non-null object.
+ * Revision 1.13 2001/06/05 19:08:12 mpowers Better determination of column
+ * class. Previously, if the first object was null, Object.class was used. Now
+ * we get the first non-null object.
*
- * Revision 1.12 2001/05/02 17:39:01 mpowers
- * Now selects from display group after initial population.
+ * Revision 1.12 2001/05/02 17:39:01 mpowers Now selects from display group
+ * after initial population.
*
- * Revision 1.11 2001/03/29 23:05:22 mpowers
- * Fixes for editing table cells.
+ * Revision 1.11 2001/03/29 23:05:22 mpowers Fixes for editing table cells.
*
- * Revision 1.10 2001/03/09 22:09:22 mpowers
- * Now better handling jdk1.1 for rendering the column header.
+ * Revision 1.10 2001/03/09 22:09:22 mpowers Now better handling jdk1.1 for
+ * rendering the column header.
*
- * Revision 1.9 2001/02/27 02:10:38 mpowers
- * No longer updating values to the display group if the value
- * has not changed.
+ * Revision 1.9 2001/02/27 02:10:38 mpowers No longer updating values to the
+ * display group if the value has not changed.
*
- * Revision 1.8 2001/02/17 16:52:05 mpowers
- * Changes in imports to support building with jdk1.1 collections.
+ * Revision 1.8 2001/02/17 16:52:05 mpowers Changes in imports to support
+ * building with jdk1.1 collections.
*
- * Revision 1.7 2001/02/16 17:47:17 mpowers
- * Now cancelling any cell editing on subjectChanged().
+ * Revision 1.7 2001/02/16 17:47:17 mpowers Now cancelling any cell editing on
+ * subjectChanged().
*
- * Revision 1.6 2001/01/12 19:11:56 mpowers
- * Fixed table column click sorting.
+ * Revision 1.6 2001/01/12 19:11:56 mpowers Fixed table column click sorting.
*
- * Revision 1.5 2001/01/12 17:20:30 mpowers
- * Moved EOSortOrdering creation to ColumnAssociation.
+ * Revision 1.5 2001/01/12 17:20:30 mpowers Moved EOSortOrdering creation to
+ * ColumnAssociation.
*
- * Revision 1.4 2001/01/11 21:55:57 mpowers
- * Implemented sort indicator for table column headers.
+ * Revision 1.4 2001/01/11 21:55:57 mpowers Implemented sort indicator for table
+ * column headers.
*
- * Revision 1.3 2001/01/11 20:34:26 mpowers
- * Implemented EOSortOrdering and added support in framework.
- * Added header-click to sort table columns.
+ * Revision 1.3 2001/01/11 20:34:26 mpowers Implemented EOSortOrdering and added
+ * support in framework. Added header-click to sort table columns.
*
- * Revision 1.2 2001/01/08 20:40:51 mpowers
- * JTables in 1.3 clear their selection when the data model changes,
- * which sends a list selection event. We now reset the selection again
- * after the data changes so we're compatible across 1.2 and 1.3.
+ * Revision 1.2 2001/01/08 20:40:51 mpowers JTables in 1.3 clear their selection
+ * when the data model changes, which sends a list selection event. We now reset
+ * the selection again after the data changes so we're compatible across 1.2 and
+ * 1.3.
*
- * Revision 1.1.1.1 2000/12/21 15:49:00 mpowers
- * Contributing wotonomy.
+ * Revision 1.1.1.1 2000/12/21 15:49:00 mpowers Contributing wotonomy.
*
- * Revision 1.7 2000/12/20 16:25:41 michael
- * Added log to all files.
+ * Revision 1.7 2000/12/20 16:25:41 michael Added log to all files.
*
*
*/
-