[Sugar-devel] [PATCH sugar 3/5] Views: Replace the hippo based layout with one using GTK+ containers

Simon Schampijer simon at schampijer.de
Mon Aug 6 09:27:28 EDT 2012


The requirement was to be able to position and size the widgets with
different layouts in the Views. We need to be able to have fixed
positions (e.g. the owner icon and current activity icon) and we need
to be able to change the position (e.g. BuddyIcon in the Neighborhood
View) and move the widget by drag and drop (e.g. RandomLayout).

The implementation uses a gtk.Container (SugarViewContainer) without
an associated GdkWindow to hold the widgets since we do not want to do
drawing in this container. The desired layout and the owner icon and
optional activity icon are passed at initialization time.

A ViewLayout is responsible to calculate the positions and sizes for
the SugarViewContainer. To keep track of the positions it uses a grid
that is initialized on the first allocation. The owner icon
(activity icon) will be added to the grid to make sure it keeps the
fixed position.

The SpreadLayout derives now from the ViewLayout. The SpreadLayout
which is used in the GroupBox and in the MeshContainer does
add all it's children to the grid in order to use the collision
detection from the grid to not place icons over each other.

The RandomLayout does place all it's children in the grid as well
for collision detection purposes.

The Journal has been made hippo-free as well. We set white background
in the ExpandedEntry of the Detail View. For that we change the
ExpandedEntry class to subclass gtk.EventBox because the
gtk.VBox doesn't have a gtk.gdk.Window associated and the background
can't be set otherwise.

This patch is based on the work from Walter Bender,
Daniel Drake and Raul Gutierrez Segales.

Signed-off-by: Simon Schampijer <simon at laptop.org>
Signed-off-by: Manuel Quiñones <manuq at laptop.org>
Signed-off-by: Daniel Narvaez <dwnarvaez at gmail.com>
---
 src/jarabe/desktop/Makefile.am        |   4 +-
 src/jarabe/desktop/favoriteslayout.py | 390 +++++++++++++++++----------------
 src/jarabe/desktop/favoritesview.py   | 398 ++++++++++++++++------------------
 src/jarabe/desktop/friendview.py      |  31 +--
 src/jarabe/desktop/grid.py            |   3 +
 src/jarabe/desktop/groupbox.py        |  51 ++---
 src/jarabe/desktop/homebox.py         |  22 +-
 src/jarabe/desktop/homewindow.py      |   2 +
 src/jarabe/desktop/meshbox.py         | 119 +++++-----
 src/jarabe/desktop/networkviews.py    |  28 +--
 src/jarabe/desktop/snowflakelayout.py | 103 +++++----
 src/jarabe/desktop/spreadlayout.py    |  89 --------
 src/jarabe/desktop/transitionbox.py   |  50 +----
 src/jarabe/desktop/viewcontainer.py   |  82 +++++++
 src/jarabe/journal/detailview.py      |  73 +++----
 src/jarabe/journal/expandedentry.py   | 342 +++++++++++++----------------
 src/jarabe/journal/keepicon.py        |  61 +++---
 src/jarabe/journal/listview.py        |  50 ++---
 src/jarabe/view/buddyicon.py          |  10 +-
 src/jarabe/view/pulsingicon.py        |  11 +-
 20 files changed, 914 insertions(+), 1005 deletions(-)
 delete mode 100644 src/jarabe/desktop/spreadlayout.py
 create mode 100644 src/jarabe/desktop/viewcontainer.py

diff --git a/src/jarabe/desktop/Makefile.am b/src/jarabe/desktop/Makefile.am
index 25fb0b4..b36404e 100644
--- a/src/jarabe/desktop/Makefile.am
+++ b/src/jarabe/desktop/Makefile.am
@@ -14,5 +14,5 @@ sugar_PYTHON =			\
 	networkviews.py		\
         schoolserver.py		\
 	snowflakelayout.py	\
-	spreadlayout.py		\
-	transitionbox.py
+	transitionbox.py	\
+	viewcontainer.py
diff --git a/src/jarabe/desktop/favoriteslayout.py b/src/jarabe/desktop/favoriteslayout.py
index 360c147..0f63f95 100644
--- a/src/jarabe/desktop/favoriteslayout.py
+++ b/src/jarabe/desktop/favoriteslayout.py
@@ -20,9 +20,7 @@ import math
 import hashlib
 from gettext import gettext as _
 
-import gobject
 import gtk
-import hippo
 
 from sugar.graphics import style
 
@@ -42,77 +40,134 @@ _ICON_SIZES = [style.MEDIUM_ICON_SIZE, style.STANDARD_ICON_SIZE,
                style.SMALL_ICON_SIZE]
 
 
-class FavoritesLayout(gobject.GObject, hippo.CanvasLayout):
-    """Base class of the different layout types."""
-
-    __gtype_name__ = 'FavoritesLayout'
-
+class Layout(object):
     def __init__(self):
-        gobject.GObject.__init__(self)
-        self.box = None
-        self.fixed_positions = {}
+        pass
 
-    def do_set_box(self, box):
-        self.box = box
+    def remove(self, child):
+        pass
 
-    def do_get_height_request(self, for_width):
-        return 0, gtk.gdk.screen_height() - style.GRID_CELL_SIZE
+    def allocate_children(self, allocation, children):
+        pass
 
-    def do_get_width_request(self):
-        return 0, gtk.gdk.screen_width()
 
-    def compare_activities(self, icon_a, icon_b):
-        return 0
+class ViewLayout(Layout):
+    def __init__(self):
+        self._grid = None
 
-    def append(self, icon, locked=False):
-        if not hasattr(type(icon), 'fixed_position'):
-            logging.debug('Icon without fixed_position: %r', icon)
+    def setup(self, allocation, owner_icon, activity_icon=None):
+        if self._grid is not None:
             return
-
-        icon.props.size = max(icon.props.size, style.STANDARD_ICON_SIZE)
-
-        relative_x, relative_y = icon.fixed_position
-        if relative_x < 0 or relative_y < 0:
-            logging.debug('Icon out of bounds: %r', icon)
+        self._grid = Grid(int(allocation.width / _CELL_SIZE),
+                          int(allocation.height / _CELL_SIZE))
+        self._grid.connect('child-changed', self.__grid_child_changed_cb)
+        self._allocate_owner_icon(allocation, owner_icon, activity_icon)
+
+    def _allocate_owner_icon(self, allocation, owner_icon, activity_icon):
+        # add owner icon to the grid, precisely centered on the screen
+        # if not None, add an activity icon directly below the owner icon
+        owner_width, owner_height = owner_icon.size_request()
+        height = allocation.height + allocation.y
+        width = allocation.width
+
+        # Find vertical center point of screen
+        y = height / 2
+
+        # This container may be offset from the top by a certain amount
+        # (e.g. for a toolbar at the top of the screen). Adjust the
+        # center-point for that
+        y -= allocation.y
+
+        # Now subtract half of the owner height. This gives us the y
+        # coordinate for the top of the owner icon.
+        y -= owner_height / 2
+
+        # calculate x coordinate and create allocation
+        x = (width - owner_width) / 2
+        owner_icon_allocation = gtk.gdk.Rectangle(x, allocation.y + y,
+                                                  owner_width, owner_height)
+        owner_icon.size_allocate(owner_icon_allocation)
+
+        # Determine grid coordinates and add to grid
+        owner_grid_width, owner_grid_height = \
+            self._get_child_grid_size(owner_icon)
+        x = int(x / float(_CELL_SIZE))
+        y = int(y / float(_CELL_SIZE))
+        self._grid.add(owner_icon, owner_grid_width, owner_grid_height,
+                       x, y, locked=True)
+
+        if activity_icon is None:
             return
 
-        min_width_, width = self.box.get_width_request()
-        min_height_, height = self.box.get_height_request(width)
-        self.fixed_positions[icon] = \
-                (int(relative_x * _BASE_SCALE / float(width)),
-                    int(relative_y * _BASE_SCALE / float(height)))
+        # Position the current activity below the XO icon
+        # FIXME must ensure we cross into next grid cell here..
+        activity_width, activity_height = activity_icon.size_request()
+        x = (width - activity_width) / 2
+        y = owner_icon_allocation.y + owner_height
+        activity_icon_allocation = gtk.gdk.Rectangle(x, y, activity_width,
+                                                     activity_height)
+        activity_icon.size_allocate(activity_icon_allocation)
+
+        # Determine grid coordinates and add to grid
+        activity_grid_width, activity_grid_height = \
+            self._get_child_grid_size(activity_icon)
+        x = int(x / float(_CELL_SIZE))
+        y = int(y / float(_CELL_SIZE))
+        self._grid.add(activity_icon, activity_grid_width,
+                       activity_grid_height, x, y, locked=True)
+
+    def allocate_children(self, allocation, children):
+        pass
+
+    def move(self, child, x, y, allocation=None):
+        self._grid.move(child, x / _CELL_SIZE, y / _CELL_SIZE, locked=True)
+        width, height = child.size_request()
+        rect = self._grid.get_child_rect(child)
+        child_allocation = gtk.gdk.Rectangle(int(round(rect.x * _CELL_SIZE)),
+                                             int(round(rect.y * _CELL_SIZE)),
+                                             width,
+                                             height)
+        child.size_allocate(child_allocation)
+
+    def _get_child_grid_size(self, child):
+        width, height = child.size_request()
+        width = math.ceil(width / _CELL_SIZE)
+        height = math.ceil(height / _CELL_SIZE)
+        return int(width), int(height)
 
-    def remove(self, icon):
-        if icon in self.fixed_positions:
-            del self.fixed_positions[icon]
+    def __grid_child_changed_cb(self, grid, child):
+        width, height = child.size_request()
+        rect = self._grid.get_child_rect(child)
+        child_allocation = gtk.gdk.Rectangle(int(round(rect.x * _CELL_SIZE)),
+                                             int(round(rect.y * _CELL_SIZE)),
+                                             width,
+                                             height)
+        child.size_allocate(child_allocation)
 
-    def move_icon(self, icon, x, y, locked=False):
-        if icon not in self.box.get_children():
-            raise ValueError('Child not in box.')
 
-        if not (hasattr(icon, 'get_bundle_id') and
-                hasattr(icon, 'get_version')):
-            logging.debug('Not an activity icon %r', icon)
-            return
+class SpreadLayout(ViewLayout):
+    def __init__(self):
+        ViewLayout.__init__(self)
 
-        min_width_, width = self.box.get_width_request()
-        min_height_, height = self.box.get_height_request(width)
-        registry = bundleregistry.get_registry()
-        registry.set_bundle_position(
-                icon.get_bundle_id(), icon.get_version(),
-                x * width / float(_BASE_SCALE),
-                y * height / float(_BASE_SCALE))
-        self.fixed_positions[icon] = (x, y)
+    def remove(self, child):
+        if self._grid.is_in_grid(child):
+            self._grid.remove(child)
 
-    def do_allocate(self, x, y, width, height, req_width, req_height,
-                    origin_changed):
-        raise NotImplementedError()
+    def allocate_children(self, allocation, children):
+        for child in children:
+            if not self._grid.is_in_grid(child):
+                width, height = self._get_child_grid_size(child)
+                self._grid.add(child, width, height, None, None, locked=False)
 
-    def allow_dnd(self):
-        return False
+            width, height = child.size_request()
+            rect = self._grid.get_child_rect(child)
+            x = int(round(rect.x * _CELL_SIZE))
+            y = int(round(rect.y * _CELL_SIZE)) + allocation.y
+            child_allocation = gtk.gdk.Rectangle(x, y, width, height)
+            child.size_allocate(child_allocation)
 
 
-class RandomLayout(FavoritesLayout):
+class RandomLayout(SpreadLayout):
     """Lay out icons randomly; try to nudge them around to resolve overlaps."""
 
     __gtype_name__ = 'RandomLayout'
@@ -128,69 +183,75 @@ class RandomLayout(FavoritesLayout):
     """String used to identify this layout in home view dropdown palette."""
 
     def __init__(self):
-        FavoritesLayout.__init__(self)
-
-        min_width_, width = self.do_get_width_request()
-        min_height_, height = self.do_get_height_request(width)
-
-        self._grid = Grid(width / _CELL_SIZE, height / _CELL_SIZE)
-        self._grid.connect('child-changed', self.__grid_child_changed_cb)
-
-    def __grid_child_changed_cb(self, grid, child):
-        child.emit_request_changed()
-
-    def append(self, icon, locked=False):
-        FavoritesLayout.append(self, icon, locked)
-
-        min_width_, child_width = icon.get_width_request()
-        min_height_, child_height = icon.get_height_request(child_width)
-        min_width_, width = self.box.get_width_request()
-        min_height_, height = self.box.get_height_request(width)
-
-        if icon in self.fixed_positions:
-            x, y = self.fixed_positions[icon]
-            x = min(x, width - child_width)
-            y = min(y, height - child_height)
-        elif hasattr(icon, 'get_bundle_id'):
-            name_hash = hashlib.md5(icon.get_bundle_id())
-            x = int(name_hash.hexdigest()[:5], 16) % (width - child_width)
-            y = int(name_hash.hexdigest()[-5:], 16) % (height - child_height)
-        else:
-            x = None
-            y = None
-
-        if x is None or y is None:
-            self._grid.add(icon,
-                           child_width / _CELL_SIZE, child_height / _CELL_SIZE)
-        else:
-            self._grid.add(icon,
-                           child_width / _CELL_SIZE, child_height / _CELL_SIZE,
-                           x / _CELL_SIZE, y / _CELL_SIZE)
+        SpreadLayout.__init__(self)
+        self.fixed_positions = {}
 
-    def remove(self, icon):
-        self._grid.remove(icon)
-        FavoritesLayout.remove(self, icon)
+    def _add_fixed_position(self, icon, allocation, locked=False):
+        if not hasattr(type(icon), 'fixed_position'):
+            logging.debug('Icon without fixed_position: %r', icon)
+            return
 
-    def move_icon(self, icon, x, y, locked=False):
-        self._grid.move(icon, x / _CELL_SIZE, y / _CELL_SIZE, locked)
-        FavoritesLayout.move_icon(self, icon, x, y, locked)
+        icon.props.pixel_size = max(icon.props.pixel_size,
+                                    style.STANDARD_ICON_SIZE)
 
-    def do_allocate(self, x, y, width, height, req_width, req_height,
-                    origin_changed):
-        for child in self.box.get_layout_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)
+        relative_x, relative_y = icon.fixed_position
+        if relative_x < 0 or relative_y < 0:
+            logging.debug('Icon out of bounds: %r', icon)
+            return
 
