[Sugar-devel] [PATCH PoC sugar] add "favourites" view for XDG applications
Sascha Silbe
sascha-pgp at silbe.org
Sat Oct 9 13:07:09 EDT 2010
This is proof-of-concept code ("executable mock-up") for a favourites view
based on XDG desktop entry files [1]. It shows icons for "legacy" applications
(e.g. Gnome or KDE applications); the activity favourites view is left intact.
XDG applications are started directly, without support for startup
notification (SL#2434 [2]), sandboxing (i.e. Rainbow) or automatic Journal
interaction (the application of course is free to implement sandboxing and/or
Journal interaction itself).
After conversion of activity.info to standard .desktop files (SL#2435 [3])
we can include both "legacy" applications and Sugar activities in the same
favourites view.
[1] http://www.freedesktop.org/wiki/Specifications/desktop-entry-spec
[2] https://bugs.sugarlabs.org/ticket/2434
[3] https://bugs.sugarlabs.org/ticket/2435
---
src/jarabe/desktop/favoritesview.py | 252 +++++++++++++++++++++++++----------
src/jarabe/desktop/homebox.py | 27 ++++-
2 files changed, 207 insertions(+), 72 deletions(-)
diff --git a/src/jarabe/desktop/favoritesview.py b/src/jarabe/desktop/favoritesview.py
index d8bd5e5..1757b5a 100644
--- a/src/jarabe/desktop/favoritesview.py
+++ b/src/jarabe/desktop/favoritesview.py
@@ -18,9 +18,11 @@
import logging
from gettext import gettext as _
import math
+import subprocess
import gobject
import gconf
+import gio
import gtk
import hippo
@@ -28,6 +30,7 @@ from sugar.graphics import style
from sugar.graphics.icon import Icon, CanvasIcon
from sugar.graphics.menuitem import MenuItem
from sugar.graphics.alert import Alert
+from sugar.graphics.palette import Palette
from sugar.graphics.xocolor import XoColor
from sugar.activity import activityfactory
from sugar.activity.activityhandle import ActivityHandle
@@ -84,9 +87,7 @@ class FavoritesView(hippo.Canvas):
self._my_icon = OwnerIcon(style.XLARGE_ICON_SIZE)
self._my_icon.connect('register-activate', self.__register_activate_cb)
self._box.append(self._my_icon)
-
- self._current_activity = CurrentActivityIcon()
- self._box.append(self._current_activity)
+ self._locked_icons = [self._my_icon]
self._layout = None
self._alert = None
@@ -102,8 +103,6 @@ class FavoritesView(hippo.Canvas):
self.connect('drag-drop', self.__drag_drop_cb)
self.connect('drag-data-received', self.__drag_data_received_cb)
- gobject.idle_add(self.__connect_to_bundle_registry_cb)
-
favorites_settings = get_settings()
favorites_settings.changed.connect(self.__settings_changed_cb)
self._set_layout(favorites_settings.layout)
@@ -112,60 +111,6 @@ class FavoritesView(hippo.Canvas):
favorites_settings = get_settings()
self._set_layout(favorites_settings.layout)
- def __connect_to_bundle_registry_cb(self):
- registry = bundleregistry.get_registry()
-
- for info in registry:
- if registry.is_bundle_favorite(info.get_bundle_id(),
- info.get_activity_version()):
- self._add_activity(info)
-
- registry.connect('bundle-added', self.__activity_added_cb)
- registry.connect('bundle-removed', self.__activity_removed_cb)
- registry.connect('bundle-changed', self.__activity_changed_cb)
-
- def _add_activity(self, activity_info):
- if activity_info.get_bundle_id() == 'org.laptop.JournalActivity':
- return
- icon = ActivityIcon(activity_info)
- icon.props.size = style.STANDARD_ICON_SIZE
- icon.set_resume_mode(self._resume_mode)
- self._box.insert_sorted(icon, 0, self._layout.compare_activities)
- self._layout.append(icon)
-
- def __activity_added_cb(self, activity_registry, activity_info):
- registry = bundleregistry.get_registry()
- if registry.is_bundle_favorite(activity_info.get_bundle_id(),
- activity_info.get_activity_version()):
- self._add_activity(activity_info)
-
- def _find_activity_icon(self, bundle_id, version):
- for icon in self._box.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.get_bundle_id(),
- activity_info.get_activity_version())
- if icon is not None:
- self._layout.remove(icon)
- self._box.remove(icon)
-
- def __activity_changed_cb(self, activity_registry, activity_info):
- if activity_info.get_bundle_id() == 'org.laptop.JournalActivity':
- return
- icon = self._find_activity_icon(activity_info.get_bundle_id(),
- activity_info.get_activity_version())
- if icon is not None:
- self._box.remove(icon)
-
- registry = bundleregistry.get_registry()
- if registry.is_bundle_favorite(activity_info.get_bundle_id(),
- activity_info.get_activity_version()):
- self._add_activity(activity_info)
-
def do_size_allocate(self, allocation):
width = allocation.width
height = allocation.height
@@ -176,14 +121,6 @@ class FavoritesView(hippo.Canvas):
y = (height - my_icon_height - style.GRID_CELL_SIZE) / 2
self._layout.move_icon(self._my_icon, x, y, locked=True)
- min_w_, icon_width = self._current_activity.get_width_request()
- min_h_, icon_height = \
- self._current_activity.get_height_request(icon_width)
- x = (width - icon_width) / 2
- y = (height - my_icon_height - style.GRID_CELL_SIZE) / 2 + \
- my_icon_height + style.DEFAULT_PADDING
- self._layout.move_icon(self._current_activity, x, y, locked=True)
-
hippo.Canvas.do_size_allocate(self, allocation)
# TODO: Dnd methods. This should be merged somehow inside hippo-canvas.
@@ -289,11 +226,11 @@ class FavoritesView(hippo.Canvas):
self._box.sort(self._layout.compare_activities)
for icon in self._box.get_children():
- if icon not in [self._my_icon, self._current_activity]:
+ if icon not in self._locked_icons:
self._layout.append(icon)
- self._layout.append(self._my_icon, locked=True)
- self._layout.append(self._current_activity, locked=True)
+ for icon in self._locked_icons:
+ self._layout.append(icon, locked=True)
if self._layout.allow_dnd():
self.drag_source_set(0, [], 0)
@@ -344,6 +281,161 @@ class FavoritesView(hippo.Canvas):
icon.set_resume_mode(self._resume_mode)
+class ActivityFavoritesView(FavoritesView):
+ __gtype_name__ = 'SugarActivityFavoritesView'
+
+ def __init__(self, **kwargs):
+ FavoritesView.__init__(self, **kwargs)
+ self._current_activity = CurrentActivityIcon()
+ self._locked_icons.append(self._current_activity)
+ self._box.append(self._current_activity)
+ gobject.idle_add(self.__connect_to_bundle_registry_cb)
+
+ def do_size_allocate(self, allocation):
+ width = allocation.width
+ height = allocation.height
+
+ min_w_, my_icon_width = self._my_icon.get_width_request()
+ min_h_, my_icon_height = self._my_icon.get_height_request(my_icon_width)
+
+ min_w_, icon_width = self._current_activity.get_width_request()
+ min_h_, icon_height = \
+ self._current_activity.get_height_request(icon_width)
+ x = (width - icon_width) / 2
+ y = (height - my_icon_height - style.GRID_CELL_SIZE) / 2 + \
+ my_icon_height + style.DEFAULT_PADDING
+ self._layout.move_icon(self._current_activity, x, y, locked=True)
+
+ FavoritesView.do_size_allocate(self, allocation)
+
+ def __connect_to_bundle_registry_cb(self):
+ registry = bundleregistry.get_registry()
+
+ for info in registry:
+ if registry.is_bundle_favorite(info.get_bundle_id(),
+ info.get_activity_version()):
+ self._add_activity(info)
+
+ registry.connect('bundle-added', self.__activity_added_cb)
+ registry.connect('bundle-removed', self.__activity_removed_cb)
+ registry.connect('bundle-changed', self.__activity_changed_cb)
+
+ def _add_activity(self, activity_info):
+ if activity_info.get_bundle_id() == 'org.laptop.JournalActivity':
+ return
+ icon = ActivityIcon(activity_info)
+ icon.props.size = style.STANDARD_ICON_SIZE
+ icon.set_resume_mode(self._resume_mode)
+ self._box.insert_sorted(icon, 0, self._layout.compare_activities)
+ self._layout.append(icon)
+
+ def __activity_added_cb(self, activity_registry, activity_info):
+ registry = bundleregistry.get_registry()
+ if registry.is_bundle_favorite(activity_info.get_bundle_id(),
+ activity_info.get_activity_version()):
+ self._add_activity(activity_info)
+
+ def _find_activity_icon(self, bundle_id, version):
+ for icon in self._box.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.get_bundle_id(),
+ activity_info.get_activity_version())
+ if icon is not None:
+ self._layout.remove(icon)
+ self._box.remove(icon)
+
+ def __activity_changed_cb(self, activity_registry, activity_info):
+ if activity_info.get_bundle_id() == 'org.laptop.JournalActivity':
+ return
+ icon = self._find_activity_icon(activity_info.get_bundle_id(),
+ activity_info.get_activity_version())
+ if icon is not None:
+ self._box.remove(icon)
+
+ registry = bundleregistry.get_registry()
+ if registry.is_bundle_favorite(activity_info.get_bundle_id(),
+ activity_info.get_activity_version()):
+ self._add_activity(activity_info)
+
+
+class XdgFavoritesView(FavoritesView):
+
+ def __init__(self, **kwargs):
+ FavoritesView.__init__(self, **kwargs)
+ self._add_entries()
+
+ def _add_entries(self):
+ for info in gio.app_info_get_all():
+ if not info.should_show():
+ continue
+
+ logging.debug('_add_entries: %r', info.get_id())
+ icon = XdgApplicationIcon(info)
+ icon.props.size = style.STANDARD_ICON_SIZE
+ self._box.insert_sorted(icon, 0, self._layout.compare_activities)
+ self._layout.append(icon)
+
+
+class XdgApplicationIcon(CanvasIcon):
+ __gtype_name__ = 'SugarXdgApplicationIcon'
+
+ _BORDER_WIDTH = style.zoom(3)
+
+ def __init__(self, info):
+ icon_theme = gtk.icon_theme_get_default()
+ info_icon = info.get_icon()
+ if isinstance(info_icon, gio.FileIcon):
+ self._file_name = info_icon.to_string()
+ self._icon_name = None
+ elif isinstance(info_icon, gio.ThemedIcon):
+ self._file_name = None
+ self._icon_name = 'image-missing'
+ for icon_name in info_icon.get_names():
+ if icon_theme.lookup_icon(icon_name, 0, 0):
+ self._icon_name = icon_name
+ break
+ else:
+ logging.warning('Unsupported or missing icon for %s: %r',
+ info.get_id(), info_icon)
+ self._file_name = None
+ self._icon_name = 'image-missing'
+
+ logging.debug('XdgApplicationIcon: file_name=%r, icon_name=%r',
+ self._file_name, self._icon_name)
+ CanvasIcon.__init__(self, cache=True, file_name=self._file_name,
+ icon_name=self._icon_name)
+
+ self._info = info
+ self.palette = None
+
+ self.connect('button-release-event', self.__button_release_event_cb)
+
+ def create_palette(self):
+ palette = XdgApplicationPalette(self._info.get_name(),
+ self._info.get_description(), icon_name=self._icon_name,
+ icon_file_name=self._file_name)
+ palette.connect('activate', self.__palette_activate_cb)
+ return palette
+
+ def __palette_activate_cb(self, palette):
+ self._activate()
+
+ def __button_release_event_cb(self, icon, event):
+ self._activate()
+
+ def _activate(self):
+ logging.debug('_activate %r', self._info.get_id())
+ if self.palette is not None:
+ self.palette.popdown(immediate=True)
+
+ self._info.launch()
+
+
class ActivityIcon(CanvasIcon):
__gtype_name__ = 'SugarFavoriteActivityIcon'
@@ -388,7 +480,7 @@ class ActivityIcon(CanvasIcon):
break
def _get_last_activity_async(self, bundle_id, properties):
- query = {'activity': bundle_id}
+ query = {'activity': bundle_id}
datastore.find(query, sorting=['+timestamp'],
limit=self._MAX_RESUME_ENTRIES,
properties=properties,
@@ -556,6 +648,24 @@ class FavoritePalette(ActivityPalette):
if entry is not None:
self.emit('entry-activate', entry)
+
+class XdgApplicationPalette(Palette):
+
+ def __init__(self, name, description, icon_name='missing', icon_file_name=None):
+ icon = Icon(icon_name=icon_name, file=icon_file_name)
+ Palette.__init__(self, primary_text=name,
+ secondary_text=description, icon=icon)
+
+ menu_item = MenuItem(text_label=_('Start'), file_name=icon_file_name,
+ icon_name=icon_name)
+ menu_item.connect('activate', self.__start_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ def __start_cb(self, menu_item):
+ self.emit('activate')
+
+
class CurrentActivityIcon(CanvasIcon, hippo.CanvasItem):
def __init__(self):
CanvasIcon.__init__(self, cache=True)
diff --git a/src/jarabe/desktop/homebox.py b/src/jarabe/desktop/homebox.py
index 85279ff..4117b04 100644
--- a/src/jarabe/desktop/homebox.py
+++ b/src/jarabe/desktop/homebox.py
@@ -32,6 +32,7 @@ from jarabe.desktop.activitieslist import ActivitiesList
_FAVORITES_VIEW = 0
_LIST_VIEW = 1
+_XDG_FAVORITES_VIEW = 2
_AUTOSEARCH_TIMEOUT = 1000
@@ -43,8 +44,9 @@ class HomeBox(gtk.VBox):
gobject.GObject.__init__(self)
- self._favorites_view = favoritesview.FavoritesView()
+ self._favorites_view = favoritesview.ActivityFavoritesView()
self._list_view = ActivitiesList()
+ self._xdg_favorites_view = favoritesview.XdgFavoritesView()
self._toolbar = HomeToolbar()
self._toolbar.connect('query-changed', self.__toolbar_query_changed_cb)
@@ -111,6 +113,8 @@ class HomeBox(gtk.VBox):
if view == _FAVORITES_VIEW:
if self._list_view in self.get_children():
self.remove(self._list_view)
+ if self._xdg_favorites_view in self.get_children():
+ self.remove(self._xdg_favorites_view)
if self._favorites_view not in self.get_children():
self.add(self._favorites_view)
@@ -118,10 +122,21 @@ class HomeBox(gtk.VBox):
elif view == _LIST_VIEW:
if self._favorites_view in self.get_children():
self.remove(self._favorites_view)
+ if self._xdg_favorites_view in self.get_children():
+ self.remove(self._xdg_favorites_view)
if self._list_view not in self.get_children():
self.add(self._list_view)
self._list_view.show()
+ elif view == _XDG_FAVORITES_VIEW:
+ if self._list_view in self.get_children():
+ self.remove(self._list_view)
+ if self._favorites_view in self.get_children():
+ self.remove(self._favorites_view)
+
+ if self._xdg_favorites_view not in self.get_children():
+ self.add(self._xdg_favorites_view)
+ self._xdg_favorites_view.show()
else:
raise ValueError('Invalid view: %r' % view)
@@ -196,9 +211,19 @@ class HomeToolbar(gtk.Toolbar):
self.insert(self._list_button, -1)
self._list_button.show()
+ xdg_favorites_button = FavoritesButton()
+ xdg_favorites_button.props.group = favorites_button
+ xdg_favorites_button.props.tooltip = _('XDG favorites view')
+ xdg_favorites_button.props.accelerator = _('<Ctrl>3')
+ xdg_favorites_button.connect('toggled', self.__view_button_toggled_cb,
+ _XDG_FAVORITES_VIEW)
+ self.insert(xdg_favorites_button, -1)
+ xdg_favorites_button.show()
+
self._add_separator()
def __view_button_toggled_cb(self, button, view):
+ logging.debug('__view_button_toggled_cb(%r)', view)
if button.props.active:
if view == _FAVORITES_VIEW:
self.search_entry.set_text('')
--
tg: (037a6bd..) t/xdg-favorites-view (depends on: t/versions)
More information about the Sugar-devel
mailing list