Author: jfuerth
Date: Tue Sep  2 14:50:11 2008
New Revision: 2675

Added:
   trunk/src/ca/sqlpower/architect/olap/CompoundEditEvent.java
   trunk/src/ca/sqlpower/architect/olap/CompoundEditListener.java
Modified:
   trunk/src/ca/sqlpower/architect/olap/OLAPObject.java
   trunk/src/ca/sqlpower/architect/olap/OLAPUtil.java
   trunk/src/ca/sqlpower/architect/olap/undo/OLAPUndoManager.java
   trunk/src/ca/sqlpower/architect/swingui/olap/action/CreateCubeAction.java
trunk/src/ca/sqlpower/architect/swingui/olap/action/CreateDimensionAction.java

Log:
Implemented compound edits on OLAPObjects, and the UndoManager support for grouping them together on the undo list.

Also made the create actions for cubes and dimensions into atomic events (without this, undoing the creation is a 2-step process: one for adding the child, and one for modifying the properties)

Added: trunk/src/ca/sqlpower/architect/olap/CompoundEditEvent.java
==============================================================================
--- (empty file)
+++ trunk/src/ca/sqlpower/architect/olap/CompoundEditEvent.java Tue Sep 2 14:50:11 2008
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2008, SQL Power Group Inc.
+ *
+ * This file is part of Power*Architect.
+ *
+ * Power*Architect is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Power*Architect is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package ca.sqlpower.architect.olap;
+
+public class CompoundEditEvent {
+
+    private final OLAPObject source;
+
+    private final String presentationName;
+
+    /**
+ * Creates a new compound edit event from the given source with the given
+     * presentation name.
+     *
+     * @param source
+     *            The object that fired the event
+     * @param presentationName
+     *            The name for this compound edit
+     */
+    public CompoundEditEvent(OLAPObject source, String presentationName) {
+        this.source = source;
+        this.presentationName = presentationName;
+    }
+
+    /**
+ * Creates a new compound edit event from the given source. The presentation
+     * name will be null.
+     *
+     * @param source
+     *            The object that fired the event
+     */
+    public CompoundEditEvent(OLAPObject source) {
+        this.source = source;
+        presentationName = null;
+    }
+
+    public OLAPObject getSource() {
+        return source;
+    }
+
+    public String getPresentationName() {
+        return presentationName;
+    }
+}

Added: trunk/src/ca/sqlpower/architect/olap/CompoundEditListener.java
==============================================================================
--- (empty file)
+++ trunk/src/ca/sqlpower/architect/olap/CompoundEditListener.java Tue Sep 2 14:50:11 2008
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2008, SQL Power Group Inc.
+ *
+ * This file is part of Power*Architect.
+ *
+ * Power*Architect is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Power*Architect is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package ca.sqlpower.architect.olap;
+
+/**
+ * Interface for listeners interested in being notified when a compound
+ * edit begins or ends.
+ */
+public interface CompoundEditListener {
+
+    /**
+     * Called when a compound edit has begun on an OLAPObject. All property
+     * change and child events from the source of this event and any of its
+ * descendants should be considered part of an atomic change that ends when + * a [EMAIL PROTECTED] #compoundEditEnded(OLAPObject, String)} event is received from
+     * the same source.
+     *
+     * @param source
+ * the object under which the following series of changes should
+     *            be considered atomic.
+     * @param presentationName
+     *            The user-visible name for the sequence of edits.
+     */
+    void compoundEditStarted(CompoundEditEvent evt);
+
+    /**
+ * Called when a compound edit has ended on an OLAPObject. This method will
+     * never be called more times than
+ * [EMAIL PROTECTED] #compoundEditStarted(OLAPObject, String)} has already been called.
+     *
+     * @param source
+     *            the object under which the compound edit state has ended.
+     */
+    void compoundEditEnded(CompoundEditEvent evt);
+
+}

Modified: trunk/src/ca/sqlpower/architect/olap/OLAPObject.java
==============================================================================
--- trunk/src/ca/sqlpower/architect/olap/OLAPObject.java        (original)
+++ trunk/src/ca/sqlpower/architect/olap/OLAPObject.java Tue Sep 2 14:50:11 2008
@@ -48,6 +48,11 @@
      * parent's addChild method(s).
      */
     private OLAPObject parent;
