Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src/sugar/tutorius
diff options
context:
space:
mode:
Diffstat (limited to 'src/sugar/tutorius')
-rw-r--r--src/sugar/tutorius/Makefile.am7
-rw-r--r--src/sugar/tutorius/actions.py125
-rw-r--r--src/sugar/tutorius/bundler.py94
-rwxr-xr-xsrc/sugar/tutorius/tests/run-tests.py30
-rw-r--r--src/sugar/tutorius/tests/serializertests.py32
5 files changed, 159 insertions, 129 deletions
diff --git a/src/sugar/tutorius/Makefile.am b/src/sugar/tutorius/Makefile.am
index 8805314..0632e47 100644
--- a/src/sugar/tutorius/Makefile.am
+++ b/src/sugar/tutorius/Makefile.am
@@ -1,3 +1,5 @@
+SUBDIRS = uam
+
sugardir = $(pythondir)/sugar/tutorius
sugar_PYTHON = \
__init__.py \
@@ -9,5 +11,8 @@ sugar_PYTHON = \
services.py \
overlayer.py \
editor.py \
- linear_creator.py\
+ linear_creator.py \
+ constraints.py \
+ properties.py \
+ tutoserialize.py \
bundler.py
diff --git a/src/sugar/tutorius/actions.py b/src/sugar/tutorius/actions.py
index 2dca6c7..2d630be 100644
--- a/src/sugar/tutorius/actions.py
+++ b/src/sugar/tutorius/actions.py
@@ -23,12 +23,13 @@ from dialog import TutoriusDialog
import overlayer
from sugar.tutorius.editor import WidgetIdentifier
from sugar.tutorius.services import ObjectStore
-
+from sugar.tutorius.properties import *
class Action(object):
"""Base class for Actions"""
def __init__(self):
object.__init__(self)
+ self.properties = None
def do(self, **kwargs):
"""
@@ -43,12 +44,17 @@ class Action(object):
pass #Should raise NotImplemented?
def get_properties(self):
- if not hasattr(self, "_props") or self._props is None:
- self._props = []
- for i in dir(self.__class__):
- if type(getattr(self.__class__,i)) is property:
- self._props.append(i)
- return self._props
+ """
+ Fills self.property with a dict of TutoriusProperty and return the list
+ of property names. get_properties has to be called before accessing
+ self.property
+ """
+ if self.properties is None:
+ self.properties = {}
+ for i in dir(self):
+ if isinstance(getattr(self,i), TutoriusProperty):
+ self.properties[i] = getattr(self,i)
+ return self.properties.keys()
class OnceWrapper(object):
"""
@@ -89,36 +95,21 @@ class DialogMessage(Action):
@param message A string to display to the user
@param pos A list of the form [x, y]
"""
- def __init__(self, message, pos=[0,0]):
+ def __init__(self, message, pos=None):
super(DialogMessage, self).__init__()
- self._message = message
- self._position = pos
self._dialog = None
-
- def set_message(self, msg):
- self._message = msg
- def get_message(self):
- return self._message
-
- message = property(fget=get_message, fset=set_message)
-
- def set_pos(self, x, y):
- self._position = [x, y]
-
- def get_pos(self):
- return self._position
-
- position = property(fget=get_pos, fset=set_pos)
+ self.message = TStringProperty(message)
+ self.position = TArrayProperty(pos or [0, 0], 2, 2)
def do(self):
"""
Show the dialog
"""
- self._dialog = TutoriusDialog(self._message)
+ self._dialog = TutoriusDialog(self.message.value)
self._dialog.set_button_clicked_cb(self._dialog.close_self)
self._dialog.set_modal(False)
- self._dialog.move(self.position[0], self.position[1])
+ self._dialog.move(self.position.value[0], self.position.value[1])
self._dialog.show()
def undo(self):
@@ -137,34 +128,20 @@ class BubbleMessage(Action):
@param message A string to display to the user
@param pos A list of the form [x, y]
@param speaker treeish representation of the speaking widget
+ @param tailpos The position of the tail of the bubble; useful to point to
+ specific elements of the interface
"""
def __init__(self, message, pos=[0,0], speaker=None, tailpos=None):
Action.__init__(self)
- self._message = message
- self._position = pos
-
+ self.message = TStringProperty(message)
+ # Create the position as an array of fixed-size 2
+ self.position = TArrayProperty(pos, 2, 2)
+ # Do the same for the tail position
+ self.tail_pos = TArrayProperty(tailpos, 2, 2)
+
self.overlay = None
self._bubble = None
self._speaker = None
- self._tailpos = tailpos
-
- def set_message(self, msg):
- self._message = msg
- def get_message(self):
- return self._message
- message = property(fget=get_message, fset=set_message, doc="Message displayed to the user")
-
- def set_pos(self, x, y):
- self._position = [x, y]
- def get_pos(self):
- return self._position
- position = property(fget=get_pos, fset=set_pos, doc="Position in [x, y] on the screen")
-
- def set_tail_pos(self, x, y):
- self._tailpos = [x, y]
- def get_tail_pos(self):
- return self._tailpos
- tail_pos = property(fget=get_tail_pos, fset=set_tail_pos, doc="Position the tail of the bubble must point to")
def do(self):
"""
@@ -245,53 +222,54 @@ class DisableWidgetAction(Action):
self._widget = gtkutils.find_widget(os.activity, self._target)
if self._widget:
self._widget.set_sensitive(False)
-
- def undo(self):
+
+ def undo(self):
"""Action undo"""
if self._widget:
self._widget.set_sensitive(True)
-
+
+
class TypeTextAction(Action):
- """
+ """
Simulate a user typing text in a widget
Work on any widget that implements a insert_text method
-
+
@param widget The treehish representation of the widget
@param text the text that is typed
- """
+ """
def __init__(self, widget, text):
Action.__init__(self)
-
+
self._widget = widget
self._text = text
-
+
def do(self, **kwargs):
- """
- Type the text
- """
+ """
+ Type the text
+ """
widget = gtkutils.find_widget(ObjectStore().activity, self._widget)
if hasattr(widget, "insert_text"):
widget.insert_text(self._text, -1)
-
- def undo(self):
- """
- no undo
- """
- pass
-
+
+ def undo(self):
+ """
+ no undo
+ """
+ pass
+
class ClickAction(Action):
- """
+ """
Action that simulate a click on a widget
Work on any widget that implements a clicked() method
-
+
@param widget The threehish representation of the widget
- """
+ """
def __init__(self, widget):
Action.__init__(self)
self._widget = widget
-
- def do(self):
- """
+
+ def do(self):
+ """
click the widget
"""
widget = gtkutils.find_widget(ObjectStore().activity, self._widget)
@@ -303,3 +281,4 @@ class ClickAction(Action):
No undo
"""
pass
+
diff --git a/src/sugar/tutorius/bundler.py b/src/sugar/tutorius/bundler.py
index c7456c4..58288ca 100644
--- a/src/sugar/tutorius/bundler.py
+++ b/src/sugar/tutorius/bundler.py
@@ -24,12 +24,12 @@ import logging
import os
import uuid
import xml.dom.minidom
-#import xml.dom.ext
from sugar.tutorius import gtkutils, overlayer
from sugar.tutorius.core import Tutorial, State, FiniteStateMachine
from sugar.tutorius.actions import *
import sugar.tutorius.filters
+#from sugar.tutorius.filters import EventFilter, TimerEvent, GtkWidgetEventFilter, GtkWidgetTypeFilter
from ConfigParser import SafeConfigParser
def _get_store_root():
@@ -141,30 +141,32 @@ class XMLSerializer(Serializer):
actionNode = doc.createElement("Action")
actionsList.appendChild(actionNode)
if type(action) is DialogMessage:
- actionNode.setAttribute("Class", type(action))
+ # Using .__class__ since type() doesn't have the same behavior
+ # with class derivating from object and class that don't
+ actionNode.setAttribute("Class", str(action.__class__))
actionNode.setAttribute("Message", action.message)
actionNode.setAttribute("Position", action.position)
elif type(action) is BubbleMessage:
- actionNode.setAttribute("Class", str(type(action)))
+ actionNode.setAttribute("Class", str(action.__class__))
actionNode.setAttribute("Message", action.message)
actionNode.setAttribute("Position", str(action.position))
actionNode.setAttribute("Tail_pos", str(action.tail_pos))
# TODO : elif for each type of action
elif type(action) is WidgetIdentifyAction:
- actionNode.setAttribute("Class", str(type(action)))
+ actionNode.setAttribute("Class", str(action.__class__))
# TODO
elif type(action) is ChainAction:
# TODO
- actionNode.setAttribute("Class", str(type(action)))
+ actionNode.setAttribute("Class", str(action.__class__))
elif type(action) is DisableWidgetAction:
# TODO
- actionNode.setAttribute("Class", str(type(action)))
+ actionNode.setAttribute("Class", str(action.__class__))
elif type(action) is TypeTextAction:
# TODO
- actionNode.setAttribute("Class", str(type(action)))
+ actionNode.setAttribute("Class", str(action.__class__))
elif type(action) is ClickAction:
# TODO
- actionNode.setAttribute("Class", str(type(action)))
+ actionNode.setAttribute("Class", str(action.__class__))
return actionsList
@@ -176,15 +178,18 @@ class XMLSerializer(Serializer):
for event_f in event_filters:
eventFilterNode = eventFiltersList.appendChild("EventFilter")
# TODO : elif for each type of event filters
- if type(event_f) is TimerEvent:
+ if type(event_f) is sugar.tutorius.filters.TimerEvent:
+ # Using .__class__ since type() doesn't have the same behavior
+ # with class derivating from object and class that don't
+
# TODO
- eventFilterNode.setAttribute("Class", str(type(event_f)))
- elif type(event_f) is GtkWidgetEventFilter:
+ eventFilterNode.setAttribute("Class", str(event_f.__class__))
+ elif type(event_f) is sugar.tutorius.filters.GtkWidgetEventFilter:
# TODO
- eventFilterNode.setAttribute("Class", str(type(event_f)))
- elif type(event_f) is GtkWidgetTypeFilter:
+ eventFilterNode.setAttribute("Class", str(event_f.__class__))
+ elif type(event_f) is sugar.tutorius.filters.GtkWidgetTypeFilter:
# TODO
- eventFilterNode.setAttribute("Class", str(type(event_f)))
+ eventFilterNode.setAttribute("Class", str(event_f.__class__))
return eventFiltersList
@@ -275,21 +280,24 @@ class XMLSerializer(Serializer):
@param filters_elem An XML Element representing a list of event filters
"""
- event_filters_list = []
+ reformed_event_filters_list = []
+ # item(0) because there is always only one <EventFilterList> tag in the xml file
+ # so states_elem should always contain only one element
+ event_filter_element_list = filters_elem.item(0).getElementsByTagName("EventFilter")
- for event_filter in actions_elem.getElementByTagName("EventFilter"):
+ for event_filter in event_filter_element_list:
# TODO : elif for each type of event filter
- if event_filter.getAttribute("Class") is TimerEvent:
+ if event_filter.getAttribute("Class") == str(sugar.tutorius.filters.TimerEvent):
# TODO
pass
- elif event_filter.getAttribute("Class") is GtkWidgetEventFilter:
+ elif event_filter.getAttribute("Class") == str(sugar.tutorius.filters.GtkWidgetEventFilter):
# TODO
pass
- elif event_filter.getAttribute("Class") is GtkWidgetTypeFilter:
+ elif event_filter.getAttribute("Class") == str(sugar.tutorius.filters.GtkWidgetTypeFilter):
# TODO
pass
- return event_filters_list
+ return reformed_event_filters_list
def _load_xml_actions(self, actions_elem):
"""
@@ -297,53 +305,59 @@ class XMLSerializer(Serializer):
@param actions_elem An XML Element representing a list of Actions
"""
- actions_list = []
+ reformed_actions_list = []
+ # item(0) because there is always only one <Actions> tag in the xml file
+ # so states_elem should always contain only one element
+ actions_element_list = actions_elem.item(0).getElementsByTagName("Action")
- for action in actions_elem.getElementByTagName("Action"):
+ for action in actions_element_list:
# TODO : elif for each type of action
- if action.getAttribute("Class") is DialogMessage:
+ if action.getAttribute("Class") == str(DialogMessage):
message = action.getAttribute("Message")
position = action.getAttribute("Postion")
- actions_list.append(DialogMessage(message,position))
- elif action.getAttribute("Class") is BubbleMessage:
+ reformed_actions_list.append(DialogMessage(message,position))
+ elif action.getAttribute("Class") == str(BubbleMessage):
message = action.getAttribute("Message")
position = action.getAttribute("Postion")
tail_pos = action.getAttribute("Tail_pos")
- actions_list.append(BubbleMessage(message,position,None,tail_pos))
- elif action.getAttribute("Class") is WidgetIdentifyAction:
+ reformed_actions_list.append(BubbleMessage(message,position,None,tail_pos))
+ elif action.getAttribute("Class") == str(WidgetIdentifyAction):
# TODO
pass
- elif action.getAttribute("Class") is ChainAction:
+ elif action.getAttribute("Class") == str(ChainAction):
# TODO
pass
- elif action.getAttribute("Class") is DisableWidgetAction:
+ elif action.getAttribute("Class") == str(DisableWidgetAction):
# TODO
pass
- elif action.getAttribute("Class") is TypeTextAction:
+ elif action.getAttribute("Class") == str(TypeTextAction):
# TODO
pass
- elif action.getAttribute("Class") is ClickAction:
+ elif action.getAttribute("Class") == str(ClickAction):
# TODO
pass
- return actions_list
+ return reformed_actions_list
def _load_xml_states(self, states_elem):
"""
- Takes in a States element and fleshes out a complete dictionnary of State
+ Takes in a States element and fleshes out a complete list of State
objects.
@param states_elem An XML Element that represents a list of States
"""
- states_dict = {}
+ reformed_state_list = []
+ # item(0) because there is always only one <States> tag in the xml file
+ # so states_elem should always contain only one element
+ states_element_list = states_elem.item(0).getElementsByTagName("State")
- for state in states_elem.getElementByTagName("State"):
- stateName = states_elem.getAttribute("Name")
- actions_list = _load_xml_actions(state.getElementByTagName("Actions"))
- event_filters_list = _load_xml_event_filters(state.getElementByTagName("EventFiltersList"))
- states_dict[stateName] = State(stateName, actions_list, event_filters_list)
+ for state in states_element_list:
+ stateName = state.getAttribute("Name")
+ actions_list = self._load_xml_actions(state.getElementsByTagName("Actions"))
+ event_filters_list = self._load_xml_event_filters(state.getElementsByTagName("EventFiltersList"))
+ reformed_state_list.append(State(stateName, actions_list, event_filters_list))
- return states_dict
+ return reformed_state_list
def _load_xml_fsm(self, fsm_elem):
"""
diff --git a/src/sugar/tutorius/tests/run-tests.py b/src/sugar/tutorius/tests/run-tests.py
index 6f22fee..97665f7 100755
--- a/src/sugar/tutorius/tests/run-tests.py
+++ b/src/sugar/tutorius/tests/run-tests.py
@@ -10,10 +10,17 @@ sys.path.insert(0,
)
FULL_PATH = os.path.join(INSTALL_PATH,"sugar/tutorius")
+SUBDIRS = ["uam"]
GLOB_PATH = os.path.join(FULL_PATH,"*.py")
import unittest
from glob import glob
+def report_files():
+ ret = glob(GLOB_PATH)
+ for dir in SUBDIRS:
+ ret += glob(os.path.join(FULL_PATH,dir,"*.py"))
+ return ret
+
import sys
if __name__=='__main__':
if "--coverage" in sys.argv:
@@ -28,26 +35,41 @@ if __name__=='__main__':
import gtkutilstests
import overlaytests
import linear_creatortests
+ import actiontests
+ import uamtests
+ import filterstests
+ import constraintstests
+ import propertiestests
import serializertests
-
suite = unittest.TestSuite()
suite.addTests(unittest.findTestCases(coretests))
suite.addTests(unittest.findTestCases(servicestests))
suite.addTests(unittest.findTestCases(gtkutilstests))
suite.addTests(unittest.findTestCases(overlaytests))
suite.addTests(unittest.findTestCases(linear_creatortests))
- suite.addTests(unittest.findTestCases(serializertests))
-
+ suite.addTests(unittest.findTestCases(actiontests))
+ suite.addTests(unittest.findTestCases(uamtests))
+ suite.addTests(unittest.findTestCases(filterstests))
+ suite.addTests(unittest.findTestCases(constraintstests))
+ suite.addTests(unittest.findTestCases(propertiestests))
+ suite.addTests(unittest.findTestCases(serializertests))
runner = unittest.TextTestRunner()
runner.run(suite)
coverage.stop()
- coverage.report(glob(GLOB_PATH))
+ coverage.report(report_files())
coverage.erase()
else:
from coretests import *
from servicestests import *
from gtkutilstests import *
from overlaytests import *
+ from actiontests import *
+ from linear_creatortests import *
+ from uamtests import *
+ from filterstests import *
+ from constraintstests import *
+ from propertiestests import *
+ from actiontests import *
from serializertests import *
unittest.main()
diff --git a/src/sugar/tutorius/tests/serializertests.py b/src/sugar/tutorius/tests/serializertests.py
index 0b7fabd..75fe8eb 100644
--- a/src/sugar/tutorius/tests/serializertests.py
+++ b/src/sugar/tutorius/tests/serializertests.py
@@ -28,7 +28,7 @@ import unittest
import logging
import linecache
import os
-import cPickle as pickle
+import shutil
from sugar.tutorius import gtkutils, overlayer
from sugar.tutorius.core import Tutorial, State, FiniteStateMachine
@@ -63,10 +63,12 @@ class XMLSerializerTest(unittest.TestCase):
self.fsm.add_state(st2)
self.uuid = uuid1()
+
- def test_save(self):
+ def test_save(self, remove=True):
"""
Writes an FSM to disk, then compares the file to the expected results.
+ "Remove" boolean argument specify if the test data must be removed or not
"""
# Make the serializer believe the test is in a activity path
testpath = "/tmp/testdata/"
@@ -78,19 +80,18 @@ class XMLSerializerTest(unittest.TestCase):
#rpdb2.start_embedded_debugger('flakyPass')
xml_ser.save_fsm(self.fsm, "fsm.xml", os.path.join(sugar.tutorius.bundler._get_store_root(), str(self.uuid)))
- # Compare the two files
-
+ #Remove test file and path
+ if remove == True:
+ os.remove(os.path.join(sugar.tutorius.bundler._get_store_root(), str(self.uuid)) + "/fsm.xml")
+ if os.path.isdir(testpath):
+ shutil.rmtree(testpath)
- #Remove test file and path (commented for testing, to uncomment)
-## os.remove(testpath + os.path.join(sugar.tutorius.bundler._get_store_root(), str(self.uuid)) + "/fsm.xml")
-## os.removedirs(testpath)
-
def test_save_and_load(self):
"""
Load up the written FSM and compare it with the object representation.
"""
- self.test_save()
-
+ self.test_save(False)
+ testpath = "/tmp/testdata/"
#rpdb2.start_embedded_debugger('flakyPass')
xml_ser = XMLSerializer()
@@ -101,12 +102,21 @@ class XMLSerializerTest(unittest.TestCase):
# Compare the two FSMs
assert loaded_fsm._states.get("INIT").name == self.fsm._states.get("INIT").name, \
'FSM underlying dictionary differ from original to pickled/reformed one'
- assert loaded_fsm._states.get("LOST").name == self.fsm._states.get("Second").name, \
+ assert loaded_fsm._states.get("Second").name == self.fsm._states.get("Second").name, \
'FSM underlying dictionary differ from original to pickled/reformed one'
+ assert loaded_fsm._states.get("INIT").get_action_list()[0].get_message() == \
+ self.fsm._states.get("INIT").get_action_list()[0].get_message(), \
+ 'FSM underlying State underlying Action differ from original to reformed one'
+
+ os.remove(os.path.join(sugar.tutorius.bundler._get_store_root(), str(self.uuid)) + "/fsm.xml")
+ if os.path.isdir(testpath):
+ shutil.rmtree(testpath)
+
# Helper classes to help testing
class SerializerTest(unittest.TestCase):
"""
+
This class has to test the Serializer methods as well as the expected
functionality.
"""