Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--addons/bubblemessage.py5
-rw-r--r--addons/bubblemessagewimg.py119
-rw-r--r--tests/enginetests.py58
-rw-r--r--tests/linear_creatortests.py79
-rw-r--r--tests/vaulttests.py66
-rw-r--r--tutorius/actions.py2
-rw-r--r--tutorius/addon.py3
-rw-r--r--tutorius/creator.py273
-rw-r--r--tutorius/engine.py169
-rw-r--r--tutorius/linear_creator.py94
-rw-r--r--tutorius/properties.py16
-rw-r--r--tutorius/propwidgets.py489
-rw-r--r--tutorius/store.py7
-rw-r--r--tutorius/vault.py63
14 files changed, 901 insertions, 542 deletions
diff --git a/addons/bubblemessage.py b/addons/bubblemessage.py
index 1ed1fe0..aaf086c 100644
--- a/addons/bubblemessage.py
+++ b/addons/bubblemessage.py
@@ -96,8 +96,9 @@ class BubbleMessage(Action):
self._drag = DragWrapper(self._bubble, self.position, True)
def exit_editmode(self, *args):
- x,y = self._drag.position
- self.position = (int(x), int(y))
+ if self._drag.moved:
+ x,y = self._drag.position
+ self.position = (int(x), int(y))
if self._drag:
self._drag.draggable = False
self._drag = None
diff --git a/addons/bubblemessagewimg.py b/addons/bubblemessagewimg.py
new file mode 100644
index 0000000..9c3dfc1
--- /dev/null
+++ b/addons/bubblemessagewimg.py
@@ -0,0 +1,119 @@
+# Copyright (C) 2009, Tutorius.org
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+from sugar.tutorius.actions import Action, DragWrapper
+from sugar.tutorius.properties import TStringProperty, TResourceProperty, TArrayProperty
+from sugar.tutorius import overlayer
+from sugar.tutorius.services import ObjectStore
+
+class BubbleMessageWImg(Action):
+ message = TStringProperty("Message")
+ # Create the position as an array of fixed-size 2
+ position = TArrayProperty((0,0), 2, 2)
+ # Do the same for the tail position
+ tail_pos = TArrayProperty((0,0), 2, 2)
+ imgpath = TResourceProperty("")
+
+ def __init__(self, message=None, position=None, speaker=None, tail_pos=None, imgpath=None):
+ """
+ Shows a dialog with a given text, at the given position on the screen.
+
+ @param message A string to display to the user
+ @param position A list of the form [x, y]
+ @param speaker treeish representation of the speaking widget
+ @param tail_pos The position of the tail of the bubble; useful to point to
+ specific elements of the interface
+ """
+ Action.__init__(self)
+
+ if position:
+ self.position = position
+ if tail_pos:
+ self.tail_pos = tail_pos
+ if message:
+ self.message = message
+ if imgpath:
+ self.imgpath = imgpath
+
+ self.overlay = None
+ self._bubble = None
+ self._speaker = None
+
+ def do(self, **kwargs):
+ """
+ Show the dialog
+ """
+ # get or inject overlayer
+ self.overlay = ObjectStore().activity._overlayer
+ # FIXME: subwindows, are left to overlap this. This behaviour is
+ # undesirable. subwindows (i.e. child of top level windows) should be
+ # handled either by rendering over them, or by finding different way to
+ # draw the overlay.
+
+ if not self.overlay:
+ self.overlay = ObjectStore().activity._overlayer
+ if not self._bubble:
+ x, y = self.position
+ # TODO: tails are relative to tailpos. They should be relative to
+ # the speaking widget. Same of the bubble position.
+ self._bubble = overlayer.TextBubbleWImg(text=self.message,
+ tailpos=self.tail_pos,imagepath=self.imgpath.default)
+ self._bubble.show()
+ self.overlay.put(self._bubble, x, y)
+ self.overlay.queue_draw()
+
+ def undo(self):
+ """
+ Destroy the dialog
+ """
+ if self._bubble:
+ self._bubble.destroy()
+ self._bubble = None
+
+ def enter_editmode(self, *args):
+ """
+ Enters edit mode. The action should display itself in some way,
+ without affecting the currently running application.
+ """
+ if not self.overlay:
+ self.overlay = ObjectStore().activity._overlayer
+ assert not self._drag, "bubble action set to editmode twice"
+ x, y = self.position
+ self._bubble = overlayer.TextBubbleWImg(text=self.message,
+ tailpos=self.tail_pos,imagepath=self.imgpath)
+ self.overlay.put(self._bubble, x, y)
+ self._bubble.show()
+
+ self._drag = DragWrapper(self._bubble, self.position, True)
+
+ def exit_editmode(self, *args):
+ x,y = self._drag.position
+ self.position = (int(x), int(y))
+ if self._drag:
+ self._drag.draggable = False
+ self._drag = None
+ if self._bubble:
+ self.overlay.remove(self._bubble)
+ self._bubble = None
+ self.overlay = None
+
+__action__ = {
+ "name" : "BubbleMessageWImg",
+ "display_name" : "Message Bubble with image",
+ "icon" : "message-bubble",
+ "class" : BubbleMessageWImg,
+ "mandatory_props" : ["message",'imgpath']
+}
+
diff --git a/tests/enginetests.py b/tests/enginetests.py
index ca86c9f..4a8a3ca 100644
--- a/tests/enginetests.py
+++ b/tests/enginetests.py
@@ -85,6 +85,60 @@ class MockProbeMgrMultiAddons(object):
del self.event_dict[event_name]
break
+class MockProbeMgrMultiAddons(object):
+ """
+ Mock probe manager that supports installing more than one action
+ at the time.
+ """
+ def __init__(self):
+ self.action_dict = {}
+ self.event_dict = {}
+ self.event_cb_dict = {}
+
+ self._action_installed_cb_list = []
+ self._install_error_cb_list = []
+ self._event_subscribed_cb_list = []
+ self._subscribe_error_cb_list = []
+
+ currentActivity = property(fget=lambda s:s, fset=lambda s, v: v)
+
+ def run_install_cb(self, action_number, action):
+ self._action_installed_cb_list[action_number](action, str(uuid1()))
+
+ def run_install_error_cb(self, action_number):
+ self._install_error_cb_list[action_number](Exception("Could not install action..."))
+
+ def run_subscribe_cb(self, event_number):
+ self._event_subscribed_cb_list[event_number](str(uuid1()))
+
+ def run_subscribe_error(self, event_number):
+ self._subscribe_error_cb_list[event_number](str(uuid1()))
+
+ def install(self, action, action_installed_cb, error_cb):
+ action_address = str(uuid1())
+ self.action_dict[action_address] = action
+ self._action_installed_cb_list.append(action_installed_cb)
+ self._install_error_cb_list.append(error_cb)
+
+ def update(self, action_address, new_action):
+ self.action_dict[action_address] = new_action
+
+ def uninstall(self, action_address):
+ del self.action_dict[action_address]
+
+ def subscribe(self, event_name, event, notif_cb, subscribe_cb, error_cb):
+ event_address = str(uuid1())
+ self.event_dict[event_name] = event_address
+ self.event_cb_dict[event_name] = notif_cb
+ self._event_subscribed_cb_list.append(subscribe_cb)
+ self._subscribe_error_cb_list.append(error_cb)
+
+ def unsubscribe(self, address):
+ for (event_name, other_event) in self.event_dict.values():
+ if event == othet_event:
+ del self.event_dict[event_name]
+ break
+
class MockProbeMgr(object):
def __init__(self):
self.action = None
@@ -172,7 +226,7 @@ class TestRunnerStates(unittest.TestCase):
self.pM._action_installed_cb('action1')
- assert self.runner._runner_state == engine.RUNNER_STATE_STOPPED
+ assert self.runner._runner_state == engine.RUNNER_STATE_IDLE
def test_stop_in_events(self):
self.runner.start()
@@ -185,7 +239,7 @@ class TestRunnerStates(unittest.TestCase):
assert self.runner._runner_state == engine.RUNNER_STATE_SETUP_EVENTS, "Tutorial should not be stopped until all events have been confirmed"
self.pM.event_sub_cB('event1')
- assert self.runner._runner_state == engine.RUNNER_STATE_STOPPED, "Tutorial should have been stopped right after the last event was confirmed"
+ assert self.runner._runner_state == engine.RUNNER_STATE_IDLE, "Tutorial should have been stopped right after the last event was confirmed"
class TestInstallationStates(unittest.TestCase):
def setUp(self):
diff --git a/tests/linear_creatortests.py b/tests/linear_creatortests.py
deleted file mode 100644
index e3c30c1..0000000
--- a/tests/linear_creatortests.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# Copyright (C) 2009, Tutorius.org
-# Greatly influenced by sugar/activity/namingalert.py
-#
-# 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
-
-from sugar.tutorius.core import *
-from sugar.tutorius.actions import *
-from sugar.tutorius.filters import *
-from sugar.tutorius.linear_creator import *
-from sugar.tutorius.addons.triggereventfilter import *
-from actiontests import CountAction
-import unittest
-
-class CreatorTests(unittest.TestCase):
-
- def test_simple_usage(self):
- creator = LinearCreator()
- fsm_name = "SimpleUsageTest"
-
- creator.set_name(fsm_name)
-
- # Generate an FSM using the steps
- creator.action(CountAction())
- creator.action(CountAction())
-
- creator.event(TriggerEventFilter())
-
- creator.action(CountAction())
-
- creator.event(TriggerEventFilter())
-
- fsm = creator.generate_fsm()
-
- # Make sure everything worked!
- assert fsm.name == fsm_name, "Name was not set properly"
-
- init_state = fsm.get_state_by_name("INIT")
-
- assert len(init_state.get_action_list()) == 2, "Creator did not insert all the actions"
-
- assert init_state.get_event_filter_list()[0][1] == "State 1" , "expected next state to be 'State 1' but got %s" % init_state.get_event_filter_list()[0][1]
-
- state1 = fsm.get_state_by_name("State 1")
-
- assert len(state1.get_action_list()) == 1, "Creator did not insert all the actions"
-
- assert state1.get_event_filter_list()[0][1] == "State 2"
-
- # Make sure we have the final state and that it's empty
- state2 = fsm.get_state_by_name("State2")
-
- assert len(state2.get_action_list()) == 0, "Creator inserted extra actions on wrong state"
-
- assert len(state2.get_event_filter_list()) == 0, "Creator assigner events to the final state"
-
- creator.action(CountAction())
-
- fsm = creator.generate_fsm()
-
- state2 = fsm.get_state_by_name("State2")
-
- assert len(state2.get_action_list()) == 1, "Creator did not add the action"
-
- assert len(state2.get_event_filter_list()) == 0, "Creator assigner events to the final state"
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/tests/vaulttests.py b/tests/vaulttests.py
index e8e732e..729d36d 100644
--- a/tests/vaulttests.py
+++ b/tests/vaulttests.py
@@ -92,7 +92,7 @@ class VaultInterfaceTest(unittest.TestCase):
ini_file2.write('guid=' + str(self.test_guid2) + '\n')
ini_file2.write('name=TestTutorial2\n')
ini_file2.write('version=2\n')
- ini_file2.write('description=This is a test tutorial 2\n')
+ ini_file2.write('description=This is a test tutorial 2. FOR_TEST\n')
ini_file2.write('rating=4\n')
ini_file2.write('category=Test2\n')
ini_file2.write('publish_state=false\n')
@@ -125,7 +125,7 @@ class VaultInterfaceTest(unittest.TestCase):
self.test_metadata_dict['publish_state'] = 'false'
activities_dict = {}
activities_dict['org.laptop.tutoriusactivity'] = '1'
- activities_dict['org.laptop,writus'] = '1'
+ activities_dict['org.laptop.writus'] = '1'
self.test_metadata_dict['activities'] = activities_dict
@@ -142,7 +142,7 @@ class VaultInterfaceTest(unittest.TestCase):
shutil.rmtree(os.path.join(os.getenv("HOME"),".sugar", 'default', 'tutorius', 'tmp'))
os.makedirs(test_path)
- # Creat a dummy tutorial .xml file
+ # Create a dummy tutorial .xml file
serializer = XMLSerializer()
with file(os.path.join(test_path, 'tutorial.xml'), 'w') as fsmfile:
@@ -159,7 +159,7 @@ class VaultInterfaceTest(unittest.TestCase):
zout = zipfile.ZipFile(os.path.join(test_path, zfilename), "w")
for fname in archive_list:
fname_splitted = fname.rsplit('/')
- file_only_name = fname_splitted[fname_splitted.__len__() - 1]
+ file_only_name = fname_splitted[-1]
zout.write(fname, file_only_name)
zout.close()
@@ -186,12 +186,10 @@ class VaultInterfaceTest(unittest.TestCase):
correspond to the specified parameters.
"""
- # Note : Temporary only test query that return ALL tutorials in the vault.
- # TODO : Test with varying parameters
-
+ # Test the return of all the tutorials
tutorial_list = Vault.query()
- if tutorial_list.__len__() < 2:
+ if len(tutorial_list) < 2:
assert False, 'Error, list doesnt have enough tutorial in it : ' + str(tutorial_list.__len__()) + ' element'
for tuto_dictionnary in tutorial_list:
@@ -208,7 +206,7 @@ class VaultInterfaceTest(unittest.TestCase):
elif tuto_dictionnary['name'] == 'TestTutorial2':
related = tuto_dictionnary['activities']
assert tuto_dictionnary['version'] == '2'
- assert tuto_dictionnary['description'] == 'This is a test tutorial 2'
+ assert tuto_dictionnary['description'] == 'This is a test tutorial 2. FOR_TEST'
assert tuto_dictionnary['rating'] == '4'
assert tuto_dictionnary['category'] == 'Test2'
assert tuto_dictionnary['publish_state'] == 'false'
@@ -218,7 +216,55 @@ class VaultInterfaceTest(unittest.TestCase):
else:
assert False, 'list is empty or name property is wrong'
-
+
+ # Test the return of just a given tutorial with a given keyword present in one
+ # or more of is metadata fields.
+ tutorial_list = Vault.query(keyword=['FOR_TEST'])
+
+ # Should return only tutorial with metadata like tutorial # 2
+ if len(tutorial_list) == 0:
+ assert False, 'Error on keyword, at least 1 tutorial should has been returned. '
+ related = tutorial_list[0]['activities']
+ assert tutorial_list[0]['version'] == '2'
+ assert tutorial_list[0]['description'] == 'This is a test tutorial 2. FOR_TEST'
+ assert tutorial_list[0]['rating'] == '4'
+ assert tutorial_list[0]['category'] == 'Test2'
+ assert tutorial_list[0]['publish_state'] == 'false'
+ assert related.has_key('org.laptop.tutoriusactivity')
+ assert related.has_key('org.laptop.writus')
+ assert related.has_key('org.laptop.testus')
+
+ # Test the return of just a given tutorial with a given related activity in is metadata
+ tutorial_list = Vault.query(relatedActivityNames=['org.laptop.testus'])
+
+ # Should return only tutorials like tutorial # 2
+ if len(tutorial_list) == 0:
+ assert False, 'Error on related activity, at least 1 tutorial should has been returned. '
+ related = tutorial_list[0]['activities']
+ assert tutorial_list[0]['version'] == '2'
+ assert tutorial_list[0]['description'] == 'This is a test tutorial 2. FOR_TEST'
+ assert tutorial_list[0]['rating'] == '4'
+ assert tutorial_list[0]['category'] == 'Test2'
+ assert tutorial_list[0]['publish_state'] == 'false'
+ assert related.has_key('org.laptop.tutoriusactivity')
+ assert related.has_key('org.laptop.writus')
+ assert related.has_key('org.laptop.testus')
+
+ # Test the return of just a given tutorial with a given category in is metadata
+ tutorial_list = Vault.query(category=['test'])
+
+ # Should return only tutorials like tutorial # 1
+ if len(tutorial_list) == 0:
+ assert False, 'Error on category, at least 1 tutorial should has been returned. '
+ related = tutorial_list[0]['activities']
+ assert tutorial_list[0]['version'] == '1'
+ assert tutorial_list[0]['description'] == 'This is a test tutorial 1'
+ assert tutorial_list[0]['rating'] == '3.5'
+ assert tutorial_list[0]['category'] == 'Test'
+ assert tutorial_list[0]['publish_state'] == 'false'
+ assert related.has_key('org.laptop.tutoriusactivity')
+ assert related.has_key('org.laptop.writus')
+
def test_loadTutorial(self):
"""
diff --git a/tutorius/actions.py b/tutorius/actions.py
index bb15459..75c9c9b 100644
--- a/tutorius/actions.py
+++ b/tutorius/actions.py
@@ -43,6 +43,7 @@ class DragWrapper(object):
self._handles = [] # event handlers
self._dragging = False # whether a drag is in progress
self.position = position # position of the widget
+ self.moved = False
self.draggable = draggable
@@ -68,6 +69,7 @@ class DragWrapper(object):
xparent, yparent = widget.translate_coordinates(widget.parent,
xparent, yparent)
self.position = (xparent-xrel, yparent-yrel)
+ self.moved = True
self._widget.parent.move(self._eventbox, *self.position)
self._widget.parent.move(self._widget, *self.position)
self._widget.parent.queue_draw()
diff --git a/tutorius/addon.py b/tutorius/addon.py
index 30b7a63..6e3d8b9 100644
--- a/tutorius/addon.py
+++ b/tutorius/addon.py
@@ -28,6 +28,7 @@ __action__ = {
"icon" : "hello",
"class" : HelloAction,
"mandatory_props" : ["text"],
+ "test" : true, (OPTIONAL)
}
"""
@@ -75,7 +76,7 @@ def list_addons():
global _cache
if not _cache:
_reload_addons()
- return _cache.keys()
+ return [k for k, v in _cache.items() if 'test' not in v]
def get_addon_meta(name):
global _cache
diff --git a/tutorius/creator.py b/tutorius/creator.py
index 906a04e..68c5fa6 100644
--- a/tutorius/creator.py
+++ b/tutorius/creator.py
@@ -36,6 +36,7 @@ from .services import ObjectStore
from .core import State
from .tutorial import Tutorial
from . import viewer
+from .propwidgets import TextInputDialog, StringPropWidget
class Creator(object):
"""
@@ -230,25 +231,7 @@ class Creator(object):
meta = addon.get_addon_meta(addonname)
for propname in meta['mandatory_props']:
prop = getattr(type(event), propname)
- if isinstance(prop, properties.TUAMProperty):
- selector = WidgetSelector(self._activity)
- setattr(event, propname, selector.select())
- elif isinstance(prop, properties.TEventType):
- try:
- dlg = SignalInputDialog(self._activity,
- text="Mandatory property",
- field=propname,
- addr=event.object_id)
- setattr(event, propname, dlg.pop())
- except AttributeError:
- pass
- elif isinstance(prop, properties.TStringProperty):
- dlg = TextInputDialog(self._activity,
- text="Mandatory property",
- field=propname)
- setattr(event, propname, dlg.pop())
- else:
- raise NotImplementedError()
+ prop.widget_class.run_dialog(self._activity, event, propname)
event_filters = self._tutorial.get_transition_dict(self._state)
@@ -360,6 +343,7 @@ class ToolBox(object):
self.tree = gtk.glade.XML(glade_file)
self.window = self.tree.get_widget('mainwindow')
self._propbox = self.tree.get_widget('propbox')
+ self._propedits = []
self.window.set_transient_for(parent)
@@ -402,74 +386,38 @@ class ToolBox(object):
"""Refresh property values from the selected action."""
if self._action is None:
return
- props = self._action._props.keys()
- for propnum in xrange(len(props)):
- row = self._propbox.get_children()[propnum]
- propname = props[propnum]
- prop = getattr(type(self._action), propname)
- propval = getattr(self._action, propname)
- if isinstance(prop, properties.TStringProperty):
- propwdg = row.get_children()[1]
- propwdg.get_buffer().set_text(propval)
- elif isinstance(prop, properties.TUAMProperty):
- propwdg = row.get_children()[1]
- propwdg.set_label(propval)
- elif isinstance(prop, properties.TIntProperty):
- propwdg = row.get_children()[1]
- propwdg.set_value(propval)
- elif isinstance(prop, properties.TArrayProperty):
- propwdg = row.get_children()[1]
- for i in xrange(len(propval)):
- entry = propwdg.get_children()[i]
- entry.set_text(str(propval[i]))
- else:
- propwdg = row.get_children()[1]
- propwdg.set_text(str(propval))
+
+ #Refresh the property editors
+ for prop in self._propedits:
+ prop.refresh_widget()
def set_action(self, action):
"""Setter for the action property."""
if self._action is action:
self.refresh_properties()
return
+
+ #Clear the prop box
for old_prop in self._propbox.get_children():
self._propbox.remove(old_prop)
+ self._propedits = []
+
self._action = action
if action is None:
return
for propname in action._props.keys():
row = gtk.HBox()
+ #Label
row.pack_start(gtk.Label(T(propname)), False, False, 10)
+
+ #Value field
prop = getattr(type(action), propname)
- propval = getattr(action, propname)
- if isinstance(prop, properties.TStringProperty):
- propwdg = gtk.TextView()
- propwdg.get_buffer().set_text(propval)
- propwdg.connect_after("focus-out-event", \
- self._str_prop_changed, action, propname)
- elif isinstance(prop, properties.TUAMProperty):
- propwdg = gtk.Button(propval)
- propwdg.connect_after("clicked", \
- self._uam_prop_changed, action, propname)
- elif isinstance(prop, properties.TIntProperty):
- adjustment = gtk.Adjustment(value=propval,
- lower=prop.lower_limit.limit,
- upper=prop.upper_limit.limit,
- step_incr=1)
- propwdg = gtk.SpinButton(adjustment=adjustment)
- propwdg.connect_after("focus-out-event", \
- self._int_prop_changed, action, prop)
- elif isinstance(prop, properties.TArrayProperty):
- propwdg = gtk.HBox()
- for i in xrange(len(propval)):
- entry = gtk.Entry()
- propwdg.pack_start(entry)
- entry.connect_after("focus-out-event", \
- self._list_prop_changed, action, propname, i)
- else:
- propwdg = gtk.Entry()
- propwdg.set_text(str(propval))
- row.pack_end(propwdg)
+ propedit = prop.widget_class(self.__parent, action, propname, self._refresh_action_cb)
+ self._propedits.append(propedit)
+ row.pack_end(propedit.widget)
+
+ #Add row
self._propbox.pack_start(row, expand=False)
self._propbox.show_all()
self.refresh_properties()
@@ -480,187 +428,10 @@ class ToolBox(object):
action = property(fset=set_action, fget=get_action, doc=\
"Action to be edited through introspection.")
- def _list_prop_changed(self, widget, evt, action, propname, idx):
- try:
- #Save props as tuples so that they can be hashed
- attr = list(getattr(action, propname))
- attr[idx] = int(widget.get_text())
- setattr(action, propname, tuple(attr))
- except ValueError:
- widget.set_text(str(getattr(action, propname)[idx]))
- self.__parent._creator._action_refresh_cb(None, None, action)
- def _uam_prop_changed(self, widget, action, propname):
- selector = WidgetSelector(self.__parent)
- selection = selector.select()
- setattr(action, propname, selection)
- self.__parent._creator._action_refresh_cb(None, None, action)
- def _str_prop_changed(self, widget, evt, action, propname):
- buf = widget.get_buffer()
- setattr(action, propname, buf.get_text(buf.get_start_iter(), buf.get_end_iter()))
- self.__parent._creator._action_refresh_cb(None, None, action)
- def _int_prop_changed(self, widget, evt, action, prop):
- setattr(action, propname, widget.get_value_as_int())
- self.__parent._creator._action_refresh_cb(None, None, action)
-
-
-class WidgetSelector(object):
- """
- Allow selecting a widget from within a window without interrupting the
- flow of the current call.
-
- The selector will run on the specified window until either a widget
- is selected or abort() gets called.
- """
- def __init__(self, window):
- super(WidgetSelector, self).__init__()
- self.window = window
- self._intro_mask = None
- self._intro_handle = None
- self._select_handle = None
- self._prelight = None
+ def _refresh_action_cb(self):
+ if self._action is not None:
+ self.__parent._creator._action_refresh_cb(None, None, self._action)
- def select(self):
- """
- Starts selecting a widget, by grabbing control of the mouse and
- highlighting hovered widgets until one is clicked.
- @returns: a widget address or None
- """
- if not self._intro_mask:
- self._prelight = None
- self._intro_mask = overlayer.Mask(catch_events=True)
- self._select_handle = self._intro_mask.connect_after(
- "button-press-event", self._end_introspect)
- self._intro_handle = self._intro_mask.connect_after(
- "motion-notify-event", self._intro_cb)
- self.window._overlayer.put(self._intro_mask, 0, 0)
- self.window._overlayer.queue_draw()
-
- while bool(self._intro_mask) and not gtk.main_iteration():
- pass
-
- return gtkutils.raddr_lookup(self._prelight)
-
- def _end_introspect(self, widget, evt):
- if evt.type == gtk.gdk.BUTTON_PRESS and self._prelight:
- self._intro_mask.catch_events = False
- self._intro_mask.disconnect(self._intro_handle)
- self._intro_handle = None
- self._intro_mask.disconnect(self._select_handle)
- self._select_handle = None
- self.window._overlayer.remove(self._intro_mask)
- self._intro_mask = None
- # for some reason, gtk may not redraw after this unless told to.
- self.window.queue_draw()
-
- def _intro_cb(self, widget, evt):
- """
- Callback for capture of widget events, when in introspect mode.
- """
- # widget has focus, let's hilight it
- win = gtk.gdk.display_get_default().get_window_at_pointer()
- if not win:
- return
- click_wdg = win[0].get_user_data()
- if not click_wdg.is_ancestor(self.window._overlayer):
- # as popups are not (yet) supported, it would break
- # badly if we were to play with a widget not in the
- # hierarchy.
- return
- for hole in self._intro_mask.pass_thru:
- self._intro_mask.mask(hole)
- self._intro_mask.unmask(click_wdg)
- self._prelight = click_wdg
-
- self.window.queue_draw()
-
- def abort(self):
- """
- Ends the selection. The control will return to the select() caller
- with a return value of None, as selection was aborted.
- """
- self._intro_mask.catch_events = False
- self._intro_mask.disconnect(self._intro_handle)
- self._intro_handle = None
- self._intro_mask.disconnect(self._select_handle)
- self._select_handle = None
- self.window._overlayer.remove(self._intro_mask)
- self._intro_mask = None
- self._prelight = None
-
-class SignalInputDialog(gtk.MessageDialog):
- def __init__(self, parent, text, field, addr):
- """
- Create a gtk signal selection dialog.
-
- @param parent: the parent window this dialog should stay over.
- @param text: the title of the dialog.
- @param field: the field description of the dialog.
- @param addr: the widget address from which to fetch signal list.
- """
- gtk.MessageDialog.__init__(self, parent,
- gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
- gtk.MESSAGE_QUESTION,
- gtk.BUTTONS_OK,
- None)
- self.set_markup(text)
- self.model = gtk.ListStore(str)
- widget = gtkutils.find_widget(parent, addr)
- for signal_name in gobject.signal_list_names(widget):
- self.model.append(row=(signal_name,))
- self.entry = gtk.ComboBox(self.model)
- cell = gtk.CellRendererText()
- self.entry.pack_start(cell)
- self.entry.add_attribute(cell, 'text', 0)
- hbox = gtk.HBox()
- lbl = gtk.Label(field)
- hbox.pack_start(lbl, False)
- hbox.pack_end(self.entry)
- self.vbox.pack_end(hbox, True, True)
- self.show_all()
-
- def pop(self):
- """
- Show the dialog. It will run in it's own loop and return control
- to the caller when a signal has been selected.
-
- @returns: a signal name or None if no signal was selected
- """
- self.run()
- self.hide()
- iter = self.entry.get_active_iter()
- if iter:
- text = self.model.get_value(iter, 0)
- return text
- return None
-
- def _dialog_done_cb(self, entry, response):
- self.response(response)
-
-class TextInputDialog(gtk.MessageDialog):
- def __init__(self, parent, text, field):
- gtk.MessageDialog.__init__(self, parent,
- gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
- gtk.MESSAGE_QUESTION,
- gtk.BUTTONS_OK,
- None)
- self.set_markup(text)
- self.entry = gtk.Entry()
- self.entry.connect("activate", self._dialog_done_cb, gtk.RESPONSE_OK)
- hbox = gtk.HBox()
- lbl = gtk.Label(field)
- hbox.pack_start(lbl, False)
- hbox.pack_end(self.entry)
- self.vbox.pack_end(hbox, True, True)
- self.show_all()
-
- def pop(self):
- self.run()
- self.hide()
- text = self.entry.get_text()
- return text
-
- def _dialog_done_cb(self, entry, response):
- self.response(response)
# The purpose of this function is to reformat text, as current IconView
# implentation does not insert carriage returns on long lines.
diff --git a/tutorius/engine.py b/tutorius/engine.py
index be0b935..34616f6 100644
--- a/tutorius/engine.py
+++ b/tutorius/engine.py
@@ -37,7 +37,6 @@ RUNNER_STATE_SETUP_EVENTS = 2
RUNNER_STATE_AWAITING_NOTIFICATIONS = 3
RUNNER_STATE_UNINSTALLING_ACTIONS = 4
RUNNER_STATE_UNSUBSCRIBING_EVENTS = 5
-RUNNER_STATE_STOPPED = 6
LOGGER = logging.getLogger("sugar.tutorius.engine")
@@ -82,7 +81,9 @@ class TutorialRunner(object):
#Temp FIX until event, actions have an activity id
def setCurrentActivity(self):
self._pM.currentActivity = self._activity_id
-
+
+ ###########################################################################
+ # Incoming messages
def start(self):
self.setCurrentActivity() #Temp Hack until activity in events/actions
self.enterState(self._tutorial.INIT)
@@ -94,10 +95,78 @@ class TutorialRunner(object):
else:
self._execute_stop()
+ def action_installed(self, action_name, action, address):
+ LOGGER.debug("TutorialRunner :: Action %s received address %s"%(action_name, address))
+ self._installed_actions[action_name] = address
+ # Verify if we just completed the installation of the actions for this state
+ self._verify_action_install_state()
+
+ def install_error(self, action_name, action, exception):
+ # TODO : Fix this as it doesn't warn the user about the problem or anything
+ LOGGER.debug("TutorialRunner :: Action could not be installed %s, exception was : %s"%(str(action), str(exception)))
+ self._installation_errors[action_name] = exception
+ self._verify_action_install_state()
+
+ def event_subscribed(self, event_name, event_address):
+ LOGGER.debug("TutorialRunner :: Event %s was subscribed to, located at address %s"%(event_name, event_address))
+ self._subscribed_events[event_name] = event_address
+
+ # Verify if we just completed the subscription of all the events for this state
+ self._verify_event_install_state()
+
+ def subscribe_error(self, event_name, exception):
+ # TODO : Do correct error handling here
+ LOGGER.debug("TutorialRunner :: Could not subscribe to event %s, got exception : %s"%(event_name, str(exception)))
+ self._subscription_errors[event_name] = exception
+
+ # Verify if we just completed the subscription of all the events for this state
+ self._verify_event_install_state()
+
+ def all_actions_installed(self):
+ self._runner_state = RUNNER_STATE_SETUP_EVENTS
+ # Process the messages that might have been stored
+ self._process_pending_messages()
+
+ # If we processed a message that changed the runner state, we need to stop
+ # processing
+ if self._runner_state != RUNNER_STATE_SETUP_EVENTS:
+ return
+
+ # Start subscribing to events
+ transitions = self._tutorial.get_transition_dict(self._state)
+
+ # If there are no transitions, raise the All Events Subscribed message
+ if len(transitions) == 0:
+ self.all_events_subscribed()
+ return
+
+ # Send all the event registration
+ for (event_name, (event, next_state)) in transitions.items():
+ self._pM.subscribe(event_name, event,
+ save_args(self._handleEvent, next_state),
+ save_args(self.event_subscribed, event_name),
+ save_args(self.subscribe_error, event_name))
+
+ def all_events_subscribed(self):
+ self._runner_state = RUNNER_STATE_AWAITING_NOTIFICATIONS
+ self._process_pending_messages()
+
+ def _uninstall_actions(self):
+ self._runner_state = RUNNER_STATE_UNINSTALLING_ACTIONS
+ self._remove_installed_actions()
+ self._execute_stop()
+
+ def _unsubscribe_events(self):
+ self._runner_state = RUNNER_STATE_UNSUBSCRIBING_EVENTS
+ self._remove_subscribed_events()
+ self._uninstall_actions()
+
+ ###########################################################################
+ # Helper functions
def _execute_stop(self):
self.setCurrentActivity() #Temp Hack until activity in events/actions
self._state = None
- self._runner_state = RUNNER_STATE_STOPPED
+ self._runner_state = RUNNER_STATE_IDLE
def _handleEvent(self, next_state, event):
# Look if we are actually receiving notifications
@@ -136,19 +205,7 @@ class TutorialRunner(object):
self._subscribed_events.clear()
self._subscription_errors.clear()
- def __action_installed(self, action_name, action, address):
- LOGGER.debug("TutorialRunner :: Action %s received address %s"%(action_name, address))
- self._installed_actions[action_name] = address
- # Verify if we just completed the installation of the actions for this state
- self.__verify_action_install_state()
-
- def __install_error(self, action_name, action, exception):
- # TODO : Fix this as it doesn't warn the user about the problem or anything
- LOGGER.debug("TutorialRunner :: Action could not be installed %s, exception was : %s"%(str(action), str(exception)))
- self._installation_errors[action_name] = exception
- self.__verify_action_install_state()
-
- def __verify_action_install_state(self):
+ def _verify_action_install_state(self):
# Do the check to see if we have finished installing all the actions by either having
# received a address for it or an error message
install_complete = True
@@ -162,24 +219,9 @@ class TutorialRunner(object):
if install_complete:
LOGGER.debug("TutorialRunner :: All actions installed!")
# Raise the All Actions Installed event for the TutorialRunner state
- self.__all_actions_installed()
+ self.all_actions_installed()
- def __event_subscribed(self, event_name, event_address):
- LOGGER.debug("TutorialRunner :: Event %s was subscribed to, located at address %s"%(event_name, event_address))
- self._subscribed_events[event_name] = event_address
-
- # Verify if we just completed the subscription of all the events for this state
- self.__verify_event_install_state()
-
- def __subscribe_error(self, event_name, exception):
- # TODO : Do correct error handling here
- LOGGER.debug("TutorialRunner :: Could not subscribe to event %s, got exception : %s"%(event_name, str(exception)))
- self._subscription_errors[event_name] = exception
-
- # Verify if we just completed the subscription of all the events for this state
- self.__verify_event_install_state()
-
- def __verify_event_install_state(self):
+ def _verify_event_install_state(self):
transitions = self._tutorial.get_transition_dict(self._state)
# Check to see if we completed all the event subscriptions
@@ -192,48 +234,9 @@ class TutorialRunner(object):
if subscribe_complete:
LOGGER.debug("TutorialRunner : Subscribed to all events!")
- self.__all_events_subscribed()
-
- def __all_actions_installed(self):
- self._runner_state = RUNNER_STATE_SETUP_EVENTS
- # Process the messages that might have been stored
- self.__process_pending_messages()
+ self.all_events_subscribed()
- # If we processed a message that changed the runner state, we need to stop
- # processing
- if self._runner_state != RUNNER_STATE_SETUP_EVENTS:
- return
-
- # Start subscribing to events
- transitions = self._tutorial.get_transition_dict(self._state)
-
- # If there are no transitions, raise the All Events Subscribed message
- if len(transitions) == 0:
- self.__all_events_subscribed()
- return
-
- # Send all the event registration
- for (event_name, (event, next_state)) in transitions.items():
- self._pM.subscribe(event_name, event,
- save_args(self._handleEvent, next_state),
- save_args(self.__event_subscribed, event_name),
- save_args(self.__subscribe_error, event_name))
-
- def __all_events_subscribed(self):
- self._runner_state = RUNNER_STATE_AWAITING_NOTIFICATIONS
- self.__process_pending_messages()
-
- def __uninstall_actions(self):
- self._runner_state = RUNNER_STATE_UNINSTALLING_ACTIONS
- self._remove_installed_actions()
- self._execute_stop()
-
- def __unsubscribe_events(self):
- self._runner_state = RUNNER_STATE_UNSUBSCRIBING_EVENTS
- self._remove_subscribed_events()
- self.__uninstall_actions()
-
- def __process_pending_messages(self):
+ def _process_pending_messages(self):
while len(self._message_queue) != 0:
(priority, message) = heappop(self._message_queue)
@@ -245,9 +248,9 @@ class TutorialRunner(object):
# Start removing the installed addons
if self._runner_state == RUNNER_STATE_AWAITING_NOTIFICATIONS:
# Start uninstalling the events
- self.__unsubscribe_events()
+ self._unsubscribe_events()
if self._runner_state == RUNNER_STATE_SETUP_EVENTS:
- self.__uninstall_actions()
+ self._uninstall_actions()
elif priority == EVENT_NOTIFICATION_MSG_PRIORITY:
LOGGER.debug("TutorialRunner :: Handling stored event notification for next_state %s"%message[0])
self._handle_event(*message)
@@ -259,22 +262,22 @@ class TutorialRunner(object):
self._actions = self._tutorial.get_action_dict(self._state)
if len(self._actions) == 0:
- self.__all_actions_installed()
+ self.all_actions_installed()
return
for (action_name, action) in self._actions.items():
LOGGER.debug("TutorialRunner :: Installed action %s"%(action_name))
self._pM.install(action,
- save_args(self.__action_installed, action_name),
- save_args(self.__install_error, action_name))
+ save_args(self.action_installed, action_name),
+ save_args(self.install_error, action_name))
def enterState(self, state_name):
"""
- Starting from the state_name, the runner execute states until
- no automatic transition are found and will wait for an external
- event to occur.
+ Starting from the state_name, the runner execute states from the
+ tutorial until no automatic transitions are found and will wait
+ for an external event to occur.
- When entering the state, actions and events from the previous
+ When entering the sate, actions and events from the previous
state are respectively uninstalled and unsubscribed and actions
and events from the state_name will be installed and subscribed.
diff --git a/tutorius/linear_creator.py b/tutorius/linear_creator.py
deleted file mode 100644
index f664c49..0000000
--- a/tutorius/linear_creator.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# Copyright (C) 2009, Tutorius.org
-# Greatly influenced by sugar/activity/namingalert.py
-#
-# 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
-
-from copy import deepcopy
-
-from .core import *
-from .actions import *
-from .filters import *
-
-class LinearCreator(object):
- """
- This class is used to create a FSM from a linear sequence of orders. The
- orders themselves are meant to be either an action or a transition.
- """
-
- def __init__(self):
- self.fsm = FiniteStateMachine("Sample Tutorial")
- self.current_actions = []
- self.nb_state = 0
- self.state_name = "INIT"
-
- def set_name(self, name):
- """
- Sets the name of the generated FSM.
- """
- self.fsm.name = name
-
- def action(self, action):
- """
- Adds an action to execute in the current state.
- """
- self.current_actions.append(action)
-
- def event(self, event_filter):
- """
- Adds a transition to another state. When executing this, all the actions
- previously called will be bundled in a single state, with the exit
- condition of this state being the transition just added.
-
- Whatever the name of the next state you inserted in the event, it will
- be replaced to point to the next event in the line.
- """
- if len(self.current_actions) != 0:
- # Set the next state name - there is no way the caller should have
- # to deal with that.
- next_state_name = "State %d" % (self.nb_state+1)
- state = State(self.state_name, action_list=self.current_actions,
- event_filter_list=[(event_filter, next_state_name),])
- self.state_name = next_state_name
-
- self.nb_state += 1
- self.fsm.add_state(state)
-
- # Clear the actions from the list
- self.current_actions = []
-
- def generate_fsm(self):
- """
- Returns a finite state machine corresponding to the sequence of calls
- that were made from this point on.
- """
- # Copy the whole FSM that was generated yet
- new_fsm = deepcopy(self.fsm)
-
- # Generate the final state
- state = None
- if len(self.current_actions) != 0:
- state = State("State" + str(self.nb_state), action_list=self.current_actions)
- # Don't increment the nb_state here - we would break the linearity
- # because we might generate more stuff with this creator later.
- # Since we rely on linearity for continuity when generating the
- # next state's name on an event filter, we cannot increment here.
- else:
- state = State("State" + str(self.nb_state))
-
- # Insert the state in the copy of the FSM
- new_fsm.add_state(state)
-
- return new_fsm
-
diff --git a/tutorius/properties.py b/tutorius/properties.py
index 6bd16ee..a462782 100644
--- a/tutorius/properties.py
+++ b/tutorius/properties.py
@@ -27,6 +27,13 @@ from .constraints import Constraint, \
ColorConstraint, FileConstraint, BooleanConstraint, EnumConstraint, \
ResourceConstraint
+from .propwidgets import PropWidget, \
+ StringPropWidget, \
+ UAMPropWidget, \
+ EventTypePropWidget, \
+ IntPropWidget, \
+ FloatPropWidget, \
+ IntArrayPropWidget
class TPropContainer(object):
"""
@@ -148,6 +155,7 @@ class TutoriusProperty(object):
get_contraints() : the constraints inserted on this property. They define
what is acceptable or not as values.
"""
+ widget_class = PropWidget
def __init__(self):
super(TutoriusProperty, self).__init__()
self.type = None
@@ -192,7 +200,7 @@ class TIntProperty(TutoriusProperty):
Represents an integer. Can have an upper value limit and/or a lower value
limit.
"""
-
+ widget_class = IntPropWidget
def __init__(self, value, lower_limit=None, upper_limit=None):
TutoriusProperty.__init__(self)
self.type = "int"
@@ -206,6 +214,7 @@ class TFloatProperty(TutoriusProperty):
Represents a floating point number. Can have an upper value limit and/or
a lower value limit.
"""
+ widget_class = FloatPropWidget
def __init__(self, value, lower_limit=None, upper_limit=None):
TutoriusProperty.__init__(self)
self.type = "float"
@@ -219,6 +228,7 @@ class TStringProperty(TutoriusProperty):
"""
Represents a string. Can have a maximum size limit.
"""
+ widget_class = StringPropWidget
def __init__(self, value, size_limit=None):
TutoriusProperty.__init__(self)
self.type = "string"
@@ -231,6 +241,7 @@ class TArrayProperty(TutoriusProperty):
Represents an array of properties. Can have a maximum number of element
limit, but there are no constraints on the content of the array.
"""
+ widget_class = IntArrayPropWidget
def __init__(self, value, min_size_limit=None, max_size_limit=None):
TutoriusProperty.__init__(self)
self.type = "array"
@@ -250,6 +261,7 @@ class TArrayProperty(TutoriusProperty):
min_size_limit=self.min_size_limit.limit,
value=self.value,
)
+
class TColorProperty(TutoriusProperty):
"""
Represents a RGB color with 3 8-bit integer values.
@@ -359,6 +371,7 @@ class TUAMProperty(TutoriusProperty):
"""
Represents a widget of the interface by storing its UAM.
"""
+ widget_class = UAMPropWidget
def __init__(self, value=None):
TutoriusProperty.__init__(self)
@@ -391,6 +404,7 @@ class TEventType(TutoriusProperty):
"""
Represents an GUI signal for a widget.
"""
+ widget_class = EventTypePropWidget
def __init__(self, value):
super(TEventType, self).__init__()
self.type = "gtk-signal"
diff --git a/tutorius/propwidgets.py b/tutorius/propwidgets.py
new file mode 100644
index 0000000..7e78ba4
--- /dev/null
+++ b/tutorius/propwidgets.py
@@ -0,0 +1,489 @@
+# Copyright (C) 2009, Tutorius.org
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+"""
+Property Widgets.
+
+Allows displaying properties cleanly.
+"""
+import gtk
+import gobject
+
+from . import gtkutils, overlayer
+###########################################################################
+# Dialog classes
+###########################################################################
+class TextInputDialog(gtk.MessageDialog):
+ def __init__(self, parent, text, field):
+ gtk.MessageDialog.__init__(self, parent,
+ gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
+ gtk.MESSAGE_QUESTION,
+ gtk.BUTTONS_OK,
+ None)
+ self.set_markup(text)
+ self.entry = gtk.Entry()
+ self.entry.connect("activate", self._dialog_done_cb, gtk.RESPONSE_OK)
+ hbox = gtk.HBox()
+ lbl = gtk.Label(field)
+ hbox.pack_start(lbl, False)
+ hbox.pack_end(self.entry)
+ self.vbox.pack_end(hbox, True, True)
+ self.show_all()
+
+ def pop(self):
+ self.run()
+ self.hide()
+ text = self.entry.get_text()
+ return text
+
+ def _dialog_done_cb(self, entry, response):
+ self.response(response)
+
+class SignalInputDialog(gtk.MessageDialog):
+ def __init__(self, parent, text, field, addr):
+ """
+ Create a gtk signal selection dialog.
+
+ @param parent: the parent window this dialog should stay over.
+ @param text: the title of the dialog.
+ @param field: the field description of the dialog.
+ @param addr: the widget address from which to fetch signal list.
+ """
+ gtk.MessageDialog.__init__(self, parent,
+ gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
+ gtk.MESSAGE_QUESTION,
+ gtk.BUTTONS_OK,
+ None)
+ self.set_markup(text)
+ self.model = gtk.ListStore(str)
+ widget = gtkutils.find_widget(parent, addr)
+ for signal_name in gobject.signal_list_names(widget):
+ self.model.append(row=(signal_name,))
+ self.entry = gtk.ComboBox(self.model)
+ cell = gtk.CellRendererText()
+ self.entry.pack_start(cell)
+ self.entry.add_attribute(cell, 'text', 0)
+ hbox = gtk.HBox()
+ lbl = gtk.Label(field)
+ hbox.pack_start(lbl, False)
+ hbox.pack_end(self.entry)
+ self.vbox.pack_end(hbox, True, True)
+ self.show_all()
+
+ def pop(self):
+ """
+ Show the dialog. It will run in it's own loop and return control
+ to the caller when a signal has been selected.
+
+ @returns: a signal name or None if no signal was selected
+ """
+ self.run()
+ self.hide()
+ iter = self.entry.get_active_iter()
+ if iter:
+ text = self.model.get_value(iter, 0)
+ return text
+ return None
+
+ def _dialog_done_cb(self, entry, response):
+ self.response(response)
+
+class WidgetSelector(object):
+ """
+ Allow selecting a widget from within a window without interrupting the
+ flow of the current call.
+
+ The selector will run on the specified window until either a widget
+ is selected or abort() gets called.
+ """
+ def __init__(self, window):
+ super(WidgetSelector, self).__init__()
+ self.window = window
+ self._intro_mask = None
+ self._intro_handle = None
+ self._select_handle = None
+ self._prelight = None
+
+ def select(self):
+ """
+ Starts selecting a widget, by grabbing control of the mouse and
+ highlighting hovered widgets until one is clicked.
+ @returns: a widget address or None
+ """
+ if not self._intro_mask:
+ self._prelight = None
+ self._intro_mask = overlayer.Mask(catch_events=True)
+ self._select_handle = self._intro_mask.connect_after(
+ "button-press-event", self._end_introspect)
+ self._intro_handle = self._intro_mask.connect_after(
+ "motion-notify-event", self._intro_cb)
+ self.window._overlayer.put(self._intro_mask, 0, 0)
+ self.window._overlayer.queue_draw()
+
+ while bool(self._intro_mask) and not gtk.main_iteration():
+ pass
+
+ return gtkutils.raddr_lookup(self._prelight)
+
+ def _end_introspect(self, widget, evt):
+ if evt.type == gtk.gdk.BUTTON_PRESS and self._prelight:
+ self._intro_mask.catch_events = False
+ self._intro_mask.disconnect(self._intro_handle)
+ self._intro_handle = None
+ self._intro_mask.disconnect(self._select_handle)
+ self._select_handle = None
+ self.window._overlayer.remove(self._intro_mask)
+ self._intro_mask = None
+ # for some reason, gtk may not redraw after this unless told to.
+ self.window.queue_draw()
+
+ def _intro_cb(self, widget, evt):
+ """
+ Callback for capture of widget events, when in introspect mode.
+ """
+ # widget has focus, let's hilight it
+ win = gtk.gdk.display_get_default().get_window_at_pointer()
+ if not win:
+ return
+ click_wdg = win[0].get_user_data()
+ if not click_wdg.is_ancestor(self.window._overlayer):
+ # as popups are not (yet) supported, it would break
+ # badly if we were to play with a widget not in the
+ # hierarchy.
+ return
+ for hole in self._intro_mask.pass_thru:
+ self._intro_mask.mask(hole)
+ self._intro_mask.unmask(click_wdg)
+ self._prelight = click_wdg
+
+ self.window.queue_draw()
+
+ def abort(self):
+ """
+ Ends the selection. The control will return to the select() caller
+ with a return value of None, as selection was aborted.
+ """
+ self._intro_mask.catch_events = False
+ self._intro_mask.disconnect(self._intro_handle)
+ self._intro_handle = None
+ self._intro_mask.disconnect(self._select_handle)
+ self._select_handle = None
+ self.window._overlayer.remove(self._intro_mask)
+ self._intro_mask = None
+ self._prelight = None
+
+###########################################################################
+# Property Widget Classes
+###########################################################################
+class PropWidget(object):
+ """
+ Base Class for property editing widgets.
+ Subclasses should implement create_widget, run_dialog and refresh_widget
+ """
+ def __init__(self, parent, edit_object, prop_name, changed_callback=None):
+ """Constructor
+ @param parent parent widget
+ @param edit_object TPropContainer being edited
+ @param prop_name name of property being edited
+ @param changed_callback optional callable to call on value changes
+ """
+ self._parent = parent
+ self._edit_object = edit_object
+ self._propname = prop_name
+ self._widget = None
+ self._changed_cb = changed_callback
+
+ ############################################################
+ # Begin Properties
+ ############################################################
+ def set_objprop(self, value):
+ """Setter for object property value"""
+ setattr(self._edit_object, self._propname, value)
+ def get_objprop(self):
+ """Getter for object property value"""
+ return getattr(self._edit_object, self._propname)
+ def _get_widget(self):
+ """Getter for widget. Creates the widget if necessary"""
+ if self._widget is None:
+ self._widget = self.create_widget(self.obj_prop)
+ return self._widget
+ def _get_prop_class(self):
+ """Getter for property type"""
+ return getattr(type(self._edit_object), self._propname)
+ def _get_parent(self):
+ """Getter for parent"""
+ return self._parent
+
+ obj_prop = property(get_objprop, set_objprop)
+ widget = property(_get_widget)
+ prop_class = property(_get_prop_class)
+ parent = property(_get_parent)
+
+ ############################################################
+ # End Properties
+ ############################################################
+
+ def notify(self):
+ """Notify a calling object that the property was changed"""
+ if self._changed_cb:
+ self._changed_cb()
+
+ ############################################################
+ # Public Interface -- Redefine those function in subclasses
+ ############################################################
+ def create_widget(self, init_value=None):
+ """
+ Create the Edit Widget for a property
+ @param init_value initial value
+ @return gtk.Widget
+ """
+ widget = gtk.Entry()
+ widget.set_text(str(init_value or ""))
+ return widget
+
+ @classmethod
+ def run_dialog(cls, parent, obj_prop, propname):
+ """
+ Class Method.
+ Prompts the user for changing an object's property
+ @param parent widget
+ @param obj_prop TPropContainer to edit
+ @param propname name of property to edit
+ """
+ raise NotImplementedError()
+
+ def refresh_widget(self):
+ """
+ Force the widget to update it's value in case the property has changed
+ """
+ pass
+
+class StringPropWidget(PropWidget):
+ """
+ Allows editing a str property
+ """
+ @classmethod
+ def _extract_value(cls, widget):
+ """
+ Class Method
+ extracts the value from the widget
+ """
+ buf = widget.get_buffer()
+ return cls._from_text(
+ buf.get_text(buf.get_start_iter(), buf.get_end_iter())
+ )
+
+ @classmethod
+ def _from_text(cls, text):
+ """
+ Class Method
+ transforms the text value into the correct type if required
+ """
+ return text
+
+ def _text_changed(self, widget, evt):
+ """callback for text change event in the edit box"""
+ self.obj_prop = self._extract_value(widget)
+ self.notify()
+
+ def create_widget(self, init_value=None):
+ """
+ Create the Edit Widget for a property
+ @param init_value initial value
+ @return gtk.Widget
+ """
+ propwdg = gtk.TextView()
+ propwdg.get_buffer().set_text(init_value or "")
+ propwdg.connect_after("focus-out-event", \
+ self._text_changed)
+
+ return propwdg
+
+ def refresh_widget(self):
+ """
+ Force the widget to update it's value in case the property has changed
+ """
+ self.widget.get_buffer().set_text(str(self.obj_prop)) #unicode() ?
+
+ @classmethod
+ def run_dialog(cls, parent, obj_prop, propname):
+ """
+ Class Method.
+ Prompts the user for changing an object's property
+ @param parent widget
+ @param obj_prop TPropContainer to edit
+ @param propname name of property to edit
+ """
+ dlg = TextInputDialog(parent,
+ text="Mandatory property",
+ field=propname)
+ setattr(obj_prop, propname, cls._from_text(dlg.pop()))
+
+class IntPropWidget(StringPropWidget):
+ """
+ Allows editing an int property with boundaries
+ """
+ @classmethod
+ def _extract_value(cls, widget):
+ """
+ Class Method
+ extracts the value from the widget
+ """
+ return widget.get_value_as_int()
+
+ @classmethod
+ def _from_text(cls, text):
+ """
+ Class Method
+ transforms the text value into the correct type if required
+ """
+ return int(text)
+
+ def create_widget(self, init_value=None):
+ """
+ Create the Edit Widget for a property
+ @param init_value initial value
+ @return gtk.Widget
+ """
+ prop = self.prop_class
+ adjustment = gtk.Adjustment(value=self.obj_prop,
+ lower=prop.lower_limit.limit,
+ upper=prop.upper_limit.limit,
+ step_incr=1)
+ propwdg = gtk.SpinButton(adjustment=adjustment)
+ propwdg.connect_after("focus-out-event", \
+ self._text_changed)
+
+class FloatPropWidget(StringPropWidget):
+ """Allows editing a float property"""
+ @classmethod
+ def _from_text(cls, text):
+ """
+ Class Method
+ transforms the text value into the correct type if required
+ """
+ return float(text)
+
+class UAMPropWidget(PropWidget):
+ """Allows editing an UAM property with a widget chooser"""
+ def _show_uam_chooser(self, widget):
+ """show the UAM chooser"""
+ selector = WidgetSelector(self.parent)
+ self.obj_prop = selector.select()
+ self.notify()
+
+ def create_widget(self, init_value=None):
+ """
+ Create the Edit Widget for a property
+ @param init_value initial value
+ @return gtk.Widget
+ """
+ propwdg = gtk.Button(self.obj_prop)
+ propwdg.connect_after("clicked", self._show_uam_chooser)
+ return propwdg
+
+ def refresh_widget(self):
+ """
+ Force the widget to update it's value in case the property has changed
+ """
+ self.widget.set_label(self.obj_prop)
+
+ @classmethod
+ def run_dialog(cls, parent, obj_prop, propname):
+ """
+ Class Method.
+ Prompts the user for changing an object's property
+ @param parent widget
+ @param obj_prop TPropContainer to edit
+ @param propname name of property to edit
+ """
+ selector = WidgetSelector(parent)
+ value = selector.select()
+ setattr(obj_prop, propname, selector.select())
+
+class EventTypePropWidget(PropWidget):
+ """Allows editing an EventType property"""
+ def refresh_widget(self):
+ """
+ Force the widget to update it's value in case the property has changed
+ """
+ self.widget.set_text(str(self.obj_prop))
+
+ @classmethod
+ def run_dialog(cls, parent, obj_prop, propname):
+ """
+ Class Method.
+ Prompts the user for changing an object's property
+ @param parent widget
+ @param obj_prop TPropContainer to edit
+ @param propname name of property to edit
+ """
+ try:
+ dlg = SignalInputDialog(parent,
+ text="Mandatory property",
+ field=propname,
+ addr=obj_prop.object_id)
+ setattr(obj_prop, propname, dlg.pop())
+ except AttributeError:
+ return
+
+class IntArrayPropWidget(PropWidget):
+ """Allows editing an array of ints property"""
+ def _item_changed(self, widget, evt, idx):
+ """callback for text changed in one of the entries"""
+ try:
+ #Save props as tuples so that they can be hashed
+ attr = list(self.obj_prop)
+ attr[idx] = int(widget.get_text())
+ self.obj_prop = tuple(attr)
+ except ValueError:
+ widget.set_text(str(self.obj_prop[idx]))
+ self.notify()
+
+ def create_widget(self, init_value=None):
+ """
+ Create the Edit Widget for a property
+ @param init_value initial value
+ @return gtk.Widget
+ """
+ value = self.obj_prop
+ propwdg = gtk.HBox()
+ for i in xrange(len(value)):
+ entry = gtk.Entry()
+ entry.set_text(str(value[i]))
+ propwdg.pack_start(entry)
+ entry.connect_after("focus-out-event", \
+ self._item_changed, i)
+ return propwdg
+
+ def refresh_widget(self):
+ """
+ Force the widget to update it's value in case the property has changed
+ """
+ children = self.widget.get_children()
+ value = self.obj_prop
+ for i in xrange(len(value)):
+ children[i].set_text(str(value[i]))
+
+ @classmethod
+ def run_dialog(cls, parent, obj_prop, propname):
+ """
+ Class Method.
+ Prompts the user for changing an object's property
+ @param parent widget
+ @param obj_prop TPropContainer to edit
+ @param propname name of property to edit
+ """
+ pass
diff --git a/tutorius/store.py b/tutorius/store.py
index dc52c82..565295d 100644
--- a/tutorius/store.py
+++ b/tutorius/store.py
@@ -343,10 +343,10 @@ class StoreProxy(object):
xml_response = minidom.parseString(response['body'])
- id_node = xml_response.getElementsByTagName("id")[0]
+ id_node = xml_response.getElementsByTagName("id")[0].firstChild
+
+ id = id_node.nodeValue
- id = id_node.getAttribute('value')
-
return id
def unpublish(self, tutorial_store_id):
@@ -423,7 +423,6 @@ class StoreProxyHelper(object):
@param response The XML response from the server
@return True if the response is an error
"""
-
# first look for HTTP errors
http_status = response['headers']['status']
diff --git a/tutorius/vault.py b/tutorius/vault.py
index dc8c434..1c1e33c 100644
--- a/tutorius/vault.py
+++ b/tutorius/vault.py
@@ -56,6 +56,7 @@ INI_GUID_PROPERTY = "guid"
INI_NAME_PROPERTY = "name"
INI_XML_FSM_PROPERTY = "fsm_filename"
INI_VERSION_PROPERTY = 'version'
+INI_CATEGORY_PROPERTY = 'category'
INI_FILENAME = "meta.ini"
TUTORIAL_FILENAME = "tutorial.xml"
RESOURCES_FOLDER = 'resources'
@@ -196,7 +197,6 @@ class Vault(object):
@returns a list of Tutorial meta-data (TutorialID, Description,
Rating, Category, PublishState, etc...)
- TODO : Search for tuto caracterised by the entry : OR between [], and between each
The returned dictionnary is of this format : key = property name, value = property value
The dictionnary also contain one dictionnary element whose key is the string 'activities'
@@ -204,25 +204,25 @@ class Vault(object):
value = related activity version number
"""
- # Temp solution for returning all tutorials metadata
-
tutorial_list = []
tuto_guid_list = []
ini_file = SafeConfigParser()
- if keyword == [] and relatedActivityNames == [] and category == []:
- # get all tutorials tuples (name:guid) for all activities and version
- tuto_dict = Vault.list_available_tutorials()
- for id in tuto_dict.keys():
- tuto_guid_list.append(id)
+ # get all tutorials tuples (name:guid) for all activities and version
+ tuto_dict = Vault.list_available_tutorials()
+ for id in tuto_dict.keys():
+ tuto_guid_list.append(id)
- # Find .ini metadata files with the guid list
+ # Find .ini metadata files with the guid list
# Get the guid from the tuto tuples
for guid in tuto_guid_list:
+ addition_flag = True
# Create a dictionnary containing the metadata and also
- # another dictionnary containing the tutorial Related Acttivities,
+ # another dictionnary containing the tutorial Related Activities,
# and add it to a list
+ ini_file = SafeConfigParser()
+
# Create a TutorialBundler object from the guid
bundler = TutorialBundler(guid)
# Find the .ini file path for this guid
@@ -233,6 +233,7 @@ class Vault(object):
metadata_dictionnary = {}
related_act_dictionnary = {}
metadata_list = ini_file.options(INI_METADATA_SECTION)
+
for metadata_name in metadata_list:
# Create a dictionnary of tutorial metadata
metadata_dictionnary[metadata_name] = ini_file.get(INI_METADATA_SECTION, metadata_name)
@@ -245,8 +246,43 @@ class Vault(object):
# Add Related Activities dictionnary to metadata dictionnary
metadata_dictionnary['activities'] = related_act_dictionnary
- # Add this dictionnary to tutorial list
- tutorial_list.append(metadata_dictionnary)
+ # Filter tutorials for keyword (full or partial)
+ if keyword != []:
+ addition_flag = False
+ # Check if at least one keyword of the list is present
+ for key in keyword:
+ if key != None:
+ for value in metadata_dictionnary.values():
+ if isinstance(value, str):
+ if value.lower().count(key.lower()) > 0:
+ addition_flag = True
+ # Check one layer of depth in the metadata to find the keyword
+ # (for exemple, related activites are a dictionnary stocked
+ # in a value of the main dictionnary)
+ elif isinstance(value, dict):
+ for inner_key, inner_value in value.items():
+ if isinstance(inner_value, str) and isinstance(inner_key, str) and (inner_value.lower().count(key.lower()) > 0 or inner_key.count(key.lower()) > 0):
+ addition_flag = True
+
+ # Filter tutorials for related activities
+ if relatedActivityNames != []:
+ addition_flag = False
+ # Check if at least one element of the list is present
+ for related in relatedActivityNames:
+ if related != None and related.lower() in related_act_dictionnary.keys():
+ addition_flag = True
+
+ # Filter tutorials for categories
+ if category != []:
+ addition_flag = False
+ # Check if at least one element of the list is present
+ for cat in category:
+ if cat != None and metadata_dictionnary.has_key(INI_CATEGORY_PROPERTY) and metadata_dictionnary[INI_CATEGORY_PROPERTY].lower() == cat.lower():
+ addition_flag = True
+
+ # Add this dictionnary to tutorial list if it has not been filtered
+ if addition_flag == True:
+ tutorial_list.append(metadata_dictionnary)
# Return tutorial list
return tutorial_list
@@ -319,9 +355,6 @@ class Vault(object):
else:
# Error, tutorial already exist
return False
-
- # TODO : wait for Ben input on how to unpublish tuto before coding this function
- # For now, no unpublishing will occur.
@staticmethod