[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH] chanlist treeview
Hi all,
Here's the (hopefully final) channel list treeview patch. Sorry for the
delay, real-world stuff intervened.
I went ahead and just finished/cleaned up the version which uses glib's
binary trees. I think the simpler code is probably worth any
performance loss. No real functionality changes; I toyed with the idea
of trying to improve the GUI, but couldn't really come up with anything
satisfactory. I leave that as an academic exercise for the reader :)
Patch is attached.
Cheers,
Vince
--
Vincent Ho
loki@internode.on.net
If all the world's economists were laid end to end, we wouldn't reach a
conclusion.
-- William Baumol
Index: src/fe-gtk/fe-gtk.h
===================================================================
RCS file: /cvsroot/xchat/xchat2/src/fe-gtk/fe-gtk.h,v
retrieving revision 1.7
diff -u -r1.7 fe-gtk.h
--- src/fe-gtk/fe-gtk.h 17 Mar 2003 03:54:23 -0000 1.7
+++ src/fe-gtk/fe-gtk.h 7 Apr 2003 13:52:44 -0000
@@ -23,6 +23,7 @@
#include <gtk/gtkwidget.h>
#include <gtk/gtkcontainer.h>
#include <gtk/gtksignal.h>
+#include <gtk/gtktreemodel.h>
#undef gtk_signal_connect
#define gtk_signal_connect g_signal_connect
@@ -47,6 +48,7 @@
GtkWidget *chanlist_wild;
GtkWidget *chanlist_window;
GtkWidget *chanlist_list;
+ GtkWidget *chanlist_view; /* treeview */
GtkWidget *chanlist_refresh;
GtkWidget *chanlist_label;
@@ -56,6 +58,10 @@
gboolean chanlist_match_wants_channel; /* match in channel name */
gboolean chanlist_match_wants_topic; /* match in topic */
+
+ GTree *chanlist_itertree; /* tree of channel iters */
+ char *chanlist_searchchan; /* channel we're currently searching for */
+ char *chanlist_prevchan; /* previous channel in sorted order */
#ifndef WIN32
regex_t chanlist_match_regex; /* compiled regular expression here */
Index: src/fe-gtk/fe-gtk.c
===================================================================
RCS file: /cvsroot/xchat/xchat2/src/fe-gtk/fe-gtk.c,v
retrieving revision 1.14
diff -u -r1.14 fe-gtk.c
--- src/fe-gtk/fe-gtk.c 23 Feb 2003 14:27:26 -0000 1.14
+++ src/fe-gtk/fe-gtk.c 7 Apr 2003 13:52:46 -0000
@@ -458,12 +458,6 @@
}
void
-fe_chan_list_end (struct server *serv)
-{
- gtk_widget_set_sensitive (serv->gui->chanlist_refresh, TRUE);
-}
-
-void
fe_notify_update (char *name)
{
if (name)
Index: src/fe-gtk/chanlist.c
===================================================================
RCS file: /cvsroot/xchat/xchat2/src/fe-gtk/chanlist.c,v
retrieving revision 1.5
diff -u -r1.5 chanlist.c
--- src/fe-gtk/chanlist.c 4 Jan 2003 08:55:34 -0000 1.5
+++ src/fe-gtk/chanlist.c 7 Apr 2003 13:52:50 -0000
@@ -29,6 +29,8 @@
#include "fe-gtk.h"
+#include <glib/gtree.h>
+
#include <gtk/gtkcheckbutton.h>
#include <gtk/gtktable.h>
#include <gtk/gtkalignment.h>
@@ -41,6 +43,9 @@
#include <gtk/gtkstock.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtkhbbox.h>
+#include <gtk/gtkliststore.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtktreeselection.h>
#include "../common/xchat.h"
#include "../common/xchatc.h"
@@ -49,6 +54,199 @@
#include "gtkutil.h"
#include "maingui.h"
+/* model for the chanlist treeview */
+enum
+{
+ CHAN_COLUMN,
+ USERS_COLUMN,
+ TOPIC_COLUMN,
+ N_COLUMNS
+};
+
+
+/* just to shorten code -Vince */
+static GtkListStore *
+get_store (struct server *serv)
+{
+ return GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (serv->gui->chanlist_view)));
+}
+
+static int
+chanlist_cmp (const char *chan1, const char *chan2)
+{
+ return strcasecmp (chan1, chan2);
+}
+
+/* Callback function for g_tree_search(), which we use to find the previous
+ * existing channel node.
+ */
+static int
+chanlist_cmp_serv (const char *nodechan, struct server *serv)
+{
+ int result = chanlist_cmp (serv->gui->chanlist_searchchan, nodechan);
+
+ /* If the channel we're searching for is greater than the one we're
+ * currently comparing against, then it's possibly the previous
+ * channel.
+ */
+ if (result > 0)
+ serv->gui->chanlist_prevchan = (char *)nodechan;
+
+ return result;
+}
+
+/* Gets an iterator pointing to the location where a channel should be
+ * inserted in ascending order.
+ */
+static void
+chanlist_itertree_get (struct server *serv, char *chan, GtkTreeIter *iter_ret)
+{
+ GtkListStore *store = get_store (serv);
+ GtkTreeIter *piter = NULL;
+ GtkTreeIter iter;
+ char *chancopy;
+ GtkTreeIter *itercopy;
+
+ /* so the search func knows what channel we want to insert */
+ serv->gui->chanlist_searchchan = chan;
+ serv->gui->chanlist_prevchan = NULL;
+ g_tree_search (serv->gui->chanlist_itertree,
+ (GCompareFunc)chanlist_cmp_serv, serv);
+ /* chanlist_prevchan can be set by the chanlist_cmp_serv() callback */
+ if (serv->gui->chanlist_prevchan)
+ {
+ piter = g_tree_lookup (serv->gui->chanlist_itertree, serv->gui->chanlist_prevchan);
+ if (!piter)
+ g_print (__FILE__ "piter for %s is null\n", serv->gui->chanlist_prevchan);
+ }
+ /* piter is now the previous channel in sorted order, or null, meaning
+ * there is no such channel.
+ */
+ gtk_list_store_insert_after (store, &iter, piter);
+
+ /* insert the new channel into the tree */
+ chancopy = g_strdup (chan);
+ itercopy = gtk_tree_iter_copy (&iter);
+ g_tree_insert (serv->gui->chanlist_itertree, chancopy, itercopy);
+
+ *iter_ret = iter;
+}
+
+/* Clears the list, and destroys/re-initialises the cache tree */
+static void clear_chanlist_view (struct server *serv)
+{
+ gtk_list_store_clear (get_store (serv));
+ g_tree_destroy (serv->gui->chanlist_itertree);
+ serv->gui->chanlist_itertree = g_tree_new_full ((GCompareDataFunc)chanlist_cmp,
+ NULL, g_free, (GDestroyNotify)gtk_tree_iter_free);
+
+}
+
+
+static void
+chanlist_join (GtkWidget * wid, struct server *serv)
+{
+ GtkTreeView *view = GTK_TREE_VIEW (serv->gui->chanlist_view);
+ GtkTreeIter iter;
+ char *chan = NULL;
+ char tbuf[CHANLEN + 6];
+
+ if (gtkutil_treeview_get_selected (view, &iter,
+ CHAN_COLUMN, &chan, -1))
+ {
+ if (serv->connected && (strcmp (chan, "*") != 0))
+ {
+ snprintf (tbuf, sizeof (tbuf), "join %s", chan);
+ handle_command (serv->server_session, tbuf, FALSE);
+ } else
+ gtkutil_simpledialog ("Not connected.");
+
+ g_free (chan);
+ }
+}
+
+static gboolean
+chanlist_treeview_clicked_cb (GtkWidget *view, GdkEventButton *event,
+ gpointer data)
+{
+ if (!event)
+ return FALSE;
+
+ switch (event->button)
+ {
+ case 1:
+ if (event->type == GDK_2BUTTON_PRESS)
+ chanlist_join (NULL, (struct server *)data);
+ break;
+ default:
+ break;
+ }
+ return FALSE;
+}
+
+/* this is the column id that represents unsorted columns
+ * We don't know this ahead of time... */
+static int unsorted_col_id;
+
+static GtkWidget *
+chanlist_treeview_new (GtkWidget *box, struct server *serv)
+{
+ GtkListStore *store;
+ GtkWidget *view;
+ GtkTreeViewColumn *col;
+
+ store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_INT,
+ G_TYPE_STRING);
+ g_return_val_if_fail (store != NULL, NULL);
+ view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store), NULL,
+ CHAN_COLUMN, _("Channel"),
+ USERS_COLUMN, _("Users"),
+ TOPIC_COLUMN, _("Topic"),
+ -1);
+
+ g_signal_connect (G_OBJECT (view), "button-press-event",
+ G_CALLBACK (chanlist_treeview_clicked_cb), serv);
+
+ col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), CHAN_COLUMN);
+ gtk_tree_view_column_set_alignment (col, 0.5);
+ unsorted_col_id = gtk_tree_view_column_get_sort_column_id (col);
+ gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
+ gtk_tree_view_column_set_max_width (col, 300);
+ gtk_tree_view_column_set_resizable (col, TRUE);
+
+ col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), USERS_COLUMN);
+ gtk_tree_view_column_set_alignment (col, 0.5);
+ gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
+ gtk_tree_view_column_set_resizable (col, TRUE);
+
+ col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), TOPIC_COLUMN);
+ gtk_tree_view_column_set_alignment (col, 0.5);
+ gtk_tree_view_column_set_resizable (col, TRUE);
+
+ gtk_widget_show (view);
+ return view;
+}
+
+/* Turn column sorting on/off */
+void toggle_sorting_enabled (struct server *serv, gboolean enabled)
+{
+ GtkTreeViewColumn *col, *col2;
+
+ col = gtk_tree_view_get_column (GTK_TREE_VIEW (serv->gui->chanlist_view),
+ CHAN_COLUMN);
+ col2 = gtk_tree_view_get_column (GTK_TREE_VIEW (serv->gui->chanlist_view),
+ USERS_COLUMN);
+ if (enabled)
+ {
+ gtk_tree_view_column_set_sort_column_id (col, CHAN_COLUMN);
+ gtk_tree_view_column_set_sort_column_id (col2, USERS_COLUMN);
+ }
+ else
+ {
+ gtk_tree_view_column_set_sort_column_id (col, unsorted_col_id);
+ gtk_tree_view_column_set_sort_column_id (col2, unsorted_col_id);
+ }
+}
/**
* Accepts a regex_t pointer and string to test it with
@@ -81,40 +279,6 @@
#endif
/**
- * Sorts the channel list based upon the user field.
- */
-static gint
-chanlist_compare_user (GtkCList * clist,
- gconstpointer ptr1, gconstpointer ptr2)
-{
- int int1;
- int int2;
-
- GtkCListRow *row1 = (GtkCListRow *) ptr1;
- GtkCListRow *row2 = (GtkCListRow *) ptr2;
-
- int1 = atoi (GTK_CELL_TEXT (row1->cell[clist->sort_column])->text);
- int2 = atoi (GTK_CELL_TEXT (row2->cell[clist->sort_column])->text);
-
- return int1 > int2 ? 1 : -1;
-}
-
-/**
- * Provides the default case-insensitive sorting for the channel
- * list.
- */
-static gint
-chanlist_compare_text_ignore_case (GtkCList * clist,
- gconstpointer ptr1, gconstpointer ptr2)
-{
- GtkCListRow *row1 = (GtkCListRow *) ptr1;
- GtkCListRow *row2 = (GtkCListRow *) ptr2;
-
- return rfc_casecmp (GTK_CELL_TEXT (row1->cell[clist->sort_column])->text,
- GTK_CELL_TEXT (row2->cell[clist->sort_column])->text);
-}
-
-/**
* Updates the caption to reflect the number of users and channels
*/
static void
@@ -203,9 +367,11 @@
static void
chanlist_place_row_in_gui (struct server *serv, gchar ** next_row)
{
- int num_users = atoi (next_row[1]);
- gfloat val, end;
- GtkAdjustment *adj;
+ GtkListStore *store;
+ GtkTreeIter iter;
+ char *chan = next_row[0];
+ int num_users = strtol (next_row[1], NULL, 10);
+ char *topic = next_row[2];
/* First, update the 'found' counter values */
serv->gui->chanlist_users_found_count += num_users;
@@ -262,24 +428,15 @@
}
}
- adj = gtk_clist_get_vadjustment (GTK_CLIST (serv->gui->chanlist_list));
- val = adj->value;
- /*
- * If all the above above tests passed or if no text was in the
- * chanlist_wild_text, add this entry to the GUI
- */
- gtk_clist_prepend (GTK_CLIST (serv->gui->chanlist_list), next_row);
+ /* it matches.. insert it now */
+ store = get_store (serv);
+ chanlist_itertree_get (serv, chan, &iter);
+ gtk_list_store_set (store, &iter, 0, chan, 1, num_users, 2, topic, -1);
/* Update the 'shown' counter values */
serv->gui->chanlist_users_shown_count += num_users;
serv->gui->chanlist_channels_shown_count++;
- /* restore original scrollbar position */
- end = adj->upper - adj->lower - adj->page_size;
- if (val > end)
- val = end;
- gtk_adjustment_set_value (adj, val);
-
chanlist_update_caption (serv);
}
@@ -291,12 +448,15 @@
{
if (serv->connected)
{
+
chanlist_data_free (serv);
chanlist_reset_counters (serv);
chanlist_update_caption (serv);
- gtk_clist_clear (GTK_CLIST (serv->gui->chanlist_list));
- gtk_widget_set_sensitive (serv->gui->chanlist_refresh, FALSE);
+ /* empty and re-initialise the list */
+ clear_chanlist_view (serv);
+ /* turn off column sorting, it slows insertion to a crawl */
+ toggle_sorting_enabled (serv, FALSE);
handle_command (serv->server_session, "list", FALSE);
} else
@@ -322,7 +482,6 @@
chanlist_build_gui_list (struct server *serv)
{
GSList *rows;
- GtkCList *clist;
/* first check if the list is present */
if (serv->gui->chanlist_data_stored_rows == NULL)
@@ -331,13 +490,9 @@
return;
}
- clist = GTK_CLIST (serv->gui->chanlist_list);
-
- /* turn off sorting because this _greatly_ quickens the reinsertion */
- gtk_clist_set_auto_sort (clist, FALSE);
- /* freeze that GtkCList to make it go fasssster as well */
- gtk_clist_freeze (clist);
- gtk_clist_clear (clist);
+ /* Clear the existing list, and disable sorting */
+ clear_chanlist_view (serv);
+ toggle_sorting_enabled (serv, FALSE);
/* Reset the counters */
chanlist_reset_counters (serv);
@@ -349,9 +504,7 @@
chanlist_place_row_in_gui (serv, (gchar **) rows->data);
}
- gtk_clist_thaw (clist);
- gtk_clist_set_auto_sort (clist, TRUE);
- gtk_clist_sort (clist);
+ toggle_sorting_enabled (serv, TRUE);
}
/**
@@ -365,6 +518,8 @@
gchar **next_row;
next_row = (gchar **) malloc (sizeof (gchar *) * 3);
+ if (!next_row)
+ return;
next_row[0] = strdup (chan);
next_row[1] = strdup (users);
next_row[2] = strip_color (topic);
@@ -376,6 +531,17 @@
chanlist_place_row_in_gui (serv, next_row);
}
+
+void
+fe_chan_list_end (struct server *serv)
+{
+ gtk_widget_set_sensitive (serv->gui->chanlist_refresh, TRUE);
+ /* re-enable sorting on the channel column of the tree */
+ toggle_sorting_enabled (serv, TRUE);
+}
+
+
+
/**
* The next several functions simply handle signals from widgets to update
* the list and state variables.
@@ -431,77 +597,24 @@
serv->gui->chanlist_match_wants_topic = GTK_TOGGLE_BUTTON (wid)->active;
}
-static void
-chanlist_click_column (GtkWidget * clist, gint column, struct server *serv)
-{
- /* If the user clicks the same column twice in a row,
- * swap the sort type. Otherwise, assume he wishes
- * to sort by another column, but using the same
- * direction.
- */
-
- if (serv->gui->chanlist_last_column == column)
- {
- if (serv->gui->chanlist_sort_type == GTK_SORT_ASCENDING)
- serv->gui->chanlist_sort_type = GTK_SORT_DESCENDING;
- else
- serv->gui->chanlist_sort_type = GTK_SORT_ASCENDING;
- }
-
- serv->gui->chanlist_last_column = column;
-
- gtk_clist_set_sort_type (GTK_CLIST (clist), serv->gui->chanlist_sort_type);
- gtk_clist_set_sort_column (GTK_CLIST (clist), column);
-
- /* Since ascii sorting the numbers is a no go, use a custom
- * sorter function for the 'users' column.
- */
- if (column == 1)
- gtk_clist_set_compare_func (GTK_CLIST (clist), (GtkCListCompareFunc)
- chanlist_compare_user);
- else
- /* In the 0 or 2 case, use the case-insensitive string
- * compare function.
- */
- gtk_clist_set_compare_func (GTK_CLIST (clist), (GtkCListCompareFunc)
- chanlist_compare_text_ignore_case);
-
- gtk_clist_sort (GTK_CLIST (clist));
-}
-
-static void
-chanlist_join (GtkWidget * wid, struct server *serv)
-{
- int row;
- char *chan;
- char tbuf[CHANLEN + 6];
-
- row = gtkutil_clist_selection (serv->gui->chanlist_list);
- if (row != -1)
- {
- gtk_clist_get_text (GTK_CLIST (serv->gui->chanlist_list), row, 0,
- &chan);
- if (serv->connected && (strcmp (chan, "*") != 0))
- {
- snprintf (tbuf, sizeof (tbuf), "join %s", chan);
- handle_command (serv->server_session, tbuf, FALSE);
- } else
- gdk_beep ();
- }
-}
static void
chanlist_filereq_done (struct server *serv, void *data2, char *file)
{
+ GtkTreeModel *model = GTK_TREE_MODEL (get_store (serv));
+ GtkTreeIter iter;
time_t t = time (0);
- int i = 0;
int fh;
- char *chan, *users, *topic;
+ char *chan, *topic;
+ int numusers;
char buf[1024];
if (!file)
return;
+ if (!gtk_tree_model_get_iter_from_string (model, &iter, "0"))
+ return;
+
fh = open (file, O_TRUNC | O_WRONLY | O_CREAT, 0600);
if (fh == -1)
return;
@@ -510,17 +623,14 @@
serv->servername, ctime (&t));
write (fh, buf, strlen (buf));
- while (1)
+ do
{
- if (!gtk_clist_get_text
- (GTK_CLIST (serv->gui->chanlist_list), i, 0, &chan))
- break;
- gtk_clist_get_text (GTK_CLIST (serv->gui->chanlist_list), i, 1, &users);
- gtk_clist_get_text (GTK_CLIST (serv->gui->chanlist_list), i, 2, &topic);
- i++;
- snprintf (buf, sizeof buf, "%-16s %-5s%s\n", chan, users, topic);
+ gtk_tree_model_get (model, &iter, 0, &chan, 1, &numusers, 2, &topic, -1);
+ snprintf (buf, sizeof buf, "%-16s %-5d%s\n", chan, numusers, topic);
write (fh, buf, strlen (buf));
- }
+ g_free (chan);
+ g_free (topic);
+ } while (gtk_tree_model_iter_next (model, &iter));
close (fh);
}
@@ -528,16 +638,14 @@
static void
chanlist_save (GtkWidget * wid, struct server *serv)
{
- char *temp;
+ GtkTreeModel *model = GTK_TREE_MODEL (get_store (serv));
- if (!gtk_clist_get_text
- (GTK_CLIST (serv->gui->chanlist_list), 0, 0, &temp))
+ if (gtk_tree_model_iter_n_children (model, NULL) > 0)
{
+ gtkutil_file_req (_("Select an output filename"), chanlist_filereq_done,
+ serv, 0, TRUE);
+ } else
gtkutil_simpledialog (_("I can't save an empty list!"));
- return;
- }
- gtkutil_file_req (_("Select an output filename"), chanlist_filereq_done,
- serv, 0, TRUE);
}
static void
@@ -552,16 +660,6 @@
serv->gui->chanlist_maxusers = gtk_spin_button_get_value_as_int (wid);
}
-static void
-chanlist_row_selected (GtkWidget * clist, gint row, gint column,
- GdkEventButton * even, struct server *serv)
-{
- if (even && even->type == GDK_2BUTTON_PRESS)
- {
- chanlist_join (0, (struct server *) serv);
- }
-}
-
/**
* Handles the window's destroy event to free allocated memory.
*/
@@ -584,15 +682,19 @@
chanlist_closegui (GtkWidget *wid, server *serv)
{
if (is_server (serv))
+ {
serv->gui->chanlist_window = 0;
+ if (serv->gui->chanlist_itertree)
+ g_tree_destroy (serv->gui->chanlist_itertree);
+ }
}
void
chanlist_opengui (struct server *serv)
{
- gchar *titles[] = { _("Channel"), _("Users"), _("Topic") };
GtkWidget *frame, *vbox, *hbox, *table, *wid;
char tbuf[256];
+ GtkWidget *vbox1;
if (serv->gui->chanlist_window)
{
@@ -615,6 +717,13 @@
mg_create_generic_tab ("chanlist", tbuf, FALSE, TRUE, chanlist_closegui,
serv, 450, 300, &vbox, serv);
+ /* inner box, for padding */
+ vbox1 = gtk_vbox_new (0, 0);
+ gtk_container_add (GTK_CONTAINER (vbox), vbox1);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox1), 6);
+ gtk_widget_show (vbox1);
+ vbox = vbox1;
+
frame = gtk_frame_new (_("List display options:"));
gtk_container_set_border_width (GTK_CONTAINER (frame), 2);
gtk_widget_show (frame);
@@ -710,25 +819,11 @@
(gpointer) serv);
gtk_widget_show (wid);
- serv->gui->chanlist_list =
- gtkutil_clist_new (3, titles, vbox, GTK_POLICY_ALWAYS,
- chanlist_row_selected, (gpointer) serv, 0, 0,
- GTK_SELECTION_BROWSE);
- gtk_clist_set_column_width (GTK_CLIST (serv->gui->chanlist_list), 0, 90);
- gtk_clist_set_column_width (GTK_CLIST (serv->gui->chanlist_list), 1, 45);
- gtk_clist_set_column_width (GTK_CLIST (serv->gui->chanlist_list), 2, 165);
- gtk_clist_column_titles_active (GTK_CLIST (serv->gui->chanlist_list));
- gtk_signal_connect (GTK_OBJECT (serv->gui->chanlist_list), "click_column",
- GTK_SIGNAL_FUNC (chanlist_click_column),
- (gpointer) serv);
- gtk_clist_set_compare_func (GTK_CLIST (serv->gui->chanlist_list),
- (GtkCListCompareFunc)
- chanlist_compare_text_ignore_case);
- gtk_clist_set_sort_column (GTK_CLIST (serv->gui->chanlist_list), 0);
- gtk_clist_set_auto_sort (GTK_CLIST (serv->gui->chanlist_list), 1);
- /* makes the horiz. scrollbar appear when needed */
- gtk_clist_set_column_auto_resize (GTK_CLIST (serv->gui->chanlist_list),
- 2, TRUE);
+ serv->gui->chanlist_view = chanlist_treeview_new (vbox, serv);
+ serv->gui->chanlist_itertree = NULL;
+ /* keep a balanced tree: keyed by channel name, data is tree iters */
+ serv->gui->chanlist_itertree = g_tree_new_full ((GCompareDataFunc)chanlist_cmp,
+ NULL, g_free, (GDestroyNotify)gtk_tree_iter_free);
/* make a label to store the user/channel info */
wid = gtk_label_new ("");
@@ -762,3 +857,4 @@
gtk_widget_show (serv->gui->chanlist_window);
}
+