-            rect = self._grid.get_child_rect(child.item)
-            child.allocate(rect.x * _CELL_SIZE,
-                           rect.y * _CELL_SIZE,
-                           child_width,
-                           child_height,
-                           origin_changed)
+        self.fixed_positions[icon] = \
+                (int(relative_x * _BASE_SCALE / float(allocation.width)),
+                 int(relative_y * _BASE_SCALE / float(allocation.height)))
+
+    def allocate_children(self, allocation, children):
+        for child in children:
+            child_width, child_height = child.size_request()
+            if not self._grid.is_in_grid(child):
+                self._add_fixed_position(child, allocation)
+
+                if child in self.fixed_positions:
+                    x, y = self.fixed_positions[child]
+                    x = min(x, allocation.width - child_width)
+                    y = min(y, allocation.height - child_height)
+                elif hasattr(child, 'get_bundle_id'):
+                    name_hash = hashlib.md5(child.get_bundle_id())
+                    x = int(name_hash.hexdigest()[:5], 16) % \
+                        (allocation.width - child_width)
+                    y = int(name_hash.hexdigest()[-5:], 16) % \
+                        (allocation.height - child_height)
+                else:
+                    x = None
+                    y = None
+
+                if x is None or y is None:
+                    self._grid.add(child, child_width / _CELL_SIZE,
+                                   child_height / _CELL_SIZE)
+                else:
+                    self._grid.add(child, child_width / _CELL_SIZE,
+                                   child_height / _CELL_SIZE,
+                                   x / _CELL_SIZE, y / _CELL_SIZE)
+
+            rect = self._grid.get_child_rect(child)
+            x = int(round(rect.x * _CELL_SIZE))
+            y = int(round(rect.y * _CELL_SIZE)) + allocation.y
+            child_allocation = gtk.gdk.Rectangle(x, y,
+                                                 child_width, child_height)
+            child.size_allocate(child_allocation)
+
+    def move_icon(self, child, x, y, allocation):
+        ViewLayout.move(self, child, x, y)
+
+        if not (hasattr(child, 'get_bundle_id') and
+                hasattr(child, 'get_version')):
+            logging.debug('Not an activity icon %r', child)
+            return
 
-    def allow_dnd(self):
-        return True
+        registry = bundleregistry.get_registry()
+        registry.set_bundle_position(
+            child.get_bundle_id(), child.get_version(),
+                x * allocation.width / float(_BASE_SCALE),
+                y * allocation.height / float(_BASE_SCALE))
+        self.fixed_positions[child] = (x, y)
 
 
 _MINIMUM_RADIUS = style.XLARGE_ICON_SIZE / 2 + style.DEFAULT_SPACING + \
@@ -203,7 +264,7 @@ _MIMIMUM_RADIUS_ENCROACHMENT = 0.75
 _INITIAL_ANGLE = math.pi
 
 
-class RingLayout(FavoritesLayout):
+class RingLayout(ViewLayout):
     """Lay out icons in a ring or spiral around the XO man."""
 
     __gtype_name__ = 'RingLayout'
@@ -216,28 +277,9 @@ class RingLayout(FavoritesLayout):
     """String used to identify this layout in home view dropdown palette."""
 
     def __init__(self):
-        FavoritesLayout.__init__(self)
-        self._locked_children = {}
+        ViewLayout.__init__(self)
         self._spiral_mode = False
 
-    def append(self, icon, locked=False):
-        FavoritesLayout.append(self, icon, locked)
-        if locked:
-            child = self.box.find_box_child(icon)
-            self._locked_children[child] = (0, 0)
-
-    def remove(self, icon):
-        child = self.box.find_box_child(icon)
-        if child in self._locked_children:
-            del self._locked_children[child]
-        FavoritesLayout.remove(self, icon)
-
-    def move_icon(self, icon, x, y, locked=False):
-        FavoritesLayout.move_icon(self, icon, x, y, locked)
-        if locked:
-            child = self.box.find_box_child(icon)
-            self._locked_children[child] = (x, y)
-
     def _calculate_radius_and_icon_size(self, children_count):
         """ Adjust the ring or spiral radius and icon size as needed. """
         self._spiral_mode = False
@@ -270,12 +312,10 @@ class RingLayout(FavoritesLayout):
         return radius, icon_size
 
     def _calculate_position(self, radius, icon_size, icon_index,
-                            children_count, sin=math.sin, cos=math.cos):
+                            children_count, width, height,
+                            sin=math.sin, cos=math.cos):
         """ Calculate an icon position on a circle or a spiral. """
-        width, height = self.box.get_allocation()
         if self._spiral_mode:
-            min_width_, box_width = self.box.get_width_request()
-            min_height_, box_height = self.box.get_height_request(box_width)
             angle, radius = self._calculate_angle_and_radius(icon_index,
                                                              icon_size)
             x, y = self._convert_from_polar_to_cartesian(angle, radius,
@@ -286,7 +326,7 @@ class RingLayout(FavoritesLayout):
             x = radius * cos(angle) + (width - icon_size) / 2
             y = radius * sin(angle) + (height - icon_size - \
                                        (style.GRID_CELL_SIZE / 2)) / 2
-        return x, y
+        return int(x), int(y)
 
     def _convert_from_polar_to_cartesian(self, angle, radius, icon_size, width,
                                          height):
@@ -311,49 +351,27 @@ class RingLayout(FavoritesLayout):
             radius += (float(icon_spacing) * spiral_spacing / n)
         return angle, radius
 
-    def _get_children_in_ring(self):
-        children_in_ring = [child for child in self.box.get_layout_children() \
-                if child not in self._locked_children]
-        return children_in_ring
-
-    def do_allocate(self, x, y, width, height, req_width, req_height,
-                    origin_changed):
-        children_in_ring = self._get_children_in_ring()
-        if children_in_ring:
-            radius, icon_size = \
-                    self._calculate_radius_and_icon_size(len(children_in_ring))
-
-            for n in range(len(children_in_ring)):
-                child = children_in_ring[n]
-
-                x, y = self._calculate_position(radius, icon_size, n,
-                                                len(children_in_ring))
-
-                # 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)
+    def allocate_children(self, allocation, children):
+        radius, icon_size = self._calculate_radius_and_icon_size(len(children))
 
-                child.allocate(int(x), int(y), child_width, child_height,
-                               origin_changed)
-                child.item.props.size = icon_size
+        children.sort(self.compare_activities)
+        for n in range(len(children)):
+            child = children[n]
 
-        for child in self._locked_children.keys():
-            x, y = self._locked_children[child]
-
-            # 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)
-
-            if child_width <= 0 or child_height <= 0:
-                return
-
-            child.allocate(int(x), int(y), child_width, child_height,
-                            origin_changed)
+            x, y = self._calculate_position(radius, icon_size, n,
+                                            len(children), allocation.width,
+                                            allocation.height)
+            child.size_request()
+            child.set_size(icon_size)
+            child_allocation = gtk.gdk.Rectangle(allocation.x + x,
+                                                 allocation.y + y,
+                                                 icon_size, icon_size)
+            child.size_allocate(child_allocation)
 
     def compare_activities(self, icon_a, icon_b):
         if hasattr(icon_a, 'installation_time') and \
                 hasattr(icon_b, 'installation_time'):
-            return icon_b.installation_time - icon_a.installation_time
+            return int(icon_b.installation_time - icon_a.installation_time)
         else:
             return 0
 
@@ -420,13 +438,11 @@ class SunflowerLayout(RingLayout):
         return i
 
     def _calculate_position(self, radius, icon_size, oindex, children_count,
-                            sin=math.sin, cos=math.cos):
+                            width, height, sin=math.sin, cos=math.cos):
         """Calculate the position of sunflower floret number 'oindex'.
         If the result is outside the bounding box, use the next index which
         is inside the bounding box."""
 
-        width, height = self.box.get_allocation()
-
         while True:
 
             index = self.adjust_index(oindex)
@@ -454,7 +470,7 @@ class SunflowerLayout(RingLayout):
                     # try again
                     continue
 
-            return x, y
+            return int(x), int(y)
 
 
 class BoxLayout(RingLayout):
@@ -476,7 +492,7 @@ class BoxLayout(RingLayout):
         RingLayout.__init__(self)
 
     def _calculate_position(self, radius, icon_size, index, children_count,
-                            sin=None, cos=None):
+                            width, height, sin=None, cos=None):
 
         # use "orthogonal" versions of cos and sin in order to square the
         # circle and turn the 'ring view' into a 'box view'
@@ -496,8 +512,8 @@ class BoxLayout(RingLayout):
         sin = lambda r: cos_d(math.degrees(r) - 90)
 
         return RingLayout._calculate_position(self, radius, icon_size, index,
-                                              children_count, sin=sin,
-                                              cos=cos)
+                                              children_count, width, height,
+                                              sin=sin, cos=cos)
 
 
 class TriangleLayout(RingLayout):
@@ -526,7 +542,7 @@ class TriangleLayout(RingLayout):
         return max(radius, _MINIMUM_RADIUS + style.MEDIUM_ICON_SIZE), icon_size
 
     def _calculate_position(self, radius, icon_size, index, children_count,
-                            sin=math.sin, cos=math.cos):
+                            width, height, sin=math.sin, cos=math.cos):
         # tweak cos and sin in order to make the 'ring' into an equilateral
         # triangle.
 
@@ -556,5 +572,5 @@ class TriangleLayout(RingLayout):
         sin = lambda r: sin_d(math.degrees(r))
 
         return RingLayout._calculate_position(self, radius, icon_size, index,
-                                              children_count, sin=sin,
-                                              cos=cos)
+                                              children_count, width, height,
+                                              sin=sin, cos=cos)
diff --git a/src/jarabe/desktop/favoritesview.py b/src/jarabe/desktop/favoritesview.py
index 654f400..1ab4bdc 100644
--- a/src/jarabe/desktop/favoritesview.py
+++ b/src/jarabe/desktop/favoritesview.py
@@ -23,10 +23,9 @@ import gobject
 import gconf
 import glib
 import gtk
-import hippo
 
 from sugar.graphics import style
-from sugar.graphics.icon import Icon, CanvasIcon
+from sugar.graphics.icon import Icon
 from sugar.graphics.menuitem import MenuItem
 from sugar.graphics.alert import Alert
 from sugar.graphics.xocolor import XoColor
@@ -35,9 +34,11 @@ from sugar import dispatch
 from sugar.datastore import datastore
 
 from jarabe.view.palettes import JournalPalette
-from jarabe.view.palettes import CurrentActivityPalette, ActivityPalette
+from jarabe.view.palettes import CurrentActivityPalette
+from jarabe.view.palettes import ActivityPalette
 from jarabe.view.buddyicon import BuddyIcon
 from jarabe.view.buddymenu import BuddyMenu
+from jarabe.view.eventicon import EventIcon
 from jarabe.model.buddy import get_owner_instance
 from jarabe.model import shell
 from jarabe.model import bundleregistry
@@ -46,6 +47,7 @@ from jarabe.journal import misc
 from jarabe.desktop import schoolserver
 from jarabe.desktop.schoolserver import RegisterError
 from jarabe.desktop import favoriteslayout
+from jarabe.desktop.viewcontainer import ViewContainer
 
 
 _logger = logging.getLogger('FavoritesView')
