r20937: Update to latest Finite State Machine with properly-handled blocked events
[sfrench/samba-autobuild/.git] / webapps / qooxdoo-0.6.3-sdk / frontend / framework / source / class / qx / util / fsm / FiniteStateMachine.js
1 /* ************************************************************************
2
3    qooxdoo - the new era of web development
4
5    http://qooxdoo.org
6
7    Copyright:
8      2006, 2007 by Derrell Lipman
9
10    License:
11      LGPL 2.1: http://www.gnu.org/licenses/lgpl.html
12
13    Authors:
14      * Derrell Lipman (derrell)
15
16 ************************************************************************ */
17
18 /* ************************************************************************
19
20 #module(util_fsm)
21
22 ************************************************************************ */
23
24 /**
25  * A finite state machine.
26  *
27  * See {@link qx.util.finitestatemacine.State} for details on creating States,
28  * and {@link qx.util.finitestatemacine.Transitions} for details on creating
29  * transitions between states.
30  *
31  * @param machineName {String} The name of this finite state machine
32  *
33  */
34 qx.OO.defineClass("qx.util.fsm.FiniteStateMachine", qx.core.Target,
35 function(machineName)
36 {
37   // Call our superclass' constructor
38   qx.core.Target.call(this);
39
40   // Save the machine name
41   this.setName(machineName);
42
43   // Initialize the states object
44   this._states = { };
45
46   // Initialize the saved-states stack
47   this._savedStates = [ ];
48
49   // Initialize the pending event queue
50   this._eventQueue = [ ];
51
52   // Initialize the blocked events queue
53   this._blockedEvents = [ ];
54
55   // Create the friendlyToObject" object.  Each object has as its property
56   // name, the friendly name of the object; and as its property value, the
57   // object itself.
58   this._friendlyToObject = { };
59
60   // Create the "friendlyToHash" object.  Each object has as its property
61   // name, the friendly name of the object; and as its property value, the
62   // hash code of the object.
63   this._friendlyToHash = { };
64
65   // Create the "hashToFriendly" object.  Each object has as its property
66   // name, the hash code of the object; and as its property value, the
67   // friendly name of the object.
68   this._hashToFriendly = { };
69
70   // Friendly names can be added to groups, for easy manipulation of enabling
71   // and disabling groups of widgets.  Track which friendly names are in which
72   // group.
73   this._groupToFriendly = { };
74
75   // We also need to be able to map back from friendly name to the groups it
76   // is in.
77   this._friendlyToGroups = { };
78 });
79
80
81 /*
82 ---------------------------------------------------------------------------
83   PROPERTIES
84 ---------------------------------------------------------------------------
85 */
86
87 /**
88  * The name of this finite state machine (for debug messages)
89  */
90 qx.OO.addProperty(
91   {
92     name         : "name",
93     type         : "string"
94   });
95
96 /**
97  * The current state of the finite state machine.
98  */
99 qx.OO.addProperty(
100   {
101     name         : "state",
102     type         : "string"
103   });
104
105 /**
106  * The previous state of the finite state machine, i.e. the state from which
107  * we most recently transitioned.  Note that this could be the same as the
108  * current state if a successful transition brought us back to the same
109  * state.
110  */
111 qx.OO.addProperty(
112   {
113     name         : "previousState",
114     type         : "string"
115   });
116
117 /**
118  * The state to which we will be transitioning.  This property is valid only
119  * during a Transition's ontransition function and a State's onexit function.
120  * At all other times, it is null.
121  */
122 qx.OO.addProperty(
123   {
124     name         : "nextState",
125     type         : "string"
126   });
127
128
129 /**
130  * The maximum number of states which may pushed onto the state-stack.  It is
131  * generally a poor idea to have very many states saved on a stack.  Following
132  * program logic becomes very difficult, and the code can be highly
133  * unmaintainable.  The default should be more than adequate.  You've been
134  * warned.
135  */
136 qx.OO.addProperty(
137   {
138     name         : "maxSavedStates",
139     type         : "number",
140     defaultValue : 2
141   });
142
143 /*
144 ---------------------------------------------------------------------------
145   MODIFIER
146 ---------------------------------------------------------------------------
147 */
148
149
150 /*
151 ---------------------------------------------------------------------------
152   UTILITIES
153 ---------------------------------------------------------------------------
154 */
155
156
157 /**
158  * Add a state to the finite state machine.
159  *
160  * @param state {qx.util.fsm.State}
161  *   An object of class qx.util.fsm.State representing a state
162  *   which is to be a part of this finite state machine.
163  */
164 qx.Proto.addState = function(state)
165 {
166   // Ensure that we got valid state info
167   if (! state instanceof qx.util.fsm.State)
168   {
169     throw new Error("Invalid state: not an instance of " +
170                     "qx.util.fsm.State");
171   }
172
173   // Retrieve the name of this state
174   var stateName = state.getName();
175
176   // Ensure that the state name doesn't already exist
177   if (stateName in this._states)
178   {
179     throw new Error("State " + state + " already exists");
180   }
181
182   // Add the new state object to the finite state machine
183   this._states[stateName] = state;
184 };
185
186
187 /**
188  * Replace a state in the finite state machine.  This is useful if initially
189  * "dummy" states are created which load the real state table for a series of
190  * operations (and possibly also load the gui associated with the new states
191  * at the same time).  Having portions of the finite state machine and their
192  * associated gui pages loaded at run time can help prevent long delays at
193  * application start-up time.
194  *
195  * @param state {qx.util.fsm.State}
196  *   An object of class qx.util.fsm.State representing a state
197  *   which is to be a part of this finite state machine.
198  *
199  * @param bDispose {Boolean}
200  *   If <i>true</i>, then dispose the old state object.  If <i>false</i>, the
201  *   old state object is returned for disposing by the caller.
202  *
203  * @return {Object}
204  *   The old state object if it was not disposed; otherwise null.
205  */
206 qx.Proto.replaceState = function(state, bDispose)
207 {
208   // Ensure that we got valid state info
209   if (! state instanceof qx.util.fsm.State)
210   {
211     throw new Error("Invalid state: not an instance of " +
212                     "qx.util.fsm.State");
213   }
214
215   // Retrieve the name of this state
216   var stateName = state.getName();
217
218   // Save the old state object, so we can return it to be disposed
219   var oldState = this._states[stateName];
220
221   // Replace the old state with the new state object.
222   this._states[stateName] = state;
223
224   // Did they request that the old state be disposed?
225   if (bDispose)
226   {
227     // Yup.  Mark it to be disposed.
228     oldState._needDispose;
229   }
230
231   return oldState;
232 };
233
234
235
236 /**
237  * Add an object (typically a widget) that is to be accessed during state
238  * transitions, to the finite state machine.
239  *
240  * @param friendlyName {String}
241  *   The friendly name to used for access to the object being added.
242  *
243  * @param obj {Object}
244  *   The object to associate with the specified friendly name
245  *
246  * @param groupNames {Array}
247  *   An optional list of group names of which this object is a member.
248  */
249 qx.Proto.addObject = function(friendlyName, obj, groupNames)
250 {
251   var hash = obj.toHashCode();
252   this._friendlyToHash[friendlyName] = hash;
253   this._hashToFriendly[hash] = friendlyName;
254   this._friendlyToObject[friendlyName] = obj;
255
256   // If no groupNames are specified, we're done.
257   if (! groupNames)
258   {
259     return;
260   }
261
262   // Allow either a single group name or an array of group names.  If the
263   // former, we convert it to the latter to make the subsequent code simpler.
264   if (typeof(groupNames) == "string")
265   {
266     groupNames = [ groupNames ];
267   }
268
269   // For each group that this friendly name is to be a member of...
270   for (var i = 0; i < groupNames.length; i++)
271   {
272     var groupName = groupNames[i];
273
274     // If the group name doesn't yet exist...
275     if (! this._groupToFriendly[groupName])
276     {
277       // ... then create it.
278       this._groupToFriendly[groupName] = { };
279     }
280
281     // Add the friendly name to the list of names in this group
282     this._groupToFriendly[groupName][friendlyName] = true;
283
284     // If the friendly name group mapping doesn't yet exist...
285     if (! this._friendlyToGroups[friendlyName])
286     {
287       // ... then create it.
288       this._friendlyToGroups[friendlyName] = [ ];
289     }
290
291     // Append this group name to the list of groups this friendly name is in
292     this._friendlyToGroups[friendlyName] =
293       this._friendlyToGroups[friendlyName].concat(groupNames);
294   }
295 };
296
297
298 /**
299  * Remove an object which had previously been added by {@link #addObject}.
300  *
301  * @param friendlyName {String}
302  *   The friendly name associated with an object, specifying which object is
303  *   to be removed.
304  */
305 qx.Proto.removeObject = function(friendlyName)
306 {
307   var hash = this._friendlyToHash[friendlyName];
308
309   // Delete references to any groupos this friendly name was in
310   if (this._friendlyToGroups[friendlyName])
311   {
312     for (groupName in this._friendlyToGroups[friendlyName])
313     {
314       delete this._groupToFriendly[groupName];
315     }
316
317     delete this._friendlyToGroups[friendlyName];
318   }
319
320   // Delete the friendly name
321   delete this._hashToFriendly[hash];
322   delete this._friendlyToHash[friendlyName];
323   delete this._friendlyToObject[friendlyName];
324 };
325
326
327 /**
328  * Retrieve an object previously saved via {@link #addObject}, using its
329  * Friendly Name.
330  *
331  * @param friendlyName {String}
332  *   The friendly name of the object to be retrieved.
333  *
334  * @return {Object}
335  *   The object which has the specified friendly name, or undefined if no
336  *   object has been associated with that name.
337  */
338 qx.Proto.getObject = function(friendlyName)
339 {
340   return this._friendlyToObject[friendlyName];
341 };
342
343
344 /**
345  * Get the friendly name of an object.
346  *
347  * @param obj {Object} The object for which the friendly name is desired
348  *
349  * @return {String}
350  *   If the object has been previously registered via {@link #addObject}, then
351  *   the friendly name of the object is returned; otherwise, null.
352  */
353 qx.Proto.getFriendlyName = function(obj)
354 {
355   var hash = obj.toHashCode();
356   return hash ? this._hashToFriendly[hash] : null;
357 };
358
359
360 /**
361  * Retrieve the list of objects which have registered, via {@link addObject} as
362  * being members of the specified group.
363  *
364  * @param groupName {String}
365  *   The name of the group for which the member list is desired.
366  *
367  * @return {Array}
368  *   An array containing the friendly names of any objects which are members
369  *   of the specified group.  The resultant array may be empty.
370  */
371 qx.Proto.getGroupObjects = function(groupName)
372 {
373   var a = [ ];
374
375   for (var name in this._groupToFriendly[groupName])
376   {
377     a.push(name);
378   }
379
380   return a;
381 };
382
383
384 /**
385  * Display all of the saved objects and their reverse mappings.
386  */
387 qx.Proto.displayAllObjects = function()
388 {
389   for (var friendlyName in this._friendlyToHash)
390   {
391     var hash = this._friendlyToHash[friendlyName];
392     var obj = this.getObject(friendlyName);
393     this.debug(friendlyName +
394                " => " +
395                hash);
396     this.debug("  " + hash +
397                " => " +
398                this._hashToFriendly[hash]);
399     this.debug("  " + friendlyName +
400                " => " +
401                this.getObject(friendlyName));
402     this.debug("  " + this.getObject(friendlyName) +
403                " => " +
404                this.getFriendlyName(obj));
405   }
406 };
407
408
409 /**
410  * Recursively display an object (as debug messages)
411  *
412  * @param obj {Object}
413  *   The object to be recursively displayed
414  */
415 qx.Proto.debugObject = function(obj, initialMessage)
416 {
417   thisClass = this;
418
419   var displayObj = function(obj, level)
420   {
421     var indentStr = "";
422     for (var i = 0; i < level; i++)
423     {
424       indentStr += "  ";
425     }
426
427     if (typeof(obj) != "object")
428     {
429       thisClass.debug(indentStr, obj);
430       return;
431     }
432
433     for (var prop in obj)
434     {
435       if (typeof(obj[prop]) == "object")
436       {
437         if (obj[prop] instanceof Array)
438         {
439           thisClass.debug(indentStr + prop + ": "  + "Array");
440         }
441         else
442         {
443           thisClass.debug(indentStr + prop + ": "  + "Object");
444         }
445
446         displayObj(obj[prop], level + 1);
447       }
448       else
449       {
450         thisClass.debug(indentStr + prop + ": " + obj[prop]);
451       }
452     }
453   }
454
455   if (initialMessage)
456   {
457     this.debug(initialMessage);
458   }
459
460   displayObj(obj, 0);
461 };
462
463
464
465 /**
466  * Start (or restart, after it has terminated) the finite state machine from
467  * the starting state.  The starting state is defined as the first state added
468  * to the finite state machine.
469  */
470 qx.Proto.start = function()
471 {
472   var stateName;
473
474   // Set the start state to be the first state which was added to the machine
475   for (stateName in this._states)
476   {
477     this.setState(stateName);
478     this.setPreviousState(null);
479     this.setNextState(null);
480     break;
481   }
482
483   if (! stateName)
484   {
485     throw new Error("Machine started with no available states");
486   }
487
488   var debugFunctions =
489     (qx.Settings.getValueOfClass("qx.util.fsm.FiniteStateMachine",
490                                  "debugFlags") &
491      qx.util.fsm.FiniteStateMachine.DebugFlags.FUNCTION_DETAIL);
492
493   // Run the actionsBeforeOnentry actions for the initial state
494   if (debugFunctions)
495   {
496     this.debug(this.getName() + "#" + stateName + "#actionsBeforeOnentry");
497   }
498   this._states[stateName].getAutoActionsBeforeOnentry()(this);
499
500   // Run the entry function for the new state, if one is specified
501   if (debugFunctions)
502   {
503     this.debug(this.getName() + "#" + stateName + "#entry");
504   }
505   this._states[stateName].getOnentry()(this, null);
506
507   // Run the actionsAfterOnentry actions for the initial state
508   if (debugFunctions)
509   {
510     this.debug(this.getName() + "#" + stateName + "#actionsAfterOnentry");
511   }
512   this._states[stateName].getAutoActionsAfterOnentry()(this);
513
514 };
515
516
517 /**
518  * Save the current or previous state on the saved-state stack.  A future
519  * transition can then provide, as its nextState value, the class constant:
520  *
521  *   qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK
522  *
523  * which will cause the next state to be whatever is at the top of the
524  * saved-state stack, and remove that top element from the saved-state stack.
525  *
526  * @param bCurrent {Boolean}
527  *   When <i>true</i>, then push the current state onto the stack.  This might
528  *   be used in a transition, before the state has changed.  When
529  *   <i>false</i>, then push the previous state onto the stack.  This might be
530  *   used in an on entry function to save the previous state to return to.
531  */
532 qx.Proto.pushState = function(bCurrent)
533 {
534   // See if there's room on the state stack for a new state
535   if (this._savedStates.length >= this.getMaxSavedStates())
536   {
537     // Nope.  Programmer error.
538     throw new Error("Saved-state stack is full");
539   }
540
541   if (bCurrent)
542   {
543     // Push the current state onto the saved-state stack
544     this._savedStates.push(this.getState());
545   }
546   else
547   {
548     // Push the previous state onto the saved-state stack
549     this._savedStates.push(this.getPreviousState());
550   }
551 };
552
553
554 /**
555  * Add the specified event to a list of events to be passed to the next state
556  * following state transition.
557  *
558  * @param event {qx.event.type.Event}
559  *   The event to add to the event queue for processing after state change.
560  */
561 qx.Proto.postponeEvent = function(event)
562 {
563   // Add this event to the blocked event queue, so it will be passed to the
564   // next state upon transition.
565   this._blockedEvents.unshift(event);
566 };
567
568
569 /**
570  * Copy an event
571  *
572  * @param event {qx.event.type.Event}
573  *   The event to be copied
574  *
575  * @return {qx.event.type.Event}
576  *   The new copy of the provided event
577  */
578 qx.Proto.copyEvent = function(event)
579 {
580   var e = { };
581   for (var prop in event)
582   {
583     e[prop] = event[prop];
584   }
585
586   return e;
587 };
588
589
590 /**
591  * Enqueue an event for processing
592  *
593  * @param event {qx.event.type.Event}
594  *   The event to be enqueued
595  *
596  * @param bAddAtHead {Boolean}
597  *   If <i>true</i>, put the event at the head of the queue for immediate
598  *   processing.  If <i>false</i>, place the event at the tail of the queue so
599  *   that it receives in-order processing.
600  */
601 qx.Proto.enqueueEvent = function(event, bAddAtHead)
602 {
603   // Add the event to the event queue
604   if (bAddAtHead)
605   {
606     // Put event at the head of the queue
607     this._eventQueue.push(event);
608   }
609   else
610   {
611     // Put event at the tail of the queue
612     this._eventQueue.unshift(event);
613   }
614
615   if (qx.Settings.getValueOfClass("qx.util.fsm.FiniteStateMachine",
616                                   "debugFlags") &
617       qx.util.fsm.FiniteStateMachine.DebugFlags.EVENTS)
618   {
619     if (bAddAtHead)
620     {
621       this.debug(this.getName() + ": Pushed event: " + event.getType());
622     }
623     else
624     {
625       this.debug(this.getName() + ": Queued event: " + event.getType());
626     }
627   }
628 };
629
630
631 /**
632  * Event listener for all event types in the finite state machine
633  *
634  * @param event {qx.event.type.Event}
635  *   The event that was dispatched.
636  */
637 qx.Proto.eventListener = function(event)
638 {
639   // Events are enqueued upon receipt.  Some events are then processed
640   // immediately; other events get processed later.  We need to allow the
641   // event dispatcher to free the source event upon our return, so we'll clone
642   // it and enqueue our clone.  The source event can then be disposed upon our
643   // return.
644   var e = this.copyEvent(event);
645
646   // Enqueue the new event on the tail of the queue
647   this.enqueueEvent(e, false);
648
649   // Process events
650   this._processEvents();
651 };
652
653
654 /**
655  * Process all of the events on the event queue.
656  */
657 qx.Proto._processEvents = function()
658 {
659   // eventListener() can potentially be called while we're processing events
660   if (this._eventProcessingInProgress)
661   {
662     // We were processing already, so don't process concurrently.
663     return;
664   }
665
666   // Track that we're processing events
667   this._eventProcessingInProgress = true;
668
669   // Process each of the events on the event queue
670   while (this._eventQueue.length > 0)
671   {
672     // Pull the next event from the pending event queue
673     var event = this._eventQueue.pop();
674
675     // Run the finite state machine with this event
676     var bDispose = this._run(event);
677
678     // If we didn't block (and re-queue) the event, dispose it.
679     if (bDispose)
680     {
681       event.dispose();
682     }
683   }
684
685   // We're no longer processing events
686   this._eventProcessingInProgress = false;
687 };
688
689 /**
690  * Run the finite state machine to process a single event.
691  *
692  * @param event {qx.event.type.Event}
693  *   An event that has been dispatched.  The event may be handled (if the
694  *   current state handles this event type), queued (if the current state
695  *   blocks this event type), or discarded (if the current state neither
696  *   handles nor blocks this event type).
697  *
698  * @return {Boolean}
699  *   Whether the event should be disposed.  If it was blocked, we've pushed it
700  *   back onto the event queue, and it should not be disposed.
701  */
702 qx.Proto._run = function(event)
703 {
704   // For use in generated functions...
705   var fsm = this;
706
707   // State name variables
708   var thisState;
709   var nextState;
710   var prevState;
711
712   // The current State object
713   var currentState;
714
715   // The transitions available in the current State
716   var transitions;
717
718   // Events handled by the current State
719   var e;
720
721   // The action to take place upon receipt of a particular event
722   var action;
723
724   // Get the debug flags
725   var debugFlags =
726     (qx.Settings.getValueOfClass("qx.util.fsm.FiniteStateMachine",
727                                  "debugFlags"));
728
729   // Allow slightly faster access to determine if debug is enableda
730   var debugEvents =
731      debugFlags & qx.util.fsm.FiniteStateMachine.DebugFlags.EVENTS;
732   var debugTransitions =
733     debugFlags & qx.util.fsm.FiniteStateMachine.DebugFlags.TRANSITIONS;
734   var debugFunctions =
735      debugFlags & qx.util.fsm.FiniteStateMachine.DebugFlags.FUNCTION_DETAIL;
736   var debugObjectNotFound =
737      debugFlags & qx.util.fsm.FiniteStateMachine.DebugFlags.OBJECT_NOT_FOUND;
738
739   if (debugEvents)
740   {
741     this.debug(this.getName() + ": Process event: " + event.getType());
742   }
743
744   // Get the current state name
745   thisState = this.getState();
746
747   // Get the current State object
748   currentState = this._states[thisState];
749
750   // Get a list of the transitions available from this state
751   transitions = currentState.transitions;
752
753   // Determine how to handle this event
754   e = currentState.getEvents()[event.getType()];
755
756   // See if we actually found this event type
757   if (! e)
758   {
759     if (debugEvents)
760     {
761       this.debug(this.getName() + ": Event '" + event.getType() + "'" +
762                  " not handled.  Ignoring.");
763     }
764     return true;
765   }
766
767   // We might have found a constant (PREDICATE or BLOCKED) or an object with
768   // each property name being the friendly name of a saved object, and the
769   // property value being one of the constants (PREDICATE or BLOCKED).
770   if (typeof(e) == "object")
771   {
772     // Individual objects are listed.  Ensure target is a saved object
773     var friendly = this.getFriendlyName(event.getTarget());
774     if (! friendly)
775     {
776       // Nope, it doesn't seem so.  Just discard it.
777       if (debugObjectNotFound)
778       {
779         this.debug(this.getName() + ": Could not find friendly name for '" +
780                    event.getType() + "' on '" + event.getTarget() + "'");
781       }
782       return true;
783     }
784
785     action = e[friendly];
786   }
787   else
788   {
789     action = e;
790   }
791
792   switch(action)
793   {
794     case qx.util.fsm.FiniteStateMachine.EventHandling.PREDICATE:
795       // Process this event.  One of the transitions should handle it.
796       break;
797
798     case qx.util.fsm.FiniteStateMachine.EventHandling.BLOCKED:
799       // This event is blocked.  Enqueue it for later, and get outta here.
800       if (debugEvents)
801       {
802         this.debug(this.getName() + ": Event '" + event.getType() + "'" +
803                    " blocked.  Re-queuing.");
804       }
805       this._blockedEvents.unshift(event);
806       return false;
807
808     default:
809       // See if we've been given an explicit transition name
810       if (typeof(action) == "string")
811       {
812         // Yup!  Ensure that it exists
813         if (transitions[action])
814         {
815           // Yup.  Create a transitions object containing only this transition.
816           var trans = transitions[action];
817           transitions = {  };
818           transitions[action] = trans;
819         }
820         else
821         {
822           throw new Error("Explicit transition " + action + " does not exist");
823         }
824
825         break;
826       }
827   }
828
829   // We handle the event.  Try each transition in turn until we find one that
830   // is acceptable.
831   for (var t in transitions)
832   {
833     var trans = transitions[t];
834
835     // Does the predicate allow use of this transition?
836     switch(trans.getPredicate()(this, event))
837     {
838     case true:
839       // Transition is allowed.  Proceed.
840       break;
841
842     case false:
843       // Transition is not allowed.  Try next transition.
844       continue;
845
846     case null:
847       // Transition indicates not to try further transitions
848       return true;
849
850     default:
851       throw new Error("Transition " + thisState + ":" + t +
852                       " returned a value other than true, false, or null.");
853     }
854
855     // We think we can transition to the next state.  Set next state.
856     nextState = trans.getNextState();
857     if (typeof(nextState) == "string")
858     {
859       // We found a literal state name.  Ensure it exists.
860       if (! nextState in this._states)
861       {
862         throw new Error("Attempt to transition to nonexistent state " +
863                         nextState);
864       }
865
866       // It exists.  Track it being the next state.
867       this.setNextState(nextState);
868     }
869     else
870     {
871       // If it's not a string, nextState must be a StateChange constant
872       switch(nextState)
873       {
874       case qx.util.fsm.FiniteStateMachine.StateChange.CURRENT_STATE:
875         // They want to remain in the same state.
876         nextState = thisState;
877         this.setNextState(nextState)
878         break;
879
880       case qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK:
881         // Switch to the state at the top of the state stack.
882         if (this._savedStates.length == 0)
883         {
884           throw new Error("Attempt to transition to POP_STATE_STACK " +
885                           "while state stack is empty.");
886         }
887
888         // Pop the state stack to retrieve the state to transition to
889         nextState = this._savedStates.pop();
890         this.setNextState(nextState);
891         break;
892
893       default:
894         throw new Error("Internal error: invalid nextState");
895         break;
896       }
897     }
898
899     // Run the actionsBeforeOntransition actions for this transition
900     if (debugFunctions)
901     {
902       this.debug(this.getName() + "#" + thisState + "#" + t +
903                  "#autoActionsBeforeOntransition");
904     }
905     trans.getAutoActionsBeforeOntransition()(this);
906
907     // Run the 'ontransition' function
908     if (debugFunctions)
909     {
910       this.debug(this.getName() + "#" + thisState + "#" + t + "#ontransition");
911     }
912     trans.getOntransition()(this, event);
913
914     // Run the autoActionsAfterOntransition actions for this transition
915     if (debugFunctions)
916     {
917       this.debug(this.getName() + "#" + thisState + "#" + t +
918                  "#autoActionsAfterOntransition");
919     }
920     trans.getAutoActionsAfterOntransition()(this);
921
922     // Run the autoActionsBeforeOnexit actions for the old state
923     if (debugFunctions)
924     {
925       this.debug(this.getName() + "#" + thisState +
926                  "#autoActionsBeforeOnexit");
927     }
928     currentState.getAutoActionsBeforeOnexit()(this);
929
930     // Run the exit function for the old state
931     if (debugFunctions)
932     {
933       this.debug(this.getName() + "#" + thisState + "#exit");
934     }
935     currentState.getOnexit()(this, event);
936
937     // Run the autoActionsAfterOnexit actions for the old state
938     if (debugFunctions)
939     {
940       this.debug(this.getName() + "#" + thisState + "#autoActionsAfterOnexit");
941     }
942     currentState.getAutoActionsAfterOnexit()(this);
943
944     // If this state has been replaced and we're supposed to dispose it...
945     if (currentState._needDispose)
946     {
947       // ... then dispose it now that it's no longer in use
948       currentState.dispose();
949     }
950
951     // Reset currentState to the new state object
952     currentState = this._states[this.getNextState()];
953
954     // set previousState and state, and clear nextState, for transition
955     this.setPreviousState(thisState);
956     this.setState(this.getNextState());
957     this.setNextState(null);
958     prevState = thisState;
959     thisState = nextState;
960     nextState = undefined;
961
962     // Run the autoActionsBeforeOnentry actions for the new state
963     if (debugFunctions)
964     {
965       this.debug(this.getName() + "#" + thisState +
966                  "#autoActionsBeforeOnentry");
967     }
968     currentState.getAutoActionsBeforeOnentry()(this);
969
970     // Run the entry function for the new state, if one is specified
971     if (debugFunctions)
972     {
973       this.debug(this.getName() + "#" + thisState + "#entry");
974     }
975     currentState.getOnentry()(this, event);
976
977     // Run the autoActionsAfterOnentry actions for the new state
978     if (debugFunctions)
979     {
980       this.debug(this.getName() + "#" + thisState +
981                  "#autoActionsAfterOnentry");
982     }
983     currentState.getAutoActionsAfterOnentry()(this);
984
985     // Add any blocked events back onto the pending event queue
986     var e;
987     for (var i = 0; i < this._blockedEvents.length; i++)
988     {
989       e = this._blockedEvents.pop();
990       this._eventQueue.unshift(e);
991     }
992
993     // Ensure that all actions have been flushed
994     qx.ui.core.Widget.flushGlobalQueues();
995
996     if (debugTransitions)
997     {
998       this.debug(this.getName() + "#" + prevState + " => " +
999                  this.getName() + "#" + thisState);
1000     }
1001
1002     // See ya!
1003     return true;
1004   }
1005
1006   if (debugTransitions)
1007   {
1008     this.debug(this.getName() + "#" + thisState +
1009                ": event '" + event.getType() + "'" +
1010                ": no transition found.  No state change.");
1011   }
1012
1013   return true;
1014 };
1015
1016
1017
1018 /*
1019 ---------------------------------------------------------------------------
1020   EVENT LISTENERS
1021 ---------------------------------------------------------------------------
1022 */
1023
1024
1025
1026 /*
1027 ---------------------------------------------------------------------------
1028   CLASS CONSTANTS
1029 ---------------------------------------------------------------------------
1030 */
1031
1032 /**
1033  * Constants which may be values of the nextState member in the transitionInfo
1034  * parameter of the Transition constructor.
1035  */
1036 qx.Class.StateChange =
1037 {
1038   /** When used as a nextState value, means remain in current state */
1039   CURRENT_STATE   : 1,
1040
1041   /** When used as a nextState value, means go to most-recently pushed state */
1042   POP_STATE_STACK : 2,
1043
1044   /** When used as a nextState value, means terminate this state machine */
1045   TERMINATE       : 3
1046 };
1047
1048
1049 /**
1050  * Constants for use in the events member of the transitionInfo parameter of
1051  * the Transition constructor.
1052  */
1053 qx.Class.EventHandling =
1054 {
1055   /**
1056    * This event is handled by this state, but the predicate of a transition
1057    * will determine whether to use that transition.
1058    */
1059   PREDICATE : 1,
1060
1061   /** Enqueue this event for possible use by the next state */
1062   BLOCKED   : 2
1063 };
1064
1065 /**
1066  * Debug bitmask values.  Set the debug flags from the application by or-ing
1067  * together bits, akin to this:
1068  *
1069  *   qx.Settings.setCustomOfClass(
1070  *     "qx.util.fsm.FiniteStateMachine",
1071  *     "debugFlags",
1072  *     (qx.util.fsm.FiniteStateMachine.DebugFlags.EVENTS |
1073  *      qx.util.fsm.FiniteStateMachine.DebugFlags.TRANSITIONS |
1074  *      qx.util.fsm.FiniteStateMachine.DebugFlags.FUNCTION_DETAIL |
1075  *      qx.util.fsm.FiniteStateMachine.DebugFlags.OBJECT_NOT_FOUND));
1076  */
1077 qx.Class.DebugFlags =
1078 {
1079   /** Show events */
1080   EVENTS           : 1,
1081
1082   /** Show transitions */
1083   TRANSITIONS      : 2,
1084
1085   /** Show individual function invocations during transitions */
1086   FUNCTION_DETAIL  : 4,
1087
1088   /** When object friendly names are referenced but not found, show message */
1089   OBJECT_NOT_FOUND : 8
1090 };
1091
1092
1093 /*
1094 ---------------------------------------------------------------------------
1095   CLASS DEFAULT SETTINGS
1096 ---------------------------------------------------------------------------
1097 */
1098
1099 /**
1100  * Debug flags: bitmap of DebugFlags (see Class Constants).
1101  */
1102 qx.Settings.setDefault(
1103   "debugFlags",
1104   (qx.util.fsm.FiniteStateMachine.DebugFlags.EVENTS |
1105    qx.util.fsm.FiniteStateMachine.DebugFlags.TRANSITIONS |
1106    qx.util.fsm.FiniteStateMachine.DebugFlags.OBJECT_NOT_FOUND));
1107
1108
1109 /*
1110 ---------------------------------------------------------------------------
1111   CLASS FUNCTIONS
1112 ---------------------------------------------------------------------------
1113 */
1114
1115 /**
1116  * Common function used by {qx.util.fsm.State} and
1117  * {qx.util.fsm.Transition} for checking the value provided for
1118  * auto actions.
1119  *
1120  * Auto-action property values passed to us look akin to:
1121  *
1122  *     <pre>
1123  *     {
1124  *       // The name of a function.
1125  *       "setEnabled" :
1126  *       [
1127  *         {
1128  *           // The parameter value(s), thus "setEnabled(true);"
1129  *           "parameters"   : [ true ],
1130  *
1131  *           // The function would be called on each object:
1132  *           //  this.getObject("obj1").setEnabled(true);
1133  *           //  this.getObject("obj2").setEnabled(true);
1134  *           "objects" : [ "obj1", "obj2" ]
1135  *
1136  *           // And similarly for each object in each specified group.
1137  *           "groups"  : [ "group1", "group2" ],
1138  *         }
1139  *       ];
1140  *
1141  *       "setColor" :
1142  *       [
1143  *         {
1144  *           "parameters" : [ "blue" ]
1145  *           "groups"     : [ "group3", "group4" ],
1146  *           "objects"    : [ "obj3", "obj4" ]
1147  *         }
1148  *       ];
1149  *     };
1150  *     </pre>
1151  *
1152  * @param actionType {String}
1153  *   The name of the action being validated (for debug messages)
1154  *
1155  * @param propValue {Object}
1156  *   The property value which is being validated
1157  *
1158  * @param propData
1159  *   Not used
1160  */
1161 qx.Class._commonCheckAutoActions = function(actionType, propValue, propData)
1162 {
1163   // Validate that we received an object property value
1164   if (typeof(propValue) != "object")
1165   {
1166     throw new Error("Invalid " + actionType + " value: " + typeof(propValue));
1167   }
1168
1169   // We'll create a function to do the requested actions.  Initialize the
1170   // string into which we'll generate the common fragment added to the
1171   // function for each object.
1172   var funcFragment;
1173
1174   // Here, we'll keep the function body.  Initialize a try block.
1175   var func =
1176     "try" +
1177     "{";
1178
1179   var param;
1180   var objectAndGroupList;
1181
1182   // Retrieve the function request, e.g.
1183   // "enabled" :
1184   for (var f in propValue)
1185   {
1186     // Get the function request value object, e.g.
1187     // "setEnabled" :
1188     // [
1189     //   {
1190     //     "parameters"   : [ true ],
1191     //     "objects" : [ "obj1", "obj2" ]
1192     //     "groups"  : [ "group1", "group2" ],
1193     //   }
1194     // ];
1195     var functionRequest = propValue[f];
1196
1197     // The function request value should be an object
1198     if (! functionRequest instanceof Array)
1199     {
1200       throw new Error("Invalid function request type: " +
1201                       "expected array, found " + typeof(functionRequest));
1202     }
1203
1204     // For each function request...
1205     for (var i = 0; i < functionRequest.length; i++)
1206     {
1207       // Retreive the object and group list object
1208       objectAndGroupList = functionRequest[i];
1209
1210       // The object and group list should be an object, e.g.
1211       // {
1212       //   "parameters"   : [ true ],
1213       //   "objects" : [ "obj1", "obj2" ]
1214       //   "groups"  : [ "group1", "group2" ],
1215       // }
1216       if (typeof(objectAndGroupList) != "object")
1217       {
1218         throw new Error("Invalid function request parameter type: " +
1219                         "expected object, found " +
1220                         typeof(functionRequest[param]));
1221       }
1222
1223       // Retrieve the parameter list
1224       params = objectAndGroupList["parameters"];
1225
1226       // If it didn't exist, ...
1227       if (! params)
1228       {
1229         // ... use an empty array.
1230         params = [ ];
1231       }
1232       else
1233       {
1234         // otherwise, ensure we got an array
1235         if (! params instanceof Array)
1236         {
1237           throw new Error("Invalid function parameters: " +
1238                           "expected array, found " + typeof(params));
1239         }
1240       }
1241
1242       // Create the function to call on each object.  The object on which the
1243       // function is called will be prepended later.
1244       funcFragment = f + "(";
1245
1246       // For each parameter...
1247       for (var j = 0; j < params.length; j++)
1248       {
1249         // If this isn't the first parameter, add a separator
1250         if (j != 0)
1251         {
1252           funcFragment += ",";
1253         }
1254
1255         if (typeof(params[j]) == "function")
1256         {
1257           // If the parameter is a function, arrange for it to be called
1258           // at run time.
1259           funcFragment += "(" + params[j] + ")(fsm)";
1260         }
1261         else if (typeof(params[j]) == "string")
1262         {
1263           // If the parameter is a string, quote it.
1264           funcFragment += '"' + params[j] + '"';
1265         }
1266         else
1267         {
1268           // Otherwise, just add the parameter's literal value
1269           funcFragment += params[j];
1270         }
1271       }
1272
1273       // Complete the function call
1274       funcFragment += ")";
1275
1276       // Get the "objects" list, e.g.
1277       //   "objects" : [ "obj1", "obj2" ]
1278       var a = objectAndGroupList["objects"];
1279
1280       // Was there an "objects" list?
1281       if (! a)
1282       {
1283         // Nope.  Simplify code by creating an empty array.
1284         a = [ ];
1285       }
1286       else if (! a instanceof Array)
1287       {
1288         throw new Error("Invalid 'objects' list: expected array, got " +
1289                         typeof(a));
1290       }
1291
1292       for (var j = 0; j < a.length; j++)
1293       {
1294         // Ensure we got a string
1295         if (typeof(a[j]) != "string")
1296         {
1297           throw new Error("Invalid friendly name in 'objects' list: " + a[j]);
1298         }
1299
1300         func += " fsm.getObject('" + a[j] + "')." + funcFragment + ";";
1301       }
1302
1303       // Get the "groups" list, e.g.
1304       //   "groups" : [ "group1, "group2" ]
1305       var g = objectAndGroupList["groups"];
1306
1307       // Was a "groups" list found?
1308       if (g)
1309       {
1310         // Yup.  Ensure it's an array.
1311         if (! g instanceof Array)
1312         {
1313           throw new Error("Invalid 'groups' list: expected array, got " +
1314                           typeof(g));
1315         }
1316
1317         for (var groupName in g)
1318         {
1319           // Arrange to call the function on each object in each group
1320           func +=
1321             "  var groupObjects = " +
1322             "    fsm.getGroupObjects('" + g[groupName] + "');" +
1323             "  for (var i = 0; i < groupObjects.length; i++)" +
1324             "  {" +
1325             "    var objName = groupObjects[i];" +
1326             "    fsm.getObject(objName)." + funcFragment + ";" +
1327             "  }";
1328         }
1329       }
1330     }
1331   }
1332
1333   // Terminate the try block for function invocations
1334   func +=
1335     "}" +
1336     "catch(e)" +
1337     "{" +
1338     "  fsm.debug(e);" +
1339     "}";
1340
1341 //  o = new qx.core.Object();
1342 //  o.debug("Dynamically created " + actionType + "(fsm) { " + func + " }");
1343
1344   // We've now built the entire body of a function that implements calls to
1345   // each of the requested automatic actions.  Create and return the function,
1346   // which will become the property value.
1347   return new Function("fsm", func);
1348 };
1349
1350
1351
1352 /*
1353 ---------------------------------------------------------------------------
1354   DISPOSER
1355 ---------------------------------------------------------------------------
1356 */
1357
1358 qx.Proto.dispose = function()
1359 {
1360   var e;
1361   var s;
1362
1363   if (this.getDisposed()) {
1364     return true;
1365   }
1366
1367   while (this._savedStates.length > 0)
1368   {
1369     s = this._savedStates.pop();
1370     s = null;
1371   }
1372   this._savedStates = null;
1373
1374   while (this._eventQueue.length > 0)
1375   {
1376     e = this._eventQueue.pop();
1377     e.dispose();
1378     e = null;
1379   }
1380   this._eventQueue = null;
1381
1382   while (this._blockedEvents.length > 0)
1383   {
1384     e = this._blockedEvents.pop();
1385     e.dispose();
1386     e = null;
1387   }
1388
1389   for (var s in this._states)
1390   {
1391     this._states[s].dispose();
1392     this._states[s] = null;
1393     delete this._states[s];
1394   }
1395   this._states = null;
1396
1397   return qx.core.Target.prototype.dispose.call(this);
1398 };