summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorRichardHitt <rbh00@netcom.com>2013-03-05 00:13:51 -0800
committerRichard Hitt <rbh00@f17.rbh00.pacbell.net>2013-03-20 15:10:30 -0700
commitf5631b2e2292adb90f0447a5a7eb0f7dece02d1c (patch)
treebf05a6f2d705fc40e2176c8fdd4c2c27c00cdb97
parentc5404b8e25afdc30b5bcafdd2a21b169443e00cc (diff)
Redesign the Ban List window. Closes Issues #303, #342, #427
 This is a combination of 18 commits.
 The first commit's message is:
Here is the initial banlist branch of RichardHitt/hexchat.
Changed files are only src/fe-gtk/{banlist.c,fe-gtk.h}.
This version works and contains my first efforts at selective
sensitization of radio buttons and control buttons.

From this point I intend to undertake a stepwise redesign.

Step 1 will be to make the existing banlist code work for
multiple simultaneous banlist windows (for different channels,
obviously).  It will be a hackathon with the only goal of getting
it working.

Step 2 will be the objectization and alpha-stage tidying-up of
all the terrible looking stuff I will have done in Step 1.

 This is the 2nd commit message:

Here's the post-Step-1 commit.  It works for multiple banlist windows.

Note particularly what I've done to banlist.h.
Note that for many functions in banlist.c the argument is now
a banlist_info *, rather than a session *.
Note in banlist.c the initialization of array modes[] which
contains driving information for the checkboxes.

Of course those checkboxes aren't yet implemented.  Maybe in Step 2
I will change to checkboxes from radio buttons; but definitely I will
change to letting modes[] drive processing.

 This is the 3rd commit message:

Converted to checkboxes.  Much additional work.
Note that the infrastructure for Auto-invite is not yet
present in the hexchat tree.  I'm nearly done with banlist,
I think!

 This is the 4th commit message:

Fleshed out 'invite'.  Tagged masks uniformly, e.g. (b) (e) (I).
General cleanup, nearly at the point of beta quality.

 This is the 5th commit message:

Added fourth mode type: quiet.  Did lots and lots of cleanup.  Beta-ready?

 This is the 6th commit message:

Get the banlist timestamps properly sortable.

 This is the 7th commit message:

Redesign the supports_foo() routines.  Now they're responsible for setting
the flags in ->capable, ->readable, ->writeable.

 This is the 8th commit message:

Deleted a couple of RBH comments.

 This is the 9th commit message:

Now the ESC key will close the banlist window.

 This is the 10th commit message:

Fix the fe-text occurrence of fe_add_ban_list().

 This is the 11th commit message:

Fixed also fe_ban_list_end() and removed fe_is_banwindow().

 This is the 12th commit message:

Use old-style initialization for array of structures modes[]

 This is the 13th commit message:

Oops, incomplete regression of modes[] initialization.  This fixes.

 This is the 14th commit message:

Fixed strptime buy implementing a special version here.
Fixed column width concerns by setting resizable and autosize.

 This is the 15th commit message:

Get rid of testing line.

 This is the 16th commit message:

Changed to gtkutil_destroy_on_esc ()

 This is the 17th commit message:

Remove no-longer-used functnion

 This is the 18th commit message:

Minor cleanups to banlist.c, banlist.h

 Please enter the commit message for your changes. Lines starting
 with '#' will be ignored, and an empty message aborts the commit.

 Author:    RichardHitt <rbh00@netcom.com>
 Committer: Richard Hitt <rbh00@f17.rbh00.pacbell.net>

 Not currently on any branch.
 Changes to be committed:
   (use "git reset HEAD <file>..." to unstage)

	modified:   src/common/fe.h
	modified:   src/common/hexchat.h
	modified:   src/common/inbound.c
	modified:   src/common/modes.c
	modified:   src/common/proto-irc.c
	modified:   src/common/server.c
	modified:   src/fe-gtk/banlist.c
	modified:   src/fe-gtk/banlist.h
	modified:   src/fe-gtk/fe-gtk.c
	modified:   src/fe-gtk/fe-gtk.h
	modified:   src/fe-gtk/maingui.c
	modified:   src/fe-text/fe-text.c
-rw-r--r--src/common/fe.h5
-rw-r--r--src/common/hexchat.h1
-rw-r--r--src/common/inbound.c11
-rw-r--r--src/common/modes.c4
-rw-r--r--src/common/proto-irc.c33
-rw-r--r--src/common/server.c1
-rw-r--r--src/fe-gtk/banlist.c580
-rw-r--r--src/fe-gtk/banlist.h59
-rw-r--r--src/fe-gtk/fe-gtk.c8
-rw-r--r--src/fe-gtk/fe-gtk.h6
-rw-r--r--src/fe-gtk/maingui.c4
-rw-r--r--src/fe-text/fe-text.c13
12 files changed, 579 insertions, 146 deletions
diff --git a/src/common/fe.h b/src/common/fe.h
index 4903ef0e..337c4b47 100644
--- a/src/common/fe.h
+++ b/src/common/fe.h
@@ -59,9 +59,8 @@ int fe_is_chanwindow (struct server *serv);
 void fe_add_chan_list (struct server *serv, char *chan, char *users,
 							  char *topic);
 void fe_chan_list_end (struct server *serv);
-int fe_is_banwindow (struct session *sess);
-void fe_add_ban_list (struct session *sess, char *mask, char *who, char *when, int is_exemption);
-void fe_ban_list_end (struct session *sess, int is_exemption);
+gboolean fe_add_ban_list (struct session *sess, char *mask, char *who, char *when, int rplcode);
+gboolean fe_ban_list_end (struct session *sess, int rplcode);
 void fe_notify_update (char *name);
 void fe_notify_ask (char *name, char *networks);
 void fe_text_clear (struct session *sess, int lines);