@@ -64,169 +66,127 @@ about the layout can be accessed with fields of the class."""
 _favorites_settings = None
 
 
-class FavoritesView(hippo.Canvas):
-    __gtype_name__ = 'SugarFavoritesView'
+class FavoritesBox(gtk.VBox):
+    __gtype_name__ = 'SugarFavoritesBox'
 
-    def __init__(self, **kwargs):
-        logging.debug('STARTUP: Loading the favorites view')
+    def __init__(self):
+        gtk.VBox.__init__(self)
 
-        gobject.GObject.__init__(self, **kwargs)
+        self._view = FavoritesView(self)
+        self.pack_start(self._view)
+        self._view.show()
 
-        # DND stuff
-        self._pressed_button = None
-        self._press_start_x = None
-        self._press_start_y = None
-        self._hot_x = None
-        self._hot_y = None
-        self._last_clicked_icon = None
+        self._alert = None
 
-        self._box = hippo.CanvasBox()
-        self._box.props.background_color = style.COLOR_WHITE.get_int()
-        self.set_root(self._box)
+    def set_filter(self, query):
+        self._view.set_filter(query)
 
-        self._my_icon = OwnerIcon(style.XLARGE_ICON_SIZE)
-        self._my_icon.connect('register-activate', self.__register_activate_cb)
-        self._box.append(self._my_icon)
+    def set_resume_mode(self, resume_mode):
+        self._view.set_resume_mode(resume_mode)
 
-        self._current_activity = CurrentActivityIcon()
-        self._box.append(self._current_activity)
+    def add_alert(self, alert):
+        if self._alert is not None:
+            self.remove_alert()
+        self._alert = alert
+        self.pack_start(alert, False)
+        self.reorder_child(alert, 0)
 
-        self._layout = None
+    def remove_alert(self):
+        self.remove(self._alert)
         self._alert = None
-        self._resume_mode = True
 
-        # More DND stuff
-        self.add_events(gtk.gdk.BUTTON_PRESS_MASK |
-                        gtk.gdk.POINTER_MOTION_HINT_MASK)
-        self.connect('motion-notify-event', self.__motion_notify_event_cb)
-        self.connect('button-press-event', self.__button_press_event_cb)
-        self.connect('drag-begin', self.__drag_begin_cb)
-        self.connect('drag-motion', self.__drag_motion_cb)
-        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)
+class FavoritesView(ViewContainer):
+    __gtype_name__ = 'SugarFavoritesView'
+
+    def __init__(self, box):
+        self._box = box
+        self._layout = None
 
         favorites_settings = get_settings()
         favorites_settings.changed.connect(self.__settings_changed_cb)
         self._set_layout(favorites_settings.layout)
 
-    def set_filter(self, query):
-        query = query.strip()
-        for icon in self._box.get_children():
-            if icon not in [self._my_icon, self._current_activity]:
-                activity_name = icon.get_activity_name().lower()
-                if activity_name.find(query) > -1:
-                    icon.alpha = 1.0
-                else:
-                    icon.alpha = 0.33
+        owner_icon = OwnerIcon(style.XLARGE_ICON_SIZE)
+        owner_icon.connect('register-activate', self.__register_activate_cb)
 
-    def __settings_changed_cb(self, **kwargs):
-        favorites_settings = get_settings()
-        self._set_layout(favorites_settings.layout)
+        current_activity = CurrentActivityIcon()
 
-    def __connect_to_bundle_registry_cb(self):
-        registry = bundleregistry.get_registry()
+        ViewContainer.__init__(self, layout=self._layout,
+                               owner_icon=owner_icon,
+                               activity_icon=current_activity)
 
-        for info in registry:
-            if registry.is_bundle_favorite(info.get_bundle_id(),
-                                           info.get_activity_version()):
-                self._add_activity(info)
+        self.add_events(gtk.gdk.BUTTON_PRESS_MASK |
+                        gtk.gdk.POINTER_MOTION_HINT_MASK)
+        self.drag_dest_set(0, [], 0)
+        self.connect('drag-motion', self.__drag_motion_cb)
+        self.connect('drag-drop', self.__drag_drop_cb)
+        self.connect('drag-data-received', self.__drag_data_received_cb)
 
-        registry.connect('bundle-added', self.__activity_added_cb)
-        registry.connect('bundle-removed', self.__activity_removed_cb)
-        registry.connect('bundle-changed', self.__activity_changed_cb)
+        self._dragging = False
+        self._pressed_button = None
+        self._press_start_x = 0
+        self._press_start_y = 0
+        self._hot_x = None
+        self._hot_y = None
+        self._last_clicked_icon = None
 
-    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)
+        self._alert = None
+        self._resume_mode = True
 
-    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)
+        gobject.idle_add(self.__connect_to_bundle_registry_cb)
 
-    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 __settings_changed_cb(self, **kwargs):
+        favorites_settings = get_settings()
+        layout_set = self._set_layout(favorites_settings.layout)
+        if layout_set:
+            self.set_layout(self._layout)
+            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)
 
-    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 _set_layout(self, layout):
+        if layout not in LAYOUT_MAP:
+            logging.warn('Unknown favorites layout: %r', layout)
+            layout = favoriteslayout.RingLayout.key
+            assert layout in LAYOUT_MAP
 
-    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)
+        if type(self._layout) == LAYOUT_MAP[layout]:
+            return False
 
-        registry = bundleregistry.get_registry()
-        if registry.is_bundle_favorite(activity_info.get_bundle_id(),
-                                       activity_info.get_activity_version()):
-            self._add_activity(activity_info)
+        self._layout = LAYOUT_MAP[layout]()
+        return True
 
-    def do_size_allocate(self, allocation):
-        width = allocation.width
-        height = allocation.height
+    layout = property(None, _set_layout)
 
-        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)
-        x = (width - my_icon_width) / 2
-        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.
-    def __button_press_event_cb(self, widget, event):
-        if event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS:
-            self._last_clicked_icon = self._get_icon_at_coords(event.x,
-                                                               event.y)
-            if self._last_clicked_icon is not None:
-                self._pressed_button = event.button
-                self._press_start_x = event.x
-                self._press_start_y = event.y
+    def do_add(self, child):
+        if child != self._owner_icon and child != self._activity_icon:
+            self._children.append(child)
+            child.connect('button-press-event', self.__button_press_cb)
+            child.connect('button-release-event', self.__button_release_cb)
+            child.connect('motion-notify-event', self.__motion_notify_event_cb)
+            child.connect('drag-begin', self.__drag_begin_cb)
+        if child.flags() & gtk.REALIZED:
+            child.set_parent_window(self.get_parent_window())
+        child.set_parent(self)
+
+    def __button_release_cb(self, widget, event):
+        if self._dragging:
+            return True
+        else:
+            return False
 
+    def __button_press_cb(self, widget, event):
+        if event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS:
+            self._last_clicked_icon = widget
+            self._pressed_button = event.button
+            self._press_start_x = event.x
+            self._press_start_y = event.y
         return False
 
-    def _get_icon_at_coords(self, x, y):
-        for icon in self._box.get_children():
-            icon_x, icon_y = icon.get_context().translate_to_widget(icon)
-            icon_width, icon_height = icon.get_allocation()
-
-            if (x >= icon_x) and (x <= icon_x + icon_width) and \
-               (y >= icon_y) and (y <= icon_y + icon_height) and \
-               isinstance(icon, ActivityIcon):
-                return icon
-        return None
-
     def __motion_notify_event_cb(self, widget, event):
-        if not self._pressed_button:
-            return False
-
         # if the mouse button is not pressed, no drag should occurr
         if not event.state & gtk.gdk.BUTTON1_MASK:
             self._pressed_button = None
@@ -242,6 +202,7 @@ class FavoritesView(hippo.Canvas):
                                        int(self._press_start_y),
                                        int(x),
                                        int(y)):
+            self._dragging = True
             context_ = widget.drag_begin([_ICON_DND_TARGET],
                                          gtk.gdk.ACTION_MOVE,
                                          1,
@@ -249,9 +210,7 @@ class FavoritesView(hippo.Canvas):
         return False
 
     def __drag_begin_cb(self, widget, context):
-        icon_file_name = self._last_clicked_icon.props.file_name
-        # TODO: we should get the pixbuf from the widget, so it has colors, etc
-        pixbuf = gtk.gdk.pixbuf_new_from_file(icon_file_name)
+        pixbuf = gtk.gdk.pixbuf_new_from_file(widget.props.file_name)
 
         self._hot_x = pixbuf.props.width / 2
         self._hot_y = pixbuf.props.height / 2
@@ -267,9 +226,9 @@ class FavoritesView(hippo.Canvas):
     def __drag_drop_cb(self, widget, context, x, y, time):
         if self._last_clicked_icon is not None:
             self.drag_get_data(context, _ICON_DND_TARGET[0])
-
             self._layout.move_icon(self._last_clicked_icon,
-                                   x - self._hot_x, y - self._hot_y)
+                                   x - self._hot_x, y - self._hot_y,
+                                   self.get_allocation())
 
             self._pressed_button = None
             self._press_start_x = None
@@ -277,6 +236,7 @@ class FavoritesView(hippo.Canvas):
             self._hot_x = None
             self._hot_y = None
             self._last_clicked_icon = None
+            self._dragging = False
 
             return True
         else:
@@ -286,49 +246,68 @@ class FavoritesView(hippo.Canvas):
                                 info, time):
         context.drop_finish(success=True, time=time)
 
-    def _set_layout(self, layout):
-        if layout not in LAYOUT_MAP:
-            logging.warn('Unknown favorites layout: %r', layout)
-            layout = favoriteslayout.RingLayout.key
-            assert layout in LAYOUT_MAP
+    def __connect_to_bundle_registry_cb(self):
+        registry = bundleregistry.get_registry()
 
-        if type(self._layout) == LAYOUT_MAP[layout]:
-            return
+        for info in registry:
+            if registry.is_bundle_favorite(info.get_bundle_id(),
+                                           info.get_activity_version()):
+                self._add_activity(info)
 
-        self._layout = LAYOUT_MAP[layout]()
-        self._box.set_layout(self._layout)
+        registry.connect('bundle-added', self.__activity_added_cb)
+        registry.connect('bundle-removed', self.__activity_removed_cb)
+        registry.connect('bundle-changed', self.__activity_changed_cb)
 
-        #TODO: compatibility hack while sort() gets added to the hippo python
-        # bindings
-        if hasattr(self._box, 'sort'):
-            self._box.sort(self._layout.compare_activities)
+    def _add_activity(self, activity_info):
+        if activity_info.get_bundle_id() == 'org.laptop.JournalActivity':
+            return
+        icon = ActivityIcon(activity_info)
+        icon.props.pixel_size = style.STANDARD_ICON_SIZE
+        #icon.set_resume_mode(self._resume_mode)
+        self.add(icon)
+        icon.show()
 
-        for icon in self._box.get_children():
-            if icon not in [self._my_icon, self._current_activity]:
-                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)
 
-        self._layout.append(self._my_icon, locked=True)
-        self._layout.append(self._current_activity, locked=True)
+    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.remove(icon)
 
-        if self._layout.allow_dnd():
-            self.drag_source_set(0, [], 0)
-            self.drag_dest_set(0, [], 0)
-        else:
-            self.drag_source_unset()
-            self.drag_dest_unset()
+    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
 
-    layout = property(None, _set_layout)
+    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.remove(icon)
 
-    def add_alert(self, alert):
-        if self._alert is not None:
-            self.remove_alert()
-        alert.set_size_request(gtk.gdk.screen_width(), -1)
-        self._alert = hippo.CanvasWidget(widget=alert)
-        self._box.append(self._alert, hippo.PACK_FIXED)
+        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 remove_alert(self):
-        self._box.remove(self._alert)
-        self._alert = None
+    def set_filter(self, query):
+        query = query.strip()
+        for icon in self.get_children():
+            if icon not in [self._owner_icon, self._activity_icon]:
+                activity_name = icon.get_activity_name().lower()
+                if activity_name.find(query) > -1:
+                    icon.alpha = 1.0
+                else:
+                    icon.alpha = 0.33
 
     def __register_activate_cb(self, icon):
         alert = Alert()
@@ -341,41 +320,43 @@ class FavoritesView(hippo.Canvas):
             alert.props.title = _('Registration Successful')
             alert.props.msg = _('You are now registered ' \
                                 'with your school server.')
-            self._my_icon.set_registered()
+            self._owner_icon.set_registered()
 
         ok_icon = Icon(icon_name='dialog-ok')
         alert.add_button(gtk.RESPONSE_OK, _('Ok'), ok_icon)
 
-        self.add_alert(alert)
+        self._box.add_alert(alert)
         alert.connect('response', self.__register_alert_response_cb)
 
     def __register_alert_response_cb(self, alert, response_id):
-        self.remove_alert()
+        self._box.remove_alert()
 
     def set_resume_mode(self, resume_mode):
         self._resume_mode = resume_mode
-        for icon in self._box.get_children():
+        for icon in self.get_children():
             if hasattr(icon, 'set_resume_mode'):
                 icon.set_resume_mode(self._resume_mode)
 
 
-class ActivityIcon(CanvasIcon):
+class ActivityIcon(EventIcon):
     __gtype_name__ = 'SugarFavoriteActivityIcon'
 
     _BORDER_WIDTH = style.zoom(3)
     _MAX_RESUME_ENTRIES = 5
 
     def __init__(self, activity_info):
-        CanvasIcon.__init__(self, cache=True,
-                            file_name=activity_info.get_icon())
+        EventIcon.__init__(self, cache=True,
+                           file_name=activity_info.get_icon())
 
         self._activity_info = activity_info
         self._journal_entries = []
         self._hovering = False
         self._resume_mode = True
 
-        self.connect('hovering-changed', self.__hovering_changed_event_cb)
-        self.connect('button-release-event', self.__button_release_event_cb)
+        self.connect('enter-notify-event', self.__enter_notify_event_cb)
+        self.connect('leave-notify-event', self.__leave_notify_event_cb)
+        self.connect_after('button-release-event',
+                           self.__button_release_event_cb)
 
         datastore.updated.connect(self.__datastore_listener_updated_cb)
         datastore.deleted.connect(self.__datastore_listener_deleted_cb)
@@ -443,15 +424,23 @@ class ActivityIcon(CanvasIcon):
     def __palette_entry_activate_cb(self, palette, metadata):
         self._resume(metadata)
 
-    def __hovering_changed_event_cb(self, icon, hovering):
-        self._hovering = hovering
-        self.emit_paint_needed(0, 0, -1, -1)
+    def __enter_notify_event_cb(self, icon, event):
+        self._hovering = True
+        self.queue_draw()
+
+    def __leave_notify_event_cb(self, icon, event):
+        self._hovering = False
+        self.queue_draw()
+
+    def do_expose_event(self, event):
+        EventIcon.do_expose_event(self, event)
 
-    def do_paint_above_children(self, cr, damaged_box):
         if not self._hovering:
             return
 
-        width, height = self.get_allocation()
+        allocation = self.get_allocation()
+        width = allocation.width
+        height = allocation.height
 
         x = ActivityIcon._BORDER_WIDTH / 2.0
         y = ActivityIcon._BORDER_WIDTH / 2.0
@@ -459,6 +448,7 @@ class ActivityIcon(CanvasIcon):
         height -= ActivityIcon._BORDER_WIDTH
         radius = width / 10.0
 
+        cr = self.window.cairo_create()
         cr.move_to(x + radius, y)
         cr.arc(x + width - radius, y + radius, radius, math.pi * 1.5,
                math.pi * 2.0)
@@ -467,21 +457,14 @@ class ActivityIcon(CanvasIcon):
         cr.arc(x + radius, y + height - radius, radius, math.pi * 0.5, math.pi)
         cr.arc(x + radius, y + radius, radius, math.pi, math.pi * 1.5)
 
-        color = style.COLOR_SELECTION_GREY.get_int()
-        hippo.cairo_set_source_rgba32(cr, color)
+        cr.set_source_color(style.COLOR_SELECTION_GREY.get_gdk_color())
         cr.set_line_width(ActivityIcon._BORDER_WIDTH)
         cr.stroke()
 
-    def do_get_content_height_request(self, for_width):
-        height, height = CanvasIcon.do_get_content_height_request(self,
-                                                                  for_width)
-        height += ActivityIcon._BORDER_WIDTH * 2
-        return height, height
-
-    def do_get_content_width_request(self):
-        width, width = CanvasIcon.do_get_content_width_request(self)
-        width += ActivityIcon._BORDER_WIDTH * 2
-        return width, width
+    def do_size_request(self, req):
+        EventIcon.do_size_request(self, req)
+        req.height += ActivityIcon._BORDER_WIDTH * 2
+        req.width += ActivityIcon._BORDER_WIDTH * 2
 
     def __button_release_event_cb(self, icon, event):
         self._activate()
@@ -575,9 +558,10 @@ class FavoritePalette(ActivityPalette):
             self.emit('entry-activate', entry)
 
 
-class CurrentActivityIcon(CanvasIcon, hippo.CanvasItem):
+class CurrentActivityIcon(EventIcon):
     def __init__(self):
-        CanvasIcon.__init__(self, cache=True)
+        EventIcon.__init__(self, icon_name='activity-journal',
+                            pixel_size=style.STANDARD_ICON_SIZE, cache=True)
         self._home_model = shell.get_model()
         self._home_activity = self._home_model.get_active_activity()
 
@@ -587,7 +571,8 @@ class CurrentActivityIcon(CanvasIcon, hippo.CanvasItem):
         self._home_model.connect('active-activity-changed',
                                  self.__active_activity_changed_cb)
 
-        self.connect('button-release-event', self.__button_release_event_cb)
+        self.connect_after('button-release-event',
+                           self.__button_release_event_cb)
 
     def __button_release_event_cb(self, icon, event):
         window = self._home_model.get_active_activity().get_window()
@@ -596,7 +581,7 @@ class CurrentActivityIcon(CanvasIcon, hippo.CanvasItem):
     def _update(self):
         self.props.file_name = self._home_activity.get_icon_path()
         self.props.xo_color = self._home_activity.get_icon_color()
-        self.props.size = style.STANDARD_ICON_SIZE
+        self.props.pixel_size = style.STANDARD_ICON_SIZE
 
         if self.palette is not None:
             self.palette.destroy()
@@ -623,7 +608,7 @@ class OwnerIcon(BuddyIcon):
     }
 
     def __init__(self, size):
-        BuddyIcon.__init__(self, buddy=get_owner_instance(), size=size)
+        BuddyIcon.__init__(self, buddy=get_owner_instance(), pixel_size=size)
 
         self.palette_invoker.cache_palette = True
 
@@ -652,9 +637,6 @@ class OwnerIcon(BuddyIcon):
 
         return palette
 
-    def get_toplevel(self):
-        return hippo.get_canvas_for_item(self).get_toplevel()
-
     def __register_activate_cb(self, menuitem):
         self.emit('register-activate')
 
diff --git a/src/jarabe/desktop/friendview.py b/src/jarabe/desktop/friendview.py
index 8dab35f..01c2b71 100644
--- a/src/jarabe/desktop/friendview.py
+++ b/src/jarabe/desktop/friendview.py
@@ -15,27 +15,30 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
-import hippo
+import gtk
 
-from sugar.graphics.icon import CanvasIcon
 from sugar.graphics import style
 
 from jarabe.view.buddyicon import BuddyIcon
+from jarabe.view.eventicon import EventIcon
 from jarabe.model import bundleregistry
 
 
-class FriendView(hippo.CanvasBox):
+class FriendView(gtk.VBox):
     def __init__(self, buddy, **kwargs):
-        hippo.CanvasBox.__init__(self, **kwargs)
+        gtk.VBox.__init__(self)
+
+        # round icon sizes to an even number so that it can be accurately
+        # centered in a larger bounding box also of even dimensions
+        size = style.LARGE_ICON_SIZE & ~1
 
         self._buddy = buddy
         self._buddy_icon = BuddyIcon(buddy)
-        self._buddy_icon.props.size = style.LARGE_ICON_SIZE
-        self.append(self._buddy_icon)
-
-        self._activity_icon = CanvasIcon(size=style.LARGE_ICON_SIZE)
-        self._activity_icon_visible = False
+        self._buddy_icon.props.pixel_size = size
+        self.add(self._buddy_icon)
+        self._buddy_icon.show()
 
+        self._activity_icon = EventIcon(pixel_size=size)
         self._update_activity()
 
         self._buddy.connect('notify::current-activity',
@@ -51,9 +54,9 @@ class FriendView(hippo.CanvasBox):
         return None
 
     def _remove_activity_icon(self):
-        if self._activity_icon_visible:
+        if self._activity_icon.get_visible():
+            self._activity_icon.hide()
             self.remove(self._activity_icon)
-            self._activity_icon_visible = False
 
     def __buddy_notify_current_activity_cb(self, buddy, pspec):
         self._update_activity()
@@ -70,9 +73,9 @@ class FriendView(hippo.CanvasBox):
         if name:
             self._activity_icon.props.file_name = name
             self._activity_icon.props.xo_color = self._buddy.props.color
-            if not self._activity_icon_visible:
-                self.append(self._activity_icon, hippo.PACK_EXPAND)
-                self._activity_icon_visible = True
+            if not self._activity_icon.get_visible():
+                self.add(self._activity_icon)
+                self._activity_icon.show()
         else:
             self._remove_activity_icon()
 
diff --git a/src/jarabe/desktop/grid.py b/src/jarabe/desktop/grid.py
index eab4033..65b33b2 100644
--- a/src/jarabe/desktop/grid.py
+++ b/src/jarabe/desktop/grid.py
@@ -75,6 +75,9 @@ class Grid(_sugarext.Grid):
         if weight > 0:
             self._detect_collisions(child)
 
+    def is_in_grid(self, child):
+        return child in self._children
+
     def remove(self, child):
         self._children.remove(child)
         self.remove_weight(self._child_rects[child])
diff --git a/src/jarabe/desktop/groupbox.py b/src/jarabe/desktop/groupbox.py
index ed8f8ae..4fcd6c2 100644
--- a/src/jarabe/desktop/groupbox.py
+++ b/src/jarabe/desktop/groupbox.py
@@ -16,47 +16,40 @@
 
 import logging
 
-import gobject
-import hippo
 import gconf
 
 from sugar.graphics import style
-from sugar.graphics.icon import CanvasIcon
 from sugar.graphics.xocolor import XoColor
 
 from jarabe.view.buddymenu import BuddyMenu
+from jarabe.view.eventicon import EventIcon
 from jarabe.model.buddy import get_owner_instance
 from jarabe.model import friends
 from jarabe.desktop.friendview import FriendView
-from jarabe.desktop.spreadlayout import SpreadLayout
+from jarabe.desktop.viewcontainer import ViewContainer
+from jarabe.desktop.favoriteslayout import SpreadLayout
 
 
-class GroupBox(hippo.Canvas):
+class GroupBox(ViewContainer):
     __gtype_name__ = 'SugarGroupBox'
 
     def __init__(self):
         logging.debug('STARTUP: Loading the group view')
 
-        gobject.GObject.__init__(self)
-
-        self._box = hippo.CanvasBox()
-        self._box.props.background_color = style.COLOR_WHITE.get_int()
-        self.set_root(self._box)
-
-        self._friends = {}
-
-        self._layout = SpreadLayout()
-        self._box.set_layout(self._layout)
+        layout = SpreadLayout()
 
         client = gconf.client_get_default()
         color = XoColor(client.get_string('/desktop/sugar/user/color'))
+        owner_icon = EventIcon(icon_name='computer-xo', cache=True,
+                               xo_color=color)
+        # Round off icon size to an even number to ensure that the icon
+        # is placed evenly in the grid
+        owner_icon.props.pixel_size = style.LARGE_ICON_SIZE & ~1
+        owner_icon.set_palette(BuddyMenu(get_owner_instance()))
 
-        self._owner_icon = CanvasIcon(icon_name='computer-xo', cache=True,
-                                      xo_color=color)
-        self._owner_icon.props.size = style.LARGE_ICON_SIZE
+        ViewContainer.__init__(self, layout, owner_icon)
 
-        self._owner_icon.set_palette(BuddyMenu(get_owner_instance()))
-        self._layout.add(self._owner_icon)
+        self._friends = {}
 
         friends_model = friends.get_model()
 
@@ -68,27 +61,15 @@ class GroupBox(hippo.Canvas):
 
     def add_friend(self, buddy_info):
         icon = FriendView(buddy_info)
-        self._layout.add(icon)
-
+        self.add(icon)
         self._friends[buddy_info.get_key()] = icon
+        icon.show()
 
     def _friend_added_cb(self, data_model, buddy_info):
         self.add_friend(buddy_info)
 
     def _friend_removed_cb(self, data_model, key):
         icon = self._friends[key]
-        self._layout.remove(icon)
+        self.remove(icon)
         del self._friends[key]
         icon.destroy()
-
-    def do_size_allocate(self, allocation):
-        width = allocation.width
-        height = allocation.height
-
-        min_w_, icon_width = self._owner_icon.get_width_request()
-        min_h_, icon_height = self._owner_icon.get_height_request(icon_width)
-        x = (width - icon_width) / 2
-        y = (height - icon_height) / 2
-        self._layout.move(self._owner_icon, x, y)
-
-        hippo.Canvas.do_size_allocate(self, allocation)
diff --git a/src/jarabe/desktop/homebox.py b/src/jarabe/desktop/homebox.py
index 2ee6ae7..33c6965 100644
--- a/src/jarabe/desktop/homebox.py
+++ b/src/jarabe/desktop/homebox.py
@@ -45,7 +45,7 @@ class HomeBox(gtk.VBox):
 
         gobject.GObject.__init__(self)
 
-        self._favorites_view = favoritesview.FavoritesView()
+        self._favorites_box = favoritesview.FavoritesBox()
         self._list_view = ActivitiesList()
 
         self._toolbar = HomeToolbar()
@@ -78,14 +78,14 @@ class HomeBox(gtk.VBox):
         if self._list_view in self.get_children():
             self._list_view.add_alert(alert)
         else:
-            self._favorites_view.add_alert(alert)
+            self._favorites_box.add_alert(alert)
         alert.connect('response', self.__software_update_response_cb)
 
     def __software_update_response_cb(self, alert, response_id):
         if self._list_view in self.get_children():
             self._list_view.remove_alert()
         else:
-            self._favorites_view.remove_alert()
+            self._favorites_box.remove_alert()
 
         if response_id != gtk.RESPONSE_REJECT:
             update_trigger_file = os.path.expanduser('~/.sugar-update')
@@ -106,7 +106,7 @@ class HomeBox(gtk.VBox):
     def __toolbar_query_changed_cb(self, toolbar, query):
         self._query = query.lower()
         self._list_view.set_filter(self._query)
-        self._favorites_view.set_filter(self._query)
+        self._favorites_box.set_filter(self._query)
 
     def __toolbar_view_changed_cb(self, toolbar, view):
         self._set_view(view)
@@ -116,12 +116,12 @@ class HomeBox(gtk.VBox):
             if self._list_view in self.get_children():
                 self.remove(self._list_view)
 
-            if self._favorites_view not in self.get_children():
-                self.add(self._favorites_view)
-                self._favorites_view.show()
+            if self._favorites_box not in self.get_children():
+                self.add(self._favorites_box)
+                self._favorites_box.show()
         elif view == _LIST_VIEW:
-            if self._favorites_view in self.get_children():
-                self.remove(self._favorites_view)
+            if self._favorites_box in self.get_children():
+                self.remove(self._favorites_box)
 
             if self._list_view not in self.get_children():
                 self.add(self._list_view)
@@ -146,10 +146,10 @@ class HomeBox(gtk.VBox):
         self._toolbar.search_entry.grab_focus()
 
     def set_resume_mode(self, resume_mode):
-        self._favorites_view.set_resume_mode(resume_mode)
+        self._favorites_box.set_resume_mode(resume_mode)
         if resume_mode and self._query != '':
             self._list_view.set_filter(self._query)
-            self._favorites_view.set_filter(self._query)
+            self._favorites_box.set_filter(self._query)
 
 
 class HomeToolbar(gtk.Toolbar):
diff --git a/src/jarabe/desktop/homewindow.py b/src/jarabe/desktop/homewindow.py
index 07deff7..a5536c0 100644
--- a/src/jarabe/desktop/homewindow.py
+++ b/src/jarabe/desktop/homewindow.py
@@ -57,6 +57,8 @@ class HomeWindow(gtk.Window):
 
         self.realize()
         self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DESKTOP)
+        self.modify_bg(gtk.STATE_NORMAL,
+                       style.COLOR_WHITE.get_gdk_color())
 
         self.add_events(gtk.gdk.VISIBILITY_NOTIFY_MASK)
         self.connect('visibility-notify-event',
diff --git a/src/jarabe/desktop/meshbox.py b/src/jarabe/desktop/meshbox.py
index 20dc413..1de3779 100644
--- a/src/jarabe/desktop/meshbox.py
+++ b/src/jarabe/desktop/meshbox.py
@@ -21,26 +21,29 @@ from gettext import gettext as _
 import logging
 
 import dbus
-import hippo
 import glib
 import gobject
 import gtk
 import gconf
 
-from sugar.graphics.icon import CanvasIcon, Icon
+from sugar.graphics.icon import Icon
 from sugar.graphics import style
 from sugar.graphics import palette
 from sugar.graphics import iconentry
 from sugar.graphics.menuitem import MenuItem
+from sugar.graphics.xocolor import XoColor
 
+from jarabe.desktop.snowflakelayout import SnowflakeLayout
 from jarabe.model import neighborhood
 from jarabe.model.buddy import get_owner_instance
 from jarabe.view.buddyicon import BuddyIcon
-from jarabe.desktop.snowflakelayout import SnowflakeLayout
-from jarabe.desktop.spreadlayout import SpreadLayout
+from jarabe.view.buddymenu import BuddyMenu
+from jarabe.view.eventicon import EventIcon
 from jarabe.desktop.networkviews import WirelessNetworkView
 from jarabe.desktop.networkviews import OlpcMeshView
 from jarabe.desktop.networkviews import SugarAdhocView
+from jarabe.desktop.viewcontainer import ViewContainer
+from jarabe.desktop.favoriteslayout import SpreadLayout
 from jarabe.model import network
 from jarabe.model.network import AccessPoint
 from jarabe.model.olpcmesh import OlpcMeshManager
@@ -55,14 +58,13 @@ _AUTOSEARCH_TIMEOUT = 1000
 _FILTERED_ALPHA = 0.33
 
 
-class _ActivityIcon(CanvasIcon):
+class _ActivityIcon(EventIcon):
     def __init__(self, model, file_name, xo_color,
                  size=style.STANDARD_ICON_SIZE):
-        CanvasIcon.__init__(self, file_name=file_name,
-                            xo_color=xo_color,
-                            size=size)
+        EventIcon.__init__(self, file_name=file_name,
+                           xo_color=xo_color, pixel_size=size)
         self._model = model
-        self.connect('activated', self._clicked_cb)
+        self.connect('button-release-event', self._button_release_cb)
 
     def create_palette(self):
         primary_text = glib.markup_escape_text(self._model.bundle.get_name())
@@ -91,15 +93,18 @@ class _ActivityIcon(CanvasIcon):
 
         return p
 
+    def _button_release_cb(self, widget, event):
+        return self._clicked_cb(item=None)
+
     def _clicked_cb(self, item):
         bundle = self._model.get_bundle()
         misc.launch(bundle, activity_id=self._model.activity_id,
                     color=self._model.get_color())
 
 
-class ActivityView(hippo.CanvasBox):
+class ActivityView(SnowflakeLayout):
     def __init__(self, model):
-        hippo.CanvasBox.__init__(self)
+        SnowflakeLayout.__init__(self)
 
         self._model = model
         self._model.connect('current-buddy-added', self.__buddy_added_cb)
@@ -107,11 +112,9 @@ class ActivityView(hippo.CanvasBox):
 
         self._icons = {}
 
-        self._layout = SnowflakeLayout()
-        self.set_layout(self._layout)
-
         self._icon = self._create_icon()
-        self._layout.add(self._icon, center=True)
+        self._icon.show()
+        self.add_icon(self._icon, center=True)
 
         self._icon.palette_invoker.cache_palette = False
 
@@ -134,11 +137,13 @@ class ActivityView(hippo.CanvasBox):
     def _add_buddy(self, buddy):
         icon = BuddyIcon(buddy, style.STANDARD_ICON_SIZE)
         self._icons[buddy.props.key] = icon
-        self._layout.add(icon)
+        self.add_icon(icon)
+        icon.show()
 
     def __buddy_removed_cb(self, activity, buddy):
         icon = self._icons[buddy.props.key]
         del self._icons[buddy.props.key]
+        self.remove(icon)
         icon.destroy()
 
     def set_filter(self, query):
@@ -401,13 +406,32 @@ class NetworkManagerObserver(object):
                         self._box.add_adhoc_networks(device)
 
 
+class MeshContainer(ViewContainer):
+    __gtype_name__ = 'SugarMeshContainer'
+
+    def __init__(self):
+
+        layout = SpreadLayout()
+
+        client = gconf.client_get_default()
+        color = XoColor(client.get_string('/desktop/sugar/user/color'))
+        owner_icon = EventIcon(icon_name='computer-xo', cache=True,
+                               xo_color=color)
+        # Round off icon size to an even number to ensure that the icon
+        # is placed evenly in the grid
+        owner_icon.props.pixel_size = style.STANDARD_ICON_SIZE & ~1
+        owner_icon.set_palette(BuddyMenu(get_owner_instance()))
+
+        ViewContainer.__init__(self, layout, owner_icon)
+
+
 class MeshBox(gtk.VBox):
     __gtype_name__ = 'SugarMeshBox'
 
     def __init__(self):
         logging.debug('STARTUP: Loading the mesh view')
 
-        gobject.GObject.__init__(self)
+        gtk.VBox.__init__(self)
 
         self.wireless_networks = {}
         self._adhoc_manager = None
@@ -420,23 +444,15 @@ class MeshBox(gtk.VBox):
         self._buddy_to_activity = {}
         self._suspended = True
         self._query = ''
-        self._owner_icon = None
 
         self._toolbar = MeshToolbar()
         self._toolbar.connect('query-changed', self._toolbar_query_changed_cb)
         self.pack_start(self._toolbar, expand=False)
         self._toolbar.show()
 
-        canvas = hippo.Canvas()
-        self.add(canvas)
-        canvas.show()
-
-        self._layout_box = hippo.CanvasBox( \
-                background_color=style.COLOR_WHITE.get_int())
-        canvas.set_root(self._layout_box)
-
-        self._layout = SpreadLayout()
-        self._layout_box.set_layout(self._layout)
+        self._mesh_container = MeshContainer()
+        self.add(self._mesh_container)
+        self._mesh_container.show()
 
         for buddy_model in self._model.get_buddies():
             self._add_buddy(buddy_model)
@@ -453,18 +469,6 @@ class MeshBox(gtk.VBox):
         netmgr_observer = NetworkManagerObserver(self)
         netmgr_observer.listen()
 
-    def do_size_allocate(self, allocation):
-        width = allocation.width
-        height = allocation.height
-
-        min_w_, icon_width = self._owner_icon.get_width_request()
-        min_h_, icon_height = self._owner_icon.get_height_request(icon_width)
-        x = (width - icon_width) / 2
-        y = (height - icon_height) / 2 - style.GRID_CELL_SIZE
-        self._layout.move(self._owner_icon, x, y)
-
-        gtk.VBox.do_size_allocate(self, allocation)
-
     def _buddy_added_cb(self, model, buddy_model):
         self._add_buddy(buddy_model)
 
@@ -482,10 +486,11 @@ class MeshBox(gtk.VBox):
                             self.__buddy_notify_current_activity_cb)
         if buddy_model.props.current_activity is not None:
             return
-        icon = BuddyIcon(buddy_model)
         if buddy_model.is_owner():
-            self._owner_icon = icon
-        self._layout.add(icon)
+            return
+        icon = BuddyIcon(buddy_model)
+        self._mesh_container.add(icon)
+        icon.show()
 
         if hasattr(icon, 'set_filter'):
             icon.set_filter(self._query)
@@ -495,9 +500,8 @@ class MeshBox(gtk.VBox):
     def _remove_buddy(self, buddy_model):
         logging.debug('MeshBox._remove_buddy')
         icon = self._buddies[buddy_model.props.key]
-        self._layout.remove(icon)
+        self._mesh_container.remove(icon)
         del self._buddies[buddy_model.props.key]
-        icon.destroy()
 
     def __buddy_notify_current_activity_cb(self, buddy_model, pspec):
         logging.debug('MeshBox.__buddy_notify_current_activity_cb %s',
@@ -510,7 +514,8 @@ class MeshBox(gtk.VBox):
 
     def _add_activity(self, activity_model):
         icon = ActivityView(activity_model)
-        self._layout.add(icon)
+        self._mesh_container.add(icon)
+        icon.show()
 
         if hasattr(icon, 'set_filter'):
             icon.set_filter(self._query)
@@ -519,9 +524,8 @@ class MeshBox(gtk.VBox):
 
     def _remove_activity(self, activity_model):
         icon = self._activities[activity_model.activity_id]
-        self._layout.remove(icon)
+        self._mesh_container.remove(icon)
         del self._activities[activity_model.activity_id]
-        icon.destroy()
 
     # add AP to its corresponding network icon on the desktop,
     # creating one if it doesn't already exist
@@ -533,7 +537,8 @@ class MeshBox(gtk.VBox):
             # this is a new network
             icon = WirelessNetworkView(ap)
             self.wireless_networks[hash_value] = icon
-            self._layout.add(icon)
+            self._mesh_container.add(icon)
+            icon.show()
             if hasattr(icon, 'set_filter'):
                 icon.set_filter(self._query)
 
@@ -541,7 +546,7 @@ class MeshBox(gtk.VBox):
         # remove a network if it has no APs left
         if net.num_aps() == 0:
             net.disconnect()
-            self._layout.remove(net)
+            self._mesh_container.remove(net)
             del self.wireless_networks[hash_value]
 
     def _ap_props_changed_cb(self, ap, old_hash_value):
@@ -619,18 +624,20 @@ class MeshBox(gtk.VBox):
 
     def remove_adhoc_networks(self):
         for icon in self._adhoc_networks:
-            self._layout.remove(icon)
+            self._mesh_container.remove(icon)
         self._adhoc_networks = []
         self._adhoc_manager.stop_listening()
 
     def _add_adhoc_network_icon(self, channel):
         icon = SugarAdhocView(channel)
-        self._layout.add(icon)
+        self._mesh_container.add(icon)
+        icon.show()
         self._adhoc_networks.append(icon)
 
     def _add_olpc_mesh_icon(self, mesh_mgr, channel):
         icon = OlpcMeshView(mesh_mgr, channel)
-        self._layout.add(icon)
+        self._mesh_container.add(icon)
+        icon.show()
         self._mesh.append(icon)
 
     def enable_olpc_mesh(self, mesh_device):
@@ -648,13 +655,13 @@ class MeshBox(gtk.VBox):
             logging.debug('removing OLPC mesh IBSS')
             net.remove_all_aps()
             net.disconnect()
-            self._layout.remove(net)
+            self._mesh_container.remove(net)
             del self.wireless_networks[hash_value]
 
     def disable_olpc_mesh(self, mesh_device):
         for icon in self._mesh:
             icon.disconnect()
-            self._layout.remove(icon)
+            self._mesh_container.remove(icon)
         self._mesh = []
 
     def suspend(self):
@@ -671,7 +678,7 @@ class MeshBox(gtk.VBox):
 
     def _toolbar_query_changed_cb(self, toolbar, query):
         self._query = query.lower()
-        for icon in self._layout_box.get_children():
+        for icon in self._mesh_container.get_children():
             if hasattr(icon, 'set_filter'):
                 icon.set_filter(self._query)
 
diff --git a/src/jarabe/desktop/networkviews.py b/src/jarabe/desktop/networkviews.py
index f42bfed..d2531bf 100644
--- a/src/jarabe/desktop/networkviews.py
+++ b/src/jarabe/desktop/networkviews.py
@@ -33,7 +33,7 @@ from sugar.graphics.menuitem import MenuItem
 from sugar.util import unique_id
 from sugar import profile
 
-from jarabe.view.pulsingicon import CanvasPulsingIcon
+from jarabe.view.pulsingicon import EventPulsingIcon
 from jarabe.desktop import keydialog
 from jarabe.model import network
 from jarabe.model.network import Settings
@@ -48,10 +48,10 @@ _OLPC_MESH_ICON_NAME = 'network-mesh'
 _FILTERED_ALPHA = 0.33
 
 
-class WirelessNetworkView(CanvasPulsingIcon):
+class WirelessNetworkView(EventPulsingIcon):
     def __init__(self, initial_ap):
-        CanvasPulsingIcon.__init__(self, size=style.STANDARD_ICON_SIZE,
-                                   cache=True)
+        EventPulsingIcon.__init__(self, pixel_size=style.STANDARD_ICON_SIZE,
+                                  cache=True)
         self._bus = dbus.SystemBus()
         self._access_points = {initial_ap.model.object_path: initial_ap}
         self._active_ap = None
@@ -255,9 +255,9 @@ class WirelessNetworkView(CanvasPulsingIcon):
         self.props.base_color = self._color
         if self._filtered:
             self.props.pulsing = False
-            self.alpha = _FILTERED_ALPHA
+            self.props.alpha = _FILTERED_ALPHA
         else:
-            self.alpha = 1.0
+            self.props.alpha = 1.0
 
     def _disconnect_activate_cb(self, item):
         ap_paths = self._access_points.keys()
@@ -436,7 +436,7 @@ class WirelessNetworkView(CanvasPulsingIcon):
                                          dbus_interface=network.NM_WIRELESS_IFACE)
 
 
-class SugarAdhocView(CanvasPulsingIcon):
+class SugarAdhocView(EventPulsingIcon):
     """To mimic the mesh behavior on devices where mesh hardware is
     not available we support the creation of an Ad-hoc network on
     three channels 1, 6, 11. This is the class for an icon
