[gnome-disk-utility/udisks2-port] Add volume grid widget



commit af91fcb18678dde9895fa0b49cdb22161a69d174
Author: David Zeuthen <davidz redhat com>
Date:   Thu Mar 10 13:45:37 2011 -0500

    Add volume grid widget
    
    Signed-off-by: David Zeuthen <davidz redhat com>

 data/ui/palimpsest.ui          |   39 +-
 src/palimpsest/Makefile.am     |   21 +
 src/palimpsest/gdu.h           |   13 +-
 src/palimpsest/gduenums.h      |   39 +
 src/palimpsest/gdutypes.h      |    5 +
 src/palimpsest/gduvolumegrid.c | 1839 ++++++++++++++++++++++++++++++++++++++++
 src/palimpsest/gduvolumegrid.h |   46 +
 src/palimpsest/gduwindow.c     |   31 +-
 8 files changed, 2018 insertions(+), 15 deletions(-)
---
diff --git a/data/ui/palimpsest.ui b/data/ui/palimpsest.ui
index b63146e..a1f739c 100644
--- a/data/ui/palimpsest.ui
+++ b/data/ui/palimpsest.ui
@@ -115,6 +115,7 @@
               <object class="GtkVBox" id="vbox1">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
+                <property name="spacing">12</property>
                 <child>
                   <object class="GtkTable" id="devtab-table">
                     <property name="visible">True</property>
@@ -422,16 +423,48 @@
                     </child>
                   </object>
                   <packing>
-                    <property name="expand">True</property>
+                    <property name="expand">False</property>
                     <property name="fill">True</property>
                     <property name="position">0</property>
                   </packing>
                 </child>
                 <child>
-                  <placeholder/>
+                  <object class="GtkLabel" id="label2">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">&lt;b&gt;Volumes&lt;/b&gt;</property>
+                    <property name="use_markup">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
                 </child>
                 <child>
-                  <placeholder/>
+                  <object class="GtkAlignment" id="alignment2">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="left_padding">12</property>
+                    <child>
+                      <object class="GtkHBox" id="devtab-grid-hbox">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
                 </child>
               </object>
               <packing>
diff --git a/src/palimpsest/Makefile.am b/src/palimpsest/Makefile.am
index 0c5bc3d..9019a68 100644
--- a/src/palimpsest/Makefile.am
+++ b/src/palimpsest/Makefile.am
@@ -1,8 +1,27 @@
 
 NULL =
+BUILT_SOURCES =
 
 bin_PROGRAMS = palimpsest
 
+gduenumtypes.h: gduenums.h gduenumtypes.h.template
+	( top_builddir=`cd $(top_builddir) && pwd`; 						\
+	 cd $(srcdir) && glib-mkenums --template gduenumtypes.h.template gduenums.h ) > 	\
+	   gduenumtypes.h.tmp && mv gduenumtypes.h.tmp gduenumtypes.h				\
+	$(NULL)
+
+gduenumtypes.c: gduenums.h gduenumtypes.c.template
+	( top_builddir=`cd $(top_builddir) && pwd`; 						\
+	 cd $(srcdir) && glib-mkenums --template gduenumtypes.c.template gduenums.h ) > 	\
+	   gduenumtypes.c.tmp && mv gduenumtypes.c.tmp gduenumtypes.c				\
+	$(NULL)
+
+enum_built_sources =							\
+	gduenumtypes.h			gduenumtypes.c			\
+	$(NULL)
+
+BUILT_SOURCES += $(enum_built_sources)
+
 palimpsest_SOURCES = 					\
 				main.c			\
 	gdu.h						\
@@ -10,7 +29,9 @@ palimpsest_SOURCES = 					\
 	gdudevicetreemodel.h	gdudevicetreemodel.c	\
 	gdutypes.h					\
 	gduutils.h		gduutils.c		\
+	gduvolumegrid.h		gduvolumegrid.c		\
 	gduwindow.h		gduwindow.c		\
+	$(enum_built_sources)				\
 	$(NULL)
 
 palimpsest_CPPFLAGS = 					\
diff --git a/src/palimpsest/gdu.h b/src/palimpsest/gdu.h
index a5fbcba..ff6e3df 100644
--- a/src/palimpsest/gdu.h
+++ b/src/palimpsest/gdu.h
@@ -23,10 +23,13 @@
 #ifndef __GDU_H__
 #define __GDU_H__
 
-#include <gdutypes.h>
-#include <gduapplication.h>
-#include <gdudevicetreemodel.h>
-#include <gduutils.h>
-#include <gduwindow.h>
+#include "gdutypes.h"
+#include "gduenums.h"
+#include "gduenumtypes.h"
+#include "gduapplication.h"
+#include "gdudevicetreemodel.h"
+#include "gduutils.h"
+#include "gduvolumegrid.h"
+#include "gduwindow.h"
 
 #endif /* __GDU_H__ */
diff --git a/src/palimpsest/gduenums.h b/src/palimpsest/gduenums.h
new file mode 100644
index 0000000..f87df65
--- /dev/null
+++ b/src/palimpsest/gduenums.h
@@ -0,0 +1,39 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2008-2011 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __GDU_ENUMS_H__
+#define __GDU_ENUMS_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+  GDU_VOLUME_GRID_ELEMENT_TYPE_NO_MEDIA,
+  GDU_VOLUME_GRID_ELEMENT_TYPE_FREE_SPACE,
+  GDU_VOLUME_GRID_ELEMENT_TYPE_DEVICE
+} GduVolumeGridElementType;
+
+G_END_DECLS
+
+#endif /* __GDU_ENUMS_H__ */
diff --git a/src/palimpsest/gdutypes.h b/src/palimpsest/gdutypes.h
index 0ea5646..dd70775 100644
--- a/src/palimpsest/gdutypes.h
+++ b/src/palimpsest/gdutypes.h
@@ -27,6 +27,8 @@
 #define UDISKS_API_IS_SUBJECT_TO_CHANGE
 #include <udisks/udisks.h>
 
+#include "gduenums.h"
+
 G_BEGIN_DECLS
 
 struct _GduApplication;
@@ -38,6 +40,9 @@ typedef struct _GduDeviceTreeModel GduDeviceTreeModel;
 struct _GduWindow;
 typedef struct _GduWindow GduWindow;
 
+struct _GduVolumeGrid;
+typedef struct _GduVolumeGrid GduVolumeGrid;
+
 G_END_DECLS
 
 #endif /* __GDU_TYPES_H__ */
