Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Event firing at end of commands, managedcommands working correctly. R…
…obotlegs ready Commands to move back and forward
  • Loading branch information
secoif committed Nov 20, 2009
1 parent ae9bd09 commit 33b193b
Show file tree
Hide file tree
Showing 15 changed files with 501 additions and 60 deletions.
2 changes: 1 addition & 1 deletion src/FlexUnitApplication.mxml
Expand Up @@ -20,7 +20,7 @@
}
private function onCreationComplete():void
{
testRunner.runWithFlexUnit4Runner(currentRunTestSuite(), "Robotlegs-Undoable-Commands");
testRunner.runWithFlexUnit4Runner(currentRunTestSuite(), "Robotlegs-UnodableCommands");
}
]]>
</fx:Script>
Expand Down
36 changes: 36 additions & 0 deletions src/org/robotlegs/utilities/undoablecommand/CommandEvent.as
@@ -0,0 +1,36 @@
package org.robotlegs.utilities.undoablecommand
{
import flash.events.Event;

import org.robotlegs.utilities.undoablecommand.interfaces.IUndoableCommand;

/**
* CommandEvents occur when commands finish executing or undoing
*
*/
public class CommandEvent extends Event
{
/**
* Defines the value of the type property of an executeComplete event object.
*/
public static const EXECUTE_COMPLETE:String = "executeComplete";
/**
* Defines the value of the type property of an undoExecuteComplete event object.
*/
public static const UNDO_EXECUTE_COMPLETE:String = "undoExecuteComplete";

/**
* The command associated with the event
*/
public var command:IUndoableCommand;

/**
* @param type The type of event
* @param command The command associated with the event
*/
public function CommandEvent(type:String, command:IUndoableCommand) {
super(type, false, false);
this.command = command;
}
}
}
83 changes: 76 additions & 7 deletions src/org/robotlegs/utilities/undoablecommand/CommandHistory.as
@@ -1,13 +1,26 @@
package org.robotlegs.utilities.undoablecommand
{
import flash.events.IEventDispatcher;

import org.robotlegs.utilities.undoablecommand.interfaces.*;

public class CommandHistory
{
/**
* Command history data store.
* Vector chosen over array for strongtyping & speed
*/
private var _historyStack:Vector.<IUndoableCommand>;

/**
* Pointer to the current command in the history stack
* Index starts at 1.
* If this is 0, we are pointing to null at the start of the stack
*/
public var currentPosition:uint;
public var isReady:Boolean;

[Inject]
public var eventDispatcher:IEventDispatcher;

public function CommandHistory() {
_historyStack = new Vector.<IUndoableCommand>();
Expand All @@ -20,7 +33,6 @@ package org.robotlegs.utilities.undoablecommand
*/
public function get canStepForward():Boolean {
return (currentPosition < numberOfHistoryItems);

}

/**
Expand All @@ -38,8 +50,11 @@ package org.robotlegs.utilities.undoablecommand
*/
public function stepForward():uint {
if (canStepForward) {
_historyStack[currentPosition++].execute();
_historyStack[currentPosition].execute();
currentPosition++;
}
this.eventDispatcher.dispatchEvent(new HistoryEvent(HistoryEvent.STEP_FORWARD_COMPLETE, currentCommand));

return currentPosition;
}

Expand All @@ -49,9 +64,29 @@ package org.robotlegs.utilities.undoablecommand
* @return position in history stack after this operation
*/
public function stepBackward():uint {
var storeCurrentPosition:uint = currentPosition;
if (canStepBackward) {
_historyStack[--currentPosition].undo();
// Hacky workaround:
// if undo was invoked from a commandHistory object,
// _historyStack[currentPosition - 1].undo() calls
// stepBackward() again (), in which case we want to prevent it from
// updating the current position twice. I'm certain thar be a better way.
_historyStack[currentPosition - 1].undo();
currentPosition = storeCurrentPosition - 1;
}

// If there's no undone command,
// dispatch null as the historyevent command
var undoneCommand:IUndoableCommand;
if (_historyStack.length > 0 && currentPosition != 0) {
//the undone command
undoneCommand = _historyStack[currentPosition];
} else {
undoneCommand = null;
}

this.eventDispatcher.dispatchEvent(new HistoryEvent(HistoryEvent.STEP_BACKWARD_COMPLETE, undoneCommand));

return currentPosition;
}

Expand All @@ -69,9 +104,12 @@ package org.robotlegs.utilities.undoablecommand
positionToMoveTo = currentPosition - numTimes;
}

// Move backward
while(canStepBackward && currentPosition != positionToMoveTo) {
stepBackward();
}

this.eventDispatcher.dispatchEvent(new HistoryEvent(HistoryEvent.REWIND_COMPLETE, currentCommand));
return currentPosition;
}

Expand All @@ -89,36 +127,67 @@ package org.robotlegs.utilities.undoablecommand
positionToMoveTo = currentPosition + numTimes;
}

// Move forward
while(canStepForward && currentPosition != positionToMoveTo) {
stepForward();
}

this.eventDispatcher.dispatchEvent(new HistoryEvent(HistoryEvent.FAST_FORWARD_COMPLETE, currentCommand));

return currentPosition;
}

/**
* @return number of items in history, both forward
* and backward from current position
* @return total number of items in history,
* irrespective of whether they have been undone
*/
public function get numberOfHistoryItems():uint {
return _historyStack.length;
}