diff --git a/src/common/hexchat.h b/src/common/hexchat.h
index fcc77251..8e459306 100644
--- a/src/common/hexchat.h
+++ b/src/common/hexchat.h
@@ -575,6 +575,7 @@ typedef struct server
 	unsigned int have_idmsg:1;		/* freenode's IDENTIFY-MSG */
 	unsigned int have_sasl:1;		/* SASL capability */
 	unsigned int have_except:1;	/* ban exemptions +e */
+	unsigned int have_invite:1;	/* invite exemptions +I */
 	unsigned int using_cp1255:1;	/* encoding is CP1255/WINDOWS-1255? */
 	unsigned int using_irc:1;		/* encoding is "IRC" (CP1252/UTF-8 hybrid)? */
 	unsigned int use_who:1;			/* whether to use WHO command to get dcc_ip */
diff --git a/src/common/inbound.c b/src/common/inbound.c
index 898dcf4b..f9083eaf 100644
--- a/src/common/inbound.c
+++ b/src/common/inbound.c
@@ -1272,12 +1272,14 @@ inbound_user_info (session *sess, char *chan, char *user, char *host,
 }
 
 int
-inbound_banlist (session *sess, time_t stamp, char *chan, char *mask, char *banner, int is_exemption)
+inbound_banlist (session *sess, time_t stamp, char *chan, char *mask, char *banner, int rplcode)
 {
 	char *time_str = ctime (&stamp);
 	server *serv = sess->server;
+	char *nl;
 
-	time_str[19] = 0;	/* get rid of the \n */
+	if ((nl = strchr (time_str, '\n')))
+		*nl = 0;
 	if (stamp == 0)
 		time_str = "";
 
@@ -1288,18 +1290,17 @@ inbound_banlist (session *sess, time_t stamp, char *chan, char *mask, char *bann
 		goto nowindow;
 	}
 
-   if (!fe_is_banwindow (sess))
+	if (!fe_add_ban_list (sess, mask, banner, time_str, rplcode))
 	{
 nowindow:
 		/* let proto-irc.c do the 'goto def' for exemptions */
-		if (is_exemption)
+		if (rplcode == 348)	/* RPL_EXCEPTLIST */
 			return FALSE;
 
 		EMIT_SIGNAL (XP_TE_BANLIST, sess, chan, mask, banner, time_str, 0);
 		return TRUE;
 	}
 
-	fe_add_ban_list (sess, mask, banner, time_str, is_exemption);
 	return TRUE;
 }
 
diff --git a/src/common/modes.c b/src/common/modes.c
index 6caa46b9..7326b6fe 100644
--- a/src/common/modes.c
+++ b/src/common/modes.c
@@ -824,6 +824,10 @@ inbound_005 (server * serv, char *word[])
 #ifndef WIN32
 			serv->have_except = TRUE;
 #endif
+		} else if (strcmp (word[w], "INVEX") == 0)
+		{
+			/* supports mode letter +I, default channel invite */
+			serv->have_invite = TRUE;
 		} else if (strncmp (word[w], "ELIST=", 6) == 0)
 		{
 			/* supports LIST >< min/max user counts? */
diff --git a/src/common/proto-irc.c b/src/common/proto-irc.c
index 18015607..13147016 100644
--- a/src/common/proto-irc.c
+++ b/src/common/proto-irc.c
@@ -778,8 +778,18 @@ process_numeric (session * sess, int n,
 		}
 		break;
 
+	case 346:	/* +I-list entry */
+		if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], 346))
+			goto def;
+		break;
+
+	case 347:	/* end of invite list */
+		if (!fe_ban_list_end (sess, 347))
+			goto def;
+		break;
+
 	case 348:	/* +e-list entry */
-		if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], TRUE))
+		if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], 348))
 			goto def;
 		break;
 
@@ -790,9 +800,8 @@ process_numeric (session * sess, int n,
 			sess = serv->front_session;
 			goto def;
 		}
-		if (!fe_is_banwindow (sess))
+		if (!fe_ban_list_end (sess, 349))
 			goto def;
-		fe_ban_list_end (sess, TRUE);
 		break;
 
 	case 353:						  /* NAMES */
@@ -806,7 +815,8 @@ process_numeric (session * sess, int n,
 		break;
 
 	case 367: /* banlist entry */
-		inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], FALSE);
+		if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], 367))
+			goto def;
 		break;
 
 	case 368:
@@ -816,9 +826,8 @@ process_numeric (session * sess, int n,
 			sess = serv->front_session;
 			goto def;
 		}
-		if (!fe_is_banwindow (sess))
+		if (!fe_ban_list_end (sess, 368))
 			goto def;
-		fe_ban_list_end (sess, FALSE);
 		break;
 
 	case 369:	/* WHOWAS end */
@@ -881,6 +890,18 @@ process_numeric (session * sess, int n,
 		notify_set_online (serv, word[4]);
 		break;
 
+	case 728:	/* +q-list entry */
+		/* NOTE:  FREENODE returns these results inconsistent with e.g. +b */
+		/* Who else has imlemented MODE_QUIET, I wonder? */
+		if (!inbound_banlist (sess, atol (word[8]), word[4], word[6], word[7], 728))
+			goto def;
+		break;
+
+	case 729:	/* end of quiet list */
+		if (!fe_ban_list_end (sess, 729))
+			goto def;
+		break;
+
 	case 903:	/* successful SASL auth */
 	case 904:	/* aborted SASL auth */
 	case 905:	/* failed SASL auth */
diff --git a/src/common/server.c b/src/common/server.c
index 42cae85f..8ad1d6ca 100644
--- a/src/common/server.c
+++ b/src/common/server.c
@@ -1890,6 +1890,7 @@ server_set_defaults (server *serv)
 	serv->have_idmsg = FALSE;
 	serv->have_sasl = FALSE;
 	serv->have_except = FALSE;
+	serv->have_invite = FALSE;
 }
 
 char *