@@ -448,9 +448,10 @@ class SugarAdhocView(CanvasPulsingIcon):
     _NAME = 'Ad-hoc Network '
 
     def __init__(self, channel):
-        CanvasPulsingIcon.__init__(self,
-                                   icon_name=self._ICON_NAME + str(channel),
-                                   size=style.STANDARD_ICON_SIZE, cache=True)
+        EventPulsingIcon.__init__(self,
+                                  icon_name=self._ICON_NAME + str(channel),
+                                  pixel_size=style.STANDARD_ICON_SIZE,
+                                  cache=True)
         self._bus = dbus.SystemBus()
         self._channel = channel
         self._disconnect_item = None
@@ -572,10 +573,11 @@ class SugarAdhocView(CanvasPulsingIcon):
         self._update_color()
 
 
-class OlpcMeshView(CanvasPulsingIcon):
+class OlpcMeshView(EventPulsingIcon):
     def __init__(self, mesh_mgr, channel):
-        CanvasPulsingIcon.__init__(self, icon_name=_OLPC_MESH_ICON_NAME,
-                                   size=style.STANDARD_ICON_SIZE, cache=True)
+        EventPulsingIcon.__init__(self, icon_name=_OLPC_MESH_ICON_NAME,
+                                  pixel_size=style.STANDARD_ICON_SIZE,
+                                  cache=True)
         self._bus = dbus.SystemBus()
         self._channel = channel
         self._mesh_mgr = mesh_mgr
