# HG changeset patch # User Sadrul Habib Chowdhury # Date 1151143853 0 # Node ID c7d84d4c5afaf62998ff403b105a8175ffaba47c # Parent cf3eb9f311b2e4afc64715c1dc8267e123c207f8 [gaim-migrate @ 16328] Change the internals of GntTree. The change was required to accommodate expand/collapsing of the groups. I have added tooltips for Groups as well, which shows the online/total count. Do we like it? I have also added emblems at the beginning of the names of the buddies to indicate their status. Currently I am using ASCII-emblems ('o' for available, '.' for away, 'x' for offline (but I am not showing any offline buddies yet)), but I plan on using some cool unicode-emblems Sean suggested to me. committer: Tailor Script diff -r cf3eb9f311b2 -r c7d84d4c5afa console/gntblist.c --- a/console/gntblist.c Sat Jun 24 08:54:33 2006 +0000 +++ b/console/gntblist.c Sat Jun 24 10:10:53 2006 +0000 @@ -9,6 +9,7 @@ #include "gnttree.h" #include "gntblist.h" +#include typedef struct { @@ -23,6 +24,7 @@ static void add_buddy(GaimBuddy *buddy, GGBlist *ggblist); static void add_group(GaimGroup *group, GGBlist *ggblist); +static void draw_tooltip(GGBlist *ggblist); static void new_node(GaimBlistNode *node) @@ -54,6 +56,8 @@ GaimGroup *group = gaim_buddy_get_group((GaimBuddy*)node); if (gaim_blist_get_group_online_count(group) == 0) node_remove(list, (GaimBlistNode*)group); + else if (ggblist->tnode == (GaimBlistNode *)group) /* Need to update the counts */ + draw_tooltip(ggblist); } if (ggblist->tnode == node) @@ -68,6 +72,10 @@ if (GAIM_BLIST_NODE_IS_BUDDY(node)) { GaimBuddy *buddy = (GaimBuddy*)node; + if (gaim_presence_is_online(gaim_buddy_get_presence(buddy))) + add_buddy(buddy, list->ui_data); + else + node_remove(gaim_get_blist(), node); } } @@ -108,6 +116,52 @@ group->name, NULL, NULL); } +static const char * +get_buddy_display_name(GaimBuddy *buddy) +{ + static char text[2096]; + char status[8]; + GaimStatusPrimitive prim; + GaimPresence *presence; + GaimStatus *now; + + presence = gaim_buddy_get_presence(buddy); + now = gaim_presence_get_active_status(presence); + + prim = gaim_status_type_get_primitive(gaim_status_get_type(now)); + + switch(prim) + { +#if 1 + case GAIM_STATUS_OFFLINE: + strncpy(status, "x", sizeof(status) - 1); + break; + case GAIM_STATUS_AVAILABLE: + strncpy(status, "o", sizeof(status) - 1); + break; + default: + strncpy(status, ".", sizeof(status) - 1); + break; +#else + /* XXX: Let's use these some time */ + case GAIM_STATUS_OFFLINE: + strncpy(status, "⊗", sizeof(status) - 1); + break; + case GAIM_STATUS_AVAILABLE: + /* XXX: Detect idleness */ + strncpy(status, "◯", sizeof(status) - 1); + break; + default: + strncpy(status, "⊖", sizeof(status) - 1); + break; +#endif + } + + snprintf(text, sizeof(text) - 1, "%s %s", status, gaim_buddy_get_alias(buddy)); + + return text; +} + static void add_buddy(GaimBuddy *buddy, GGBlist *ggblist) { @@ -120,7 +174,10 @@ add_group(group, ggblist); node->ui_data = gnt_tree_add_row_after(GNT_TREE(ggblist->tree), buddy, - gaim_buddy_get_alias(buddy), group, NULL); + get_buddy_display_name(buddy), group, NULL); + + if (ggblist->tnode == (GaimBlistNode*)group) + draw_tooltip(ggblist); } static void @@ -147,17 +204,20 @@ } static void -selection_changed(GntWidget *widget, int old, int current, GGBlist *ggblist) +draw_tooltip(GGBlist *ggblist) { GaimBlistNode *node; - GntTree *tree = GNT_TREE(widget); int x, y, top, width; GString *str; GaimPlugin *prpl; GaimPluginProtocolInfo *prpl_info; GaimAccount *account; - GntWidget *box, *label; - char *title; + GntTree *tree; + GntWidget *widget, *box, *label; + char *title = NULL; + + widget = ggblist->tree; + tree = GNT_TREE(widget); if (ggblist->tooltip) { @@ -195,6 +255,16 @@ title = g_strdup(gaim_buddy_get_name(buddy)); } + else if (GAIM_BLIST_NODE_IS_GROUP(node)) + { + GaimGroup *group = (GaimGroup *)node; + + g_string_append_printf(str, _("Online: %d\nTotal: %d"), + gaim_blist_get_group_online_count(group), + gaim_blist_get_group_size(group, FALSE)); + + title = g_strdup(group->name); + } else { g_string_free(str, TRUE); @@ -217,12 +287,20 @@ gnt_widget_set_position(box, x, y); gnt_widget_draw(box); - + + g_free(title); g_string_free(str, TRUE); ggblist->tooltip = box; ggblist->tnode = node; } +static void +selection_changed(GntWidget *widget, gpointer old, gpointer current, GGBlist *ggblist) +{ + draw_tooltip(ggblist); +} + + static gboolean key_pressed(GntWidget *widget, const char *text, GGBlist *ggblist) { @@ -239,6 +317,14 @@ return FALSE; } +static void +buddy_status_changed(GaimBuddy *buddy, GaimStatus *old, GaimStatus *now, GGBlist *ggblist) +{ + gnt_tree_change_text(GNT_TREE(ggblist->tree), buddy, get_buddy_display_name(buddy)); + if (ggblist->tnode == (GaimBlistNode*)buddy) + draw_tooltip(ggblist); +} + void gg_blist_init() { ggblist = g_new0(GGBlist, 1); @@ -256,13 +342,16 @@ gnt_box_add_widget(GNT_BOX(ggblist->window), ggblist->tree); gnt_widget_show(ggblist->window); + + gaim_signal_connect(gaim_blist_get_handle(), "buddy-status-changed", gg_blist_get_handle(), + GAIM_CALLBACK(buddy_status_changed), ggblist); +#if 0 gaim_signal_connect(gaim_blist_get_handle(), "buddy-signed-on", gg_blist_get_handle(), GAIM_CALLBACK(buddy_signed_on), ggblist); gaim_signal_connect(gaim_blist_get_handle(), "buddy-signed-off", gg_blist_get_handle(), GAIM_CALLBACK(buddy_signed_off), ggblist); -#if 0 /* These I plan to use to indicate unread-messages etc. */ gaim_signal_connect(gaim_conversations_get_handle(), "received-im-msg", gg_blist_get_handle(), GAIM_CALLBACK(received_im_msg), list); diff -r cf3eb9f311b2 -r c7d84d4c5afa console/gntgaim.c --- a/console/gntgaim.c Sat Jun 24 08:54:33 2006 +0000 +++ b/console/gntgaim.c Sat Jun 24 10:10:53 2006 +0000 @@ -168,6 +168,9 @@ int main(int argc, char **argv) { + /* XXX: Don't puke */ + freopen("/dev/null", "w", stderr); + /* Initialize the libgaim stuff */ init_libgaim(); diff -r cf3eb9f311b2 -r c7d84d4c5afa console/libgnt/Makefile --- a/console/libgnt/Makefile Sat Jun 24 08:54:33 2006 +0000 +++ b/console/libgnt/Makefile Sat Jun 24 10:10:53 2006 +0000 @@ -36,7 +36,7 @@ all: libgnt -test: $(OBJECTS) +test2: $(OBJECTS) key: $(OBJECTS) gntwidget.o: gntwidget.c $(HEADERS) diff -r cf3eb9f311b2 -r c7d84d4c5afa console/libgnt/gntbox.c --- a/console/libgnt/gntbox.c Sat Jun 24 08:54:33 2006 +0000 +++ b/console/libgnt/gntbox.c Sat Jun 24 10:10:53 2006 +0000 @@ -32,7 +32,7 @@ else { /* XXX: Position of the title might be configurable */ - pos = (widget->priv.width - pos - 2) / 2; + pos = (widget->priv.width - pos) / 2; } wbkgdset(widget->window, '\0' | COLOR_PAIR(GNT_COLOR_TITLE)); mvwprintw(widget->window, 0, pos, title); diff -r cf3eb9f311b2 -r c7d84d4c5afa console/libgnt/gnttree.c --- a/console/libgnt/gnttree.c Sat Jun 24 08:54:33 2006 +0000 +++ b/console/libgnt/gnttree.c Sat Jun 24 10:10:53 2006 +0000 @@ -1,6 +1,8 @@ #include "gnttree.h" #include "gntutils.h" +#include + enum { SIG_SELECTION_CHANGED, @@ -17,7 +19,8 @@ char *text; void *data; /* XXX: unused */ - /* XXX: These are also unused */ + gboolean collapsed; + GntTreeRow *parent; GntTreeRow *child; GntTreeRow *next; @@ -27,29 +30,165 @@ static GntWidgetClass *parent_class = NULL; static guint signals[SIGS] = { 0 }; +static GntTreeRow * +_get_next(GntTreeRow *row, gboolean godeep) +{ + if (row == NULL) + return NULL; + if (godeep && row->child) + return row->child; + if (row->next) + return row->next; + return _get_next(row->parent, FALSE); +} + +static GntTreeRow * +get_next(GntTreeRow *row) +{ + if (row == NULL) + return; + return _get_next(row, !row->collapsed); +} + +/* Returns the n-th next row. If it doesn't exist, returns NULL */ +static GntTreeRow * +get_next_n(GntTreeRow *row, int n) +{ + while (row && n--) + row = get_next(row); + return row; +} + +/* Returns the n-th next row. If it doesn't exist, then the last non-NULL node */ +static GntTreeRow * +get_next_n_opt(GntTreeRow *row, int n, int *pos) +{ + GntTreeRow *next = row; + int r = 0; + + if (row == NULL) + return NULL; + + while (row && n--) + { + row = get_next(row); + if (row) + { + next = row; + r++; + } + } + + if (pos) + *pos = r; + + return next; +} + +static GntTreeRow * +get_last_child(GntTreeRow *row) +{ + if (row == NULL) + return NULL; + if (!row->collapsed && row->child) + row = row->child; + else + return row; + + while(row->next) + row = row->next; + if (row->child) + row = get_last_child(row->child); + return row; +} + +static GntTreeRow * +get_prev(GntTreeRow *row) +{ + if (row == NULL) + return NULL; + if (row->prev) + return get_last_child(row->prev); + return row->parent; +} + +static GntTreeRow * +get_prev_n(GntTreeRow *row, int n) +{ + while (row && n--) + row = get_prev(row); + return row; +} + +/* Distance of row from the root */ +/* XXX: This is uber-inefficient */ +static int +get_root_distance(GntTreeRow *row) +{ + if (row == NULL) + return -1; + return get_root_distance(get_prev(row)) + 1; +} + +/* Returns the distance between a and b. + * If a is 'above' b, then the distance is positive */ +static int +get_distance(GntTreeRow *a, GntTreeRow *b) +{ + /* First get the distance from a to the root. + * Then the distance from b to the root. + * Subtract. + * It's not that good, but it works. */ + int ha = get_root_distance(a); + int hb = get_root_distance(b); + + return (hb - ha); +} + static void redraw_tree(GntTree *tree) { int start; GntWidget *widget = GNT_WIDGET(tree); - GList *iter; + GntTreeRow *row; int pos; + gboolean deep; if (GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER)) pos = 0; else pos = 1; + if (tree->top == NULL) + tree->top = tree->root; + if (tree->current == NULL) + tree->current = tree->root; + wbkgd(widget->window, COLOR_PAIR(GNT_COLOR_NORMAL)); - for (start = tree->top, iter = g_list_nth(tree->list, tree->top); - iter && start < tree->bottom; start++, iter = iter->next) + deep = TRUE; + row = tree->top; + for (start = pos; row && start < widget->priv.height - pos; + start++, row = get_next(row)) { - char str[2096]; /* XXX: This should be safe for any terminal */ + char str[2048]; int wr; - GntTreeRow *row = g_hash_table_lookup(tree->hash, iter->data); + char format[16] = ""; + + deep = TRUE; - if ((wr = snprintf(str, widget->priv.width, "%s", row->text)) >= widget->priv.width) + if (row->parent == NULL && row->child) + { + if (row->collapsed) + { + strcpy(format, "+ "); + deep = FALSE; + } + else + strcpy(format, "- "); + } + + if ((wr = g_snprintf(str, widget->priv.width, "%s%s", format, row->text)) >= widget->priv.width) { /* XXX: ellipsize */ str[widget->priv.width - 1 - pos] = 0; @@ -61,19 +200,20 @@ str[wr] = 0; } - if (start == tree->current) + if (row == tree->current) { wbkgdset(widget->window, '\0' | COLOR_PAIR(GNT_COLOR_HIGHLIGHT)); - mvwprintw(widget->window, start - tree->top + pos, pos, str); + mvwprintw(widget->window, start, pos, str); wbkgdset(widget->window, '\0' | COLOR_PAIR(GNT_COLOR_NORMAL)); } else - mvwprintw(widget->window, start - tree->top + pos, pos, str); + mvwprintw(widget->window, start, pos, str); + tree->bottom = row; } - while (start < tree->bottom) + while (start < widget->priv.height - pos) { - mvwhline(widget->window, start - tree->top + pos, pos, ' ', + mvwhline(widget->window, start, pos, ' ', widget->priv.width - pos * 2); start++; } @@ -85,13 +225,12 @@ gnt_tree_draw(GntWidget *widget) { GntTree *tree = GNT_TREE(widget); + int bottom; scrollok(widget->window, TRUE); wsetscrreg(widget->window, 0, widget->priv.height - 1); - tree->top = 0; - tree->bottom = widget->priv.height - - (GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER) ? 0 : 2); + tree->top = tree->root; redraw_tree(tree); @@ -116,32 +255,42 @@ } static void -tree_selection_changed(GntTree *tree, int old, int current) +tree_selection_changed(GntTree *tree, GntTreeRow *old, GntTreeRow *current) { g_signal_emit(tree, signals[SIG_SELECTION_CHANGED], 0, old, current); } +static GntTreeRow * +get_nth_row(GntTree *tree, int n) +{ + gpointer key = g_list_nth_data(tree->list, n); + return g_hash_table_lookup(tree->hash, key); +} + static gboolean gnt_tree_key_pressed(GntWidget *widget, const char *text) { GntTree *tree = GNT_TREE(widget); - int old = tree->current; + GntTreeRow *old = tree->current; + GntTreeRow *row; if (text[0] == 27) { - if (strcmp(text+1, GNT_KEY_DOWN) == 0 && tree->current < g_list_length(tree->list) - 1) + int dist; + if (strcmp(text+1, GNT_KEY_DOWN) == 0 && (row = get_next(tree->current)) != NULL) { - tree->current++; - if (tree->current >= tree->bottom) - gnt_tree_scroll(tree, 1 + tree->current - tree->bottom); + tree->current = row; + if ((dist = get_distance(tree->current, tree->bottom)) < 0) + gnt_tree_scroll(tree, -dist); else redraw_tree(tree); } - else if (strcmp(text+1, GNT_KEY_UP) == 0 && tree->current > 0) + else if (strcmp(text+1, GNT_KEY_UP) == 0 && (row = get_prev(tree->current)) != NULL) { - tree->current--; - if (tree->current < tree->top) - gnt_tree_scroll(tree, tree->current - tree->top); + tree->current = row; + + if ((dist = get_distance(tree->current, tree->top)) > 0) + gnt_tree_scroll(tree, -dist); else redraw_tree(tree); } @@ -150,6 +299,16 @@ { gnt_widget_activate(widget); } + else if (text[0] == ' ' && text[1] == 0) + { + /* Space pressed */ + GntTreeRow *row = tree->current; + if (row && row->child) + { + row->collapsed = !row->collapsed; + redraw_tree(tree); + } + } if (old != tree->current) tree_selection_changed(tree, old, tree->current); @@ -184,8 +343,8 @@ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GntTreeClass, selection_changed), NULL, NULL, - gnt_closure_marshal_VOID__INT_INT, - G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT); + gnt_closure_marshal_VOID__POINTER_POINTER, + G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER); DEBUG; } @@ -268,16 +427,22 @@ void gnt_tree_scroll(GntTree *tree, int count) { - if (tree->top == 0 && count < 0) - return; + GntTreeRow *row; - if (count > 0 && tree->bottom + count >= g_list_length(tree->list)) - count = g_list_length(tree->list) - tree->bottom; - else if (count < 0 && tree->top + count < 0) - count = -tree->top; - - tree->top += count; - tree->bottom += count; + if (count < 0) + { + if (get_root_distance(tree->top) == 0) + return; + row = get_prev_n(tree->top, -count); + if (row == NULL) + row = tree->root; + tree->top = row; + } + else + { + get_next_n_opt(tree->bottom, count, &count); + tree->top = get_next_n(tree->top, count); + } redraw_tree(tree); } @@ -300,7 +465,7 @@ { GntTreeRow *row = g_new0(GntTreeRow, 1), *pr = NULL; - g_hash_table_insert(tree->hash, key, row); + g_hash_table_replace(tree->hash, key, row); if (tree->root == NULL) { @@ -342,11 +507,13 @@ if (pr == NULL) { - if (tree->root) tree->root->prev = row; - row->next = tree->root; - tree->root = row; + GntTreeRow *r = tree->root; + while (r->next) + r = r->next; + r->next = row; + row->prev = r; - tree->list = g_list_prepend(tree->list, key); + tree->list = g_list_append(tree->list, key); } else { @@ -366,12 +533,9 @@ gpointer gnt_tree_get_selection_data(GntTree *tree) { - return g_list_nth_data(tree->list, tree->current); -} - -int gnt_tree_get_selection_index(GntTree *tree) -{ - return tree->current; + if (tree->current) + return tree->current->key; /* XXX: perhaps we should just get rid of 'data' */ + return NULL; } /* XXX: Should this also remove all the children of the row being removed? */ @@ -380,24 +544,70 @@ GntTreeRow *row = g_hash_table_lookup(tree->hash, key); if (row) { - int len, pos; + gboolean redraw = FALSE; + + if (get_distance(tree->top, row) >= 0 && get_distance(row, tree->bottom) >= 0) + redraw = TRUE; - pos = g_list_index(tree->list, key); + /* Update root/top/current/bottom if necessary */ + if (tree->root == row) + tree->root = get_next(row); + if (tree->top == row) + { + if (tree->top != tree->root) + tree->top = get_prev(row); + else + tree->top = get_next(row); + if (tree->current == row) + tree->current = tree->top; + } + else if (tree->current == row) + { + if (tree->current != tree->root) + tree->current = get_prev(row); + else + tree->current = get_next(row); + } + else if (tree->bottom == row) + { + tree->bottom = get_prev(row); + } + /* Fix the links */ + if (row->next) + row->next->prev = row->prev; + if (row->parent && row->parent->child == row) + row->parent->child = row->next; + if (row->prev) + row->prev->next = row->next; + g_hash_table_remove(tree->hash, key); tree->list = g_list_remove(tree->list, key); - if (pos >= tree->top && pos < tree->bottom) + if (redraw) { redraw_tree(tree); } - g_hash_table_replace(tree->hash, key, NULL); } } int gnt_tree_get_selection_visible_line(GntTree *tree) { - return (tree->current - tree->top) + + return get_distance(tree->top, tree->current) + !!(GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_NO_BORDER)); } +void gnt_tree_change_text(GntTree *tree, gpointer key, const char *text) +{ + GntTreeRow *row = g_hash_table_lookup(tree->hash, key); + if (row) + { + g_free(row->text); + row->text = g_strdup_printf("%*s%s", TAB_SIZE * find_depth(row), "", text); + + if (get_distance(tree->top, row) >= 0 && get_distance(row, tree->bottom) > 0) + redraw_tree(tree); + } +} + + diff -r cf3eb9f311b2 -r c7d84d4c5afa console/libgnt/gnttree.h --- a/console/libgnt/gnttree.h Sat Jun 24 08:54:33 2006 +0000 +++ b/console/libgnt/gnttree.h Sat Jun 24 10:10:53 2006 +0000 @@ -27,10 +27,10 @@ { GntWidget parent; - int current; /* current selection */ + GntTreeRow *current; /* current selection */ - int top; /* The index in 'list' of the topmost visible item */ - int bottom; /* The index in 'list' of the bottommost visible item */ + GntTreeRow *top; /* The topmost visible item */ + GntTreeRow *bottom; /* The bottommost visible item */ GntTreeRow *root; /* The root of all evil */ @@ -66,13 +66,13 @@ gpointer gnt_tree_get_selection_data(GntTree *tree); -int gnt_tree_get_selection_index(GntTree *tree); - void gnt_tree_remove(GntTree *tree, gpointer key); /* Returns the visible line number of the selected row */ int gnt_tree_get_selection_visible_line(GntTree *tree); +void gnt_tree_change_text(GntTree *tree, gpointer key, const char *text); + G_END_DECLS #endif /* GNT_TREE_H */ diff -r cf3eb9f311b2 -r c7d84d4c5afa console/libgnt/gntutils.c --- a/console/libgnt/gntutils.c Sat Jun 24 08:54:33 2006 +0000 +++ b/console/libgnt/gntutils.c Sat Jun 24 10:10:53 2006 +0000 @@ -98,3 +98,35 @@ data2); } +void gnt_closure_marshal_VOID__POINTER_POINTER(GClosure *closure, + GValue *ret_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef void (*func) (gpointer data1, gpointer, gpointer, gpointer data2); + register func callback; + register GCClosure *cc = (GCClosure*)closure; + register gpointer data1, data2; + + g_return_if_fail(n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA(closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer(param_values + 0); + } + else + { + data1 = g_value_peek_pointer(param_values + 0); + data2 = closure->data; + } + + callback = (func) (marshal_data ? marshal_data : cc->callback); + callback(data1, + g_value_get_pointer(param_values + 1) , + g_value_get_pointer(param_values + 2) , + data2); +} + diff -r cf3eb9f311b2 -r c7d84d4c5afa console/libgnt/gntutils.h --- a/console/libgnt/gntutils.h Sat Jun 24 08:54:33 2006 +0000 +++ b/console/libgnt/gntutils.h Sat Jun 24 10:10:53 2006 +0000 @@ -21,3 +21,10 @@ gpointer invocation_hint, gpointer marshal_data); +void gnt_closure_marshal_VOID__POINTER_POINTER(GClosure *closure, + GValue *ret_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); +