/**
* Push a command onto the history stack
* If there are commands that are yet to be redone,
* those commands are lost and this command becomes
* the top of the command stack.
*
* @return position in history stack after this operation
*/
public function push(command:IUndoableCommand):uint {

if (currentPosition != numberOfHistoryItems) {
_historyStack = _historyStack.slice(0, currentPosition);
}
_historyStack.push(command);
// Executes the command

// Execute the command & move pointer forward
stepForward();
return currentPosition;
}

/**
* @return command at the current position in the history stack,
* or null if we're at position 0, or there are simply no commands
* @see currentPosition
*/
public function get currentCommand():IUndoableCommand {
if (_historyStack.length == 0 || currentPosition == 0) {
return null;
}
return _historyStack[currentPosition - 1];
}

/**
* @private
*/
public function toString():String {
var output:String = "";
var count:uint = 0;
for each(var command:IUndoableCommand in _historyStack) {
output += String(count) + String(command) + "\n";
count++;
}
return output;
}
}
}
50 changes: 50 additions & 0 deletions src/org/robotlegs/utilities/undoablecommand/HistoryEvent.as
@@ -0,0 +1,50 @@
package org.robotlegs.utilities.undoablecommand
{
import org.robotlegs.utilities.undoablecommand.interfaces.IUndoableCommand;

public class HistoryEvent extends CommandEvent
{
/**
* Defines the value of the type property of a stepBackward event object.
* Map this event to StepBackwardCommand to trigger an undo action.
*/
public static const STEP_BACKWARD:String = "stepBackward";
/**
* Defines the value of the type property of a stepforward event object.
* Map this event to StepForwardCommand to trigger a redo action.
*/
public static const STEP_FORWARD:String = "stepForward";

/**
* Defines the value of the type property of a stepForwardComplete event object.
*/
public static const STEP_FORWARD_COMPLETE:String = "stepForwardComplete";

/**
* Defines the value of the type property of a stepBackwardComplete event object.
*/
public static const STEP_BACKWARD_COMPLETE:String = "stepBackwardComplete";

/**
* Defines the value of the type property of a rewind event object.
*/
public static const REWIND:String = "rewind";
/**
* Defines the value of the type property of a fastForward event object.
*/
public static const FAST_FORWARD:String = "fastForwardHistory";

/**
* Defines the value of the type property of a rewindComplete event object.
*/
public static const REWIND_COMPLETE:String = "rewindComplete";
/**
* Defines the value of the type property of an fastForwardComplete event object.
*/
public static const FAST_FORWARD_COMPLETE:String = "fastForwardComplete";

public function HistoryEvent(type:String, command:IUndoableCommand = null) {
super(type, command);
}
}
}
Expand Up @@ -2,9 +2,10 @@ package org.robotlegs.utilities.undoablecommand
{

/**
* This command handles it's own history when provided/injected with a CommandHistory object
* This command handles its own history when provided/injected with a CommandHistory object
* All functions assume the CommandHistory dependency (history) has been provided
* Command is only pushed to the CommandHistory when Command is executed.
*
*/
public class ManagedUndoableCommand extends UndoableCommand
{
Expand All @@ -14,6 +15,11 @@ package org.robotlegs.utilities.undoablecommand
*/
private var hasRegisteredWithHistory:Boolean;

/**
* @private
* Flag true after this Command has been stepped back by CommandHistory
*/
private var hasSteppedBack:Boolean;
/**
* Reference to the CommandHistory being used by this Command
*/
Expand All @@ -23,35 +29,45 @@ package org.robotlegs.utilities.undoablecommand
/**
* @inheritDoc
*/
public function ManagedUndoableCommand(autoExecute:Boolean = true, doFunction:Function = null, undoFunction:Function = null) {
super(autoExecute, doFunction, undoFunction);

public function ManagedUndoableCommand(doFunction:Function = null, undoFunction:Function = null) {
super(doFunction, undoFunction);
}

/**
* @throws Error if this Command is not on the top of the history stack. Prevents calling execute Commands out of CommandHistory order
* Executes the command.
* Override this function in your subclasses to implement your command's actions.
* Note command is only automatically pushed to history once we try to execute this command
* @inheritDoc
* @see undoExecute
*/
override protected function doExecute():void {
// Only push to history once we actually try to execute this command
registerIfRequired()

if (history.currentCommand !== this) {
throw new Error("Cannot execute command unless command is first in command history");
if (!hasRegisteredWithHistory) {
hasRegisteredWithHistory = true;
hasExecuted = true;
history.push(this);
}


}

/**
* @throws Error If this Command is not on the top of the history stack. Prevents undoing Commands out of order
* Override this function in your subclasses to implement the undo of the actions performed in doExecute().
* @inheritDoc
* @see doExecute
*/
override protected function undoExecute():void {
if (history.currentCommand != this) {
throw new Error("Cannot undo command unless command is first in command history");
override protected function undoExecute() : void {
if (hasExecuted) {
this.hasExecuted = false;

if (!hasSteppedBack) {
hasSteppedBack = true;
if (history.currentCommand != this) {
throw new Error("Cannot undo command unless command is first in command history");
}

history.stepBackward();
hasSteppedBack = false;
}
}
history.stepBackward();
}

/**
Expand All @@ -61,8 +77,8 @@ package org.robotlegs.utilities.undoablecommand
*/
private function registerIfRequired():void {
if (!hasRegisteredWithHistory) {
history.push(this);
hasRegisteredWithHistory = true;
history.push(this);
}
}
}
Expand Down

0 comments on commit 33b193b

Please sign in to comment.