summaryrefslogtreecommitdiff
path: root/projects/net.wotonomy.ui/src/main/java/net
diff options
context:
space:
mode:
Diffstat (limited to 'projects/net.wotonomy.ui/src/main/java/net')
-rw-r--r--projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DebuggingDelegate.java333
-rw-r--r--projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DelegateAdapter.java257
-rw-r--r--projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DisplayGroup.java300
-rw-r--r--projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/EOAssociation.java565
-rw-r--r--projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/EODisplayGroup.java2359
-rw-r--r--projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/GenericAssociation.java373
-rw-r--r--projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/MasterDetailAssociation.java406
-rw-r--r--projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/MirrorDetailAssociation.java106
-rw-r--r--projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/ObservableArray.java349
-rw-r--r--projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/package.html14
10 files changed, 5062 insertions, 0 deletions
diff --git a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DebuggingDelegate.java b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DebuggingDelegate.java
new file mode 100644
index 0000000..8e9fae2
--- /dev/null
+++ b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DebuggingDelegate.java
@@ -0,0 +1,333 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Intersect Software Corporation
+
+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.util.List;
+
+import net.wotonomy.control.EODataSource;
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSNotification;
+
+ /**
+ * A display group delegate that prints messages for each
+ * of the delegate methods.
+ *
+ * @author michael@mpowers.net
+ * @author $Author: cgruber $
+ * @version $Revision: 904 $
+ */
+ public class DebuggingDelegate implements EODisplayGroup.Delegate
+ {
+ /**
+ * Called when the specified data source fails
+ * to create an object for the specified display group.
+ */
+ public void displayGroupCreateObjectFailed (
+ EODisplayGroup aDisplayGroup,
+ EODataSource aDataSource )
+ {
+ report( "displayGroupCreateObjectFailed",
+ aDisplayGroup,
+ new Object[] { aDataSource } );
+ }
+
+ /**
+ * Called after the specified display group's
+ * data source is changed.
+ */
+ public void displayGroupDidChangeDataSource (
+ EODisplayGroup aDisplayGroup )
+ {
+ report( "displayGroupDidChangeDataSource",
+ aDisplayGroup,
+ new Object[] { } );
+ }
+
+ /**
+ * Called after the specified display group's
+ * selection has changed.
+ */
+ public void displayGroupDidChangeSelectedObjects (
+ EODisplayGroup aDisplayGroup )
+ {
+ report( "displayGroupDidChangeSelectedObjects",
+ aDisplayGroup,
+ new Object[] { } );
+ }
+
+ /**
+ * Called after the specified display group's
+ * selection has changed.
+ */
+ public void displayGroupDidChangeSelection (
+ EODisplayGroup aDisplayGroup )
+ {
+ report( "displayGroupDidChangeSelection",
+ aDisplayGroup,
+ new Object[] { } );
+ }
+
+ /**
+ * Called after the specified object display group's
+ * selection has changed.
+ */
+ public void displayGroupDidDeleteObject (
+ EODisplayGroup aDisplayGroup,
+ Object anObject )
+ {
+ report( "displayGroupDidDeleteObject",
+ aDisplayGroup,
+ new Object[] { anObject } );
+ }
+
+ /**
+ * Called after the specified display group
+ * has fetched the specified object list.
+ */
+ public void displayGroupDidFetchObjects (
+ EODisplayGroup aDisplayGroup,
+ List anObjectList )
+ {
+ report( "displayGroupDidFetchObjects",
+ aDisplayGroup,
+ new Object[] { anObjectList } );
+ }
+
+ /**
+ * Called after the specified display group
+ * has inserted the specified object into
+ * its internal object list.
+ */
+ public void displayGroupDidInsertObject (
+ EODisplayGroup aDisplayGroup,
+ Object anObject )
+ {
+ report( "displayGroupDidInsertObject",
+ aDisplayGroup,
+ new Object[] { anObject } );
+ }
+
+ /**
+ * Called after the specified display group
+ * has set the specified value for the specified
+ * object and key.
+ */
+ public void displayGroupDidSetValueForObject (
+ EODisplayGroup aDisplayGroup,
+ Object aValue,
+ Object anObject,
+ String aKey )
+ {
+ report( "displayGroupDidSetValueForObject",
+ aDisplayGroup,
+ new Object[] { aValue, anObject, 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.
+ */
+ public NSArray displayGroupDisplayArrayForObjects (
+ EODisplayGroup aDisplayGroup,
+ List aList )
+ {
+ return get( "displayGroupDisplayArrayForObjects",
+ aDisplayGroup,
+ aList );
+ }
+
+ /**
+ * Called by the specified display group before
+ * it attempts to change the selection.
+ * This implementation returns true.
+ * @return True to allow the selection to change,
+ * false otherwise.
+ */
+ public boolean displayGroupShouldChangeSelection (
+ EODisplayGroup aDisplayGroup,
+ List aSelectionList )
+ {
+ return ask( "displayGroupShouldChangeSelection",
+ aDisplayGroup,
+ new Object[] { aSelectionList } );
+ }
+
+ /**
+ * Called by the specified display group before
+ * it attempts to delete the specified object.
+ * This implementation returns true.
+ * @return True to allow the object to be deleted
+ * false to prevent the deletion.
+ */
+ public boolean displayGroupShouldDeleteObject (
+ EODisplayGroup aDisplayGroup,
+ Object anObject )
+ {
+ return ask( "displayGroupShouldDeleteObject",
+ aDisplayGroup,
+ new Object[] { anObject } );
+ }
+
+ /**
+ * Called by the specified display group before
+ * it attempts display the specified alert to
+ * the user.
+ * This implementation returns true.
+ * @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.
+ */
+ public boolean displayGroupShouldDisplayAlert (
+ EODisplayGroup aDisplayGroup,
+ String aTitle,
+ String aMessage )
+ {
+ return ask( "displayGroupShouldDisplayAlert",
+ aDisplayGroup,
+ new Object[] { aTitle, aMessage } );
+ }
+
+ /**
+ * Called by the specified display group before
+ * it attempts fetch objects.
+ * This implementation returns true.
+ * @return True to allow the fetch to take place,
+ * false to prevent the fetch.
+ */
+ public boolean displayGroupShouldFetch (
+ EODisplayGroup aDisplayGroup )
+ {
+ return ask( "displayGroupShouldFetch",
+ aDisplayGroup,
+ new Object[] { } );
+ }
+
+ /**
+ * Called by the specified display group before
+ * it attempts to insert the specified object.
+ * This implementation returns true.
+ * @return True to allow the object to be inserted
+ * false to prevent the insertion.
+ */
+ public boolean displayGroupShouldInsertObject (
+ EODisplayGroup aDisplayGroup,
+ Object anObject,
+ int anIndex )
+ {
+ return ask( "displayGroupShouldInsertObject",
+ aDisplayGroup,
+ new Object[] { anObject, new Integer( anIndex ) } );
+ }
+
+ /**
+ * No idea what this might indicate,
+ * nor what the notification indicates.
+ * This implementation returns true.
+ */
+ public boolean displayGroupShouldRedisplay (
+ EODisplayGroup aDisplayGroup,
+ NSNotification aNotification )
+ {
+ return ask( "displayGroupShouldRedisplay",
+ aDisplayGroup,
+ new Object[] { aNotification } );
+ }
+
+ /**
+ * No idea what this might indicate,
+ * nor what the notification indicates.
+ */
+ public boolean displayGroupShouldRefetch (
+ EODisplayGroup aDisplayGroup,
+ NSNotification aNotification )
+ {
+ return ask( "displayGroupShouldRefetch",
+ aDisplayGroup,
+ new Object[] { aNotification } );
+ }
+
+ /**
+ * This method is called by all delegate methods that
+ * return void.
+ * This implementation calls System.out.println.
+ * Override to customize or replace the output.
+ */
+ protected void report( String aTitle,
+ EODisplayGroup aDisplayGroup,
+ Object[] aParameterArray )
+ {
+ String result = aTitle + " : " + aDisplayGroup;
+ for ( int i = 0; i < aParameterArray.length; i++ )
+ {
+ result += " : " + aParameterArray[i];
+ }
+ System.out.println( result );
+ }
+
+ /**
+ * This method is called by displayGroupDisplayArrayForObjects.
+ * This implementation calls report
+ * and returns a copy of the specified list.
+ */
+ protected NSArray get( String aTitle,
+ EODisplayGroup aDisplayGroup,
+ List anObjectList )
+ {
+ report( aTitle, aDisplayGroup, new Object[] { anObjectList } );
+ return new NSArray( anObjectList );
+ }
+
+ /**
+ * This method is called by the methods that
+ * return a boolean.
+ * This implementation calls report and return true.
+ */
+ protected boolean ask( String aTitle,
+ EODisplayGroup aDisplayGroup,
+ Object[] aParameterArray )
+ {
+ report( aTitle, aDisplayGroup, aParameterArray );
+ return true;
+ }
+
+
+ }
+
+/*
+ * $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.2 2003/08/06 23:07:52 chochos
+ * general code cleanup (mostly, removing unused imports)
+ *
+ * Revision 1.1 2001/01/24 14:37:24 mpowers
+ * Contributing a delegate useful for debugging.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DelegateAdapter.java b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DelegateAdapter.java
new file mode 100644
index 0000000..ed572f4
--- /dev/null
+++ b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DelegateAdapter.java
@@ -0,0 +1,257 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Intersect Software Corporation
+
+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.util.List;
+
+import net.wotonomy.control.EODataSource;
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSNotification;
+
+ /**
+ * A convenience class for creating display group delegates.
+ * All methods of the delegate interface are implemented
+ * to do nothing.
+ *
+ * @author michael@mpowers.net
+ * @author $Author: cgruber $
+ * @version $Revision: 904 $
+ */
+ public class DelegateAdapter implements EODisplayGroup.Delegate
+ {
+ /**
+ * Called when the specified data source fails
+ * to create an object for the specified display group.
+ */
+ public void displayGroupCreateObjectFailed (
+ EODisplayGroup aDisplayGroup,
+ EODataSource aDataSource )
+ {
+
+ }
+
+ /**
+ * Called after the specified display group's
+ * data source is changed.
+ */
+ public void displayGroupDidChangeDataSource (
+ EODisplayGroup aDisplayGroup )
+ {
+
+ }
+
+ /**
+ * Called after the specified display group's
+ * selection has changed.
+ */
+ public void displayGroupDidChangeSelectedObjects (
+ EODisplayGroup aDisplayGroup )
+ {
+
+ }
+
+ /**
+ * Called after the specified display group's
+ * selection has changed.
+ */
+ public void displayGroupDidChangeSelection (
+ EODisplayGroup aDisplayGroup )
+ {
+
+ }
+
+ /**
+ * Called after the specified object display group's
+ * selection has changed.
+ */
+ public void displayGroupDidDeleteObject (
+ EODisplayGroup aDisplayGroup,
+ Object anObject )
+ {
+
+ }
+
+ /**
+ * Called after the specified display group
+ * has fetched the specified object list.
+ */
+ public void displayGroupDidFetchObjects (
+ EODisplayGroup aDisplayGroup,
+ List anObjectList )
+ {
+
+ }
+
+ /**
+ * Called after the specified display group
+ * has inserted the specified object into
+ * its internal object list.
+ */
+ public void displayGroupDidInsertObject (
+ EODisplayGroup aDisplayGroup,
+ Object anObject )
+ {
+
+ }
+
+ /**
+ * Called after the specified display group
+ * has set the specified value for the specified
+ * object and key.
+ */
+ public 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.
+ */
+ public NSArray displayGroupDisplayArrayForObjects (
+ EODisplayGroup aDisplayGroup,
+ List aList )
+ {
+ return new NSArray( aList );
+ }
+
+ /**
+ * Called by the specified display group before
+ * it attempts to change the selection.
+ * This implementation returns true.
+ * @return True to allow the selection to change,
+ * false otherwise.
+ */
+ public boolean displayGroupShouldChangeSelection (
+ EODisplayGroup aDisplayGroup,
+ List aSelectionList )
+ {
+ return true;
+ }
+
+ /**
+ * Called by the specified display group before
+ * it attempts to delete the specified object.
+ * This implementation returns true.
+ * @return True to allow the object to be deleted
+ * false to prevent the deletion.
+ */
+ public boolean displayGroupShouldDeleteObject (
+ EODisplayGroup aDisplayGroup,
+ Object anObject )
+ {
+ return true;
+ }
+
+ /**
+ * Called by the specified display group before
+ * it attempts display the specified alert to
+ * the user.
+ * This implementation returns true.
+ * @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.
+ */
+ public boolean displayGroupShouldDisplayAlert (
+ EODisplayGroup aDisplayGroup,
+ String aTitle,
+ String aMessage )
+ {
+ return true;
+ }
+
+ /**
+ * Called by the specified display group before
+ * it attempts fetch objects.
+ * This implementation returns true.
+ * @return True to allow the fetch to take place,
+ * false to prevent the fetch.
+ */
+ public boolean displayGroupShouldFetch (
+ EODisplayGroup aDisplayGroup )
+ {
+ return true;
+ }
+
+ /**
+ * Called by the specified display group before
+ * it attempts to insert the specified object.
+ * This implementation returns true.
+ * @return True to allow the object to be inserted
+ * false to prevent the insertion.
+ */
+ public boolean displayGroupShouldInsertObject (
+ EODisplayGroup aDisplayGroup,
+ Object anObject,
+ int anIndex )
+ {
+ return true;
+ }
+
+ /**
+ * No idea what this might indicate,
+ * nor what the notification indicates.
+ * This implementation returns true.
+ */
+ public boolean displayGroupShouldRedisplay (
+ EODisplayGroup aDisplayGroup,
+ NSNotification aNotification )
+ {
+ return true;
+ }
+
+ /**
+ * No idea what this might indicate,
+ * nor what the notification indicates.
+ */
+ public boolean displayGroupShouldRefetch (
+ EODisplayGroup aDisplayGroup,
+ NSNotification aNotification )
+ {
+ return true;
+ }
+
+ }
+
+/*
+ * $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.2 2003/08/06 23:07:52 chochos
+ * general code cleanup (mostly, removing unused imports)
+ *
+ * Revision 1.1 2001/01/24 14:23:17 mpowers
+ * Contributing DelegateAdapter.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DisplayGroup.java b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DisplayGroup.java
new file mode 100644
index 0000000..9fcbe34
--- /dev/null
+++ b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DisplayGroup.java
@@ -0,0 +1,300 @@
+/*
+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.util.List;
+import java.util.Map;
+
+import net.wotonomy.control.EODataSource;
+import net.wotonomy.control.EOQualifier;
+
+/**
+* DisplayGroup 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.
+* This class extends EODisplayGroup to provide java-friendly
+* convenience methods.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 904 $
+*/
+public class DisplayGroup extends EODisplayGroup
+{
+ /**
+ * Returns the current data source backing this display group,
+ * or null if no dataSource is currently used.
+ */
+ public EODataSource getDataSource()
+ {
+ return super.dataSource();
+ }
+
+ /**
+ * Returns the current delegate for this display group,
+ * or null if no delegate is currently set.
+ */
+ public Object getDelegate()
+ {
+ return delegate();
+ }
+
+ /**
+ * Returns the current string matching format.
+ * If not set, defaults to "%@*".
+ */
+ public String getDefaultStringMatchFormat()
+ {
+ return defaultStringMatchFormat();
+ }
+
+ /**
+ * Returns the current string matching operator.
+ * If not set, defaults to "caseInsensitiveLike".
+ */
+ public String getdefaultStringMatchOperator()
+ {
+ return defaultStringMatchOperator();
+ }
+
+ /**
+ * Returns a Map of default values that are applied
+ * to new objects that are inserted into the list.
+ */
+ public Map getInsertedObjectDefaultValues()
+ {
+ return insertedObjectDefaultValues();
+ }
+
+ /**
+ * Returns the keys that were declared when read from
+ * an external resource file.
+ */
+ public List getLocalKeys()
+ {
+ return localKeys();
+ }
+
+ /**
+ * Returns the List of sort orderings for this display group.
+ */
+ public List getSortOrderings ()
+ {
+ return sortOrderings();
+ }
+
+ /**
+ * Returns a qualifier that will be applied all the objects
+ * in this display group to determine which objects will
+ * be displayed.
+ */
+ public EOQualifier getQualifier ()
+ {
+ return qualifier();
+ }
+
+ /**
+ * Returns a new qualifier built from the three query
+ * value maps: greater than, equal to, and less than.
+ */
+ public EOQualifier getQualifierFromQueryValues ()
+ {
+ return qualifierFromQueryValues();
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to binding query values.
+ */
+ public Map getQueryBindingValues()
+ {
+ return queryBindingValues();
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to operator values.
+ */
+ public Map getQueryOperatorValues()
+ {
+ return queryOperatorValues();
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to query values that will be used to test for equality.
+ */
+ public Map getEqualToQueryValues()
+ {
+ return equalToQueryValues();
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to query values that will be used to test for greater value.
+ */
+ public Map getGreaterThanQueryValues()
+ {
+ return greaterThanQueryValues();
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to query values that will be used to test for lesser value.
+ */
+ public Map getLessThanQueryValues()
+ {
+ return lessThanQueryValues();
+ }
+
+ /**
+ * Returns the association that is currently being edited,
+ * or null if no editing is taking place.
+ */
+ public EOAssociation getEditingAssociation ()
+ {
+ return editingAssociation();
+ }
+
+ /**
+ * Returns a List of associations that are observing
+ * this display group.
+ */
+ public List getObservingAssociations()
+ {
+ return observingAssociations();
+ }
+
+ /**
+ * Returns a List containing all objects managed by the display group.
+ * This includes those objects not visible due to disqualification.
+ */
+ public List getAllObjects()
+ {
+ return allObjects();
+ }
+
+ /**
+ * Returns a List of all objects in the display group
+ * that are currently displayed by the associations.
+ * The list is a copy of the internal displayed object list.
+ */
+ public List getDisplayedObjects()
+ {
+ return displayedObjects();
+ }
+
+ /**
+ * Returns the currently selected object, or null if
+ * there is no selection.
+ */
+ public Object getSelectedObject()
+ {
+ return selectedObject();
+ }
+
+ /**
+ * Returns a List containing all selected objects, if any.
+ * Returns an empty list if no objects are selected.
+ */
+ public List getSelectedObjects()
+ {
+ return selectedObjects();
+ }
+
+ /**
+ * Returns a List containing the indexes of all selected
+ * objects, if any. The list contains instances of
+ * java.lang.Number; call intValue() retrieve the index.
+ */
+ public List getSelectionIndexes()
+ {
+ return selectionIndexes();
+ }
+
+ /**
+ * Returns the index of the changed object. If more than
+ * one object has changed, -1 is returned.
+ */
+ public int getUpdatedObjectIndex()
+ {
+ return updatedObjectIndex();
+ }
+
+ /**
+ * Returns a value on the selected object for the specified key.
+ */
+ public Object getSelectedObjectValueForKey ( String aKey )
+ {
+ return selectedObjectValueForKey( aKey );
+ }
+
+ /**
+ * Returns the value for the specified key on the specified object.
+ */
+ public Object getValueForObject ( Object anObject, String aKey )
+ {
+ return valueForObject( anObject, aKey );
+ }
+
+ /**
+ * Calls valueForObject() for the object at the specified index.
+ */
+ public Object getValueForObjectAtIndex ( int anIndex, String aKey )
+ {
+ return valueForObjectAtIndex( anIndex, aKey );
+ }
+
+ /**
+ * Specifies the default string matching format for all
+ * display groups.
+ */
+ public static String getGlobalDefaultStringMatchFormat ()
+ {
+ return globalDefaultStringMatchFormat();
+ }
+
+ /**
+ * Specifies the default string matching operator for all
+ * display groups.
+ */
+ public static String getGlobalDefaultStringMatchOperator ()
+ {
+ return globalDefaultStringMatchOperator();
+ }
+}
+
+/*
+ * $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.2 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.1 2002/03/26 21:24:43 mpowers
+ * Contributing DisplayGroup as convenience for people coming from java.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/EOAssociation.java b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/EOAssociation.java
new file mode 100644
index 0000000..635c5b9
--- /dev/null
+++ b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/EOAssociation.java
@@ -0,0 +1,565 @@
+/*
+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.util.Enumeration;
+
+import net.wotonomy.control.EODelayedObserver;
+import net.wotonomy.control.EOObserverCenter;
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSMutableArray;
+import net.wotonomy.foundation.NSMutableDictionary;
+
+/**
+* Associations observe DisplayGroups and associate
+* a user interface component with one or more keys
+* on the objects in the display group. <br><br>
+*
+* Associations are created with a ui component in
+* the constructor. Then, one or more aspects are
+* bound to display groups and/or property keys with
+* the bindAspect() method. Finally, the association
+* is initialized with the establishConnection()
+* method. <br><br>
+*
+* Per the openstep convention, you do not need to
+* retain a reference to the association after it is
+* created; the association will be garbage-collected
+* when the ui component is garbage-collected. <br><br>
+*
+* (Because java components don't have delegates like
+* openstep components do, java-based associations
+* will likely need to implement some sort of listener
+* and add itself to the component's list of listeners
+* so that the component will have a strong reference
+* (and the only reference) to the association.) <br><br>
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 904 $
+*/
+public class EOAssociation extends EODelayedObserver
+{
+ // aspect constants
+ public static final String ActionAspect = "action";
+ public static final String EnabledAspect = "enabled";
+ public static final String SourceAspect = "source";
+ public static final String ArgumentAspect = "argument";
+ public static final String ParentAspect = "parent";
+ public static final String TitlesAspect = "titles";
+ public static final String BoldAspect = "bold";
+ public static final String SelectedObjectAspect = "selectedObject";
+ public static final String ValueAspect = "value";
+ public static final String DestinationAspect = "destination";
+ public static final String SelectedTitleAspect = "selectedTitle";
+ public static final String URLAspect = "URL";
+ public static final String ItalicAspect = "italic";
+ public static final String ChildrenAspect = "children"; // not in spec
+ public static final String IsLeafAspect = "isLeaf"; // not in spec
+ public static final String EditableAspect = "editable"; // not in spec
+ public static final String VisibleAspect = "visible"; // not in spec
+ public static final String ObjectsAspect = "objects"; // not in spec
+ public static final String LabelAspect = "label"; // not in spec
+ public static final String IconAspect = "icon"; // not in spec
+
+ public static final String AttributeAspectSignature = "A";
+ public static final String NullAspectSignature = "";
+ public static final String AttributeToOneAspectSignature = "A1";
+ public static final String ToOneAspectSignature = "1";
+ public static final String AttributeToOneToManyAspectSignature = "A1M";
+ public static final String ToOneToManyAspectSignature = "1M";
+ public static final String AttributeToManyAspectSignature = "AM";
+ public static final String ToManyAspectSignature = "M";
+
+ protected Object control;
+ protected NSMutableDictionary aspectToGroup;
+ protected NSMutableDictionary aspectToKey;
+
+ /**
+ * Default constructor.
+ */
+ public EOAssociation ()
+ {
+ aspectToGroup = new NSMutableDictionary();
+ aspectToKey = new NSMutableDictionary();
+ control = null;
+ }
+
+ /**
+ * Constructor specifying the object to be controlled by this
+ * association. Does not establish connection.
+ */
+ public EOAssociation ( Object anObject )
+ {
+ this();
+ control = anObject;
+ }
+
+ /**
+ * Returns a List of aspect signatures whose contents
+ * correspond with the aspects list. Each element is
+ * a string whose characters represent a capability of
+ * the corresponding aspect. <ul>
+ * <li>"A" attribute: the aspect can be bound to
+ * an attribute.</li>
+ * <li>"1" to-one: the aspect can be bound to a
+ * property that returns a single object.</li>
+ * <li>"M" to-one: the aspect can be bound to a
+ * property that returns multiple objects.</li>
+ * </ul>
+ * An empty signature "" means that the aspect can
+ * bind without needing a key.
+ * This implementation returns "A1M" for each
+ * element in the aspects array.
+ */
+ public static NSArray aspectSignatures ()
+ {
+ int size = aspects().count();
+ NSMutableArray result = new NSMutableArray();
+ for ( int i = 0; i < size; i++ )
+ {
+ result.addObject( AttributeToOneToManyAspectSignature );
+ }
+ return result;
+ }
+
+ /**
+ * Returns a List that describes the aspects supported
+ * by this class. Each element in the list is the string
+ * name of the aspect. This implementation returns an
+ * empty list.
+ *
+ * TODO: Is this static in the WebObjects published API? If not, fix.
+ */
+ public static NSArray aspects ()
+ {
+ return new NSArray();
+ }
+
+ /**
+ * Returns all registered subclasses of EOAssociation
+ * for which usableWithObject with the specified object
+ * returns true. You should only call this method on
+ * the EOAssociation class.
+ */
+ public static NSArray associationClassesForObject (
+ Object anObject )
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a List of EOAssociation subclasses that,
+ * for the objects that are usable for this association,
+ * are less suitable than this association.
+ * This implementation returns an empty list.
+ */
+ public static NSArray associationClassesSuperseded ()
+ {
+ return new NSArray();
+ }
+
+
+ /**
+ * Binds the specified aspect of this association to the
+ * specified key on the specified display group.
+ */
+ public void bindAspect (
+ String anAspect, EODisplayGroup aDisplayGroup, String aKey )
+ {
+ // unattach old group, if any
+ EODisplayGroup oldGroup = displayGroupForAspect( anAspect );
+ if ( oldGroup != null )
+ {
+ EOObserverCenter.removeObserver( this, oldGroup );
+ }
+
+ // attach new group
+ if ( aDisplayGroup != null )
+ {
+ aspectToGroup.setObjectForKey( aDisplayGroup, anAspect );
+ }
+ else
+ {
+ aspectToGroup.removeObjectForKey( anAspect );
+ }
+ // attach new key
+ if ( aKey != null )
+ {
+ aspectToKey.setObjectForKey( aKey, anAspect );
+ }
+ else
+ {
+ aspectToKey.removeObjectForKey( anAspect );
+ }
+ }
+
+ /**
+ * Breaks the connection between this association and
+ * its object. Override to stop listening for events
+ * from the object, but remember to call this method.
+ * This implementation unregisters this association
+ * from observing each bound display group.
+ */
+ public void breakConnection ()
+ {
+ discardPendingNotification();
+ Enumeration e = aspectToGroup.objectEnumerator();
+ while ( e.hasMoreElements() )
+ {
+ EOObserverCenter.removeObserver( this, e.nextElement() );
+ }
+ }
+
+ /**
+ * Returns whether this association can bind to the
+ * specified display group on the specified key for
+ * the specified aspect.
+ * This implementation returns false.
+ */
+ public boolean canBindAspect (
+ String anAspect, EODisplayGroup aDisplayGroup, String aKey)
+ {
+ return false;
+ }
+
+ /**
+ * Copies the binding for each aspect in this association
+ * that has a matching aspect in the specified association.
+ */
+ public void copyMatchingBindingsFromAssociation (
+ EOAssociation anAssociation )
+ {
+ //FIXME: this is broken: aspects() returns EOAssociation.aspects()
+ //NOTE: This is actually quite crazy - should this even be static? -ceg
+ NSMutableArray ourAspects = new NSMutableArray( this.aspects() );
+ NSArray theirAspects = anAssociation.aspects();
+
+ String aspect;
+ Enumeration e = ourAspects.objectEnumerator();
+ while ( e.hasMoreElements() )
+ {
+ aspect = e.nextElement().toString();
+ if ( theirAspects.containsObject( aspect ) )
+ {
+ this.bindAspect(
+ aspect,
+ anAssociation.displayGroupForAspect( aspect ),
+ anAssociation.displayGroupKeyForAspect( aspect ) );
+ }
+ }
+ }
+
+ /**
+ * Returns the display group that is bound the specified
+ * aspect, or null if no display group is currently
+ * bound to that aspect.
+ */
+ public EODisplayGroup displayGroupForAspect ( String anAspect )
+ {
+ return (EODisplayGroup) aspectToGroup.objectForKey( anAspect );
+ }
+
+ /**
+ * Returns the key for the display group bound to the
+ * specified aspect, or null if no display group is currently
+ * bound to that aspect.
+ */
+ public String displayGroupKeyForAspect ( String anAspect )
+ {
+ return (String) aspectToKey.objectForKey( anAspect );
+ }
+
+ /**
+ * The human-readable descriptive name for this association.
+ * This implementation returns the class name.
+ */
+ public String displayName () // was static - can static method get class name?
+ {
+ String className = getClass().getName();
+ int index = className.lastIndexOf( "." );
+ if ( index == -1 ) return className;
+ return className.substring( index+1 );
+ }
+
+ /**
+ * Forces this association to cause the object to
+ * stop editing and validate the user's input.
+ * This implementation returns true.
+ * @return false if there were problems validating,
+ * or true to continue.
+ */
+ public boolean endEditing ()
+ {
+ return true;
+ }
+
+ /**
+ * Establishes a connection between this association
+ * and the controlled object. Subclasses should populate
+ * their controlled object from the display group and begin
+ * listening for events from their controlled object here,
+ * but remember to call this method. Any existing value
+ * in the controlled object will be overwritten.
+ * This implementation registers this assocation to
+ * observer changes to each of the bound display groups.
+ */
+ public void establishConnection ()
+ {
+ Enumeration e = aspectToGroup.objectEnumerator();
+ while ( e.hasMoreElements() )
+ {
+ EOObserverCenter.addObserver( this, e.nextElement() );
+ }
+ }
+
+ /**
+ * Returns whether this class can control the specified
+ * object. This implementation returns false.
+ */
+ public static boolean isUsableWithObject ( Object anObject )
+ {
+ return false;
+ }
+
+ /**
+ * Returns the object that is currently controlled by this
+ * association, or null if no object is currently controlled.
+ */
+ public Object object ()
+ {
+ return control;
+ }
+
+ /**
+ * Returns a List of properties of the controlled object
+ * that are controlled by this class. For example,
+ * "stringValue", or "selected".
+ * This implementation returns an empty list.
+ */
+ public static NSArray objectKeysTaken ()
+ {
+ return new NSArray();
+ }
+
+ /**
+ * Returns the aspect that is considered primary
+ * or default. This is typically "value" or somesuch.
+ * This implementation returns null.
+ */
+ public static String primaryAspect ()
+ {
+ return null;
+ }
+
+ /**
+ * Writes the specified value for the display group
+ * and key currently bound to the specified aspect.
+ * This implementation calls
+ * EODisplayGroup.setSelectedObjectValue().
+ * @return True if the value was successfully set,
+ * otherwise false.
+ */
+ public boolean setValueForAspect (
+ Object aValue,
+ String anAspect )
+ {
+ EODisplayGroup group = (EODisplayGroup)
+ aspectToGroup.objectForKey( anAspect );
+ if ( group == null ) return false;
+ String key = (String)
+ aspectToKey.objectForKey( anAspect );
+ if ( key == null ) return false;
+
+ //FIXME: is this the right method to call
+ return group.setSelectedObjectValue( aValue, key );
+ }
+
+
+ /**
+ * Writes the specified value for the display group
+ * and key currently bound to the specified aspect,
+ * at the specified index (assuming it's an indexed
+ * property).
+ * This implementation calls
+ * EODisplayGroup.setValueForObjectAtIndex().
+ * @return True if the value was successfully set,
+ * otherwise false.
+ */
+ public boolean setValueForAspectAtIndex (
+ Object aValue,
+ String anAspect,
+ int anIndex )
+ {
+ EODisplayGroup group = (EODisplayGroup)
+ aspectToGroup.objectForKey( anAspect );
+ if ( group == null ) return false;
+ String key = (String)
+ aspectToKey.objectForKey( anAspect );
+ if ( key == null ) return false;
+
+ //FIXME: is this the right method to call?
+ return group.setValueForObjectAtIndex( aValue, anIndex, key );
+ }
+
+ /**
+ * Called by subclasses to notify that the user's input
+ * failed validation. Notifies the display group for
+ * the aspect, returning the result.
+ * This implementation calls
+ * EODisplayGroup.associationFailedToValidateValue()
+ * with the display group's selected object.
+ * @return True if the user should be allowed to continue,
+ * meaning that the display group with handle user notification,
+ * otherwise false meaning the caller should notify the user.
+ */
+ public boolean shouldEndEditing (
+ String anAspect,
+ String anInvalidInput,
+ String anErrorDescription )
+ {
+ EODisplayGroup group = (EODisplayGroup)
+ aspectToGroup.objectForKey( anAspect );
+ if ( group == null ) return false;
+ String key = (String)
+ aspectToKey.objectForKey( anAspect );
+ if ( key == null ) return false;
+ return group.associationFailedToValidateValue(
+ this, anInvalidInput, key,
+ group.selectedObject(), //FIXME: is this correct?
+ anErrorDescription );
+ }
+
+ /**
+ * Called by subclasses to notify that the user's input
+ * failed validation. Notifies the display group for
+ * the aspect, returning the result.
+ * This implementation calls
+ * EODisplayGroup.associationFailedToValidateValue()
+ * with the object in the display group's displayed
+ * objects array at the specified index.
+ * @return True if the user should be allowed to continue,
+ * meaning that the display group with handle user notification,
+ * otherwise false meaning the caller should notify the user.
+ */
+ public boolean shouldEndEditingAtIndex (
+ String anAspect,
+ String anInvalidInput,
+ String anErrorDescription,
+ int anIndex )
+ {
+ EODisplayGroup group = (EODisplayGroup)
+ aspectToGroup.objectForKey( anAspect );
+ if ( group == null ) return false;
+ String key = (String)
+ aspectToKey.objectForKey( anAspect );
+ if ( key == null ) return false;
+ return group.associationFailedToValidateValue(
+ this, anInvalidInput, key,
+ group.displayedObjects().objectAtIndex( anIndex ),
+ //FIXME: is this correct?
+ anErrorDescription );
+ }
+
+ /**
+ * Called when either the selection or the contents of
+ * an associated display group have changed.
+ * This implementation does nothing.
+ */
+ public void subjectChanged ()
+ {
+ // does nothing
+ }
+
+ /**
+ * Returns the current value for the display group
+ * and key associated with the specified aspect.
+ */
+ public Object valueForAspect ( String anAspect )
+ {
+ EODisplayGroup group = (EODisplayGroup)
+ aspectToGroup.objectForKey( anAspect );
+ if ( group == null ) return null;
+ String key = (String)
+ aspectToKey.objectForKey( anAspect );
+ if ( key == null ) return null;
+
+ //FIXME: is this correct? use selected object?
+ return group.valueForObject( group.selectedObject(), key );
+ }
+
+ /**
+ * Returns the current value for the display group
+ * and key associated with the specified aspect,
+ * and the specified index (assuming multiple values).
+ */
+ public Object valueForAspectAtIndex (
+ String anAspect, int anIndex )
+ {
+ EODisplayGroup group = (EODisplayGroup)
+ aspectToGroup.objectForKey( anAspect );
+ if ( group == null ) return null;
+ String key = (String)
+ aspectToKey.objectForKey( anAspect );
+ if ( key == null ) return null;
+
+ //FIXME: is this the right method to call?
+ return group.valueForObjectAtIndex( anIndex, key );
+ }
+
+}
+
+/*
+ * $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.7 2005/05/11 15:21:53 cgruber
+ * Change enum to enumeration, since enum is now a keyword as of Java 5.0
+ *
+ * A few other comments in the code.
+ *
+ * Revision 1.6 2003/08/06 23:07:52 chochos
+ * general code cleanup (mostly, removing unused imports)
+ *
+ * Revision 1.5 2003/06/06 20:29:45 mpowers
+ * Now dequeuing the association when connection is broken.
+ * Coalesced change events had been processed even after breaking connection.
+ *
+ * Revision 1.4 2001/03/06 23:43:46 mpowers
+ * Implemented icon aspect for text association.
+ *
+ * Revision 1.3 2001/02/17 16:52:05 mpowers
+ * Changes in imports to support building with jdk1.1 collections.
+ *
+ * Revision 1.2 2001/01/25 17:46:11 mpowers
+ * Clarified usage and gc expectations.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:48:07 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.11 2000/12/20 16:25:39 michael
+ * Added log to all files.
+ *
+ *
+ */
+
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.
+ *
+ */
+
diff --git a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/GenericAssociation.java b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/GenericAssociation.java
new file mode 100644
index 0000000..3a4ff08
--- /dev/null
+++ b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/GenericAssociation.java
@@ -0,0 +1,373 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Intersect Software Corporation
+
+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.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import net.wotonomy.control.EOKeyValueCoding;
+import net.wotonomy.control.EOKeyValueCodingSupport;
+import net.wotonomy.control.EOObserverCenter;
+import net.wotonomy.foundation.NSArray;
+
+/**
+* GenericAssociation binds one or more properties on an
+* observable object to a display group. The controlled
+* object is expected to use the ObserverCenter and will
+* be observed by this association. <br><br>
+*
+* Bindings for this association are <i>generic</i>: the
+* name of the aspect will be treated as a property key
+* on the displayed object(s) and synchronized with the
+* bound property on the controlled object. <br><br>
+*
+* NOTE: because we cannot assume that the controlled
+* object will retain a reference to this association,
+* you must explicitly retain a reference to prevent
+* the association from getting garbage collected.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 904 $
+*/
+public class GenericAssociation extends EOAssociation
+{
+ protected boolean objectModified;
+ protected Set aspectsModified;
+
+ static final NSArray aspects =
+ new NSArray( new Object[] {
+ } );
+ static final NSArray aspectSignatures =
+ new NSArray( new Object[] {
+ } );
+ static final NSArray objectKeysTaken =
+ new NSArray( new Object[] {
+ } );
+
+ /**
+ * Constructor specifying the object to be controlled by this
+ * association. Does not establish connection.
+ */
+ public GenericAssociation ( Object anObject )
+ {
+ super( anObject );
+ objectModified = false;
+ aspectsModified = new HashSet();
+ }
+
+ /**
+ * Returns a List of aspect signatures whose contents
+ * correspond with the aspects list. Each element is
+ * a string whose characters represent a capability of
+ * the corresponding aspect. <ul>
+ * <li>"A" attribute: the aspect can be bound to
+ * an attribute.</li>
+ * <li>"1" to-one: the aspect can be bound to a
+ * property that returns a single object.</li>
+ * <li>"M" to-one: the aspect can be bound to a
+ * property that returns multiple objects.</li>
+ * </ul>
+ * An empty signature "" means that the aspect can
+ * bind without needing a key.
+ * This implementation returns "A1M" for each
+ * element in the aspects array.
+ */
+ public static NSArray aspectSignatures ()
+ {
+ return aspectSignatures;
+ }
+
+ /**
+ * Returns a List that describes the aspects supported
+ * by this class. Each element in the list is the string
+ * name of the aspect. This implementation returns an
+ * empty list.
+ */
+ public static NSArray aspects ()
+ {
+ return aspects;
+ }
+
+ /**
+ * Returns a List of EOAssociation subclasses that,
+ * for the objects that are usable for this association,
+ * are less suitable than this association.
+ */
+ public static NSArray associationClassesSuperseded ()
+ {
+ return new NSArray();
+ }
+
+ /**
+ * Returns whether this class can control the specified
+ * object.
+ */
+ public static boolean isUsableWithObject ( Object anObject )
+ {
+ return true;
+ }
+
+ /**
+ * Returns a List of properties of the controlled object
+ * that are controlled by this class. For example,
+ * "stringValue", or "selected".
+ */
+ public static NSArray objectKeysTaken ()
+ {
+ return objectKeysTaken;
+ }
+
+ /**
+ * Returns the aspect that is considered primary
+ * or default. This is typically "value" or somesuch.
+ */
+ public static String primaryAspect ()
+ {
+ return "";
+ }
+
+ /**
+ * Returns whether this association can bind to the
+ * specified display group on the specified key for
+ * the specified aspect.
+ */
+ public boolean canBindAspect (
+ String anAspect, EODisplayGroup aDisplayGroup, String aKey)
+ {
+ return true;
+ }
+
+ /**
+ * Establishes a connection between this association
+ * and the controlled object. This implementation
+ * registers with ObserverCenter for change notifications
+ * from the controlled object.
+ */
+ public void establishConnection ()
+ {
+ EOObserverCenter.addObserver( this, object() );
+ super.establishConnection();
+
+ // forces update from bindings
+ subjectChanged();
+ }
+
+ /**
+ * Breaks the connection between this association and
+ * its object. Override to stop listening for events
+ * from the object.
+ */
+ public void breakConnection ()
+ {
+ EOObserverCenter.removeObserver( this, object() );
+ super.breakConnection();
+ }
+
+ /**
+ * Overridden to track which observed object is changing.
+ */
+ public void objectWillChange( Object anObject )
+ {
+ if ( object() == anObject )
+ {
+ objectModified = true;
+ }
+ else
+ {
+ aspectsModified.add( aspectToGroup.allKeysForObject( anObject ) );
+ }
+ }
+
+ /**
+ * Called when either the selection or the contents
+ * of an associated display group have changed.
+ */
+ public void subjectChanged ()
+ {
+ String aspect;
+ String key;
+ Object value;
+ EODisplayGroup displayGroup;
+ Iterator iterator;
+
+ iterator = aspectsModified.iterator();
+ while ( iterator.hasNext() )
+ {
+ aspect = (String) iterator.next();
+ key = displayGroupKeyForAspect( aspect );
+ displayGroup = displayGroupForAspect( aspect );
+
+ value = readValueFromDisplayGroupForKey( displayGroup, key );
+ writeValueForKey( object(), value, key );
+ }
+ aspectsModified.clear();
+
+ if ( objectModified )
+ {
+ iterator = aspectToGroup.keySet().iterator();
+ while ( iterator.hasNext() )
+ {
+ aspect = (String) iterator.next();
+ key = displayGroupKeyForAspect( aspect );
+ value = readValueForKey( object(), key );
+ displayGroup = displayGroupForAspect( aspect );
+ displayGroup.setSelectedObjectValue( value, key );
+ }
+ }
+ }
+
+ protected Object readValueForKey( Object object, String key )
+ {
+ if ( object instanceof EOKeyValueCoding )
+ {
+ return ((EOKeyValueCoding)object).valueForKey( key );
+ }
+ return EOKeyValueCodingSupport.valueForKey( object, key );
+ }
+
+ protected void writeValueForKey( Object object, Object value, String key )
+ {
+ if ( object instanceof EOKeyValueCoding )
+ {
+ ((EOKeyValueCoding)object).takeValueForKey( value, key );
+ }
+ else
+ {
+ EOKeyValueCodingSupport.takeValueForKey( object, value, key );
+ }
+ }
+
+ protected Object readValueFromDisplayGroupForKey(
+ EODisplayGroup displayGroup, String key )
+ {
+ Object value;
+
+ if ( displayGroup.selectedObjects().size() > 1 )
+ {
+ // if there're more than one object selected, set
+ // the value to blank for all of them.
+ Object previousValue;
+
+ Iterator indexIterator = displayGroup.selectionIndexes().
+ iterator();
+
+ // get value for the first selected object.
+ int initialIndex = ( (Integer)indexIterator.next() ).intValue();
+ previousValue = displayGroup.valueForObjectAtIndex(
+ initialIndex, key );
+ value = null;
+
+ // go through the rest of the selected objects, compare each
+ // value with the previous one. continue comparing if two
+ // values are equal, break the while loop if they're different.
+ // the final value will be the common value of all selected objects
+ // if there is one, or be blank if there is not.
+ while ( indexIterator.hasNext() )
+ {
+ int index = ( (Integer)indexIterator.next() ).intValue();
+ Object currentValue = displayGroup.valueForObjectAtIndex(
+ index, key );
+ if ( currentValue != null && !currentValue.equals( previousValue ) )
+ {
+ value = null;
+ break;
+ }
+ else
+ {
+ // currentValue is the same as the previous one
+ value = currentValue;
+ }
+
+ } // end while
+
+ } else {
+
+ value = displayGroup.selectedObjectValueForKey( key );
+ } // end checking size of displayGroup
+
+ return value;
+ }
+
+ /**
+ * Writes the value currently in the component
+ * to the selected object in the display group
+ * bound to the value aspect.
+ * @return false if there were problems validating,
+ * or true to continue.
+ */
+ protected boolean writeValueForAspect( Object value, String aspect )
+ {
+ EODisplayGroup displayGroup =
+ displayGroupForAspect( aspect );
+ if ( displayGroup != null )
+ {
+ String key = displayGroupKeyForAspect( aspect );
+
+ boolean returnValue = true;
+ Iterator selectedIterator = displayGroup.selectionIndexes().iterator();
+ while ( selectedIterator.hasNext() )
+ {
+ int index = ( (Integer)selectedIterator.next() ).intValue();
+
+ if ( !displayGroup.setValueForObjectAtIndex( value, index, key ) )
+ {
+ returnValue = false;
+ }
+ }
+ return returnValue;
+ }
+ return false;
+ }
+
+ /**
+ * Forces this association to cause the object to
+ * stop editing and validate the user's input.
+ * @return false if there were problems validating,
+ * or true to continue.
+ */
+ public boolean endEditing ()
+ {
+ return false;
+//! return writeValueToDisplayGroup();
+ }
+
+}
+
+/*
+ * $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.3 2003/08/06 23:07:52 chochos
+ * general code cleanup (mostly, removing unused imports)
+ *
+ * Revision 1.2 2001/11/08 19:51:24 mpowers
+ * Draft implementation.
+ *
+ * Revision 1.1 2001/11/02 23:15:04 mpowers
+ * Contributing "generic association".
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/MasterDetailAssociation.java b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/MasterDetailAssociation.java
new file mode 100644
index 0000000..2aea8d3
--- /dev/null
+++ b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/MasterDetailAssociation.java
@@ -0,0 +1,406 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2000 Intersect Software Corporation
+
+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 net.wotonomy.control.EOClassDescription;
+import net.wotonomy.control.EODataSource;
+import net.wotonomy.control.EOEditingContext;
+import net.wotonomy.control.EOObserverCenter;
+import net.wotonomy.control.PropertyDataSource;
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSMutableArray;
+
+/**
+* MasterDetailAssociation binds a display group to a property
+* on the selected object of another display group.
+* Bindings are:
+* <ul>
+* <li>parent: The property on the selected object of the
+* bound display group that is expected to be an indexed property.</li>
+* </ul>
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 904 $
+*/
+public class MasterDetailAssociation extends EOAssociation
+{
+ static final NSArray aspects =
+ new NSArray( new Object[] {
+ ParentAspect
+ } );
+ static final NSArray aspectSignatures =
+ new NSArray( new Object[] {
+ AttributeToOneAspectSignature
+ } );
+ static final NSArray objectKeysTaken =
+ new NSArray( new Object[] {
+ "allObjects"
+ } );
+
+ /**
+ * Used to be notified of changes to objects in the
+ * controlled display group. requalify() should place
+ * all objects fetched into the controlled group into
+ * this array.
+ * Otherwise, the parent object is only marked as
+ * changed for inserts and deletes.
+ */
+ protected NSMutableArray observableArray;
+
+ /**
+ * Constructor expecting an EODisplayGroup.
+ * If the controlled display group does not have a data source,
+ * a new PropertyDataSource will be used.
+ */
+ public MasterDetailAssociation ( Object anObject )
+ {
+ super( anObject );
+ observableArray = new ObservableArray( this );
+ }
+
+ /**
+ * Returns a List of aspect signatures whose contents
+ * correspond with the aspects list. Each element is
+ * a string whose characters represent a capability of
+ * the corresponding aspect. <ul>
+ * <li>"A" attribute: the aspect can be bound to
+ * an attribute.</li>
+ * <li>"1" to-one: the aspect can be bound to a
+ * property that returns a single object.</li>
+ * <li>"M" to-one: the aspect can be bound to a
+ * property that returns multiple objects.</li>
+ * </ul>
+ * An empty signature "" means that the aspect can
+ * bind without needing a key.
+ * This implementation returns "A1M" for each
+ * element in the aspects array.
+ */
+ public static NSArray aspectSignatures ()
+ {
+ return aspectSignatures;
+ }
+
+ /**
+ * Returns a List that describes the aspects supported
+ * by this class. Each element in the list is the string
+ * name of the aspect. This implementation returns an
+ * empty list.
+ */
+ public static NSArray aspects ()
+ {
+ return aspects;
+ }
+
+ /**
+ * Returns a List of EOAssociation subclasses that,
+ * for the objects that are usable for this association,
+ * are less suitable than this association.
+ */
+ public static NSArray associationClassesSuperseded ()
+ {
+ return new NSArray();
+ }
+
+ /**
+ * Returns whether this class can control the specified
+ * object.
+ */
+ public static boolean isUsableWithObject ( Object anObject )
+ {
+ return ( anObject instanceof EODisplayGroup );
+ }
+
+ /**
+ * Returns a List of properties of the controlled object
+ * that are controlled by this class. For example,
+ * "stringValue", or "selected".
+ */
+ public static NSArray objectKeysTaken ()
+ {
+ return objectKeysTaken;
+ }
+
+ /**
+ * Returns the aspect that is considered primary
+ * or default. This is typically "value" or somesuch.
+ */
+ public static String primaryAspect ()
+ {
+ return ParentAspect;
+ }
+
+ /**
+ * Returns whether this association can bind to the
+ * specified display group on the specified key for
+ * the specified aspect.
+ */
+ public boolean canBindAspect (
+ String anAspect, EODisplayGroup aDisplayGroup, String aKey)
+ {
+ return ( aspects.containsObject( anAspect ) );
+ }
+
+ /**
+ * Establishes a connection between this association
+ * and the controlled object. Subclasses should begin
+ * listening for events from their controlled object here.
+ */
+ public void establishConnection ()
+ {
+ //NOTE: if nothing refers to this assocation, it gets gc'd.
+ // otherwise, this is not needed.
+ component().addObserver( this );
+
+ EODisplayGroup displayGroup =
+ displayGroupForAspect( ParentAspect );
+ String key =
+ displayGroupKeyForAspect( ParentAspect );
+
+ // obtain and qualify new data source from existing source if necessary
+ if ( component().dataSource() == null )
+ {
+ if ( ( displayGroup != null ) && ( displayGroup.dataSource() != null ) )
+ {
+ component().setDataSource(
+ displayGroup.dataSource().
+ dataSourceQualifiedByKey( key ) );
+ }
+ }
+
+ // set up proxy data source if necessary
+ if ( component().dataSource() == null )
+ {
+ // get context and class desc from master group
+ EOEditingContext editingContext = null;
+ EOClassDescription classDesc = null;
+ if ( displayGroup != null )
+ {
+ EODataSource dataSource = displayGroup.dataSource();
+ if ( dataSource != null )
+ {
+ editingContext = dataSource.editingContext();
+ EOClassDescription parentDesc = dataSource.classDescriptionForObjects();
+ if ( parentDesc != null )
+ {
+ classDesc = parentDesc.classDescriptionForDestinationKey( key );
+ }
+ }
+ }
+
+ //FIXME: should this be called DetailDataSource?
+ component().setDataSource(
+ new PropertyDataSource( editingContext, classDesc ) );
+ }
+
+ super.establishConnection();
+ requalify();
+ }
+
+ /**
+ * Breaks the connection between this association and
+ * its object. Override to stop listening for events
+ * from the object.
+ */
+ public void breakConnection ()
+ {
+ //NOTE: if nothing refers to this assocation, it gets gc'd.
+ // otherwise, this is not needed.
+ component().deleteObserver( this );
+
+ super.breakConnection();
+ }
+
+ /**
+ * Called when either the selection or the contents
+ * of an associated display group have changed.
+ */
+ public void subjectChanged ()
+ {
+ EODisplayGroup displayGroup;
+
+ // parent aspect
+ displayGroup = displayGroupForAspect( ParentAspect );
+ if ( displayGroup != null )
+ {
+ if ( displayGroup.selectionChanged() )
+ {
+ requalify();
+ }
+ else
+ if ( displayGroup.contentsChanged() )
+ {
+ requalify();
+ }
+ }
+ }
+
+ /**
+ * Overridden to intercept notifications of changes to objects
+ * in the controlled display group and broadcast a change on the
+ * parent group's selected object. All other notifications are
+ * passed to the super implementation.
+ */
+ public void objectWillChange ( Object anObject )
+ {
+ // if child display group is notifying
+ if ( ! ( anObject instanceof EODisplayGroup ) )
+ {
+ // mark parent group's object as changed
+ EODisplayGroup displayGroup = displayGroupForAspect( ParentAspect );
+ if ( displayGroup != null )
+ {
+ Object selected = displayGroup.selectedObject();
+ if ( selected != null )
+ {
+ // only notify if childrenKey is an attribute of parentDesc
+ // (and therefore not a toOne or toMany relationship)
+ EOClassDescription parentDesc =
+ EOClassDescription.classDescriptionForClass(
+ selected.getClass() );
+ String key = displayGroupKeyForAspect( ParentAspect );
+ if ( key != null )
+ {
+ int idx = key.indexOf( '.' );
+ if ( idx != -1 ) key = key.substring( 0, idx );
+ if ( parentDesc.attributeKeys().contains( key ) )
+ {
+ // only notify if we are an attribute key
+ EOObserverCenter.notifyObserversObjectWillChange( selected );
+ }
+ }
+ }
+ }
+ }
+ else // display group is notifying
+ {
+ // call super so subjectChanged will be called
+ super.objectWillChange( anObject );
+ }
+ }
+
+ /**
+ * Called by subjectChanged() to requalify the controlled
+ * display group with the selected object and the bound key.
+ */
+ protected void requalify()
+ {
+ EODisplayGroup component = component();
+ EODisplayGroup displayGroup = displayGroupForAspect( ParentAspect );
+ String key = displayGroupKeyForAspect( ParentAspect );
+
+ if ( ( displayGroup.selectedObject() != null )
+ && ( component.dataSource() != null ) )
+ {
+ component.dataSource().qualifyWithRelationshipKey(
+ key, displayGroup.selectedObject() );
+ component.fetch();
+ observableArray.setArray( component.allObjects() );
+ }
+ else // no selection or no data source, clear
+ {
+ component.setObjectArray( null );
+ observableArray.removeAllObjects();
+ }
+ component.updateDisplayedObjects();
+ }
+
+ /**
+ * This implementation returns ObserverPrioritySecond
+ * so that master detail assocations are notified before
+ * other associations.
+ */
+ public int priority ()
+ {
+ return ObserverPrioritySecond;
+ }
+
+ // convenience
+
+ private EODisplayGroup component()
+ {
+ return (EODisplayGroup) object();
+ }
+
+}
+
+/*
+ * $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.14 2004/02/04 20:00:49 mpowers
+ * Improved change notification for dotted key paths.
+ *
+ * Revision 1.13 2003/08/06 23:07:52 chochos
+ * general code cleanup (mostly, removing unused imports)
+ *
+ * Revision 1.12 2001/10/26 18:39:05 mpowers
+ * Now a delayed observer with a higher priority, so that it is processed
+ * before other associations.
+ *
+ * Revision 1.11 2001/06/26 21:39:33 mpowers
+ * Added check for null component data source before requalifying.
+ *
+ * Revision 1.10 2001/05/21 14:04:15 mpowers
+ * No longer changing a detail group's data source if it's already specified.
+ *
+ * Revision 1.9 2001/05/18 21:08:46 mpowers
+ * Now calling updateDisplayedObjects on detail after master changes.
+ *
+ * Revision 1.8 2001/05/14 15:26:42 mpowers
+ * Modified logic for controlled groups that have no data source already set.
+ *
+ * Revision 1.7 2001/05/04 14:42:58 mpowers
+ * Now getting stored values in KeyValueCoding.
+ * MasterDetail now marks dirty based on whether it's an attribute
+ * or relation.
+ * Implemented editing context marker.
+ *
+ * Revision 1.6 2001/04/29 02:29:31 mpowers
+ * Debugging relationship faulting.
+ *
+ * Revision 1.4 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.3 2001/01/18 16:57:18 mpowers
+ * Fixed problem with losing connection: the association was getting
+ * garbage collected because nothing referred to it. All other associations
+ * make themselves listeners of their controlled object, and that has been
+ * the only thing keeping them from getting gc'd. This will need to be fixed.
+ *
+ * Revision 1.2 2001/01/17 23:06:09 mpowers
+ * TreeAssociation now modifies the contents of the children display
+ * group rather than adding items to the titles display group.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:48:23 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.5 2000/12/20 16:25:40 michael
+ * Added log to all files.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/MirrorDetailAssociation.java b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/MirrorDetailAssociation.java
new file mode 100644
index 0000000..aeac376
--- /dev/null
+++ b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/MirrorDetailAssociation.java
@@ -0,0 +1,106 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Intersect Software Corporation
+
+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 net.wotonomy.foundation.NSArray;
+
+/**
+* This master detail association synchronizes the contents
+* and selection of the master group into the detail group.
+*/
+public class MirrorDetailAssociation extends MasterDetailAssociation{
+
+ /**
+ * Standard constructor specifying the detail display group.
+ * @param displayGroup the detail display group of this
+ * Master-Detail Association
+ */
+ public MirrorDetailAssociation(EODisplayGroup displayGroup){
+ super(displayGroup);
+ }
+
+ /**
+ * Called by subjectChanged() to requalify the controlled
+ * display group with the indexed object and the bound key.
+ * This implementation ignores both and sets the object array
+ * of the detail group to the displayed objects of the master
+ * and sets the selection to match.
+ */
+ protected void requalify()
+ {
+ EODisplayGroup detail = (EODisplayGroup) object();
+ EODisplayGroup master =
+ displayGroupForAspect( ParentAspect );
+
+ if ( master != null )
+ {
+ NSArray masterObjects = master.displayedObjects();
+ NSArray detailObjects = detail.displayedObjects();
+ int size = masterObjects.size();
+ boolean different = false;
+
+ // see if lists contain the same object instances
+ if ( size == detailObjects.size() )
+ {
+ for ( int i = 0; i < size; i++ )
+ {
+ if ( masterObjects.objectAtIndex(i)
+ != detailObjects.objectAtIndex(i) )
+ {
+ different = true;
+ break;
+ }
+ }
+ }
+ else // different sizes
+ {
+ different = true;
+ }
+
+ // if different, sync contents and selection with master
+ if ( different )
+ {
+ detail.setObjectArray( masterObjects );
+ detail.setSelectionIndexes( master.selectionIndexes() );
+ }
+ else // if selection changed, sync selection with master
+ if ( master.selectionChanged() )
+ {
+ detail.setSelectionIndexes( master.selectionIndexes() );
+ }
+ }
+ else // no bound display group, clear
+ {
+ detail.setObjectArray( null );
+ }
+ }
+}
+/*
+ * $Log$
+ * Revision 1.1 2006/02/16 13:22:22 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.2 2001/07/05 22:13:28 mpowers
+ * Now only updating if master has actually changed.
+ *
+ * Revision 1.1 2001/05/29 19:57:47 mpowers
+ * Added some neglected files.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/ObservableArray.java b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/ObservableArray.java
new file mode 100644
index 0000000..a398e97
--- /dev/null
+++ b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/ObservableArray.java
@@ -0,0 +1,349 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 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.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import net.wotonomy.control.EOObserverCenter;
+import net.wotonomy.control.EOObserving;
+import net.wotonomy.foundation.NSMutableArray;
+import net.wotonomy.foundation.NSRange;
+
+/**
+* A package class that extends NSMutableArray but makes use
+* of the fact that wotonomy's implementation extends ArrayList
+* to intercept insertions and deletion and register and
+* unregister objects for change notifications as appropriate.
+* Since we can't be sure of ArrayList's implementation, we're
+* forced to override each and every add and remove method,
+* some of which probably call each other. However,
+* EOObserverCenter will only register us once per object.
+*/
+class ObservableArray extends NSMutableArray
+{
+ EOObserving observer;
+
+ ObservableArray( EOObserving anObserver )
+ {
+ observer = anObserver;
+ }
+
+ /**
+ * Removes the last object from the array.
+ */
+ public void removeLastObject ()
+ {
+ remove( count() - 1 );
+ }
+
+ /**
+ * Removes the object at the specified index.
+ */
+ public void removeObjectAtIndex (int index)
+ {
+ remove( index );
+ }
+
+ /**
+ * Adds all objects in the specified collection.
+ */
+ public void addObjectsFromArray (Collection aCollection)
+ {
+ addAll( aCollection );
+ }
+
+ /**
+ * Removes all objects from the array.
+ */
+ public void removeAllObjects ()
+ {
+ clear();
+ }
+
+ /**
+ * Removes all objects equivalent to the specified object
+ * within the range of specified indices.
+ */
+ public void removeObject (Object anObject, NSRange aRange)
+ {
+ if ( ( anObject == null ) || ( aRange == null ) ) return;
+
+ int loc = aRange.location();
+ int max = aRange.maxRange();
+ for ( int i = loc; i < max; i++ )
+ {
+ if ( anObject.equals( get( i ) ) )
+ {
+ remove( i );
+ i = i - 1;
+ max = max - 1;
+ }
+ }
+ }
+
+ /**
+ * Removes all instances of the specified object within the
+ * range of specified indices, comparing by reference.
+ */
+ public void removeIdenticalObject (Object anObject, NSRange aRange)
+ {
+ if ( ( anObject == null ) || ( aRange == null ) ) return;
+
+ int loc = aRange.location();
+ int max = aRange.maxRange();
+ for ( int i = loc; i < max; i++ )
+ {
+ if ( anObject == get( i ) )
+ {
+ remove( i );
+ i = i - 1;
+ max = max - 1;
+ }
+ }
+ }
+
+ /**
+ * Removes all objects in the specified collection from the array.
+ */
+ public void removeObjectsInArray (Collection aCollection)
+ {
+ removeAll( aCollection );
+ }
+
+ /**
+ * Removes all objects in the indices within the specified range
+ * from the array.
+ */
+ public void removeObjectsInRange (NSRange aRange)
+ {
+ if ( aRange == null ) return;
+
+ for ( int i = 0; i < aRange.length(); i++ )
+ {
+ remove( aRange.location() );
+ }
+ }
+
+ /**
+ * Replaces objects in the current range with objects from
+ * the specified range of the specified array. If currentRange
+ * is larger than otherRange, the extra objects are removed.
+ * If otherRange is larger than currentRange, the extra objects
+ * are added.
+ */
+ public void replaceObjectsInRange (NSRange currentRange,
+ List otherArray, NSRange otherRange)
+ {
+ if ( ( currentRange == null ) || ( otherArray == null ) ||
+ ( otherRange == null ) ) return;
+
+ // transform otherRange if out of bounds for array
+ if ( otherRange.maxRange() > otherArray.size() )
+ {
+ // TODO: Test this logic.
+ int loc = Math.min( otherRange.location(), otherArray.size() - 1 );
+ otherRange = new NSRange( loc, otherArray.size() - loc );
+ }
+
+ Object o;
+ List subList = subList(
+ currentRange.location(), currentRange.maxRange() );
+ int otherIndex = otherRange.location();
+ // TODO: Test this logic.
+ for ( int i = 0; i < subList.size(); i++ )
+ {
+ if ( otherIndex < otherRange.maxRange() )
+ { // set object
+ subList.set( i, otherArray.get( otherIndex ) );
+ }
+ else
+ { // remove extra elements from currentRange
+ subList.remove( i );
+ i--;
+ }
+ otherIndex++;
+ }
+ // TODO: Test this logic.
+ for ( int i = otherIndex; i < otherRange.maxRange(); i++ )
+ {
+ add( otherArray.get( i ) );
+ }
+ }
+
+ /**
+ * Clears the current array and then populates it with the
+ * contents of the specified collection.
+ */
+ public void setArray (Collection aCollection)
+ {
+ clear();
+ addAll( aCollection );
+ }
+
+ /**
+ * Removes all objects equivalent to the specified object.
+ */
+ public void removeObject (Object anObject)
+ {
+ remove( anObject );
+ }
+
+ /**
+ * Removes all occurences of the specified object,
+ * comparing by reference.
+ */
+ public void removeIdenticalObject (Object anObject)
+ {
+ EOObserverCenter.removeObserver( observer, anObject );
+ super.removeIdenticalObject( anObject );
+ }
+
+ /**
+ * Inserts the specified object into this array at the
+ * specified index.
+ */
+ public void insertObjectAtIndex (Object anObject, int anIndex)
+ {
+ add( anIndex, anObject );
+ }
+
+ /**
+ * Replaces the object at the specified index with the
+ * specified object.
+ */
+ public void replaceObjectAtIndex (int anIndex, Object anObject)
+ {
+ set( anIndex, anObject );
+ }
+
+ /**
+ * Adds the specified object to the end of this array.
+ */
+ public void addObject (Object anObject)
+ {
+ add( anObject );
+ }
+
+ // interface List: mutators
+
+ public void add(int index, Object element)
+ {
+ EOObserverCenter.addObserver( observer, element );
+ super.add( index, element );
+ }
+
+ public boolean add(Object o)
+ {
+ EOObserverCenter.addObserver( observer, o );
+ return super.add(o);
+ }
+
+ public boolean addAll(Collection coll)
+ {
+ Iterator it = coll.iterator();
+ while ( it.hasNext() )
+ {
+ EOObserverCenter.addObserver( observer, it.next() );
+ }
+ return super.addAll(coll);
+ }
+
+ public boolean addAll(int index, Collection c)
+ {
+ Iterator it = c.iterator();
+ while ( it.hasNext() )
+ {
+ EOObserverCenter.addObserver( observer, it.next() );
+ }
+ return super.addAll( index, c );
+ }
+
+ public void clear()
+ {
+ Iterator it = iterator();
+ while ( it.hasNext() )
+ {
+ EOObserverCenter.removeObserver( observer, it.next() );
+ }
+ super.clear();
+ }
+
+ public Object remove(int index)
+ {
+ EOObserverCenter.removeObserver( observer, get(index) );
+ return super.remove( index );
+ }
+
+ public boolean remove(Object o)
+ {
+ EOObserverCenter.removeObserver( observer, o );
+ return super.remove(o);
+ }
+
+ public boolean removeAll(Collection coll)
+ {
+ Iterator it = coll.iterator();
+ while ( it.hasNext() )
+ {
+ EOObserverCenter.removeObserver( observer, it.next() );
+ }
+ return super.removeAll(coll);
+ }
+
+ public boolean retainAll(Collection coll)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object set(int index, Object element)
+ {
+ EOObserverCenter.removeObserver( observer, get(index) );
+ EOObserverCenter.addObserver( observer, element );
+ return super.set( index, element );
+ }
+}
+
+/*
+ * $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.3 2003/08/06 23:07:52 chochos
+ * general code cleanup (mostly, removing unused imports)
+ *
+ * Revision 1.2 2002/10/24 21:15:36 mpowers
+ * New implementations of NSArray and subclasses.
+ *
+ * Revision 1.1 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.1 2001/01/24 14:37:24 mpowers
+ * Contributing a delegate useful for debugging.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/package.html b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/package.html
new file mode 100644
index 0000000..710c57b
--- /dev/null
+++ b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/package.html
@@ -0,0 +1,14 @@
+<body>
+<p>
+An implementation of the JavaClient
+framework. It tries to be API-compatible at the
+EODisplayGroup and EOAssociation layer. The
+associations themselves are inspired by their EO
+counterparts, but are extended so as to be
+more useful for java application development.
+</p>
+<p>
+This package has dependencies on the control,
+foundation, and util packages.
+</p>
+</body>