diff console/libgnt/gnttree.c @ 13864:c7d84d4c5afa

[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 <tailor@pidgin.im>
author Sadrul Habib Chowdhury <imadil@gmail.com>
date Sat, 24 Jun 2006 10:10:53 +0000
parents 55fb5cd9bac9
children d78ab363e02d
line wrap: on
line diff
--- 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 <string.h>
+
 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);
+	}
+}
+
+