From a4114a946cc7a57c1cfce5760737c3f05425bc86 Mon Sep 17 00:00:00 2001 From: mike Date: Sun, 26 Apr 2009 18:55:55 +0000 Subject: Merge branch 'jc' of ssh://mike@bobthebuilder.mine.nu:8080/home/git into jc_support --- diff --git a/src/sugar/activity/activity.py b/src/sugar/activity/activity.py index a5188fd..3e2d3d4 100644 --- a/src/sugar/activity/activity.py +++ b/src/sugar/activity/activity.py @@ -77,6 +77,7 @@ from sugar.datastore import datastore from sugar.session import XSMPClient from sugar import wm from sugar.tutorius.services import ObjectStore +from sugar.tutorius.tutoserialize import TutoSerializer _ = lambda msg: gettext.dgettext('sugar-toolkit', msg) @@ -128,15 +129,14 @@ class ActivityToolbar(gtk.Toolbar): self.tutorials.combo.connect('changed', self.__tutorial_changed_cb) # Get tutorial list by file logging.debug("************************************ before creating serialize") -## serialize = TutoSerializer() -## logging.debug("************************************ before calling load_tuto_list()") -## -## #tutorials = self._activity.get_tutorials() -## if getattr(self._activity,"_tutorials",None) is None: -## tutorials = serialize.load_tuto_list() + serialize = TutoSerializer() + logging.debug("************************************ before calling load_tuto_list()") + + #tutorials = self._activity.get_tutorials() + if getattr(self._activity,"_tutorials",None) is None: + tutorials = serialize.load_tuto_list() self._current_tutorial = None - tutorials = None if tutorials: for key, tutorial in tutorials.items(): # self.tutorials.combo.append_item(key, _(tutorial.name)) 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 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 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 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. """ -- cgit v0.9.1