Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/tutorius
diff options
context:
space:
mode:
authormike <michael.jmontcalm@gmail.com>2009-10-02 22:24:56 (GMT)
committer mike <michael.jmontcalm@gmail.com>2009-10-02 22:24:56 (GMT)
commitcf40c1951f4f0f26090226fb4969ca147341a031 (patch)
treeb3bb17043645aff11a93940eba90d7488bcebf45 /tutorius
parent09b2ea3369df967309f030f9196c2f9861bc1b2c (diff)
LP 439980 : Refactored the XMLSerializer to support complex components; Corrected specs for the addons properties and constructor parameters names; Moved all existing actions and events to components (except a few left in code for testing purposes)
Diffstat (limited to 'tutorius')
-rw-r--r--tutorius/actions.py284
-rw-r--r--tutorius/addon.py6
-rw-r--r--tutorius/bundler.py201
-rw-r--r--tutorius/core.py76
-rw-r--r--tutorius/filters.py214
-rw-r--r--tutorius/properties.py42
-rw-r--r--tutorius/uam/__init__.py3
7 files changed, 557 insertions, 269 deletions
diff --git a/tutorius/actions.py b/tutorius/actions.py
index 4269cd7..0db7988 100644
--- a/tutorius/actions.py
+++ b/tutorius/actions.py
@@ -176,149 +176,149 @@ class Action(TPropContainer):
x, y = self._drag.position
self.position = [int(x), int(y)]
self.__edit_img.destroy()
-
-class OnceWrapper(Action):
- """
- Wraps a class to perform an action once only
-
- This ConcreteActions's do() method will only be called on the first do()
- and the undo() will be callable after do() has been called
- """
-
- _action = TAddonProperty()
-
- def __init__(self, action):
- Action.__init__(self)
- self._called = False
- self._need_undo = False
- self._action = action
-
- def do(self):
- """
- Do the action only on the first time
- """
- if not self._called:
- self._called = True
- self._action.do()
- self._need_undo = True
-
- def undo(self):
- """
- Undo the action if it's been done
- """
- if self._need_undo:
- self._action.undo()
- self._need_undo = False
-
-class WidgetIdentifyAction(Action):
- def __init__(self):
- Action.__init__(self)
- self.activity = None
- self._dialog = None
-
- def do(self):
- os = ObjectStore()
- if os.activity:
- self.activity = os.activity
-
- self._dialog = WidgetIdentifier(self.activity)
- self._dialog.show()
-
-
- def undo(self):
- if self._dialog:
- self._dialog.destroy()
-
-class ChainAction(Action):
- """Utility class to allow executing actions in a specific order"""
- def __init__(self, *actions):
- """ChainAction(action1, ... ) builds a chain of actions"""
- Action.__init__(self)
- self._actions = actions
-
- def do(self,**kwargs):
- """do() each action in the chain"""
- for act in self._actions:
- act.do(**kwargs)
-
- def undo(self):
- """undo() each action in the chain, starting with the last"""
- for act in reversed(self._actions):
- act.undo()
-
-class DisableWidgetAction(Action):
- def __init__(self, target):
- """Constructor
- @param target target treeish
- """
- Action.__init__(self)
- self._target = target
- self._widget = None
-
- def do(self):
- """Action do"""
- os = ObjectStore()
- if os.activity:
- self._widget = gtkutils.find_widget(os.activity, self._target)
- if self._widget:
- self._widget.set_sensitive(False)
-
- 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
- """
- 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 OnceWrapper(Action):
+## """
+## Wraps a class to perform an action once only
+##
+## This ConcreteActions's do() method will only be called on the first do()
+## and the undo() will be callable after do() has been called
+## """
+##
+## _action = TAddonProperty()
+##
+## def __init__(self, action):
+## Action.__init__(self)
+## self._called = False
+## self._need_undo = False
+## self._action = action
+##
+## def do(self):
+## """
+## Do the action only on the first time
+## """
+## if not self._called:
+## self._called = True
+## self._action.do()
+## self._need_undo = True
+##
+## def undo(self):
+## """
+## Undo the action if it's been done
+## """
+## if self._need_undo:
+## self._action.undo()
+## self._need_undo = False
+##
+##class WidgetIdentifyAction(Action):
+## def __init__(self):
+## Action.__init__(self)
+## self.activity = None
+## self._dialog = None
+
+## def do(self):
+## os = ObjectStore()
+## if os.activity:
+## self.activity = os.activity
+
+## self._dialog = WidgetIdentifier(self.activity)
+## self._dialog.show()
+
+
+## def undo(self):
+## if self._dialog:
+## self._dialog.destroy()
+
+##class ChainAction(Action):
+## """Utility class to allow executing actions in a specific order"""
+## def __init__(self, *actions):
+## """ChainAction(action1, ... ) builds a chain of actions"""
+## Action.__init__(self)
+## self._actions = actions
+##
+## def do(self,**kwargs):
+## """do() each action in the chain"""
+## for act in self._actions:
+## act.do(**kwargs)
+##
+## def undo(self):
+## """undo() each action in the chain, starting with the last"""
+## for act in reversed(self._actions):
+## act.undo()
+
+##class DisableWidgetAction(Action):
+## def __init__(self, target):
+## """Constructor
+## @param target target treeish
+## """
+## Action.__init__(self)
+## self._target = target
+## self._widget = None
+
+## def do(self):
+## """Action do"""
+## os = ObjectStore()
+## if os.activity:
+## self._widget = gtkutils.find_widget(os.activity, self._target)
+## if self._widget:
+## self._widget.set_sensitive(False)
-class ClickAction(Action):
- """
- Action that simulate a click on a widget
- Work on any widget that implements a clicked() method
+## def undo(self):
+## """Action undo"""
+## if self._widget:
+## self._widget.set_sensitive(True)
- @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
+##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/tutorius/addon.py b/tutorius/addon.py
index 51791d1..e311a65 100644
--- a/tutorius/addon.py
+++ b/tutorius/addon.py
@@ -56,6 +56,12 @@ def create(name, *args, **kwargs):
if not _cache:
_reload_addons()
try:
+ comp_metadata = _cache[name]
+ try:
+ return comp_metadata['class'](*args, **kwargs)
+ except:
+ logging.error("Could not instantiate %s with parameters %s, %s"%(comp_metadata['name'],str(args), str(kwargs)))
+ return None
return _cache[name]['class'](*args, **kwargs)
except KeyError:
logging.error("Addon not found for class '%s'", name)
diff --git a/tutorius/bundler.py b/tutorius/bundler.py
index 8808d93..734c679 100644
--- a/tutorius/bundler.py
+++ b/tutorius/bundler.py
@@ -48,6 +48,46 @@ INI_XML_FSM_PROPERTY = "FSM_FILENAME"
INI_FILENAME = "meta.ini"
TUTORIAL_FILENAME = "tutorial.xml"
NODE_COMPONENT = "Component"
+NODE_SUBCOMPONENT = "SubComponent"
+NODE_SUBCOMPONENTLIST = "SubComponentList"
+
+class Vault(object):
+ """
+ The Vault is the primary interface for the storage and installation of tutorials
+ on the machine. It needs to accomplish the following tasks :
+ - query() : Lists the
+ - installTutorial() :
+ - deleteTutorial() :
+ - readTutorial() :
+ - saveTutorial() :
+ """
+ def query(keyword="", category="", start_index=0, num_results=10):
+ """
+ Returns a list of tutorial meta-data corresponding to the keywords
+ and category mentionned.
+
+ @param keyword The keyword to look for in the tutorial title and description.
+ @param category The category in which to look for tutorials
+ @param start_index The first result to be shown (e.g. )
+ @param num_results The number of results to show
+ @return The list of tutorial metadata that corresponds to the query parameters.
+ """
+ raise NotImplementedError("The query function on the Vault is not implemented")
+
+ def installTutorial(path ,force_install=False):
+ """
+ Inserts the tutorial inside the Vault. Once installed, it will show up
+ """
+ raise NotImplementedError("Installation in the Vault not supported yet")
+
+ def deleteTutorial(tutorial_id):
+ raise NotImplementedError("")
+
+ def readTutorial(tutorial_id):
+ raise NotImplementedError("")
+
+ def saveTutorial(tutorial, metadata, resource_list):
+ raise NotImplementedError("")
class TutorialStore(object):
@@ -150,6 +190,71 @@ class XMLSerializer(Serializer):
eventfiltersList = stateNode.appendChild(self._create_event_filters_node(state.get_event_filter_list(), doc))
return statesList
+ def _create_addon_component_node(self, parent_attr_name, comp, doc):
+ """
+ Takes a component that is embedded in another component (e.g. the content
+ of a OnceWrapper) and encapsulate it in a node with the property name.
+
+ e.g.
+ <Component Class="OnceWrapper">
+ <SubComponent property="addon">
+ <Component Class="BubbleMessage" message="'Hi!'" position="[12,32]"/>
+ </SubComponent>
+ </Component>
+
+ When reloading this node, we should look up the property name for the parent
+ in the attribute of the node, then examine the subnode to create the addon
+ object itself.
+
+ @param parent_attr_name The name of the parent's attribute for this addon
+ e.g. the OnceWrapper has the action attribute, which corresponds to a
+ sub-action it must execute once.
+ @param comp The component node itself
+ @param doc The XML document root (only used to create the nodes)
+ @returns A NODE_SUBCOMPONENT node, with the property attribute and a sub node
+ that represents another component.
+ """
+ subCompNode = doc.createElement(NODE_SUBCOMPONENT)
+ subCompNode.setAttribute("property", parent_attr_name)
+
+ subNode = self._create_component_node(comp, doc)
+
+ subCompNode.appendChild(subNode)
+
+ return subCompNode
+
+ def _create_addonlist_component_node(self, parent_attr_name, comp_list, doc):
+ """
+ Takes a list of components that are embedded in another component (ex. the
+ content of a ChainAction) and encapsulate them in a node with the property
+ name.
+
+ e.g.
+ <Component Class="ChainAction">
+ <SubComponentList property="actions">
+ <Component Class="BubbleMessage" message="'Hi!'" position="[15,35]"/>
+ <Component Class="DialogMessage" message="'Multi-action!'" position="[45,10]"/>
+ </SubComponentList>
+ </Component>
+
+ When reloading this node, we should look up the property name for the parent
+ in the the attribute of the node, then rebuild the list by appending the
+ content of all the subnodes.
+
+ @param parent_attr_name The name of the parent component's property
+ @param comp_list A list of components that comprise the property
+ @param doc The XML document root (only for creating new nodes)
+ @returns A NODE_SUBCOMPONENTLIST node with the property attribute
+ """
+ subCompListNode = doc.createElement(NODE_SUBCOMPONENTLIST)
+ subCompListNode.setAttribute("property", parent_attr_name)
+
+ for comp in comp_list:
+ compNode = self._create_component_node(comp, doc)
+ subCompListNode.appendChild(compNode)
+
+ return subCompListNode
+
def _create_component_node(self, comp, doc):
"""
Takes a single component (action or eventfilter) and transforms it
@@ -169,10 +274,10 @@ class XMLSerializer(Serializer):
for propname in comp.get_properties():
propval = getattr(comp, propname)
if getattr(type(comp), propname).type == "addonlist":
- for subval in propval:
- compNode.appendChild(self._create_component_node(subval, doc))
- elif getattr(type(comp), propname).type == "addonlist":
- compNode.appendChild(self._create_component_node(subval, doc))
+ compNode.appendChild(self._create_addonlist_component_node(propname, propval, doc))
+ elif getattr(type(comp), propname).type == "addon":
+ #import rpdb2; rpdb2.start_embedded_debugger('pass')
+ compNode.appendChild(self._create_addon_component_node(propname, propval, doc))
else:
# repr instead of str, as we want to be able to eval() it into a
# valid object.
@@ -282,6 +387,27 @@ class XMLSerializer(Serializer):
# Error : none of these directories contain the tutorial
raise IOError(2, "Neither the global nor the bundle directory contained the tutorial with GUID %s"%guid)
+ def _get_direct_descendants_by_tag_name(self, node, name):
+ """
+ Searches in the list of direct descendants of a node to find all the node
+ that have the given name.
+
+ This is used because the Document.getElementsByTagName() function returns the
+ list of all the descendants (whatever their distance to the start node) that
+ have that name. In the case of complex components, we absolutely need to inspect
+ a single layer of the tree at the time.
+
+ @param node The node from which we want the direct descendants with a particular
+ name
+ @param name The name of the node
+ @returns A list, possibly empty, of direct descendants of node that have this name
+ """
+ return_list = []
+ for childNode in node.childNodes:
+ if childNode.nodeName == name:
+ return_list.append(childNode)
+ return return_list
+
def _load_xml_properties(self, properties_elem):
"""
Changes a list of properties into fully instanciated properties.
@@ -298,7 +424,7 @@ class XMLSerializer(Serializer):
@param filters_elem An XML Element representing a list of event filters
"""
reformed_event_filters_list = []
- event_filter_element_list = filters_elem.getElementsByTagName(NODE_COMPONENT)
+ event_filter_element_list = self._get_direct_descendants_by_tag_name(filters_elem, NODE_COMPONENT)
new_event_filter = None
for event_filter in event_filter_element_list:
@@ -309,6 +435,42 @@ class XMLSerializer(Serializer):
return reformed_event_filters_list
+ def _load_xml_subcomponents(self, node, properties):
+ """
+ Loads all the subcomponent node below the given node and inserts them with
+ the right property name inside the properties dictionnary.
+
+ @param node The parent node that contains one or many SubComponent nodes.
+ @param properties A dictionnary where the subcomponent property names
+ and the instantiated components will be stored
+ @returns Nothing. The properties dict will contain the property->comp mapping.
+ """
+ subCompList = self._get_direct_descendants_by_tag_name(node, NODE_SUBCOMPONENT)
+
+ for subComp in subCompList:
+ property_name = subComp.getAttribute("property")
+ internal_comp_node = self._get_direct_descendants_by_tag_name(subComp, NODE_COMPONENT)[0]
+ internal_comp = self._load_xml_component(internal_comp_node)
+ properties[str(property_name)] = internal_comp
+
+ def _load_xml_subcomponent_lists(self, node, properties):
+ """
+ Loads all the subcomponent lists below the given node and stores them
+ under the correct property name for that node.
+
+ @param node The node from which we want to read the subComponent lists
+ @param properties The dictionnary that will contain the mapping of prop->subCompList
+ @returns Nothing. The values are returns inside the properties dict.
+ """
+ listOf_subCompListNode = self._get_direct_descendants_by_tag_name(node, NODE_SUBCOMPONENTLIST)
+ for subCompListNode in listOf_subCompListNode:
+ property_name = subCompListNode.getAttribute("property")
+ subCompList = []
+ for subCompNode in self._get_direct_descendants_by_tag_name(subCompListNode, NODE_COMPONENT):
+ subComp = self._load_xml_component(subCompNode)
+ subCompList.append(subComp)
+ properties[str(property_name)] = subCompList
+
def _load_xml_component(self, node):
"""
Loads a single addon component instance from an Xml node.
@@ -318,20 +480,23 @@ class XMLSerializer(Serializer):
@return The addon component object of the correct type according to the XML
description
"""
- new_action = addon.create(node.getAttribute("Class"))
- if not new_action:
- return None
+ class_name = node.getAttribute("Class")
+
+ properties = {}
+
+ for prop in node.attributes.keys():
+ if prop == "Class" : continue
+ # security : keep sandboxed
+ properties[str(prop)] = eval(node.getAttribute(prop))
+
+ # Read the complex attributes
+ self._load_xml_subcomponents(node, properties)
+ self._load_xml_subcomponent_lists(node, properties)
- for attrib in node.attributes.keys():
- if attrib == "Class": continue
- # security note: keep sandboxed
- setattr(new_action, attrib, eval(node.getAttribute(attrib), {}, {}))
+ new_action = addon.create(class_name, **properties)
- # recreate complex attributes
- for sub in node.childNodes:
- name = getattr(new_action, sub.nodeName)
- if name == "addon":
- setattr(new_action, sub.getAttribute("Name"), self._load_xml_action(sub))
+ if not new_action:
+ return None
return new_action
@@ -342,7 +507,7 @@ class XMLSerializer(Serializer):
@param actions_elem An XML Element representing a list of Actions
"""
reformed_actions_list = []
- actions_element_list = actions_elem.getElementsByTagName(NODE_COMPONENT)
+ actions_element_list = self._get_direct_descendants_by_tag_name(actions_elem, NODE_COMPONENT)
for action in actions_element_list:
new_action = self._load_xml_component(action)
diff --git a/tutorius/core.py b/tutorius/core.py
index dd2435e..41089f1 100644
--- a/tutorius/core.py
+++ b/tutorius/core.py
@@ -258,6 +258,42 @@ class State(object):
tutorial.
"""
self._event_filters = []
+
+ def is_identical(self, otherState):
+ """
+ Compares two states and tells whether they contain the same states and
+
+` """
+ 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
+ for act in self._actions:
+ found = False
+ for otherAct in otherState._actions:
+ if act.is_identical(otherAct):
+ found = True
+ break
+ if found == False:
+ return False
+
+ # Do they have the same event filters?
+ if len(self._actions) != len(otherState._actions):
+ return False
+ for event in self._event_filters:
+ found = False
+ for otherEvent in otherState._event_filters:
+ if event.is_identical(otherEvent):
+ found = True
+ break
+ if found == False:
+ return False
+
+ return True
class FiniteStateMachine(State):
"""
@@ -526,3 +562,43 @@ class FiniteStateMachine(State):
for st in self._states.itervalues():
out_string += st.name + ", "
return out_string
+
+ def is_identical(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
+
+ if not (self.name == otherFSM.name) or \
+ not (self.start_state_name == otherFSM.start_state_name):
+ return False
+
+ 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 otherAct in otherFSM._actions:
+ if act.is_identical(otherAct):
+ found = True
+ break
+ if found == False:
+ return False
+
+ if len(self._states) != len(otherFSM._states):
+ return False
+
+ for state in self._states.itervalues():
+ found = False
+ for otherState in otherFSM._states.itervalues():
+ if state.is_identical(otherState):
+ found = True
+ break
+ if found == False:
+ return False
+
+ return True
diff --git a/tutorius/filters.py b/tutorius/filters.py
index aa8c997..fc58562 100644
--- a/tutorius/filters.py
+++ b/tutorius/filters.py
@@ -94,111 +94,111 @@ class EventFilter(properties.TPropContainer):
if self._callback:
self._callback(self)
-class TimerEvent(EventFilter):
- """
- TimerEvent is a special EventFilter that uses gobject
- timeouts to trigger a state change after a specified amount
- of time. It must be used inside a gobject main loop to work.
- """
- def __init__(self,next_state,timeout_s):
- """Constructor.
-
- @param next_state default EventFilter param, passed on to EventFilter
- @param timeout_s timeout in seconds
- """
- super(TimerEvent,self).__init__(next_state)
- self._timeout = timeout_s
- self._handler_id = None
-
- def install_handlers(self, callback, **kwargs):
- """install_handlers creates the timer and starts it"""
- super(TimerEvent,self).install_handlers(callback, **kwargs)
- #Create the timer
- self._handler_id = gobject.timeout_add_seconds(self._timeout, self._timeout_cb)
-
- def remove_handlers(self):
- """remove handler removes the timer"""
- super(TimerEvent,self).remove_handlers()
- if self._handler_id:
- try:
- #XXX What happens if this was already triggered?
- #remove the timer
- gobject.source_remove(self._handler_id)
- except:
- pass
-
- def _timeout_cb(self):
- """
- _timeout_cb triggers the eventfilter callback.
-
- It is necessary because gobject timers only stop if the callback they
- trigger returns False
- """
- self.do_callback()
- return False #Stops timeout
-
-class GtkWidgetTypeFilter(EventFilter):
- """
- Event Filter that listens for keystrokes on a widget
- """
- def __init__(self, next_state, object_id, text=None, strokes=None):
- """Constructor
- @param next_state default EventFilter param, passed on to EventFilter
- @param object_id object tree-ish identifier
- @param text resulting text expected
- @param strokes list of strokes expected
-
- At least one of text or strokes must be supplied
- """
- super(GtkWidgetTypeFilter, self).__init__(next_state)
- self._object_id = object_id
- self._text = text
- self._captext = ""
- self._strokes = strokes
- self._capstrokes = []
- self._widget = None
- self._handler_id = None
-
- def install_handlers(self, callback, **kwargs):
- """install handlers
- @param callback default EventFilter callback arg
- """
- super(GtkWidgetTypeFilter, self).install_handlers(callback, **kwargs)
- logger.debug("~~~GtkWidgetTypeFilter install")
- activity = ObjectStore().activity
- if activity is None:
- logger.error("No activity")
- raise RuntimeWarning("no activity in the objectstore")
-
- self._widget = find_widget(activity, self._object_id)
- if self._widget:
- self._handler_id= self._widget.connect("key-press-event",self.__keypress_cb)
- logger.debug("~~~Connected handler %d on %s" % (self._handler_id,self._object_id) )
-
- def remove_handlers(self):
- """remove handlers"""
- super(GtkWidgetTypeFilter, self).remove_handlers()
- #if an event was connected, disconnect it
- if self._handler_id:
- self._widget.handler_disconnect(self._handler_id)
- self._handler_id=None
-
- def __keypress_cb(self, widget, event, *args):
- """keypress callback"""
- logger.debug("~~~keypressed!")
- key = event.keyval
- keystr = event.string
- logger.debug("~~~Got key: " + str(key) + ":"+ keystr)
- self._capstrokes += [key]
- #TODO Treat other stuff, such as arrows
- if key == gtk.keysyms.BackSpace:
- self._captext = self._captext[:-1]
- else:
- self._captext = self._captext + keystr
-
- logger.debug("~~~Current state: " + str(self._capstrokes) + ":" + str(self._captext))
- if not self._strokes is None and self._strokes in self._capstrokes:
- self.do_callback()
- if not self._text is None and self._text in self._captext:
- self.do_callback()
+##class TimerEvent(EventFilter):
+## """
+## TimerEvent is a special EventFilter that uses gobject
+## timeouts to trigger a state change after a specified amount
+## of time. It must be used inside a gobject main loop to work.
+## """
+## def __init__(self,next_state,timeout_s):
+## """Constructor.
+##
+## @param next_state default EventFilter param, passed on to EventFilter
+## @param timeout_s timeout in seconds
+## """
+## super(TimerEvent,self).__init__(next_state)
+## self._timeout = timeout_s
+## self._handler_id = None
+##
+## def install_handlers(self, callback, **kwargs):
+## """install_handlers creates the timer and starts it"""
+## super(TimerEvent,self).install_handlers(callback, **kwargs)
+## #Create the timer
+## self._handler_id = gobject.timeout_add_seconds(self._timeout, self._timeout_cb)
+##
+## def remove_handlers(self):
+## """remove handler removes the timer"""
+## super(TimerEvent,self).remove_handlers()
+## if self._handler_id:
+## try:
+## #XXX What happens if this was already triggered?
+## #remove the timer
+## gobject.source_remove(self._handler_id)
+## except:
+## pass
+##
+## def _timeout_cb(self):
+## """
+## _timeout_cb triggers the eventfilter callback.
+##
+## It is necessary because gobject timers only stop if the callback they
+## trigger returns False
+## """
+## self.do_callback()
+## return False #Stops timeout
+##
+##class GtkWidgetTypeFilter(EventFilter):
+## """
+## Event Filter that listens for keystrokes on a widget
+## """
+## def __init__(self, next_state, object_id, text=None, strokes=None):
+## """Constructor
+## @param next_state default EventFilter param, passed on to EventFilter
+## @param object_id object tree-ish identifier
+## @param text resulting text expected
+## @param strokes list of strokes expected
+##
+## At least one of text or strokes must be supplied
+## """
+## super(GtkWidgetTypeFilter, self).__init__(next_state)
+## self._object_id = object_id
+## self._text = text
+## self._captext = ""
+## self._strokes = strokes
+## self._capstrokes = []
+## self._widget = None
+## self._handler_id = None
+##
+## def install_handlers(self, callback, **kwargs):
+## """install handlers
+## @param callback default EventFilter callback arg
+## """
+## super(GtkWidgetTypeFilter, self).install_handlers(callback, **kwargs)
+## logger.debug("~~~GtkWidgetTypeFilter install")
+## activity = ObjectStore().activity
+## if activity is None:
+## logger.error("No activity")
+## raise RuntimeWarning("no activity in the objectstore")
+##
+## self._widget = find_widget(activity, self._object_id)
+## if self._widget:
+## self._handler_id= self._widget.connect("key-press-event",self.__keypress_cb)
+## logger.debug("~~~Connected handler %d on %s" % (self._handler_id,self._object_id) )
+##
+## def remove_handlers(self):
+## """remove handlers"""
+## super(GtkWidgetTypeFilter, self).remove_handlers()
+## #if an event was connected, disconnect it
+## if self._handler_id:
+## self._widget.handler_disconnect(self._handler_id)
+## self._handler_id=None
+##
+## def __keypress_cb(self, widget, event, *args):
+## """keypress callback"""
+## logger.debug("~~~keypressed!")
+## key = event.keyval
+## keystr = event.string
+## logger.debug("~~~Got key: " + str(key) + ":"+ keystr)
+## self._capstrokes += [key]
+## #TODO Treat other stuff, such as arrows
+## if key == gtk.keysyms.BackSpace:
+## self._captext = self._captext[:-1]
+## else:
+## self._captext = self._captext + keystr
+##
+## logger.debug("~~~Current state: " + str(self._capstrokes) + ":" + str(self._captext))
+## if not self._strokes is None and self._strokes in self._capstrokes:
+## self.do_callback()
+## if not self._text is None and self._text in self._captext:
+## self.do_callback()
diff --git a/tutorius/properties.py b/tutorius/properties.py
index abf76e5..6d30a8d 100644
--- a/tutorius/properties.py
+++ b/tutorius/properties.py
@@ -95,6 +95,39 @@ class TPropContainer(object):
"""
return object.__getattribute__(self, "_props").keys()
+ def is_identical(self, otherContainer):
+ for prop in self._props.keys():
+ found = False
+ for otherProp in otherContainer._props.keys():
+ if prop == otherProp:
+ this_type = getattr(type(self), prop).type
+ other_type = getattr(type(otherContainer), prop).type
+ if this_type != other_type:
+ return False
+ if this_type == "addonlist":
+ for inner_cont in self._props[prop]:
+ inner_found = False
+ for other_inner in otherContainer._props[prop]:
+ if inner_cont.is_identical(other_inner):
+ inner_found = True
+ break
+ if inner_found == False:
+ return False
+ found = True
+ break
+ elif this_type == "addon":
+ if not self._props[prop].is_identical(otherContainer._props[prop]):
+ return False
+ found = True
+ break
+ else:
+ if self._props[prop]== otherContainer._props[prop]:
+ found = True
+ break
+ if found == False:
+ return False
+ return True
+
class TutoriusProperty(object):
"""
The base class for all actions' properties. The interface is the following :
@@ -317,8 +350,15 @@ class TAddonListProperty(TutoriusProperty):
See TAddonProperty
"""
def __init__(self):
- super(TAddonProperty, self).__init__()
+ TutoriusProperty.__init__(self)
self.type = "addonlist"
self.default = []
+ def validate(self, value):
+ if isinstance(value, list):
+ for component in value:
+ if not (isinstance(component, TPropContainer)):
+ raise ValueError("Expected a list of TPropContainer instances inside TAddonListProperty value, got a %s" % (str(type(component))))
+ return value
+ raise ValueError("Value proposed to TAddonListProperty is not a list")
diff --git a/tutorius/uam/__init__.py b/tutorius/uam/__init__.py
index 7cf5671..bcd67e1 100644
--- a/tutorius/uam/__init__.py
+++ b/tutorius/uam/__init__.py
@@ -65,7 +65,8 @@ for subscheme in [".".join([SCHEME,s]) for s in __parsers]:
class SchemeError(Exception):
def __init__(self, message):
Exception.__init__(self, message)
- self.message = message
+ ## Commenting this line as it is causing an error in the tests
+ ##self.message = message
def parse_uri(uri):