From e79aea7e6bb84d5a24c2566592fa17693e042c4b Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Wed, 02 Apr 2008 12:43:08 +0000 Subject: New activities ring. --- diff --git a/data/Makefile.am b/data/Makefile.am index 698721d..7771ce9 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -8,7 +8,6 @@ sugar-xo.gtkrc: gtkrc.em sugardir = $(pkgdatadir)/data sugar_DATA = \ - activities.defaults \ kbdconfig \ mime.defaults \ $(GTKRC_FILES) diff --git a/data/activities.defaults b/data/activities.defaults deleted file mode 100644 index af368f0..0000000 --- a/data/activities.defaults +++ /dev/null @@ -1,19 +0,0 @@ -# Ordered list of the activities displayed in the frame - -org.laptop.Chat -org.laptop.WebActivity -org.laptop.AbiWordActivity -org.laptop.RecordActivity -org.laptop.Oficina -org.laptop.TamTamMini -org.vpri.EtoysActivity -org.laptop.TurtleArtActivity -org.laptop.Pippy -org.laptop.Calculate -org.laptop.Terminal -org.laptop.MeasureActivity -org.laptop.AcousticMeasure -org.laptop.Memorize -org.laptop.TamTamJam -org.laptop.TamTamEdit -org.laptop.TamTamSynthLab diff --git a/service/activityregistryservice.py b/service/activityregistryservice.py index 9c2dda7..9ead767 100644 --- a/service/activityregistryservice.py +++ b/service/activityregistryservice.py @@ -33,6 +33,7 @@ class ActivityRegistry(dbus.service.Object): bundle_registry = bundleregistry.get_registry() bundle_registry.connect('bundle-added', self._bundle_added_cb) bundle_registry.connect('bundle-removed', self._bundle_removed_cb) + bundle_registry.connect('bundle-changed', self._bundle_changed_cb) @dbus.service.method(_ACTIVITY_REGISTRY_IFACE, in_signature='s', out_signature='b') @@ -101,6 +102,12 @@ class ActivityRegistry(dbus.service.Object): result.append(self._bundle_to_dict(bundle)) return result + @dbus.service.method(_ACTIVITY_REGISTRY_IFACE, + in_signature='sib', out_signature='') + def SetActivityFavorite(self, bundle_id, version, favorite): + registry = bundleregistry.get_registry() + registry.set_bundle_favorite(bundle_id, version, favorite) + @dbus.service.signal(_ACTIVITY_REGISTRY_IFACE, signature='a{sv}') def ActivityAdded(self, activity_info): pass @@ -109,14 +116,22 @@ class ActivityRegistry(dbus.service.Object): def ActivityRemoved(self, activity_info): pass + @dbus.service.signal(_ACTIVITY_REGISTRY_IFACE, signature='a{sv}') + def ActivityChanged(self, activity_info): + pass + def _bundle_to_dict(self, bundle): + registry = bundleregistry.get_registry() + favorite = registry.is_bundle_favorite(bundle.get_bundle_id(), + bundle.get_activity_version()) return {'name': bundle.get_name(), 'icon': bundle.get_icon(), 'bundle_id': bundle.get_bundle_id(), 'version': bundle.get_activity_version(), 'path': bundle.get_path(), 'command': bundle.get_command(), - 'show_launcher': bundle.get_show_launcher()} + 'show_launcher': bundle.get_show_launcher(), + 'favorite': favorite} def _bundle_added_cb(self, bundle_registry, bundle): self.ActivityAdded(self._bundle_to_dict(bundle)) @@ -124,6 +139,9 @@ class ActivityRegistry(dbus.service.Object): def _bundle_removed_cb(self, bundle_registry, bundle): self.ActivityRemoved(self._bundle_to_dict(bundle)) + def _bundle_changed_cb(self, bundle_registry, bundle): + self.ActivityChanged(self._bundle_to_dict(bundle)) + _instance = None def get_instance(): diff --git a/service/bundleregistry.py b/service/bundleregistry.py index 25a3440..9cf81cc 100644 --- a/service/bundleregistry.py +++ b/service/bundleregistry.py @@ -18,6 +18,7 @@ import os import logging import gobject +import simplejson from sugar.bundle.activitybundle import ActivityBundle from sugar.bundle.bundle import MalformedBundleException @@ -51,9 +52,11 @@ class BundleRegistry(gobject.GObject): """Service that tracks the available activity bundles""" __gsignals__ = { - 'bundle-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), + 'bundle-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), 'bundle-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'bundle-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])) } @@ -64,6 +67,17 @@ class BundleRegistry(gobject.GObject): self._search_path = [] self._mime_defaults = _load_mime_defaults() + path = env.get_profile_path('favorite_activities') + if os.path.exists(path): + try: + self._favorite_bundles = simplejson.load(open(path)) + print 'loaded %r' % self._favorite_bundles + except ValueError, e: + logging.error('Error while loading favorite_activities: %r.' % e) + self._favorite_bundles = [] + else: + self._favorite_bundles = [] + def get_bundle(self, bundle_id): """Returns an bundle given his service name""" for bundle in self._bundles: @@ -139,6 +153,32 @@ class BundleRegistry(gobject.GObject): else: return None + def _find_bundle(self, bundle_id, version): + for bundle in self._bundles: + if bundle.get_bundle_id() == bundle_id and \ + bundle.get_activity_version() == version: + return bundle + raise ValueError('No bundle %r with version %r exists.' % \ + (bundle_id, version)) + + def set_bundle_favorite(self, bundle_id, version, favorite): + bundle = self._find_bundle(bundle_id, version) + if favorite and not [bundle_id, version] in self._favorite_bundles: + self._favorite_bundles.append([bundle_id, version]) + self.emit('bundle-changed', bundle) + self._write_favorites_file() + elif not favorite and [bundle_id, version] in self._favorite_bundles: + self._favorite_bundles.remove([bundle_id, version]) + self.emit('bundle-changed', bundle) + self._write_favorites_file() + + def is_bundle_favorite(self, bundle_id, version): + return [bundle_id, version] in self._favorite_bundles + + def _write_favorites_file(self): + path = env.get_profile_path('favorite_activities') + simplejson.dump(self._favorite_bundles, open(path, 'w')) + def get_registry(): return _bundle_registry diff --git a/src/model/homemodel.py b/src/model/homemodel.py index 44d5417..8de989c 100644 --- a/src/model/homemodel.py +++ b/src/model/homemodel.py @@ -18,7 +18,6 @@ import logging import gobject import wnck -import dbus from sugar import wm from sugar import activity @@ -270,7 +269,9 @@ class HomeModel(gobject.GObject): def notify_activity_launch_failed(self, activity_id): home_activity = self._get_activity_by_id(activity_id) if home_activity: - logging.debug("Activity %s (%s) launch failed" % (activity_id, home_activity.get_type())) + logging.debug("Activity %s (%s) launch failed" % \ + (activity_id, home_activity.get_type())) + home_activity.props.launching = False self._remove_activity(home_activity) else: logging.error('Model for activity id %s does not exist.' % activity_id) @@ -278,6 +279,7 @@ class HomeModel(gobject.GObject): def _check_activity_launched(self, activity_id): home_activity = self._get_activity_by_id(activity_id) if home_activity and home_activity.props.launching: - logging.debug('Activity %s still launching, assuming it failed...', activity_id) + logging.debug('Activity %s still launching, assuming it failed...', + activity_id) self.notify_activity_launch_failed(activity_id) return False diff --git a/src/view/Makefile.am b/src/view/Makefile.am index abbb230..b09d965 100644 --- a/src/view/Makefile.am +++ b/src/view/Makefile.am @@ -11,4 +11,5 @@ sugar_PYTHON = \ keyhandler.py \ pulsingicon.py \ OverlayWindow.py \ + palettes.py \ Shell.py diff --git a/src/view/frame/activitiestray.py b/src/view/frame/activitiestray.py index 2060783..e2a40cc 100644 --- a/src/view/frame/activitiestray.py +++ b/src/view/frame/activitiestray.py @@ -14,140 +14,19 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -import os -import statvfs import logging from gettext import gettext as _ import gtk -from sugar import env -from sugar import profile from sugar.graphics import style from sugar.graphics.tray import HTray from sugar.graphics.radiotoolbutton import RadioToolButton from sugar.graphics.icon import Icon -from sugar.graphics.palette import Palette -from sugar.graphics.menuitem import MenuItem +from view.palettes import JournalPalette, CurrentActivityPalette from view.frame.frameinvoker import FrameWidgetInvoker -class _Palette(Palette): - def __init__(self, widget, home_activity): - Palette.__init__(self, '', menu_after_content=True) - - self.props.invoker = FrameWidgetInvoker(widget) - self.set_group_id('frame') - - if home_activity.props.launching: - home_activity.connect('notify::launching', self._launching_changed_cb) - self.set_primary_text(_('Starting...')) - else: - self.setup_palette() - - def _launching_changed_cb(self, home_activity, pspec): - if not home_activity.props.launching: - self.setup_palette() - - def setup_palette(self): - raise NotImplementedError - -class ActivityPalette(_Palette): - def __init__(self, widget, home_activity): - _Palette.__init__(self, widget, home_activity) - self._home_activity = home_activity - - def setup_palette(self): - self.set_primary_text(self._home_activity.get_title()) - - menu_item = MenuItem(_('Resume'), 'activity-start') - menu_item.connect('activate', self.__resume_activate_cb) - self.menu.append(menu_item) - menu_item.show() - - """ Not implemented yet - menu_item = MenuItem(_('Share with'), 'zoom-neighborhood') - #menu_item.connect('activate', self.__share_activate_cb) - self.menu.append(menu_item) - menu_item.show() - - menu_item = MenuItem(_('Keep')) - icon = Icon(icon_name='document-save', icon_size=gtk.ICON_SIZE_MENU, - xo_color=profile.get_color()) - menu_item.set_image(icon) - icon.show() - #menu_item.connect('activate', self.__keep_activate_cb) - self.menu.append(menu_item) - menu_item.show() - """ - - separator = gtk.SeparatorMenuItem() - self.menu.append(separator) - separator.show() - - menu_item = MenuItem(_('Stop'), 'activity-stop') - menu_item.connect('activate', self.__stop_activate_cb) - self.menu.append(menu_item) - menu_item.show() - - def __resume_activate_cb(self, menu_item): - self._home_activity.get_window().activate(1) - - def __stop_activate_cb(self, menu_item): - self._home_activity.get_window().close(1) - -class JournalPalette(_Palette): - def __init__(self, widget, home_activity): - _Palette.__init__(self, widget, home_activity) - self._home_activity = home_activity - self._progress_bar = None - self._free_space_label = None - - def setup_palette(self): - self.set_primary_text(self._home_activity.get_title()) - - vbox = gtk.VBox() - self.set_content(vbox) - vbox.show() - - self._progress_bar = gtk.ProgressBar() - vbox.add(self._progress_bar) - self._progress_bar.show() - - self._free_space_label = gtk.Label() - self._free_space_label.set_alignment(0.5, 0.5) - vbox.add(self._free_space_label) - self._free_space_label.show() - - self.connect('popup', self.__popup_cb) - - menu_item = MenuItem(_('Show contents')) - - icon = Icon(file=self._home_activity.get_icon_path(), - icon_size=gtk.ICON_SIZE_MENU, - xo_color=self._home_activity.get_icon_color()) - menu_item.set_image(icon) - icon.show() - - menu_item.connect('activate', self.__open_activate_cb) - self.menu.append(menu_item) - menu_item.show() - - def __open_activate_cb(self, menu_item): - self._home_activity.get_window().activate(1) - - def __popup_cb(self, palette): - # TODO: we should be able to ask the datastore this info, as that's the - # component that knows about mount points. - stat = os.statvfs(env.get_profile_path()) - free_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BAVAIL] - total_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BLOCKS] - - fraction = (total_space - free_space) / float(total_space) - self._progress_bar.props.fraction = fraction - self._free_space_label.props.label = _('%(free_space)d MB Free') % \ - {'free_space': free_space / (1024 * 1024)} - class ActivityButton(RadioToolButton): def __init__(self, home_activity, group): RadioToolButton.__init__(self, group=group) @@ -165,7 +44,9 @@ class ActivityButton(RadioToolButton): if self._home_activity.get_type() == "org.laptop.JournalActivity": palette = JournalPalette(self, self._home_activity) else: - palette = ActivityPalette(self, self._home_activity) + palette = CurrentActivityPalette(self, self._home_activity) + palette.props.invoker = FrameWidgetInvoker(self) + palette.set_group_id('frame') self.set_palette(palette) class ActivitiesTray(HTray): diff --git a/src/view/home/HomeBox.py b/src/view/home/HomeBox.py index 95ddc78..76fc9b1 100644 --- a/src/view/home/HomeBox.py +++ b/src/view/home/HomeBox.py @@ -46,7 +46,7 @@ class HomeBox(hippo.CanvasBox, hippo.CanvasItem): self._toolbar.connect('view-changed', self.__toolbar_view_changed_cb) self.append(hippo.CanvasWidget(widget=self._toolbar)) - self._set_view(_LIST_VIEW) + self._set_view(_RING_VIEW) def __toolbar_view_changed_cb(self, toolbar, view): self._set_view(view) @@ -153,7 +153,6 @@ class HomeToolbar(gtk.Toolbar): list_button.connect('toggled', self.__view_button_toggled_cb, _LIST_VIEW) self.insert(list_button, -1) list_button.show() - list_button.props.active = True self._add_separator() diff --git a/src/view/home/MeshBox.py b/src/view/home/MeshBox.py index 7d78b41..40e39c6 100644 --- a/src/view/home/MeshBox.py +++ b/src/view/home/MeshBox.py @@ -23,6 +23,7 @@ import gobject import gtk from sugar.graphics.icon import CanvasIcon +from sugar.graphics.xocolor import XoColor from sugar.graphics import style from sugar.graphics.icon import get_icon_state from sugar.graphics import style @@ -37,7 +38,7 @@ from model.devices.network import wireless from hardware import hardwaremanager from hardware import nmclient from view.BuddyIcon import BuddyIcon -from view.pulsingicon import PulsingIcon +from view.pulsingicon import CanvasPulsingIcon from view.home.snowflakelayout import SnowflakeLayout from view.home.spreadlayout import SpreadLayout @@ -46,9 +47,9 @@ from hardware.nmclient import NM_802_11_CAP_PROTO_WEP, NM_802_11_CAP_PROTO_WPA, _ICON_NAME = 'network-wireless' -class AccessPointView(PulsingIcon): +class AccessPointView(CanvasPulsingIcon): def __init__(self, model, mesh_device=None): - PulsingIcon.__init__(self, size=style.STANDARD_ICON_SIZE, cache=True) + CanvasPulsingIcon.__init__(self, size=style.STANDARD_ICON_SIZE, cache=True) self._model = model self._meshdev = mesh_device self._disconnect_item = None @@ -60,9 +61,9 @@ class AccessPointView(PulsingIcon): model.connect('notify::name', self._name_changed_cb) model.connect('notify::state', self._state_changed_cb) - (stroke, fill) = model.get_nm_network().get_colors() - self._device_stroke = stroke - self._device_fill = fill + pulse_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(), + style.COLOR_TRANSPARENT.get_svg())) + self.props.pulse_color = pulse_color self._palette = self._create_palette() self.set_palette(self._palette) @@ -128,35 +129,22 @@ class AccessPointView(PulsingIcon): if self._model.props.state == accesspointmodel.STATE_CONNECTING: if self._disconnect_item: self._disconnect_item.hide() - self.props.pulse_time = 1.0 - self.props.colors = [ - [ style.Color(self._device_stroke).get_svg(), - style.Color(self._device_fill).get_svg() ], - [ style.Color(self._device_stroke).get_svg(), - '#e2e2e2' ] - ] + self.props.pulsing = True elif self._model.props.state == accesspointmodel.STATE_CONNECTED: if self._disconnect_item: self._disconnect_item.show() - self.props.pulse_time = 0.0 - self.props.colors = [ - [ '#ffffff', - style.Color(self._device_fill).get_svg() ], - [ '#ffffff', - style.Color(self._device_fill).get_svg() ] - ] + self.props.pulsing = False elif self._model.props.state == accesspointmodel.STATE_NOTCONNECTED: if self._disconnect_item: self._disconnect_item.hide() - self.props.pulse_time = 0.0 - self.props.colors = [ - [ style.Color(self._device_stroke).get_svg(), - style.Color(self._device_fill).get_svg() ] - ] + self.props.pulsing = False if self._greyed_out: - self.props.pulse_time = 0.0 - self.props.colors = [['#D5D5D5', '#D5D5D5']] + self.props.pulsing = False + self.props.base_color = XoColor('#D5D5D5,#D5D5D5') + else: + self.props.base_color = XoColor('%s,%s' % \ + self._model.get_nm_network().get_colors()) def set_filter(self, query): self._greyed_out = self._model.props.name.lower().find(query) == -1 @@ -164,7 +152,7 @@ class AccessPointView(PulsingIcon): _MESH_ICON_NAME = 'network-mesh' -class MeshDeviceView(PulsingIcon): +class MeshDeviceView(CanvasPulsingIcon): def __init__(self, nm_device, channel): if not channel in [1, 6, 11]: raise ValueError("Invalid channel %d" % channel) @@ -181,9 +169,9 @@ class MeshDeviceView(PulsingIcon): self._palette = self._create_palette() self.set_palette(self._palette) - mycolor = profile.get_color() - self._device_fill = mycolor.get_fill_color() - self._device_stroke = mycolor.get_stroke_color() + pulse_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(), + style.COLOR_TRANSPARENT.get_svg())) + self.props.pulse_color = pulse_color self.connect('activated', self._activate_cb) @@ -221,35 +209,21 @@ class MeshDeviceView(PulsingIcon): def _update_state(self): state = self._nm_device.get_state() chan = wireless.freq_to_channel(self._nm_device.get_frequency()) - if self._greyed_out: - self.props.colors = [['#D5D5D5', '#D5D5D5']] - elif state == nmclient.DEVICE_STATE_ACTIVATING and chan == self.channel: + if state == nmclient.DEVICE_STATE_ACTIVATING and chan == self.channel: self._disconnect_item.hide() - self.props.pulse_time = 0.75 - self.props.colors = [ - [ style.Color(self._device_stroke).get_svg(), - style.Color(self._device_fill).get_svg() ], - [ style.Color(self._device_stroke).get_svg(), - '#e2e2e2' ] - ] + self.props.pulsing = True elif state == nmclient.DEVICE_STATE_ACTIVATED and chan == self.channel: self._disconnect_item.show() - self.props.pulse_time = 0.0 - self.props.colors = [ - [ '#ffffff', - style.Color(self._device_fill).get_svg() ], - [ '#ffffff', - style.Color(self._device_fill).get_svg() ] - ] + self.props.pulsing = False elif state == nmclient.DEVICE_STATE_INACTIVE or chan != self.channel: self._disconnect_item.hide() - self.props.pulse_time = 0.0 - self.props.colors = [ - [ style.Color(self._device_stroke).get_svg(), - style.Color(self._device_fill).get_svg() ] - ] + self.props.pulsing = False + + if self._greyed_out: + self.props.pulsing = False + self.props.base_color = XoColor('#D5D5D5,#D5D5D5') else: - raise RuntimeError("Shouldn't get here") + self.props.base_color = profile.get_color() def set_filter(self, query): self._greyed_out = (query != '') diff --git a/src/view/home/activitieslist.py b/src/view/home/activitieslist.py index 5ccfe57..2fa404d 100644 --- a/src/view/home/activitieslist.py +++ b/src/view/home/activitieslist.py @@ -17,7 +17,6 @@ import logging import gobject -import gtk import hippo from sugar import profile @@ -38,33 +37,25 @@ class ActivitiesList(hippo.CanvasScrollbars): registry = activity.get_registry() registry.get_activities_async(reply_handler=self._get_activities_cb) - - registry.connect('activity-added', self._activity_added_cb) - registry.connect('activity-removed', self._activity_removed_cb) + registry.connect('activity-added', self.__activity_added_cb) + registry.connect('activity-removed', self.__activity_removed_cb) def _get_activities_cb(self, activity_list): for info in activity_list: if info.bundle_id != 'org.laptop.JournalActivity': self._add_activity(info) - def _activity_added_cb(self, activity_registry, activity_info): + def __activity_added_cb(self, activity_registry, activity_info): self._add_activity(activity_info) - def _activity_removed_cb(self, activity_registry, activity_info): - """ - for item in self._tray.get_children(): - if item.get_bundle_id() == activity_info.bundle_id: - self._tray.remove_item(item) + def __activity_removed_cb(self, activity_registry, activity_info): + for entry in self.get_children(): + if entry.get_bundle_id() == activity_info.bundle_id: + self.remove(entry) return - """ - # TODO: Implement activity removal. - pass def _add_activity(self, activity_info): - entry = ActivityEntry(self._shell, activity_info) - #item.connect('clicked', self._activity_clicked_cb) - #item.connect('remove_activity', self._remove_activity_cb) - self._box.append(entry) + self._box.append(ActivityEntry(self._shell, activity_info)) class ActivityEntry(hippo.CanvasBox, hippo.CanvasItem): __gtype_name__ = 'SugarActivityEntry' @@ -85,18 +76,20 @@ class ActivityEntry(hippo.CanvasBox, hippo.CanvasItem): self._shell = shell self._activity_info = activity_info - favorite_icon = FavoriteIcon(False) - #favorite_icon.connect('button-release-event', - # self._favorite_icon_button_release_event_cb) + favorite_icon = FavoriteIcon(self._activity_info.favorite) + favorite_icon.connect('notify::favorite', self.__favorite_changed_cb) self.append(favorite_icon) - icon = CanvasIcon(size=style.STANDARD_ICON_SIZE, cache=True, + self.icon = CanvasIcon(size=style.STANDARD_ICON_SIZE, cache=True, file_name=activity_info.icon, stroke_color=style.COLOR_BUTTON_GREY.get_svg(), fill_color=style.COLOR_TRANSPARENT.get_svg()) - icon.connect_after('button-release-event', - self.__icon_button_release_event_cb) - self.append(icon) + self.icon.connect('hovering-changed', + self.__icon_hovering_changed_event_cb) + self.icon.connect('button-release-event', + self.__icon_button_release_event_cb) + + self.append(self.icon) title = hippo.CanvasText(text=activity_info.name, xalign=hippo.ALIGNMENT_START, @@ -119,9 +112,24 @@ class ActivityEntry(hippo.CanvasBox, hippo.CanvasItem): box_width=ActivityEntry._DATE_COL_WIDTH) self.append(date) + def __favorite_changed_cb(self, favorite_icon, pspec): + registry = activity.get_registry() + registry.set_activity_favorite(self._activity_info.bundle_id, + self._activity_info.version, favorite_icon.props.favorite) + + def __icon_hovering_changed_event_cb(self, icon, event): + if event: + self.icon.props.xo_color = profile.get_color() + else: + self.icon.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg() + self.icon.props.fill_color = style.COLOR_TRANSPARENT.get_svg() + def __icon_button_release_event_cb(self, icon, event): self._shell.start_activity(self._activity_info.bundle_id) + def get_bundle_id(self): + return self._activity_info.bundle_id + class FavoriteIcon(CanvasIcon): __gproperties__ = { 'favorite' : (bool, None, None, False, @@ -134,6 +142,7 @@ class FavoriteIcon(CanvasIcon): size=style.SMALL_ICON_SIZE) self._favorite = None self._set_favorite(favorite) + self.connect('button-release-event', self.__release_event_cb) def _set_favorite(self, favorite): if favorite == self._favorite: @@ -158,3 +167,6 @@ class FavoriteIcon(CanvasIcon): else: return CanvasIcon.do_get_property(self, pspec) + def __release_event_cb(self, icon, event): + self.props.favorite = not self.props.favorite + diff --git a/src/view/home/activitiesring.py b/src/view/home/activitiesring.py index 5967623..4566823 100644 --- a/src/view/home/activitiesring.py +++ b/src/view/home/activitiesring.py @@ -20,6 +20,7 @@ import logging import signal from gettext import gettext as _ import re +import math import gobject import gtk @@ -29,9 +30,14 @@ import dbus from hardware import hardwaremanager from sugar.graphics import style from sugar.graphics.palette import Palette +from sugar.graphics.icon import Icon, CanvasIcon +from sugar.graphics.xocolor import XoColor +from sugar.graphics.menuitem import MenuItem from sugar.profile import get_profile from sugar import env +from sugar import activity +from view.palettes import JournalPalette, CurrentActivityPalette from view.home.MyIcon import MyIcon from model.shellmodel import ShellModel from hardware import schoolserver @@ -42,15 +48,57 @@ class ActivitiesRing(hippo.CanvasBox, hippo.CanvasItem): __gtype_name__ = 'SugarActivitiesRing' def __init__(self, shell): - hippo.CanvasBox.__init__(self, background_color=0xe2e2e2ff) + hippo.CanvasBox.__init__(self, background_color=style.COLOR_WHITE.get_int()) - shell_model = shell.get_model() + self._shell = shell + shell.get_model().connect('notify::state', self._shell_state_changed_cb) self._my_icon = _MyIcon(shell, style.XLARGE_ICON_SIZE) self.append(self._my_icon, hippo.PACK_FIXED) - shell_model.connect('notify::state', - self._shell_state_changed_cb) + self._current_activity = CurrentActivityIcon(shell) + self.append(self._current_activity, hippo.PACK_FIXED) + + self.set_layout(RingLayout()) + + registry = activity.get_registry() + registry.get_activities_async(reply_handler=self._get_activities_cb) + registry.connect('activity-added', self.__activity_added_cb) + registry.connect('activity-removed', self.__activity_removed_cb) + registry.connect('activity-changed', self.__activity_changed_cb) + + def _get_activities_cb(self, activity_list): + for info in activity_list: + if info.favorite and info.bundle_id != "org.laptop.JournalActivity": + self.append(ActivityIcon(self._shell, info)) + + def __activity_added_cb(self, activity_registry, activity_info): + if activity_info.favorite and \ + activity_info.bundle_id != "org.laptop.JournalActivity": + self.append(ActivityIcon(self._shell, activity_info)) + + def _find_activity_icon(self, bundle_id, version): + for icon in self.get_children(): + if isinstance(icon, ActivityIcon) and \ + icon.bundle_id == bundle_id and icon.version == version: + return icon + return None + + def __activity_removed_cb(self, activity_registry, activity_info): + icon = self._find_activity_icon(activity_info.bundle_id, + activity_info.version) + if icon is not None: + self.remove(icon) + + def __activity_changed_cb(self, activity_registry, activity_info): + if activity_info.bundle_id == "org.laptop.JournalActivity": + return + icon = self._find_activity_icon(activity_info.bundle_id, + activity_info.version) + if icon is not None and not activity_info.favorite: + self.remove(icon) + elif icon is None and activity_info.favorite: + self.append(ActivityIcon(self._shell, activity_info)) def _shell_state_changed_cb(self, model, pspec): # FIXME implement this @@ -60,13 +108,194 @@ class ActivitiesRing(hippo.CanvasBox, hippo.CanvasItem): def do_allocate(self, width, height, origin_changed): hippo.CanvasBox.do_allocate(self, width, height, origin_changed) - [icon_width, icon_height] = self._my_icon.get_allocation() - self.set_position(self._my_icon, (width - icon_width) / 2, - (height - icon_height) / 2) + [my_icon_width, my_icon_height] = self._my_icon.get_allocation() + x = (width - my_icon_width) / 2 + y = (height - my_icon_height - style.GRID_CELL_SIZE) / 2 + self.set_position(self._my_icon, x, y) + + [icon_width, icon_height] = self._current_activity.get_allocation() + x = (width - icon_width) / 2 + y = (height + my_icon_height + style.DEFAULT_PADDING - style.GRID_CELL_SIZE) / 2 + self.set_position(self._current_activity, x, y) def enable_xo_palette(self): self._my_icon.enable_palette() +class ActivityIcon(CanvasIcon): + def __init__(self, shell, activity_info): + CanvasIcon.__init__(self, cache=True, file_name=activity_info.icon) + self._shell = shell + self._activity_info = activity_info + self.set_palette(ActivityPalette(shell, activity_info)) + self.connect('hovering-changed', self.__hovering_changed_event_cb) + self.connect('button-release-event', self.__button_release_event_cb) + + self.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg() + self.props.fill_color = style.COLOR_TRANSPARENT.get_svg() + + def __hovering_changed_event_cb(self, icon, event): + if event: + self.props.xo_color = get_profile().color + else: + self.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg() + self.props.fill_color = style.COLOR_TRANSPARENT.get_svg() + + def __button_release_event_cb(self, icon, event): + self._shell.start_activity(self._activity_info.bundle_id) + + def get_bundle_id(self): + return self._activity_info.bundle_id + bundle_id = property(get_bundle_id, None) + + def get_version(self): + return self._activity_info.version + version = property(get_version, None) + +class ActivityPalette(Palette): + def __init__(self, shell, activity_info): + activity_icon = Icon(file=activity_info.icon, + xo_color=get_profile().color, + icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR) + + Palette.__init__(self, None, None, primary_text=activity_info.name, + icon=activity_icon) + + self._shell = shell + self._activity_info = activity_info + + menu_item = MenuItem(_('Start'), 'activity-start') + menu_item.connect('activate', self.__start_activate_cb) + self.menu.append(menu_item) + menu_item.show() + + menu_item = MenuItem(_('Start with'), 'activity-start') + menu_item.props.sensitive = False + #menu_item.connect('activate', self.__start_with_activate_cb) + self.menu.append(menu_item) + menu_item.show() + + def __start_activate_cb(self, menu_item): + self._shell.start_activity(self._activity_info.bundle_id) + +class CurrentActivityIcon(CanvasIcon, hippo.CanvasItem): + def __init__(self, shell): + CanvasIcon.__init__(self, cache=True) + self._home_model = shell.get_model().get_home() + + if self._home_model.get_pending_activity() is not None: + self._update(self._home_model.get_pending_activity()) + + self._home_model.connect('pending-activity-changed', + self.__pending_activity_changed_cb) + + self.connect('button-release-event', self.__button_release_event_cb) + + def __button_release_event_cb(self, icon, event): + self._home_model.get_pending_activity().get_window().activate(1) + + def _update(self, home_activity): + _logger.debug('CurrentActivityIcon._update') + self.props.file_name = home_activity.get_icon_path() + self.props.xo_color = home_activity.get_icon_color() + self.props.size = style.STANDARD_ICON_SIZE + + if home_activity.get_type() == "org.laptop.JournalActivity": + palette = JournalPalette(self, home_activity) + else: + palette = CurrentActivityPalette(self, home_activity) + self.set_palette(palette) + + def __pending_activity_changed_cb(self, home_model, home_activity): + self._update(home_activity) + +class RingLayout(gobject.GObject, hippo.CanvasLayout): + __gtype_name__ = 'SugarRingLayout' + def __init__(self): + gobject.GObject.__init__(self) + self._box = None + + def do_set_box(self, box): + self._box = box + + def do_get_height_request(self, for_width): + return 0, gtk.gdk.screen_height() - style.GRID_CELL_SIZE + + def do_get_width_request(self): + return 0, gtk.gdk.screen_width() + + def _calculate_radius_and_icon_size(self, children_count): + minimum_radius = style.XLARGE_ICON_SIZE / 2 + style.DEFAULT_SPACING + \ + style.STANDARD_ICON_SIZE * 2 + maximum_radius = (gtk.gdk.screen_height() - style.GRID_CELL_SIZE) / 2 - \ + style.STANDARD_ICON_SIZE - style.DEFAULT_SPACING + angle = 2 * math.pi / children_count + + _logger.debug('minimum_radius %r maximum_radius %r angle %r' % \ + (minimum_radius, maximum_radius, angle)) + + # what's the radius required without downscaling? + distance = style.STANDARD_ICON_SIZE + style.DEFAULT_SPACING + icon_size = style.STANDARD_ICON_SIZE + + if children_count == 1: + radius = 0 + else: + radius = math.sqrt(distance ** 2 / + (math.sin(angle) ** 2 + (math.cos(angle) - 1) ** 2)) + + _logger.debug('radius 1 %r' % radius) + + if radius < minimum_radius: + # we can upscale, if we want + icon_size += style.STANDARD_ICON_SIZE * \ + (0.5 * (minimum_radius - radius)/minimum_radius) + radius = minimum_radius + elif radius > maximum_radius: + radius = maximum_radius + # need to downscale. what's the icon size required? + distance = math.sqrt((radius * math.sin(angle)) ** 2 + \ + (radius * (math.cos(angle) - 1)) ** 2) + icon_size = distance - style.DEFAULT_SPACING + + _logger.debug('radius 2 %r icon_size %r' % (radius, icon_size)) + + return radius, icon_size + + def _calculate_position(self, radius, icon_size, index, children_count): + width, height = self._box.get_allocation() + angle = index * (2 * math.pi / children_count) - math.pi/2 + x = radius * math.cos(angle) + (width - icon_size) / 2 + y = radius * math.sin(angle) + (height - icon_size - style.GRID_CELL_SIZE) / 2 + return x, y + + def do_allocate(self, x, y, width, height, req_width, req_height, + origin_changed): + _logger.debug('RingLayout.do_allocate: %r %r %r %r %r %r %r' % (x, y, + width, height, req_width, req_height, origin_changed)) + + children = self._box.get_layout_children() + if not children: + return + + radius, icon_size = self._calculate_radius_and_icon_size(len(children)) + + for n in range(len(children)): + child = children[n] + # TODO: We get here a glib warning and I don't know why. + child.item.props.size = icon_size + + x, y = self._calculate_position(radius, icon_size, n, len(children)) + + # We need to always get requests to not confuse hippo + min_w, child_width = child.get_width_request() + min_h, child_height = child.get_height_request(child_width) + + child.allocate(int(x), + int(y), + child_width, + child_height, + origin_changed) + class _MyIcon(MyIcon): def __init__(self, shell, scale): MyIcon.__init__(self, scale) @@ -76,29 +305,40 @@ class _MyIcon(MyIcon): self._profile = get_profile() def enable_palette(self): - palette = Palette(self._profile.nick_name) + palette_icon = Icon(icon_name='computer-xo', + icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR, + xo_color=self._profile.color) + palette = Palette(self._profile.nick_name, + #secondary_text='Sample secondary label', + icon=palette_icon) + + item = MenuItem(_('About this XO')) + + icon = Icon(icon_name='computer-xo', icon_size=gtk.ICON_SIZE_MENU, + xo_color=self._profile.color) + item.set_image(icon) + icon.show() + + item.connect('activate', self._about_activate_cb) + palette.menu.append(item) + item.show() - item = gtk.MenuItem(_('Reboot')) + item = MenuItem(_('Restart'), 'system-restart') item.connect('activate', self._reboot_activate_cb) palette.menu.append(item) item.show() - item = gtk.MenuItem(_('Shutdown')) + item = MenuItem(_('Shutdown'), 'system-shutdown') item.connect('activate', self._shutdown_activate_cb) palette.menu.append(item) item.show() if not self._profile.is_registered(): - item = gtk.MenuItem(_('Register')) + item = MenuItem(_('Register'), 'media-record') item.connect('activate', self._register_activate_cb) palette.menu.append(item) item.show() - item = gtk.MenuItem(_('About this XO')) - item.connect('activate', self._about_activate_cb) - palette.menu.append(item) - item.show() - self.set_palette(palette) def _reboot_activate_cb(self, menuitem): diff --git a/src/view/pulsingicon.py b/src/view/pulsingicon.py index 9e7b3d9..b937816 100644 --- a/src/view/pulsingicon.py +++ b/src/view/pulsingicon.py @@ -1,4 +1,4 @@ -# Copyright (C) 2006-2007 Red Hat, Inc. +# Copyright (C) 2008 One Laptop Per Child # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -15,76 +15,150 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import gobject +import math from sugar.graphics.icon import CanvasIcon +from sugar.graphics.style import Color +from sugar.graphics.xocolor import XoColor + +class CanvasPulsingIcon(CanvasIcon): + __gtype_name__ = 'SugarCanvasPulsingIcon' + + _INTERVAL = 100 + _STEP = math.pi / 10 # must be a fraction of pi, for clean caching -class PulsingIcon(CanvasIcon): __gproperties__ = { - 'paused' : (bool, None, None, False, - gobject.PARAM_READWRITE), - 'colors' : (object, None, None, - gobject.PARAM_READWRITE), - 'pulse-time' : (float, None, None, - 0.0, 500.0, 0.0, - gobject.PARAM_READWRITE), + 'base-color' : (object, None, None, gobject.PARAM_READWRITE), + 'pulse-color' : (object, None, None, gobject.PARAM_READWRITE), + 'pulsing' : (bool, None, None, False, gobject.PARAM_READWRITE), + 'paused' : (bool, None, None, False, gobject.PARAM_READWRITE) } def __init__(self, **kwargs): + self._base_color = None + self._pulse_color = None + self._pulse_hid = None self._paused = False - self._pulse_time = 0.0 - self._colors = None - self._pulse_sid = 0 - self._pos = 0 + self._pulsing = False + self._level = 0 + self._phase = 0 CanvasIcon.__init__(self, **kwargs) + def _start_pulsing(self, restart=False): + if restart: + self._phase = 0 + self._pulse_hid = gobject.timeout_add(self._INTERVAL, self.__pulse_cb) + + def _stop_pulsing(self): + if self._pulse_hid is not None: + gobject.source_remove(self._pulse_hid) + self._pulse_hid = None + self.props.xo_color = self._base_color + + def _get_color(self, orig_color, target_color): + next_point = (orig_color[0] + self._level * (target_color[0] - orig_color[0]), + orig_color[1] + self._level * (target_color[1] - orig_color[1]), + orig_color[2] + self._level * (target_color[2] - orig_color[2])) + return Color('#%02x%02x%02x' % (int(next_point[0] * 255), + int(next_point[1] * 255), + int(next_point[2] * 255))) + + def __pulse_cb(self): + self._phase += self._STEP + self._level = (math.sin(self._phase) + 1) / 2 + self._update_colors() + + return True + + def _get_as_rgba(self, html_color): + if html_color == 'none': + return Color('#FFFFFF', alpha=1.0).get_rgba() + else: + return Color(html_color).get_rgba() + + def _update_colors(self): + if self._pulsing: + base_stroke = self._get_as_rgba(self._base_color.get_stroke_color()) + pulse_stroke = self._get_as_rgba(self._pulse_color.get_stroke_color()) + base_fill = self._get_as_rgba(self._base_color.get_fill_color()) + pulse_fill = self._get_as_rgba(self._pulse_color.get_fill_color()) + + self.props.stroke_color = \ + self._get_color(base_stroke, pulse_stroke).get_svg() + self.props.fill_color = \ + self._get_color(base_fill, pulse_fill).get_svg() + else: + self.props.xo_color = self._base_color + def do_set_property(self, pspec, value): - CanvasIcon.do_set_property(self, pspec, value) - - if pspec.name == 'pulse-time': - self._pulse_time = value - self._stop() - if not self._paused and self._pulse_time > 0.0: - self._start() - elif pspec.name == 'colors': - self._colors = value - self._pos = 0 - self._update_colors() + if pspec.name == 'base-color': + if self._base_color != value: + self._base_color = value + self._update_colors() + elif pspec.name == 'pulse-color': + if self._pulse_color != value: + self._pulse_color = value + self._update_colors() + elif pspec.name == 'pulsing': + if self._pulsing != value: + self._pulsing = value + if self._pulsing: + self._start_pulsing(restart=True) + else: + self._stop_pulsing() elif pspec.name == 'paused': - self._paused = value - if not self._paused and self._pulse_time > 0.0: - self._start() - else: - self._stop() + if self._paused != value: + self._paused = value + if self._paused: + self._stop_pulsing() + else: + self._start_pulsing(restart=False) + else: + CanvasIcon.do_set_property(self, pspec, value) def do_get_property(self, pspec): - CanvasIcon.do_get_property(self, pspec) + if pspec.name == 'base-color': + return self._base_color + elif pspec.name == 'pulse-color': + return self._pulse_color + elif pspec.name == 'pulsing': + return self._pulsing + elif pspec.name == 'paused': + return self._paused + else: + return CanvasIcon.do_get_property(self, pspec) - if pspec.name == 'pulse-time': - return self._pulse_time - elif pspec.name == 'colors': - return self._colors + """ + def set_base_color(self, base_color): + self._base_color = base_color - def _update_colors(self): - self.props.stroke_color = self._colors[self._pos][0] - self.props.fill_color = self._colors[self._pos][1] + def get_base_color(self): + return self._base_color + base_color = gobject.property(type=object, setter=set_base_color, + getter=get_base_color) - def _pulse_timeout(self): - if self._colors: - self._update_colors() + def set_pulse_color(self, pulse_color): + self._pulse_color = pulse_color - self._pos += 1 - if self._pos == len(self._colors): - self._pos = 0 + def get_pulse_color(self): + return self._pulse_color + pulse_color = gobject.property(type=object, setter=set_pulse_color, + getter=get_pulse_color) - return True + def set_pulsing(self, pulsing): + pass + + def get_pulsing(self): + return self._pulse_hid != None + pulsing = gobject.property(type=bool, default=False, setter=set_pulsing, + getter=get_pulsing) - def _start(self): - if self._pulse_sid == 0: - self._pulse_sid = gobject.timeout_add( - int(self._pulse_time * 1000), self._pulse_timeout) + def set_paused(self, paused): + self._paused = paused - def _stop(self): - if self._pulse_sid: - gobject.source_remove(self._pulse_sid) - self._pulse_sid = 0 + def get_paused(self): + return self._paused + paused = gobject.property(type=bool, default=False, setter=set_paused, + getter=get_paused) + """ -- cgit v0.9.1