[gtk+/parasite2: 1/38] Initial import



commit b0a2dedc3776811e7579690b595b504b01ce82ff
Author: Matthias Clasen <mclasen redhat com>
Date:   Fri May 2 21:48:33 2014 -0400

    Initial import
    
    This is a copy of https://github.com/chipx86/gtkparasite.git
    with minimal edits to make it build.

 configure.ac                                    |    2 +
 modules/Makefile.am                             |    2 +-
 modules/other/Makefile.am                       |    1 +
 modules/other/parasite/Makefile.am              |   52 +++
 modules/other/parasite/button-path.c            |  103 ++++++
 modules/other/parasite/button-path.h            |   60 ++++
 modules/other/parasite/classes-list.c           |  344 +++++++++++++++++++
 modules/other/parasite/classes-list.h           |   58 ++++
 modules/other/parasite/css-editor.c             |  388 +++++++++++++++++++++
 modules/other/parasite/css-editor.h             |   59 ++++
 modules/other/parasite/inspect-button.c         |  242 +++++++++++++
 modules/other/parasite/module.c                 |   40 +++
 modules/other/parasite/object-hierarchy.c       |  126 +++++++
 modules/other/parasite/object-hierarchy.h       |   58 ++++
 modules/other/parasite/parasite.h               |   69 ++++
 modules/other/parasite/prop-list.c              |  367 ++++++++++++++++++++
 modules/other/parasite/prop-list.h              |   60 ++++
 modules/other/parasite/property-cell-renderer.c |  410 ++++++++++++++++++++++
 modules/other/parasite/property-cell-renderer.h |   70 ++++
 modules/other/parasite/python-hooks.c           |  238 +++++++++++++
 modules/other/parasite/python-hooks.h           |   38 ++
 modules/other/parasite/python-shell.c           |  409 ++++++++++++++++++++++
 modules/other/parasite/python-shell.h           |   68 ++++
 modules/other/parasite/themes.c                 |  177 ++++++++++
 modules/other/parasite/themes.h                 |   56 +++
 modules/other/parasite/widget-tree.c            |  418 +++++++++++++++++++++++
 modules/other/parasite/widget-tree.h            |   78 +++++
 modules/other/parasite/window.c                 |  318 +++++++++++++++++
 28 files changed, 4310 insertions(+), 1 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 70912f3..195d874 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1898,6 +1898,8 @@ modules/printbackends/lpr/Makefile
 modules/printbackends/file/Makefile
 modules/printbackends/papi/Makefile
 modules/printbackends/test/Makefile
+modules/other/Makefile
+modules/other/parasite/Makefile
 ])
 
 AC_OUTPUT
diff --git a/modules/Makefile.am b/modules/Makefile.am
index f8e7bb8..34b26ac 100644
--- a/modules/Makefile.am
+++ b/modules/Makefile.am
@@ -1,6 +1,6 @@
 include $(top_srcdir)/Makefile.decl
 
-SUBDIRS = input
+SUBDIRS = input other
 
 if OS_UNIX
 SUBDIRS += printbackends
