summaryrefslogtreecommitdiff
path: root/projects/net.wotonomy.test/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'projects/net.wotonomy.test/src/main/java')
-rw-r--r--projects/net.wotonomy.test/src/main/java/net/wotonomy/test/BindingController.java35
-rw-r--r--projects/net.wotonomy.test/src/main/java/net/wotonomy/test/BindingPanel.java172
-rw-r--r--projects/net.wotonomy.test/src/main/java/net/wotonomy/test/DataKeyID.java88
-rw-r--r--projects/net.wotonomy.test/src/main/java/net/wotonomy/test/DataObjectStore.java483
-rw-r--r--projects/net.wotonomy.test/src/main/java/net/wotonomy/test/EditController.java214
-rw-r--r--projects/net.wotonomy.test/src/main/java/net/wotonomy/test/EditPanel.java61
-rw-r--r--projects/net.wotonomy.test/src/main/java/net/wotonomy/test/InspectorController.java137
-rw-r--r--projects/net.wotonomy.test/src/main/java/net/wotonomy/test/Test.java120
-rw-r--r--projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestController.java395
-rw-r--r--projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestDataSource.java115
-rw-r--r--projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestMap.java164
-rw-r--r--projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObject.java362
-rw-r--r--projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObjectClassDesc.java31
-rw-r--r--projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObjectStore.java258
-rw-r--r--projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestPanel.java110
-rw-r--r--projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreeController.java285
-rw-r--r--projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreeInspectorController.java184
-rw-r--r--projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreePanel.java39
18 files changed, 3253 insertions, 0 deletions
diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/BindingController.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/BindingController.java
new file mode 100644
index 0000000..be83eb4
--- /dev/null
+++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/BindingController.java
@@ -0,0 +1,35 @@
+package net.wotonomy.test;
+
+//import net.wotonomy.foundation.*;
+import javax.swing.JDialog;
+
+import net.wotonomy.ui.EOAssociation;
+import net.wotonomy.ui.EODisplayGroup;
+import net.wotonomy.ui.swing.TreeAssociation;
+import net.wotonomy.ui.swing.util.WindowUtilities;
+
+/**
+* A simple editor panel with a few textfields.
+*/
+public class BindingController
+{
+ public BindingController( EODisplayGroup titlesGroup, EODisplayGroup childGroup )
+ {
+ BindingPanel bindingPanel = new BindingPanel();
+
+ EOAssociation ta;
+ ta = new TreeAssociation( bindingPanel.treeChooser, "People" );
+ ta.bindAspect( EOAssociation.TitlesAspect, titlesGroup, "lastName" );
+ ta.bindAspect( EOAssociation.ChildrenAspect, childGroup, "children" );
+ ta.bindAspect( EOAssociation.IsLeafAspect, titlesGroup, "childCount" );
+ ta.establishConnection();
+
+ JDialog d = new JDialog();
+ d.getContentPane().add( bindingPanel );
+ d.setTitle( "Chooser Panel" );
+ d.pack();
+ WindowUtilities.cascade( d );
+ d.show();
+ }
+
+}
diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/BindingPanel.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/BindingPanel.java
new file mode 100644
index 0000000..624dc37
--- /dev/null
+++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/BindingPanel.java
@@ -0,0 +1,172 @@
+package net.wotonomy.test;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.JPanel;
+import javax.swing.UIManager;
+import javax.swing.border.EmptyBorder;
+
+import net.wotonomy.ui.swing.components.ButtonPanel;
+import net.wotonomy.ui.swing.components.TreeChooser;
+
+/**
+* BindingPanel is a FileChooser-like panel that
+* uses a TreeModel as a data source. It basically
+* provides an alternative to JTree for rendering
+* and manipulating tree-like data.
+*/
+public class BindingPanel extends JPanel
+{
+ protected TreeChooser treeChooser;
+ protected ButtonPanel okPanel;
+
+ public BindingPanel()
+ {
+ init();
+ }
+
+ protected void init()
+ {
+ this.setBorder( new EmptyBorder( 10, 10, 10, 10 ) );
+ this.setLayout( new BorderLayout( 10, 10 ) );
+
+ this.add( treeChooser = new TreeChooser(), BorderLayout.CENTER );
+
+ okPanel = new ButtonPanel( new String[] { "OK", "Cancel" } );
+ this.add( okPanel, BorderLayout.SOUTH );
+ }
+
+ /**
+ * Creates a new folder.
+ */
+ protected class NewFolderAction extends AbstractAction
+ {
+ protected NewFolderAction()
+ {
+ super("New Folder", UIManager.getIcon("FileChooser.newFolderIcon") );
+ }
+ public void actionPerformed(ActionEvent e)
+ {
+ }
+ }
+
+ /**
+ * Acts on the "home" key event or equivalent event.
+ */
+ protected class GoHomeAction extends AbstractAction
+ {
+ protected GoHomeAction()
+ {
+ super("Go Home", UIManager.getIcon("FileChooser.homeFolderIcon") );
+ }
+ public void actionPerformed(ActionEvent e)
+ {
+ }
+ }
+
+ protected class ChangeToParentDirectoryAction extends AbstractAction
+ {
+ protected ChangeToParentDirectoryAction()
+ {
+ super("Go Up", UIManager.getIcon("FileChooser.upFolderIcon") );
+ }
+ public void actionPerformed(ActionEvent e)
+ {
+ }
+ }
+
+ /**
+ * Responds to an Open or Save request
+ */
+ protected class ApproveSelectionAction extends AbstractAction {
+ public void actionPerformed(ActionEvent e)
+ {
+ }
+ }
+
+
+ /**
+ * Responds to a cancel request.
+ */
+ protected class CancelSelectionAction extends AbstractAction {
+ public void actionPerformed(ActionEvent e)
+ {
+ }
+ }
+
+ /**
+ * Rescans the files in the current directory
+ */
+ protected class UpdateAction extends AbstractAction {
+ public void actionPerformed(ActionEvent e)
+ {
+ }
+ }
+
+ //
+ // Renderer for DirectoryComboBox
+ //
+/*
+ class DirectoryComboBoxRenderer extends DefaultListCellRenderer {
+ IndentIcon ii = new IndentIcon();
+ public Component getListCellRendererComponent(JList list, Object value,
+ int index, boolean isSelected,
+ boolean cellHasFocus) {
+
+ super.getListCellRendererComponent(list, value, index,
+ isSelected, cellHasFocus);
+ File directory = (File) value;
+ if(directory == null) {
+ setText("");
+ return this;
+ }
+
+ String fileName = getFileChooser().getName(directory);
+ setText(fileName);
+
+ // Find the depth of the directory
+ int depth = 0;
+ if(index != -1) {
+ File f = directory;
+ while(f.getParent() != null) {
+ depth++;
+ f = getFileChooser().getFileSystemView().createFileObject(
+ f.getParent()
+ );
+ }
+ }
+
+ Icon icon = getFileChooser().getIcon(directory);
+
+ ii.icon = icon;
+ ii.depth = depth;
+
+ setIcon(ii);
+
+ return this;
+ }
+ }
+
+ final static int space = 10;
+ class IndentIcon implements Icon {
+
+ Icon icon = null;
+ int depth = 0;
+
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ icon.paintIcon(c, g, x+depth*space, y);
+ }
+
+ public int getIconWidth() {
+ return icon.getIconWidth() + depth*space;
+ }
+
+ public int getIconHeight() {
+ return icon.getIconHeight();
+ }
+
+ }
+*/
+}
diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/DataKeyID.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/DataKeyID.java
new file mode 100644
index 0000000..fca6c98
--- /dev/null
+++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/DataKeyID.java
@@ -0,0 +1,88 @@
+package net.wotonomy.test;
+
+import net.wotonomy.control.EOGlobalID;
+import net.wotonomy.datastore.DataKey;
+
+/**
+* A test implementation of EOGlobalID that
+* wraps a DataKey.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 893 $
+*/
+public class DataKeyID extends EOGlobalID
+{
+ private DataKey key;
+
+ /**
+ * Constructor takes a data key.
+ */
+ public DataKeyID( DataKey aKey )
+ {
+ key = aKey;
+ }
+
+ /**
+ * Returns the wrapped data key.
+ */
+ public DataKey getKey()
+ {
+ return key;
+ }
+
+ public boolean isTemporary()
+ {
+ return false;
+ }
+
+ public Object clone()
+ {
+ return new DataKeyID( (DataKey) key.clone() );
+ }
+
+ public String toString()
+ {
+ return "[DataKeyID:"+key.toString()+"]";
+ }
+
+ public int hashCode()
+ {
+ return key.hashCode();
+ }
+
+ public boolean equals( Object anObject )
+ {
+ if ( anObject instanceof DataKeyID )
+ {
+ if ( ((DataKeyID)anObject).key.equals( key ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
+
+/*
+ * $Log$
+ * Revision 1.1 2006/02/16 13:22:22 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.3 2001/02/23 23:44:44 mpowers
+ * Fixes for hashcode to ensure proper key comparison.
+ *
+ * Revision 1.2 2001/02/22 20:56:07 mpowers
+ * Tests of notification handling.
+ *
+ * Revision 1.1 2001/02/15 21:14:45 mpowers
+ * Test suite now using a persistent object store with editing context.
+ *
+ * Revision 1.1 2001/02/05 03:45:37 mpowers
+ * Starting work on EOEditingContext.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/DataObjectStore.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/DataObjectStore.java
new file mode 100644
index 0000000..d175f6e
--- /dev/null
+++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/DataObjectStore.java
@@ -0,0 +1,483 @@
+package net.wotonomy.test;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import net.wotonomy.control.ArrayFault;
+import net.wotonomy.control.EOEditingContext;
+import net.wotonomy.control.EOFetchSpecification;
+import net.wotonomy.control.EOGlobalID;
+import net.wotonomy.control.EOObjectStore;
+import net.wotonomy.control.EOObserverCenter;
+import net.wotonomy.control.KeyValueCodingUtilities;
+import net.wotonomy.datastore.DataKey;
+import net.wotonomy.datastore.DataSoup;
+import net.wotonomy.datastore.DataView;
+import net.wotonomy.datastore.XMLFileSoup;
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSMutableArray;
+import net.wotonomy.foundation.NSMutableDictionary;
+import net.wotonomy.foundation.NSNotification;
+import net.wotonomy.foundation.NSNotificationQueue;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+/**
+* An object store that wraps a datastore for vending test objects.
+*/
+public class DataObjectStore extends EOObjectStore
+{
+ DataSoup soup;
+
+ /**
+ * Constructor specifies path to datastore.
+ */
+ public DataObjectStore( String aPath )
+ {
+ soup = new XMLFileSoup( aPath );
+ }
+
+ /**
+ * This implementation returns an appropriately configured array fault.
+ */
+ public NSArray arrayFaultWithSourceGlobalID (
+ EOGlobalID aGlobalID,
+ String aRelationship,
+ EOEditingContext aContext )
+ {
+ return new ArrayFault(
+ aGlobalID, aRelationship, aContext );
+ }
+
+ /**
+ * This implementation returns the actual
+ * object for the specified id.
+ */
+ public Object faultForGlobalID (
+ EOGlobalID aGlobalID,
+ EOEditingContext aContext )
+ {
+System.out.println( "DataObjectStore.faultForGlobalID: * reading object * : " + aGlobalID );
+ Object result = soup.getObjectByKey(
+ ((DataKeyID)aGlobalID).getKey() );
+
+ if ( result == null ) return null;
+
+ //! transpose keys to objects
+ convertRelationKeysToObjects( aContext, result, aGlobalID );
+ //!
+
+ aContext.recordObject( result, aGlobalID );
+ return result;
+ }
+
+ /**
+ * Returns a fault representing an object of
+ * the specified entity type with values from
+ * the specified dictionary. The fault should
+ * belong to the specified editing context.
+ */
+ public Object faultForRawRow (
+ Map aDictionary,
+ String anEntityName,
+ EOEditingContext aContext )
+ {
+ //TODO: faults are not yet supported
+ throw new WotonomyException(
+ "Faults are not yet supported." );
+ }
+
+ /**
+ * Given a newly instantiated object, this method
+ * initializes its properties to values appropriate
+ * for the specified id. The object should belong
+ * to the specified editing context.
+ * This method is called to populate faults.
+ */
+ public void initializeObject(Object anObject, EOGlobalID aGlobalID,
+ EOEditingContext aContext) {
+ if (aGlobalID.isTemporary()) {
+ // TODO: this should never happen, but it does now until we get
+ // faults.
+
+ // do not reinit an uncommitted object
+ return;
+ }
+
+ System.out.println("DataObjectStore.initializeObject: * reading object * : "
+ + aGlobalID);
+ //net.wotonomy.ui.swing.util.StackTraceInspector.printShortStackTrace();
+ Object original = soup.getObjectByKey(((DataKeyID) aGlobalID).getKey());
+
+ // ! transpose keys to objects
+ convertRelationKeysToObjects(aContext, original, aGlobalID);
+ // !
+ EOObserverCenter.notifyObserversObjectWillChange(anObject);
+ KeyValueCodingUtilities.copy(aContext, original, aContext, anObject);
+ }
+
+ /**
+ * Remove all values from all objects in memory, turning them into faults,
+ * and posts a notification that all objects have been invalidated.
+ */
+ public void invalidateAllObjects ()
+ {
+ // does nothing except post notification
+
+ NSNotificationQueue.defaultQueue().enqueueNotification(
+ new NSNotification(
+ InvalidatedAllObjectsInStoreNotification, this ),
+ NSNotificationQueue.PostNow );
+ }
+
+ /**
+ * Removes values with the specified ids from memory,
+ * turning them into faults, and posts a notification
+ * that those objects have been invalidated.
+ */
+ public void invalidateObjectsWithGlobalIDs (
+ List aList )
+ {
+ // does nothing
+ }
+
+ /**
+ * Returns false because locking is not permitted.
+ */
+ public boolean isObjectLockedWithGlobalID (
+ EOGlobalID aGlobalID,
+ EOEditingContext aContext )
+ {
+ return false;
+ }
+
+ /**
+ * Does nothing because locking is not permitted.
+ */
+ public void lockObjectWithGlobalID (
+ EOGlobalID aGlobalID,
+ EOEditingContext aContext )
+ {
+ // does nothing
+ }
+
+ /**
+ * Returns a List of objects associated with the object
+ * with the specified id for the specified property
+ * relationship. This method may not return an array fault
+ * because array faults call this method to fetch on demand.
+ * All objects must be registered the specified editing context.
+ * The specified relationship key must produce a result of
+ * type Collection for the source object or an exception is thrown.
+ */
+ public NSArray objectsForSourceGlobalID (
+ EOGlobalID aGlobalID,
+ String aRelationship,
+ EOEditingContext aContext )
+ {
+ System.out.println( "DataObjectStore.objectsForSourceGlobalID: * reading object * : " + aGlobalID );
+ Object object = soup.getObjectByKey(((DataKeyID)aGlobalID).getKey() );
+
+ if ( object == null ) return null;
+
+ Object fault;
+ EOGlobalID id;
+ NSMutableArray result = new NSMutableArray();
+
+ Iterator it = ((TestObject)object).getChildList().iterator();
+ while ( it.hasNext() )
+ {
+ id = new DataKeyID((DataKey)it.next());
+ fault = aContext.faultForGlobalID( id, aContext );
+
+ // if key still exists
+ if ( fault != null )
+ {
+//System.out.println( "objectsForSourceGlobalID: found: " + id + " : " + fault );
+ result.add( fault );
+
+// for testing purposes
+((TestObject)fault).setParent( (TestObject) object );
+ }
+ else // key no longer exists
+ {
+ // do not add
+System.out.println( "objectsForSourceGlobalID: could not find fault for id: " + id );
+ }
+ }
+ return result;
+
+ }
+
+ /**
+ * Returns a List of objects the meet the criteria of
+ * the supplied specification.
+ * Each object is registered with the specified editing context.
+ * If any object is already registered in the specified context,
+ * it is not refetched and that object should be used in the array.
+ */
+ public NSArray objectsWithFetchSpecification (
+ EOFetchSpecification aFetchSpec,
+ EOEditingContext aContext )
+ {
+ //TODO: fetch specs are not yet supported
+
+ DataView view = soup.queryObjects( null, null );
+System.out.println( "DataObjectStore: ** querying all objects **" );
+
+ // we've changed this implementation so that
+ // it simply calls faultForGlobalID on the context
+ // for each id in the result set.
+ // this way, child contexts inherit parent's state.
+ // however, it's unclear if the specification allows
+ // faults in the resulting array. sounds like it doesn't.
+ NSMutableArray result = new NSMutableArray();
+ DataKeyID id;
+ Iterator it = view.iterator();
+ while ( it.hasNext() )
+ {
+ id = new DataKeyID( view.getKeyForObject( it.next() ) );
+ result.addObject( aContext.faultForGlobalID( id, aContext ) );
+ }
+ return result;
+ }
+
+ /**
+ * Removes all values from the specified object,
+ * converting it into a fault for the specified id.
+ * New or deleted objects should not be refaulted.
+ */
+ public void refaultObject (
+ Object anObject,
+ EOGlobalID aGlobalID,
+ EOEditingContext aContext )
+ {
+ //TODO: faults are not yet supported
+ // just re-initialize the object
+ initializeObject( anObject, aGlobalID, aContext );
+ }
+
+ /**
+ * Writes all changes in the specified editing context
+ * to the respository.
+ */
+ public void saveChangesInEditingContext (
+ EOEditingContext aContext )
+ {
+ Object o;
+ DataKeyID id;
+ Iterator it;
+
+ // process deletes
+ it = aContext.deletedObjects().iterator();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ id = (DataKeyID) aContext.globalIDForObject( o );
+System.out.println( "DataObjectStore: * deleting object * : " + id );
+ soup.removeObject( id.getKey() );
+ // remove object from editing context
+ aContext.forgetObject( o );
+ }
+
+ // process inserts
+ NSMutableDictionary userInfo = null;
+ it = aContext.insertedObjects().iterator();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ EOGlobalID oldId = aContext.globalIDForObject( o );
+
+ //! transpose objects to keys
+ convertRelationObjectsToKeys( aContext, (TestObject) o );
+ id = new DataKeyID( soup.addObject( o ) );
+ convertRelationKeysToObjects( aContext, (TestObject) o, oldId );
+ //!
+
+System.out.println( "DataObjectStore: * adding object * : " + id );
+
+ // save mapping of old id to new id
+ if ( userInfo == null )
+ {
+ userInfo = new NSMutableDictionary();
+ }
+ userInfo.setObjectForKey( id, oldId );
+ }
+
+ // broadcast inserted objects' new ids if necessary
+ if ( userInfo != null )
+ {
+ NSNotificationQueue.defaultQueue().enqueueNotification(
+ new NSNotification(
+ EOGlobalID.GlobalIDChangedNotification, null, userInfo ),
+ NSNotificationQueue.PostNow );
+ }
+
+ System.out.println( aContext.updatedObjects() );
+
+ // process updates
+ it = aContext.updatedObjects().iterator();
+ while ( it.hasNext() )
+ {
+//if ( true ) // test validation error message handling
+//throw new RuntimeException( "Update not allowed." );
+ o = it.next();
+ id = (DataKeyID) aContext.globalIDForObject( o );
+System.out.println( "DataObjectStore: * updating object * : " + id );
+
+ //! transpose objects to keys
+ convertRelationObjectsToKeys( aContext, (TestObject) o );
+ soup.updateObject( id.getKey(), o );
+ convertRelationKeysToObjects( aContext, (TestObject) o, id );
+ //!
+
+ }
+ }
+
+ private void convertRelationKeysToObjects(
+ EOEditingContext aContext, Object anObject, EOGlobalID aGlobalID )
+ { // System.out.println( "convertRelationKeysToObjects: " + anObject );
+// set editing context for testing
+((TestObject)anObject).editingContext = aContext;
+
+ Object fault;
+ DataKeyID id;
+ List result = new LinkedList();
+ Iterator it = ((TestObject)anObject).getChildList().iterator();
+ while ( it.hasNext() )
+ {
+ id = new DataKeyID((DataKey)it.next());
+ fault = aContext.faultForGlobalID( id, aContext );
+
+ // if key still exists
+ if ( fault != null )
+ {
+//System.out.println( "convertRelationObjectsToKeys: found: " + id + " : " + fault );
+ result.add( fault );
+
+// for testing purposes
+((TestObject)fault).setParent( (TestObject) anObject );
+ }
+ else // key no longer exists
+ {
+ // do not add
+System.out.println( "convertRelationObjectsToKeys: could not find fault for id: " + id );
+ }
+ }
+ // this tests loading manually on-demand
+// ((TestObject)anObject).setChildList( null );
+ // this tests loading immediately
+ ((TestObject)anObject).setChildList( result );
+ // this tests loading array faults
+// ((TestObject)result).setChildList( null );
+ ((TestObject)anObject).setChildList(
+ aContext.arrayFaultWithSourceGlobalID(
+ aGlobalID, "childList", aContext ) );
+
+ }
+
+ private void convertRelationObjectsToKeys(
+ EOEditingContext aContext, Object anObject )
+ { // System.out.println( "convertRelationObjectsToKeys: " + anObject );
+ Object o;
+ DataKeyID id;
+ List result = new LinkedList();
+ Iterator it = ((TestObject)anObject).getChildList().iterator();
+// for testing purposes
+((TestObject)anObject).setParent( null );
+((TestObject)anObject).editingContext = null;
+ while ( it.hasNext() )
+ {
+ o = it.next();
+//System.out.println( "convertRelationObjectsToKeys: " + o + " : " + aContext.globalIDForObject( o ) );
+ id = (DataKeyID)aContext.globalIDForObject( o );
+
+ // if object still exists in context
+ if ( id != null )
+ {
+ result.add( id.getKey() );
+ }
+ else // object was deleted
+ {
+ // do not add
+System.out.println( "convertRelationObjectsToKeys: could not find id for object: " + o );
+System.out.println( aContext.registeredObjects() );
+ }
+
+ }
+ ((TestObject)anObject).setChildList( result );
+ }
+
+
+/*
+ * $Log$
+ * Revision 1.1 2006/02/19 16:30:25 cgruber
+ * Update imports and maven dependencies.
+ *
+ * Revision 1.1 2006/02/16 13:18:56 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.18 2002/03/11 03:18:39 mpowers
+ * Now properly handling ObserverChangesLater.
+ *
+ * Revision 1.17 2001/10/26 18:39:44 mpowers
+ * Posting notifications immediately, rather than delayed.
+ *
+ * Revision 1.16 2001/05/06 18:27:10 mpowers
+ * More broadly catching editing contexts for now.
+ *
+ * Revision 1.15 2001/05/05 23:05:43 mpowers
+ * Implemented Array Faults.
+ *
+ * Revision 1.14 2001/05/05 15:00:06 mpowers
+ * Tested load-on-demand: still works.
+ * Now using registerClone for consistency.
+ * Editing context is temporarily posting notification on objectWillChange.
+ *
+ * Revision 1.13 2001/05/04 23:24:30 mpowers
+ * Changes to test code.
+ *
+ * Revision 1.12 2001/05/04 16:57:56 mpowers
+ * Now correctly transposing references to editing contexts when
+ * cloning/copying between editing contexts.
+ *
+ * Revision 1.11 2001/05/02 17:33:28 mpowers
+ * More changes for testing.
+ *
+ * Revision 1.10 2001/04/30 13:15:24 mpowers
+ * Child contexts re-initializing objects invalidated in parent now
+ * propery transpose relationships.
+ *
+ * Revision 1.9 2001/04/29 22:02:45 mpowers
+ * Work on id transposing between editing contexts.
+ *
+ * Revision 1.8 2001/04/29 02:29:31 mpowers
+ * Debugging relationship faulting.
+ *
+ * Revision 1.7 2001/04/28 22:17:51 mpowers
+ * Revised PropertyDataSource to be EOClassDescription-aware.
+ *
+ * Revision 1.6 2001/04/28 16:18:44 mpowers
+ * Implementing relationships.
+ *
+ * Revision 1.5 2001/04/13 16:33:36 mpowers
+ * Now broadcasting notifications.
+ *
+ * Revision 1.4 2001/04/08 21:00:54 mpowers
+ * Changes to support new objectsForFetchSpecification scheme.
+ *
+ * Revision 1.3 2001/03/22 21:37:52 mpowers
+ * Testing new features.
+ *
+ * Revision 1.2 2001/03/15 21:10:41 mpowers
+ * Implemented global id re-registration for newly saved inserts.
+ *
+ * Revision 1.1 2001/03/05 22:12:11 mpowers
+ * Created the control package for a datastore-specific implementation
+ * of EOObjectStore.
+ *
+ *
+ */
+}
+
diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/EditController.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/EditController.java
new file mode 100644
index 0000000..b304ade
--- /dev/null
+++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/EditController.java
@@ -0,0 +1,214 @@
+package net.wotonomy.test;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.AbstractButton;
+import javax.swing.BorderFactory;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+
+import net.wotonomy.control.ChildDataSource;
+import net.wotonomy.control.EODataSource;
+import net.wotonomy.control.PropertyDataSource;
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.ui.EOAssociation;
+import net.wotonomy.ui.EODisplayGroup;
+import net.wotonomy.ui.MasterDetailAssociation;
+import net.wotonomy.ui.swing.DisplayGroupActionAssociation;
+import net.wotonomy.ui.swing.ListAssociation;
+import net.wotonomy.ui.swing.RadioPanelAssociation;
+import net.wotonomy.ui.swing.TextAssociation;
+import net.wotonomy.ui.swing.components.ButtonPanel;
+import net.wotonomy.ui.swing.util.WindowUtilities;
+
+/**
+* A simple editor panel with a few textfields.
+*/
+public class EditController
+{
+ EODisplayGroup group;
+ JDialog dialog;
+
+ public EditController( EODataSource aDataSource )
+ {
+ EditPanel editPanel = new EditPanel();
+ editPanel.infoPanel.setBorder(
+ BorderFactory.createCompoundBorder(
+ BorderFactory.createRaisedBevelBorder(),
+ BorderFactory.createEmptyBorder( 10, 10, 10, 10 ) ) );
+ editPanel.setBorder(
+ BorderFactory.createEmptyBorder( 0, 0, 0, 0 ) );
+ ButtonPanel okPanel = new ButtonPanel(
+ new String[] { "Revert", "Refault", "Refresh", "Commit" } );
+ editPanel.add( okPanel, BorderLayout.SOUTH );
+
+ group = new EODisplayGroup();
+ group.setDataSource( aDataSource );
+ group.fetch();
+ group.selectNext();
+
+ // text associations
+
+ EOAssociation ta;
+
+ ta = new TextAssociation( editPanel.firstNameField );
+ ta.bindAspect( EOAssociation.ValueAspect, group, "firstName" );
+ ta.establishConnection();
+
+ ta = new TextAssociation( editPanel.middleNameField );
+ ta.bindAspect( EOAssociation.ValueAspect, group, "middleName" );
+ ta.establishConnection();
+
+ ta = new TextAssociation( editPanel.lastNameField );
+ ta.bindAspect( EOAssociation.ValueAspect, group, "lastName" );
+ ta.establishConnection();
+
+ // radio panels
+
+ ta = new RadioPanelAssociation( editPanel.yearRadioPanel );
+ ta.bindAspect( EOAssociation.ValueAspect, group, "createDate.year" );
+
+ EODisplayGroup yearTitles = new EODisplayGroup();
+ yearTitles.setObjectArray( new NSArray(
+ new Object[] { "1999", "2000", "2001" } ) );
+ ta.bindAspect( EOAssociation.TitlesAspect, yearTitles, "" );
+
+ EODisplayGroup yearObjects = new EODisplayGroup();
+ yearObjects.setObjectArray( new NSArray(
+ new Object[] { new Integer( 99 ), new Integer( 100 ), new Integer( 101 ) } ) );
+ ta.bindAspect( EOAssociation.ObjectsAspect, yearObjects, "" );
+
+ ta.establishConnection();
+
+ // detail group
+
+ final EODisplayGroup detailGroup = new EODisplayGroup();
+ detailGroup.setDataSource( new PropertyDataSource(
+ aDataSource.editingContext(), TestObject.class ) );
+
+ ta = new MasterDetailAssociation( detailGroup );
+ ta.bindAspect( EOAssociation.ParentAspect, group, "childList" );
+ ta.establishConnection();
+
+ ta = new ListAssociation( editPanel.list );
+ ta.bindAspect( EOAssociation.TitlesAspect, detailGroup, "fullName" );
+ ta.establishConnection();
+
+ // display group action associations
+
+ AbstractButton button = (AbstractButton)
+ editPanel.addPanel.getButton( "Add" );
+ button.addActionListener( new ActionListener()
+ {
+ public void actionPerformed( ActionEvent evt )
+ {
+ detailGroup.insertNewObjectAtIndex( 0 );
+ }
+ } );
+
+ ta = new DisplayGroupActionAssociation(
+ editPanel.addPanel.getButton( "Remove" ) );
+ ta.bindAspect( EOAssociation.ActionAspect, detailGroup, "deleteSelection" );
+ ta.establishConnection();
+
+ // ok / cancel buttons
+
+ button = (AbstractButton)
+ okPanel.getButton( "Commit" );
+ button.addActionListener( new ActionListener()
+ {
+ public void actionPerformed( ActionEvent evt )
+ {
+ group.dataSource().editingContext().saveChanges();
+ }
+ } );
+
+ button = (AbstractButton)
+ okPanel.getButton( "Refresh" );
+ button.addActionListener( new ActionListener()
+ {
+ public void actionPerformed( ActionEvent evt )
+ {
+ group.dataSource().editingContext().invalidateAllObjects();
+ }
+ } );
+
+ button = (AbstractButton)
+ okPanel.getButton( "Refault" );
+ button.addActionListener( new ActionListener()
+ {
+ public void actionPerformed( ActionEvent evt )
+ {
+/*
+ Object o = group.displayedObjects().objectAtIndex( 0 );
+ group.dataSource().editingContext().refaultObject(
+ o,
+ group.dataSource().editingContext().globalIDForObject( o ),
+ group.dataSource().editingContext() );
+*/
+ group.dataSource().editingContext().revert();
+ group.dataSource().editingContext().refaultObjects();
+ }
+ } );
+
+ button = (AbstractButton)
+ okPanel.getButton( "Revert" );
+ button.addActionListener( new ActionListener()
+ {
+ public void actionPerformed( ActionEvent evt )
+ {
+ group.dataSource().editingContext().revert();
+ }
+ } );
+
+ // add mouse listener for list
+
+ editPanel.list.addMouseListener( new MouseAdapter()
+ {
+ public void mouseClicked(MouseEvent e)
+ {
+ if ( e.getClickCount() == 2 )
+ {
+ Object item = detailGroup.selectedObject();
+ if ( item != null )
+ {
+// new InspectorController( item );
+
+ new EditController(
+ new ChildDataSource(
+ group.dataSource(), item ) );
+ }
+ }
+ }
+ });
+
+ // launch
+
+ dialog = new JDialog();
+ // add WindowListener for frame
+ dialog.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
+ dialog.getContentPane().add( editPanel );
+ dialog.setTitle( "Edit Panel" );
+ dialog.pack();
+// dialog.setSize( 300, dialog.getSize().height );
+ WindowUtilities.cascade( dialog );
+ dialog.show();
+
+ // workaround for memory issues on jdk1.2.2
+ dialog.addWindowListener( new WindowAdapter()
+ {
+ // exit on close
+ public void windowClosing(WindowEvent e)
+ {
+ ((JDialog)e.getWindow()).getContentPane().removeAll();
+ }
+ });
+ }
+
+}
diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/EditPanel.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/EditPanel.java
new file mode 100644
index 0000000..63b1317
--- /dev/null
+++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/EditPanel.java
@@ -0,0 +1,61 @@
+package net.wotonomy.test;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+import javax.swing.border.EmptyBorder;
+import javax.swing.text.JTextComponent;
+
+import net.wotonomy.ui.swing.components.ButtonPanel;
+import net.wotonomy.ui.swing.components.InfoPanel;
+import net.wotonomy.ui.swing.components.RadioButtonPanel;
+
+/**
+* A simple editor panel with a few textfields.
+*/
+public class EditPanel extends JPanel
+{
+ public JTextComponent firstNameField;
+ public JTextField middleNameField, lastNameField;
+ public RadioButtonPanel yearRadioPanel;
+ public InfoPanel infoPanel;
+ public JList list;
+ public ButtonPanel addPanel;
+
+
+ public EditPanel()
+ {
+ this.setLayout( new BorderLayout() );
+ this.setBorder( new EmptyBorder( 10, 10, 10, 10 ) );
+
+ infoPanel = new InfoPanel();
+
+ // name fields
+ firstNameField = new JTextField();
+ infoPanel.addPair( "First Name", firstNameField );
+ middleNameField = new JTextField();
+ infoPanel.addPair( "Middle Name", middleNameField );
+ lastNameField = new JTextField();
+ infoPanel.addPair( "Last Name", lastNameField );
+ yearRadioPanel = new RadioButtonPanel();
+ infoPanel.addPair( "Year", yearRadioPanel );
+
+ list = new JList();
+ JPanel containerPanel = new JPanel();
+ containerPanel.setLayout( new BorderLayout( 0, 5 ) );
+ JScrollPane scrollPane = new JScrollPane( list );
+ scrollPane.setPreferredSize( new java.awt.Dimension( 100, 100 ) );
+ addPanel = new ButtonPanel( new String[] { "Add", "Remove" } );
+ addPanel.setAlignment( FlowLayout.CENTER );
+ containerPanel.add( scrollPane, BorderLayout.CENTER );
+ containerPanel.add( addPanel, BorderLayout.SOUTH );
+ infoPanel.addRow( "Children", containerPanel );
+
+ this.add( infoPanel, BorderLayout.CENTER );
+ }
+
+}
diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/InspectorController.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/InspectorController.java
new file mode 100644
index 0000000..58e2d9a
--- /dev/null
+++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/InspectorController.java
@@ -0,0 +1,137 @@
+package net.wotonomy.test;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.AbstractButton;
+import javax.swing.JDialog;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.ui.EOAssociation;
+import net.wotonomy.ui.EODisplayGroup;
+import net.wotonomy.ui.MasterDetailAssociation;
+import net.wotonomy.ui.swing.DisplayGroupActionAssociation;
+import net.wotonomy.ui.swing.ListAssociation;
+import net.wotonomy.ui.swing.RadioPanelAssociation;
+import net.wotonomy.ui.swing.TextAssociation;
+import net.wotonomy.ui.swing.util.WindowUtilities;
+
+/**
+* A simple editor panel with a few textfields.
+*/
+public class InspectorController
+{
+ public InspectorController( Object o )
+ {
+ EditPanel editPanel = new EditPanel();
+
+ EODisplayGroup group = new EODisplayGroup();
+ group.setDataSource( new TestDataSource() );
+ group.setObjectArray( new NSArray( o ) );
+ group.selectNext();
+
+ // text associations
+
+ EOAssociation ta;
+
+ ta = new TextAssociation( editPanel.firstNameField );
+ ta.bindAspect( EOAssociation.ValueAspect, group, "firstName" );
+ ta.establishConnection();
+
+ ta = new TextAssociation( editPanel.middleNameField );
+ ta.bindAspect( EOAssociation.ValueAspect, group, "middleName" );
+ ta.establishConnection();
+
+ ta = new TextAssociation( editPanel.lastNameField );
+ ta.bindAspect( EOAssociation.ValueAspect, group, "lastName" );
+ ta.establishConnection();
+
+ // radio panels
+
+ ta = new RadioPanelAssociation( editPanel.yearRadioPanel );
+ ta.bindAspect( EOAssociation.ValueAspect, group, "createDate.year" );
+
+ EODisplayGroup yearTitles = new EODisplayGroup();
+ yearTitles.setObjectArray( new NSArray(
+ new Object[] { "1999", "2000", "2001" } ) );
+ ta.bindAspect( EOAssociation.TitlesAspect, yearTitles, "" );
+
+ EODisplayGroup yearObjects = new EODisplayGroup();
+ yearObjects.setObjectArray( new NSArray(
+ new Object[] { new Integer( 99 ), new Integer( 100 ), new Integer( 101 ) } ) );
+ ta.bindAspect( EOAssociation.ObjectsAspect, yearObjects, "" );
+
+ ta.establishConnection();
+
+ // detail group
+
+ final EODisplayGroup detailGroup = new EODisplayGroup();
+
+ ta = new MasterDetailAssociation( detailGroup );
+ ta.bindAspect( EOAssociation.ParentAspect, group, "childList" );
+ ta.establishConnection();
+
+ ta = new ListAssociation( editPanel.list );
+ ta.bindAspect( EOAssociation.TitlesAspect, detailGroup, "fullName" );
+ ta.establishConnection();
+
+ // display group action associations
+
+ AbstractButton button = (AbstractButton)
+ editPanel.addPanel.getButton( "Add" );
+ button.addActionListener( new ActionListener()
+ {
+ public void actionPerformed( ActionEvent evt )
+ {
+ detailGroup.insertNewObjectAtIndex( 0 );
+ }
+ } );
+
+ ta = new DisplayGroupActionAssociation(
+ editPanel.addPanel.getButton( "Remove" ) );
+ ta.bindAspect( EOAssociation.ActionAspect, detailGroup, "deleteSelection" );
+ ta.establishConnection();
+
+ // add mouse listener for list
+
+ editPanel.list.addMouseListener( new MouseAdapter()
+ {
+ public void mouseClicked(MouseEvent e)
+ {
+ if ( e.getClickCount() == 2 )
+ {
+ Object item = detailGroup.selectedObject();
+ if ( item != null )
+ {
+ new InspectorController( item );
+ }
+ }
+ }
+ });
+
+ // launch
+
+ JDialog dialog = new JDialog();
+ dialog.getContentPane().add( editPanel );
+ dialog.setTitle( "Inspector Panel" );
+ dialog.pack();
+ dialog.setSize( 300, dialog.getSize().height );
+ WindowUtilities.cascade( dialog );
+ dialog.show();
+
+ // workaround for memory issues on jdk1.2.2
+ dialog.addWindowListener( new WindowAdapter()
+ {
+ // exit on close
+ public void windowClosing(WindowEvent e)
+ {
+ ((JDialog)e.getWindow()).getContentPane().removeAll();
+ }
+ });
+ }
+
+}
diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/Test.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/Test.java
new file mode 100644
index 0000000..ba6a1dc
--- /dev/null
+++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/Test.java
@@ -0,0 +1,120 @@
+package net.wotonomy.test;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.KeyStroke;
+import javax.swing.UIManager;
+
+import net.wotonomy.control.EOEditingContext;
+import net.wotonomy.control.EOObjectStore;
+
+/**
+* A simple test-bed for wotonomy.
+* Shows a JFrame containing the TestPanel
+* which is controlled by the TestController.
+*/
+public class Test
+{
+ static EOObjectStore objectStore;
+ static EOEditingContext editingContext;
+ static public void main( String[] argv )
+ {
+// NSRunLoop.currentRunLoop();
+
+ // system l&f
+ try
+ {
+// UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel");
+ UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
+// UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+// UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
+ }
+ catch (Exception e)
+ {
+ // no system l&f - fail silently
+ }
+
+ // launch notification monitor if desired
+ for ( int i = 0; i < argv.length; i++ )
+ {
+ if ( argv[i].indexOf( "monitor" ) != -1 )
+ {
+ new net.wotonomy.ui.swing.NotificationInspector();
+ }
+ }
+ new net.wotonomy.ui.swing.NotificationInspector();
+
+ // set up editing context hierarchy
+ objectStore = new DataObjectStore( "data" );
+ editingContext = new EOEditingContext( objectStore );
+
+ // connect panel to controller
+ TestPanel testPanel = new TestPanel();
+ final TestController controller = new TestController( testPanel );
+
+ // create frame and show
+ JFrame frame = new JFrame();
+ frame.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
+
+ // setup menus
+ JMenu menu;
+ JMenuItem menuItem;
+ JMenuBar menuBar = new JMenuBar();
+ menu = new JMenu( "File" );
+ menu.add( "New" );
+ menuItem = new JMenuItem( "Save" );
+ menuItem.setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_S, KeyEvent.CTRL_MASK ) );
+ menuItem.addActionListener( new ActionListener()
+ {
+ public void actionPerformed( ActionEvent evt )
+ {
+ controller.displayGroup.dataSource().editingContext().saveChanges();
+ }
+ });
+
+ menu.add( menuItem );
+ menu.add( "Close" );
+ menuBar.add( menu );
+ menu = new JMenu( "Edit" );
+ menu.add( "Cut" );
+ menu.add( "Copy" );
+ menu.add( "Paste" );
+ menuBar.add( menu );
+ frame.setJMenuBar( menuBar );
+
+ frame.getContentPane().add( testPanel, BorderLayout.CENTER );
+ frame.setTitle( "Test Frame" );
+ frame.setBounds( 50, 50, 750, 500 );
+ frame.show(); // comment out this to avoid memory leak from jdk1.2.2 bug
+
+ // add WindowListener for frame
+ frame.addWindowListener( new WindowAdapter()
+ {
+ // exit on close
+ public void windowClosing(WindowEvent e)
+ {
+ System.exit( 0 );
+ }
+ });
+
+ /* uncomment this to avoid memory leak from jdk1.2.2 bug
+ frame.getContentPane().removeAll();
+ */
+/*
+ NSNotificationCenter.defaultCenter().addObserver(
+ frame,
+ new NSSelector( "hitMe", new Class[] { NSNotification.class } ),
+ null, null );
+*/
+ }
+
+}
diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestController.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestController.java
new file mode 100644
index 0000000..8bbb452
--- /dev/null
+++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestController.java
@@ -0,0 +1,395 @@
+package net.wotonomy.test;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.text.DateFormat;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.swing.AbstractButton;
+import javax.swing.Icon;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JOptionPane;
+import javax.swing.UIManager;
+import javax.swing.table.TableColumn;
+
+import net.wotonomy.control.ChildDataSource;
+import net.wotonomy.control.EOEditingContext;
+import net.wotonomy.control.EOGlobalID;
+import net.wotonomy.control.EOKeyValueQualifier;
+import net.wotonomy.control.EOQualifier;
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSNotification;
+import net.wotonomy.ui.EOAssociation;
+import net.wotonomy.ui.EODisplayGroup;
+import net.wotonomy.ui.swing.ButtonAssociation;
+import net.wotonomy.ui.swing.ComboBoxAssociation;
+import net.wotonomy.ui.swing.DisplayGroupActionAssociation;
+import net.wotonomy.ui.swing.SliderAssociation;
+import net.wotonomy.ui.swing.TableColumnAssociation;
+import net.wotonomy.ui.swing.TextAssociation;
+import net.wotonomy.ui.swing.TreeColumnAssociation;
+import net.wotonomy.ui.swing.components.AlternatingRowCellRenderer;
+import net.wotonomy.ui.swing.components.FormattedCellRenderer;
+import net.wotonomy.ui.swing.components.IconCellRenderer;
+import net.wotonomy.ui.swing.components.KeyableCellEditor;
+
+/**
+* Controller for the TestPanel.
+*/
+public class TestController implements ActionListener
+{
+ EODisplayGroup displayGroup;
+ TestPanel panel;
+
+ public TestController( TestPanel aPanel )
+ {
+ panel = aPanel;
+
+ // setup display group
+
+ displayGroup = new EODisplayGroup();
+ displayGroup.setSortOrderings( new NSArray( new Object[]
+ { "firstName", "middleName", "lastName" } ) );
+
+ // fetch the data
+// displayGroup.setUsesOptimisticRefresh( true );
+ displayGroup.setDataSource( new TestDataSource() );
+// displayGroup.setSelectsFirstObjectAfterFetch( true );
+ displayGroup.fetch();
+ displayGroup.selectNext();
+
+displayGroup.setDelegate( this );
+
+ // set up associations
+
+ EOAssociation assoc;
+
+ // table association
+
+ TableColumn column;
+
+ column = new TableColumn();
+ column.setHeaderValue( "First" );
+ IconCellRenderer iconRenderer = new IconCellRenderer()
+ {
+ private Icon icon = UIManager.getIcon("FileChooser.homeFolderIcon");
+ public Icon getIconForContext(
+ JComponent container, Object value,
+ int row, int col,
+ boolean isSelected, boolean hasFocus,
+ boolean isExpanded, boolean isLeaf )
+ {
+ return icon;
+ }
+ };
+ iconRenderer.addActionListener( this );
+ column.setCellRenderer( new AlternatingRowCellRenderer( iconRenderer ) );
+// column.setCellEditor( iconRenderer );
+// assoc = new TableColumnAssociation( column );
+ assoc = new TreeColumnAssociation( column );
+ assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "firstName" );
+//new net.wotonomy.ui.swing.DisplayGroupInspector( displayGroup );
+//displayGroup = new EODisplayGroup();
+// assoc.bindAspect( EOAssociation.ChildrenAspect, displayGroup, "childList" );
+ assoc.bindAspect( EOAssociation.EditableAspect, null, "true" );
+ assoc.bindAspect( EOAssociation.IsLeafAspect, null, "childCount" );
+ ((TableColumnAssociation)assoc).setTable( panel.table );
+ assoc.establishConnection();
+ ((TreeColumnAssociation)assoc).getTreeModelAssociation().setInsertingAfter( false );
+ ((TreeColumnAssociation)assoc).getTreeModelAssociation().setInsertingChild( false );
+
+// column.setCellRenderer( new AlternatingRowCellRenderer( column.getCellRenderer() ) );
+
+/*
+ // test the standalone mode of the icon cell renderer
+ panel.add( iconRenderer, java.awt.BorderLayout.SOUTH );
+ iconRenderer.setText( "Hello World!" );
+ iconRenderer.setIcon( UIManager.getIcon("FileChooser.homeFolderIcon") );
+*/
+
+ column = new TableColumn();
+ column.setHeaderValue( "Middle" );
+ column.setCellRenderer( new AlternatingRowCellRenderer() );
+ assoc = new TableColumnAssociation( column );
+ ((TableColumnAssociation)assoc).setSortCaseSensitive( true );
+ assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "middleName" );
+ ((TableColumnAssociation)assoc).setTable( panel.table );
+ assoc.establishConnection();
+
+ column = new TableColumn();
+ column.setHeaderValue( "Last" );
+ column.setCellRenderer( new AlternatingRowCellRenderer() );
+ column.setCellEditor( new KeyableCellEditor() );
+ assoc = new TableColumnAssociation( column );
+ assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "lastName" );
+ assoc.bindAspect( EOAssociation.EditableAspect, null, "true" );
+ ((TableColumnAssociation)assoc).setTable( panel.table );
+ assoc.establishConnection();
+
+ column = new TableColumn();
+ column.setHeaderValue( "Created" );
+ FormattedCellRenderer renderer = new FormattedCellRenderer();
+ renderer.setFormat( DateFormat.getDateInstance() );
+ column.setCellRenderer( new AlternatingRowCellRenderer( renderer ) );
+ assoc = new TableColumnAssociation( column );
+ assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "createDate" );
+ ((TableColumnAssociation)assoc).setTable( panel.table );
+ assoc.establishConnection();
+
+ column = new TableColumn();
+ column.setHeaderValue( "Special" );
+ column.setCellRenderer( new AlternatingRowCellRenderer(
+ panel.table.getDefaultRenderer( Boolean.class ) ) );
+ assoc = new TableColumnAssociation( column );
+ assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "special" );
+ assoc.bindAspect( EOAssociation.EditableAspect, null, "true" );
+ ((TableColumnAssociation)assoc).setTable( panel.table );
+ assoc.establishConnection();
+
+ // text associations
+
+ assoc = new TextAssociation( panel.firstNameField );
+ assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "firstName" );
+//EODisplayGroup controllerDisplayGroup = new EODisplayGroup();
+//controllerDisplayGroup.setObjectArray( new NSArray( this ) );
+//controllerDisplayGroup.selectNext();
+//assoc.bindAspect( EOAssociation.ValueAspect, controllerDisplayGroup, "filter" );
+ assoc.establishConnection();
+
+ assoc = new TextAssociation( panel.middleNameField );
+ assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "middleName" );
+ assoc.establishConnection();
+
+ assoc = new TextAssociation( panel.lastNameField );
+ assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "lastName" );
+ assoc.bindAspect( EOAssociation.LabelAspect, displayGroup, "special" );
+ assoc.establishConnection();
+
+ assoc = new ButtonAssociation( panel.checkbox );
+ assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "special" );
+// assoc.bindAspect( EOAssociation.EnabledAspect, displayGroup, "special" );
+// assoc.bindAspect( EOAssociation.VisibleAspect, displayGroup, "special" );
+ assoc.establishConnection();
+
+ // combo associations
+
+ assoc = new ComboBoxAssociation( panel.dateBox );
+ assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "createDate.date" );
+ // no titles aspect: uses existing combobox options
+ assoc.establishConnection();
+
+ assoc = new ComboBoxAssociation( panel.monthBox );
+ assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "createDate.month" );
+ EODisplayGroup monthTitlesGroup = new EODisplayGroup();
+ monthTitlesGroup.setObjectArray( new NSArray(
+ new Object[] { "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December" } ) );
+ assoc.bindAspect( EOAssociation.TitlesAspect, monthTitlesGroup, "" );
+
+ EODisplayGroup monthObjectsGroup = new EODisplayGroup();
+ monthObjectsGroup.setObjectArray( new NSArray(
+ new Object[] { new Integer( 0 ),
+ new Integer( 1 ), new Integer( 2 ), new Integer( 3 ),
+ new Integer( 4 ), new Integer( 5 ), new Integer( 6 ),
+ new Integer( 7 ), new Integer( 8 ), new Integer( 9 ),
+ new Integer( 10 ), new Integer( 11 ) } ) );
+ assoc.bindAspect( EOAssociation.ObjectsAspect, monthObjectsGroup, "" );
+
+ assoc.establishConnection();
+
+
+ assoc = new ComboBoxAssociation( panel.yearBox );
+ assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "createDate.year" );
+
+ EODisplayGroup yearTitlesGroup = new EODisplayGroup();
+ yearTitlesGroup.setObjectArray( new NSArray(
+ new Object[] { "1999", "2000", "2001" } ) );
+ assoc.bindAspect( EOAssociation.TitlesAspect, yearTitlesGroup, "" );
+
+ EODisplayGroup yearObjectsGroup = new EODisplayGroup();
+ yearObjectsGroup.setObjectArray( new NSArray(
+ new Object[] { new Integer( 99 ), new Integer( 100 ), new Integer( 101 ) } ) );
+ assoc.bindAspect( EOAssociation.ObjectsAspect, yearObjectsGroup, "" );
+
+ assoc.establishConnection();
+
+ assoc = new SliderAssociation( panel.slider );
+ assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "createDate.date" );
+ assoc.bindAspect( EOAssociation.VisibleAspect, displayGroup, "special" );
+ assoc.establishConnection();
+
+ assoc = new TextAssociation( panel.infoPanel.getLabelForKey( "Day of Month" ) );
+ assoc.bindAspect( EOAssociation.VisibleAspect, displayGroup, "special" );
+ assoc.establishConnection();
+
+ // display group action associations
+ AbstractButton button;
+
+ button = (AbstractButton)
+ panel.savePanel.getButton( "Refresh All" );
+ button.addActionListener( new ActionListener()
+ {
+ public void actionPerformed( ActionEvent evt )
+ { // panel.lastNameField.setText( panel.lastNameField.getText().trim() );
+
+ // test all three ways
+
+ displayGroup.dataSource().editingContext().invalidateAllObjects();
+// displayGroup.dataSource().editingContext().parentObjectStore().invalidateAllObjects();
+// displayGroup.dataSource().editingContext().revert();
+ }
+ } );
+
+ button = (AbstractButton)
+ panel.savePanel.getButton( "Commit" );
+ button.addActionListener( new ActionListener()
+ {
+ public void actionPerformed( ActionEvent evt )
+ {
+ try
+ {
+ displayGroup.dataSource().editingContext().saveChanges();
+ }
+ catch ( RuntimeException exc )
+ {
+ JOptionPane.showMessageDialog(
+ (java.awt.Component)evt.getSource(), exc.getMessage() );
+ exc.printStackTrace();
+ }
+ }
+ } );
+
+ button = (AbstractButton)
+ panel.buttonPanel.getButton( "Add" );
+ button.addActionListener( new ActionListener()
+ {
+ public void actionPerformed( ActionEvent evt )
+ {
+ displayGroup.insertNewObjectAtIndex( 0 );
+ }
+ } );
+
+ assoc = new DisplayGroupActionAssociation(
+ panel.buttonPanel.getButton( "Remove" ) );
+ assoc.bindAspect( EOAssociation.ActionAspect, displayGroup, "deleteSelection" );
+ assoc.establishConnection();
+/*
+ assoc = new DisplayGroupActionAssociation(
+ panel.infoPanel.getButtonPanel().getButton( "Refresh" ) );
+ assoc.bindAspect( EOAssociation.ActionAspect, displayGroup, "updateDisplayedObjects" );
+ assoc.establishConnection();
+
+ assoc = new DisplayGroupActionAssociation(
+ panel.infoPanel.getButtonPanel().getButton( "Commit" ) );
+ assoc.bindAspect( EOAssociation.ActionAspect, displayGroup, "updateDisplayedObjects" );
+ assoc.establishConnection();
+*/
+ // add MouseListener for table
+ panel.table.addMouseListener( new MouseAdapter()
+ {
+ public void mouseClicked(MouseEvent e)
+ {
+ if ( e.getClickCount() == 2 )
+ {
+ Object o = displayGroup.selectedObject();
+ if ( o != null )
+ {
+// new InspectorController( o );
+ new EditController(
+ new ChildDataSource(
+ displayGroup.dataSource(), o ) );
+ }
+ }
+ }
+ });
+
+ // add ActionListener for tree button
+ ((JButton) panel.buttonPanel.getButton(
+ "Tree View" ) ).addActionListener( new ActionListener()
+ {
+ public void actionPerformed( ActionEvent evt )
+ {
+ EOEditingContext parentContext = Test.editingContext;
+ EOEditingContext childContext = new EOEditingContext( parentContext );
+
+ // transpose objects to ids to faults
+ List ids = new LinkedList();
+ Iterator i = displayGroup.selectedObjects().iterator();
+ while ( i.hasNext() )
+ {
+ ids.add( parentContext.globalIDForObject( i.next() ) );
+ }
+ List objects = new LinkedList();
+ i = ids.iterator();
+ while( i.hasNext() )
+ {
+ objects.add( childContext.faultForGlobalID( (EOGlobalID) i.next(), childContext ) );
+ }
+
+ EODisplayGroup treeGroup = new EODisplayGroup();
+ treeGroup.setSortOrderings( new NSArray( "lastName" ) );
+ treeGroup.setObjectArray( objects );
+
+ EODisplayGroup childGroup = new EODisplayGroup();
+
+ //childGroup.setDelegate( new DebuggingDelegate() );
+ //new TreeInspectorController( treeGroup, childGroup );
+ //new BindingController( treeGroup, childGroup );
+
+ new TreeController( childContext, treeGroup, childGroup );
+
+ //NOTE: ChildDataSource is fundamentally broken
+// new TreeController( new ChildDataSource(
+// displayGroup.dataSource(), displayGroup.selectedObjects() ) );
+ }
+ });
+
+/*
+ NSNotificationCenter.defaultCenter().addObserver(
+ this,
+ new NSSelector( "hitMe", new Class[] { NSNotification.class } ),
+ null, null );
+*/
+ }
+
+ private String filter;
+ public String getFilter()
+ {
+ return filter;
+ }
+
+ public void setFilter( String aFilter )
+ {
+ filter = aFilter;
+
+ EOQualifier qualifier = null;
+ if ( ! "".equals( aFilter ) )
+ {
+ qualifier = new EOKeyValueQualifier(
+ "firstName", EOQualifier.QualifierOperatorContains, filter );
+ }
+ displayGroup.setQualifier( qualifier );
+ displayGroup.updateDisplayedObjects();
+ }
+
+ public void hitMe( NSNotification aNote )
+ {
+ System.out.println( aNote );
+ }
+
+ public void actionPerformed( ActionEvent evt )
+ {
+ Object o = displayGroup.selectedObject();
+ if ( o != null )
+ {
+// new ObjectInspector( o );
+ new InspectorController( o );
+ }
+ }
+
+}
diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestDataSource.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestDataSource.java
new file mode 100644
index 0000000..1d36bef
--- /dev/null
+++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestDataSource.java
@@ -0,0 +1,115 @@
+package net.wotonomy.test;
+
+import net.wotonomy.control.EOClassDescription;
+import net.wotonomy.control.EODataSource;
+import net.wotonomy.control.EOEditingContext;
+import net.wotonomy.control.EOFetchSpecification;
+import net.wotonomy.foundation.NSArray;
+
+/**
+* A custom DataSource that works with
+* the datastore package for persistence.
+*/
+public class TestDataSource extends EODataSource
+{
+ private EOEditingContext context;
+ private Object source;
+ private String key;
+
+ public TestDataSource()
+ {
+ this( Test.editingContext );
+ }
+
+ public TestDataSource( EOEditingContext aContext )
+ {
+ context = aContext;
+ }
+
+ public EOEditingContext editingContext()
+ {
+ return context;
+ }
+
+ /**
+ * This implementation does nothing.
+ */
+ public void insertObject ( Object anObject )
+ {
+ // creates are handled by createObject().
+ }
+
+ /**
+ * Deletes the specified object from this data source.
+ */
+ public void deleteObject ( Object anObject )
+ {
+ editingContext().deleteObject( anObject );
+ }
+
+ /**
+ * Returns a List containing the objects in this
+ * data source. This implementation returns all
+ * TestObjects that have been persisted to the
+ * datastore in the data directory.
+ */
+ public NSArray fetchObjects ()
+ {
+ if ( source == null )
+ {
+ NSArray result = editingContext().objectsWithFetchSpecification(
+ new EOFetchSpecification() );
+ if ( result.size() > 0 )
+ {
+ result = new NSArray( result.objectAtIndex( 0 ) );
+//result.add( result.objectAtIndex( 0 ) );
+ }
+ return result;
+ }
+ else
+ {
+ return new NSArray(
+ ((TestObject)source).getChildList() );
+ }
+ }
+
+ /**
+ * Returns a data source that is capable of
+ * manipulating objects of the type returned by
+ * applying the specified key to objects
+ * vended by this data source.
+ * @see #qualifyWithRelationshipKey
+ */
+ public EODataSource
+ dataSourceQualifiedByKey ( String aKey )
+ {
+ return new TestDataSource( editingContext() );
+ }
+
+ /**
+ * Restricts this data source to vend those
+ * objects that are associated with the specified
+ * key on the specified object.
+ */
+ public void
+ qualifyWithRelationshipKey (
+ String aKey, Object anObject )
+ {
+ key = aKey;
+ source = anObject;
+ }
+
+ /**
+ * Returns the description of the class of the
+ * objects that is vended by this data source,
+ * or null if this cannot be determined.
+ * This implementation returns TestObject.
+ */
+ public EOClassDescription
+ classDescriptionForObjects ()
+ {
+ return EOClassDescription.classDescriptionForClass(
+ TestObject.class );
+ }
+
+}
diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestMap.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestMap.java
new file mode 100644
index 0000000..8a88e68
--- /dev/null
+++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestMap.java
@@ -0,0 +1,164 @@
+package net.wotonomy.test;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+import java.util.StringTokenizer;
+
+import net.wotonomy.datastore.DataSoup;
+import net.wotonomy.datastore.SerializedFileSoup;
+import net.wotonomy.datastore.XMLFileSoup;
+import net.wotonomy.foundation.internal.ValueConverter;
+
+public class TestMap extends HashMap
+{
+ public TestMap()
+ {
+ put( "date", new Date() );
+ put( "firstName", randomParse(
+ "Bert|Ernie|Elmo|Zoe|Arthur|Emily|DJ|Grover|Oscar|Max|Big|Twinkle") );
+ put( "middleName", new StringBuffer( randomParse(
+ "Rufus|Remy|Martin|Josephus|Ulysses|Homer|Bart|Tip|Onegin|Meredith|Jay") ) );
+ put( "lastName", randomParse(
+ "Alejandro|Alexander|Bird|Gosling|Joy|Van Hoff|Pedia|Marr|McNealy|Ping") );
+ put( "address", randomParse( "1|2|3|4" ) + randomParse( "0|1|00|10|5|50" ) +
+ randomParse( "0|00|1|01|5|05|9|09||000" ) + " " + randomParse(
+ "Merry|Berry|Perry|Jerry|Meadow|Falls|Elm|Raspberry|Strawberry") + " "
+ + randomParse( "Road|Lane|Court|Drive|Parkway|Terrace" ) );
+ put( "city", randomParse(
+ "Springfield|Sterling|Cascades|Vienna|Reston|Paris|London|Runnymeade") );
+ put( "state", randomParse(
+ "TX|NJ|NY|VA|DC|MD|NC|SC|WV|AR|FL|CA|TN" ) );
+ put( "zip", ValueConverter.getInteger(
+ randomParse( "1|2|3|4" ) + "0" + randomParse( "0|1|2|3|5" ) +
+ randomParse( "6|7|8|9" ) + randomParse( "6|7|8|9" ) ) );
+ put( "age", new Short( (short) ( new Random().nextDouble() * 40 + 18 ) ) );
+ childCount = -1;
+ }
+
+ protected int childCount;
+ public int getChildCount()
+ {
+ if ( childCount == -1 )
+ {
+ //childCount = (int) ( random.nextDouble() * 6 ) - 3; // + 100; // tree scalability test
+ if ( childCount < 0 ) childCount = 0;
+ }
+ return childCount;
+ };
+
+ protected TestMap[] children;
+ public TestMap[] getChildren()
+ {
+ if ( get( "children" ) == null )
+ {
+ int n = getChildCount();
+ TestMap[] children = new TestMap[ n ];
+ for ( int i = 0; i < n; i++ )
+ {
+ children[i] = new TestMap();
+ }
+ put( "children", children );
+ }
+ return (TestMap[]) get( "children" );
+ }
+ public void setChildren( TestMap[] aChildArray )
+ {
+ put( "children", aChildArray );
+ }
+ public List getChildList()
+ {
+ List result = new LinkedList();
+ TestMap[] childArray = getChildren();
+ for ( int i = 0; i < childArray.length; i++ )
+ {
+ result.add( childArray[i] );
+ }
+ return result;
+ }
+ public void setChildList( List aChildList )
+ {
+ TestMap[] children = new TestMap[ aChildList.size() ];
+ for ( int i = 0; i < children.length; i++ )
+ {
+ children[i] = (TestMap) aChildList.get( i );
+ }
+ setChildren( children );
+ }
+
+ public String getFullName()
+ {
+ return get( "firstName" ) + " " + get( "middleName" ) + " " + get( "lastName" );
+ }
+
+ public boolean equals( Object anObject )
+ {
+ return anObject == this;
+ }
+
+ public String toString()
+ {
+ return "[" + getClass().getName() + ":" + getFullName() + "]";
+ }
+
+ // statics
+
+ private static Random random = new Random();
+ private static String randomParse( String aString )
+ {
+ String result = "";
+ StringTokenizer tokens = new StringTokenizer( aString, "|" );
+ int n = (int) ( random.nextDouble() * tokens.countTokens() );
+ for ( int i = 0; i <= n; i++ )
+ {
+ result = tokens.nextToken();
+ }
+ return result;
+ }
+
+ public static void main( String[] argv )
+ {
+ int count = 100;
+ boolean xmlMode = false;
+ if ( argv.length > 0 )
+ {
+ Integer parsed = ValueConverter.getInteger( argv[0] );
+ if ( parsed != null ) count = parsed.intValue();
+
+ if ( argv.length > 1 )
+ {
+ if ( argv[1].indexOf( "xml" ) > -1 )
+ {
+ xmlMode = true;
+ }
+ }
+ }
+
+long millis = System.currentTimeMillis();
+
+ DataSoup store = null;
+ if ( xmlMode )
+ {
+ store = new XMLFileSoup( "testMaps-xml" );
+ }
+ else
+ {
+ store = new SerializedFileSoup( "testMaps-java" );
+ }
+
+ Object o;
+ for ( int i = 0; i < count; i++ )
+ {
+ store.addObject( new TestMap() );
+ }
+ /*
+ store.addIndex( "age", "age" );
+ store.addIndex( "zipCode", "zipCode" );
+ store.addIndex( "firstName", "firstName" );
+ store.addIndex( "lastName", "lastName" );
+*/
+System.out.println( System.currentTimeMillis() - millis + " milliseconds" );
+ }
+}
diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObject.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObject.java
new file mode 100644
index 0000000..72a3dbc
--- /dev/null
+++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObject.java
@@ -0,0 +1,362 @@
+package net.wotonomy.test;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+import java.util.StringTokenizer;
+
+import net.wotonomy.control.EOEditingContext;
+import net.wotonomy.control.EOKeyValueCodingSupport;
+import net.wotonomy.datastore.DataSoup;
+import net.wotonomy.datastore.SerializedFileSoup;
+import net.wotonomy.datastore.XMLFileSoup;
+import net.wotonomy.foundation.internal.ValueConverter;
+
+public class TestObject implements Serializable // , EOKeyValueCoding
+{
+ static final long serialVersionUID = -5482454640042392838L;
+
+// for testing manual array faulting
+public EOEditingContext editingContext;
+public EOEditingContext getEditingContext() { return editingContext; };
+
+ public TestObject()
+ {
+ date = new Date();
+ firstName = randomParse(
+ "Bert|Ernie|Elmo|Zoe|Arthur|Emily|DJ|Grover|Oscar|Max|Big|Twinkle");
+ middleName = new StringBuffer( randomParse(
+ "Rufus|Remy|Martin|Josephus|Ulysses|Homer|Bart|Tip|Onegin|Meredith|Jay") );
+ lastName = randomParse(
+ "Alejandro|Alexander|Bird|Gosling|Joy|Van Hoff|Pedia|Marr|McNealy|Ping");
+ address = randomParse( "1|2|3|4" ) + randomParse( "0|1|00|10|5|50" ) +
+ randomParse( "0|00|1|01|5|05|9|09||000" ) + " " + randomParse(
+ "Merry|Berry|Perry|Jerry|Meadow|Falls|Elm|Raspberry|Strawberry") + " "
+ + randomParse( "Road|Lane|Court|Drive|Parkway|Terrace" );
+ city = randomParse(
+ "Springfield|Sterling|Cascades|Vienna|Reston|Paris|London|Runnymeade");
+ state = randomParse(
+ "TX|NJ|NY|VA|DC|MD|NC|SC|WV|AR|FL|CA|TN" );
+ zip = ValueConverter.getIntValue(
+ randomParse( "1|2|3|4" ) + "0" + randomParse( "0|1|2|3|5" ) +
+ randomParse( "6|7|8|9" ) + randomParse( "6|7|8|9" ) );
+ age = (short) ( new Random().nextDouble() * 40 + 18 );
+ childCount = -1;
+// children = null;
+ childList = null;
+ }
+
+ protected Date date;
+ public Date getCreateDate() { return date; }
+ public void setCreateDate( Date aDate ) { date = aDate; }
+
+ protected String firstName;
+ public String getFirstName() { return firstName; }
+ public void setFirstName( String aName ) { firstName = aName; }
+
+ protected String lastName;
+ public String getLastName() { return lastName; }
+ public void setLastName( String aName ) {
+ if ( "Jones".equals( aName ) ) throw new RuntimeException( "Jones not allowed" ) ;
+ lastName = aName;
+ }
+
+ protected StringBuffer middleName;
+ public StringBuffer getMiddleName() { return middleName; }
+ public void setMiddleName( StringBuffer aName ) { middleName = aName; }
+
+ protected String address;
+ public String getAddress() { return address; }
+ public void setAddress( String anAddress ) { address = anAddress; }
+
+ protected String city;
+ public String getCity() { return city; }
+ public void setCity( String aCity ) { city = aCity; }
+
+ protected String state;
+ public String getState() { return state; }
+ public void setState( String aState ) { state = aState; }
+
+ protected int zip;
+ public int getZipCode() { return zip; }
+ public void setZipCode( int aZipCode ) { zip = aZipCode; }
+
+ protected short age;
+ public short getAge() { return age; }
+ public void setAge( short anAge ) { age = anAge; }
+
+ protected boolean special;
+ public Boolean isSpecial() { return new Boolean( special ); }
+ public void setSpecial( Boolean isSpecial ) { special = isSpecial.booleanValue(); }
+
+/*
+ protected Object[] children;
+
+ private Object[] getChildren()
+ {
+ if ( children == null )
+ {
+ int n = getChildCount();
+ children = new Object[ n ];
+ for ( int i = 0; i < n; i++ )
+ {
+ children[i] = new TestObject();
+ }
+ //System.out.println( "TestObject.getChildren: " + toString() + " : " + getChildCount() );
+ }
+ return children;
+ }
+ private void setChildren( Object[] aChildArray )
+ {
+ children = aChildArray;
+ childCount = aChildArray.length;
+ }
+
+ // following child list implementation wraps child array
+
+ public List getChildList()
+ {
+ List result = new LinkedList();
+ Object[] childArray = getChildren();
+ for ( int i = 0; i < childArray.length; i++ )
+ {
+ result.add( childArray[i] );
+ }
+ return result;
+ }
+ public void setChildList( List aChildList )
+ {
+ children = new Object[ aChildList.size() ];
+ for ( int i = 0; i < children.length; i++ )
+ {
+ children[i] = (TestObject) aChildList.get( i );
+ }
+ childCount = children.length;
+ }
+*/
+ protected int childCount;
+ public int getChildCount()
+ {
+ if ( childCount == -1 )
+ {
+// uncomment this to enable random children
+// childCount = (int) ( random.nextDouble() * 6 ) - 3; // + 100; // tree scalability test
+ if ( childCount < 0 ) childCount = 0;
+ }
+
+// this tests internal count
+// return childCount;
+// this tests deferred count
+ if ( childList != null )
+ {
+ return childList.size();
+ }
+ else
+ {
+ return 0;
+ }
+ };
+
+ // following child list implementation stands alone
+
+ protected List childList;
+ public List getChildList()
+ {
+/*
+ // this tests random child population
+ if ( childList == null )
+ {
+ int n = getChildCount();
+ childList = new LinkedList();
+ for ( int i = 0; i < n; i++ )
+ {
+ childList.add( new TestObject() );
+ }
+ }
+*/
+ // this tests manual loading
+ if ( childList == null )
+ {
+ childList = new LinkedList();
+ }
+ return childList;
+ }
+ public void setChildList( List aChildList )
+ {
+ childList = aChildList;
+ }
+
+ protected TestObject parent;
+ public TestObject getParent() { return parent; }
+ public void setParent( TestObject anObject ) { parent = anObject; }
+
+ public String getHash() { return Integer.toHexString( System.identityHashCode( this ) ); }
+
+ public String getFullName()
+ {
+// return getHash() + ": " + firstName + " " + middleName + " " + lastName;
+ return firstName + " " + middleName + " " + lastName;
+ }
+
+ public boolean equals( Object anObject )
+ {
+ return anObject == this;
+ }
+
+ public String toString()
+ {
+ return "[" + getClass().getName()
+ + "@" + Integer.toHexString( System.identityHashCode(this) )
+ + ":" + getFullName() + "]";
+ }
+
+ // statics
+
+ private static Random random = new Random();
+ private static String randomParse( String aString )
+ {
+ String result = "";
+ StringTokenizer tokens = new StringTokenizer( aString, "|" );
+ int n = (int) ( random.nextDouble() * tokens.countTokens() );
+ for ( int i = 0; i <= n; i++ )
+ {
+ result = tokens.nextToken();
+ }
+ return result;
+ }
+
+
+ // interface EOKeyValueCoding:
+ // disable this interface by commenting out the "implements" declaration
+
+ /**
+ * Returns the value for the specified property.
+ * If the property does not exist, this method should
+ * call handleQueryWithUnboundKey.
+ */
+ public Object valueForKey( String aKey )
+ {
+ System.out.println( "valueForKey: " + aKey );
+ return EOKeyValueCodingSupport.valueForKey( this, aKey );
+ }
+
+ /**
+ * Sets the property to the specified value.
+ * If the property does not exist, this method should
+ * call handleTakeValueForUnboundKey.
+ * If the property is of a type that cannot allow
+ * null (e.g. primitive types) and aValue is null,
+ * this method should call unableToSetNullForKey.
+ */
+ public void takeValueForKey( Object aValue, String aKey )
+ {
+ System.out.println( "takeValueForKey: " + aValue + " : " + aKey );
+ EOKeyValueCodingSupport.takeValueForKey( this, aValue, aKey );
+ }
+
+ /**
+ * Returns the value for the private field that
+ * corresponds to the specified property.
+ */
+ public Object storedValueForKey( String aKey )
+ {
+ System.out.println( "storedValueForKey: " + aKey );
+ return EOKeyValueCodingSupport.storedValueForKey( this, aKey );
+ }
+
+ /**
+ * Sets the the private field that corresponds to the
+ * specified property to the specified value.
+ */
+ public void takeStoredValueForKey( Object aValue, String aKey )
+ {
+ System.out.println( "takeStoredValueForKey: " + aValue + " : " + aKey );
+ EOKeyValueCodingSupport.takeStoredValueForKey( this, aValue, aKey );
+ }
+
+ /**
+ * Called by valueForKey when the specified key is
+ * not found on this object. Implementing classes
+ * should handle the specified value or otherwise
+ * throw an exception.
+ */
+ public Object handleQueryWithUnboundKey( String aKey )
+ {
+ System.out.println( "handleQueryWithUnboundKey: " + aKey );
+ return EOKeyValueCodingSupport.handleQueryWithUnboundKey( this, aKey );
+ }
+
+ /**
+ * Called by takeValueForKey when the specified key
+ * is not found on this object. Implementing classes
+ * should handle the specified value or otherwise
+ * throw an exception.
+ */
+ public void handleTakeValueForUnboundKey( Object aValue, String aKey )
+ {
+ System.out.println( "handleTakeValueForUnboundKey: " + aValue + " : " + aKey );
+ EOKeyValueCodingSupport.handleTakeValueForUnboundKey( this, aValue, aKey );
+ }
+
+ /**
+ * Called by takeValueForKey when the type of the
+ * specified key is not allowed to be null, as is
+ * the case with primitive types. Implementing
+ * classes should handle this case appropriately
+ * or otherwise throw an exception.
+ */
+ public void unableToSetNullForKey( String aKey )
+ {
+ System.out.println( "unableToSetNullForKey: " + aKey );
+ EOKeyValueCodingSupport.unableToSetNullForKey( this, aKey );
+ }
+
+
+ // main entry point
+
+ public static void main( String[] argv )
+ {
+ int count = 100;
+ boolean xmlMode = false;
+ if ( argv.length > 0 )
+ {
+ Integer parsed = ValueConverter.getInteger( argv[0] );
+ if ( parsed != null ) count = parsed.intValue();
+
+ if ( argv.length > 1 )
+ {
+ if ( argv[1].indexOf( "xml" ) > -1 )
+ {
+ xmlMode = true;
+ }
+ }
+ }
+
+long millis = System.currentTimeMillis();
+
+ DataSoup store = null;
+ if ( xmlMode )
+ {
+ store = new XMLFileSoup( "testObjects-xml" );
+ }
+ else
+ {
+ store = new SerializedFileSoup( "testObjects-java" );
+ }
+
+ Object o;
+ for ( int i = 0; i < count; i++ )
+ {
+ store.addObject( new TestObject() );
+ }
+ /*
+ store.addIndex( "age", "age" );
+ store.addIndex( "zipCode", "zipCode" );
+ store.addIndex( "firstName", "firstName" );
+ store.addIndex( "lastName", "lastName" );
+*/
+System.out.println( System.currentTimeMillis() - millis + " milliseconds" );
+ }
+}
diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObjectClassDesc.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObjectClassDesc.java
new file mode 100644
index 0000000..beb852c
--- /dev/null
+++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObjectClassDesc.java
@@ -0,0 +1,31 @@
+package net.wotonomy.test;
+
+import net.wotonomy.control.EOClassDescription;
+import net.wotonomy.foundation.NSArray;
+
+/**
+* A simple class description for testing.
+*/
+public class TestObjectClassDesc extends EOClassDescription
+{
+ public TestObjectClassDesc()
+ {
+ super( TestObject.class );
+ }
+
+ public EOClassDescription classDescriptionForDestinationKey(
+ String detailKey )
+ {
+ if ( "childList".equals( detailKey ) )
+ {
+ return this;
+ }
+ return null;
+ }
+
+ public NSArray toManyRelationshipKeys()
+ {
+ return new NSArray( "childList" );
+ }
+
+}
diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObjectStore.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObjectStore.java
new file mode 100644
index 0000000..8fcb8a0
--- /dev/null
+++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObjectStore.java
@@ -0,0 +1,258 @@
+package net.wotonomy.test;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import net.wotonomy.control.EOEditingContext;
+import net.wotonomy.control.EOFetchSpecification;
+import net.wotonomy.control.EOGlobalID;
+import net.wotonomy.control.EOObjectStore;
+import net.wotonomy.control.EOObserverCenter;
+import net.wotonomy.test.DataKeyID;
+import net.wotonomy.datastore.DataSoup;
+import net.wotonomy.datastore.DataView;
+import net.wotonomy.datastore.XMLFileSoup;
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSMutableArray;
+import net.wotonomy.foundation.internal.Duplicator;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+/**
+* An object store that wraps a datastore
+* for vending test objects.
+*/
+public class TestObjectStore extends EOObjectStore
+{
+ DataSoup soup;
+
+ /**
+ * Constructor specifies path to datastore.
+ */
+ public TestObjectStore( String aPath )
+ {
+ soup = new XMLFileSoup( aPath );
+ }
+
+ /**
+ * This implementation simply returns
+ * objectsWithSourceGlobalID.
+ */
+ public NSArray arrayFaultWithSourceGlobalID (
+ EOGlobalID aGlobalID,
+ String aRelationship,
+ EOEditingContext aContext )
+ {
+ return objectsForSourceGlobalID(
+ aGlobalID, aRelationship, aContext );
+ }
+
+ /**
+ * This implementation returns the actual
+ * object for the specified id.
+ */
+ public Object faultForGlobalID (
+ EOGlobalID aGlobalID,
+ EOEditingContext aContext )
+ {
+System.out.println( "TestObjectStore: * reading object * : " + aGlobalID );
+ return soup.getObjectByKey(
+ ((DataKeyID)aGlobalID).getKey() );
+ }
+
+ /**
+ * Returns a fault representing an object of
+ * the specified entity type with values from
+ * the specified dictionary. The fault should
+ * belong to the specified editing context.
+ */
+ public Object faultForRawRow (
+ Map aDictionary,
+ String anEntityName,
+ EOEditingContext aContext )
+ {
+ //TODO: faults are not yet supported
+ throw new WotonomyException(
+ "Faults are not yet supported." );
+ }
+
+ /**
+ * Given a newly instantiated object, this method
+ * initializes its properties to values appropriate
+ * for the specified id. The object should belong
+ * to the specified editing context.
+ * This method is called to populate faults.
+ */
+ public void initializeObject (
+ Object anObject,
+ EOGlobalID aGlobalID,
+ EOEditingContext aContext )
+ {
+System.out.println( "TestObjectStore: * reading object * : " + aGlobalID );
+ Object original = soup.getObjectByKey(
+ ((DataKeyID)aGlobalID).getKey() );
+ EOObserverCenter.notifyObserversObjectWillChange( anObject );
+ Duplicator.deepCopy( original, anObject );
+ //TODO: need to handle child object registration in aContext
+ }
+
+ /**
+ * Remove all values from all objects in memory,
+ * turning them into faults, and posts a notification
+ * that all objects have been invalidated.
+ */
+ public void invalidateAllObjects ()
+ {
+ // does nothing
+ }
+
+ /**
+ * Removes values with the specified ids from memory,
+ * turning them into faults, and posts a notification
+ * that those objects have been invalidated.
+ */
+ public void invalidateObjectsWithGlobalIDs (
+ List aList )
+ {
+ // does nothing
+ }
+
+ /**
+ * Returns false because locking is not permitted.
+ */
+ public boolean isObjectLockedWithGlobalID (
+ EOGlobalID aGlobalID,
+ EOEditingContext aContext )
+ {
+ return false;
+ }
+
+ /**
+ * Does nothing because locking is not permitted.
+ */
+ public void lockObjectWithGlobalID (
+ EOGlobalID aGlobalID,
+ EOEditingContext aContext )
+ {
+ // does nothing
+ }
+
+ /**
+ * Returns a List of objects associated with the object
+ * with the specified id for the specified property
+ * relationship. Faults are not allowed in the array.
+ * All objects should belong to the specified editing context.
+ */
+ public NSArray objectsForSourceGlobalID (
+ EOGlobalID aGlobalID,
+ String aRelationship,
+ EOEditingContext aContext )
+ {
+ //TODO: relationships are not yet supported
+ throw new WotonomyException(
+ "Relationships are not yet supported." );
+ }
+
+ /**
+ * Returns a List of objects the meet the criteria of
+ * the supplied specification. Faults are not allowed in the array.
+ * Each object is registered with the specified editing context.
+ * If any object is already registered in the specified context,
+ * it is not refetched and that object should be used in the array.
+ */
+ public NSArray objectsWithFetchSpecification (
+ EOFetchSpecification aFetchSpec,
+ EOEditingContext aContext )
+ {
+ //TODO: fetch specs are not yet supported
+
+ DataView view = soup.queryObjects( null, null );
+System.out.println( "TestObjectStore: ** querying all objects **" );
+ NSMutableArray result = new NSMutableArray();
+ Object o;
+ Object existing;
+ DataKeyID id;
+ Iterator it = view.iterator();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ id = new DataKeyID( view.getKeyForObject( o ) );
+ existing = aContext.objectForGlobalID( id );
+ if ( existing != null )
+ {
+ o = existing;
+ }
+ else
+ {
+ aContext.recordObject( o, id );
+ }
+ result.addObject( o );
+ }
+ return result;
+ }
+
+ /**
+ * Removes all values from the specified object,
+ * converting it into a fault for the specified id.
+ * New or deleted objects should not be refaulted.
+ */
+ public void refaultObject (
+ Object anObject,
+ EOGlobalID aGlobalID,
+ EOEditingContext aContext )
+ {
+ //TODO: faults are not yet supported
+ // just re-initialize the object
+ initializeObject( anObject, aGlobalID, aContext );
+ }
+
+ /**
+ * Writes all changes in the specified editing context
+ * to the respository.
+ */
+ public void saveChangesInEditingContext (
+ EOEditingContext aContext )
+ {
+ Object o;
+ DataKeyID id;
+ Iterator it;
+
+ System.out.println( aContext.updatedObjects() );
+
+ // process updates
+ it = aContext.updatedObjects().iterator();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ id = (DataKeyID) aContext.globalIDForObject( o );
+System.out.println( "TestObjectStore: * updating object * : " + id );
+ soup.updateObject( id.getKey(), o );
+ }
+
+ // process deletes
+ it = aContext.deletedObjects().iterator();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ id = (DataKeyID) aContext.globalIDForObject( o );
+System.out.println( "TestObjectStore: * deleting object * : " + id );
+ soup.removeObject( id.getKey() );
+ // remove object from editing context
+ aContext.forgetObject( o );
+ }
+
+ // process inserts
+ it = aContext.insertedObjects().iterator();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ id = new DataKeyID( soup.addObject( o ) );
+System.out.println( "TestObjectStore: * adding object * : " + id );
+ // register object in editing context with new id
+ aContext.forgetObject( o );
+ aContext.recordObject( o, id );
+ }
+
+ }
+}
+
diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestPanel.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestPanel.java
new file mode 100644
index 0000000..7e868d0
--- /dev/null
+++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestPanel.java
@@ -0,0 +1,110 @@
+package net.wotonomy.test;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Insets;
+import java.util.Vector;
+
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSlider;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.border.EmptyBorder;
+import javax.swing.text.JTextComponent;
+
+import net.wotonomy.ui.swing.components.BetterFlowLayout;
+import net.wotonomy.ui.swing.components.ButtonPanel;
+import net.wotonomy.ui.swing.components.InfoPanel;
+
+/**
+* A master-detail panel with a list, some
+* textfields and some buttons.
+*/
+public class TestPanel extends JPanel
+{
+// public JList list;
+ public JTable table;
+ public InfoPanel infoPanel;
+ public ButtonPanel savePanel;
+ public ButtonPanel buttonPanel;
+ public JTextComponent firstNameField;
+ public JTextComponent middleNameField, lastNameField;
+ public JComboBox dateBox, monthBox, yearBox;
+ public JSlider slider;
+ public JCheckBox checkbox;
+
+ public TestPanel()
+ {
+ this.setLayout( new BorderLayout( 10, 10 ) );
+ this.setBorder( new EmptyBorder( 10, 10, 10, 10 ) );
+
+ JPanel overviewPanel = new JPanel();
+ overviewPanel.setLayout( new BorderLayout() );
+
+ //list = new JList();
+ //JScrollPane scrollPane = new JScrollPane( list );
+
+ table = new JTable();
+ JScrollPane scrollPane = new JScrollPane( table );
+
+ overviewPanel.add( scrollPane, BorderLayout.CENTER );
+
+ this.add( overviewPanel, BorderLayout.CENTER );
+
+ infoPanel = new InfoPanel();
+ infoPanel.setColumns( 1 );
+
+ // name fields
+ firstNameField = new JTextField();
+// infoPanel.addPair( "First Name", firstNameField );
+ middleNameField = new JTextField();
+// infoPanel.addPair( "Middle Name", middleNameField );
+ lastNameField = new JTextField();
+// infoPanel.addPair( "Last Name", lastNameField );
+ checkbox = new JCheckBox();
+
+ infoPanel.addRow( "Name", new Component[] {
+ firstNameField, middleNameField, lastNameField, checkbox } );
+
+ // date comboboxen
+ Vector datesList = new Vector();
+ for ( int i = 1; i < 32; i++ ) datesList.add( new Integer( i ) );
+ dateBox = new JComboBox( datesList );
+ dateBox.setEditable( true );
+ monthBox = new JComboBox();
+ yearBox = new JComboBox();
+ infoPanel.addRow( "Create Date",
+ dateBox, monthBox, yearBox );
+
+ // year slider
+ infoPanel.addRow( "Day of Month", slider = new JSlider(
+ JSlider.HORIZONTAL, 1, 31, 1 ) );
+
+ // navigation buttons
+
+ JPanel navigationPanel = new JPanel();
+ navigationPanel.setLayout( new BorderLayout() );
+
+ buttonPanel = new ButtonPanel( new String[] { "Tree View", "Add", "Remove" } );
+ buttonPanel.setAlignment( BetterFlowLayout.LEFT );
+ buttonPanel.setInsets( new Insets( 0, 0, 0, 0 ) );
+ navigationPanel.add( buttonPanel, BorderLayout.WEST );
+
+ savePanel = new ButtonPanel( new String[] { "Refresh All", "Commit" } );
+ savePanel.setAlignment( BetterFlowLayout.RIGHT );
+ navigationPanel.add( savePanel, BorderLayout.EAST );
+
+ // bottom panel layout
+
+ JPanel bottomPanel = new JPanel();
+ bottomPanel.setLayout( new BorderLayout() );
+ bottomPanel.add( infoPanel, BorderLayout.NORTH );
+ bottomPanel.add( navigationPanel, BorderLayout.SOUTH );
+
+ this.add( bottomPanel, BorderLayout.SOUTH );
+ }
+
+}
diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreeController.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreeController.java
new file mode 100644
index 0000000..d61d8ff
--- /dev/null
+++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreeController.java
@@ -0,0 +1,285 @@
+package net.wotonomy.test;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.AbstractButton;
+import javax.swing.BorderFactory;
+import javax.swing.JDialog;
+
+import net.wotonomy.control.EODataSource;
+import net.wotonomy.control.EOEditingContext;
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.ui.EOAssociation;
+import net.wotonomy.ui.EODisplayGroup;
+import net.wotonomy.ui.MasterDetailAssociation;
+import net.wotonomy.ui.swing.ListAssociation;
+import net.wotonomy.ui.swing.RadioPanelAssociation;
+import net.wotonomy.ui.swing.TextAssociation;
+import net.wotonomy.ui.swing.TreeAssociation;
+import net.wotonomy.ui.swing.components.ButtonPanel;
+import net.wotonomy.ui.swing.util.ObjectInspector;
+import net.wotonomy.ui.swing.util.WindowUtilities;
+
+/**
+* A simple editor panel with a few textfields.
+*/
+public class TreeController implements ActionListener
+{
+ final EODisplayGroup group;
+ final EODisplayGroup titlesGroup;
+ final EODisplayGroup detailGroup;
+ final EOEditingContext editingContext;
+
+ public TreeController( EODataSource aDataSource )
+ {
+ titlesGroup = new EODisplayGroup();
+ group = new EODisplayGroup();
+ detailGroup = new EODisplayGroup();
+ titlesGroup.setDataSource( aDataSource );
+ editingContext = aDataSource.editingContext();
+ init();
+ }
+
+ public TreeController( EOEditingContext aContext,
+ EODisplayGroup aTitlesGroup, EODisplayGroup aChildGroup )
+ {
+ titlesGroup = aTitlesGroup;
+ group = aChildGroup;
+ detailGroup = new EODisplayGroup();
+ editingContext = aContext;
+ init();
+ }
+
+ public void init()
+ {
+ final TreePanel treePanel = new TreePanel();
+ treePanel.panel.setBorder(
+ BorderFactory.createCompoundBorder(
+ BorderFactory.createRaisedBevelBorder(),
+ BorderFactory.createEmptyBorder( 10, 10, 10, 10 ) ) );
+ treePanel.setBorder(
+ BorderFactory.createEmptyBorder( 0, 0, 0, 0 ) );
+ ButtonPanel okPanel = new ButtonPanel(
+ new String[] { "Refresh", "Commit" } );
+ treePanel.add( okPanel, BorderLayout.SOUTH );
+/*
+
+ // set up renderer
+ IconCellRenderer iconRenderer = new IconCellRenderer()
+ {
+ private Icon icon = UIManager.getIcon("FileChooser.homeFolderIcon");
+ public Icon getIconForContext(
+ JComponent container, Object value,
+ int row, int col,
+ boolean isSelected, boolean hasFocus,
+ boolean isExpanded, boolean isLeaf )
+ {
+ return icon;
+ }
+ };
+ treePanel.tree.setCellRenderer( iconRenderer );
+
+ // enable icon clicking
+ treePanel.tree.setCellEditor( iconRenderer );
+ iconRenderer.addActionListener( this );
+*/
+
+ treePanel.tree.setEditable( true );
+
+ // set up display groups
+
+ titlesGroup.fetch();
+
+ // text associations
+ EOAssociation ta;
+
+ ta = new TreeAssociation( treePanel.tree, "People" );
+ ta.bindAspect( EOAssociation.TitlesAspect, titlesGroup, "fullName" );
+ ta.bindAspect( EOAssociation.ChildrenAspect, group, "childList" );
+ ta.bindAspect( EOAssociation.IsLeafAspect, titlesGroup, "childCount" );
+// ta.bindAspect( EOAssociation.IsLeafAspect, null, "false" );
+ ta.establishConnection();
+ treePanel.tree.setEditable( true );
+
+ ta = new TextAssociation( treePanel.editPanel.firstNameField );
+ ta.bindAspect( EOAssociation.ValueAspect, group, "firstName" );
+ ta.establishConnection();
+
+ ta = new TextAssociation( treePanel.editPanel.middleNameField );
+ ta.bindAspect( EOAssociation.ValueAspect, group, "middleName" );
+ ta.establishConnection();
+
+ ta = new TextAssociation( treePanel.editPanel.lastNameField );
+ ta.bindAspect( EOAssociation.ValueAspect, group, "lastName" );
+ ta.establishConnection();
+
+ ta = new RadioPanelAssociation( treePanel.editPanel.yearRadioPanel );
+ ta.bindAspect( EOAssociation.ValueAspect, group, "createDate.year" );
+
+ EODisplayGroup yearTitles = new EODisplayGroup();
+ yearTitles.setObjectArray( new NSArray(
+ new Object[] { "1999", "2000", "2001" } ) );
+ ta.bindAspect( EOAssociation.TitlesAspect, yearTitles, "" );
+
+ EODisplayGroup yearObjects = new EODisplayGroup();
+ yearObjects.setObjectArray( new NSArray(
+ new Object[] { new Integer( 99 ), new Integer( 100 ), new Integer( 101 ) } ) );
+ ta.bindAspect( EOAssociation.ObjectsAspect, yearObjects, "" );
+
+ ta.establishConnection();
+
+ // detail group
+
+ ta = new MasterDetailAssociation( detailGroup );
+ ta.bindAspect( EOAssociation.ParentAspect, group, "childList" );
+ ta.establishConnection();
+
+ ta = new ListAssociation( treePanel.editPanel.list );
+ ta.bindAspect( EOAssociation.TitlesAspect, detailGroup, "fullName" );
+ ta.establishConnection();
+
+ // display group action associations
+
+ AbstractButton addButton = (AbstractButton)
+ treePanel.editPanel.addPanel.getButton( "Add" );
+ addButton.addActionListener( new ActionListener()
+ {
+ public void actionPerformed( ActionEvent evt )
+ {
+ Object testObject = new TestObject();
+ editingContext.insertObject( testObject );
+ detailGroup.insertObjectAtIndex( testObject, 0 );
+ }
+ } );
+
+ AbstractButton removeButton = (AbstractButton)
+ treePanel.editPanel.addPanel.getButton( "Remove" );
+ removeButton.addActionListener( new ActionListener()
+ {
+ public void actionPerformed( ActionEvent evt )
+ {
+ if ( detailGroup.selectedObject() != null )
+ detailGroup.deleteSelection();
+ }
+ } );
+
+ // ok / cancel buttons
+
+ AbstractButton button;
+
+ button = (AbstractButton)
+ okPanel.getButton( "Commit" );
+ button.addActionListener( new ActionListener()
+ {
+ public void actionPerformed( ActionEvent evt )
+ {
+ editingContext.saveChanges();
+ }
+ } );
+
+ button = (AbstractButton)
+ okPanel.getButton( "Refresh" );
+ button.addActionListener( new ActionListener()
+ {
+ public void actionPerformed( ActionEvent evt )
+ {
+ editingContext.revert();
+// editingContext.invalidateAllObjects();
+ }
+ } );
+
+
+/*
+ AbstractButton refreshButton = (AbstractButton)
+ treePanel.editPanel.addPanel.getButton( "Refresh" );
+ refreshButton.addActionListener( new ActionListener()
+ {
+ int count = 0;
+ public void actionPerformed( ActionEvent evt )
+ {
+ EODisplayGroup displayGroup = (EODisplayGroup)
+ treePanel.tree.getSelectionPath().getLastPathComponent();
+// displayGroup.insertObjectAtIndex( new TestObject(), 0 );
+// displayGroup.deleteObjectAtIndex( 0 );
+ List sortOrderings = new LinkedList();
+ if ( count++ % 2 == 0 )
+ {
+ sortOrderings.add( new EOSortOrdering(
+ "lastName", EOSortOrdering.CompareAscending ) );
+ }
+ else
+ {
+ sortOrderings.add( new EOSortOrdering(
+ "lastName", EOSortOrdering.CompareDescending ) );
+ }
+ displayGroup.setSortOrderings( sortOrderings );
+ displayGroup.updateDisplayedObjects();
+ }
+ } );
+*/
+/*
+ ta = new DisplayGroupActionAssociation(
+ treePanel.editPanel.addPanel.getButton( "Remove" ) );
+ ta.bindAspect( EOAssociation.ActionAspect, detailGroup, "deleteSelection" );
+ ta.establishConnection();
+
+ ta = new DisplayGroupActionAssociation(
+ treePanel.editPanel.addPanel.getButton( "Refresh" ) );
+ ta.bindAspect( EOAssociation.ActionAspect, group, "updateDisplayedObjects" );
+ ta.establishConnection();
+*/
+
+ // add mouse listener for list
+
+ treePanel.editPanel.list.addMouseListener( new MouseAdapter()
+ {
+ public void mouseClicked(MouseEvent e)
+ {
+ if ( e.getClickCount() == 2 )
+ {
+ Object item = detailGroup.selectedObject();
+ if ( item != null )
+ {
+ new InspectorController( item );
+ }
+ }
+ }
+ });
+
+ // launch
+
+ JDialog d = new JDialog();
+ d.getContentPane().add( treePanel );
+ d.setTitle( "Tree Panel" );
+ d.pack();
+ WindowUtilities.cascade( d );
+ d.show();
+
+ // workaround for memory issues on jdk1.2.2
+ d.addWindowListener( new WindowAdapter()
+ {
+ // exit on close
+ public void windowClosing(WindowEvent e)
+ {
+ ((JDialog)e.getWindow()).getContentPane().removeAll();
+ }
+ });
+ }
+
+ public void actionPerformed( ActionEvent evt )
+ {
+ Object item = group.selectedObject();
+ if ( item != null )
+ {
+// new InspectorController( item );
+ new ObjectInspector( item );
+ }
+ }
+
+}
diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreeInspectorController.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreeInspectorController.java
new file mode 100644
index 0000000..8ec9554
--- /dev/null
+++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreeInspectorController.java
@@ -0,0 +1,184 @@
+package net.wotonomy.test;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.AbstractButton;
+import javax.swing.JDialog;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.ui.EOAssociation;
+import net.wotonomy.ui.EODisplayGroup;
+import net.wotonomy.ui.MasterDetailAssociation;
+import net.wotonomy.ui.swing.ListAssociation;
+import net.wotonomy.ui.swing.RadioPanelAssociation;
+import net.wotonomy.ui.swing.TextAssociation;
+import net.wotonomy.ui.swing.TreeAssociation;
+import net.wotonomy.ui.swing.util.WindowUtilities;
+
+/**
+* A simple editor panel with a few textfields.
+*/
+public class TreeInspectorController
+{
+ public TreeInspectorController( EODisplayGroup titlesGroup, EODisplayGroup childrenGroup )
+ {
+ final EODisplayGroup group = childrenGroup;
+// final EODisplayGroup group = titlesGroup;
+ final TreePanel treePanel = new TreePanel();
+
+ // text associations
+
+ EOAssociation ta;
+
+ ta = new TreeAssociation( treePanel.tree, "People" );
+ ta.bindAspect( EOAssociation.TitlesAspect, titlesGroup, "lastName" );
+ ta.bindAspect( EOAssociation.ChildrenAspect, group, "childList" );
+ ta.bindAspect( EOAssociation.IsLeafAspect, titlesGroup, "childCount" );
+// ta.bindAspect( EOAssociation.IsLeafAspect, null, "false" );
+ ta.establishConnection();
+ treePanel.tree.setEditable( true );
+
+ ta = new TextAssociation( treePanel.editPanel.firstNameField );
+ ta.bindAspect( EOAssociation.ValueAspect, group, "firstName" );
+ ta.establishConnection();
+
+ ta = new TextAssociation( treePanel.editPanel.middleNameField );
+ ta.bindAspect( EOAssociation.ValueAspect, group, "middleName" );
+ ta.establishConnection();
+
+ ta = new TextAssociation( treePanel.editPanel.lastNameField );
+ ta.bindAspect( EOAssociation.ValueAspect, group, "lastName" );
+ ta.establishConnection();
+
+ ta = new RadioPanelAssociation( treePanel.editPanel.yearRadioPanel );
+ ta.bindAspect( EOAssociation.ValueAspect, group, "createDate.year" );
+
+ EODisplayGroup yearTitles = new EODisplayGroup();
+ yearTitles.setObjectArray( new NSArray(
+ new Object[] { "1999", "2000", "2001" } ) );
+ ta.bindAspect( EOAssociation.TitlesAspect, yearTitles, "" );
+
+ EODisplayGroup yearObjects = new EODisplayGroup();
+ yearObjects.setObjectArray( new NSArray(
+ new Object[] { new Integer( 99 ), new Integer( 100 ), new Integer( 101 ) } ) );
+ ta.bindAspect( EOAssociation.ObjectsAspect, yearObjects, "" );
+
+ ta.establishConnection();
+
+ // detail group
+
+ final EODisplayGroup detailGroup = new EODisplayGroup();
+
+ ta = new MasterDetailAssociation( detailGroup );
+ ta.bindAspect( EOAssociation.ParentAspect, group, "childList" );
+ ta.establishConnection();
+
+ ta = new ListAssociation( treePanel.editPanel.list );
+ ta.bindAspect( EOAssociation.TitlesAspect, detailGroup, "fullName" );
+ ta.establishConnection();
+
+ // display group action associations
+
+ AbstractButton addButton = (AbstractButton)
+ treePanel.editPanel.addPanel.getButton( "Add" );
+ addButton.addActionListener( new ActionListener()
+ {
+ public void actionPerformed( ActionEvent evt )
+ {
+ detailGroup.insertObjectAtIndex( new TestObject(), 0 );
+ }
+ } );
+
+ AbstractButton removeButton = (AbstractButton)
+ treePanel.editPanel.addPanel.getButton( "Remove" );
+ removeButton.addActionListener( new ActionListener()
+ {
+ public void actionPerformed( ActionEvent evt )
+ {
+ if ( detailGroup.selectedObject() != null )
+ detailGroup.deleteSelection();
+ }
+ } );
+/*
+ AbstractButton refreshButton = (AbstractButton)
+ treePanel.editPanel.addPanel.getButton( "Refresh" );
+ refreshButton.addActionListener( new ActionListener()
+ {
+ int count = 0;
+ public void actionPerformed( ActionEvent evt )
+ {
+ EODisplayGroup displayGroup = (EODisplayGroup)
+ treePanel.tree.getSelectionPath().getLastPathComponent();
+// displayGroup.insertObjectAtIndex( new TestObject(), 0 );
+// displayGroup.deleteObjectAtIndex( 0 );
+ List sortOrderings = new LinkedList();
+ if ( count++ % 2 == 0 )
+ {
+ sortOrderings.add( new EOSortOrdering(
+ "lastName", EOSortOrdering.CompareAscending ) );
+ }
+ else
+ {
+ sortOrderings.add( new EOSortOrdering(
+ "lastName", EOSortOrdering.CompareDescending ) );
+ }
+ displayGroup.setSortOrderings( sortOrderings );
+ displayGroup.updateDisplayedObjects();
+ }
+ } );
+*/
+/*
+ ta = new DisplayGroupActionAssociation(
+ treePanel.editPanel.addPanel.getButton( "Remove" ) );
+ ta.bindAspect( EOAssociation.ActionAspect, detailGroup, "deleteSelection" );
+ ta.establishConnection();
+
+ ta = new DisplayGroupActionAssociation(
+ treePanel.editPanel.addPanel.getButton( "Refresh" ) );
+ ta.bindAspect( EOAssociation.ActionAspect, group, "updateDisplayedObjects" );
+ ta.establishConnection();
+*/
+
+ // add mouse listener for list
+
+ treePanel.editPanel.list.addMouseListener( new MouseAdapter()
+ {
+ public void mouseClicked(MouseEvent e)
+ {
+ if ( e.getClickCount() == 2 )
+ {
+ Object item = detailGroup.selectedObject();
+ if ( item != null )
+ {
+ new InspectorController( item );
+ }
+ }
+ }
+ });
+
+ // launch
+
+ JDialog d = new JDialog();
+ d.getContentPane().add( treePanel );
+ d.setTitle( "Tree Panel" );
+ d.pack();
+ WindowUtilities.cascade( d );
+ d.show();
+
+ // workaround for memory issues on jdk1.2.2
+ d.addWindowListener( new WindowAdapter()
+ {
+ // exit on close
+ public void windowClosing(WindowEvent e)
+ {
+ ((JDialog)e.getWindow()).getContentPane().removeAll();
+ }
+ });
+ }
+
+}
diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreePanel.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreePanel.java
new file mode 100644
index 0000000..48ebd81
--- /dev/null
+++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreePanel.java
@@ -0,0 +1,39 @@
+package net.wotonomy.test;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTree;
+import javax.swing.border.EmptyBorder;
+
+/**
+* A simple editor panel with a few textfields.
+*/
+public class TreePanel extends JPanel
+{
+ public JTree tree;
+ public EditPanel editPanel;
+ public JPanel panel;
+
+ public TreePanel()
+ {
+ panel = new JPanel();
+ panel.setLayout( new BorderLayout() );
+ panel.setBorder( new EmptyBorder( 10, 10, 10, 10 ) );
+
+ tree = new JTree();
+ tree.setRootVisible( false );
+ tree.setShowsRootHandles( true );
+ JScrollPane scrollPane = new JScrollPane( tree );
+ scrollPane.setPreferredSize( new Dimension( 150, 200 ) );
+ panel.add( scrollPane, BorderLayout.CENTER );
+ editPanel = new EditPanel();
+ panel.add( editPanel, BorderLayout.EAST );
+
+ this.setLayout( new BorderLayout() );
+ this.add( panel, BorderLayout.CENTER );
+ }
+
+}