diff options
| author | Benjamin Culkin <scorpress@gmail.com> | 2024-05-19 17:56:33 -0400 |
|---|---|---|
| committer | Benjamin Culkin <scorpress@gmail.com> | 2024-05-19 17:56:33 -0400 |
| commit | aedc34d55462a75e329bbf342251ff6504cd117e (patch) | |
| tree | bcc8f1f2352582717b484df302aeea6696b8f000 /projects/net.wotonomy.ui/src/main/java/net | |
Initial import from SVN
Diffstat (limited to 'projects/net.wotonomy.ui/src/main/java/net')
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> |