+
+    /**
+     * All compound edit listeners registered on this object.
+     */
+ private final List<CompoundEditListener> compoundEditListeners = new ArrayList<CompoundEditListener>();

     protected OLAPObject() {
         pcs = new PropertyChangeSupport(this);
@@ -167,15 +172,40 @@
public void removePropertyChangeListener(PropertyChangeListener listener) {
         pcs.removePropertyChangeListener(listener);
     }
-
-    public void startCompoundEdit(String description) {
+
+    public void addCompoundEditListener(CompoundEditListener listener) {
+        compoundEditListeners.add(listener);
+    }
+
+    public void removeCompoundEditListener(CompoundEditListener listener) {
+        compoundEditListeners.remove(listener);
+    }
+
+    public void startCompoundEdit(String presentationName) {
         compoundEditDepth++;
-        // TODO fire compound edit event
+ logger.debug("Compound edit on " + getClass().getSimpleName() + " starting. Depth=" + compoundEditDepth);
+        if (compoundEditDepth == 1) {
+ logger.debug("Firing compoundEditStarted to " + compoundEditListeners.size() + " listeners..."); + CompoundEditEvent evt = new CompoundEditEvent(this, presentationName);
+            for (int i = compoundEditListeners.size() - 1; i >= 0; i--) {
+                compoundEditListeners.get(i).compoundEditStarted(evt);
+            }
+        }
     }

     public void endCompoundEdit() {
+ logger.debug("Compound edit on " + getClass().getSimpleName() + " ending. Depth=" + compoundEditDepth);
+        if (compoundEditDepth <= 0) {
+ throw new IllegalStateException("Compound edit depth is already " + compoundEditDepth);
+        }
         compoundEditDepth--;
-        // TODO fire compound edit event
+        if (compoundEditDepth == 0) {
+ logger.debug("Firing compoundEditEnded to " + compoundEditListeners.size() + " listeners...");
+            CompoundEditEvent evt = new CompoundEditEvent(this);
+            for (int i = compoundEditListeners.size() - 1; i >= 0; i--) {
+                compoundEditListeners.get(i).compoundEditEnded(evt);
+            }
+        }
     }

protected void fireChildAdded(Class<? extends OLAPObject> childClass, int index, OLAPObject child) {

Modified: trunk/src/ca/sqlpower/architect/olap/OLAPUtil.java
==============================================================================
--- trunk/src/ca/sqlpower/architect/olap/OLAPUtil.java  (original)
+++ trunk/src/ca/sqlpower/architect/olap/OLAPUtil.java Tue Sep 2 14:50:11 2008
@@ -108,18 +108,49 @@
* OLAPObjects rooted at root. If you don't want to know about * property changes to the nodes themselves, you can leave this
      *            parameter as null.
+     * @param cel
+ * The CompoundEditListener to add to the subtree of OLAPObjects + * rooted at root. If you don't want to know about compound edit
+     *            groupings, you can leave this parameter as null.
      */
- public static void listenToHierarchy(OLAPObject root, OLAPChildListener ocl, PropertyChangeListener pcl) {
+    public static void listenToHierarchy(
+            OLAPObject root,
+            OLAPChildListener ocl,
+            PropertyChangeListener pcl,
+            CompoundEditListener cel) {
         root.addChildListener(ocl);
         if (pcl != null) {
             root.addPropertyChangeListener(pcl);
         }
+        if (cel != null) {
+            root.addCompoundEditListener(cel);
+        }
         for (OLAPObject child : root.getChildren()) {
-            listenToHierarchy(child, ocl, pcl);
+            listenToHierarchy(child, ocl, pcl, cel);
         }
     }

     /**
+ * Adds the given OLAPChildListener and optional PropertyChangeListener to
+     * the given root object and all of its descendants.
+     *
+     * @param ocl
+ * The OLAPChildListener to add to the subtree of OLAPObjects
+     *            rooted at root.
+     * @param pcl
+     *            The PropertyChangeListener to add to the subtree of
+ * OLAPObjects rooted at root. If you don't want to know about + * property changes to the nodes themselves, you can leave this
+     *            parameter as null.
+     */
+    public static void listenToHierarchy(
+            OLAPObject root,
+            OLAPChildListener ocl,
+            PropertyChangeListener pcl) {
+        listenToHierarchy(root, ocl, pcl, null);
+    }
+
+    /**
* Removes the given OLAPChildListener and optional PropertyChangeListener
      * from the given root object and all of its descendants.
      *
@@ -137,15 +168,55 @@
      *            if you want. If you weren't listening for property change
* events, you can leave this parameter as null. Note that this
      *            parameter is pronounced "pockle," not "pickle."
+     * @param cel
+     *            The CompoundEditListener to add to the subtree of
+ * OLAPObjects rooted at root. It is not an error if the listener
+     *            is not registered with any or all of the objects in the
+ * subtree, so it's safe to call this with the root of the tree
+     *            if you want. If you weren't listening for compound edit
+     *            events, you can leave this parameter as null.
      */
- public static void unlistenToHierarchy(OLAPObject root, OLAPChildListener ocl, PropertyChangeListener pcl) {
+    public static void unlistenToHierarchy(
+            OLAPObject root,
+            OLAPChildListener ocl,
+            PropertyChangeListener pcl,
+            CompoundEditListener cel) {
         root.removeChildListener(ocl);
         if (pcl != null) {
             root.removePropertyChangeListener(pcl);
         }
+        if (cel != null) {
+            root.removeCompoundEditListener(cel);
+        }
         for (OLAPObject child : root.getChildren()) {
-            unlistenToHierarchy(child, ocl, pcl);
+            unlistenToHierarchy(child, ocl, pcl, cel);
         }
+    }
+
+    /**
+ * Removes the given OLAPChildListener and optional PropertyChangeListener
+     * from the given root object and all of its descendants.
+     *
+     * @param ocl
+     *            The OLAPChildListener to remove from the subtree of
+ * OLAPObjects rooted at root. It is not an error if the listener
+     *            is not registered with any or all of the objects in the
+ * subtree, so it's safe to call this with the root of the tree
+     *            if you want.
+     * @param pcl
+     *            The PropertyChangeListener to add to the subtree of
+ * OLAPObjects rooted at root. It is not an error if the listener
+     *            is not registered with any or all of the objects in the
+ * subtree, so it's safe to call this with the root of the tree
+     *            if you want. If you weren't listening for property change
+ * events, you can leave this parameter as null. Note that this
+     *            parameter is pronounced "pockle," not "pickle."
+     */
+    public static void unlistenToHierarchy(
+            OLAPObject root,
+            OLAPChildListener ocl,
+            PropertyChangeListener pcl) {
+        unlistenToHierarchy(root, ocl, pcl, null);
     }

     /**

Modified: trunk/src/ca/sqlpower/architect/olap/undo/OLAPUndoManager.java
==============================================================================
--- trunk/src/ca/sqlpower/architect/olap/undo/OLAPUndoManager.java (original) +++ trunk/src/ca/sqlpower/architect/olap/undo/OLAPUndoManager.java Tue Sep 2 14:50:11 2008
@@ -28,11 +28,14 @@
 import javax.swing.event.ChangeListener;
 import javax.swing.undo.CannotRedoException;
 import javax.swing.undo.CannotUndoException;
+import javax.swing.undo.CompoundEdit;
 import javax.swing.undo.UndoManager;
 import javax.swing.undo.UndoableEdit;

 import org.apache.log4j.Logger;

+import ca.sqlpower.architect.olap.CompoundEditEvent;
+import ca.sqlpower.architect.olap.CompoundEditListener;
 import ca.sqlpower.architect.olap.OLAPChildEvent;
 import ca.sqlpower.architect.olap.OLAPChildListener;
 import ca.sqlpower.architect.olap.OLAPObject;
@@ -51,8 +54,23 @@

     private boolean undoing = false;

+    /**
+ * Will be non-null when this undo manager is in the middle of receiving a + * series of edits that are supposed to be undone and redone as one step.
+     */
+    private CompoundEdit currentCompoundEdit;
+
+    /**
+ * The number of times we've started a compound edit minus the number of + * times we've finished one. If greater than 0, we are in a compound edit
+     * and [EMAIL PROTECTED] #currentCompoundEdit} will be non-null.
+     * <p>
+     * This value will never be less than 0.
+     */
+    private int compoundEditDepth = 0;
+
     public OLAPUndoManager(OLAPObject root) {
-        OLAPUtil.listenToHierarchy(root, eventHandler, eventHandler);
+ OLAPUtil.listenToHierarchy(root, eventHandler, eventHandler, eventHandler);
         // TODO need to track the playpen components as well
     }

@@ -93,20 +111,53 @@
         }
     }

- private class UndoableEventHandler implements OLAPChildListener, PropertyChangeListener { + private class UndoableEventHandler implements OLAPChildListener, PropertyChangeListener, CompoundEditListener {

         public void olapChildAdded(OLAPChildEvent e) {
             addEdit(new OLAPChildEdit(e, false));
-            OLAPUtil.listenToHierarchy(e.getChild(), this, this);
+            OLAPUtil.listenToHierarchy(e.getChild(), this, this, this);
         }

         public void olapChildRemoved(OLAPChildEvent e) {
             addEdit(new OLAPChildEdit(e, true));
-            OLAPUtil.unlistenToHierarchy(e.getChild(), this, this);
+            OLAPUtil.unlistenToHierarchy(e.getChild(), this, this, this);
         }

         public void propertyChange(PropertyChangeEvent e) {
             addEdit(new PropertyChangeEdit(e));
+        }
+
+        public void compoundEditStarted(CompoundEditEvent evt) {
+            compoundEditDepth++;
+ logger.debug("Got start compound event. My depth: " + compoundEditDepth);
+            if (compoundEditDepth == 1) {
+                final String presentationName = evt.getPresentationName();
+ logger.debug("Beginning compound edit \"" + presentationName + "\"");
+                currentCompoundEdit = new CompoundEdit() {
+                    @Override
+                    public String getPresentationName() {
+                        return presentationName;
+                    }
+                };
+                addEdit(currentCompoundEdit);
+            }
+        }
+
+        public void compoundEditEnded(CompoundEditEvent evt) {
+ logger.debug("Got end compound event. My depth: " + compoundEditDepth);
+            if (currentCompoundEdit == null || compoundEditDepth <= 0) {
+                throw new IllegalStateException(
+                        "Received a compoundEditEnded but was not " +
+ "in a compound edit state: depth=" + compoundEditDepth +
+                        "; currentEdit=" + currentCompoundEdit);
+            }
+            compoundEditDepth--;
+            if (compoundEditDepth == 0) {
+ logger.debug("Ending compound edit \"" + currentCompoundEdit.getPresentationName() + "\"");
+                currentCompoundEdit.end();
+                currentCompoundEdit = null;
+                fireChangeEvent();
+            }
         }

     }

Modified: trunk/src/ca/sqlpower/architect/swingui/olap/action/CreateCubeAction.java
==============================================================================
--- trunk/src/ca/sqlpower/architect/swingui/olap/action/CreateCubeAction.java (original) +++ trunk/src/ca/sqlpower/architect/swingui/olap/action/CreateCubeAction.java Tue Sep 2 14:50:11 2008
@@ -78,6 +78,7 @@

         @Override
         public DataEntryPanel place(Point p) throws ArchitectException {
+            schema.startCompoundEdit("Create Cube");
             schema.addCube(cp.getModel());
             playpen.selectNone();
             playpen.addPlayPenComponent(cp, p);
@@ -87,6 +88,14 @@
                 @Override
                 public void discardChanges() {
                     schema.removeCube(cp.getModel());
+                    schema.endCompoundEdit();
+                }
+
+                @Override
+                public boolean applyChanges() {
+                    boolean applied = super.applyChanges();
+                    schema.endCompoundEdit();
+                    return applied;
                 }
             };
             return editPanel;

Modified: trunk/src/ca/sqlpower/architect/swingui/olap/action/CreateDimensionAction.java
==============================================================================
--- trunk/src/ca/sqlpower/architect/swingui/olap/action/CreateDimensionAction.java (original) +++ trunk/src/ca/sqlpower/architect/swingui/olap/action/CreateDimensionAction.java Tue Sep 2 14:50:11 2008
@@ -81,6 +81,7 @@

         @Override
         public DataEntryPanel place(Point p) throws ArchitectException {
+            schema.startCompoundEdit("Create a dimension");
             schema.addDimension(dp.getModel());
             playpen.selectNone();
             playpen.addPlayPenComponent(dp, p);
@@ -90,6 +91,14 @@
                 @Override
                 public void discardChanges() {
                     schema.removeDimension(dp.getModel());
+                    schema.endCompoundEdit();
+                }
+
+                @Override
+                public boolean applyChanges() {
+                    boolean applied = super.applyChanges();
+                    schema.endCompoundEdit();
+                    return applied;
                 }
             };
             return editPanel;
@@ -97,3 +106,5 @@
     }

 }
+
+

Reply via email to