Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVincent Vinet <vince.vinet@gmail.com>2009-12-06 19:43:16 (GMT)
committer Vincent Vinet <vince.vinet@gmail.com>2009-12-06 20:41:39 (GMT)
commit2bda2c8c50797a6214126edc5afdc3fc5d8e9969 (patch)
tree4697873cfa4de532ee691a17a29e781fe5c5782d
parentfb422aef7ee6832c85c8fa8a703e491838e74d62 (diff)
improve coverage report and cleanup old stuff
-rwxr-xr-xsetup.py18
-rw-r--r--tests/coretests.py629
-rw-r--r--tests/skip1
-rw-r--r--tutorius/core.py618
-rw-r--r--tutorius/dialog.py59
-rw-r--r--tutorius/testwin.py92
-rw-r--r--tutorius/textbubble.py109
7 files changed, 17 insertions, 1509 deletions
diff --git a/setup.py b/setup.py
index 49bae4c..fb500c1 100755
--- a/setup.py
+++ b/setup.py
@@ -6,6 +6,14 @@ import os, sys
import glob
from unittest import TestLoader, TextTestRunner, TestSuite
+COVERAGE_IGNORE=[
+ "tutorius/apilib",
+ "tutorius/viewer.py",
+ "tutorius/creator.py",
+ "tutorius/overlayer.py",
+ "tutorius/addons",
+]
+
class TestCommand(Command):
user_options = [('coverage',
None,
@@ -69,13 +77,21 @@ class TestCommand(Command):
if self.coverage:
coverage.stop()
sources = []
- os.path.walk(os.path.join(prefix,'sugar','tutorius'),
+ #To reduce report format, change to the tutorius dir before reporting
+ curdir = os.getcwd()
+ os.chdir(os.path.join(prefix, 'sugar'))
+ os.path.walk('tutorius',
self._listsources,
sources)
coverage.report(sources)
+ os.chdir(curdir)
coverage.erase()
def _listsources(self, arg, dirname, fnames):
+ for name in list(fnames):
+ if os.path.join(dirname, name) in COVERAGE_IGNORE:
+ fnames.remove(name)
+
fnames = filter(lambda x:x.endswith('.py'), fnames)
for name in fnames:
arg.append(pjoin(dirname, name))
diff --git a/tests/coretests.py b/tests/coretests.py
deleted file mode 100644
index 1cc5431..0000000
--- a/tests/coretests.py
+++ /dev/null
@@ -1,629 +0,0 @@
-# Copyright (C) 2009, Tutorius.org
-# Copyright (C) 2009, Michael Janelle-Montcalm <michael.jmontcalm@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 Tests
-
-This module contains all the tests that pertain to the usage of the Tutorius
-Core. This means that the Event Filters, the Finite State Machine and all the
-related elements and interfaces are tested here.
-
-Usage of actions and event filters is tested, but not the concrete actions
-and event filters. Those are in their separate test module
-
-"""
-
-import unittest
-
-from copy import deepcopy
-import logging
-from sugar.tutorius.actions import *
-from sugar.tutorius.addon import *
-from sugar.tutorius.core import *
-from sugar.tutorius.filters import *
-
-from actiontests import CountAction, FakeEventFilter
-
-# Helper classes to help testing
-class SimpleTutorial(Tutorial):
- """
- Fake tutorial
- """
- def __init__(self, start_name="INIT"):
- #Tutorial.__init__(self, "Simple Tutorial", None)
- self.current_state_name = start_name
- self.activity = "TODO : This should be an activity"
-
- def set_state(self, name):
- self.current_state_name = name
-
-class TutorialTest(unittest.TestCase):
- """Tests the tutorial functions that are not covered elsewhere."""
- def test_detach(self):
- class Activity(object):
- name = "this"
-
- activity1 = Activity()
- activity2 = Activity()
-
- fsm = FiniteStateMachine("Sample example")
-
- tutorial = Tutorial("Test tutorial", fsm)
-
- assert tutorial.activity == None, "There is a default activity in the tutorial"
-
- tutorial.attach(activity1)
-
- assert tutorial.activity == activity1, "Activity should have been associated to this tutorial"
-
- tutorial.attach(activity2)
- assert tutorial.activity == activity2, "Activity should have been changed to activity2"
-
-class TutorialWithFSM(Tutorial):
- """
- Fake tutorial, but associated with a FSM.
- """
- def __init__(self, start_name="INIT", fsm=None):
- Tutorial.__init__(self, start_name, fsm)
- self.activity = activity.Activity()
-
-class TrueWhileActiveAction(Action):
- """
- This action's active member is set to True after a do and to False after
- an undo.
-
- Used to verify that a State correctly triggers the do and undo actions.
- """
- def __init__(self):
- Action.__init__(self)
- self.active = False
-
- def do(self):
- self.active = True
-
- def undo(self):
- self.active = False
-
-# State testing class
-class StateTest(unittest.TestCase):
- """
- This class has to test the State interface as well as the expected
- functionality.
- """
-
- def test_action_toggle(self):
- """
- Validate that the actions are properly done on setup and undone on
- teardown.
-
- Pretty awesome.
- """
- act = TrueWhileActiveAction()
-
- state = State("action_test", action_list=[act])
-
- assert act.active == False, "Action is not initialized properly"
-
- state.setup()
-
- assert act.active == True, "Action was not triggered properly"
-
- state.teardown()
-
- assert act.active == False, "Action was not undone properly"
-
- def test_event_filter(self):
- """
- Tests the fact that the event filters are correctly installed on setup
- and uninstalled on teardown.
- """
- event_filter = addon.create('TriggerEventFilter')
-
- state = State("event_test", event_filter_list=[(event_filter, "second_state")])
- state.set_tutorial(SimpleTutorial())
-
- assert event_filter.toggle_on_callback == False, "Wrong init of event_filter"
- assert event_filter._callback == None, "Event filter has a registered callback before installing handlers"
-
- state.setup()
-
- assert event_filter._callback != None, "Event filter did not register callback!"
-
- # 'Trigger' the event - This is more like a EventFilter test.
- event_filter.do_callback()
-
- assert event_filter.toggle_on_callback == True, "Event filter did not execute callback"
-
- state.teardown()
-
- assert event_filter._callback == None, "Event filter did not remove callback properly"
-
- def test_warning_set_tutorial_twice(self):
- """
- Calls set_tutorial twice and expects a warning on the second.
- """
- state = State("start_state")
- tut = SimpleTutorial("First")
- tut2 = SimpleTutorial("Second")
-
- state.set_tutorial(tut)
-
- try:
- state.set_tutorial(tut2)
- assert False, "No RuntimeWarning was raised on second set_tutorial"
- except :
- pass
-
- def test_add_action(self):
- """
- Tests on manipulating the actions inside a state.
- """
- state = State("INIT")
-
- act1 = CountAction()
- act2 = CountAction()
- act3 = CountAction()
-
- # Try to add the actions
- assert state.add_action(act1), "Could not add the first action"
- assert state.add_action(act2), "Could not add the second action"
- assert state.add_action(act3), "Could not add the third action"
-
- # Fetch the associated actions
- actions = state.get_action_list()
-
- # Make sure all the actions are present in the state
- assert act1 in actions and act2 in actions and act3 in actions, \
- "The actions were not properly inserted in the state"
-
- # Clear the list
- state.clear_actions()
-
- # Make sure the list of actions is empty now
- assert len(state.get_action_list()) == 0, "Clearing of actions failed"
-
- def test_add_event_filter(self):
- state = State("INIT")
-
- event1 = addon.create('TriggerEventFilter')
- event2 = addon.create('TriggerEventFilter')
-
- # Insert the event filters
- assert state.add_event_filter(event1, "s"), "Could not add event filter 1"
-
- # Make sure we cannot insert an event twice
- assert state.add_event_filter(event1, "s") == False, "Could add twice the event filter"
- assert state.add_event_filter(event2, "t") == False, "Could add event filter 2"
-
- # Get the list of event filters
- event_filters = map(lambda x: x[0],state.get_event_filter_list())
-
- #even if we added only the event 1, they are equivalent
- assert event1 in event_filters and event2 in event_filters, \
- "The event filters were not all added inside the state"
-
- # Clear the list
- state.clear_event_filters()
-
- assert len(state.get_event_filter_list()) == 0, \
- "Could not clear the event filter list properly"
-
- def test_eq_simple(self):
- """
- Two empty states with the same name must be identical
- """
- st1 = State("Identical")
- st2 = State("Identical")
-
- assert st1 == st2, "Empty states with the same name should be identical"
-
- def test_eq(self):
- """
- Test whether two states share the same set of actions and event filters.
- """
- st1 = State("Identical")
- st2 = State("Identical")
-
- non_state = object()
-
- act1 = addon.create("BubbleMessage", message="Hi", position=[132,450])
- act2 = addon.create("BubbleMessage", message="Hi", position=[132,450])
-
- event1 = addon.create("GtkWidgetEventFilter", object_id="0.0.0.1.1.2.3.1", event_name="clicked")
-
- act3 = addon.create("DialogMessage", message="Hello again.", position=[200, 400])
-
- # Build the first state
- st1.add_action(act1)
- st1.add_action(act3)
- st1.add_event_filter(event1, "nextState")
-
- # Build the second state
- st2.add_action(act2)
- st2.add_action(act3)
- st2.add_event_filter(event1, "nextState")
-
- # Make sure that they are identical for now
- assert st1 == st2, "States should be considered as identical"
- assert st2 == st1, "States should be considered as identical"
-
- # Modify the second bubble message action
- act2.message = "New message"
-
- # Since one action changed in the second state, this should indicate that the states
- # are not identical anymore
- assert not (st1 == st2), "Action was changed and states should be different"
- assert not (st2 == st1), "Action was changed and states should be different"
-
- # Make sure that trying to find identity with something else than a State object fails properly
- assert not (st1 == non_state), "Passing a non-State object should fail for identity"
-
- st2.name = "Not identical anymore"
- assert not(st1 == st2), "Different state names should give different states"
- st2.name = "Identical"
-
- st3 = deepcopy(st1)
- st3.add_action(addon.create("BubbleMessage", message="Hi!", position=[128,264]))
-
- assert not (st1 == st3), "States having a different number of actions should be different"
-
- st4 = deepcopy(st1)
- st4.add_event_filter(addon.create("GtkWidgetEventFilter", object_id="0.0.1.1.2.2.3", event_name="clicked"), "next_state")
-
- assert not (st1 == st4), "States having a different number of events should be different"
-
- st5 = deepcopy(st1)
- st5._event_filters = []
-
- st5.add_event_filter(addon.create("GtkWidgetEventFilter", object_id="0.1.2.3.4.1.2", event_name="pressed"), "other_state")
-
- assert not (st1 == st5), "States having the same number of event filters" \
- + " but those being different should be different"
-
-class FSMTest(unittest.TestCase):
- """
- This class needs to text the interface and functionality of the Finite
- State Machine.
- """
-
- def test_sample_usage(self):
- act_init = TrueWhileActiveAction()
- act_second = TrueWhileActiveAction()
-
- event_init = FakeEventFilter()
-
- content = {
- "INIT": State("INIT", action_list=[act_init],event_filter_list=[(event_init,"SECOND")]),
- "SECOND": State("SECOND", action_list=[act_second])
- }
-
- fsm = FiniteStateMachine("SampleUsage", state_dict=content)
-
- assert fsm is not None, "Unable to create FSM"
-
- tut = Tutorial("SampleUsageTutorial", fsm)
-
- tut.attach(None)
- event_init.set_tutorial(tut)
-
- assert fsm.current_state.name == "INIT", "Unable to set state to initial state"
-
- assert act_init.active, "FSM did not call the state's action DO properly"
-
- # Trigger the event of the INIT state
- event_init.do_callback()
-
- assert act_init.active == False, "FSM did not teardown INIT properly"
-
- assert fsm.current_state.name == "SECOND", "FSM did not switch to SECOND state"
-
- assert act_second.active == True, "FSM did not setup SECOND properly"
-
- tut.detach()
-
- assert act_second.active == False, "FSM did not teardown SECOND properly"
-
-
-
- def test_state_insert(self):
- """
- This is a simple test to insert, then find a state.
- """
- st1 = State("FakeState")
-
- fsm = FiniteStateMachine("StateInsertTest")
-
- fsm.add_state(st1)
-
- inserted_state = fsm.get_state_by_name(st1.name)
-
- assert inserted_state is st1, "Inserting, then fetching a state did not work"
-
- # Make sure we cannot insert it twice
- try :
- fsm.add_state(st1)
- assert False, "No error raised on addition of an already present state"
- except KeyError:
- pass
-
- def test_state_find_by_name(self):
- """
- Tests the interface for fetching a state by name.
- - Basic functionnality
- - Non-existent state
- """
-
- st1 = State("INIT")
-
- st2 = State("second")
-
- fsm = FiniteStateMachine("StateFindTest")
-
- fsm.add_state(st1)
- fsm.add_state(st2)
-
- # Test the fetch by name
- fetched_st1 = fsm.get_state_by_name(st1.name)
-
- assert fetched_st1 is st1, "Fetched state is not the same as the inserted one"
-
- fetched_st2 = fsm.get_state_by_name(st2.name)
-
- assert fetched_st2 is st2, "Fetched state is not the same as the inserted one"
-
- try:
- fsm.get_state_by_name("no such state")
- assert False, "Did not get a KeyError on non-existing key search"
- except KeyError:
- pass
- except Exception:
- assert False, "Did not get the right error on non-existing key search"
-
- def test_state_removal(self):
- """
- This test removes a state from the FSM. It also verifies that the links
- from other states going into the removed state are gone.
- """
- st1 = State("INIT", event_filter_list=[(addon.create('TriggerEventFilter'), "second")])
- st2 = State("second", event_filter_list=[(addon.create('TriggerEventFilter'), "third")])
- st3 = State("third", event_filter_list=[(addon.create('TriggerEventFilter'), "second")])
-
- fsm = FiniteStateMachine("StateRemovalTest")
-
- fsm.add_state(st1)
- fsm.add_state(st2)
- fsm.add_state(st3)
-
- # First tests - Removing a non-existing state and make sure we get a
- # KeyError
- try:
- fsm.remove_state("Non-existing")
- assert False, "Removing a non-existing state did not throw a KeyError"
- except KeyError:
- pass
- except Exception:
- assert False, "Removing a non-existing state dit not throw the right kind of exception"
-
- # Now try removing the second state
- fsm.remove_state("second")
-
- # Make sure it cannot be fetched
- try :
- fetched_state = fsm.get_state_by_name("second")
- assert False, "The supposedly removed state is still present in the FSM"
- except KeyError:
- pass
-
- # Make sure that there is no link to the removed state in the rest
- # of the FSM
- assert "second" not in fsm.get_following_states("INIT"), \
- "The link to second from INIT still exists after removal"
-
- assert "second" not in fsm.get_following_states("third"), \
- "The link to second from third still exists after removal"
-
- def test_set_same_state(self):
- fsm = FiniteStateMachine("Set same state")
-
- st1 = State("INIT")
- st1.add_action(CountAction())
-
- fsm.add_state(st1)
-
- tut = SimpleTutorial()
-
- fsm.set_tutorial(tut)
-
- fsm.set_state("INIT")
-
- assert fsm.get_state_by_name("INIT").get_action_list()[0].do_count == 1, \
- "The action was not triggered on 'INIT'"
-
- fsm.set_state("INIT")
-
- do_count = fsm.get_state_by_name("INIT").get_action_list()[0].do_count
- assert fsm.get_state_by_name("INIT").get_action_list()[0].do_count == 1, \
- "The action was triggered a second time, do_count = %d"%do_count
-
- undo_count = fsm.get_state_by_name("INIT").get_action_list()[0].undo_count
- assert fsm.get_state_by_name("INIT").get_action_list()[0].undo_count == 0, \
- "The action has been undone unappropriately, undo_count = %d"%undo_count
-
- def test_setup(self):
- fsm = FiniteStateMachine("New state machine")
-
- try:
- fsm.setup()
- assert False, "fsm should throw an exception when trying to setup and not bound to a tutorial"
- except UnboundLocalError:
- pass
-
- def test_setup_actions(self):
- tut = SimpleTutorial()
-
- states_dict = {"INIT": State("INIT")}
- fsm = FiniteStateMachine("New FSM", state_dict=states_dict)
-
- act = CountAction()
- fsm.add_action(act)
-
- fsm.set_tutorial(tut)
-
- fsm.setup()
-
- # Let's also test the current state name
- assert fsm.get_current_state_name() == "INIT", "Initial state should be INIT"
-
- assert act.do_count == 1, "Action should have been called during setup"
-
- fsm._fsm_has_finished = True
-
- fsm.teardown()
-
- assert act.undo_count == 1, "Action should have been undone"
-
- def test_string_rep(self):
- fsm = FiniteStateMachine("Testing machine")
-
- st1 = State("INIT")
- st2 = State("Other State")
- st3 = State("Final State")
-
- st1.add_action(addon.create("BubbleMessage", message="Hi!", position=[132,312]))
-
- fsm.add_state(st1)
- fsm.add_state(st2)
- fsm.add_state(st3)
-
- assert str(fsm) == "INIT, Final State, Other State, "
-
- def test_eq_(self):
- fsm = FiniteStateMachine("Identity test")
-
- non_fsm_object = object()
-
- assert not (fsm == non_fsm_object), "Testing with non FSM object should not give identity"
-
- # Compare FSMs
- act1 = CountAction()
-
- fsm.add_action(act1)
-
- fsm2 = deepcopy(fsm)
-
- assert fsm == fsm2
-
- act2 = CountAction()
- fsm2.add_action(act2)
-
- assert not(fsm == fsm2), \
- "FSMs having a different number of actions should be different"
-
- fsm3 = FiniteStateMachine("Identity test")
-
- act3 = addon.create("BubbleMessage", message="Hi!", position=[123,312])
- fsm3.add_action(act3)
-
- assert not(fsm3 == fsm), \
- "Actions having the same number of actions but different ones should be different"
-
- st1 = State("INIT")
-
- st2 = State("OtherState")
-
- fsm.add_state(st1)
- fsm.add_state(st2)
-
- fsm4 = deepcopy(fsm)
-
- assert fsm == fsm4
-
- st3 = State("Last State")
-
- fsm4.add_state(st3)
-
- assert not (fsm == fsm4), "FSMs having a different number of states should not be identical"
-
- fsm4.remove_state("OtherState")
-
- assert not (fsm == fsm4), "FSMs having different states should be different"
-
- fsm4.remove_state("Last State")
-
- st5 = State("OtherState")
- st5.add_action(CountAction())
-
- fsm4.add_state(st5)
-
- assert not(fsm == fsm4), "FSMs having states with same name but different content should be different"
-
-class FSMExplorationTests(unittest.TestCase):
- def setUp(self):
- self.buildFSM()
-
- def buildFSM(self):
- """
- Create a sample FSM to play with in the rest of the tests.
- """
- st1 = State("INIT")
- st1.add_action(CountAction())
- st1.add_event_filter(addon.create('TriggerEventFilter'), "Second")
- st1.add_event_filter(addon.create('TriggerEventFilter'), "Third")
-
- st2 = State("Second")
- st2.add_action(TrueWhileActiveAction())
- st2.add_event_filter(addon.create('TriggerEventFilter'), "Third")
- st2.add_event_filter(addon.create('TriggerEventFilter'), "Fourth")
-
- st3 = State("Third")
- st3.add_action(CountAction())
- st3.add_action(TrueWhileActiveAction())
-
- self.fsm = FiniteStateMachine("ExplorationTestingMachine")
- self.fsm.add_state(st1)
- self.fsm.add_state(st2)
- self.fsm.add_state(st3)
-
- def validate_following_states(self, in_name, out_name_list):
- nextStates = self.fsm.get_following_states(in_name)
- assert list(nextStates).sort() == list(out_name_list).sort(), \
- "The following states for %s are wrong : got %s"%\
- (in_name, str(nextStates))
-
- def validate_previous_states(self, in_name, out_name_list):
- prevStates = self.fsm.get_previous_states(in_name)
- assert list(prevStates).sort() == list(out_name_list).sort(), \
- "The following states for %s are wrong : got %s"%\
- (in_name, str(prevStates))
-
- def test_get_following_states(self):
- self.validate_following_states("INIT", ('Second', 'Third'))
-
- self.validate_following_states("Second", ("Third", "Fourth"))
-
- self.validate_following_states("Third", ())
-
- def test_get_previous_states(self):
- self.validate_previous_states("INIT", ())
-
- self.validate_previous_states("Second", ("INIT"))
-
- self.validate_previous_states("Third", ("INIT", "Second"))
-
- self.validate_previous_states("Fourth", ("Second"))
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tests/skip b/tests/skip
index 028ecaf..3868383 100644
--- a/tests/skip
+++ b/tests/skip
@@ -2,4 +2,3 @@ utils.py
run-tests.py
overlaytests.py
viewer.py
-coretests.py
diff --git a/tutorius/core.py b/tutorius/core.py
deleted file mode 100644
index bfbe07b..0000000
--- a/tutorius/core.py
+++ /dev/null
@@ -1,618 +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 logging
-import os
-
-from .TProbe import ProbeManager
-from .dbustools import save_args
-from . import addon
-
-logger = logging.getLogger("tutorius")
-
-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):
- """
- 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._probeMgr = ProbeManager()
- self._activity_id = None
- #Rest of initialisation happens when attached
-
- def attach(self, activity_id):
- """
- Attach to a running activity
-
- @param activity_id the id of the activity to attach to
- """
- #For now, absolutely detach if a previous one!
- if self._activity_id:
- self.detach()
- self._activity_id = activity_id
- self._probeMgr.attach(activity_id)
- self._probeMgr.currentActivity = activity_id
- 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()
-
- if not self._activity_id is None:
- self._probeMgr.detach(self._activity_id)
- self._activity_id = None
-
- def set_state(self, name):
- """
- Switch to a new state
- """
- logger.debug("==== NEW STATE: %s ====" % name)
-
- self.state_machine.set_state(name)
-
- 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
- readfile = addon.create("ReadFile", filename=filename)
- if readfile:
- self._probeMgr.install(readfile)
- #Uninstall now while we have the reference handy
- self._probeMgr.uninstall(readfile)
-
-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 []
-
- self._transitions= dict(event_filter_list or [])
-
- self._installedEvents = set()
-
- 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 (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)
-
- 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
- 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, 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 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(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
-
- @param new_action The new action to execute when in this state
- @return True if added, False otherwise
- """
- 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
-
- 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.
- """
- #FIXME What if the action is currently installed?
- self._actions = []
-
- 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 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 not in self._transitions.keys():
- self._transitions[event]=next_state
- return True
- return False
-
- def get_event_filter_list(self):
- """
- @return The list of event filters associated with this state.
- """
- return self._transitions.items()
-
- 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._transitions = {}
-
- def __eq__(self, otherState):
- """
- 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
- if self.name != otherState.name:
- return False
-
- # 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 == 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 self._transitions != otherState._transitions:
- 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):
- """
- 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:
- self.tutorial.probeManager.install(action)
-
- # 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:
- self.tutorial.probeManager.uninstall(action)
-
- # 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 in st._transitions:
- if st._transitions[event] == state_name:
- del st._transitions[event]
-
- # 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, state in state._transitions.items():
- next_states.add(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, state in st._transitions.items():
- if state == state_name:
- states.append(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
-
- 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
- """
- 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 == 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 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
diff --git a/tutorius/dialog.py b/tutorius/dialog.py
deleted file mode 100644
index be51a0e..0000000
--- a/tutorius/dialog.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# Copyright (C) 2009, Tutorius.org
-#
-# 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
-"""
-The Dialog module provides means of interacting with the user
-through the use of Dialogs.
-"""
-import gtk
-
-class TutoriusDialog(gtk.Dialog):
- """
- TutoriusDialog is a simple wrapper around gtk.Dialog.
-
- It allows creating and showing a dialog and connecting the response and
- button click events to callbacks.
- """
- def __init__(self, label="Hint", button_clicked_cb=None, response_cb=None):
- """
- Constructor.
-
- @param label text to be shown on the dialog
- @param button_clicked_cb callback for the button click
- @param response_cb callback for the dialog response
- """
- gtk.Dialog.__init__(self)
-
- self._button = gtk.Button(label)
-
- self.add_action_widget(self._button, 1)
-
- if not button_clicked_cb == None:
- self._button.connect("clicked", button_clicked_cb)
-
- self._button.show()
-
- if not response_cb == None:
- self.connect("response", response_cb)
-
- self.set_decorated(False)
-
- def set_button_clicked_cb(self, funct):
- """Setter for the button_clicked callback"""
- self._button.connect("clicked", funct)
-
- def close_self(self, arg=None):
- """Close the dialog"""
- self.destroy()
diff --git a/tutorius/testwin.py b/tutorius/testwin.py
deleted file mode 100644
index ef92b7f..0000000
--- a/tutorius/testwin.py
+++ /dev/null
@@ -1,92 +0,0 @@
-#!/usr/bin/env python
-# Copyright (C) 2009, Tutorius.org
-#
-# 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
-
-import gtk
-import dragbox
-import textbubble
-
-box = None
-
-def _destroy(widget, data=None):
- gtk.main_quit()
-
-def _delete_event(widget, event, data=None):
- print "quitting"
- return False
-
-def blublu(widget, data=""):
- print data
-
-def _drag_toggle(widget, data=None):
- global box
- box.dragMode = not box.dragMode
-
-
-def addBtn(widget, data, bubble=0, btns=[0]):
- if bubble == 1:
- bt = textbubble.TextBubble("Bubble(%d)"%btns[0])
- else:
- bt = gtk.Button("Bubble(%d)"%btns[0])
- ##bt.set_size_request(60,40)
- bt.show()
- data.attach(bt)
- btns[0] += 1
-
-def main():
- global box
- win = gtk.Window(type=gtk.WINDOW_TOPLEVEL)
- win.connect("delete_event", _delete_event)
- win.connect("destroy", _destroy)
-
- win.set_default_size(800,600)
-
- vbox = gtk.VBox()
- vbox.show()
- win.add(vbox)
-
- check = gtk.CheckButton(label="dragMode")
- check.connect("toggled", _drag_toggle)
- check.show()
- vbox.pack_start(check, expand=False)
-
- btnadd = gtk.Button("Add Bubble")
- btnadd.show()
- vbox.pack_start(btnadd, expand=False)
- btnadd2 = gtk.Button("Add Button")
- btnadd2.show()
- vbox.pack_start(btnadd2, expand=False)
-
-## bubble = textbubble.TextBubble("Bubbles!")
-## bubble.show()
-## bubble.set_size_request(40,40)
-## vbox.pack_start(bubble, expand=False)
-
- box = dragbox.DragBox()
- box.set_border_width(10)
- box.show()
- vbox.pack_start(box, expand=True, fill=True)
-
- btnadd.connect("clicked", addBtn, box, 1)
- btnadd2.connect("clicked", addBtn, box)
-
- win.show()
- gtk.main()
-
-
-if __name__ == "__main__":
- main()
-
diff --git a/tutorius/textbubble.py b/tutorius/textbubble.py
deleted file mode 100644
index e09b298..0000000
--- a/tutorius/textbubble.py
+++ /dev/null
@@ -1,109 +0,0 @@
-# Copyright (C) 2009, Tutorius.org
-#
-# 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
-"""
-This module represents TextBubble widget. Also, it aims to be a short example
-of drawing with Cairo.
-"""
-
-import gtk
-from math import pi as M_PI
-import cairo
-
-# FIXME set as subclass of gtk.Widget, not EventBox
-class TextBubble(gtk.EventBox):
- def __init__(self, label):
- gtk.EventBox.__init__(self)
-
- ##self.set_app_paintable(True) # else may be blank
- # FIXME ensure previous call does not interfere with widget stacking
- self.label = label
- self.lineWidth = 5
-
- self.connect("expose-event", self._on_expose)
-
- def __draw_with_cairo__(self, context):
- """
-
- """
- pass
-
- def _on_expose(self, widget, event):
- """Redraw event callback."""
- # TODO
- ctx = self.window.cairo_create()
-
- # set drawing region. Useless since this widget has its own window.
- ##region = gtk.gdk.region_rectangle(self.allocation)
- ##region.intersect(gtk.gdk.region_rectangle(event.area))
- ##ctx.region(region)
- ##ctx.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
- ##ctx.clip()
-
- ##import pdb; pdb.set_trace()
- ##ctx.set_operator(cairo.OPERATOR_CLEAR)
- ##ctx.paint()
- ##ctx.set_operator(cairo.OPERATOR_OVER)
-
- width = self.allocation.width
- height = self.allocation.height
- xradius = width/2
- yradius = height/2
- width -= self.lineWidth
- height -= self.lineWidth
- ctx.move_to(self.lineWidth, yradius)
- ctx.curve_to(self.lineWidth, self.lineWidth,
- self.lineWidth, self.lineWidth, xradius, self.lineWidth)
- ctx.curve_to(width, self.lineWidth,
- width, self.lineWidth, width, yradius)
- ctx.curve_to(width, height, width, height, xradius, height)
- ctx.curve_to(self.lineWidth, height,
- self.lineWidth, height, self.lineWidth, yradius)
- ctx.set_source_rgb(1.0, 1.0, 1.0)
- ctx.fill_preserve()
- ctx.set_line_width(self.lineWidth)
- ctx.set_source_rgb(0.0, 0.0, 0.0)
- ctx.stroke()
-
- _, _, textWidth, textHeight, _, _ = ctx.text_extents(self._label)
- ctx.move_to(int((self.allocation.width-textWidth)/2),
- int((self.allocation.height+textHeight)/2))
- ctx.text_path(self._label)
- ctx.fill()
-
- return True
-
-
- def _set_label(self, value):
- """Sets the label and flags the widget to be redrawn."""
- self._label = value
- # FIXME hack to calculate size. necessary because may not have been
- # realized
- surf = cairo.SVGSurface("/dev/null", 0, 0)
- ctx = cairo.Context(surf)
- _, _, width, height, _, _ = ctx.text_extents(self._label)
- del ctx, surf
-
- # FIXME bogus values follows
- self.set_size_request(int(width+20), int(height+40))
- # TODO test changing a realized label
-
- def _get_label(self):
- """Getter method for the label property"""
- return self._label
-
- label = property(fget=_get_label, fset=_set_label,\
- doc="Text label which is to be painted on the top of the widget")
-