[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