+
@@ -86,6 +87,14 @@ require(["dojo/dom-geometry","dojo/dom-class","dojo/on","dojo/dom","dojo/Evented
controller.setState(new iD.controller.shape.NoSelection());
});
+ $('#undo').click(function() {
+ controller.undoStack.undo();
+ });
+
+ $('#redo').click(function() {
+ controller.undoStack.redo();
+ });
+
// ----------------------------------------------------
// Map control handlers
@@ -118,7 +127,11 @@ require(["dojo/dom-geometry","dojo/dom-class","dojo/on","dojo/dom","dojo/Evented
diff --git a/js/iD/Controller.js b/js/iD/Controller.js
index f21f998d8..724ee2534 100755
--- a/js/iD/Controller.js
+++ b/js/iD/Controller.js
@@ -14,7 +14,7 @@ declare("iD.Controller", [Evented], {
constructor:function(map) {
// summary: The Controller marshalls ControllerStates and passes events to them.
this.map = map;
- this.undoStack = new iD.actions.UndoStack();
+ this.undoStack = new iD.UndoStack();
this.editorCache = {};
},
@@ -29,17 +29,15 @@ declare("iD.Controller", [Evented], {
if (this.state) {
this.emit("exitState", {
bubbles: true,
- cancelable: true,
- state: this.state.stateNameAsArray()
+ cancelable: true
});
}
- newState.setController(this);
+ newState.controller = this;
this.state = newState;
newState.enterState();
this.emit("enterState", {
bubbles: true,
- cancelable: true,
- state: this.state.stateNameAsArray()
+ cancelable: true
});
},
diff --git a/js/iD/actions/CreateEntityAction.js b/js/iD/actions/CreateEntityAction.js
index 74ef2400a..48fa94fdd 100644
--- a/js/iD/actions/CreateEntityAction.js
+++ b/js/iD/actions/CreateEntityAction.js
@@ -16,7 +16,7 @@ declare("iD.actions.CreateEntityAction", [iD.actions.UndoableEntityAction], {
this.setName("Create " + entity.entityType);
},
- doAction: function() {
+ run: function() {
// summary: Call out to the specified method (in the Controller) to create the entity.
// See undoAction for explanation of special redo handling.
if (this.deleteAction!==null) {
@@ -28,7 +28,7 @@ declare("iD.actions.CreateEntityAction", [iD.actions.UndoableEntityAction], {
return this.SUCCESS;
},
- undoAction: function() {
+ undo: function() {
// summary: Special handling for undoing a create. When undo is called, instead
// of simply removing the entity, we work through to make a Delete[Entity]Action,
// call that, and store it for later. Then, when this action is called again
@@ -43,7 +43,6 @@ declare("iD.actions.CreateEntityAction", [iD.actions.UndoableEntityAction], {
// summary: Set the associated delete action (see undoAction for explanation).
deleteAction = action;
}
-
});
// ----------------------------------------------------------------------
diff --git a/js/iD/actions/CreatePOIAction.js b/js/iD/actions/CreatePOIAction.js
index 7e0659baf..e12afc23b 100644
--- a/js/iD/actions/CreatePOIAction.js
+++ b/js/iD/actions/CreatePOIAction.js
@@ -17,34 +17,31 @@ declare("iD.actions.CreatePOIAction", [iD.actions.CompositeUndoableAction], {
constructor: function(connection, tags, lat, lon) {
// summary: Create a new node and set it as a POI. Used by drag-and-drop. Note that the
// node is remembered, so that on redo we can just reinstate it.
- this.setName('Create POI');
+ this.setName('Create POI: ' + iD.Util.friendlyName(tags));
this.connection = connection;
this.tags = tags;
this.lat = lat;
this.lon = lon;
},
- doAction:function() {
+ run: function() {
if (this.newNode === null) {
this.newNode = this.connection.doCreateNode(this.tags,
this.lat, this.lon,
_.bind(this.push, this));
}
- this.inherited(arguments);
this.connection.registerPOI(this.newNode);
- return this.SUCCESS;
+ return true;
},
- undoAction:function() {
- this.inherited(arguments);
+ undo: function() {
this.connection.unregisterPOI(this.newNode);
- return this.SUCCESS;
+ return true;
},
- getNode:function() {
+ getNode: function() {
return this.newNode;
}
-
});
// ----------------------------------------------------------------------
diff --git a/js/iD/actions/MoveNodeAction.js b/js/iD/actions/MoveNodeAction.js
index 700db8f8c..5af2f4b5f 100644
--- a/js/iD/actions/MoveNodeAction.js
+++ b/js/iD/actions/MoveNodeAction.js
@@ -15,7 +15,7 @@ declare("iD.actions.MoveNodeAction", [iD.actions.UndoableEntityAction], {
newLon: NaN,
setLatLon: null,
- constructor:function(node, newLat, newLon, setLatLon) {
+ constructor: function(node, newLat, newLon, setLatLon) {
// summary: Move a node to a new position.
this.entity = node;
this.newLat = newLat;
@@ -24,25 +24,28 @@ declare("iD.actions.MoveNodeAction", [iD.actions.UndoableEntityAction], {
this.createTime = new Date().getTime();
},
- doAction:function() {
+ run: function() {
var node = this.entity;
this.oldLat = node.lat;
this.oldLon = node.lon;
- if (this.oldLat==this.newLat && this.oldLon==this.newLon) { return NO_CHANGE; }
+ if (this.oldLat === this.newLat &&
+ this.oldLon === this.newLon) {
+ return NO_CHANGE;
+ }
this.setLatLon(this.newLat, this.newLon);
this.markDirty();
node.refresh();
- return this.SUCCESS;
+ return true;
},
- undoAction:function() {
+ undo: function() {
this.setLatLon(this.oldLat, this.oldLon);
this.markClean();
this.refresh();
- return this.SUCCESS;
+ return true;
},
- mergePrevious:function(prev) {
+ mergePrevious: function(prev) {
if (prev.declaredClass!=this.declaredClass) { return false; }
if (prev.entity == this.entity && prev.createTime+1000>this.createTime) {
@@ -52,7 +55,6 @@ declare("iD.actions.MoveNodeAction", [iD.actions.UndoableEntityAction], {
}
return false;
}
-
});
// ----------------------------------------------------------------------
diff --git a/js/iD/actions/UndoStack.js b/js/iD/actions/UndoStack.js
index ee0f2ef17..9129dab59 100644
--- a/js/iD/actions/UndoStack.js
+++ b/js/iD/actions/UndoStack.js
@@ -1,124 +1,104 @@
// iD/actions/UndoStack.js
// ** FIXME: a couple of AS3-isms in undoIfAction/removeLastIfAction
-define(['dojo/_base/declare'], function(declare){
-
// ----------------------------------------------------------------------
// UndoStack base class
-declare("iD.actions.UndoStack", null, {
-
- undoActions: null,
- redoActions: null,
+if (typeof iD === 'undefined') iD = {};
- FAIL: 0,
- SUCCESS: 1,
- NO_CHANGE: 2,
+iD.UndoStack = function() {
- constructor: function() {
- // summary: An undo stack. There can be any number of these, but almost all operations will
- // take place on the global undo stack - implemented as a singleton-like property of
- // the Controller.
- this.undoActions=[];
- this.redoActions=[];
- },
-
- addAction: function(_action) {
- // summary: Do an action, and add it to the undo stack if it succeeded.
- var result = _action.doAction();
- switch (result) {
- case this.FAIL:
- // do something bad
- break;
+ var stack = {},
+ undoActions = [],
+ redoActions = [];
- case this.NO_CHANGE:
- break;
+ var FAIL = 0,
+ SUCCESS = 1,
+ NO_CHANGE = 2;
- case this.SUCCESS:
- default:
- if (this.undoActions.length>0) {
- var previous = this.undoActions[this.undoActions.length-1];
- if (_action.mergePrevious(previous)) {
- _action.wasDirty = previous.wasDirty;
- _action.connectionWasDirty = previous.connectionWasDirty;
- this.undoActions.pop();
- }
- }
- this.undoActions.push(_action);
- this.redoActions=[];
- break;
- }
- },
+ stack.add = function(action) {
+ // summary: Add an action to the undo stack
+ if (undoActions.length > 0) {
+ var previous = undoActions[undoActions.length - 1];
+ if (action.mergePrevious(previous)) {
+ action.wasDirty = previous.wasDirty;
+ action.connectionWasDirty = previous.connectionWasDirty;
+ undoActions.pop();
+ }
+ }
+ undoActions.push(action);
+ redoActions = [];
+ };
- breakUndo: function() {
- // summary: Wipe the undo stack - typically used after saving.
- this.undoActions = [];
- this.redoActions = [];
- },
+ stack.breakUndo = function() {
+ // summary: Wipe the undo stack - typically used after saving.
+ undoActions = [];
+ redoActions = [];
+ };
- canUndo: function() {
- // summary: Are there any items on the undo stack?
- return this.undoActions.length > 0;
- },
+ stack.canUndo = function() {
+ // summary: Are there any items on the undo stack?
+ return undoActions.length > 0;
+ };
- canRedo: function() {
- // summary: Are there any redoable actions?
- return this.redoActions.length > 0;
- },
+ stack.canRedo = function() {
+ // summary: Are there any redoable actions?
+ return redoActions.length > 0;
+ };
- undo: function() {
- // summary: Undo the most recent action, and add it to the top of the redo stack.
- if (!this.undoActions.length) { return; }
- var action = undoActions.pop();
- action.undoAction();
- redoActions.push(action);
- },
- undoIfAction: function(_action) {
- // summary: Undo the most recent action _only_ if it was of a certain type.
- // Fixme: isInstanceOf needs to be made into JavaScript.
- if (!this.undoActions.length) { return; }
- if (this.undoActions[this.undoActions.length-1].isInstanceOf(_action)) {
- this.undo();
- return true;
- }
- return false;
- },
- removeLastIfAction: function(_action) {
- // summary: Remove the most recent action from the stack _only_ if it was of a certain type.
- // Fixme: isInstanceOf needs to be made into JavaScript.
- if (this.undoActions.length && this.undoActions[this.undoActions.length-1].isInstanceOf(_action)) {
- this.undoActions.pop();
- }
- },
+ stack.undo = function() {
+ // summary: Undo the most recent action, and add it to the top of the redo stack.
+ if (!stack.canUndo()) { return; }
+ var action = undoActions.pop();
+ action.undo();
+ redoActions.push(action);
+ };
- getUndoDescription: function() {
- // summary: Get the name of the topmost item on the undo stack.
- if (!this.undoActions.length) return null;
- if (this.undoActions[this.undoActions.length-1].name) {
- return this.undoActions[this.undoActions.length-1].name;
- }
- return null;
- },
+ stack.undoIfAction = function(_action) {
+ // summary: Undo the most recent action _only_ if it was of a certain type.
+ // Fixme: isInstanceOf needs to be made into JavaScript.
+ if (!undoActions.length) { return; }
+ if (undoActions[undoActions.length-1].isInstanceOf(_action)) {
+ undo();
+ return true;
+ }
+ return false;
+ };
- getRedoDescription: function() {
- // summary: Get the name of the topmost item on the redo stack.
- if (!this.redoActions.length) return null;
- if (this.redoActions[this.redoActions.length-1].name) {
- return this.redoActions[this.redoActions.length-1].name;
- }
- return null;
- },
+ stack.removeLastIfAction = function(_action) {
+ // summary: Remove the most recent action from the stack _only_ if it was of a certain type.
+ // Fixme: isInstanceOf needs to be made into JavaScript.
+ if (undoActions.length &&
+ undoActions[undoActions.length - 1].isInstanceOf(_action)) {
+ undoActions.pop();
+ }
+ };
- redo: function() {
- // summary: Takes the action most recently undone, does it, and adds it to the undo stack.
- if (!this.redoActions.length) { return; }
- var action = this.redoActions.pop();
- action.doAction();
- this.undoActions.push(action);
- }
+ stack.getUndoDescription = function() {
+ // summary: Get the name of the topmost item on the undo stack.
+ if (!undoActions.length) return null;
+ if (undoActions[undoActions.length - 1].name) {
+ return undoActions[undoActions.length - 1].name;
+ }
+ return null;
+ };
-});
+ stack.getRedoDescription = function() {
+ // summary: Get the name of the topmost item on the redo stack.
+ if (!redoActions.length) return null;
+ if (redoActions[redoActions.length - 1].name) {
+ return redoActions[redoActions.length - 1].name;
+ }
+ return null;
+ };
-// ----------------------------------------------------------------------
-// End of module
-});
+ stack.redo = function() {
+ // summary: Takes the action most recently undone, does it, and adds it to the undo stack.
+ if (!stack.canRedo()) { return; }
+ var action = redoActions.pop();
+ action.run();
+ undoActions.push(action);
+ };
+
+ return stack;
+};
diff --git a/js/iD/actions/UndoableAction.js b/js/iD/actions/UndoableAction.js
index 003c7557a..0971af7e8 100644
--- a/js/iD/actions/UndoableAction.js
+++ b/js/iD/actions/UndoableAction.js
@@ -7,42 +7,14 @@ define(['dojo/_base/declare'], function(declare){
// UndoableAction base class
declare("iD.actions.UndoableAction", null, {
-
- FAIL: 0,
- SUCCESS: 1,
- NO_CHANGE: 2,
-
- name: "",
-
- constructor:function() {
- // summary: An UndoableAction is a user-induced change to the map data (held within the Connection)
- // which can be undone. The UndoableAction doesn't actually change the data: it provides
- // the logic around the change, but the change itself is generally made by the model.
- // Therefore the UndoableAction constructor needs to be passed a reference
- // to a method that actually makes the change - for example, MoveNodeAction will be
- // passed a reference to Node._setLatLonImmediate.
- },
-
- doAction:function() {
- // summary: Do the action.
- return FAIL;
- },
-
- undoAction:function() {
- // summary: Undo the action.
- return FAIL;
- },
-
mergePrevious:function() {
// summary: If two successive actions can be merged (in particular, two successive node moves), do that.
return false;
},
-
setName:function(_name) {
// summary: Set the name of an action. For UI and debugging purposes.
this.name=_name;
}
-
});
// ----------------------------------------------------------------------
@@ -63,14 +35,14 @@ declare("iD.actions.UndoableEntityAction", [iD.actions.UndoableAction], {
},
markDirty:function() {
- // summary: Mark a change to the entity ('dirtying' it).
+ // summary: Mark a change to the entity ('dirtying' it).
if (!this.initialised) this.init();
if (!this.wasDirty) this.entity._markDirty();
if (!this.connectionWasDirty) this.entity.connection.modified = true;
},
markClean:function() {
- // summary: If the entity was clean before, revert the dirty flag to that state.
+ // summary: If the entity was clean before, revert the dirty flag to that state.
if (!this.initialised) this.init();
if (!this.wasDirty) this.entity._markClean();
if (!this.connectionWasDirty) this.entity.connection.modified = false;
@@ -102,7 +74,7 @@ declare("iD.actions.CompositeUndoableAction", [iD.actions.UndoableAction], {
// summary: A CompositeUndoableAction is a group of user-induced changes to the map data
// (held within the Connection) which are executed, and therefore undone,
// in a batch. For example, creating a new node and a new way containing it.
- this.actions=[];
+ this.actions = [];
},
push:function(action) {
@@ -112,14 +84,14 @@ declare("iD.actions.CompositeUndoableAction", [iD.actions.UndoableAction], {
clearActions:function() {
// summary: Clear the list of actions.
- this.actions=[];
+ this.actions = [];
},
doAction:function() {
// summary: Execute all the actions one-by-one as a transaction (i.e. roll them all back if one fails).
if (this.actionsDone) { return this.FAIL; }
- var somethingDone=false;
- for (var i=0; i