diff options
Diffstat (limited to 'tutorius/core.py')
-rw-r--r-- | tutorius/core.py | 177 |
1 files changed, 106 insertions, 71 deletions
diff --git a/tutorius/core.py b/tutorius/core.py index be2b0ba..80e1b4f 100644 --- a/tutorius/core.py +++ b/tutorius/core.py @@ -24,8 +24,9 @@ This module contains the core classes for tutorius import logging import os -from sugar.tutorius.TProbe import ProbeManager -from sugar.tutorius import addon +from .TProbe import ProbeManager +from .dbustools import save_args +from . import addon logger = logging.getLogger("tutorius") @@ -33,6 +34,9 @@ class Tutorial (object): """ Tutorial Class, used to run through the FSM. """ + #Properties + probeManager = property(lambda self: self._probeMgr) + activityId = property(lambda self: self._activity_id) def __init__(self, name, fsm, filename=None): """ @@ -52,9 +56,6 @@ class Tutorial (object): self._activity_id = None #Rest of initialisation happens when attached - probeManager = property(lambda self: self._probeMgr) - activityId = property(lambda self: self._activity_id) - def attach(self, activity_id): """ Attach to a running activity @@ -78,7 +79,6 @@ class Tutorial (object): # Uninstall the whole FSM self.state_machine.teardown() - #FIXME (Old) There should be some amount of resetting done here... if not self._activity_id is None: self._probeMgr.detach(self._activity_id) self._activity_id = None @@ -91,18 +91,6 @@ class Tutorial (object): self.state_machine.set_state(name) - - # Currently unused -- equivalent function is in each state - def _eventfilter_state_done(self, eventfilter): - """ - Callback handler for eventfilter to notify - when we must go to the next state. - """ - #XXX Tests should be run here normally - - #Swith to the next state pointed by the eventfilter - self.set_state(eventfilter.get_next_state()) - def _prepare_activity(self): """ Prepare the activity for the tutorial by loading the saved state and @@ -116,7 +104,9 @@ class Tutorial (object): self.activity_init_state_filename readfile = addon.create("ReadFile", filename=filename) if readfile: - self._probeMgr.install(self._activity_id, readfile) + self._probeMgr.install(readfile) + #Uninstall now while we have the reference handy + self._probeMgr.uninstall(readfile) class State(object): """ @@ -143,10 +133,9 @@ class State(object): self._actions = action_list or [] - # Unused for now - #self.tests = [] + self._transitions= dict(event_filter_list or []) - self._event_filters = event_filter_list or [] + self._installedEvents = set() self.tutorial = tutorial @@ -170,8 +159,8 @@ class State(object): Install the state itself, by first registering the event filters and then triggering the actions. """ - for eventfilter in self._event_filters: - self.tutorial.probeManager.subscribe(eventfilter, self._event_filter_state_done_cb ) + for (event, next_state) in self._transitions.items(): + self._installedEvents.add(self.tutorial.probeManager.subscribe(event, save_args(self._event_filter_state_done_cb, next_state ))) for action in self._actions: self.tutorial.probeManager.install(action) @@ -183,38 +172,37 @@ class State(object): removing dialogs that were displayed, removing highlights, etc... """ # Remove the handlers for the all of the state's event filters - for event_filter in self._event_filters: - self.tutorial.probeManager.unsubscribe(event_filter, self._event_filter_state_done_cb ) + while len(self._installedEvents) > 0: + self.tutorial.probeManager.unsubscribe(self._installedEvents.pop()) # Undo all the actions related to this state for action in self._actions: self.tutorial.probeManager.uninstall(action) - def _event_filter_state_done_cb(self, event_filter): + def _event_filter_state_done_cb(self, next_state, event): """ Callback for event filters. This function needs to inform the tutorial that the state is over and tell it what is the next state. - @param event_filter The event filter that was called + @param next_state The next state for the transition + @param event The event that occured """ # Run the tests here, if need be # Warn the higher level that we wish to change state - self.tutorial.set_state(event_filter.get_next_state()) + self.tutorial.set_state(next_state) # Model manipulation # These functions are used to simplify the creation of states def add_action(self, new_action): """ - Adds an action to the state (only if it wasn't added before) + Adds an action to the state @param new_action The new action to execute when in this state @return True if added, False otherwise """ - if new_action not in self._actions: - self._actions.append(new_action) - return True - return False + self._actions.append(new_action) + return True # remove_action - We did not define names for the action, hence they're # pretty hard to remove on a precise basis @@ -230,19 +218,21 @@ class State(object): Removes all the action associated with this state. A cleared state will not do anything when entered or exited. """ + #FIXME What if the action is currently installed? self._actions = [] - def add_event_filter(self, event_filter): + def add_event_filter(self, event, next_state): """ Adds an event filter that will cause a transition from this state. The same event filter may not be added twice. - @param event_filter The new event filter that will trigger a transition + @param event The event that will trigger a transition + @param next_state The state to which the transition will lead @return True if added, False otherwise """ - if event_filter not in self._event_filters: - self._event_filters.append(event_filter) + if event not in self._transitions.keys(): + self._transitions[event]=next_state return True return False @@ -250,7 +240,7 @@ class State(object): """ @return The list of event filters associated with this state. """ - return self._event_filters + return self._transitions.items() def clear_event_filters(self): """ @@ -258,12 +248,19 @@ class State(object): was just cleared will become a sink and will be the end of the tutorial. """ - self._event_filters = [] + self._transitions = {} - def is_identical(self, otherState): + def __eq__(self, otherState): """ - Compares two states and tells whether they contain the same states and + Compares two states and tells whether they contain the same states with the + same actions and event filters. + @param otherState The other State that we wish to match + @returns True if every action in this state has a matching action in the + other state with the same properties and values AND if every + event filters in this state has a matching filter in the + other state having the same properties and values AND if both + states have the same name. ` """ if not isinstance(otherState, State): return False @@ -273,27 +270,28 @@ class State(object): # Do they have the same actions? if len(self._actions) != len(otherState._actions): return False + + if len(self._transitions) != len(otherState._transitions): + return False + for act in self._actions: found = False + # For each action in the other state, try to match it with this one. for otherAct in otherState._actions: - if act.is_identical(otherAct): + if act == otherAct: found = True break if found == False: + # If we arrive here, then we could not find an action with the + # same values in the other state. We know they're not identical return False # Do they have the same event filters? - if len(self._actions) != len(otherState._actions): + if self._transitions != otherState._transitions: return False - for event in self._event_filters: - found = False - for otherEvent in otherState._event_filters: - if event.is_identical(otherEvent): - found = True - break - if found == False: - return False + # If nothing failed up to now, then every actions and every filters can + # be found in the other state return True class FiniteStateMachine(State): @@ -507,9 +505,9 @@ class FiniteStateMachine(State): #TODO : Move this code inside the State itself - we're breaking # encap :P - for event_filter in st._event_filters: - if event_filter.get_next_state() == state_name: - st._event_filters.remove(event_filter) + for event in st._transitions: + if st._transitions[event] == state_name: + del st._transitions[event] # Remove the state from the dictionary del self._states[state_name] @@ -527,8 +525,8 @@ class FiniteStateMachine(State): next_states = set() - for event_filter in state._event_filters: - next_states.add(event_filter.get_next_state()) + for event, state in state._transitions.items(): + next_states.add(state) return tuple(next_states) @@ -550,9 +548,9 @@ class FiniteStateMachine(State): states = [] # Walk through the list of states for st in self._states.itervalues(): - for event_filter in st._event_filters: - if event_filter.get_next_state() == state_name: - states.append(event_filter.get_next_state()) + for event, state in st._transitions.items(): + if state == state_name: + states.append(state) continue return tuple(states) @@ -564,42 +562,79 @@ class FiniteStateMachine(State): out_string += st.name + ", " return out_string - def is_identical(self, otherFSM): + def __eq__(self, otherFSM): """ Compares the elements of two FSM to ensure and returns true if they have the same set of states, containing the same actions and the same event filters. - @returns True if the two FSMs have the same content false otherwise + @returns True if the two FSMs have the same content, False otherwise """ if not isinstance(otherFSM, FiniteStateMachine): return False + # Make sure they share the same name if not (self.name == otherFSM.name) or \ not (self.start_state_name == otherFSM.start_state_name): return False - + + # Ensure they have the same number of FSM-level actions if len(self._actions) != len(otherFSM._actions): return False + # Test that we have all the same FSM level actions for act in self._actions: found = False + # For every action in the other FSM, try to match it with the + # current one. for otherAct in otherFSM._actions: - if act.is_identical(otherAct): + if act == otherAct: found = True break if found == False: return False + # Make sure we have the same number of states in both FSMs if len(self._states) != len(otherFSM._states): return False - for state in self._states.itervalues(): - found = False - for otherState in otherFSM._states.itervalues(): - if state.is_identical(otherState): - found = True - break - if found == False: + # For each state, try to find a corresponding state in the other FSM + for state_name in self._states.keys(): + state = self._states[state_name] + other_state = None + try: + # Attempt to use this key in the other FSM. If it's not present + # the dictionary will throw an exception and we'll know we have + # at least one different state in the other FSM + other_state = otherFSM._states[state_name] + except: + return False + # If two states with the same name exist, then we want to make sure + # they are also identical + if not state == other_state: + return False + + # If we made it here, then all the states in this FSM could be matched to an + # identical state in the other FSM. + return True + if len(self._states) != len(otherFSM._states): + return False + + # For each state, try to find a corresponding state in the other FSM + for state_name in self._states.keys(): + state = self._states[state_name] + other_state = None + try: + # Attempt to use this key in the other FSM. If it's not present + # the dictionary will throw an exception and we'll know we have + # at least one different state in the other FSM + other_state = otherFSM._states[state_name] + except: + return False + # If two states with the same name exist, then we want to make sure + # they are also identical + if not state == other_state: return False + # If we made it here, then all the states in this FSM could be matched to an + # identical state in the other FSM. return True |