changeset 18837:b5d6e360eca0

propagate from branch 'im.pidgin.pidgin' (head 5e5168d639fb9a3a29ab3fe44b976d6d25924403) to branch 'libgnt.pygnt' (head ec6a81b752b20677effe5ad3da03b7a1dbc922bf)
author Sadrul Habib Chowdhury <imadil@gmail.com>
date Tue, 07 Aug 2007 21:45:31 +0000
parents 3750e2e176cb (current diff) fad7efbb98a6 (diff)
children 1744f915edb2
files finch/libgnt/gnttree.c libpurple/protocols/bonjour/mdns_howl.h libpurple/protocols/bonjour/mdns_win32.h libpurple/protocols/oscar/family_chat.c pidgin/gtkcellrendererexpander.c pidgin/pixmaps/status/16/message-pending.png
diffstat 19 files changed, 1214 insertions(+), 33 deletions(-) [+]
line wrap: on
line diff
--- a/finch/libgnt/gntbindable.c	Tue Aug 07 18:14:59 2007 +0000
+++ b/finch/libgnt/gntbindable.c	Tue Aug 07 21:45:31 2007 +0000
@@ -184,7 +184,7 @@
 
 	action = g_hash_table_lookup(klass->actions, name);
 	if (!action) {
-		g_printerr("GntWidget: Invalid action name %s for %s\n",
+		g_printerr("GntBindable: Invalid action name %s for %s\n",
 				name, g_type_name(G_OBJECT_CLASS_TYPE(klass)));
 		if (list)
 			g_list_free(list);
--- a/finch/libgnt/gntmain.c	Tue Aug 07 18:14:59 2007 +0000
+++ b/finch/libgnt/gntmain.c	Tue Aug 07 21:45:31 2007 +0000
@@ -517,7 +517,8 @@
 
 void gnt_screen_release(GntWidget *widget)
 {
-	gnt_wm_window_close(wm, widget);
+	if (wm)
+		gnt_wm_window_close(wm, widget);
 }
 
 void gnt_screen_update(GntWidget *widget)
@@ -564,7 +565,9 @@
 
 void gnt_quit()
 {
-	g_hash_table_destroy(wm->nodes); /* XXX: */
+	g_object_unref(G_OBJECT(wm));
+	wm = NULL;
+
 	update_panels();
 	doupdate();
 	gnt_uninit_colors();
--- a/finch/libgnt/gnttextview.c	Tue Aug 07 18:14:59 2007 +0000
+++ b/finch/libgnt/gnttextview.c	Tue Aug 07 21:45:31 2007 +0000
@@ -68,17 +68,31 @@
 	int i = 0;
 	GList *lines;
 	int rows, scrcol;
+	int comp = 0;          /* Used for top-aligned text */
 	gboolean has_scroll = !(view->flags & GNT_TEXT_VIEW_NO_SCROLL);
 
 	wbkgd(widget->window, COLOR_PAIR(GNT_COLOR_NORMAL));
 	werase(widget->window);
 
+	if ((view->flags & GNT_TEXT_VIEW_TOP_ALIGN) &&
+			g_list_length(view->list) < widget->priv.height) {
+		GList *now = view->list;
+		comp = widget->priv.height - g_list_length(view->list);
+		view->list = g_list_nth_prev(view->list, comp);
+		if (!view->list) {
+			view->list = g_list_first(now);
+			comp = widget->priv.height - g_list_length(view->list);
+		} else {
+			comp = 0;
+		}
+	}
+
 	for (i = 0, lines = view->list; i < widget->priv.height && lines; i++, lines = lines->next)
 	{
 		GList *iter;
 		GntTextLine *line = lines->data;
 
-		wmove(widget->window, widget->priv.height - 1 - i, 0);
+		wmove(widget->window, widget->priv.height - 1 - i - comp, 0);
 
 		for (iter = line->segments; iter; iter = iter->next)
 		{
@@ -398,7 +412,7 @@
 static void
 gnt_text_view_size_changed(GntWidget *widget, int w, int h)
 {
-	if (w != widget->priv.width) {
+	if (w != widget->priv.width && GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_MAPPED)) {
 		gnt_text_view_reflow(GNT_TEXT_VIEW(widget));
 	}
 }
@@ -422,11 +436,16 @@
 gnt_text_view_init(GTypeInstance *instance, gpointer class)
 {
 	GntWidget *widget = GNT_WIDGET(instance);
-	
-	GNT_WIDGET_SET_FLAGS(GNT_WIDGET(instance), GNT_WIDGET_GROW_Y | GNT_WIDGET_GROW_X);
+	GntTextView *view = GNT_TEXT_VIEW(widget);
+	GntTextLine *line = g_new0(GntTextLine, 1);
 
+	GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_BORDER | GNT_WIDGET_NO_SHADOW | 
+            GNT_WIDGET_GROW_Y | GNT_WIDGET_GROW_X);
 	widget->priv.minw = 5;
 	widget->priv.minh = 2;
+	view->string = g_string_new(NULL);
+	view->list = g_list_append(view->list, line);
+
 	GNTDEBUG;
 }
 
@@ -464,13 +483,6 @@
 GntWidget *gnt_text_view_new()
 {
 	GntWidget *widget = g_object_new(GNT_TYPE_TEXT_VIEW, NULL);
-	GntTextView *view = GNT_TEXT_VIEW(widget);
-	GntTextLine *line = g_new0(GntTextLine, 1);
-
-	GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_BORDER | GNT_WIDGET_NO_SHADOW);
-
-	view->string = g_string_new(NULL);
-	view->list = g_list_append(view->list, line);
 
 	return widget;
 }
--- a/finch/libgnt/gnttextview.h	Tue Aug 07 18:14:59 2007 +0000
+++ b/finch/libgnt/gnttextview.h	Tue Aug 07 21:45:31 2007 +0000
@@ -47,9 +47,11 @@
 typedef struct _GntTextViewPriv		GntTextViewPriv;
 typedef struct _GntTextViewClass		GntTextViewClass;
 
-typedef enum _GntTextViewFlag {
+typedef enum
+{
 	GNT_TEXT_VIEW_NO_SCROLL     = 1 << 0,
 	GNT_TEXT_VIEW_WRAP_CHAR     = 1 << 1,
+	GNT_TEXT_VIEW_TOP_ALIGN     = 1 << 2,
 } GntTextViewFlag;
 
 struct _GntTextView
--- a/finch/libgnt/gnttree.c	Tue Aug 07 18:14:59 2007 +0000
+++ b/finch/libgnt/gnttree.c	Tue Aug 07 21:45:31 2007 +0000
@@ -450,7 +450,7 @@
 			if (COLUMN_INVISIBLE(tree, i)) {
 				continue;
 			}
-			mvwaddstr(widget->window, pos, x + 1, tree->columns[i].title);
+			mvwaddnstr(widget->window, pos, x + (x != pos), tree->columns[i].title, tree->columns[i].width);
 			NEXT_X;
 		}
 		if (pos)
@@ -768,6 +768,7 @@
 		g_string_free(tree->priv->search, TRUE);
 		tree->priv->search = NULL;
 		tree->priv->search_timeout = 0;
+		GNT_WIDGET_UNSET_FLAGS(GNT_WIDGET(tree), GNT_WIDGET_DISABLE_ACTIONS);
 	}
 }
 
@@ -1053,7 +1054,9 @@
 	GntTree *tree = GNT_TREE(widget);
 	tree->show_separator = TRUE;
 	tree->priv = g_new0(GntTreePriv, 1);
-	GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_GROW_X | GNT_WIDGET_GROW_Y | GNT_WIDGET_CAN_TAKE_FOCUS);
+	GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_GROW_X | GNT_WIDGET_GROW_Y |
+			GNT_WIDGET_CAN_TAKE_FOCUS | GNT_WIDGET_NO_SHADOW);
+	gnt_widget_set_take_focus(widget, TRUE);
 	widget->priv.minw = 4;
 	widget->priv.minh = 1;
 	GNTDEBUG;