diff --git a/src/fe-gtk/banlist.c b/src/fe-gtk/banlist.c
index a783ea07..d2f413e4 100644
--- a/src/fe-gtk/banlist.c
+++ b/src/fe-gtk/banlist.c
@@ -36,6 +36,7 @@
 #include <gtk/gtkmessagedialog.h>
 #include <gtk/gtktreeview.h>
 #include <gtk/gtktreeselection.h>
+#include <glib.h>
 
 #include "../common/hexchat.h"
 #include "../common/fe.h"
@@ -46,6 +47,52 @@
 #include "maingui.h"
 #include "banlist.h"
 
+/*
+ * These supports_* routines set capable, readable, writable bits */
+static void supports_bans (banlist_info *, int);
+static void supports_exempt (banlist_info *, int);
+static void supports_invite (banlist_info *, int);
+static void supports_quiet (banlist_info *, int);
+
+static mode_info modes[MODE_CT] = {
+	{
+		"Bans",
+		"(b) ",
+		'b',
+		RPL_BANLIST,
+		RPL_ENDOFBANLIST,
+		1<<MODE_BAN,
+		supports_bans
+	}
+	,{
+		"Exempts",
+		"(e) ",
+		'e',
+		RPL_EXCEPTLIST,
+		RPL_ENDOFEXCEPTLIST,
+		1<<MODE_EXEMPT,
+		supports_exempt
+	}
+	,{
+		"Invites",
+		"(I) ",
+		'I',
+		RPL_INVITELIST,
+		RPL_ENDOFINVITELIST,
+		1<<MODE_INVITE,
+		supports_invite
+	}
+	,{
+		"Quiets",
+		"(q) ",
+		'q',
+		RPL_QUIETLIST,
+		RPL_ENDOFQUIETLIST,
+		1<<MODE_QUIET,
+		supports_quiet
+	}
+};
+
 /* model for the banlist tree */
 enum
 {
@@ -58,7 +105,7 @@ enum
 static GtkTreeView *
 get_view (struct session *sess)
 {
-	return GTK_TREE_VIEW (sess->res->banlist_treeview);
+	return GTK_TREE_VIEW (sess->res->banlist->treeview);
 }
 
 static GtkListStore *
@@ -67,91 +114,281 @@ get_store (struct session *sess)
 	return GTK_LIST_STORE (gtk_tree_view_get_model (get_view (sess)));
 }
 
-static gboolean
-supports_exempt (server *serv)
+static void
+supports_bans (banlist_info *banl, int i)
+{
+	int bit = 1<<i;
+
+	banl->capable |= bit;
+	banl->readable |= bit;
+	banl->writeable |= bit;
+	return;
+}
+
+static void
+supports_exempt (banlist_info *banl, int i)
 {
+	server *serv = banl->sess->server;
 	char *cm = serv->chanmodes;
+	int bit = 1<<i;
 
 	if (serv->have_except)
-		return TRUE;
+		goto yes;
 
 	if (!cm)
-		return FALSE;
+		return;
 
 	while (*cm)
 	{
 		if (*cm == ',')
 			break;
 		if (*cm == 'e')
-			return TRUE;
+			goto yes;
 		cm++;
 	}
+	return;
 
-	return FALSE;
+yes:
+	banl->capable |= bit;
+	banl->writeable |= bit;
 }
 
-void
-fe_add_ban_list (struct session *sess, char *mask, char *who, char *when, int is_exempt)
+static void
+supports_invite (banlist_info *banl, int i)
+{
+	server *serv = banl->sess->server;
+	char *cm = serv->chanmodes;
+	int bit = 1<<i;
+
+	if (serv->have_invite)
+		goto yes;
+
+	if (!cm)
+		return;
+
+	while (*cm)
+	{
+		if (*cm == ',')
+			break;
+		if (*cm == 'I')
+			goto yes;
+		cm++;
+	}
+	return;
+
+yes:
+	banl->capable |= bit;
+	banl->writeable |= bit;
+}
+
+static void
+supports_quiet (banlist_info *banl, int i)
+{
+	server *serv = banl->sess->server;
+	char *cm = serv->chanmodes;
+	int bit = 1<<i;
+
+	if (!cm)
+		return;
+
+	while (*cm)
+	{
+		if (*cm == ',')
+			break;
+		if (*cm == modes[i].letter)
+			goto yes;
+		cm++;
+	}
+	return;
+
+yes:
+	banl->capable |= bit;
+	banl->readable |= bit;
+	banl->writeable |= bit;
+}
+
+/* fe_add_ban_list() and fe_ban_list_end() return TRUE if consumed, FALSE otherwise */
+gboolean
+fe_add_ban_list (struct session *sess, char *mask, char *who, char *when, int rplcode)
 {
+	banlist_info *banl = sess->res->banlist;
+	int i;
 	GtkListStore *store;
 	GtkTreeIter iter;
 	char buf[512];
 
-	store = get_store (sess);
-	gtk_list_store_append (store, &iter);
+	if (!banl)
+		return FALSE;
 
-	if (is_exempt)
+	for (i = 0; i < MODE_CT; i++)
+		if (modes[i].code == rplcode)
+			break;
+	if (i == MODE_CT)
+	{
+		/* printf ("Unexpected value in fe_add_ban_list:  %d\n", rplcode); */
+		return FALSE;
+	}
+	if (banl->pending & 1<<i)
 	{
-		snprintf (buf, sizeof (buf), "(EX) %s", mask);
+		store = get_store (sess);
+		gtk_list_store_append (store, &iter);
+
+		g_snprintf (buf, sizeof buf, "%s%s", modes[i].tag, mask);
 		gtk_list_store_set (store, &iter, 0, buf, 1, who, 2, when, -1);
-	} else
+
+		banl->line_ct++;
+		return TRUE;
+	}
+	else return FALSE;
+}
+
+/* Sensitize checkboxes and buttons as appropriate for the moment  */
+static void
+banlist_sensitize (banlist_info *banl)
+{
+	int checkable, i;
+
+	/* CHECKBOXES -- */
+	checkable = banl->sess->me->op? banl->writeable: banl->readable;
+	for (i = 0; i < MODE_CT; i++)
 	{
-		gtk_list_store_set (store, &iter, 0, mask, 1, who, 2, when, -1);
+		if (banl->checkboxes[i] == NULL)
+			continue;
+		if ((checkable & 1<<i) == 0)
+		/* Checkbox is not checkable.  Grey it and uncheck it. */
+		{
+			gtk_widget_set_sensitive (banl->checkboxes[i], FALSE);
+			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (banl->checkboxes[i]), FALSE);
+		}
+		else
+		/* Checkbox is checkable.  Be sure it's sensitive. */
+		{
+			gtk_widget_set_sensitive (banl->checkboxes[i], TRUE);
+		}
 	}
