[gtk+/parasite: 1/4] Initial import



commit 408ff5772e2bcc4f87717615d0fd007e91edb63c
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/hadess/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              |   39 ++
 modules/other/parasite/action-list.c            |  323 ++++++++++++++
 modules/other/parasite/action-list.h            |   66 +++
 modules/other/parasite/inspect-button.c         |  247 +++++++++++
 modules/other/parasite/module.c                 |   38 ++
 modules/other/parasite/parasite.h               |   67 +++
 modules/other/parasite/prop-list.c              |  248 +++++++++++
 modules/other/parasite/prop-list.h              |   72 +++
 modules/other/parasite/property-cell-renderer.c |  464 ++++++++++++++++++++
 modules/other/parasite/property-cell-renderer.h |   69 +++
 modules/other/parasite/widget-tree.c            |  537 +++++++++++++++++++++++
 modules/other/parasite/widget-tree.h            |   73 +++
 modules/other/parasite/window.c                 |  224 ++++++++++
 16 files changed, 2471 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..daa39b8
--- /dev/null
+++ b/modules/other/parasite/Makefile.am
@@ -0,0 +1,39 @@
+moduledir = $(libdir)/gtk-3.0/modules
+
+module_LTLIBRARIES = libgtkparasite.la
+
+libgtkparasite_la_SOURCES = \
+       action-list.c \
+       action-list.h \
+       inspect-button.c \
+       module.c \
+       parasite.h \
+       prop-list.h \
+       prop-list.c \
+       property-cell-renderer.c \
+       property-cell-renderer.h \
+       widget-tree.h \
+       widget-tree.c \
+       window.c
+
+libgtkparasite_la_CPPFLAGS = \
+       -I$(top_srcdir)                 \
+       -I$(top_srcdir)/gtk             \
+       -I$(top_builddir)/gtk           \
+       -I$(top_srcdir)/gdk             \
+       -I$(top_builddir)/gdk           \
+       -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/action-list.c b/modules/other/parasite/action-list.c
new file mode 100644
index 0000000..c62e77c
--- /dev/null
+++ b/modules/other/parasite/action-list.c
@@ -0,0 +1,323 @@
+/*
+ * 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 "action-list.h"
+#include "parasite.h"
+
+
+enum
+{
+   ACTION_LABEL,
+   ACTION_NAME,
+   ACTION_ICON,
+   ROW_COLOR,
+   SORT_NAME,
+   ADDRESS,
+   NUM_COLUMNS
+};
+
+
+struct _ParasiteActionListPrivate
+{
+    GtkTreeStore *model;
+    GSList *uimanagers;
+    guint update_timeout;
+};
+
+#define PARASITE_ACTIONLIST_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PARASITE_TYPE_ACTIONLIST, 
ParasiteActionListPrivate))
+
+static GtkTreeViewClass *parent_class;
+
+
+gboolean
+update(ParasiteActionList *actionlist)
+{
+    GSList *i;
+
+    gtk_tree_store_clear(actionlist->priv->model);
+
+    for (i = actionlist->priv->uimanagers; i != NULL; i = g_slist_next(i))
+    {
+        GtkUIManager *uimanager;
+        GList *action_groups;
+        GList *j;
+        gchar *name;
+
+        uimanager = GTK_UI_MANAGER(i->data);
+
+        GtkTreeIter i_iter;
+        gtk_tree_store_append(actionlist->priv->model, &i_iter, NULL);
+
+        name = g_strdup_printf("UIManager at %p", uimanager);
+        gtk_tree_store_set(actionlist->priv->model, &i_iter,
+                           ACTION_LABEL, name,
+                           SORT_NAME, name,
+                           ADDRESS, uimanager,
+                           -1);
+        g_free(name);
+
+        action_groups = gtk_ui_manager_get_action_groups(uimanager);
+        for (j = action_groups; j != NULL; j = g_list_next(j))
+        {
+            GtkActionGroup *action_group;
+            GtkTreeIter j_iter;
+            GList *actions;
+            GList *k;
+
+            action_group = GTK_ACTION_GROUP(j->data);
+
+            gtk_tree_store_append(actionlist->priv->model, &j_iter, &i_iter);
+
+            name = (gchar*) gtk_action_group_get_name(action_group);
+            gtk_tree_store_set(actionlist->priv->model, &j_iter,
+                               ACTION_LABEL, name,
+                               SORT_NAME, name,
+                               ROW_COLOR, gtk_action_group_get_sensitive(action_group)
+                                              ? "black" : "grey",
+                               ADDRESS, action_group,
+                               -1);
+
+            actions = gtk_action_group_list_actions(action_group);
+            for (k = actions; k != NULL; k = g_list_next(k))
+            {
+                GtkTreeIter k_iter;
+                GtkAction *action;
+                gchar *action_label;
+                gchar *action_name;
+                gchar *action_stock;
+                gchar *sort_name;
+
+                action = GTK_ACTION(k->data);
+                g_object_get(action,
+                             "label",    &action_label,
+                             "name",     &action_name,
+                             "stock-id", &action_stock,
+                             NULL);
+
+                sort_name = g_strdup_printf("%s%s", name, action_name);
+
+                gtk_tree_store_append(actionlist->priv->model, &k_iter, &j_iter);
+                // FIXME: format the mnemonic
+                gtk_tree_store_set(actionlist->priv->model, &k_iter,
+                                   ACTION_LABEL, action_label,
+                                   ACTION_NAME, action_name,
+                                   ACTION_ICON, action_stock,
+                                   ROW_COLOR, gtk_action_is_sensitive(action)
+                                                  ? "black" : "grey",
+                                   SORT_NAME, sort_name,
+                                   ADDRESS, action,
+                                   -1);
+
+                g_free(sort_name);
+                g_free(action_stock);
+                g_free(action_name);
+                g_free(action_label);
+            }
+        }
+    }
+
+    // FIXME: I'm undecided about this, but I also don't really want to try to
+    // preserve the exsting expansion state of the whole tree.
+    gtk_tree_view_expand_all(GTK_TREE_VIEW(actionlist));
+
+    actionlist->priv->update_timeout = 0;
+
+    return FALSE;
+}
+
+
+void
+uimanager_dispose_cb(gpointer data,
+                     GObject *object)
+{
+    ParasiteActionList *actionlist = PARASITE_ACTIONLIST(data);
+    actionlist->priv->uimanagers =
+        g_slist_remove(actionlist->priv->uimanagers, object);
+
+    if (actionlist->priv->update_timeout == 0) {
+        actionlist->priv->update_timeout =
+            g_timeout_add(20, (GSourceFunc) update, actionlist);
+    }
+}
+
+
+gboolean
+actions_changed_cb(GSignalInvocationHint *hint,
+                   guint n_param_values,
+                   const GValue *param_values,
+                   gpointer data)
+{
+    ParasiteActionList *actionlist = PARASITE_ACTIONLIST(data);
+    GtkUIManager *uimanager;
+    GSList *i;
+
+    uimanager = GTK_UI_MANAGER(g_value_get_object(&param_values[0]));
+
+    i = g_slist_find(actionlist->priv->uimanagers, uimanager);
+    if (i == NULL) {
+        actionlist->priv->uimanagers =
+            g_slist_prepend(actionlist->priv->uimanagers, uimanager);
+        g_object_weak_ref(G_OBJECT(uimanager), uimanager_dispose_cb, data);
+    }
+
+    if (actionlist->priv->update_timeout == 0) {
+        actionlist->priv->update_timeout =
+            g_timeout_add(20, (GSourceFunc) update, actionlist);
+    }
+
+    return TRUE;
+}
+
+
+static void
+parasite_actionlist_init(ParasiteActionList *actionlist,
+                         ParasiteActionListClass *klass)
+{
+    GtkCellRenderer *renderer;
+    GtkTreeViewColumn *column;
+    GTypeClass *uimanager_type;
+    guint uimanager_signal;
+
+    actionlist->priv = PARASITE_ACTIONLIST_GET_PRIVATE(actionlist);
+    actionlist->priv->uimanagers = NULL;
+
+    actionlist->priv->model =
+        gtk_tree_store_new(NUM_COLUMNS,
+                           G_TYPE_STRING,   // ACTION_LABEL
+                           G_TYPE_STRING,   // ACTION_NAME
+                           G_TYPE_STRING,   // ACTION_ICON
+                           G_TYPE_STRING,   // ROW_COLOR,
+                           G_TYPE_STRING,   // SORT_NAME
+                           G_TYPE_POINTER); // ADDRESS
+    gtk_tree_view_set_model(GTK_TREE_VIEW(actionlist),
+                            GTK_TREE_MODEL(actionlist->priv->model));
+
+    column = gtk_tree_view_column_new();
+    gtk_tree_view_append_column(GTK_TREE_VIEW(actionlist), column);
+    gtk_tree_view_column_set_title(column, "Label");
+
+    renderer = gtk_cell_renderer_pixbuf_new();
+    gtk_tree_view_column_pack_start(column, renderer, FALSE);
+    gtk_tree_view_column_set_attributes(column, renderer,
+                                        "stock-id", ACTION_ICON,
+                                        NULL);
+
+    renderer = gtk_cell_renderer_text_new();
+    gtk_tree_view_column_pack_start(column, renderer, FALSE);
+    gtk_tree_view_column_set_attributes(column, renderer,
+                                        "text", ACTION_LABEL,
+                                        "foreground", ROW_COLOR,
+                                        NULL);
+
+    renderer = gtk_cell_renderer_text_new();
+    column = gtk_tree_view_column_new_with_attributes("Action",
+                                                      renderer,
+                                                      "text", ACTION_NAME,
+                                                      "foreground", ROW_COLOR,
+                                                      NULL);
+    gtk_tree_view_append_column(GTK_TREE_VIEW(actionlist), column);
+
+    gtk_tree_sortable_set_sort_column_id(
+        GTK_TREE_SORTABLE(actionlist->priv->model),
+        SORT_NAME, GTK_SORT_ASCENDING);
+
+    // Listen to all "actions-changed" signal emissions
+    uimanager_type = g_type_class_ref(GTK_TYPE_UI_MANAGER);
+    uimanager_signal = g_signal_lookup("actions-changed", GTK_TYPE_UI_MANAGER);
+    g_signal_add_emission_hook(uimanager_signal, 0,
+                               actions_changed_cb,
+                               actionlist,
+                               NULL);
+    g_type_class_unref(uimanager_type);
+}
+
+
+static void
+parasite_actionlist_class_init(ParasiteActionListClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+    parent_class = g_type_class_peek_parent(klass);
+
+    g_type_class_add_private(object_class, sizeof(ParasiteActionListPrivate));
+}
+
+
+GType
+parasite_actionlist_get_type()
+{
+    static GType type = 0;
+
+    if (type == 0)
+    {
+        static const GTypeInfo info =
+        {
+            sizeof(ParasiteActionListClass),
+            NULL, // base_init
+            NULL, // base_finalize
+            (GClassInitFunc) parasite_actionlist_class_init,
+            NULL,
+            NULL, // class_data
+            sizeof(ParasiteActionList),
+            0, // n_preallocs
+            (GInstanceInitFunc) parasite_actionlist_init,
+        };
+
+        type = g_type_register_static(GTK_TYPE_TREE_VIEW,
+                                      "ParasiteActionList",
+                                      &info, 0);
+    }
+
+    return type;
+}
+
+
+GtkWidget *
+parasite_actionlist_new()
+{
+    return GTK_WIDGET(g_object_new(PARASITE_TYPE_ACTIONLIST, NULL));
+}
+
+
+gpointer
+parasite_actionlist_get_selected_object(ParasiteActionList *actionlist)
+{
+    GtkTreeIter iter;
+    GtkTreeSelection *sel;
+    GtkTreeModel *model;
+
+    sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(actionlist));
+
+    if (gtk_tree_selection_get_selected(sel, &model, &iter))
+    {
+        gpointer pointer;
+
+        gtk_tree_model_get(GTK_TREE_MODEL(model), &iter,
+                           ADDRESS, &pointer,
+                           -1);
+        return pointer;
+    }
+    return NULL;
+}
+
+
+// vim: set et sw=4 ts=4:
diff --git a/modules/other/parasite/action-list.h b/modules/other/parasite/action-list.h
new file mode 100644
index 0000000..fdb0236
--- /dev/null
+++ b/modules/other/parasite/action-list.h
@@ -0,0 +1,66 @@
+/*
+ * 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_ACTIONLIST_H_
+#define _GTKPARASITE_ACTIONLIST_H_
+
+
+#include <gtk/gtk.h>
+
+
+#define PARASITE_TYPE_ACTIONLIST            (parasite_actionlist_get_type())
+#define PARASITE_ACTIONLIST(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), PARASITE_TYPE_ACTIONLIST, 
ParasiteActionList))
+#define PARASITE_ACTIONLIST_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), PARASITE_TYPE_ACTIONLIST, 
ParasiteActionListClass))
+#define PARASITE_IS_ACTIONLIST(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), PARASITE_TYPE_ACTIONLIST))
+#define PARASITE_IS_ACTIONLIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PARASITE_TYPE_ACTIONLIST))
+#define PARASITE_ACTIONLIST_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), PARASITE_TYPE_ACTIONLIST, 
ParasiteActionListClass))
+
+
+typedef struct _ParasiteActionListPrivate ParasiteActionListPrivate;
+
+typedef struct _ParasiteActionList {
+    GtkTreeView parent;
+
+    // Private
+    ParasiteActionListPrivate *priv;
+} ParasiteActionList;
+
+typedef struct _ParasiteActionListClass {
+    GtkTreeViewClass parent;
+} ParasiteActionListClass;
+
+
+G_BEGIN_DECLS
+
+
+GType parasite_actionlist_get_type();
+GtkWidget *parasite_actionlist_new();
+gpointer parasite_actionlist_get_selected_object(ParasiteActionList *actionlist);
+
+
+G_END_DECLS
+
+
+#endif // _GTKPARASITE_ACTIONLIST_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..ae54d0a
--- /dev/null
+++ b/modules/other/parasite/inspect-button.c
@@ -0,0 +1,247 @@
+/*
+ * 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_pointer_ungrab(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_widget(PARASITE_WIDGET_TREE(parasite->widget_tree),
+                                               widget);
+        }
+    }
+}
+
+
+static void
+on_highlight_window_show(GtkWidget *window,
+                         ParasiteWindow *parasite)
+{
+    if (gtk_widget_is_composited(parasite->window))
+    {
+        gtk_window_set_opacity(GTK_WINDOW(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)
+{
+    GdkColor color;
+
+    if (parasite->highlight_window != NULL)
+        return;
+
+    color.red = 0;
+    color.green = 0;
+    color.blue = 65535;
+
+    parasite->highlight_window = gtk_window_new(GTK_WINDOW_POPUP);
+    gtk_widget_modify_bg(parasite->highlight_window, GTK_STATE_NORMAL,
+                         &color);
+
+    g_signal_connect(G_OBJECT(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_display_get_window_at_pointer(
+        gtk_widget_get_display(grab_window), 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);
+    height = gdk_window_get_height (selected_window);
+    width = gdk_window_get_width (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(G_OBJECT(parasite->grab_window),
+                         "button_release_event",
+                         G_CALLBACK(on_inspect_widget), parasite);
+        g_signal_connect(G_OBJECT(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_pointer_grab(gtk_widget_get_window(parasite->grab_window), FALSE,
+                     events,
+                     NULL,
+                     cursor,
+                     event->time);
+    gdk_cursor_unref(cursor);
+}
+
+
+GtkWidget *
+gtkparasite_inspect_button_new(ParasiteWindow *parasite)
+{
+    GtkWidget *button;
+
+    button = gtk_button_new_with_label("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 allocation;
+        gdk_window_get_origin(parent_window, &x, &y);
+        gtk_widget_get_allocation (widget, &allocation);
+        x += allocation.x;
+        y += allocation.y;
+
+        width = allocation.width;
+        height = allocation.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..ac34cf6
--- /dev/null
+++ b/modules/other/parasite/module.c
@@ -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.
+ */
+#include <glib.h>
+
+#include "config.h"
+#include "parasite.h"
+
+
+int
+gtk_module_init(gint argc, char *argv[])
+{
+    gtkparasite_window_create();
+    return FALSE;
+
+    return 0;
+}
+
+// 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..cc993a8
--- /dev/null
+++ b/modules/other/parasite/parasite.h
@@ -0,0 +1,67 @@
+/*
+ * 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 *action_list;
+    GtkWidget *python_shell;
+
+    GtkWidget *grab_window;
+    GtkWidget *highlight_window;
+
+    GtkWidget *widget_popup;
+    GtkWidget *action_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..0e505d2
--- /dev/null
+++ b/modules/other/parasite/prop-list.c
@@ -0,0 +1,248 @@
+/*
+ * 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_OBJECT,
+    NUM_COLUMNS
+};
+
+
+struct _ParasitePropListPrivate
+{
+    GtkWidget *widget;
+    GtkListStore *model;
+    GHashTable *prop_iters;
+    GList *signal_cnxs;
+};
+
+#define PARASITE_PROPLIST_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PARASITE_TYPE_PROPLIST, 
ParasitePropListPrivate))
+
+static GtkTreeViewClass *parent_class;
+
+
+static void
+parasite_proplist_init(ParasitePropList *proplist,
+                       ParasitePropListClass *klass)
+{
+    GtkCellRenderer *renderer;
+    GtkTreeViewColumn *column;
+
+    proplist->priv = PARASITE_PROPLIST_GET_PRIVATE(proplist);
+    proplist->priv->prop_iters =
+        g_hash_table_new_full(g_str_hash, g_str_equal,
+                              NULL, (GDestroyNotify)gtk_tree_iter_free);
+
+    proplist->priv->model =
+        gtk_list_store_new(NUM_COLUMNS,
+                           G_TYPE_STRING,  // COLUMN_NAME
+                           G_TYPE_STRING,  // COLUMN_VALUE
+                           G_TYPE_OBJECT); // COLUMN_OBJECT
+    gtk_tree_view_set_model(GTK_TREE_VIEW(proplist),
+                            GTK_TREE_MODEL(proplist->priv->model));
+
+    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("Property", renderer,
+                                                     "text", COLUMN_NAME,
+                                                     NULL);
+    gtk_tree_view_append_column(GTK_TREE_VIEW(proplist), column);
+    gtk_tree_view_column_set_resizable(column, TRUE);
+    gtk_tree_view_column_set_sort_order(column, GTK_SORT_ASCENDING);
+    gtk_tree_view_column_set_sort_column_id(column, COLUMN_NAME);
+
+    renderer = parasite_property_cell_renderer_new();
+    g_object_set(G_OBJECT(renderer), "scale", TREE_TEXT_SCALE, NULL);
+    g_object_set(G_OBJECT(renderer), "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(proplist), column);
+    gtk_tree_view_column_set_resizable(column, TRUE);
+
+    gtk_tree_sortable_set_sort_column_id(
+        GTK_TREE_SORTABLE(proplist->priv->model),
+        COLUMN_NAME, GTK_SORT_ASCENDING);
+}
+
+
+static void
+parasite_proplist_class_init(ParasitePropListClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+    parent_class = g_type_class_peek_parent(klass);
+
+    g_type_class_add_private(object_class, sizeof(ParasitePropListPrivate));
+}
+
+static void
+parasite_prop_list_update_prop(ParasitePropList *proplist,
+                               GtkTreeIter *iter,
+                               GParamSpec *prop)
+{
+    GValue gvalue = {0};
+    char *value;
+
+    g_value_init(&gvalue, prop->value_type);
+    g_object_get_property(G_OBJECT(proplist->priv->widget),
+                          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(proplist->priv->model, iter,
+                       COLUMN_NAME, prop->name,
+                       COLUMN_VALUE, value,
+                       COLUMN_OBJECT, proplist->priv->widget,
+                       -1);
+
+    g_free(value);
+    g_value_unset(&gvalue);
+}
+
+static void
+parasite_proplist_prop_changed_cb(GObject *pspec,
+                                  GParamSpec *prop,
+                                  ParasitePropList *proplist)
+{
+    GtkTreeIter *iter = g_hash_table_lookup(proplist->priv->prop_iters,
+                                            prop->name);
+
+    if (iter != NULL)
+        parasite_prop_list_update_prop(proplist, iter, prop);
+}
+
+
+GType
+parasite_proplist_get_type()
+{
+    static GType type = 0;
+
+    if (type == 0)
+    {
+        static const GTypeInfo info =
+        {
+            sizeof(ParasitePropListClass),
+            NULL, // base_init
+            NULL, // base_finalize
+            (GClassInitFunc) parasite_proplist_class_init,
+            NULL,
+            NULL, // class_data
+            sizeof(ParasitePropList),
+            0, // n_preallocs
+            (GInstanceInitFunc) parasite_proplist_init,
+        };
+
+        type = g_type_register_static(GTK_TYPE_TREE_VIEW,
+                                      "ParasitePropList",
+                                      &info, 0);
+    }
+
+    return type;
+}
+
+
+GtkWidget *
+parasite_proplist_new()
+{
+    return GTK_WIDGET(g_object_new(PARASITE_TYPE_PROPLIST, NULL));
+}
+
+void
+parasite_proplist_set_widget(ParasitePropList* proplist,
+                             GtkWidget *widget)
+{
+    GtkTreeIter iter;
+    GParamSpec **props;
+    guint num_properties;
+    guint i;
+    GList *l;
+
+    proplist->priv->widget = widget;
+
+    for (l = proplist->priv->signal_cnxs; l != NULL; l = l->next)
+    {
+        gulong id = GPOINTER_TO_UINT(l->data);
+
+        if (g_signal_handler_is_connected(widget, id))
+            g_signal_handler_disconnect(widget, id);
+    }
+
+    g_list_free(proplist->priv->signal_cnxs);
+    proplist->priv->signal_cnxs = NULL;
+
+    g_hash_table_remove_all(proplist->priv->prop_iters);
+    gtk_list_store_clear(proplist->priv->model);
+
+    props = g_object_class_list_properties(G_OBJECT_GET_CLASS(widget),
+                                           &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(proplist->priv->model, &iter);
+        parasite_prop_list_update_prop(proplist, &iter, prop);
+
+        g_hash_table_insert(proplist->priv->prop_iters, prop->name,
+                            gtk_tree_iter_copy(&iter));
+
+        /* Listen for updates */
+        signal_name = g_strdup_printf("notify::%s", prop->name);
+
+        proplist->priv->signal_cnxs =
+            g_list_prepend(proplist->priv->signal_cnxs, GINT_TO_POINTER(
+                g_signal_connect(G_OBJECT(widget), signal_name,
+                                 G_CALLBACK(parasite_proplist_prop_changed_cb),
+                                 proplist)));
+
+        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..354761b
--- /dev/null
+++ b/modules/other/parasite/prop-list.h
@@ -0,0 +1,72 @@
+/*
+ * 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;
+
+   // Private
+   ParasitePropListPrivate *priv;
+} ParasitePropList;
+
+typedef struct _ParasitePropListClass {
+   GtkTreeViewClass parent;
+
+   // Padding for future expansion
+   void (*reserved0)(void);
+   void (*reserved1)(void);
+   void (*reserved2)(void);
+   void (*reserved3)(void);
+} ParasitePropListClass;
+
+
+G_BEGIN_DECLS
+
+
+GType parasite_proplist_get_type();
+GtkWidget *parasite_proplist_new();
+void parasite_proplist_set_widget(ParasitePropList* proplist,
+                                  GtkWidget *widget);
+
+
+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..dc48936
--- /dev/null
+++ b/modules/other/parasite/property-cell-renderer.c
@@ -0,0 +1,464 @@
+/*
+ * 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"
+
+
+#define PARASITE_PROPERTY_CELL_RENDERER_GET_PRIVATE(obj) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((obj), PARASITE_TYPE_PROPERTY_CELL_RENDERER, \
+                                 ParasitePropertyCellRendererPrivate))
+
+
+typedef struct
+{
+    GObject *object;
+    char *name;
+
+} ParasitePropertyCellRendererPrivate;
+
+
+static void parasite_property_cell_renderer_get_property(GObject *obj,
+                                                         guint param_id,
+                                                         GValue *value,
+                                                         GParamSpec *pspec);
+static void parasite_property_cell_renderer_set_property(GObject *obj,
+                                                         guint param_id,
+                                                         const GValue *value,
+                                                         GParamSpec *pspec);
+
+#if 0
+static void parasite_property_cell_renderer_get_size(
+    GtkCellRenderer *renderer,
+    GtkWidget *widget,
+    GdkRectangle *cell_area,
+    gint *x_offset,
+    gint *y_offset,
+    gint *width,
+    gint *height);
+
+static void parasite_property_cell_renderer_render(
+    GtkCellRenderer *renderer,
+    GdkWindow *window,
+    GtkWidget *widget,
+    GdkRectangle *background_area,
+    GdkRectangle *cell_area,
+    GdkRectangle *expose_area,
+    GtkCellRendererState flags);
+#endif
+
+static GtkCellEditable *parasite_property_cell_renderer_start_editing(
+    GtkCellRenderer *renderer,
+    GdkEvent *event,
+    GtkWidget *widget,
+    const gchar *path,
+    const GdkRectangle *background_area,
+    const GdkRectangle *cell_area,
+    GtkCellRendererState flags);
+
+static void parasite_property_cell_renderer_stop_editing(
+    GtkCellEditable *editable,
+    GtkCellRenderer *renderer);
+
+enum
+{
+    LAST_SIGNAL
+};
+
+enum
+{
+    PROP_0,
+    PROP_OBJECT,
+    PROP_NAME
+};
+
+
+G_DEFINE_TYPE(ParasitePropertyCellRenderer, parasite_property_cell_renderer,
+              GTK_TYPE_CELL_RENDERER_TEXT);
+
+static void
+parasite_property_cell_renderer_init(ParasitePropertyCellRenderer *renderer)
+{
+}
+
+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 = parasite_property_cell_renderer_get_property;
+    object_class->set_property = parasite_property_cell_renderer_set_property;
+
+    cell_class->start_editing = parasite_property_cell_renderer_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));
+
+    g_type_class_add_private(object_class,
+                             sizeof(ParasitePropertyCellRendererPrivate));
+}
+
+static void
+parasite_property_cell_renderer_get_property(GObject *object,
+                                             guint param_id,
+                                             GValue *value,
+                                             GParamSpec *pspec)
+{
+    ParasitePropertyCellRendererPrivate *priv =
+        PARASITE_PROPERTY_CELL_RENDERER_GET_PRIVATE(object);
+
+    switch (param_id)
+    {
+        case PROP_OBJECT:
+            g_value_set_object(value, priv->object);
+            break;
+
+        case PROP_NAME:
+            g_value_set_string(value, priv->name);
+            break;
+
+        default:
+            G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
+            break;
+    }
+}
+
+static void
+parasite_property_cell_renderer_set_property(GObject *object,
+                                             guint param_id,
+                                             const GValue *value,
+                                             GParamSpec *pspec)
+{
+    ParasitePropertyCellRendererPrivate *priv =
+        PARASITE_PROPERTY_CELL_RENDERER_GET_PRIVATE(object);
+
+    switch (param_id)
+    {
+        case PROP_OBJECT:
+            priv->object = g_value_get_object(value);
+            g_object_notify(object, "object");
+            break;
+
+        case PROP_NAME:
+            g_free(priv->name);
+            priv->name = g_strdup(g_value_get_string(value));
+            g_object_notify(object, "name");
+            break;
+
+        default:
+            G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
+            break;
+    }
+}
+
+#if 0
+static void
+parasite_property_cell_renderer_render(GtkCellRenderer *renderer,
+                                       GdkWindow *window,
+                                       GtkWidget *widget,
+                                       GdkRectangle *background_area,
+                                       GdkRectangle *cell_area,
+                                       GdkRectangle *expose_area,
+                                       GtkCellRendererState flags)
+{
+}
+#endif
+
+static GtkCellEditable *
+parasite_property_cell_renderer_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);
+
+    if (!(prop->flags & G_PARAM_WRITABLE))
+        return NULL;
+
+    g_value_init(&gvalue, prop->value_type);
+    g_object_get_property(object, name, &gvalue);
+
+    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);
+    }
+
+    font_desc = pango_font_description_new();
+    pango_font_description_set_size(font_desc, 8 * PANGO_SCALE);
+    gtk_widget_modify_font(GTK_WIDGET(editable), font_desc);
+    pango_font_description_free(font_desc);
+
+    g_value_unset(&gvalue);
+
+    g_signal_connect(G_OBJECT(editable), "editing_done",
+                     G_CALLBACK(parasite_property_cell_renderer_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_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 (renderer, "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);
+}
+
+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..5f7aec7
--- /dev/null
+++ b/modules/other/parasite/property-cell-renderer.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_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
+{
+   GtkCellRendererText parent;
+
+} 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/widget-tree.c b/modules/other/parasite/widget-tree.c
new file mode 100644
index 0000000..0628532
--- /dev/null
+++ b/modules/other/parasite/widget-tree.c
@@ -0,0 +1,537 @@
+/*
+ * 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 "widget-tree.h"
+
+#include <string.h>
+#if HAVE_X11
+#include <gdk/gdkx.h>
+#endif
+
+
+enum
+{
+    WIDGET,
+    WIDGET_TYPE,
+    WIDGET_NAME,
+    WIDGET_REALIZED,
+    WIDGET_VISIBLE,
+    WIDGET_MAPPED,
+    WIDGET_WINDOW,
+    WIDGET_ADDRESS,
+    ROW_COLOR,
+    NUM_COLUMNS
+};
+
+
+enum
+{
+    WIDGET_CHANGED,
+    LAST_SIGNAL
+};
+
+
+struct _ParasiteWidgetTreePrivate
+{
+    GtkTreeStore *model;
+    gboolean edit_mode_enabled;
+};
+
+#define PARASITE_WIDGET_TREE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PARASITE_TYPE_WIDGET_TREE, 
ParasiteWidgetTreePrivate))
+
+static GtkTreeViewClass *parent_class;
+static guint widget_tree_signals[LAST_SIGNAL] = { 0 };
+
+
+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
+handle_toggle(GtkCellRendererToggle *toggle,
+              char *path_str,
+              ParasiteWidgetTree *widget_tree,
+              int column,
+              void (*enable_func)(GtkWidget*),
+              void (*disable_func)(GtkWidget*))
+{
+    GtkTreeIter iter;
+    GtkWidget *widget;
+    gboolean new_active = !gtk_cell_renderer_toggle_get_active(toggle);
+
+    if (!widget_tree->priv->edit_mode_enabled)
+        return;
+
+    gtk_tree_model_get_iter(GTK_TREE_MODEL(widget_tree->priv->model),
+                            &iter,
+                            gtk_tree_path_new_from_string(path_str));
+    gtk_tree_model_get(GTK_TREE_MODEL(widget_tree->priv->model), &iter,
+                       WIDGET, &widget,
+                       -1);
+
+    if (new_active)
+        enable_func(widget);
+    else
+        disable_func(widget);
+
+    gtk_tree_store_set(widget_tree->priv->model, &iter,
+                       column, new_active,
+                       -1);
+}
+
+
+static void
+on_toggle_realize(GtkCellRendererToggle *toggle,
+                  char *path_str,
+                  GtkWidget *treeview)
+{
+    handle_toggle(toggle,
+                  path_str,
+                  PARASITE_WIDGET_TREE(treeview),
+                  WIDGET_REALIZED,
+                  gtk_widget_realize,
+                  gtk_widget_unrealize);
+}
+
+
+static void
+on_toggle_visible(GtkCellRendererToggle *toggle,
+                  char *path_str,
+                  GtkWidget *treeview)
+{
+    handle_toggle(toggle,
+                  path_str,
+                  PARASITE_WIDGET_TREE(treeview),
+                  WIDGET_VISIBLE,
+                  gtk_widget_show,
+                  gtk_widget_hide);
+}
+
+
+static void
+on_toggle_map(GtkCellRendererToggle *toggle,
+              char *path_str,
+              GtkWidget *treeview)
+{
+    handle_toggle(toggle,
+                  path_str,
+                  PARASITE_WIDGET_TREE(treeview),
+                  WIDGET_MAPPED,
+                  gtk_widget_map,
+                  gtk_widget_unmap);
+}
+
+
+static void
+parasite_widget_tree_init(ParasiteWidgetTree *widget_tree,
+                          ParasiteWidgetTreeClass *klass)
+{
+    GtkCellRenderer *renderer;
+    GtkTreeViewColumn *column;
+    GtkTreeSelection *selection;
+
+    widget_tree->priv = PARASITE_WIDGET_TREE_GET_PRIVATE(widget_tree);
+
+    widget_tree->priv->model = gtk_tree_store_new(
+        NUM_COLUMNS,
+        G_TYPE_POINTER, // WIDGET
+        G_TYPE_STRING,  // WIDGET_NAME
+        G_TYPE_STRING,  // WIDGET_NAME
+        G_TYPE_BOOLEAN, // WIDGET_REALIZED
+        G_TYPE_BOOLEAN, // WIDGET_VISIBLE
+        G_TYPE_BOOLEAN, // WIDGET_MAPPED
+        G_TYPE_STRING,  // WIDGET_WINDOW
+        G_TYPE_STRING,  // WIDGET_ADDRESS
+        G_TYPE_STRING); // ROW_COLOR
+
+    widget_tree->priv->edit_mode_enabled = FALSE;
+
+    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), WIDGET_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", WIDGET_TYPE,
+                                                      "foreground", ROW_COLOR,
+                                                      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", WIDGET_NAME,
+                                                      "foreground", ROW_COLOR,
+                                                      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);
+    g_signal_connect(G_OBJECT(renderer), "toggled",
+                     G_CALLBACK(on_toggle_realize), widget_tree);
+
+    // 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);
+    //g_signal_connect(G_OBJECT(renderer), "toggled",
+    //                 G_CALLBACK(on_toggle_map), widget_tree);
+
+    // 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);
+    //g_signal_connect(G_OBJECT(renderer), "toggled",
+    //                 G_CALLBACK(on_toggle_visible), widget_tree);
+
+    // X Window 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("X Window",
+                                                      renderer,
+                                                      "text", WIDGET_WINDOW,
+                                                      "foreground", ROW_COLOR,
+                                                      NULL);
+    gtk_tree_view_append_column(GTK_TREE_VIEW(widget_tree), column);
+    gtk_tree_view_column_set_resizable(column, TRUE);
+
+    // 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", WIDGET_ADDRESS,
+                                                      "foreground", ROW_COLOR,
+                                                      NULL);
+    gtk_tree_view_append_column(GTK_TREE_VIEW(widget_tree), column);
+    gtk_tree_view_column_set_resizable(column, TRUE);
+}
+
+
+static void
+parasite_widget_tree_class_init(ParasiteWidgetTreeClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+    klass->widget_changed = NULL;
+
+    parent_class = g_type_class_peek_parent(klass);
+
+    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);
+
+    g_type_class_add_private(object_class, sizeof(ParasiteWidgetTreePrivate));
+}
+
+
+GType
+parasite_widget_tree_get_type()
+{
+    static GType type = 0;
+
+    if (type == 0)
+    {
+        static const GTypeInfo info =
+        {
+            sizeof(ParasiteWidgetTreeClass),
+            NULL, // base_init
+            NULL, // base_finalize
+            (GClassInitFunc) parasite_widget_tree_class_init,
+            NULL,
+            NULL, // class_data
+            sizeof(ParasiteWidgetTree),
+            0, // n_preallocs
+            (GInstanceInitFunc) parasite_widget_tree_init,
+        };
+
+        type = g_type_register_static(GTK_TYPE_TREE_VIEW,
+                                      "ParasiteWidgetTree",
+                                      &info, 0);
+    }
+
+    return type;
+}
+
+
+GtkWidget *
+parasite_widget_tree_new()
+{
+    return GTK_WIDGET(g_object_new(PARASITE_TYPE_WIDGET_TREE, NULL));
+}
+
+
+GtkWidget *
+parasite_widget_tree_get_selected_widget(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))
+    {
+        GtkWidget *widget;
+
+        gtk_tree_model_get(GTK_TREE_MODEL(model), &iter,
+                           WIDGET, &widget,
+                           -1);
+        return widget;
+    }
+    return NULL;
+}
+
+
+static void
+append_widget(GtkTreeStore *model,
+              GtkWidget *widget,
+              GtkTreeIter *parent_iter)
+{
+    GtkTreeIter iter;
+    const char *class_name = G_OBJECT_CLASS_NAME(GTK_WIDGET_GET_CLASS(widget));
+    const char *name;
+    const char *row_color;
+    GdkWindow *window;
+    char *window_info;
+    char *address;
+    gboolean realized;
+    gboolean mapped;
+    gboolean visible;
+    GList *l;
+
+    name = gtk_widget_get_name(widget);
+    if (name == NULL || strcmp(name, class_name) == 0) {
+        if (GTK_IS_LABEL(widget))
+        {
+            name = gtk_label_get_text(GTK_LABEL(widget));
+        }
+        else if (GTK_IS_BUTTON(widget))
+        {
+            name = gtk_button_get_label(GTK_BUTTON(widget));
+        }
+        else if (GTK_IS_WINDOW(widget))
+        {
+            name = gtk_window_get_title(GTK_WINDOW(widget));
+        }
+        else
+        {
+            name = "";
+        }
+    }
+
+    window = gtk_widget_get_window (widget);
+    if (window_info)
+    {
+#if HAVE_X11
+       window_info = g_strdup_printf("%p (XID 0x%x)", window,
+                                     (int)GDK_WINDOW_XID(window));
+#else
+       window_info = g_strdup("");
+#endif
+    }
+    else
+    {
+        window_info = g_strdup("");
+    }
+
+    address = g_strdup_printf("%p", widget);
+
+    realized = gtk_widget_get_realized (widget);
+    mapped = gtk_widget_get_mapped (widget);
+    visible = gtk_widget_get_visible (widget);
+
+    row_color = (realized && mapped && visible) ? "black" : "grey";
+
+    gtk_tree_store_append(model, &iter, parent_iter);
+    gtk_tree_store_set(model, &iter,
+                       WIDGET, widget,
+                       WIDGET_TYPE, class_name,
+                       WIDGET_NAME, name,
+                       WIDGET_REALIZED, realized,
+                       WIDGET_MAPPED, mapped,
+                       WIDGET_VISIBLE, visible,
+                       WIDGET_WINDOW, window_info,
+                       WIDGET_ADDRESS, address,
+                       ROW_COLOR, row_color,
+                       -1);
+
+    g_free(window_info);
+    g_free(address);
+
+    if (GTK_IS_CONTAINER(widget))
+    {
+        for (l = gtk_container_get_children(GTK_CONTAINER(widget));
+             l != NULL;
+             l = l->next)
+        {
+            append_widget(model, GTK_WIDGET(l->data), &iter);
+        }
+    }
+}
+
+
+void
+parasite_widget_tree_scan(ParasiteWidgetTree *widget_tree,
+                          GtkWidget *window)
+{
+    gtk_tree_store_clear(widget_tree->priv->model);
+    append_widget(widget_tree->priv->model, window, NULL);
+    gtk_tree_view_columns_autosize(GTK_TREE_VIEW(widget_tree));
+}
+
+
+void
+parasite_widget_tree_set_edit_mode(ParasiteWidgetTree *widget_tree,
+                                   gboolean edit)
+{
+    widget_tree->priv->edit_mode_enabled = edit;
+}
+
+
+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;
+}
+
+
+void
+parasite_widget_tree_select_widget(ParasiteWidgetTree *widget_tree,
+                                   GtkWidget *widget)
+{
+    GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(widget_tree));
+    GList *parents = get_parents(widget, NULL);
+    GList *l;
+    GtkTreeIter 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(model, &iter,
+                                                  in_root ? NULL
+                                                  : &parent_iter);
+              valid;
+              valid = gtk_tree_model_iter_next(model, &iter))
+        {
+            GtkWidget *iter_widget;
+
+            gtk_tree_model_get(model, &iter,
+                               WIDGET, &iter_widget,
+                               -1);
+
+            if (iter_widget == cur_widget)
+            {
+                parent_iter = iter;
+                in_root = FALSE;
+                found = TRUE;
+                break;
+            }
+        }
+
+        if (!found)
+        {
+            /* No good. Bail.. */
+            break;
+        }
+    }
+
+    if (found)
+    {
+        GtkTreePath *path = gtk_tree_model_get_path(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);
+    }
+
+    g_list_free(parents);
+}
+
+
+// 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..008c3a9
--- /dev/null
+++ b/modules/other/parasite/widget-tree.h
@@ -0,0 +1,73 @@
+/*
+ * 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();
+GtkWidget *parasite_widget_tree_new();
+GtkWidget *parasite_widget_tree_get_selected_widget(ParasiteWidgetTree *widget_tree);
+void parasite_widget_tree_scan(ParasiteWidgetTree *widget_tree,
+                               GtkWidget *window);
+void parasite_widget_tree_select_widget(ParasiteWidgetTree *widget_tree,
+                                        GtkWidget *widget);
+void parasite_widget_tree_set_edit_mode(ParasiteWidgetTree *widget_tree,
+                                        gboolean edit);
+
+
+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..a98e3e0
--- /dev/null
+++ b/modules/other/parasite/window.c
@@ -0,0 +1,224 @@
+/*
+ * 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 "action-list.h"
+#include "parasite.h"
+#include "prop-list.h"
+#include "widget-tree.h"
+
+#include "config.h"
+
+
+static void
+on_widget_tree_selection_changed(ParasiteWidgetTree *widget_tree,
+                                 ParasiteWindow *parasite)
+{
+    GtkWidget *selected = parasite_widget_tree_get_selected_widget(widget_tree);
+    if (selected != NULL) {
+        parasite_proplist_set_widget(PARASITE_PROPLIST(parasite->prop_list),
+                                     selected);
+
+        /* Flash the widget. */
+        gtkparasite_flash_widget(parasite, selected);
+    }
+}
+
+
+static GtkWidget *
+create_widget_list_pane(ParasiteWindow *parasite)
+{
+    GtkWidget *swin;
+
+    swin = gtk_scrolled_window_new(NULL, NULL);
+    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);
+
+    parasite->widget_tree = parasite_widget_tree_new();
+    gtk_widget_show(parasite->widget_tree);
+    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);
+
+    return swin;
+}
+
+static GtkWidget *
+create_prop_list_pane(ParasiteWindow *parasite)
+{
+    GtkWidget *swin;
+
+    swin = gtk_scrolled_window_new(NULL, NULL);
+    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);
+    gtk_widget_set_size_request(swin, 250, -1);
+
+    parasite->prop_list = parasite_proplist_new();
+    gtk_widget_show(parasite->prop_list);
+    gtk_container_add(GTK_CONTAINER(swin), parasite->prop_list);
+
+    return swin;
+}
+
+static void
+on_edit_mode_toggled(GtkWidget *toggle_button,
+                     ParasiteWindow *parasite)
+{
+    gboolean active =
+        gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle_button));
+
+    parasite->edit_mode_enabled = active;
+    parasite_widget_tree_set_edit_mode(PARASITE_WIDGET_TREE(parasite->widget_tree),
+                                       active);
+}
+
+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_widget_tree(ParasiteWindow *parasite)
+{
+    GtkWidget *vbox;
+    GtkWidget *bbox;
+    GtkWidget *button;
+    GtkWidget *swin;
+    GtkWidget *hpaned;
+
+    vbox = gtk_vbox_new(FALSE, 6);
+    gtk_widget_show(vbox);
+    gtk_container_set_border_width(GTK_CONTAINER(vbox), 12);
+
+    bbox = gtk_hbutton_box_new();
+    gtk_widget_show(bbox);
+    gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
+    gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_START);
+    gtk_box_set_spacing(GTK_BOX(bbox), 6);
+
+    button = gtkparasite_inspect_button_new(parasite);
+    gtk_widget_show(button);
+    gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+
+    button = gtk_toggle_button_new_with_mnemonic("_Edit Mode");
+    gtk_widget_show(button);
+    gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+
+    g_signal_connect(G_OBJECT(button), "toggled",
+                     G_CALLBACK(on_edit_mode_toggled), parasite);
+
+    button = gtk_toggle_button_new_with_mnemonic("_Show Graphic Updates");
+    gtk_widget_show(button);
+    gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+
+    g_signal_connect(G_OBJECT(button), "toggled",
+                     G_CALLBACK(on_show_graphic_updates_toggled), parasite);
+
+    hpaned = gtk_hpaned_new();
+    gtk_widget_show(hpaned);
+    gtk_box_pack_start(GTK_BOX(vbox), hpaned, TRUE, TRUE, 0);
+
+    swin = create_widget_list_pane(parasite);
+    gtk_widget_show(swin);
+    gtk_paned_pack1(GTK_PANED(hpaned), swin, TRUE, TRUE);
+
+    swin = create_prop_list_pane(parasite);
+    gtk_widget_show(swin);
+    gtk_paned_pack2(GTK_PANED(hpaned), swin, FALSE, TRUE);
+
+    return vbox;
+}
+
+static GtkWidget *
+create_action_list(ParasiteWindow *parasite)
+{
+    GtkWidget *vbox;
+    GtkWidget *swin;
+
+    vbox = gtk_vbox_new(FALSE, 6);
+    gtk_widget_show(vbox);
+    gtk_container_set_border_width(GTK_CONTAINER(vbox), 12);
+
+    swin = gtk_scrolled_window_new(NULL, NULL);
+    gtk_widget_show(swin);
+    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);
+    gtk_box_pack_start(GTK_BOX(vbox), swin, TRUE, TRUE, 0);
+
+    parasite->action_list = parasite_actionlist_new(parasite);
+    gtk_widget_show(parasite->action_list);
+    gtk_container_add(GTK_CONTAINER(swin), parasite->action_list);
+
+    return vbox;
+}
+
+void
+gtkparasite_window_create()
+{
+    ParasiteWindow *window;
+    GtkWidget *vpaned;
+    GtkWidget *notebook;
+    char *title;
+
+    window = g_new0(ParasiteWindow, 1);
+
+    /*
+     * Create the top-level window.
+     */
+    window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    gtk_window_set_default_size(GTK_WINDOW(window->window), 1000, 500);
+    gtk_container_set_border_width(GTK_CONTAINER(window->window), 12);
+    gtk_widget_show(window->window);
+
+    title = g_strdup_printf("Parasite - %s", g_get_application_name());
+    gtk_window_set_title(GTK_WINDOW(window->window), title);
+    g_free(title);
+
+    vpaned = gtk_vpaned_new();
+    gtk_widget_show(vpaned);
+    gtk_container_add(GTK_CONTAINER(window->window), vpaned);
+
+    notebook = gtk_notebook_new();
+    gtk_widget_show(notebook);
+    gtk_paned_pack1(GTK_PANED(vpaned), notebook, TRUE, FALSE);
+
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+                             create_widget_tree(window),
+                             gtk_label_new("Widget Tree"));
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+                             create_action_list(window),
+                             gtk_label_new("Action List"));
+}
+
+// vim: set et sw=4 ts=4:



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