Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src/sugar/tutorius/core.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/sugar/tutorius/core.py')
-rw-r--r--src/sugar/tutorius/core.py528
1 files changed, 0 insertions, 528 deletions
diff --git a/src/sugar/tutorius/core.py b/src/sugar/tutorius/core.py
deleted file mode 100644
index dd2435e..0000000
--- a/src/sugar/tutorius/core.py
+++ /dev/null
@@ -1,528 +0,0 @@
-# Copyright (C) 2009, Tutorius.org
-# Copyright (C) 2009, Vincent Vinet <vince.vinet@gmail.com>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-"""
-Core
-
-This module contains the core classes for tutorius
-
-"""
-
-import gtk
-import logging
-import copy
-import os
-
-from sugar.tutorius.dialog import TutoriusDialog
-from sugar.tutorius.gtkutils import find_widget
-from sugar.tutorius.services import ObjectStore
-
-logger = logging.getLogger("tutorius")
-
-class Tutorial (object):
- """
- Tutorial Class, used to run through the FSM.
- """
-
- def __init__(self, name, fsm,filename= None):
- """
- Creates an unattached tutorial.
- """
- object.__init__(self)
- self.name = name
- self.activity_init_state_filename = filename
-
- self.state_machine = fsm
- self.state_machine.set_tutorial(self)
-
- self.state = None
-
- self.handlers = []
- self.activity = None
- #Rest of initialisation happens when attached
-
- def attach(self, activity):
- """
- Attach to a running activity
-
- @param activity the activity to attach to
- """
- #For now, absolutely detach if a previous one!
- if self.activity:
- self.detach()
- self.activity = activity
- ObjectStore().activity = activity
- ObjectStore().tutorial = self
- self._prepare_activity()
- self.state_machine.set_state("INIT")
-
- def detach(self):
- """
- Detach from the current activity
- """
-
- # Uninstall the whole FSM
- self.state_machine.teardown()
-
- #FIXME There should be some amount of resetting done here...
- self.activity = None
-
-
- def set_state(self, name):
- """
- Switch to a new state
- """
- logger.debug("==== NEW STATE: %s ====" % name)
-
- 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
- emitting gtk signals
- """
- #Load the saved activity if any
- if self.activity_init_state_filename is not None:
- #For now the file will be saved in the data folder
- #of the activity root directory
- filename = os.getenv("SUGAR_ACTIVITY_ROOT") + "/data/" +\
- self.activity_init_state_filename
- if os.path.exists(filename):
- self.activity.read_file(filename)
-
-
-class State(object):
- """
- This is a step in a tutorial. The state represents a collection of actions
- to undertake when entering the state, and a series of event filters
- with associated actions that point to a possible next state.
- """
-
- def __init__(self, name="", action_list=None, event_filter_list=None, tutorial=None):
- """
- Initializes the content of the state, like loading the actions
- that are required and building the correct tests.
-
- @param action_list The list of actions to execute when entering this
- state
- @param event_filter_list A list of tuples of the form
- (event_filter, next_state_name), that explains the outgoing links for
- this state
- @param tutorial The higher level container of the state
- """
- object.__init__(self)
-
- self.name = name
-
- self._actions = action_list or []
-
- # Unused for now
- #self.tests = []
-
- self._event_filters = event_filter_list or []
-
- self.tutorial = tutorial
-
- def set_tutorial(self, tutorial):
- """
- Associates this state with a tutorial. A tutorial must be set prior
- to executing anything in the state. The reason for this is that the
- states need to have access to the activity (via the tutorial) in order
- to properly register their callbacks on the activities' widgets.
-
- @param tutorial The tutorial that this state runs under.
- """
- if self.tutorial == None :
- self.tutorial = tutorial
- else:
- raise RuntimeWarning(\
- "The state %s was already associated with a tutorial." % self.name)
-
- def setup(self):
- """
- Install the state itself, by first registering the event filters
- and then triggering the actions.
- """
- for eventfilter in self._event_filters:
- eventfilter.install_handlers(self._event_filter_state_done_cb,
- activity=self.tutorial.activity)
-
- for action in self._actions:
- action.do()
-
- def teardown(self):
- """
- Uninstall all the event filters that were active in this state.
- Also undo every action that was installed for this state. This means
- 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:
- event_filter.remove_handlers()
-
- # Undo all the actions related to this state
- for action in self._actions:
- action.undo()
-
- def _event_filter_state_done_cb(self, event_filter):
- """
- 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
- """
- # 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())
-
- # 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)
-
- @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
-
- # remove_action - We did not define names for the action, hence they're
- # pretty hard to remove on a precise basis
-
- def get_action_list(self):
- """
- @return A list of actions that the state will execute
- """
- return self._actions
-
- def clear_actions(self):
- """
- Removes all the action associated with this state. A cleared state will
- not do anything when entered or exited.
- """
- self._actions = []
-
- def add_event_filter(self, event_filter):
- """
- 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
- @return True if added, False otherwise
- """
- if event_filter not in self._event_filters:
- self._event_filters.append(event_filter)
- return True
- return False
-
- def get_event_filter_list(self):
- """
- @return The list of event filters associated with this state.
- """
- return self._event_filters
-
- def clear_event_filters(self):
- """
- Removes all the event filters associated with this state. A state that
- was just cleared will become a sink and will be the end of the
- tutorial.
- """
- self._event_filters = []
-
-class FiniteStateMachine(State):
- """
- This is a collection of states, with a start state and an end callback.
- It is used to simplify the development of the various tutorials by
- encapsulating a collection of states that represent a given learning
- process.
-
- For now, we will consider that there can only be states
- inserted in the FSM, and that there are no nested FSM inside.
- """
-
- def __init__(self, name, tutorial=None, state_dict=None, start_state_name="INIT", action_list=None):
- """
- The constructor for a FSM. Pass in the start state and the setup
- actions that need to be taken when the FSM itself start (which may be
- different from what is done in the first state of the machine).
-
- @param name A short descriptive name for this FSM
- @param tutorial The tutorial that will execute this FSM. If None is
- attached on creation, then one must absolutely be attached before
- executing the FSM with set_tutorial().
- @param state_dict A dictionary containing the state names as keys and
- the state themselves as entries.
- @param start_state_name The name of the starting state, if different
- from "INIT"
- @param action_list The actions to undertake when initializing the FSM
- """
- State.__init__(self, name)
-
- self.name = name
- self.tutorial = tutorial
-
- # Dictionnary of states contained in the FSM
- self._states = state_dict or {}
-
- self.start_state_name = start_state_name
- # Set the current state to None - we are not executing anything yet
- self.current_state = None
-
- # Register the actions for the FSM - They will be processed at the
- # FSM level, meaning that when the FSM will start, it will first
- # execute those actions. When the FSM closes, it will tear down the
- # inner actions of the state, then close its own actions
- self.actions = action_list or []
-
- # Flag to mention that the FSM was initialized
- self._fsm_setup_done = False
- # Flag that must be raised when the FSM is to be teared down
- self._fsm_teardown_done = False
- # Flag used to declare that the FSM has reached an end state
- self._fsm_has_finished = False
-
- def set_tutorial(self, tutorial):
- """
- This associates the FSM to the given tutorial. It MUST be associated
- either in the constructor or with this function prior to executing the
- FSM.
-
- @param tutorial The tutorial that will execute this FSM.
- """
- # If there was no tutorial associated
- if self.tutorial == None:
- # Associate it with this FSM and all the underlying states
- self.tutorial = tutorial
- for state in self._states.itervalues():
- state.set_tutorial(tutorial)
- else:
- raise RuntimeWarning(\
- "The FSM %s is already associated with a tutorial."%self.name)
-
- def setup(self):
- """
- This function initializes the FSM the first time it is called.
- Then, every time it is called, it initializes the current state.
- """
- # Are we associated with a tutorial?
- if self.tutorial == None:
- raise UnboundLocalError("No tutorial was associated with FSM %s" % self.name)
-
- # If we never initialized the FSM itself, then we need to run all the
- # actions associated with the FSM.
- if self._fsm_setup_done == False:
- # Remember the initial state - we might want to reset
- # or rewind the FSM at a later moment
- self.start_state = self._states[self.start_state_name]
- self.current_state = self.start_state
- # Flag the FSM level setup as done
- self._fsm_setup_done = True
- # Execute all the FSM level actions
- for action in self.actions:
- action.do()
-
- # Then, we need to run the setup of the current state
- self.current_state.setup()
-
- def set_state(self, new_state_name):
- """
- This functions changes the current state of the finite state machine.
-
- @param new_state The identifier of the state we need to go to
- """
- # TODO : Since we assume no nested FSMs, we don't set state on the
- # inner States / FSMs
-## # Pass in the name to the internal state - it might be a FSM and
-## # this name will apply to it
-## self.current_state.set_state(new_state_name)
-
- # Make sure the given state is owned by the FSM
- if not self._states.has_key(new_state_name):
- # If we did not recognize the name, then we do not possess any
- # state by that name - we must ignore this state change request as
- # it will be done elsewhere in the hierarchy (or it's just bogus).
- return
-
- if self.current_state != None:
- if new_state_name == self.current_state.name:
- # If we already are in this state, we do not need to change
- # anything in the current state - By design, a state may not point
- # to itself
- return
-
- new_state = self._states[new_state_name]
-
- # Undo the actions of the old state
- self.teardown()
-
- # Insert the new state
- self.current_state = new_state
-
- # Call the initial actions in the new state
- self.setup()
-
- def get_current_state_name(self):
- """
- Returns the name of the current state.
-
- @return A string representing the name of the current state
- """
- return self.current_state.name
-
- def teardown(self):
- """
- Revert any changes done by setup()
- """
- # Teardown the current state
- if self.current_state is not None:
- self.current_state.teardown()
-
- # If we just finished the whole FSM, we need to also call the teardown
- # on the FSM level actions
- if self._fsm_has_finished == True:
- # Flag the FSM teardown as not needed anymore
- self._fsm_teardown_done = True
- # Undo all the FSM level actions here
- for action in self.actions:
- action.undo()
-
- # TODO : It might be nice to have a start() and stop() method for the
- # FSM.
-
- # Data manipulation section
- # These functions are dedicated to the building and editing of a graph.
- def add_state(self, new_state):
- """
- Inserts a new state in the FSM.
-
- @param new_state The State object that will now be part of the FSM
- @raise KeyError In the case where a state with this name already exists
- """
- if self._states.has_key(new_state.name):
- raise KeyError("There is already a state by this name in the FSM")
-
- self._states[new_state.name] = new_state
-
- # Not such a great name for the state accessor... We already have a
- # set_state name, so get_state would conflict with the notion of current
- # state - I would recommend having a set_current_state instead.
- def get_state_by_name(self, state_name):
- """
- Fetches a state from the FSM, based on its name. If there is no
- such state, the method will throw a KeyError.
-
- @param state_name The name of the desired state
- @return The State object having the given name
- """
- return self._states[state_name]
-
- def remove_state(self, state_name):
- """
- Removes a state from the FSM. Raises a KeyError when the state is
- not existent.
-
- Warning : removing a state will also remove all the event filters that
- point to this given name, to preserve the FSM's integrity. If you only
- want to edit a state, you would be better off fetching this state with
- get_state_by_name().
-
- @param state_name A string being the name of the state to remove
- @raise KeyError When the state_name does not a represent a real state
- stored in the dictionary
- """
-
- state_to_remove = self._states[state_name]
-
- # Remove the state from the states' dictionnary
- for st in self._states.itervalues():
- # Iterate through the list of event filters and remove those
- # that point to the state that will be removed
-
- #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)
-
- # Remove the state from the dictionary
- del self._states[state_name]
-
- # Exploration methods - used to know more about a given state
- def get_following_states(self, state_name):
- """
- Returns a tuple of the names of the states that point to the given
- state. If there is no such state, the function raises a KeyError.
-
- @param state_name The name of the state to analyse
- @raise KeyError When there is no state by this name in the FSM
- """
- state = self._states[state_name]
-
- next_states = set()
-
- for event_filter in state._event_filters:
- next_states.add(event_filter.get_next_state())
-
- return tuple(next_states)
-
- def get_previous_states(self, state_name):
- """
- Returns a tuple of the names of the state that can transition to
- the given state. If there is no such state, the function raises a
- KeyError.
-
- @param state_name The name of the state that the returned states might
- transition to.
- """
- # This might seem a bit funny, but we don't verify if the given
- # state is present or not in the dictionary.
- # This is due to the fact that when building a graph, we might have a
- # prototypal state that has not been inserted yet. We could not know
- # which states are pointing to it until we insert it in the graph.
-
- 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())
- continue
-
- return tuple(states)
-
- # Convenience methods to see the content of a FSM
- def __str__(self):
- out_string = ""
- for st in self._states.itervalues():
- out_string += st.name + ", "
- return out_string