From 5511467f6f7eab041b53563c883f72cd7547402f Mon Sep 17 00:00:00 2001 From: mike Date: Wed, 02 Dec 2009 22:36:46 +0000 Subject: Creator : Updating for working property updates from activity to Shell (HACK alert in saveTutorial!!!) --- 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) -- cgit v0.9.1