Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src/sugar
diff options
context:
space:
mode:
authormike <michael.jmontcalm@gmail.com>2009-04-17 04:36:31 (GMT)
committer mike <michael.jmontcalm@gmail.com>2009-04-17 04:36:31 (GMT)
commite784a2f0b92ecaf46a77ddca94b31fcc86e0cbae (patch)
tree56e23c87959b877e70614e0692b158eae2fd4fc8 /src/sugar
parente3633a2cc4a5aa061172f4962da86ce346151ff8 (diff)
parent544b836dab71d36290d3da5131afaef77c88ccd8 (diff)
Merge branch 'jc_support' into mike
Conflicts: source/external/source/sugar-toolkit/src/sugar/tutorius/tests/coretests.py source/external/source/sugar-toolkit/src/sugar/tutorius/tests/run-tests.py
Diffstat (limited to 'src/sugar')
-rw-r--r--src/sugar/activity/activity.py43
-rw-r--r--src/sugar/tutorius/Makefile.am7
-rw-r--r--src/sugar/tutorius/actions.py53
-rw-r--r--src/sugar/tutorius/bundler.py440
-rw-r--r--src/sugar/tutorius/core.py20
-rw-r--r--src/sugar/tutorius/services.py1
-rw-r--r--src/sugar/tutorius/temp.py1
-rw-r--r--src/sugar/tutorius/tests/bundlertests.py65
-rw-r--r--src/sugar/tutorius/tests/coretests.py130
-rwxr-xr-xsrc/sugar/tutorius/tests/run-tests.py5
-rw-r--r--src/sugar/tutorius/tests/serializertests.py122
11 files changed, 847 insertions, 40 deletions
diff --git a/src/sugar/activity/activity.py b/src/sugar/activity/activity.py
index 21e38f6..a5188fd 100644
--- a/src/sugar/activity/activity.py
+++ b/src/sugar/activity/activity.py
@@ -126,11 +126,22 @@ class ActivityToolbar(gtk.Toolbar):
if hasattr(self._activity,"get_tutorials") and hasattr(self._activity.get_tutorials,"__call__"):
self.tutorials = ToolComboBox(label_text=_('Tutorials:'))
self.tutorials.combo.connect('changed', self.__tutorial_changed_cb)
- tutorials = self._activity.get_tutorials()
+ # 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()
+
self._current_tutorial = None
+ tutorials = None
if tutorials:
for key, tutorial in tutorials.items():
- self.tutorials.combo.append_item(key, tutorial.name)
+ # self.tutorials.combo.append_item(key, _(tutorial.name))
+ self.tutorials.combo.append_item(key, _(tutorial))
+
self.insert(self.tutorials, -1)
self.tutorials.show()
@@ -196,16 +207,34 @@ class ActivityToolbar(gtk.Toolbar):
"""
Callback for tutorial combobox item change
"""
- model = combo.get_model()
- it = combo.get_active_iter()
- (key, ) = model.get(it, 0)
- tutorial = self._activity.get_tutorials().get(key,None)
- if not tutorial is None:
+ logging.debug("************ function __tutorial_changed_cb called")
+ serialize = TutoSerializer()
+
+ if self._current_tutorial:
+ self._current_tutorial.detach()
+
+ model = self.tutorials.combo.get_model()
+ it = self.tutorials.combo.get_active_iter()
+ (key,) = model.get(it, 0)
+
+ #Load and build chosen tutorial from Pickle file
+ logging.debug("****************** before tuto build")
+## tutorials = self._activity.get_tutorials()
+ tuto = serialize.build_tutorial(key)
+ self._activity._tutorials = tuto
+ logging.debug("****************** after tuto build")
+## tutorial = self._activity.get_tutorials().get(key,None)
+ tutorial = tuto.get(key, None)
+
+ if not getattr(self._activity,"_tutorials",None) is None:
if not self._current_tutorial is None:
self._current_tutorial.detach()
+
self._current_tutorial = tutorial
+ logging.debug(" *************** try to attach tuto")
self._current_tutorial.attach(self._activity)
+
def __keep_clicked_cb(self, button):
self._activity.copy()
diff --git a/src/sugar/tutorius/Makefile.am b/src/sugar/tutorius/Makefile.am
index 02f832b..8805314 100644
--- a/src/sugar/tutorius/Makefile.am
+++ b/src/sugar/tutorius/Makefile.am
@@ -1,5 +1,3 @@
-SUBDIRS = uam
-
sugardir = $(pythondir)/sugar/tutorius
sugar_PYTHON = \
__init__.py \
@@ -11,6 +9,5 @@ sugar_PYTHON = \
services.py \
overlayer.py \
editor.py \
- linear_creator.py \
- constraints.py \
- properties.py
+ linear_creator.py\
+ bundler.py
diff --git a/src/sugar/tutorius/actions.py b/src/sugar/tutorius/actions.py
index fc364e0..d81e3c2 100644
--- a/src/sugar/tutorius/actions.py
+++ b/src/sugar/tutorius/actions.py
@@ -222,3 +222,56 @@ class DisableWidgetAction(Action):
"""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
+ """
+ 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
+
+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):
+ """
+ click the widget
+ """
+ widget = gtkutils.find_widget(ObjectStore().activity, self._widget)
+ if hasattr(widget, "clicked"):
+ widget.clicked()
+
+ def undo(self):
+ """
+ No undo
+ """
+ pass
diff --git a/src/sugar/tutorius/bundler.py b/src/sugar/tutorius/bundler.py
index 585ba88..a28d6ef 100644
--- a/src/sugar/tutorius/bundler.py
+++ b/src/sugar/tutorius/bundler.py
@@ -21,33 +21,89 @@ This module contains all the data handling class of Tutorius
"""
import logging
-import linecache
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 DialogMessage, OnceWrapper, BubbleMessage
-from sugar.tutorius.filters import GtkWidgetEventFilter, TimerEvent
+import sugar.tutorius.actions
+import sugar.tutorius.filters
+from ConfigParser import SafeConfigParser
+
+def __get_store_root():
+ return os.path.join(os.getenv("SUGAR_PREFIX"),"share","tutorius","data")
+def __get_bundle_root():
+ return os.path.join(os.getenv("SUGAR_BUNDLE_PATH"),"data","tutorius","data")
+
+INI_ACTIVITY_SECTION = "RELATED_ACTIVITIES"
+INI_METADATA_SECTION = "GENERAL_METADATA"
+INI_GUID_PROPERTY = "GUID"
+INI_NAME_PROPERTY = "NAME"
+INI_XML_FSM_PROPERTY = "FSM_FILENAME"
class TutorialStore:
- def list_avaible_tutorials(self, activity_name):
+ def list_avaible_tutorials(self, activity_name, activity_vers):
"""
Recuperate the list of all tutorials present on disk for a
given activity.
"""
- NotImplementedError
+
+ store_root = __get_store_root()
+ bundle_root = __get_bundle_root()
+
+ logging.debug("*********** Path of store_root : " + store_root)
+
+ # Create /data/tutorius if no exists
+ if not os.path.exists(store_root):
+ os.mkdir(store_root)
+ logging.debug("************* Creating %s folder" % store_root)
+
+ tutoGuidName = {}
+
+ # iterate in each GUID subfolder
+ for dir in os.listdir(store_root):
+ # iterate for each ".ini" file in the activity store_root folder
+ for file_name in os.listdir(store_root + "/" + dir):
+
+ if file_name.endswith(".ini"):
+ logging.debug("************** .ini file found : " + file_name)
+ # Filter for just .ini files who metadata ACTIVITY_NAME
+ # match 'activity_name' given in argument.
+ config = SafeConfigParser()
+ config.read(file_name)
+ # Get all activity tuples (Activity_Name: Activity_Version)
+ file_activity_tuples = config.items(INI_ACTIVITY_SECTION)
+
+ for i in range(0, len(file_activity_tuples) - 1):
+
+ if file_activity_tuples[i][0] == activity_name and \
+ int(file_activity_tuples[i][1]) == activity_vers:
+ # Add this tutorial guid and name in the dictionary
+ file_activity_guid = config.get(INI_METADATA_SECTION,
+ INI_GUID_PROPERTY)
+ file_activity_name = config.get(INI_METADATA_SECTION,
+ INI_NAME_PROPERTY)
+ tutoGuidName[file_activity_name] = file_activity_guid
+
+ return tutoGuidName
class Serializer:
"""
- Class that provide serializing and deserializing of the FSM
- used in the tutorials to/from disk.
+ Interface that provide serializing and deserializing of the FSM
+ used in the tutorials to/from disk. Must be inherited.
"""
- def save_fsm(self,fsm):
+ def save_fsm(self,fsm, guid = None):
"""
- Save fsm to disk.
+ Save fsm to disk. If a GUID parameter is provided, the existing GUID is
+ located in the .ini files in the store root and bundle root and
+ the corresponding FSM is/are overwritten. If the GUId is not found, an
+ exception occur. If no GUID is provided, FSM is written in a new file
+ in the store root.
"""
NotImplementedError
@@ -60,43 +116,381 @@ class Serializer:
class XMLSerializer(Serializer):
"""
Class that provide serializing and deserializing of the FSM
- used in the tutorials to/from a xml file.
+ used in the tutorials to/from a .xml file. Inherit from Serializer
"""
- def save_fsm(self,fsm):
+ def create_state_dict_node(self, state_dict, doc):
"""
- Save fsm to .xml file
+ Create and return a xml Node from a State dictionnary.
"""
- NotImplementedError
+ statesList = doc.createElement("States")
+ for state_name, state in state_dict.items():
+ stateNode = statesList.appendChild("State")
+ stateNode.setAttribute("State:Name", state_name)
+ stateNode = stateNode.appendChild(create_action_list_node(state.action_list, doc))
+ stateNode = stateNode.appendChild(create_event_filters_node(state.event_filters, doc))
+ return statesList
+
+ def create_action_list_node(self, action_list, doc):
+ """
+ Create and return a xml Node from a Action list.
+ """
+ actionsList = doc.createElement("Actions")
+ for action in action_list:
+ actionNode = actionsList.appendChild("Action")
+ if type(action) is DialogMessage:
+ actionNode.setAttribute("Action:Class", type(action))
+ actionNode.setAttribute("Action:Message", action.message)
+ actionNode.setAttribute("Action:Position", action.position)
+ elif type(action) is BubbleMessage:
+ actionNode.setAttribute("Action:Class", str(type(action)))
+ actionNode.setAttribute("Action:Message", action.message)
+ actionNode.setAttribute("Action:Position", action.position)
+ actionNode.setAttribute("Action:Tail_pos", action.tail_pos)
+ # TODO : elif for each type of action
+ elif type(action) is WidgetIdentifyAction:
+ actionNode.setAttribute("Action:Class", str(type(action)))
+ # TODO
+ elif type(action) is ChainAction:
+ # TODO
+ actionNode.setAttribute("Action:Class", str(type(action)))
+ elif type(action) is DisableWidgetAction:
+ # TODO
+ actionNode.setAttribute("Action:Class", str(type(action)))
+ elif type(action) is TypeTextAction:
+ # TODO
+ actionNode.setAttribute("Action:Class", str(type(action)))
+ elif type(action) is ClickAction:
+ # TODO
+ actionNode.setAttribute("Action:Class", str(type(action)))
+
+ return actionsList
+
+ def create_event_filters_node(self, event_filters, doc):
+ """
+ Create and return a xml Node from a event filters.
+ """
+ eventFiltersList = doc.createElement("EventFiltersList")
+ for event_f in event_filters:
+ eventFilterNode = eventFiltersList.appendChild("EventFilter")
+ # TODO : elif for each type of event filters
+ if type(event_f) is TimerEvent:
+ # TODO
+ eventFilterNode.setAttribute("EventFilter:Class", str(type(event_f)))
+ elif type(event_f) is GtkWidgetEventFilter:
+ # TODO
+ eventFilterNode.setAttribute("EventFilter:Class", str(type(event_f)))
+ elif type(event_f) is GtkWidgetTypeFilter:
+ # TODO
+ eventFilterNode.setAttribute("EventFilter:Class", str(type(event_f)))
+
+ return eventFiltersList
+ def save_fsm(self, fsm, xml_filename, path):
+ """
+ Save fsm to disk, in the xml file specified by "xml_filename", in the
+ "path" folder. If the specified file doesn't exist, it will be created.
+ """
+ doc = xml.dom.minidom.Document()
+ fsm_element = doc.createElement("FSM")
+ doc.appendChild(fsm_element)
+ fsm_element.setAttribute("fsm:Name", fsm.name)
+ fsm_element.setAttribute("fsm:StartStateName", fsm.start_state_name)
+ fsm_element = fsm_element.appendChild(create_state_dict_node(fsm.state_dict, doc))
+ fsm_element = fsm_element.appendChild(create_action_list_node(fsm.action_list, doc))
+
+ file_object = open(path + "/" + xml_filename, "w")
+ xml.dom.ext.PrettyPrint(doc, file_object)
+ file_object.close()
+
+
+ def _find_tutorial_dir_with_guid(self, guid):
+ """
+ Finds the tutorial with the associated GUID. If it is found, return
+ the path to the tutorial's directory. If it doesn't exist, raise an
+ IOError.
+
+ A note : if there are two tutorials with this GUID in the folders,
+ they will both be inspected and the one with the highest version
+ number will be returned. If they have the same version number, the one
+ from the global store will be returned.
+
+ @param guid The GUID of the tutorial that is to be loaded.
+ """
+ # Attempt to find the tutorial's directory in the global directory
+ global_dir = os.path.join(__get_store_root(), guid)
+ # Then in the activty's bundle path
+ activity_dir = os.path.join(__get_bundle_root(), guid)
+
+ # If they both exist
+ if os.path.isdir(global_filename) and os.path.isdir(activity_dir):
+ # Inspect both metadata files
+ global_meta = os.path.join(global_dir, "meta.ini")
+ activity_meta = os.path.join(activity_dir, "meta.ini")
+
+ # Open both config files
+ global_parser = SafeConfigParser()
+ global_parser.read(global_meta)
+
+ activity_parser = SafeConfigParser()
+ activity_parser.read(activity_meta)
+
+ # Get the version number for each tutorial
+ global_version = global_parser.get(INI_METADATA_SECTION, "version")
+ activity_version = activity_parser.get(INI_METADATA_SECTION, "version")
+
+ # If the global version is higher or equal, we'll take it
+ if global_version >= activity_version:
+ return global_dir
+ else:
+ return activity_dir
+
+ # Do we just have the global directory?
+ if os.path.isdir(global_dir):
+ return global_dir
+
+ # Or just the activity's bundle directory?
+ if os.path.isdir(activity_dir):
+ return activity_dir
+
+ # Error : none of these directories contain the tutorial
+ raise IOError(2, "Neither the global nor the bundle directory contained the tutorial with GUID"%guid)
+
+ def _load_xml_properties(self, properties_elem):
+ """
+ Changes a list of properties into fully instanciated properties.
+
+ @param properties_elem An XML element reprensenting a list of
+ properties
+ """
+ return []
+
+ def _load_xml_event_filters(self, filters_elem):
+ """
+ Loads up a list of Event Filters.
+
+ @param filters_elem An XML Element representing a list of event filters
+ """
+ return []
+
+ def _load_xml_actions(self, actions_elem):
+ """
+ Transforms an Actions element into a list of instanciated Action.
+
+ @param actions_elem An XML Element representing a list of Actions
+ """
+ return []
+
+ def _load_xml_states(self, states_elem):
+ """
+ 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
+ """
+ return []
+
+ def _load_xml_fsm(self, fsm_elem):
+ """
+ Takes in an XML element representing an FSM and returns the fully
+ crafted FSM.
+
+ @param fsm_elem The XML element that describes a FSM
+ """
+ # Load the FSM's name and start state's name
+ fsm_name = fsm_elem.getAttribute("fsm:Name")
+
+ fsm_start_state_name = None
+ try:
+ fsm_start_state_name = fsm_elem.getAttribute("fsm:StartStateName")
+ except:
+ pass
+
+ fsm = FiniteStateMachine(fsm_name, start_state_name=fsm_start_state_name)
+
+ # Load the states
+ states = self._load_xml_states(fsm_elem.getElementsByName("States"))
+ for state in states:
+ fsm.add_state(state)
+
+ # Load the actions on this FSM
+ actions = self._load_xml_actions(fsm_elem.getElementsByName("Actions"))
+ for action in actions:
+ fsm.add_action(action)
+
+ # Load the event filters
+ events = self._load_xml_event_filters(fsm_elem.getElementsByName("EventFiltersList"))
+ for event in events:
+ fsm.add_event_filter(event)
+
+ return fsm
+
def load_fsm(self, guid):
"""
- Load fsm from xml file who .ini file guid match argument guid.
+ Load fsm from xml file whose .ini file guid match argument guid.
"""
- NotImplementedError
+ # Fetch the directory (if any)
+ tutorial_dir = self._find_tutorial_dir_with_guid(guid)
+
+ # Open the XML file
+ tutorial_file = os.path.join(tutorial_dir, "fsm.xml")
+
+ xml_dom = xml.dom.minidom.parse(tutorial_file)
+ fsm_elem = xml_dom.getElementsByTagName("fsm")[0]
+ return self._load_xml_fsm(fsm_elem)
+
+
class TutorialBundler:
"""
This class provide the various data handling methods useable by the tutorial
editor.
"""
- def write_metadata_file(data):
+ def __init__(self,generated_guid = None):
"""
- Write metadata to a .ini file.
+ TODO.
+ Tutorial_bundler constructor. If a GUID is given in the parameter, the
+ Tutorial_bundler object will be associated with it. If no GUID is given,
+ a new GUID will be generated,
"""
- NotImplementedError
-
- def write_fsm(fsm):
+
+ self.Guid = generated_guid or uuid.uuid1()
+
+ #Look for the file in the path if a uid is supplied
+ if generated_guid:
+ #General store
+ store_path = os.path.join(__get_store_root(), generated_guid, INI_FILENAME)
+ if os.path.isfile(store_path):
+ self.Path = os.path.dirname(store_path)
+ else:
+ #Bundle store
+ bundle_path = os.path.join(__get_bundle_root(), generated_guid, INI_FILENAME)
+ if os.path.isfile(bundle_path):
+ self.Path = os.path.dirname(bundle_path)
+ else:
+ raise IOError(2,"Unable to locate metadata file for guid '%s'" % generated_guid)
+
+ else:
+ #Create the folder, any failure will go through to the caller for now
+ store_path = os.path.join(__get_store_root(), generated_guid)
+ os.mkdir(store_path)
+ self.Path = store_path
+
+
+ def __SetGuid(self, value):
+ self.__guid = value
+
+ def __GetGuid(self):
+ return self.__guid
+
+ def __DelGuid(self):
+ del self.__guid
+
+ def __SetPath(self, value):
+ self.__path = value
+
+ def __GetPath(self):
+ return self.__path
+
+ def __DelPath(self):
+ del self.__path
+
+ Guid = property(fget=__SetGuid,
+ fset=__GetGuid,
+ fdel=__DelGuid,
+ doc="The guid associated with the Tutoria_Bundler")
+
+ Path = property(fget=__SetPath,
+ fset=__GetPath,
+ fdel=__DelPath,
+ doc="The path associated with the Tutoria_Bundler")
+
+
+ def write_metadata_file(self, data):
"""
- Write fsm to disk.
+ Write metadata to a property file. If a GUID is provided, TutorialBundler
+ will try to find and overwrite the existing property file who contain the
+ given GUID, and will raise an exception if it cannot find it.
"""
NotImplementedError
- def add_resources(typename, file):
+ def get_tutorial_path(self):
+ """
+ Return the path of the .ini file associated with the guiven guid set in
+ the Guid property of the Tutorial_Bundler. If the guid is present in
+ more than one path, the store_root is given priority.
+ """
+
+ store_root = __get_store_root()
+ bundle_root = __get_bundle_root()
+
+ config = SafeConfigParser()
+ path = None
+
+ logging.debug("************ Path of store_root folder of activity : " \
+ + store_root)
+
+ # iterate in each GUID subfolder
+ for dir in os.listdir(store_root):
+
+ # iterate for each .ini file in the store_root folder
+
+ for file_name in os.listdir(store_root + "/" + dir):
+ if file_name.endswith(".ini"):
+ logging.debug("******************* Found .ini file : " \
+ + file_name)
+ config.read(file_name)
+ if config.get(INI_METADATA_SECTION, INI_GUID_PROPERTY) == self.Guid:
+ xml_filename = config.get(INI_METADATA_SECTION,
+ INI_XML_FSM_PROPERTY)
+
+ path = os.path.join(store_root, self.Guid)
+ return path
+
+ logging.debug("************ Path of bundle_root folder of activity : " \
+ + bundle_root)
+
+
+ # iterate in each GUID subfolder
+ for dir in os.listdir(bundle_root):
+
+ # iterate for each .ini file in the bundle_root folder
+ for file_name in os.listdir(bundle_root + "/" + dir):
+ if file_name.endswith(".ini"):
+ logging.debug("******************* Found .ini file : " \
+ + file_name)
+ config.read(file_name)
+ if config.get(INI_METADATA_SECTION, INI_GUID_PROPERTY) == guid:
+ path = os.path.join(bundle_root, self.Guid)
+ return path
+
+ if path is None:
+ logging.debug("**************** Error : GUID not found")
+ raise KeyError
+
+ def write_fsm(self, fsm, guid=None):
+
+ """
+ Save fsm to disk. If a GUID parameter is provided, the existing GUID is
+ located in the .ini files in the store root and bundle root and
+ the corresponding FSM is/are created or overwritten. If the GUID is not
+ found, an exception occur.
+ """
+
+ config = SafeConfigParser()
+
+ if guid is not None:
+ path = get_tutorial_path() + "/meta.ini"
+ config.read(path)
+ xml_filename = config.get(INI_METADATA_SECTION, INI_XML_FSM_PROPERTY)
+ save_fsm(fsm, xml_filename, store_root)
+
+
+ def add_resources(self, typename, file):
"""
Add ressources to metadata.
"""
- NotImplementedError \ No newline at end of file
+ raise NotImplementedError("add_resources not implemented")
diff --git a/src/sugar/tutorius/core.py b/src/sugar/tutorius/core.py
index 4d53684..602947f 100644
--- a/src/sugar/tutorius/core.py
+++ b/src/sugar/tutorius/core.py
@@ -24,6 +24,7 @@ This module contains the core classes for tutorius
import gtk
import logging
import copy
+import os
from sugar.tutorius.dialog import TutoriusDialog
from sugar.tutorius.gtkutils import find_widget
@@ -36,12 +37,13 @@ class Tutorial (object):
Tutorial Class, used to run through the FSM.
"""
- def __init__(self, name, fsm):
+ def __init__(self, name, fsm,filename= None):
"""
Creates an unattached tutorial.
"""
object.__init__(self)
self.name = name
+ self.activity_init_state_filename = filename
self.state_machine = fsm
self.state_machine.set_tutorial(self)
@@ -64,6 +66,7 @@ class Tutorial (object):
self.activity = activity
ObjectStore().activity = activity
ObjectStore().tutorial = self
+ self._prepare_activity()
self.state_machine.set_state("INIT")
def detach(self):
@@ -97,6 +100,21 @@ class Tutorial (object):
#Swith to the next state pointed by the eventfilter
self.set_state(eventfilter.get_next_state())
+
+ def _prepare_activity(self):
+ """
+ Prepare the activity for the tutorial by loading the saved state and
+ emitting gtk signals
+ """
+ #Load the saved activity if any
+ if self.activity_init_state_filename is not None:
+ #For now the file will be saved in the data folder
+ #of the activity root directory
+ filename = os.getenv("SUGAR_ACTIVITY_ROOT") + "/data/" +\
+ self.activity_init_state_filename
+ if os.path.exists(filename):
+ self.activity.read_file(filename)
+
class State(object):
"""
diff --git a/src/sugar/tutorius/services.py b/src/sugar/tutorius/services.py
index 467eca0..9ed2e50 100644
--- a/src/sugar/tutorius/services.py
+++ b/src/sugar/tutorius/services.py
@@ -66,3 +66,4 @@ class ObjectStore(object):
tutorial = property(fset=set_tutorial,fget=get_tutorial,doc="tutorial")
__doc__ = __ObjectStore.__doc__
+
diff --git a/src/sugar/tutorius/temp.py b/src/sugar/tutorius/temp.py
new file mode 100644
index 0000000..6c7fb85
--- /dev/null
+++ b/src/sugar/tutorius/temp.py
@@ -0,0 +1 @@
+Ex GUID : 79521158-22b4-11de-8cfa-000c293a027a \ No newline at end of file
diff --git a/src/sugar/tutorius/tests/bundlertests.py b/src/sugar/tutorius/tests/bundlertests.py
new file mode 100644
index 0000000..8da2310
--- /dev/null
+++ b/src/sugar/tutorius/tests/bundlertests.py
@@ -0,0 +1,65 @@
+# Copyright (C) 2009, Tutorius.org
+# Copyright (C) 2009, Charles-Etienne Carriere <iso.swiffer@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
+"""
+Bundler tests
+
+This module contains all the tests for the storage mecanisms for tutorials
+This mean testing savins and loading tutorial, .ini file management and
+adding ressources to tutorial
+"""
+
+import unittest
+import os
+import uuid
+
+from sugar.tutorius import bundler
+
+class TutorialBundlerTests(unittest.TestCase):
+
+ def setUp(self):
+
+ #generate a test GUID
+ self.test_guid = uuid.uuid1()
+ self.guid_path = os.path.join(bundler._get_store_root(),str(self.test_guid))
+ os.mkdir(self.guid_path)
+
+ self.ini_file = os.path.join(self.guid_path, "meta.ini")
+
+ f = open(self.ini_file,'w')
+ f.write("[GENERAL_METADATA]")
+ f.write(os.linesep)
+ f.write("GUID:")
+ f.write(str(self.test_guid))
+ f.close()
+
+ def tearDown(self):
+ os.remove(self.ini_file)
+ os.rmdir(self.guid_path)
+
+ def test_add_ressource(self):
+ bund = bundler.TutorialBundler(self.test_guid)
+
+ temp_file = open("test.txt",'w')
+ temp_file.write('test')
+ temp_file.close()
+
+ bund.add_resource("test.txt")
+
+ assert os.path.exists(os.path.join(self.guid_path,"test.txt")), "add_ressource did not create the file"
+
+if __name__ == "__main__":
+ unittest.main() \ No newline at end of file
diff --git a/src/sugar/tutorius/tests/coretests.py b/src/sugar/tutorius/tests/coretests.py
index 1d864f9..c27846d 100644
--- a/src/sugar/tutorius/tests/coretests.py
+++ b/src/sugar/tutorius/tests/coretests.py
@@ -29,8 +29,7 @@ and event filters. Those are in their separate test module
import unittest
import logging
-import sugar.activity
-from sugar.tutorius.actions import Action, OnceWrapper
+from sugar.tutorius.actions import Action, OnceWrapper, ClickAction, TypeTextAction
from sugar.tutorius.core import *
from sugar.tutorius.filters import *
@@ -73,6 +72,44 @@ class TrueWhileActiveAction(Action):
def undo(self):
self.active = False
+
+class ClickableWidget():
+ """
+ This class fakes a widget with a clicked() method
+ """
+ def __init__(self):
+ self.click_count = 0
+
+ def clicked(self):
+ self.click_count += 1
+
+class FakeTextEntry():
+ """
+ This class fakes a widget with an insert_text() method
+ """
+ def __init__(self):
+ self.text_lines = []
+ self.last_entered_line = ""
+ self.displayed_text = ""
+
+ def insert_text(self, text, index):
+ self.last_entered_line = text
+ self.text_lines.append(text)
+ self.displayed_text = self.displayed_text[0:index] + text + self.displayed_text[index+1:]
+
+class FakeParentWidget():
+ """
+ This class fakes a widet container, it implements the get_children() method
+ """
+ def __init__(self):
+ self._children = []
+
+ def add_child(self, child):
+ self._children.append(child)
+
+ def get_children(self):
+ return self._children
+
@@ -112,7 +149,96 @@ class FakeEventFilter(TriggerEventFilter):
self.tutorial.set_state(event_filter.get_next_state())
+class ClickActionTests(unittest.TestCase):
+ """
+ Test class for click action
+ """
+ def test_do_action(self):
+ activity = FakeParentWidget()
+ widget = ClickableWidget()
+ activity.add_child(widget)
+ ObjectStore().activity = activity
+
+ action = ClickAction("0.0")
+
+ assert widget == ObjectStore().activity.get_children()[0],\
+ "The clickable widget isn't reachable from the object store \
+ the test cannot pass"
+
+ action.do()
+
+ assert widget.click_count == 1, "clicked() should have been called by do()"
+
+ action.do()
+
+ assert widget.click_count == 2, "clicked() should have been called by do()"
+ def test_undo(self):
+ activity = FakeParentWidget()
+ widget = ClickableWidget()
+ activity.add_child(widget)
+ ObjectStore().activity = activity
+
+ action = ClickAction("0.0")
+
+ assert widget == ObjectStore().activity.get_children()[0],\
+ "The clickable widget isn't reachable from the object store \
+ the test cannot pass"
+
+ action.undo()
+
+ #There is no undo for this action so the test should not fail
+ assert True
+
+
+
+class TypeTextActionTests(unittest.TestCase):
+ """
+ Test class for type text action
+ """
+ def test_do_action(self):
+ activity = FakeParentWidget()
+ widget = FakeTextEntry()
+ activity.add_child(widget)
+ ObjectStore().activity = activity
+
+ test_text = "This is text"
+
+
+ action = TypeTextAction("0.0", test_text)
+
+ assert widget == ObjectStore().activity.get_children()[0],\
+ "The clickable widget isn't reachable from the object store \
+ the test cannot pass"
+
+ action.do()
+
+ assert widget.last_entered_line == test_text, "insert_text() should have been called by do()"
+
+ action.do()
+
+ assert widget.last_entered_line == test_text, "insert_text() should have been called by do()"
+ assert len(widget.text_lines) == 2, "insert_text() should have been called twice"
+
+ def test_undo(self):
+ activity = FakeParentWidget()
+ widget = FakeTextEntry()
+ activity.add_child(widget)
+ ObjectStore().activity = activity
+
+ test_text = "This is text"
+
+
+ action = TypeTextAction("0.0", test_text)
+
+ assert widget == ObjectStore().activity.get_children()[0],\
+ "The clickable widget isn't reachable from the object store \
+ the test cannot pass"
+
+ action.undo()
+
+ #There is no undo for this action so the test should not fail
+ assert True
# State testing class
class StateTest(unittest.TestCase):
diff --git a/src/sugar/tutorius/tests/run-tests.py b/src/sugar/tutorius/tests/run-tests.py
index 87edd57..1fc534e 100755
--- a/src/sugar/tutorius/tests/run-tests.py
+++ b/src/sugar/tutorius/tests/run-tests.py
@@ -14,7 +14,6 @@ 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:
@@ -40,6 +39,7 @@ if __name__=='__main__':
import filterstests
import constraintstests
import propertiestests
+ import serializertests
suite = unittest.TestSuite()
suite.addTests(unittest.findTestCases(coretests))
suite.addTests(unittest.findTestCases(servicestests))
@@ -51,10 +51,10 @@ if __name__=='__main__':
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(report_files())
coverage.erase()
@@ -70,5 +70,6 @@ if __name__=='__main__':
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
new file mode 100644
index 0000000..a53e196
--- /dev/null
+++ b/src/sugar/tutorius/tests/serializertests.py
@@ -0,0 +1,122 @@
+# Copyright (C) 2009, Tutorius.org
+# Copyright (C) 2009, Jean-Christophe Savard <savard.jean.christophe@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
+"""
+Serialization Tests
+
+This module contains all the tests that pertain to the usage of the Tutorius
+Serializer object. This means testing saving a tutorial dictionary to a .tml
+file, loading the list of tutorials for this activity and building chosen
+tutorial.
+"""
+
+import unittest
+
+import logging
+import linecache
+import os
+import cPickle as pickle
+
+from sugar.tutorius import gtkutils, overlayer
+from sugar.tutorius.core import Tutorial, State, FiniteStateMachine
+from sugar.tutorius.actions import DialogMessage, OnceWrapper, BubbleMessage
+from sugar.tutorius.filters import GtkWidgetEventFilter, TimerEvent
+
+
+# Helper classes to help testing
+
+
+
+
+class SerializerTest(unittest.TestCase):
+ """
+ This class has to test the Serializer methods as well as the expected
+ functionality.
+ """
+
+ # Voiding test as it is meant to be used with the pickle serializer,
+ # that was deprecated
+## def test_pickle_integrity(self):
+## """
+## Validates content is uncorrupted trough a pickle file save/load.
+## """
+##
+## # Sample valid FSM dict
+## sampleDict = {
+## "INIT":State("INIT",
+## action_list=[
+## OnceWrapper(BubbleMessage(message="Welcome to the text editor tutorial!\n\n Click on the canvas and type a letter.", pos=[100,100], tailpos=[-10,-20])),
+## ],
+## event_filter_list=[
+## GtkWidgetEventFilter("TEXT","0.0.0.1.0.0.0","key-press-event"),
+## TimerEvent("LOST",15),
+## ],
+## ),
+## "LOST":State("LOST",
+## action_list=[BubbleMessage("Click in the canvas and type on your keyboard", [400, 400]),],
+## event_filter_list=[
+## GtkWidgetEventFilter("TEXT","0.0.0.1.0.0.0","key-press-event"),
+## TimerEvent("INIT",5),
+## ],
+## ),
+## "TEXT":State("TEXT",
+## action_list=[OnceWrapper(BubbleMessage(" You can type more letters if you want!\n\n" +
+## "To proceed to the next step, select your text.\n\n Click and drag over the text!", [200,150])),],
+## event_filter_list=[
+## GtkWidgetEventFilter("SELECTED","0.0.0.1.0.0","text-selected"),
+## ],
+## ),
+## }
+##
+## testpath = "/tmp/testdata/"
+##
+## # Create testdata/ folder if no exists
+## if not os.path.exists(testpath):
+## os.mkdir(testpath)
+##
+## serialize = TutoSerializer()
+##
+## # Make the class believe the test is in a activity path
+## os.environ["SUGAR_ACTIVITY_ROOT"] = testpath
+##
+## fsm = FiniteStateMachine("Test", state_dict=sampleDict)
+##
+## serialize.save_tutorial("Test", "Test", fsm, "serializeTest")
+##
+## fileDict = serialize.load_tuto_list()
+##
+## for filekey, tutorial in fileDict.items():
+## if filekey == "Test":
+## reformedTuto = serialize.build_tutorial(filekey)
+##
+## reformedfsm = reformedTuto.get("Test").state_machine
+##
+## #Tests
+## assert reformedfsm._states.get("INIT").name == fsm._states.get("INIT").name, \
+## 'FSM underlying dictionary differ from original to pickled/reformed one'
+## assert reformedfsm._states.get("LOST").name == fsm._states.get("LOST").name, \
+## 'FSM underlying dictionary differ from original to pickled/reformed one'
+## assert reformedfsm._states.get("TEXT").name == fsm._states.get("TEXT").name, \
+## 'FSM underlying dictionary differ from original to pickled/reformed one'
+##
+##
+## os.remove(testpath + "serializeTest.tml")
+## os.rmdir(testpath)
+## os.rmdir("/tmp")
+
+
+if __name__ == "__main__":
+ unittest.main() \ No newline at end of file