diff --git a/src/jarabe/desktop/snowflakelayout.py b/src/jarabe/desktop/snowflakelayout.py
index e4963ba..25cae76 100644
--- a/src/jarabe/desktop/snowflakelayout.py
+++ b/src/jarabe/desktop/snowflakelayout.py
@@ -16,8 +16,7 @@
 
 import math
 
-import gobject
-import hippo
+import gtk
 
 from sugar.graphics import style
 
@@ -26,54 +25,71 @@ _BASE_DISTANCE = style.zoom(25)
 _CHILDREN_FACTOR = style.zoom(3)
 
 
-class SnowflakeLayout(gobject.GObject, hippo.CanvasLayout):
+class SnowflakeLayout(gtk.Container):
     __gtype_name__ = 'SugarSnowflakeLayout'
 
     def __init__(self):
-        gobject.GObject.__init__(self)
+        gtk.Container.__init__(self)
+        self.set_has_window(False)
         self._nflakes = 0
-        self._box = None
-
-    def add(self, child, center=False):
+        self._children = {}
+
+    def do_realize(self):
+        # FIXME what is this for?
+        self.set_flags(gtk.REALIZED)
+        self.set_window(self.get_parent_window())
+        self.style.attach(self.window)
+        for child in self._children.keys():
+            child.set_parent_window(self.get_parent_window())
+        self.queue_resize()
+
+    def do_add(self, child):
+        if child.flags() & gtk.REALIZED:
+            child.set_parent_window(self.get_parent_window())
+        child.set_parent(self)
+
+    def do_forall(self, include_internals, callback, data):
+        for child in self._children.keys():
+            callback(child, data)
+
+    def do_remove(self, child):
+        child.unparent()
+
+    def add_icon(self, child, center=False):
         if not center:
             self._nflakes += 1
 
-        self._box.append(child)
-
-        box_child = self._box.find_box_child(child)
-        box_child.is_center = center
+        self._children[child] = center
+        self.add(child)
 
     def remove(self, child):
-        box_child = self._box.find_box_child(child)
-        if not box_child.is_center:
-            self._nflakes -= 1
-
-        self._box.remove(child)
+        if not child in self._children:
+            return
 
-    def do_set_box(self, box):
-        self._box = box
+        if not self._children[child]:  # not centered
+            self._nflakes -= 1
 
-    def do_get_height_request(self, for_width):
-        size = self._calculate_size()
-        return (size, size)
+        del self._children[child]
+        self.remove(child)
 
-    def do_get_width_request(self):
+    def do_size_request(self, requisition):
         size = self._calculate_size()
-        return (size, size)
+        requisition.width = size
+        requisition.height = size
 
-    def do_allocate(self, x, y, width, height,
-                    req_width, req_height, origin_changed):
+    def do_size_allocate(self, allocation):
         r = self._get_radius()
         index = 0
 
-        for child in self._box.get_layout_children():
-            min_width, child_width = child.get_width_request()
-            min_height, child_height = child.get_height_request(child_width)
+        for child, centered in self._children.items():
+            child_width, child_height = child.size_request()
+            rect = gtk.gdk.Rectangle(0, 0, child_width, child_height)
 
