summaryrefslogtreecommitdiff
path: root/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/FiniteStateMachine.js
diff options
context:
space:
mode:
Diffstat (limited to 'webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/FiniteStateMachine.js')
-rw-r--r--webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/FiniteStateMachine.js1386
1 files changed, 1386 insertions, 0 deletions
diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/FiniteStateMachine.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/FiniteStateMachine.js
new file mode 100644
index 0000000000..7c293eb76b
--- /dev/null
+++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/FiniteStateMachine.js
@@ -0,0 +1,1386 @@
+/* ************************************************************************
+
+ qooxdoo - the new era of web development
+
+ http://qooxdoo.org
+
+ Copyright:
+ 2006 by Derrell Lipman
+
+ License:
+ LGPL 2.1: http://www.gnu.org/licenses/lgpl.html
+
+ Authors:
+ * Derrell Lipman (derrell)
+
+************************************************************************ */
+
+/* ************************************************************************
+
+#module(util_fsm)
+
+************************************************************************ */
+
+/**
+ * A finite state machine.
+ *
+ * See {@see qx.util.finitestatemacine.State} for details on creating States,
+ * and {@see qx.util.finitestatemacine.Transitions} for details on creating
+ * transitions between states.
+ *
+ * *EXPERIMENTAL*
+ * The interface to the finite state machine, states, and transitions is
+ * experimental. It may change in non-backward-compatible ways as more
+ * experience is gained in its use.
+ *
+ * @param machineName {string} The name of this finite state machine
+ *
+ */
+qx.OO.defineClass("qx.util.fsm.FiniteStateMachine", qx.core.Target,
+function(machineName)
+{
+ // Call our superclass' constructor
+ qx.core.Target.call(this);
+
+ // Save the machine name
+ this.setName(machineName);
+
+ // Initialize the states object
+ this._states = { };
+
+ // Initialize the saved-states stack
+ this._savedStates = [ ];
+
+ // Initialize the pending event queue
+ this._eventQueue = [ ];
+
+ // Initialize the blocked events queue
+ this._blockedEvents = [ ];
+
+ // Create the friendlyToObject" object. Each object has as its property
+ // name, the friendly name of the object; and as its property value, the
+ // object itself.
+ this._friendlyToObject = { };
+
+ // Create the "friendlyToHash" object. Each object has as its property
+ // name, the friendly name of the object; and as its property value, the
+ // hash code of the object.
+ this._friendlyToHash = { };
+
+ // Create the "hashToFriendly" object. Each object has as its property
+ // name, the hash code of the object; and as its property value, the
+ // friendly name of the object.
+ this._hashToFriendly = { };
+
+ // Friendly names can be added to groups, for easy manipulation of enabling
+ // and disabling groups of widgets. Track which friendly names are in which
+ // group.
+ this._groupToFriendly = { };
+
+ // We also need to be able to map back from friendly name to the groups it
+ // is in.
+ this._friendlyToGroups = { };
+});
+
+
+/*
+---------------------------------------------------------------------------
+ PROPERTIES
+---------------------------------------------------------------------------
+*/
+
+/**
+ * The name of this finite state machine (for debug messages)
+ */
+qx.OO.addProperty(
+ {
+ name : "name",
+ type : "string"
+ });
+
+/**
+ * The current state of the finite state machine.
+ */
+qx.OO.addProperty(
+ {
+ name : "state",
+ type : "string"
+ });
+
+/**
+ * The previous state of the finite state machine, i.e. the state from which
+ * we most recently transitioned. Note that this could be the same as the
+ * current state if a successful transition brought us back to the same
+ * state.
+ */
+qx.OO.addProperty(
+ {
+ name : "previousState",
+ type : "string"
+ });
+
+/**
+ * The state to which we will be transitioning. This property is valid only
+ * during a Transition's ontransition function and a State's onexit function.
+ * At all other times, it is null.
+ */
+qx.OO.addProperty(
+ {
+ name : "nextState",
+ type : "string"
+ });
+
+
+/**
+ * The maximum number of states which may pushed onto the state-stack. It is
+ * generally a poor idea to have very many states saved on a stack. Following
+ * program logic becomes very difficult, and the code can be highly
+ * unmaintainable. The default should be more than adequate. You've been
+ * warned.
+ */
+qx.OO.addProperty(
+ {
+ name : "maxSavedStates",
+ type : "number",
+ defaultValue : 2
+ });
+
+/*
+---------------------------------------------------------------------------
+ MODIFIER
+---------------------------------------------------------------------------
+*/
+
+
+/*
+---------------------------------------------------------------------------
+ UTILITIES
+---------------------------------------------------------------------------
+*/
+
+
+/**
+ * Add a state to the finite state machine.
+ *
+ * @param state {qx.util.fsm.State}
+ * An object of class qx.util.fsm.State representing a state
+ * which is to be a part of this finite state machine.
+ */
+qx.Proto.addState = function(state)
+{
+ // Ensure that we got valid state info
+ if (! state instanceof qx.util.fsm.State)
+ {
+ throw new Error("Invalid state: not an instance of " +
+ "qx.util.fsm.State");
+ }
+
+ // Retrieve the name of this state
+ var stateName = state.getName();
+
+ // Ensure that the state name doesn't already exist
+ if (stateName in this._states)
+ {
+ throw new Error("State " + state + " already exists");
+ }
+
+ // Add the new state object to the finite state machine
+ this._states[stateName] = state;
+};
+
+
+/**
+ * Replace a state in the finite state machine. This is useful if initially
+ * "dummy" states are created which load the real state table for a series of
+ * operations (and possibly also load the gui associated with the new states
+ * at the same time). Having portions of the finite state machine and their
+ * associated gui pages loaded at run time can help prevent long delays at
+ * application start-up time.
+ *
+ * @param state {qx.util.fsm.State}
+ * An object of class qx.util.fsm.State representing a state
+ * which is to be a part of this finite state machine.
+ *
+ * @param bDispose {boolean}
+ * If <i>true</i>, then dispose the old state object. If <i>false</i>, the
+ * old state object is returned for disposing by the caller.
+ *
+ * @return {Object}
+ * The old state object if it was not disposed; otherwise null.
+ */
+qx.Proto.replaceState = function(state, bDispose)
+{
+ // Ensure that we got valid state info
+ if (! state instanceof qx.util.fsm.State)
+ {
+ throw new Error("Invalid state: not an instance of " +
+ "qx.util.fsm.State");
+ }
+
+ // Retrieve the name of this state
+ var stateName = state.getName();
+
+ // Save the old state object, so we can return it to be disposed
+ var oldState = this._states[stateName];
+
+ // Replace the old state with the new state object.
+ this._states[stateName] = state;
+
+ // Did they request that the old state be disposed?
+ if (bDispose)
+ {
+ // Yup. Mark it to be disposed.
+ oldState._needDispose;
+ }
+
+ return oldState;
+};
+
+
+
+/**
+ * Add an object (typically a widget) that is to be accessed during state
+ * transitions, to the finite state machine.
+ *
+ * @param friendlyName {string}
+ * The friendly name to used for access to the object being added.
+ *
+ * @param obj {Object}
+ * The object to associate with the specified friendly name
+ *
+ * @param groupNames {Array}
+ * An optional list of group names of which this object is a member.
+ */
+qx.Proto.addObject = function(friendlyName, obj, groupNames)
+{
+ var hash = obj.toHashCode();
+ this._friendlyToHash[friendlyName] = hash;
+ this._hashToFriendly[hash] = friendlyName;
+ this._friendlyToObject[friendlyName] = obj;
+
+ // If no groupNames are specified, we're done.
+ if (! groupNames)
+ {
+ return;
+ }
+
+ // Allow either a single group name or an array of group names. If the
+ // former, we convert it to the latter to make the subsequent code simpler.
+ if (typeof(groupNames) == "string")
+ {
+ groupNames = [ groupNames ];
+ }
+
+ // For each group that this friendly name is to be a member of...
+ for (var i = 0; i < groupNames.length; i++)
+ {
+ var groupName = groupNames[i];
+
+ // If the group name doesn't yet exist...
+ if (! this._groupToFriendly[groupName])
+ {
+ // ... then create it.
+ this._groupToFriendly[groupName] = { };
+ }
+
+ // Add the friendly name to the list of names in this group
+ this._groupToFriendly[groupName][friendlyName] = true;
+
+ // If the friendly name group mapping doesn't yet exist...
+ if (! this._friendlyToGroups[friendlyName])
+ {
+ // ... then create it.
+ this._friendlyToGroups[friendlyName] = [ ];
+ }
+
+ // Append this group name to the list of groups this friendly name is in
+ this._friendlyToGroups[friendlyName] =
+ this._friendlyToGroups[friendlyName].concat(groupNames);
+ }
+};
+
+
+/**
+ * Remove an object which had previously been added by {@see #addObject}.
+ *
+ * @param friendlyName {string}
+ * The friendly name associated with an object, specifying which object is
+ * to be removed.
+ */
+qx.Proto.removeObject = function(friendlyName)
+{
+ var hash = this._friendlyToHash[friendlyName];
+
+ // Delete references to any groupos this friendly name was in
+ if (this._friendlyToGroups[friendlyName])
+ {
+ for (groupName in this._friendlyToGroups[friendlyName])
+ {
+ delete this._groupToFriendly[groupName];
+ }
+
+ delete this._friendlyToGroups[friendlyName];
+ }
+
+ // Delete the friendly name
+ delete this._hashToFriendly[hash];
+ delete this._friendlyToHash[friendlyName];
+ delete this._friendlyToObject[friendlyName];
+};
+
+
+/**
+ * Retrieve an object previously saved via {@see #addObject}, using its
+ * Friendly Name.
+ *
+ * @param friendlyName {string}
+ * The friendly name of the object to be retrieved.
+ *
+ * @return {Object}
+ * The object which has the specified friendly name, or undefined if no
+ * object has been associated with that name.
+ */
+qx.Proto.getObject = function(friendlyName)
+{
+ return this._friendlyToObject[friendlyName];
+};
+
+
+/**
+ * Get the friendly name of an object.
+ *
+ * @param obj {Object} The object for which the friendly name is desired
+ *
+ * @return {string}
+ * If the object has been previously registered via {@see #addObject}, then
+ * the friendly name of the object is returned; otherwise, null.
+ */
+qx.Proto.getFriendlyName = function(obj)
+{
+ var hash = obj.toHashCode();
+ return hash ? this._hashToFriendly[hash] : null;
+};
+
+
+/**
+ * Retrieve the list of objects which have registered, via {@see addObject} as
+ * being members of the specified group.
+ *
+ * @param groupName {string}
+ * The name of the group for which the member list is desired.
+ *
+ * @return {Array}
+ * An array containing the friendly names of any objects which are members
+ * of the specified group. The resultant array may be empty.
+ */
+qx.Proto.getGroupObjects = function(groupName)
+{
+ var a = [ ];
+
+ for (var name in this._groupToFriendly[groupName])
+ {
+ a.push(name);
+ }
+
+ return a;
+};
+
+
+/**
+ * Display all of the saved objects and their reverse mappings.
+ */
+qx.Proto.displayAllObjects = function()
+{
+ for (var friendlyName in this._friendlyToHash)
+ {
+ var hash = this._friendlyToHash[friendlyName];
+ var obj = this.getObject(friendlyName);
+ this.debug(friendlyName +
+ " => " +
+ hash);
+ this.debug(" " + hash +
+ " => " +
+ this._hashToFriendly[hash]);
+ this.debug(" " + friendlyName +
+ " => " +
+ this.getObject(friendlyName));
+ this.debug(" " + this.getObject(friendlyName) +
+ " => " +
+ this.getFriendlyName(obj));
+ }
+};
+
+
+/**
+ * Recursively display an object (as debug messages)
+ *
+ * @param obj {Object}
+ * The object to be recursively displayed
+ */
+qx.Proto.debugObject = function(obj)
+{
+ thisClass = this;
+
+ var displayObj = function(obj, level)
+ {
+ var indentStr = "";
+ for (var i = 0; i < level; i++)
+ {
+ indentStr += " ";
+ }
+
+ if (typeof(obj) != "object")
+ {
+ thisClass.debug(indentStr, obj);
+ return;
+ }
+
+ for (var prop in obj)
+ {
+ if (typeof(obj[prop]) == "object")
+ {
+ if (obj[prop] instanceof Array)
+ {
+ thisClass.debug(indentStr + prop + ": " + "Array");
+ }
+ else
+ {
+ thisClass.debug(indentStr + prop + ": " + "Object");
+ }
+
+ displayObj(obj[prop], level + 1);
+ }
+ else
+ {
+ thisClass.debug(indentStr + prop + ": " + obj[prop]);
+ }
+ }
+ }
+
+ displayObj(obj, 0);
+};
+
+
+
+/**
+ * Start (or restart, after it has terminated) the finite state machine from
+ * the starting state. The starting state is defined as the first state added
+ * to the finite state machine.
+ */
+qx.Proto.start = function()
+{
+ var stateName;
+
+ // Set the start state to be the first state which was added to the machine
+ for (stateName in this._states)
+ {
+ this.setState(stateName);
+ this.setPreviousState(null);
+ this.setNextState(null);
+ break;
+ }
+
+ if (! stateName)
+ {
+ throw new Error("Machine started with no available states");
+ }
+
+ var debugFunctions =
+ (qx.Settings.getValueOfClass("qx.util.fsm.FiniteStateMachine",
+ "debugFlags") &
+ qx.util.fsm.FiniteStateMachine.DebugFlags.FUNCTION_DETAIL);
+
+ // Run the actionsBeforeOnentry actions for the initial state
+ if (debugFunctions)
+ {
+ this.debug(this.getName() + "#" + stateName + "#actionsBeforeOnentry");
+ }
+ this._states[stateName].getAutoActionsBeforeOnentry()(this);
+
+ // Run the entry function for the new state, if one is specified
+ if (debugFunctions)
+ {
+ this.debug(this.getName() + "#" + stateName + "#entry");
+ }
+ this._states[stateName].getOnentry()(this, null);
+
+ // Run the actionsAfterOnentry actions for the initial state
+ if (debugFunctions)
+ {
+ this.debug(this.getName() + "#" + stateName + "#actionsAfterOnentry");
+ }
+ this._states[stateName].getAutoActionsAfterOnentry()(this);
+
+};
+
+
+/**
+ * Save the current or previous state on the saved-state stack. A future
+ * transition can then provide, as its nextState value, the class constant:
+ *
+ * qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK
+ *
+ * which will cause the next state to be whatever is at the top of the
+ * saved-state stack, and remove that top element from the saved-state stack.
+ *
+ * @param bCurrent {boolean}
+ * When <i>true</i>, then push the current state onto the stack. This might
+ * be used in a transition, before the state has changed. When
+ * <i>false</i>, then push the previous state onto the stack. This might be
+ * used in an on entry function to save the previous state to return to.
+ */
+qx.Proto.pushState = function(bCurrent)
+{
+ // See if there's room on the state stack for a new state
+ if (this._savedStates.length >= this.getMaxSavedStates())
+ {
+ // Nope. Programmer error.
+ throw new Error("Saved-state stack is full");
+ }
+
+ if (bCurrent)
+ {
+ // Push the current state onto the saved-state stack
+ this._savedStates.push(this.getState());
+ }
+ else
+ {
+ // Push the previous state onto the saved-state stack
+ this._savedStates.push(this.getPreviousState());
+ }
+};
+
+
+/**
+ * Add the specified event to a list of events to be passed to the next state
+ * following state transition.
+ *
+ * @param event {qx.event.type.Event}
+ * The event to add to the event queue for processing after state change.
+ */
+qx.Proto.postponeEvent = function(event)
+{
+ // Add this event to the blocked event queue, so it will be passed to the
+ // next state upon transition.
+ this._blockedEvents.unshift(event);
+};
+
+
+/**
+ * Copy an event
+ *
+ * @param event {qx.event.type.Event}
+ * The event to be copied
+ *
+ * @return {qx.event.type.Event}
+ * The new copy of the provided event
+ */
+qx.Proto.copyEvent = function(event)
+{
+ var e = { };
+ for (var prop in event)
+ {
+ e[prop] = event[prop];
+ }
+
+ return e;
+};
+
+
+/**
+ * Enqueue an event for processing
+ *
+ * @param event {qx.event.type.Event}
+ * The event to be enqueued
+ *
+ * @param bAddAtHead {boolean}
+ * If <i>true</i>, put the event at the head of the queue for immediate
+ * processing. If <i>false</i>, place the event at the tail of the queue so
+ * that it receives in-order processing.
+ */
+qx.Proto.enqueueEvent = function(event, bAddAtHead)
+{
+ // Add the event to the event queue
+ if (bAddAtHead)
+ {
+ // Put event at the head of the queue
+ this._eventQueue.push(event);
+ }
+ else
+ {
+ // Put event at the tail of the queue
+ this._eventQueue.unshift(event);
+ }
+
+ if (qx.Settings.getValueOfClass("qx.util.fsm.FiniteStateMachine",
+ "debugFlags") &
+ qx.util.fsm.FiniteStateMachine.DebugFlags.EVENTS)
+ {
+ if (bAddAtHead)
+ {
+ this.debug(this.getName() + ": Pushed event: " + event.getType());
+ }
+ else
+ {
+ this.debug(this.getName() + ": Queued event: " + event.getType());
+ }
+ }
+};
+
+
+/**
+ * Event listener for all event types in the finite state machine
+ *
+ * @param event {qx.event.type.Event}
+ * The event that was dispatched.
+ */
+qx.Proto.eventListener = function(event)
+{
+ // Events are enqueued upon receipt. Some events are then processed
+ // immediately; other events get processed later. We need to allow the
+ // event dispatcher to free the source event upon our return, so we'll clone
+ // it and enqueue our clone. The source event can then be disposed upon our
+ // return.
+ var e = this.copyEvent(event);
+
+ // Enqueue the new event on the tail of the queue
+ this.enqueueEvent(e, false);
+
+ // Process events
+ this._processEvents();
+};
+
+
+/**
+ * Process all of the events on the event queue.
+ */
+qx.Proto._processEvents = function()
+{
+ // eventListener() can potentially be called while we're processing events
+ if (this._eventProcessingInProgress)
+ {
+ // We were processing already, so don't process concurrently.
+ return;
+ }
+
+ // Track that we're processing events
+ this._eventProcessingInProgress = true;
+
+ // Process each of the events on the event queue
+ while (this._eventQueue.length > 0)
+ {
+ // Pull the next event from the pending event queue
+ var event = this._eventQueue.pop();
+
+ // Run the finite state machine with this event
+ this._run(event);
+
+ // We can now dispose the event
+ event.dispose();
+ }
+
+ // We're no longer processing events
+ this._eventProcessingInProgress = false;
+};
+
+/**
+ * Run the finite state machine to process a single event.
+ *
+ * @param event {qx.event.type.Event}
+ * An event that has been dispatched. The event may be handled (if the
+ * current state handles this event type), queued (if the current state
+ * blocks this event type), or discarded (if the current state neither
+ * handles nor blocks this event type).
+ */
+qx.Proto._run = function(event)
+{
+ // For use in generated functions...
+ var fsm = this;
+
+ // State name variables
+ var thisState;
+ var nextState;
+ var prevState;
+
+ // The current State object
+ var currentState;
+
+ // The transitions available in the current State
+ var transitions;
+
+ // Events handled by the current State
+ var e;
+
+ // The action to take place upon receipt of a particular event
+ var action;
+
+ // Get the debug flags
+ var debugFlags =
+ (qx.Settings.getValueOfClass("qx.util.fsm.FiniteStateMachine",
+ "debugFlags"));
+
+ // Allow slightly faster access to determine if debug is enableda
+ var debugEvents =
+ debugFlags & qx.util.fsm.FiniteStateMachine.DebugFlags.EVENTS;
+ var debugTransitions =
+ debugFlags & qx.util.fsm.FiniteStateMachine.DebugFlags.TRANSITIONS;
+ var debugFunctions =
+ debugFlags & qx.util.fsm.FiniteStateMachine.DebugFlags.FUNCTION_DETAIL;
+ var debugObjectNotFound =
+ debugFlags & qx.util.fsm.FiniteStateMachine.DebugFlags.OBJECT_NOT_FOUND;
+
+ if (debugEvents)
+ {
+ this.debug(this.getName() + ": Process event: " + event.getType());
+ }
+
+ // Get the current state name
+ thisState = this.getState();
+
+ // Get the current State object
+ currentState = this._states[thisState];
+
+ // Get a list of the transitions available from this state
+ transitions = currentState.transitions;
+
+ // Determine how to handle this event
+ e = currentState.getEvents()[event.getType()];
+
+ // See if we actually found this event type
+ if (! e)
+ {
+ if (this.debugEvents)
+ {
+ this.debug(this.getName() + ": Event '" + event.getType() + "'" +
+ " not handled. Ignoring.");
+ }
+ return;
+ }
+
+ // We might have found a constant (PREDICATE or BLOCKED) or an object with
+ // each property name being the friendly name of a saved object, and the
+ // property value being one of the constants (PREDICATE or BLOCKED).
+ if (typeof(e) == "object")
+ {
+ // Individual objects are listed. Ensure target is a saved object
+ var friendly = this.getFriendlyName(event.getTarget());
+ if (! friendly)
+ {
+ // Nope, it doesn't seem so. Just discard it.
+ if (debugObjectNotFound)
+ {
+ this.debug(this.getName() + ": Could not find friendly name for '" +
+ event.getType() + "' on '" + event.getTarget() + "'");
+ }
+ return;
+ }
+
+ action = e[friendly];
+ }
+ else
+ {
+ action = e;
+ }
+
+ switch(action)
+ {
+ case qx.util.fsm.FiniteStateMachine.EventHandling.PREDICATE:
+ // Process this event. One of the transitions should handle it.
+ break;
+
+ case qx.util.fsm.FiniteStateMachine.EventHandling.BLOCKED:
+ // This event is blocked. Enqueue it for later, and get outta here.
+ this._blockedEvents.unshift(event);
+ return;
+
+ default:
+ // See if we've been given an explicit transition name
+ if (typeof(action) == "string")
+ {
+ // Yup! Ensure that it exists
+ if (transitions[action])
+ {
+ // Yup. Create a transitions object containing only this transition.
+ var trans = transitions[action];
+ transitions = { };
+ transitions[action] = trans;
+ }
+ else
+ {
+ throw new Error("Explicit transition " + action + " does not exist");
+ }
+
+ break;
+ }
+ }
+
+ // We handle the event. Try each transition in turn until we find one that
+ // is acceptable.
+ for (var t in transitions)
+ {
+ var trans = transitions[t];
+
+ // Does the predicate allow use of this transition?
+ switch(trans.getPredicate()(this, event))
+ {
+ case true:
+ // Transition is allowed. Proceed.
+ break;
+
+ case false:
+ // Transition is not allowed. Try next transition.
+ continue;
+
+ case null:
+ // Transition indicates not to try further transitions
+ return;
+
+ default:
+ throw new Error("Transition " + thisState + ":" + t +
+ " returned a value other than true, false, or null.");
+ return;
+ }
+
+ // We think we can transition to the next state. Set next state.
+ nextState = trans.getNextState();
+ if (typeof(nextState) == "string")
+ {
+ // We found a literal state name. Ensure it exists.
+ if (! nextState in this._states)
+ {
+ throw new Error("Attempt to transition to nonexistent state " +
+ nextState);
+ }
+
+ // It exists. Track it being the next state.
+ this.setNextState(nextState);
+ }
+ else
+ {
+ // If it's not a string, nextState must be a StateChange constant
+ switch(nextState)
+ {
+ case qx.util.fsm.FiniteStateMachine.StateChange.CURRENT_STATE:
+ // They want to remain in the same state.
+ nextState = thisState;
+ this.setNextState(nextState)
+ break;
+
+ case qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK:
+ // Switch to the state at the top of the state stack.
+ if (this._savedStates.length == 0)
+ {
+ throw new Error("Attempt to transition to POP_STATE_STACK " +
+ "while state stack is empty.");
+ }
+
+ // Pop the state stack to retrieve the state to transition to
+ nextState = this._savedStates.pop();
+ this.setNextState(nextState);
+ break;
+
+ default:
+ throw new Error("Internal error: invalid nextState");
+ break;
+ }
+ }
+
+ // Run the actionsBeforeOntransition actions for this transition
+ if (debugFunctions)
+ {
+ this.debug(this.getName() + "#" + thisState + "#" + t +
+ "#autoActionsBeforeOntransition");
+ }
+ trans.getAutoActionsBeforeOntransition()(this);
+
+ // Run the 'ontransition' function
+ if (debugFunctions)
+ {
+ this.debug(this.getName() + "#" + thisState + "#" + t + "#ontransition");
+ }
+ trans.getOntransition()(this, event);
+
+ // Run the autoActionsAfterOntransition actions for this transition
+ if (debugFunctions)
+ {
+ this.debug(this.getName() + "#" + thisState + "#" + t +
+ "#autoActionsAfterOntransition");
+ }
+ trans.getAutoActionsAfterOntransition()(this);
+
+ // Run the autoActionsBeforeOnexit actions for the old state
+ if (debugFunctions)
+ {
+ this.debug(this.getName() + "#" + thisState +
+ "#autoActionsBeforeOnexit");
+ }
+ currentState.getAutoActionsBeforeOnexit()(this);
+
+ // Run the exit function for the old state
+ if (debugFunctions)
+ {
+ this.debug(this.getName() + "#" + thisState + "#exit");
+ }
+ currentState.getOnexit()(this, event);
+
+ // Run the autoActionsAfterOnexit actions for the old state
+ if (debugFunctions)
+ {
+ this.debug(this.getName() + "#" + thisState + "#autoActionsAfterOnexit");
+ }
+ currentState.getAutoActionsAfterOnexit()(this);
+
+ // If this state has been replaced and we're supposed to dispose it...
+ if (currentState._needDispose)
+ {
+ // ... then dispose it now that it's no longer in use
+ currentState.dispose();
+ }
+
+ // Reset currentState to the new state object
+ currentState = this._states[this.getNextState()];
+
+ // set previousState and state, and clear nextState, for transition
+ this.setPreviousState(thisState);
+ this.setState(this.getNextState());
+ this.setNextState(null);
+ prevState = thisState;
+ thisState = nextState;
+ nextState = undefined;
+
+ // Run the autoActionsBeforeOnentry actions for the new state
+ if (debugFunctions)
+ {
+ this.debug(this.getName() + "#" + thisState +
+ "#autoActionsBeforeOnentry");
+ }
+ currentState.getAutoActionsBeforeOnentry()(this);
+
+ // Run the entry function for the new state, if one is specified
+ if (debugFunctions)
+ {
+ this.debug(this.getName() + "#" + thisState + "#entry");
+ }
+ currentState.getOnentry()(this, event);
+
+ // Run the autoActionsAfterOnentry actions for the new state
+ if (debugFunctions)
+ {
+ this.debug(this.getName() + "#" + thisState +
+ "#autoActionsAfterOnentry");
+ }
+ currentState.getAutoActionsAfterOnentry()(this);
+
+ // Add the blocked events to the pending event queue
+ if (this._blockedEvents.length > 0)
+ {
+ this._eventQueue.unshift(this._blockedEvents);
+ }
+
+ // The blocked event list is now empty
+ this._blockedEvents = [ ];
+
+ // Ensure that all actions have been flushed
+ qx.ui.core.Widget.flushGlobalQueues();
+
+ if (debugTransitions)
+ {
+ this.debug(this.getName() + "#" + prevState + " => " +
+ this.getName() + "#" + thisState);
+ }
+
+ // See ya!
+ return;
+ }
+
+ if (debugTransitions)
+ {
+ this.debug(this.getName() + "#" + thisState +
+ ": event '" + event.getType() + "'" +
+ ": no transition found. No state change.");
+ }
+};
+
+
+
+/*
+---------------------------------------------------------------------------
+ EVENT LISTENERS
+---------------------------------------------------------------------------
+*/
+
+
+
+/*
+---------------------------------------------------------------------------
+ CLASS CONSTANTS
+---------------------------------------------------------------------------
+*/
+
+/**
+ * Constants which may be values of the nextState member in the transitionInfo
+ * parameter of the Transition constructor.
+ */
+qx.Class.StateChange =
+{
+ /** When used as a nextState value, means remain in current state */
+ CURRENT_STATE : 1,
+
+ /** When used as a nextState value, means go to most-recently pushed state */
+ POP_STATE_STACK : 2,
+
+ /** When used as a nextState value, means terminate this state machine */
+ TERMINATE : 3
+};
+
+
+/**
+ * Constants for use in the events member of the transitionInfo parameter of
+ * the Transition constructor.
+ */
+qx.Class.EventHandling =
+{
+ /**
+ * This event is handled by this state, but the predicate of a transition
+ * will determine whether to use that transition.
+ */
+ PREDICATE : 1,
+
+ /** Enqueue this event for possible use by the next state */
+ BLOCKED : 2
+};
+
+/**
+ * Debug bitmask values. Set the debug flags from the application by or-ing
+ * together bits, akin to this:
+ *
+ * qx.Settings.setCustomOfClass(
+ * "qx.util.fsm.FiniteStateMachine",
+ * "debugFlags",
+ * (qx.util.fsm.FiniteStateMachine.DebugFlags.EVENTS |
+ * qx.util.fsm.FiniteStateMachine.DebugFlags.TRANSITIONS |
+ * qx.util.fsm.FiniteStateMachine.DebugFlags.FUNCTION_DETAIL |
+ * qx.util.fsm.FiniteStateMachine.DebugFlags.OBJECT_NOT_FOUND));
+ */
+qx.Class.DebugFlags =
+{
+ /** Show events */
+ EVENTS : 1,
+
+ /** Show transitions */
+ TRANSITIONS : 2,
+
+ /** Show individual function invocations during transitions */
+ FUNCTION_DETAIL : 4,
+
+ /** When object friendly names are referenced but not found, show message */
+ OBJECT_NOT_FOUND : 8
+};
+
+
+/*
+---------------------------------------------------------------------------
+ CLASS DEFAULT SETTINGS
+---------------------------------------------------------------------------
+*/
+
+/**
+ * Debug flags: bitmap of DebugFlags (see Class Constants).
+ */
+qx.Settings.setDefault(
+ "debugFlags",
+ (qx.util.fsm.FiniteStateMachine.DebugFlags.EVENTS |
+ qx.util.fsm.FiniteStateMachine.DebugFlags.TRANSITIONS |
+ qx.util.fsm.FiniteStateMachine.DebugFlags.OBJECT_NOT_FOUND));
+
+
+/*
+---------------------------------------------------------------------------
+ CLASS FUNCTIONS
+---------------------------------------------------------------------------
+*/
+
+/**
+ * Common function used by {qx.util.fsm.State} and
+ * {qx.util.fsm.Transition} for checking the value provided for
+ * auto actions.
+ *
+ * Auto-action property values passed to us look akin to:
+ *
+ * <pre>
+ * {
+ * // The name of a function.
+ * "setEnabled" :
+ * [
+ * {
+ * // The parameter value(s), thus "setEnabled(true);"
+ * "parameters" : [ true ],
+ *
+ * // The function would be called on each object:
+ * // this.getObject("obj1").setEnabled(true);
+ * // this.getObject("obj2").setEnabled(true);
+ * "objects" : [ "obj1", "obj2" ]
+ *
+ * // And similarly for each object in each specified group.
+ * "groups" : [ "group1", "group2" ],
+ * }
+ * ];
+ *
+ * "setColor" :
+ * [
+ * {
+ * "parameters" : [ "blue" ]
+ * "groups" : [ "group3", "group4" ],
+ * "objects" : [ "obj3", "obj4" ]
+ * }
+ * ];
+ * };
+ * </pre>
+ *
+ * @param actionType {string}
+ * The name of the action being validated (for debug messages)
+ *
+ * @param propValue {Object}
+ * The property value which is being validated
+ *
+ * @param propData
+ * Not used
+ */
+qx.Class._commonCheckAutoActions = function(actionType, propValue, propData)
+{
+ // Validate that we received an object property value
+ if (typeof(propValue) != "object")
+ {
+ throw new Error("Invalid " + actionType + " value: " + typeof(propValue));
+ }
+
+ // We'll create a function to do the requested actions. Initialize the
+ // string into which we'll generate the common fragment added to the
+ // function for each object.
+ var funcFragment;
+
+ // Here, we'll keep the function body. Initialize a try block.
+ var func =
+ "try" +
+ "{";
+
+ var param;
+ var objectAndGroupList;
+
+ // Retrieve the function request, e.g.
+ // "enabled" :
+ for (var f in propValue)
+ {
+ // Get the function request value object, e.g.
+ // "setEnabled" :
+ // [
+ // {
+ // "parameters" : [ true ],
+ // "objects" : [ "obj1", "obj2" ]
+ // "groups" : [ "group1", "group2" ],
+ // }
+ // ];
+ var functionRequest = propValue[f];
+
+ // The function request value should be an object
+ if (! functionRequest instanceof Array)
+ {
+ throw new Error("Invalid function request type: " +
+ "expected array, found " + typeof(functionRequest));
+ }
+
+ // For each function request...
+ for (var i = 0; i < functionRequest.length; i++)
+ {
+ // Retreive the object and group list object
+ objectAndGroupList = functionRequest[i];
+
+ // The object and group list should be an object, e.g.
+ // {
+ // "parameters" : [ true ],
+ // "objects" : [ "obj1", "obj2" ]
+ // "groups" : [ "group1", "group2" ],
+ // }
+ if (typeof(objectAndGroupList) != "object")
+ {
+ throw new Error("Invalid function request parameter type: " +
+ "expected object, found " +
+ typeof(functionRequest[param]));
+ }
+
+ // Retrieve the parameter list
+ params = objectAndGroupList["parameters"];
+
+ // If it didn't exist, ...
+ if (! params)
+ {
+ // ... use an empty array.
+ params = [ ];
+ }
+ else
+ {
+ // otherwise, ensure we got an array
+ if (! params instanceof Array)
+ {
+ throw new Error("Invalid function parameters: " +
+ "expected array, found " + typeof(params));
+ }
+ }
+
+ // Create the function to call on each object. The object on which the
+ // function is called will be prepended later.
+ funcFragment = f + "(";
+
+ // For each parameter...
+ for (var j = 0; j < params.length; j++)
+ {
+ // If this isn't the first parameter, add a separator
+ if (j != 0)
+ {
+ funcFragment += ",";
+ }
+
+ if (typeof(params[j]) == "function")
+ {
+ // If the parameter is a function, arrange for it to be called
+ // at run time.
+ funcFragment += "(" + params[j] + ")(fsm)";
+ }
+ else if (typeof(params[j]) == "string")
+ {
+ // If the parameter is a string, quote it.
+ funcFragment += '"' + params[j] + '"';
+ }
+ else
+ {
+ // Otherwise, just add the parameter's literal value
+ funcFragment += params[j];
+ }
+ }
+
+ // Complete the function call
+ funcFragment += ")";
+
+ // Get the "objects" list, e.g.
+ // "objects" : [ "obj1", "obj2" ]
+ var a = objectAndGroupList["objects"];
+
+ // Was there an "objects" list?
+ if (! a)
+ {
+ // Nope. Simplify code by creating an empty array.
+ a = [ ];
+ }
+ else if (! a instanceof Array)
+ {
+ throw new Error("Invalid 'objects' list: expected array, got " +
+ typeof(a));
+ }
+
+ for (var j = 0; j < a.length; j++)
+ {
+ // Ensure we got a string
+ if (typeof(a[j]) != "string")
+ {
+ throw new Error("Invalid friendly name in 'objects' list: " + a[j]);
+ }
+
+ func += " fsm.getObject('" + a[j] + "')." + funcFragment + ";";
+ }
+
+ // Get the "groups" list, e.g.
+ // "groups" : [ "group1, "group2" ]
+ var g = objectAndGroupList["groups"];
+
+ // Was a "groups" list found?
+ if (g)
+ {
+ // Yup. Ensure it's an array.
+ if (! g instanceof Array)
+ {
+ throw new Error("Invalid 'groups' list: expected array, got " +
+ typeof(g));
+ }
+
+ for (var groupName in g)
+ {
+ // Arrange to call the function on each object in each group
+ func +=
+ " var groupObjects = " +
+ " fsm.getGroupObjects('" + g[groupName] + "');" +
+ " for (var i = 0; i < groupObjects.length; i++)" +
+ " {" +
+ " var objName = groupObjects[i];" +
+ " fsm.getObject(objName)." + funcFragment + ";" +
+ " }";
+ }
+ }
+ }
+ }
+
+ // Terminate the try block for function invocations
+ func +=
+ "}" +
+ "catch(e)" +
+ "{" +
+ " fsm.debug(e);" +
+ "}";
+
+// o = new qx.core.Object();
+// o.debug("Dynamically created " + actionType + "(fsm) { " + func + " }");
+
+ // We've now built the entire body of a function that implements calls to
+ // each of the requested automatic actions. Create and return the function,
+ // which will become the property value.
+ return new Function("fsm", func);
+};
+
+
+
+/*
+---------------------------------------------------------------------------
+ DISPOSER
+---------------------------------------------------------------------------
+*/
+
+qx.Proto.dispose = function()
+{
+ var e;
+ var s;
+
+ if (this.getDisposed()) {
+ return true;
+ }
+
+ while (this._savedStates.length > 0)
+ {
+ s = this._savedStates.pop();
+ s = null;
+ }
+ this._savedStates = null;
+
+ while (this._eventQueue.length > 0)
+ {
+ e = this._eventQueue.pop();
+ e.dispose();
+ e = null;
+ }
+ this._eventQueue = null;
+
+ while (this._blockedEvents.length > 0)
+ {
+ e = this._blockedEvents.pop();
+ e.dispose();
+ e = null;
+ }
+
+ for (var s in this._states)
+ {
+ this._states[s].dispose();
+ this._states[s] = null;
+ delete this._states[s];
+ }
+ this._states = null;
+
+ return qx.core.Target.prototype.dispose.call(this);
+}