@@ -1605,9 +1608,6 @@
 			"columns", col,
 			NULL);
 
-	GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_SHADOW);
-	gnt_widget_set_take_focus(widget, TRUE);
-
 	return widget;
 }
 
--- a/finch/libgnt/gntwm.c	Tue Aug 07 18:14:59 2007 +0000
+++ b/finch/libgnt/gntwm.c	Tue Aug 07 21:45:31 2007 +0000
@@ -684,6 +684,8 @@
 		{'j', "&#x2518;"},
 		{'a', "&#x2592;"},
 		{'n', "&#x253c;"},
+		{'w', "&#x252c;"},
+		{'v', "&#x2534;"},
 		{'\0', NULL}
 	};
 
@@ -1137,9 +1139,37 @@
 }
 
 static void
+accumulate_windows(gpointer window, gpointer node, gpointer p)
+{
+	GList *list = *(GList**)p;
+	list = g_list_prepend(list, window);
+	*(GList**)p = list;
+}
+
+static void
+gnt_wm_destroy(GObject *obj)
+{
+	GntWM *wm = GNT_WM(obj);
+	GList *list = NULL;
+	g_hash_table_foreach(wm->nodes, accumulate_windows, &list);
+	g_list_foreach(list, (GFunc)gnt_widget_destroy, NULL);
+	g_list_free(list);
+	g_hash_table_destroy(wm->nodes);
+	wm->nodes = NULL;
+
+	while (wm->workspaces) {
+		g_object_unref(wm->workspaces->data);
+		wm->workspaces = g_list_delete_link(wm->workspaces, wm->workspaces);
+	}
+}
+
+static void
 gnt_wm_class_init(GntWMClass *klass)
 {
 	int i;
+	GObjectClass *gclass = G_OBJECT_CLASS(klass);
+
+	gclass->dispose = gnt_wm_destroy;
 
 	klass->new_window = gnt_wm_new_window_real;
 	klass->decorate_window = NULL;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/libgnt/pygnt/Makefile.am	Tue Aug 07 21:45:31 2007 +0000
@@ -0,0 +1,40 @@
+EXTRA_DIST = gendef.sh
+
+pg_LTLIBRARIES = gnt.la
+
+pgdir = $(libdir)
+
+sources = \
+	gnt.def \
+	gnt.override \
+	gntbox.override \
+	gntfilesel.override \
+	gnttree.override \
+	gntwidget.override
+
+gnt_la_SOURCES = gnt.c common.c common.h gntmodule.c
+
+gnt_la_LDFLAGS = -module -avoid-version \
+	`pkg-config --libs pygobject-2.0`
+
+gnt_la_LIBADD = \
+	$(GLIB_LIBS) \
+	../libgnt.la
+
+AM_CPPFLAGS = \
+	-I../ \
+	$(GLIB_CFLAGS) \
+	$(GNT_CFLAGS)  \
+	-I/usr/include/python2.4 \
+	`pkg-config --cflags pygobject-2.0`
+
+CLEANFILES = gnt.def gnt.c gnt.defe
+
+gnt.def: $(srcdir)/../*.h
+	$(srcdir)/gendef.sh
+
+gnt.c: $(sources)
+	pygtk-codegen-2.0 --prefix gnt \
+	--override gnt.override \
+	gnt.def > $@
+
--- a/finch/libgnt/pygnt/Makefile.make	Tue Aug 07 18:14:59 2007 +0000
+++ b/finch/libgnt/pygnt/Makefile.make	Tue Aug 07 21:45:31 2007 +0000
@@ -10,5 +10,7 @@
 	--override gnt.override \
 	gnt.def > $@
 
+#python codegen/codegen.py --prefix gnt \
+
 clean:
 	@rm *.so *.o gnt.c
--- a/finch/libgnt/pygnt/dbus-gnt	Tue Aug 07 18:14:59 2007 +0000
+++ b/finch/libgnt/pygnt/dbus-gnt	Tue Aug 07 21:45:31 2007 +0000
@@ -24,6 +24,7 @@
     key = get_dict_key(conv)
     stuff = convwins[key]
     stuff[0].destroy()
+	# if a conv window is closed, then reopened, this thing crashes
     convwins[key] = None
 
 def wrote_msg(account, who, msg, conv, flags):
@@ -34,7 +35,8 @@
     tv.append_text_with_flags(who + ": ", 1)
     tv.append_text_with_flags(msg, 0)
     tv.scroll(0)
-    stuff[0].set_urgent()
+    if flags & 3:
+        stuff[0].set_urgent()
 
 bus = dbus.SessionBus()
 obj = bus.get_object("im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject")
@@ -91,6 +93,7 @@
     tv.clear()
     win.show()
     convwins[key] = [win, tv, entry]
+	# XXX: listen to "destroy" for the window, and reset the key
     return convwins[key]
 
 def show_buddylist():
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/libgnt/pygnt/example/rss/gnthtml.py	Tue Aug 07 21:45:31 2007 +0000
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+
+"""
+gr - An RSS-reader built using libgnt and feedparser.
+
+Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im>
+
+This application is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This application is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this application; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+USA
+"""
+
+"""
+This file defines GParser, which is a simple HTML parser to display HTML
+in a GntTextView nicely.
+"""
+
+import sgmllib
+import gnt
+
+class GParser(sgmllib.SGMLParser):
+    def __init__(self, view):
+        sgmllib.SGMLParser.__init__(self, False)
+        self.link = None
+        self.view = view
+        self.flag = gnt.TEXT_FLAG_NORMAL
+
+    def parse(self, s):
+        self.feed(s)
+        self.close()
+
+    def unknown_starttag(self, tag, attrs):
+        if tag in ["b", "i", "blockquote", "strong"]:
+            self.flag = self.flag | gnt.TEXT_FLAG_BOLD
+        elif tag in ["p", "hr", "br"]:
+            self.view.append_text_with_flags("\n", self.flag)
+        else:
+            print tag
+
+    def unknown_endtag(self, tag):
+        if tag in ["b", "i", "blockquote", "strong"]:
+            self.flag = self.flag & ~gnt.TEXT_FLAG_BOLD
+        elif tag in ["p", "hr", "br"]:
+            self.view.append_text_with_flags("\n", self.flag)
+        else:
+            print tag
+
+    def start_u(self, attrs):
+        self.flag = self.flag | gnt.TEXT_FLAG_UNDERLINE
+
+    def end_u(self):
+        self.flag = self.flag & ~gnt.TEXT_FLAG_UNDERLINE
+
+    def start_a(self, attributes):
+        for name, value in attributes:
+            if name == "href":
+                self.link = value
+
+    def do_img(self, attrs):
+        for name, value in attrs:
+            if name == 'src':
+                self.view.append_text_with_flags("[img:" + value + "]", self.flag)
+
+    def end_a(self):
+        if not self.link:
+            return
+        self.view.append_text_with_flags(" (", self.flag)
+        self.view.append_text_with_flags(self.link, self.flag | gnt.TEXT_FLAG_UNDERLINE)
+        self.view.append_text_with_flags(")", self.flag)
+        self.link = None
+
+    def handle_data(self, data):
+        if len(data.strip()) == 0:
+            return
+        self.view.append_text_with_flags(data, self.flag)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/libgnt/pygnt/example/rss/gntrss-ui.py	Tue Aug 07 21:45:31 2007 +0000
@@ -0,0 +1,399 @@
+#!/usr/bin/env python
+
+"""
+gr - An RSS-reader built using libgnt and feedparser.
+
+Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im>
+
+This application is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This application is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this application; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+USA
+"""
+
+"""
+This file deals with the UI part (gnt) of the application
+
+TODO:
+    - Allow showing feeds of only selected 'category' and/or 'priority'. A different
+      window should be used to change such filtering.
+    - Display details of each item in its own window.
+    - Add search capability, and allow searching only in title/body. Also allow
+      filtering in the search results.
+    - Show the data and time for feed items (probably in a separate column .. perhaps not)
+    - Have a simple way to add a feed.
+    - Allow renaming a feed.
+"""
+
+import gntrss
+import gnthtml
+import gnt
+import gobject
+import sys
+
+__version__ = "0.0.1alpha"
+__author__ = "Sadrul Habib Chowdhury (sadrul@pidgin.im)"
+__copyright__ = "Copyright 2007, Sadrul Habib Chowdhury"
+__license__ = "GPL" # see full license statement above
+
+gnt.gnt_init()
+
+class RssTree(gnt.Tree):
+    __gsignals__ = {
+        'active_changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,))
+    }
+
+    __gntbindings__ = {
+        'jump-next-unread' : ('jump_next_unread', 'J')
+    }
+
+    def jump_next_unread(self, null):
+        first = None
+        next = None
+        all = self.get_rows()
+        for item in all:
+            if item.unread:
+                if next:
+                    first = item
+                    break
+                elif not first and self.active != item:
+                    first = item
+            if self.active == item:
+                next = item
+        if first:
+            self.set_active(first)
+            self.set_selected(first)
+
+    def __init__(self):
+        self.active = None
+        gnt.Tree.__init__(self)
+        gnt.set_flag(self, 8)    # remove borders
+        self.connect('key_pressed', self.do_key_pressed)
+
+    def set_active(self, active):
+        if self.active == active:
+            return
+        if self.active:
+            flag = gnt.TEXT_FLAG_NORMAL
+            if self.active.unread:
+                flag = flag | gnt.TEXT_FLAG_BOLD
+            self.set_row_flags(self.active, flag)
+        old = self.active
+        self.active = active
+        flag = gnt.TEXT_FLAG_UNDERLINE
+        if self.active.unread:
+            flag = flag | gnt.TEXT_FLAG_BOLD
+        self.set_row_flags(self.active, flag)
+        self.emit('active_changed', old)
+
+    def do_key_pressed(self, null, text):
+        if text == '\r':
+            now = self.get_selection_data()
+            self.set_active(now)
+            return True
+        return False
+
+gobject.type_register(RssTree)
+gnt.register_bindings(RssTree)
+
+win = gnt.Box(homo = False, vert = True)
+win.set_toplevel(True)
+win.set_title("GntRss")
+win.set_pad(0)
+
+#
+# [[[ Generic feed/item callbacks
+#
+def feed_item_added(feed, item):
+    add_feed_item(item)
+
+def add_feed(feed):
+    if not feed.get_data('gntrss-connected'):
+        feed.connect('added', feed_item_added)
+        feed.connect('notify', update_feed_title)
+        feed.set_data('gntrss-connected', True)
+    feeds.add_row_after(feed, [feed.title, str(feed.unread)], None, None)
+
+def remove_item(item, feed):
+    items.remove(item)
+
+def update_feed_item(item, property):
+    if property.name == 'unread':
+        if feeds.active == item.parent:
+            flag = 0
+            if item == items.active:
+                flag = gnt.TEXT_FLAG_UNDERLINE
+            if item.unread:
+                flag = flag | gnt.TEXT_FLAG_BOLD
+            else:
+                flag = flag | gnt.TEXT_FLAG_NORMAL
+            items.set_row_flags(item, flag)
+
+        unread = item.parent.unread
+        if item.unread:
+            unread = unread + 1
+        else:
+            unread = unread - 1
+        item.parent.set_property('unread', unread)
+
+def add_feed_item(item):
+    currentfeed = feeds.active
+    if item.parent != currentfeed:
+        return
+    months = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+    dt = str(item.date_parsed[2]) + "." + months[item.date_parsed[1]] + "." + str(item.date_parsed[0])
+    items.add_row_after(item, [str(item.title), dt], None, None)
+    if item.unread:
+        items.set_row_flags(item, gnt.TEXT_FLAG_BOLD)
+    if not item.get_data('gntrss-connected'):
+        item.set_data('gntrss-connected', True)
+        # this needs to happen *without* having to add the item in the tree
+        item.connect('notify', update_feed_item)
+        item.connect('delete', remove_item)
+
+#
+# ]]] Generic feed/item callbacks
+#
+
+
+####
+# [[[ The list of feeds
+###
+
+# 'Add Feed' dialog
+add_feed_win = None
+def add_feed_win_closed(win):
+    global add_feed_win
+    add_feed_win = None
+
+def add_new_feed():
+    global add_feed_win
+
+    if add_feed_win:
+        gnt.gnt_window_present(add_feed_win)
+        return
+    win = gnt.Window()
+    win.set_title("New Feed")
+
+    box = gnt.Box(False, False)
+    label = gnt.Label("Link")
+    box.add_widget(label)
+    entry = gnt.Entry("")
+    entry.set_size(40, 1)
+    box.add_widget(entry)
+
+    win.add_widget(box)
+    win.show()
+    add_feed_win = win
+    add_feed_win.connect("destroy", add_feed_win_closed)
+
+#
+# The active row in the feed-list has changed. Update the feed-item table.
+def feed_active_changed(tree, old):
+    items.remove_all()
+    if not tree.active:
+        return
+    update_items_title()
+    for item in tree.active.items:
+        add_feed_item(item)
+    win.give_focus_to_child(items)
+
+#
+# Check for the action keys and decide how to deal with them.
+def feed_key_pressed(tree, text):
+    if tree.is_searching():
+        return
+    if text == 'r':
+        feed = tree.get_selection_data()
+        tree.perform_action_key('j')
+        #tree.perform_action('move-down')
+        feed.refresh()
+    elif text == 'R':
+        feeds = tree.get_rows()
+        for feed in feeds:
+            feed.refresh()
+    elif text == 'm':
+        feed = tree.get_selection_data()
+        if feed:
+            feed.mark_read()
+            feed.set_property('unread', 0)
+    elif text == 'a':
+        add_new_feed()
+    else:
+        return False
+    return True
+
+feeds = RssTree()
+feeds.set_property('columns', 2)
+feeds.set_col_width(0, 20)
+feeds.set_col_width(1, 6)
+feeds.set_column_resizable(0, False)
+feeds.set_column_resizable(1, False)
+feeds.set_column_is_right_aligned(1, True)
+feeds.set_show_separator(False)
+feeds.set_column_title(0, "Feeds")
+feeds.set_show_title(True)
+
+feeds.connect('active_changed', feed_active_changed)
+feeds.connect('key_pressed', feed_key_pressed)
+gnt.unset_flag(feeds, 256)   # Fix the width
+
+####
+# ]]] The list of feeds
+###
+
+####
+# [[[ The list of items in the feed
+####
+
+#
+# The active item in the feed-item list has changed. Update the
+# summary content.
+def item_active_changed(tree, old):
+    details.clear()
+    if not tree.active:
+        return
+    item = tree.active
+    details.append_text_with_flags(str(item.title) + "\n", gnt.TEXT_FLAG_BOLD)
+    details.append_text_with_flags("Link: ", gnt.TEXT_FLAG_BOLD)
+    details.append_text_with_flags(str(item.link) + "\n", gnt.TEXT_FLAG_UNDERLINE)
+    details.append_text_with_flags("Date: ", gnt.TEXT_FLAG_BOLD)
+    details.append_text_with_flags(str(item.date) + "\n", gnt.TEXT_FLAG_NORMAL)
+    details.append_text_with_flags("\n", gnt.TEXT_FLAG_NORMAL)
+    parser = gnthtml.GParser(details)
+    parser.parse(str(item.summary))
+    item.mark_unread(False)
+
+    if old and old.unread:   # If the last selected item is marked 'unread', then make sure it's bold
+        items.set_row_flags(old, gnt.TEXT_FLAG_BOLD)
+
+#
+# Look for action keys in the feed-item list.
+def item_key_pressed(tree, text):
+    if tree.is_searching():
+        return
+    current = tree.get_selection_data()
+    if text == 'M':     # Mark all of the items 'read'
+        feed = feeds.active
+        if feed:
+            feed.mark_read()
+    elif text == 'm':     # Mark the current item 'read'
+        current.mark_unread(False)
+        tree.perform_action_key('j')
+    elif text == 'U':     # Mark the current item 'unread'
+        current.mark_unread(True)
+    elif text == 'd':
+        current.remove()
+        tree.perform_action_key('j')
+    else:
+        return False
+    return True
+
+items = RssTree()
+items.set_property('columns', 2)
+items.set_col_width(0, 40)
+items.set_col_width(1, 11)
+items.set_column_resizable(1, False)
+items.set_column_title(0, "Items")
+items.set_column_title(1, "Date")
+items.set_show_title(True)
+items.connect('key_pressed', item_key_pressed)
+items.connect('active_changed', item_active_changed)
+
+####
+# ]]] The list of items in the feed
+####
+
+#
+# Update the title of the items list depending on the selection in the feed list
+def update_items_title():
+    feed = feeds.active
+    if feed:
+        items.set_column_title(0, str(feed.title) + ": " + str(feed.unread) + "(" + str(len(feed.items)) + ")")
+    else:
+        items.set_column_title(0, "Items")
+    items.draw()
+
+# The container on the top
+line = gnt.Line(vertical = False)
+
+# The textview to show the details of a feed
+details = gnt.TextView()
+details.set_take_focus(True)
+details.set_flag(gnt.TEXT_VIEW_TOP_ALIGN)
+details.attach_scroll_widget(details)
+
+# Make it look nice
+s = feeds.get_size()
+size = gnt.screen_size()
+size[0] = size[0] - s[0]
+items.set_size(size[0], size[1] / 2)
+details.set_size(size[0], size[1] / 2)
+
+# Category tree
+cat = gnt.Tree()
+cat.set_property('columns', 1)
+cat.set_column_title(0, 'Category')
+cat.set_show_title(True)
+gnt.set_flag(cat, 8)    # remove borders
+
+box = gnt.Box(homo = False, vert = False)
+box.set_pad(0)
+
+vbox = gnt.Box(homo = False, vert = True)
+vbox.set_pad(0)
+vbox.add_widget(feeds)
+vbox.add_widget(gnt.Line(False))
+vbox.add_widget(cat)
+box.add_widget(vbox)
+
+box.add_widget(gnt.Line(True))
+
+vbox = gnt.Box(homo = False, vert = True)
+vbox.set_pad(0)
+vbox.add_widget(items)
+vbox.add_widget(gnt.Line(False))
+vbox.add_widget(details)
+box.add_widget(vbox)
+
+win.add_widget(box)
+win.show()
+
+def update_feed_title(feed, property):
+    if property.name == 'title':
+        if feed.customtitle:
+            title = feed.customtitle
+        else:
+            title = feed.title
+        feeds.change_text(feed, 0, title)
+    elif property.name == 'unread':
+        feeds.change_text(feed, 1, str(feed.unread) + "(" + str(len(feed.items)) + ")")
+        flag = 0
+        if feeds.active == feed:
+            flag = gnt.TEXT_FLAG_UNDERLINE
+            update_items_title()
+        if feed.unread > 0:
+            flag = flag | gnt.TEXT_FLAG_BOLD
+        feeds.set_row_flags(feed, flag)
+
+# populate everything
+for feed in gntrss.feeds:
+    feed.refresh()
+    feed.set_auto_refresh(True)
+    add_feed(feed)
+
+gnt.gnt_register_action("Stuff", add_new_feed)
+gnt.gnt_main()
+
+gnt.gnt_quit()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/libgnt/pygnt/example/rss/gntrss.py	Tue Aug 07 21:45:31 2007 +0000
@@ -0,0 +1,250 @@
+#!/usr/bin/env python
+
+"""
+gr - An RSS-reader built using libgnt and feedparser.
+
+Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im>
+
+This application is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This application is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this application; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+USA
+"""
+
+"""
+This file deals with the rss parsing part (feedparser) of the application
+"""
+
+import os
+import tempfile, urllib2
+import feedparser
+import gobject
+import sys
+import time
+
+##
+# The FeedItem class. It will update emit 'delete' signal when it's
+# destroyed.
+##
+class FeedItem(gobject.GObject):
+    __gproperties__ = {
+        'unread' : (gobject.TYPE_BOOLEAN, 'read',
+            'The unread state of the item.',
+            False, gobject.PARAM_READWRITE)
+    }
+    __gsignals__ = {
+        'delete' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,))
+    }
+    def __init__(self, item, parent):
+        self.__gobject_init__()
+        try:
+            "Apparently some feed items don't have any dates in them"
+            self.date = item['date']
+            self.date_parsed = item['date_parsed']
+        except:
+            item['date'] = self.date = time.ctime()
+            self.date_parsed = feedparser._parse_date(self.date)
+
+        self.title = item['title']
+        sum = item['summary']
+        self.summary = item['summary'].encode('utf8')
+        self.link = item['link']
+        self.parent = parent
+        self.unread = True
+
+    def remove(self):
+        self.emit('delete', self.parent)
+        if self.unread:
+            self.parent.set_property('unread', self.parent.unread - 1)
+
+    def do_set_property(self, property, value):
+        if property.name == 'unread':
+            self.unread = value
+
+    def mark_unread(self, unread):
+        if self.unread == unread:
+            return
+        self.set_property('unread', unread)
+
+gobject.type_register(FeedItem)
+
+def item_hash(item):
+    return str(item['date'] + item['title'])
+
+"""
+The Feed class. It will update the 'link', 'title', 'desc' and 'items'
+attributes if/when they are updated (triggering 'notify::<attr>' signal)
+
+TODO:
+    - Add a 'count' attribute
+    - Each feed will have a 'uidata', which will be its display window
+    - Look into 'category'. Is it something that feed defines, or the user?
+    - Have separate refresh times for each feed.
+    - Have 'priority' for each feed. (somewhat like category, perhaps?)
+"""
+class Feed(gobject.GObject):
+    __gproperties__ = {
+        'link' : (gobject.TYPE_STRING, 'link',
+            'The web page this feed is associated with.',
+            '...', gobject.PARAM_READWRITE),
+        'title' : (gobject.TYPE_STRING, 'title',
+            'The title of the feed.',
+            '...', gobject.PARAM_READWRITE),
+        'desc' : (gobject.TYPE_STRING, 'description',
+            'The description for the feed.',
+            '...', gobject.PARAM_READWRITE),
+        'items' : (gobject.TYPE_POINTER, 'items',
+            'The items in the feed.', gobject.PARAM_READWRITE),
+        'unread' : (gobject.TYPE_INT, 'unread',
+            'Number of unread items in the feed.', 0, 10000, 0, gobject.PARAM_READWRITE)
+    }
+    __gsignals__ = {
+        'added' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,))
+    }
+
+    def __init__(self, feed):
+        self.__gobject_init__()
+        url = feed['link']
+        name = feed['name']
+        self.url = url           # The url of the feed itself
+        self.link = url          # The web page associated with the feed
+        self.desc = url
+        self.title = (name, url)[not name]
+        self.customtitle = name
+        self.unread = 0
+        self.items = []
+        self.hash = {}
+        self.pending = False
+        self._refresh = {'time' : 30, 'id' : 0}
+
+    def do_set_property(self, property, value):
+        if property.name == 'link':
+            self.link = value
+        elif property.name == 'desc':
+            self.desc = value
+        elif property.name == 'title':
+            self.title = value
+        elif property.name == 'unread':
+            self.unread = value
+        pass
+
+    def set_result(self, result):
+        # XXX Look at result['bozo'] first, and emit some signal that the UI can use
+        # to indicate (dim the row?) that the feed has invalid XML format or something
+
+        try:
+            channel = result['channel']
+            self.set_property('link', channel['link'])
+            self.set_property('desc', channel['description'])
+            self.set_property('title', channel['title'])
+            items = result['items']
+        except:
+            items = ()
+
+        tmp = {}
+        for item in self.items:
+            tmp[hash(item)] = item
+
+        unread = self.unread
+        for item in items:
+            try:
+                exist = self.hash[item_hash(item)]
+                del tmp[hash(exist)]
+            except:
+                itm = FeedItem(item, self)
+                self.items.append(itm)
+                self.emit('added', itm)
+                self.hash[item_hash(item)] = itm
+                unread = unread + 1
+
+        if unread != self.unread:
+            self.set_property('unread', unread)
+
+        for hv in tmp:
+            tmp[hv].remove()
+            "Also notify the UI about the count change"
+
+        self.pending = False
+        return False
+
+    def refresh(self):
+        if self.pending:
+            return
+        self.pending = True
+        FeedReader(self).run()
+        return True
+
+    def mark_read(self):
+        for item in self.items:
+            item.mark_unread(False)
+
+    def set_auto_refresh(self, auto):
+        if auto:
+            if self._refresh['id']:
+                return
+            if self._refresh['time'] < 1:
+                self._refresh['time'] = 1
+            self.id = gobject.timeout_add(self._refresh['time'] * 1000 * 60, self.refresh)
+        else:
+            if not self._refresh['id']:
+                return
+            gobject.source_remove(self._refresh['id'])
+            self._refresh['id'] = 0
+
+gobject.type_register(Feed)
+
+"""
+The FeedReader updates a Feed. It fork()s off a child to avoid blocking.
+"""
+class FeedReader:
+    def __init__(self, feed):
+        self.feed = feed
+
+    def reap_child(self, pid, status):
+        result = feedparser.parse(self.tmpfile.name)
+        self.tmpfile.close()
+        self.feed.set_result(result)
+
+    def run(self):
+        self.tmpfile = tempfile.NamedTemporaryFile()
+        self.pid = os.fork()
+        if self.pid == 0:
+            tmp = urllib2.urlopen(self.feed.url)
+            content = tmp.read()
+            tmp.close()
+            self.tmpfile.write(content)
+            self.tmpfile.flush()
+            # Do NOT close tmpfile here
+            os._exit(os.EX_OK)
+        gobject.child_watch_add(self.pid, self.reap_child)
+
+feeds = []
+urls = (
+    {'name': '/.',
+     'link': "http://rss.slashdot.org/Slashdot/slashdot"},
+    {'name': 'KernelTrap',
+     'link': "http://kerneltrap.org/node/feed"},
+    {'name': None,
+     'link': "http://pidgin.im/rss.php"},
+    {'name': "F1",
+     'link': "http://www.formula1.com/rss/news/latest.rss"},
+    {'name': "Freshmeat",
+     'link': "http://www.pheedo.com/f/freshmeatnet_announcements_unix"},
+    {'name': "Cricinfo",
+     'link': "http://www.cricinfo.com/rss/livescores.xml"}
+)
+
+for url in urls:
+    feed = Feed(url)
+    feeds.append(feed)
+
--- a/finch/libgnt/pygnt/gendef.sh	Tue Aug 07 18:14:59 2007 +0000
+++ b/finch/libgnt/pygnt/gendef.sh	Tue Aug 07 21:45:31 2007 +0000
@@ -28,7 +28,7 @@
 	gnt.h"
 
 # Generate the def file
-rm gnt.def
+rm -f gnt.def
 for file in $FILES
 do
 	python /usr/share/pygtk/2.0/codegen/h2def.py ../$file >> gnt.def
@@ -43,6 +43,7 @@
 GNT_TYPE_KEY_PRESS_MODE
 GNT_TYPE_ENTRY_FLAG
 GNT_TYPE_TEXT_FORMAT_FLAGS
+GNT_TYPE_TEXT_VIEW_FLAG
 "
 
 for enum in $ENUMS
--- a/finch/libgnt/pygnt/gnt.override	Tue Aug 07 18:14:59 2007 +0000
+++ b/finch/libgnt/pygnt/gnt.override	Tue Aug 07 21:45:31 2007 +0000
@@ -29,8 +29,10 @@
 #include "common.h"
 %%
 include
+ gntbox.override
  gntfilesel.override
  gnttree.override
+ gntwidget.override
 %%
 modulename gnt
 %%
@@ -39,3 +41,154 @@
 ignore-glob
 	*_get_gtype
 %%
+define set_flag
+static PyObject *
+_wrap_set_flag(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+	static char *kwlist[] = {"flags", NULL};
+	PyGObject *widget;
+	int flags;
+
+	if (!PyArg_ParseTuple(args, "O!i:gnt.set_flag", &PyGntWidget_Type, &widget,
+				&flags)) {
+		return NULL;
+	}
+
+	GNT_WIDGET_SET_FLAGS(widget->obj, flags);
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+%%
+define unset_flag
+static PyObject *
+_wrap_unset_flag(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+	static char *kwlist[] = {"flags", NULL};
+	PyGObject *widget;
+	int flags;
+
+	if (!PyArg_ParseTuple(args, "O!i:gnt.unset_flag", &PyGntWidget_Type, &widget,
+				&flags)) {
+		return NULL;
+	}
+
+	GNT_WIDGET_UNSET_FLAGS(widget->obj, flags);
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+%%
+define screen_size noargs
+static PyObject *
+_wrap_screen_size(PyObject *self)
+{
+	PyObject *list = PyList_New(0);
+
+	if (list == NULL)
+		return NULL;
+
+	PyList_Append(list, PyInt_FromLong((long)getmaxx(stdscr)));
+	PyList_Append(list, PyInt_FromLong((long)getmaxy(stdscr)));
+
+	return list;
+}
+%%
+override gnt_register_action
+static GHashTable *actions;
+
+
+
+static PyObject *
+_wrap_gnt_register_action(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+	static char *kwlist[] = {"name", "callback", NULL};
+	PyGObject *callback;
+	GClosure *closure;
+	char *name;
+
+	if (!PyArg_ParseTuple(args, "sO:gnt.gnt_register_action", &name, &callback)) {
+		return NULL;
+	}
+
+	if (!PyCallable_Check(callback)) {
+		PyErr_SetString(PyExc_TypeError, "the callback must be callable ... doh!");
+		return NULL;
+	}
+
+	gnt_register_action(name, callback->obj);
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+%%
+define register_bindings
+
+static gboolean
+pygnt_binding_callback(GntBindable *bindable, GList *list)
+{
+	PyObject *wrapper = pygobject_new(G_OBJECT(bindable));
+	PyObject_CallMethod(wrapper, list->data, "O", Py_None);
+	Py_DECREF(wrapper);
+	return TRUE;
+}
+
+static PyObject *
+_wrap_register_bindings(PyObject *self, PyObject *args)
+{
+	PyTypeObject *class;
+	int pos = 0;
+	PyObject *key, *value, *gbindings;
+	GntBindableClass *bindable;
+
+	if (!PyArg_ParseTuple(args, "O!:gnt.register_bindings",
+				&PyType_Type, &class)) {
+		/* Make sure it's a GntBindableClass subclass */
+		PyErr_SetString(PyExc_TypeError,
+				"argument must be a GntBindable subclass");
+		return NULL;
+	}
+
+	gbindings = PyDict_GetItemString(class->tp_dict, "__gntbindings__");
+	if (!gbindings)
+		goto end;
+
+	if (!PyDict_Check(gbindings)) {
+		PyErr_SetString(PyExc_TypeError,
+				"__gntbindings__ attribute not a dict!");
+		return NULL;
+	}
+
+	bindable = g_type_class_ref(pyg_type_from_object((PyObject *)class));
+	while (PyDict_Next(gbindings, &pos, &key, &value)) {
+		const char *trigger, *callback, *name;
+		GList *list = NULL;
+
+		if (!PyString_Check(key)) {
+			PyErr_SetString(PyExc_TypeError,
+					"__gntbindings__ keys must be strings");
+			g_type_class_unref(bindable);
+			return NULL;
+		}
+		name = PyString_AsString(key);
+
+		if (!PyTuple_Check(value) ||
+				!PyArg_ParseTuple(value, "ss", &callback, &trigger)) {
+			PyErr_SetString(PyExc_TypeError,
+					"__gntbindings__ values must be (callback, trigger) tupples");
+			g_type_class_unref(bindable);
+			return NULL;
+		}
+
+		gnt_bindable_class_register_action(bindable, name, pygnt_binding_callback,
+				trigger, g_strdup(callback), NULL);
+	}
+	if (gbindings)
+		PyDict_DelItemString(class->tp_dict, "__gntbindings__");
+	g_type_class_unref(bindable);
+
+end:
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/libgnt/pygnt/gntbox.override	Tue Aug 07 21:45:31 2007 +0000
@@ -0,0 +1,38 @@
+/**
+ * pygnt- Python bindings for the GNT toolkit.
+ * Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im>
+ *
+ *   gntbox.override: overrides for the box widget.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+%%
+override gnt_box_add_widget kwargs
+static PyObject *
+_wrap_gnt_box_add_widget(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+    static char *kwlist[] = { "widget", NULL };
+    PyGObject *widget;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!:GntBox.add_widget", kwlist, &PyGntWidget_Type, &widget))
+        return NULL;
+    
+    gnt_box_add_widget(GNT_BOX(self->obj), GNT_WIDGET(widget->obj));
+	Py_INCREF(widget);
+    
+    Py_INCREF(Py_None);
+    return Py_None;
+}
--- a/finch/libgnt/pygnt/gntmodule.c	Tue Aug 07 18:14:59 2007 +0000
+++ b/finch/libgnt/pygnt/gntmodule.c	Tue Aug 07 21:45:31 2007 +0000
@@ -9,11 +9,12 @@
     PyObject *m, *d;
  
     init_pygobject ();
- 
+
     m = Py_InitModule ("gnt", gnt_functions);
     d = PyModule_GetDict (m);
  
     gnt_register_classes (d);
+    gnt_add_constants(m, "GNT_");
  
     if (PyErr_Occurred ()) {
         Py_FatalError ("can't initialise module sad");
--- a/finch/libgnt/pygnt/gnttree.override	Tue Aug 07 18:14:59 2007 +0000
+++ b/finch/libgnt/pygnt/gnttree.override	Tue Aug 07 21:45:31 2007 +0000
@@ -20,7 +20,7 @@
  * USA
  */
 %%
