From cfb054124936cd8256703ed172a9417b7fe3811e Mon Sep 17 00:00:00 2001 From: Morgan Collett Date: Tue, 21 Aug 2007 11:39:05 +0100 Subject: [PATCH 01/36] Clean up leave() and callbacks --- sugar/presence/activity.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sugar/presence/activity.py b/sugar/presence/activity.py index 2df8793d..bdfc74f5 100644 --- a/sugar/presence/activity.py +++ b/sugar/presence/activity.py @@ -167,16 +167,17 @@ class Activity(gobject.GObject): return bus_name, connection, channels def _leave_cb(self): - # XXX Is this the right thing to do? + """Callback for async action of leaving shared activity.""" self.emit("joined", False, "left activity") def _leave_error_cb(self, err): - # XXX We are closing down anyway + """Callback for error in async leaving of shared activity. + + XXX Add logging!""" pass def leave(self): """Leave this shared activity""" - # FIXME self._joined = False self._activity.Leave(reply_handler=self._leave_cb, error_handler=self._leave_error_cb) From d09b8d3ea43bb877c39e0f2e169cf3d885521ff2 Mon Sep 17 00:00:00 2001 From: Morgan Collett Date: Tue, 21 Aug 2007 12:08:33 +0100 Subject: [PATCH 02/36] Document _share_activity_cb --- sugar/presence/presenceservice.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sugar/presence/presenceservice.py b/sugar/presence/presenceservice.py index 4a92dd30..81835fc8 100644 --- a/sugar/presence/presenceservice.py +++ b/sugar/presence/presenceservice.py @@ -357,7 +357,11 @@ class PresenceService(gobject.GObject): return self._new_object(owner_op) def _share_activity_cb(self, activity, op): - """Notify with GObject event of successful sharing of activity""" + """Notify with GObject event of successful sharing of activity + + op -- full dbus path of the new object, must be + prefixed with either of _PS_BUDDY_OP or _PS_ACTIVITY_OP + """ psact = self._new_object(op) psact._joined = True self.emit("activity-shared", True, psact, None) From 52f2bea3ed542e65f22664b5c837280fdab6960d Mon Sep 17 00:00:00 2001 From: Morgan Collett Date: Wed, 22 Aug 2007 15:54:12 +0100 Subject: [PATCH 03/36] Use new PresenceService API for sharing by invitation only --- sugar/activity/activity.py | 21 +++++++++++++++------ sugar/presence/presenceservice.py | 13 +++++++++---- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/sugar/activity/activity.py b/sugar/activity/activity.py index 553b1a1a..f63174a7 100644 --- a/sugar/activity/activity.py +++ b/sugar/activity/activity.py @@ -448,13 +448,22 @@ class Activity(Window, gtk.Container): self._shared_activity = activity self.emit('shared') - def share(self): - """Request that the activity be shared on the network.""" + def share(self, private=False): + """Request that the activity be shared on the network. + + private -- bool: True to share by invitation only, + False to advertise as shared to everyone. + """ + # FIXME: Make private=True to turn on the by-invitation-only scope if self._shared_activity and self._shared_activity.props.joined: - raise RuntimeError("Activity %s already shared." % self._activity_id) - logging.debug('Requesting share of activity %s.' % self._activity_id) - self._share_id = self._pservice.connect("activity-shared", self._internal_share_cb) - self._pservice.share_activity(self) + raise RuntimeError("Activity %s already shared." % + self._activity_id) + verb = private and 'private' or 'public' + logging.debug('Requesting %s share of activity %s.' % + (verb, self._activity_id)) + self._share_id = self._pservice.connect("activity-shared", + self._internal_share_cb) + self._pservice.share_activity(self, private) def _realize_cb(self, window): wm.set_bundle_id(window.window, self.get_service_name()) diff --git a/sugar/presence/presenceservice.py b/sugar/presence/presenceservice.py index 81835fc8..238012d2 100644 --- a/sugar/presence/presenceservice.py +++ b/sugar/presence/presenceservice.py @@ -371,10 +371,14 @@ class PresenceService(gobject.GObject): _logger.debug("Error sharing activity %s: %s" % (activity.get_id(), err)) self.emit("activity-shared", False, None, err) - def share_activity(self, activity, properties={}): - """Ask presence service to ask the activity to share itself + def share_activity(self, activity, private=True): + """Ask presence service to ask the activity to share itself publicly. + + activity -- sugar.activity.activity.Activity instance + private -- bool: True to share by invitation only, + False to advertise as shared to everyone. - Uses the ShareActivity method on the service to ask for the + Uses the AdvertiseActivity method on the service to ask for the sharing of the given activity. Arranges to emit activity-shared event with: @@ -395,7 +399,8 @@ class PresenceService(gobject.GObject): atype = activity.get_service_name() name = activity.props.title - self._ps.ShareActivity(actid, atype, name, properties, + # FIXME: Test, then make this AdvertiseActivity: + self._ps.ShareActivity(actid, atype, name, private, reply_handler=lambda *args: self._share_activity_cb(activity, *args), error_handler=lambda *args: self._share_activity_error_cb(activity, *args)) From d2261e405136845217a8f769995ebeb832b9ffac Mon Sep 17 00:00:00 2001 From: Morgan Collett Date: Thu, 23 Aug 2007 13:48:16 +0100 Subject: [PATCH 04/36] Fix sharing publicly --- sugar/activity/activity.py | 2 +- sugar/presence/presenceservice.py | 29 +++++++++++++++++++---------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/sugar/activity/activity.py b/sugar/activity/activity.py index eea76a8d..3f782b51 100644 --- a/sugar/activity/activity.py +++ b/sugar/activity/activity.py @@ -484,7 +484,7 @@ class Activity(Window, gtk.Container): (verb, self._activity_id)) self._share_id = self._pservice.connect("activity-shared", self._internal_share_cb) - self._pservice.share_activity(self, private) + self._pservice.share_activity(self, private=private) def _realize_cb(self, window): wm.set_bundle_id(window.window, self.get_service_name()) diff --git a/sugar/presence/presenceservice.py b/sugar/presence/presenceservice.py index 238012d2..f4f102d7 100644 --- a/sugar/presence/presenceservice.py +++ b/sugar/presence/presenceservice.py @@ -371,12 +371,8 @@ class PresenceService(gobject.GObject): _logger.debug("Error sharing activity %s: %s" % (activity.get_id(), err)) self.emit("activity-shared", False, None, err) - def share_activity(self, activity, private=True): + def share_activity(self, activity, properties={}, private=True): """Ask presence service to ask the activity to share itself publicly. - - activity -- sugar.activity.activity.Activity instance - private -- bool: True to share by invitation only, - False to advertise as shared to everyone. Uses the AdvertiseActivity method on the service to ask for the sharing of the given activity. Arranges to emit activity-shared @@ -389,20 +385,33 @@ class PresenceService(gobject.GObject): returns None """ actid = activity.get_id() + _logger.debug('XXXX in share_activity') # Ensure the activity is not already shared/joined for obj in self._objcache.values(): if not isinstance(object, Activity): continue if obj.props.id == actid or obj.props.joined: - raise RuntimeError("Activity %s is already shared." % actid) + raise RuntimeError("Activity %s is already shared." % + actid) atype = activity.get_service_name() name = activity.props.title - # FIXME: Test, then make this AdvertiseActivity: - self._ps.ShareActivity(actid, atype, name, private, - reply_handler=lambda *args: self._share_activity_cb(activity, *args), - error_handler=lambda *args: self._share_activity_error_cb(activity, *args)) + if private: + _logger.debug('XXXX private, so calling InviteActivity') + self._ps.InviteActivity(actid, atype, name, properties, + reply_handler=lambda *args: \ + self._share_activity_cb(activity, *args), + error_handler=lambda *args: \ + self._share_activity_error_cb(activity, *args)) + else: + # FIXME: Test, then make this AdvertiseActivity: + _logger.debug('XXXX not private, so calling ShareActivity') + self._ps.ShareActivity(actid, atype, name, properties, + reply_handler=lambda *args: \ + self._share_activity_cb(activity, *args), + error_handler=lambda *args: \ + self._share_activity_error_cb(activity, *args)) def get_preferred_connection(self): """Gets the preferred telepathy connection object that an activity From f1d588452eac011f2a9217e90c6ab7af61667bbe Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sat, 25 Aug 2007 13:15:28 +0200 Subject: [PATCH 05/36] Reorganize canvas items. --- shell/intro/colorpicker.py | 2 +- shell/intro/intro.py | 4 +- shell/view/BuddyIcon.py | 2 +- shell/view/clipboardicon.py | 2 +- shell/view/devices/battery.py | 11 +- shell/view/devices/deviceview.py | 2 +- shell/view/devices/network/wireless.py | 8 +- shell/view/frame/FriendsBox.py | 2 +- shell/view/home/FriendView.py | 2 +- shell/view/home/MeshBox.py | 7 +- shell/view/home/MyIcon.py | 2 +- shell/view/home/activitiesdonut.py | 2 +- shell/view/pulsingicon.py | 2 +- sugar/graphics/Makefile.am | 7 +- sugar/graphics/{canvasbutton.py => button.py} | 0 sugar/graphics/{canvasentry.py => entry.py} | 0 sugar/graphics/icon.py | 414 +++++++++++++++++- sugar/graphics/iconbutton.py | 2 +- sugar/graphics/objectchooser.py | 4 +- .../{canvasroundbox.py => roundbox.py} | 0 20 files changed, 443 insertions(+), 32 deletions(-) rename sugar/graphics/{canvasbutton.py => button.py} (100%) rename sugar/graphics/{canvasentry.py => entry.py} (100%) rename sugar/graphics/{canvasroundbox.py => roundbox.py} (100%) diff --git a/shell/intro/colorpicker.py b/shell/intro/colorpicker.py index f418807b..52b96871 100644 --- a/shell/intro/colorpicker.py +++ b/shell/intro/colorpicker.py @@ -16,7 +16,7 @@ import hippo -from sugar.graphics.canvasicon import CanvasIcon +from sugar.graphics.icon import CanvasIcon from sugar.graphics import style from sugar.graphics.xocolor import XoColor diff --git a/shell/intro/intro.py b/shell/intro/intro.py index ba633e39..54558130 100644 --- a/shell/intro/intro.py +++ b/shell/intro/intro.py @@ -26,8 +26,8 @@ import logging from sugar import env from sugar.graphics import style -from sugar.graphics.canvasbutton import CanvasButton -from sugar.graphics.canvasentry import CanvasEntry +from sugar.graphics.button import CanvasButton +from sugar.graphics.entry import CanvasEntry import colorpicker diff --git a/shell/view/BuddyIcon.py b/shell/view/BuddyIcon.py index 5079fe5b..1ab0ed74 100644 --- a/shell/view/BuddyIcon.py +++ b/shell/view/BuddyIcon.py @@ -14,7 +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 -from sugar.graphics.canvasicon import CanvasIcon +from sugar.graphics.icon import CanvasIcon from sugar.graphics.palette import Palette from view.BuddyMenu import BuddyMenu diff --git a/shell/view/clipboardicon.py b/shell/view/clipboardicon.py index 5f4c47e6..d7756f1d 100644 --- a/shell/view/clipboardicon.py +++ b/shell/view/clipboardicon.py @@ -20,7 +20,7 @@ from gettext import gettext as _ import gobject -from sugar.graphics.canvasicon import CanvasIcon +from sugar.graphics.icon import CanvasIcon from view.clipboardmenu import ClipboardMenu from sugar.graphics.xocolor import XoColor from sugar.graphics import style diff --git a/shell/view/devices/battery.py b/shell/view/devices/battery.py index 5aafc5cf..4ac3c17b 100644 --- a/shell/view/devices/battery.py +++ b/shell/view/devices/battery.py @@ -17,7 +17,8 @@ import gtk from gettext import gettext as _ -from sugar.graphics import canvasicon +from sugar.graphics.icon import CanvasIcon +from sugar.graphics.icon import get_icon_state from sugar.graphics import style from sugar.graphics.palette import Palette @@ -27,9 +28,9 @@ _STATUS_CHARGING = 0 _STATUS_DISCHARGING = 1 _STATUS_FULLY_CHARGED = 2 -class DeviceView(canvasicon.CanvasIcon): +class DeviceView(CanvasIcon): def __init__(self, model): - canvasicon.CanvasIcon.__init__(self, size=style.MEDIUM_ICON_SIZE) + CanvasIcon.__init__(self, size=style.MEDIUM_ICON_SIZE) self._model = model self._palette = BatteryPalette(_('My Battery life')) self.set_palette(self._palette) @@ -40,8 +41,8 @@ class DeviceView(canvasicon.CanvasIcon): self._update_info() def _update_info(self): - self.props.icon_name = canvasicon.get_icon_state( - _ICON_NAME, self._model.props.level) + name = get_icon_state(_ICON_NAME, self._model.props.level) + self.props.icon_name = name # Update palette if self._model.props.charging: diff --git a/shell/view/devices/deviceview.py b/shell/view/devices/deviceview.py index 5bff35a9..f58da021 100644 --- a/shell/view/devices/deviceview.py +++ b/shell/view/devices/deviceview.py @@ -14,7 +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 -from sugar.graphics.canvasicon import CanvasIcon +from sugar.graphics.icon import CanvasIcon def create(model): name = 'view.devices.' + model.get_type() diff --git a/shell/view/devices/network/wireless.py b/shell/view/devices/network/wireless.py index 9f0abd8b..5102f3bb 100644 --- a/shell/view/devices/network/wireless.py +++ b/shell/view/devices/network/wireless.py @@ -15,11 +15,12 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -from sugar.graphics import canvasicon +from sugar.graphics.icon import get_icon_state +from sugar.graphics.icon import CanvasIcon from sugar.graphics import style from sugar.graphics import style + from model.devices.network import wireless -from sugar.graphics.canvasicon import CanvasIcon from model.devices import device _ICON_NAME = 'network-wireless' @@ -47,8 +48,7 @@ class DeviceView(CanvasIcon): self._update_state() def _update_icon(self): - icon_name = canvasicon.get_icon_state( - _ICON_NAME, self._model.props.strength) + icon_name = get_icon_state(_ICON_NAME, self._model.props.strength) if icon_name: self.props.icon_name = icon_name diff --git a/shell/view/frame/FriendsBox.py b/shell/view/frame/FriendsBox.py index 30f59635..fee5661c 100644 --- a/shell/view/frame/FriendsBox.py +++ b/shell/view/frame/FriendsBox.py @@ -17,7 +17,7 @@ import hippo from sugar.graphics.palette import Palette -from sugar.graphics.canvasicon import CanvasIcon +from sugar.graphics.icon import CanvasIcon from sugar.graphics import style from sugar.presence import presenceservice diff --git a/shell/view/home/FriendView.py b/shell/view/home/FriendView.py index ed058927..eb1eab55 100644 --- a/shell/view/home/FriendView.py +++ b/shell/view/home/FriendView.py @@ -17,7 +17,7 @@ import hippo import gobject -from sugar.graphics.canvasicon import CanvasIcon +from sugar.graphics.icon import CanvasIcon from sugar.graphics import style from sugar.presence import presenceservice from sugar import activity diff --git a/shell/view/home/MeshBox.py b/shell/view/home/MeshBox.py index ff6d2907..04ddf4ed 100644 --- a/shell/view/home/MeshBox.py +++ b/shell/view/home/MeshBox.py @@ -21,10 +21,10 @@ import gobject from gettext import gettext as _ from sugar.graphics.spreadlayout import SpreadLayout -from sugar.graphics.canvasicon import CanvasIcon +from sugar.graphics.icon import CanvasIcon from sugar.graphics import style from sugar.graphics import xocolor -from sugar.graphics import canvasicon +from sugar.graphics.icon import get_icon_state from sugar.graphics import style from sugar import profile @@ -86,8 +86,7 @@ class AccessPointView(PulsingIcon): self.set_tooltip(self._model.props.name) def _update_icon(self): - icon_name = canvasicon.get_icon_state( - _ICON_NAME, self._model.props.strength) + icon_name = get_icon_state(_ICON_NAME, self._model.props.strength) if icon_name: self.props.icon_name = icon_name diff --git a/shell/view/home/MyIcon.py b/shell/view/home/MyIcon.py index 894bd83e..b46c730d 100644 --- a/shell/view/home/MyIcon.py +++ b/shell/view/home/MyIcon.py @@ -14,7 +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 -from sugar.graphics.canvasicon import CanvasIcon +from sugar.graphics.icon import CanvasIcon from sugar import profile class MyIcon(CanvasIcon): diff --git a/shell/view/home/activitiesdonut.py b/shell/view/home/activitiesdonut.py index 8aa3933d..2a96a96b 100644 --- a/shell/view/home/activitiesdonut.py +++ b/shell/view/home/activitiesdonut.py @@ -24,7 +24,7 @@ import hippo import gobject import gtk -from sugar.graphics.canvasicon import CanvasIcon +from sugar.graphics.icon import CanvasIcon from sugar.graphics.menuitem import MenuItem from sugar.graphics.palette import Palette from sugar.graphics import style diff --git a/shell/view/pulsingicon.py b/shell/view/pulsingicon.py index 3820a6d8..9e7b3d90 100644 --- a/shell/view/pulsingicon.py +++ b/shell/view/pulsingicon.py @@ -16,7 +16,7 @@ import gobject -from sugar.graphics.canvasicon import CanvasIcon +from sugar.graphics.icon import CanvasIcon class PulsingIcon(CanvasIcon): __gproperties__ = { diff --git a/sugar/graphics/Makefile.am b/sugar/graphics/Makefile.am index 7fb63dcb..8911b68f 100644 --- a/sugar/graphics/Makefile.am +++ b/sugar/graphics/Makefile.am @@ -2,11 +2,9 @@ sugardir = $(pythondir)/sugar/graphics sugar_PYTHON = \ __init__.py \ animator.py \ - canvasbutton.py \ - canvasicon.py \ - canvasentry.py \ - canvasroundbox.py \ + button.py \ combobox.py \ + entry.py \ icon.py \ iconbutton.py \ iconentry.py \ @@ -17,6 +15,7 @@ sugar_PYTHON = \ palette.py \ palettegroup.py \ panel.py \ + roundbox.py \ spreadlayout.py \ style.py \ toggletoolbutton.py \ diff --git a/sugar/graphics/canvasbutton.py b/sugar/graphics/button.py similarity index 100% rename from sugar/graphics/canvasbutton.py rename to sugar/graphics/button.py diff --git a/sugar/graphics/canvasentry.py b/sugar/graphics/entry.py similarity index 100% rename from sugar/graphics/canvasentry.py rename to sugar/graphics/entry.py diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index ff4e09a1..13f60656 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -16,11 +16,20 @@ # Boston, MA 02111-1307, USA. import os +import re +import time +import logging + import gobject import gtk -import re +import hippo +import rsvg +import cairo from sugar.graphics.style import Color +from sugar.graphics.xocolor import XoColor +from sugar.graphics import style +from sugar.graphics.palette import Palette, CanvasInvoker class Icon(gtk.Image): __gtype_name__ = 'SugarIcon' @@ -152,3 +161,406 @@ class Icon(gtk.Image): return self._stroke_color else: return gtk.Image.do_get_property(self, pspec) + +_ICON_REQUEST_SIZE = 50 +_BADGE_SIZE = 0.45 + +class _IconCacheIcon: + def __init__(self, name, fill_color, stroke_color, now): + self.last_used = now + self.usage_count = 1 + self.badge_x = 1.0 - _BADGE_SIZE / 2 + self.badge_y = 1.0 - _BADGE_SIZE / 2 + + if name[0:6] == "theme:": + info = gtk.icon_theme_get_default().lookup_icon( + name[6:], _ICON_REQUEST_SIZE, 0) + if not info: + raise ValueError("Icon '" + name + "' not found.") + + fname = info.get_filename() + attach_points = info.get_attach_points() + if attach_points is not None: + self.badge_x = float(attach_points[0][0]) / _ICON_REQUEST_SIZE + self.badge_y = float(attach_points[0][1]) / _ICON_REQUEST_SIZE + del info + else: + fname = name + + self.handle = self._read_icon_data(fname, fill_color, stroke_color) + + def _read_icon_data(self, filename, fill_color, stroke_color): + icon_file = open(filename, 'r') + data = icon_file.read() + icon_file.close() + + if fill_color: + entity = '' % fill_color + data = re.sub('', entity, data) + + if stroke_color: + entity = '' % stroke_color + data = re.sub('', entity, data) + + self.data_size = len(data) + return rsvg.Handle(data=data) + +class _IconCache: + _CACHE_MAX = 50000 # in bytes + + def __init__(self): + self._icons = {} + self._cache_size = 0 + + def _cache_cleanup(self, key, now): + while self._cache_size > self._CACHE_MAX: + evict_key = None + oldest_key = None + oldest_time = now + for icon_key, icon in self._icons.items(): + # Don't evict the icon we are about to use if it's in the cache + if icon_key == key: + continue + + # evict large icons first + if icon.data_size > self._CACHE_MAX: + evict_key = icon_key + break + # evict older icons next; those used over 2 minutes ago + if icon.last_used < now - 120: + evict_key = icon_key + break + # otherwise, evict the oldest + if oldest_time > icon.last_used: + oldest_time = icon.last_used + oldest_key = icon_key + + # If there's nothing specific to evict, try evicting + # the oldest thing + if not evict_key: + if not oldest_key: + break + evict_key = oldest_key + + self._cache_size -= self._icons[evict_key].data_size + del self._icons[evict_key] + + def get_icon(self, name, fill_color, stroke_color): + if not name: + return None + + if fill_color or stroke_color: + key = (name, fill_color, stroke_color) + else: + key = name + + # If we're over the cache limit, evict something from the cache + now = time.time() + self._cache_cleanup(key, now) + + if self._icons.has_key(key): + icon = self._icons[key] + icon.usage_count += 1 + icon.last_used = now + else: + icon = _IconCacheIcon(name, fill_color, stroke_color, now) + self._icons[key] = icon + self._cache_size += icon.data_size + return icon + + +class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem): + __gtype_name__ = 'CanvasIcon' + + __gproperties__ = { + 'icon-name' : (str, None, None, None, + gobject.PARAM_READWRITE), + 'xo-color' : (object, None, None, + gobject.PARAM_WRITABLE), + 'fill-color' : (object, None, None, + gobject.PARAM_READWRITE), + 'stroke-color' : (object, None, None, + gobject.PARAM_READWRITE), + 'size' : (int, None, None, 0, 1024, 0, + gobject.PARAM_READWRITE), + 'scale' : (int, None, None, 0, 1024, 0, + gobject.PARAM_READWRITE), + 'cache' : (bool, None, None, False, + gobject.PARAM_READWRITE), + 'active' : (bool, None, None, True, + gobject.PARAM_READWRITE), + 'badge-name' : (str, None, None, None, + gobject.PARAM_READWRITE) + } + + _cache = _IconCache() + + def __init__(self, **kwargs): + self._buffers = {} + self._cur_buffer = None + self._size = 0 + self._scale = 0 + self._fill_color = None + self._stroke_color = None + self._icon_name = None + self._cache = False + self._icon = None + self._active = True + self._palette = None + self._badge_name = None + self._badge_icon = None + + hippo.CanvasBox.__init__(self, **kwargs) + + self.connect_after('motion-notify-event', self._motion_notify_event_cb) + + def _clear_buffers(self): + icon_key = self._get_current_buffer_key(self._icon_name) + badge_key = None + if self._badge_name: + badge_key = self._get_current_buffer_key(self._badge_name) + for key in self._buffers.keys(): + if key != icon_key: + if not badge_key or (key != badge_key): + del self._buffers[key] + self._buffers = {} + + def do_set_property(self, pspec, value): + if pspec.name == 'icon-name': + if self._icon_name != value and not self._cache: + self._clear_buffers() + self._icon_name = value + self._icon = None + self.emit_paint_needed(0, 0, -1, -1) + elif pspec.name == 'xo-color': + self.props.fill_color = style.Color(value.get_fill_color()) + self.props.stroke_color = style.Color(value.get_stroke_color()) + elif pspec.name == 'fill-color': + if self._fill_color != value: + if not self._cache: + self._clear_buffers() + self._fill_color = value + self._icon = None + self._badge_icon = None + self.emit_paint_needed(0, 0, -1, -1) + elif pspec.name == 'stroke-color': + if self._stroke_color != value: + if not self._cache: + self._clear_buffers() + self._stroke_color = value + self._icon = None + self._badge_icon = None + self.emit_paint_needed(0, 0, -1, -1) + elif pspec.name == 'size': + if self._size != value and not self._cache: + self._clear_buffers() + self._size = value + self.emit_request_changed() + elif pspec.name == 'scale': + if self._scale != value and not self._cache: + self._clear_buffers() + self._scale = value + self.emit_request_changed() + elif pspec.name == 'cache': + self._cache = value + elif pspec.name == 'active': + if self._active != value: + if not self._cache: + self._clear_buffers() + self._active = value + self._icon = None + self.emit_paint_needed(0, 0, -1, -1) + elif pspec.name == 'badge-name': + if self._badge_name != value and not self._cache: + self._clear_buffers() + self._badge_name = value + self._badge_icon = None + self.emit_paint_needed(0, 0, -1, -1) + + def _choose_colors(self): + fill_color = None + stroke_color = None + if self._active: + if self._fill_color: + fill_color = self._fill_color.get_svg() + if self._stroke_color: + stroke_color = self._stroke_color.get_svg() + else: + stroke_color = color.ICON_STROKE_INACTIVE.get_svg() + if self._fill_color: + fill_color = self._fill_color.get_svg() + return [fill_color, stroke_color] + + def _get_icon_from_cache(self, name, icon): + if not icon: + cache = CanvasIcon._cache + + [fill_color, stroke_color] = self._choose_colors() + + icon = cache.get_icon(name, fill_color, stroke_color) + return icon + + def _get_icon(self): + self._icon = self._get_icon_from_cache(self._icon_name, self._icon) + return self._icon + + def _get_badge_icon(self): + self._badge_icon = self._get_icon_from_cache(self._badge_name, + self._badge_icon) + return self._badge_icon + + def _get_current_buffer_key(self, name): + [fill_color, stroke_color] = self._choose_colors() + return (name, fill_color, stroke_color, self._size) + + def do_get_property(self, pspec): + if pspec.name == 'size': + return self._size + elif pspec.name == 'icon-name': + return self._icon_name + elif pspec.name == 'fill-color': + return self._fill_color + elif pspec.name == 'stroke-color': + return self._stroke_color + elif pspec.name == 'cache': + return self._cache + elif pspec.name == 'active': + return self._active + elif pspec.name == 'badge-name': + return self._badge_name + elif pspec.name == 'scale': + return self._scale + + def _get_icon_size(self, icon): + if icon: + dimensions = icon.handle.get_dimension_data() + return int(dimensions[0]), int(dimensions[1]) + else: + return [0, 0] + + def _get_size(self, icon): + width, height = self._get_icon_size(icon) + if self._scale != 0: + width = int(width * self._scale) + height = int(height * self._scale) + elif self._size != 0: + width = height = self._size + + return [width, height] + + def _get_buffer(self, cr, name, icon, scale_factor=None): + """Return a cached cairo surface for the SVG icon, or if none exists, + create a new cairo surface with the right size.""" + buf = None + + key = self._get_current_buffer_key(name) + if self._buffers.has_key(key): + buf = self._buffers[key] + else: + [icon_w, icon_h] = self._get_icon_size(icon) + [target_w, target_h] = self._get_size(icon) + + if scale_factor: + target_w = int(target_w * scale_factor) + target_h = int(target_h * scale_factor) + + target = cr.get_target() + buf = target.create_similar(cairo.CONTENT_COLOR_ALPHA, + target_w, target_h) + ctx = cairo.Context(buf) + ctx.scale(float(target_w) / float(icon_w), + float(target_h) / float(icon_h)) + icon.handle.render_cairo(ctx) + + del ctx + self._buffers[key] = buf + + return buf + + def do_paint_below_children(self, cr, damaged_box): + icon = self._get_icon() + if icon is None: + return + + icon_buf = self._get_buffer(cr, self._icon_name, icon) + [width, height] = self.get_allocation() + icon_x = (width - icon_buf.get_width()) / 2 + icon_y = (height - icon_buf.get_height()) / 2 + + cr.set_source_surface(icon_buf, icon_x, icon_y) + cr.paint() + + if self._badge_name: + badge_icon = self._get_badge_icon() + if badge_icon: + badge_buf = self._get_buffer(cr, self._badge_name, badge_icon, _BADGE_SIZE) + badge_x = (icon_x + icon.badge_x * icon_buf.get_width() - + badge_buf.get_width() / 2) + badge_y = (icon_y + icon.badge_y * icon_buf.get_height() - + badge_buf.get_height() / 2) + cr.set_source_surface(badge_buf, badge_x, badge_y) + cr.paint() + + def do_get_content_width_request(self): + icon = self._get_icon() + [width, height] = self._get_size(icon) + if self._badge_name is not None: + # If the badge goes outside the bounding box, add space + # on *both* sides (to keep the main icon centered) + if icon.badge_x < 0.0: + width = int(width * 2 * (1.0 - icon.badge_x)) + elif icon.badge_x + _BADGE_SIZE > 1.0: + width = int(width * 2 * (icon.badge_x + _BADGE_SIZE - 1.0)) + return (width, width) + + def do_get_content_height_request(self, for_width): + icon = self._get_icon() + [width, height] = self._get_size(icon) + if self._badge_name is not None: + if icon.badge_y < 0.0: + height = int(height * 2 * (1.0 - icon.badge_y)) + elif icon.badge_y + _BADGE_SIZE > 1.0: + height = int(height * 2 * (icon.badge_y + _BADGE_SIZE - 1.0)) + return (height, height) + + def do_button_press_event(self, event): + self.emit_activated() + return True + + def _motion_notify_event_cb(self, button, event): + if event.detail == hippo.MOTION_DETAIL_ENTER: + self.prelight(True) + elif event.detail == hippo.MOTION_DETAIL_LEAVE: + self.prelight(False) + return False + + def prelight(self, enter): + """ + Override this method for adding prelighting behavior. + """ + pass + + def get_palette(self): + return self._palette + + def set_palette(self, palette): + self._palette = palette + if not self._palette.props.invoker: + self._palette.props.invoker = CanvasInvoker(self) + + def set_tooltip(self, text): + self.set_palette(Palette(text)) + + palette = property(get_palette, set_palette) + +def get_icon_state(base_name, perc): + step = 5 + strength = round(perc / step) * step + icon_theme = gtk.icon_theme_get_default() + + while strength <= 100: + icon_name = '%s-%03d' % (base_name, strength) + if icon_theme.has_icon(icon_name): + return 'theme:' + icon_name + + strength = strength + step diff --git a/sugar/graphics/iconbutton.py b/sugar/graphics/iconbutton.py index ba9fad8d..6489b344 100644 --- a/sugar/graphics/iconbutton.py +++ b/sugar/graphics/iconbutton.py @@ -24,7 +24,7 @@ import sys import gobject import hippo -from sugar.graphics.canvasicon import CanvasIcon +from sugar.graphics.icon import CanvasIcon from sugar.graphics import style class IconButton(CanvasIcon, hippo.CanvasItem): diff --git a/sugar/graphics/objectchooser.py b/sugar/graphics/objectchooser.py index f9290630..56a5a31b 100644 --- a/sugar/graphics/objectchooser.py +++ b/sugar/graphics/objectchooser.py @@ -24,9 +24,9 @@ import hippo from sugar.activity.bundle import Bundle from sugar.date import Date from sugar.graphics import style -from sugar.graphics.canvasicon import CanvasIcon +from sugar.graphics.icon import CanvasIcon from sugar.graphics.xocolor import XoColor -from sugar.graphics.canvasroundbox import CanvasRoundBox +from sugar.graphics.roundbox import CanvasRoundBox from sugar.datastore import datastore from sugar import activity from sugar.objects import objecttype diff --git a/sugar/graphics/canvasroundbox.py b/sugar/graphics/roundbox.py similarity index 100% rename from sugar/graphics/canvasroundbox.py rename to sugar/graphics/roundbox.py From 1cb214bc967f16f4fd2f1bdc5dec65b444e01ef6 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sat, 25 Aug 2007 13:18:30 +0200 Subject: [PATCH 06/36] Remove canvasicon module. --- sugar/graphics/canvasicon.py | 433 ----------------------------------- 1 file changed, 433 deletions(-) delete mode 100644 sugar/graphics/canvasicon.py diff --git a/sugar/graphics/canvasicon.py b/sugar/graphics/canvasicon.py deleted file mode 100644 index 36325514..00000000 --- a/sugar/graphics/canvasicon.py +++ /dev/null @@ -1,433 +0,0 @@ -# Copyright (C) 2006-2007 Red Hat, Inc. -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the -# Free Software Foundation, Inc., 59 Temple Place - Suite 330, -# Boston, MA 02111-1307, USA. - -import logging -import re - -import gobject -import gtk -import hippo -import rsvg -import cairo -import time - -from sugar.graphics.xocolor import XoColor -from sugar.graphics import style -from sugar.graphics.palette import Palette, CanvasInvoker - -_ICON_REQUEST_SIZE = 50 -_BADGE_SIZE = 0.45 - -class _IconCacheIcon: - def __init__(self, name, fill_color, stroke_color, now): - self.last_used = now - self.usage_count = 1 - self.badge_x = 1.0 - _BADGE_SIZE / 2 - self.badge_y = 1.0 - _BADGE_SIZE / 2 - - if name[0:6] == "theme:": - info = gtk.icon_theme_get_default().lookup_icon( - name[6:], _ICON_REQUEST_SIZE, 0) - if not info: - raise ValueError("Icon '" + name + "' not found.") - - fname = info.get_filename() - attach_points = info.get_attach_points() - if attach_points is not None: - self.badge_x = float(attach_points[0][0]) / _ICON_REQUEST_SIZE - self.badge_y = float(attach_points[0][1]) / _ICON_REQUEST_SIZE - del info - else: - fname = name - - self.handle = self._read_icon_data(fname, fill_color, stroke_color) - - def _read_icon_data(self, filename, fill_color, stroke_color): - icon_file = open(filename, 'r') - data = icon_file.read() - icon_file.close() - - if fill_color: - entity = '' % fill_color - data = re.sub('', entity, data) - - if stroke_color: - entity = '' % stroke_color - data = re.sub('', entity, data) - - self.data_size = len(data) - return rsvg.Handle(data=data) - -class _IconCache: - _CACHE_MAX = 50000 # in bytes - - def __init__(self): - self._icons = {} - self._cache_size = 0 - - def _cache_cleanup(self, key, now): - while self._cache_size > self._CACHE_MAX: - evict_key = None - oldest_key = None - oldest_time = now - for icon_key, icon in self._icons.items(): - # Don't evict the icon we are about to use if it's in the cache - if icon_key == key: - continue - - # evict large icons first - if icon.data_size > self._CACHE_MAX: - evict_key = icon_key - break - # evict older icons next; those used over 2 minutes ago - if icon.last_used < now - 120: - evict_key = icon_key - break - # otherwise, evict the oldest - if oldest_time > icon.last_used: - oldest_time = icon.last_used - oldest_key = icon_key - - # If there's nothing specific to evict, try evicting - # the oldest thing - if not evict_key: - if not oldest_key: - break - evict_key = oldest_key - - self._cache_size -= self._icons[evict_key].data_size - del self._icons[evict_key] - - def get_icon(self, name, fill_color, stroke_color): - if not name: - return None - - if fill_color or stroke_color: - key = (name, fill_color, stroke_color) - else: - key = name - - # If we're over the cache limit, evict something from the cache - now = time.time() - self._cache_cleanup(key, now) - - if self._icons.has_key(key): - icon = self._icons[key] - icon.usage_count += 1 - icon.last_used = now - else: - icon = _IconCacheIcon(name, fill_color, stroke_color, now) - self._icons[key] = icon - self._cache_size += icon.data_size - return icon - - -class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem): - __gtype_name__ = 'CanvasIcon' - - __gproperties__ = { - 'icon-name' : (str, None, None, None, - gobject.PARAM_READWRITE), - 'xo-color' : (object, None, None, - gobject.PARAM_WRITABLE), - 'fill-color' : (object, None, None, - gobject.PARAM_READWRITE), - 'stroke-color' : (object, None, None, - gobject.PARAM_READWRITE), - 'size' : (int, None, None, 0, 1024, 0, - gobject.PARAM_READWRITE), - 'scale' : (int, None, None, 0, 1024, 0, - gobject.PARAM_READWRITE), - 'cache' : (bool, None, None, False, - gobject.PARAM_READWRITE), - 'active' : (bool, None, None, True, - gobject.PARAM_READWRITE), - 'badge-name' : (str, None, None, None, - gobject.PARAM_READWRITE) - } - - _cache = _IconCache() - - def __init__(self, **kwargs): - self._buffers = {} - self._cur_buffer = None - self._size = 0 - self._scale = 0 - self._fill_color = None - self._stroke_color = None - self._icon_name = None - self._cache = False - self._icon = None - self._active = True - self._palette = None - self._badge_name = None - self._badge_icon = None - - hippo.CanvasBox.__init__(self, **kwargs) - - self.connect_after('motion-notify-event', self._motion_notify_event_cb) - - def _clear_buffers(self): - icon_key = self._get_current_buffer_key(self._icon_name) - badge_key = None - if self._badge_name: - badge_key = self._get_current_buffer_key(self._badge_name) - for key in self._buffers.keys(): - if key != icon_key: - if not badge_key or (key != badge_key): - del self._buffers[key] - self._buffers = {} - - def do_set_property(self, pspec, value): - if pspec.name == 'icon-name': - if self._icon_name != value and not self._cache: - self._clear_buffers() - self._icon_name = value - self._icon = None - self.emit_paint_needed(0, 0, -1, -1) - elif pspec.name == 'xo-color': - self.props.fill_color = style.Color(value.get_fill_color()) - self.props.stroke_color = style.Color(value.get_stroke_color()) - elif pspec.name == 'fill-color': - if self._fill_color != value: - if not self._cache: - self._clear_buffers() - self._fill_color = value - self._icon = None - self._badge_icon = None - self.emit_paint_needed(0, 0, -1, -1) - elif pspec.name == 'stroke-color': - if self._stroke_color != value: - if not self._cache: - self._clear_buffers() - self._stroke_color = value - self._icon = None - self._badge_icon = None - self.emit_paint_needed(0, 0, -1, -1) - elif pspec.name == 'size': - if self._size != value and not self._cache: - self._clear_buffers() - self._size = value - self.emit_request_changed() - elif pspec.name == 'scale': - if self._scale != value and not self._cache: - self._clear_buffers() - self._scale = value - self.emit_request_changed() - elif pspec.name == 'cache': - self._cache = value - elif pspec.name == 'active': - if self._active != value: - if not self._cache: - self._clear_buffers() - self._active = value - self._icon = None - self.emit_paint_needed(0, 0, -1, -1) - elif pspec.name == 'badge-name': - if self._badge_name != value and not self._cache: - self._clear_buffers() - self._badge_name = value - self._badge_icon = None - self.emit_paint_needed(0, 0, -1, -1) - - def _choose_colors(self): - fill_color = None - stroke_color = None - if self._active: - if self._fill_color: - fill_color = self._fill_color.get_svg() - if self._stroke_color: - stroke_color = self._stroke_color.get_svg() - else: - stroke_color = color.ICON_STROKE_INACTIVE.get_svg() - if self._fill_color: - fill_color = self._fill_color.get_svg() - return [fill_color, stroke_color] - - def _get_icon_from_cache(self, name, icon): - if not icon: - cache = CanvasIcon._cache - - [fill_color, stroke_color] = self._choose_colors() - - icon = cache.get_icon(name, fill_color, stroke_color) - return icon - - def _get_icon(self): - self._icon = self._get_icon_from_cache(self._icon_name, self._icon) - return self._icon - - def _get_badge_icon(self): - self._badge_icon = self._get_icon_from_cache(self._badge_name, - self._badge_icon) - return self._badge_icon - - def _get_current_buffer_key(self, name): - [fill_color, stroke_color] = self._choose_colors() - return (name, fill_color, stroke_color, self._size) - - def do_get_property(self, pspec): - if pspec.name == 'size': - return self._size - elif pspec.name == 'icon-name': - return self._icon_name - elif pspec.name == 'fill-color': - return self._fill_color - elif pspec.name == 'stroke-color': - return self._stroke_color - elif pspec.name == 'cache': - return self._cache - elif pspec.name == 'active': - return self._active - elif pspec.name == 'badge-name': - return self._badge_name - elif pspec.name == 'scale': - return self._scale - - def _get_icon_size(self, icon): - if icon: - dimensions = icon.handle.get_dimension_data() - return int(dimensions[0]), int(dimensions[1]) - else: - return [0, 0] - - def _get_size(self, icon): - width, height = self._get_icon_size(icon) - if self._scale != 0: - width = int(width * self._scale) - height = int(height * self._scale) - elif self._size != 0: - width = height = self._size - - return [width, height] - - def _get_buffer(self, cr, name, icon, scale_factor=None): - """Return a cached cairo surface for the SVG icon, or if none exists, - create a new cairo surface with the right size.""" - buf = None - - key = self._get_current_buffer_key(name) - if self._buffers.has_key(key): - buf = self._buffers[key] - else: - [icon_w, icon_h] = self._get_icon_size(icon) - [target_w, target_h] = self._get_size(icon) - - if scale_factor: - target_w = int(target_w * scale_factor) - target_h = int(target_h * scale_factor) - - target = cr.get_target() - buf = target.create_similar(cairo.CONTENT_COLOR_ALPHA, - target_w, target_h) - ctx = cairo.Context(buf) - ctx.scale(float(target_w) / float(icon_w), - float(target_h) / float(icon_h)) - icon.handle.render_cairo(ctx) - - del ctx - self._buffers[key] = buf - - return buf - - def do_paint_below_children(self, cr, damaged_box): - icon = self._get_icon() - if icon is None: - return - - icon_buf = self._get_buffer(cr, self._icon_name, icon) - [width, height] = self.get_allocation() - icon_x = (width - icon_buf.get_width()) / 2 - icon_y = (height - icon_buf.get_height()) / 2 - - cr.set_source_surface(icon_buf, icon_x, icon_y) - cr.paint() - - if self._badge_name: - badge_icon = self._get_badge_icon() - if badge_icon: - badge_buf = self._get_buffer(cr, self._badge_name, badge_icon, _BADGE_SIZE) - badge_x = (icon_x + icon.badge_x * icon_buf.get_width() - - badge_buf.get_width() / 2) - badge_y = (icon_y + icon.badge_y * icon_buf.get_height() - - badge_buf.get_height() / 2) - cr.set_source_surface(badge_buf, badge_x, badge_y) - cr.paint() - - def do_get_content_width_request(self): - icon = self._get_icon() - [width, height] = self._get_size(icon) - if self._badge_name is not None: - # If the badge goes outside the bounding box, add space - # on *both* sides (to keep the main icon centered) - if icon.badge_x < 0.0: - width = int(width * 2 * (1.0 - icon.badge_x)) - elif icon.badge_x + _BADGE_SIZE > 1.0: - width = int(width * 2 * (icon.badge_x + _BADGE_SIZE - 1.0)) - return (width, width) - - def do_get_content_height_request(self, for_width): - icon = self._get_icon() - [width, height] = self._get_size(icon) - if self._badge_name is not None: - if icon.badge_y < 0.0: - height = int(height * 2 * (1.0 - icon.badge_y)) - elif icon.badge_y + _BADGE_SIZE > 1.0: - height = int(height * 2 * (icon.badge_y + _BADGE_SIZE - 1.0)) - return (height, height) - - def do_button_press_event(self, event): - self.emit_activated() - return True - - def _motion_notify_event_cb(self, button, event): - if event.detail == hippo.MOTION_DETAIL_ENTER: - self.prelight(True) - elif event.detail == hippo.MOTION_DETAIL_LEAVE: - self.prelight(False) - return False - - def prelight(self, enter): - """ - Override this method for adding prelighting behavior. - """ - pass - - def get_palette(self): - return self._palette - - def set_palette(self, palette): - self._palette = palette - if not self._palette.props.invoker: - self._palette.props.invoker = CanvasInvoker(self) - - def set_tooltip(self, text): - self.set_palette(Palette(text)) - - palette = property(get_palette, set_palette) - -def get_icon_state(base_name, perc): - step = 5 - strength = round(perc / step) * step - icon_theme = gtk.icon_theme_get_default() - - while strength <= 100: - icon_name = '%s-%03d' % (base_name, strength) - if icon_theme.has_icon(icon_name): - return 'theme:' + icon_name - - strength = strength + step From 1938fb13f048b62bbbef1d1d4045bf655b79c91f Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sat, 25 Aug 2007 18:26:59 +0200 Subject: [PATCH 07/36] Rework the Icon implementation by splitting out the icon rendering part. The plan is to share this code with CanvasIcon once it has all the necessary features. Also cleanup the API by just reusing gtk.Image icon_name and file properties. --- sugar/graphics/icon.py | 238 ++++++++++++++++++----------- sugar/graphics/menuitem.py | 2 +- sugar/graphics/radiotoolbutton.py | 2 +- sugar/graphics/toggletoolbutton.py | 2 +- sugar/graphics/toolbutton.py | 2 +- tests/graphics/iconwidget.py | 45 ++++++ 6 files changed, 199 insertions(+), 92 deletions(-) create mode 100644 tests/graphics/iconwidget.py diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index 13f60656..81c28da6 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -31,6 +31,113 @@ from sugar.graphics.xocolor import XoColor from sugar.graphics import style from sugar.graphics.palette import Palette, CanvasInvoker +_svg_loader = None + +def _get_svg_loader(): + global _svg_loader + if _svg_loader == None: + _svg_loader = _SVGLoader() + return _svg_loader + +class _SVGLoader(object): + def load(self, file_name, entities): + icon_file = open(file_name, 'r') + data = icon_file.read() + icon_file.close() + + for entity, value in entities.items(): + xml = '' % (entity, value) + data = re.sub('' % entity, xml, data) + + return rsvg.Handle(data=data) + +class _IconBuffer(gobject.GObject): + def __init__(self): + gobject.GObject.__init__(self) + + self._svg_loader = _get_svg_loader() + self._surface = None + + self.icon_name = None + self.file_name = None + self.fill_color = None + self.stroke_color = None + self.width = None + self.height = None + + def _load_svg(self, file_name): + entities = {} + if self.fill_color: + entities['fill_color'] = self.fill_color + if self.stroke_color: + entities['stroke_color'] = self.stroke_color + + return self._svg_loader.load(file_name, entities) + + def _load_pixbuf(self, file_name): + if self.width is None or self.height is None: + pixbuf = gtk.gdk.pixbuf_new_from_file(file_name) + else: + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size( + file_name, self.width, self.height) + return hippo.cairo_surface_from_gdk_pixbuf(pixbuf) + + def _get_file_name(self): + file_name = None + + if self.file_name: + return self.file_name + + if self.icon_name: + theme = gtk.icon_theme_get_default() + + size = 0 + if self.width != None: + size = self.width + + info = theme.lookup_icon(self.icon_name, size, 0) + if info: + return info.get_filename() + + return None + + def get_surface(self): + if self._surface is not None: + return self._surface + + file_name = self._get_file_name() + if file_name is None: + return None + + if file_name.endswith('.svg'): + handle = self._load_svg(file_name) + + dimensions = handle.get_dimension_data() + icon_width = int(dimensions[0]) + icon_height = int(dimensions[1]) + + if self.width is not None and self.height is not None: + target_width = self.width + target_height = self.height + else: + target_width = icon_width + target_height = icon_height + + self._surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, + target_width, target_height) + + context = cairo.Context(self._surface) + context.scale(float(target_width) / float(icon_width), + float(target_height) / float(icon_height)) + handle.render_cairo(context) + else: + self._surface = self._load_pixbuf(file_name) + + return self._surface + + def invalidate(self): + self._surface = None + class Icon(gtk.Image): __gtype_name__ = 'SugarIcon' @@ -43,122 +150,77 @@ class Icon(gtk.Image): gobject.PARAM_READWRITE) } - def __init__(self, name, **kwargs): - self._constructed = False - self._fill_color = None - self._stroke_color = None - self._icon_name = name - self._theme = gtk.icon_theme_get_default() - self._data = None + def __init__(self, **kwargs): + self._buffer = _IconBuffer() gobject.GObject.__init__(self, **kwargs) - self._constructed = True - self._update_icon() + def _sync_image_properties(self): + if self._buffer.icon_name != self.props.icon_name: + self._buffer.icon_name = self.props.icon_name + self._buffer.invalidate() - def _get_pixbuf(self, data, width, height): - loader = gtk.gdk.PixbufLoader('svg') - loader.set_size(width, height) - loader.write(data, len(data)) - loader.close() - return loader.get_pixbuf() + if self._buffer.file_name != self.props.file: + self._buffer.file_name = self.props.file + self._buffer.invalidate() - def _read_icon_data(self, icon_name): - filename = self._get_real_name(icon_name) - icon_file = open(filename, 'r') - data = icon_file.read() - icon_file.close() + width, height = gtk.icon_size_lookup(self.props.icon_size) + if self._buffer.width != width and self._buffer.height != height: + self._buffer.width = width + self._buffer.height = height + self._buffer.invalidate() - return data + def _icon_size_changed_cb(self, image, pspec): + self._buffer.icon_size = self.props.icon_size + self._buffer.invalidate() - def _update_normal_icon(self): - icon_theme = gtk.icon_theme_get_for_screen(self.get_screen()) - icon_set = gtk.IconSet() + def _icon_name_changed_cb(self, image, pspec): + self._buffer.icon_name = self.props.icon_name + self._buffer.invalidate() - if icon_theme.has_icon(self._icon_name) or os.path.exists(self._icon_name): - source = gtk.IconSource() + def _file_changed_cb(self, image, pspec): + self._buffer.file_name = self.props.file + self._buffer.invalidate() - if os.path.exists(self._icon_name): - source.set_filename(self._icon_name) - else: - source.set_icon_name(self._icon_name) + def _update_buffer_size(self): + width, height = gtk.icon_size_lookup(self.props.icon_size) - icon_set.add_source(source) + self._buffer.width = width + self._buffer.height = height - inactive_name = self._icon_name + '-inactive' - if icon_theme.has_icon(inactive_name) or os.path.exists(inactive_name): - source = gtk.IconSource() + self._buffer.invalidate() - if os.path.exists(inactive_name): - source.set_filename(inactive_name) - else: - source.set_icon_name(inactive_name) + def do_expose_event(self, event): + self._sync_image_properties() - source.set_state(gtk.STATE_INSENSITIVE) - icon_set.add_source(source) + surface = self._buffer.get_surface() + if surface is not None: + cr = self.window.cairo_create() - self.props.icon_set = icon_set + x = self.allocation.x + y = self.allocation.y - def _update_icon(self): - if not self._constructed: - return - - if not self._fill_color and not self._stroke_color: - self._update_normal_icon() - return - - if not self._data: - data = self._read_icon_data(self._icon_name) - else: - data = self._data - - if self._fill_color: - entity = '' % self._fill_color - data = re.sub('', entity, data) - - if self._stroke_color: - entity = '' % self._stroke_color - data = re.sub('', entity, data) - - self._data = data - - # Redraw pixbuf - [w, h] = gtk.icon_size_lookup(self.props.icon_size) - pixbuf = self._get_pixbuf(self._data, w, h) - self.set_from_pixbuf(pixbuf) - - def _get_real_name(self, name): - if os.path.exists(name): - return name - - info = self._theme.lookup_icon(name, self.props.icon_size, 0) - if not info: - raise ValueError("Icon '" + name + "' not found.") - fname = info.get_filename() - del info - return fname + cr.set_source_surface(surface, x, y) + cr.paint() def do_set_property(self, pspec, value): if pspec.name == 'xo-color': self.props.fill_color = value.get_fill_color() self.props.stroke_color = value.get_stroke_color() elif pspec.name == 'fill-color': - self._fill_color = value - self._update_icon() + self._buffer.fill_color = value + self._buffer.invalidate() elif pspec.name == 'stroke-color': - self._stroke_color = value - self._update_icon() + self._buffer.fill_color = value + self._buffer.invalidate() else: gtk.Image.do_set_property(self, pspec, value) - if pspec.name == 'icon-size': - self._update_icon() - def do_get_property(self, pspec): if pspec.name == 'fill-color': - return self._fill_color + return self._buffer.fill_color elif pspec.name == 'stroke-color': - return self._stroke_color + return self._buffer.stroke_color else: return gtk.Image.do_get_property(self, pspec) diff --git a/sugar/graphics/menuitem.py b/sugar/graphics/menuitem.py index db4a2936..5b457a53 100644 --- a/sugar/graphics/menuitem.py +++ b/sugar/graphics/menuitem.py @@ -22,7 +22,7 @@ class MenuItem(gtk.ImageMenuItem): def __init__(self, text_label, icon_name=None): gtk.ImageMenuItem.__init__(self, text_label) if icon_name: - icon = Icon(icon_name, icon_size=gtk.ICON_SIZE_MENU) + icon = Icon(icon_name=icon_name, icon_size=gtk.ICON_SIZE_MENU) self.set_image(icon) icon.show() diff --git a/sugar/graphics/radiotoolbutton.py b/sugar/graphics/radiotoolbutton.py index cf0cc64a..a8269ddd 100644 --- a/sugar/graphics/radiotoolbutton.py +++ b/sugar/graphics/radiotoolbutton.py @@ -31,7 +31,7 @@ class RadioToolButton(gtk.RadioToolButton): self.set_named_icon(named_icon) def set_named_icon(self, named_icon): - icon = Icon(named_icon, + icon = Icon(icon_name=named_icon, xo_color=self._xo_color, icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR) self.set_icon_widget(icon) diff --git a/sugar/graphics/toggletoolbutton.py b/sugar/graphics/toggletoolbutton.py index a83bebcc..4fbfa662 100644 --- a/sugar/graphics/toggletoolbutton.py +++ b/sugar/graphics/toggletoolbutton.py @@ -29,7 +29,7 @@ class ToggleToolButton(gtk.ToggleToolButton): self.set_named_icon(named_icon) def set_named_icon(self, named_icon): - icon = Icon(named_icon) + icon = Icon(icon_name=named_icon) self.set_icon_widget(icon) icon.show() diff --git a/sugar/graphics/toolbutton.py b/sugar/graphics/toolbutton.py index 2b90fd40..4db7d90a 100644 --- a/sugar/graphics/toolbutton.py +++ b/sugar/graphics/toolbutton.py @@ -33,7 +33,7 @@ class ToolButton(gtk.ToolButton): self.connect('clicked', self._button_clicked_cb) def set_icon(self, icon_name): - icon = Icon(icon_name) + icon = Icon(icon_name=icon_name) self.set_icon_widget(icon) icon.show() diff --git a/tests/graphics/iconwidget.py b/tests/graphics/iconwidget.py new file mode 100644 index 00000000..829ab854 --- /dev/null +++ b/tests/graphics/iconwidget.py @@ -0,0 +1,45 @@ +# Copyright (C) 2007, Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +""" +Test the sugar.graphics.icon.Icon widget. +""" + +import gtk + +from sugar.graphics.icon import Icon +from sugar.graphics.xocolor import XoColor + +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() + +test.show() + +if __name__ == "__main__": + common.main(test) From ae545425244a51e5aa26063318ae23662444ec45 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sun, 26 Aug 2007 00:26:11 +0200 Subject: [PATCH 08/36] Add badge support to IconBuffer --- sugar/graphics/icon.py | 80 ++++++++++++++++++++++++++++++------ tests/graphics/iconwidget.py | 6 +++ 2 files changed, 74 insertions(+), 12 deletions(-) diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index 81c28da6..acb70e7f 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -31,6 +31,8 @@ from sugar.graphics.xocolor import XoColor from sugar.graphics import style from sugar.graphics.palette import Palette, CanvasInvoker +_BADGE_SIZE = 0.45 + _svg_loader = None def _get_svg_loader(): @@ -62,6 +64,7 @@ class _IconBuffer(gobject.GObject): self.file_name = None self.fill_color = None self.stroke_color = None + self.badge_name = None self.width = None self.height = None @@ -82,6 +85,12 @@ class _IconBuffer(gobject.GObject): file_name, self.width, self.height) return hippo.cairo_surface_from_gdk_pixbuf(pixbuf) + def _get_icon_size_request(self): + if self.width != None: + return self.width + else: + return 50 + def _get_file_name(self): file_name = None @@ -90,17 +99,53 @@ class _IconBuffer(gobject.GObject): if self.icon_name: theme = gtk.icon_theme_get_default() - - size = 0 - if self.width != None: - size = self.width - - info = theme.lookup_icon(self.icon_name, size, 0) + size_request = self._get_icon_size_request() + info = theme.lookup_icon(self.icon_name, size_request, 0) if info: return info.get_filename() return None + def _render_badge(self, surface): + context = cairo.Context(surface) + theme = gtk.icon_theme_get_default() + + size_request = self._get_icon_size_request() + icon_info = theme.lookup_icon(self.icon_name, size_request, 0) + if not icon_info or not icon_info.get_attach_points(): + logging.info( + 'Badge attach points not found, icon %s.' % self.icon_name) + return + + attach_points = icon_info.get_attach_points() + attach_x = float(attach_points[0][0]) / size_request + attach_y = float(attach_points[0][1]) / size_request + + badge_size = int(_BADGE_SIZE * surface.get_width()) + badge_x = attach_x * surface.get_width() - badge_size / 2 + badge_y = attach_y * surface.get_height() - badge_size / 2 + + badge_info = theme.lookup_icon(self.badge_name, badge_size, 0) + if not badge_info: + logging.info('Badge not found, %s.' % self.badge_name) + return + + badge_file_name = badge_info.get_filename() + if badge_file_name.endswith('.svg'): + handle = self._svg_loader.load(badge_file_name, {}) + + context.translate(badge_x, badge_y) + scale = float(badge_size) / float(badge_info.get_base_size()) + context.scale(scale, scale) + + handle.render_cairo(context) + else: + buf = gtk.gdk.pixbuf_new_from_file_at_size( + badge_file_name, badge_size, badge_size) + surface = hippo.cairo_surface_from_gdk_pixbuf(buf) + context.set_source_surface(badge_buf, badge_x, badge_y) + context.paint() + def get_surface(self): if self._surface is not None: return self._surface @@ -123,17 +168,22 @@ class _IconBuffer(gobject.GObject): target_width = icon_width target_height = icon_height - self._surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, - target_width, target_height) + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, + target_width, target_height) - context = cairo.Context(self._surface) + context = cairo.Context(surface) context.scale(float(target_width) / float(icon_width), float(target_height) / float(icon_height)) handle.render_cairo(context) else: - self._surface = self._load_pixbuf(file_name) + surface = self._load_pixbuf(file_name) - return self._surface + if self.badge_name: + self._render_badge(surface) + + self._surface = surface + + return surface def invalidate(self): self._surface = None @@ -147,6 +197,8 @@ class Icon(gtk.Image): 'fill-color' : (object, None, None, gobject.PARAM_READWRITE), 'stroke-color' : (object, None, None, + gobject.PARAM_READWRITE), + 'badge-name' : (str, None, None, None, gobject.PARAM_READWRITE) } @@ -213,6 +265,9 @@ class Icon(gtk.Image): elif pspec.name == 'stroke-color': self._buffer.fill_color = value self._buffer.invalidate() + elif pspec.name == 'badge-name': + self._buffer.badge_name = value + self._buffer.invalidate() else: gtk.Image.do_set_property(self, pspec, value) @@ -221,11 +276,12 @@ class Icon(gtk.Image): return self._buffer.fill_color elif pspec.name == 'stroke-color': return self._buffer.stroke_color + elif pspec.name == 'badge-name': + return self._buffer.badge_name else: return gtk.Image.do_get_property(self, pspec) _ICON_REQUEST_SIZE = 50 -_BADGE_SIZE = 0.45 class _IconCacheIcon: def __init__(self, name, fill_color, stroke_color, now): diff --git a/tests/graphics/iconwidget.py b/tests/graphics/iconwidget.py index 829ab854..22d9276d 100644 --- a/tests/graphics/iconwidget.py +++ b/tests/graphics/iconwidget.py @@ -39,6 +39,12 @@ icon = Icon(icon_name='computer-xo', 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() + test.show() if __name__ == "__main__": From 6432dcfb0e769bef5f461a6c6d7dff693a78a5ad Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sun, 26 Aug 2007 01:57:48 +0200 Subject: [PATCH 09/36] More work on IconBuffer badges. --- sugar/graphics/icon.py | 161 ++++++++++++++++++++++------------------- 1 file changed, 86 insertions(+), 75 deletions(-) diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index acb70e7f..c8f90510 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -53,10 +53,14 @@ class _SVGLoader(object): return rsvg.Handle(data=data) -class _IconBuffer(gobject.GObject): +class _IconInfo(object): def __init__(self): - gobject.GObject.__init__(self) + self.file_name = None + self.attach_x = 0 + self.attach_y = 0 +class _IconBuffer(object): + def __init__(self): self._svg_loader = _get_svg_loader() self._surface = None @@ -77,109 +81,116 @@ class _IconBuffer(gobject.GObject): return self._svg_loader.load(file_name, entities) - def _load_pixbuf(self, file_name): - if self.width is None or self.height is None: - pixbuf = gtk.gdk.pixbuf_new_from_file(file_name) - else: - pixbuf = gtk.gdk.pixbuf_new_from_file_at_size( - file_name, self.width, self.height) - return hippo.cairo_surface_from_gdk_pixbuf(pixbuf) - def _get_icon_size_request(self): if self.width != None: return self.width else: return 50 - def _get_file_name(self): - file_name = None + def _get_attach_points(self, info, size_request): + attach_points = info.get_attach_points() + + if attach_points: + attach_x = float(attach_points[0][0]) / size_request + attach_y = float(attach_points[0][1]) / size_request + else: + attach_x = attach_y = 0 + + return attach_x, attach_y + + def _get_icon_info(self): + icon_info = _IconInfo() if self.file_name: - return self.file_name - - if self.icon_name: + icon_info.file_name = self.file_name + elif self.icon_name: theme = gtk.icon_theme_get_default() - size_request = self._get_icon_size_request() - info = theme.lookup_icon(self.icon_name, size_request, 0) + + size = 50 + if self.width != None: + size = self.width + + info = theme.lookup_icon(self.icon_name, size, 0) if info: - return info.get_filename() + attach_x, attach_y = self._get_attach_points(info, size) - return None + icon_info.file_name = info.get_filename() + icon_info.attach_x = attach_x + icon_info.attach_y = attach_y - def _render_badge(self, surface): - context = cairo.Context(surface) - theme = gtk.icon_theme_get_default() - - size_request = self._get_icon_size_request() - icon_info = theme.lookup_icon(self.icon_name, size_request, 0) - if not icon_info or not icon_info.get_attach_points(): - logging.info( - 'Badge attach points not found, icon %s.' % self.icon_name) - return - - attach_points = icon_info.get_attach_points() - attach_x = float(attach_points[0][0]) / size_request - attach_y = float(attach_points[0][1]) / size_request - - badge_size = int(_BADGE_SIZE * surface.get_width()) - badge_x = attach_x * surface.get_width() - badge_size / 2 - badge_y = attach_y * surface.get_height() - badge_size / 2 - - badge_info = theme.lookup_icon(self.badge_name, badge_size, 0) - if not badge_info: - logging.info('Badge not found, %s.' % self.badge_name) - return - - badge_file_name = badge_info.get_filename() - if badge_file_name.endswith('.svg'): - handle = self._svg_loader.load(badge_file_name, {}) - - context.translate(badge_x, badge_y) - scale = float(badge_size) / float(badge_info.get_base_size()) - context.scale(scale, scale) - - handle.render_cairo(context) - else: - buf = gtk.gdk.pixbuf_new_from_file_at_size( - badge_file_name, badge_size, badge_size) - surface = hippo.cairo_surface_from_gdk_pixbuf(buf) - context.set_source_surface(badge_buf, badge_x, badge_y) - context.paint() + return icon_info def get_surface(self): if self._surface is not None: return self._surface - file_name = self._get_file_name() - if file_name is None: + icon_info = self._get_icon_info() + if icon_info.file_name is None: return None - if file_name.endswith('.svg'): - handle = self._load_svg(file_name) + is_svg = icon_info.file_name.endswith('.svg') + if is_svg: + handle = self._load_svg(icon_info.file_name) dimensions = handle.get_dimension_data() icon_width = int(dimensions[0]) icon_height = int(dimensions[1]) + else: + pixbuf = gtk.gdk.pixbuf_new_from_file(file_name) + icon_width = surface.get_width() + icon_height = surface.get_height() - if self.width is not None and self.height is not None: - target_width = self.width - target_height = self.height - else: - target_width = icon_width - target_height = icon_height + badge_size = int(_BADGE_SIZE * icon_width) + badge_x = icon_info.attach_x * icon_width - badge_size / 2 + badge_y = icon_info.attach_y * icon_height - badge_size / 2 - surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, - target_width, target_height) + icon_padding = 0 + if self.badge_name: + if badge_x < 0 or badge_y < 0: + icon_padding = max(-badge_x, -badge_y) + elif badge_x + badge_size > icon_width or \ + badge_y + badge_size > icon_height: + x_padding = icon_width - badge_x - badge_size + y_padding = icon_height - badge_y - badge_size + icon_padding = max(x_padding, y_padding) - context = cairo.Context(surface) - context.scale(float(target_width) / float(icon_width), - float(target_height) / float(icon_height)) + if self.width is not None and self.height is not None: + target_width = self.width + target_height = self.height + else: + target_width = icon_width + target_height = icon_height + + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, + target_width, target_height) + context = cairo.Context(surface) + + context.translate(icon_padding, icon_padding) + context.scale(float(target_width) / (icon_width + icon_padding * 2), + float(target_height) / (icon_height + icon_padding * 2)) + + if is_svg: + total_icon_width = icon_width + icon_padding handle.render_cairo(context) else: - surface = self._load_pixbuf(file_name) + pixbuf_surface = hippo.cairo_surface_from_gdk_pixbuf(pixbuf) + context.set_source_surface(pixbuf_surface, 0, 0) + context.paint() if self.badge_name: - self._render_badge(surface) + context.translate(badge_x, badge_y) + theme = gtk.icon_theme_get_default() + badge_info = theme.lookup_icon(self.badge_name, badge_size, 0) + if badge_info: + badge_file_name = badge_info.get_filename() + if badge_file_name.endswith('.svg'): + handle = self._svg_loader.load(badge_file_name, {}) + handle.render_cairo(context) + else: + pixbuf = gtk.gdk.pixbuf_new_from_file(badge_file_name) + surface = hippo.cairo_surface_from_gdk_pixbuf(pixbuf) + context.set_source_surface(surface, badge_x, badge_y) + context.paint() self._surface = surface From 05f2722d90a7f6e1289a78dd0775e4bde7bc8efa Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sun, 26 Aug 2007 02:24:45 +0200 Subject: [PATCH 10/36] Get badges positioning right. --- sugar/graphics/icon.py | 61 +++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index c8f90510..364d376e 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -81,12 +81,6 @@ class _IconBuffer(object): return self._svg_loader.load(file_name, entities) - def _get_icon_size_request(self): - if self.width != None: - return self.width - else: - return 50 - def _get_attach_points(self, info, size_request): attach_points = info.get_attach_points() @@ -120,6 +114,30 @@ class _IconBuffer(object): return icon_info + def _draw_badge(self, context, size): + 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._svg_loader.load(badge_file_name, {}) + handle.render_cairo(context) + 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() + + def _get_size(self, icon_width, icon_height): + if self.width is not None and self.height is not None: + width = self.width + height = self.height + else: + width = icon_width + height = icon_height + + return width, height + def get_surface(self): if self._surface is not None: return self._surface @@ -154,23 +172,16 @@ class _IconBuffer(object): y_padding = icon_height - badge_y - badge_size icon_padding = max(x_padding, y_padding) - if self.width is not None and self.height is not None: - target_width = self.width - target_height = self.height - else: - target_width = icon_width - target_height = icon_height + width, height = self._get_size(icon_width, icon_height) + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) - surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, - target_width, target_height) context = cairo.Context(surface) + context.scale(float(width) / (icon_width + icon_padding * 2), + float(height) / (icon_height + icon_padding * 2)) + context.save() context.translate(icon_padding, icon_padding) - context.scale(float(target_width) / (icon_width + icon_padding * 2), - float(target_height) / (icon_height + icon_padding * 2)) - if is_svg: - total_icon_width = icon_width + icon_padding handle.render_cairo(context) else: pixbuf_surface = hippo.cairo_surface_from_gdk_pixbuf(pixbuf) @@ -178,19 +189,9 @@ class _IconBuffer(object): context.paint() if self.badge_name: + context.restore() context.translate(badge_x, badge_y) - theme = gtk.icon_theme_get_default() - badge_info = theme.lookup_icon(self.badge_name, badge_size, 0) - if badge_info: - badge_file_name = badge_info.get_filename() - if badge_file_name.endswith('.svg'): - handle = self._svg_loader.load(badge_file_name, {}) - handle.render_cairo(context) - else: - pixbuf = gtk.gdk.pixbuf_new_from_file(badge_file_name) - surface = hippo.cairo_surface_from_gdk_pixbuf(pixbuf) - context.set_source_surface(surface, badge_x, badge_y) - context.paint() + self._draw_badge(context, badge_size) self._surface = surface From 65871156f5ae87e757536012087e77a81b2541f8 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sun, 26 Aug 2007 02:59:05 +0200 Subject: [PATCH 11/36] Cleanups --- sugar/graphics/icon.py | 51 +++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index 364d376e..c53d34bd 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -59,6 +59,13 @@ class _IconInfo(object): self.attach_x = 0 self.attach_y = 0 +class _BadgeInfo(object): + def __init__(self): + self.attach_x = 0 + self.attach_y = 0 + self.size = 0 + self.icon_padding = 0 + class _IconBuffer(object): def __init__(self): self._svg_loader = _get_svg_loader() @@ -138,6 +145,25 @@ class _IconBuffer(object): return width, height + def _get_badge_info(self, icon_info, icon_width, icon_height): + info = _BadgeInfo() + if self.badge_name is None: + return info + + info.size = int(_BADGE_SIZE * icon_width) + info.attach_x = icon_info.attach_x * icon_width - info.size / 2 + info.attach_y = icon_info.attach_y * icon_height - info.size / 2 + + if info.attach_x < 0 or info.attach_y < 0: + info.icon_padding = max(-info.attach_x, -info.attach_y) + elif info.attach_x + info.size > icon_width or \ + info.attach_y + info.size > icon_height: + x_padding = icon_width - info.attach_x - info.size + y_padding = icon_height - info.attach_y - info.size + info.icon_padding = max(x_padding, y_padding) + + return info + def get_surface(self): if self._surface is not None: return self._surface @@ -158,29 +184,18 @@ class _IconBuffer(object): icon_width = surface.get_width() icon_height = surface.get_height() - badge_size = int(_BADGE_SIZE * icon_width) - badge_x = icon_info.attach_x * icon_width - badge_size / 2 - badge_y = icon_info.attach_y * icon_height - badge_size / 2 - - icon_padding = 0 - if self.badge_name: - if badge_x < 0 or badge_y < 0: - icon_padding = max(-badge_x, -badge_y) - elif badge_x + badge_size > icon_width or \ - badge_y + badge_size > icon_height: - x_padding = icon_width - badge_x - badge_size - y_padding = icon_height - badge_y - badge_size - icon_padding = max(x_padding, y_padding) + badge_info = self._get_badge_info(icon_info, icon_width, icon_height) width, height = self._get_size(icon_width, icon_height) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) context = cairo.Context(surface) - context.scale(float(width) / (icon_width + icon_padding * 2), - float(height) / (icon_height + icon_padding * 2)) + padding = badge_info.icon_padding + context.scale(float(width) / (icon_width + padding * 2), + float(height) / (icon_height + padding * 2)) context.save() - context.translate(icon_padding, icon_padding) + context.translate(padding, padding) if is_svg: handle.render_cairo(context) else: @@ -190,8 +205,8 @@ class _IconBuffer(object): if self.badge_name: context.restore() - context.translate(badge_x, badge_y) - self._draw_badge(context, badge_size) + context.translate(badge_info.attach_x, badge_info.attach_y) + self._draw_badge(context, badge_info.size) self._surface = surface From a8938ab8ba7aa15254c32f5df9c35f7b99fda315 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sun, 26 Aug 2007 12:08:50 +0200 Subject: [PATCH 12/36] Add an svg cache for SVGLoader --- sugar/graphics/icon.py | 69 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index c53d34bd..ce848390 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -41,17 +41,76 @@ def _get_svg_loader(): _svg_loader = _SVGLoader() return _svg_loader +class _SVGIcon(object): + def __init__(self, data): + self.data = data + self.data_size = len(data) + self.last_used = time.time() + class _SVGLoader(object): + CACHE_MAX_SIZE = 50000 + CACHE_MAX_ICON_SIZE = 5000 + + def __init__(self): + self._cache = {} + self._cache_size = 0 + def load(self, file_name, entities): - icon_file = open(file_name, 'r') - data = icon_file.read() - icon_file.close() + icon = self._get_icon(file_name) for entity, value in entities.items(): xml = '' % (entity, value) - data = re.sub('' % entity, xml, data) + icon.data = re.sub('' % entity, xml, icon.data) - return rsvg.Handle(data=data) + return rsvg.Handle(data=icon.data) + + def _get_icon(self, file_name): + if self._cache.has_key(file_name): + data = self._cache[file_name] + data.last_used = time.time() + return data + + icon_file = open(file_name, 'r') + icon = _SVGIcon(icon_file.read()) + icon_file.close() + + self._cache[file_name] = icon + self._cleanup_cache() + + return icon + + def _cleanup_cache(self): + now = time.time() + while self._cache_size > self.CACHE_MAX_SIZE: + evict_key = None + oldest_key = None + oldest_time = now + + for icon_key, icon in self._cache.items(): + # evict large icons first + if icon.data_size > self.CACHE_MAX_ICON_SIZE: + evict_key = icon_key + break + + # evict older icons next; those used over 2 minutes ago + if icon.last_used < now - 120: + evict_key = icon_key + break + + # otherwise, evict the oldest + if oldest_time > icon.last_used: + oldest_time = icon.last_used + oldest_key = icon_key + + # If there's nothing specific to evict, try evicting + # the oldest thing + if not evict_key: + if not oldest_key: + break + evict_key = oldest_key + + self._cache_size -= self._cache[evict_key].data_size + del self._cache[evict_key] class _IconInfo(object): def __init__(self): From f44c340b3a3d38dac49d50a8fa3a55871a973670 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sun, 26 Aug 2007 12:36:34 +0200 Subject: [PATCH 13/36] Smarter/configurable surface cache in IconBuffer --- sugar/graphics/icon.py | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index ce848390..823b4aa7 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -128,7 +128,8 @@ class _BadgeInfo(object): class _IconBuffer(object): def __init__(self): self._svg_loader = _get_svg_loader() - self._surface = None + self._surface_cache = {} + self._cache_size = 1 self.icon_name = None self.file_name = None @@ -138,6 +139,10 @@ class _IconBuffer(object): self.width = None self.height = None + def _get_cache_key(self): + return (self.icon_name, self.file_name, self.fill_color, + self.stroke_color, self.badge_name, self.width, self.height) + def _load_svg(self, file_name): entities = {} if self.fill_color: @@ -224,8 +229,12 @@ class _IconBuffer(object): return info def get_surface(self): - if self._surface is not None: - return self._surface + cache_key = self._get_cache_key() + if self._surface_cache.has_key(cache_key): + print cache_key + return self._surface_cache[cache_key] + + print 'gah' icon_info = self._get_icon_info() if icon_info.file_name is None: @@ -267,13 +276,26 @@ class _IconBuffer(object): context.translate(badge_info.attach_x, badge_info.attach_y) self._draw_badge(context, badge_info.size) - self._surface = surface + if (len(self._surface_cache) == self._cache_size): + self._surface_cache.popitem() + self._surface_cache[cache_key] = surface return surface def invalidate(self): self._surface = None + def get_cache_size(self): + return self._cache_size + + def set_cache_size(self, cache_size): + while len(self._surface_cache) > cache_size: + print len(self._surface_cache) + self._surface_cache.popitem() + self._cache_size + + cache_size = property(get_cache_size, set_cache_size) + class Icon(gtk.Image): __gtype_name__ = 'SugarIcon' From 98157bbccb3756a7c45bd29df9eae284d23a0290 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sun, 26 Aug 2007 14:16:09 +0200 Subject: [PATCH 14/36] Make CanvasIcon use IconBuffer. Change the API to use a separate property for icon_name and file_name. --- shell/intro/colorpicker.py | 2 +- shell/view/BuddyIcon.py | 2 +- shell/view/devices/battery.py | 2 +- shell/view/devices/network/mesh.py | 10 +- shell/view/devices/network/wired.py | 2 +- shell/view/devices/network/wireless.py | 9 +- shell/view/frame/ActivitiesBox.py | 6 +- shell/view/frame/zoombox.py | 8 +- shell/view/home/MeshBox.py | 24 +- shell/view/home/MyIcon.py | 2 +- shell/view/home/activitiesdonut.py | 6 +- sugar/graphics/icon.py | 389 ++++--------------------- sugar/graphics/iconbutton.py | 6 +- 13 files changed, 102 insertions(+), 366 deletions(-) diff --git a/shell/intro/colorpicker.py b/shell/intro/colorpicker.py index 52b96871..90dbc26c 100644 --- a/shell/intro/colorpicker.py +++ b/shell/intro/colorpicker.py @@ -26,7 +26,7 @@ class ColorPicker(hippo.CanvasBox, hippo.CanvasItem): self.props.orientation = hippo.ORIENTATION_HORIZONTAL self._xo = CanvasIcon(size=style.XLARGE_ICON_SIZE, - icon_name='theme:computer-xo') + icon_name='computer-xo') self._set_random_colors() self._xo.connect('activated', self._xo_activated_cb) self.append(self._xo) diff --git a/shell/view/BuddyIcon.py b/shell/view/BuddyIcon.py index 1ab0ed74..047035ec 100644 --- a/shell/view/BuddyIcon.py +++ b/shell/view/BuddyIcon.py @@ -20,7 +20,7 @@ from view.BuddyMenu import BuddyMenu class BuddyIcon(CanvasIcon): def __init__(self, shell, buddy): - CanvasIcon.__init__(self, icon_name='theme:computer-xo', + CanvasIcon.__init__(self, icon_name='computer-xo', xo_color=buddy.get_color()) self._shell = shell diff --git a/shell/view/devices/battery.py b/shell/view/devices/battery.py index 4ac3c17b..ec971a54 100644 --- a/shell/view/devices/battery.py +++ b/shell/view/devices/battery.py @@ -47,7 +47,7 @@ class DeviceView(CanvasIcon): # Update palette if self._model.props.charging: status = _STATUS_CHARGING - self.props.badge_name = 'theme:badge-charging' + self.props.badge_name = 'badge-charging' elif self._model.props.discharging: status = _STATUS_DISCHARGING self.props.badge_name = None diff --git a/shell/view/devices/network/mesh.py b/shell/view/devices/network/mesh.py index 10830f2a..f8f394a4 100644 --- a/shell/view/devices/network/mesh.py +++ b/shell/view/devices/network/mesh.py @@ -22,7 +22,7 @@ from model.devices import device class DeviceView(canvasicon.CanvasIcon): def __init__(self, model): canvasicon.CanvasIcon.__init__(self, size=style.MEDIUM_ICON_SIZE, - icon_name='theme:network-mesh') + icon_name='network-mesh') self._model = model model.connect('notify::state', self._state_changed_cb) @@ -35,11 +35,11 @@ class DeviceView(canvasicon.CanvasIcon): # FIXME Change icon colors once we have real icons state = self._model.props.state if state == device.STATE_ACTIVATING: - self.props.fill_color = style.COLOR_INACTIVE_FILL - self.props.stroke_color = style.COLOR_INACTIVE_STROKE + self.props.fill_color = style.COLOR_INACTIVE_FILL.get_svg() + self.props.stroke_color = style.COLOR_INACTIVE_STROKE.get_svg() elif state == device.STATE_ACTIVATED: self.props.fill_color = None self.props.stroke_color = None elif state == device.STATE_INACTIVE: - self.props.fill_color = style.COLOR_INACTIVE_FILL - self.props.stroke_color = style.COLOR_INACTIVE_STROKE + self.props.fill_color = style.COLOR_INACTIVE_FILL.get_svg() + self.props.stroke_color = style.COLOR_INACTIVE_STROKE.get_svg() diff --git a/shell/view/devices/network/wired.py b/shell/view/devices/network/wired.py index 662c672d..dc83a088 100644 --- a/shell/view/devices/network/wired.py +++ b/shell/view/devices/network/wired.py @@ -19,4 +19,4 @@ from view.devices import deviceview class DeviceView(deviceview.DeviceView): def __init__(self, model): deviceview.DeviceView.__init__(self, model) - self.props.icon_name = 'theme:network-wired' + self.props.icon_name = 'network-wired' diff --git a/shell/view/devices/network/wireless.py b/shell/view/devices/network/wireless.py index 5102f3bb..7f03f404 100644 --- a/shell/view/devices/network/wireless.py +++ b/shell/view/devices/network/wireless.py @@ -18,7 +18,6 @@ from sugar.graphics.icon import get_icon_state from sugar.graphics.icon import CanvasIcon from sugar.graphics import style -from sugar.graphics import style from model.devices.network import wireless from model.devices import device @@ -56,11 +55,11 @@ class DeviceView(CanvasIcon): # FIXME Change icon colors once we have real icons state = self._model.props.state if state == device.STATE_ACTIVATING: - self.props.fill_color = style.COLOR_INACTIVE_FILL - self.props.stroke_color = style.COLOR_INACTIVE_STROKE + self.props.fill_color = style.COLOR_INACTIVE_FILL.get_svg() + self.props.stroke_color = style.COLOR_INACTIVE_STROKE.get_svg() elif state == device.STATE_ACTIVATED: self.props.fill_color = None self.props.stroke_color = None elif state == device.STATE_INACTIVE: - self.props.fill_color = style.COLOR_INACTIVE_FILL - self.props.stroke_color = style.COLOR_INACTIVE_STROKE + self.props.fill_color = style.COLOR_INACTIVE_FILL.get_svg() + self.props.stroke_color = style.COLOR_INACTIVE_STROKE.get_svg() diff --git a/shell/view/frame/ActivitiesBox.py b/shell/view/frame/ActivitiesBox.py index 909a5f2c..0e377aff 100644 --- a/shell/view/frame/ActivitiesBox.py +++ b/shell/view/frame/ActivitiesBox.py @@ -28,9 +28,9 @@ from frameinvoker import FrameCanvasInvoker class ActivityButton(IconButton): def __init__(self, activity_info): - IconButton.__init__(self, icon_name=activity_info.icon, - stroke_color=style.COLOR_WHITE, - fill_color=style.COLOR_TRANSPARENT) + IconButton.__init__(self, file_name=activity_info.icon, + stroke_color=style.COLOR_WHITE.get_svg(), + fill_color=style.COLOR_TRANSPARENT.get_svg()) palette = Palette(activity_info.name) palette.props.invoker = FrameCanvasInvoker(self) diff --git a/shell/view/frame/zoombox.py b/shell/view/frame/zoombox.py index 5634f561..37bfcc02 100644 --- a/shell/view/frame/zoombox.py +++ b/shell/view/frame/zoombox.py @@ -30,7 +30,7 @@ class ZoomBox(hippo.CanvasBox): self._shell = shell - icon = IconButton(icon_name='theme:zoom-mesh') + icon = IconButton(icon_name='zoom-mesh') icon.connect('activated', self._level_clicked_cb, ShellModel.ZOOM_MESH) @@ -41,7 +41,7 @@ class ZoomBox(hippo.CanvasBox): palette.set_group_id('frame') icon.set_palette(palette) - icon = IconButton(icon_name='theme:zoom-friends') + icon = IconButton(icon_name='zoom-friends') icon.connect('activated', self._level_clicked_cb, ShellModel.ZOOM_FRIENDS) @@ -52,7 +52,7 @@ class ZoomBox(hippo.CanvasBox): palette.set_group_id('frame') icon.set_palette(palette) - icon = IconButton(icon_name='theme:zoom-home') + icon = IconButton(icon_name='zoom-home') icon.connect('activated', self._level_clicked_cb, ShellModel.ZOOM_HOME) @@ -63,7 +63,7 @@ class ZoomBox(hippo.CanvasBox): palette.set_group_id('frame') icon.set_palette(palette) - icon = IconButton(icon_name='theme:zoom-activity') + icon = IconButton(icon_name='zoom-activity') icon.connect('activated', self._level_clicked_cb, ShellModel.ZOOM_ACTIVITY) diff --git a/shell/view/home/MeshBox.py b/shell/view/home/MeshBox.py index 04ddf4ed..b099351d 100644 --- a/shell/view/home/MeshBox.py +++ b/shell/view/home/MeshBox.py @@ -94,33 +94,33 @@ class AccessPointView(PulsingIcon): if self._model.props.state == accesspointmodel.STATE_CONNECTING: self.props.pulse_time = 1.0 self.props.colors = [ - [ style.Color(self._device_stroke), - style.Color(self._device_fill) ], - [ style.Color(self._device_stroke), - style.Color('#e2e2e2') ] + [ style.Color(self._device_stroke).get_svg(), + style.Color(self._device_fill).get_svg() ], + [ style.Color(self._device_stroke).get_svg(), + '#e2e2e2' ] ] elif self._model.props.state == accesspointmodel.STATE_CONNECTED: self.props.pulse_time = 2.0 self.props.colors = [ - [ style.Color(self._device_stroke), - style.Color(self._device_fill) ], - [ style.Color('#ffffff'), - style.Color(self._device_fill) ] + [ style.Color(self._device_stroke).get_svg(), + style.Color(self._device_fill).get_svg() ], + [ '#ffffff', + style.Color(self._device_fill).get_svg() ] ] elif self._model.props.state == accesspointmodel.STATE_NOTCONNECTED: self.props.pulse_time = 0.0 self.props.colors = [ - [ style.Color(self._device_stroke), - style.Color(self._device_fill) ] + [ style.Color(self._device_stroke).get_svg(), + style.Color(self._device_fill).get_svg() ] ] -_MESH_ICON_NAME = 'theme:network-mesh' +_MESH_ICON_NAME = 'network-mesh' class MeshDeviceView(PulsingIcon): def __init__(self, nm_device): PulsingIcon.__init__(self, size=style.MEDIUM_ICON_SIZE, - icon_name=_MESH_ICON_NAME) + icon_name=_MESH_ICON_NAME) self._nm_device = nm_device self.set_tooltip(_("Mesh Network")) diff --git a/shell/view/home/MyIcon.py b/shell/view/home/MyIcon.py index b46c730d..af0f6ced 100644 --- a/shell/view/home/MyIcon.py +++ b/shell/view/home/MyIcon.py @@ -20,5 +20,5 @@ from sugar import profile class MyIcon(CanvasIcon): def __init__(self, size): CanvasIcon.__init__(self, size=size, - icon_name='theme:computer-xo', + icon_name='computer-xo', xo_color=profile.get_color()) diff --git a/shell/view/home/activitiesdonut.py b/shell/view/home/activitiesdonut.py index 2a96a96b..45411da0 100644 --- a/shell/view/home/activitiesdonut.py +++ b/shell/view/home/activitiesdonut.py @@ -73,8 +73,8 @@ class ActivityIcon(CanvasIcon): self._level = self._level_max color = self._icon_colors[self._level] - CanvasIcon.__init__(self, icon_name=icon_name, xo_color=color, - size=style.MEDIUM_ICON_SIZE, cache=True) + CanvasIcon.__init__(self, file_name=icon_name, xo_color=color, + size=style.MEDIUM_ICON_SIZE) self._activity = activity self._pulse_id = 0 @@ -119,8 +119,6 @@ class ActivityIcon(CanvasIcon): if self._pulse_id: gobject.source_remove(self._pulse_id) self._pulse_id = 0 - # dispose of all rendered icons from launch feedback - self._clear_buffers() def _compute_icon_colors(self): _LEVEL_MAX = 1.6 diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index 823b4aa7..6b0f5a78 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -231,11 +231,8 @@ class _IconBuffer(object): def get_surface(self): cache_key = self._get_cache_key() if self._surface_cache.has_key(cache_key): - print cache_key return self._surface_cache[cache_key] - print 'gah' - icon_info = self._get_icon_info() if icon_info.file_name is None: return None @@ -290,7 +287,6 @@ class _IconBuffer(object): def set_cache_size(self, cache_size): while len(self._surface_cache) > cache_size: - print len(self._surface_cache) self._surface_cache.popitem() self._cache_size @@ -389,116 +385,12 @@ class Icon(gtk.Image): else: return gtk.Image.do_get_property(self, pspec) -_ICON_REQUEST_SIZE = 50 - -class _IconCacheIcon: - def __init__(self, name, fill_color, stroke_color, now): - self.last_used = now - self.usage_count = 1 - self.badge_x = 1.0 - _BADGE_SIZE / 2 - self.badge_y = 1.0 - _BADGE_SIZE / 2 - - if name[0:6] == "theme:": - info = gtk.icon_theme_get_default().lookup_icon( - name[6:], _ICON_REQUEST_SIZE, 0) - if not info: - raise ValueError("Icon '" + name + "' not found.") - - fname = info.get_filename() - attach_points = info.get_attach_points() - if attach_points is not None: - self.badge_x = float(attach_points[0][0]) / _ICON_REQUEST_SIZE - self.badge_y = float(attach_points[0][1]) / _ICON_REQUEST_SIZE - del info - else: - fname = name - - self.handle = self._read_icon_data(fname, fill_color, stroke_color) - - def _read_icon_data(self, filename, fill_color, stroke_color): - icon_file = open(filename, 'r') - data = icon_file.read() - icon_file.close() - - if fill_color: - entity = '' % fill_color - data = re.sub('', entity, data) - - if stroke_color: - entity = '' % stroke_color - data = re.sub('', entity, data) - - self.data_size = len(data) - return rsvg.Handle(data=data) - -class _IconCache: - _CACHE_MAX = 50000 # in bytes - - def __init__(self): - self._icons = {} - self._cache_size = 0 - - def _cache_cleanup(self, key, now): - while self._cache_size > self._CACHE_MAX: - evict_key = None - oldest_key = None - oldest_time = now - for icon_key, icon in self._icons.items(): - # Don't evict the icon we are about to use if it's in the cache - if icon_key == key: - continue - - # evict large icons first - if icon.data_size > self._CACHE_MAX: - evict_key = icon_key - break - # evict older icons next; those used over 2 minutes ago - if icon.last_used < now - 120: - evict_key = icon_key - break - # otherwise, evict the oldest - if oldest_time > icon.last_used: - oldest_time = icon.last_used - oldest_key = icon_key - - # If there's nothing specific to evict, try evicting - # the oldest thing - if not evict_key: - if not oldest_key: - break - evict_key = oldest_key - - self._cache_size -= self._icons[evict_key].data_size - del self._icons[evict_key] - - def get_icon(self, name, fill_color, stroke_color): - if not name: - return None - - if fill_color or stroke_color: - key = (name, fill_color, stroke_color) - else: - key = name - - # If we're over the cache limit, evict something from the cache - now = time.time() - self._cache_cleanup(key, now) - - if self._icons.has_key(key): - icon = self._icons[key] - icon.usage_count += 1 - icon.last_used = now - else: - icon = _IconCacheIcon(name, fill_color, stroke_color, now) - self._icons[key] = icon - self._cache_size += icon.data_size - return icon - - class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem): __gtype_name__ = 'CanvasIcon' __gproperties__ = { + 'file-name' : (str, None, None, None, + gobject.PARAM_READWRITE), 'icon-name' : (str, None, None, None, gobject.PARAM_READWRITE), 'xo-color' : (object, None, None, @@ -509,263 +401,110 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem): gobject.PARAM_READWRITE), 'size' : (int, None, None, 0, 1024, 0, gobject.PARAM_READWRITE), - 'scale' : (int, None, None, 0, 1024, 0, + 'scale' : (float, None, None, -1024.0, 1024.0, 1.0, gobject.PARAM_READWRITE), - 'cache' : (bool, None, None, False, - gobject.PARAM_READWRITE), - 'active' : (bool, None, None, True, + 'cache_size' : (int, None, None, 0, 1024, 0, gobject.PARAM_READWRITE), 'badge-name' : (str, None, None, None, gobject.PARAM_READWRITE) } - _cache = _IconCache() - def __init__(self, **kwargs): - self._buffers = {} - self._cur_buffer = None - self._size = 0 - self._scale = 0 - self._fill_color = None - self._stroke_color = None - self._icon_name = None - self._cache = False - self._icon = None - self._active = True - self._palette = None - self._badge_name = None - self._badge_icon = None + self._buffer = _IconBuffer() hippo.CanvasBox.__init__(self, **kwargs) - - self.connect_after('motion-notify-event', self._motion_notify_event_cb) - def _clear_buffers(self): - icon_key = self._get_current_buffer_key(self._icon_name) - badge_key = None - if self._badge_name: - badge_key = self._get_current_buffer_key(self._badge_name) - for key in self._buffers.keys(): - if key != icon_key: - if not badge_key or (key != badge_key): - del self._buffers[key] - self._buffers = {} + self._palette = None def do_set_property(self, pspec, value): - if pspec.name == 'icon-name': - if self._icon_name != value and not self._cache: - self._clear_buffers() - self._icon_name = value - self._icon = None + if pspec.name == 'file-name': + self._buffer.file_name = value + self._buffer.invalidate() + self.emit_paint_needed(0, 0, -1, -1) + elif pspec.name == 'icon-name': + self._buffer.icon_name = value + self._buffer.invalidate() self.emit_paint_needed(0, 0, -1, -1) elif pspec.name == 'xo-color': - self.props.fill_color = style.Color(value.get_fill_color()) - self.props.stroke_color = style.Color(value.get_stroke_color()) + self.props.fill_color = value.get_fill_color() + self.props.stroke_color = value.get_stroke_color() elif pspec.name == 'fill-color': - if self._fill_color != value: - if not self._cache: - self._clear_buffers() - self._fill_color = value - self._icon = None - self._badge_icon = None - self.emit_paint_needed(0, 0, -1, -1) + self._buffer.fill_color = value + self._buffer.invalidate() + self.emit_paint_needed(0, 0, -1, -1) elif pspec.name == 'stroke-color': - if self._stroke_color != value: - if not self._cache: - self._clear_buffers() - self._stroke_color = value - self._icon = None - self._badge_icon = None - self.emit_paint_needed(0, 0, -1, -1) + self._buffer.stroke_color = value + self._buffer.invalidate() + self.emit_paint_needed(0, 0, -1, -1) elif pspec.name == 'size': - if self._size != value and not self._cache: - self._clear_buffers() - self._size = value + self._buffer.width = value + self._buffer.height = value + self._buffer.invalidate() self.emit_request_changed() elif pspec.name == 'scale': - if self._scale != value and not self._cache: - self._clear_buffers() - self._scale = value + self._buffer.scale = value + self._buffer.invalidate() self.emit_request_changed() - elif pspec.name == 'cache': - self._cache = value - elif pspec.name == 'active': - if self._active != value: - if not self._cache: - self._clear_buffers() - self._active = value - self._icon = None - self.emit_paint_needed(0, 0, -1, -1) + elif pspec.name == 'cache-size': + self._buffer.cache_size = value elif pspec.name == 'badge-name': - if self._badge_name != value and not self._cache: - self._clear_buffers() - self._badge_name = value - self._badge_icon = None self.emit_paint_needed(0, 0, -1, -1) - def _choose_colors(self): - fill_color = None - stroke_color = None - if self._active: - if self._fill_color: - fill_color = self._fill_color.get_svg() - if self._stroke_color: - stroke_color = self._stroke_color.get_svg() - else: - stroke_color = color.ICON_STROKE_INACTIVE.get_svg() - if self._fill_color: - fill_color = self._fill_color.get_svg() - return [fill_color, stroke_color] - - def _get_icon_from_cache(self, name, icon): - if not icon: - cache = CanvasIcon._cache - - [fill_color, stroke_color] = self._choose_colors() - - icon = cache.get_icon(name, fill_color, stroke_color) - return icon - - def _get_icon(self): - self._icon = self._get_icon_from_cache(self._icon_name, self._icon) - return self._icon - - def _get_badge_icon(self): - self._badge_icon = self._get_icon_from_cache(self._badge_name, - self._badge_icon) - return self._badge_icon - - def _get_current_buffer_key(self, name): - [fill_color, stroke_color] = self._choose_colors() - return (name, fill_color, stroke_color, self._size) - def do_get_property(self, pspec): if pspec.name == 'size': - return self._size + return self._buffer.width + elif pspec.name == 'file-name': + return self._buffer.file_name elif pspec.name == 'icon-name': - return self._icon_name + return self._buffer.icon_name elif pspec.name == 'fill-color': - return self._fill_color + return self._buffer.fill_color elif pspec.name == 'stroke-color': - return self._stroke_color - elif pspec.name == 'cache': - return self._cache - elif pspec.name == 'active': - return self._active + return self._buffer.stroke_color + elif pspec.name == 'cache-size': + return self._buffer.cache_size elif pspec.name == 'badge-name': - return self._badge_name + return self._buffer.badge_name elif pspec.name == 'scale': - return self._scale - - def _get_icon_size(self, icon): - if icon: - dimensions = icon.handle.get_dimension_data() - return int(dimensions[0]), int(dimensions[1]) - else: - return [0, 0] - - def _get_size(self, icon): - width, height = self._get_icon_size(icon) - if self._scale != 0: - width = int(width * self._scale) - height = int(height * self._scale) - elif self._size != 0: - width = height = self._size - - return [width, height] - - def _get_buffer(self, cr, name, icon, scale_factor=None): - """Return a cached cairo surface for the SVG icon, or if none exists, - create a new cairo surface with the right size.""" - buf = None - - key = self._get_current_buffer_key(name) - if self._buffers.has_key(key): - buf = self._buffers[key] - else: - [icon_w, icon_h] = self._get_icon_size(icon) - [target_w, target_h] = self._get_size(icon) - - if scale_factor: - target_w = int(target_w * scale_factor) - target_h = int(target_h * scale_factor) - - target = cr.get_target() - buf = target.create_similar(cairo.CONTENT_COLOR_ALPHA, - target_w, target_h) - ctx = cairo.Context(buf) - ctx.scale(float(target_w) / float(icon_w), - float(target_h) / float(icon_h)) - icon.handle.render_cairo(ctx) - - del ctx - self._buffers[key] = buf - - return buf + return self._buffer.scale def do_paint_below_children(self, cr, damaged_box): - icon = self._get_icon() - if icon is None: - return + surface = self._buffer.get_surface() + if surface: + width, height = self.get_allocation() - icon_buf = self._get_buffer(cr, self._icon_name, icon) - [width, height] = self.get_allocation() - icon_x = (width - icon_buf.get_width()) / 2 - icon_y = (height - icon_buf.get_height()) / 2 + x = (width - surface.get_width()) / 2 + y = (height - surface.get_height()) / 2 - cr.set_source_surface(icon_buf, icon_x, icon_y) - cr.paint() - - if self._badge_name: - badge_icon = self._get_badge_icon() - if badge_icon: - badge_buf = self._get_buffer(cr, self._badge_name, badge_icon, _BADGE_SIZE) - badge_x = (icon_x + icon.badge_x * icon_buf.get_width() - - badge_buf.get_width() / 2) - badge_y = (icon_y + icon.badge_y * icon_buf.get_height() - - badge_buf.get_height() / 2) - cr.set_source_surface(badge_buf, badge_x, badge_y) - cr.paint() + cr.set_source_surface(surface, x, y) + cr.paint() def do_get_content_width_request(self): - icon = self._get_icon() - [width, height] = self._get_size(icon) - if self._badge_name is not None: - # If the badge goes outside the bounding box, add space - # on *both* sides (to keep the main icon centered) - if icon.badge_x < 0.0: - width = int(width * 2 * (1.0 - icon.badge_x)) - elif icon.badge_x + _BADGE_SIZE > 1.0: - width = int(width * 2 * (icon.badge_x + _BADGE_SIZE - 1.0)) - return (width, width) + surface = self._buffer.get_surface() + if surface: + size = surface.get_width() + elif self._buffer.width: + size = self._buffer.width + else: + size = 0 + + return size, size def do_get_content_height_request(self, for_width): - icon = self._get_icon() - [width, height] = self._get_size(icon) - if self._badge_name is not None: - if icon.badge_y < 0.0: - height = int(height * 2 * (1.0 - icon.badge_y)) - elif icon.badge_y + _BADGE_SIZE > 1.0: - height = int(height * 2 * (icon.badge_y + _BADGE_SIZE - 1.0)) - return (height, height) + surface = self._buffer.get_surface() + if surface: + size = surface.get_height() + elif self._buffer.height: + size = self._buffer.height + else: + size = 0 + + return size, size def do_button_press_event(self, event): self.emit_activated() return True - def _motion_notify_event_cb(self, button, event): - if event.detail == hippo.MOTION_DETAIL_ENTER: - self.prelight(True) - elif event.detail == hippo.MOTION_DETAIL_LEAVE: - self.prelight(False) - return False - - def prelight(self, enter): - """ - Override this method for adding prelighting behavior. - """ - pass - def get_palette(self): return self._palette @@ -787,6 +526,6 @@ def get_icon_state(base_name, perc): while strength <= 100: icon_name = '%s-%03d' % (base_name, strength) if icon_theme.has_icon(icon_name): - return 'theme:' + icon_name + return icon_name strength = strength + step diff --git a/sugar/graphics/iconbutton.py b/sugar/graphics/iconbutton.py index 6489b344..7ec49afc 100644 --- a/sugar/graphics/iconbutton.py +++ b/sugar/graphics/iconbutton.py @@ -31,11 +31,11 @@ class IconButton(CanvasIcon, hippo.CanvasItem): __gtype_name__ = 'SugarIconButton' def __init__(self, **kwargs): - CanvasIcon.__init__(self, cache=True, **kwargs) + CanvasIcon.__init__(self, **kwargs) if not self.props.fill_color and not self.props.stroke_color: - self.props.fill_color = style.Color("#404040") - self.props.stroke_color = style.Color("#FFFFFF") + self.props.fill_color = style.Color("#404040").get_svg() + self.props.stroke_color = style.Color("#FFFFFF").get_svg() self.connect('activated', self._icon_clicked_cb) From 5f773b21afeb986e836619d5a5b8c2ab6f9075c8 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sun, 26 Aug 2007 14:30:02 +0200 Subject: [PATCH 15/36] Remove invalidation logic which is not necessary with the new cache. --- sugar/graphics/icon.py | 20 -------------------- sugar/graphics/iconbutton.py | 12 ------------ 2 files changed, 32 deletions(-) diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index 6b0f5a78..2fe85b2d 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -279,9 +279,6 @@ class _IconBuffer(object): return surface - def invalidate(self): - self._surface = None - def get_cache_size(self): return self._cache_size @@ -314,29 +311,23 @@ class Icon(gtk.Image): def _sync_image_properties(self): if self._buffer.icon_name != self.props.icon_name: self._buffer.icon_name = self.props.icon_name - self._buffer.invalidate() if self._buffer.file_name != self.props.file: self._buffer.file_name = self.props.file - self._buffer.invalidate() width, height = gtk.icon_size_lookup(self.props.icon_size) if self._buffer.width != width and self._buffer.height != height: self._buffer.width = width self._buffer.height = height - self._buffer.invalidate() def _icon_size_changed_cb(self, image, pspec): self._buffer.icon_size = self.props.icon_size - self._buffer.invalidate() def _icon_name_changed_cb(self, image, pspec): self._buffer.icon_name = self.props.icon_name - self._buffer.invalidate() def _file_changed_cb(self, image, pspec): self._buffer.file_name = self.props.file - self._buffer.invalidate() def _update_buffer_size(self): width, height = gtk.icon_size_lookup(self.props.icon_size) @@ -344,8 +335,6 @@ class Icon(gtk.Image): self._buffer.width = width self._buffer.height = height - self._buffer.invalidate() - def do_expose_event(self, event): self._sync_image_properties() @@ -365,13 +354,10 @@ class Icon(gtk.Image): self.props.stroke_color = value.get_stroke_color() elif pspec.name == 'fill-color': self._buffer.fill_color = value - self._buffer.invalidate() elif pspec.name == 'stroke-color': self._buffer.fill_color = value - self._buffer.invalidate() elif pspec.name == 'badge-name': self._buffer.badge_name = value - self._buffer.invalidate() else: gtk.Image.do_set_property(self, pspec, value) @@ -419,31 +405,25 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem): def do_set_property(self, pspec, value): if pspec.name == 'file-name': self._buffer.file_name = value - self._buffer.invalidate() self.emit_paint_needed(0, 0, -1, -1) elif pspec.name == 'icon-name': self._buffer.icon_name = value - self._buffer.invalidate() self.emit_paint_needed(0, 0, -1, -1) elif pspec.name == 'xo-color': self.props.fill_color = value.get_fill_color() self.props.stroke_color = value.get_stroke_color() elif pspec.name == 'fill-color': self._buffer.fill_color = value - self._buffer.invalidate() self.emit_paint_needed(0, 0, -1, -1) elif pspec.name == 'stroke-color': self._buffer.stroke_color = value - self._buffer.invalidate() self.emit_paint_needed(0, 0, -1, -1) elif pspec.name == 'size': self._buffer.width = value self._buffer.height = value - self._buffer.invalidate() self.emit_request_changed() elif pspec.name == 'scale': self._buffer.scale = value - self._buffer.invalidate() self.emit_request_changed() elif pspec.name == 'cache-size': self._buffer.cache_size = value diff --git a/sugar/graphics/iconbutton.py b/sugar/graphics/iconbutton.py index 7ec49afc..fd977f69 100644 --- a/sugar/graphics/iconbutton.py +++ b/sugar/graphics/iconbutton.py @@ -43,18 +43,6 @@ class IconButton(CanvasIcon, hippo.CanvasItem): self.props.box_height = style.GRID_CELL_SIZE self.props.size = style.STANDARD_ICON_SIZE - def do_button_press_event(self, event): - if self._active: - self.emit_activated() - return True - - def prelight(self, enter): - if enter: - if self.props.active: - self.props.background_color = 0x000000FF - else: - self.props.background_color = 0x00000000 - def _icon_clicked_cb(self, button): if self._palette: self._palette.popdown(True) From 7042b4392effd921d502447606473031bf413ed8 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sun, 26 Aug 2007 14:33:16 +0200 Subject: [PATCH 16/36] Fix CanvasIcon badges --- sugar/graphics/icon.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index 2fe85b2d..b3dc0349 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -428,6 +428,7 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem): elif pspec.name == 'cache-size': self._buffer.cache_size = value elif pspec.name == 'badge-name': + self._buffer.badge_name = value self.emit_paint_needed(0, 0, -1, -1) def do_get_property(self, pspec): From ba4f68ef58272fb57f6005b73d4d59dc7911470d Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sun, 26 Aug 2007 14:45:59 +0200 Subject: [PATCH 17/36] Cache the donut animation. --- shell/view/home/activitiesdonut.py | 2 ++ sugar/graphics/icon.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/shell/view/home/activitiesdonut.py b/shell/view/home/activitiesdonut.py index 45411da0..950afbd0 100644 --- a/shell/view/home/activitiesdonut.py +++ b/shell/view/home/activitiesdonut.py @@ -159,6 +159,7 @@ class ActivityIcon(CanvasIcon): if self._pulse_id: return + self.props.cache_size = self._level_max self._pulse_id = gobject.timeout_add(self._INTERVAL, self._pulse_cb) def _stop_pulsing(self): @@ -167,6 +168,7 @@ class ActivityIcon(CanvasIcon): self._cleanup() self._level = 100.0 + self.props.cache_size = 1 self.props.xo_color = self._orig_color def _resume_activate_cb(self, menuitem): diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index b3dc0349..ad57da3e 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -285,7 +285,7 @@ class _IconBuffer(object): def set_cache_size(self, cache_size): while len(self._surface_cache) > cache_size: self._surface_cache.popitem() - self._cache_size + self._cache_size = cache_size cache_size = property(get_cache_size, set_cache_size) From f1fc80b4a83135473d9546390d58a6ea50b1fd53 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sun, 26 Aug 2007 15:01:16 +0200 Subject: [PATCH 18/36] Fix icon padding. --- sugar/graphics/icon.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index ad57da3e..bd050ae4 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -215,15 +215,15 @@ class _IconBuffer(object): return info info.size = int(_BADGE_SIZE * icon_width) - info.attach_x = icon_info.attach_x * icon_width - info.size / 2 - info.attach_y = icon_info.attach_y * icon_height - info.size / 2 + info.attach_x = int(icon_info.attach_x * icon_width - info.size / 2) + info.attach_y = int(icon_info.attach_y * icon_height - info.size / 2) if info.attach_x < 0 or info.attach_y < 0: info.icon_padding = max(-info.attach_x, -info.attach_y) elif info.attach_x + info.size > icon_width or \ info.attach_y + info.size > icon_height: - x_padding = icon_width - info.attach_x - info.size - y_padding = icon_height - info.attach_y - info.size + x_padding = info.attach_x + info.size - icon_width + y_padding = info.attach_y + info.size - icon_height info.icon_padding = max(x_padding, y_padding) return info From 8ff7bdebb7337d63875cb1f5c513800d9b2c3c23 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sun, 26 Aug 2007 15:10:43 +0200 Subject: [PATCH 19/36] Typo --- sugar/graphics/icon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index bd050ae4..cee7dec2 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -389,7 +389,7 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem): gobject.PARAM_READWRITE), 'scale' : (float, None, None, -1024.0, 1024.0, 1.0, gobject.PARAM_READWRITE), - 'cache_size' : (int, None, None, 0, 1024, 0, + 'cache-size' : (int, None, None, 0, 1024, 0, gobject.PARAM_READWRITE), 'badge-name' : (str, None, None, None, gobject.PARAM_READWRITE) From be99aa6dcf91aa5daedd3d7067f3b3ece02256f8 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Sun, 26 Aug 2007 16:14:29 +0200 Subject: [PATCH 20/36] Fix typos in activitiesdonut.py --- shell/view/home/activitiesdonut.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/view/home/activitiesdonut.py b/shell/view/home/activitiesdonut.py index 950afbd0..de3f8bd7 100644 --- a/shell/view/home/activitiesdonut.py +++ b/shell/view/home/activitiesdonut.py @@ -317,7 +317,7 @@ class ActivitiesDonut(hippo.CanvasBox, hippo.CanvasItem): try: smaps = ProcSmaps(pid) - _subtract_mappings(smaps, shell_mappings) + self._subtract_mappings(smaps, shell_mappings) for mapping in smaps.mappings: if mapping.shared_clean > 0 or mapping.shared_dirty > 0: if num_mappings.has_key(mapping.name): @@ -408,7 +408,7 @@ class ActivitiesDonut(hippo.CanvasBox, hippo.CanvasItem): if icon.size > _MIN_WEDGE_SIZE: icon.size -= (icon.size - _MIN_WEDGE_SIZE) * reduction - def _subtract_mappings(smaps, mappings_to_remove): + def _subtract_mappings(self, smaps, mappings_to_remove): for mapping in smaps.mappings: if mappings_to_remove.has_key(mapping.name): mapping.shared_clean = 0 From 5b1db87cec9b159ffad84a465b871b27b75fad82 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Sun, 26 Aug 2007 19:00:08 +0200 Subject: [PATCH 21/36] Add some warnings to the new icon stuff. --- sugar/graphics/icon.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index cee7dec2..f4b466c8 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -182,6 +182,8 @@ class _IconBuffer(object): icon_info.file_name = info.get_filename() icon_info.attach_x = attach_x icon_info.attach_y = attach_y + else: + logging.warning('No icon with the name %s was found in the theme.' % self.icon_name) return icon_info @@ -413,9 +415,13 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem): self.props.fill_color = value.get_fill_color() self.props.stroke_color = value.get_stroke_color() elif pspec.name == 'fill-color': + if not isinstance(value, str): + raise TypeError('fill-color must be a string') self._buffer.fill_color = value self.emit_paint_needed(0, 0, -1, -1) elif pspec.name == 'stroke-color': + if not isinstance(value, str): + raise TypeError('stroke-color must be a string') self._buffer.stroke_color = value self.emit_paint_needed(0, 0, -1, -1) elif pspec.name == 'size': From db02d6b54c0d7bd6beee0d8f91378432440aefa4 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Sun, 26 Aug 2007 19:23:14 +0200 Subject: [PATCH 22/36] Correct latest commit. --- sugar/graphics/icon.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index f4b466c8..7912208d 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -415,13 +415,13 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem): self.props.fill_color = value.get_fill_color() self.props.stroke_color = value.get_stroke_color() elif pspec.name == 'fill-color': - if not isinstance(value, str): - raise TypeError('fill-color must be a string') + if not isinstance(value, basestring): + raise TypeError('fill-color must be a string, not %r' % type(value)) self._buffer.fill_color = value self.emit_paint_needed(0, 0, -1, -1) elif pspec.name == 'stroke-color': - if not isinstance(value, str): - raise TypeError('stroke-color must be a string') + if not isinstance(value, basestring): + raise TypeError('stroke-color must be a string, not %r' % type(value)) self._buffer.stroke_color = value self.emit_paint_needed(0, 0, -1, -1) elif pspec.name == 'size': From cf7ff39c5ad7750f71baeded213f04a89470e658 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Sun, 26 Aug 2007 20:21:49 +0200 Subject: [PATCH 23/36] Allow for None values in fill-color and stroke-color. --- sugar/graphics/icon.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index 7912208d..8e36e14b 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -415,12 +415,12 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem): self.props.fill_color = value.get_fill_color() self.props.stroke_color = value.get_stroke_color() elif pspec.name == 'fill-color': - if not isinstance(value, basestring): + if not isinstance(value, basestring) and value is not None: raise TypeError('fill-color must be a string, not %r' % type(value)) self._buffer.fill_color = value self.emit_paint_needed(0, 0, -1, -1) elif pspec.name == 'stroke-color': - if not isinstance(value, basestring): + if not isinstance(value, basestring) and value is not None: raise TypeError('stroke-color must be a string, not %r' % type(value)) self._buffer.stroke_color = value self.emit_paint_needed(0, 0, -1, -1) From 405dafc90850807bb478e3a00a031e07f602f0ff Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Sun, 26 Aug 2007 20:44:51 +0200 Subject: [PATCH 24/36] Adapt to changes in the icon API. --- services/shell/objecttypeservice.py | 12 ++++++------ shell/model/homeactivity.py | 2 +- shell/view/frame/overlaybox.py | 2 +- sugar/activity/activity.py | 4 ++-- sugar/graphics/combobox.py | 15 ++++++++------- sugar/graphics/objectchooser.py | 2 +- 6 files changed, 19 insertions(+), 18 deletions(-) diff --git a/services/shell/objecttypeservice.py b/services/shell/objecttypeservice.py index cfaac33c..8a04d61f 100644 --- a/services/shell/objecttypeservice.py +++ b/services/shell/objecttypeservice.py @@ -30,27 +30,27 @@ class ObjectTypeRegistry(dbus.service.Object): self._types = {} - self._add_primitive('Text', _('Text'), 'theme:text-x-generic', + self._add_primitive('Text', _('Text'), 'text-x-generic', ['text/plain', 'text/rtf', 'application/pdf', 'application/x-pdf', 'text/html', 'application/vnd.oasis.opendocument.text', 'application/rtf', 'text/rtf']) - self._add_primitive('Image', _('Image'), 'theme:image-x-generic', + self._add_primitive('Image', _('Image'), 'image-x-generic', ['image/png', 'image/gif', 'image/jpeg']) - self._add_primitive('Audio', _('Audio'), 'theme:audio-x-generic', + self._add_primitive('Audio', _('Audio'), 'audio-x-generic', ['audio/ogg']) - self._add_primitive('Video', _('Video'), 'theme:video-x-generic', + self._add_primitive('Video', _('Video'), 'video-x-generic', ['video/ogg', 'application/ogg']) self._add_primitive('Etoys project', _('Etoys project'), - 'theme:application-x-squeak-project', + 'application-x-squeak-project', ['application/x-squeak-project']) self._add_primitive('Link', _('Link'), - 'theme:text-uri-list', + 'text-uri-list', ['text/x-moz-url', 'text/uri-list']) def _add_primitive(self, type_id, name, icon, mime_types): diff --git a/shell/model/homeactivity.py b/shell/model/homeactivity.py index 434a3a68..1d29be23 100644 --- a/shell/model/homeactivity.py +++ b/shell/model/homeactivity.py @@ -103,7 +103,7 @@ class HomeActivity(gobject.GObject): if self._activity_info: return self._activity_info.icon else: - return 'theme:image-missing' + return 'image-missing' def get_icon_color(self): """Retrieve the appropriate icon colour for this activity diff --git a/shell/view/frame/overlaybox.py b/shell/view/frame/overlaybox.py index f9726e8e..bb74f187 100644 --- a/shell/view/frame/overlaybox.py +++ b/shell/view/frame/overlaybox.py @@ -24,7 +24,7 @@ class OverlayBox(hippo.CanvasBox): self._shell = shell - icon = IconButton(icon_name='theme:stock-chat') + icon = IconButton(icon_name='stock-chat') icon.connect('activated', self._overlay_clicked_cb) self.append(icon) diff --git a/sugar/activity/activity.py b/sugar/activity/activity.py index 3f782b51..6655b243 100644 --- a/sugar/activity/activity.py +++ b/sugar/activity/activity.py @@ -75,9 +75,9 @@ class ActivityToolbar(gtk.Toolbar): self.share = ToolComboBox(label_text='Share with:') self.share.combo.connect('changed', self._share_changed_cb) self.share.combo.append_item(None, _('Private'), - 'theme:zoom-home-mini') + 'zoom-home-mini') self.share.combo.append_item(None, _('My Neighborhood'), - 'theme:zoom-neighborhood-mini') + 'zoom-neighborhood-mini') self.insert(self.share, -1) self.share.show() diff --git a/sugar/graphics/combobox.py b/sugar/graphics/combobox.py index 3251dc2c..75573f0a 100644 --- a/sugar/graphics/combobox.py +++ b/sugar/graphics/combobox.py @@ -61,8 +61,8 @@ class ComboBox(gtk.ComboBox): del info return fname - def append_item(self, action_id, text, icon_name=None): - if not self._icon_renderer and icon_name: + def append_item(self, action_id, text, icon_name=None, file_name=None): + if not self._icon_renderer and (icon_name or file_name): self._icon_renderer = gtk.CellRendererPixbuf() settings = self.get_settings() @@ -77,16 +77,17 @@ class ComboBox(gtk.ComboBox): self.pack_end(self._text_renderer, True) self.add_attribute(self._text_renderer, 'text', 1) - if icon_name: + if icon_name or file_name: if text: size = gtk.ICON_SIZE_MENU else: size = gtk.ICON_SIZE_LARGE_TOOLBAR - width, height = gtk.icon_size_lookup(size) - if icon_name[0:6] == "theme:": - icon_name = self._get_real_name_from_theme(icon_name[6:], size) - pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(icon_name, width, height) + + if icon_name: + file_name = self._get_real_name_from_theme(icon_name[6:], size) + + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(file_name, width, height) else: pixbuf = None diff --git a/sugar/graphics/objectchooser.py b/sugar/graphics/objectchooser.py index 56a5a31b..4128cf31 100644 --- a/sugar/graphics/objectchooser.py +++ b/sugar/graphics/objectchooser.py @@ -150,7 +150,7 @@ class CollapsedEntry(CanvasRoundBox): self._icon_name = type.icon if not self._icon_name: - self._icon_name = 'theme:image-missing' + self._icon_name = 'image-missing' return self._icon_name From 8c288d7deeda09cf9d98b9ce57186bed5c808f3e Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Sun, 26 Aug 2007 16:49:55 -0400 Subject: [PATCH 25/36] Adapt Button() to new icon API --- sugar/graphics/button.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sugar/graphics/button.py b/sugar/graphics/button.py index fc869f99..647f36df 100644 --- a/sugar/graphics/button.py +++ b/sugar/graphics/button.py @@ -25,7 +25,7 @@ class CanvasButton(hippo.CanvasButton): hippo.CanvasButton.__init__(self, text=label) if icon_name: - icon = Icon(icon_name,icon_size=gtk.ICON_SIZE_BUTTON) + icon = Icon(icon_name=icon_name, icon_size=gtk.ICON_SIZE_BUTTON) self.props.widget.set_image(icon) icon.show() From 60c5e564cc5241200bb2c4a9fa6e827949fcdff4 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Mon, 27 Aug 2007 00:43:56 +0200 Subject: [PATCH 26/36] Fix getting pixbuf size, patch by jennjacobsen --- pylint.sh | 8 ++------ sugar/graphics/icon.py | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/pylint.sh b/pylint.sh index 07917423..f055f2ee 100755 --- a/pylint.sh +++ b/pylint.sh @@ -1,13 +1,9 @@ #!/bin/sh -TODO="C0111,C0301,C0322,W0311,C0324,W0331,W0212,W0611,W0613,W0201,W0106,W0622,W0403,W0612,W0102,W0404,W0704,W0402,W0702,W0401,E0602,E1111,W0101,W0105,W0601,W0602,W0703,W0701,W0312,W0231,W0233" +TODO="C0111,C0301,C0322,W0311,C0324,W0331,W0212,W0611,W0613,W0201,W0106,W0622,W0403,W0102,W0404,W0704,W0402,W0702,W0401,E0602,E1102,C0321,E0611,E1103,W1001,E0213,W0107,R0921,R0401,E1111,W0101,W0105,W0601,W0602,W0703,W0701,W0312,W0231,W0233,F0401,W0612" BROKEN="C0103,E1101" DISABLE="W0142,R0913,W0621,R0903,R0201,R0904,W0511,W0232,R0902,W0603,R0914,C0302,C0102,I0011,R0911,R0912,R0901,R0801,R0923,R0915" -PYTHONPATH=.:./shell:$SUGAR_PREFIX/lib/python2.4/site-packages/gtk-2.0:$PYTHONPATH \ - pylint \ - --include-ids=y \ - --disable-msg=$TODO,$BROKEN,$DISABLE \ - shell sugar +pylint --include-ids=y --disable-msg=$TODO,$BROKEN,$DISABLE shell sugar diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index cee7dec2..3ab65fa4 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -246,8 +246,8 @@ class _IconBuffer(object): icon_height = int(dimensions[1]) else: pixbuf = gtk.gdk.pixbuf_new_from_file(file_name) - icon_width = surface.get_width() - icon_height = surface.get_height() + icon_width = pixbuf.get_width() + icon_height = pixbuf.get_height() badge_info = self._get_badge_info(icon_info, icon_width, icon_height) From 14d51cc3818272ce79931d3e225eba0a46fc3816 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Mon, 27 Aug 2007 10:35:59 +0200 Subject: [PATCH 27/36] Update arabic translation. --- NEWS | 1 + po/ar.po | 153 ++++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 114 insertions(+), 40 deletions(-) diff --git a/NEWS b/NEWS index a9b66bbe..100fa3e8 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,4 @@ +* Update arabic translation. (khaled) * Restore Icon's ability to load absolute file paths. (tomeu) * #722 Show "charging" badge on battery. (danw) * #2010 Remember state when scrubbing. (marco) diff --git a/po/ar.po b/po/ar.po index 2c57d0a6..d1549e6c 100644 --- a/po/ar.po +++ b/po/ar.po @@ -7,82 +7,155 @@ msgid "" msgstr "" "Project-Id-Version: olpc-sugar.master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2007-06-28 10:44-0700\n" -"PO-Revision-Date: 2007-06-28 21:36+0300\n" +"POT-Creation-Date: 2007-08-13 17:41-0700\n" +"PO-Revision-Date: 2007-08-16 00:17+0300\n" "Last-Translator: Khaled Hosny \n" "Language-Team: Arabic \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: KBabel 1.11.4\n" -"Plural-Forms: nplurals=4; plural=n==1 ? 0 : n==2 ? 1 : n>=3 && n<=10 ? 2 : 3\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\nnplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" -#: ../shell/intro/intro.py:77 -msgid "Pick a buddy picture" -msgstr "اختر صورة صاحب" +#: ../shell/intro/intro.py:63 +msgid "Name:" +msgstr "الاسم:" -#: ../shell/intro/intro.py:100 -msgid "My Picture:" -msgstr "صورتي:" +#: ../shell/intro/intro.py:93 +msgid "Click to change color:" +msgstr "انقر لتغيير اللون:" -#: ../shell/intro/intro.py:180 -msgid "My Name:" -msgstr "اسمي:" +#: ../shell/intro/intro.py:147 +msgid "Back" +msgstr "السابق" -#: ../shell/intro/intro.py:204 -msgid "My Color:" -msgstr "لوني:" +#: ../shell/intro/intro.py:155 +msgid "Done" +msgstr "تمّ" + +#: ../shell/intro/intro.py:158 +msgid "Next" +msgstr "التالي" #: ../shell/view/BuddyMenu.py:83 msgid "Remove friend" msgstr "أزل صديق" -#: ../shell/view/BuddyMenu.py:87 +#: ../shell/view/BuddyMenu.py:86 msgid "Make friend" msgstr "اصنع صديق" -#: ../shell/view/BuddyMenu.py:97 +#. FIXME check that the buddy is not in the activity already +#: ../shell/view/BuddyMenu.py:98 msgid "Invite" msgstr "ادعُ" -#: ../shell/view/clipboardmenu.py:103 +#: ../shell/view/clipboardmenu.py:65 msgid "Remove" msgstr "أزل" -#: ../shell/view/clipboardmenu.py:110 +#: ../shell/view/clipboardmenu.py:70 msgid "Open" msgstr "افتح" -#: ../shell/view/clipboardmenu.py:117 -msgid "Stop download" -msgstr "أوقف التنزيل" - -#: ../shell/view/clipboardmenu.py:124 +#. self._stop_item = MenuItem(_('Stop download'), 'stock-close') +#. TODO: Implement stopping downloads +#. self._stop_item.connect('activate', self._stop_item_activate_cb) +#. self.append_menu_item(self._stop_item) +#: ../shell/view/clipboardmenu.py:80 msgid "Add to journal" -msgstr "أضف جرنال" +msgstr "أضف يوميّة" -#: ../services/clipboard/objecttypeservice.py:32 -msgid "Text" -msgstr "نص" - -#: ../services/clipboard/objecttypeservice.py:35 -msgid "Image" -msgstr "صورة" - -#: ../shell/view/Shell.py:227 -msgid "Screenshot" -msgstr "لقطة شاشة" - -#: ../shell/view/clipboardicon.py:211 +#: ../shell/view/clipboardmenu.py:158 #, python-format msgid "Clipboard object: %s." msgstr "عنصر الحافظة: %s." -#: ../shell/view/home/MeshBox.py:122 +#: ../shell/view/frame/zoombox.py:39 +msgid "Neighborhood" +msgstr "الجِوَار" + +#: ../shell/view/frame/zoombox.py:50 +msgid "Group" +msgstr "مجموعة" + +#: ../shell/view/frame/zoombox.py:61 +msgid "Home" +msgstr "منزل" + +#: ../shell/view/frame/zoombox.py:72 +msgid "Activity" +msgstr "النشاط" + +#: ../services/shell/objecttypeservice.py:32 +msgid "Text" +msgstr "نص" + +#: ../services/shell/objecttypeservice.py:36 +msgid "Image" +msgstr "صورة" + +#: ../shell/hardware/keydialog.py:113 +msgid "Authentication Type:" +msgstr "نوع الاستيثاق:" + +#: ../shell/hardware/keydialog.py:158 +msgid "Encryption Type:" +msgstr "نوع التعمية:" + +#: ../shell/view/home/activitiesdonut.py:75 +msgid "Starting..." +msgstr "يبدأ..." + +#: ../shell/view/home/activitiesdonut.py:89 +msgid "Resume" +msgstr "استكمل" + +#: ../shell/view/home/activitiesdonut.py:96 ../sugar/activity/activity.py:89 +msgid "Stop" +msgstr "قف" + +#: ../shell/view/Shell.py:214 +msgid "Screenshot" +msgstr "لقطة شاشة" + +#: ../shell/view/home/HomeBox.py:131 +msgid "Shutdown" +msgstr "أطفيء" + +#: ../shell/view/home/MeshBox.py:126 msgid "Mesh Network" msgstr "شبكة عُروِيّة" -#: ../sugar/activity/activity.py:232 +#: ../shell/view/devices/battery.py:34 +msgid "My Battery life" +msgstr "عمر بطاريتي" + +#: ../shell/view/devices/battery.py:87 +msgid "Battery charging" +msgstr "شحن البطاريّة" + +#: ../shell/view/devices/battery.py:89 +msgid "Battery discharging" +msgstr "تفريغ البطاريّة" + +#: ../shell/view/devices/battery.py:91 +msgid "Battery fully charged" +msgstr "البطارية مشحونة بالكامل" + +#: ../sugar/activity/activity.py:73 +msgid "Private" +msgstr "خاص" + +#: ../sugar/activity/activity.py:75 +msgid "My Neighborhood" +msgstr "جِوارِي" + +#: ../sugar/activity/activity.py:83 +msgid "Keep" +msgstr "ابقِ" + +#: ../sugar/activity/activity.py:262 #, python-format msgid "%s Activity" msgstr "نشاط %s" From b70182bbde5c177ccaac611da64ae5e3f667cf09 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Mon, 27 Aug 2007 11:07:46 +0200 Subject: [PATCH 28/36] Fix typo. --- sugar/graphics/icon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index 41df32b0..53db97d5 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -357,7 +357,7 @@ class Icon(gtk.Image): elif pspec.name == 'fill-color': self._buffer.fill_color = value elif pspec.name == 'stroke-color': - self._buffer.fill_color = value + self._buffer.stroke_color = value elif pspec.name == 'badge-name': self._buffer.badge_name = value else: From 42d0085b0b0940f4d0730912cfe38971b1ba5a04 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Mon, 27 Aug 2007 12:18:30 +0200 Subject: [PATCH 29/36] Use xo color for mesh and battery devices. --- shell/view/devices/battery.py | 7 +++++-- shell/view/devices/network/mesh.py | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/shell/view/devices/battery.py b/shell/view/devices/battery.py index ec971a54..8ba9aec9 100644 --- a/shell/view/devices/battery.py +++ b/shell/view/devices/battery.py @@ -14,9 +14,11 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +from gettext import gettext as _ + import gtk -from gettext import gettext as _ +from sugar import profile from sugar.graphics.icon import CanvasIcon from sugar.graphics.icon import get_icon_state from sugar.graphics import style @@ -30,7 +32,8 @@ _STATUS_FULLY_CHARGED = 2 class DeviceView(CanvasIcon): def __init__(self, model): - CanvasIcon.__init__(self, size=style.MEDIUM_ICON_SIZE) + CanvasIcon.__init__(self, size=style.MEDIUM_ICON_SIZE, + xo_color=profile.get_color()) self._model = model self._palette = BatteryPalette(_('My Battery life')) self.set_palette(self._palette) diff --git a/shell/view/devices/network/mesh.py b/shell/view/devices/network/mesh.py index f8f394a4..81c9914e 100644 --- a/shell/view/devices/network/mesh.py +++ b/shell/view/devices/network/mesh.py @@ -15,6 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +from sugar import profile from sugar.graphics import canvasicon from sugar.graphics import style from model.devices import device @@ -22,7 +23,7 @@ from model.devices import device class DeviceView(canvasicon.CanvasIcon): def __init__(self, model): canvasicon.CanvasIcon.__init__(self, size=style.MEDIUM_ICON_SIZE, - icon_name='network-mesh') + icon_name='network-mesh') self._model = model model.connect('notify::state', self._state_changed_cb) @@ -38,8 +39,7 @@ class DeviceView(canvasicon.CanvasIcon): self.props.fill_color = style.COLOR_INACTIVE_FILL.get_svg() self.props.stroke_color = style.COLOR_INACTIVE_STROKE.get_svg() elif state == device.STATE_ACTIVATED: - self.props.fill_color = None - self.props.stroke_color = None + self.props.xo_color = profile.get_color() elif state == device.STATE_INACTIVE: self.props.fill_color = style.COLOR_INACTIVE_FILL.get_svg() self.props.stroke_color = style.COLOR_INACTIVE_STROKE.get_svg() From 59ee222f962e98368f6cf1dffaa30d630d726541 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Mon, 27 Aug 2007 13:17:50 +0200 Subject: [PATCH 30/36] Fix comboboxes --- sugar/graphics/combobox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sugar/graphics/combobox.py b/sugar/graphics/combobox.py index 75573f0a..5584267e 100644 --- a/sugar/graphics/combobox.py +++ b/sugar/graphics/combobox.py @@ -85,7 +85,7 @@ class ComboBox(gtk.ComboBox): width, height = gtk.icon_size_lookup(size) if icon_name: - file_name = self._get_real_name_from_theme(icon_name[6:], size) + file_name = self._get_real_name_from_theme(icon_name, size) pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(file_name, width, height) else: From 7d0aedeb368d61dafff9e765512ba2a62c236e7e Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Mon, 27 Aug 2007 14:31:38 +0200 Subject: [PATCH 31/36] Fix typo. --- sugar/graphics/icon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index 53db97d5..6d7a4c6d 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -247,7 +247,7 @@ class _IconBuffer(object): icon_width = int(dimensions[0]) icon_height = int(dimensions[1]) else: - pixbuf = gtk.gdk.pixbuf_new_from_file(file_name) + pixbuf = gtk.gdk.pixbuf_new_from_file(icon_info.file_name) icon_width = pixbuf.get_width() icon_height = pixbuf.get_height() From 53d1ada429f00f0fcfb39917ce49fae0fed908f1 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Mon, 27 Aug 2007 15:58:53 +0200 Subject: [PATCH 32/36] One more icon fix. --- shell/view/clipboardicon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/view/clipboardicon.py b/shell/view/clipboardicon.py index d7756f1d..72f88db0 100644 --- a/shell/view/clipboardicon.py +++ b/shell/view/clipboardicon.py @@ -88,7 +88,7 @@ class ClipboardIcon(CanvasIcon): if icon_name: self.props.icon_name = icon_name else: - self.props.icon_name = 'theme:application-octet-stream' + self.props.icon_name = 'application-octet-stream' self._name = name self._percent = percent From 40ddf944572d594ec42a3d935ca1dd8040a30718 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Mon, 27 Aug 2007 16:26:57 +0200 Subject: [PATCH 33/36] Some more icon fixes. --- shell/view/frame/ActivitiesBox.py | 2 +- shell/view/home/FriendView.py | 2 +- shell/view/home/MeshBox.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shell/view/frame/ActivitiesBox.py b/shell/view/frame/ActivitiesBox.py index 0e377aff..291c5118 100644 --- a/shell/view/frame/ActivitiesBox.py +++ b/shell/view/frame/ActivitiesBox.py @@ -44,7 +44,7 @@ class ActivityButton(IconButton): class InviteButton(IconButton): def __init__(self, activity_model, invite): - IconButton.__init__(self, icon_name=activity_model.get_color()) + IconButton.__init__(self, file_name=activity_model.get_icon()) self.props.xo_color = activity_model.get_color() self._invite = invite diff --git a/shell/view/home/FriendView.py b/shell/view/home/FriendView.py index eb1eab55..3a6c5b85 100644 --- a/shell/view/home/FriendView.py +++ b/shell/view/home/FriendView.py @@ -67,7 +67,7 @@ class FriendView(hippo.CanvasBox): # than hiding the icon? name = self._get_new_icon_name(home_activity) if name: - self._activity_icon.props.icon_name = name + self._activity_icon.props.file_name = name self._activity_icon.props.xo_color = buddy.get_color() if not self._activity_icon_visible: self.append(self._activity_icon, hippo.PACK_EXPAND) diff --git a/shell/view/home/MeshBox.py b/shell/view/home/MeshBox.py index b099351d..e3f0c0fe 100644 --- a/shell/view/home/MeshBox.py +++ b/shell/view/home/MeshBox.py @@ -176,7 +176,7 @@ class ActivityView(hippo.CanvasBox): self._layout = SnowflakeLayout() self.set_layout(self._layout) - self._icon = CanvasIcon(icon_name=model.get_icon_name(), + self._icon = CanvasIcon(file_name=model.get_icon_name(), xo_color=model.get_color(), box_width=80) self._icon.connect('activated', self._clicked_cb) self._icon.set_tooltip(self._model.get_title()) From 294a5231b9a4b67220a38cacd0998e3a40ebd47b Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Mon, 27 Aug 2007 18:25:45 +0200 Subject: [PATCH 34/36] 2647, if security is enabled let rainbow launch activities. Also notify rainbow when active activity changes. Patch by Ashsong, had to merge it manually. --- bin/sugar.in | 6 +++++- shell/shellservice.py | 17 ++++++++++++++++- sugar/activity/activityfactory.py | 29 +++++++++++++++++++++++++---- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/bin/sugar.in b/bin/sugar.in index 4e6fc8b3..5026ad04 100644 --- a/bin/sugar.in +++ b/bin/sugar.in @@ -3,4 +3,8 @@ export SUGAR_PREFIX=@prefix@ export SUGAR_PATH=@prefix@/share/sugar export GTK2_RC_FILES=@prefix@/share/sugar/data/sugar-xo.gtkrc -dbus-launch --exit-with-session sugar-shell +if [ -f /etc/olpc-security ] ; then + dbus-launch --exit-with-session --config-file=/etc/dbus-1/session-olpc.conf sugar-shell +else + dbus-launch --exit-with-session sugar-shell +fi diff --git a/shell/shellservice.py b/shell/shellservice.py index d577a447..fdb26637 100644 --- a/shell/shellservice.py +++ b/shell/shellservice.py @@ -16,6 +16,7 @@ """D-bus service providing access to the shell's functionality""" import dbus +import os _DBUS_SERVICE = "org.laptop.Shell" _DBUS_SHELL_IFACE = "org.laptop.Shell" @@ -40,6 +41,9 @@ class ShellService(dbus.service.Object): XXX At the moment the d-bus service methods do not appear to do anything other than add_bundle """ + + _rainbow = None + def __init__(self, shell): self._shell = shell self._shell_model = shell.get_model() @@ -98,9 +102,20 @@ class ShellService(dbus.service.Object): def _owner_icon_changed_cb(self, new_icon): self.IconChanged(dbus.ByteArray(new_icon)) + def _get_rainbow_service(self): + """Lazily initializes an interface to the Rainbow security daemon.""" + if self._rainbow is None: + service = iface = 'org.laptop.security.Rainbow' + system_bus = dbus.SystemBus() + object = system_bus.get_object(service, '/') + self._rainbow = dbus.Interface(object, dbus_interface=iface, + follow_name_owner_change=True) + return self._rainbow + @dbus.service.signal(_DBUS_OWNER_IFACE, signature="s") def CurrentActivityChanged(self, activity_id): - pass + if os.path.exists('/etc/olpc-security'): + self._get_rainbow_service().ChangeActivity(activity_id, dbus_interface=iface) def _cur_activity_changed_cb(self, owner, new_activity): new_id = "" diff --git a/sugar/activity/activityfactory.py b/sugar/activity/activityfactory.py index 404e5f4c..d7d6d92f 100644 --- a/sugar/activity/activityfactory.py +++ b/sugar/activity/activityfactory.py @@ -26,12 +26,18 @@ from sugar.presence import presenceservice from sugar.activity.activityhandle import ActivityHandle from sugar import util +import os + _SHELL_SERVICE = "org.laptop.Shell" _SHELL_PATH = "/org/laptop/Shell" _SHELL_IFACE = "org.laptop.Shell" _ACTIVITY_FACTORY_INTERFACE = "org.laptop.ActivityFactory" +_RAINBOW_SERVICE_NAME = "org.laptop.security.Rainbow" +_RAINBOW_ACTIVITY_FACTORY_PATH = "/" +_RAINBOW_ACTIVITY_FACTORY_INTERFACE = "org.laptop.security.Rainbow" + def create_activity_id(): """Generate a new, unique ID for this activity""" pservice = presenceservice.get_instance() @@ -84,6 +90,9 @@ class ActivityCreationHandler(gobject.GObject): particular type of activity is created during the activity registration process in shell bundle registry which creates service definition files for each registered bundle type. + + If the file '/etc/olpc-security' exists, then activity launching + will be delegated to the prototype 'Rainbow' security service. """ gobject.GObject.__init__(self) self._service_name = service_name @@ -112,10 +121,22 @@ class ActivityCreationHandler(gobject.GObject): reply_handler=self._no_reply_handler, error_handler=self._notify_launch_error_handler) - self._factory.create(self._activity_handle.get_dict(), - timeout=120 * 1000, - reply_handler=self._no_reply_handler, - error_handler=self._create_error_handler) + if not os.path.exists('/etc/olpc-security'): + self._factory.create(self._activity_handle.get_dict(), + timeout=120 * 1000, + reply_handler=self._no_reply_handler, + error_handler=self._create_error_handler) + else: + system_bus = dbus.SystemBus() + factory = system_bus.get_object(_RAINBOW_SERVICE_NAME, + _RAINBOW_ACTIVITY_FACTORY_PATH) + factory.CreateActivity( + self._service_name, + self._activity_handle.get_dict(), + timeout=120 * 1000, + reply_handler=self._create_reply_handler, + error_handler=self._create_error_handler, + dbus_interface=_RAINBOW_ACTIVITY_FACTORY_INTERFACE) def get_activity_id(self): """Retrieve the unique identity for this activity""" From 1430fdc24f597828bd823193dc840a3d7874e0c2 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Mon, 27 Aug 2007 19:44:49 +0200 Subject: [PATCH 35/36] Add a copy method to the datastore wrapper. --- sugar/datastore/datastore.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/sugar/datastore/datastore.py b/sugar/datastore/datastore.py index 7af2e939..1fe9cc3f 100644 --- a/sugar/datastore/datastore.py +++ b/sugar/datastore/datastore.py @@ -63,6 +63,9 @@ class DSMetadata(gobject.GObject): def get_dictionary(self): return self._props + def copy(self): + return DSMetadata(self._props.copy()) + class DSObject(object): def __init__(self, object_id, metadata=None, file_path=None): self.object_id = object_id @@ -161,6 +164,9 @@ class DSObject(object): 'Please call DSObject.destroy() before disposing it.') self.destroy() + def copy(self): + return DSObject(None, self._metadata.copy(), self._file_path) + def get(object_id): logging.debug('datastore.get') metadata = dbus_helpers.get_properties(object_id) @@ -224,6 +230,16 @@ def find(query, sorting=None, limit=None, offset=None, reply_handler=None, return objects, total_count +def copy(jobject, mount_point): + + new_jobject = jobject.copy() + new_jobject.metadata['mountpoint'] = mount_point + + # this will cause the file be retrieved from the DS + new_jobject.file_path = jobject.file_path + + write(new_jobject) + def mount(uri, options): return dbus_helpers.mount(uri, options) From 7fcc23b4c8c36b28f2e17904c1a64244fdd553b6 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Mon, 27 Aug 2007 21:28:20 +0200 Subject: [PATCH 36/36] Very first go at the tray widget, based on Eduardo and Simon work. --- sugar/graphics/tray.py | 89 ++++++++++++++++++++++++++++++++++++++++++ tests/graphics/tray.py | 50 ++++++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 sugar/graphics/tray.py create mode 100644 tests/graphics/tray.py diff --git a/sugar/graphics/tray.py b/sugar/graphics/tray.py new file mode 100644 index 00000000..bfc4ad1a --- /dev/null +++ b/sugar/graphics/tray.py @@ -0,0 +1,89 @@ +# Copyright (C) 2007, One Laptop Per Child +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +import gobject +import gtk + +from sugar.graphics.toolbutton import ToolButton +from sugar.graphics.icon import Icon + +class _TrayViewport(gtk.Viewport): + def __init__(self): + gobject.GObject.__init__(self) + + self.set_shadow_type(gtk.SHADOW_NONE) + + self.traybar = gtk.Toolbar() + self.traybar.set_show_arrow(False) + self.add(self.traybar) + self.traybar.show() + + def scroll_right(self): + adj = self.get_hadjustment() + new_value = adj.value + self.allocation.width + adj.value = min(new_value, adj.upper - self.allocation.width) + + def scroll_left(self): + adj = self.get_hadjustment() + new_value = adj.value - self.allocation.width + adj.value = max(adj.lower, new_value) + +class HTray(gtk.HBox): + def __init__(self, **kwargs): + gobject.GObject.__init__(self, **kwargs) + + self._scroll_left = gtk.Button() + self._scroll_left.set_relief(gtk.RELIEF_NONE) + self._scroll_left.connect('clicked', self._scroll_left_cb) + + icon = Icon(icon_name='go-left', icon_size=gtk.ICON_SIZE_MENU) + self._scroll_left.set_image(icon) + icon.show() + + self.pack_start(self._scroll_left, False) + self._scroll_left.show() + + self._viewport = _TrayViewport() + self.pack_start(self._viewport) + self._viewport.show() + + self._scroll_right = gtk.Button() + self._scroll_right.set_relief(gtk.RELIEF_NONE) + self._scroll_right.connect('clicked', self._scroll_right_cb) + + icon = Icon(icon_name='go-right', icon_size=gtk.ICON_SIZE_MENU) + self._scroll_right.set_image(icon) + icon.show() + + self.pack_start(self._scroll_right, False) + self._scroll_right.show() + + def _scroll_left_cb(self, button): + self._viewport.scroll_left() + + def _scroll_right_cb(self, button): + self._viewport.scroll_right() + + def add_item(self, item, index=-1): + self._viewport.traybar.insert(item, index) + + def remove_item(self, index): + self._viewport.traybar.remove(item) + +class TrayButton(ToolButton): + def __init__(self, **kwargs): + ToolButton.__init__(self, **kwargs) diff --git a/tests/graphics/tray.py b/tests/graphics/tray.py new file mode 100644 index 00000000..8088fde7 --- /dev/null +++ b/tests/graphics/tray.py @@ -0,0 +1,50 @@ +# Copyright (C) 2007, Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +""" +Test the sugar.graphics.icon.Icon widget. +""" + +import gtk + +from sugar.graphics.tray import HTray +from sugar.graphics.tray import TrayButton + +import common + +test = common.Test() + +box = gtk.VBox() + +tray = HTray() +box.pack_start(tray, False) +tray.show() + +theme_icons = gtk.icon_theme_get_default().list_icons() + +for i in range(0, 100): + button = TrayButton(icon_name=theme_icons[i]) + tray.add_item(button) + button.show() + +test.pack_start(box) +box.show() + +test.show() + +if __name__ == "__main__": + common.main(test)