From c14688d67a82b7ec7746beda90da915c98600a3d Mon Sep 17 00:00:00 2001 From: erick Date: Sat, 05 Dec 2009 21:03:59 +0000 Subject: Merge branch 'frame_integration' into revamped_dragndrop Conflicts: tutorius/actions.py --- (limited to 'tutorius/TProbe.py') diff --git a/tutorius/TProbe.py b/tutorius/TProbe.py index b384e6c..acba26f 100644 --- a/tutorius/TProbe.py +++ b/tutorius/TProbe.py @@ -20,16 +20,20 @@ import os import gobject -import dbus import dbus.service import cPickle as pickle +from functools import partial + +from jarabe.model.shell import get_model +from sugar.bundle.activitybundle import ActivityBundle from . import addon +from . import properties from .services import ObjectStore -from .properties import TPropContainer from .dbustools import save_args, ignore, logError +from .gtkutils import find_widget, raddr_lookup import copy """ @@ -129,11 +133,12 @@ class TProbe(dbus.service.Object): # ------------------ Action handling -------------------------------------- @dbus.service.method("org.tutorius.ProbeInterface", - in_signature='s', out_signature='s') - def install(self, pickled_action): + in_signature='sb', out_signature='s') + def install(self, pickled_action, is_editing): """ Install an action on the Activity @param pickled_action string pickled action + @param is_editing whether this action comes from the editor @return string address of installed action """ loaded_action = pickle.loads(str(pickled_action)) @@ -146,17 +151,22 @@ class TProbe(dbus.service.Object): if action._props: action._props.update(loaded_action._props) - action.do(activity=self._activity) - + if not is_editing: + action.do(activity=self._activity, probe=self) + else: + action.enter_editmode() + action.set_notification_cb(partial(self.update_action, address)) + return address @dbus.service.method("org.tutorius.ProbeInterface", - in_signature='ss', out_signature='') - def update(self, address, action_props): + in_signature='ssb', out_signature='') + def update(self, address, action_props, is_editing): """ Update an already registered action @param address string address returned by install() @param action_props pickled action properties + @param is_editing whether this action comes from the editor @return None """ action = self._installedActions[address] @@ -164,26 +174,47 @@ class TProbe(dbus.service.Object): if action._props: props = pickle.loads(str(action_props)) action._props.update(props) - action.undo() - action.do() + if not is_editing: + action.undo() + action.do() + else: + action.exit_editmode() + action.enter_editmode() @dbus.service.method("org.tutorius.ProbeInterface", - in_signature='s', out_signature='') - def uninstall(self, address): + in_signature='sb', out_signature='') + def uninstall(self, address, is_editing): """ Uninstall an action @param address string address returned by install() + @param is_editing whether this action comes from the editor @return None """ if self._installedActions.has_key(address): action = self._installedActions[address] - action.undo() + if not is_editing: + action.undo() + else: + action.exit_editmode() self._installedActions.pop(address) # ------------------ Event handling --------------------------------------- @dbus.service.method("org.tutorius.ProbeInterface", in_signature='s', out_signature='s') + def create_event(self, addon_name): + # avoid recursive imports + event = addon.create(addon_name) + addonname = type(event).__name__ + meta = addon.get_addon_meta(addonname) + for propname in meta['mandatory_props']: + prop = getattr(type(event), propname) + prop.widget_class.run_dialog(self._activity, event, propname) + + return pickle.dumps(event) + + @dbus.service.method("org.tutorius.ProbeInterface", + in_signature='s', out_signature='s') def subscribe(self, pickled_event): """ Subscribe to an Event @@ -201,7 +232,7 @@ class TProbe(dbus.service.Object): def callback(*args): self.notify(eventfilter) - eventfilter.install_handlers(callback, activity=self._activity) + eventfilter.install_handlers(callback, activity=self._activity, probe=self) name = self._generate_event_reference(eventfilter) self._subscribedEvents[name] = eventfilter @@ -216,7 +247,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() @@ -237,6 +267,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 @@ -263,6 +308,108 @@ class TProbe(dbus.service.Object): return name + str(suffix) + # ------------------ Helper functions specific to a component -------------- + def find_widget(self, base, path, ignore_errors=True): + """ + Finds a widget from a base object. Symmetric with retrieve_path + + @param base the parent widget + @param path fqdn-style target object name + + @return widget found + """ + return find_widget(base, path, ignore_errors) + + def retrieve_path(self, widget): + """ + Retrieve the path to access a specific widget. + Symmetric with find_widget. + + @param widget the widget to find a path for + + @return path to the widget + """ + return raddr_lookup(widget) + +class FrameProbe(TProbe): + """ + Identical to the base probe except that helper functions are redefined + to handle the four windows that are part of the Frame. + """ + # ------------------ Helper functions specific to a component -------------- + def find_widget(self, base, path, ignore_errors=True): + """ + Finds a widget from a base object. Symmetric with retrieve_path + + format for the path for the frame should be: + + frame:/// + where panel: top | bottom | left | right + path: number[.number]* + + @param base the parent widget + @param path fqdn-style target object name + + @return widget found + """ + protocol, p = path.split("://") + assert protocol == "frame" + + window, object_id = p.split("/") + if window == "top": + return find_widget(base._top_panel, object_id, ignore_errors) + elif window == "bottom": + return find_widget(base._bottom_panel, object_id, ignore_errors) + elif window == "left": + return find_widget(base._left_panel, object_id, ignore_errors) + elif window == "right": + return find_widget(base._right_panel, object_id, ignore_errors) + else: + raise RuntimeWarning("Invalid frame panel: '%s'"%window) + + return find_widget(base, path, ignore_errors) + + def retrieve_path(self, widget): + """ + Retrieve the path to access a specific widget. + Symmetric with find_widget. + + format for the path for the frame should be: + + frame:/// + where panel: top | bottom | left | right + path: number[.number]* + + @param widget the widget to find a path for + + @return path to the widget + """ + name = [] + child = widget + parent = widget.parent + while parent: + name.append(str(parent.get_children().index(child))) + child = parent + parent = child.parent + + name.append("0") # root object itself + name.reverse() + + window = "" + if parent._position == gtk.POS_TOP: + window = "top" + elif parent._position == gtk.POS_BOTTOM: + window = "bottom" + elif parent._position == gtk.POS_LEFT: + window = "left" + elif parent._position == gtk.POS_RIGHT: + window = "right" + else: + raise RuntimeWarning("Invalid root panel in frame: %s"%str(parent)) + + return "frame://"+window+"/"+(".".join(name)) + + class ProbeProxy: """ ProbeProxy is a Proxy class for connecting to a remote TProbe. @@ -284,6 +431,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 @@ -292,6 +440,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)) @@ -312,33 +471,47 @@ 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): + 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), - reply_handler=save_args(self.__update_action, action, action_installed_cb), - error_handler=save_args(error_cb, action)) + self._probe.install(pickle.dumps(action), + is_editing, + 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): + def update(self, action_address, newaction, is_editing=False): """ Update an already installed action's properties and run it again @param action_address The address of the action to update. This is provided by the install callback method. @param newaction Action to update it with @param block Force a synchroneous dbus call if True + @param is_editing whether this action comes from the editor @return None """ #TODO review how to make this work well @@ -346,19 +519,20 @@ class ProbeProxy: raise RuntimeWarning("Action not installed") #TODO Check error handling return self._probe.update(action_address, pickle.dumps(newaction._props), + is_editing, reply_handler=ignore, error_handler=logError) - def uninstall(self, action_address): + def uninstall(self, action_address, is_editing): """ Uninstall an installed action @param action_address The address of the action to uninstall. This address was given on action installation - @param block Force a synchroneous dbus call if True + @param is_editing whether this action comes from the editor """ if action_address in self._actions: self._actions.pop(action_address, None) - self._probe.uninstall(action_address, reply_handler=ignore, error_handler=logError) + self._probe.uninstall(action_address, is_editing, reply_handler=ignore, error_handler=logError) def __update_event(self, event, callback, event_subscribed_cb, address): LOGGER.debug("ProbeProxy :: Registered event %s with address %s", str(hash(event)), str(address)) @@ -409,7 +583,18 @@ class ProbeProxy: else: LOGGER.debug("ProbeProxy :: unsubsribe address %s inconsistency : not registered", address) + def create_event(self, addon_name): + """ + Create an event on the app side and request the user to fill the + properties before returning it. + + @param addon_name: the add-on name of the event + @returns: an eventfilter instance + """ + return pickle.loads(str(self._probe.create_event(addon_name))) + def subscribe(self, event, notification_cb, event_subscribed_cb, error_cb): + """ Register an event listener @param event Event to listen for @@ -419,8 +604,6 @@ class ProbeProxy: @return address identifier used for unsubscribing """ LOGGER.debug("ProbeProxy :: Registering event %s", str(hash(event))) - #if not block: - # raise RuntimeError("This function does not allow non-blocking mode yet") # TODO elavoie 2009-07-25 When we will allow for patterns both # for event types and sources, we will need to revise the lookup @@ -429,11 +612,10 @@ class ProbeProxy: reply_handler=save_args(self.__update_event, event, notification_cb, event_subscribed_cb), error_handler=save_args(error_cb, event)) - def unsubscribe(self, address, block=True): + def unsubscribe(self, address): """ Unregister an event listener @param address identifier given by subscribe() - @param block Force a synchroneous dbus call if True @return None """ LOGGER.debug("ProbeProxy :: Unregister adress %s issued", str(address)) @@ -445,18 +627,19 @@ class ProbeProxy: else: LOGGER.debug("ProbeProxy :: unsubscribe address %s failed : not registered", address) - def detach(self, block=False): + def detach(self): """ Detach the ProbeProxy from it's TProbe. All installed actions and subscribed events should be removed. """ for action_addr in self._actions.keys(): - self.uninstall(action_addr) + # 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(): self.unsubscribe(address) - class ProbeManager(object): """ The ProbeManager provides multiplexing across multiple activity ProbeProxies @@ -482,49 +665,78 @@ class ProbeManager(object): def setCurrentActivity(self, activity_id): if not activity_id in self._probes: - raise RuntimeError("Activity not attached") + raise RuntimeError("Activity not attached, id : %s"%activity_id) self._current_activity = activity_id def getCurrentActivity(self): + # TODO : Insert the correct call to remember the current activity, + # taking the views and frame into account + current_act = get_model().get_active_activity() + current_act_bundle = ActivityBundle(current_act.get_bundle_path()) + current_act_id = current_act_bundle.get_bundle_id() + self._current_activity = current_act_id return self._current_activity currentActivity = property(fget=getCurrentActivity, fset=setCurrentActivity) - def install(self, action, action_installed_cb, error_cb): + 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 @param action_installed_cb The callback to call once the action is installed @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: - return self._first_proxy(self.currentActivity).install(action, action_installed_cb, error_cb) + return self._first_proxy(self.currentActivity).install( + action=action, + is_editing=is_editing, + action_installed_cb=action_installed_cb, + error_cb=error_cb, + editing_cb=editing_cb) else: raise RuntimeWarning("No activity attached") - def update(self, action_address, newaction): + def update(self, action_address, newaction, is_editing=False): """ Update an already installed action's properties and run it again @param action_address Action to update @param newaction Action to update it with @param block Force a synchroneous dbus call if True + @param is_editing whether this action comes from the editor @return None """ if self.currentActivity: - return self._first_proxy(self.currentActivity).update(action_address, newaction) + return self._first_proxy(self.currentActivity).update(action_address, newaction, is_editing) else: raise RuntimeWarning("No activity attached") - def uninstall(self, action_address): + def uninstall(self, action_address, is_editing=False): """ Uninstall an installed action - @param action Action to uninstall + @param action_address Action to uninstall @param block Force a synchroneous dbus call if True + @param is_editing whether this action comes from the editor """ if self.currentActivity: - return self._first_proxy(self.currentActivity).uninstall(action_address) + return self._first_proxy(self.currentActivity).uninstall(action_address, is_editing) + else: + raise RuntimeWarning("No activity attached") + + def create_event(self, addon_name): + """ + Create an event on the app side and request the user to fill the + properties before returning it. + + @param addon_name: the add-on name of the event + @returns: an eventfilter instance + """ + if self.currentActivity: + return self._first_proxy(self.currentActivity).create_event(addon_name) else: raise RuntimeWarning("No activity attached") @@ -601,8 +813,6 @@ class ProbeManager(object): return self._probes[process_name] else: return [] - - def _first_proxy(self, process_name): """ -- cgit v0.9.1