+
+	/* BUTTONS --- */
+	if (banl->sess->me->op == 0 || banl->line_ct == 0)
+	{
+		/* If user is not op or list is empty, buttons should be all greyed */
+		gtk_widget_set_sensitive (banl->but_clear, FALSE);
+		gtk_widget_set_sensitive (banl->but_crop, FALSE);
+		gtk_widget_set_sensitive (banl->but_remove, FALSE);
+	}
+	else
+	{
+		/* If no lines are selected, only the CLEAR button should be sensitive */
+		if (banl->select_ct == 0)
+		{
+			gtk_widget_set_sensitive (banl->but_clear, TRUE);
+			gtk_widget_set_sensitive (banl->but_crop, FALSE);
+			gtk_widget_set_sensitive (banl->but_remove, FALSE);
+		}
+		/* If any lines are selected, only the REMOVE and CROP buttons should be sensitive */
+		else
+		{
+			gtk_widget_set_sensitive (banl->but_clear, FALSE);
+			gtk_widget_set_sensitive (banl->but_crop, TRUE);
+			gtk_widget_set_sensitive (banl->but_remove, TRUE);
+		}
+	}
+
+	/* Set "Refresh" sensitvity */
+	gtk_widget_set_sensitive (banl->but_refresh, banl->pending? FALSE: banl->checked? TRUE: FALSE);
 }
+/* fe_ban_list_end() returns TRUE if consumed, FALSE otherwise */
+gboolean
+fe_ban_list_end (struct session *sess, int rplcode)
+{
+	banlist_info *banl = sess->res->banlist;
+	int i;
 
-void
-fe_ban_list_end (struct session *sess, int is_exemption)
+	if (!banl)
+		return FALSE;
+
+	for (i = 0; i < MODE_CT; i++)
+		if (modes[i].endcode == rplcode)
+			break;
+	if (i == MODE_CT)
+	{
+		/* printf ("Unexpected rplcode value in fe_ban_list_end:  %d\n", rplcode); */
+		return FALSE;
+	}
+	if (banl->pending & modes[i].bit)
+	{
+		banl->pending &= ~modes[i].bit;
+		if (!banl->pending)
+		{
+			gtk_widget_set_sensitive (banl->but_refresh, TRUE);
+			banlist_sensitize (banl);
+		}
+		return TRUE;
+	}
+	else return FALSE;
+}
+
+static void
+banlist_select_changed (GtkWidget *item, banlist_info *banl)
 {
-	gtk_widget_set_sensitive (sess->res->banlist_butRefresh, TRUE);
+	GList *list;
+
+	if (banl->line_ct == 0)
+		banl->select_ct = 0;
+	else
+	{
+		list = gtk_tree_selection_get_selected_rows (GTK_TREE_SELECTION (item), NULL);
+		banl->select_ct = list? 1: 0;
+		g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
+		g_list_free (list);
+	}
+	banlist_sensitize (banl);
 }
 
 /**
  *  * Performs the actual refresh operations.
  *  */
 static void
-banlist_do_refresh (struct session *sess)
+banlist_do_refresh (banlist_info *banl)
 {
+	session *sess = banl->sess;
 	char tbuf[256];
+	int i;
+	char *tbufp;
+
+	banlist_sensitize (banl);
+
 	if (sess->server->connected)
 	{
 		GtkListStore *store;
 
-		gtk_widget_set_sensitive (sess->res->banlist_butRefresh, FALSE);
-
-		snprintf (tbuf, sizeof tbuf, DISPLAY_NAME": Ban List (%s, %s)",
+		g_snprintf (tbuf, sizeof tbuf, DISPLAY_NAME": Ban List (%s, %s)",
 						sess->channel, sess->server->servername);
-		mg_set_title (sess->res->banlist_window, tbuf);
+		mg_set_title (banl->window, tbuf);
 
 		store = get_store (sess);
 		gtk_list_store_clear (store);
-
-		handle_command (sess, "ban", FALSE);
-
-		if (supports_exempt (sess->server))
+		banl->line_ct = 0;
+		banl->pending = banl->checked;
+		if (banl->pending)
 		{
-			snprintf (tbuf, sizeof (tbuf), "quote mode %s +e", sess->channel);
+			tbufp = tbuf + g_snprintf (tbuf, sizeof tbuf, "quote mode %s +", sess->channel);
+			for (i = 0; i < MODE_CT; i++)
+				if (banl->pending & 1<<i)
+				{
+					*tbufp++ = modes[i].letter;
+				}
+			*tbufp = 0;
 			handle_command (sess, tbuf, FALSE);
 		}
-
-	} else
+	}
+	else
 	{
 		fe_message (_("Not connected."), FE_MSG_ERROR);
 	}
 }
 
 static void
