diff options
Diffstat (limited to 'webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm')
4 files changed, 2593 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); +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/State.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/State.js new file mode 100644 index 0000000000..fc054e304a --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/State.js @@ -0,0 +1,613 @@ +/* ************************************************************************ + + 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) +#require(qx.util.fsm.FiniteStateMachine) + +************************************************************************ */ + +/** + * Create a new state which may be added to a finite state machine. + * + * *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 + * stateName - + * The name of this state. This is the name which may be referenced in + * objects of class qx.util.fsm.Transition, when passing of + * the the transition's predicate means transition to this state. + * + * @param + * stateInfo - + * An object containing any of the following properties: + * + * onentry - + * A function which is called upon entry to the state. Its signature is + * function(fsm, event) and it is saved in the onentry property of the + * state object. (This function is called after the Transition's action + * function and after the previous state's onexit function.) + * + * In the onentry function: + * + * fsm - + * The finite state machine object to which this state is attached. + * + * event - + * The event that caused the finite state machine to run + * + * onexit - + * A function which is called upon exit from the state. Its signature + * is function(fsm, event) and it is saved in the onexit property of the + * state object. (This function is called after the Transition's action + * function and before the next state's onentry function.) + * + * In the onexit function: + * + * fsm - + * The finite state machine object to which this state is attached. + * + * event - + * The event that caused the finite state machine to run + * + * autoActionsBeforeOnentry - + * autoActionsAfterOnentry - + * autoActionsBeforeOnexit - + * autoActionsAfterOnexit - + * Automatic actions which take place at the time specified by the + * property name. In all cases, the action takes place immediately + * before or after the specified function. + * + * The property value for each of these properties is an object which + * describes some number of functions to invoke on a set of specified + * objects (typically widgets). + * + * An example, using autoActionsBeforeOnentry, might look like this: + * + * "autoActionsBeforeOnentry" : + * { + * // The name of a function. + * "enabled" : + * [ + * { + * // The parameter value, 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" ] + * } + * ], + * + * // The name of another function. + * "visible" : + * [ + * { + * // The parameter value, thus "setEnabled(true);" + * "parameters" : [ false ], + * + * // The function would be called on each object and group, as + * // described above. + * "objects" : [ "obj3", "obj4" ], + * "groups" : [ "group3", "group4" ] + * } + * ] + * }; + * + * + * events (required) - + * A description to the finite state machine of how to handle a + * particular event, optionally associated with a specific target object + * on which the event was dispatched. This should be an object + * containing one property for each event which is either handled or + * blocked. The property name should be the event name. The property + * value should be one of: + * + * (a) qx.util.fsm.FiniteStateMachine.EventHandling.PREDICATE + * + * (b) qx.util.fsm.FiniteStateMachine.EventHandling.BLOCKED + * + * (c) a string containing the name of an explicit Transition to use + * + * (d) an object where each property name is the Friendly Name of an + * object (meaning that this rule applies if both the event and + * the event's target object's Friendly Name match), and its + * property value is one of (a), (b) or (c), above. + * + * This object is saved in the events property of the state object. + * + * Additional properties may be provided in stateInfo. They will not be + * used by the finite state machine, but will be available via + * this.getUserData("<propertyName>") during the state's onentry and + * onexit functions. + */ +qx.OO.defineClass("qx.util.fsm.State", qx.core.Object, +function(stateName, stateInfo) +{ + // Call our superclass' constructor + qx.core.Object.call(this, true); + + // Save the state name + this.setName(stateName); + + // Ensure they passed in an object + if (typeof(stateInfo) != "object") + { + throw new Error("State info must be an object"); + } + + // Save data from the stateInfo object + for (var field in stateInfo) + { + // If we find one of our properties, call its setter. + switch(field) + { + case "onentry": + this.setOnentry(stateInfo[field]); + break; + + case "onexit": + this.setOnexit(stateInfo[field]); + break; + + case "autoActionsBeforeOnentry": + this.setAutoActionsBeforeOnentry(stateInfo[field]); + break; + + case "autoActionsAfterOnentry": + this.setAutoActionsAfterOnentry(stateInfo[field]); + break; + + case "autoActionsBeforeOnexit": + this.setAutoActionsBeforeOnexit(stateInfo[field]); + break; + + case "autoActionsAfterOnexit": + this.setAutoActionsAfterOnexit(stateInfo[field]); + break; + + case "events": + this.setEvents(stateInfo[field]); + break; + + default: + // Anything else is user-provided data for their own use. Save it. + this.setUserData(field, stateInfo[field]); + + // Log it in case it was a typo and they intended a built-in field + this.debug("State " + stateName + ": " + + "Adding user-provided field to state: " + field); + + break; + } + } + + + // Check for required but missing properties + if (! this.getEvents()) + { + throw new Error("The events object must be provided in new state info"); + } + + + // Initialize the transition list + this.transitions = { }; +}); + + + + +/* +--------------------------------------------------------------------------- + PROPERTIES +--------------------------------------------------------------------------- +*/ + +/** + * The name of this state. This name may be used as a Transition's nextState + * value, or an explicit next state in the 'events' handling list in a State. + */ +qx.OO.addProperty( + { + name : "name", + type : "string" + }); + +/** + * The onentry function for this state. This is documented in the + * constructor, and is typically provided through the constructor's stateInfo + * object, but it is also possible (but highly NOT recommended) to change this + * dynamically. + */ +qx.OO.addProperty( + { + name : "onentry", + defaultValue : function(fsm, event) { } + }); + +/** + * The onexit function for this state. This is documented in the constructor, + * and is typically provided through the constructor's stateInfo object, but + * it is also possible (but highly NOT recommended) to change this + * dynamically. + */ +qx.OO.addProperty( + { + name : "onexit", + defaultValue : function(fsm, event) { } + }); + +/** + * Automatic actions to take prior to calling the state's onentry function. + * + * The value passed to setAutoActionsBeforeOnentry() should like something + * akin to: + * + * "autoActionsBeforeOnentry" : + * { + * // The name of a function. This would become "setEnabled(" + * "enabled" : + * [ + * { + * // The parameter value, 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" ], + * } + * ]; + * }; + */ +qx.OO.addProperty( + { + name : "autoActionsBeforeOnentry", + defaultValue : function(fsm, event) { } + }); + +/** + * Automatic actions to take after return from the state's onentry function. + * + * The value passed to setAutoActionsAfterOnentry() should like something akin + * to: + * + * "autoActionsAfterOnentry" : + * { + * // The name of a function. This would become "setEnabled(" + * "enabled" : + * [ + * { + * // The parameter value, 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" ], + * } + * ]; + * }; + */ +qx.OO.addProperty( + { + name : "autoActionsAfterOnentry", + defaultValue : function(fsm, event) { } + }); + +/** + * Automatic actions to take prior to calling the state's onexit function. + * + * The value passed to setAutoActionsBeforeOnexit() should like something akin + * to: + * + * "autoActionsBeforeOnexit" : + * { + * // The name of a function. This would become "setEnabled(" + * "enabled" : + * [ + * { + * // The parameter value, 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" ], + * } + * ]; + * }; + */ +qx.OO.addProperty( + { + name : "autoActionsBeforeOnexit", + defaultValue : function(fsm, event) { } + }); + + +/** + * Automatic actions to take after returning from the state's onexit function. + * + * The value passed to setAutoActionsAfterOnexit() should like something akin + * to: + * + * "autoActionsBeforeOnexit" : + * { + * // The name of a function. This would become "setEnabled(" + * "enabled" : + * [ + * { + * // The parameter value, 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" ], + * } + * ]; + * }; + */ +qx.OO.addProperty( + { + name : "autoActionsAfterOnexit", + defaultValue : function(fsm, event) { } + }); + + +/** + * The object representing handled and blocked events for this state. This is + * documented in the constructor, and is typically provided through the + * constructor's stateInfo object, but it is also possible (but highly NOT + * recommended) to change this dynamically. + */ +qx.OO.addProperty( + { + name : "events" + }); + + + +/* +--------------------------------------------------------------------------- + MODIFIER +--------------------------------------------------------------------------- +*/ + +qx.Proto._checkName = function(propValue, propData) +{ + // Ensure that we got a valid state name + if (typeof(propValue) != "string" || propValue.length < 1) + { + throw new Error("Invalid state name"); + } + + return propValue; +}; + +qx.Proto._checkOnentry = function(propValue, propData) +{ + // Validate the onentry function + switch(typeof(propValue)) + { + case "undefined": + // None provided. Convert it to a null function + return function(fsm, event) {}; + + case "function": + // We're cool. No changes required + return propValue; + + default: + throw new Error("Invalid onentry type: " + typeof(propValue)); + return null; + } +}; + +qx.Proto._checkOnexit = function(propValue, propData) +{ + // Validate the onexit function + switch(typeof(propValue)) + { + case "undefined": + // None provided. Convert it to a null function + return function(fsm, event) {}; + + case "function": + // We're cool. No changes required + return propValue; + + default: + throw new Error("Invalid onexit type: " + typeof(propValue)); + return null; + } +}; + +qx.Proto._checkEvents = function(propValue, propData) +{ + // Validate that events is an object + if (typeof(propValue) != "object") + { + throw new Error("events must be an object"); + } + + // Confirm that each property is a valid value + // The property value should be one of: + // + // (a) qx.util.fsm.FiniteStateMachine.EventHandling.PREDICATE + // + // (b) qx.util.fsm.FiniteStateMachine.EventHandling.BLOCKED + // + // (c) a string containing the name of an explicit Transition to use + // + // (d) an object where each property name is the Friendly Name of an + // object (meaning that this rule applies if both the event and + // the event's target object's Friendly Name match), and its + // property value is one of (a), (b) or (c), above. + for (var e in propValue) + { + var action = propValue[e]; + if (typeof(action) == "number" && + action != qx.util.fsm.FiniteStateMachine.EventHandling.PREDICATE && + action != qx.util.fsm.FiniteStateMachine.EventHandling.BLOCKED) + { + throw new Error("Invalid numeric value in events object: " + + e + ": " + action); + } + else if (typeof(action) == "object") + { + for (action_e in action) + { + if (typeof(action[action_e]) == "number" && + action != qx.util.fsm.FiniteStateMachine.EventHandling.PREDICATE && + action != qx.util.fsm.FiniteStateMachine.EventHandling.BLOCKED) + { + throw new Error("Invalid numeric value in events object " + + "(" + e + "): " + + action_e + ": " + action[action_e]); + } + else if (typeof(action[action_e]) != "string") + { + throw new Error("Invalid value in events object " + + "(" + e + "): " + + action_e + ": " + action[action_e]); + } + } + } + else if (typeof(action) != "string") + { + throw new Error("Invalid value in events object: " + + e + ": " + propValue[e]); + } + } + + // We're cool. No changes required. + return propValue; +}; + +qx.Proto._checkAutoActionsBeforeOnentry = function(propValue, propData) +{ + return qx.util.fsm.FiniteStateMachine._commonCheckAutoActions( + "autoActionsBeforeOnentry", + propValue, + propData); +}; + +qx.Proto._checkAutoActionsAfterOnentry = function(propValue, propData) +{ + return qx.util.fsm.FiniteStateMachine._commonCheckAutoActions( + "autoActionsAfterOnentry", + propValue, + propData); +}; + +qx.Proto._checkAutoActionsBeforeOnexit = function(propValue, propData) +{ + return qx.util.fsm.FiniteStateMachine._commonCheckAutoActions( + "autoActionsBeforeOnexit", + propValue, + propData); +}; + +qx.Proto._checkAutoActionsAfterOnexit = function(propValue, propData) +{ + return qx.util.fsm.FiniteStateMachine._commonCheckAutoActions( + "autoActionsAfterOnexit", + propValue, + propData); +}; + + +/* +--------------------------------------------------------------------------- + UTILITIES +--------------------------------------------------------------------------- +*/ + +/** + * Add a transition to a state + * + * @param trans {qx.util.fsm.Transition} + * An object of class qx.util.fsm.Transition representing a + * transition which is to be a part of this state. + */ +qx.Proto.addTransition = function(trans) +{ + // Ensure that we got valid transition info + if (! trans instanceof qx.util.fsm.Transition) + { + throw new Error("Invalid transition: not an instance of " + + "qx.util.fsm.Transition"); + } + + // Add the new transition object to the state + this.transitions[trans.getName()] = trans; +}; + + + + +/* +--------------------------------------------------------------------------- + EVENT LISTENERS +--------------------------------------------------------------------------- +*/ + + + +/* +--------------------------------------------------------------------------- + CLASS CONSTANTS +--------------------------------------------------------------------------- +*/ + + + +/* +--------------------------------------------------------------------------- + DISPOSER +--------------------------------------------------------------------------- +*/ + +qx.Proto.dispose = function() +{ + if (this.getDisposed()) { + return true; + } + + return qx.core.Object.prototype.dispose.call(this); +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/Transition.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/Transition.js new file mode 100644 index 0000000000..e417e23298 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/Transition.js @@ -0,0 +1,384 @@ +/* ************************************************************************ + + 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) +#require(qx.util.fsm.FiniteStateMachine) + +************************************************************************ */ + +/** + * Create a new possible transition from one state to another. + * + * *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 transitionName {string} + * The name of this transition, used in debug messages. + * + * @param transitionInfo {Object} + * An object optionally containing any of the following properties: + * + * predicate - + * A function which is called to determine whether this transition is + * acceptable. An acceptable transition will cause the transition's + * "ontransition" function to be run, the current state's "onexit" + * function to be run, and the new state's "onentry" function to be run. + * + * The predicate function's signature is function(fsm, event) and it is + * saved in the predicate property of the transition object. In the + * predicate function: + * + * fsm - + * The finite state machine object to which this state is attached. + * + * event - + * The event that caused a run of the finite state machine + * + * The predicate function should return one of the following three + * values: + * + * - true means the transition is acceptable + * + * - false means the transition is not acceptable, and the next + * transition (if one exists) should be tried to determine if it is + * acceptable + * + * - null means that the transition determined that no further + * transitions should be tried. This might be used when the + * transition ascertained that the event is for a target that is not + * available in the current state, and the event has called + * fsm.queueEvent() to have the event delivered upon state + * transition. + * + * It is possible to create a default predicate -- one that will cause a + * transition to be acceptable always -- by either not providing a + * predicate property, or by explicitely either setting the predicate + * property to 'true' or setting it to a function that unconditionally + * returns 'true'. This default transition should, of course, always be + * the last transition added to a state, since no transition added after + * it will ever be tried. + * + * nextState - + * The state to which we transition, if the predicate returns true + * (meaning the transition is acceptable). The value of nextState may + * be: + * + * - a string, the state name of the state to transition to + * + * - One of the constants: + * - qx.util.fsm.FiniteStateMachine.StateChange.CURRENT_STATE: + * Remain in whatever is the current state + * - qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK: + * Transition to the state at the top of the saved-state stack, + * and remove the top element from the saved-state stack. + * Elements are added to the saved-state stack using + * fsm.pushState(). It is an error if no state exists on the + * saved-state stack. + * - qx.util.fsm.FiniteStateMachine.StateChange.TERMINATE: + * TBD + * + * autoActionsBeforeOntransition - + * autoActionsAfterOntransition - + * Automatic actions which take place at the time specified by the + * property name. In all cases, the action takes place immediately + * before or after the specified function. + * + * The property value for each of these properties is an object which + * describes some number of functions to invoke on a set of specified + * objects (typically widgets). + * + * See {@see qx.util.fsm.State} for an example of autoActions. + * + * ontransition - + * A function which is called if the predicate function for this + * transition returns true. Its signature is function(fsm, event) and + * it is saved in the ontransition property of the transition object. + * In the ontransition function: + * + * fsm - + * The finite state machine object to which this state is attached. + * + * event - + * The event that caused a run of the finite state machine + * + * Additional properties may be provided in transInfo. They will not be + * used by the finite state machine, but will be available via + * this.getUserData("<propertyName>") during the transition's predicate + * and ontransition functions. + */ +qx.OO.defineClass("qx.util.fsm.Transition", qx.core.Object, +function(transitionName, transitionInfo) +{ + // Call our superclass' constructor + qx.core.Object.call(this, true); + + // Save the state name + this.setName(transitionName); + + // Save data from the transitionInfo object + for (var field in transitionInfo) + { + // If we find one of our properties, call its setter. + switch(field) + { + case "predicate": + this.setPredicate(transitionInfo[field]); + break; + + case "nextState": + this.setNextState(transitionInfo[field]); + break; + + case "autoActionsBeforeOntransition": + this.setAutoActionsBeforeOntransition(transitionInfo[field]); + break; + + case "autoActionsAfterOntransition": + this.setAutoActionsAfterOntransition(transitionInfo[field]); + break; + + case "ontransition": + this.setOntransition(transitionInfo[field]); + break; + + default: + // Anything else is user-provided data for their own use. Save it. + this.setUserData(field, transitionInfo[field]); + + // Log it in case it was a typo and they intended a built-in field + this.debug("Transition " + transitionName + ": " + + "Adding user-provided field to transition: " + field); + + break; + } + } +}); + + + + +/* +--------------------------------------------------------------------------- + PROPERTIES +--------------------------------------------------------------------------- +*/ + +/** + * The name of this transition + */ +qx.OO.addProperty( + { + name : "name", + type : "string" + }); + +/** + * The predicate function for this transition. This is documented in the + * constructor, and is typically provided through the constructor's + * transitionInfo object, but it is also possible (but highly NOT recommended) + * to change this dynamically. + */ +qx.OO.addProperty( + { + name : "predicate", + defaultValue : function(fsm, event) { return true; } + }); + +/** + * The state to transition to, if the predicate determines that this + * transition is acceptable. This is documented in the constructor, and is + * typically provided through the constructor's transitionInfo object, but it + * is also possible (but highly NOT recommended) to change this dynamically. + */ +qx.OO.addProperty( + { + name : "nextState", + defaultValue : qx.util.fsm.FiniteStateMachine.StateChange.CURRENT_STATE + }); + +/** + * Automatic actions to take prior to calling the transition's ontransition + * function. This is documented in the constructor, and is typically provided + * through the constructor's transitionInfo object, but it is also possible + * (but highly NOT recommended) to change this dynamically. + */ +qx.OO.addProperty( + { + name : "autoActionsBeforeOntransition", + defaultValue : function(fsm, event) { } + }); + +/** + * Automatic actions to take immediately after calling the transition's + * ontransition function. This is documented in the constructor, and is + * typically provided through the constructor's transitionInfo object, but it + * is also possible (but highly NOT recommended) to change this dynamically. + */ +qx.OO.addProperty( + { + name : "autoActionsAfterOntransition", + defaultValue : function(fsm, event) { } + }); + + +/** + * The function run when the transition is accepted. This is documented in + * the constructor, and is typically provided through the constructor's + * transitionInfo object, but it is also possible (but highly NOT recommended) + * to change this dynamically. + */ +qx.OO.addProperty( + { + name : "ontransition", + defaultValue : function(fsm, event) { } + }); + + + + +/* +--------------------------------------------------------------------------- + MODIFIER +--------------------------------------------------------------------------- +*/ + +qx.Proto._checkName = function(propValue, propData) +{ + // Ensure that we got a valid state name + if (typeof(propValue) != "string" || propValue.length < 1) + { + throw new Error("Invalid transition name"); + } + + return propValue; +}; + +qx.Proto._checkPredicate = function(propValue, propData) +{ + // Validate the predicate. Convert all valid types to function. + switch(typeof(propValue)) + { + case "undefined": + // No predicate means predicate passes + return function(fsm, event) { return true; }; + + case "boolean": + // Convert boolean predicate to a function which returns that value + return function(fsm, event) { return propValue; }; + + case "function": + // Use user-provided function. + return propValue; + + default: + throw new Error("Invalid transition predicate type: " + + typeof(propValue)); + break; + } +}; + +qx.Proto._checkNextState = function(propValue, propData) +{ + // Validate nextState. It must be a string or a number. + switch(typeof(propValue)) + { + case "string": + return propValue; + + case "number": + // Ensure that it's one of the possible state-change constants + switch(propValue) + { + case qx.util.fsm.FiniteStateMachine.StateChange.CURRENT_STATE: + case qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK: + case qx.util.fsm.FiniteStateMachine.StateChange.TERMINATE: + return propValue; + + default: + throw new Error("Invalid transition nextState value: " + + propValue + + ": nextState must be an explicit state name, " + + "or one of the Fsm.StateChange constants"); + } + break; + + default: + throw new Error("Invalid transition nextState type: " + typeof(propValue)); + break; + } +}; + +qx.Proto._checkOntransition = function(propValue, propData) +{ + // Validate the ontransition function. Convert undefined to function. + switch(typeof(propValue) ) + { + case "undefined": + // No provided function just means do nothing. Use a null function. + return function(fsm, event) { }; + + case "function": + // Use user-provided function. + return propValue; + + default: + throw new Error("Invalid ontransition type: " + typeof(propValue)); + break; + } +}; + +/* +--------------------------------------------------------------------------- + UTILITIES +--------------------------------------------------------------------------- +*/ + + +/* +--------------------------------------------------------------------------- + EVENT LISTENERS +--------------------------------------------------------------------------- +*/ + + + +/* +--------------------------------------------------------------------------- + CLASS CONSTANTS +--------------------------------------------------------------------------- +*/ + + + +/* +--------------------------------------------------------------------------- + DISPOSER +--------------------------------------------------------------------------- +*/ + +qx.Proto.dispose = function() +{ + if (this.getDisposed()) { + return true; + } + + return qx.core.Object.prototype.dispose.call(this); +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/example.txt b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/example.txt new file mode 100644 index 0000000000..bb92f70083 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/example.txt @@ -0,0 +1,210 @@ +var fsm; +var state; +var trans; + +// Create a new finite state machine called "Test Machine" +fsm = new qx.util.finitestatemachine.Fsm("Test machine"); + +// State S1 +state = new qx.util.finitestatemachine.State( + // State name + "S1", + + // Object with state information + { + // Function called on entry to this state + "onentry" : + function(fsm, event) + { + alert("Previous state: " + fsm.getPreviousState()); + }; + + // Function called on exit from this state + "onexit" : + function(fsm, event) + { + alert("Next state: " + fsm.getNextState()); + }; + + // Automatic actions to take place before a (possibly) new state's onentry + // function is called. + "autoActionsBeforeOnentry" : + { + // 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" ] + } + ]; + }; + + // also available, in same format as actionsBeforeOnentry: + // "actionsAfterOnentry", + // "actionsBeforeOnexit" + // "actionsAfterOnexit" + + // Events handled by this state, or queued for processing by a future state + "events" : + { + // The event type "compete" is handled by one of the transitions in this + // state. The transitions will be searched in order of their addition + // to the state, until the predicate for a transition returns true (or + // no predicate is specified for the transition, which is an implicit + // "true") That transition will be used. + "complete" : qx.util.finitestatemachine.Fsm.EventHandling.PREDICATE, + + // The event type "interval" has two objects specified by their + // "friendly name". The action when an event of type "interval" occurs + // depends on which object was the target of the event. + "interval" : + { + // If the target of the event was the object to which we have given + // the friendly name "flash" then use a transition specified by name + "flash" : "S1_S3_interval_flash", + + // If the target of the event was the object to which we have given + // the friendly name "timeout", then enqueue this event for possible + // processing by a future state. + "timeout" : qx.util.finitestatemachine.Fsm.EventHandling.BLOCKED + }, + + // The event type "execute", too, has two objects specified by their + // "friendly name". + "execute" : + { + // If the target of the event was the object to which we have given + // the friend name "ok", search the transitions in order looking for + // one where the predicate is true + "ok" : qx.util.finitestatemachine.Fsm.EventHandling.PREDICATE + + // If the target of the event was the object to which we have given + // the friendly name "restart", then enqueue this event for possible + // processing by a future state. + "restart" : qx.util.finitestatemachine.Fsm.EventHandling.BLOCKED + } + + // all events other than those which are handled or blocked are ignored. + }; + }); + +// Add State S1 to the finite state machine. +fsm.addState(state); + +// Transition from S1 to S2 due to event 1 +trans = new qx.util.finitestatemachine.Transition( + // Transition name + "S1_S2_ev1", + + // Object with transition information + { + // return TRUE to pass + "predicate" : + function(fsm, event) + { + var type = event.getType(); + if (type == "somethingWeCareAbout") + { + return true; + } + else if (type == "somethingToHandleInAnotherState") + { + // reattempt event delivery following state transition + fsm.postponeEvent(event); + + // do no further transition attempts for this event for now + return null; + } + else + { + return false; + } + }, + + // if event matches and predicate passes, pop the state stack and go to + // the state which was found at the top of the stack. States are added to + // the state stack by calling fsm.pushState() during a state's onexit + // function or by a transition's action function. + "nextState" : qx.util.finintestatemachine.Fsm.StateChange.POP_STATE_STACK, + + // action taken during transisition + "action" : + function(fsm, event) + { + // save current state so a future transition can get back to + // this saved state + fsm.pushState(); + } + }); +state.addTransition(trans); + +// Default transition (any event): remain in current state +trans = new qx.util.finitestatemachine.Transition( + "S1_S1_default", + { + // true or undefined : always pass + "predicate" : + function(fsm, event) + { + // This predicate does not pass, and we return null to tell the finite + // state machine that no additional transitions in the transition list + // should be tested. (Note that the next transition is the one + // explicitly called for by the "interval" event on the object with + // friendly name "flash". We do not want a predicate search to find + // it. + return null; + }, + + // return to current state + "nextState" : qx.util.finitestatemacine.CURRENT_STATE, + }); +state.addTransition(trans); + +// Transition from S1 to S2 due to event 2. Since the previous transition +// returned null in its predicate function, the only way to get to this +// transition is when it is called out explicitly in the state's event list. +// This one was specified for the "interval" event on the object with friendly +// name "flash". +trans = new qx.util.finitestatememachine.Transition( + "S1_S3_interval_flash", + { + // No predicate or a value of 'true' means that the predicate passes as if + // a predicate function returned true. + "predicate" : true, + + // if event matches, go to this state + "nextState" : "S2", + + // action taken during transisition + "action" : + function(fsm, event) + { + alert(this.getName() + "action function"); + } + }); +state.addTransition(trans); + +// We would, of course, need to add state S2 since it is specified in a +// nextState property. That is left as an exercise for the reader. + + +// Initialize and start the machine running +fsm.start(); |