-headrs
+headers
 #include "common.h"
 %%
 ignore 
@@ -49,7 +49,7 @@
 		return NULL;
 	}
 	while (list) {
-		PyObject *obj = pyg_pointer_new(G_TYPE_POINTER, list->data);
+		PyObject *obj = list->data;
 		PyList_Append(py_list, obj);
 		Py_DECREF(obj);
 		list = list->next;
@@ -100,4 +100,79 @@
 	Py_INCREF(Py_None);
 	return Py_None;
 }
+%%
+override gnt_tree_get_selection_data noargs
+static PyObject *
+_wrap_gnt_tree_get_selection_data(PyGObject *self)
+{
+	PyObject *ret = gnt_tree_get_selection_data(GNT_TREE(self->obj));
+	if (!ret)
+		ret = Py_None;
+	Py_INCREF(ret);
+	return ret;
+}
+%%
+override gnt_tree_change_text
+static PyObject *
+_wrap_gnt_tree_change_text(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+	static char *kwlist[] = { "key", "colno", "text", NULL };
+	char *text;
+	int colno;
+	gpointer key;
 
+	if (!PyArg_ParseTupleAndKeywords(args, kwargs,"Ois:GntTree.change_text", kwlist, &key, &colno, &text))
+		return NULL;
+
+	gnt_tree_change_text(GNT_TREE(self->obj), key, colno, text);
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+%%
+override gnt_tree_set_row_flags
+static PyObject *
+_wrap_gnt_tree_set_row_flags(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+	static char *kwlist[] = { "key", "flag", NULL };
+	int flag;
+	gpointer key;
+
+	if (!PyArg_ParseTupleAndKeywords(args, kwargs,"Oi:GntTree.set_row_flags", kwlist, &key, &flag))
+		return NULL;
+
+	gnt_tree_set_row_flags(GNT_TREE(self->obj), key, flag);
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+%%
+override gnt_tree_remove
+static PyObject *
+_wrap_gnt_tree_remove(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+	static char *kwlist[] = { "key", NULL };
+	gpointer key;
+
+	if (!PyArg_ParseTupleAndKeywords(args, kwargs,"O:GntTree.remove", kwlist, &key))
+		return NULL;
+
+	gnt_tree_remove(GNT_TREE(self->obj), key);
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+%%
+override gnt_tree_set_selected
+static PyObject *
+_wrap_gnt_tree_set_selected(PyGObject *self, PyObject *args)
+{
+	gpointer key;
+	if (!PyArg_ParseTuple(args, "O:GntTree.set_selected", &key)) {
+		return NULL;
+	}
+	gnt_tree_set_selected(GNT_TREE(self->obj), key);
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/libgnt/pygnt/gntwidget.override	Tue Aug 07 21:45:31 2007 +0000
@@ -0,0 +1,36 @@
+/**
+ * pygnt- Python bindings for the GNT toolkit.
+ * Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im>
+ *
+ *   gntwidget.override: overrides for generic widgets.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+%%
+override gnt_widget_get_size args
+static PyObject *
+_wrap_gnt_widget_get_size(PyGObject *self)
+{
+    PyObject *list = PyList_New(0);
+    int x = 0, y = 0;
+
+    gnt_widget_get_size(GNT_WIDGET(self->obj), &x, &y);
+    PyList_Append(list, PyInt_FromLong((long)x));
+    PyList_Append(list, PyInt_FromLong((long)y));
+
+    return list;
+}
+
--- a/finch/libgnt/pygnt/test.py	Tue Aug 07 18:14:59 2007 +0000
+++ b/finch/libgnt/pygnt/test.py	Tue Aug 07 21:45:31 2007 +0000
@@ -1,18 +1,59 @@
 #!/usr/bin/python
+import gobject
 import gnt
 
+class MyObject(gobject.GObject):
+    __gproperties__ = {
+        'mytype': (gobject.TYPE_INT, 'mytype', 'the type of the object',
+                0, 10000, 0, gobject.PARAM_READWRITE),
+        'string': (gobject.TYPE_STRING, 'string property', 'the string',
+                None, gobject.PARAM_READWRITE),
+        'gobject': (gobject.TYPE_OBJECT, 'object property', 'the object',
+                gobject.PARAM_READWRITE),
+    }
+
+    def __init__(self, type = 'string', value = None):
+        self.__gobject_init__()
+        self.set_property(type, value)
+
+    def do_set_property(self, pspec, value):
+        if pspec.name == 'string':
+            self.string = value
+            self.type = gobject.TYPE_STRING
+        elif pspec.name == 'gobject':
+            self.gobject = value
+            self.type = gobject.TYPE_OBJECT
+        else:
+            raise AttributeError, 'unknown property %s' % pspec.name
+    def do_get_property(self, pspec):
+        if pspec.name == 'string':
+            return self.string
+        elif pspec.name == 'gobject':
+            return self.gobject
+        elif pspec.name == 'mytype':
+            return self.type
+        else:
+            raise AttributeError, 'unknown property %s' % pspec.name
+gobject.type_register(MyObject)
+
 def button_activate(button, tree):
-	list = tree.get_selection_text_list()
-	str = ""
-	for i in list:
-		str = str + i
-	entry.set_text("clicked!!!" + str)
+    list = tree.get_selection_text_list()
+    ent = tree.get_selection_data()
+    if ent.type == gobject.TYPE_STRING:
+        str = ""
+        for i in list:
+            str = str + i
+        entry.set_text("clicked!!!" + str)
+    elif ent.type == gobject.TYPE_OBJECT:
+        ent.gobject.set_text("mwhahaha!!!")
 
 gnt.gnt_init()
 
 win = gnt.Window()
 
 entry = gnt.Entry("")
+obj = MyObject()
+obj.set_property('gobject', entry)
 
 win.add_widget(entry)
 win.set_title("Entry")
@@ -27,12 +68,20 @@
 # so random non-string values can be used as the key for a row in a GntTree!
 last = None
 for i in range(1, 100):
-	tree.add_row_after(i, [str(i), ""], None, i-1)
-tree.add_row_after(entry, ["asd"], None, None)
-tree.add_row_after("b", ["123", ""], entry, None)
+    key = MyObject('string', str(i))
+    tree.add_row_after(key, [str(i)], None, last)
+    last = key
+
+tree.add_row_after(MyObject('gobject', entry), ["asd"], None, None)
+tree.add_row_after(MyObject('string', "b"), ["123"], MyObject('gobject', entry), None)
 
 button.connect("activate", button_activate, tree)
 
+tv = gnt.TextView()
+
+win.add_widget(tv)
+tv.append_text_with_flags("What up!!", gnt.TEXT_FLAG_BOLD)
+
 win.show()
 
 gnt.gnt_main()