Mercurial > pidgin
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', "┘"}, {'a', "▒"}, {'n', "┼"}, + {'w', "┬"}, + {'v', "┴"}, {'\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()