-banlist_refresh (GtkWidget * wid, struct session *sess)
+banlist_refresh (GtkWidget * wid, banlist_info *banl)
 {
 	/* JG NOTE: Didn't see actual use of wid here, so just forwarding
 	   *          * this to chanlist_do_refresh because I use it without any widget
@@ -159,123 +396,106 @@ banlist_refresh (GtkWidget * wid, struct session *sess)
 	   *          * or apply for the first time if the list has not yet been
 	   *          * received.
 	   *          */
-	banlist_do_refresh (sess);
+	banlist_do_refresh (banl);
 }
 
 static int
-banlist_unban_inner (gpointer none, struct session *sess, int do_exempts)
+banlist_unban_inner (gpointer none, banlist_info *banl, int mode_num)
 {
+	session *sess = banl->sess;
 	GtkTreeModel *model;
 	GtkTreeSelection *sel;
 	GtkTreeIter iter;
 	char tbuf[2048];
-	char **masks, *tmp, *space;
-	int num_sel, i;
+	char **masks, *mask;
+	int num_sel, taglen, i;
+
 
 	/* grab the list of selected items */
 	model = GTK_TREE_MODEL (get_store (sess));
 	sel = gtk_tree_view_get_selection (get_view (sess));
-	num_sel = 0;
-	if (gtk_tree_model_get_iter_first (model, &iter))
-	{
-		do
-		{
-			if (gtk_tree_selection_iter_is_selected (sel, &iter))
-				num_sel++;
-		}
-		while (gtk_tree_model_iter_next (model, &iter));
-	}
 
-	if (num_sel < 1)
+	if (!gtk_tree_model_get_iter_first (model, &iter))
 		return 0;
 
-	/* create an array of all the masks */
-	masks = calloc (1, num_sel * sizeof (char *));
-
-	i = 0;
-	gtk_tree_model_get_iter_first (model, &iter);
+	taglen = strlen (modes[mode_num].tag);
+	masks = g_malloc (sizeof (char *) * banl->line_ct);
+	num_sel = 0;
 	do
 	{
 		if (gtk_tree_selection_iter_is_selected (sel, &iter))
 		{
-			gtk_tree_model_get (model, &iter, MASK_COLUMN, &masks[i], -1);
-			space = strchr (masks[i], ' ');
+			/* Get the mask part of this selected line */
+			gtk_tree_model_get (model, &iter, MASK_COLUMN, &mask, -1);
 
-			if (do_exempts)
-			{
-				if (space)
-				{
-					/* remove the "(EX) " */
-					tmp = masks[i];
-					masks[i] = g_strdup (space + 1);
-					g_free (tmp);
-					i++;
-				}
-			} else
-			{
-				if (!space)
-					i++;
-			}
+			/* If it's the wrong type of mask, just continue */
+			if (strncmp (modes[mode_num].tag, mask, taglen) != 0)
+				continue;
+
+			/* Otherwise add it to our array of mask pointers */
+			masks[num_sel++] = g_strdup (mask + taglen);
+			g_free (mask);
 		}
 	}
 	while (gtk_tree_model_iter_next (model, &iter));
 
 	/* and send to server */
-	if (do_exempts)
-		send_channel_modes (sess, tbuf, masks, 0, i, '-', 'e', 0);
-	else
-		send_channel_modes (sess, tbuf, masks, 0, i, '-', 'b', 0);
+	if (num_sel)
+		send_channel_modes (sess, tbuf, masks, 0, num_sel, '-', modes[mode_num].letter, 0);
 
-	/* now free everything, and refresh banlist */	
+	/* now free everything */
 	for (i=0; i < num_sel; i++)
 		g_free (masks[i]);
-	free (masks);
+	g_free (masks);
 
 	return num_sel;
 }
 
 static void
-banlist_unban (GtkWidget * wid, struct session *sess)
+banlist_unban (GtkWidget * wid, banlist_info *banl)
 {
-	int num = 0;
+	int i, num = 0;
 
-	num += banlist_unban_inner (wid, sess, FALSE);
-	num += banlist_unban_inner (wid, sess, TRUE);
+	for (i = 0; i < MODE_CT; i++)
+		num += banlist_unban_inner (wid, banl, i);
 
+	/* This really should not occur with the redesign */
 	if (num < 1)
 	{
 		fe_message (_("You must select some bans."), FE_MSG_ERROR);
 		return;
 	}
 
-	banlist_do_refresh (sess);
+	banlist_do_refresh (banl);
 }
 
 static void
-banlist_clear_cb (GtkDialog *dialog, gint response, gpointer sess)
+banlist_clear_cb (GtkDialog *dialog, gint response, gpointer data)
 {
+	banlist_info *banl = data;
 	GtkTreeSelection *sel;
 
 	gtk_widget_destroy (GTK_WIDGET (dialog));
 
 	if (response == GTK_RESPONSE_OK)
 	{
-		sel = gtk_tree_view_get_selection (get_view (sess));
+		sel = gtk_tree_view_get_selection (get_view (banl->sess));
 		gtk_tree_selection_select_all (sel);
-		banlist_unban (NULL, sess);
+		banlist_unban (NULL, banl);
 	}
 }
 
 static void
-banlist_clear (GtkWidget * wid, struct session *sess)
+banlist_clear (GtkWidget * wid, banlist_info *banl)
 {
 	GtkWidget *dialog;
 
 	dialog = gtk_message_dialog_new (NULL, 0,
 								GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL,
-					_("Are you sure you want to remove all bans in %s?"), sess->channel);
+					_("Are you sure you want to remove all listed items in %s?"), banl->sess->channel);
+
 	g_signal_connect (G_OBJECT (dialog), "response",
-							G_CALLBACK (banlist_clear_cb), sess);
+							G_CALLBACK (banlist_clear_cb), banl);
 	gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
 	gtk_widget_show (dialog);
 }