-            if child.is_center:
-                child.allocate(x + (width - child_width) / 2,
-                               y + (height - child_height) / 2,
-                               child_width, child_height, origin_changed)
+            width = allocation.width - child_width
+            height = allocation.height - child_height
+            if centered:
+                rect.x = allocation.x + width / 2
+                rect.y = allocation.y + height / 2
             else:
                 angle = 2 * math.pi * index / self._nflakes
 
@@ -83,29 +99,26 @@ class SnowflakeLayout(gobject.GObject, hippo.CanvasLayout):
                 dx = math.cos(angle) * r
                 dy = math.sin(angle) * r
 
-                child_x = int(x + (width - child_width) / 2 + dx)
-                child_y = int(y + (height - child_height) / 2 + dy)
-
-                child.allocate(child_x, child_y, child_width,
-                               child_height, origin_changed)
+                rect.x = int(allocation.x + width / 2 + dx)
+                rect.y = int(allocation.y + height / 2 + dy)
 
                 index += 1
 
+            child.size_allocate(rect)
+
     def _get_radius(self):
         radius = int(_BASE_DISTANCE + _CHILDREN_FACTOR * self._nflakes)
-        for child in self._box.get_layout_children():
-            if child.is_center:
-                [min_w, child_w] = child.get_width_request()
-                [min_h, child_h] = child.get_height_request(child_w)
+        for child, centered in self._children.items():
+            if centered:
+                child_w, child_h = child.size_request()
                 radius += max(child_w, child_h) / 2
 
         return radius
 
     def _calculate_size(self):
         thickness = 0
-        for child in self._box.get_layout_children():
-            [min_width, child_width] = child.get_width_request()
-            [min_height, child_height] = child.get_height_request(child_width)
-            thickness = max(thickness, max(child_width, child_height))
+        for child in self._children.keys():
+            width, height = child.size_request()
+            thickness = max(thickness, max(width, height))
 
         return self._get_radius() * 2 + thickness
diff --git a/src/jarabe/desktop/spreadlayout.py b/src/jarabe/desktop/spreadlayout.py
deleted file mode 100644
index b5c623e..0000000
--- a/src/jarabe/desktop/spreadlayout.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# Copyright (C) 2007 Red Hat, Inc.
-#
-# 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
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
-
-import math
-
-import hippo
-import gobject
-import gtk
-
-from sugar.graphics import style
-
-from jarabe.desktop.grid import Grid
-
-
-_CELL_SIZE = 4.0
-
-
-class SpreadLayout(gobject.GObject, hippo.CanvasLayout):
-    __gtype_name__ = 'SugarSpreadLayout'
-
-    def __init__(self):
-        gobject.GObject.__init__(self)
-        self._box = None
-
-        min_width, width = self.do_get_width_request()
-        min_height, height = self.do_get_height_request(width)
-
-        self._grid = Grid(int(width / _CELL_SIZE), int(height / _CELL_SIZE))
-        self._grid.connect('child-changed', self._grid_child_changed_cb)
-
-    def add(self, child):
-        self._box.append(child)
-
-        width, height = self._get_child_grid_size(child)
-        self._grid.add(child, width, height)
-
-    def remove(self, child):
-        self._grid.remove(child)
-        self._box.remove(child)
-
-    def move(self, child, x, y):
-        self._grid.move(child, x / _CELL_SIZE, y / _CELL_SIZE, locked=True)
-
-    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 do_allocate(self, x, y, width, height,
-                    req_width, req_height, origin_changed):
-        for child in self._box.get_layout_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)
-
-            rect = self._grid.get_child_rect(child.item)
-            child.allocate(int(round(rect.x * _CELL_SIZE)),
-                           int(round(rect.y * _CELL_SIZE)),
-                           child_width,
-                           child_height,
-                           origin_changed)
-
-    def _get_child_grid_size(self, child):
-        min_width, width = child.get_width_request()
-        min_height, height = child.get_height_request(width)
-        width = math.ceil(width / _CELL_SIZE)
-        height = math.ceil(height / _CELL_SIZE)
-
-        return int(width), int(height)
-
-    def _grid_child_changed_cb(self, grid, child):
-        child.emit_request_changed()
diff --git a/src/jarabe/desktop/transitionbox.py b/src/jarabe/desktop/transitionbox.py
index fd2112c..54a70de 100644
--- a/src/jarabe/desktop/transitionbox.py
+++ b/src/jarabe/desktop/transitionbox.py
@@ -14,7 +14,6 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
-import hippo
 import gobject
 
 from sugar.graphics import style
@@ -34,37 +33,10 @@ class _Animation(animator.Animation):
 
     def next_frame(self, current):
         d = (self.end_size - self.start_size) * current
-        self._icon.props.size = int(self.start_size + d)
+        self._icon.props.pixel_size = int(self.start_size + d)
 
 
-class _Layout(gobject.GObject, hippo.CanvasLayout):
-    __gtype_name__ = 'SugarTransitionBoxLayout'
-
-    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, 0
-
-    def do_get_width_request(self):
-        return 0, 0
-
-    def do_allocate(self, x, y, width, height,
-                    req_width, req_height, origin_changed):
-        for child in self._box.get_layout_children():
-            min_width, child_width = child.get_width_request()
-            min_height, child_height = child.get_height_request(child_width)
-
-            child.allocate(x + (width - child_width) / 2,
-                           y + (height - child_height) / 2,
-                           child_width, child_height, origin_changed)
-
-
-class TransitionBox(hippo.Canvas):
+class TransitionBox(BuddyIcon):
     __gtype_name__ = 'SugarTransitionBox'
 
     __gsignals__ = {
@@ -72,18 +44,8 @@ class TransitionBox(hippo.Canvas):
     }
 
     def __init__(self):
-        gobject.GObject.__init__(self)
-
-        self._box = hippo.CanvasBox()
-        self._box.props.background_color = style.COLOR_WHITE.get_int()
-        self.set_root(self._box)
-
-        self._layout = _Layout()
-        self._box.set_layout(self._layout)
-
-        self._my_icon = BuddyIcon(buddy=get_owner_instance(),
-                                  size=style.XLARGE_ICON_SIZE)
-        self._box.append(self._my_icon)
+        BuddyIcon.__init__(self, buddy=get_owner_instance(),
+                           pixel_size=style.XLARGE_ICON_SIZE)
 
         self._animator = animator.Animator(0.3)
         self._animator.connect('completed', self._animation_completed_cb)
@@ -92,8 +54,6 @@ class TransitionBox(hippo.Canvas):
         self.emit('completed')
 
     def start_transition(self, start_size, end_size):
-        self._my_icon.props.size = start_size
-
         self._animator.remove_all()
-        self._animator.add(_Animation(self._my_icon, start_size, end_size))
+        self._animator.add(_Animation(self, start_size, end_size))
         self._animator.start()
diff --git a/src/jarabe/desktop/viewcontainer.py b/src/jarabe/desktop/viewcontainer.py
new file mode 100644
index 0000000..1c76fb1
--- /dev/null
+++ b/src/jarabe/desktop/viewcontainer.py
@@ -0,0 +1,82 @@
+# Copyright (C) 2011-2012 One Laptop Per Child
+# Copyright (C) 2010 Tomeu Vizoso
+# Copyright (C) 2011 Walter Bender
+# Copyright (C) 2011 Raul Gutierrez Segales
+#
+# 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
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+import gtk
+
+
+class ViewContainer(gtk.Container):
+    __gtype_name__ = 'SugarViewContainer'
+
+    def __init__(self, layout, owner_icon, activity_icon=None, **kwargs):
+        gtk.Container.__init__(self, **kwargs)
+        self.set_has_window(False)
+
+        self._activity_icon = None
+        self._owner_icon = None
+        self._layout = None
+
+        self._children = []
+        self.set_layout(layout)
+
+        if owner_icon:
+            self._owner_icon = owner_icon
+            self.add(self._owner_icon)
+            self._owner_icon.show()
+
+        if activity_icon:
+            self._activity_icon = activity_icon
+            self.add(self._activity_icon)
+            self._activity_icon.show()
+
+    def do_add(self, child):
+        if child != self._owner_icon and child != self._activity_icon:
+            self._children.append(child)
+        if child.flags() & gtk.REALIZED:
+            child.set_parent_window(self.get_parent_window())
+        child.set_parent(self)
+
+    def do_remove(self, child):
+        was_visible = child.get_visible()
+        if child in self._children:
+            self._children.remove(child)
+            child.unparent()
+            self._layout.remove(child)
+            if was_visible and self.get_visible():
+                self.queue_resize()
+
+    def do_size_allocate(self, allocation):
+        self.allocation = allocation
+        if self._owner_icon:
+            self._layout.setup(allocation, self._owner_icon,
+                                       self._activity_icon)
+
+        self._layout.allocate_children(allocation, self._children)
+
+    def do_forall(self, include_internals, callback, callback_data):
+        for child in self._children:
+            callback(child, callback_data)
+        if self._owner_icon:
+            callback(self._owner_icon, callback_data)
+        if self._activity_icon:
+            callback(self._activity_icon, callback_data)
+
+    def set_layout(self, layout):
+        for child in self.get_children():
+            self.remove(child)
+        self._layout = layout
diff --git a/src/jarabe/journal/detailview.py b/src/jarabe/journal/detailview.py
index aa8c039..628af01 100644
--- a/src/jarabe/journal/detailview.py
+++ b/src/jarabe/journal/detailview.py
@@ -19,10 +19,9 @@ from gettext import gettext as _
 
 import gobject
 import gtk
-import hippo
 
 from sugar.graphics import style
-from sugar.graphics.icon import CanvasIcon
+from sugar.graphics.icon import Icon
 
 from jarabe.journal.expandedentry import ExpandedEntry
 from jarabe.journal import model
@@ -39,21 +38,15 @@ class DetailView(gtk.VBox):
         self._metadata = None
         self._expanded_entry = None
 
-        canvas = hippo.Canvas()
-
-        self._root = hippo.CanvasBox()
-        self._root.props.background_color = style.COLOR_PANEL_GREY.get_int()
-        canvas.set_root(self._root)
+        gobject.GObject.__init__(self, **kwargs)
+        gtk.VBox.__init__(self)
 
         back_bar = BackBar()
         back_bar.connect('button-release-event',
                          self.__back_bar_release_event_cb)
-        self._root.append(back_bar)
-
-        gobject.GObject.__init__(self, **kwargs)
+        self.pack_start(back_bar, expand=False)
 
-        self.pack_start(canvas)
-        canvas.show()
+        self.show_all()
 
     def _fav_icon_activated_cb(self, fav_icon):
         keep = not self._expanded_entry.get_keep()
@@ -67,8 +60,9 @@ class DetailView(gtk.VBox):
     def _update_view(self):
         if self._expanded_entry is None:
             self._expanded_entry = ExpandedEntry()
-            self._root.append(self._expanded_entry, hippo.PACK_EXPAND)
+            self.pack_start(self._expanded_entry)
         self._expanded_entry.set_metadata(self._metadata)
+        self.show_all()
 
     def refresh(self):
         logging.debug('DetailView.refresh')
@@ -86,34 +80,37 @@ class DetailView(gtk.VBox):
             type=object, getter=get_metadata, setter=set_metadata)
 
 
-class BackBar(hippo.CanvasBox):
+class BackBar(gtk.EventBox):
     def __init__(self):
-        hippo.CanvasBox.__init__(self,
-                orientation=hippo.ORIENTATION_HORIZONTAL,
-                border=style.LINE_WIDTH,
-                background_color=style.COLOR_PANEL_GREY.get_int(),
-                border_color=style.COLOR_SELECTION_GREY.get_int(),
-                padding=style.DEFAULT_PADDING,
-                padding_left=style.DEFAULT_SPACING,
-                spacing=style.DEFAULT_SPACING)
-
-        icon = CanvasIcon(icon_name='go-previous',
-                          size=style.SMALL_ICON_SIZE,
-                          fill_color=style.COLOR_TOOLBAR_GREY.get_svg())
-        self.append(icon)
-
-        label = hippo.CanvasText(text=_('Back'),
-                                 font_desc=style.FONT_NORMAL.get_pango_desc())
-        self.append(label)
+        gtk.EventBox.__init__(self)
+        self.modify_bg(gtk.STATE_NORMAL,
+                       style.COLOR_PANEL_GREY.get_gdk_color())
+        hbox = gtk.HBox(spacing=style.DEFAULT_PADDING)
+        hbox.set_border_width(style.DEFAULT_PADDING)
+        icon = Icon(icon_name='go-previous', icon_size=gtk.ICON_SIZE_MENU,
+                    fill_color=style.COLOR_TOOLBAR_GREY.get_svg())
+        hbox.pack_start(icon, False, False)
+
+        label = gtk.Label()
+        label.set_text(_('Back'))
+        halign = gtk.Alignment(0, 0.5, 0, 1)
+        halign.add(label)
+        hbox.pack_start(halign, True, True)
+        hbox.show()
+        self.add(hbox)
 
         if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
-            self.reverse()
+            hbox.reverse()
+
+        self.connect('enter-notify-event', self.__enter_notify_event_cb)
+        self.connect('leave-notify-event', self.__leave_notify_event_cb)
 
-        self.connect('motion-notify-event', self.__motion_notify_event_cb)
+    def __enter_notify_event_cb(self, box, event):
+        box.modify_bg(gtk.STATE_NORMAL,
+                      style.COLOR_SELECTION_GREY.get_gdk_color())
+        return False
 
-    def __motion_notify_event_cb(self, box, event):
-        if event.detail == hippo.MOTION_DETAIL_ENTER:
-            box.props.background_color = style.COLOR_SELECTION_GREY.get_int()
-        elif event.detail == hippo.MOTION_DETAIL_LEAVE:
-            box.props.background_color = style.COLOR_PANEL_GREY.get_int()
+    def __leave_notify_event_cb(self, box, event):
+        box.modify_bg(gtk.STATE_NORMAL,
+                      style.COLOR_PANEL_GREY.get_gdk_color())
         return False
diff --git a/src/jarabe/journal/expandedentry.py b/src/jarabe/journal/expandedentry.py
index 03f8cd1..e0c603f 100644
--- a/src/jarabe/journal/expandedentry.py
+++ b/src/jarabe/journal/expandedentry.py
@@ -20,7 +20,6 @@ import StringIO
 import time
 import os
 
-import hippo
 import cairo
 import gobject
 import glib
@@ -28,158 +27,144 @@ import gtk
 import simplejson
 
 from sugar.graphics import style
-from sugar.graphics.icon import CanvasIcon
 from sugar.graphics.xocolor import XoColor
