From aedc34d55462a75e329bbf342251ff6504cd117e Mon Sep 17 00:00:00 2001 From: Benjamin Culkin Date: Sun, 19 May 2024 17:56:33 -0400 Subject: Initial import from SVN --- .../main/java/net/wotonomy/ui/EODisplayGroup.java | 2359 ++++++++++++++++++++ 1 file changed, 2359 insertions(+) create mode 100644 projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/EODisplayGroup.java (limited to 'projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/EODisplayGroup.java') diff --git a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/EODisplayGroup.java b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/EODisplayGroup.java new file mode 100644 index 0000000..ed65b1c --- /dev/null +++ b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/EODisplayGroup.java @@ -0,0 +1,2359 @@ +/* +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.ui; + +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +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 javax.swing.JOptionPane; + +import net.wotonomy.control.EODataSource; +import net.wotonomy.control.EODelayedObserver; +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.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.NSNotification; +import net.wotonomy.foundation.NSNotificationCenter; +import net.wotonomy.foundation.NSSelector; +import net.wotonomy.foundation.internal.Duplicator; +import net.wotonomy.foundation.internal.WotonomyException; + +/** +* EODisplayGroup provides an abstraction of a user interface, +* comprising of an ordered collection of data objects, some +* of which are displayed, and of those some are selected. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 904 $ +*/ +public class EODisplayGroup extends Observable + implements EOObserving, EOEditingContext.Editor +{ + /** + * 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 EOAssociation editingAssociation; + 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 contentsChanged; + boolean selectionChanged; + int updatedObjectIndex; + + // this property is not in the spec + private boolean compareByReference = false; + + private EOObserving lastGroupObserver; + + /** + * Creates a new display group. + */ + public EODisplayGroup () + { + 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; + editingAssociation = null; + qualifier = null; + + localKeys = new NSArray(); // not implemented + insertedObjectDefaultValues = new NSDictionary(); + fetchesOnLoad = false; // not implemented + selectsFirstObjectAfterFetch = false; + usesOptimisticRefresh = false; + inQueryMode = false; // not implemented + + contentsChanged = false; + selectionChanged = false; + updatedObjectIndex = -1; + + // create our private delayed observer + lastGroupObserver = new LastGroupObserver( this ); + EOObserverCenter.addObserver( lastGroupObserver, this ); + } + + + + // 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; + } + + + + // 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 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." ); + } + + + // interface to associations + + /** + * Called by an association when it begins editing. + */ + public void associationDidBeginEditing ( EOAssociation anAssociation ) + { // System.out.println( "EODisplayGroup.associationDidBeginEditing: " + anAssociation ); + if ( dataSource != null ) + { + if ( dataSource.editingContext() != null ) + { + dataSource.editingContext().setMessageHandler( this ); + } + } + editingAssociation = anAssociation; + } + + /** + * Called by an association when it is finished editing. + */ + public void associationDidEndEditing ( EOAssociation anAssociation ) + { // System.out.println( "EODisplayGroup.associationDidEndEditing: " + anAssociation ); + editingAssociation = null; + } + + /** + * Called by associations to determine whether the contents + * of any objects have been changed. Returns true if the + * contents have changed and not all observers have been + * notified. + */ + public boolean contentsChanged () + { + return contentsChanged; + } + + /** + * Called by associations to determine whether the + * selection has been changed. Returns true if the + * selection has changed and not all observers have + * been notified. + */ + public boolean selectionChanged () + { + return selectionChanged; + } + + /** + * Called by an association when a user-specified value fails the association's + * validation rules. This implementation returns true, unless the delegate + * prevents this. + * @return True to allow the association to handle user notification, + * otherwise return false to let the association know that the + * display group notified the user. + */ + public boolean associationFailedToValidateValue ( + EOAssociation anAssociation, + String aValue, + String aKey, + Object anObject, + String anErrorDescription ) + { + Object result = notifyDelegate( + "displayGroupShouldDisplayAlert", + new Class[] { EODisplayGroup.class, String.class, String.class }, + new Object[] { this, "Validation Failed", anErrorDescription } ); + if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) ) + { + return true; + } + return false; + } + + /** + * Called by an association to determine whether it should enable + * a component that displayes a value for the specified key. + * @return true if an object is selected, or if + * the specified key is a query key. Otherwise false. + */ + public boolean enabledToSetSelectedObjectValueForKey ( String aKey ) + { + if ( ( aKey != null ) && ( aKey.startsWith( "@" ) ) ) return true; + return ( selectedObject() != null ); + } + + + // association management + + /** + * Returns the association that is currently being edited, + * or null if no editing is taking place. + */ + public EOAssociation editingAssociation () + { + return editingAssociation; + } + + /** + * Asks the association currently editing to stop editing. + * @returns true if editing was stopped, false if the + * association refused to stop editing (if a modal dialog + * is displayed or a value failed to validate). + */ + public boolean endEditing () + { + if ( editingAssociation == null ) return true; + return editingAssociation.endEditing(); + } + + /** + * Returns a read-only List of associations that are observing + * this display group. + */ + public NSArray observingAssociations () + { + NSArray observers = + EOObserverCenter.observersForObject( this ); + NSMutableArray result = new NSMutableArray(); + + Object o; + Enumeration e = observers.objectEnumerator(); + while ( e.hasMoreElements() ) + { + o = e.nextElement(); + if ( o instanceof EOAssociation ) + { + result.addObject( o ); + } + } + return result; + } + + + + // 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; + } + + /** + * 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[] { EODisplayGroup.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[] { EODisplayGroup.class }, + new Object[] { this } ); + notifyDelegate( + "displayGroupDidChangeSelectedObjects", + new Class[] { EODisplayGroup.class }, + new Object[] { this } ); + + return true; + } + + /** + * 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[] { EODisplayGroup.class, Object.class }, + new Object[] { this, target } ); + if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) ) + { + return false; + } + + contentsChanged = true; + + deleteObjectAtIndexNoNotify( anIndex ); + + if ( dataSource != null ) + { + dataSource.deleteObject( target ); + } + + notifyDelegate( + "displayGroupDidDeleteObject", + new Class[] { EODisplayGroup.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" ); + return displayedObjectsProxy; + } + + /** + * 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. + */ + public boolean fetch () + { + endEditing(); + + if ( dataSource == null ) + { + return false; + } + + Object result = notifyDelegate( + "displayGroupShouldFetch", + new Class[] { EODisplayGroup.class }, + new Object[] { this } ); + if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) ) + { + return false; + } + + NSNotificationCenter.defaultCenter().postNotification( + DisplayGroupWillFetchNotification, this, new NSDictionary() ); + + NSArray objectList = dataSource.fetchObjects(); + + notifyDelegate( + "displayGroupDidFetchObjects", + new Class[] { EODisplayGroup.class, List.class }, + new Object[] { this, objectList } ); + + if ( selectsFirstObjectAfterFetch ) + { + //note: there's a good chance this logic ought to be in master-detail assoc: + // we're doing this because changes in the master object trigger a refetch + // on the child display group which annoyingly changes the selection. + NSArray original = new NSArray( allObjects ); + setObjectArray( objectList ); + if ( displayedObjects.size() > 0 + && !original.equals( allObjects ) ) // don't change if no change + { + setSelectionIndexes( new NSArray( new Integer( 0 ) ) ); + } + } + else + { + setObjectArray( objectList ); + } + + return true; + } + + /** + * Creates a new object at the specified index. + * Calls insertObjectAtIndex() with the result + * from sending createObject() to the data source. + * Presents a JOptionPane if the create fails, unless + * the delegate implements displayGroupCreateObjectFailed. + * @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[] { EODisplayGroup.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(); + } + } + } + + // no delegate or delegate does not implement displayGroupCreateObjectFailed + + String message = "Data source could not create new object"; + Object delegateResult = notifyDelegate( + "displayGroupShouldDisplayAlert", + new Class[] { EODisplayGroup.class, String.class, String.class }, + new Object[] { this, "Error", message } ); + if ( ( delegateResult == null ) || ( Boolean.TRUE.equals( delegateResult ) ) ) + { + JOptionPane.showMessageDialog( null, message ); + } + } + 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[] { EODisplayGroup.class, Object.class, int.class }, + new Object[] { this, anObject, new Integer(anIndex) } ); + if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) ) + { + return; + } + + contentsChanged = true; + updatedObjectIndex = anIndex; + willChange(); + + + // 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[] { EODisplayGroup.class, Object.class }, + new Object[] { this, anObject } ); + } + + /** + * Sets contentsChanged to true and notifies all observers. + */ + public void redisplay () + { + contentsChanged = true; + 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 true if an object was selected. + */ + public boolean selectNext () + { + int count = displayedObjects.count(); + if ( count == 0 ) return false; + if ( count == 1 ) + { + selectObject( displayedObjects.objectAtIndex( 0 ) ); + return true; + } + + 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 ); + } + + return selectObject( selectedObject ); + } + + /** + * 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[] { EODisplayGroup.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[] { EODisplayGroup.class }, + new Object[] { this } ); + notifyDelegate( + "displayGroupDidChangeSelectedObjects", + new Class[] { EODisplayGroup.class }, + new Object[] { this } ); + + return true; + } + + /** + * 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 true if an object was selected. + */ + public boolean selectPrevious () + { + int i = displayedObjects.count(); + if ( i == 0 ) return false; + if ( i == 1 ) + { + selectObject( displayedObjects.objectAtIndex( 0 ) ); + return true; + } + + 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; + } + + return selectObject( displayedObjects.objectAtIndex( i ) ); + } + + /** + * 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[] { EODisplayGroup.class, List.class }, + new Object[] { this, anObjectList } ); + if ( result != null ) + { + anObjectList = (List) result; + } + + contentsChanged = true; + 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 ); + } + + /** + * 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 () + { + contentsChanged = true; + 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[] { EODisplayGroup.class }, + new Object[] { this } ); + notifyDelegate( + "displayGroupDidChangeSelectedObjects", + new Class[] { EODisplayGroup.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[] { EODisplayGroup.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[] { EODisplayGroup.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[] { EODisplayGroup.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. + */ + public Object valueForObjectAtIndex ( int anIndex, String aKey ) + { + 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[] { EODisplayGroup.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[] { EODisplayGroup.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( "EODisplayGroup: updated: " + index ); + if ( ! didChange ) + { + didChange = true; + contentsChanged = 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( "EODisplayGroup: invalidated: " + index ); + if ( ! didChange ) + { + didChange = true; + contentsChanged = 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( "EODisplayGroup: 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 ); + } + + /** + * Called by LastGroupObserver to clear flags. + */ + protected void processRecentChanges() + { + contentsChanged = false; + selectionChanged = false; + } + + /** + * 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 and contentsChanged as appropriate. + */ + public void objectWillChange(Object anObject) + { + int index = indexOf( displayedObjects, anObject ); + if ( index != NSArray.NotFound ) + { + updatedObjectIndex = index; + contentsChanged = true; + 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. + */ + public boolean editorHasChangesForEditingContext( + EOEditingContext anEditingContext ) + { + return ( editingAssociation() != null ); + } + + // interface EOEditingContext.MessageHandler + + /** + * Called to display a message for an error that occurred + * in the specified editing context. If the delegate allows, + * this implementation presents an informational JOptionPane. + * Override to customize. + */ + public void editingContextPresentErrorMessage( + EOEditingContext anEditingContext, + String aMessage ) + { + Object result = notifyDelegate( + "displayGroupShouldDisplayAlert", + new Class[] { EODisplayGroup.class, String.class, String.class }, + new Object[] { this, "Error", aMessage } ); + if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) ) + { + JOptionPane.showMessageDialog( null, 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 presents an JOptionPane allowing the user + * to specify whether to continue. Override to customize. + */ + public boolean editingContextShouldContinueFetching( + EOEditingContext anEditingContext, + int count, + int limit, + EOObjectStore anObjectStore ) + { + return ( JOptionPane.showConfirmDialog( null, + "Fetch limit reached: do you wish to continue?", + "Continue?", + JOptionPane.YES_NO_OPTION ) == JOptionPane.YES_OPTION ); + } + + /** + * 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 ( + EODisplayGroup aDisplayGroup, + EODataSource aDataSource ); + + /** + * Called after the specified display group's + * data source is changed. + */ + void displayGroupDidChangeDataSource ( + EODisplayGroup aDisplayGroup ); + + /** + * Called after a change occurs in the specified + * display group's selected objects. + */ + void displayGroupDidChangeSelectedObjects ( + EODisplayGroup aDisplayGroup ); + + /** + * Called after the specified display group's + * selection has changed. + */ + void displayGroupDidChangeSelection ( + EODisplayGroup aDisplayGroup ); + + /** + * Called after the specified display group has + * deleted the specified object. + */ + void displayGroupDidDeleteObject ( + EODisplayGroup aDisplayGroup, + Object anObject ); + + /** + * Called after the specified display group + * has fetched the specified object list. + */ + void displayGroupDidFetchObjects ( + EODisplayGroup aDisplayGroup, + List anObjectList ); + + /** + * Called after the specified display group + * has inserted the specified object into + * its internal object list. + */ + void displayGroupDidInsertObject ( + EODisplayGroup aDisplayGroup, + Object anObject ); + + /** + * Called after the specified display group + * has set the specified value for the specified + * object and key. + */ + void displayGroupDidSetValueForObject ( + EODisplayGroup 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 ( + EODisplayGroup 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 ( + EODisplayGroup 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 ( + EODisplayGroup 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 ( + EODisplayGroup 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 ( + EODisplayGroup 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 ( + EODisplayGroup 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 ( + EODisplayGroup 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 ( + EODisplayGroup aDisplayGroup, + NSNotification aNotification ); + + } + +} + + /** + * A private class that will serve to clear the contentsChanged + * and selectionChanged flags after all Associations have been + * notified. + */ + class LastGroupObserver extends EODelayedObserver + { + Reference ref; + + public LastGroupObserver( EODisplayGroup aDisplayGroup ) + { + ref = new WeakReference( aDisplayGroup ); + } + + /** + * We want to be informed last, after all Associations + * have been notified to changes in the DisplayGroup. + */ + public int priority() + { + return ObserverPrioritySixth; + } + + /** + * After all Associations have been notified, + * clear the contentsChanged and selectionChanged flags. + */ + public void subjectChanged () + { + EODisplayGroup group = (EODisplayGroup) ref.get(); + if ( group != null ) + { + group.processRecentChanges(); + } + } + } + +/* + * $Log$ + * Revision 1.2 2006/02/18 23:14:35 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.51 2004/01/28 18:35:40 mpowers + * Slight optimization: only comparing list in fetch if we have to. + * + * Revision 1.50 2004/01/27 20:42:30 mpowers + * No longer reselecting first after fetch if contents are identical. + * + * Revision 1.49 2003/12/18 11:37:45 mpowers + * Now calling qualifier internally. + * + * Revision 1.48 2003/08/06 23:07:52 chochos + * general code cleanup (mostly, removing unused imports) + * + * Revision 1.47 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. + * + */ + -- cgit v1.2.3