summaryrefslogtreecommitdiff
path: root/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODisplayGroup.java
diff options
context:
space:
mode:
Diffstat (limited to 'projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODisplayGroup.java')
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODisplayGroup.java2455
1 files changed, 2455 insertions, 0 deletions
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODisplayGroup.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODisplayGroup.java
new file mode 100644
index 0000000..bda1dd5
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODisplayGroup.java
@@ -0,0 +1,2455 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2000 Michael Powers
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, see http://www.gnu.org
+*/
+
+package net.wotonomy.web;
+
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Observable;
+
+import net.wotonomy.control.EODataSource;
+import net.wotonomy.control.EOEditingContext;
+import net.wotonomy.control.EOKeyValueCoding;
+import net.wotonomy.control.EOKeyValueCodingSupport;
+import net.wotonomy.control.EOObjectStore;
+import net.wotonomy.control.EOObserverCenter;
+import net.wotonomy.control.EOObserving;
+import net.wotonomy.control.EOQualifier;
+import net.wotonomy.control.EOSortOrdering;
+import net.wotonomy.control.OrderedDataSource;
+import net.wotonomy.control.PropertyDataSource;
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSMutableArray;
+import net.wotonomy.foundation.NSMutableDictionary;
+import net.wotonomy.foundation.NSNotification;
+import net.wotonomy.foundation.NSNotificationCenter;
+import net.wotonomy.foundation.NSRange;
+import net.wotonomy.foundation.NSSelector;
+import net.wotonomy.foundation.internal.Duplicator;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+/**
+* WODisplayGroup manages a set of objects,
+* allowing them to be sorted, batched, and filtered.
+* WODisplay also acts as a bridge to the wotonomy's
+* control package, including WODisplayGroup and
+* EOEditingContext.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class WODisplayGroup extends Observable
+ implements EOObserving, EOEditingContext.Editor,
+ java.io.Serializable
+{
+ /**
+ * Notification sent when the display group is about to fetch.
+ */
+ public static final String DisplayGroupWillFetchNotification
+ = "DisplayGroupWillFetchNotification";
+
+ private static boolean
+ globalDefaultForValidatesChangesImmediately = true;
+ private static String
+ globalDefaultStringMatchFormat = "caseInsensitiveLike";
+ private static String
+ globalDefaultStringMatchOperator = "%@*";
+
+ protected NSMutableArray allObjects;
+ protected NSArray allObjectsProxy;
+ protected NSMutableArray displayedObjects;
+ protected NSArray displayedObjectsProxy;
+ protected NSMutableArray selectedObjects;
+ protected NSArray selectedObjectsProxy;
+ protected NSMutableArray selectedIndexes;
+
+ private String defaultStringMatchOperator;
+ private String defaultStringMatchFormat;
+
+ private boolean validatesChangesImmediately;
+ private Object delegate;
+ private EODataSource dataSource;
+ private EOQualifier qualifier;
+ private NSMutableArray sortOrderings;
+ private NSArray sortOrderingsProxy;
+
+ private NSArray localKeys;
+ private NSDictionary insertedObjectDefaultValues;
+ private boolean fetchesOnLoad;
+ private boolean selectsFirstObjectAfterFetch;
+ private boolean usesOptimisticRefresh;
+ private boolean inQueryMode;
+
+ // change detection: package access for helper classes
+ boolean selectionChanged;
+ int updatedObjectIndex;
+
+ // batching
+ private int batchIndex;
+ private int batchSize;
+
+ // this property is not in the spec
+ private boolean compareByReference = false;
+
+ private EOObserving lastGroupObserver;
+
+ /**
+ * Creates a new display group.
+ */
+ public WODisplayGroup()
+ {
+ validatesChangesImmediately =
+ globalDefaultForValidatesChangesImmediately();
+ defaultStringMatchOperator =
+ globalDefaultStringMatchFormat();
+ defaultStringMatchFormat =
+ globalDefaultStringMatchOperator();
+
+ allObjects = new ObservableArray( this );
+ allObjectsProxy = NSArray.arrayBackedByList( allObjects );
+ displayedObjects = new NSMutableArray();
+ displayedObjectsProxy = NSArray.arrayBackedByList( displayedObjects );
+ selectedObjects = new NSMutableArray();
+ selectedObjectsProxy = NSArray.arrayBackedByList( selectedObjects );
+ sortOrderings = new NSMutableArray();
+ sortOrderingsProxy = NSArray.arrayBackedByList( sortOrderings );
+ selectedIndexes = new NSMutableArray();
+
+ delegate = null;
+ dataSource = null;
+ qualifier = null;
+
+ localKeys = new NSArray(); // not implemented
+ insertedObjectDefaultValues = new NSDictionary();
+ fetchesOnLoad = false; // not implemented
+ selectsFirstObjectAfterFetch = false;
+ usesOptimisticRefresh = false;
+ inQueryMode = false; // not implemented
+
+ selectionChanged = false;
+ updatedObjectIndex = -1;
+
+ batchIndex = 0;
+ batchSize = 0;
+ }
+
+
+
+ // specify optional data source
+
+ /**
+ * Sets the data source that will be used by
+ * this display group.
+ */
+ public void setDataSource ( EODataSource aDataSource )
+ {
+ if ( ( dataSource != null )
+ && ( dataSource.editingContext() != null ) )
+ {
+ // un-register for notifications from existing parent store
+ NSNotificationCenter.defaultCenter().removeObserver(
+ this, null, dataSource.editingContext() );
+ dataSource.editingContext().removeEditor( this );
+ if ( dataSource.editingContext().messageHandler() == this )
+ {
+ dataSource.editingContext().setMessageHandler( null );
+ }
+
+ }
+
+ dataSource = aDataSource;
+
+ if ( ( dataSource != null )
+ && ( dataSource.editingContext() != null ) )
+ {
+ // register for notifications from parent store
+ NSNotificationCenter.defaultCenter().addObserver(
+ this, new NSSelector( "objectsInvalidatedInEditingContext",
+ new Class[] { NSNotification.class } ),
+ null, dataSource.editingContext() );
+
+ // add ourselves as editor
+ dataSource.editingContext().addEditor( this );
+
+ // add ourselves as message handler if no such handler exists
+ if ( dataSource.editingContext().messageHandler() == null )
+ {
+ dataSource.editingContext().setMessageHandler( this );
+ }
+ }
+ }
+
+ /**
+ * Returns the current data source backing this display group,
+ * or null if no dataSource is currently used.
+ */
+ public EODataSource dataSource()
+ {
+ return dataSource;
+ }
+
+ /**
+ * Returns the key by which this display group is bound a master
+ * display group, or null if this is not a detail display group.
+ */
+ public String detailKey()
+ {
+ if ( dataSource instanceof PropertyDataSource )
+ {
+ return ((PropertyDataSource)dataSource).key();
+ }
+ return null;
+ }
+
+ /**
+ * Sets the key by which this display group is bound a master
+ * display group. Does nothing if this is not a detail display group.
+ */
+ public void setDetailKey( String aKey )
+ {
+ if ( dataSource instanceof PropertyDataSource )
+ {
+ ((PropertyDataSource)dataSource).setKey( aKey );
+ }
+ }
+
+ /**
+ * Returns whether the data source is a detail data source,
+ * suggesting that this is a detail display group.
+ */
+ public boolean hasDetailDataSource()
+ {
+ return ( dataSource instanceof PropertyDataSource );
+ }
+
+ /**
+ * Returns the selected object in the master display group,
+ * or null if this is not a detail display group.
+ */
+ public Object masterObject()
+ {
+ if ( dataSource instanceof PropertyDataSource )
+ {
+ return ((PropertyDataSource)dataSource).source();
+ }
+ return null;
+ }
+
+ /**
+ * Sets the master object in the detail data source.
+ * Does nothing if there is no detail data source.
+ */
+ public void setMasterObject( Object anObject )
+ {
+ if ( dataSource instanceof PropertyDataSource )
+ {
+ ((PropertyDataSource)dataSource).setSource( anObject );
+ }
+ }
+
+ // specify optional delegate
+
+ /**
+ * Sets the display group delegate that
+ * will be used by this display group.
+ */
+ public void setDelegate ( Object aDelegate )
+ {
+ delegate = aDelegate;
+ }
+
+ /**
+ * Returns the current delegate for this display group,
+ * or null if no delegate is currently set.
+ */
+ public Object delegate()
+ {
+ return delegate;
+ }
+
+
+
+ // display group configuration
+
+ /**
+ * Returns the current string matching format.
+ * If not set, defaults to "%@*".
+ */
+ public String defaultStringMatchFormat()
+ {
+ return defaultStringMatchFormat;
+ }
+
+ /**
+ * Returns the current string matching operator.
+ * If not set, defaults to "caseInsensitiveLike".
+ */
+ public String defaultStringMatchOperator()
+ {
+ return defaultStringMatchOperator;
+ }
+
+ /**
+ * Sets the display group and associations to edit a
+ * "query by example" query object. This method is
+ * used for target/action connections.
+ */
+ public void enterQueryMode ( Object aSender )
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns whether this display group should immediate
+ * fetch when loaded.
+ */
+ public boolean fetchesOnLoad()
+ {
+ return fetchesOnLoad;
+ }
+
+ /**
+ * Returns whether this display group is in "query by
+ * example" mode.
+ */
+ public boolean inQueryMode()
+ {
+ return inQueryMode;
+ }
+
+ /**
+ * Returns a Map of default values that are applied
+ * to new objects that are inserted into the list.
+ */
+ public NSDictionary insertedObjectDefaultValues()
+ {
+ return insertedObjectDefaultValues;
+ }
+
+ /**
+ * Returns the keys that were declared when read from
+ * an external resource file.
+ */
+ public NSArray localKeys()
+ {
+ return localKeys;
+ }
+
+ /**
+ * Sets whether this display group will select the
+ * first object in the list after a fetch.
+ */
+ public boolean selectsFirstObjectAfterFetch()
+ {
+ return selectsFirstObjectAfterFetch;
+ }
+
+ /**
+ * Sets the default string matching format that
+ * will be used by this display group.
+ */
+ public void setDefaultStringMatchFormat ( String aFormat )
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Sets the default string matching operator that
+ * will be used by this display group.
+ */
+ public void setDefaultStringMatchOperator ( String anOperator )
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Sets whether this display group will fetch objects
+ * from its data source on load.
+ */
+ public void setFetchesOnLoad ( boolean willFetch )
+ {
+ fetchesOnLoad = willFetch;
+ }
+
+ /**
+ * Sets whether this display group is in "query by example"
+ * mode. If true, all associations will bind to a special
+ * "example" object.
+ */
+ public void setInQueryMode ( boolean isInQueryMode )
+ {
+ inQueryMode = isInQueryMode;
+ }
+
+ /**
+ * Sets the mapping that contains the values that will
+ * be applied to new objects inserted into the display group.
+ */
+ public void setInsertedObjectDefaultValues ( Map aMap )
+ {
+ insertedObjectDefaultValues = new NSDictionary( aMap );
+ }
+
+ /**
+ * Sets the keys that are declared when instantiated from
+ * an external resource file.
+ */
+ public void setLocalKeys ( List aKeyList )
+ {
+ localKeys = new NSArray( (Collection) aKeyList );
+ }
+
+ /**
+ * Sets whether the first object in the list will be
+ * selected after a fetch.
+ */
+ public void setSelectsFirstObjectAfterFetch (
+ boolean selectsFirst )
+ {
+ selectsFirstObjectAfterFetch = selectsFirst;
+ }
+
+ /**
+ * Sets the order of the keys by which this display group
+ * will be ordered after a fetch or after a call to
+ * updateDisplayedObjects(). The elements in the display
+ * group will be sorted first by the first key, within
+ * the first key, by the second key, and so on.
+ */
+ public void setSortOrderings ( List aList )
+ {
+ sortOrderings.removeAllObjects();
+
+ Object o;
+ Iterator it = aList.iterator();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ // handle the convenience of specifying just a key
+ if ( ! ( o instanceof EOSortOrdering ) )
+ {
+ o = new EOSortOrdering(
+ o.toString(), EOSortOrdering.CompareAscending );
+ }
+ sortOrderings.add( o );
+ }
+ }
+
+ /**
+ * Sets whether only changed objects are refreshed (optimistic),
+ * or whether all objects are refreshed (pessimistic, default).
+ * By default, when the display group receives notification that
+ * one of its objects has changed, updateDisplayedObjects is called.
+ */
+ public void setUsesOptimisticRefresh ( boolean isOptimistic )
+ {
+ usesOptimisticRefresh = isOptimistic;
+ }
+
+ /**
+ * Sets whether changes made by associations are validated
+ * immediately, or when changes are saved.
+ */
+ public void setValidatesChangesImmediately (
+ boolean validatesImmediately )
+ {
+ validatesChangesImmediately = validatesImmediately;
+ }
+
+ /**
+ * Returns a read-only List of sort orderings for this display group.
+ */
+ public NSArray sortOrderings()
+ {
+ return sortOrderingsProxy;
+ }
+
+ /**
+ * Returns whether this display group refreshes only
+ * the changed objects or all objects on refresh.
+ */
+ public boolean usesOptimisticRefresh()
+ {
+ return usesOptimisticRefresh;
+ }
+
+ /**
+ * Returns whether this display group validates changes
+ * immediately. Otherwise, validation should occur when
+ * changes are saved. Default is the global default,
+ * which is initially true.
+ */
+ public boolean validatesChangesImmediately()
+ {
+ return validatesChangesImmediately;
+ }
+
+
+ // qualification
+
+ /**
+ * Returns a qualifier that will be applied all the objects
+ * in this display group to determine which objects will
+ * be displayed.
+ */
+ public EOQualifier qualifier()
+ {
+ return qualifier;
+ }
+
+ /**
+ * Returns a new qualifier built from the three query
+ * value maps: greater than, equal to, and less than.
+ */
+ public EOQualifier qualifierFromQueryValues()
+ {
+ //TODO: assemble qualifier from query values
+
+ return new EOQualifier()
+ {
+ // use inner class until we actually implement one
+ public EOQualifier qualifierWithBindings(
+ Map aMap,
+ boolean requireAll )
+ {
+ return null;
+ }
+ public Throwable
+ validateKeysWithRootClassDescription( Class aClass )
+ {
+ return null;
+ }
+ public boolean evaluateWithObject(Object o)
+ {
+ return false;
+ }
+ };
+ }
+
+ /**
+ * Calls qualifierFromQueryValues(), applies the result
+ * to the data source, and calls fetch().
+ */
+ public void qualifyDataSource()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Calls qualifierFromQueryValues(), sets the qualifier
+ * with setQualifier(), and calls updateDisplayedObjects().
+ */
+ public void qualifyDisplayGroup()
+ {
+ setQualifier( qualifierFromQueryValues() );
+ updateDisplayedObjects();
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to binding query values.
+ */
+ public NSDictionary queryBindingValues()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to binding query values.
+ */
+ public NSMutableDictionary queryBindings()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to binding query values for a matching query.
+ */
+ public NSMutableDictionary queryMatch()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to binding query values for a minimum value query.
+ */
+ public NSMutableDictionary queryMin()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to binding query values for a maximum value query.
+ */
+ public NSMutableDictionary queryMax()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to operator values.
+ */
+ public NSMutableDictionary queryOperator()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a list containing all supported qualifier operators.
+ */
+ public NSArray allQualifierOperators()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to operator values.
+ */
+ public NSDictionary queryOperatorValues()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Sets the qualifier that will be used by
+ * updateDisplayedObjects() to filter displayed objects.
+ */
+ public void setQualifier ( EOQualifier aQualifier )
+ {
+ qualifier = aQualifier;
+ }
+
+ /**
+ * Sets the mapping that contains the mappings of keys
+ * to binding values.
+ */
+ public void setQueryBindingValues ( Map aMap )
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Sets the mapping that contains the mappings of keys
+ * to operator values.
+ */
+ public void setQueryOperatorValues ( Map aMap )
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+
+
+ // qualifier query values
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to query values that will be used to test for equality.
+ */
+ public NSDictionary equalToQueryValues()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to query values that will be used to test for greater value.
+ */
+ public NSDictionary greaterThanQueryValues()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to query values that will be used to test for lesser value.
+ */
+ public NSDictionary lessThanQueryValues()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Sets the Map that contains the mappings of keys
+ * to query values that will be used to test for equality.
+ */
+ public void setEqualToQueryValues ( Map aMap )
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Sets the mapping that contains the mappings of keys
+ * to query values that will be used to test for greater value.
+ */
+ public void setGreaterThanQueryValues ( Map aMap )
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Sets the mapping that contains the mappings of keys
+ * to query values that will be used to test for lesser value.
+ */
+ public void setLessThanQueryValues ( Map aMap )
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Deprecated: returns true.
+ */
+ public boolean endEditing()
+ {
+ return true;
+ }
+
+
+ // object management
+
+ /**
+ * Returns a read-only List containing all objects managed by the display group.
+ * This includes those objects not visible due to disqualification.
+ */
+ public NSArray allObjects()
+ { //System.out.println( "avoided allocation: allObjects" );
+ return allObjectsProxy;
+ }
+
+ /**
+ * Returns the total number of batches held by this display group.
+ */
+ public int batchCount()
+ {
+ if ( batchSize < 1 ) return 1;
+ int count = displayedObjects.count();
+ if ( count == 0 ) return 1;
+ return ((count-1) / batchSize) + 1;
+ }
+
+ /**
+ * Returns the index of the currently displayed batch.
+ */
+ public int currentBatchIndex()
+ {
+ return batchIndex;
+ }
+
+ /**
+ * Sets the index of the currently displayed batch.
+ */
+ public void setCurrentBatchIndex( int aBatchIndex )
+ {
+ batchIndex = aBatchIndex;
+ updateDisplayedObjects();
+ }
+
+ /**
+ * Sets the displayed objects to the batch containing
+ * the first selected object, and updates the current
+ * batch index, and returns null to force a page reload.
+ * Displays the first batch is there is no selection.
+ */
+ public Object displayBatchContainingSelectedObject()
+ {
+ if ( batchSize < 1 ) return null;
+ NSArray indexes = selectionIndexes();
+ if ( indexes.count() > 0 )
+ {
+ batchIndex =
+ ((Number)indexes.objectAtIndex( 0 )).intValue() / batchSize;
+ }
+ else
+ {
+ batchIndex = 0;
+ }
+ updateDisplayedObjects();
+ return null;
+ }
+
+ /**
+ * Sets the displayed objects to the next batch
+ * and updates the current batch index, and returns null
+ * to force a page reload. If there is no next batch
+ * the first batch is displayed.
+ */
+ public Object displayNextBatch()
+ {
+ batchIndex = (batchIndex + 1) % batchCount();
+ updateDisplayedObjects();
+ return null;
+ }
+
+ /**
+ * Sets the displayed objects to the next batch
+ * and updates the current batch index, and returns null
+ * to force a page reload. If there is no previous
+ * batch, the last batch is displayed.
+ */
+ public Object displayPreviousBatch()
+ {
+ batchIndex--;
+ if ( batchIndex < 0 ) batchIndex = batchCount() - 1;
+ updateDisplayedObjects();
+ return null;
+ }
+
+ /**
+ * Returns whether the displayed objects have been batched.
+ */
+ public boolean hasMultipleBatches()
+ {
+ return batchCount() > 1;
+ }
+
+ /**
+ * Returns the one-based index within the displayed objects list
+ * of the first displayed object in the current batch.
+ */
+ public int indexOfFirstDisplayedObject()
+ {
+ if ( batchSize < 1 ) return 1;
+ return batchIndex * batchSize + 1;
+ }
+
+ /**
+ * Returns the one-based index within the displayed objects list
+ * of the first last object in the current batch.
+ */
+ public int indexOfLastDisplayedObject()
+ {
+ if ( batchSize < 1 ) return displayedObjects.count();
+ return Math.min(
+ ((batchIndex+1) * batchSize),
+ displayedObjects.count() );
+ }
+
+ /**
+ * Returns the number of objects per batch.
+ */
+ public int numberOfObjectsPerBatch()
+ {
+ return batchSize;
+ }
+
+ /**
+ * Returns the number of objects per batch.
+ */
+ public void setNumberOfObjectsPerBatch( int aSize )
+ {
+ batchSize = aSize;
+ updateDisplayedObjects();
+ }
+
+ /**
+ * Clears the current selection.
+ * @return True is the selection was cleared,
+ * False if the selection could not be cleared
+ * @see #setSelectionIndexes
+ */
+ public boolean clearSelection()
+ {
+ Object result = notifyDelegate(
+ "displayGroupShouldChangeSelection",
+ new Class[] { WODisplayGroup.class, List.class },
+ new Object[] { this, new NSArray( selectedObjects ) } );
+ if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) )
+ {
+ return false;
+ }
+
+ selectionChanged = true;
+ willChange();
+
+ selectedObjects.removeAllObjects();
+ selectedIndexes.removeAllObjects();
+
+ notifyDelegate(
+ "displayGroupDidChangeSelection",
+ new Class[] { WODisplayGroup.class },
+ new Object[] { this } );
+ notifyDelegate(
+ "displayGroupDidChangeSelectedObjects",
+ new Class[] { WODisplayGroup.class },
+ new Object[] { this } );
+
+ return true;
+ }
+
+ /**
+ * Convenience for binding to a component action:
+ * calls deleteSelection() and then displayBatchContainingSelectedObject()
+ * and returns null, which is a suitable result from a component action.
+ */
+ public Object delete()
+ {
+ deleteSelection();
+ displayBatchContainingSelectedObject();
+ return null;
+ }
+
+ /**
+ * Deletes the object at the specified index,
+ * notifying the delegate before and after the operation,
+ * and then updating the selection if needed.
+ * @return True if delete was successful, false if the
+ * object was not deleted.
+ */
+ public boolean deleteObjectAtIndex ( int anIndex )
+ {
+ Object target = displayedObjects.objectAtIndex( anIndex );
+
+ Object result = notifyDelegate(
+ "displayGroupShouldDeleteObject",
+ new Class[] { WODisplayGroup.class, Object.class },
+ new Object[] { this, target } );
+ if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) )
+ {
+ return false;
+ }
+
+ deleteObjectAtIndexNoNotify( anIndex );
+
+ if ( dataSource != null )
+ {
+ dataSource.deleteObject( target );
+ }
+
+ notifyDelegate(
+ "displayGroupDidDeleteObject",
+ new Class[] { WODisplayGroup.class, Object.class },
+ new Object[] { this, target } );
+
+ return true;
+ }
+
+ private void deleteObjectAtIndexNoNotify ( int anIndex )
+ {
+ Object target = displayedObjects.objectAtIndex( anIndex );
+
+ int i;
+
+ // remove from selected objects if necessary
+ i = indexOf( selectedObjects, target );
+ if ( i != NSArray.NotFound )
+ {
+ selectionChanged = true;
+ willChange(); // notify before removing
+ selectedObjects.removeObjectAtIndex( i );
+ selectedIndexes.remove( new Integer( i ) ); // comps by value
+ }
+ else // notify - no selection change needed
+ {
+ willChange();
+ }
+
+ // remove from all objects
+ i = indexOf( allObjects, target );
+ if ( i != NSArray.NotFound )
+ {
+ allObjects.removeObjectAtIndex( i );
+ }
+ else // otherwise should never happen
+ {
+// throw new WotonomyException(
+// "Displayed object not found in allObjects" );
+ }
+
+ // remove from displayed objects
+ displayedObjects.removeObjectAtIndex( anIndex );
+ }
+
+ /**
+ * Deletes the currently selected objects.
+ * This implementation calls deleteObjectAtIndex() for
+ * each index in the selection list, immediately returning
+ * false if any delete operation fails.
+ * @return True if all selected objects were deleted,
+ * false if any deletion failed.
+ */
+ public boolean deleteSelection()
+ {
+ int i;
+ boolean result = true;
+
+ Enumeration e = new NSArray( selectedObjects ).objectEnumerator();
+ while ( e.hasMoreElements() )
+ {
+ i = indexOf( displayedObjects, e.nextElement() );
+ if ( i == NSArray.NotFound )
+ {
+ // should never happen
+ throw new WotonomyException(
+ "Selected object not found in displayedObjects" );
+ }
+ result = result && deleteObjectAtIndex( i );
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns a read-only List of all objects in the display group
+ * that are currently displayed by the associations.
+ */
+ public NSArray displayedObjects()
+ { // System.out.println( "avoided allocation: displayedObjects" );
+ if ( batchSize < 1 ) return displayedObjectsProxy;
+ return displayedObjectsProxy.subarrayWithRange(
+ new NSRange( batchIndex * batchSize, batchSize ) );
+ }
+
+ /**
+ * Requests a list of objects from the DataSource
+ * and calls setObjectArray to populate the list.
+ * More specifically, calls endEditing(), asks the
+ * delegate, fetches the objects, notifies the delegate,
+ * and populates the list. Returns null to force a
+ * page reload.
+ */
+ public Object fetch()
+ {
+ endEditing();
+
+ if ( dataSource == null )
+ {
+ return null;
+ }
+
+ Object result = notifyDelegate(
+ "displayGroupShouldFetch",
+ new Class[] { WODisplayGroup.class },
+ new Object[] { this } );
+ if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) )
+ {
+ return null;
+ }
+
+ NSNotificationCenter.defaultCenter().postNotification(
+ DisplayGroupWillFetchNotification, this, new NSDictionary() );
+
+ NSArray objectList = dataSource.fetchObjects();
+
+ notifyDelegate(
+ "displayGroupDidFetchObjects",
+ new Class[] { WODisplayGroup.class, List.class },
+ new Object[] { this, objectList } );
+
+ setObjectArray( objectList );
+
+ if ( ( selectsFirstObjectAfterFetch ) && ( displayedObjects.size() > 0 ) )
+ {
+ setSelectionIndexes( new NSArray( new Integer( 0 ) ) );
+ }
+
+ return null;
+ }
+
+ /**
+ * Convenience to call insertNewObjectAtIndex with the current selection plus one,
+ * or at the end of the list if there is no selection.
+ * Returns null to force a page reload.
+ */
+ public Object insert()
+ {
+ NSArray indexes = selectionIndexes();
+ int size = indexes.count();
+ if ( size == 0 )
+ {
+ insertNewObjectAtIndex( displayedObjects.count() );
+ }
+ else
+ {
+ insertNewObjectAtIndex(
+ ((Number)selectedIndexes.objectAtIndex( size-1 )).intValue()+1 );
+ }
+ return null;
+ }
+
+ /**
+ * Creates a new object at the specified index.
+ * Calls insertObjectAtIndex() with the result
+ * from sending createObject() to the data source.
+ * @return the newly created object.
+ */
+ public Object insertNewObjectAtIndex ( int anIndex )
+ {
+ Object result = null;
+ if ( dataSource != null )
+ {
+ result = dataSource.createObject();
+ }
+ if ( result != null )
+ {
+ if ( insertedObjectDefaultValues != null )
+ {
+ Duplicator.writePropertiesForObject(
+ insertedObjectDefaultValues, result );
+ }
+ insertObjectAtIndex( result, anIndex );
+ }
+ else // create failed
+ {
+ if ( delegate() != null )
+ {
+ NSSelector selector = new NSSelector(
+ "displayGroupCreateObjectFailed",
+ new Class[] { WODisplayGroup.class, EODataSource.class } );
+ if ( selector.implementedByObject( delegate() ) )
+ {
+ try
+ {
+ selector.invoke( delegate(), new Object[] { this, dataSource } );
+ return result;
+ }
+ catch ( Exception exc )
+ {
+ System.err.println( "Error notifying delegate: displayGroupCreateObjectFailed" );
+ exc.printStackTrace();
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Inserts the specified object into the list at
+ * the specified index.
+ */
+ public void insertObjectAtIndex ( Object anObject, int anIndex )
+ {
+ Object result = notifyDelegate(
+ "displayGroupShouldInsertObject",
+ new Class[] { WODisplayGroup.class, Object.class, int.class },
+ new Object[] { this, anObject, new Integer(anIndex) } );
+ if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) )
+ {
+ return;
+ }
+
+ updatedObjectIndex = anIndex;
+ willChange();
+
+ int i;
+
+ // add to all objects
+ if ( anIndex == displayedObjects.size() )
+ {
+ allObjects.addObject( anObject );
+ }
+ else // insert before same object
+ {
+ Object target = displayedObjects.objectAtIndex( anIndex );
+ int targetIndex = indexOf( allObjects, target );
+ if ( targetIndex != NSArray.NotFound )
+ {
+ allObjects.insertObjectAtIndex( anObject, targetIndex );
+ }
+ else // should never happen
+ {
+ throw new WotonomyException(
+ "Could not find displayed object in all objects list: "
+ + target );
+ }
+ }
+
+ // add to displayed objects
+ displayedObjects.insertObjectAtIndex( anObject, anIndex );
+
+ if ( dataSource != null )
+ {
+ if ( dataSource instanceof OrderedDataSource )
+ {
+ ((OrderedDataSource)dataSource).insertObjectAtIndex(
+ anObject, anIndex );
+ }
+ else
+ {
+ dataSource.insertObject( anObject );
+ }
+ }
+
+ notifyDelegate(
+ "displayGroupDidInsertObject",
+ new Class[] { WODisplayGroup.class, Object.class },
+ new Object[] { this, anObject } );
+ }
+
+ /**
+ * Sets contentsChanged to true and notifies all observers.
+ */
+ public void redisplay()
+ {
+ willChange();
+ }
+
+ /**
+ * Sets the selection to the next displayed object after the current
+ * selection. If the last object is selected, or if no object
+ * is selected, then the first object becomes selected.
+ * If multiple items are selected, the first selected item is
+ * considered the selected item for the purposes of this method.
+ * Does not call redisplay().
+ * @return null to force a page reload.
+ */
+ public Object selectNext()
+ {
+ int count = displayedObjects.count();
+ if ( count == 0 ) return null;
+ if ( count == 1 )
+ {
+ selectObject( displayedObjects.objectAtIndex( 0 ) );
+ return null;
+ }
+
+ int i = -1;
+ Object selectedObject = selectedObject();
+ if ( selectedObject != null )
+ {
+ i = indexOf( displayedObjects, selectedObject );
+ }
+ if ( i == NSArray.NotFound ) i = -1;
+
+ // select next object
+ i++;
+ if ( i != displayedObjects.count() )
+ {
+ // set to next object
+ selectedObject = displayedObjects.objectAtIndex( i );
+ }
+ else // out of range
+ {
+ // set to null
+ selectedObject = displayedObjects.objectAtIndex( 0 );
+ }
+
+ selectObject( selectedObject );
+ return null;
+ }
+
+ /**
+ * Sets the selection to the specified object.
+ * If the specified object is null or does not exist
+ * in the list of displayed objects, the selection
+ * will be cleared.
+ * @return true if the object was selected.
+ */
+ public boolean selectObject ( Object anObject )
+ {
+ if ( ( anObject == null ) ||
+ ( indexOf( displayedObjects, anObject )
+ == NSArray.NotFound ) )
+ {
+ clearSelection();
+ return false;
+ }
+
+ selectObjectsIdenticalTo( new NSArray( new Object[] { anObject } ) );
+ return true;
+ }
+
+ /**
+ * Sets the selection to the specified objects.
+ * If the specified list is null or if none of the objects
+ * in the list exist in the list of displayed objects, the
+ * selection will be cleared.
+ * @return true if all specified objects were selected.
+ */
+ public boolean selectObjectsIdenticalTo ( List anObjectList )
+ {
+ // optimization: check for resetting of selection
+ if ( ( anObjectList != null ) && ( selectedObjects.size() == anObjectList.size() ) )
+ {
+ boolean identical = true;
+ int size = selectedObjects.size();
+ for ( int i = 0; ( i < size ) && identical; i++ )
+ {
+ // compare by reference
+ if ( anObjectList.get( i ) != selectedObjects.get( i ) )
+ {
+ identical = false;
+ }
+ else if ( displayedObjects.indexOfIdenticalObject(
+ anObjectList.get( i ) ) == NSArray.NotFound )
+ {
+ identical = false;
+ }
+ }
+ if ( identical )
+ {
+ return true;
+ }
+ }
+
+ Object result = notifyDelegate(
+ "displayGroupShouldChangeSelection",
+ new Class[] { WODisplayGroup.class, List.class },
+ new Object[] { this, anObjectList } );
+ if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) )
+ {
+ // need to notify the calling component
+ // to revert back to the previous selection
+ selectionChanged = true;
+ willChange();
+ return false;
+ }
+
+ int i;
+ selectionChanged = true;
+ willChange();
+ Object o;
+ selectedObjects.removeAllObjects();
+ selectedIndexes.removeAllObjects();
+ Iterator it = anObjectList.iterator();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ if ( ( i = displayedObjects.indexOfIdenticalObject( o ) )
+ != NSArray.NotFound )
+ {
+ selectedObjects.addObject( o );
+ selectedIndexes.addObject( new Integer( i ) );
+ }
+ }
+
+ notifyDelegate(
+ "displayGroupDidChangeSelection",
+ new Class[] { WODisplayGroup.class },
+ new Object[] { this } );
+ notifyDelegate(
+ "displayGroupDidChangeSelectedObjects",
+ new Class[] { WODisplayGroup.class },
+ new Object[] { this } );
+
+ return true;
+ }
+
+ /**
+ * Calls selectObjectsIdenticalTo and if false is returned
+ * and selectFirstIfNoMatch is true, selects the first object.
+ */
+ public boolean selectObjectsIdenticalToSelectFirstOnNoMatch(
+ List anObjectList, boolean selectFirstIfNoMatch )
+ {
+ if ( selectObjectsIdenticalTo( anObjectList ) )
+ {
+ return true;
+ }
+ if ( selectFirstIfNoMatch )
+ {
+ clearSelection();
+ selectNext();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Sets the selection to the previous displayed object before the current
+ * selection. If the first object is selected, or if no object
+ * is selected, then the last object becomes selected.
+ * If multiple items are selected, the first selected item is
+ * considered the selected item for the purposes of this method.
+ * Does not call redisplay().
+ * @return null to force a page reload.
+ */
+ public Object selectPrevious()
+ {
+ int i = displayedObjects.count();
+ if ( i == 0 ) return null;
+ if ( i == 1 )
+ {
+ selectObject( displayedObjects.objectAtIndex( 0 ) );
+ return null;
+ }
+
+ Object selectedObject = selectedObject();
+ if ( selectedObject != null )
+ {
+ i = indexOf( displayedObjects, selectedObject );
+ }
+ if ( i == NSArray.NotFound ) i = displayedObjects.count();
+
+ // select next object
+ i--;
+ if ( i < 0 )
+ {
+ // out of range - select last object
+ i = displayedObjects.count() - 1;
+ }
+
+ selectObject( displayedObjects.objectAtIndex( i ) );
+ return null;
+ }
+
+ /**
+ * Returns the currently selected object, or null if
+ * there is no selection.
+ */
+ public Object selectedObject()
+ {
+ if ( selectedObjects.count() == 0 )
+ {
+ return null;
+ }
+ return selectedObjects.objectAtIndex( 0 );
+ }
+
+ /**
+ * Returns a read-only List containing all selected objects, if any.
+ * Returns an empty list if no objects are selected.
+ */
+ public NSArray selectedObjects()
+ { // System.out.println( "avoided allocation: selectedObjects" );
+ return selectedObjectsProxy;
+ }
+
+ /**
+ * Returns a read-only List containing the indexes of all selected
+ * objects, if any. The list contains instances of
+ * java.lang.Number; call intValue() to retrieve the index.
+ */
+ public NSArray selectionIndexes()
+ {
+// return selectedIndexes;
+ int i;
+ NSMutableArray result = new NSMutableArray();
+ Enumeration e = selectedObjects.objectEnumerator();
+ while ( e.hasMoreElements() )
+ {
+ i = indexOf( displayedObjects, e.nextElement() );
+ if ( i != NSArray.NotFound )
+ {
+ result.addObject( new Integer( i ) );
+ }
+ else
+ {
+ System.err.println(
+ "Should never happen: selected objects not in displayed objects" );
+ new RuntimeException().printStackTrace( System.err );
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Sets the objects managed by this display group.
+ * updateDisplayedObjects() is called to filter the
+ * display objects. The previous selection will be
+ * maintained if possible. The data source is not
+ * notified.
+ */
+ public void setObjectArray ( List anObjectList )
+ {
+ if ( anObjectList == null ) anObjectList = new NSArray();
+
+ Object result = notifyDelegate(
+ "displayGroupDisplayArrayForObjects",
+ new Class[] { WODisplayGroup.class, List.class },
+ new Object[] { this, anObjectList } );
+ if ( result != null )
+ {
+ anObjectList = (List) result;
+ }
+
+ willChange();
+
+ NSArray oldSelectedObjects = new NSArray( selectedObjects ); // copy
+
+ // reset allObjects to new list
+ allObjects.removeAllObjects();
+ allObjects.addObjectsFromArray( anObjectList );
+
+ // update the displayed object list
+ updateDisplayedObjects();
+
+ // restore the selection if possible
+ selectObjectsIdenticalTo( oldSelectedObjects );
+
+ batchIndex = 0;
+ displayBatchContainingSelectedObject();
+ }
+
+ /**
+ * Sets the currently selected object, or clears the
+ * selection if the object is not found or is null.
+ * Note: it's not clear how this differs from
+ * selectObject in the spec. It is recommended that
+ * you call selectObject for now.
+ */
+ public void setSelectedObject ( Object anObject )
+ {
+ selectObject( anObject );
+ }
+
+ /**
+ * Sets the current selection to the specified objects.
+ * The previous selection is cleared, and any objects
+ * in the display group that are in the specified list
+ * are then selected. If no items in the specified list
+ * are found in the display group, then the selection is
+ * effectively cleared.
+ * Note: it's not clear how this differs from
+ * selectObjectsIdenticalTo in the spec.
+ * It is recommended that you call that method for now.
+ */
+ public void setSelectedObjects ( List aList )
+ {
+ selectObjectsIdenticalTo( aList );
+ }
+
+ /**
+ * Sets the current selection to the objects at the
+ * specified indexes. Items in the list are assumed
+ * to be instances of java.lang.Number.
+ * The previous selection is cleared, and any objects
+ * in the display group that are in the specified list
+ * are then selected. If no items in the specified list
+ * are found in the display group, then the selection is
+ * effectively cleared.
+ */
+ public boolean setSelectionIndexes ( List aList )
+ {
+ Object o;
+ int index;
+ NSMutableArray objects = new NSMutableArray();
+ Iterator it = aList.iterator();
+ while ( it.hasNext() )
+ {
+ index = ((Number)it.next()).intValue();
+ if ( index < displayedObjects.count() )
+ {
+ o = displayedObjects.objectAtIndex( index );
+ if ( o != null )
+ {
+ objects.add( o );
+ }
+ }
+ }
+ return selectObjectsIdenticalTo( objects );
+ }
+
+ /**
+ * Applies the qualifier to all objects and sorts
+ * the results to update the list of displayed objects.
+ * Observing associations are notified to reflect the changes.
+ */
+ public void updateDisplayedObjects()
+ {
+ updatedObjectIndex = -1;
+ willChange();
+
+ displayedObjects.removeAllObjects();
+
+ displayedObjects.addObjectsFromArray( allObjects );
+
+ // apply qualifier, if any
+ if ( qualifier != null )
+ {
+ EOQualifier.filterArrayWithQualifier(
+ displayedObjects, qualifier );
+ }
+
+ // apply sort orderings, if any
+ NSArray orderings = sortOrderings();
+ if ( orderings != null )
+ {
+ if ( orderings.count() > 0 )
+ {
+ selectionChanged = true;
+ willChange();
+ EOSortOrdering.sortArrayUsingKeyOrderArray(
+ displayedObjects, orderings );
+ }
+ }
+
+ // make sure the selectedObjects is a subset of displayedObjects
+ int i;
+ Object o;
+ Iterator it = new LinkedList( selectedObjects ).iterator();
+ boolean removeflag = false;
+ selectedIndexes.removeAllObjects();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ if ( ( i = displayedObjects.indexOfIdenticalObject( o ) )
+ == NSArray.NotFound )
+ {
+ selectedObjects.removeIdenticalObject( o );
+ removeflag = true;
+ }
+ else
+ {
+ selectedIndexes.addObject( new Integer( i ) );
+ }
+ }
+
+ //Note: it is important to put the
+ //selectionChanged = true line below remove.
+ if (removeflag)
+ {
+ selectionChanged = true;
+ willChange();
+
+ notifyDelegate(
+ "displayGroupDidChangeSelection",
+ new Class[] { WODisplayGroup.class },
+ new Object[] { this } );
+ notifyDelegate(
+ "displayGroupDidChangeSelectedObjects",
+ new Class[] { WODisplayGroup.class },
+ new Object[] { this } );
+ }
+ }
+
+ /**
+ * Returns the index of the changed object. If more than
+ * one object has changed, -1 is returned.
+ */
+ public int updatedObjectIndex()
+ {
+ return updatedObjectIndex;
+ }
+
+ // getting and setting values in objects
+
+ /**
+ * Returns a value on the selected object for the specified key.
+ */
+ public Object selectedObjectValueForKey ( String aKey )
+ {
+ Object selectedObject = selectedObject();
+ if ( selectedObject == null ) return null;
+ return valueForObject( selectedObject, aKey );
+ }
+
+ /**
+ * Sets the specified value for the specified key on
+ * all selected objects.
+ */
+ public boolean setSelectedObjectValue (
+ Object aValue, String aKey )
+ {
+ Object selectedObject = selectedObject();
+ if ( selectedObject == null ) return false;
+ return setValueForObject( aValue, selectedObject, aKey );
+ }
+
+ /**
+ * Sets the specified value for the specified key on
+ * the specified object. Validations may be triggered,
+ * and error dialogs may appear to the user.
+ * @return True if the value was set successfully,
+ * false if the value could not be set and the update
+ * operation should not continue.
+ */
+ public boolean setValueForObject (
+ Object aValue, Object anObject, String aKey )
+ {
+ // notify object's observers:
+ // this includes us, and will notify our observers
+ EOObserverCenter.notifyObserversObjectWillChange( anObject );
+
+ //TODO: if key is null, need to remove old object
+ // and add new object instead of simply replacing it.
+
+ try
+ {
+ if ( anObject instanceof EOKeyValueCoding )
+ {
+ ((EOKeyValueCoding)anObject).takeValueForKey( aValue, aKey );
+ }
+ else
+ {
+ EOKeyValueCodingSupport.takeValueForKey( anObject, aValue, aKey );
+ }
+ }
+ catch ( RuntimeException exc )
+ {
+ Object result = notifyDelegate(
+ "displayGroupShouldDisplayAlert",
+ new Class[] { WODisplayGroup.class, String.class, String.class },
+ new Object[] { this, "Error", exc.getMessage() } );
+ if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
+ {
+ throw exc;
+ }
+ return false;
+ }
+
+ notifyDelegate(
+ "displayGroupDidSetValueForObject",
+ new Class[] { WODisplayGroup.class, Object.class, Object.class, String.class },
+ new Object[] { this, aValue, anObject, aKey } );
+
+ return true;
+ }
+
+ /**
+ * Calls setValueForObject() for the object at
+ * the specified index.
+ */
+ public boolean setValueForObjectAtIndex (
+ Object aValue, int anIndex, String aKey )
+ {
+ return setValueForObject(
+ aValue, displayedObjects.objectAtIndex( anIndex ), aKey );
+ }
+
+ /**
+ * Returns the value for the specified key on the specified object.
+ */
+ public Object valueForObject ( Object anObject, String aKey )
+ {
+ // empty string is considered the identity property
+ if ( aKey == null ) return anObject;
+ if ( aKey.equals( "" ) ) return anObject;
+
+ try
+ {
+ if ( anObject instanceof EOKeyValueCoding )
+ {
+ return ((EOKeyValueCoding)anObject).valueForKey( aKey );
+ }
+ else
+ {
+ return EOKeyValueCodingSupport.valueForKey( anObject, aKey );
+ }
+ }
+ catch ( RuntimeException exc )
+ {
+ Object result = notifyDelegate(
+ "displayGroupShouldDisplayAlert",
+ new Class[] { WODisplayGroup.class, String.class, String.class },
+ new Object[] { this, "Error", exc.getMessage() } );
+ if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
+ {
+ throw exc;
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Calls valueForObject() for the object at the specified index.
+ * Returns null if out of bounds.
+ */
+ public Object valueForObjectAtIndex ( int anIndex, String aKey )
+ {
+ if ( displayedObjects.count() <= anIndex ) return null;
+ Object o = displayedObjects.objectAtIndex( anIndex );
+ return valueForObject( o, aKey );
+ }
+
+ /**
+ * Prints out the list of displayed objects.
+ */
+ public String toString()
+ {
+ return displayedObjects.toString();
+ }
+
+
+ /**
+ * Handles notifications from the data source's editing context,
+ * looking for InvalidatedAllObjectsInStoreNotification and
+ * ObjectsChangedInEditingContextNotification, refetching in
+ * the former case and updating displayed objects in the latter.
+ * Note: This method is not in the public specification.
+ */
+ public void objectsInvalidatedInEditingContext( NSNotification aNotification )
+ {
+ if ( EOObjectStore.InvalidatedAllObjectsInStoreNotification
+ .equals( aNotification.name() ) )
+ {
+ Object result = notifyDelegate(
+ "displayGroupShouldRefetch",
+ new Class[] { WODisplayGroup.class, NSNotification.class },
+ new Object[] { this, aNotification } );
+ if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
+ {
+ fetch();
+ }
+ }
+ else
+ if ( EOEditingContext.ObjectsChangedInEditingContextNotification
+ .equals( aNotification.name() ) )
+ {
+ Object result = notifyDelegate(
+ "displayGroupShouldRedisplay",
+ new Class[] { WODisplayGroup.class, NSNotification.class },
+ new Object[] { this, aNotification } );
+ if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
+ {
+ int index;
+ Enumeration e;
+ boolean didChange = false;
+ NSDictionary userInfo = aNotification.userInfo();
+
+ // inserts are ignored
+
+ // mark updated objects as updated
+ NSArray updates = (NSArray) userInfo.objectForKey(
+ EOObjectStore.UpdatedKey );
+ e = updates.objectEnumerator();
+ while ( e.hasMoreElements() )
+ {
+ index = indexOf( displayedObjects, e.nextElement() );
+ if ( index != NSArray.NotFound )
+ {
+ //System.out.println( "WODisplayGroup: updated: " + index );
+ if ( ! didChange )
+ {
+ didChange = true;
+ willChange();
+ updatedObjectIndex = index;
+ }
+ else
+ {
+ updatedObjectIndex = -1;
+ }
+ }
+ }
+
+ // treat invalidated objects as updated
+ NSArray invalidates = (NSArray) userInfo.objectForKey(
+ EOObjectStore.InvalidatedKey );
+ e = invalidates.objectEnumerator();
+ while ( e.hasMoreElements() )
+ {
+ index = indexOf( displayedObjects, e.nextElement() );
+ if ( index != NSArray.NotFound )
+ {
+ //System.out.println( "WODisplayGroup: invalidated: " + index );
+ if ( ! didChange )
+ {
+ didChange = true;
+ willChange();
+ updatedObjectIndex = index;
+ }
+ else
+ {
+ updatedObjectIndex = -1;
+ }
+ }
+ }
+
+ // remove deletes from display group if they exist
+ NSArray deletes = (NSArray) userInfo.objectForKey(
+ EOObjectStore.DeletedKey );
+ e = deletes.objectEnumerator();
+ Object o;
+ while ( e.hasMoreElements() )
+ {
+ o = e.nextElement();
+ index = indexOf( displayedObjects, o );
+ if ( index != NSArray.NotFound )
+ {
+ //System.out.println( "WODisplayGroup: deleted: " + o );
+ deleteObjectAtIndexNoNotify( index );
+ }
+ }
+
+ if ( !usesOptimisticRefresh() )
+ {
+ updateDisplayedObjects();
+ }
+ }
+ }
+
+ }
+
+ // static methods
+
+ /**
+ * Specifies the default behavior for whether changes
+ * should be validated immediately for all display groups.
+ */
+ public static boolean
+ globalDefaultForValidatesChangesImmediately()
+ {
+ return globalDefaultForValidatesChangesImmediately;
+ }
+
+ /**
+ * Specifies the default string matching format for all
+ * display groups.
+ */
+ public static String globalDefaultStringMatchFormat()
+ {
+ return globalDefaultStringMatchFormat;
+ }
+
+ /**
+ * Specifies the default string matching operator for all
+ * display groups.
+ */
+ public static String globalDefaultStringMatchOperator()
+ {
+ return globalDefaultStringMatchOperator;
+ }
+
+ /**
+ * Sets the default behavior for validating changes
+ * for all display groups.
+ */
+ public static void
+ setGlobalDefaultForValidatesChangesImmediately (
+ boolean validatesImmediately )
+ {
+ globalDefaultForValidatesChangesImmediately =
+ validatesImmediately;
+ }
+
+ /**
+ * Sets the default string matching format that
+ * will be used by all display groups.
+ */
+ public static void
+ setGlobalDefaultStringMatchFormat ( String aFormat )
+ {
+ globalDefaultStringMatchFormat = aFormat;
+ }
+
+ /**
+ * Sets the default string matching operator that
+ * will be used by all display groups.
+ */
+ public static void
+ setGlobalDefaultStringMatchOperator ( String anOperator )
+ {
+ globalDefaultStringMatchOperator = anOperator;
+ }
+
+ /**
+ * Needed because we don't inherit from NSObject.
+ * Calls EOObserverCenter.notifyObserversObjectWillChange.
+ */
+ protected void willChange()
+ {
+ EOObserverCenter.notifyObserversObjectWillChange( this );
+ }
+
+ /**
+ * Returns the index of the specified object in the
+ * specified NSArray, comparing by value or by reference
+ * as determined by the private instance variable
+ * compareByReference. If not found, returns NSArray.NotFound.
+ */
+ private int indexOf( NSArray anArray, Object anObject )
+ {
+ if ( compareByReference )
+ {
+ return anArray.indexOfIdenticalObject( anObject );
+ }
+ else
+ {
+ return anArray.indexOf( anObject );
+ }
+ }
+
+ // interface EOObserving
+
+ /**
+ * Receives notifications of changes from objects that
+ * are managed by this display group. This implementation
+ * sets updatedObjectIndex as appropriate.
+ */
+ public void objectWillChange(Object anObject)
+ {
+ int index = indexOf( displayedObjects, anObject );
+ if ( index != NSArray.NotFound )
+ {
+ updatedObjectIndex = index;
+ willChange();
+ }
+ }
+
+ // interface EOEditingContext.Editor
+
+ /**
+ * Called before the editing context begins to save changes.
+ * This implementation calls endEditing().
+ */
+ public void editingContextWillSaveChanges(
+ EOEditingContext anEditingContext )
+ {
+ endEditing();
+ }
+
+ /**
+ * Called to determine whether this editor has changes
+ * that have not been committed to the object in the context.
+ * This implementation returns false.
+ */
+ public boolean editorHasChangesForEditingContext(
+ EOEditingContext anEditingContext )
+ {
+ return false;
+ }
+
+ // interface EOEditingContext.MessageHandler
+
+ /**
+ * Called to display a message for an error that occurred
+ * in the specified editing context. If the delegate allows,
+ * this implementation writes a message to the standard output.
+ * Override to customize.
+ */
+ public void editingContextPresentErrorMessage(
+ EOEditingContext anEditingContext,
+ String aMessage )
+ {
+ Object result = notifyDelegate(
+ "displayGroupShouldDisplayAlert",
+ new Class[] { WODisplayGroup.class, String.class, String.class },
+ new Object[] { this, "Error", aMessage } );
+ if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
+ {
+ System.out.println( aMessage );
+ }
+ }
+
+ /**
+ * Called by the specified object store to determine whether
+ * fetching should continue, where count is the current count
+ * and limit is the limit as specified by the fetch specification.
+ * This implementation returns true. Override to customize.
+ */
+ public boolean editingContextShouldContinueFetching(
+ EOEditingContext anEditingContext,
+ int count,
+ int limit,
+ EOObjectStore anObjectStore )
+ {
+ return true;
+ }
+
+ /**
+ * Sends the specified message to the delegate.
+ * Returns the return value of the method,
+ * or null if no return value or no delegate
+ * or no implementation.
+ */
+ private Object notifyDelegate(
+ String aMethodName, Class[] types, Object[] params )
+ {
+ try
+ {
+ Object delegate = delegate();
+ if ( delegate == null ) return null;
+ return NSSelector.invoke(
+ aMethodName, types, delegate, params );
+ }
+ catch ( NoSuchMethodException e )
+ {
+ // ignore: not implemented
+ }
+ catch ( Exception exc )
+ {
+ // log to standard error
+ System.err.println(
+ "Error while messaging delegate: " +
+ delegate + " : " + aMethodName );
+ exc.printStackTrace();
+ }
+
+ return null;
+ }
+
+ /**
+ * DisplayGroups can delegate important decisions to a Delegate.
+ * Note that DisplayGroup doesn't require its delegates to implement
+ * this interface: rather, this interface defines the methods that
+ * DisplayGroup will attempt to invoke dynamically on its delegate.
+ * The delegate may choose to implement only a subset of the methods
+ * on the interface.
+ */
+ public interface Delegate
+ {
+ /**
+ * Called when the specified data source fails
+ * to create an object for the specified display group.
+ */
+ void displayGroupCreateObjectFailed (
+ WODisplayGroup aDisplayGroup,
+ EODataSource aDataSource );
+
+ /**
+ * Called after the specified display group's
+ * data source is changed.
+ */
+ void displayGroupDidChangeDataSource (
+ WODisplayGroup aDisplayGroup );
+
+ /**
+ * Called after a change occurs in the specified
+ * display group's selected objects.
+ */
+ void displayGroupDidChangeSelectedObjects (
+ WODisplayGroup aDisplayGroup );
+
+ /**
+ * Called after the specified display group's
+ * selection has changed.
+ */
+ void displayGroupDidChangeSelection (
+ WODisplayGroup aDisplayGroup );
+
+ /**
+ * Called after the specified display group has
+ * deleted the specified object.
+ */
+ void displayGroupDidDeleteObject (
+ WODisplayGroup aDisplayGroup,
+ Object anObject );
+
+ /**
+ * Called after the specified display group
+ * has fetched the specified object list.
+ */
+ void displayGroupDidFetchObjects (
+ WODisplayGroup aDisplayGroup,
+ List anObjectList );
+
+ /**
+ * Called after the specified display group
+ * has inserted the specified object into
+ * its internal object list.
+ */
+ void displayGroupDidInsertObject (
+ WODisplayGroup aDisplayGroup,
+ Object anObject );
+
+ /**
+ * Called after the specified display group
+ * has set the specified value for the specified
+ * object and key.
+ */
+ void displayGroupDidSetValueForObject (
+ WODisplayGroup aDisplayGroup,
+ Object aValue,
+ Object anObject,
+ String aKey );
+
+ /**
+ * Called by the specified display group to
+ * determine what objects should be displayed
+ * for the objects in the specified list.
+ * @return An NSArray containing the objects
+ * to be displayed for the objects in the
+ * specified list.
+ */
+ NSArray displayGroupDisplayArrayForObjects (
+ WODisplayGroup aDisplayGroup,
+ List aList );
+
+ /**
+ * Called by the specified display group before
+ * it attempts to change the selection.
+ * @return True to allow the selection to change,
+ * false otherwise.
+ */
+ boolean displayGroupShouldChangeSelection (
+ WODisplayGroup aDisplayGroup,
+ List aSelectionList );
+
+ /**
+ * Called by the specified display group before
+ * it attempts to delete the specified object.
+ * @return True to allow the object to be deleted
+ * false to prevent the deletion.
+ */
+ boolean displayGroupShouldDeleteObject (
+ WODisplayGroup aDisplayGroup,
+ Object anObject );
+
+ /**
+ * Called by the specified display group before
+ * it attempts display the specified alert to
+ * the user.
+ * @return True to allow the message to be
+ * displayed, false if you want to handle the
+ * alert yourself and suppress the display group's
+ * notification.
+ */
+ boolean displayGroupShouldDisplayAlert (
+ WODisplayGroup aDisplayGroup,
+ String aTitle,
+ String aMessage );
+
+ /**
+ * Called by the specified display group before
+ * it attempts fetch objects.
+ * @return True to allow the fetch to take place,
+ * false to prevent the fetch.
+ */
+ boolean displayGroupShouldFetch (
+ WODisplayGroup aDisplayGroup );
+
+ /**
+ * Called by the specified display group before
+ * it attempts to insert the specified object.
+ * @return True to allow the object to be inserted
+ * false to prevent the insertion.
+ */
+ boolean displayGroupShouldInsertObject (
+ WODisplayGroup aDisplayGroup,
+ Object anObject,
+ int anIndex );
+
+ /**
+ * Called by the specified display group when
+ * it receives the specified
+ * ObjectsChangedInEditingContextNotification.
+ * @return True to allow the display group to
+ * update the display (recommended), false
+ * to prevent the update.
+ */
+ boolean displayGroupShouldRedisplay (
+ WODisplayGroup aDisplayGroup,
+ NSNotification aNotification );
+
+ /**
+ * Called by the specified display group when
+ * it receives the specified
+ * InvalidatedAllObjectsInStoreNotification.
+ * @return True to allow the display group to
+ * refetch (recommended), false to prevent the
+ * refetch.
+ */
+ boolean displayGroupShouldRefetch (
+ WODisplayGroup aDisplayGroup,
+ NSNotification aNotification );
+
+ }
+
+}
+
+/*
+ * $Log$
+ * Revision 1.2 2006/02/19 01:44:02 cgruber
+ * Add xmlrpc files
+ * Remove jclark and replace with dom4j and javax.xml.sax stuff
+ * Re-work dependencies and imports so it all compiles.
+ *
+ * Revision 1.1 2006/02/16 13:22:22 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.6 2003/08/07 00:15:15 chochos
+ * general cleanup (mostly removing unused imports)
+ *
+ * Revision 1.5 2003/01/22 23:01:06 mpowers
+ * Better handling for index out of bounds.
+ *
+ * Revision 1.4 2003/01/21 22:27:02 mpowers
+ * Corrected context id usage.
+ * Implemented backtracking.
+ *
+ * Revision 1.3 2003/01/21 17:54:01 mpowers
+ * Batch indices are one-based, not zero-based.
+ *
+ * Revision 1.2 2003/01/21 14:38:42 mpowers
+ * Fixed batching.
+ *
+ * Revision 1.1 2003/01/18 23:30:42 mpowers
+ * WODisplayGroup now compiles.
+ *
+ * Revision 1.46 2002/10/24 21:15:36 mpowers
+ * New implementations of NSArray and subclasses.
+ *
+ * Revision 1.45 2002/10/24 18:20:20 mpowers
+ * Because NSArray is read-only, we are returning our internal representations
+ * to callers of allObjects(), displayedObjects(), and selectedObjects().
+ *
+ * Revision 1.44 2002/08/06 18:20:25 mpowers
+ * Now posting DisplayGroupWillFetch notifications before fetch.
+ * Implemented support for usesOptimisticRefresh.
+ * No longer supporting inserted/updated/deleted lists: not part of spec.
+ *
+ * Revision 1.43 2002/05/17 15:01:49 mpowers
+ * Implemented dynamic lookup of delegate methods so delegates no longer
+ * need to implement the DisplayGroup.Delegate interface.
+ *
+ * Revision 1.42 2002/03/26 21:46:06 mpowers
+ * Contributing EditingContext as a java-friendly convenience.
+ *
+ * Revision 1.41 2002/03/11 03:17:56 mpowers
+ * Provided control point for coalesced changes.
+ *
+ * Revision 1.40 2002/03/05 23:18:28 mpowers
+ * Added documentation.
+ * Added isSelectionPaintedImmediate and isSelectionTracking attributes
+ * to TableAssociation.
+ * Added getTableAssociation to TableColumnAssociation.
+ *
+ * Revision 1.39 2002/02/19 22:26:04 mpowers
+ * Implemented EOEditingContext.MessageHandler support.
+ *
+ * Revision 1.38 2002/02/19 16:37:38 mpowers
+ * Implemented support for EOEditingContext.Editor
+ *
+ * Revision 1.37 2001/12/11 22:17:48 mpowers
+ * Now properly handling exceptions in valueForObject.
+ * No longer trying to retain selection based only on index.
+ *
+ * Revision 1.36 2001/11/08 21:42:00 mpowers
+ * Now we know what to do with shouldRefetch and shouldRedisplay.
+ *
+ * Revision 1.35 2001/11/04 18:26:58 mpowers
+ * Fixed bug where exceptions were not properly reported when updating
+ * a value and the display group did not have a delegate.
+ *
+ * Revision 1.34 2001/11/02 20:59:36 mpowers
+ * Now correctly ensuring selected objects are a subset of displayed objects.
+ *
+ * Revision 1.33 2001/10/30 22:56:45 mpowers
+ * Added support for EOQualifier.
+ *
+ * Revision 1.32 2001/10/23 22:27:53 mpowers
+ * Now running at ObserverPrioritySixth.
+ *
+ * Revision 1.31 2001/10/23 18:45:05 mpowers
+ * Rolling back changes.
+ *
+ * Revision 1.28 2001/08/22 19:23:41 mpowers
+ * No longer asserting objects in all objects list.
+ *
+ * Revision 1.27 2001/07/30 16:17:01 mpowers
+ * Minor code cleanup.
+ *
+ * Revision 1.26 2001/07/10 22:49:07 mpowers
+ * Fixed bug in optimization for selectObjectsIdenticalTo (found by Dongzhi).
+ *
+ * Revision 1.25 2001/06/19 15:40:21 mpowers
+ * Now only changing the selection if the new selection is different
+ * from the old.
+ *
+ * Revision 1.24 2001/05/24 17:36:15 mpowers
+ * Fixed problem with selectedObjectsIdenticalTo: it was using compare
+ * by value instead of compare by reference.
+ *
+ * Revision 1.23 2001/05/18 21:09:19 mpowers
+ * Now throwing exceptions if the delegate cannot handle error from update.
+ *
+ * Revision 1.22 2001/05/14 15:26:12 mpowers
+ * Now checking for null delegate before and after selection change.
+ *
+ * Revision 1.21 2001/05/08 18:47:34 mpowers
+ * Minor fixes for d3.
+ *
+ * Revision 1.20 2001/04/29 22:02:45 mpowers
+ * Work on id transposing between editing contexts.
+ *
+ * Revision 1.19 2001/04/13 16:38:09 mpowers
+ * Alpha3 release.
+ *
+ * Revision 1.18 2001/04/03 20:36:01 mpowers
+ * Fixed refaulting/reverting/invalidating to be self-consistent.
+ *
+ * Revision 1.17 2001/03/29 03:31:13 mpowers
+ * No longer using Introspector.
+ *
+ * Revision 1.16 2001/02/27 03:32:18 mpowers
+ * Implemented default values for new objects.
+ *
+ * Revision 1.15 2001/02/27 02:11:17 mpowers
+ * Now throwing exception when cloning fails.
+ * Removed debugging printlns.
+ *
+ * Revision 1.14 2001/02/26 22:41:51 mpowers
+ * Implemented null placeholder classes.
+ * Duplicator now uses NSNull.
+ * No longer catching base exception class.
+ *
+ * Revision 1.13 2001/02/26 15:53:22 mpowers
+ * Fine-tuning notification firing.
+ * Child display groups now update properly after parent save or invalidate.
+ *
+ * Revision 1.12 2001/02/22 20:55:06 mpowers
+ * Implemented notification handling.
+ *
+ * Revision 1.11 2001/02/21 20:40:42 mpowers
+ * setObjectArray now falls back to index when trying to retain the
+ * same selection.
+ *
+ * Revision 1.10 2001/02/20 16:38:55 mpowers
+ * MasterDetailAssociations now observe their controlled display group's
+ * objects for changes to that the parent object will be marked as updated.
+ * Before, only inserts and deletes to an object's items are registered.
+ * Also, moved ObservableArray to package access.
+ *
+ * Revision 1.9 2001/02/17 17:23:49 mpowers
+ * More changes to support compiling 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/01/24 16:35:37 mpowers
+ * Improved documentation on TreeAssociation.
+ * SortOrderings are now inherited from parent nodes.
+ * Updates after sorting are still lost on TreeController.
+ *
+ * Revision 1.6 2001/01/24 14:23:05 mpowers
+ * Added support for OrderedDataSource.
+ *
+ * Revision 1.5 2001/01/12 17:21:37 mpowers
+ * Implicit creation of EOSortOrderings now happens in setSortOrderings.
+ *
+ * Revision 1.4 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/10 22:49:44 mpowers
+ * Implemented similarly named selection methods instead of
+ * throwing exceptions.
+ *
+ * Revision 1.2 2001/01/09 20:12:52 mpowers
+ * Moved inner classes to package access.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:48:20 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.21 2000/12/20 16:25:39 michael
+ * Added log to all files.
+ *
+ * Revision 1.20 2000/12/15 15:04:42 michael
+ * Added doc.
+ *
+ * Revision 1.19 2000/12/11 13:32:48 michael
+ * Finish the much better TreeAssociation implementation.
+ * TreeAssociation now has no gui dependencies.
+ *
+ * Revision 1.18 2000/12/05 17:41:46 michael
+ * Broadcasts selection change after delegate refuses selection change
+ * so the initiating association gets refreshed.
+ *
+ */
+