-from sugar.graphics.canvastextview import CanvasTextView
 from sugar.util import format_size
 
 from jarabe.journal.keepicon import KeepIcon
 from jarabe.journal.palettes import ObjectPalette, BuddyPalette
 from jarabe.journal import misc
 from jarabe.journal import model
+from jarabe.view.eventicon import EventIcon
 
 
-class Separator(hippo.CanvasBox, hippo.CanvasItem):
+class Separator(gtk.VBox):
     def __init__(self, orientation):
-        hippo.CanvasBox.__init__(self,
-                background_color=style.COLOR_PANEL_GREY.get_int())
-
-        if orientation == hippo.ORIENTATION_VERTICAL:
-            self.props.box_width = style.LINE_WIDTH
-        else:
-            self.props.box_height = style.LINE_WIDTH
+        gtk.VBox.__init__(self,
+                background_color=style.COLOR_PANEL_GREY.get_gdk_color())
 
 
-class BuddyList(hippo.CanvasBox):
+class BuddyList(gtk.Alignment):
     def __init__(self, buddies):
-        hippo.CanvasBox.__init__(self, xalign=hippo.ALIGNMENT_START,
-                                 orientation=hippo.ORIENTATION_HORIZONTAL)
+        gtk.Alignment.__init__(self, 0, 0, 0, 0)
 
+        hbox = gtk.HBox()
         for buddy in buddies:
             nick_, color = buddy
-            hbox = hippo.CanvasBox(orientation=hippo.ORIENTATION_HORIZONTAL)
-            icon = CanvasIcon(icon_name='computer-xo',
-                              xo_color=XoColor(color),
-                              size=style.STANDARD_ICON_SIZE)
+            icon = EventIcon(icon_name='computer-xo',
+                             xo_color=XoColor(color),
+                             pixel_size=style.STANDARD_ICON_SIZE)
             icon.set_palette(BuddyPalette(buddy))
-            hbox.append(icon)
-            self.append(hbox)
+            hbox.pack_start(icon)
+        self.add(hbox)
 
 
-class ExpandedEntry(hippo.CanvasBox):
+class ExpandedEntry(gtk.EventBox):
     def __init__(self):
-        hippo.CanvasBox.__init__(self)
-        self.props.orientation = hippo.ORIENTATION_VERTICAL
-        self.props.background_color = style.COLOR_WHITE.get_int()
-        self.props.padding_top = style.DEFAULT_SPACING * 3
+        gtk.EventBox.__init__(self)
+        self._vbox = gtk.VBox()
+        self.add(self._vbox)
 
         self._metadata = None
         self._update_title_sid = None
 
-        # Create header
-        header = hippo.CanvasBox(orientation=hippo.ORIENTATION_HORIZONTAL,
-                                 padding=style.DEFAULT_PADDING,
-                                 padding_right=style.GRID_CELL_SIZE,
-                                 spacing=style.DEFAULT_SPACING)
-        self.append(header)
-
-        # Create two column body
+        self.modify_bg(gtk.STATE_NORMAL, style.COLOR_WHITE.get_gdk_color())
 
-        body = hippo.CanvasBox(orientation=hippo.ORIENTATION_HORIZONTAL,
-                               spacing=style.DEFAULT_SPACING * 3,
-                               padding_left=style.GRID_CELL_SIZE,
-                               padding_right=style.GRID_CELL_SIZE,
-                               padding_top=style.DEFAULT_SPACING * 3)
+        # Create a header
+        header = gtk.HBox()
+        self._vbox.pack_start(header, False, False, style.DEFAULT_SPACING * 2)
 
-        self.append(body, hippo.PACK_EXPAND)
+        # Create a two-column body
+        body_box = gtk.EventBox()
+        body_box.set_border_width(style.DEFAULT_SPACING)
+        body_box.modify_bg(gtk.STATE_NORMAL, style.COLOR_WHITE.get_gdk_color())
+        self._vbox.pack_start(body_box)
+        body = gtk.HBox()
+        body_box.add(body)
 
-        first_column = hippo.CanvasBox(orientation=hippo.ORIENTATION_VERTICAL,
-                                       spacing=style.DEFAULT_SPACING)
-        body.append(first_column)
+        first_column = gtk.VBox()
+        body.pack_start(first_column, False, False, style.DEFAULT_SPACING)
 
-        second_column = hippo.CanvasBox(orientation=hippo.ORIENTATION_VERTICAL,
-                                       spacing=style.DEFAULT_SPACING)
-        body.append(second_column, hippo.PACK_EXPAND)
+        second_column = gtk.VBox()
+        body.pack_start(second_column)
 
         # Header
-
         self._keep_icon = self._create_keep_icon()
-        header.append(self._keep_icon)
+        header.pack_start(self._keep_icon, False, False, style.DEFAULT_SPACING)
 
         self._icon = None
-        self._icon_box = hippo.CanvasBox()
-        header.append(self._icon_box)
+        self._icon_box = gtk.HBox()
+        header.pack_start(self._icon_box, False, False, style.DEFAULT_SPACING)
 
         self._title = self._create_title()
-        header.append(self._title, hippo.PACK_EXPAND)
+        header.pack_start(self._title)
 
         # TODO: create a version list popup instead of a date label
         self._date = self._create_date()
-        header.append(self._date)
+        header.pack_start(self._date, False, False, style.DEFAULT_SPACING)
 
         if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
             header.reverse()
 
-        # First column
-
-        self._preview_box = hippo.CanvasBox()
-        first_column.append(self._preview_box)
+        # First body column
+        self._preview_box = gtk.Frame()
+        first_column.pack_start(self._preview_box, expand=False)
 
-        self._technical_box = hippo.CanvasBox()
-        first_column.append(self._technical_box)
-
-        # Second column
+        self._technical_box = gtk.VBox()
+        first_column.pack_start(self._technical_box)
 
+        # Second body column
         description_box, self._description = self._create_description()
-        second_column.append(description_box)
+        second_column.pack_start(description_box, True, True,
+                                 style.DEFAULT_SPACING)
 
         tags_box, self._tags = self._create_tags()
-        second_column.append(tags_box)
+        second_column.pack_start(tags_box, True, True,
+                                 style.DEFAULT_SPACING)
+
+        self._buddy_list = gtk.VBox()
+        second_column.pack_start(self._buddy_list)
 
-        self._buddy_list = hippo.CanvasBox()
-        second_column.append(self._buddy_list)
+        self.show_all()
 
     def set_metadata(self, metadata):
         if self._metadata == metadata:
             return
         self._metadata = metadata
 
-        self._keep_icon.keep = (str(metadata.get('keep', 0)) == '1')
+        self._keep_icon.set_active(int(metadata.get('keep', 0)) == 1)
 
         self._icon = self._create_icon()
-        self._icon_box.clear()
-        self._icon_box.append(self._icon)
-
-        self._date.props.text = misc.get_date(metadata)
+        self._icon_box.foreach(self._icon_box.remove)
+        self._icon_box.pack_start(self._icon, False, False)
 
-        title = self._title.props.widget
-        title.props.text = metadata.get('title', _('Untitled'))
-        title.props.editable = model.is_editable(metadata)
+        self._date.set_text(misc.get_date(metadata))
 
-        self._preview_box.clear()
-        self._preview_box.append(self._create_preview())
+        self._title.set_text(metadata.get('title', _('Untitled')))
 
-        self._technical_box.clear()
-        self._technical_box.append(self._create_technical())
+        if self._preview_box.get_child():
+            self._preview_box.remove(self._preview_box.get_child())
+        self._preview_box.add(self._create_preview())
 
-        self._buddy_list.clear()
-        self._buddy_list.append(self._create_buddy_list())
+        self._technical_box.foreach(self._technical_box.remove)
+        self._technical_box.pack_start(self._create_technical(),
+                                       False, False, style.DEFAULT_SPACING)
 
-        description = self._description.text_view_widget
-        description.props.buffer.props.text = metadata.get('description', '')
-        description.props.editable = model.is_editable(metadata)
+        self._buddy_list.foreach(self._buddy_list.remove)
+        self._buddy_list.pack_start(self._create_buddy_list(), False, False,
+                                    style.DEFAULT_SPACING)
 
-        tags = self._tags.text_view_widget
-        tags.props.buffer.props.text = metadata.get('tags', '')
-        tags.props.editable = model.is_editable(metadata)
+        description = metadata.get('description', '')
+        self._description.get_buffer().set_text(description)
+        tags = metadata.get('tags', '')
+        self._tags.get_buffer().set_text(tags)
 
     def _create_keep_icon(self):
-        keep_icon = KeepIcon(False)
-        keep_icon.connect('activated', self._keep_icon_activated_cb)
+        keep_icon = KeepIcon()
+        keep_icon.connect('toggled', self._keep_icon_toggled_cb)
         return keep_icon
 
     def _create_icon(self):
-        icon = CanvasIcon(file_name=misc.get_icon_name(self._metadata))
+        icon = EventIcon(file_name=misc.get_icon_name(self._metadata))
         icon.connect_after('button-release-event',
                            self._icon_button_release_event_cb)
 
@@ -202,17 +187,17 @@ class ExpandedEntry(hippo.CanvasBox):
         entry.modify_bg(gtk.STATE_INSENSITIVE, bg_color)
         entry.modify_base(gtk.STATE_INSENSITIVE, bg_color)
 
-        return hippo.CanvasWidget(widget=entry)
+        return entry
 
     def _create_date(self):
-        date = hippo.CanvasText(xalign=hippo.ALIGNMENT_START,
-                                font_desc=style.FONT_NORMAL.get_pango_desc())
+        date = gtk.Label()
         return date
 
     def _create_preview(self):
         width = style.zoom(320)
         height = style.zoom(240)
-        box = hippo.CanvasBox()
+        box = gtk.EventBox()
+        box.modify_bg(gtk.STATE_NORMAL, style.COLOR_WHITE.get_gdk_color())
 
         if len(self._metadata.get('preview', '')) > 4:
             if self._metadata['preview'][1:4] == 'PNG':
@@ -225,7 +210,17 @@ class ExpandedEntry(hippo.CanvasBox):
 
             png_file = StringIO.StringIO(preview_data)
             try:
+                # Load image and scale to dimensions
                 surface = cairo.ImageSurface.create_from_png(png_file)
+                png_width = surface.get_width()
+                png_height = surface.get_height()
+                pixmap = gtk.gdk.Pixmap(None, png_width, png_height, 24)
+                cr = pixmap.cairo_create()
+                cr.set_source_surface(surface, 0, 0)
+                cr.scale(width / png_width, height / png_height)
+                cr.paint()
+
+                im = gtk.image_new_from_pixmap(pixmap, None)
                 has_preview = True
             except Exception:
                 logging.exception('Error while loading the preview')
@@ -234,50 +229,35 @@ class ExpandedEntry(hippo.CanvasBox):
             has_preview = False
 
         if has_preview:
-            preview_box = hippo.CanvasImage(image=surface,
-                    border=style.LINE_WIDTH,
-                    border_color=style.COLOR_BUTTON_GREY.get_int(),
-                    xalign=hippo.ALIGNMENT_CENTER,
-                    yalign=hippo.ALIGNMENT_CENTER,
-                    scale_width=width,
-                    scale_height=height)
+            box.add(im)
         else:
-            preview_box = hippo.CanvasText(text=_('No preview'),
-                    font_desc=style.FONT_NORMAL.get_pango_desc(),
-                    xalign=hippo.ALIGNMENT_CENTER,
-                    yalign=hippo.ALIGNMENT_CENTER,
-                    border=style.LINE_WIDTH,
-                    border_color=style.COLOR_BUTTON_GREY.get_int(),
-                    color=style.COLOR_BUTTON_GREY.get_int(),
-                    box_width=width,
-                    box_height=height)
-        preview_box.connect_after('button-release-event',
-                                  self._preview_box_button_release_event_cb)
-        box.append(preview_box)
+            label = gtk.Label()
+            label.set_text(_('No preview'))
+            label.set_size_request(width, height)
+            box.add(label)
+
+        box.connect_after('button-release-event',
+                          self._preview_box_button_release_event_cb)
         return box
 
     def _create_technical(self):
-        vbox = hippo.CanvasBox()
+        vbox = gtk.VBox()
         vbox.props.spacing = style.DEFAULT_SPACING
 
-        lines = [
-            _('Kind: %s') % (self._metadata.get('mime_type') or _('Unknown'),),
-            _('Date: %s') % (self._format_date(),),
-            _('Size: %s') % (format_size(int(self._metadata.get('filesize',
-                                model.get_file_size(self._metadata['uid']))))),
-            ]
-
-        for line in lines:
-            text = hippo.CanvasText(text=line,
-                font_desc=style.FONT_NORMAL.get_pango_desc())
-            text.props.color = style.COLOR_BUTTON_GREY.get_int()
-
-            if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
-                text.props.xalign = hippo.ALIGNMENT_END
-            else:
-                text.props.xalign = hippo.ALIGNMENT_START
-
-            vbox.append(text)
+        label = \
+            _('Kind: %s') % (self._metadata.get('mime_type') or \
+                                 _('Unknown'),) + '\n' + \
+            _('Date: %s') % (self._format_date(),) + '\n' + \
+            _('Size: %s') % (format_size(int(self._metadata.get(
+                        'filesize',
+                        model.get_file_size(self._metadata['uid'])))))
+
+        text = gtk.Label()
+        text.set_markup('<span foreground="%s">%s</span>' % (
+                style.COLOR_BUTTON_GREY.get_html(), label))
+        halign = gtk.Alignment(0, 0, 0, 0)
+        halign.add(text)
+        vbox.pack_start(halign, False, False, 0)
 
         return vbox
 
@@ -295,76 +275,55 @@ class ExpandedEntry(hippo.CanvasBox):
 
     def _create_buddy_list(self):
 
-        vbox = hippo.CanvasBox()
+        vbox = gtk.VBox()
         vbox.props.spacing = style.DEFAULT_SPACING
 
-        text = hippo.CanvasText(text=_('Participants:'),
-                                font_desc=style.FONT_NORMAL.get_pango_desc())
-        text.props.color = style.COLOR_BUTTON_GREY.get_int()
-
-        if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
-            text.props.xalign = hippo.ALIGNMENT_END
-        else:
-            text.props.xalign = hippo.ALIGNMENT_START
-
-        vbox.append(text)
+        text = gtk.Label()
+        text.set_markup('<span foreground="%s">%s</span>' % (
+                style.COLOR_BUTTON_GREY.get_html(), _('Participants:')))
+        halign = gtk.Alignment(0, 0, 0, 0)
+        halign.add(text)
+        vbox.pack_start(halign, False, False, 0)
 
         if self._metadata.get('buddies'):
             buddies = simplejson.loads(self._metadata['buddies']).values()