diff --git a/modules/other/Makefile.am b/modules/other/Makefile.am
new file mode 100644
index 0000000..e168924
--- /dev/null
+++ b/modules/other/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = parasite
diff --git a/modules/other/parasite/Makefile.am b/modules/other/parasite/Makefile.am
new file mode 100644
index 0000000..03243d6
--- /dev/null
+++ b/modules/other/parasite/Makefile.am
@@ -0,0 +1,52 @@
+moduledir = $(libdir)/gtk-3.0/modules
+
+module_LTLIBRARIES = libgtkparasite.la
+
+libgtkparasite_la_SOURCES = \
+       inspect-button.c \
+       module.c \
+       parasite.h \
+       prop-list.h \
+       prop-list.c \
+       property-cell-renderer.c \
+       property-cell-renderer.h \
+       python-hooks.c \
+       python-hooks.h \
+       python-shell.c \
+       python-shell.h \
+       widget-tree.h \
+       widget-tree.c \
+       window.c \
+       button-path.h \
+       button-path.c \
+       classes-list.h \
+       classes-list.c \
+       css-editor.h \
+       css-editor.c \
+       object-hierarchy.h \
+       object-hierarchy.c \
+       themes.h \
+       themes.c
+
+libgtkparasite_la_CPPFLAGS = \
+       -I$(top_srcdir)                 \
+       -I$(top_srcdir)/gtk             \
+       -I$(top_builddir)/gtk           \
+       -I$(top_srcdir)/gdk             \
+       -I$(top_builddir)/gdk           \
+       -DGTK_DATADIR=\"$(datadir)\"    \
+       -DGTK_COMPILATION               \
+       $(GTK_DEP_CFLAGS)               \
+       $(GTK_DEBUG_FLAGS)
+
+if PLATFORM_WIN32
+no_undefined = -no-undefined
+endif
+
+libgtkparasite_la_LDFLAGS = -avoid-version -module $(no_undefined)
+
+libgtkparasite_la_LIBADD = \
+       $(top_builddir)/gtk/libgtk-3.la \
+       $(GTK_DEP_LIBS)
+
+-include $(top_srcdir)/git.mk
diff --git a/modules/other/parasite/button-path.c b/modules/other/parasite/button-path.c
new file mode 100644
index 0000000..b8f3510
--- /dev/null
+++ b/modules/other/parasite/button-path.c
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2013 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "button-path.h"
+
+struct _ParasiteButtonPathPrivate
+{
+    GtkWidget *sw;
+    GtkWidget *button_box;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ParasiteButtonPath, parasite_buttonpath, GTK_TYPE_BOX)
+
+static void
+parasite_buttonpath_init (ParasiteButtonPath *bp)
+{
+  GtkWidget *label;
+
+  bp->priv = parasite_buttonpath_get_instance_private (bp);
+
+  g_object_set (bp,
+                "orientation", GTK_ORIENTATION_HORIZONTAL,
+                NULL);
+
+  bp->priv->sw = g_object_new (GTK_TYPE_SCROLLED_WINDOW,
+                               "hscrollbar-policy", GTK_POLICY_AUTOMATIC,
+                               "vscrollbar-policy", GTK_POLICY_NEVER,
+                               "hexpand", TRUE,
+                               NULL);
+  gtk_container_add (GTK_CONTAINER (bp), bp->priv->sw);
+
+  bp->priv->button_box = g_object_new (GTK_TYPE_BOX,
+                                       "orientation", GTK_ORIENTATION_HORIZONTAL,
+                                       "hexpand", TRUE,
+                                       "margin", 6,
+                                       NULL);
+  gtk_style_context_add_class (gtk_widget_get_style_context (bp->priv->button_box), "linked");
+  gtk_container_add (GTK_CONTAINER (bp->priv->sw), bp->priv->button_box);
+
+  label = g_object_new (GTK_TYPE_LABEL,
+                        "label", "Choose a widget through the inspector",
+                        "xalign", 0.5,
+                        "hexpand", TRUE,
+                        NULL);
+  gtk_container_add (GTK_CONTAINER (bp->priv->button_box), label);
+}
+
+static void
+parasite_buttonpath_class_init(ParasiteButtonPathClass *klass)
+{
+}
+
+GtkWidget *
+parasite_buttonpath_new ()
+{
+    return GTK_WIDGET (g_object_new (PARASITE_TYPE_BUTTONPATH, NULL));
+}
+
+void
+parasite_buttonpath_set_widget(ParasiteButtonPath *bp,
+                               GtkWidget *widget)
+{
+  char *path, **words;
+  int i;
+  GtkWidget *b;
+  GtkContainer *box = GTK_CONTAINER (bp->priv->button_box);
+
+  gtk_container_foreach (box, (GtkCallback)gtk_widget_destroy, NULL);
+
+  path = gtk_widget_path_to_string (gtk_widget_get_path (widget));
+  words = g_strsplit (path, " ", 0);
+
+  for (i = 0; i < g_strv_length (words); i++)
+    {
+      b = gtk_button_new_with_label (words[i]);
+      gtk_widget_show (b);
+      gtk_container_add (box, b);
+    }
+
+  g_strfreev (words);
+  g_free (path);
+}
+
+// vim: set et sw=4 ts=4:
diff --git a/modules/other/parasite/button-path.h b/modules/other/parasite/button-path.h
new file mode 100644
index 0000000..e05424c
--- /dev/null
+++ b/modules/other/parasite/button-path.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2013 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef _GTKPARASITE_BUTTONPATH_H_
+#define _GTKPARASITE_BUTTONPATH_H_
+
+
+#include <gtk/gtk.h>
+
+#define PARASITE_TYPE_BUTTONPATH            (parasite_buttonpath_get_type())
+#define PARASITE_BUTTONPATH(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), PARASITE_TYPE_BUTTONPATH, 
ParasiteButtonPath))
+#define PARASITE_BUTTONPATH_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), PARASITE_TYPE_BUTTONPATH, 
ParasiteButtonPathClass))
+#define PARASITE_IS_BUTTONPATH(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), PARASITE_TYPE_BUTTONPATH))
+#define PARASITE_IS_BUTTONPATH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PARASITE_TYPE_BUTTONPATH))
+#define PARASITE_BUTTONPATH_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), PARASITE_TYPE_BUTTONPATH, 
ParasiteButtonPathClass))
+
+
+typedef struct _ParasiteButtonPathPrivate ParasiteButtonPathPrivate;
+
+typedef struct _ParasiteButtonPath {
+   GtkBox parent;
+   ParasiteButtonPathPrivate *priv;
+} ParasiteButtonPath;
+
+typedef struct _ParasiteButtonPathClass {
+   GtkBoxClass parent;
+} ParasiteButtonPathClass;
+
+
+G_BEGIN_DECLS
+
+GType parasite_buttonpath_get_type ();
+GtkWidget *parasite_buttonpath_new ();
+void parasite_buttonpath_set_widget (ParasiteButtonPath *bp,
+                                     GtkWidget *widget);
+
+G_END_DECLS
+
+
+#endif // _GTKPARASITE_BUTTONPATH_H_
+
+// vim: set et sw=4 ts=4:
diff --git a/modules/other/parasite/classes-list.c b/modules/other/parasite/classes-list.c
new file mode 100644
index 0000000..6164c4c
--- /dev/null
+++ b/modules/other/parasite/classes-list.c
@@ -0,0 +1,344 @@
+/*
+ * Copyright (c) 2013 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "classes-list.h"
+#include "parasite.h"
+
+enum
+{
+  COLUMN_ENABLED,
+  COLUMN_NAME,
+  COLUMN_USER,
+  NUM_COLUMNS
+};
+
+typedef struct
+{
+  gboolean enabled;
+  gboolean user;
+} ParasiteClassesListByContext;
+
+struct _ParasiteClassesListPrivate
+{
+  GtkWidget *toolbar;
+  GtkWidget *view;
+  GtkListStore *model;
+  GHashTable *contexts;
+  GtkStyleContext *current_context;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ParasiteClassesList, parasite_classeslist, GTK_TYPE_BOX)
+
+static void
+enabled_toggled (GtkCellRendererToggle *renderer, gchar *path, ParasiteClassesList *cl)
+{
+  GtkTreeIter iter;
+  gboolean enabled;
+  GHashTable *context;
+  ParasiteClassesListByContext *c;
+  gchar *name;
+
+  if (!gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (cl->priv->model), &iter, path))
+    {
+      g_warning ("Parasite: Couldn't find the css class path for %s.", path);
+      return;
+    }
+
+  gtk_tree_model_get (GTK_TREE_MODEL (cl->priv->model), &iter,
+                      COLUMN_ENABLED, &enabled,
+                      COLUMN_NAME, &name,
+                      -1);
+  enabled = !enabled;
+  gtk_list_store_set (cl->priv->model, &iter,
+                      COLUMN_ENABLED, enabled,
+                      -1);
+
+  context = g_hash_table_lookup (cl->priv->contexts, cl->priv->current_context);
+  if (context)
+    {
+      c = g_hash_table_lookup (context, name);
+      if (c)
+        {
+          c->enabled = enabled;
+          if (enabled)
+            {
+              gtk_style_context_add_class (cl->priv->current_context, name);
+            }
+          else
+            {
+              gtk_style_context_remove_class (cl->priv->current_context, name);
+            }
+        }
+      else
+        {
+          g_warning ("Parasite: Couldn't find the css class %s in the class hash table.", name);
+        }
+    }
+  else
+    {
+      g_warning ("Parasite: Couldn't find the hash table for the style context for css class %s.", name);
+    }
+}
+
+static void
+add_clicked (GtkButton *button, ParasiteClassesList *cl)
+{
+  GtkWidget *dialog, *content_area, *entry;
+
+  dialog = gtk_dialog_new_with_buttons ("New class",
+                                         GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (cl))),
+                                         GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+                                         "_OK", GTK_RESPONSE_OK,
+                                         "Cancel", GTK_RESPONSE_CANCEL,
+                                         NULL);
+  gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+
+  entry = g_object_new (GTK_TYPE_ENTRY,
+                        "visible", TRUE,
+                        "margin", 5,
+                        "placeholder-text", "Class name",
+                        "activates-default", TRUE,
+                        NULL);
+  content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+  gtk_container_add (GTK_CONTAINER (content_area), entry);
+
+  if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK)
+    {
+      const gchar *name = gtk_entry_get_text (GTK_ENTRY (entry));
+      GHashTable *context = g_hash_table_lookup (cl->priv->contexts, cl->priv->current_context);
+
+      if (*name && !g_hash_table_contains (context, name))
+        {
+          GtkTreeIter tree_iter;
+
+          gtk_style_context_add_class (cl->priv->current_context, name);
+
+          ParasiteClassesListByContext *c = g_new0 (ParasiteClassesListByContext, 1);
+          c->enabled = TRUE;
+          c->user = TRUE;
+          g_hash_table_insert (context, (gpointer)g_strdup (name), c);
+
+          gtk_list_store_append (cl->priv->model, &tree_iter);
+          gtk_list_store_set (cl->priv->model, &tree_iter,
+                             COLUMN_ENABLED, TRUE,
+                             COLUMN_NAME, name,
+                             COLUMN_USER, TRUE,
+                            -1);
+        }
+    }
+
+  gtk_widget_destroy (dialog);
+}
+
+static void
+read_classes_from_style_context (ParasiteClassesList *cl)
+{
+  GList *l, *classes;
+  ParasiteClassesListByContext *c;
+  GtkTreeIter tree_iter;
+  GHashTable *hash_context;
+
+  hash_context = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+  classes = gtk_style_context_list_classes (cl->priv->current_context);
+
+  for (l = classes; l; l = l->next)
+    {
+      c = g_new0 (ParasiteClassesListByContext, 1);
+      c->enabled = TRUE;
+      g_hash_table_insert (hash_context, g_strdup (l->data), c);
+
+      gtk_list_store_append (cl->priv->model, &tree_iter);
+      gtk_list_store_set (cl->priv->model, &tree_iter,
+                          COLUMN_ENABLED, TRUE,
+                          COLUMN_NAME, l->data,
+                          COLUMN_USER, FALSE,
+                          -1);
+    }
+    g_list_free (classes);
+    g_hash_table_replace (cl->priv->contexts, cl->priv->current_context, hash_context);
+}
+
+static void
+restore_defaults_clicked (GtkButton *button, ParasiteClassesList *cl)
+{
+  GHashTableIter hash_iter;
+  gchar *name;
+  ParasiteClassesListByContext *c;
+  GHashTable *hash_context = g_hash_table_lookup (cl->priv->contexts, cl->priv->current_context);
+
+  g_hash_table_iter_init (&hash_iter, hash_context);
+  while (g_hash_table_iter_next (&hash_iter, (gpointer *)&name, (gpointer *)&c))
+    {
+      if (c->user)
+        {
+          gtk_style_context_remove_class (cl->priv->current_context, name);
+        }
+      else if (!c->enabled)
+        {
+          gtk_style_context_add_class (cl->priv->current_context, name);
+        }
+    }
+
+  gtk_list_store_clear (cl->priv->model);
+  read_classes_from_style_context (cl);
+}
+
+static void
+create_toolbar (ParasiteClassesList *cl)
+{
+  GtkWidget *button;
+
+  cl->priv->toolbar = g_object_new (GTK_TYPE_TOOLBAR,
+                                    "icon-size", GTK_ICON_SIZE_SMALL_TOOLBAR,
+                                    "sensitive", FALSE,
+                                    NULL);
+  gtk_container_add (GTK_CONTAINER (cl), cl->priv->toolbar);
+
+  button = g_object_new (GTK_TYPE_TOOL_BUTTON,
+                         "icon-name", "add",
+                         "tooltip-text", "Add a class",
+                         NULL);
+  g_signal_connect (button, "clicked", G_CALLBACK (add_clicked), cl);
+  gtk_container_add (GTK_CONTAINER (cl->priv->toolbar), button);
+
+  button = g_object_new (GTK_TYPE_TOOL_BUTTON,
+                         "icon-name", "revert",
+                         "tooltip-text", "Restore defaults for this widget",
+                         NULL);
+  g_signal_connect (button, "clicked", G_CALLBACK (restore_defaults_clicked), cl);
+  gtk_container_add (GTK_CONTAINER (cl->priv->toolbar), button);
+}
+
+static void
+draw_name_column (GtkTreeViewColumn   *column,
+                  GtkCellRenderer     *renderer,
+                  GtkTreeModel        *model,
+                  GtkTreeIter         *iter,
+                  ParasiteClassesList *cl)
+{
+  gboolean user;
+
+  gtk_tree_model_get (model, iter, COLUMN_USER, &user, -1);
+  if (user)
+    {
+      g_object_set (renderer, "style", PANGO_STYLE_ITALIC, NULL);
+    }
+  else
+    {
+      g_object_set (renderer, "style", PANGO_STYLE_NORMAL, NULL);
+    }
+}
+
+static void
+parasite_classeslist_init (ParasiteClassesList *cl)
+{
+  GtkCellRenderer *renderer;
+  GtkTreeViewColumn *column;
+  GtkWidget *sw;
+
+  g_object_set (cl, "orientation", GTK_ORIENTATION_VERTICAL, NULL);
+
+  cl->priv = parasite_classeslist_get_instance_private (cl);
+
+  create_toolbar (cl);
+
+  sw = g_object_new (GTK_TYPE_SCROLLED_WINDOW,
+                     "expand", TRUE,
+                     NULL);
+  gtk_container_add (GTK_CONTAINER (cl), sw);
+
+  cl->priv->contexts = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, 
(GDestroyNotify)g_hash_table_destroy);
+  cl->priv->model = gtk_list_store_new (NUM_COLUMNS,
+                                        G_TYPE_BOOLEAN,  // COLUMN_ENABLED
+                                        G_TYPE_STRING,   // COLUMN_NAME
+                                        G_TYPE_BOOLEAN); // COLUMN_USER
+  cl->priv->view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (cl->priv->model));
+  gtk_container_add (GTK_CONTAINER (sw), cl->priv->view);
+
+  renderer = gtk_cell_renderer_toggle_new ();
+  g_signal_connect (renderer, "toggled", G_CALLBACK (enabled_toggled), cl);
+  column = gtk_tree_view_column_new_with_attributes ("", renderer,
+                                                     "active", COLUMN_ENABLED,
+                                                     NULL);
+  gtk_tree_view_append_column (GTK_TREE_VIEW (cl->priv->view), column);
+
+  renderer = gtk_cell_renderer_text_new ();
+  g_object_set (renderer, "scale", TREE_TEXT_SCALE, NULL);
+  column = gtk_tree_view_column_new_with_attributes ("Name", renderer,
+                                                     "text", COLUMN_NAME,
+                                                     NULL);
+  gtk_tree_view_column_set_cell_data_func (column, renderer, (GtkTreeCellDataFunc)draw_name_column, cl, 
NULL);
+  gtk_tree_view_append_column (GTK_TREE_VIEW (cl->priv->view), column);
+}
+
+void
+parasite_classeslist_set_widget (ParasiteClassesList *cl,
+                                 GtkWidget *widget)
+{
+  GtkStyleContext *widget_context;
+  GHashTable *hash_context;
+  GtkTreeIter tree_iter;
+  ParasiteClassesListByContext *c;
+
+  gtk_list_store_clear (cl->priv->model);
+
+  gtk_widget_set_sensitive (GTK_WIDGET (cl), TRUE);
+  widget_context = gtk_widget_get_style_context (widget);
+
+  cl->priv->current_context = widget_context;
+  gtk_widget_set_sensitive (cl->priv->toolbar, TRUE);
+
+  hash_context = g_hash_table_lookup (cl->priv->contexts, widget_context);
+  if (hash_context)
+    {
+      GHashTableIter hash_iter;
+      gchar *name;
+
+      g_hash_table_iter_init (&hash_iter, hash_context);
+      while (g_hash_table_iter_next (&hash_iter, (gpointer *)&name, (gpointer *)&c))
+        {
+          gtk_list_store_append (cl->priv->model, &tree_iter);
+          gtk_list_store_set (cl->priv->model, &tree_iter,
+                             COLUMN_ENABLED, c->enabled,
+                             COLUMN_NAME, name,
+                             COLUMN_USER, c->user,
+                            -1);
+        }
+    }
+  else
+    {
+      read_classes_from_style_context (cl);
+    }
+}
+
+static void
+parasite_classeslist_class_init (ParasiteClassesListClass *klass)
+{
+}
+
+GtkWidget *
+parasite_classeslist_new ()
+{
+    return GTK_WIDGET (g_object_new (PARASITE_TYPE_CLASSESLIST, NULL));
+}
+
+// vim: set et sw=4 ts=4:
diff --git a/modules/other/parasite/classes-list.h b/modules/other/parasite/classes-list.h
new file mode 100644
index 0000000..19bc800
--- /dev/null
+++ b/modules/other/parasite/classes-list.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2013 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef _GTKPARASITE_CLASSESLIST_H_
+#define _GTKPARASITE_CLASSESLIST_H_
+
+#include <gtk/gtk.h>
+
+#define PARASITE_TYPE_CLASSESLIST            (parasite_classeslist_get_type())
+#define PARASITE_CLASSESLIST(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), PARASITE_TYPE_CLASSESLIST, 
ParasiteClassesList))
+#define PARASITE_CLASSESLIST_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), PARASITE_TYPE_CLASSESLIST, 
ParasiteClassesListClass))
+#define PARASITE_IS_CLASSESLIST(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), PARASITE_TYPE_CLASSESLIST))
+#define PARASITE_IS_CLASSESLIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PARASITE_TYPE_CLASSESLIST))
+#define PARASITE_CLASSESLIST_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), PARASITE_TYPE_CLASSESLIST, 
ParasiteClassesListClass))
+
+
+typedef struct _ParasiteClassesListPrivate ParasiteClassesListPrivate;
+
+typedef struct _ParasiteClassesList {
+   GtkBox parent;
+   ParasiteClassesListPrivate *priv;
+} ParasiteClassesList;
+
+typedef struct _ParasiteClassesListClass {
+   GtkBoxClass parent;
+} ParasiteClassesListClass;
+
+G_BEGIN_DECLS
+
+GType parasite_classeslist_get_type ();
+GtkWidget *parasite_classeslist_new ();
+void parasite_classeslist_set_widget (ParasiteClassesList* classeslist,
+                                      GtkWidget *widget);
+
+G_END_DECLS
+
+#endif // _GTKPARASITE_CLASSESLIST_H_
+
+// vim: set et sw=4 ts=4:
diff --git a/modules/other/parasite/css-editor.c b/modules/other/parasite/css-editor.c
new file mode 100644
index 0000000..ca4dd5b
--- /dev/null
+++ b/modules/other/parasite/css-editor.c
@@ -0,0 +1,388 @@
+/*
+ * Copyright (c) 2013 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "css-editor.h"
+#include "parasite.h"
+
+#define PARASITE_CSSEDITOR_TEXT "parasite-csseditor-text"
+#define PARASITE_CSSEDITOR_PROVIDER "parasite-csseditor-provider"
+
+enum
+{
+  COLUMN_ENABLED,
+  COLUMN_NAME,
+  COLUMN_USER,
+  NUM_COLUMNS
+};
+
+enum
+{
+  PROP_0,
+  PROP_GLOBAL
+};
+
+typedef struct
+{
+  gboolean enabled;
+  gboolean user;
+} ParasiteCssEditorByContext;
+
+struct _ParasiteCssEditorPrivate
+{
+  GtkWidget *toolbar;
+  GtkTextBuffer *text;
+  GtkCssProvider *provider;
+  gboolean global;
+  GtkStyleContext *selected_context;
+  GtkToggleToolButton *disable_button;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ParasiteCssEditor, parasite_csseditor, GTK_TYPE_BOX)
+
+static const gchar *initial_text_global =
+          "/*\n"
+          "You can type here any CSS rule recognized by GTK+.\n"
+          "You can temporarily disable this custom CSS by clicking on the \"Pause\" button above.\n\n"
+          "Changes are applied instantly and globally, for the whole application.\n"
+          "*/\n\n";
+static const gchar *initial_text_widget =
+          "/*\n"
+          "You can type here any CSS rule recognized by GTK+.\n"
+          "You can temporarily disable this custom CSS by clicking on the \"Pause\" button above.\n\n"
+          "Changes are applied instantly, only for this selected widget.\n"
+          "*/\n\n";
+
+static void
+disable_toggled (GtkToggleToolButton *button, ParasiteCssEditor *editor)
+{
+  if (gtk_toggle_tool_button_get_active (button))
+    {
+      if (editor->priv->global)
+        {
+          gtk_style_context_remove_provider_for_screen (gdk_screen_get_default (),
+                                                        GTK_STYLE_PROVIDER (editor->priv->provider));
+        }
+      else if (editor->priv->selected_context)
+        {
+          gtk_style_context_remove_provider (editor->priv->selected_context,
+                                             GTK_STYLE_PROVIDER (g_object_get_data (G_OBJECT 
(editor->priv->selected_context), PARASITE_CSSEDITOR_PROVIDER)));
+        }
+    }
+  else
+    {
+      if (editor->priv->global)
+        {
+          gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
+                                                     GTK_STYLE_PROVIDER (editor->priv->provider),
+                                                     GTK_STYLE_PROVIDER_PRIORITY_USER);
+        }
+      else if (editor->priv->selected_context)
+        {
+          gtk_style_context_add_provider (editor->priv->selected_context,
+                                          GTK_STYLE_PROVIDER (g_object_get_data (G_OBJECT 
(editor->priv->selected_context), PARASITE_CSSEDITOR_PROVIDER)),
+                                          G_MAXUINT);
+        }
+    }
+}
+
+static void
+create_toolbar (ParasiteCssEditor *editor)
+{
+  editor->priv->toolbar = g_object_new (GTK_TYPE_TOOLBAR,
+                                        "icon-size", GTK_ICON_SIZE_SMALL_TOOLBAR,
+                                        NULL);
+  gtk_container_add (GTK_CONTAINER (editor), editor->priv->toolbar);
+
+  editor->priv->disable_button = g_object_new (GTK_TYPE_TOGGLE_TOOL_BUTTON,
+                                               "icon-name", "media-playback-pause",
+                                               "tooltip-text", "Disable this custom css",
+                                               NULL);
+  g_signal_connect (editor->priv->disable_button,
+                    "toggled",
+                    G_CALLBACK (disable_toggled),
+                    editor);
+  gtk_container_add (GTK_CONTAINER (editor->priv->toolbar),
+                     GTK_WIDGET (editor->priv->disable_button));
+}
+
+static void
+apply_system_font (GtkWidget *widget)
+{
+  GSettings *s = g_settings_new ("org.gnome.desktop.interface");
+  gchar *font_name = g_settings_get_string (s, "monospace-font-name");
+  PangoFontDescription *font_desc = pango_font_description_from_string (font_name);
+
+  gtk_widget_override_font (widget, font_desc);
+
+  pango_font_description_free (font_desc);
+  g_free (font_name);
+  g_object_unref (s);
+}
+
+static gchar *
+get_current_text (GtkTextBuffer *buffer)
+{
+  GtkTextIter start, end;
+
+  gtk_text_buffer_get_start_iter (buffer, &start);
+  gtk_text_buffer_get_end_iter (buffer, &end);
+  gtk_text_buffer_remove_all_tags (buffer, &start, &end);
+
+  return gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+}
+
+static void
+text_changed (GtkTextBuffer *buffer, ParasiteCssEditor *editor)
+{
+  GtkCssProvider *provider;
+  char *text;
+
+  if (editor->priv->global)
+    provider = editor->priv->provider;
+  else if (editor->priv->selected_context)
+    provider = g_object_get_data (G_OBJECT (editor->priv->selected_context), PARASITE_CSSEDITOR_PROVIDER);
+  else
+    return;
+
+  text = get_current_text (buffer);
+  gtk_css_provider_load_from_data (provider, text, -1, NULL);
+  g_free (text);
+
+  gtk_style_context_reset_widgets (gdk_screen_get_default ());
+}
+
+static void
+show_parsing_error (GtkCssProvider    *provider,
+                    GtkCssSection     *section,
+                    const GError      *error,
+                    ParasiteCssEditor *editor)
+{
+  GtkTextIter start, end;
+  const char *tag_name;
+  GtkTextBuffer *buffer = GTK_TEXT_BUFFER (editor->priv->text);
+
+  gtk_text_buffer_get_iter_at_line_index (buffer,
+                                          &start,
+                                          gtk_css_section_get_start_line (section),
+                                          gtk_css_section_get_start_position (section));
+  gtk_text_buffer_get_iter_at_line_index (buffer,
+                                          &end,
+                                          gtk_css_section_get_end_line (section),
+                                          gtk_css_section_get_end_position (section));
+
+  if (g_error_matches (error, GTK_CSS_PROVIDER_ERROR, GTK_CSS_PROVIDER_ERROR_DEPRECATED))
+    tag_name = "warning";
+  else
+    tag_name = "error";
+
+  gtk_text_buffer_apply_tag_by_name (buffer, tag_name, &start, &end);
+}
+
+static void
+create_text_widget (ParasiteCssEditor *editor)
+{
+  GtkWidget *sw, *view;
+
+  editor->priv->text = gtk_text_buffer_new (NULL);
+
+  if (editor->priv->global)
+    gtk_text_buffer_set_text (GTK_TEXT_BUFFER (editor->priv->text), initial_text_global, -1);
+  else
+    gtk_text_buffer_set_text (GTK_TEXT_BUFFER (editor->priv->text), initial_text_widget, -1);
+
+  g_signal_connect (editor->priv->text, "changed", G_CALLBACK (text_changed), editor);
+
+  gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (editor->priv->text),
+                              "warning",
+                              "underline", PANGO_UNDERLINE_SINGLE,
+                              NULL);
+  gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (editor->priv->text),
+                              "error",
+                              "underline", PANGO_UNDERLINE_ERROR,
+                              NULL);
+
+  sw = g_object_new (GTK_TYPE_SCROLLED_WINDOW,
+                     "expand", TRUE,
+                     NULL);
+  gtk_container_add (GTK_CONTAINER (editor), sw);
+
+  view = g_object_new (GTK_TYPE_TEXT_VIEW,
+                       "buffer", editor->priv->text,
+                       "wrap-mode", GTK_WRAP_WORD,
+                       NULL);
+  apply_system_font (view);
+  gtk_container_add (GTK_CONTAINER (sw), view);
+}
+
+static void
+create_provider (ParasiteCssEditor *editor)
+{
+  GtkCssProvider *provider = gtk_css_provider_new ();
+
+  if (editor->priv->global)
+    {
+      editor->priv->provider = provider;
+      gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
+                                                 GTK_STYLE_PROVIDER (editor->priv->provider),
+                                                 GTK_STYLE_PROVIDER_PRIORITY_USER);
+    }
+  else if (editor->priv->selected_context)
+    {
+      gtk_style_context_add_provider (editor->priv->selected_context,
+                                      GTK_STYLE_PROVIDER (provider),
+                                      G_MAXUINT);
+      g_object_set_data (G_OBJECT (editor->priv->selected_context),
+                         PARASITE_CSSEDITOR_PROVIDER,
+                         provider);
+    }
+
+  g_signal_connect (provider,
+                    "parsing-error",
+                    G_CALLBACK (show_parsing_error),
+                    editor);
+}
+
+static void
+parasite_csseditor_init (ParasiteCssEditor *editor)
+{
+  editor->priv = parasite_csseditor_get_instance_private (editor);
+}
+
+static void
+constructed (GObject *object)
+{
+  ParasiteCssEditor *editor = PARASITE_CSSEDITOR (object);
+
+  g_object_set (editor,
+                "orientation", GTK_ORIENTATION_VERTICAL,
+                "sensitive", editor->priv->global,
+                NULL);
+
+  create_toolbar (editor);
+  create_provider (editor);
+  create_text_widget (editor);
+}
+
+static void
+get_property (GObject    *object,
+              guint      param_id,
+              GValue     *value,
+              GParamSpec *pspec)
+{
+  ParasiteCssEditor *editor = PARASITE_CSSEDITOR (object);
+
+  switch (param_id)
+    {
+      case PROP_GLOBAL:
+        g_value_set_boolean (value, editor->priv->global);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
+        break;
+    }
+}
+
+static void
+set_property (GObject      *object,
+              guint        param_id,
+              const GValue *value,
+              GParamSpec   *pspec)
+{
+  ParasiteCssEditor *editor = PARASITE_CSSEDITOR (object);
+
+  switch (param_id)
+    {
+      case PROP_GLOBAL:
+        editor->priv->global = g_value_get_boolean (value);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
+        break;
+    }
+}
+
+static void
+parasite_csseditor_class_init (ParasiteCssEditorClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->get_property = get_property;
+  object_class->set_property = set_property;
+  object_class->constructed  = constructed;
+
+  g_object_class_install_property (object_class,
+                                   PROP_GLOBAL,
+                                   g_param_spec_boolean ("global",
+                                                         "Global",
+                                                         "Whether this editor changes the whole application 
or just the selected widget",
+                                                         TRUE,
+                                                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+GtkWidget *
+parasite_csseditor_new (gboolean global)
+{
+    return GTK_WIDGET (g_object_new (PARASITE_TYPE_CSSEDITOR,
+                                     "global", global,
+                                     NULL));
+}
+
+void
+parasite_csseditor_set_widget (ParasiteCssEditor *editor, GtkWidget *widget)
+{
+  gchar *text;
+  GtkCssProvider *provider;
+
+  g_return_if_fail (PARASITE_IS_CSSEDITOR (editor));
+  g_return_if_fail (!editor->priv->global);
+
+  gtk_widget_set_sensitive (GTK_WIDGET (editor), TRUE);
+
+  if (editor->priv->selected_context)
+    {
+      text = get_current_text (GTK_TEXT_BUFFER (editor->priv->text));
+      g_object_set_data_full (G_OBJECT (editor->priv->selected_context),
+                              PARASITE_CSSEDITOR_TEXT,
+                              text,
+                              g_free);
+    }
+
+  editor->priv->selected_context = gtk_widget_get_style_context (widget);
+
+  provider = g_object_get_data (G_OBJECT (editor->priv->selected_context), PARASITE_CSSEDITOR_PROVIDER);
+  if (!provider)
+    {
+      create_provider (editor);
+    }
+
+  text = g_object_get_data (G_OBJECT (editor->priv->selected_context), PARASITE_CSSEDITOR_TEXT);
+  if (text)
+    gtk_text_buffer_set_text (GTK_TEXT_BUFFER (editor->priv->text), text, -1);
+  else
+    gtk_text_buffer_set_text (GTK_TEXT_BUFFER (editor->priv->text), initial_text_widget, -1);
+
+  disable_toggled (editor->priv->disable_button, editor);
+}
+
+// vim: set et sw=4 ts=4:
diff --git a/modules/other/parasite/css-editor.h b/modules/other/parasite/css-editor.h
new file mode 100644
index 0000000..e7d58dd
--- /dev/null
+++ b/modules/other/parasite/css-editor.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2013 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef _GTKPARASITE_CSSEDITOR_H_
+#define _GTKPARASITE_CSSEDITOR_H_
+
+#include <gtk/gtk.h>
+
+#define PARASITE_TYPE_CSSEDITOR            (parasite_csseditor_get_type())
+#define PARASITE_CSSEDITOR(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), PARASITE_TYPE_CSSEDITOR, 
ParasiteCssEditor))
+#define PARASITE_CSSEDITOR_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), PARASITE_TYPE_CSSEDITOR, 
ParasiteCssEditorClass))
+#define PARASITE_IS_CSSEDITOR(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), PARASITE_TYPE_CSSEDITOR))
+#define PARASITE_IS_CSSEDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PARASITE_TYPE_CSSEDITOR))
+#define PARASITE_CSSEDITOR_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), PARASITE_TYPE_CSSEDITOR, 
ParasiteCssEditorClass))
+
+
+typedef struct _ParasiteCssEditorPrivate ParasiteCssEditorPrivate;
+
+typedef struct _ParasiteCssEditor {
+   GtkBox parent;
+   ParasiteCssEditorPrivate *priv;
+} ParasiteCssEditor;
+
+typedef struct _ParasiteCssEditorClass {
+   GtkBoxClass parent;
+} ParasiteCssEditorClass;
+
+G_BEGIN_DECLS
+
+GType parasite_csseditor_get_type ();
+
+GtkWidget *parasite_csseditor_new (gboolean global);
+void parasite_csseditor_set_widget (ParasiteCssEditor *editor,
+                                    GtkWidget         *widget);
+
+G_END_DECLS
+
+#endif // _GTKPARASITE_CSSEDITOR_H_
+
+// vim: set et sw=4 ts=4:
diff --git a/modules/other/parasite/inspect-button.c b/modules/other/parasite/inspect-button.c
new file mode 100644
index 0000000..b875ef6
--- /dev/null
+++ b/modules/other/parasite/inspect-button.c
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2008-2009  Christian Hammond
+ * Copyright (c) 2008-2009  David Trowbridge
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "parasite.h"
+#include "widget-tree.h"
+
+static void
+on_inspect_widget(GtkWidget *grab_window,
+                  GdkEventButton *event,
+                  ParasiteWindow *parasite)
+{
+  gdk_device_ungrab (gtk_get_current_event_device (), event->time);
+  gtk_widget_hide(parasite->highlight_window);
+
+  if (parasite->selected_window != NULL)
+    {
+      GtkWidget *toplevel = NULL;
+      GtkWidget *widget = NULL;
+
+      gdk_window_get_user_data(
+            gdk_window_get_toplevel(parasite->selected_window),
+            (gpointer*)&toplevel);
+
+      gdk_window_get_user_data (parasite->selected_window, (gpointer*)&widget);
+
+      if (toplevel)
+        {
+          parasite_widget_tree_scan (PARASITE_WIDGET_TREE (parasite->widget_tree),
+                                     toplevel);
+        }
+
+      if (widget)
+        {
+          parasite_widget_tree_select_object (PARASITE_WIDGET_TREE (parasite->widget_tree),
+                                              G_OBJECT (widget));
+        }
+    }
+}
+
+static void
+on_highlight_window_show (GtkWidget *window, ParasiteWindow *parasite)
+{
+  if (gtk_widget_is_composited (parasite->window))
+    {
+      gtk_widget_set_opacity (parasite->highlight_window, 0.2);
+    }
+  else
+    {
+      /*
+       * TODO: Do something different when there's no compositing manager.
+       *         Draw a border or something.
+       */
+    }
+}
+
+static void
+ensure_highlight_window (ParasiteWindow *parasite)
+{
+  GdkRGBA color;
+
+  if (parasite->highlight_window != NULL)
+    return;
+
+  color.red = 0;
+  color.green = 0;
+  color.blue = 65535;
+  color.alpha = 1;
+
+  parasite->highlight_window = gtk_window_new (GTK_WINDOW_POPUP);
+  gtk_widget_override_background_color (parasite->highlight_window,
+                                        GTK_STATE_NORMAL,
+                                        &color);
+
+  g_signal_connect (parasite->highlight_window, "show", G_CALLBACK (on_highlight_window_show), parasite);
+}
+
+static void
+on_highlight_widget(GtkWidget *grab_window,
+                    GdkEventMotion *event,
+                    ParasiteWindow *parasite)
+{
+  GdkWindow *selected_window;
+  gint x, y, width, height;
+
+  ensure_highlight_window (parasite);
+
+  gtk_widget_hide (parasite->highlight_window);
+
+  selected_window = gdk_device_get_window_at_position (gtk_get_current_event_device (),
+                                                       NULL,
+                                                       NULL);
+  if (selected_window == NULL)
+    {
+      /* This window isn't in-process. Ignore it. */
+      parasite->selected_window = NULL;
+      return;
+    }
+
+  if (gdk_window_get_toplevel(selected_window) == gtk_widget_get_window(parasite->window))
+    {
+       /* Don't hilight things in the parasite window */
+        parasite->selected_window = NULL;
+        return;
+    }
+
+  parasite->selected_window = selected_window;
+
+  gdk_window_get_origin (selected_window, &x, &y);
+  width = gdk_window_get_width (selected_window);
+  height = gdk_window_get_height (selected_window);
+
+  gtk_window_move (GTK_WINDOW (parasite->highlight_window), x, y);
+  gtk_window_resize (GTK_WINDOW (parasite->highlight_window), width, height);
+  gtk_widget_show (parasite->highlight_window);
+}
+
+static void
+on_inspect_button_release (GtkWidget *button,
+                           GdkEventButton *event,
+                           ParasiteWindow *parasite)
+{
+  GdkCursor *cursor;
+  GdkEventMask events;
+
+  events = GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK;
+
+  if (parasite->grab_window == NULL)
+    {
+      parasite->grab_window = gtk_window_new (GTK_WINDOW_POPUP);
+      gtk_widget_show(parasite->grab_window);
+      gtk_window_resize (GTK_WINDOW (parasite->grab_window), 1, 1);
+      gtk_window_move (GTK_WINDOW (parasite->grab_window), -100, -100);
+      gtk_widget_add_events (parasite->grab_window, events);
+
+      g_signal_connect (parasite->grab_window, "button_release_event", G_CALLBACK (on_inspect_widget), 
parasite);
+      g_signal_connect (parasite->grab_window, "motion_notify_event", G_CALLBACK(on_highlight_widget), 
parasite);
+    }
+
+  cursor = gdk_cursor_new_for_display (gtk_widget_get_display (button), GDK_CROSSHAIR);
+  gdk_device_grab (gtk_get_current_event_device (),
+                   gtk_widget_get_window (parasite->grab_window),
+                   GDK_OWNERSHIP_WINDOW,
+                   FALSE,
+                   events,
+                   cursor,
+                   event->time);
+  g_object_unref (cursor);
+}
+
+
+GtkWidget *
+gtkparasite_inspect_button_new(ParasiteWindow *parasite)
+{
+    GtkWidget *button;
+
+    button = gtk_button_new_from_icon_name ("find", GTK_ICON_SIZE_BUTTON);
+    gtk_widget_set_tooltip_text (button, "Inspect");
+    g_signal_connect(G_OBJECT(button), "button_release_event",
+                     G_CALLBACK(on_inspect_button_release), parasite);
+
+    return button;
+}
+
+static gboolean
+on_flash_timeout(ParasiteWindow *parasite)
+{
+    parasite->flash_count++;
+
+    if (parasite->flash_count == 8)
+    {
+        parasite->flash_cnx = 0;
+        return FALSE;
+    }
+
+    if (parasite->flash_count % 2 == 0)
+    {
+        if (gtk_widget_get_visible(parasite->highlight_window))
+            gtk_widget_hide(parasite->highlight_window);
+        else
+            gtk_widget_show(parasite->highlight_window);
+    }
+
+    return TRUE;
+}
+
+void
+gtkparasite_flash_widget(ParasiteWindow *parasite, GtkWidget *widget)
+{
+    gint x, y, width, height;
+    GdkWindow *parent_window;
+
+    if (!gtk_widget_get_visible(widget) || !gtk_widget_get_mapped(widget))
+        return;
+
+    ensure_highlight_window(parasite);
+
+    parent_window = gtk_widget_get_parent_window(widget);
+    if (parent_window != NULL) {
+       GtkAllocation alloc;
+       gtk_widget_get_allocation(widget, &alloc);
+       
+        gdk_window_get_origin(parent_window, &x, &y);
+        x += alloc.x;
+        y += alloc.y;
+
+        width = alloc.width;
+        height = alloc.height;
+
+        gtk_window_move(GTK_WINDOW(parasite->highlight_window), x, y);
+        gtk_window_resize(GTK_WINDOW(parasite->highlight_window), width, height);
+        gtk_widget_show(parasite->highlight_window);
+
+        if (parasite->flash_cnx != 0)
+            g_source_remove(parasite->flash_cnx);
+
+        parasite->flash_count = 0;
+        parasite->flash_cnx = g_timeout_add(150, (GSourceFunc)on_flash_timeout,
+                                            parasite);
+    }
+}
+
+// vim: set et sw=4 ts=4:
diff --git a/modules/other/parasite/module.c b/modules/other/parasite/module.c
new file mode 100644
index 0000000..3c72060
--- /dev/null
+++ b/modules/other/parasite/module.c
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2008-2009  Christian Hammond
+ * Copyright (c) 2008-2009  David Trowbridge
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <glib.h>
+
+#include "config.h"
+#include "parasite.h"
+#include "python-hooks.h"
+
+
+void
+gtk_module_init(gint *argc, gchar ***argv)
+{
+#ifdef ENABLE_PYTHON
+    parasite_python_init();
+#endif
+
+    gtkparasite_window_create();
+}
+
+// vim: set et sw=4 ts=4:
diff --git a/modules/other/parasite/object-hierarchy.c b/modules/other/parasite/object-hierarchy.c
new file mode 100644
index 0000000..3dba5af
--- /dev/null
+++ b/modules/other/parasite/object-hierarchy.c
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2013 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "object-hierarchy.h"
+#include "parasite.h"
+
+enum
+{
+  COLUMN_OBJECT_NAME,
+  NUM_COLUMNS
+};
+
+struct _ParasiteObjectHierarchyPrivate
+{
+  GtkTreeStore *model;
+  GtkTreeView *tree;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ParasiteObjectHierarchy, parasite_objecthierarchy, GTK_TYPE_BOX)
+
+static void
+parasite_objecthierarchy_init (ParasiteObjectHierarchy *oh)
+{
+  oh->priv = parasite_objecthierarchy_get_instance_private (oh);
+}
+
+static void
+constructed (GObject *object)
+{
+  ParasiteObjectHierarchy *oh = PARASITE_OBJECTHIERARCHY (object);
+  GtkWidget *sw;
+  GtkCellRenderer *renderer;
+  GtkTreeViewColumn *column;
+
+  g_object_set (object,
+                "orientation", GTK_ORIENTATION_VERTICAL,
+                NULL);
+
+  sw = g_object_new (GTK_TYPE_SCROLLED_WINDOW,
+                     "expand", TRUE,
+                     NULL);
+  gtk_container_add (GTK_CONTAINER (object), sw);
+
+  oh->priv->model = gtk_tree_store_new (NUM_COLUMNS,
+                                        G_TYPE_STRING);   // COLUMN_OBJECT_NAME
+  oh->priv->tree = GTK_TREE_VIEW (gtk_tree_view_new_with_model (GTK_TREE_MODEL (oh->priv->model)));
+  gtk_container_add (GTK_CONTAINER (sw), GTK_WIDGET (oh->priv->tree));
+
+
+  renderer = gtk_cell_renderer_text_new ();
+  g_object_set (renderer, "scale", TREE_TEXT_SCALE, NULL);
+  column = gtk_tree_view_column_new_with_attributes ("Object Hierarchy", renderer,
+                                                     "text", COLUMN_OBJECT_NAME,
+                                                     NULL);
+  gtk_tree_view_append_column (GTK_TREE_VIEW (oh->priv->tree), column);
+}
+
+static void
+parasite_objecthierarchy_class_init (ParasiteObjectHierarchyClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed  = constructed;
+}
+
+GtkWidget *
+parasite_objecthierarchy_new (void)
+{
+  return GTK_WIDGET (g_object_new (PARASITE_TYPE_OBJECTHIERARCHY,
+                                   NULL));
+}
+
+void
+parasite_objecthierarchy_set_object (ParasiteObjectHierarchy *oh, GObject *object)
+{
+  GObjectClass *klass = G_OBJECT_GET_CLASS (object);
+  const gchar *class_name;
+  GtkTreeIter iter, parent;
+  GSList *list = NULL, *l;
+
+  gtk_tree_store_clear (oh->priv->model);
+
+  do
+    {
+      class_name = G_OBJECT_CLASS_NAME (klass);
+      list = g_slist_append (list, (gpointer)class_name);
+    }
+  while ((klass = g_type_class_peek_parent (klass))) ;
+  list = g_slist_reverse (list);
+
+  for (l = list; l; l = l->next)
+    {
+      gtk_tree_store_append (oh->priv->model, &iter, l == list ? NULL : &parent);
+      gtk_tree_store_set (oh->priv->model, &iter,
+                          COLUMN_OBJECT_NAME, l->data,
+                          -1);
+      parent = iter;
+    }
+
+  g_slist_free (list);
+
+  gtk_tree_view_expand_all (oh->priv->tree);
+  gtk_tree_selection_select_iter (gtk_tree_view_get_selection (oh->priv->tree),
+                                  &iter);
+}
+
+// vim: set et sw=4 ts=4:
diff --git a/modules/other/parasite/object-hierarchy.h b/modules/other/parasite/object-hierarchy.h
new file mode 100644
index 0000000..6eab63a
--- /dev/null
+++ b/modules/other/parasite/object-hierarchy.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2013 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef _GTKPARASITE_OBJECTHIERARCHY_H_
+#define _GTKPARASITE_OBJECTHIERARCHY_H_
+
+#include <gtk/gtk.h>
+
+#define PARASITE_TYPE_OBJECTHIERARCHY            (parasite_objecthierarchy_get_type())
+#define PARASITE_OBJECTHIERARCHY(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), 
PARASITE_TYPE_OBJECTHIERARCHY, ParasiteObjectHierarchy))
+#define PARASITE_OBJECTHIERARCHY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), 
PARASITE_TYPE_OBJECTHIERARCHY, ParasiteObjectHierarchyClass))
+#define PARASITE_IS_OBJECTHIERARCHY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), 
PARASITE_TYPE_OBJECTHIERARCHY))
+#define PARASITE_IS_OBJECTHIERARCHY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), 
PARASITE_TYPE_OBJECTHIERARCHY))
+#define PARASITE_OBJECTHIERARCHY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), 
PARASITE_TYPE_OBJECTHIERARCHY, ParasiteObjectHierarchyClass))
+
+
+typedef struct _ParasiteObjectHierarchyPrivate ParasiteObjectHierarchyPrivate;
+
+typedef struct _ParasiteObjectHierarchy {
+   GtkBox parent;
+   ParasiteObjectHierarchyPrivate *priv;
+} ParasiteObjectHierarchy;
+
+typedef struct _ParasiteObjectHierarchyClass {
+   GtkBoxClass parent;
+} ParasiteObjectHierarchyClass;
+
+G_BEGIN_DECLS
+
+GType     parasite_objecthierarchy_get_type   (void);
+GtkWidget *parasite_objecthierarchy_new       (void);
+void      parasite_objecthierarchy_set_object (ParasiteObjectHierarchy *editor,
+                                               GObject                 *object);
+
+G_END_DECLS
+
+#endif // _GTKPARASITE_OBJECTHIERARCHY_H_
+
+// vim: set et sw=4 ts=4:
diff --git a/modules/other/parasite/parasite.h b/modules/other/parasite/parasite.h
new file mode 100644
index 0000000..8ff6d09
--- /dev/null
+++ b/modules/other/parasite/parasite.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2008-2009  Christian Hammond
+ * Copyright (c) 2008-2009  David Trowbridge
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef _GTKPARASITE_H_
+#define _GTKPARASITE_H_
+
+
+#include <gtk/gtk.h>
+
+
+#define TREE_TEXT_SCALE 0.8
+#define TREE_CHECKBOX_SIZE (gint)(0.8 * 13)
+
+
+typedef struct
+{
+    GtkWidget *window;
+    GtkWidget *widget_tree;
+    GtkWidget *prop_list;
+    GtkWidget *python_shell;
+    GtkWidget *button_path;
+    GtkWidget *classes_list;
+    GtkWidget *widget_css_editor;
+    GtkWidget *oh;
+
+    GtkWidget *grab_window;
+    GtkWidget *highlight_window;
+
+    GtkWidget *widget_popup;
+
+    GdkWindow *selected_window;
+
+    gboolean edit_mode_enabled;
+
+    int flash_count;
+    int flash_cnx;
+
+} ParasiteWindow;
+
+
+void gtkparasite_window_create();
+
+void gtkparasite_flash_widget(ParasiteWindow *parasite, GtkWidget *widget);
+
+GtkWidget *gtkparasite_inspect_button_new(ParasiteWindow *parasite);
+
+
+#endif // _GTKPARASITE_H_
+
+// vim: set et sw=4 ts=4:
diff --git a/modules/other/parasite/prop-list.c b/modules/other/parasite/prop-list.c
new file mode 100644
index 0000000..90d395e
--- /dev/null
+++ b/modules/other/parasite/prop-list.c
@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2008-2009  Christian Hammond
+ * Copyright (c) 2008-2009  David Trowbridge
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "parasite.h"
+#include "prop-list.h"
+#include "property-cell-renderer.h"
+
+
+enum
+{
+  COLUMN_NAME,
+  COLUMN_VALUE,
+  COLUMN_DEFINED_AT,
+  COLUMN_OBJECT,
+  COLUMN_TOOLTIP,
+  COLUMN_RO,
+  NUM_COLUMNS
+};
+
+enum
+{
+  PROP_0,
+  PROP_WIDGET_TREE
+};
+
+struct _ParasitePropListPrivate
+{
+  GObject *object;
+  GtkListStore *model;
+  GHashTable *prop_iters;
+  GList *signal_cnxs;
+  GtkWidget *widget_tree;
+  GtkTreeViewColumn *property_column;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ParasitePropList, parasite_proplist, GTK_TYPE_TREE_VIEW)
+
+static void
+parasite_proplist_init (ParasitePropList *pl)
+{
+  pl->priv = parasite_proplist_get_instance_private (pl);
+}
+
+static gboolean
+query_tooltip_cb (GtkWidget        *widget,
+                             gint             x,
+                             gint             y,
+                             gboolean         keyboard_tip,
+                             GtkTooltip       *tooltip,
+                             ParasitePropList *pl)
+{
+  GtkTreeIter iter;
+  GtkTreeView *tree_view = GTK_TREE_VIEW (widget);
+  GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
+  GtkTreePath *path = NULL;
+  gchar *tooltip_text;
+
+  if (!gtk_tree_view_get_tooltip_context (tree_view,
+                                          &x,
+                                          &y,
+                                          keyboard_tip,
+                                          &model,
+                                          &path,
+                                          &iter))
+    return FALSE;
+
+  gtk_tree_model_get (model, &iter, COLUMN_TOOLTIP, &tooltip_text, -1);
+  gtk_tooltip_set_text (tooltip, tooltip_text);
+
+  gtk_tree_view_set_tooltip_cell (tree_view,
+                                  tooltip,
+                                  path,
+                                  pl->priv->property_column,
+                                  NULL);
+
+  gtk_tree_path_free (path);
+  g_free (tooltip_text);
+
+  return TRUE;
+}
+
+static void
+draw_columns (GtkTreeViewColumn *column,
+              GtkCellRenderer   *renderer,
+              GtkTreeModel      *model,
+              GtkTreeIter       *iter,
+              ParasitePropList  *pl)
+{
+  gboolean ro;
+
+  gtk_tree_model_get (model, iter, COLUMN_RO, &ro, -1);
+  if (ro)
+    {
+      g_object_set (renderer, "foreground", "#a7aba7", NULL);
+    }
+  else
+    {
+      g_object_set (renderer, "foreground-set", FALSE, NULL);
+    }
+}
+
+static void
+constructed (GObject *object)
+{
+  ParasitePropList *pl = PARASITE_PROPLIST (object);
+  GtkCellRenderer *renderer;
+  GtkTreeViewColumn *column;
+
+  pl->priv->prop_iters = g_hash_table_new_full (g_str_hash,
+                                                g_str_equal,
+                                                NULL,
+                                                (GDestroyNotify) gtk_tree_iter_free);
+
+  pl->priv->model = gtk_list_store_new(NUM_COLUMNS,
+                                       G_TYPE_STRING,  // COLUMN_NAME
+                                       G_TYPE_STRING,  // COLUMN_VALUE
+                                       G_TYPE_STRING,  // COLUMN_DEFINED_AT
+                                       G_TYPE_OBJECT,  // COLUMN_OBJECT
+                                       G_TYPE_STRING,  // COLUMN_TOOLTIP
+                                       G_TYPE_BOOLEAN);// COLUMN_RO
+  gtk_tree_view_set_model (GTK_TREE_VIEW (pl),
+                           GTK_TREE_MODEL (pl->priv->model));
+
+  renderer = gtk_cell_renderer_text_new();
+  g_object_set (renderer, "scale", TREE_TEXT_SCALE, NULL);
+  pl->priv->property_column = gtk_tree_view_column_new_with_attributes ("Property",
+                                                                        renderer,
+                                                                        "text", COLUMN_NAME,
+                                                                        NULL);
+  gtk_tree_view_append_column (GTK_TREE_VIEW (pl), pl->priv->property_column);
+  g_object_set (pl->priv->property_column,
+                "resizable", TRUE,
+                "sort-order", GTK_SORT_ASCENDING,
+                "sort-column-id", COLUMN_NAME,
+                NULL);
+ gtk_tree_view_column_set_cell_data_func (pl->priv->property_column,
+                                          renderer,
+                                          (GtkTreeCellDataFunc) draw_columns,
+                                          pl,
+                                          NULL);
+
+  renderer = parasite_property_cell_renderer_new ();
+  g_object_set_data (G_OBJECT (renderer), "parasite-widget-tree", pl->priv->widget_tree);
+  g_object_set (renderer,
+                "scale", TREE_TEXT_SCALE,
+                "editable", TRUE,
+                NULL);
+  column = gtk_tree_view_column_new_with_attributes ("Value", renderer,
+                                                     "text", COLUMN_VALUE,
+                                                     "object", COLUMN_OBJECT,
+                                                     "name", COLUMN_NAME,
+                                                      NULL);
+  gtk_tree_view_append_column (GTK_TREE_VIEW (pl), column);
+  gtk_tree_view_column_set_resizable (column, TRUE);
+
+  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (pl->priv->model),
+                                        COLUMN_NAME,
+                                        GTK_SORT_ASCENDING);
+ gtk_tree_view_column_set_cell_data_func (column,
+                                          renderer,
+                                          (GtkTreeCellDataFunc) draw_columns,
+                                          pl,
+                                          NULL);
+
+  renderer = gtk_cell_renderer_text_new ();
+  g_object_set (renderer, "scale", TREE_TEXT_SCALE, NULL);
+  column = gtk_tree_view_column_new_with_attributes ("Defined at",
+                                                     renderer,
+                                                     "text", COLUMN_DEFINED_AT,
+                                                     NULL);
+  gtk_tree_view_append_column (GTK_TREE_VIEW (pl), column);
+  gtk_tree_view_column_set_cell_data_func (column,
+                                           renderer,
+                                           (GtkTreeCellDataFunc) draw_columns,
+                                           pl,
+                                           NULL);
+
+  g_object_set (object, "has-tooltip", TRUE, NULL);
+  g_signal_connect (object, "query-tooltip", G_CALLBACK (query_tooltip_cb), pl);
+}
+
+static void
+get_property (GObject    *object,
+              guint      param_id,
+              GValue     *value,
+              GParamSpec *pspec)
+{
+  ParasitePropList *pl = PARASITE_PROPLIST (object);
+
+  switch (param_id)
+    {
+      case PROP_WIDGET_TREE:
+        g_value_take_object (value, pl->priv->widget_tree);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+        break;
+    }
+}
+
+static void
+set_property (GObject      *object,
+              guint        param_id,
+              const GValue *value,
+              GParamSpec   *pspec)
+{
+  ParasitePropList *pl = PARASITE_PROPLIST (object);
+
+  switch (param_id)
+    {
+      case PROP_WIDGET_TREE:
+        pl->priv->widget_tree = g_value_get_object (value);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+        break;
+    }
+}
+
+static void
+parasite_proplist_class_init (ParasitePropListClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+  object_class->get_property = get_property;
+  object_class->set_property = set_property;
+  object_class->constructed  = constructed;
+
+  g_object_class_install_property (object_class,
+                                   PROP_WIDGET_TREE,
+                                   g_param_spec_object ("widget-tree",
+                                                         "Widget Tree",
+                                                         "Widget tree",
+                                                         GTK_TYPE_WIDGET,
+                                                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+parasite_prop_list_update_prop (ParasitePropList *pl,
+                                GtkTreeIter *iter,
+                                GParamSpec *prop)
+{
+  GValue gvalue = {0};
+  char *value;
+
+  g_value_init(&gvalue, prop->value_type);
+  g_object_get_property (pl->priv->object, prop->name, &gvalue);
+
+  if (G_VALUE_HOLDS_ENUM (&gvalue))
+    {
+      GEnumClass *enum_class = G_PARAM_SPEC_ENUM(prop)->enum_class;
+      GEnumValue *enum_value = g_enum_get_value(enum_class, g_value_get_enum(&gvalue));
+
+      value = g_strdup (enum_value->value_name);
+    }
+  else
+    {
+      value = g_strdup_value_contents(&gvalue);
+    }
+
+  gtk_list_store_set (pl->priv->model, iter,
+                      COLUMN_NAME, prop->name,
+                      COLUMN_VALUE, value,
+                      COLUMN_DEFINED_AT, g_type_name (prop->owner_type),
+                      COLUMN_OBJECT, pl->priv->object,
+                      COLUMN_TOOLTIP, g_param_spec_get_blurb (prop),
+                      COLUMN_RO, !(prop->flags & G_PARAM_WRITABLE),
+                      -1);
+
+  g_free (value);
+  g_value_unset (&gvalue);
+}
+
+static void
+parasite_proplist_prop_changed_cb (GObject *pspec,
+                                   GParamSpec *prop,
+                                   ParasitePropList *pl)
+{
+  GtkTreeIter *iter = g_hash_table_lookup(pl->priv->prop_iters, prop->name);
+
+  if (iter != NULL)
+    parasite_prop_list_update_prop (pl, iter, prop);
+}
+
+GtkWidget *
+parasite_proplist_new (GtkWidget *widget_tree)
+{
+    return g_object_new (PARASITE_TYPE_PROPLIST,
+                         "widget-tree", widget_tree,
+                         NULL);
+}
+
+void
+parasite_proplist_set_object (ParasitePropList* pl, GObject *object)
+{
+  GtkTreeIter iter;
+  GParamSpec **props;
+  guint num_properties;
+  guint i;
+  GList *l;
+
+  pl->priv->object = object;
+
+  for (l = pl->priv->signal_cnxs; l != NULL; l = l->next)
+    {
+      gulong id = GPOINTER_TO_UINT (l->data);
+
+      if (g_signal_handler_is_connected (object, id))
+        g_signal_handler_disconnect (object, id);
+    }
+
+  g_list_free (pl->priv->signal_cnxs);
+  pl->priv->signal_cnxs = NULL;
+
+  g_hash_table_remove_all (pl->priv->prop_iters);
+  gtk_list_store_clear (pl->priv->model);
+
+  props = g_object_class_list_properties (G_OBJECT_GET_CLASS (object), &num_properties);
+  for (i = 0; i < num_properties; i++)
+    {
+      GParamSpec *prop = props[i];
+      char *signal_name;
+
+      if (! (prop->flags & G_PARAM_READABLE))
+        continue;
+
+      gtk_list_store_append (pl->priv->model, &iter);
+      parasite_prop_list_update_prop (pl, &iter, prop);
+
+      g_hash_table_insert (pl->priv->prop_iters, (gpointer) prop->name, gtk_tree_iter_copy (&iter));
+
+      /* Listen for updates */
+      signal_name = g_strdup_printf ("notify::%s", prop->name);
+
+      pl->priv->signal_cnxs =
+            g_list_prepend (pl->priv->signal_cnxs, GINT_TO_POINTER(
+                g_signal_connect(object, signal_name,
+                                 G_CALLBACK (parasite_proplist_prop_changed_cb),
+                                 pl)));
+
+        g_free (signal_name);
+    }
+}
+
+
+// vim: set et sw=4 ts=4:
diff --git a/modules/other/parasite/prop-list.h b/modules/other/parasite/prop-list.h
new file mode 100644
index 0000000..d5eed22
--- /dev/null
+++ b/modules/other/parasite/prop-list.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2008-2009  Christian Hammond
+ * Copyright (c) 2008-2009  David Trowbridge
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef _GTKPARASITE_PROPLIST_H_
+#define _GTKPARASITE_PROPLIST_H_
+
+
+#include <gtk/gtk.h>
+
+#define PARASITE_TYPE_PROPLIST            (parasite_proplist_get_type())
+#define PARASITE_PROPLIST(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), PARASITE_TYPE_PROPLIST, 
ParasitePropList))
+#define PARASITE_PROPLIST_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), PARASITE_TYPE_PROPLIST, 
ParasitePropListClass))
+#define PARASITE_IS_PROPLIST(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), PARASITE_TYPE_PROPLIST))
+#define PARASITE_IS_PROPLIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PARASITE_TYPE_PROPLIST))
+#define PARASITE_PROPLIST_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), PARASITE_TYPE_PROPLIST, 
ParasitePropListClass))
+
+
+typedef struct _ParasitePropListPrivate ParasitePropListPrivate;
+
+typedef struct _ParasitePropList {
+   GtkTreeView parent;
+   ParasitePropListPrivate *priv;
+} ParasitePropList;
+
+typedef struct _ParasitePropListClass {
+   GtkTreeViewClass parent;
+} ParasitePropListClass;
+
+
+G_BEGIN_DECLS
+
+GType      parasite_proplist_get_type   (void);
+GtkWidget *parasite_proplist_new        (GtkWidget *widget_tree);
+void       parasite_proplist_set_object (ParasitePropList *proplist,
+                                         GObject          *object);
+
+G_END_DECLS
+
+#endif // _GTKPARASITE_PROPLIST_H_
+
+// vim: set et sw=4 ts=4:
diff --git a/modules/other/parasite/property-cell-renderer.c b/modules/other/parasite/property-cell-renderer.c
new file mode 100644
index 0000000..16a7d0f
--- /dev/null
+++ b/modules/other/parasite/property-cell-renderer.c
@@ -0,0 +1,410 @@
+/*
+ * Copyright (c) 2008-2009  Christian Hammond
+ * Copyright (c) 2008-2009  David Trowbridge
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "parasite.h"
+#include "property-cell-renderer.h"
+#include "widget-tree.h"
+
+struct _ParasitePropertyCellRendererPrivate
+{
+  GObject *object;
+  char *name;
+};
+
+enum
+{
+  PROP_0,
+  PROP_OBJECT,
+  PROP_NAME
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ParasitePropertyCellRenderer, parasite_property_cell_renderer, 
GTK_TYPE_CELL_RENDERER_TEXT);
+
+static void
+parasite_property_cell_renderer_init(ParasitePropertyCellRenderer *renderer)
+{
+  renderer->priv = parasite_property_cell_renderer_get_instance_private (renderer);
+}
+
+static void
+get_property (GObject *object,
+              guint param_id,
+              GValue *value,
+              GParamSpec *pspec)
+{
+  ParasitePropertyCellRenderer *r = PARASITE_PROPERTY_CELL_RENDERER (object);
+
+  switch (param_id)
+    {
+      case PROP_OBJECT:
+        g_value_set_object(value, r->priv->object);
+        break;
+
+      case PROP_NAME:
+        g_value_set_string(value, r->priv->name);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
+        break;
+    }
+}
+
+static void
+set_property (GObject *object,
+              guint param_id,
+              const GValue *value,
+              GParamSpec *pspec)
+{
+  ParasitePropertyCellRenderer *r = PARASITE_PROPERTY_CELL_RENDERER (object);
+
+  switch (param_id)
+    {
+      case PROP_OBJECT:
+        r->priv->object = g_value_get_object (value);
+        break;
+
+      case PROP_NAME:
+        g_free (r->priv->name);
+        r->priv->name = g_value_dup_string (value);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
+        break;
+    }
+}
+
+static void
+stop_editing(GtkCellEditable *editable, GtkCellRenderer *renderer)
+{
+    GObject *object;
+    const char *name;
+    GValue gvalue = {0};
+    GParamSpec *prop;
+
+    object = g_object_get_data(G_OBJECT(editable), "_prop_object");
+    name   = g_object_get_data(G_OBJECT(editable), "_prop_name");
+
+    prop = g_object_class_find_property(G_OBJECT_GET_CLASS(object), name);
+    g_value_init(&gvalue, prop->value_type);
+
+    if (GTK_IS_ENTRY(editable))
+    {
+        gboolean canceled;
+        g_object_get(editable, "editing_canceled", &canceled, NULL);
+        gtk_cell_renderer_stop_editing(renderer, canceled);
+
+        if (canceled)
+            return;
+
+        if (GTK_IS_SPIN_BUTTON(editable))
+        {
+            double value =
+                g_ascii_strtod(gtk_entry_get_text(GTK_ENTRY(editable)), NULL);
+
+            if (G_IS_PARAM_SPEC_INT(prop))
+                g_value_set_int(&gvalue, (gint)value);
+            else if G_IS_PARAM_SPEC_UINT(prop)
+                g_value_set_uint(&gvalue, (guint)value);
+            else if G_IS_PARAM_SPEC_INT64(prop)
+                g_value_set_int64(&gvalue, (gint64)value);
+            else if G_IS_PARAM_SPEC_UINT64(prop)
+                g_value_set_uint64(&gvalue, (guint64)value);
+            else if G_IS_PARAM_SPEC_LONG(prop)
+                g_value_set_long(&gvalue, (glong)value);
+            else if G_IS_PARAM_SPEC_ULONG(prop)
+                g_value_set_ulong(&gvalue, (gulong)value);
+            else if G_IS_PARAM_SPEC_DOUBLE(prop)
+                g_value_set_double(&gvalue, (gdouble)value);
+            else
+                return;
+        }
+        else
+        {
+            g_value_set_string(&gvalue,
+                               gtk_entry_get_text(GTK_ENTRY(editable)));
+        }
+    }
+    else if (GTK_IS_COMBO_BOX(editable))
+    {
+        // We have no way of getting the canceled state for a GtkComboBox.
+        gtk_cell_renderer_stop_editing(renderer, FALSE);
+
+        if (G_IS_PARAM_SPEC_BOOLEAN(prop))
+        {
+            g_value_set_boolean(&gvalue,
+                gtk_combo_box_get_active(GTK_COMBO_BOX(editable)) == 1);
+        }
+        else if (G_IS_PARAM_SPEC_ENUM(prop))
+        {
+            char *enum_name = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (editable));
+            GEnumClass *enum_class;
+            GEnumValue *enum_value;
+
+            if (enum_name == NULL)
+                return;
+
+            enum_class = G_PARAM_SPEC_ENUM(prop)->enum_class;
+            enum_value = g_enum_get_value_by_name(enum_class, enum_name);
+            g_value_set_enum(&gvalue, enum_value->value);
+
+            g_free(enum_name);
+        }
+    }
+
+    g_object_set_property(object, name, &gvalue);
+    g_value_unset(&gvalue);
+}
+
+static GtkCellEditable *
+start_editing (GtkCellRenderer *renderer,
+               GdkEvent *event,
+               GtkWidget *widget,
+               const gchar *path,
+               const GdkRectangle *background_area,
+               const GdkRectangle *cell_area,
+               GtkCellRendererState flags)
+{
+    PangoFontDescription *font_desc;
+    GtkCellEditable *editable = NULL;
+    GObject *object;
+    const char *name;
+    GValue gvalue = {0};
+    GParamSpec *prop;
+
+    g_object_get(renderer,
+                 "object", &object,
+                 "name", &name,
+                 NULL);
+
+    prop = g_object_class_find_property(G_OBJECT_GET_CLASS(object), name);
+    g_value_init(&gvalue, prop->value_type);
+    g_object_get_property(object, name, &gvalue);
+
+    if (G_VALUE_HOLDS_OBJECT (&gvalue))
+      {
+        ParasiteWidgetTree *widget_tree = g_object_get_data (G_OBJECT (renderer), "parasite-widget-tree");
+        GObject *prop_object = g_value_get_object (&gvalue);
+        GtkTreeIter iter;
+
+        if (prop_object)
+          {
+            /* First check if the value is already in the tree (happens with 'parent' for instance) */
+            if (parasite_widget_tree_find_object (widget_tree, prop_object, &iter))
+              {
+                parasite_widget_tree_select_object (widget_tree, prop_object);
+              }
+            else if (parasite_widget_tree_find_object (widget_tree, object, &iter))
+              {
+                parasite_widget_tree_append_object (widget_tree, prop_object, &iter);
+                parasite_widget_tree_select_object (widget_tree, prop_object);
+              }
+            else
+              {
+                 g_warning ("Parasite: couldn't find the widget in the tree");
+              }
+          }
+        g_value_unset (&gvalue);
+        return NULL;
+      }
+    else
+      {
+        if (!(prop->flags & G_PARAM_WRITABLE))
+          return NULL;
+
+     if (G_VALUE_HOLDS_ENUM(&gvalue) || G_VALUE_HOLDS_BOOLEAN(&gvalue))
+    {
+        GtkWidget *combobox = gtk_combo_box_text_new ();
+        gtk_widget_show(combobox);
+        g_object_set(G_OBJECT(combobox), "has-frame", FALSE, NULL);
+        GList *renderers;
+
+        if (G_VALUE_HOLDS_BOOLEAN(&gvalue))
+        {
+            gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combobox), "FALSE");
+            gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combobox), "TRUE");
+
+            gtk_combo_box_set_active(GTK_COMBO_BOX (combobox), g_value_get_boolean (&gvalue) ? 1 : 0);
+        }
+        else if (G_VALUE_HOLDS_ENUM(&gvalue))
+        {
+            gint value = g_value_get_enum(&gvalue);
+            GEnumClass *enum_class = G_PARAM_SPEC_ENUM(prop)->enum_class;
+            guint i;
+
+            for (i = 0; i < enum_class->n_values; i++)
+            {
+                GEnumValue *enum_value = &enum_class->values[i];
+
+                gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combobox),
+                                                enum_value->value_name);
+
+                if (enum_value->value == value)
+                    gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), i);
+            }
+
+        }
+
+        renderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(combobox));
+        g_object_set(G_OBJECT(renderers->data), "scale", TREE_TEXT_SCALE, NULL);
+        g_list_free(renderers);
+
+        editable = GTK_CELL_EDITABLE(combobox);
+    }
+    else if (G_VALUE_HOLDS_STRING(&gvalue))
+    {
+        GtkWidget *entry = gtk_entry_new();
+        gtk_widget_show(entry);
+        gtk_entry_set_text(GTK_ENTRY(entry), g_value_get_string(&gvalue));
+
+        editable = GTK_CELL_EDITABLE(entry);
+    }
+    else if (G_VALUE_HOLDS_INT(&gvalue)    ||
+             G_VALUE_HOLDS_UINT(&gvalue)   ||
+             G_VALUE_HOLDS_INT64(&gvalue)  ||
+             G_VALUE_HOLDS_UINT64(&gvalue) ||
+             G_VALUE_HOLDS_LONG(&gvalue)   ||
+             G_VALUE_HOLDS_ULONG(&gvalue)  ||
+             G_VALUE_HOLDS_DOUBLE(&gvalue))
+    {
+        double min, max, value;
+        GtkWidget *spinbutton;
+        guint digits = 0;
+
+        if (G_VALUE_HOLDS_INT(&gvalue))
+        {
+            GParamSpecInt *paramspec = G_PARAM_SPEC_INT(prop);
+            min = paramspec->minimum;
+            max = paramspec->maximum;
+            value = g_value_get_int(&gvalue);
+        }
+        else if (G_VALUE_HOLDS_UINT(&gvalue))
+        {
+            GParamSpecUInt *paramspec = G_PARAM_SPEC_UINT(prop);
+            min = paramspec->minimum;
+            max = paramspec->maximum;
+            value = g_value_get_uint(&gvalue);
+        }
+        else if (G_VALUE_HOLDS_INT64(&gvalue))
+        {
+            GParamSpecInt64 *paramspec = G_PARAM_SPEC_INT64(prop);
+            min = paramspec->minimum;
+            max = paramspec->maximum;
+            value = g_value_get_int64(&gvalue);
+        }
+        else if (G_VALUE_HOLDS_UINT64(&gvalue))
+        {
+            GParamSpecUInt64 *paramspec = G_PARAM_SPEC_UINT64(prop);
+            min = paramspec->minimum;
+            max = paramspec->maximum;
+            value = g_value_get_uint64(&gvalue);
+        }
+        else if (G_VALUE_HOLDS_LONG(&gvalue))
+        {
+            GParamSpecLong *paramspec = G_PARAM_SPEC_LONG(prop);
+            min = paramspec->minimum;
+            max = paramspec->maximum;
+            value = g_value_get_long(&gvalue);
+        }
+        else if (G_VALUE_HOLDS_ULONG(&gvalue))
+        {
+            GParamSpecULong *paramspec = G_PARAM_SPEC_ULONG(prop);
+            min = paramspec->minimum;
+            max = paramspec->maximum;
+            value = g_value_get_ulong(&gvalue);
+        }
+        else if (G_VALUE_HOLDS_DOUBLE(&gvalue))
+        {
+            GParamSpecDouble *paramspec = G_PARAM_SPEC_DOUBLE(prop);
+            min = paramspec->minimum;
+            max = paramspec->maximum;
+            value = g_value_get_double(&gvalue);
+            digits = 2;
+        }
+        else
+        {
+            // Shouldn't really be able to happen.
+            return NULL;
+        }
+
+        spinbutton = gtk_spin_button_new_with_range(min, max, 1);
+        gtk_widget_show(spinbutton);
+        gtk_spin_button_set_value(GTK_SPIN_BUTTON(spinbutton), value);
+        gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spinbutton), digits);
+
+        editable = GTK_CELL_EDITABLE(spinbutton);
+    }
+  }
+    g_value_unset(&gvalue);
+
+    if (!editable)
+      return NULL;
+
+    font_desc = pango_font_description_new();
+    pango_font_description_set_size(font_desc, 8 * PANGO_SCALE);
+    gtk_widget_override_font (GTK_WIDGET (editable), font_desc);
+    pango_font_description_free(font_desc);
+
+    g_signal_connect(editable, "editing_done", G_CALLBACK (stop_editing), renderer);
+    g_object_set_data_full (G_OBJECT (editable), "_prop_name", g_strdup (name), g_free);
+    g_object_set_data (G_OBJECT (editable), "_prop_object", object);
+
+    return editable;
+}
+
+static void
+parasite_property_cell_renderer_class_init (ParasitePropertyCellRendererClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass);
+
+  object_class->get_property = get_property;
+  object_class->set_property = set_property;
+
+  cell_class->start_editing = start_editing;
+
+  g_object_class_install_property(object_class,
+        PROP_OBJECT,
+        g_param_spec_object ("object",
+                             "Object",
+                             "The object owning the property",
+                             G_TYPE_OBJECT,
+                             G_PARAM_READWRITE));
+
+  g_object_class_install_property(object_class,
+        PROP_NAME,
+        g_param_spec_string ("name",
+                             "Name",
+                             "The property name",
+                             NULL,
+                             G_PARAM_READWRITE));
+}
+
+GtkCellRenderer *
+parasite_property_cell_renderer_new(void)
+{
+    return g_object_new(PARASITE_TYPE_PROPERTY_CELL_RENDERER, NULL);
+}
+
+
+// vim: set et ts=4:
diff --git a/modules/other/parasite/property-cell-renderer.h b/modules/other/parasite/property-cell-renderer.h
new file mode 100644
index 0000000..cc88def
--- /dev/null
+++ b/modules/other/parasite/property-cell-renderer.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2008-2009  Christian Hammond
+ * Copyright (c) 2008-2009  David Trowbridge
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef _GTKPARASITE_PROPERTY_CELL_RENDERER_H_
+#define _GTKPARASITE_PROPERTY_CELL_RENDERER_H_
+
+
+#include <gtk/gtk.h>
+
+
+#define PARASITE_TYPE_PROPERTY_CELL_RENDERER            (parasite_property_cell_renderer_get_type())
+#define PARASITE_PROPERTY_CELL_RENDERER(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), 
PARASITE_TYPE_PROPERTY_CELL_RENDERER, ParasitePropertyCellRenderer))
+#define PARASITE_PROPERTY_CELL_RENDERER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), 
PARASITE_TYPE_PROPERTY_CELL_RENDERER, ParasitePropertyCellRendererClass))
+#define PARASITE_IS_PROPERTY_CELL_RENDERER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), 
PARASITE_TYPE_PROPERTY_CELL_RENDERER))
+#define PARASITE_IS_PROPERTY_CELL_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), 
PARASITE_TYPE_PROPERTY_CELL_RENDERER))
+#define PARASITE_PROPERTY_CELL_RENDERER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), 
PARASITE_TYPE_PROPERTY_CELL_RENDERER, ParasitePropertyCellRendererClass))
+
+typedef struct _ParasitePropertyCellRendererPrivate ParasitePropertyCellRendererPrivate;
+
+typedef struct
+{
+  GtkCellRendererText parent;
+  ParasitePropertyCellRendererPrivate *priv;
+} ParasitePropertyCellRenderer;
+
+typedef struct
+{
+   GtkCellRendererTextClass parent;
+
+   // Padding for future expansion
+   void (*reserved0)(void);
+   void (*reserved1)(void);
+   void (*reserved2)(void);
+   void (*reserved3)(void);
+
+} ParasitePropertyCellRendererClass;
+
+
+G_BEGIN_DECLS
+
+
+GType parasite_property_cell_renderer_get_type();
+GtkCellRenderer *parasite_property_cell_renderer_new();
+
+
+G_END_DECLS
+
+
+#endif // _GTKPARASITE_PROPERTY_CELL_RENDERER_H_
+
+// vim: set et sw=4 ts=4:
diff --git a/modules/other/parasite/python-hooks.c b/modules/other/parasite/python-hooks.c
new file mode 100644
index 0000000..2148ec3
--- /dev/null
+++ b/modules/other/parasite/python-hooks.c
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) 2008-2009  Christian Hammond
+ * Copyright (c) 2008-2009  David Trowbridge
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <dlfcn.h>
+#include "config.h"
+#include <signal.h>
+
+#ifdef ENABLE_PYTHON
+# include <Python.h>
+# include <pygobject.h>
+#endif
+
+#include "python-hooks.h"
+
+static gboolean python_enabled = FALSE;
+
+#ifdef ENABLE_PYTHON
+static GString *captured_stdout = NULL;
+static GString *captured_stderr = NULL;
+
+
+static PyObject *
+capture_stdout(PyObject *self, PyObject *args)
+{
+    char *str = NULL;
+
+    if (!PyArg_ParseTuple(args, "s", &str))
+        return NULL;
+
+    g_string_append(captured_stdout, str);
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyObject *
+capture_stderr(PyObject *self, PyObject *args)
+{
+    char *str = NULL;
+
+    if (!PyArg_ParseTuple(args, "s", &str))
+        return NULL;
+
+    g_string_append(captured_stderr, str);
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyObject *
+wrap_gobj(PyObject *self, PyObject *args)
+{
+    void *addr;
+    GObject *obj;
+
+    if (!PyArg_ParseTuple(args, "l", &addr))
+        return NULL;
+
+    if (!G_IS_OBJECT(addr))
+        return NULL; // XXX
+
+    obj = G_OBJECT(addr);
+
+    if (!obj)
+        return NULL; // XXX
+
+    return pygobject_new(obj);
+}
+
+static PyMethodDef parasite_python_methods[] = {
+    {"capture_stdout", capture_stdout, METH_VARARGS, "Captures stdout"},
+    {"capture_stderr", capture_stderr, METH_VARARGS, "Captures stderr"},
+    {"gobj", wrap_gobj, METH_VARARGS, "Wraps a C GObject"},
+    {NULL, NULL, 0, NULL}
+};
+
+
+static gboolean
+is_blacklisted(void)
+{
+    const char *prgname = g_get_prgname();
+
+    return (!strcmp(prgname, "gimp"));
+}
+#endif // ENABLE_PYTHON
+
+void
+parasite_python_init(void)
+{
+#ifdef ENABLE_PYTHON
+    int res;
+    struct sigaction old_sigint;
+
+    if (is_blacklisted())
+        return;
+
+    /* This prevents errors such as "undefined symbol: PyExc_ImportError" */
+    if (!dlopen(PYTHON_SHARED_LIB, RTLD_NOW | RTLD_GLOBAL))
+    {
+        g_error("%s\n", dlerror());
+        return;
+    }
+
+    captured_stdout = g_string_new("");
+    captured_stderr = g_string_new("");
+
+    /* Back up and later restore SIGINT so Python doesn't steal it from us. */
+    res = sigaction(SIGINT, NULL, &old_sigint);
+
+    if (!Py_IsInitialized())
+        Py_Initialize();
+
+    res = sigaction(SIGINT, &old_sigint, NULL);
+
+    Py_InitModule("parasite", parasite_python_methods);
+    PyRun_SimpleString(
+        "import parasite\n"
+        "import sys\n"
+        "\n"
+        "class StdoutCatcher:\n"
+        "    def write(self, str):\n"
+        "        parasite.capture_stdout(str)\n"
+        "\n"
+        "class StderrCatcher:\n"
+        "    def write(self, str):\n"
+        "        parasite.capture_stderr(str)\n"
+        "\n"
+    );
+
+    if (!pygobject_init(-1, -1, -1))
+    {
+        fprintf(stderr, "Error initializing pygobject support.\n");
+        PyErr_Print();
+        return;
+    }
+
+    char *argv[] = { "", NULL };
+    PySys_SetArgv(0, argv);
+
+    if (!PyImport_ImportModule("gi._gobject"))
+      {
+        PyErr_SetString(PyExc_ImportError, "could not import gi.gobject");
+        return;
+      }
+    if (!PyImport_ImportModule("gi.repository"))
+      {
+        PyErr_SetString(PyExc_ImportError, "could not import gi.repository");
+        return;
+      }
+    if (!PyImport_ImportModule("gi.repository.Gtk"))
+      {
+        PyErr_SetString(PyExc_ImportError, "could not import gtk");
+        return;
+      }
+
+    python_enabled = TRUE;
+#endif // ENABLE_PYTHON
+}
+
+void
+parasite_python_run(const char *command,
+                    ParasitePythonLogger stdout_logger,
+                    ParasitePythonLogger stderr_logger,
+                    gpointer user_data)
+{
+#ifdef ENABLE_PYTHON
+    PyGILState_STATE gstate;
+    PyObject *module;
+    PyObject *dict;
+    PyObject *obj;
+
+    gstate = PyGILState_Ensure();
+
+    module = PyImport_AddModule("__main__");
+    dict = PyModule_GetDict(module);
+
+    PyRun_SimpleString("old_stdout = sys.stdout\n"
+                       "old_stderr = sys.stderr\n"
+                       "sys.stdout = StdoutCatcher()\n"
+                       "sys.stderr = StderrCatcher()\n");
+
+    obj = PyRun_String(command, Py_single_input, dict, dict);
+
+    PyRun_SimpleString("sys.stdout = old_stdout\n"
+                       "sys.stderr = old_stderr\n");
+
+    if (stdout_logger != NULL)
+        stdout_logger(captured_stdout->str, user_data);
+
+    if (stderr_logger != NULL)
+        stderr_logger(captured_stderr->str, user_data);
+
+    // Print any returned object
+    if (obj != NULL && obj != Py_None) {
+       PyObject *repr = PyObject_Repr(obj);
+       if (repr != NULL) {
+           char *string = PyString_AsString(repr);
+
+           stdout_logger(string, user_data);
+           stdout_logger("\n", user_data);
+        }
+
+        Py_XDECREF(repr);
+    }
+    Py_XDECREF(obj);
+
+    PyGILState_Release(gstate);
+    g_string_erase(captured_stdout, 0, -1);
+    g_string_erase(captured_stderr, 0, -1);
+#endif // ENABLE_PYTHON
+}
+
+gboolean
+parasite_python_is_enabled(void)
+{
+    return python_enabled;
+}
+
+// vim: set et sw=4 ts=4:
diff --git a/modules/other/parasite/python-hooks.h b/modules/other/parasite/python-hooks.h
new file mode 100644
index 0000000..d6bf1bd
--- /dev/null
+++ b/modules/other/parasite/python-hooks.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2008-2009  Christian Hammond
+ * Copyright (c) 2008-2009  David Trowbridge
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef _GTKPARASITE_PYTHON_MODULE_H_
+#define _GTKPARASITE_PYTHON_MODULE_H_
+
+#include <glib.h>
+
+
+typedef void (*ParasitePythonLogger)(const char *text, gpointer user_data);
+
+void parasite_python_init(void);
+void parasite_python_run(const char *command,
+                         ParasitePythonLogger stdout_logger,
+                         ParasitePythonLogger stderr_logger,
+                         gpointer user_data);
+gboolean parasite_python_is_enabled(void);
+
+#endif // _GTKPARASITE_PYTHON_MODULE_H_
diff --git a/modules/other/parasite/python-shell.c b/modules/other/parasite/python-shell.c
new file mode 100644
index 0000000..53fc5c6
--- /dev/null
+++ b/modules/other/parasite/python-shell.c
@@ -0,0 +1,409 @@
+/*
+ * Copyright (c) 2008-2009  Christian Hammond
+ * Copyright (c) 2008-2009  David Trowbridge
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <gdk/gdkkeysyms.h>
+#include <string.h>
+
+#include "python-hooks.h"
+#include "python-shell.h"
+
+#define MAX_HISTORY_LENGTH 20
+
+struct _ParasitePythonShellPrivate
+{
+    GtkWidget *textview;
+
+    GtkTextMark *scroll_mark;
+    GtkTextMark *line_start_mark;
+
+    GQueue *history;
+    GList *cur_history_item;
+
+    GString *pending_command;
+    gboolean in_block;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ParasitePythonShell, parasite_python_shell, GTK_TYPE_BOX);
+
+/* Widget functions */
+static void parasite_python_shell_finalize (GObject *obj);
+
+/* Python integration */
+static void parasite_python_shell_write_prompt(GtkWidget *python_shell);
+static char *parasite_python_shell_get_input(GtkWidget *python_shell);
+
+/* Callbacks */
+static gboolean parasite_python_shell_key_press_cb(GtkWidget *textview,
+                                                   GdkEventKey *event,
+                                                   GtkWidget *python_shell);
+static void
+parasite_python_shell_class_init(ParasitePythonShellClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+    object_class->finalize = parasite_python_shell_finalize;
+}
+
+static void
+parasite_python_shell_init (ParasitePythonShell *python_shell)
+{
+    GtkWidget *swin;
+    GtkTextBuffer *buffer;
+    GtkTextIter iter;
+    PangoFontDescription *font_desc;
+
+    python_shell->priv = parasite_python_shell_get_instance_private (python_shell);
+
+    python_shell->priv->history = g_queue_new();
+
+    gtk_box_set_spacing(GTK_BOX(python_shell), 6);
+
+    swin = gtk_scrolled_window_new(NULL, NULL);
+    gtk_widget_show(swin);
+    gtk_box_pack_start(GTK_BOX(python_shell), swin, TRUE, TRUE, 0);
+    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin),
+                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
+    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin),
+                                        GTK_SHADOW_IN);
+
+    python_shell->priv->textview = gtk_text_view_new();
+    gtk_widget_show(python_shell->priv->textview);
+    gtk_container_add(GTK_CONTAINER(swin), python_shell->priv->textview);
+    gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(python_shell->priv->textview), TRUE);
+    gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(python_shell->priv->textview), 3);
+    gtk_text_view_set_left_margin(GTK_TEXT_VIEW(python_shell->priv->textview), 3);
+    gtk_text_view_set_right_margin(GTK_TEXT_VIEW(python_shell->priv->textview), 3);
+
+    g_signal_connect(python_shell->priv->textview, "key_press_event",
+                     G_CALLBACK(parasite_python_shell_key_press_cb),
+                     python_shell);
+
+    /* Make the textview monospaced */
+    font_desc = pango_font_description_from_string("monospace");
+    pango_font_description_set_size(font_desc, 8 * PANGO_SCALE);
+    gtk_widget_override_font(python_shell->priv->textview, font_desc);
+    pango_font_description_free(font_desc);
+
+    /* Create the end-of-buffer mark */
+    buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(python_shell->priv->textview));
+    gtk_text_buffer_get_end_iter(buffer, &iter);
+    python_shell->priv->scroll_mark = gtk_text_buffer_create_mark(buffer, "scroll_mark",
+                                                    &iter, FALSE);
+
+    /* Create the beginning-of-line mark */
+    python_shell->priv->line_start_mark = gtk_text_buffer_create_mark(buffer,
+                                                        "line_start_mark",
+                                                        &iter, TRUE);
+
+    /* Register some tags */
+    gtk_text_buffer_create_tag(buffer, "stdout", NULL);
+    gtk_text_buffer_create_tag(buffer, "stderr",
+                               "foreground", "red",
+                               "paragraph-background", "#FFFFE0",
+                               NULL);
+    gtk_text_buffer_create_tag(buffer, "prompt",
+                               "foreground", "blue",
+                               NULL);
+
+    parasite_python_shell_write_prompt(GTK_WIDGET(python_shell));
+}
+
+static void
+parasite_python_shell_finalize(GObject *python_shell)
+{
+    ParasitePythonShellPrivate *priv = PARASITE_PYTHON_SHELL(python_shell)->priv;
+
+    g_queue_free(priv->history);
+}
+
+static void
+parasite_python_shell_log_stdout(const char *text, gpointer python_shell)
+{
+    parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell),
+                                      text, "stdout");
+}
+
+static void
+parasite_python_shell_log_stderr(const char *text, gpointer python_shell)
+{
+    parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell),
+                                      text, "stderr");
+}
+
+static void
+parasite_python_shell_write_prompt(GtkWidget *python_shell)
+{
+    ParasitePythonShellPrivate *priv = PARASITE_PYTHON_SHELL(python_shell)->priv;
+    GtkTextBuffer *buffer =
+        gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
+    GtkTextIter iter;
+    const char *prompt = (priv->pending_command == NULL ? ">>> " : "... ");
+
+    parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell),
+                                      prompt, "prompt");
+
+    gtk_text_buffer_get_end_iter(buffer, &iter);
+    gtk_text_buffer_move_mark(buffer, priv->line_start_mark, &iter);
+}
+
+static void
+parasite_python_shell_process_line(GtkWidget *python_shell)
+{
+    ParasitePythonShellPrivate *priv = PARASITE_PYTHON_SHELL(python_shell)->priv;
+    char *command = parasite_python_shell_get_input(python_shell);
+    char last_char;
+
+    parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell),
+                                      "\n", NULL);
+
+    if (*command != '\0')
+    {
+        /* Save this command in the history. */
+        g_queue_push_head(priv->history, command);
+        priv->cur_history_item = NULL;
+
+        if (g_queue_get_length(priv->history) > MAX_HISTORY_LENGTH)
+            g_free(g_queue_pop_tail(priv->history));
+    }
+
+    last_char = command[MAX(0, strlen(command) - 1)];
+
+    if (last_char == ':' || last_char == '\\' ||
+        (priv->in_block && g_ascii_isspace(command[0])))
+    {
+        printf("in block.. %c, %d, %d\n",
+               last_char, priv->in_block,
+               g_ascii_isspace(command[0]));
+        /* This is a multi-line expression */
+        if (priv->pending_command == NULL)
+            priv->pending_command = g_string_new(command);
+        else
+            g_string_append(priv->pending_command, command);
+
+        g_string_append_c(priv->pending_command, '\n');
+
+        if (last_char == ':')
+            priv->in_block = TRUE;
+    }
+    else
+    {
+        if (priv->pending_command != NULL)
+        {
+            g_string_append(priv->pending_command, command);
+            g_string_append_c(priv->pending_command, '\n');
+
+            /* We're not actually leaking this. It's in the history. */
+            command = g_string_free(priv->pending_command, FALSE);
+        }
+
+        parasite_python_run(command,
+                            parasite_python_shell_log_stdout,
+                            parasite_python_shell_log_stderr,
+                            python_shell);
+
+        if (priv->pending_command != NULL)
+        {
+            /* Now do the cleanup. */
+            g_free(command);
+            priv->pending_command = NULL;
+            priv->in_block = FALSE;
+        }
+    }
+
+    parasite_python_shell_write_prompt(python_shell);
+}
+
+static void
+parasite_python_shell_replace_input(GtkWidget *python_shell,
+                                    const char *text)
+{
+    ParasitePythonShellPrivate *priv = PARASITE_PYTHON_SHELL(python_shell)->priv;
+    GtkTextBuffer *buffer =
+        gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
+    GtkTextIter start_iter;
+    GtkTextIter end_iter;
+
+    gtk_text_buffer_get_iter_at_mark(buffer, &start_iter,
+                                     priv->line_start_mark);
+    gtk_text_buffer_get_end_iter(buffer, &end_iter);
+
+    gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
+    gtk_text_buffer_insert(buffer, &end_iter, text, -1);
+}
+
+static char *
+parasite_python_shell_get_input(GtkWidget *python_shell)
+{
+    ParasitePythonShellPrivate *priv = PARASITE_PYTHON_SHELL(python_shell)->priv;
+    GtkTextBuffer *buffer =
+        gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
+    GtkTextIter start_iter;
+    GtkTextIter end_iter;
+
+    gtk_text_buffer_get_iter_at_mark(buffer, &start_iter,
+                                     priv->line_start_mark);
+    gtk_text_buffer_get_end_iter(buffer, &end_iter);
+
+    return gtk_text_buffer_get_text(buffer, &start_iter, &end_iter, FALSE);
+}
+
+static const char *
+parasite_python_shell_get_history_back(GtkWidget *python_shell)
+{
+    ParasitePythonShellPrivate *priv = PARASITE_PYTHON_SHELL(python_shell)->priv;
+
+    if (priv->cur_history_item == NULL)
+    {
+        priv->cur_history_item = g_queue_peek_head_link(priv->history);
+
+        if (priv->cur_history_item == NULL)
+            return "";
+    }
+    else if (priv->cur_history_item->next != NULL)
+        priv->cur_history_item = priv->cur_history_item->next;
+
+    return (const char *)priv->cur_history_item->data;
+}
+
+static const char *
+parasite_python_shell_get_history_forward(GtkWidget *python_shell)
+{
+    ParasitePythonShellPrivate *priv = PARASITE_PYTHON_SHELL(python_shell)->priv;
+
+    if (priv->cur_history_item == NULL || priv->cur_history_item->prev == NULL)
+    {
+        priv->cur_history_item = NULL;
+        return "";
+    }
+
+    priv->cur_history_item = priv->cur_history_item->prev;
+
+    return (const char *)priv->cur_history_item->data;
+}
+
+static gboolean
+parasite_python_shell_key_press_cb(GtkWidget *textview,
+                                   GdkEventKey *event,
+                                   GtkWidget *python_shell)
+{
+    if (event->keyval == GDK_KEY_Return)
+    {
+        parasite_python_shell_process_line(python_shell);
+        return TRUE;
+    }
+    else if (event->keyval == GDK_KEY_Up)
+    {
+        parasite_python_shell_replace_input(python_shell,
+            parasite_python_shell_get_history_back(python_shell));
+        return TRUE;
+    }
+    else if (event->keyval == GDK_KEY_Down)
+    {
+        parasite_python_shell_replace_input(python_shell,
+            parasite_python_shell_get_history_forward(python_shell));
+        return TRUE;
+    }
+    else if (event->string != NULL)
+    {
+        ParasitePythonShellPrivate *priv = PARASITE_PYTHON_SHELL(python_shell)->priv;
+        GtkTextBuffer *buffer =
+            gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
+        GtkTextMark *insert_mark = gtk_text_buffer_get_insert(buffer);
+        GtkTextMark *selection_mark =
+            gtk_text_buffer_get_selection_bound(buffer);
+        GtkTextIter insert_iter;
+        GtkTextIter selection_iter;
+        GtkTextIter start_iter;
+        gint cmp_start_insert;
+        gint cmp_start_select;
+        gint cmp_insert_select;
+
+        gtk_text_buffer_get_iter_at_mark(buffer, &start_iter,
+                                         priv->line_start_mark);
+        gtk_text_buffer_get_iter_at_mark(buffer, &insert_iter, insert_mark);
+        gtk_text_buffer_get_iter_at_mark(buffer, &selection_iter,
+                                         selection_mark);
+
+        cmp_start_insert = gtk_text_iter_compare(&start_iter, &insert_iter);
+        cmp_start_select = gtk_text_iter_compare(&start_iter, &selection_iter);
+        cmp_insert_select = gtk_text_iter_compare(&insert_iter,
+                                                  &selection_iter);
+
+        if (cmp_start_insert == 0 && cmp_start_select == 0 &&
+            (event->keyval == GDK_KEY_BackSpace ||
+             event->keyval == GDK_KEY_Left))
+        {
+            return TRUE;
+        }
+        if (cmp_start_insert <= 0 && cmp_start_select <= 0)
+        {
+            return FALSE;
+        }
+        else if (cmp_start_insert > 0 && cmp_start_select > 0)
+        {
+            gtk_text_buffer_place_cursor(buffer, &start_iter);
+        }
+        else if (cmp_insert_select < 0)
+        {
+            gtk_text_buffer_move_mark(buffer, insert_mark, &start_iter);
+        }
+        else if (cmp_insert_select > 0)
+        {
+            gtk_text_buffer_move_mark(buffer, selection_mark, &start_iter);
+        }
+    }
+
+    return FALSE;
+}
+
+GtkWidget *
+parasite_python_shell_new(void)
+{
+    return g_object_new(PARASITE_TYPE_PYTHON_SHELL, NULL);
+}
+
+void
+parasite_python_shell_append_text(ParasitePythonShell *python_shell,
+                                  const char *str,
+                                  const char *tag)
+{
+    ParasitePythonShellPrivate *priv = python_shell->priv;
+
+    GtkTextIter end;
+    GtkTextBuffer *buffer =
+        gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
+    GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
+
+    gtk_text_buffer_get_end_iter(buffer, &end);
+    gtk_text_buffer_move_mark(buffer, mark, &end);
+    gtk_text_buffer_insert_with_tags_by_name(buffer, &end, str, -1, tag, NULL);
+    gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(priv->textview), mark,
+                                 0, TRUE, 0, 1);
+}
+
+void
+parasite_python_shell_focus(ParasitePythonShell *python_shell)
+{
+   gtk_widget_grab_focus (python_shell->priv->textview);
+}
+
+// vim: set et ts=4:
diff --git a/modules/other/parasite/python-shell.h b/modules/other/parasite/python-shell.h
new file mode 100644
index 0000000..8d4f58d
--- /dev/null
+++ b/modules/other/parasite/python-shell.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2008-2009  Christian Hammond
+ * Copyright (c) 2008-2009  David Trowbridge
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef _PARASITE_PYTHON_SHELL_H_
+#define _PARASITE_PYTHON_SHELL_H_
+
+typedef struct _ParasitePythonShell         ParasitePythonShell;
+typedef struct _ParasitePythonShellClass    ParasitePythonShellClass;
+typedef struct _ParasitePythonShellPrivate  ParasitePythonShellPrivate;
+
+#include <gtk/gtk.h>
+
+#define PARASITE_TYPE_PYTHON_SHELL (parasite_python_shell_get_type())
+#define PARASITE_PYTHON_SHELL(obj) \
+               (G_TYPE_CHECK_INSTANCE_CAST((obj), PARASITE_TYPE_PYTHON_SHELL, ParasitePythonShell))
+#define PARASITE_PYTHON_SHELL_CLASS(klass) \
+               (G_TYPE_CHECK_CLASS_CAST((klass), PARASITE_TYPE_PYTHON_SHELL, ParasitePythonShellClass))
+#define PARASITE_IS_PYTHON_SHELL(obj) \
+               (G_TYPE_CHECK_INSTANCE_TYPE((obj), PARASITE_TYPE_PYTHON_SHELL))
+#define PARASITE_IS_PYTHON_SHELL_CLASS(klass) \
+               (G_TYPE_CHECK_CLASS_TYPE((klass), PARASITE_TYPE_PYTHON_SHELL))
+#define PARASITE_PYTHON_SHELL_GET_CLASS(obj) \
+               (G_TYPE_INSTANCE_GET_CLASS ((obj), PARASITE_TYPE_PYTHON_SHELL, ParasitePythonShellClass))
+
+
+struct _ParasitePythonShell
+{
+  GtkBox parent_object;
+  ParasitePythonShellPrivate *priv;
+};
+
+struct _ParasitePythonShellClass
+{
+ GtkBoxClass parent_class;
+};
+
+G_BEGIN_DECLS
+
+GType parasite_python_shell_get_type(void);
+
+GtkWidget *parasite_python_shell_new(void);
+void parasite_python_shell_append_text(ParasitePythonShell *python_shell,
+                                       const char *str,
+                                       const char *tag);
+void parasite_python_shell_focus(ParasitePythonShell *python_shell);
+
+G_END_DECLS
+
+#endif // _PARASITE_PYTHON_SHELL_H_
diff --git a/modules/other/parasite/themes.c b/modules/other/parasite/themes.c
new file mode 100644
index 0000000..ed0ff5c
--- /dev/null
+++ b/modules/other/parasite/themes.c
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2013 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "themes.h"
+
+struct _ParasiteThemesPrivate
+{
+  int dummy;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ParasiteThemes, parasite_themes, GTK_TYPE_LIST_BOX)
+
+static void
+parasite_themes_init (ParasiteThemes *pt)
+{
+  pt->priv = parasite_themes_get_instance_private (pt);
+}
+
+static GtkWidget *
+create_dark (ParasiteThemes *pt)
+{
+  GtkWidget *b, *l, *s;
+
+  b = g_object_new (GTK_TYPE_BOX,
+                    "orientation", GTK_ORIENTATION_HORIZONTAL,
+                    "margin", 10,
+                    NULL);
+
+  l = g_object_new (GTK_TYPE_LABEL,
+                    "label", "Use dark variant",
+                    "hexpand", TRUE,
+                    "xalign", 0.0,
+                    NULL);
+  gtk_container_add (GTK_CONTAINER (b), l);
+
+  s = gtk_switch_new ();
+  g_object_bind_property (s, "active",
+                          gtk_settings_get_default (), "gtk-application-prefer-dark-theme",
+                          G_BINDING_BIDIRECTIONAL);
+  gtk_container_add (GTK_CONTAINER (b), s);
+
+  return b;
+}
+
+static void
+fill_gtk (const char *path, GHashTable *t)
+{
+  const gchar *dir_entry;
+  GDir *dir = g_dir_open (path, 0, NULL);
+
+  if (!dir)
+    return;
+
+  while ((dir_entry = g_dir_read_name (dir)))
+    {
+      char *filename = g_build_filename (path, dir_entry, "gtk-3.0", NULL);
+
+      if (g_file_test (filename, G_FILE_TEST_IS_DIR)
+          && !g_hash_table_contains (t, dir_entry))
+        g_hash_table_add (t, g_strdup (dir_entry));
+
+      g_free (filename);
+    }
+}
+
+static void
+gtk_changed (GtkComboBox *c, ParasiteThemes *pt)
+{
+  char *theme = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (c));
+
+  g_object_set (gtk_settings_get_default (),
+                "gtk-theme-name", theme,
+                NULL);
+  g_free (theme);
+}
+
+static GtkWidget *
+create_gtk (ParasiteThemes *pt)
+{
+  GtkWidget *b, *l, *c;
+  GHashTable *t;
+  char *theme, *default_theme, *path;
+  GHashTableIter iter;
+  int i, pos;
+  GSettings *settings;
+
+  b = g_object_new (GTK_TYPE_BOX,
+                    "orientation", GTK_ORIENTATION_HORIZONTAL,
+                    "margin", 10,
+                    NULL);
+
+  l = g_object_new (GTK_TYPE_LABEL,
+                    "label", "GTK+ Theme",
+                    "hexpand", TRUE,
+                    "xalign", 0.0,
+                    NULL);
+  gtk_container_add (GTK_CONTAINER (b), l);
+
+  t = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+  fill_gtk (GTK_DATADIR "/themes", t);
+  path = g_build_filename (g_get_user_data_dir (), "themes", NULL);
+  fill_gtk (path, t);
+  g_free (path);
+
+  c = gtk_combo_box_text_new ();
+  gtk_container_add (GTK_CONTAINER (b), c);
+
+  settings = g_settings_new ("org.gnome.desktop.interface");
+  default_theme = g_settings_get_string (settings, "gtk-theme");
+  g_object_unref (settings);
+
+  g_hash_table_iter_init (&iter, t);
+  pos = i = 0;
+  while (g_hash_table_iter_next (&iter, (gpointer *)&theme, NULL))
+    {
+      gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (c), theme);
+      if (g_strcmp0 (theme, default_theme) == 0)
+        pos = i;
+      i++;
+    }
+  g_hash_table_destroy (t);
+
+  gtk_combo_box_set_active (GTK_COMBO_BOX (c), pos);
+  g_signal_connect (c, "changed", G_CALLBACK (gtk_changed), pt);
+
+  return b;
+}
+
+static void
+constructed (GObject *object)
+{
+  ParasiteThemes *pt = PARASITE_THEMES (object);
+  GtkContainer *box = GTK_CONTAINER (object);
+
+  g_object_set (object,
+                "selection-mode", GTK_SELECTION_NONE,
+                NULL);
+
+  gtk_container_add (box, create_dark (pt));
+  gtk_container_add (box, create_gtk (pt));
+}
+
+static void
+parasite_themes_class_init (ParasiteThemesClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed  = constructed;
+}
+
+GtkWidget *
+parasite_themes_new (void)
+{
+  return GTK_WIDGET (g_object_new (PARASITE_TYPE_THEMES, NULL));
+}
+
+// vim: set et sw=4 ts=4:
diff --git a/modules/other/parasite/themes.h b/modules/other/parasite/themes.h
new file mode 100644
index 0000000..9f76650
--- /dev/null
+++ b/modules/other/parasite/themes.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2013 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef _GTKPARASITE_THEMES_H_
+#define _GTKPARASITE_THEMES_H_
+
+#include <gtk/gtk.h>
+
+#define PARASITE_TYPE_THEMES            (parasite_themes_get_type())
+#define PARASITE_THEMES(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), PARASITE_TYPE_THEMES, 
ParasiteThemes))
+#define PARASITE_THEMES_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), PARASITE_TYPE_THEMES, 
ParasiteThemesClass))
+#define PARASITE_IS_THEMES(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), PARASITE_TYPE_THEMES))
+#define PARASITE_IS_THEMES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PARASITE_TYPE_THEMES))
+#define PARASITE_THEMES_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), PARASITE_TYPE_THEMES, 
ParasiteThemesClass))
+
+
+typedef struct _ParasiteThemesPrivate ParasiteThemesPrivate;
+
+typedef struct _ParasiteThemes {
+   GtkListBox parent;
+   ParasiteThemesPrivate *priv;
+} ParasiteThemes;
+
+typedef struct _ParasiteThemesClass {
+   GtkListBoxClass parent;
+} ParasiteThemesClass;
+
+G_BEGIN_DECLS
+
+GType      parasite_themes_get_type (void);
+GtkWidget *parasite_themes_new      (void);
+
+G_END_DECLS
+
+#endif // _GTKPARASITE_THEMES_H_
+
+// vim: set et sw=4 ts=4:
diff --git a/modules/other/parasite/widget-tree.c b/modules/other/parasite/widget-tree.c
new file mode 100644
index 0000000..e35c505
--- /dev/null
+++ b/modules/other/parasite/widget-tree.c
@@ -0,0 +1,418 @@
+/*
+ * Copyright (c) 2008-2009  Christian Hammond
+ * Copyright (c) 2008-2009  David Trowbridge
+ * Copyright (c) 2013 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "parasite.h"
+#include "prop-list.h"
+#include "widget-tree.h"
+#include <string.h>
+
+enum
+{
+  OBJECT,
+  OBJECT_TYPE,
+  OBJECT_NAME,
+  WIDGET_REALIZED,
+  WIDGET_VISIBLE,
+  WIDGET_MAPPED,
+  OBJECT_ADDRESS,
+  SENSITIVE,
+  NUM_COLUMNS
+};
+
+
+enum
+{
+  WIDGET_CHANGED,
+  LAST_SIGNAL
+};
+
+
+struct _ParasiteWidgetTreePrivate
+{
+  GtkTreeStore *model;
+  GHashTable *iters;
+};
+
+static guint widget_tree_signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE_WITH_PRIVATE (ParasiteWidgetTree, parasite_widget_tree, GTK_TYPE_TREE_VIEW)
+
+static void
+parasite_widget_tree_on_widget_selected(GtkTreeSelection *selection,
+                                        ParasiteWidgetTree *widget_tree)
+{
+    g_signal_emit(widget_tree, widget_tree_signals[WIDGET_CHANGED], 0);
+}
+
+
+static void
+parasite_widget_tree_init (ParasiteWidgetTree *widget_tree)
+{
+    GtkCellRenderer *renderer;
+    GtkTreeViewColumn *column;
+    GtkTreeSelection *selection;
+
+    widget_tree->priv = parasite_widget_tree_get_instance_private (widget_tree);
+
+    widget_tree->priv->iters = g_hash_table_new_full (g_direct_hash,
+                                                      g_direct_equal,
+                                                      NULL,
+                                                      (GDestroyNotify) gtk_tree_iter_free);
+
+    widget_tree->priv->model = gtk_tree_store_new(
+        NUM_COLUMNS,
+        G_TYPE_POINTER, // OBJECT
+        G_TYPE_STRING,  // OBJECT_TYPE
+        G_TYPE_STRING,  // OBJECT_NAME
+        G_TYPE_BOOLEAN, // WIDGET_REALIZED
+        G_TYPE_BOOLEAN, // WIDGET_VISIBLE
+        G_TYPE_BOOLEAN, // WIDGET_MAPPED
+        G_TYPE_STRING,  // OBJECT_ADDRESS
+        G_TYPE_BOOLEAN);// SENSITIVE
+
+    gtk_tree_view_set_model(GTK_TREE_VIEW(widget_tree),
+                            GTK_TREE_MODEL(widget_tree->priv->model));
+    gtk_tree_view_set_enable_search(GTK_TREE_VIEW(widget_tree), TRUE);
+    gtk_tree_view_set_search_column(GTK_TREE_VIEW(widget_tree), OBJECT_NAME);
+
+    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget_tree));
+    g_signal_connect(G_OBJECT(selection), "changed",
+                     G_CALLBACK(parasite_widget_tree_on_widget_selected),
+                     widget_tree);
+
+    // Widget column
+    renderer = gtk_cell_renderer_text_new();
+    g_object_set(G_OBJECT(renderer), "scale", TREE_TEXT_SCALE, NULL);
+    column = gtk_tree_view_column_new_with_attributes("Widget", renderer,
+                                                      "text", OBJECT_TYPE,
+                                                      "sensitive", SENSITIVE,
+                                                      NULL);
+    gtk_tree_view_append_column(GTK_TREE_VIEW(widget_tree), column);
+    gtk_tree_view_column_set_resizable(column, TRUE);
+
+    // Name column
+    renderer = gtk_cell_renderer_text_new();
+    g_object_set(G_OBJECT(renderer), "scale", TREE_TEXT_SCALE, NULL);
+    column = gtk_tree_view_column_new_with_attributes("Name", renderer,
+                                                      "text", OBJECT_NAME,
+                                                      "sensitive", SENSITIVE,
+                                                      NULL);
+    gtk_tree_view_append_column(GTK_TREE_VIEW(widget_tree), column);
+    gtk_tree_view_column_set_resizable(column, TRUE);
+
+    // Realized column
+    renderer = gtk_cell_renderer_toggle_new();
+    g_object_set(G_OBJECT(renderer),
+                 "activatable", TRUE,
+                 "indicator-size", TREE_CHECKBOX_SIZE,
+                 NULL);
+    column = gtk_tree_view_column_new_with_attributes("Realized",
+                                                      renderer,
+                                                      "active", WIDGET_REALIZED,
+                                                      NULL);
+    gtk_tree_view_append_column(GTK_TREE_VIEW(widget_tree), column);
+
+    // Mapped column
+    renderer = gtk_cell_renderer_toggle_new();
+    g_object_set(G_OBJECT(renderer),
+                 "activatable", TRUE,
+                 "indicator-size", TREE_CHECKBOX_SIZE,
+                 NULL);
+    column = gtk_tree_view_column_new_with_attributes("Mapped",
+                                                      renderer,
+                                                      "active", WIDGET_MAPPED,
+                                                      NULL);
+    gtk_tree_view_append_column(GTK_TREE_VIEW(widget_tree), column);
+
+    // Visible column
+    renderer = gtk_cell_renderer_toggle_new();
+    g_object_set(G_OBJECT(renderer),
+                 "activatable", TRUE,
+                 "indicator-size", TREE_CHECKBOX_SIZE,
+                 NULL);
+    column = gtk_tree_view_column_new_with_attributes("Visible",
+                                                      renderer,
+                                                      "active", WIDGET_VISIBLE,
+                                                      NULL);
+    gtk_tree_view_append_column(GTK_TREE_VIEW(widget_tree), column);
+
+    // Poinder Address column
+    renderer = gtk_cell_renderer_text_new();
+    g_object_set(G_OBJECT(renderer),
+                 "scale", TREE_TEXT_SCALE,
+                 "family", "monospace",
+                 NULL);
+    column = gtk_tree_view_column_new_with_attributes("Pointer Address",
+                                                      renderer,
+                                                      "text", OBJECT_ADDRESS,
+                                                      "sensitive", SENSITIVE,
+                                                      NULL);
+    gtk_tree_view_append_column(GTK_TREE_VIEW(widget_tree), column);
+    gtk_tree_view_column_set_resizable(column, TRUE);
+
+  parasite_widget_tree_append_object (widget_tree, G_OBJECT (gtk_settings_get_default ()), NULL);
+}
+
+
+static void
+parasite_widget_tree_class_init(ParasiteWidgetTreeClass *klass)
+{
+    klass->widget_changed = NULL;
+
+    widget_tree_signals[WIDGET_CHANGED] =
+        g_signal_new("widget-changed",
+                     G_OBJECT_CLASS_TYPE(klass),
+                     G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
+                     G_STRUCT_OFFSET(ParasiteWidgetTreeClass, widget_changed),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__VOID,
+                     G_TYPE_NONE, 0);
+}
+
+
+GtkWidget *
+parasite_widget_tree_new ()
+{
+  return g_object_new (PARASITE_TYPE_WIDGET_TREE, NULL);
+}
+
+
+GObject *
+parasite_widget_tree_get_selected_object (ParasiteWidgetTree *widget_tree)
+{
+  GtkTreeIter iter;
+  GtkTreeSelection *sel;
+  GtkTreeModel *model;
+
+  sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget_tree));
+
+  if (gtk_tree_selection_get_selected (sel, &model, &iter))
+    {
+      GObject *object;
+      gtk_tree_model_get (model, &iter,
+                          OBJECT, &object,
+                          -1);
+      return object;
+    }
+
+  return NULL;
+}
+
+static void
+on_container_forall(GtkWidget *widget, gpointer data)
+{
+    GList **list = (GList **)data;
+
+    *list = g_list_append(*list, widget);
+}
+
+void
+parasite_widget_tree_append_object (ParasiteWidgetTree *widget_tree,
+                                    GObject            *object,
+                                    GtkTreeIter        *parent_iter)
+{
+  GtkTreeIter iter;
+  const char *class_name = G_OBJECT_CLASS_NAME (G_OBJECT_GET_CLASS (object));
+  const char *name = NULL;
+  char *address;
+  gboolean realized;
+  gboolean mapped;
+  gboolean visible;
+  gboolean is_widget;
+  GList *l;
+
+  realized = mapped = visible = FALSE;
+
+  is_widget = GTK_IS_WIDGET (object);
+  if (is_widget)
+    {
+      GtkWidget *widget = GTK_WIDGET (object);
+      name = gtk_widget_get_name (GTK_WIDGET (object));
+
+      realized = gtk_widget_get_realized  (widget);
+      mapped = gtk_widget_get_mapped (widget);
+      visible = gtk_widget_get_visible (widget);
+    }
+
+  if (name == NULL || g_strcmp0 (name, class_name) == 0)
+    {
+      if (GTK_IS_LABEL (object))
+        {
+          name = gtk_label_get_text (GTK_LABEL (object));
+        }
+      else if (GTK_IS_BUTTON (object))
+        {
+          name = gtk_button_get_label (GTK_BUTTON (object));
+        }
+      else if (GTK_IS_WINDOW (object))
+        {
+          name = gtk_window_get_title (GTK_WINDOW (object));
+        }
+      else
+        {
+          name = "";
+        }
+    }
+
+  address = g_strdup_printf ("%p", object);
+
+  gtk_tree_store_append (widget_tree->priv->model, &iter, parent_iter);
+  gtk_tree_store_set (widget_tree->priv->model, &iter,
+                      OBJECT, object,
+                      OBJECT_TYPE, class_name,
+                      OBJECT_NAME, name,
+                      WIDGET_REALIZED, realized,
+                      WIDGET_MAPPED, mapped,
+                      WIDGET_VISIBLE, visible,
+                      OBJECT_ADDRESS, address,
+                      SENSITIVE, !is_widget || (realized && mapped && visible),
+                      -1);
+  g_hash_table_insert (widget_tree->priv->iters, object, gtk_tree_iter_copy (&iter));
+
+  g_free(address);
+
+  if (GTK_IS_CONTAINER (object))
+    {
+      GList* children = NULL;
+
+      /* Pick up all children, including those that are internal. */
+      gtk_container_forall (GTK_CONTAINER (object),
+                            on_container_forall, &children);
+
+      for (l = children; l != NULL; l = l->next)
+        {
+          parasite_widget_tree_append_object (widget_tree, l->data, &iter);
+        }
+
+      g_list_free(children);
+    }
+}
+
+void
+parasite_widget_tree_scan (ParasiteWidgetTree *widget_tree,
+                           GtkWidget          *window)
+{
+  gtk_tree_store_clear (widget_tree->priv->model);
+  g_hash_table_remove_all (widget_tree->priv->iters);
+  parasite_widget_tree_append_object (widget_tree, G_OBJECT (gtk_settings_get_default ()), NULL);
+  parasite_widget_tree_append_object (widget_tree, G_OBJECT (window), NULL);
+
+  gtk_tree_view_columns_autosize (GTK_TREE_VIEW (widget_tree));
+}
+
+
+/*
+static GList *
+get_parents(GtkWidget *widget,
+            GList *parents)
+{
+    GtkWidget *parent = gtk_widget_get_parent(widget);
+
+    parents = g_list_prepend(parents, widget);
+
+    if (parent != NULL)
+        return get_parents(parent, parents);
+
+    return parents;
+}
+
+gboolean
+parasite_widget_tree_find_widget (ParasiteWidgetTree *widget_tree,
+                                  GtkWidget          *widget,
+                                  GtkTreeIter        *iter)
+{
+  GList *parents = get_parents (widget, NULL);
+  GList *l;
+  GtkTreeIter inner_iter, parent_iter = {0};
+  gboolean found = FALSE;
+  gboolean in_root = TRUE;
+
+  for (l = parents; l != NULL; l = l->next)
+    {
+      GtkWidget *cur_widget = GTK_WIDGET (l->data);
+      gboolean valid;
+      found = FALSE;
+
+      for (valid = gtk_tree_model_iter_children (widget_tree->priv->model,
+                                                 &inner_iter,
+                                                 in_root ? NULL : &parent_iter);
+           valid;
+           valid = gtk_tree_model_iter_next (widget_tree->priv->model, &inner_iter))
+        {
+          GtkWidget *iter_widget;
+          gtk_tree_model_get (widget_tree->priv->model,
+                              &inner_iter,
+                              WIDGET, &iter_widget,
+                              -1);
+          if (iter_widget == cur_widget)
+            {
+              parent_iter = inner_iter;
+              in_root = FALSE;
+              found = TRUE;
+              break;
+            }
+        }
+    }
+
+  g_list_free(parents);
+
+  *iter = inner_iter;
+  return found;
+}
+*/
+
+gboolean
+parasite_widget_tree_find_object (ParasiteWidgetTree *widget_tree,
+                                  GObject            *object,
+                                  GtkTreeIter        *iter)
+{
+  GtkTreeIter *internal_iter = g_hash_table_lookup (widget_tree->priv->iters, object);
+  if (internal_iter)
+    {
+      *iter = *internal_iter;
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+void
+parasite_widget_tree_select_object (ParasiteWidgetTree *widget_tree,
+                                    GObject            *object)
+{
+  GtkTreeIter iter;
+
+  if (parasite_widget_tree_find_object (widget_tree, object, &iter))
+    {
+      GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (widget_tree->priv->model), &iter);
+      gtk_tree_view_expand_to_path(GTK_TREE_VIEW(widget_tree), path);
+      gtk_tree_selection_select_iter(
+            gtk_tree_view_get_selection(GTK_TREE_VIEW(widget_tree)),
+            &iter);
+      gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (widget_tree), path, NULL, FALSE, 0, 0);
+    }
+
+}
+
+
+// vim: set et sw=4 ts=4:
diff --git a/modules/other/parasite/widget-tree.h b/modules/other/parasite/widget-tree.h
new file mode 100644
index 0000000..f3c5017
--- /dev/null
+++ b/modules/other/parasite/widget-tree.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2008-2009  Christian Hammond
+ * Copyright (c) 2008-2009  David Trowbridge
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef _GTKPARASITE_WIDGET_TREE_H_
+#define _GTKPARASITE_WIDGET_TREE_H_
+
+
+#include <gtk/gtk.h>
+
+
+#define PARASITE_TYPE_WIDGET_TREE            (parasite_widget_tree_get_type())
+#define PARASITE_WIDGET_TREE(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), PARASITE_TYPE_WIDGET_TREE, 
ParasiteWidgetTree))
+#define PARASITE_WIDGET_TREE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), PARASITE_TYPE_WIDGET_TREE, 
ParasiteWidgetTreeClass))
+#define PARASITE_IS_WIDGET_TREE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), PARASITE_TYPE_WIDGET_TREE))
+#define PARASITE_IS_WIDGET_TREE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PARASITE_TYPE_WIDGET_TREE))
+#define PARASITE_WIDGET_TREE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), PARASITE_TYPE_WIDGET_TREE, 
ParasiteWidgetTreeClass))
+
+
+typedef struct _ParasiteWidgetTreePrivate ParasiteWidgetTreePrivate;
+
+typedef struct _ParasiteWidgetTree {
+   GtkTreeView parent;
+
+   // Private
+   ParasiteWidgetTreePrivate *priv;
+} ParasiteWidgetTree;
+
+typedef struct _ParasiteWidgetTreeClass {
+   GtkTreeViewClass parent;
+
+    void (*widget_changed)(ParasiteWidgetTree *tree);
+} ParasiteWidgetTreeClass;
+
+
+G_BEGIN_DECLS
+
+
+GType      parasite_widget_tree_get_type            (void);
+GtkWidget *parasite_widget_tree_new                 (void);
+
+GObject   *parasite_widget_tree_get_selected_object (ParasiteWidgetTree *widget_tree);
+
+void       parasite_widget_tree_scan                (ParasiteWidgetTree *widget_tree,
+                                                     GtkWidget *window);
+void       parasite_widget_tree_select_object       (ParasiteWidgetTree *widget_tree,
+                                                     GObject            *object);
+void       parasite_widget_tree_append_object       (ParasiteWidgetTree *widget_tree,
+                                                     GObject            *object,
+                                                     GtkTreeIter        *parent_iter);
+gboolean   parasite_widget_tree_find_object         (ParasiteWidgetTree *widget_tree,
+                                                     GObject            *object,
+                                                     GtkTreeIter        *iter);
+
+G_END_DECLS
+
+
+#endif // _GTKPARASITE_WIDGETTREE_H_
+
+// vim: set et sw=4 ts=4:
diff --git a/modules/other/parasite/window.c b/modules/other/parasite/window.c
new file mode 100644
index 0000000..56670dd
--- /dev/null
+++ b/modules/other/parasite/window.c
@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) 2008-2009  Christian Hammond
+ * Copyright (c) 2008-2009  David Trowbridge
+ * Copyright (c) 2013 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include "parasite.h"
+#include "prop-list.h"
+#include "classes-list.h"
+#include "css-editor.h"
+#include "object-hierarchy.h"
+#include "widget-tree.h"
+#include "python-hooks.h"
+#include "python-shell.h"
+#include "button-path.h"
+#include "themes.h"
+
+static void
+on_widget_tree_selection_changed (ParasiteWidgetTree *widget_tree,
+                                  ParasiteWindow     *parasite)
+{
+  GObject *selected = parasite_widget_tree_get_selected_object (widget_tree);
+
+  if (selected != NULL)
+    {
+      parasite_proplist_set_object (PARASITE_PROPLIST (parasite->prop_list),
+                                    selected);
+      parasite_objecthierarchy_set_object (PARASITE_OBJECTHIERARCHY (parasite->oh),
+                                           selected);
+
+      if (GTK_IS_WIDGET (selected))
+        {
+          GtkWidget *widget = GTK_WIDGET (selected);
+
+          gtkparasite_flash_widget(parasite, widget);
+          parasite_buttonpath_set_widget (PARASITE_BUTTONPATH (parasite->button_path), widget);
+          parasite_classeslist_set_widget (PARASITE_CLASSESLIST (parasite->classes_list), widget);
+          parasite_csseditor_set_widget (PARASITE_CSSEDITOR (parasite->widget_css_editor), widget);
+        }
+      else
+        {
+          gtk_widget_set_sensitive (parasite->classes_list, FALSE);
+          gtk_widget_set_sensitive (parasite->widget_css_editor, FALSE);
+        }
+    }
+}
+
+
+static gboolean
+on_widget_tree_button_press(ParasiteWidgetTree *widget_tree,
+                            GdkEventButton *event,
+                            ParasiteWindow *parasite)
+{
+    if (event->button == 3)
+    {
+        gtk_menu_popup(GTK_MENU(parasite->widget_popup), NULL, NULL,
+                       NULL, NULL, event->button, event->time);
+    }
+
+    return FALSE;
+}
+
+
+static void
+on_send_widget_to_shell_activate(GtkWidget *menuitem,
+                                 ParasiteWindow *parasite)
+{
+  char *str;
+  GObject *object;
+
+  object = parasite_widget_tree_get_selected_object (PARASITE_WIDGET_TREE (parasite->widget_tree));
+
+  if (!object)
+    return;
+
+  str = g_strdup_printf ("parasite.gobj(%p)", object);
+  parasite_python_shell_append_text (PARASITE_PYTHON_SHELL (parasite->python_shell),
+                                     str,
+                                     NULL);
+
+  g_free(str);
+  parasite_python_shell_focus (PARASITE_PYTHON_SHELL (parasite->python_shell));
+}
+
+
+static GtkWidget *
+create_widget_list_pane(ParasiteWindow *parasite)
+{
+    GtkWidget *swin;
+
+    swin = g_object_new (GTK_TYPE_SCROLLED_WINDOW,
+                        "hscrollbar-policy", GTK_POLICY_AUTOMATIC,
+                        "vscrollbar-policy", GTK_POLICY_ALWAYS,
+                        "shadow-type", GTK_SHADOW_IN,
+                        "width-request", 250,
+                        "expand", TRUE,
+                        NULL);
+
+    parasite->widget_tree = parasite_widget_tree_new();
+    gtk_container_add(GTK_CONTAINER(swin), parasite->widget_tree);
+
+    g_signal_connect(G_OBJECT(parasite->widget_tree),
+                     "widget-changed",
+                     G_CALLBACK(on_widget_tree_selection_changed),
+                     parasite);
+
+    if (parasite_python_is_enabled())
+    {
+        g_signal_connect(G_OBJECT(parasite->widget_tree),
+                         "button-press-event",
+                         G_CALLBACK(on_widget_tree_button_press),
+                         parasite);
+    }
+
+    return swin;
+}
+
+static GtkWidget *
+create_prop_list_pane(ParasiteWindow *parasite)
+{
+    GtkWidget *swin;
+
+    swin = g_object_new (GTK_TYPE_SCROLLED_WINDOW,
+                        "hscrollbar-policy", GTK_POLICY_AUTOMATIC,
+                        "vscrollbar-policy", GTK_POLICY_ALWAYS,
+                        "shadow-type", GTK_SHADOW_IN,
+                        "width-request", 250,
+                        NULL);
+
+    parasite->prop_list = parasite_proplist_new (parasite->widget_tree);
+    gtk_container_add(GTK_CONTAINER(swin), parasite->prop_list);
+
+    return swin;
+}
+
+static void
+on_show_graphic_updates_toggled(GtkWidget *toggle_button,
+                                ParasiteWindow *parasite)
+{
+    gdk_window_set_debug_updates(
+        gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle_button)));
+}
+
+static GtkWidget *
+create_toolbar (ParasiteWindow *window) {
+  GtkWidget *button;
+  GtkWidget *box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+  GtkStyleContext *context = gtk_widget_get_style_context (box);
+  GtkWidget *image;
+
+  gtk_style_context_add_class (context, "linked");
+
+  button = gtkparasite_inspect_button_new (window);
+  gtk_container_add (GTK_CONTAINER (box), button);
+
+  button = gtk_toggle_button_new ();
+  image = gtk_image_new_from_icon_name ("view-refresh", GTK_ICON_SIZE_BUTTON);
+  gtk_button_set_image (GTK_BUTTON (button), image);
+  gtk_widget_set_tooltip_text (button, "Show Graphic Updates");
+  gtk_container_add (GTK_CONTAINER (box), button);
+  g_signal_connect (button,
+                    "toggled",
+                    G_CALLBACK (on_show_graphic_updates_toggled),
+                    window);
+
+  gtk_widget_show_all (box);
+  return box;
+}
+
+static void
+delete_window (GtkWidget *widget) {
+  GApplication *app = g_application_get_default ();
+
+  gtk_widget_hide (widget);
+
+  if (app)
+    g_application_quit (app);
+  else
+    exit (0);
+}
+
+void
+gtkparasite_window_create()
+{
+    ParasiteWindow *window;
+    GtkWidget *vpaned, *hpaned;
+    GtkWidget *header;
+    GtkWidget *box;
+    GtkWidget *nb;
+    char *title;
+
+    window = g_new0(ParasiteWindow, 1);
+
+    /*
+     * Create the top-level window.
+     */
+    window->window = g_object_new (GTK_TYPE_WINDOW,
+                                   "default-height", 500,
+                                   "default-width", 1000,
+                                   NULL);
+    g_signal_connect (window->window,
+                      "delete-event",
+                      G_CALLBACK (delete_window),
+                      NULL);
+
+    title = g_strdup_printf("Parasite - %s", g_get_application_name());
+    gtk_window_set_title (GTK_WINDOW (window->window), title);
+
+    header = gtk_header_bar_new ();
+    gtk_header_bar_set_title (GTK_HEADER_BAR (header), title);
+    gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (header), TRUE);
+    gtk_window_set_titlebar (GTK_WINDOW (window->window), header);
+    gtk_header_bar_pack_start (GTK_HEADER_BAR (header), create_toolbar (window));
+
+    g_free(title);
+
+    nb = g_object_new (GTK_TYPE_NOTEBOOK,
+                       "show-border", FALSE,
+                       "margin-left", 6,
+                       "margin-right", 6,
+                       "margin-bottom", 6,
+                       NULL);
+    gtk_container_add (GTK_CONTAINER (window->window), nb);
+
+    box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+    gtk_notebook_append_page (GTK_NOTEBOOK (nb),
+                              box,
+                              gtk_label_new ("Widget Tree"));
+
+    gtk_notebook_append_page (GTK_NOTEBOOK (nb),
+                              parasite_themes_new (),
+                              gtk_label_new ("Themes"));
+
+    gtk_notebook_append_page (GTK_NOTEBOOK (nb),
+                              parasite_csseditor_new (TRUE),
+                              gtk_label_new ("Custom CSS"));
+
+    window->button_path = parasite_buttonpath_new ();
+    gtk_container_add (GTK_CONTAINER (box), window->button_path);
+
+    hpaned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
+    gtk_container_add (GTK_CONTAINER (box), hpaned);
+
+    vpaned = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
+    gtk_paned_pack1 (GTK_PANED (hpaned), vpaned, TRUE, FALSE);
+    gtk_paned_pack1 (GTK_PANED (vpaned), create_widget_list_pane (window), TRUE, FALSE);
+
+    nb = g_object_new (GTK_TYPE_NOTEBOOK,
+                       "enable-popup", TRUE,
+                       "show-border", FALSE,
+                       NULL);
+    gtk_notebook_append_page (GTK_NOTEBOOK (nb),
+                              create_prop_list_pane (window),
+                              gtk_label_new ("GObject Properties"));
+
+    window->oh = parasite_objecthierarchy_new ();
+    gtk_notebook_append_page (GTK_NOTEBOOK (nb),
+                              window->oh,
+                              gtk_label_new ("Hierarchy"));
+
+    window->classes_list = parasite_classeslist_new ();
+    gtk_notebook_append_page (GTK_NOTEBOOK (nb),
+                              window->classes_list,
+                              gtk_label_new ("CSS Classes"));
+
+    window->widget_css_editor = parasite_csseditor_new (FALSE);
+    gtk_notebook_append_page (GTK_NOTEBOOK (nb),
+                              window->widget_css_editor,
+                              gtk_label_new ("Custom CSS"));
+
+    gtk_paned_pack2 (GTK_PANED (hpaned), nb, FALSE, FALSE);
+
+    if (parasite_python_is_enabled())
+    {
+        GtkWidget *menuitem;
+
+        window->python_shell = parasite_python_shell_new();
+        gtk_paned_pack2(GTK_PANED(vpaned), window->python_shell, FALSE, FALSE);
+
+        /*
+         * XXX Eventually we'll want to put more in here besides the menu
+         *     item we define below. At that point, we'll need to make this
+         *     more generic.
+         */
+        window->widget_popup = gtk_menu_new();
+        gtk_widget_show(window->widget_popup);
+
+        menuitem = gtk_menu_item_new_with_label("Send Widget to Shell");
+        gtk_widget_show(menuitem);
+        gtk_menu_shell_append(GTK_MENU_SHELL(window->widget_popup), menuitem);
+
+        g_signal_connect(G_OBJECT(menuitem), "activate",
+                         G_CALLBACK(on_send_widget_to_shell_activate), window);
+    }
+
+    gtk_widget_show_all (window->window);
+}
+
+// vim: set et sw=4 ts=4:


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