@@ -298,8 +518,9 @@ banlist_add_selected_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *it
 }
 
 static void
-banlist_crop (GtkWidget * wid, struct session *sess)
+banlist_crop (GtkWidget * wid, banlist_info *banl)
 {
+	session *sess = banl->sess;
 	GtkTreeSelection *select;
 	GSList *list = NULL, *node;
 	int num_sel;
@@ -322,22 +543,112 @@ banlist_crop (GtkWidget * wid, struct session *sess)
 		g_slist_foreach (list, (GFunc)g_free, NULL);
 		g_slist_free (list);
 
-		banlist_unban (NULL, sess);
+		banlist_unban (NULL, banl);
 	} else
 		fe_message (_("You must select some bans."), FE_MSG_ERROR);
 }
 
+static void
+banlist_toggle (GtkWidget *item, gpointer data)
+{
+	banlist_info *banl = data;
+	int i, bit = 0;
+
+	for (i = 0; i < MODE_CT; i++)
+		if (banl->checkboxes[i] == item)
+		{
+			bit = 1<<i;
+			break;
+		}
+
+	if (bit)		/* Should be gassert() */
+	{
+		banl->checked &= ~bit;
+		banl->checked |= (GTK_TOGGLE_BUTTON (item)->active)? bit: 0;
+		banlist_do_refresh (banl);
+	}
+}
+
+/* NOTICE:  The official strptime() is not available on all platforms so
+ * I've implemented a special version here.  The official version is
+ * vastly more general than this:  it uses locales for weekday and month
+ * names and its second arg is a format character-string.  This special
+ * version depends on the format returned by ctime(3) whose manpage
+ * says it returns:
+ *     "a null-terminated string of the form "Wed Jun 30 21:49:08 1993\n"
+ *
+ * If the real strpftime() comes available, use this format string:
+ *		#define DATE_FORMAT "%a %b %d %T %Y"
+ */
+static void
+strptime (char *ti, struct tm *tm)
+{
+	/* Expect something like "Sat Mar 16 21:24:27 2013" */
+	static char *mon[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+								  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL };
+	int M = -1, d = -1, h = -1, m = -1, s = -1, y = -1; 
+
+	if (*ti == 0)
+	{
+		memset (tm, 0, sizeof *tm);
+		return;
+	}
+	/* No need to supply tm->tm_wday; mktime() doesn't read it */
+	ti += 4;
+	while ((mon[++M]))
+		if (strncmp (ti, mon[M], 3) == 0)
+			break;
+	ti += 4;
+
+	d = strtol (ti, &ti, 10);
+	h = strtol (++ti, &ti, 10);
+	m = strtol (++ti, &ti, 10);
+	s = strtol (++ti, &ti, 10);
+	y = strtol (++ti, NULL, 10) - 1900;
+
+	tm->tm_sec = s;
+	tm->tm_min = m;
+	tm->tm_hour = h;
+	tm->tm_mday = d;
+	tm->tm_mon = M;
+	tm->tm_year = y;
+}
+
+gint
+banlist_date_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data)
+{
+	struct tm tm1, tm2;
+	time_t t1, t2;
+	char *time1, *time2;
+
+	gtk_tree_model_get(model, a, 2, &time1, -1);
+	gtk_tree_model_get(model, b, 2, &time2, -1);
+	strptime (time1, &tm1);
+	strptime (time2, &tm2);
+	t1 = mktime (&tm1);
+	t2 = mktime (&tm2);
+
+	if (t1 < t2) return 1;
+	if (t1 == t2) return 0;
+	return -1;
+}
+
 static GtkWidget *
-banlist_treeview_new (GtkWidget *box)
+banlist_treeview_new (GtkWidget *box, banlist_info *banl)
 {
 	GtkListStore *store;
 	GtkWidget *view;
 	GtkTreeSelection *select;
 	GtkTreeViewColumn *col;
+	GtkTreeSortable *sortable;
 
 	store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING,
 	                            G_TYPE_STRING);
 	g_return_val_if_fail (store != NULL, NULL);
+
+	sortable = GTK_TREE_SORTABLE (store);
+	gtk_tree_sortable_set_sort_func (sortable, 2, banlist_date_sort, GINT_TO_POINTER (2), NULL);
+
 	view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store), NULL,
 	                             MASK_COLUMN, _("Mask"),
 	                             FROM_COLUMN, _("From"),
@@ -345,17 +656,25 @@ banlist_treeview_new (GtkWidget *box)
 
 	col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), MASK_COLUMN);
 	gtk_tree_view_column_set_alignment (col, 0.5);
-	gtk_tree_view_column_set_min_width (col, 300);
+	gtk_tree_view_column_set_min_width (col, 100);
 	gtk_tree_view_column_set_sort_column_id (col, MASK_COLUMN);
+	gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
+	gtk_tree_view_column_set_resizable (col, TRUE);
 
 	col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), FROM_COLUMN);
 	gtk_tree_view_column_set_alignment (col, 0.5);
 	gtk_tree_view_column_set_sort_column_id (col, FROM_COLUMN);
+	gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
+	gtk_tree_view_column_set_resizable (col, TRUE);
 
 	col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), DATE_COLUMN);
 	gtk_tree_view_column_set_alignment (col, 0.5);
+	gtk_tree_view_column_set_sort_column_id (col, DATE_COLUMN);
+	gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
+	gtk_tree_view_column_set_resizable (col, TRUE);
 
 	select = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
