diff options
author | Vincent 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) |
commit | 2bda2c8c50797a6214126edc5afdc3fc5d8e9969 (patch) | |
tree | 4697873cfa4de532ee691a17a29e781fe5c5782d | |
parent | fb422aef7ee6832c85c8fa8a703e491838e74d62 (diff) |
improve coverage report and cleanup old stuff
-rwxr-xr-x | setup.py | 18 | ||||
-rw-r--r-- | tests/coretests.py | 629 | ||||
-rw-r--r-- | tests/skip | 1 | ||||
-rw-r--r-- | tutorius/core.py | 618 | ||||
-rw-r--r-- | tutorius/dialog.py | 59 | ||||
-rw-r--r-- | tutorius/testwin.py | 92 | ||||
-rw-r--r-- | tutorius/textbubble.py | 109 |
7 files changed, 17 insertions, 1509 deletions
@@ -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() @@ -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") - |