From 2a48021a782a1ea3677883aaf1cafdd259246691 Mon Sep 17 00:00:00 2001 From: Simon Schampijer Date: Thu, 20 Dec 2007 18:25:34 +0000 Subject: Merge branch 'master' of ssh+git://dev.laptop.org/git/sugar --- diff --git a/NEWS b/NEWS index 0b6e8f0..1db2cef 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,4 @@ +* #4892: Use the engine to make sugar icons insensitive (benzea) * #4941: Add menu entry with dialog to show About this XO (erikos) * #5089: show frame shortly when adding object to clipboard (erikos) * #5097: Fix pasting text between activities through the clipboard. (tomeu) diff --git a/bin/sugar-activity b/bin/sugar-activity index 2e10922..c01b263 100755 --- a/bin/sugar-activity +++ b/bin/sugar-activity @@ -94,6 +94,17 @@ if len(args) == 0: bundle_path = os.environ['SUGAR_BUNDLE_PATH'] sys.path.append(bundle_path) +bundle = ActivityBundle(bundle_path) + +os.environ['SUGAR_BUNDLE_ID'] = bundle.get_bundle_id() +os.environ['SUGAR_BUNDLE_NAME'] = bundle.get_name() + +gtk.icon_theme_get_default().append_search_path(bundle.get_icons_path()) + +gettext.bindtextdomain(bundle.get_bundle_id(), + bundle.get_locale_path()) +gettext.textdomain(bundle.get_bundle_id()) + splitted_module = args[0].rsplit('.', 1) module_name = splitted_module[0] class_name = splitted_module[1] @@ -133,17 +144,6 @@ if options.single_process is True: if hasattr(module, 'start'): module.start() -bundle = ActivityBundle(bundle_path) - -os.environ['SUGAR_BUNDLE_ID'] = bundle.get_bundle_id() -os.environ['SUGAR_BUNDLE_NAME'] = bundle.get_name() - -gettext.bindtextdomain(bundle.get_bundle_id(), - bundle.get_locale_path()) -gettext.textdomain(bundle.get_bundle_id()) - -gtk.icon_theme_get_default().append_search_path(bundle.get_icons_path()) - create_activity_instance(constructor, handle) gtk.main() diff --git a/lib/sugar/activity/activity.py b/lib/sugar/activity/activity.py index 7c44981..96e757a 100644 --- a/lib/sugar/activity/activity.py +++ b/lib/sugar/activity/activity.py @@ -76,6 +76,10 @@ SCOPE_PRIVATE = "private" SCOPE_INVITE_ONLY = "invite" # shouldn't be shown in UI, it's implicit when you invite somebody SCOPE_NEIGHBORHOOD = "public" +J_DBUS_SERVICE = 'org.laptop.Journal' +J_DBUS_PATH = '/org/laptop/Journal' +J_DBUS_INTERFACE = 'org.laptop.Journal' + class ActivityToolbar(gtk.Toolbar): """The Activity toolbar with the Journal entry title, sharing, Keep and Stop buttons @@ -905,3 +909,9 @@ def get_activity_root(): return os.environ['SUGAR_ACTIVITY_ROOT'] else: raise RuntimeError("No SUGAR_ACTIVITY_ROOT set.") + +def show_object_in_journal(object_id): + bus = dbus.SessionBus() + obj = bus.get_object(J_DBUS_SERVICE, J_DBUS_PATH) + journal = dbus.Interface(obj, J_DBUS_INTERFACE) + journal.ShowObject(object_id) diff --git a/lib/sugar/activity/activityfactory.py b/lib/sugar/activity/activityfactory.py index 119dcea..835ec13 100644 --- a/lib/sugar/activity/activityfactory.py +++ b/lib/sugar/activity/activityfactory.py @@ -270,7 +270,7 @@ class ActivityCreationHandler(gobject.GObject): def _activate_error_handler(self, err): logging.error("Activity activation request failed %s" % err) - def _create_reply_handler(self, xid): + def _create_reply_handler(self): logging.debug("Activity created %s (%s)." % (self._handle.activity_id, self._service_name)) diff --git a/lib/sugar/activity/registry.py b/lib/sugar/activity/registry.py index c21d4a5..ac672d5 100644 --- a/lib/sugar/activity/registry.py +++ b/lib/sugar/activity/registry.py @@ -29,15 +29,17 @@ def _activity_info_from_dict(info_dict): if not info_dict: return None return ActivityInfo(info_dict['name'], info_dict['icon'], - info_dict['bundle_id'], info_dict['path'], - info_dict['show_launcher'], info_dict['command']) + info_dict['bundle_id'], info_dict['version'], + info_dict['path'], info_dict['show_launcher'], + info_dict['command']) class ActivityInfo(object): - def __init__(self, name, icon, bundle_id, + def __init__(self, name, icon, bundle_id, version, path, show_launcher, command): self.name = name self.icon = icon self.bundle_id = bundle_id + self.version = version self.path = path self.command = command self.show_launcher = show_launcher @@ -146,6 +148,8 @@ class ActivityRegistry(gobject.GObject): self._mime_type_to_activities.clear() def remove_bundle(self, bundle_path): + self._service_name_to_activity_info.clear() + self._mime_type_to_activities.clear() return self._registry.RemoveBundle(bundle_path) def _activity_removed_cb(self, info_dict): diff --git a/lib/sugar/bundle/activitybundle.py b/lib/sugar/bundle/activitybundle.py index ce1510f..ee72f80 100644 --- a/lib/sugar/bundle/activitybundle.py +++ b/lib/sugar/bundle/activitybundle.py @@ -22,10 +22,15 @@ import locale import os import tempfile -from sugar.bundle.bundle import Bundle, MalformedBundleException +from sugar.bundle.bundle import Bundle, MalformedBundleException, \ + AlreadyInstalledException, RegistrationException, \ + NotInstalledException + from sugar import activity from sugar import env +import logging + class ActivityBundle(Bundle): """A Sugar activity bundle @@ -204,8 +209,16 @@ class ActivityBundle(Bundle): else: return False + def need_upgrade(self): + act = activity.get_registry().get_activity(self._bundle_id) + if act is None or act.version != self._activity_version: + return True + else: + return False + def install(self): - if self.is_installed(): + act = activity.get_registry().get_activity(self._bundle_id) + if act is not None and act.path.startswith(env.get_user_activities_path()): raise AlreadyInstalledException install_dir = env.get_user_activities_path() @@ -250,12 +263,21 @@ class ActivityBundle(Bundle): if not activity.get_registry().add_bundle(install_path): raise RegistrationException - def uninstall(self): + def uninstall(self, force=False): if self._unpacked: install_path = self._path else: if not self.is_installed(): raise NotInstalledException + + act = activity.get_registry().get_activity(self._bundle_id) + if not force and act.version != self._activity_version: + logging.warning('Not uninstalling because different bundle present') + return + elif not act.path.startswith(env.get_user_activities_path()): + logging.warning('Not uninstalling system activity') + return + install_path = os.path.join(env.get_user_activities_path(), self._zip_root_dir) @@ -283,3 +305,17 @@ class ActivityBundle(Bundle): if not activity.get_registry().remove_bundle(install_path): raise RegistrationException + def upgrade(self): + act = activity.get_registry().get_activity(self._bundle_id) + if act is None: + logging.warning('Activity not installed') + elif act.path.startswith(env.get_user_activities_path()): + try: + self.uninstall(force=True) + except Exception, e: + logging.warning('Uninstall failed (%s), still trying to install newer bundle', e) + else: + logging.warning('Unable to uninstall system activity, installing upgraded version in user activities') + + self.install() + diff --git a/lib/sugar/datastore/datastore.py b/lib/sugar/datastore/datastore.py index 5238d18..334c866 100644 --- a/lib/sugar/datastore/datastore.py +++ b/lib/sugar/datastore/datastore.py @@ -160,10 +160,16 @@ class DSObject(object): if bundle_id is not None: raise ValueError('Object is a bundle, cannot be resumed as an activity.') + logging.debug('Creating activity bundle') bundle = ActivityBundle(self.file_path) if not bundle.is_installed(): + logging.debug('Installing activity bundle') bundle.install() + elif bundle.need_upgrade(): + logging.debug('Upgrading activity bundle') + bundle.upgrade() + logging.debug('activityfactory.creating bundle with id %r', bundle.get_bundle_id()) activityfactory.create(bundle.get_bundle_id()) else: if not self.get_activities() and bundle_id is None: diff --git a/lib/sugar/graphics/icon.py b/lib/sugar/graphics/icon.py index 2f88f5e..a739341 100644 --- a/lib/sugar/graphics/icon.py +++ b/lib/sugar/graphics/icon.py @@ -88,9 +88,10 @@ class _IconBuffer(object): self.cache = False self.scale = 1.0 - def _get_cache_key(self): + def _get_cache_key(self, sensitive): return (self.icon_name, self.file_name, self.fill_color, - self.stroke_color, self.badge_name, self.width, self.height) + self.stroke_color, self.badge_name, self.width, self.height, + sensitive) def _load_svg(self, file_name): entities = {} @@ -139,19 +140,22 @@ class _IconBuffer(object): return icon_info - def _draw_badge(self, context, size): + def _draw_badge(self, context, size, sensitive, widget): theme = gtk.icon_theme_get_default() badge_info = theme.lookup_icon(self.badge_name, size, 0) if badge_info: badge_file_name = badge_info.get_filename() if badge_file_name.endswith('.svg'): handle = self._loader.load(badge_file_name, {}, self.cache) - handle.render_cairo(context) + pixbuf = handle.get_pixbuf() else: pixbuf = gtk.gdk.pixbuf_new_from_file(badge_file_name) - surface = hippo.cairo_surface_from_gdk_pixbuf(pixbuf) - context.set_source_surface(surface, 0, 0) - context.paint() + + if not sensitive: + pixbuf = self._get_insensitive_pixbuf(pixbuf, widget) + surface = hippo.cairo_surface_from_gdk_pixbuf(pixbuf) + context.set_source_surface(surface, 0, 0) + context.paint() def _get_size(self, icon_width, icon_height, padding): if self.width is not None and self.height is not None: @@ -196,8 +200,30 @@ class _IconBuffer(object): self.stroke_color = None self.fill_color = None - def get_surface(self): - cache_key = self._get_cache_key() + def _get_insensitive_pixbuf (self, pixbuf, widget): + if not (widget and widget.style): + return pixbuf + + icon_source = gtk.IconSource() + # Special size meaning "don't touch" + icon_source.set_size(-1) + icon_source.set_pixbuf(pixbuf) + icon_source.set_state(gtk.STATE_INSENSITIVE) + icon_source.set_direction_wildcarded(False) + icon_source.set_size_wildcarded(False) + + # Please note that the pixbuf returned by this function is leaked + # with current stable versions of pygtk. The relevant bug is + # http://bugzilla.gnome.org/show_bug.cgi?id=502871 + # -- 2007-12-14 Benjamin Berg + pixbuf = widget.style.render_icon(icon_source, widget.get_direction(), + gtk.STATE_INSENSITIVE, -1, widget, + "sugar-icon") + + return pixbuf + + def get_surface(self, sensitive=True, widget=None): + cache_key = self._get_cache_key(sensitive) if cache_key in self._surface_cache: return self._surface_cache[cache_key] @@ -230,8 +256,18 @@ class _IconBuffer(object): context.translate(padding, padding) if is_svg: - handle.render_cairo(context) + if sensitive: + handle.render_cairo(context) + else: + pixbuf = handle.get_pixbuf() + pixbuf = self._get_insensitive_pixbuf(pixbuf, widget) + + pixbuf_surface = hippo.cairo_surface_from_gdk_pixbuf(pixbuf) + context.set_source_surface(pixbuf_surface, 0, 0) + context.paint() else: + if not sensitive: + pixbuf = self._get_insensitive_pixbuf(pixbuf, widget) pixbuf_surface = hippo.cairo_surface_from_gdk_pixbuf(pixbuf) context.set_source_surface(pixbuf_surface, 0, 0) context.paint() @@ -239,7 +275,7 @@ class _IconBuffer(object): if self.badge_name: context.restore() context.translate(badge_info.attach_x, badge_info.attach_y) - self._draw_badge(context, badge_info.size) + self._draw_badge(context, badge_info.size, sensitive, widget) self._surface_cache[cache_key] = surface @@ -307,7 +343,8 @@ class Icon(gtk.Image): def do_expose_event(self, event): self._sync_image_properties() - surface = self._buffer.get_surface() + sensitive = (self.state != gtk.STATE_INSENSITIVE) + surface = self._buffer.get_surface(sensitive, self) if surface is None: return diff --git a/services/shell/activityregistryservice.py b/services/shell/activityregistryservice.py index b4323ee..9c2dda7 100644 --- a/services/shell/activityregistryservice.py +++ b/services/shell/activityregistryservice.py @@ -113,6 +113,7 @@ class ActivityRegistry(dbus.service.Object): return {'name': bundle.get_name(), 'icon': bundle.get_icon(), 'bundle_id': bundle.get_bundle_id(), + 'version': bundle.get_activity_version(), 'path': bundle.get_path(), 'command': bundle.get_command(), 'show_launcher': bundle.get_show_launcher()} diff --git a/shell/model/Friends.py b/shell/model/Friends.py index 2b7d6bf..6fc3e97 100644 --- a/shell/model/Friends.py +++ b/shell/model/Friends.py @@ -14,6 +14,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +import dbus import os from ConfigParser import ConfigParser @@ -86,3 +87,28 @@ class Friends(gobject.GObject): fileobject = open(self._path, 'w') cp.write(fileobject) fileobject.close() + + self._sync_friends() + + def _sync_friends(self): + # XXX: temporary hack + # remove this when the shell service has a D-Bus API for buddies + + def friends_synced(): + pass + + def friends_synced_error(e): + logging.error("Error asking presence service to sync friends: %s" + % e) + + keys = [] + for friend in self: + keys.append(friend.get_key()) + + bus = dbus.SessionBus() + ps = bus.get_object('org.laptop.Sugar.Presence', + '/org/laptop/Sugar/Presence') + psi = dbus.Interface(ps, 'org.laptop.Sugar.Presence') + psi.SyncFriends(keys, + reply_handler=friends_synced, + error_handler=friends_synced_error) diff --git a/shell/view/Shell.py b/shell/view/Shell.py index b9d259f..72aa3b1 100644 --- a/shell/view/Shell.py +++ b/shell/view/Shell.py @@ -20,6 +20,7 @@ import logging import tempfile import os import time +import shutil import gobject import gtk @@ -80,8 +81,16 @@ class Shell(gobject.GObject): def _start_journal_idle(self): # Mount the datastore in internal flash - datastore.mount(env.get_profile_path('datastore'), [], - timeout=120 * DBUS_PYTHON_TIMEOUT_UNITS_PER_SECOND) + ds_path = env.get_profile_path('datastore') + try: + datastore.mount(ds_path, [], timeout=120 * \ + DBUS_PYTHON_TIMEOUT_UNITS_PER_SECOND) + except: + # Don't explode if there's corruption; move the data out of the way + # and attempt to create a store from scratch. + shutil.move(ds_path, os.path.abspath(ds_path) + str(time.time())) + datastore.mount(ds_path, [], timeout=120 * \ + DBUS_PYTHON_TIMEOUT_UNITS_PER_SECOND) # Checking for the bundle existence will also ensure # that the shell service is started up. diff --git a/shell/view/frame/activitiestray.py b/shell/view/frame/activitiestray.py index 6f8e89e..3dbf955 100644 --- a/shell/view/frame/activitiestray.py +++ b/shell/view/frame/activitiestray.py @@ -129,7 +129,7 @@ class ActivitiesTray(hippo.CanvasBox): def _activity_removed_cb(self, activity_registry, activity_info): for item in self._tray.get_children(): - if item.get_bundle_id() == activity_info.service_name: + if item.get_bundle_id() == activity_info.bundle_id: self._tray.remove_item(item) return diff --git a/shell/view/home/MeshBox.py b/shell/view/home/MeshBox.py index ee8599c..a13aeba 100644 --- a/shell/view/home/MeshBox.py +++ b/shell/view/home/MeshBox.py @@ -74,7 +74,7 @@ class AccessPointView(PulsingIcon): # Update badge caps = model.props.capabilities if model.get_nm_network().is_favorite(): - self.props.badge_name = "emblem-star" + self.props.badge_name = "emblem-favorite" elif (caps & NM_802_11_CAP_PROTO_WEP) or (caps & NM_802_11_CAP_PROTO_WPA) or (caps & NM_802_11_CAP_PROTO_WPA2): self.props.badge_name = "emblem-locked" diff --git a/shell/view/home/activitiesdonut.py b/shell/view/home/activitiesdonut.py index ebdf8be..8e09006 100755 --- a/shell/view/home/activitiesdonut.py +++ b/shell/view/home/activitiesdonut.py @@ -55,7 +55,7 @@ def html_to_rgb(html_color): return (r, g, b) class ActivityIcon(CanvasIcon): - _INTERVAL = 100 + _INTERVAL = 200 __gsignals__ = { 'resume': (gobject.SIGNAL_RUN_FIRST, diff --git a/tests/graphics/iconwidget.py b/tests/graphics/iconwidget.py index 22d9276..cacf501 100644 --- a/tests/graphics/iconwidget.py +++ b/tests/graphics/iconwidget.py @@ -28,24 +28,60 @@ import common test = common.Test() -icon = Icon(icon_name='go-previous') -icon.props.icon_size = gtk.ICON_SIZE_LARGE_TOOLBAR -test.pack_start(icon) -icon.show() - -icon = Icon(icon_name='computer-xo', - icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR, - xo_color=XoColor()) -test.pack_start(icon) -icon.show() - -icon = Icon(icon_name='battery-000', - icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR, - badge_name='badge-busy') -test.pack_start(icon) -icon.show() +hbox = gtk.HBox() +test.pack_start(hbox) +sensitive_box = gtk.VBox() +insensitive_box = gtk.VBox() + +hbox.pack_start(sensitive_box) +hbox.pack_start(insensitive_box) +hbox.show_all() + + +def create_icon_widgets(box, sensitive=True): + icon = Icon(icon_name='go-previous') + icon.props.icon_size = gtk.ICON_SIZE_LARGE_TOOLBAR + box.pack_start(icon) + icon.set_sensitive(sensitive) + icon.show() + + icon = Icon(icon_name='computer-xo', + icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR, + xo_color=XoColor()) + box.pack_start(icon) + icon.set_sensitive(sensitive) + icon.show() + + icon = Icon(icon_name='battery-000', + icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR, + badge_name='emblem-busy') + box.pack_start(icon) + icon.set_sensitive(sensitive) + icon.show() + + icon = Icon(icon_name='gtk-new', + icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR, + badge_name='gtk-cancel') + box.pack_start(icon) + icon.set_sensitive(sensitive) + icon.show() + + +create_icon_widgets(sensitive_box, True) +create_icon_widgets(insensitive_box, False) test.show() +# This can be used to test for leaks by setting the LRU cache size +# in icon.py to 1. +#def idle_cb(): +# import gc +# gc.collect() +# test.queue_draw() +# return True +# +#import gobject +#gobject.idle_add(idle_cb) + if __name__ == "__main__": common.main(test) diff --git a/tests/lib/test_mime.py b/tests/lib/test_mime.py index 88598fe..3df0ce6 100644 --- a/tests/lib/test_mime.py +++ b/tests/lib/test_mime.py @@ -20,28 +20,28 @@ import sys import unittest -from sugar import objects +from sugar import mime class TestMime(unittest.TestCase): def test_from_file_name(self): - self.assertEqual(objects.mime.get_from_file_name('test.pdf'), + self.assertEqual(mime.get_from_file_name('test.pdf'), 'application/pdf') def test_choose_most_significant(self): # Mozilla's text in dnd - mime_type = objects.mime.choose_most_significant( + mime_type = mime.choose_most_significant( ['text/plain', 'text/_moz_htmlcontext', 'text/unicode', 'text/html', 'text/_moz_htmlinfo']) self.assertEqual(mime_type, 'text/html') # Mozilla's text in c&v - mime_type = objects.mime.choose_most_significant( + mime_type = mime.choose_most_significant( ['text/_moz_htmlcontext', 'STRING', 'text/html', 'text/_moz_htmlinfo', 'text/x-moz-url-priv', 'UTF8_STRING', 'COMPOUND_TEXT']) self.assertEqual(mime_type, 'text/html') # Mozilla gif in dnd - mime_type = objects.mime.choose_most_significant( + mime_type = mime.choose_most_significant( ['application/x-moz-file-promise-url', 'application/x-moz-file-promise-dest-filename', 'text/_moz_htmlinfo', 'text/x-moz-url-desc', 'text/_moz_htmlcontext', 'text/x-moz-url-data', @@ -49,24 +49,33 @@ class TestMime(unittest.TestCase): self.assertEqual(mime_type, 'text/uri-list') # Mozilla url in dnd - mime_type = objects.mime.choose_most_significant( + mime_type = mime.choose_most_significant( ['text/_moz_htmlcontext', 'text/html', 'text/_moz_htmlinfo', '_NETSCAPE_URL', 'text/x-moz-url', 'text/x-moz-url-desc', 'text/x-moz-url-data', 'text/plain', 'text/unicode']) self.assertEqual(mime_type, 'text/x-moz-url') # Abiword text in dnd - mime_type = objects.mime.choose_most_significant( + mime_type = mime.choose_most_significant( ['text/rtf', 'text/uri-list']) self.assertEqual(mime_type, 'text/uri-list') # Abiword text in c&v - mime_type = objects.mime.choose_most_significant( + mime_type = mime.choose_most_significant( ['UTF8_STRING', 'STRING', 'text/html', 'TEXT', 'text/rtf', 'COMPOUND_TEXT', 'application/rtf', 'text/plain', 'application/xhtml+xml']) self.assertEqual(mime_type, 'application/rtf') + # Abiword text in c&v + mime_type = mime.choose_most_significant( + ['GTK_TEXT_BUFFER_CONTENTS', + 'application/x-gtk-text-buffer-rich-text', + 'UTF8_STRING', 'COMPOUND_TEXT', 'TEXT', 'STRING', + 'text/plain;charset=utf-8', 'text/plain;charset=UTF-8', + 'text/plain']) + self.assertEqual(mime_type, 'text/plain') + if __name__ == "__main__": unittest.main() -- cgit v0.9.1