+	g_signal_connect (G_OBJECT (select), "changed", G_CALLBACK (banlist_select_changed), banl);
 	gtk_tree_selection_set_mode (select, GTK_SELECTION_MULTIPLE);
 
 	gtk_widget_show (view);
@@ -363,56 +682,99 @@ banlist_treeview_new (GtkWidget *box)
 }
 
 static void
-banlist_closegui (GtkWidget *wid, session *sess)
+banlist_closegui (GtkWidget *wid, banlist_info *banl)
 {
-	if (is_session (sess))
-		sess->res->banlist_window = 0;
+	session *sess = banl->sess;
+
+	if (sess->res->banlist == banl)
+	{
+		g_free (banl);
+		sess->res->banlist = NULL;
+	}
 }
 
 void
 banlist_opengui (struct session *sess)
 {
-	GtkWidget *vbox1;
-	GtkWidget *bbox;
+	banlist_info *banl;
+	int i;
+	GtkWidget *table, *vbox, *bbox;
 	char tbuf[256];
 
-	if (sess->res->banlist_window)
+	if (sess->type != SESS_CHANNEL)
 	{
-		mg_bring_tofront (sess->res->banlist_window);
+		fe_message (_("You can only open the Ban List window while in a channel tab."), FE_MSG_ERROR);
 		return;
 	}
 
-	if (sess->type != SESS_CHANNEL)
+	if (!sess->res->banlist)
 	{
-		fe_message (_("You can only open the Ban List window while in a channel tab."), FE_MSG_ERROR);
+		sess->res->banlist = g_malloc0 (sizeof (banlist_info));
+		if (!sess->res->banlist)
+		{
+			fe_message (_("Banlist initialization failed."), FE_MSG_ERROR);
+			return;
+		}
+	}
+	banl = sess->res->banlist;
+	if (banl->window)
+	{
+		mg_bring_tofront (banl->window);
 		return;
 	}
 
-	snprintf (tbuf, sizeof tbuf, _(DISPLAY_NAME": Ban List (%s)"),
+	/* New banlist for this session -- Initialize it */
+	banl->sess = sess;
+	/* For each mode set its bit in capable/readable/writeable */
+	for (i = 0; i < MODE_CT; i++)
+		modes[i].tester (banl, i);
+	/* Force on the checkmark in the "Bans" box */
+	banl->checked = 1<<MODE_BAN;
+
+	g_snprintf (tbuf, sizeof tbuf, _(DISPLAY_NAME": Ban List (%s)"),
 					sess->server->servername);
 
-	sess->res->banlist_window = mg_create_generic_tab ("BanList", tbuf, FALSE,
-					TRUE, banlist_closegui, sess, 550, 200, &vbox1, sess->server);
+	banl->window = mg_create_generic_tab ("BanList", tbuf, FALSE,
+					TRUE, banlist_closegui, banl, 550, 200, &vbox, sess->server);
+	gtkutil_destroy_on_esc (banl->window);
+
+	gtk_container_set_border_width (GTK_CONTAINER (banl->window), 3);
+	gtk_box_set_spacing (GTK_BOX (vbox), 3);
 
 	/* create banlist view */
-	sess->res->banlist_treeview = banlist_treeview_new (vbox1);
+	banl->treeview = banlist_treeview_new (vbox, banl);
+
+	table = gtk_table_new (1, 3, FALSE);
+	gtk_table_set_col_spacings (GTK_TABLE (table), 16);
+	gtk_box_pack_start (GTK_BOX (vbox), table, 0, 0, 0);
+
+	for (i = 0; i < MODE_CT; i++)
+	{
+		if (!(banl->capable & 1<<i))
+			continue;
+		banl->checkboxes[i] = gtk_check_button_new_with_label (_(modes[i].name));
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (banl->checkboxes[i]), (banl->checked & 1<<i? TRUE: FALSE));
+		g_signal_connect (G_OBJECT (banl->checkboxes[i]), "toggled",
+								G_CALLBACK (banlist_toggle), banl);
+		gtk_table_attach (GTK_TABLE (table), banl->checkboxes[i], i+1, i+2, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
+	}
 
 	bbox = gtk_hbutton_box_new ();
 	gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD);
 	gtk_container_set_border_width (GTK_CONTAINER (bbox), 5);
-	gtk_box_pack_end (GTK_BOX (vbox1), bbox, 0, 0, 0);
+	gtk_box_pack_end (GTK_BOX (vbox), bbox, 0, 0, 0);
 	gtk_widget_show (bbox);
 
-	gtkutil_button (bbox, GTK_STOCK_REMOVE, 0, banlist_unban, sess,
+	banl->but_remove = gtkutil_button (bbox, GTK_STOCK_REMOVE, 0, banlist_unban, banl,
 	                _("Remove"));
-	gtkutil_button (bbox, GTK_STOCK_REMOVE, 0, banlist_crop, sess,
+	banl->but_crop = gtkutil_button (bbox, GTK_STOCK_REMOVE, 0, banlist_crop, banl,
 	                _("Crop"));
-	gtkutil_button (bbox, GTK_STOCK_CLEAR, 0, banlist_clear, sess,
+	banl->but_clear = gtkutil_button (bbox, GTK_STOCK_CLEAR, 0, banlist_clear, banl,
 	                _("Clear"));
 
-	sess->res->banlist_butRefresh = gtkutil_button (bbox, GTK_STOCK_REFRESH, 0, banlist_refresh, sess, _("Refresh"));
+	banl->but_refresh = gtkutil_button (bbox, GTK_STOCK_REFRESH, 0, banlist_refresh, banl, _("Refresh"));
 
-	banlist_do_refresh (sess);
+	banlist_do_refresh (banl);
 
-	gtk_widget_show (sess->res->banlist_window);
+	gtk_widget_show_all (banl->window);
 }
diff --git a/src/fe-gtk/banlist.h b/src/fe-gtk/banlist.h
index 7ceccd00..40316024 100644
--- a/src/fe-gtk/banlist.h
+++ b/src/fe-gtk/banlist.h
@@ -1 +1,60 @@
+#ifndef BANLIST_H
+#define BANLIST_H
+
+#include "../common/hexchat.h"
 void banlist_opengui (session *sess);
+
+#ifndef RPL_BANLIST
+/* Where's that darn header file, that would have all these defines ? */
+#define RPL_BANLIST 367
+#define RPL_ENDOFBANLIST 368
+#define RPL_INVITELIST 346
+#define RPL_ENDOFINVITELIST 347
+#define RPL_EXCEPTLIST 348
+#define RPL_ENDOFEXCEPTLIST 349
+#define RPL_QUIETLIST 728
+#define RPL_ENDOFQUIETLIST 729
+#endif
+
+typedef enum banlist_modes_e {
+	MODE_BAN,
+	MODE_EXEMPT,
+	MODE_INVITE,
+	MODE_QUIET,
+	MODE_CT
+} banlist_modes;
+
+typedef struct banlist_info_s banlist_info;
+
+typedef struct mode_info_s {
+	char *name;		/* Checkbox name, e.g. "Bans" */
+	char *tag;		/* Prefix line with this, e.g. "(EX) " for MODE_EXEMPT */
+	char letter;	/* /mode-command letter, e.g. 'b' for MODE_BAN */
+	int code;		/* rfc RPL_foo code, e.g. 367 for RPL_BANLIST */
+	int endcode;	/* rfc RPL_ENDOFfoo code, e.g. 368 for RPL_ENDOFBANLIST */
+	int bit;			/* Mask bit, e.g., 1<<MODE_BAN  */
+	void (*tester)(banlist_info *, int);	/* Function returns true to set bit into checkable */
+} mode_info;
+
+typedef struct banlist_info_s {
+	session *sess;
+	int capable;	/* MODE bitmask */
+	int readable;	/* subset of capable if not op */
+	int writeable;	/* subset of capable if op */
+	int checked;	/* subset of (op? writeable: readable) */
+	int pending;	/* subset of checked */
+	int current;	/* index of currently processing mode */
+	int line_ct;	/* count of presented lines */
+	int select_ct;	/* count of selected lines */
+		/* Not really; 1 if any are selected otherwise 0 */
+	GtkWidget *window;
+	GtkWidget *treeview;
+	GtkWidget *radios[MODE_CT];
+	GtkWidget *checkboxes[MODE_CT];
+	GtkWidget *but_remove;
+	GtkWidget *but_crop;
+	GtkWidget *but_clear;
+	GtkWidget *but_refresh;
+	GtkWidget *checkbox[MODE_CT];	/* Checkbox widget for mode */
+} banlist_info;
+#endif /* BANLIST_H */
diff --git a/src/fe-gtk/fe-gtk.c b/src/fe-gtk/fe-gtk.c
index a29a6fda..7ed55cbd 100644
--- a/src/fe-gtk/fe-gtk.c
+++ b/src/fe-gtk/fe-gtk.c
@@ -604,14 +604,6 @@ fe_is_chanwindow (struct server *serv)
 	return 1;
 }
 
-int
-fe_is_banwindow (struct session *sess)
-{
-   if (!sess->res->banlist_window)
-     return 0;
-   return 1;
-}
-
 void
 fe_notify_update (char *name)
 {
diff --git a/src/fe-gtk/fe-gtk.h b/src/fe-gtk/fe-gtk.h
index 313afc4b..d15980c6 100644
--- a/src/fe-gtk/fe-gtk.h
+++ b/src/fe-gtk/fe-gtk.h
@@ -29,6 +29,7 @@
 #include <gtk/gtkwidget.h>
 #include <gtk/gtkcontainer.h>
 #include <gtk/gtksignal.h>
+#include "banlist.h"
 
 #undef gtk_signal_connect
 #define gtk_signal_connect g_signal_connect
@@ -97,10 +98,7 @@ struct server_gui
 
 typedef struct restore_gui
 {
-	/* banlist stuff */
-	GtkWidget *banlist_window;
-	GtkWidget *banlist_treeview;
-	GtkWidget *banlist_butRefresh;
+	banlist_info *banlist;
 
 	void *tab;			/* (chan *) */
 
diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c
index 41344c8f..2d02c59d 100644
--- a/src/fe-gtk/maingui.c
+++ b/src/fe-gtk/maingui.c
@@ -3530,8 +3530,8 @@ fe_server_callback (server *serv)
 void
 fe_session_callback (session *sess)
 {
-	if (sess->res->banlist_window)
-		mg_close_gen (NULL, sess->res->banlist_window);
+	if (sess->res->banlist && sess->res->banlist->window)
+		mg_close_gen (NULL, sess->res->banlist->window);
 
 	if (sess->res->input_text)
 		free (sess->res->input_text);
diff --git a/src/fe-text/fe-text.c b/src/fe-text/fe-text.c
index f603f43e..74496cff 100644
--- a/src/fe-text/fe-text.c
+++ b/src/fe-text/fe-text.c
@@ -651,17 +651,12 @@ void
 fe_chan_list_end (struct server *serv)
 {
 }
-int
-fe_is_banwindow (struct session *sess)
-{
-	return 0;
-}
-void
-fe_add_ban_list (struct session *sess, char *mask, char *who, char *when, int is_exemption)
+gboolean
+fe_add_ban_list (struct session *sess, char *mask, char *who, char *when, int rplcode)
 {
 }
-void
-fe_ban_list_end (struct session *sess, int is_exemption)
+gboolean
+fe_ban_list_end (struct session *sess, int rplcode)
 {
 }
 void