Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--addons/bubblemessage.py2
-rw-r--r--addons/bubblemessagewimg.py2
-rw-r--r--tutorius/TProbe.py60
-rw-r--r--tutorius/actions.py53
-rw-r--r--tutorius/creator.py52
-rw-r--r--tutorius/properties.py14
-rw-r--r--tutorius/translator.py6
7 files changed, 161 insertions, 28 deletions
diff --git a/addons/bubblemessage.py b/addons/bubblemessage.py
index aaf086c..7e91d00 100644
--- a/addons/bubblemessage.py
+++ b/addons/bubblemessage.py
@@ -93,7 +93,7 @@ class BubbleMessage(Action):
self.overlay.put(self._bubble, x, y)
self._bubble.show()
- self._drag = DragWrapper(self._bubble, self.position, True)
+ self._drag = DragWrapper(self._bubble, self.position, update_action_cb=self.update_property, draggable=True)
def exit_editmode(self, *args):
if self._drag.moved:
diff --git a/addons/bubblemessagewimg.py b/addons/bubblemessagewimg.py
index 9c3dfc1..0ad444f 100644
--- a/addons/bubblemessagewimg.py
+++ b/addons/bubblemessagewimg.py
@@ -96,7 +96,7 @@ class BubbleMessageWImg(Action):
self.overlay.put(self._bubble, x, y)
self._bubble.show()
- self._drag = DragWrapper(self._bubble, self.position, True)
+ self._drag = DragWrapper(self._bubble, self.position, update_action_cb=self.update_property, draggable=True)
def exit_editmode(self, *args):
x,y = self._drag.position
diff --git a/tutorius/TProbe.py b/tutorius/TProbe.py
index 8361314..dbadefa 100644
--- a/tutorius/TProbe.py
+++ b/tutorius/TProbe.py
@@ -23,6 +23,7 @@ import gobject
import dbus.service
import cPickle as pickle
+from functools import partial
from . import addon
from . import properties
@@ -148,6 +149,7 @@ class TProbe(dbus.service.Object):
action.do(activity=self._activity)
else:
action.enter_editmode()
+ action.set_notification_cb(partial(self.update_action, address))
return address
@@ -239,7 +241,6 @@ class TProbe(dbus.service.Object):
@param address string adress returned by subscribe()
@return None
"""
-
if self._subscribedEvents.has_key(address):
eventfilter = self._subscribedEvents[address]
eventfilter.remove_handlers()
@@ -260,6 +261,21 @@ class TProbe(dbus.service.Object):
else:
raise RuntimeWarning("Attempted to raise an unregistered event")
+ @dbus.service.signal("org.tutorius.ProbeInterface")
+ def addonUpdated(self, addon_address, pickled_diff_dict):
+ # Don't do any added processing, the signal will be sent
+ # when the method exits
+ pass
+
+ def update_action(self, addon_address, diff_dict):
+ LOGGER.debug("TProbe :: Trying to update action %s with new property dict %s"%(addon_address, str(diff_dict)))
+ # Check that this action is installed
+ if addon_address in self._installedActions.keys():
+ LOGGER.debug("TProbe :: Updating action %s"%(addon_address))
+ self.addonUpdated(addon_address, pickle.dumps(diff_dict))
+ else:
+ raise RuntimeWarning("Attempted to updated an action that wasn't installed")
+
# Return a unique name for this action
def _generate_action_reference(self, action):
# TODO elavoie 2009-07-25 Should return a universal address
@@ -307,6 +323,7 @@ class ProbeProxy:
self._probe = dbus.Interface(self._object, "org.tutorius.ProbeInterface")
self._actions = {}
+ self._edition_callbacks = {}
# We keep those two data structures to be able to have multiple callbacks
# for the same event and be able to remove them independently
# _subscribedEvents holds a list of callback addresses's for each event
@@ -315,6 +332,17 @@ class ProbeProxy:
self._registeredCallbacks = {}
self._object.connect_to_signal("eventOccured", self._handle_signal, dbus_interface="org.tutorius.ProbeInterface")
+ self._object.connect_to_signal("addonUpdated", self._handle_update_signal, dbus_interface="org.tutorius.ProbeInterface")
+
+ def _handle_update_signal(self, addon_address, pickled_diff_dict):
+ address = str(addon_address)
+ diff_dict = pickle.loads(str(pickled_diff_dict))
+ LOGGER.debug("ProbeProxy :: Received update property for action %s"%(address))
+ # Launch the callback to warn the upper layers of a modification of the addon
+ # from a widget inside the activity
+ if self._edition_callbacks.has_key(address):
+ LOGGER.debug("ProbeProxy :: Executing update callback...")
+ self._edition_callbacks[address](address, diff_dict)
def _handle_signal(self, pickled_event):
event = pickle.loads(str(pickled_event))
@@ -335,26 +363,37 @@ class ProbeProxy:
except:
return False
- def __update_action(self, action, callback, address):
+ def __update_action(self, action, callback, editing_cb, address):
LOGGER.debug("ProbeProxy :: Updating action %s with address %s", str(action), str(address))
+ address = str(address)
+ # Store the action
self._actions[address] = action
+ # Store the edition callback
+ if editing_cb:
+ self._edition_callbacks[address] = editing_cb
+ # Propagate the action installed callback upwards in the stack
callback(address)
def __clear_action(self, address):
+ # Remove the action installed at this address
self._actions.pop(address, None)
+ # Remove the edition callback
+ self._edition_callbacks.pop(address, None)
- def install(self, action, action_installed_cb, error_cb, is_editing=False):
+ def install(self, action, action_installed_cb, error_cb, is_editing=False, editing_cb=None):
"""
Install an action on the TProbe's activity
@param action Action to install
@param action_installed_cb The callback function to call once the action is installed
@param error_cb The callback function to call when an error happens
@param is_editing whether this action comes from the editor
+ @param editing_cb The function to execute when the action is updated
+ (this is only done in edition mode)
@return None
"""
self._probe.install(pickle.dumps(action), is_editing,
- reply_handler=save_args(self.__update_action, action, action_installed_cb),
- error_handler=save_args(error_cb, action))
+ reply_handler=save_args(self.__update_action, action, action_installed_cb, editing_cb),
+ error_handler=save_args(error_cb, action))
def update(self, action_address, newaction, is_editing=False):
"""
@@ -487,6 +526,8 @@ class ProbeProxy:
subscribed events should be removed.
"""
for action_addr in self._actions.keys():
+ # TODO : Make sure there is a way for each action to be properly
+ # uninstalled according to its right edition mode
self.uninstall(action_addr, True)
for address in self._subscribedEvents.keys():
@@ -529,7 +570,7 @@ class ProbeManager(object):
currentActivity = property(fget=getCurrentActivity, fset=setCurrentActivity)
- def install(self, action, action_installed_cb, error_cb, is_editing=False):
+ def install(self, action, action_installed_cb, error_cb, is_editing=False, editing_cb=None):
"""
Install an action on the current activity
@param action Action to install
@@ -537,6 +578,8 @@ class ProbeManager(object):
@param error_cb The callback that will be called if there is an error during installation
@param block Force a synchroneous dbus call if True
@param is_editing whether this action comes from the editor
+ @param editing_cb The function to execute when propagating changes on
+ this action (only used when is_editing is true)
@return None
"""
if self.currentActivity:
@@ -544,7 +587,8 @@ class ProbeManager(object):
action=action,
is_editing=is_editing,
action_installed_cb=action_installed_cb,
- error_cb=error_cb)
+ error_cb=error_cb,
+ editing_cb=editing_cb)
else:
raise RuntimeWarning("No activity attached")
@@ -660,8 +704,6 @@ class ProbeManager(object):
return self._probes[process_name]
else:
return []
-
-
def _first_proxy(self, process_name):
"""
diff --git a/tutorius/actions.py b/tutorius/actions.py
index 75c9c9b..a32138f 100644
--- a/tutorius/actions.py
+++ b/tutorius/actions.py
@@ -26,14 +26,21 @@ from . import addon
from .services import ObjectStore
from .properties import *
+import pickle
+
+import logging
+
+LOGGER = logging.getLogger("actions")
+
class DragWrapper(object):
"""Wrapper to allow gtk widgets to be dragged around"""
- def __init__(self, widget, position, draggable=False):
+ def __init__(self, widget, position, update_action_cb, draggable=False):
"""
Creates a wrapper to allow gtk widgets to be mouse dragged, if the
parent container supports the move() method, like a gtk.Layout.
@param widget the widget to enhance with drag capability
@param position the widget's position. Will translate the widget if needed
+ @param update_action_cb The callback to trigger
@param draggable wether to enable the drag functionality now
"""
self._widget = widget
@@ -45,6 +52,7 @@ class DragWrapper(object):
self.position = position # position of the widget
self.moved = False
+ self.update_action_cb = update_action_cb
self.draggable = draggable
def _pressed_cb(self, widget, evt):
@@ -79,10 +87,13 @@ class DragWrapper(object):
self._eventbox.grab_remove()
self._dragging = False
+ LOGGER.debug("DragWrapper :: Sending update notification...")
+ self.update_action_cb('position', self.position)
+
def _drag_end(self, *args):
"""Callback for end of drag (stolen focus)."""
self._dragging = False
-
+
def set_draggable(self, value):
"""Setter for the draggable property"""
if bool(value) ^ bool(self._drag_on):
@@ -139,6 +150,12 @@ class Action(TPropContainer):
TPropContainer.__init__(self)
self.position = (0,0)
self._drag = None
+ # The differences dictionary. This is a structure that holds all the
+ # modifications that were made to the properties since the action
+ # was last installed or the last moment the notification was executed.
+ # Every property change will be logged inside it and it will be sent
+ # to the creator to update its action edition dialog.
+ self._property_update_cb = None
def do(self, **kwargs):
"""
@@ -152,7 +169,35 @@ class Action(TPropContainer):
"""
pass #Should raise NotImplemented?
- def enter_editmode(self, **kwargs):
+
+ def set_notification_cb(self, notif_cb):
+ LOGGER.debug("Action :: Setting notification callback for creator...")
+ self._property_update_cb = notif_cb
+
+ def update_property(self, name, value):
+ """
+ Callback used in the wrapper to send a new value to an action.
+ """
+ LOGGER.debug("Action :: update_property on %s with value '%s'"%(name, str(value)))
+ #property = getattr(self, name)
+ #property = value
+
+ #self._props[name] = value
+ self.__setattr__(name, value)
+
+ # Send the notification to the creator
+ self.notify()
+
+ def notify(self):
+ LOGGER.debug("Action :: Notifying creator with new values in dict : %s"%(str(self._diff_dict)))
+ # If a notification callback was registered
+ if self._property_update_cb:
+ # Propagate it
+ self._property_update_cb(self._diff_dict)
+ # Empty the diff dict as we just synchronized with the creator
+ self._diff_dict.clear()
+
+ def enter_editmode(self, *args, **kwargs):
"""
Enters edit mode. The action should display itself in some way,
without affecting the currently running application. The default is
@@ -171,7 +216,7 @@ class Action(TPropContainer):
ObjectStore().activity._overlayer.put(self.__edit_img, x, y)
self.__edit_img.show_all()
- self._drag = DragWrapper(self.__edit_img, self.position, True)
+ self._drag = DragWrapper(self.__edit_img, self.position, update_action_cb=self.update_property, draggable=True)
def exit_editmode(self, **kwargs):
x, y = self._drag.position
diff --git a/tutorius/creator.py b/tutorius/creator.py
index 04c96fd..4ac2033 100644
--- a/tutorius/creator.py
+++ b/tutorius/creator.py
@@ -25,6 +25,8 @@ import gtk.glade
import gobject
from gettext import gettext as T
+import pickle
+
import uuid
import os
from sugar.graphics import icon, style
@@ -246,7 +248,8 @@ class Creator(Object):
self._probe_mgr.install(action,
action_installed_cb=return_cb,
error_cb=self._dbus_exception,
- is_editing=True)
+ is_editing=True,
+ editing_cb=self.update_addon_property)
if state_actions:
# I'm really lazy right now and to keep things simple I simply
@@ -262,12 +265,14 @@ class Creator(Object):
def _add_action_cb(self, widget, path):
"""Callback for the action creation toolbar tool"""
action_type = self._propedit.actions_list[path][ToolBox.ICON_NAME]
+ LOGGER.debug("Creator :: Adding an action = %s"%(action_type))
action = addon.create(action_type)
return_cb = partial(self._action_installed_cb, action)
self._probe_mgr.install(action,
action_installed_cb=return_cb,
error_cb=self._dbus_exception,
- is_editing=True)
+ is_editing=True,
+ editing_cb=self.update_addon_property)
self._tutorial.add_action(self._state, action)
self._propedit.action = action
self._overview.win.queue_draw()
@@ -324,8 +329,7 @@ class Creator(Object):
address = action.address
self._probe_mgr.update(address,
action,
- is_editing=True
- )
+ is_editing=True)
def _update_error(self, exception):
pass
@@ -341,7 +345,8 @@ class Creator(Object):
self._probe_mgr.install(action,
action_installed_cb=return_cb,
error_cb=self._dbus_exception,
- is_editing=True)
+ is_editing=True,
+ editing_cb=self.update_addon_property)
self._propedit.action = action
self._overview.win.queue_draw()
@@ -393,10 +398,17 @@ class Creator(Object):
vault.INI_GUID_PROPERTY: self._guid,
vault.INI_NAME_PROPERTY: tutorial_name,
vault.INI_VERSION_PROPERTY: '1',
- 'activities':{os.environ['SUGAR_BUNDLE_NAME']:
- os.environ['SUGAR_BUNDLE_VERSION']
- },
}
+ # FIXME : The environment does not dispose of the appropriate
+ # variables to inform the creator at this point. We will
+ # need to iterate inside all the actions and remember
+ # their sources.
+
+ # FIXME : I insist. This is a hack.
+ related_activities_dict = {}
+ related_activities_dict['calculate'] = '27'
+
+ self._metadata['activities'] = dict(related_activities_dict)
vault.Vault.saveTutorial(self._tutorial, self._metadata)
@@ -413,13 +425,13 @@ class Creator(Object):
action.address = address
self._installed_actions.append(action)
- def _dbus_exception(self, exception):
+ def _dbus_exception(self, event, exception):
"""
This is a callback intented to be use to receive exceptions on remote
DBUS calls.
@param exception: the exception thrown by the remote process
"""
- pass
+ LOGGER.debug("Creator :: Got exception -> %s"%(str(exception)))
@method(BUS_NAME,
in_signature='',
@@ -430,6 +442,26 @@ class Creator(Object):
"""
return self.is_authoring
+ def update_addon_property(self, addon_address, diff_dict):
+ """
+ Updates the properties on an addon.
+
+ @param addon_address The address of the addon that has the property
+ @param diff_dict The updates to apply to the property dict.
+ This is treated as a partial update to the addon's
+ dictionary and contains at least one property value pair
+ @returns True if the property was updated, False otherwise
+ """
+ # Look up the registered addresses inside the installed actions
+ for action in self._installed_actions:
+ # If this is the correct action
+ if action.address == addon_address:
+ # Update its property with the new value
+ action._props.update(diff_dict)
+ # Update the property edition dialog with it
+ self._propedit.action = action
+ return True
+
class ToolBox(object):
"""
Palette window for edition tools, including the actions, states and
diff --git a/tutorius/properties.py b/tutorius/properties.py
index cc76748..85e8aa5 100644
--- a/tutorius/properties.py
+++ b/tutorius/properties.py
@@ -36,6 +36,9 @@ from .propwidgets import PropWidget, \
FloatPropWidget, \
IntArrayPropWidget
+import logging
+LOGGER = logging.getLogger("properties")
+
class TPropContainer(object):
"""
A class containing properties. This does the attribute wrapping between
@@ -62,6 +65,12 @@ class TPropContainer(object):
copy(propinstance.default))
self.__id = hash(uuid.uuid4())
+ # The differences dictionary. This is a structure that holds all the
+ # modifications that were made to the properties since the action
+ # was last installed or the last moment the notification was executed.
+ # Every property change will be logged inside it and it will be sent
+ # to the creator to update its action edition dialog.
+ self._diff_dict = {}
def __getattribute__(self, name):
"""
@@ -96,8 +105,11 @@ class TPropContainer(object):
try:
# We attempt to get the property object with __getattribute__
# to work through inheritance and benefit of the MRO.
- return props.__setitem__(name,
+ real_value = props.__setitem__(name,
object.__getattribute__(self, name).validate(value))
+ LOGGER.debug("Action :: caching %s = %s in diff dict"%(name, str(value)))
+ self._diff_dict[name] = value
+ return real_value
except AttributeError:
return object.__setattr__(self, name, value)
diff --git a/tutorius/translator.py b/tutorius/translator.py
index 4f29078..bd24f8f 100644
--- a/tutorius/translator.py
+++ b/tutorius/translator.py
@@ -177,7 +177,7 @@ class ResourceTranslator(object):
install_error_cb(old_action, exception)
# Decorated functions
- def install(self, action, action_installed_cb, error_cb):
+ def install(self, action, action_installed_cb, error_cb, is_editing=False, editing_cb=None):
# Make a new copy of the action that we want to install,
# because translate() changes the action and we
# don't want to modify the caller's action representation
@@ -187,7 +187,9 @@ class ResourceTranslator(object):
# Send the new action to the probe manager
self._probe_manager.install(new_action, save_args(self.action_installed, action_installed_cb),
- save_args(self.action_install_error, error_cb, new_action))
+ save_args(self.action_install_error, error_cb, new_action),
+ is_editing=is_editing,
+ editing_cb=editing_cb)
def update(self, action_address, newaction):
translated_new_action = copy_module.deepcopy(newaction)