From b27257fadb7f198c3072a2480c3711b7b57cd43f Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sat, 6 Jan 2007 16:29:13 -0500 Subject: [PATCH 1/5] Make shell responsible for activity ID generation Enables tracking of activity launch throughout the whole process, so that the shell can be aware of the activity ID from the moment the activity is started by the shell, until the activity becomes active. Previously, the activity itself generated its own ID and told the shell what it was. --- shell/sugar-activity | 3 ++- shell/view/Shell.py | 51 +++++++++++++++++++++++++++++++++---- shell/view/clipboardicon.py | 3 ++- sugar/activity/Activity.py | 12 ++++----- 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/shell/sugar-activity b/shell/sugar-activity index bb6cf30f..464eaf69 100755 --- a/shell/sugar-activity +++ b/shell/sugar-activity @@ -21,6 +21,7 @@ import os from sugar.activity import ActivityFactory from sugar import env +from sugar import util ppath = env.get_profile_path() bus_file = os.path.join(ppath, "session_bus_address") @@ -30,4 +31,4 @@ f.close() os.environ['DBUS_SESSION_BUS_ADDRESS'] = bus_name activity = ActivityFactory.create(sys.argv[1]) -activity.start() +activity.start(util.unique_id()) diff --git a/shell/view/Shell.py b/shell/view/Shell.py index f3f6ca0b..169ee353 100644 --- a/shell/view/Shell.py +++ b/shell/view/Shell.py @@ -68,6 +68,8 @@ class Shell(gobject.GObject): self._frame = Frame(self) self._frame.show_and_hide(3) + self._pservice = PresenceService.get_instance() + #self.start_activity('org.laptop.JournalActivity') def _handle_camera_key(self): @@ -190,13 +192,11 @@ class Shell(gobject.GObject): return self._model def join_activity(self, bundle_id, activity_id): - pservice = PresenceService.get_instance() - activity = self.get_activity(activity_id) if activity: activity.present() else: - activity_ps = pservice.get_activity(activity_id) + activity_ps = self._pservice.get_activity(activity_id) if activity_ps: # Get the service name for this activity, if @@ -218,10 +218,51 @@ class Shell(gobject.GObject): else: logging.error('Cannot start activity.') + def _find_unique_activity_id(self): + # create a new unique activity ID + i = 0 + act_id = None + while i < 10: + act_id = sugar.util.unique_id() + i += 1 + + # check through existing activities + found = False + for xid, act_host in self._hosts.items(): + if act_host.get_id() == act_id: + found = True + break + if found: + act_id = None + continue + + # check through network activities + activities = self._pservice.get_activities() + for act in activities: + if act_id == act.get_id(): + found = True + break + if found: + act_id = None + continue + + return act_id + def start_activity(self, activity_type): logging.debug('Shell.start_activity') - activity = ActivityFactory.create(activity_type) - activity.start() + act_id = self._find_unique_activity_id() + if not act_id: + logging.error("Couldn't find available activity ID.") + return None + + try: + logging.debug("Shell.start_activity will start %s:%s" % (activity_type, act_id)) + activity = ActivityFactory.create(activity_type) + except dbus.DBusException, e: + logging.debug("Couldn't start activity '%s':\n %s" % (activity_type, e)) + return None + + activity.start(act_id) return activity def set_zoom_level(self, level): diff --git a/shell/view/clipboardicon.py b/shell/view/clipboardicon.py index ade37bd4..4e702d39 100644 --- a/shell/view/clipboardicon.py +++ b/shell/view/clipboardicon.py @@ -4,6 +4,7 @@ from sugar.graphics.menuicon import MenuIcon from view.clipboardmenu import ClipboardMenu from sugar.activity import ActivityFactory from sugar.clipboard import clipboardservice +from sugar import util class ClipboardIcon(MenuIcon): @@ -53,7 +54,7 @@ class ClipboardIcon(MenuIcon): if activity_id: activity = ActivityFactory.create(activity_id) - activity.start() + activity.start(util.unique_id()) activity.execute("open_document", [self._object_id]) def _popup_action_cb(self, popup, action): diff --git a/sugar/activity/Activity.py b/sugar/activity/Activity.py index c0023ab3..7926daed 100644 --- a/sugar/activity/Activity.py +++ b/sugar/activity/Activity.py @@ -62,9 +62,9 @@ class ActivityDbusService(dbus.service.Object): self._pservice = PresenceService.get_instance() @dbus.service.method(ACTIVITY_INTERFACE) - def start(self): - """Start the activity.""" - self._activity.start() + def start(self, activity_id): + """Start the activity in unshared mode.""" + self._activity.start(activity_id) @dbus.service.method(ACTIVITY_INTERFACE) def join(self, activity_ps_path): @@ -120,13 +120,13 @@ class Activity(gtk.Window): self._bus = ActivityDbusService(self) - def start(self): + def start(self, activity_id): """Start the activity.""" if self._activity_id != None: logging.warning('The activity has been already started.') return - self._activity_id = sugar.util.unique_id() + self._activity_id = activity_id #ds = datastore.get_instance() #self._journal_object = ds.create('', {}, self._activity_id) @@ -162,9 +162,9 @@ class Activity(gtk.Window): if self._activity_id != None: logging.warning('The activity has been already started.') return + self._activity_id = activity_ps.get_id() self._shared = True - self._activity_id = activity_ps.get_id() # Publish the default service, it's a copy of # one of those we found on the network. From 8cea4c5fc6de37dd47843af014c4730f04f4dc1d Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sat, 6 Jan 2007 19:31:19 -0500 Subject: [PATCH 2/5] Track activities while they launch HomeModel now uses the activity ID to track activities, and creates the HomeActivity object when the activity is launched, not when its window appears. --- shell/model/homeactivity.py | 76 +++++++++++++++++++++++++++++++++---- shell/model/homemodel.py | 73 +++++++++++++++++++++++++++++++---- shell/view/Shell.py | 15 ++++++-- 3 files changed, 146 insertions(+), 18 deletions(-) diff --git a/shell/model/homeactivity.py b/shell/model/homeactivity.py index 2143ef2e..efaa2bb1 100644 --- a/shell/model/homeactivity.py +++ b/shell/model/homeactivity.py @@ -14,23 +14,73 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +import time +import gobject +import logging + from sugar.presence import PresenceService from sugar.activity import Activity from sugar import profile -class HomeActivity: - def __init__(self, registry, window): +class HomeActivity(gobject.GObject): + __gsignals__ = { + 'launch-timeout': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])), + } + + def __init__(self, bundle, activity_id): + gobject.GObject.__init__(self) + self._window = None + self._xid = None + self._service = None + self._id = activity_id + self._type = bundle.get_service_name() + self._icon_name = bundle.get_icon() + + self._launch_time = time.time() + self._launched = False + self._launch_timeout_id = gobject.timeout_add(10000, self._launch_timeout_cb) + + logging.debug("Activity %s (%s) launching..." % (self._id, self._type)) + + def __del__(self): + gobject.source_remove(self._launch_timeout_id) + self._launch_timeout_id = 0 + + def _launch_timeout_cb(self, user_data=None): + logging.debug("Activity %s (%s) launch timed out" % (self._id, self._type)) + self._launch_timeout_id = 0 + self.emit('launch-timeout') + return False + + def set_window(self, window): + """An activity is 'launched' once we get its window.""" + logging.debug("Activity %s (%s) finished launching" % (self._id, self._type)) + self._launched = True + gobject.source_remove(self._launch_timeout_id) + self._launch_timeout_id = 0 + + if self._window or self._xid: + raise RuntimeError("Activity is already launched!") + if not window: + raise ValueError("window must be valid") + self._window = window self._xid = window.get_xid() - self._service = Activity.get_service(window.get_xid()) - self._id = self._service.get_id() - self._type = self._service.get_type() - info = registry.get_bundle(self._type) - self._icon_name = info.get_icon() + # verify id and type details + act_id = self._service.get_id() + if act_id != self._id: + raise RuntimeError("Activity's real ID (%s) didn't match expected (%s)." % (act_id, self._id)) + act_type = self._service.get_type() + if act_type != self._type: + raise RuntimeError("Activity's real type (%s) didn't match expected (%s)." % (act_type, self._type)) def get_title(self): + if not self._launched: + raise RuntimeError("Activity is still launching.") return self._window.get_name() def get_icon_name(self): @@ -47,13 +97,25 @@ class HomeActivity: return self._id def get_xid(self): + if not self._launched: + raise RuntimeError("Activity is still launching.") return self._xid def get_window(self): + if not self._launched: + raise RuntimeError("Activity is still launching.") return self._window def get_type(self): return self._type def get_shared(self): + if not self._launched: + raise RuntimeError("Activity is still launching.") return self._service.get_shared() + + def get_launch_time(self): + return self._launch_time + + def get_launched(self): + return self._launched diff --git a/shell/model/homemodel.py b/shell/model/homemodel.py index f4fd3eec..f7d16c9a 100644 --- a/shell/model/homemodel.py +++ b/shell/model/homemodel.py @@ -20,10 +20,14 @@ import gobject import wnck from model.homeactivity import HomeActivity +from sugar.activity import Activity class HomeModel(gobject.GObject): __gsignals__ = { + 'activity-launched': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), 'activity-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), @@ -68,6 +72,12 @@ class HomeModel(gobject.GObject): if window.get_window_type() == wnck.WINDOW_NORMAL: self._remove_activity(window.get_xid()) + def _get_activity_by_xid(self, xid): + for act in self._activities.values(): + if act.get_xid() == xid: + return act + return None + def _active_window_changed_cb(self, screen): window = screen.get_active_window() if window == None: @@ -77,8 +87,13 @@ class HomeModel(gobject.GObject): return xid = window.get_xid() - if self._activities.has_key(xid): - self._current_activity = self._activities[xid] + act = self._get_activity_by_xid(window.get_xid()) + if act: + if act.get_launched() == True: + self._current_activity = act + else: + self._current_activity = None + logging.error('Actiivty for window %d was not yet launched.' % xid) else: self._current_activity = None logging.error('Model for window %d does not exist.' % xid) @@ -86,13 +101,57 @@ class HomeModel(gobject.GObject): self.emit('active-activity-changed', self._current_activity) def _add_activity(self, window): - activity = HomeActivity(self._bundle_registry, window) - self._activities[window.get_xid()] = activity + act_service = Activity.get_service(window.get_xid()) + act_id = act_service.get_id() + + activity = None + if self._activities.has_key(act_id): + activity = self._activities[act_id] + else: + # activity got lost, took longer to launch than we allow, + # or it was launched by something other than the shell + act_type = act_service.get_type() + bundle = self._bundle_registry.get_bundle(act_type) + if not bundle: + raise RuntimeError("No bundle for activity type '%s'." % act_type) + return + activity = HomeActivity(bundle, act_id) + self._activities[act_id] = activity + + activity.set_window(window) self.emit('activity-added', activity) + def _internal_remove_activity(self, activity): + self.emit('activity-removed', activity) + act_id = activity.get_id() + del self._activities[act_id] + def _remove_activity(self, xid): - if self._activities.has_key(xid): - self.emit('activity-removed', self._activities[xid]) - del self._activities[xid] + activity = self._get_activity_by_xid(window.get_xid()) + if activity: + self._internal_remove_activity(activity) else: logging.error('Model for window %d does not exist.' % xid) + + def _activity_launch_timeout_cb(self, activity): + act_id = activity.get_id() + if not act_id in self._activities.keys(): + return + self._internal_remove_activity(activity) + + def notify_activity_launch(self, activity_id, service_name): + bundle = self._bundle_registry.get_bundle(service_name) + if not bundle: + raise ValueError("Activity service name '%s' was not found in the bundle registry." % service_name) + activity = HomeActivity(bundle, activity_id) + activity.connect('launch-timeout', self._activity_launch_timeout_cb) + self._activities[activity_id] = activity + self.emit('activity-launched', activity) + + def notify_activity_launch_failed(self, activity_id): + if self._activities.has_key(activity_id): + activity = self._activities[activity_id] + logging.debug("Activity %s (%s) launch failed" % (activity_id, activity.get_type())) + self._internal_remove_activity(activity) + else: + logging.error('Model for activity id %s does not exist.' % activity_id) diff --git a/shell/view/Shell.py b/shell/view/Shell.py index 169ee353..9e81cab4 100644 --- a/shell/view/Shell.py +++ b/shell/view/Shell.py @@ -205,13 +205,16 @@ class Shell(gobject.GObject): breg = self._model.get_bundle_registry() bundle = breg.find_by_default_type(bundle_id) if bundle: - serv_name = bundle.get_service_name() + act_type = bundle.get_service_name() + home_model = self._model.get_home() + home_model.notify_activity_launch(activity_id, act_type) try: - activity = ActivityFactory.create(serv_name) + activity = ActivityFactory.create(act_type) except DBusException, e: - logging.error("Couldn't launch activity %s:\n%s" % (serv_name, e)) + logging.error("Couldn't launch activity %s:\n%s" % (act_type, e)) + home_mode.notify_activity_launch_failed(activity_id) else: - logging.debug("Joining activity type %s id %s" % (serv_name, activity_id)) + logging.debug("Joining activity type %s id %s" % (act_type, activity_id)) activity.join(activity_ps.object_path()) else: logging.error("Couldn't find activity for type %s" % bundle_id) @@ -255,11 +258,15 @@ class Shell(gobject.GObject): logging.error("Couldn't find available activity ID.") return None + home_model = self._model.get_home() + home_model.notify_activity_launch(act_id, activity_type) + try: logging.debug("Shell.start_activity will start %s:%s" % (activity_type, act_id)) activity = ActivityFactory.create(activity_type) except dbus.DBusException, e: logging.debug("Couldn't start activity '%s':\n %s" % (activity_type, e)) + home_mode.notify_activity_launch_failed(act_id) return None activity.start(act_id) From fb716ae0466547ea4e6c580b9d55fe015139201f Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sun, 7 Jan 2007 00:04:30 -0500 Subject: [PATCH 3/5] Make activity launching asynchronous The ActivityFactory create() method now returns a handler GObject, which callers may attach signals to to receive success and error signals from the result of the activity launch request. --- shell/model/homemodel.py | 2 +- shell/sugar-activity | 17 ++++++- shell/view/Shell.py | 76 +++++++++++++++++-------------- shell/view/clipboardicon.py | 40 ++++++++++------ sugar/activity/ActivityFactory.py | 53 ++++++++++++++------- 5 files changed, 119 insertions(+), 69 deletions(-) diff --git a/shell/model/homemodel.py b/shell/model/homemodel.py index f7d16c9a..b1b4c8f7 100644 --- a/shell/model/homemodel.py +++ b/shell/model/homemodel.py @@ -127,7 +127,7 @@ class HomeModel(gobject.GObject): del self._activities[act_id] def _remove_activity(self, xid): - activity = self._get_activity_by_xid(window.get_xid()) + activity = self._get_activity_by_xid(xid) if activity: self._internal_remove_activity(activity) else: diff --git a/shell/sugar-activity b/shell/sugar-activity index 464eaf69..43b56dc8 100755 --- a/shell/sugar-activity +++ b/shell/sugar-activity @@ -18,11 +18,19 @@ import sys import os +import gobject from sugar.activity import ActivityFactory from sugar import env from sugar import util +def _success_cb(handler, activity, loop): + activity.start(util.unique_id()) + loop.quit() + +def _error_cb(handler, err, loop): + loop.quit() + ppath = env.get_profile_path() bus_file = os.path.join(ppath, "session_bus_address") f = open(bus_file, "r") @@ -30,5 +38,10 @@ bus_name = f.read() f.close() os.environ['DBUS_SESSION_BUS_ADDRESS'] = bus_name -activity = ActivityFactory.create(sys.argv[1]) -activity.start(util.unique_id()) +loop = gobject.MainLoop() + +handler = ActivityFactory.create(sys.argv[1]) +handler.connect('success', _success_cb, loop) +handler.connect('error', _error_cb, loop) + +loop.run() diff --git a/shell/view/Shell.py b/shell/view/Shell.py index 9e81cab4..03a3badc 100644 --- a/shell/view/Shell.py +++ b/shell/view/Shell.py @@ -191,35 +191,41 @@ class Shell(gobject.GObject): def get_model(self): return self._model + def _join_success_cb(self, handler, activity, activity_ps, activity_id, activity_type): + logging.debug("Joining activity %s (%s)" % (activity_id, activity_type)) + activity.join(activity_ps.object_path()) + + def _join_error_cb(self, handler, err, home_model, activity_id, activity_type): + logging.error("Couldn't launch activity %s (%s):\n%s" % (activity_id, activity_type, err)) + home_mode.notify_activity_launch_failed(activity_id) + def join_activity(self, bundle_id, activity_id): activity = self.get_activity(activity_id) if activity: activity.present() - else: - activity_ps = self._pservice.get_activity(activity_id) + return - if activity_ps: - # Get the service name for this activity, if - # we have a bundle on the system capable of handling - # this activity type - breg = self._model.get_bundle_registry() - bundle = breg.find_by_default_type(bundle_id) - if bundle: - act_type = bundle.get_service_name() - home_model = self._model.get_home() - home_model.notify_activity_launch(activity_id, act_type) - try: - activity = ActivityFactory.create(act_type) - except DBusException, e: - logging.error("Couldn't launch activity %s:\n%s" % (act_type, e)) - home_mode.notify_activity_launch_failed(activity_id) - else: - logging.debug("Joining activity type %s id %s" % (act_type, activity_id)) - activity.join(activity_ps.object_path()) - else: - logging.error("Couldn't find activity for type %s" % bundle_id) - else: - logging.error('Cannot start activity.') + activity_ps = self._pservice.get_activity(activity_id) + if not activity_ps: + logging.error("Couldn't find shared activity for %s" % activity_id) + return + + # Get the service name for this activity, if + # we have a bundle on the system capable of handling + # this activity type + breg = self._model.get_bundle_registry() + bundle = breg.find_by_default_type(bundle_id) + if not bundle: + logging.error("Couldn't find activity for type %s" % bundle_id) + return + + act_type = bundle.get_service_name() + home_model = self._model.get_home() + home_model.notify_activity_launch(activity_id, act_type) + + handler = ActivityFactory.create(act_type) + handler.connect('success', self._join_success_cb, activity_ps, activity_id, act_type) + handler.connect('error', self._join_error_cb, home_model, activity_id, act_type) def _find_unique_activity_id(self): # create a new unique activity ID @@ -251,6 +257,14 @@ class Shell(gobject.GObject): return act_id + def _start_success_cb(self, handler, activity, activity_id, activity_type): + logging.debug("Started activity %s (%s)" % (activity_id, activity_type)) + activity.start(activity_id) + + def _start_error_cb(self, handler, err, home_model, activity_id, activity_type): + logging.error("Couldn't launch activity %s (%s):\n%s" % (activity_id, activity_type, err)) + home_mode.notify_activity_launch_failed(activity_id) + def start_activity(self, activity_type): logging.debug('Shell.start_activity') act_id = self._find_unique_activity_id() @@ -261,16 +275,10 @@ class Shell(gobject.GObject): home_model = self._model.get_home() home_model.notify_activity_launch(act_id, activity_type) - try: - logging.debug("Shell.start_activity will start %s:%s" % (activity_type, act_id)) - activity = ActivityFactory.create(activity_type) - except dbus.DBusException, e: - logging.debug("Couldn't start activity '%s':\n %s" % (activity_type, e)) - home_mode.notify_activity_launch_failed(act_id) - return None - - activity.start(act_id) - return activity + logging.debug("Shell.start_activity will start %s (%s)" % (act_id, activity_type)) + handler = ActivityFactory.create(activity_type) + handler.connect('success', self._start_success_cb, act_id, activity_type) + handler.connect('error', self._start_error_cb, home_model, act_id, activity_type) def set_zoom_level(self, level): if level == sugar.ZOOM_ACTIVITY: diff --git a/shell/view/clipboardicon.py b/shell/view/clipboardicon.py index 4e702d39..42c54532 100644 --- a/shell/view/clipboardicon.py +++ b/shell/view/clipboardicon.py @@ -40,22 +40,32 @@ class ClipboardIcon(MenuIcon): else: return None - def _icon_activated_cb(self, icon): - if self._percent == 100: - cb_service = clipboardservice.get_instance() - - (name, percent, icon, preview, format_types) = \ - cb_service.get_object(self._object_id) + def _activity_create_success_cb(self, handler, activity): + activity.start(util.unique_id()) + activity.execute("open_document", [self._object_id]) - if format_types: - logging.debug("_icon_activated_cb: " + self._object_id) - - activity_id = self._get_activity_for_mime_type(format_types[0]) - - if activity_id: - activity = ActivityFactory.create(activity_id) - activity.start(util.unique_id()) - activity.execute("open_document", [self._object_id]) + def _activity_create_error_cb(self, handler, err): + pass + + def _icon_activated_cb(self, icon): + if self._percent < 100: + return + + cb_service = clipboardservice.get_instance() + (name, percent, icon, preview, format_types) = \ + cb_service.get_object(self._object_id) + if not format_types: + return + + logging.debug("_icon_activated_cb: " + self._object_id) + activity_type = self._get_activity_for_mime_type(format_types[0]) + if not activity_type: + return + + # Launch the activity to handle this item + handler = ActivityFactory.create(activity_type) + handler.connect('success', self._activity_create_success_cb) + handler.connect('error', self._activity_create_error_cb) def _popup_action_cb(self, popup, action): self.popdown() diff --git a/sugar/activity/ActivityFactory.py b/sugar/activity/ActivityFactory.py index 481c5b92..94e765ec 100644 --- a/sugar/activity/ActivityFactory.py +++ b/sugar/activity/ActivityFactory.py @@ -76,24 +76,43 @@ class ActivityFactory(dbus.service.Object): if len(self._activities) == 0: gtk.main_quit() +class ActivityCreationHandler(gobject.GObject): + + __gsignals__ = { + 'error': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'success': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])) + } + + def __init__(self, activity_name): + gobject.GObject.__init__(self) + + bus = dbus.SessionBus() + factory_name = activity_name + factory_path = get_path(factory_name) + + proxy_obj = bus.get_object(factory_name, factory_path) + factory = dbus.Interface(proxy_obj, "com.redhat.Sugar.ActivityFactory") + + factory.create(reply_handler=self._reply_handler, error_handler=self._error_handler) + + def _reply_handler(self, xid): + bus = dbus.SessionBus() + proxy_obj = bus.get_object(Activity.get_service_name(xid), + Activity.get_object_path(xid)) + activity = dbus.Interface(proxy_obj, Activity.ACTIVITY_INTERFACE) + self.emit('success', activity) + + def _error_handler(self, err): + logging.debug("Couldn't create activity: %s" % err) + self.emit('error', err) + def create(activity_name): - """Create a new activity from his name.""" - bus = dbus.SessionBus() - - factory_name = activity_name - factory_path = get_path(factory_name) - - proxy_obj = bus.get_object(factory_name, factory_path) - factory = dbus.Interface(proxy_obj, "com.redhat.Sugar.ActivityFactory") - - xid = factory.create() - - bus = dbus.SessionBus() - proxy_obj = bus.get_object(Activity.get_service_name(xid), - Activity.get_object_path(xid)) - activity = dbus.Interface(proxy_obj, Activity.ACTIVITY_INTERFACE) - - return activity + """Create a new activity from its name.""" + return ActivityCreationHandler(activity_name) def start_factory(activity_class, bundle_path): """Start the activity factory.""" From 70a5e27edd2ef7fc299f406025b47103f6b82542 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sun, 7 Jan 2007 01:18:57 -0500 Subject: [PATCH 4/5] Refactor activity icon handling in the Home View donut Give each activity icon in the donut its own class to track launch status in preparation for pulsing launch feedback. --- shell/view/home/activitiesdonut.py | 68 +++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/shell/view/home/activitiesdonut.py b/shell/view/home/activitiesdonut.py index b4fe22ac..5e4d4f27 100644 --- a/shell/view/home/activitiesdonut.py +++ b/shell/view/home/activitiesdonut.py @@ -16,10 +16,44 @@ import hippo import math +import gobject from sugar.graphics.canvasicon import CanvasIcon from sugar.graphics import style +class ActivityIcon(CanvasIcon): + def __init__(self, activity): + icon_name = activity.get_icon_name() + icon_color = activity.get_icon_color() + CanvasIcon.__init__(self, icon_name=icon_name, color=icon_color) + style.apply_stylesheet(self, 'ring.ActivityIcon') + + self._activity = activity + self._pulse_id = 0 + self._launched = False + + self._pulse_id = gobject.timeout_add(200, self._pulse_cb) + + def __del__(self): + if self._pulse_id > 0: + gobject.source_remove(self._pulse_id) + + def _pulse_cb(self): + pass + + def set_launched(self): + if self._launched: + return + self._launched = True + gobject.source_remove(self._pulse_id) + self._pulse_id = 0 + + def get_launched(self): + return self._launched + + def get_activity(self): + return self._activity + class ActivitiesDonut(hippo.CanvasBox, hippo.CanvasItem): __gtype_name__ = 'SugarActivitiesDonut' def __init__(self, shell, **kwargs): @@ -29,34 +63,46 @@ class ActivitiesDonut(hippo.CanvasBox, hippo.CanvasItem): self._shell = shell self._model = shell.get_model().get_home() + self._model.connect('activity-launched', self._activity_launched_cb) self._model.connect('activity-added', self._activity_added_cb) - self._model.connect('activity-removed', self._activity_removed_cb) + self._model.connect('activity-removed', self._activity_removed_cb) + + def _activity_launched_cb(self, model, activity): + self._add_activity(activity) def _activity_added_cb(self, model, activity): - self._add_activity(activity) + # Mark the activity as launched + act_id = activity.get_id() + if not self._activities.has_key(act_id): + return + icon = self._activities[act_id] + icon.set_launched() def _activity_removed_cb(self, model, activity): self._remove_activity(activity) def _remove_activity(self, activity): - icon = self._activities[activity.get_id()] + act_id = activity.get_id() + if not self._activities.has_key(act_id): + return + icon = self._activities[act_id] self.remove(icon) - del self._activities[activity.get_id()] + del self._activities[act_id] def _add_activity(self, activity): - icon_name = activity.get_icon_name() - icon_color = activity.get_icon_color() - - icon = CanvasIcon(icon_name=icon_name, color=icon_color) - style.apply_stylesheet(icon, 'ring.ActivityIcon') - icon.connect('activated', self._activity_icon_clicked_cb, activity) + icon = ActivityIcon(activity) + icon.connect('activated', self._activity_icon_clicked_cb) self.append(icon, hippo.PACK_FIXED) self._activities[activity.get_id()] = icon self.emit_paint_needed(0, 0, -1, -1) - def _activity_icon_clicked_cb(self, item, activity): + def _activity_icon_clicked_cb(self, icon): + activity = icon.get_activity() + if not icon.get_launched(): + return + activity_host = self._shell.get_activity(activity.get_id()) if activity_host: activity_host.present() From 0265f06b3e039aee88312cda7b6c0e98aefe067d Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sun, 7 Jan 2007 01:20:42 -0500 Subject: [PATCH 5/5] Add a _sugar.cairo_surface_from_gdk_pixbuf() function generic function to create a surface from a gdk pixbuf --- configure.ac | 2 ++ lib/python/Makefile.am | 2 ++ lib/python/_sugar.defs | 9 +++++++++ lib/python/_sugar.override | 22 ++++++++++++++++++++++ lib/python/_sugarmodule.c | 5 +++++ 5 files changed, 40 insertions(+) diff --git a/configure.ac b/configure.ac index a6e254bb..951d6b96 100644 --- a/configure.ac +++ b/configure.ac @@ -38,6 +38,8 @@ AC_SUBST(GNOMEPYTHONEXTRAS_DEFSDIR) PYGTK_DEFSDIR=`$PKG_CONFIG --variable=defsdir pygtk-2.0` AC_SUBST(PYGTK_DEFSDIR) +PKG_CHECK_MODULES(PYCAIRO, pycairo) + # # Setup GETTEXT # diff --git a/lib/python/Makefile.am b/lib/python/Makefile.am index be712d70..6f28d335 100644 --- a/lib/python/Makefile.am +++ b/lib/python/Makefile.am @@ -1,6 +1,7 @@ INCLUDES = \ $(PYTHON_INCLUDES) \ $(PYGTK_CFLAGS) \ + $(PYCAIRO_CFLAGS) \ $(LIB_CFLAGS) \ -I $(top_srcdir)/lib/src @@ -11,6 +12,7 @@ pkgpyexec_LTLIBRARIES = _sugar.la _sugar_la_LDFLAGS = -module -avoid-version -R$(MOZILLA_HOME) _sugar_la_LIBADD = \ $(LIB_LIBS) \ + $(PYCAIRO_LIBS) \ $(top_builddir)/lib/src/libsugarprivate.la _sugar_la_SOURCES = \ diff --git a/lib/python/_sugar.defs b/lib/python/_sugar.defs index bead7b48..4ea9b8de 100644 --- a/lib/python/_sugar.defs +++ b/lib/python/_sugar.defs @@ -247,6 +247,15 @@ '("GdkPixbuf*" "pixbuf") ) ) + +(define-function cairo_surface_from_gdk_pixbuf + (c-name "sugar_cairo_surface_from_gdk_pixbuf") + (return-type "cairo_surface_t*") + (parameters + '("GdkPixbuf*" "pixbuf") + ) +) + ;; Enumerations and flags ... diff --git a/lib/python/_sugar.override b/lib/python/_sugar.override index 6e0fe720..8dd90f96 100644 --- a/lib/python/_sugar.override +++ b/lib/python/_sugar.override @@ -13,9 +13,11 @@ headers #include "sugar-download.h" #include "sugar-audio-manager.h" +#include "pycairo.h" #include #include +extern Pycairo_CAPI_t *Pycairo_CAPI; %% modulename gecko @@ -159,3 +161,23 @@ _wrap_sugar_hippo_canvas_image_set_image_from_gdk_pixbuf(PyGObject *self, PyObje return Py_None; } %% +override sugar_cairo_surface_from_gdk_pixbuf kwargs +static PyObject* +_wrap_sugar_cairo_surface_from_gdk_pixbuf(PyGObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = { "pixbuf", NULL }; + PyGObject *child; + cairo_surface_t *surface; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs,"O!:sugar.cairo_surface_from_gdk_pixbuf", kwlist, &PyGdkPixbuf_Type, &child)) + return NULL; + + surface = _cairo_surface_from_pixbuf(GDK_PIXBUF (child->obj)); + if (surface == NULL) { + PyErr_SetString(PyExc_RuntimeError, "pixbuf could not be converted"); + return NULL; + } + + return PycairoSurface_FromSurface(surface, NULL); +} +%% diff --git a/lib/python/_sugarmodule.c b/lib/python/_sugarmodule.c index 4d8511b9..c576efb0 100644 --- a/lib/python/_sugarmodule.c +++ b/lib/python/_sugarmodule.c @@ -5,6 +5,9 @@ /* include this first, before NO_IMPORT_PYGOBJECT is defined */ #include +#include +Pycairo_CAPI_t *Pycairo_CAPI; + void py_sugar_register_classes (PyObject *d); extern PyMethodDef py_sugar_functions[]; @@ -16,6 +19,8 @@ init_sugar(void) init_pygobject (); + Pycairo_IMPORT; + m = Py_InitModule ("_sugar", py_sugar_functions); d = PyModule_GetDict (m);