-            vbox.append(BuddyList(buddies))
+            vbox.pack_start(BuddyList(buddies), False, False, 0)
             return vbox
         else:
             return vbox
 
-    def _create_description(self):
-        vbox = hippo.CanvasBox()
+    def _create_scrollable(self, label):
+        vbox = gtk.VBox()
         vbox.props.spacing = style.DEFAULT_SPACING
 
-        text = hippo.CanvasText(text=_('Description:'),
-                                font_desc=style.FONT_NORMAL.get_pango_desc())
-        text.props.color = style.COLOR_BUTTON_GREY.get_int()
+        text = gtk.Label()
+        text.set_markup('<span foreground="%s">%s</span>' % (
+                style.COLOR_BUTTON_GREY.get_html(), label))
 
-        if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
-            text.props.xalign = hippo.ALIGNMENT_END
-        else:
-            text.props.xalign = hippo.ALIGNMENT_START
+        halign = gtk.Alignment(0, 0, 0, 0)
+        halign.add(text)
+        vbox.pack_start(halign, False, False, 0)
 
-        vbox.append(text)
+        scrolled_window = gtk.ScrolledWindow()
+        scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+        scrolled_window.set_border_width(style.LINE_WIDTH)
+        text_buffer = gtk.TextBuffer()
+        text_view = gtk.TextView(text_buffer)
+        text_view.set_left_margin(style.DEFAULT_PADDING)
+        text_view.set_wrap_mode(gtk.WRAP_WORD_CHAR)
+        scrolled_window.add_with_viewport(text_view)
+        vbox.pack_start(scrolled_window)
 
-        text_view = CanvasTextView('',
-                box_height=style.GRID_CELL_SIZE * 2)
-        vbox.append(text_view, hippo.PACK_EXPAND)
-
-        text_view.text_view_widget.props.accepts_tab = False
-        text_view.text_view_widget.connect('focus-out-event',
-                self._description_focus_out_event_cb)
+        # text_view.text_view_widget.connect('focus-out-event',
+        #                  self._description_focus_out_event_cb)
 
         return vbox, text_view
 
-    def _create_tags(self):
-        vbox = hippo.CanvasBox()
-        vbox.props.spacing = style.DEFAULT_SPACING
-
-        text = hippo.CanvasText(text=_('Tags:'),
-                                font_desc=style.FONT_NORMAL.get_pango_desc())
-        text.props.color = style.COLOR_BUTTON_GREY.get_int()
-
-        if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
-            text.props.xalign = hippo.ALIGNMENT_END
-        else:
-            text.props.xalign = hippo.ALIGNMENT_START
-
-        vbox.append(text)
-
-        text_view = CanvasTextView('',
-                box_height=style.GRID_CELL_SIZE * 2)
-        vbox.append(text_view, hippo.PACK_EXPAND)
-
-        text_view.text_view_widget.props.accepts_tab = False
-        text_view.text_view_widget.connect('focus-out-event',
-                self._tags_focus_out_event_cb)
+    def _create_description(self):
+        return self._create_scrollable(_('Description:'))
 
-        return vbox, text_view
+    def _create_tags(self):
+        return self._create_scrollable(_('Tags:'))
 
     def _title_notify_text_cb(self, entry, pspec):
         if not self._update_title_sid:
@@ -385,7 +344,7 @@ class ExpandedEntry(hippo.CanvasBox):
             return
 
         old_title = self._metadata.get('title', None)
-        new_title = self._title.props.widget.props.text
+        new_title = self._title.get_text()
         if old_title != new_title:
             label = glib.markup_escape_text(new_title)
             self._icon.palette.props.primary_text = label
@@ -393,15 +352,18 @@ class ExpandedEntry(hippo.CanvasBox):
             self._metadata['title_set_by_user'] = '1'
             needs_update = True
 
+        bounds = self._tags.get_buffer().get_bounds()
         old_tags = self._metadata.get('tags', None)
-        new_tags = self._tags.text_view_widget.props.buffer.props.text
+        new_tags = self._tags.get_buffer().get_text(bounds[0], bounds[1])
+
         if old_tags != new_tags:
             self._metadata['tags'] = new_tags
             needs_update = True
 
+        bounds = self._description.get_buffer().get_bounds()
         old_description = self._metadata.get('description', None)
-        new_description = \
-                self._description.text_view_widget.props.buffer.props.text
+        new_description = self._description.get_buffer().get_text(
+            bounds[0], bounds[1])
         if old_description != new_description:
             self._metadata['description'] = new_description
             needs_update = True
@@ -418,16 +380,12 @@ class ExpandedEntry(hippo.CanvasBox):
 
         self._update_title_sid = None
 
-    def get_keep(self):
-        return (str(self._metadata.get('keep', 0)) == '1')
-
-    def _keep_icon_activated_cb(self, keep_icon):
-        if self.get_keep():
-            self._metadata['keep'] = 0
-        else:
+    def _keep_icon_toggled_cb(self, keep_icon):
+        if keep_icon.get_active():
             self._metadata['keep'] = 1
+        else:
+            self._metadata['keep'] = 0
         self._update_entry(needs_update=True)
-        keep_icon.props.keep = self.get_keep()
 
     def _icon_button_release_event_cb(self, button, event):
         logging.debug('_icon_button_release_event_cb')
diff --git a/src/jarabe/journal/keepicon.py b/src/jarabe/journal/keepicon.py
index 5bc299b..85b1728 100644
--- a/src/jarabe/journal/keepicon.py
+++ b/src/jarabe/journal/keepicon.py
@@ -14,51 +14,42 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
-import gobject
-import hippo
+import gtk
 import gconf
+import logging
 
-from sugar.graphics.icon import CanvasIcon
+from sugar.graphics.icon import Icon
 from sugar.graphics import style
 from sugar.graphics.xocolor import XoColor
 
 
-class KeepIcon(CanvasIcon):
-    def __init__(self, keep):
-        CanvasIcon.__init__(self, icon_name='emblem-favorite',
-                            box_width=style.GRID_CELL_SIZE * 3 / 5,
-                            size=style.SMALL_ICON_SIZE)
-        self.connect('motion-notify-event', self.__motion_notify_event_cb)
+class KeepIcon(gtk.ToggleButton):
+    def __init__(self):
+        gtk.ToggleButton.__init__(self)
+        self.set_relief(gtk.RELIEF_NONE)
+        self.set_focus_on_click(False)
 
-        self._keep = None
-        self.set_keep(keep)
+        self._icon = Icon(icon_name='emblem-favorite',
+                          pixel_size=style.SMALL_ICON_SIZE)
+        self.set_image(self._icon)
+        self.connect('toggled', self.__toggled_cb)
+        self.connect('leave-notify-event', self.__leave_notify_event_cb)
+        self.connect('enter-notify-event', self.__enter_notify_event_cb)
 
-    def set_keep(self, keep):
-        if keep == self._keep:
-            return
-
-        self._keep = keep
-        if keep:
+    def __toggled_cb(self, widget):
+        if self.get_active():
             client = gconf.client_get_default()
             color = XoColor(client.get_string('/desktop/sugar/user/color'))
-            self.props.xo_color = color
+            self._icon.props.xo_color = color
+            logging.debug('KEEPICON: setting xo_color')
         else:
-            self.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg()
-            self.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
-
-    def get_keep(self):
-        return self._keep
+            self._icon.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg()
+            self._icon.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
 
-    keep = gobject.property(type=int, default=0, getter=get_keep,
-                            setter=set_keep)
+    def __enter_notify_event_cb(self, icon, event):
+        if not self.get_active():
+            self._icon.props.fill_color = style.COLOR_BUTTON_GREY.get_svg()
 
-    def __motion_notify_event_cb(self, icon, event):
-        if not self._keep:
-            if event.detail == hippo.MOTION_DETAIL_ENTER:
-                client = gconf.client_get_default()
-                prelit_color = XoColor(client.get_string('/desktop/sugar/user/color'))
-                icon.props.stroke_color = prelit_color.get_stroke_color()
-                icon.props.fill_color = prelit_color.get_fill_color()
-            elif event.detail == hippo.MOTION_DETAIL_LEAVE:
-                icon.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg()
-                icon.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
+    def __leave_notify_event_cb(self, icon, event):
+        if not self.get_active():
+            self._icon.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
diff --git a/src/jarabe/journal/listview.py b/src/jarabe/journal/listview.py
index 57836f2..f6a867f 100644
--- a/src/jarabe/journal/listview.py
+++ b/src/jarabe/journal/listview.py
@@ -18,14 +18,14 @@ import logging
 from gettext import gettext as _
 import time
 
+import glib
 import gobject
 import gtk
-import hippo
 import gconf
 import pango
 
 from sugar.graphics import style
-from sugar.graphics.icon import CanvasIcon, Icon, CellRendererIcon
+from sugar.graphics.icon import Icon, CellRendererIcon
 from sugar.graphics.xocolor import XoColor
 from sugar import util
 
@@ -33,6 +33,7 @@ from jarabe.journal.listmodel import ListModel
 from jarabe.journal.palettes import ObjectPalette, BuddyPalette
 from jarabe.journal import model
 from jarabe.journal import misc
+from jarabe.view.eventicon import EventIcon
 
 
 UPDATE_INTERVAL = 300
@@ -370,38 +371,33 @@ class BaseListView(gtk.Bin):
         self._progress_bar = None
 
     def _show_message(self, message, show_clear_query=False):
-        canvas = hippo.Canvas()
+        box = gtk.VBox()
         self.remove(self.child)
-        self.add(canvas)
-        canvas.show()
-
-        box = hippo.CanvasBox(orientation=hippo.ORIENTATION_VERTICAL,
-                              background_color=style.COLOR_WHITE.get_int(),
-                              yalign=hippo.ALIGNMENT_CENTER,
-                              spacing=style.DEFAULT_SPACING,
-                              padding_bottom=style.GRID_CELL_SIZE)
-        canvas.set_root(box)
-
-        icon = CanvasIcon(size=style.LARGE_ICON_SIZE,
-                          icon_name='activity-journal',
-                          stroke_color=style.COLOR_BUTTON_GREY.get_svg(),
-                          fill_color=style.COLOR_TRANSPARENT.get_svg())
-        box.append(icon)
-
-        text = hippo.CanvasText(text=message,
-                xalign=hippo.ALIGNMENT_CENTER,
-                font_desc=style.FONT_BOLD.get_pango_desc(),
-                color=style.COLOR_BUTTON_GREY.get_int())
-        box.append(text)
+
+        alignment = gtk.Alignment(0.5, 0.5, 0.1, 0.1)
+        self.add(alignment)
+
+        icon = EventIcon(pixel_size=style.LARGE_ICON_SIZE,
+                         icon_name='activity-journal',
+                         stroke_color=style.COLOR_BUTTON_GREY.get_svg(),
+                         fill_color=style.COLOR_TRANSPARENT.get_svg())
+        box.pack_start(icon, expand=True, fill=False)
+
+        label = gtk.Label()
+        color = style.COLOR_BUTTON_GREY.get_html()
+        label.set_markup('<span weight="bold" color="%s">%s</span>' % ( \
+                color, glib.markup_escape_text(message)))
+        box.pack_start(label, expand=True, fill=False)
 
         if show_clear_query:
             button = gtk.Button(label=_('Clear search'))
             button.connect('clicked', self.__clear_button_clicked_cb)
             button.props.image = Icon(icon_name='dialog-cancel',
                                       icon_size=gtk.ICON_SIZE_BUTTON)
-            canvas_button = hippo.CanvasWidget(widget=button,
-                                               xalign=hippo.ALIGNMENT_CENTER)
-            box.append(canvas_button)
+            box.pack_start(button, expand=True, fill=False)
+
+        alignment.add(box)
+        alignment.show_all()
 
     def __clear_button_clicked_cb(self, button):
         self.emit('clear-clicked')
diff --git a/src/jarabe/view/buddyicon.py b/src/jarabe/view/buddyicon.py
index e0e8b3f..663bd92 100644
--- a/src/jarabe/view/buddyicon.py
+++ b/src/jarabe/view/buddyicon.py
@@ -14,17 +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
 
-from sugar.graphics.icon import CanvasIcon
 from sugar.graphics import style
 
 from jarabe.view.buddymenu import BuddyMenu
+from jarabe.view.eventicon import EventIcon
+
 
 _FILTERED_ALPHA = 0.33
 
 
-class BuddyIcon(CanvasIcon):
-    def __init__(self, buddy, size=style.STANDARD_ICON_SIZE):
-        CanvasIcon.__init__(self, icon_name='computer-xo', size=size)
+class BuddyIcon(EventIcon):
+    def __init__(self, buddy, pixel_size=style.STANDARD_ICON_SIZE):
+        EventIcon.__init__(self, icon_name='computer-xo',
+                           pixel_size=pixel_size)
 
         self._filtered = False
         self._buddy = buddy
diff --git a/src/jarabe/view/pulsingicon.py b/src/jarabe/view/pulsingicon.py
index 9a98a80..39e0bab 100644
--- a/src/jarabe/view/pulsingicon.py
+++ b/src/jarabe/view/pulsingicon.py
@@ -18,9 +18,12 @@ import math
 
 import gobject
 
-from sugar.graphics.icon import Icon, CanvasIcon
+from sugar.graphics.icon import Icon
 from sugar.graphics import style
 
+from jarabe.view.eventicon import EventIcon
+
+
 _INTERVAL = 100
 _STEP = math.pi / 10  # must be a fraction of pi, for clean caching
 _MINIMAL_ALPHA_VALUE = 0.33
@@ -169,8 +172,8 @@ class PulsingIcon(Icon):
             self._palette.destroy()
 
 
-class CanvasPulsingIcon(CanvasIcon):
-    __gtype_name__ = 'SugarCanvasPulsingIcon'
+class EventPulsingIcon(EventIcon):
+    __gtype_name__ = 'SugarEventPulsingIcon'
 
     def __init__(self, **kwargs):
         self._pulser = Pulser(self)
@@ -179,7 +182,7 @@ class CanvasPulsingIcon(CanvasIcon):
         self._paused = False
         self._pulsing = False
 
-        CanvasIcon.__init__(self, **kwargs)
+        EventIcon.__init__(self, **kwargs)
 
         self.connect('destroy', self.__destroy_cb)
 
-- 
1.7.11.2



More information about the Sugar-devel mailing list