diff --git a/src/palimpsest/gduvolumegrid.c b/src/palimpsest/gduvolumegrid.c
new file mode 100644
index 0000000..e6a2954
--- /dev/null
+++ b/src/palimpsest/gduvolumegrid.c
@@ -0,0 +1,1839 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2008-2011 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#include <math.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdkx.h>
+#include <stdlib.h>
+
+#include "gduvolumegrid.h"
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+#define ELEMENT_MINIMUM_WIDTH 60
+
+typedef enum
+{
+  GRID_EDGE_NONE    = 0,
+  GRID_EDGE_TOP    = (1<<0),
+  GRID_EDGE_BOTTOM = (1<<1),
+  GRID_EDGE_LEFT   = (1<<2),
+  GRID_EDGE_RIGHT  = (1<<3)
+} GridEdgeFlags;
+
+typedef struct GridElement GridElement;
+
+struct GridElement
+{
+  GduVolumeGridElementType type;
+
+  /* these values are set in recompute_grid() */
+  gdouble size_ratio;
+  GDBusObjectProxy *object_proxy;
+  guint64 offset;
+  guint64 size;
+
+  GList *embedded_elements;
+  GridElement *parent;
+  GridElement *prev;
+  GridElement *next;
+
+  /* these values are set in recompute_size() */
+  guint x;
+  guint y;
+  guint width;
+  guint height;
+  GridEdgeFlags edge_flags;
+
+  gchar *text;
+
+  /* used for the job spinner */
+  guint spinner_current;
+};
+
+static void
+grid_element_free (GridElement *element)
+{
+  if (element->object_proxy != NULL)
+    g_object_unref (element->object_proxy);
+  g_free (element->text);
+  g_list_foreach (element->embedded_elements, (GFunc) grid_element_free, NULL);
+  g_list_free (element->embedded_elements);
+
+  g_free (element);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct _GduVolumeGridClass GduVolumeGridClass;
+struct _GduVolumeGrid
+{
+  GtkDrawingArea parent;
+
+  UDisksClient *client;
+  GDBusObjectProxy *block_device;
+
+  GList *elements;
+
+  GridElement *selected;
+  GridElement *focused;
+
+  guint animation_timeout_id;
+};
+
+struct _GduVolumeGridClass
+{
+  GtkDrawingAreaClass parent_class;
+
+  /* signals */
+  void (*changed) (GduVolumeGrid *grid);
+};
+
+enum
+{
+  PROP_0,
+  PROP_CLIENT,
+  PROP_BLOCK_DEVICE
+};
+
+enum
+{
+  CHANGED_SIGNAL,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+static void on_object_proxy_added (GDBusProxyManager   *manager,
+                                   GDBusObjectProxy    *object_proxy,
+                                   gpointer             user_data);
+
+static void on_object_proxy_removed (GDBusProxyManager   *manager,
+                                     GDBusObjectProxy    *object_proxy,
+                                     gpointer             user_data);
+
+static void on_interface_proxy_added (GDBusProxyManager   *manager,
+                                      GDBusObjectProxy    *object_proxy,
+                                      GDBusProxy          *interface_proxy,
+                                      gpointer             user_data);
+
+static void on_interface_proxy_removed (GDBusProxyManager   *manager,
+                                        GDBusObjectProxy    *object_proxy,
+                                        GDBusProxy          *interface_proxy,
+                                        gpointer             user_data);
+
+static void on_interface_proxy_properties_changed (GDBusProxyManager   *manager,
+                                                   GDBusObjectProxy    *object_proxy,
+                                                   GDBusProxy          *interface_proxy,
+                                                   GVariant            *changed_properties,
+                                                   const gchar *const *invalidated_properties,
+                                                   gpointer            user_data);
+
+G_DEFINE_TYPE (GduVolumeGrid, gdu_volume_grid, GTK_TYPE_DRAWING_AREA)
+
+static guint get_depth (GList *elements);
+
+static guint get_num_elements_for_slice (GList *elements);
+
+static void recompute_grid (GduVolumeGrid *grid);
+
+static void recompute_size (GduVolumeGrid *grid,
+                            guint          width,
+                            guint          height);
+
+static GridElement *find_element_for_position (GduVolumeGrid *grid,
+                                               guint x,
+                                               guint y);
+
+static gboolean gdu_volume_grid_draw (GtkWidget *widget,
+                                      cairo_t   *cr);
+
+static void
+gdu_volume_grid_finalize (GObject *object)
+{
+  GduVolumeGrid *grid = GDU_VOLUME_GRID (object);
+  GDBusProxyManager *proxy_manager;
+
+  proxy_manager = udisks_client_get_proxy_manager (grid->client);
+  g_signal_handlers_disconnect_by_func (proxy_manager,
+                                        G_CALLBACK (on_object_proxy_added),
+                                        grid);
+  g_signal_handlers_disconnect_by_func (proxy_manager,
+                                        G_CALLBACK (on_object_proxy_removed),
+                                        grid);
+  g_signal_handlers_disconnect_by_func (proxy_manager,
+                                        G_CALLBACK (on_interface_proxy_added),
+                                        grid);
+  g_signal_handlers_disconnect_by_func (proxy_manager,
+                                        G_CALLBACK (on_interface_proxy_removed),
+                                        grid);
+  g_signal_handlers_disconnect_by_func (proxy_manager,
+                                        G_CALLBACK (on_interface_proxy_properties_changed),
+                                        grid);
+
+  g_list_foreach (grid->elements, (GFunc) grid_element_free, NULL);
+  g_list_free (grid->elements);
+
+  if (grid->animation_timeout_id > 0)
+    {
+      g_source_remove (grid->animation_timeout_id);
+      grid->animation_timeout_id = 0;
+    }
+
+  if (grid->block_device != NULL)
+    g_object_unref (grid->block_device);
+  g_object_unref (grid->client);
+
+  G_OBJECT_CLASS (gdu_volume_grid_parent_class)->finalize (object);
+}
+
+static void
+gdu_volume_grid_get_property (GObject    *object,
+                              guint       property_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
+{
+  GduVolumeGrid *grid = GDU_VOLUME_GRID (object);
+
+  switch (property_id)
+    {
+    case PROP_CLIENT:
+      g_value_set_object (value, grid->client);
+      break;
+
+    case PROP_BLOCK_DEVICE:
+      g_value_set_object (value, grid->block_device);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gdu_volume_grid_set_property (GObject      *object,
+                              guint         property_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
+{
+  GduVolumeGrid *grid = GDU_VOLUME_GRID (object);
+
+  switch (property_id)
+    {
+    case PROP_CLIENT:
+      grid->client = g_value_dup_object (value);
+      break;
+
+    case PROP_BLOCK_DEVICE:
+      gdu_volume_grid_set_block_device (grid, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gdu_volume_grid_constructed (GObject *object)
+{
+  GduVolumeGrid *grid = GDU_VOLUME_GRID (object);
+  GDBusProxyManager *proxy_manager;
+
+  proxy_manager = udisks_client_get_proxy_manager (grid->client);
+  g_signal_connect (proxy_manager,
+                    "object-proxy-added",
+                    G_CALLBACK (on_object_proxy_added),
+                    grid);
+  g_signal_connect (proxy_manager,
+                    "object-proxy-removed",
+                    G_CALLBACK (on_object_proxy_removed),
+                    grid);
+  g_signal_connect (proxy_manager,
+                    "interface-proxy-added",
+                    G_CALLBACK (on_interface_proxy_added),
+                    grid);
+  g_signal_connect (proxy_manager,
+                    "interface-proxy-removed",
+                    G_CALLBACK (on_interface_proxy_removed),
+                    grid);
+  g_signal_connect (proxy_manager,
+                    "interface-proxy-properties-changed",
+                    G_CALLBACK (on_interface_proxy_properties_changed),
+                    grid);
+
+  recompute_grid (grid);
+
+  /* select the first element */
+  if (grid->elements != NULL)
+    {
+      GridElement *element = grid->elements->data;
+      grid->selected = element;
+      grid->focused = element;
+    }
+
+  if (G_OBJECT_CLASS (gdu_volume_grid_parent_class)->constructed != NULL)
+    G_OBJECT_CLASS (gdu_volume_grid_parent_class)->constructed (object);
+}
+
+static gboolean
+gdu_volume_grid_key_press_event (GtkWidget      *widget,
+                                 GdkEventKey    *event)
+{
+  GduVolumeGrid *grid = GDU_VOLUME_GRID (widget);
+  gboolean handled;
+  GridElement *target;
+
+  handled = FALSE;
+
+  if (event->type != GDK_KEY_PRESS)
+    goto out;
+
+  switch (event->keyval) {
+  case GDK_KEY_Left:
+  case GDK_KEY_Right:
+  case GDK_KEY_Up:
+  case GDK_KEY_Down:
+    target = NULL;
+
+    if (grid->focused == NULL)
+      {
+        g_warning ("TODO: handle nothing being selected/focused");
+      }
+    else
+      {
+        GridElement *element;
+
+        element = grid->focused;
+        if (element != NULL)
+          {
+            if (event->keyval == GDK_KEY_Left)
+              {
+                if (element->prev != NULL)
+                  {
+                    target = element->prev;
+                  }
+                else
+                  {
+                    if (element->parent && element->parent->prev != NULL)
+                      target = element->parent->prev;
+                  }
+              }
+            else if (event->keyval == GDK_KEY_Right)
+              {
+                if (element->next != NULL)
+                  {
+                    target = element->next;
+                  }
+                else
+                  {
+                    if (element->parent && element->parent->next != NULL)
+                      target = element->parent->next;
+                  }
+              }
+            else if (event->keyval == GDK_KEY_Up)
+              {
+                if (element->parent != NULL)
+                  {
+                    target = element->parent;
+                  }
+              }
+            else if (event->keyval == GDK_KEY_Down)
+              {
+                if (element->embedded_elements != NULL)
+                  {
+                    target = (GridElement *) element->embedded_elements->data;
+                  }
+              }
+          }
+      }
+
+    if (target != NULL)
+      {
+        if ((event->state & GDK_CONTROL_MASK) != 0)
+          {
+            grid->focused = target;
+          }
+        else
+          {
+            grid->selected = target;
+            grid->focused = target;
+            g_signal_emit (grid,
+                           signals[CHANGED_SIGNAL],
+                           0);
+          }
+        gtk_widget_queue_draw (GTK_WIDGET (grid));
+      }
+    handled = TRUE;
+    break;
+
+  case GDK_KEY_Return:
+  case GDK_KEY_space:
+    if (grid->focused != grid->selected &&
+        grid->focused != NULL)
+      {
+        grid->selected = grid->focused;
+        g_signal_emit (grid,
+                       signals[CHANGED_SIGNAL],
+                       0);
+        gtk_widget_queue_draw (GTK_WIDGET (grid));
+      }
+    handled = TRUE;
+    break;
+
+  default:
+    break;
+  }
+
+ out:
+  return handled;
+}
+
+static gboolean
+gdu_volume_grid_button_press_event (GtkWidget      *widget,
+                                    GdkEventButton *event)
+{
+  GduVolumeGrid *grid = GDU_VOLUME_GRID (widget);
+  gboolean handled;
+
+  handled = FALSE;
+
+  if (event->type != GDK_BUTTON_PRESS)
+    goto out;
+
+  if (event->button == 1)
+    {
+      GridElement *element;
+
+      element = find_element_for_position (grid, event->x, event->y);
+      if (element != NULL)
+        {
+          grid->selected = element;
+          grid->focused = element;
+          g_signal_emit (grid,
+                         signals[CHANGED_SIGNAL],
+                         0);
+          gtk_widget_grab_focus (GTK_WIDGET (grid));
+          gtk_widget_queue_draw (GTK_WIDGET (grid));
+        }
+      handled = TRUE;
+    }
+
+ out:
+  return handled;
+}
+
+static void
+gdu_volume_grid_realize (GtkWidget *widget)
+{
+  GduVolumeGrid *grid = GDU_VOLUME_GRID (widget);
+  GdkWindow *window;
+  GdkWindowAttr attributes;
+  gint attributes_mask;
+  GtkAllocation allocation;
+  GtkStyleContext *context;
+
+  gtk_widget_set_realized (widget, TRUE);
+  gtk_widget_get_allocation (widget, &allocation);
+
+  attributes.x = allocation.x;
+  attributes.y = allocation.y;
+  attributes.width = allocation.width;
+  attributes.height = allocation.height;
+  attributes.wclass = GDK_INPUT_OUTPUT;
+  attributes.window_type = GDK_WINDOW_CHILD;
+  attributes.event_mask = gtk_widget_get_events (widget) |
+    GDK_KEY_PRESS_MASK |
+    GDK_EXPOSURE_MASK |
+    GDK_BUTTON_PRESS_MASK |
+    GDK_BUTTON_RELEASE_MASK |
+    GDK_ENTER_NOTIFY_MASK |
+    GDK_LEAVE_NOTIFY_MASK;
+  attributes.visual = gtk_widget_get_visual (widget);
+
+  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
+
+  window = gtk_widget_get_parent_window (widget);
+  gtk_widget_set_window (widget, window);
+  g_object_ref (window);
+
+  window = gdk_window_new (gtk_widget_get_parent_window (widget),
+                           &attributes,
+                           attributes_mask);
+  gtk_widget_set_window (widget, window);
+  gdk_window_set_user_data (window, grid);
+
+  context = gtk_widget_get_style_context (widget);
+  gtk_style_context_set_background (context, window);
+}
+
+static guint
+get_num_elements_for_slice (GList *elements)
+{
+  GList *l;
+  guint num_elements;
+
+  num_elements = 0;
+  for (l = elements; l != NULL; l = l->next)
+    {
+      GridElement *element = l->data;
+      num_elements += get_num_elements_for_slice (element->embedded_elements);
+    }
+
+  if (num_elements > 0)
+    return num_elements;
+  else
+    return 1;
+}
+
+static void
+gdu_volume_grid_get_preferred_width (GtkWidget *widget,
+                                     gint      *minimal_width,
+                                     gint      *natural_width)
+{
+  GduVolumeGrid *grid = GDU_VOLUME_GRID (widget);
+  guint num_elements;
+  gint width;
+
+  num_elements = get_num_elements_for_slice (grid->elements);
+  width = num_elements * ELEMENT_MINIMUM_WIDTH;
+
+  *minimal_width = *natural_width = width;
+}
+
+static void
+gdu_volume_grid_get_preferred_height (GtkWidget *widget,
+                                      gint      *minimal_height,
+                                      gint      *natural_height)
+{
+  *minimal_height = *natural_height = 100;
+}
+
+static void
+gdu_volume_grid_class_init (GduVolumeGridClass *klass)
+{
+  GObjectClass *gobject_class;
+  GtkWidgetClass *gtkwidget_class;
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->get_property = gdu_volume_grid_get_property;
+  gobject_class->set_property = gdu_volume_grid_set_property;
+  gobject_class->constructed  = gdu_volume_grid_constructed;
+  gobject_class->finalize     = gdu_volume_grid_finalize;
+
+  gtkwidget_class = GTK_WIDGET_CLASS (klass);
+  gtkwidget_class->realize              = gdu_volume_grid_realize;
+  gtkwidget_class->key_press_event      = gdu_volume_grid_key_press_event;
+  gtkwidget_class->button_press_event   = gdu_volume_grid_button_press_event;
+  gtkwidget_class->get_preferred_width  = gdu_volume_grid_get_preferred_width;
+  gtkwidget_class->get_preferred_height = gdu_volume_grid_get_preferred_height;
+  gtkwidget_class->draw                 = gdu_volume_grid_draw;
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_CLIENT,
+                                   g_param_spec_object ("client",
+                                                        "Client",
+                                                        "The UDisksClient to use",
+                                                        UDISKS_TYPE_CLIENT,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_BLOCK_DEVICE,
+                                   g_param_spec_object ("block-device",
+                                                        "Block Device",
+                                                        "The top-level block device to show a grid for",
+                                                        G_TYPE_DBUS_OBJECT_PROXY,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_STATIC_STRINGS));
+
+  signals[CHANGED_SIGNAL] = g_signal_new ("changed",
+                                          GDU_TYPE_VOLUME_GRID,
+                                          G_SIGNAL_RUN_LAST,
+                                          G_STRUCT_OFFSET (GduVolumeGridClass, changed),
+                                          NULL,
+                                          NULL,
+                                          g_cclosure_marshal_VOID__VOID,
+                                          G_TYPE_NONE,
+                                          0);
+}
+
+static void
+gdu_volume_grid_init (GduVolumeGrid *grid)
+{
+  gtk_widget_set_can_focus (GTK_WIDGET (grid), TRUE);
+}
+
+GtkWidget *
+gdu_volume_grid_new (UDisksClient *client)
+{
+  g_return_val_if_fail (UDISKS_IS_CLIENT (client), NULL);
+  return GTK_WIDGET (g_object_new (GDU_TYPE_VOLUME_GRID,
+                                   "client", client,
+                                   NULL));
+}
+
+void
+gdu_volume_grid_set_block_device (GduVolumeGrid     *grid,
+                                  GDBusObjectProxy  *block_device)
+{
+  g_return_if_fail (GDU_IS_VOLUME_GRID (grid));
+
+  if (block_device == grid->block_device)
+    goto out;
+
+  if (grid->block_device != NULL)
+    g_object_unref (grid->block_device);
+  grid->block_device = block_device != NULL ? g_object_ref (block_device) : NULL;
+
+  recompute_grid (grid);
+
+  /* select the first element */
+  if (grid->elements != NULL)
+    {
+      GridElement *element = grid->elements->data;
+      grid->selected = element;
+      grid->focused = element;
+    }
+
+  g_object_notify (G_OBJECT (grid), "block-device");
+ out:
+  ;
+}
+
+
+static guint
+get_depth (GList *elements)
+{
+  guint depth;
+  GList *l;
+
+  depth = 0;
+  if (elements == NULL)
+    goto out;
+
+  for (l = elements; l != NULL; l = l->next)
+    {
+      GridElement *ee = l->data;
+      guint ee_depth;
+
+      ee_depth = get_depth (ee->embedded_elements) + 1;
+      if (ee_depth > depth)
+        depth = ee_depth;
+    }
+
+ out:
+  return depth;
+}
+
+static void
+recompute_size_for_slice (GList          *elements,
+                          guint           width,
+                          guint           height,
+                          guint           total_width,
+                          guint           total_height,
+                          guint           offset_x,
+                          guint           offset_y)
+{
+  GList *l;
+  gint x;
+  gint pixels_left;
+  guint num_elements;
+
+  /* first steal all the allocated minimum width - then distribute remaining pixels
+   * based on the size_ratio and add the allocated minimum width.
+   */
+  num_elements = get_num_elements_for_slice (elements);
+  width -= num_elements * ELEMENT_MINIMUM_WIDTH;
+  g_warn_if_fail (width >= 0);
+
+  x = 0;
+  pixels_left = width;
+  for (l = elements; l != NULL; l = l->next)
+    {
+      GridElement *element = l->data;
+      gint element_width;
+      gboolean is_last;
+      guint element_depth;
+
+      is_last  = (l->next == NULL);
+
+      element_depth = get_depth (element->embedded_elements);
+      //g_debug ("element_depth = %d (x,y)=(%d,%d) height=%d", element_depth, offset_x, offset_y, height);
+
+      if (is_last)
+        {
+          element_width = pixels_left;
+          pixels_left = 0;
+        }
+      else
+        {
+          element_width = element->size_ratio * width;
+          if (element_width > pixels_left)
+            element_width = pixels_left;
+          pixels_left -= element_width;
+        }
+
+      num_elements = get_num_elements_for_slice (element->embedded_elements);
+      element_width += num_elements * ELEMENT_MINIMUM_WIDTH;
+
+      element->x = x + offset_x;
+      element->y = offset_y;
+      element->width = element_width;
+      if (element_depth > 0)
+        {
+          element->height = height / (element_depth + 1);
+        }
+      else
+        {
+          element->height = height;
+        }
+
+      if (element->x == 0)
+        element->edge_flags |= GRID_EDGE_LEFT;
+      if (element->y == 0)
+        element->edge_flags |= GRID_EDGE_TOP;
+      if (element->x + element->width == total_width)
+        element->edge_flags |= GRID_EDGE_RIGHT;
+      if (element->y + element->height == total_height)
+        element->edge_flags |= GRID_EDGE_BOTTOM;
+
+      x += element_width;
+
+      recompute_size_for_slice (element->embedded_elements,
+                                element->width,
+                                height - element->height,
+                                total_width,
+                                total_height,
+                                element->x,
+                                element->height + element->y);
+    }
+}
+
+static void
+recompute_size (GduVolumeGrid *grid,
+                guint          width,
+                guint          height)
+{
+  recompute_size_for_slice (grid->elements,
+                            width,
+                            height,
+                            width,
+                            height,
+                            0,
+                            0);
+}
+
+static void
+render_spinner (cairo_t   *cr,
+                guint      size,
+                guint      num_lines,
+                guint      current,
+                gdouble    x,
+                gdouble    y)
+{
+  guint n;
+  gdouble radius;
+  gdouble cx;
+  gdouble cy;
+  gdouble half;
+
+  cx = x + size/2.0;
+  cy = y + size/2.0;
+  radius = size/2.0;
+  half = num_lines / 2;
+
+  current = current % num_lines;
+
+  for (n = 0; n < num_lines; n++)
+    {
+      gdouble inset;
+      gdouble t;
+
+      inset = 0.7 * radius;
+
+      /* transparency is a function of time and intial value */
+      t = (gdouble) ((n + num_lines - current) % num_lines) / num_lines;
+
+      cairo_set_source_rgba (cr, 0, 0, 0, t);
+      cairo_set_line_width (cr, 2.0);
+      cairo_move_to (cr,
+                     cx + (radius - inset) * cos (n * M_PI / half),
+                     cy + (radius - inset) * sin (n * M_PI / half));
+      cairo_line_to (cr,
+                     cx + radius * cos (n * M_PI / half),
+                     cy + radius * sin (n * M_PI / half));
+      cairo_stroke (cr);
+    }
+}
+
+static void
+render_pixbuf (cairo_t   *cr,
+               gdouble    x,
+               gdouble    y,
+               GdkPixbuf *pixbuf)
+{
+  gdk_cairo_set_source_pixbuf (cr, pixbuf, x, y);
+  cairo_rectangle (cr,
+                   x,
+                   y,
+                   gdk_pixbuf_get_width (pixbuf),
+                   gdk_pixbuf_get_height (pixbuf));
+  cairo_fill (cr);
+}
+
+static void
+round_rect (cairo_t *cr,
+            gdouble x, gdouble y,
+            gdouble w, gdouble h,
+            gdouble r,
+            GridEdgeFlags edge_flags)
+{
+  gboolean top_left_round;
+  gboolean top_right_round;
+  gboolean bottom_right_round;
+  gboolean bottom_left_round;
+
+  top_left_round     = ((edge_flags & GRID_EDGE_TOP)    && (edge_flags & GRID_EDGE_LEFT));
+  top_right_round    = ((edge_flags & GRID_EDGE_TOP)    && (edge_flags & GRID_EDGE_RIGHT));
+  bottom_right_round = ((edge_flags & GRID_EDGE_BOTTOM) && (edge_flags & GRID_EDGE_RIGHT));
+  bottom_left_round  = ((edge_flags & GRID_EDGE_BOTTOM) && (edge_flags & GRID_EDGE_LEFT));
+
+  if (top_left_round)
+    {
+      cairo_move_to  (cr,
+                      x + r, y);
+    }
+  else
+    {
+      cairo_move_to  (cr,
+                      x, y);
+    }
+
+  if (top_right_round)
+    {
+      cairo_line_to  (cr,
+                      x + w - r, y);
+      cairo_curve_to (cr,
+                      x + w, y,
+                      x + w, y,
+                      x + w, y + r);
+    }
+  else
+    {
+      cairo_line_to  (cr,
+                      x + w, y);
+    }
+
+  if (bottom_right_round)
+    {
+      cairo_line_to  (cr,
+                      x + w, y + h - r);
+      cairo_curve_to (cr,
+                      x + w, y + h,
+                      x + w, y + h,
+                      x + w - r, y + h);
+    }
+  else
+    {
+      cairo_line_to  (cr,
+                      x + w, y + h);
+    }
+
+  if (bottom_left_round)
+    {
+      cairo_line_to  (cr,
+                      x + r, y + h);
+      cairo_curve_to (cr,
+                      x, y + h,
+                      x, y + h,
+                      x, y + h - r);
+    }
+  else
+    {
+      cairo_line_to  (cr,
+                      x, y + h);
+    }
+
+  if (top_left_round)
+    {
+      cairo_line_to  (cr,
+                      x, y + r);
+      cairo_curve_to (cr,
+                      x, y,
+                      x, y,
+                      x + r, y);
+    }
+  else
+    {
+      cairo_line_to  (cr,
+                      x, y);
+    }
+}
+
+/* returns true if an animation timeout is needed */
+static gboolean
+render_element (GduVolumeGrid *grid,
+                cairo_t       *cr,
+                GridElement   *element,
+                gboolean       is_selected,
+                gboolean       is_focused,
+                gboolean       is_grid_focused)
+{
+  gboolean need_animation_timeout;
+  gdouble fill_red;
+  gdouble fill_green;
+  gdouble fill_blue;
+  gdouble fill_selected_red;
+  gdouble fill_selected_green;
+  gdouble fill_selected_blue;
+  gdouble fill_selected_not_focused_red;
+  gdouble fill_selected_not_focused_green;
+  gdouble fill_selected_not_focused_blue;
+  gdouble focus_rect_red;
+  gdouble focus_rect_green;
+  gdouble focus_rect_blue;
+  gdouble focus_rect_selected_red;
+  gdouble focus_rect_selected_green;
+  gdouble focus_rect_selected_blue;
+  gdouble focus_rect_selected_not_focused_red;
+  gdouble focus_rect_selected_not_focused_green;
+  gdouble focus_rect_selected_not_focused_blue;
+  gdouble stroke_red;
+  gdouble stroke_green;
+  gdouble stroke_blue;
+  gdouble stroke_selected_red;
+  gdouble stroke_selected_green;
+  gdouble stroke_selected_blue;
+  gdouble stroke_selected_not_focused_red;
+  gdouble stroke_selected_not_focused_green;
+  gdouble stroke_selected_not_focused_blue;
+  gdouble text_red;
+  gdouble text_green;
+  gdouble text_blue;
+  gdouble text_selected_red;
+  gdouble text_selected_green;
+  gdouble text_selected_blue;
+  gdouble text_selected_not_focused_red;
+  gdouble text_selected_not_focused_green;
+  gdouble text_selected_not_focused_blue;
+  PangoLayout *layout;
+  PangoFontDescription *desc;
+  gint width, height;
+
+  need_animation_timeout = FALSE;
+
+  /* TODO: use GtkStyleContext and/or CSS etc. instead of hard-coding colors */
+  fill_red     = 1;
+  fill_green   = 1;
+  fill_blue    = 1;
+  fill_selected_red     = 0.40;
+  fill_selected_green   = 0.60;
+  fill_selected_blue    = 0.80;
+  fill_selected_not_focused_red     = 0.60;
+  fill_selected_not_focused_green   = 0.60;
+  fill_selected_not_focused_blue    = 0.60;
+  focus_rect_red     = 0.75;
+  focus_rect_green   = 0.75;
+  focus_rect_blue    = 0.75;
+  focus_rect_selected_red     = 0.70;
+  focus_rect_selected_green   = 0.70;
+  focus_rect_selected_blue    = 0.80;
+  focus_rect_selected_not_focused_red     = 0.70;
+  focus_rect_selected_not_focused_green   = 0.70;
+  focus_rect_selected_not_focused_blue    = 0.70;
+  stroke_red   = 0.75;
+  stroke_green = 0.75;
+  stroke_blue  = 0.75;
+  stroke_selected_red   = 0.3;
+  stroke_selected_green = 0.45;
+  stroke_selected_blue  = 0.6;
+  stroke_selected_not_focused_red   = 0.45;
+  stroke_selected_not_focused_green = 0.45;
+  stroke_selected_not_focused_blue  = 0.45;
+  text_red     = 0;
+  text_green   = 0;
+  text_blue    = 0;
+  text_selected_red     = 1;
+  text_selected_green   = 1;
+  text_selected_blue    = 1;
+  text_selected_not_focused_red     = 1;
+  text_selected_not_focused_green   = 1;
+  text_selected_not_focused_blue    = 1;
+
+#if 0
+  g_debug ("rendering element: x=%d w=%d",
+           element->x,
+           element->width);
+#endif
+
+  cairo_save (cr);
+  cairo_rectangle (cr,
+                   element->x + 0.5,
+                   element->y + 0.5,
+                   element->width,
+                   element->height);
+  cairo_clip (cr);
+
+  round_rect (cr,
+              element->x + 0.5,
+              element->y + 0.5,
+              element->width,
+              element->height,
+              10,
+              element->edge_flags);
+
+  if (is_selected)
+    {
+      cairo_pattern_t *gradient;
+      gradient = cairo_pattern_create_radial (element->x + element->width / 2,
+                                              element->y + element->height / 2,
+                                              0.0,
+                                              element->x + element->width / 2,
+                                              element->y + element->height / 2,
+                                              element->width/2.0);
+      if (is_grid_focused)
+        {
+          cairo_pattern_add_color_stop_rgb (gradient,
+                                            0.0,
+                                            1.0 * fill_selected_red,
+                                            1.0 * fill_selected_green,
+                                            1.0 * fill_selected_blue);
+          cairo_pattern_add_color_stop_rgb (gradient,
+                                            1.0,
+                                            0.8 * fill_selected_red,
+                                            0.8 * fill_selected_green,
+                                            0.8 * fill_selected_blue);
+        }
+      else
+        {
+          cairo_pattern_add_color_stop_rgb (gradient,
+                                            0.0,
+                                            1.0 * fill_selected_not_focused_red,
+                                            1.0 * fill_selected_not_focused_green,
+                                            1.0 * fill_selected_not_focused_blue);
+          cairo_pattern_add_color_stop_rgb (gradient,
+                                            1.0,
+                                            0.8 * fill_selected_not_focused_red,
+                                            0.8 * fill_selected_not_focused_green,
+                                            0.8 * fill_selected_not_focused_blue);
+        }
+      cairo_set_source (cr, gradient);
+      cairo_pattern_destroy (gradient);
+    }
+  else
+    {
+      cairo_set_source_rgb (cr,
+                            fill_red,
+                            fill_green,
+                            fill_blue);
+    }
+  cairo_fill_preserve (cr);
+  if (is_selected)
+    {
+      if (is_grid_focused)
+        {
+          cairo_set_source_rgb (cr,
+                                stroke_selected_red,
+                                stroke_selected_green,
+                                stroke_selected_blue);
+        }
+      else
+        {
+          cairo_set_source_rgb (cr,
+                                stroke_selected_not_focused_red,
+                                stroke_selected_not_focused_green,
+                                stroke_selected_not_focused_blue);
+        }
+    }
+  else
+    {
+      cairo_set_source_rgb (cr,
+                            stroke_red,
+                            stroke_green,
+                            stroke_blue);
+    }
+  cairo_set_dash (cr, NULL, 0, 0.0);
+  cairo_set_line_width (cr, 1.0);
+  cairo_stroke (cr);
+
+  /* focus indicator */
+  if (is_focused && is_grid_focused)
+    {
+      gdouble dashes[] = {2.0};
+      round_rect (cr,
+                  element->x + 0.5 + 3,
+                  element->y + 0.5 + 3,
+                  element->width - 3 * 2,
+                  element->height - 3 * 2,
+                  20,
+                  element->edge_flags);
+      cairo_set_source_rgb (cr, focus_rect_red, focus_rect_green, focus_rect_blue);
+      cairo_set_dash (cr, dashes, 1, 0.0);
+      cairo_set_line_width (cr, 1.0);
+      cairo_stroke (cr);
+    }
+
+  if (is_selected)
+    {
+      if (is_grid_focused)
+        {
+          cairo_set_source_rgb (cr,
+                                text_selected_red,
+                                text_selected_green,
+                                text_selected_blue);
+        }
+      else
+        {
+          cairo_set_source_rgb (cr,
+                                text_selected_not_focused_red,
+                                text_selected_not_focused_green,
+                                text_selected_not_focused_blue);
+        }
+    }
+  else
+    {
+      cairo_set_source_rgb (cr, text_red, text_green, text_blue);
+    }
+  layout = pango_cairo_create_layout (cr);
+  pango_layout_set_text (layout, element->text != NULL ? element->text : "", -1);
+  desc = pango_font_description_from_string ("Sans 7.0");
+  pango_layout_set_font_description (layout, desc);
+  pango_font_description_free (desc);
+  pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
+  pango_layout_set_width (layout, pango_units_from_double (element->width));
+  pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END);
+  pango_layout_get_size (layout, &width, &height);
+  cairo_move_to (cr,
+                 ceil(element->x),
+                 ceil (element->y + element->height / 2 - pango_units_to_double (height) / 2));
+  pango_cairo_show_layout (cr, layout);
+  g_object_unref (layout);
+
+  gint icon_offset;
+  gboolean render_padlock_closed;
+  gboolean render_padlock_open;
+  gboolean render_job_in_progress;
+  GPtrArray *pixbufs_to_render;
+  guint n;
+
+  icon_offset = 0;
+  render_padlock_closed = FALSE;
+  render_padlock_open = FALSE;
+  render_job_in_progress = FALSE;
+
+  if (render_job_in_progress)
+    {
+      render_spinner (cr,
+                      16,
+                      12,
+                      element->spinner_current,
+                      ceil (element->x + element->width - 16 - icon_offset - 4),
+                      ceil (element->y + element->height - 16 - 4));
+
+      icon_offset += 16 + 2; /* padding */
+
+      element->spinner_current += 1;
+
+      need_animation_timeout = TRUE;
+    }
+
+  /* icons */
+  pixbufs_to_render = g_ptr_array_new_with_free_func (g_object_unref);
+  if (render_padlock_open)
+    g_ptr_array_add (pixbufs_to_render,
+                     gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+                                               "gdu-encrypted-unlock",
+                                               16, 0, NULL));
+  if (render_padlock_closed)
+    g_ptr_array_add (pixbufs_to_render,
+                     gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+                                               "gdu-encrypted-lock",
+                                               16, 0, NULL));
+  for (n = 0; n < pixbufs_to_render->len; n++)
+    {
+      GdkPixbuf *pixbuf = GDK_PIXBUF (pixbufs_to_render->pdata[n]);
+      guint icon_width;
+      guint icon_height;
+
+      icon_width = gdk_pixbuf_get_width (pixbuf);
+      icon_height = gdk_pixbuf_get_height (pixbuf);
+
+      render_pixbuf (cr,
+                     ceil (element->x + element->width - icon_width - icon_offset - 4),
+                     ceil (element->y + element->height - icon_height - 4),
+                     pixbuf);
+
+      icon_offset += icon_width + 2; /* padding */
+    }
+  g_ptr_array_free (pixbufs_to_render, TRUE);
+
+  cairo_restore (cr);
+
+  return need_animation_timeout;
+}
+
+static gboolean
+on_animation_timeout (gpointer data)
+{
+  GduVolumeGrid *grid = GDU_VOLUME_GRID (data);
+
+  gtk_widget_queue_draw (GTK_WIDGET (grid));
+
+  return TRUE; /* keep timeout around */
+}
+
+static gboolean
+render_slice (GduVolumeGrid *grid,
+              cairo_t       *cr,
+              GList         *elements)
+{
+  GList *l;
+  gboolean need_animation_timeout;
+
+  need_animation_timeout = FALSE;
+  for (l = elements; l != NULL; l = l->next)
+    {
+      GridElement *element = l->data;
+      gboolean is_selected;
+      gboolean is_focused;
+      gboolean is_grid_focused;
+
+      is_selected = FALSE;
+      is_focused = FALSE;
+      is_grid_focused = gtk_widget_has_focus (GTK_WIDGET (grid));
+
+      if (element == grid->selected)
+        is_selected = TRUE;
+
+      if (element == grid->focused)
+        {
+          if (grid->focused != grid->selected && is_grid_focused)
+            is_focused = TRUE;
+        }
+
+      need_animation_timeout |= render_element (grid,
+                                                cr,
+                                                element,
+                                                is_selected,
+                                                is_focused,
+                                                is_grid_focused);
+
+      need_animation_timeout |= render_slice (grid,
+                                              cr,
+                                              element->embedded_elements);
+    }
+
+  return need_animation_timeout;
+}
+
+static gboolean
+gdu_volume_grid_draw (GtkWidget *widget,
+                      cairo_t   *cr)
+{
+  GduVolumeGrid *grid = GDU_VOLUME_GRID (widget);
+  GtkAllocation allocation;
+  gdouble width;
+  gdouble height;
+  gboolean need_animation_timeout;
+
+  gtk_widget_get_allocation (widget, &allocation);
+  width = allocation.width;
+  height = allocation.height;
+
+  recompute_size (grid,
+                  width - 1,
+                  height -1);
+
+  need_animation_timeout = render_slice (grid, cr, grid->elements);
+
+  if (need_animation_timeout)
+    {
+      if (grid->animation_timeout_id == 0)
+        {
+          grid->animation_timeout_id = g_timeout_add (80,
+                                                      on_animation_timeout,
+                                                      grid);
+        }
+    }
+  else
+    {
+      if (grid->animation_timeout_id > 0)
+        {
+          g_source_remove (grid->animation_timeout_id);
+          grid->animation_timeout_id = 0;
+        }
+    }
+
+  return FALSE;
+}
+
+static GridElement *
+do_find_element_for_position (GList *elements,
+                              guint  x,
+                              guint  y)
+{
+  GList *l;
+  GridElement *ret;
+
+  ret = NULL;
+
+  for (l = elements; l != NULL; l = l->next)
+    {
+      GridElement *e = l->data;
+
+      if ((x >= e->x) &&
+          (x  < e->x + e->width) &&
+          (y >= e->y) &&
+          (y  < e->y + e->height))
+        {
+          ret = e;
+          goto out;
+        }
+
+      ret = do_find_element_for_position (e->embedded_elements, x, y);
+      if (ret != NULL)
+        goto out;
+    }
+
+ out:
+  return ret;
+}
+
+static GridElement *
+find_element_for_position (GduVolumeGrid *grid,
+                           guint x,
+                           guint y)
+{
+  return do_find_element_for_position (grid->elements, x, y);
+}
+
+static GridElement *
+do_find_element_for_offset_and_object_proxy (GList *elements,
+                                             guint64 offset,
+                                             GDBusObjectProxy *object_proxy)
+{
+  GList *l;
+  GridElement *ret;
+
+  ret = NULL;
+
+  for (l = elements; l != NULL; l = l->next)
+    {
+      GridElement *e = l->data;
+
+      if (e->offset == offset && e->object_proxy == object_proxy)
+        {
+          ret = e;
+          goto out;
+        }
+
+      ret = do_find_element_for_offset_and_object_proxy (e->embedded_elements, offset, object_proxy);
+      if (ret != NULL)
+        goto out;
+    }
+
+ out:
+  return ret;
+}
+
+static GridElement *
+find_element_for_offset_and_object_proxy (GduVolumeGrid    *grid,
+                                          guint64           offset,
+                                          GDBusObjectProxy *object_proxy)
+{
+  return do_find_element_for_offset_and_object_proxy (grid->elements, offset, object_proxy);
+}
+
+static gint
+partition_sort_by_offset_func (GDBusObjectProxy *a,
+                               GDBusObjectProxy *b)
+{
+  guint64 oa;
+  guint64 ob;
+  oa = udisks_block_device_get_part_entry_offset (UDISKS_PEEK_BLOCK_DEVICE (a));
+  ob = udisks_block_device_get_part_entry_offset (UDISKS_PEEK_BLOCK_DEVICE (b));
+  if (oa > ob)
+    return 1;
+  else if (oa < ob)
+    return -1;
+  else
+    return 0;
+}
+
+static void grid_element_set_details (GduVolumeGrid  *grid,
+                                      GridElement    *element);
+
+static GList *
+recompute_grid_add_partitions (GduVolumeGrid    *grid,
+                               guint64           total_size,
+                               GridElement      *parent,
+                               guint64           free_space_slack,
+                               guint64           top_offset,
+                               guint64           top_size,
+                               GList            *partitions,
+                               GDBusObjectProxy *extended_partition,
+                               GList            *logical_partitions)
+{
+  guint64 prev_end;
+  GridElement *element;
+  GridElement *prev_element;
+  GList *l;
+  GList *ret;
+
+  ret = NULL;
+
+  /* Partitioned... first handle primary partitions, adding free space as needed */
+  partitions = g_list_sort (partitions, (GCompareFunc) partition_sort_by_offset_func);
+  prev_end = top_offset;
+  prev_element = NULL;
+  for (l = partitions; l != NULL; l = l->next)
+    {
+      GDBusObjectProxy *object_proxy = G_DBUS_OBJECT_PROXY (l->data);
+      UDisksBlockDevice *block;
+      guint64 begin, end, size;
+
+      block = UDISKS_PEEK_BLOCK_DEVICE (object_proxy);
+      begin = udisks_block_device_get_part_entry_offset (block);
+      size = udisks_block_device_get_part_entry_size (block);
+      end = begin + size;
+
+      if (begin - prev_end > free_space_slack)
+        {
+          element = g_new0 (GridElement, 1);
+          element->type = GDU_VOLUME_GRID_ELEMENT_TYPE_FREE_SPACE;
+          element->parent = parent;
+          element->size_ratio = ((gdouble) (begin - prev_end)) / top_size;
+          element->prev = prev_element;
+          element->offset = prev_end;
+          element->size = begin - prev_end;
+          if (prev_element != NULL)
+            prev_element->next = element;
+          ret = g_list_append (ret, element);
+          prev_element = element;
+          grid_element_set_details (grid, element);
+        }
+
+      element = g_new0 (GridElement, 1);
+      element->type = GDU_VOLUME_GRID_ELEMENT_TYPE_DEVICE;
+      element->parent = parent;
+      element->size_ratio = ((gdouble) udisks_block_device_get_part_entry_size (block)) / top_size;
+      element->object_proxy = g_object_ref (object_proxy);
+      element->offset = begin;
+      element->size = size;
+      element->prev = prev_element;
+      if (prev_element != NULL)
+        prev_element->next = element;
+      ret = g_list_append (ret, element);
+      prev_element = element;
+      prev_end = end;
+      grid_element_set_details (grid, element);
+
+      if (object_proxy == extended_partition)
+        {
+          element->embedded_elements = recompute_grid_add_partitions (grid,
+                                                                      total_size,
+                                                                      element,
+                                                                      free_space_slack,
+                                                                      udisks_block_device_get_part_entry_offset (block),
+                                                                      udisks_block_device_get_part_entry_size (block),
+                                                                      logical_partitions,
+                                                                      NULL,
+                                                                      NULL);
+        }
+    }
+  if (top_size + top_offset - prev_end > free_space_slack)
+    {
+      element = g_new0 (GridElement, 1);
+      element->type = GDU_VOLUME_GRID_ELEMENT_TYPE_FREE_SPACE;
+      element->parent = parent;
+      element->size_ratio = ((gdouble) (top_size - prev_end)) / top_size;
+      element->prev = prev_element;
+      element->offset = prev_end;
+      element->size = top_size + top_offset - prev_end;
+      if (prev_element != NULL)
+        prev_element->next = element;
+      ret = g_list_append (ret, element);
+      prev_element = element;
+      grid_element_set_details (grid, element);
+    }
+
+  return ret;
+}
+
+static void
+recompute_grid (GduVolumeGrid *grid)
+{
+  GList *partitions;
+  GList *logical_partitions;
+  GDBusObjectProxy *extended_partition;
+  GList *object_proxies;
+  GDBusProxyManager *proxy_manager;
+  GList *l;
+  const gchar *top_object_proxy_path;
+  UDisksBlockDevice *top_block;
+  guint64 top_size;
+  guint64 free_space_slack;
+  GridElement *element;
+  guint64 cur_selected_offset;
+  guint64 cur_focused_offset;
+  GDBusObjectProxy *cur_selected_object_proxy;
+  GDBusObjectProxy *cur_focused_object_proxy;
+
+  cur_selected_offset = 0;
+  cur_selected_object_proxy = NULL;
+  if (grid->selected != NULL)
+    {
+      cur_selected_offset = grid->selected->offset;
+      cur_selected_object_proxy = grid->selected->object_proxy;
+    }
+  cur_focused_offset = 0;
+  cur_focused_object_proxy = NULL;
+  if (grid->focused != NULL)
+    {
+      cur_focused_offset = grid->focused->offset;
+      cur_focused_object_proxy = grid->focused->object_proxy;
+    }
+
+  /* delete all old elements */
+  g_list_foreach (grid->elements, (GFunc) grid_element_free, NULL);
+  g_list_free (grid->elements);
+  grid->elements = NULL;
+
+#if 0
+  g_debug ("TODO: recompute grid for %s",
+           grid->block_device != NULL ?
+           g_dbus_object_proxy_get_object_path (grid->block_device) : "<nothing selected>");
+#endif
+
+  if (grid->block_device == NULL)
+    {
+      element = g_new0 (GridElement, 1);
+      element->type = GDU_VOLUME_GRID_ELEMENT_TYPE_NO_MEDIA;
+      element->size_ratio = 1.0;
+      element->offset = 0;
+      element->size = 0;
+      grid->elements = g_list_append (grid->elements, element);
+      grid_element_set_details (grid, element);
+      goto out;
+    }
+
+  top_object_proxy_path = g_dbus_object_proxy_get_object_path (grid->block_device);
+  top_block = UDISKS_PEEK_BLOCK_DEVICE (grid->block_device);
+  top_size = udisks_block_device_get_size (top_block);
+
+  /* include "Free Space" elements if there is at least this much slack between
+   * partitions (currently 1% of the disk, at least 1MB)
+   */
+  free_space_slack = MAX (top_size / 100, 1000*1000);
+
+  partitions = NULL;
+  logical_partitions = NULL;
+  extended_partition = NULL;
+  proxy_manager = udisks_client_get_proxy_manager (grid->client);
+  object_proxies = g_dbus_proxy_manager_get_all (proxy_manager);
+  for (l = object_proxies; l != NULL; l = l->next)
+    {
+      GDBusObjectProxy *object_proxy = G_DBUS_OBJECT_PROXY (l->data);
+      UDisksBlockDevice *block;
+      gboolean is_logical;
+
+      block = UDISKS_PEEK_BLOCK_DEVICE (object_proxy);
+      if (block != NULL &&
+          g_strcmp0 (udisks_block_device_get_part_entry_table (block),
+                     top_object_proxy_path) == 0)
+        {
+          is_logical = FALSE;
+          if (g_strcmp0 (udisks_block_device_get_part_entry_scheme (block), "mbr") == 0)
+            {
+              if (udisks_block_device_get_part_entry_number (block) >= 5)
+                {
+                  is_logical = TRUE;
+                }
+              else
+                {
+                  gint type;
+                  type = strtol (udisks_block_device_get_part_entry_type (block), NULL, 0);
+                  if (type == 0x05 || type == 0x0f || type == 0x85)
+                    {
+                      g_warn_if_fail (extended_partition == NULL);
+                      extended_partition = object_proxy;
+                    }
+                }
+            }
+
+          if (is_logical)
+            logical_partitions = g_list_prepend (logical_partitions, object_proxy);
+          else
+            partitions = g_list_prepend (partitions, object_proxy);
+        }
+    }
+
+  if (partitions == NULL && !udisks_block_device_get_part_table (top_block))
+    {
+      /* No partitions and whole-disk has no partition table signature... */
+      if (top_size == 0)
+        {
+          element = g_new0 (GridElement, 1);
+          element->type = GDU_VOLUME_GRID_ELEMENT_TYPE_NO_MEDIA;
+          element->size_ratio = 1.0;
+          element->offset = 0;
+          element->size = 0;
+          grid->elements = g_list_append (grid->elements, element);
+          grid_element_set_details (grid, element);
+        }
+      else
+        {
+          element = g_new0 (GridElement, 1);
+          element->type = GDU_VOLUME_GRID_ELEMENT_TYPE_DEVICE;
+          element->size_ratio = 1.0;
+          element->offset = 0;
+          element->size = top_size;
+          element->object_proxy = g_object_ref (grid->block_device);
+          grid->elements = g_list_append (grid->elements, element);
+          grid_element_set_details (grid, element);
+        }
+    }
+  else
+    {
+      grid->elements = recompute_grid_add_partitions (grid,
+                                                      top_size,
+                                                      NULL,
+                                                      free_space_slack,
+                                                      0,
+                                                      top_size,
+                                                      partitions,
+                                                      extended_partition,
+                                                      logical_partitions);
+    }
+
+  g_list_free (logical_partitions);
+  g_list_free (partitions);
+  g_list_foreach (object_proxies, (GFunc) g_object_unref, NULL);
+  g_list_free (object_proxies);
+
+ out:
+
+  /* reselect focused and selected elements */
+  grid->selected = find_element_for_offset_and_object_proxy (grid, cur_selected_offset, cur_selected_object_proxy);
+  grid->focused = find_element_for_offset_and_object_proxy (grid, cur_focused_offset, cur_focused_object_proxy);
+
+  /* ensure something is always focused/selected */
+  if (grid->selected == NULL)
+    grid->selected = grid->elements->data;
+  if (grid->focused == NULL)
+    grid->focused = grid->elements->data;
+
+  /* queue a redraw */
+  gtk_widget_queue_draw (GTK_WIDGET (grid));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+GduVolumeGridElementType
+gdu_volume_grid_get_selected_type (GduVolumeGrid *grid)
+{
+  g_return_val_if_fail (GDU_IS_VOLUME_GRID (grid), 0);
+  return grid->selected->type;
+}
+
+GDBusObjectProxy *
+gdu_volume_grid_get_selected_device (GduVolumeGrid *grid)
+{
+  g_return_val_if_fail (GDU_IS_VOLUME_GRID (grid), NULL);
+  return grid->selected->object_proxy;
+}
+
+guint64
+gdu_volume_grid_get_selected_offset (GduVolumeGrid *grid)
+{
+  g_return_val_if_fail (GDU_IS_VOLUME_GRID (grid), 0);
+  return grid->selected->offset;
+}
+
+guint64
+gdu_volume_grid_get_selected_size (GduVolumeGrid *grid)
+{
+  g_return_val_if_fail (GDU_IS_VOLUME_GRID (grid), 0);
+  return grid->selected->size;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+grid_element_set_details (GduVolumeGrid  *grid,
+                          GridElement    *element)
+{
+  switch (element->type)
+    {
+    case GDU_VOLUME_GRID_ELEMENT_TYPE_NO_MEDIA:
+      element->text = g_strdup (_("No Media"));
+      break;
+
+    case GDU_VOLUME_GRID_ELEMENT_TYPE_FREE_SPACE:
+      {
+        gchar *size_str;
+        size_str = udisks_util_get_size_for_display (element->size, FALSE, FALSE);
+        /* Translators: This is shown in the volume grid - the first %s is the amount of free space */
+        element->text = g_strdup_printf (_("%s Free Space"), size_str);
+        g_free (size_str);
+      }
+      break;
+
+    case GDU_VOLUME_GRID_ELEMENT_TYPE_DEVICE:
+      {
+        UDisksBlockDevice *block;
+        gchar *s;
+        gchar *size_str;
+        const gchar *usage;
+        const gchar *type;
+
+        size_str = udisks_util_get_size_for_display (element->size, FALSE, FALSE);
+        block = UDISKS_PEEK_BLOCK_DEVICE (element->object_proxy);
+
+        usage = udisks_block_device_get_id_usage (block);
+        type = udisks_block_device_get_id_type (block);
+
+        if (g_strcmp0 (usage, "filesystem") == 0)
+          {
+            const gchar *label;
+            label = udisks_block_device_get_id_label (block);
+            if (strlen (label) == 0)
+              label = C_("volume-grid", "Filesystem");
+            s = g_strdup_printf ("%s\n%s %s", label, size_str, type);
+          }
+        else if (g_strcmp0 (usage, "other") == 0 && g_strcmp0 (type, "swap") == 0)
+          {
+            const gchar *label;
+            label = udisks_block_device_get_id_label (block);
+            if (strlen (label) == 0)
+              s = g_strdup_printf ("%s\n%s",
+                                   C_("volume-grid", "Swap"),
+                                   size_str);
+            else
+              s = g_strdup_printf ("%s\n%s %s", label, size_str,
+                                   C_("volume-grid", "Swap"));
+          }
+        else if (g_strcmp0 (usage, "crypto") == 0)
+          {
+            s = g_strdup_printf ("%s\n%s",
+                                 C_("volume-grid", "Encrypted"),
+                                 size_str);
+            /* TODO: emblems for locked/unlocked */
+          }
+        else
+          {
+            s = g_strdup_printf (_("%s %s"), size_str,
+                                 C_("volume-grid", "Unknown"));
+          }
+        element->text = s;
+        g_free (size_str);
+      }
+      break;
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+maybe_update (GduVolumeGrid    *grid,
+              GDBusObjectProxy *object_proxy)
+{
+  UDisksBlockDevice *block;
+
+  //g_debug ("in maybe_update %s", g_dbus_object_proxy_get_object_path (object_proxy));
+
+  if (grid->block_device == NULL)
+    goto out;
+
+  block = UDISKS_PEEK_BLOCK_DEVICE (object_proxy);
+  if (block == NULL)
+    goto out;
+
+  if (object_proxy == grid->block_device ||
+      g_strcmp0 (udisks_block_device_get_part_entry_table (block),
+                 g_dbus_object_proxy_get_object_path (grid->block_device)) == 0)
+    {
+      /* object_proxy is either the block device we're a grid for or a partition of it */
+      recompute_grid (grid);
+    }
+
+ out:
+  ;
+}
+
+static void
+on_object_proxy_added (GDBusProxyManager   *manager,
+                       GDBusObjectProxy    *object_proxy,
+                       gpointer             user_data)
+{
+  GduVolumeGrid *grid = GDU_VOLUME_GRID (user_data);
+  maybe_update (grid, object_proxy);
+}
+
+static void
+on_object_proxy_removed (GDBusProxyManager   *manager,
+                         GDBusObjectProxy    *object_proxy,
+                         gpointer             user_data)
+{
+  GduVolumeGrid *grid = GDU_VOLUME_GRID (user_data);
+  maybe_update (grid, object_proxy);
+}
+
+static void
+on_interface_proxy_added (GDBusProxyManager   *manager,
+                          GDBusObjectProxy    *object_proxy,
+                          GDBusProxy          *interface_proxy,
+                          gpointer             user_data)
+{
+  GduVolumeGrid *grid = GDU_VOLUME_GRID (user_data);
+  maybe_update (grid, object_proxy);
+}
+
+static void
+on_interface_proxy_removed (GDBusProxyManager   *manager,
+                            GDBusObjectProxy    *object_proxy,
+                            GDBusProxy          *interface_proxy,
+                            gpointer             user_data)
+{
+  GduVolumeGrid *grid = GDU_VOLUME_GRID (user_data);
+  maybe_update (grid, object_proxy);
+}
+
+static void
+on_interface_proxy_properties_changed (GDBusProxyManager   *manager,
+                                       GDBusObjectProxy    *object_proxy,
+                                       GDBusProxy          *interface_proxy,
+                                       GVariant            *changed_properties,
+                                       const gchar *const *invalidated_properties,
+                                       gpointer            user_data)
+{
+  GduVolumeGrid *grid = GDU_VOLUME_GRID (user_data);
+  maybe_update (grid, object_proxy);
+}
diff --git a/src/palimpsest/gduvolumegrid.h b/src/palimpsest/gduvolumegrid.h
new file mode 100644
index 0000000..7545c1f
--- /dev/null
+++ b/src/palimpsest/gduvolumegrid.h
@@ -0,0 +1,46 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2008-2011 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __GDU_VOLUME_GRID_H__
+#define __GDU_VOLUME_GRID_H__
+
+#include <gtk/gtk.h>
+#include "gdutypes.h"
+
+G_BEGIN_DECLS
+
+#define GDU_TYPE_VOLUME_GRID         gdu_volume_grid_get_type()
+#define GDU_VOLUME_GRID(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDU_TYPE_VOLUME_GRID, GduVolumeGrid))
+#define GDU_IS_VOLUME_GRID(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDU_TYPE_VOLUME_GRID))
+
+GType                     gdu_volume_grid_get_type            (void) G_GNUC_CONST;
+GtkWidget*                gdu_volume_grid_new                 (UDisksClient        *client);
+void                      gdu_volume_grid_set_block_device    (GduVolumeGrid       *grid,
+                                                               GDBusObjectProxy    *block_device);
+GduVolumeGridElementType  gdu_volume_grid_get_selected_type   (GduVolumeGrid       *grid);
+GDBusObjectProxy         *gdu_volume_grid_get_selected_device (GduVolumeGrid       *grid);
+guint64                   gdu_volume_grid_get_selected_offset (GduVolumeGrid       *grid);
+guint64                   gdu_volume_grid_get_selected_size   (GduVolumeGrid       *grid);
+
+G_END_DECLS
+
+#endif /* __GDU_VOLUME_GRID_H__ */
diff --git a/src/palimpsest/gduwindow.c b/src/palimpsest/gduwindow.c
index 6b970f7..d7ad50d 100644
--- a/src/palimpsest/gduwindow.c
+++ b/src/palimpsest/gduwindow.c
@@ -27,6 +27,7 @@
 #include "gduwindow.h"
 #include "gdudevicetreemodel.h"
 #include "gduutils.h"
+#include "gduvolumegrid.h"
 
 /* Keep in sync with tabs in palimpsest.ui file */
 typedef enum
@@ -47,6 +48,8 @@ struct _GduWindow
 
   DetailsPage current_page;
   GDBusObjectProxy *current_object_proxy;
+
+  GtkWidget *volume_grid;
 };
 
 typedef struct
@@ -332,6 +335,11 @@ gdu_window_constructed (GObject *object)
                     "interface-proxy-properties-changed",
                     G_CALLBACK (on_interface_proxy_properties_changed),
                     window);
+
+  window->volume_grid = gdu_volume_grid_new (window->client);
+  gtk_box_pack_start (GTK_BOX (gdu_window_get_widget (window, "devtab-grid-hbox")),
+                      window->volume_grid,
+                      TRUE, TRUE, 0);
 }
 
 static void
@@ -472,6 +480,7 @@ teardown_details_page (GduWindow         *window,
     case DETAILS_PAGE_NOT_SELECTED:
       break;
     case DETAILS_PAGE_DEVICE:
+      gdu_volume_grid_set_block_device (GDU_VOLUME_GRID (window->volume_grid), NULL);
       break;
     }
 }
@@ -544,7 +553,7 @@ get_top_level_block_devices_for_lun (GduWindow   *window,
       if (g_strcmp0 (udisks_block_device_get_lun (block), lun_object_path) == 0 &&
           !udisks_block_device_get_part_entry (block))
         {
-          ret = g_list_append (ret, g_object_ref (block));
+          ret = g_list_append (ret, g_object_ref (object_proxy));
         }
       g_object_unref (block);
     }
@@ -554,11 +563,11 @@ get_top_level_block_devices_for_lun (GduWindow   *window,
 }
 
 static gint
-block_device_compare_on_preferred (UDisksBlockDevice *a,
-                                   UDisksBlockDevice *b)
+block_device_compare_on_preferred (GDBusObjectProxy *a,
+                                   GDBusObjectProxy *b)
 {
-  return g_strcmp0 (udisks_block_device_get_preferred_device (a),
-                    udisks_block_device_get_preferred_device (b));
+  return g_strcmp0 (udisks_block_device_get_preferred_device (UDISKS_PEEK_BLOCK_DEVICE (a)),
+                    udisks_block_device_get_preferred_device (UDISKS_PEEK_BLOCK_DEVICE (b)));
 }
 
 static void
@@ -590,15 +599,22 @@ setup_device_page (GduWindow         *window,
       GList *l;
       GString *str;
 
+      /* TODO: for multipath, ensure e.g. mpathk is before sda, sdb */
       block_devices = get_top_level_block_devices_for_lun (window, g_dbus_object_proxy_get_object_path (object_proxy));
       block_devices = g_list_sort (block_devices, (GCompareFunc) block_device_compare_on_preferred);
       str = g_string_new (NULL);
+
+      if (block_devices != NULL)
+        gdu_volume_grid_set_block_device (GDU_VOLUME_GRID (window->volume_grid), block_devices->data);
+      else
+        gdu_volume_grid_set_block_device (GDU_VOLUME_GRID (window->volume_grid), NULL);
+
       for (l = block_devices; l != NULL; l = l->next)
         {
-          UDisksBlockDevice *lun_block = UDISKS_BLOCK_DEVICE (l->data);
+          GDBusObjectProxy *block_object_proxy = G_DBUS_OBJECT_PROXY (l->data);
           if (str->len > 0)
             g_string_append_c (str, ' ');
-          g_string_append (str, udisks_block_device_get_preferred_device (lun_block));
+          g_string_append (str, udisks_block_device_get_preferred_device (UDISKS_PEEK_BLOCK_DEVICE (block_object_proxy)));
         }
       s = g_string_free (str, FALSE);
       set_string (window,
@@ -646,6 +662,7 @@ setup_device_page (GduWindow         *window,
   else if (block != NULL)
     {
       const gchar *backing_file;
+      gdu_volume_grid_set_block_device (GDU_VOLUME_GRID (window->volume_grid), object_proxy);
       set_string (window,
                   "devtab-device-label",
                   "devtab-device-value-label",



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]