summary refs log tree commit diff stats
path: root/src/fe-gtk
diff options
context:
space:
mode:
Diffstat (limited to 'src/fe-gtk')
-rw-r--r--src/fe-gtk/Makefile.am32
-rw-r--r--src/fe-gtk/about.c161
-rw-r--r--src/fe-gtk/about.h1
-rw-r--r--src/fe-gtk/ascii.c177
-rw-r--r--src/fe-gtk/ascii.h1
-rw-r--r--src/fe-gtk/banlist.c418
-rw-r--r--src/fe-gtk/banlist.h1
-rw-r--r--src/fe-gtk/chanlist.c943
-rw-r--r--src/fe-gtk/chanlist.h1
-rw-r--r--src/fe-gtk/chanview-tabs.c779
-rw-r--r--src/fe-gtk/chanview-tree.c364
-rw-r--r--src/fe-gtk/chanview.c643
-rw-r--r--src/fe-gtk/chanview.h31
-rw-r--r--src/fe-gtk/custom-list.c753
-rw-r--r--src/fe-gtk/custom-list.h85
-rw-r--r--src/fe-gtk/dccgui.c1098
-rw-r--r--src/fe-gtk/editlist.c409
-rw-r--r--src/fe-gtk/editlist.h1
-rw-r--r--src/fe-gtk/fe-gtk.c1063
-rw-r--r--src/fe-gtk/fe-gtk.h197
-rw-r--r--src/fe-gtk/fkeys.c1814
-rw-r--r--src/fe-gtk/fkeys.h5
-rw-r--r--src/fe-gtk/gtkutil.c675
-rw-r--r--src/fe-gtk/gtkutil.h39
-rw-r--r--src/fe-gtk/ignoregui.c449
-rw-r--r--src/fe-gtk/joind.c257
-rw-r--r--src/fe-gtk/joind.h2
-rw-r--r--src/fe-gtk/maingui.c3811
-rw-r--r--src/fe-gtk/maingui.h33
-rw-r--r--src/fe-gtk/menu.c2270
-rw-r--r--src/fe-gtk/menu.h41
-rw-r--r--src/fe-gtk/mmx_cmod.S530
-rw-r--r--src/fe-gtk/mmx_cmod.h4
-rw-r--r--src/fe-gtk/notifygui.c442
-rw-r--r--src/fe-gtk/notifygui.h2
-rw-r--r--src/fe-gtk/palette.c226
-rw-r--r--src/fe-gtk/palette.h15
-rw-r--r--src/fe-gtk/pixmaps.c123
-rw-r--r--src/fe-gtk/pixmaps.h19
-rw-r--r--src/fe-gtk/plugin-tray.c852
-rw-r--r--src/fe-gtk/plugin-tray.h4
-rw-r--r--src/fe-gtk/plugingui.c239
-rw-r--r--src/fe-gtk/plugingui.h2
-rw-r--r--src/fe-gtk/rawlog.c152
-rw-r--r--src/fe-gtk/rawlog.h1
-rw-r--r--src/fe-gtk/search.c159
-rw-r--r--src/fe-gtk/search.h1
-rw-r--r--src/fe-gtk/servlistgui.c1918
-rw-r--r--src/fe-gtk/setup.c2137
-rw-r--r--src/fe-gtk/sexy-spell-entry.c1329
-rw-r--r--src/fe-gtk/sexy-spell-entry.h87
-rw-r--r--src/fe-gtk/textgui.c456
-rw-r--r--src/fe-gtk/textgui.h2
-rw-r--r--src/fe-gtk/urlgrab.c208
-rw-r--r--src/fe-gtk/urlgrab.h2
-rw-r--r--src/fe-gtk/userlistgui.c718
-rw-r--r--src/fe-gtk/userlistgui.h8
-rw-r--r--src/fe-gtk/xtext.c5478
-rw-r--r--src/fe-gtk/xtext.h275
59 files changed, 31943 insertions, 0 deletions
diff --git a/src/fe-gtk/Makefile.am b/src/fe-gtk/Makefile.am
new file mode 100644
index 00000000..7db38096
--- /dev/null
+++ b/src/fe-gtk/Makefile.am
@@ -0,0 +1,32 @@
+localedir = $(datadir)/locale
+
+bin_PROGRAMS = xchat
+
+INCLUDES = $(GUI_CFLAGS) -DG_DISABLE_CAST_CHECKS -DLOCALEDIR=\"$(localedir)\"
+
+xchat_LDADD = ../common/libxchatcommon.a $(GUI_LIBS)
+
+EXTRA_DIST = \
+	about.h ascii.h banlist.h chanlist.h chanview.h chanview-tabs.c \
+	chanview-tree.c custom-list.h editlist.h fe-gtk.h fkeys.h gtkutil.h joind.h \
+	maingui.h menu.h mmx_cmod.S mmx_cmod.h notifygui.h palette.h pixmaps.h \
+	plugin-tray.h plugingui.c plugingui.h rawlog.h search.h sexy-spell-entry.h \
+   textgui.h urlgrab.h userlistgui.h xtext.h
+
+if USE_MMX
+mmx_cmod_S = mmx_cmod.S
+endif
+
+if DO_PLUGIN
+plugingui_c = plugingui.c
+endif
+
+if USE_LIBSEXY
+sexy_spell_entry_c = sexy-spell-entry.c
+endif
+
+xchat_SOURCES = about.c ascii.c banlist.c chanlist.c chanview.c custom-list.c \
+	dccgui.c editlist.c fe-gtk.c fkeys.c gtkutil.c ignoregui.c joind.c menu.c \
+	maingui.c $(mmx_cmod_S) notifygui.c palette.c pixmaps.c plugin-tray.c $(plugingui_c) \
+	rawlog.c search.c servlistgui.c setup.c $(sexy_spell_entry_c) textgui.c \
+	urlgrab.c userlistgui.c xtext.c
diff --git a/src/fe-gtk/about.c b/src/fe-gtk/about.c
new file mode 100644
index 00000000..60700aec
--- /dev/null
+++ b/src/fe-gtk/about.c
@@ -0,0 +1,161 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkmain.h>
+#include <gtk/gtkcontainer.h>
+#include <gtk/gtkdialog.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkimage.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkwindow.h>
+
+#ifdef USE_XLIB
+#include <gdk/gdkx.h>
+#endif
+
+#include "../common/xchat.h"
+#include "../common/util.h"
+#include "palette.h"
+#include "pixmaps.h"
+#include "gtkutil.h"
+#include "about.h"
+
+
+#if 0 /*def USE_GNOME*/
+
+void
+menu_about (GtkWidget * wid, gpointer sess)
+{
+	char buf[512];
+	const gchar *author[] = { "Peter Zelezny <zed@xchat.org>", 0 };
+
+	(snprintf) (buf, sizeof (buf),
+				 "An IRC Client for UNIX.\n\n"
+				 "This binary was compiled on "__DATE__"\n"
+				 "Using GTK %d.%d.%d X %d\n"
+				 "Running on %s",
+				 gtk_major_version, gtk_minor_version, gtk_micro_version,
+#ifdef USE_XLIB
+				VendorRelease (GDK_DISPLAY ()), get_cpu_str());
+#else
+				666, get_cpu_str());
+#endif
+
+	gtk_widget_show (gnome_about_new ("X-Chat", PACKAGE_VERSION,
+							"(C) 1998-2005 Peter Zelezny", author, buf, 0));
+}
+
+#else
+
+static GtkWidget *about = 0;
+
+static int
+about_close (void)
+{
+	about = 0;
+	return 0;
+}
+
+void
+menu_about (GtkWidget * wid, gpointer sess)
+{
+	GtkWidget *vbox, *label, *hbox;
+	char buf[512];
+	const char *locale = NULL;
+	extern GtkWindow *parent_window;      /* maingui.c */
+
+	if (about)
+	{
+		gtk_window_present (GTK_WINDOW (about));
+		return;
+	}
+
+	about = gtk_dialog_new ();
+	gtk_window_set_position (GTK_WINDOW (about), GTK_WIN_POS_CENTER);
+	gtk_window_set_resizable (GTK_WINDOW (about), FALSE);
+	gtk_window_set_title (GTK_WINDOW (about), _("About "DISPLAY_NAME));
+	if (parent_window)
+		gtk_window_set_transient_for (GTK_WINDOW (about), parent_window);
+	g_signal_connect (G_OBJECT (about), "destroy",
+							G_CALLBACK (about_close), 0);
+
+	vbox = GTK_DIALOG (about)->vbox;
+
+	wid = gtk_image_new_from_pixbuf (pix_xchat);
+	gtk_container_add (GTK_CONTAINER (vbox), wid);
+
+	label = gtk_label_new (NULL);
+	gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+	gtk_container_add (GTK_CONTAINER (vbox), label);
+	g_get_charset (&locale);
+	(snprintf) (buf, sizeof (buf),
+				"<span size=\"x-large\"><b>"DISPLAY_NAME" "PACKAGE_VERSION"</b></span>\n\n"
+				"%s\n\n"
+#ifdef WIN32
+				/* leave this message to avoid time wasting bug reports! */
+				"This version is unofficial and comes with no support.\n\n"
+#endif
+				"%s\n"
+				"<b>Charset</b>: %s "
+#ifdef WIN32 
+				"<b>GTK+</b>: %i.%i.%i\n"
+#else
+				"<b>Renderer</b>: %s\n"
+#endif
+				"<b>Compiled</b>: "__DATE__"\n\n"
+				"<small>\302\251 1998-2010 Peter \305\275elezn\303\275 &lt;zed@xchat.org></small>",
+					_("A multiplatform IRC Client"),
+					get_cpu_str(),
+					locale,
+#ifdef WIN32
+					gtk_major_version,
+					gtk_minor_version,
+					gtk_micro_version
+#else
+#ifdef USE_XFT
+					"Xft"
+#else
+					"Pango"
+#endif
+#endif
+					);
+	gtk_label_set_markup (GTK_LABEL (label), buf);
+	gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER);
+
+	hbox = gtk_hbox_new (0, 2);
+	gtk_container_add (GTK_CONTAINER (vbox), hbox);
+
+	wid = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
+	GTK_WIDGET_SET_FLAGS (GTK_WIDGET (wid), GTK_CAN_DEFAULT);
+	gtk_box_pack_end (GTK_BOX (GTK_DIALOG (about)->action_area), wid, 0, 0, 0);
+	gtk_widget_grab_default (wid);
+	g_signal_connect (G_OBJECT (wid), "clicked",
+							G_CALLBACK (gtkutil_destroy), about);
+
+	gtk_widget_show_all (about);
+}
+#endif
diff --git a/src/fe-gtk/about.h b/src/fe-gtk/about.h
new file mode 100644
index 00000000..2bad159c
--- /dev/null
+++ b/src/fe-gtk/about.h
@@ -0,0 +1 @@
+void menu_about (GtkWidget * wid, gpointer sess);
diff --git a/src/fe-gtk/ascii.c b/src/fe-gtk/ascii.c
new file mode 100644
index 00000000..f1adbdfc
--- /dev/null
+++ b/src/fe-gtk/ascii.c
@@ -0,0 +1,177 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkeditable.h>
+#include <gtk/gtkframe.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkbutton.h>
+
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "gtkutil.h"
+#include "ascii.h"
+#include "maingui.h"
+
+static const unsigned char table[]=
+{
+/* Line 1 */ '\n', 
+0xc2,0xa1,0xc2,0xbf,0xc2,0xa2,0xc2,0xa3,0xe2,0x82,0xac,0xc2,0xa5,0xc2,0xa6,0xc2,
+0xa7,0xc2,0xa8,0xc2,0xa9,0xc2,0xae,0xc2,0xaa,0xc2,0xab,0xc2,0xbb,0xc2,0xac,0xc2,
+0xad,0xc2,0xaf,0xe2,0x99,0xaa,
+/* Line 2 */ '\n', 
+0xc2,0xba,0xc2,0xb9,0xc2,0xb2,0xc2,0xb3,0xc2,0xb4,0xc2,0xb5,0xc3,0x9e,0xc3,0xbe,
+0xc2,0xb6,0xc2,0xb7,0xc2,0xb8,0xc2,0xb0,0xc2,0xbc,0xc2,0xbd,0xc2,0xbe,0xc3,0x97,
+0xc2,0xb1,0xc3,0xb7,
+/* Line 3 */ '\n', 
+0xc3,0x80,0xc3,0x81,0xc3,0x82,0xc3,0x83,0xc3,0x84,0xc3,0x85,0xc3,0x86,0xc4,0x82,
+0xc4,0x84,0x20,0xc3,0x87,0xc4,0x86,0xc4,0x8c,0xc5,0x92,0x20,0xc4,0x8e,0xc4,0x90,
+0x20,
+/* Line 4 */ '\n', 
+0xc3,0xa0,0xc3,0xa1,0xc3,0xa2,0xc3,0xa3,0xc3,0xa4,0xc3,0xa5,0xc3,0xa6,0xc4,0x83,
+0xc4,0x85,0x20,0xc3,0xa7,0xc4,0x87,0xc4,0x8d,0xc5,0x93,0x20,0xc4,0x8f,0xc4,0x91,
+0x20,
+/* Line 5 */ '\n', 
+0xc3,0x88,0xc3,0x89,0xc3,0x8a,0xc3,0x8b,0xc4,0x98,0xc4,0x9a,0x20,0xc4,0x9e,0x20,
+0xc3,0x8c,0xc3,0x8d,0xc3,0x8e,0xc3,0x8f,0xc4,0xb0,0x20,0xc4,0xb9,0xc4,0xbd,0xc5,
+0x81,
+/* Line 6 */ '\n', 
+0xc3,0xa8,0xc3,0xa9,0xc3,0xaa,0xc3,0xab,0xc4,0x99,0xc4,0x9b,0x20,0xc4,0x9f,0x20,
+0xc3,0xac,0xc3,0xad,0xc3,0xae,0xc3,0xaf,0xc4,0xb1,0x20,0xc4,0xba,0xc4,0xbe,0xc5,
+0x82,
+/* Line 7 */ '\n', 
+0xc3,0x91,0xc5,0x83,0xc5,0x87,0x20,0xc3,0x92,0xc3,0x93,0xc3,0x94,0xc3,0x95,0xc3,
+0x96,0xc3,0x98,0xc5,0x90,0x20,0xc5,0x94,0xc5,0x98,0x20,0xc5,0x9a,0xc5,0x9e,0xc5,
+0xa0,
+/* Line 8 */ '\n', 
+0xc3,0xb1,0xc5,0x84,0xc5,0x88,0x20,0xc3,0xb2,0xc3,0xb3,0xc3,0xb4,0xc3,0xb5,0xc3,
+0xb6,0xc3,0xb8,0xc5,0x91,0x20,0xc5,0x95,0xc5,0x99,0x20,0xc5,0x9b,0xc5,0x9f,0xc5,
+0xa1,
+/* Line 9 */ '\n', 
+0x20,0xc5,0xa2,0xc5,0xa4,0x20,0xc3,0x99,0xc3,0x9a,0xc3,0x9b,0xc5,0xb2,0xc3,0x9c,
+0xc5,0xae,0xc5,0xb0,0x20,0xc3,0x9d,0xc3,0x9f,0x20,0xc5,0xb9,0xc5,0xbb,0xc5,0xbd,
+/* Line 10 */ '\n', 
+0x20,0xc5,0xa3,0xc5,0xa5,0x20,0xc3,0xb9,0xc3,0xba,0xc3,0xbb,0xc5,0xb3,0xc3,0xbc,
+0xc5,0xaf,0xc5,0xb1,0x20,0xc3,0xbd,0xc3,0xbf,0x20,0xc5,0xba,0xc5,0xbc,0xc5,0xbe,
+/* Line 11 */ '\n', 
+0xd0,0x90,0xd0,0x91,0xd0,0x92,0xd0,0x93,0xd0,0x94,0xd0,0x95,0xd0,0x81,0xd0,0x96,
+0xd0,0x97,0xd0,0x98,0xd0,0x99,0xd0,0x9a,0xd0,0x9b,0xd0,0x9c,0xd0,0x9d,0xd0,0x9e,
+0xd0,0x9f,0xd0,0xa0,
+/* Line 12 */ '\n', 
+0xd0,0xb0,0xd0,0xb1,0xd0,0xb2,0xd0,0xb3,0xd0,0xb4,0xd0,0xb5,0xd1,0x91,0xd0,0xb6,
+0xd0,0xb7,0xd0,0xb8,0xd0,0xb9,0xd0,0xba,0xd0,0xbb,0xd0,0xbc,0xd0,0xbd,0xd0,0xbe,
+0xd0,0xbf,0xd1,0x80,
+/* Line 13 */ '\n', 
+0xd0,0xa1,0xd0,0xa2,0xd0,0xa3,0xd0,0xa4,0xd0,0xa5,0xd0,0xa6,0xd0,0xa7,0xd0,0xa8,
+0xd0,0xa9,0xd0,0xaa,0xd0,0xab,0xd0,0xac,0xd0,0xad,0xd0,0xae,0xd0,0xaf,
+/* Line 14 */ '\n', 
+0xd1,0x81,0xd1,0x82,0xd1,0x83,0xd1,0x84,0xd1,0x85,0xd1,0x86,0xd1,0x87,0xd1,0x88,
+0xd1,0x89,0xd1,0x8a,0xd1,0x8b,0xd1,0x8c,0xd1,0x8d,0xd1,0x8e,0xd1,0x8f,0
+};
+
+
+static gboolean
+ascii_enter (GtkWidget * wid, GdkEventCrossing *event, GtkWidget *label)
+{
+	char buf[64];
+	const char *text;
+	gunichar u;
+
+	text = gtk_button_get_label (GTK_BUTTON (wid));
+	u = g_utf8_get_char (text);
+	sprintf (buf, "%s U+%04X", text, u);
+	gtk_label_set_text (GTK_LABEL (label), buf);
+
+	return FALSE;
+}
+
+static void
+ascii_click (GtkWidget * wid, gpointer userdata)
+{
+	int tmp_pos;
+	const char *text;
+
+	if (current_sess)
+	{
+		text = gtk_button_get_label (GTK_BUTTON (wid));
+		wid = current_sess->gui->input_box;
+		tmp_pos = SPELL_ENTRY_GET_POS (wid);
+		SPELL_ENTRY_INSERT (wid, text, -1, &tmp_pos);
+		SPELL_ENTRY_SET_POS (wid, tmp_pos);
+	}
+}
+
+void
+ascii_open (void)
+{
+	int i, len;
+	const unsigned char *table_pos;
+	char name[8];
+	GtkWidget *frame, *label, *but, *hbox = NULL, *vbox, *win;
+
+	win = mg_create_generic_tab ("charmap", _("Character Chart"), TRUE, TRUE,
+										  NULL, NULL, 0, 0, &vbox, NULL);
+	gtk_container_set_border_width (GTK_CONTAINER (win), 5);
+
+	label = gtk_label_new (NULL);
+
+	table_pos = table;
+	i = 0;
+	while (table_pos[0] != 0)
+	{
+		if (table_pos[0] == '\n' || i == 0)
+		{
+			table_pos++;
+			hbox = gtk_hbox_new (0, 0);
+			gtk_container_add (GTK_CONTAINER (vbox), hbox);
+			gtk_widget_show (hbox);
+			i++;
+			continue;
+		}
+
+		i++;
+		len = g_utf8_skip[table_pos[0]];
+		memcpy (name, table_pos, len);
+		name[len] = 0;
+
+		but = gtk_button_new_with_label (name);
+		gtk_widget_set_size_request (but, 28, -1);
+		g_signal_connect (G_OBJECT (but), "clicked",
+								G_CALLBACK (ascii_click), NULL);
+		g_signal_connect (G_OBJECT (but), "enter_notify_event",
+								G_CALLBACK (ascii_enter), label);
+		gtk_box_pack_start (GTK_BOX (hbox), but, 0, 0, 0);
+		gtk_widget_show (but);
+
+		table_pos += len;
+	}
+
+	frame = gtk_frame_new ("");
+	gtk_container_add (GTK_CONTAINER (hbox), frame);
+	gtk_container_add (GTK_CONTAINER (frame), label);
+	gtk_widget_show (label);
+	gtk_widget_show (frame);
+
+	gtk_widget_show (win);
+}
diff --git a/src/fe-gtk/ascii.h b/src/fe-gtk/ascii.h
new file mode 100644
index 00000000..afd3bd4f
--- /dev/null
+++ b/src/fe-gtk/ascii.h
@@ -0,0 +1 @@
+void ascii_open (void);
diff --git a/src/fe-gtk/banlist.c b/src/fe-gtk/banlist.c
new file mode 100644
index 00000000..afaa7eb4
--- /dev/null
+++ b/src/fe-gtk/banlist.c
@@ -0,0 +1,418 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkliststore.h>
+#include <gtk/gtkmessagedialog.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtktreeselection.h>
+
+#include "../common/xchat.h"
+#include "../common/fe.h"
+#include "../common/modes.h"
+#include "../common/outbound.h"
+#include "../common/xchatc.h"
+#include "gtkutil.h"
+#include "maingui.h"
+#include "banlist.h"
+
+/* model for the banlist tree */
+enum
+{
+	MASK_COLUMN,
+	FROM_COLUMN,
+	DATE_COLUMN,
+	N_COLUMNS
+};
+
+static GtkTreeView *
+get_view (struct session *sess)
+{
+	return GTK_TREE_VIEW (sess->res->banlist_treeview);
+}
+
+static GtkListStore *
+get_store (struct session *sess)
+{
+	return GTK_LIST_STORE (gtk_tree_view_get_model (get_view (sess)));
+}
+
+static gboolean
+supports_exempt (server *serv)
+{
+	char *cm = serv->chanmodes;
+
+	if (serv->have_except)
+		return TRUE;
+
+	if (!cm)
+		return FALSE;
+
+	while (*cm)
+	{
+		if (*cm == ',')
+			break;
+		if (*cm == 'e')
+			return TRUE;
+		cm++;
+	}
+
+	return FALSE;
+}
+
+void
+fe_add_ban_list (struct session *sess, char *mask, char *who, char *when, int is_exempt)
+{
+	GtkListStore *store;
+	GtkTreeIter iter;
+	char buf[512];
+
+	store = get_store (sess);
+	gtk_list_store_append (store, &iter);
+
+	if (is_exempt)
+	{
+		snprintf (buf, sizeof (buf), "(EX) %s", mask);
+		gtk_list_store_set (store, &iter, 0, buf, 1, who, 2, when, -1);
+	} else
+	{
+		gtk_list_store_set (store, &iter, 0, mask, 1, who, 2, when, -1);
+	}
+}
+
+void
+fe_ban_list_end (struct session *sess, int is_exemption)
+{
+	gtk_widget_set_sensitive (sess->res->banlist_butRefresh, TRUE);
+}
+
+/**
+ *  * Performs the actual refresh operations.
+ *  */
+static void
+banlist_do_refresh (struct session *sess)
+{
+	char tbuf[256];
+	if (sess->server->connected)
+	{
+		GtkListStore *store;
+
+		gtk_widget_set_sensitive (sess->res->banlist_butRefresh, FALSE);
+
+		snprintf (tbuf, sizeof tbuf, "XChat: Ban List (%s, %s)",
+						sess->channel, sess->server->servername);
+		mg_set_title (sess->res->banlist_window, tbuf);
+
+		store = get_store (sess);
+		gtk_list_store_clear (store);
+
+		handle_command (sess, "ban", FALSE);
+#ifdef WIN32
+		if (0)
+#else
+		if (supports_exempt (sess->server))
+#endif
+		{
+			snprintf (tbuf, sizeof (tbuf), "quote mode %s +e", sess->channel);
+			handle_command (sess, tbuf, FALSE);
+		}
+
+	} else
+	{
+		fe_message (_("Not connected."), FE_MSG_ERROR);
+	}
+}
+
+static void
+banlist_refresh (GtkWidget * wid, struct session *sess)
+{
+	/* 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
+	   *          * param in chanlist_build_gui_list when the user presses enter
+	   *          * or apply for the first time if the list has not yet been
+	   *          * received.
+	   *          */
+	banlist_do_refresh (sess);
+}
+
+static int
+banlist_unban_inner (gpointer none, struct session *sess, int do_exempts)
+{
+	GtkTreeModel *model;
+	GtkTreeSelection *sel;
+	GtkTreeIter iter;
+	char tbuf[2048];
+	char **masks, *tmp, *space;
+	int num_sel, 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)
+		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);
+	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], ' ');
+
+			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++;
+			}
+		}
+	}
+	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);
+
+	/* now free everything, and refresh banlist */	
+	for (i=0; i < num_sel; i++)
+		g_free (masks[i]);
+	free (masks);
+
+	return num_sel;
+}
+
+static void
+banlist_unban (GtkWidget * wid, struct session *sess)
+{
+	int num = 0;
+
+	num += banlist_unban_inner (wid, sess, FALSE);
+	num += banlist_unban_inner (wid, sess, TRUE);
+
+	if (num < 1)
+	{
+		fe_message (_("You must select some bans."), FE_MSG_ERROR);
+		return;
+	}
+
+	banlist_do_refresh (sess);
+}
+
+static void
+banlist_clear_cb (GtkDialog *dialog, gint response, gpointer sess)
+{
+	GtkTreeSelection *sel;
+
+	gtk_widget_destroy (GTK_WIDGET (dialog));
+
+	if (response == GTK_RESPONSE_OK)
+	{
+		sel = gtk_tree_view_get_selection (get_view (sess));
+		gtk_tree_selection_select_all (sel);
+		banlist_unban (NULL, sess);
+	}
+}
+
+static void
+banlist_clear (GtkWidget * wid, struct session *sess)
+{
+	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);
+	g_signal_connect (G_OBJECT (dialog), "response",
+							G_CALLBACK (banlist_clear_cb), sess);
+	gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
+	gtk_widget_show (dialog);
+}
+
+static void
+banlist_add_selected_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
+{
+	GSList **lp = data;
+	GSList *list = NULL;
+	GtkTreeIter *copy;
+
+	if (!lp) return;
+	list = *lp;
+	copy = g_malloc (sizeof (GtkTreeIter));
+	g_return_if_fail (copy != NULL);
+	*copy = *iter;
+
+	list = g_slist_append (list, copy);
+	*(GSList **)data = list;
+}
+
+static void
+banlist_crop (GtkWidget * wid, struct session *sess)
+{
+	GtkTreeSelection *select;
+	GSList *list = NULL, *node;
+	int num_sel;
+
+	/* remember which bans are selected */
+	select = gtk_tree_view_get_selection (get_view (sess));
+	/* gtk_tree_selected_get_selected_rows() isn't present in gtk 2.0.x */
+	gtk_tree_selection_selected_foreach (select, banlist_add_selected_cb,
+	                                     &list);
+
+	num_sel = g_slist_length (list);
+	/* select all, then unselect those that we remembered */
+	if (num_sel)
+	{
+		gtk_tree_selection_select_all (select);
+
+		for (node = list; node; node = node->next)
+			gtk_tree_selection_unselect_iter (select, node->data);
+		
+		g_slist_foreach (list, (GFunc)g_free, NULL);
+		g_slist_free (list);
+
+		banlist_unban (NULL, sess);
+	} else
+		fe_message (_("You must select some bans."), FE_MSG_ERROR);
+}
+
+static GtkWidget *
+banlist_treeview_new (GtkWidget *box)
+{
+	GtkListStore *store;
+	GtkWidget *view;
+	GtkTreeSelection *select;
+	GtkTreeViewColumn *col;
+
+	store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING,
+	                            G_TYPE_STRING);
+	g_return_val_if_fail (store != NULL, NULL);
+	view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store), NULL,
+	                             MASK_COLUMN, _("Mask"),
+	                             FROM_COLUMN, _("From"),
+	                             DATE_COLUMN, _("Date"), -1);
+
+	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_sort_column_id (col, MASK_COLUMN);
+
+	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);
+
+	col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), DATE_COLUMN);
+	gtk_tree_view_column_set_alignment (col, 0.5);
+
+	select = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
+	gtk_tree_selection_set_mode (select, GTK_SELECTION_MULTIPLE);
+
+	gtk_widget_show (view);
+	return view;
+}
+
+static void
+banlist_closegui (GtkWidget *wid, session *sess)
+{
+	if (is_session (sess))
+		sess->res->banlist_window = 0;
+}
+
+void
+banlist_opengui (struct session *sess)
+{
+	GtkWidget *vbox1;
+	GtkWidget *bbox;
+	char tbuf[256];
+
+	if (sess->res->banlist_window)
+	{
+		mg_bring_tofront (sess->res->banlist_window);
+		return;
+	}
+
+	if (sess->type != SESS_CHANNEL)
+	{
+		fe_message (_("You can only open the Ban List window while in a channel tab."), FE_MSG_ERROR);
+		return;
+	}
+
+	snprintf (tbuf, sizeof tbuf, _("XChat: 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);
+
+	/* create banlist view */
+	sess->res->banlist_treeview = banlist_treeview_new (vbox1);
+
+	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_widget_show (bbox);
+
+	gtkutil_button (bbox, GTK_STOCK_REMOVE, 0, banlist_unban, sess,
+	                _("Remove"));
+	gtkutil_button (bbox, GTK_STOCK_REMOVE, 0, banlist_crop, sess,
+	                _("Crop"));
+	gtkutil_button (bbox, GTK_STOCK_CLEAR, 0, banlist_clear, sess,
+	                _("Clear"));
+
+	sess->res->banlist_butRefresh = gtkutil_button (bbox, GTK_STOCK_REFRESH, 0, banlist_refresh, sess, _("Refresh"));
+
+	banlist_do_refresh (sess);
+
+	gtk_widget_show (sess->res->banlist_window);
+}
diff --git a/src/fe-gtk/banlist.h b/src/fe-gtk/banlist.h
new file mode 100644
index 00000000..7ceccd00
--- /dev/null
+++ b/src/fe-gtk/banlist.h
@@ -0,0 +1 @@
+void banlist_opengui (session *sess);
diff --git a/src/fe-gtk/chanlist.c b/src/fe-gtk/chanlist.c
new file mode 100644
index 00000000..2f65b518
--- /dev/null
+++ b/src/fe-gtk/chanlist.c
@@ -0,0 +1,943 @@
+/* X-Chat
+ * Copyright (C) 1998-2006 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkalignment.h>
+#include <gtk/gtkcellrenderertext.h>
+#include <gtk/gtkcheckbutton.h>
+#include <gtk/gtkcombobox.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkliststore.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkspinbutton.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkvseparator.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "../common/cfgfiles.h"
+#include "../common/outbound.h"
+#include "../common/util.h"
+#include "../common/fe.h"
+#include "../common/server.h"
+#include "gtkutil.h"
+#include "maingui.h"
+
+
+#include "custom-list.h"
+
+
+enum
+{
+	COL_CHANNEL,
+	COL_USERS,
+	COL_TOPIC,
+	N_COLUMNS
+};
+
+#ifndef CUSTOM_LIST
+typedef struct	/* this is now in custom-list.h */
+{
+	char *topic;
+	char *collation_key;
+	guint32	pos;
+	guint32 users;
+	/* channel string lives beyond "users" */
+#define GET_CHAN(row) (((char *)row)+sizeof(chanlistrow))
+}
+chanlistrow;
+#endif
+
+#define GET_MODEL(xserv) (gtk_tree_view_get_model(GTK_TREE_VIEW(xserv->gui->chanlist_list)))
+
+
+static gboolean
+chanlist_match (server *serv, const char *str)
+{
+	switch (serv->gui->chanlist_search_type)
+	{
+	case 1:
+		return match (GTK_ENTRY (serv->gui->chanlist_wild)->text, str);
+#ifndef WIN32
+	case 2:
+		if (!serv->gui->have_regex)
+			return 0;
+		/* regex returns 0 if it's a match: */
+		return !regexec (&serv->gui->chanlist_match_regex, str, 1, NULL, REG_NOTBOL);
+#endif
+	default:	/* case 0: */
+		return nocasestrstr (str, GTK_ENTRY (serv->gui->chanlist_wild)->text) ? 1 : 0;
+	}
+}
+
+/**
+ * Updates the caption to reflect the number of users and channels
+ */
+static void
+chanlist_update_caption (server *serv)
+{
+	gchar tbuf[256];
+
+	snprintf (tbuf, sizeof tbuf,
+				 _("Displaying %d/%d users on %d/%d channels."),
+				 serv->gui->chanlist_users_shown_count,
+				 serv->gui->chanlist_users_found_count,
+				 serv->gui->chanlist_channels_shown_count,
+				 serv->gui->chanlist_channels_found_count);
+
+	gtk_label_set_text (GTK_LABEL (serv->gui->chanlist_label), tbuf);
+	serv->gui->chanlist_caption_is_stale = FALSE;
+}
+
+static void
+chanlist_update_buttons (server *serv)
+{
+	if (serv->gui->chanlist_channels_shown_count)
+	{
+		gtk_widget_set_sensitive (serv->gui->chanlist_join, TRUE);
+		gtk_widget_set_sensitive (serv->gui->chanlist_savelist, TRUE);
+	}
+	else
+	{
+		gtk_widget_set_sensitive (serv->gui->chanlist_join, FALSE);
+		gtk_widget_set_sensitive (serv->gui->chanlist_savelist, FALSE);
+	}
+}
+
+static void
+chanlist_reset_counters (server *serv)
+{
+	serv->gui->chanlist_users_found_count = 0;
+	serv->gui->chanlist_users_shown_count = 0;
+	serv->gui->chanlist_channels_found_count = 0;
+	serv->gui->chanlist_channels_shown_count = 0;
+
+	chanlist_update_caption (serv);
+	chanlist_update_buttons (serv);
+}
+
+/* free up our entire linked list and all the nodes */
+
+static void
+chanlist_data_free (server *serv)
+{
+	GSList *rows;
+	chanlistrow *data;
+
+	if (serv->gui->chanlist_data_stored_rows)
+	{
+		for (rows = serv->gui->chanlist_data_stored_rows; rows != NULL;
+			  rows = rows->next)
+		{
+			data = rows->data;
+			g_free (data->topic);
+			g_free (data->collation_key);
+			free (data);
+		}
+
+		g_slist_free (serv->gui->chanlist_data_stored_rows);
+		serv->gui->chanlist_data_stored_rows = NULL;
+	}
+
+	g_slist_free (serv->gui->chanlist_pending_rows);
+	serv->gui->chanlist_pending_rows = NULL;
+}
+
+/* add any rows we received from the server in the last 0.25s to the GUI */
+
+static void
+chanlist_flush_pending (server *serv)
+{
+	GSList *list = serv->gui->chanlist_pending_rows;
+	GtkTreeModel *model;
+	chanlistrow *row;
+
+	if (!list)
+	{
+		if (serv->gui->chanlist_caption_is_stale)
+			chanlist_update_caption (serv);
+		return;
+	}
+	model = GET_MODEL (serv);
+
+	while (list)
+	{
+		row = list->data;
+		custom_list_append (CUSTOM_LIST (model), row);
+		list = list->next;
+	}
+
+	g_slist_free (serv->gui->chanlist_pending_rows);
+	serv->gui->chanlist_pending_rows = NULL;
+	chanlist_update_caption (serv);
+}
+
+static gboolean
+chanlist_timeout (server *serv)
+{
+	chanlist_flush_pending (serv);
+	return TRUE;
+}
+
+/**
+ * Places a data row into the gui GtkTreeView, if and only if the row matches
+ * the user and regex/search requirements.
+ */
+static void
+chanlist_place_row_in_gui (server *serv, chanlistrow *next_row, gboolean force)
+{
+	GtkTreeModel *model;
+
+	/* First, update the 'found' counter values */
+	serv->gui->chanlist_users_found_count += next_row->users;
+	serv->gui->chanlist_channels_found_count++;
+
+	if (serv->gui->chanlist_channels_shown_count == 1)
+		/* join & save buttons become live */
+		chanlist_update_buttons (serv);
+
+	if (next_row->users < serv->gui->chanlist_minusers)
+	{
+		serv->gui->chanlist_caption_is_stale = TRUE;
+		return;
+	}
+
+	if (next_row->users > serv->gui->chanlist_maxusers
+		 && serv->gui->chanlist_maxusers > 0)
+	{
+		serv->gui->chanlist_caption_is_stale = TRUE;
+		return;
+	}
+
+	if (GTK_ENTRY (serv->gui->chanlist_wild)->text[0])
+	{
+		/* Check what the user wants to match. If both buttons or _neither_
+		 * button is checked, look for match in both by default. 
+		 */
+		if (serv->gui->chanlist_match_wants_channel ==
+			 serv->gui->chanlist_match_wants_topic)
+		{
+			if (!chanlist_match (serv, GET_CHAN (next_row))
+				 && !chanlist_match (serv, next_row->topic))
+			{
+				serv->gui->chanlist_caption_is_stale = TRUE;
+				return;
+			}
+		}
+
+		else if (serv->gui->chanlist_match_wants_channel)
+		{
+			if (!chanlist_match (serv, GET_CHAN (next_row)))
+			{
+				serv->gui->chanlist_caption_is_stale = TRUE;
+				return;
+			}
+		}
+
+		else if (serv->gui->chanlist_match_wants_topic)
+		{
+			if (!chanlist_match (serv, next_row->topic))
+			{
+				serv->gui->chanlist_caption_is_stale = TRUE;
+				return;
+			}
+		}
+	}
+
+	if (force || serv->gui->chanlist_channels_shown_count < 20)
+	{
+		model = GET_MODEL (serv);
+		/* makes it appear fast :) */
+		custom_list_append (CUSTOM_LIST (model), next_row);
+		chanlist_update_caption (serv);
+	}
+	else
+		/* add it to GUI at the next update interval */
+		serv->gui->chanlist_pending_rows = g_slist_prepend (serv->gui->chanlist_pending_rows, next_row);
+
+	/* Update the 'shown' counter values */
+	serv->gui->chanlist_users_shown_count += next_row->users;
+	serv->gui->chanlist_channels_shown_count++;
+}
+
+/* Performs the LIST download from the IRC server. */
+
+static void
+chanlist_do_refresh (server *serv)
+{
+	if (serv->gui->chanlist_flash_tag)
+	{
+		g_source_remove (serv->gui->chanlist_flash_tag);
+		serv->gui->chanlist_flash_tag = 0;
+	}
+
+	if (!serv->connected)
+	{
+		fe_message (_("Not connected."), FE_MSG_ERROR);
+		return;
+	}
+
+	custom_list_clear ((CustomList *)GET_MODEL (serv));
+	gtk_widget_set_sensitive (serv->gui->chanlist_refresh, FALSE);
+
+	chanlist_data_free (serv);
+	chanlist_reset_counters (serv);
+
+	/* can we request a list with minusers arg? */
+	if (serv->use_listargs)
+	{
+		/* yes - it will download faster */
+		serv->p_list_channels (serv, "", serv->gui->chanlist_minusers);
+		/* don't allow the spin button below this value from now on */
+		serv->gui->chanlist_minusers_downloaded = serv->gui->chanlist_minusers;
+	}
+	else
+	{
+		/* download all, filter minusers locally only */
+		serv->p_list_channels (serv, "", 1);
+		serv->gui->chanlist_minusers_downloaded = 1;
+	}
+
+/*	gtk_spin_button_set_range ((GtkSpinButton *)serv->gui->chanlist_min_spin,
+										serv->gui->chanlist_minusers_downloaded, 999999);*/
+}
+
+static void
+chanlist_refresh (GtkWidget * wid, server *serv)
+{
+	chanlist_do_refresh (serv);
+}
+
+/**
+ * Fills the gui GtkTreeView with stored items from the GSList.
+ */
+static void
+chanlist_build_gui_list (server *serv)
+{
+	GSList *rows;
+
+	/* first check if the list is present */
+	if (serv->gui->chanlist_data_stored_rows == NULL)
+	{
+		/* start a download */
+		chanlist_do_refresh (serv);
+		return;
+	}
+
+	custom_list_clear ((CustomList *)GET_MODEL (serv));
+
+	/* discard pending rows FIXME: free the structs? */
+	g_slist_free (serv->gui->chanlist_pending_rows);
+	serv->gui->chanlist_pending_rows = NULL;
+
+	/* Reset the counters */
+	chanlist_reset_counters (serv);
+
+	/* Refill the list */
+	for (rows = serv->gui->chanlist_data_stored_rows; rows != NULL;
+		  rows = rows->next)
+	{
+		chanlist_place_row_in_gui (serv, rows->data, TRUE);
+	}
+
+	custom_list_resort ((CustomList *)GET_MODEL (serv));
+}
+
+/**
+ * Accepts incoming channel data from inbound.c, allocates new space for a
+ * chanlistrow, adds it to our linked list and calls chanlist_place_row_in_gui.
+ */
+void
+fe_add_chan_list (server *serv, char *chan, char *users, char *topic)
+{
+	chanlistrow *next_row;
+	int len = strlen (chan) + 1;
+
+	/* we allocate the struct and channel string in one go */
+	next_row = malloc (sizeof (chanlistrow) + len);
+	memcpy (((char *)next_row) + sizeof (chanlistrow), chan, len);
+	next_row->topic = strip_color (topic, -1, STRIP_ALL);
+	next_row->collation_key = g_utf8_collate_key (chan, len-1);
+	if (!(next_row->collation_key))
+		next_row->collation_key = g_strdup (chan);
+	next_row->users = atoi (users);
+
+	/* add this row to the data */
+	serv->gui->chanlist_data_stored_rows =
+		g_slist_prepend (serv->gui->chanlist_data_stored_rows, next_row);
+
+	/* _possibly_ add the row to the gui */
+	chanlist_place_row_in_gui (serv, next_row, FALSE);
+}
+
+void
+fe_chan_list_end (server *serv)
+{
+	/* download complete */
+	chanlist_flush_pending (serv);
+	gtk_widget_set_sensitive (serv->gui->chanlist_refresh, TRUE);
+	custom_list_resort ((CustomList *)GET_MODEL (serv));
+}
+
+static void
+chanlist_search_pressed (GtkButton * button, server *serv)
+{
+	chanlist_build_gui_list (serv);
+}
+
+static void
+chanlist_find_cb (GtkWidget * wid, server *serv)
+{
+#ifndef WIN32
+	/* recompile the regular expression. */
+	if (serv->gui->have_regex)
+	{
+		serv->gui->have_regex = 0;
+		regfree (&serv->gui->chanlist_match_regex);
+	}
+
+	if (regcomp (&serv->gui->chanlist_match_regex, GTK_ENTRY (wid)->text,
+					 REG_ICASE | REG_EXTENDED | REG_NOSUB) == 0)
+		serv->gui->have_regex = 1;
+#endif
+}
+
+static void
+chanlist_match_channel_button_toggled (GtkWidget * wid, server *serv)
+{
+	serv->gui->chanlist_match_wants_channel = GTK_TOGGLE_BUTTON (wid)->active;
+}
+
+static void
+chanlist_match_topic_button_toggled (GtkWidget * wid, server *serv)
+{
+	serv->gui->chanlist_match_wants_topic = GTK_TOGGLE_BUTTON (wid)->active;
+}
+
+static char *
+chanlist_get_selected (server *serv, gboolean get_topic)
+{
+	char *chan;
+	GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (serv->gui->chanlist_list));
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+
+	if (!gtk_tree_selection_get_selected (sel, &model, &iter))
+		return NULL;
+
+	gtk_tree_model_get (model, &iter, get_topic ? COL_TOPIC : COL_CHANNEL, &chan, -1);
+	return chan;
+}
+
+static void
+chanlist_join (GtkWidget * wid, server *serv)
+{
+	char tbuf[CHANLEN + 6];
+	char *chan = chanlist_get_selected (serv, FALSE);
+	if (chan)
+	{
+		if (serv->connected && (strcmp (chan, "*") != 0))
+		{
+			snprintf (tbuf, sizeof (tbuf), "join %s", chan);
+			handle_command (serv->server_session, tbuf, FALSE);
+		} else
+			gdk_beep ();
+		g_free (chan);
+	}
+}
+
+static void
+chanlist_filereq_done (server *serv, char *file)
+{
+	time_t t = time (0);
+	int fh, users;
+	char *chan, *topic;
+	char buf[1024];
+	GtkTreeModel *model = GET_MODEL (serv);
+	GtkTreeIter iter;
+
+	if (!file)
+		return;
+
+	fh = xchat_open_file (file, O_TRUNC | O_WRONLY | O_CREAT, 0600,
+								 XOF_DOMODE | XOF_FULLPATH);
+	if (fh == -1)
+		return;
+
+	snprintf (buf, sizeof buf, "XChat Channel List: %s - %s\n",
+				 serv->servername, ctime (&t));
+	write (fh, buf, strlen (buf));
+
+	if (gtk_tree_model_get_iter_first (model, &iter))
+	{
+		do
+		{
+			gtk_tree_model_get (model, &iter,
+									  COL_CHANNEL, &chan,
+									  COL_USERS, &users,
+									  COL_TOPIC, &topic, -1);
+			snprintf (buf, sizeof buf, "%-16s %-5d%s\n", chan, users, topic);
+			g_free (chan);
+			g_free (topic);
+			write (fh, buf, strlen (buf));
+		}
+		while (gtk_tree_model_iter_next (model, &iter));
+	}
+
+	close (fh);
+}
+
+static void
+chanlist_save (GtkWidget * wid, server *serv)
+{
+	GtkTreeIter iter;
+	GtkTreeModel *model = GET_MODEL (serv);
+
+	if (gtk_tree_model_get_iter_first (model, &iter))
+		gtkutil_file_req (_("Select an output filename"), chanlist_filereq_done,
+								serv, NULL, FRF_WRITE);
+}
+
+static gboolean
+chanlist_flash (server *serv)
+{
+	if (serv->gui->chanlist_refresh->state != GTK_STATE_ACTIVE)
+		gtk_widget_set_state (serv->gui->chanlist_refresh, GTK_STATE_ACTIVE);
+	else
+		gtk_widget_set_state (serv->gui->chanlist_refresh, GTK_STATE_PRELIGHT);
+
+	return TRUE;
+}
+
+static void
+chanlist_minusers (GtkSpinButton *wid, server *serv)
+{
+	serv->gui->chanlist_minusers = gtk_spin_button_get_value_as_int (wid);
+
+	if (serv->gui->chanlist_minusers < serv->gui->chanlist_minusers_downloaded)
+	{
+		if (serv->gui->chanlist_flash_tag == 0)
+			serv->gui->chanlist_flash_tag = g_timeout_add (500, (GSourceFunc)chanlist_flash, serv);
+	}
+	else
+	{
+		if (serv->gui->chanlist_flash_tag)
+		{
+			g_source_remove (serv->gui->chanlist_flash_tag);
+			serv->gui->chanlist_flash_tag = 0;
+		}
+	}
+}
+
+static void
+chanlist_maxusers (GtkSpinButton *wid, server *serv)
+{
+	serv->gui->chanlist_maxusers = gtk_spin_button_get_value_as_int (wid);
+}
+
+static void
+chanlist_dclick_cb (GtkTreeView *view, GtkTreePath *path,
+						  GtkTreeViewColumn *column, gpointer data)
+{
+	chanlist_join (0, (server *) data);	/* double clicked a row */
+}
+
+static void
+chanlist_menu_destroy (GtkWidget *menu, gpointer userdata)
+{
+	gtk_widget_destroy (menu);
+	g_object_unref (menu);
+}
+
+static void
+chanlist_copychannel (GtkWidget *item, server *serv)
+{
+	char *chan = chanlist_get_selected (serv, FALSE);
+	if (chan)
+	{
+		gtkutil_copy_to_clipboard (item, NULL, chan);
+		g_free (chan);
+	}
+}
+
+static void
+chanlist_copytopic (GtkWidget *item, server *serv)
+{
+	char *topic = chanlist_get_selected (serv, TRUE);
+	if (topic)
+	{
+		gtkutil_copy_to_clipboard (item, NULL, topic);
+		g_free (topic);
+	}
+}
+
+static gboolean
+chanlist_button_cb (GtkTreeView *tree, GdkEventButton *event, server *serv)
+{
+	GtkWidget *menu;
+	GtkTreeSelection *sel;
+	GtkTreePath *path;
+	char *chan;
+
+	if (event->button != 3)
+		return FALSE;
+
+	if (!gtk_tree_view_get_path_at_pos (tree, event->x, event->y, &path, 0, 0, 0))
+		return FALSE;
+
+	/* select what they right-clicked on */
+	sel = gtk_tree_view_get_selection (tree);
+	gtk_tree_selection_unselect_all (sel);
+	gtk_tree_selection_select_path (sel, path);
+	gtk_tree_path_free (path);
+
+	menu = gtk_menu_new ();
+	if (event->window)
+		gtk_menu_set_screen (GTK_MENU (menu), gdk_drawable_get_screen (event->window));
+	g_object_ref (menu);
+	g_object_ref_sink (menu);
+	g_object_unref (menu);
+	g_signal_connect (G_OBJECT (menu), "selection-done",
+							G_CALLBACK (chanlist_menu_destroy), NULL);
+	mg_create_icon_item (_("_Join Channel"), GTK_STOCK_JUMP_TO, menu,
+								chanlist_join, serv);
+	mg_create_icon_item (_("_Copy Channel Name"), GTK_STOCK_COPY, menu,
+								chanlist_copychannel, serv);
+	mg_create_icon_item (_("Copy _Topic Text"), GTK_STOCK_COPY, menu,
+								chanlist_copytopic, serv);
+
+	chan = chanlist_get_selected (serv, FALSE);
+	menu_addfavoritemenu (serv, menu, chan);
+	g_free (chan);
+
+	gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 0, event->time);
+
+	return TRUE;
+}
+
+static void
+chanlist_destroy_widget (GtkWidget *wid, server *serv)
+{
+	custom_list_clear ((CustomList *)GET_MODEL (serv));
+	chanlist_data_free (serv);
+
+	if (serv->gui->chanlist_flash_tag)
+	{
+		g_source_remove (serv->gui->chanlist_flash_tag);
+		serv->gui->chanlist_flash_tag = 0;
+	}
+
+	if (serv->gui->chanlist_tag)
+	{
+		g_source_remove (serv->gui->chanlist_tag);
+		serv->gui->chanlist_tag = 0;
+	}
+
+#ifndef WIN32
+	if (serv->gui->have_regex)
+	{
+		regfree (&serv->gui->chanlist_match_regex);
+		serv->gui->have_regex = 0;
+	}
+#endif
+}
+
+static void
+chanlist_closegui (GtkWidget *wid, server *serv)
+{
+	if (is_server (serv))
+		serv->gui->chanlist_window = NULL;
+}
+
+static void
+chanlist_add_column (GtkWidget *tree, int textcol, int size, char *title, gboolean right_justified)
+{
+	GtkCellRenderer *renderer;
+	GtkTreeViewColumn *col;
+
+	renderer = gtk_cell_renderer_text_new ();
+	if (right_justified)
+		g_object_set (G_OBJECT (renderer), "xalign", (gfloat) 1.0, NULL);
+	g_object_set (G_OBJECT (renderer), "ypad", (gint) 0, NULL);
+	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), -1, title,
+																renderer, "text", textcol, NULL);
+	gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1);
+
+	col = gtk_tree_view_get_column (GTK_TREE_VIEW (tree), textcol);
+	gtk_tree_view_column_set_sort_column_id (col, textcol);
+	gtk_tree_view_column_set_resizable (col, TRUE);
+	if (textcol != COL_TOPIC)
+	{
+		gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_FIXED);
+		gtk_tree_view_column_set_fixed_width (col, size);
+	}
+}
+
+static void
+chanlist_combo_cb (GtkWidget *combo, server *serv)
+{
+	serv->gui->chanlist_search_type = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
+}
+
+void
+chanlist_opengui (server *serv, int do_refresh)
+{
+	GtkWidget *vbox, *hbox, *table, *wid, *view;
+	char tbuf[256];
+	GtkListStore *store;
+
+	if (serv->gui->chanlist_window)
+	{
+		mg_bring_tofront (serv->gui->chanlist_window);
+		return;
+	}
+
+	snprintf (tbuf, sizeof tbuf, _("XChat: Channel List (%s)"),
+				 server_get_network (serv, TRUE));
+
+	serv->gui->chanlist_pending_rows = NULL;
+	serv->gui->chanlist_tag = 0;
+	serv->gui->chanlist_flash_tag = 0;
+	serv->gui->chanlist_data_stored_rows = NULL;
+
+	if (!serv->gui->chanlist_minusers)
+		serv->gui->chanlist_minusers = 5;
+
+	if (!serv->gui->chanlist_maxusers)
+		serv->gui->chanlist_maxusers = 9999;
+
+	serv->gui->chanlist_window =
+		mg_create_generic_tab ("ChanList", tbuf, FALSE, TRUE, chanlist_closegui,
+								serv, 640, 480, &vbox, serv);
+
+	gtk_container_set_border_width (GTK_CONTAINER (vbox), 6);
+	gtk_box_set_spacing (GTK_BOX (vbox), 12);
+
+	/* make a label to store the user/channel info */
+	wid = gtk_label_new (NULL);
+	gtk_box_pack_start (GTK_BOX (vbox), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+	serv->gui->chanlist_label = wid;
+
+	/* ============================================================= */
+
+	store = (GtkListStore *) custom_list_new();
+	view = gtkutil_treeview_new (vbox, GTK_TREE_MODEL (store), NULL, -1);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (view->parent),
+													 GTK_SHADOW_IN);
+	serv->gui->chanlist_list = view;
+
+	g_signal_connect (G_OBJECT (view), "row_activated",
+							G_CALLBACK (chanlist_dclick_cb), serv);
+	g_signal_connect (G_OBJECT (view), "button-press-event",
+							G_CALLBACK (chanlist_button_cb), serv);
+
+	chanlist_add_column (view, COL_CHANNEL, 96, _("Channel"), FALSE);
+	chanlist_add_column (view, COL_USERS,   50, _("Users"),   TRUE);
+	chanlist_add_column (view, COL_TOPIC,   50, _("Topic"),   FALSE);
+	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE);
+	/* this is a speed up, but no horizontal scrollbar :( */
+	/*gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (view), TRUE);*/
+	gtk_widget_show (view);
+
+	/* ============================================================= */
+
+	table = gtk_table_new (4, 4, FALSE);
+	gtk_table_set_col_spacings (GTK_TABLE (table), 12);
+	gtk_table_set_row_spacings (GTK_TABLE (table), 3);
+	gtk_box_pack_start (GTK_BOX (vbox), table, 0, 1, 0);
+	gtk_widget_show (table);
+
+	wid = gtkutil_button (NULL, GTK_STOCK_FIND, 0, chanlist_search_pressed, serv,
+								 _("_Search"));
+	serv->gui->chanlist_search = wid;
+	gtk_table_attach (GTK_TABLE (table), wid, 3, 4, 3, 4,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+
+	wid = gtkutil_button (NULL, GTK_STOCK_REFRESH, 0, chanlist_refresh, serv,
+								 _("_Download List"));
+	serv->gui->chanlist_refresh = wid;
+	gtk_table_attach (GTK_TABLE (table), wid, 3, 4, 2, 3,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+
+	wid = gtkutil_button (NULL, GTK_STOCK_SAVE_AS, 0, chanlist_save, serv,
+								 _("Save _List..."));
+	serv->gui->chanlist_savelist = wid;
+	gtk_table_attach (GTK_TABLE (table), wid, 3, 4, 1, 2,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+
+	wid = gtkutil_button (NULL, GTK_STOCK_JUMP_TO, 0, chanlist_join, serv,
+						 _("_Join Channel"));
+	serv->gui->chanlist_join = wid;
+	gtk_table_attach (GTK_TABLE (table), wid, 3, 4, 0, 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+
+	/* ============================================================= */
+
+	wid = gtk_label_new (_("Show only:"));
+	gtk_misc_set_alignment (GTK_MISC (wid), 0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), wid, 0, 1, 3, 4,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+	gtk_widget_show (wid);
+
+	hbox = gtk_hbox_new (0, 0);
+	gtk_box_set_spacing (GTK_BOX (hbox), 9);
+	gtk_table_attach (GTK_TABLE (table), hbox, 1, 2, 3, 4,
+							GTK_FILL, GTK_FILL, 0, 0);
+	gtk_widget_show (hbox);
+
+	wid = gtk_label_new (_("channels with"));
+	gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+
+	wid = gtk_spin_button_new_with_range (1, 999999, 1);
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (wid),
+										serv->gui->chanlist_minusers);
+	g_signal_connect (G_OBJECT (wid), "value_changed",
+							G_CALLBACK (chanlist_minusers), serv);
+	gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+	serv->gui->chanlist_min_spin = wid;
+
+	wid = gtk_label_new (_("to"));
+	gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+
+	wid = gtk_spin_button_new_with_range (1, 999999, 1);
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (wid),
+										serv->gui->chanlist_maxusers);
+	g_signal_connect (G_OBJECT (wid), "value_changed",
+							G_CALLBACK (chanlist_maxusers), serv);
+	gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+
+	wid = gtk_label_new (_("users."));
+	gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+
+	/* ============================================================= */
+
+	wid = gtk_label_new (_("Look in:"));
+	gtk_misc_set_alignment (GTK_MISC (wid), 0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), wid, 0, 1, 2, 3,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+	gtk_widget_show (wid);
+
+	hbox = gtk_hbox_new (0, 0);
+	gtk_box_set_spacing (GTK_BOX (hbox), 12);
+	gtk_table_attach (GTK_TABLE (table), hbox, 1, 2, 2, 3,
+							GTK_FILL, GTK_FILL, 0, 0);
+	gtk_widget_show (hbox);
+
+	wid = gtk_check_button_new_with_label (_("Channel name"));
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), TRUE);
+	gtk_signal_connect (GTK_OBJECT (wid), "toggled",
+							  GTK_SIGNAL_FUNC
+							  (chanlist_match_channel_button_toggled), serv);
+	gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+
+	wid = gtk_check_button_new_with_label (_("Topic"));
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), TRUE);
+	gtk_signal_connect (GTK_OBJECT (wid), "toggled",
+							  GTK_SIGNAL_FUNC (chanlist_match_topic_button_toggled),
+							  serv);
+	gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+
+	serv->gui->chanlist_match_wants_channel = 1;
+	serv->gui->chanlist_match_wants_topic = 1;
+
+	/* ============================================================= */
+
+	wid = gtk_label_new (_("Search type:"));
+	gtk_misc_set_alignment (GTK_MISC (wid), 0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), wid, 0, 1, 1, 2,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+	gtk_widget_show (wid);
+
+	wid = gtk_combo_box_new_text ();
+	gtk_combo_box_append_text (GTK_COMBO_BOX (wid), _("Simple Search"));
+	gtk_combo_box_append_text (GTK_COMBO_BOX (wid), _("Pattern Match (Wildcards)"));
+#ifndef WIN32
+	gtk_combo_box_append_text (GTK_COMBO_BOX (wid), _("Regular Expression"));
+#endif
+	gtk_combo_box_set_active (GTK_COMBO_BOX (wid), serv->gui->chanlist_search_type);
+	gtk_table_attach (GTK_TABLE (table), wid, 1, 2, 1, 2,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+	g_signal_connect (G_OBJECT (wid), "changed",
+							G_CALLBACK (chanlist_combo_cb), serv);
+	gtk_widget_show (wid);
+
+	/* ============================================================= */
+
+	wid = gtk_label_new (_("Find:"));
+	gtk_misc_set_alignment (GTK_MISC (wid), 0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), wid, 0, 1, 0, 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+	gtk_widget_show (wid);
+
+	wid = gtk_entry_new_with_max_length (255);
+	gtk_signal_connect (GTK_OBJECT (wid), "changed",
+							  GTK_SIGNAL_FUNC (chanlist_find_cb), serv);
+	gtk_signal_connect (GTK_OBJECT (wid), "activate",
+							  GTK_SIGNAL_FUNC (chanlist_search_pressed),
+							  (gpointer) serv);
+	gtk_table_attach (GTK_TABLE (table), wid, 1, 2, 0, 1,
+							GTK_EXPAND | GTK_FILL, 0, 0, 0);
+	gtk_widget_show (wid);
+	serv->gui->chanlist_wild = wid;
+
+	chanlist_find_cb (wid, serv);
+
+	/* ============================================================= */
+
+	wid = gtk_vseparator_new ();
+	gtk_table_attach (GTK_TABLE (table), wid, 2, 3, 0, 5,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+	gtk_widget_show (wid);
+
+	g_signal_connect (G_OBJECT (serv->gui->chanlist_window), "destroy",
+							G_CALLBACK (chanlist_destroy_widget), serv);
+
+	/* reset the counters. */
+	chanlist_reset_counters (serv);
+
+	serv->gui->chanlist_tag = g_timeout_add (250, (GSourceFunc)chanlist_timeout, serv);
+
+	if (do_refresh)
+		chanlist_do_refresh (serv);
+
+	chanlist_update_buttons (serv);
+	gtk_widget_show (serv->gui->chanlist_window);
+	gtk_widget_grab_focus (serv->gui->chanlist_refresh);
+}
diff --git a/src/fe-gtk/chanlist.h b/src/fe-gtk/chanlist.h
new file mode 100644
index 00000000..19a8b25e
--- /dev/null
+++ b/src/fe-gtk/chanlist.h
@@ -0,0 +1 @@
+void chanlist_opengui (server *serv, int do_refresh);
diff --git a/src/fe-gtk/chanview-tabs.c b/src/fe-gtk/chanview-tabs.c
new file mode 100644
index 00000000..8e3da8e6
--- /dev/null
+++ b/src/fe-gtk/chanview-tabs.c
@@ -0,0 +1,779 @@
+/* file included in chanview.c */
+
+typedef struct
+{
+	GtkWidget *outer;	/* outer box */
+	GtkWidget *inner;	/* inner box */
+	GtkWidget *b1;		/* button1 */
+	GtkWidget *b2;		/* button2 */
+} tabview;
+
+static void chanview_populate (chanview *cv);
+
+/* ignore "toggled" signal? */
+static int ignore_toggle = FALSE;
+static int tab_left_is_moving = 0;
+static int tab_right_is_moving = 0;
+
+/* userdata for gobjects used here:
+ *
+ * tab (togglebuttons inside boxes):
+ *   "u" userdata passed to tab-focus callback function (sess)
+ *   "c" the tab's (chan *)
+ *
+ * box (family box)
+ *   "f" family
+ *
+ */
+
+/*
+ * GtkViewports request at least as much space as their children do.
+ * If we don't intervene here, the GtkViewport will be granted its
+ * request, even at the expense of resizing the top-level window.
+ */
+static void
+cv_tabs_sizerequest (GtkWidget *viewport, GtkRequisition *requisition, chanview *cv)
+{
+	if (!cv->vertical)
+		requisition->width = 1;
+	else
+		requisition->height = 1;
+}
+
+static void
+cv_tabs_sizealloc (GtkWidget *widget, GtkAllocation *allocation, chanview *cv)
+{
+	GtkAdjustment *adj;
+	GtkWidget *inner;
+	gint viewport_size;
+
+	inner = ((tabview *)cv)->inner;
+
+	if (cv->vertical)
+	{
+		adj = gtk_viewport_get_vadjustment (GTK_VIEWPORT (inner->parent));
+		gdk_window_get_geometry (inner->parent->window, 0, 0, 0, &viewport_size, 0);
+	} else
+	{
+		adj = gtk_viewport_get_hadjustment (GTK_VIEWPORT (inner->parent));
+		gdk_window_get_geometry (inner->parent->window, 0, 0, &viewport_size, 0, 0);
+	}
+
+	if (adj->upper <= viewport_size)
+	{
+		gtk_widget_hide (((tabview *)cv)->b1);
+		gtk_widget_hide (((tabview *)cv)->b2);
+	} else
+	{
+		gtk_widget_show (((tabview *)cv)->b1);
+		gtk_widget_show (((tabview *)cv)->b2);
+	}
+}
+
+static gint
+tab_search_offset (GtkWidget *inner, gint start_offset,
+				   gboolean forward, gboolean vertical)
+{
+	GList *boxes;
+	GList *tabs;
+	GtkWidget *box;
+	GtkWidget *button;
+	gint found;
+
+	boxes = GTK_BOX (inner)->children;
+	if (!forward && boxes)
+		boxes = g_list_last (boxes);
+
+	while (boxes)
+	{
+		box = ((GtkBoxChild *)boxes->data)->widget;
+		boxes = (forward ? boxes->next : boxes->prev);
+
+		tabs = GTK_BOX (box)->children;
+		if (!forward && tabs)
+			tabs = g_list_last (tabs);
+
+		while (tabs)
+		{
+			button = ((GtkBoxChild *)tabs->data)->widget;
+			tabs = (forward ? tabs->next : tabs->prev);
+
+			if (!GTK_IS_TOGGLE_BUTTON (button))
+				continue;
+
+			found = (vertical ? button->allocation.y : button->allocation.x);
+			if ((forward && found > start_offset) ||
+				(!forward && found < start_offset))
+				return found;
+		}
+	}
+
+	return 0;
+}
+
+static void
+tab_scroll_left_up_clicked (GtkWidget *widget, chanview *cv)
+{
+	GtkAdjustment *adj;
+	gint viewport_size;
+	gfloat new_value;
+	GtkWidget *inner;
+	gfloat i;
+
+	inner = ((tabview *)cv)->inner;
+
+	if (cv->vertical)
+	{
+		adj = gtk_viewport_get_vadjustment (GTK_VIEWPORT (inner->parent));
+		gdk_window_get_geometry (inner->parent->window, 0, 0, 0, &viewport_size, 0);
+	} else
+	{
+		adj = gtk_viewport_get_hadjustment (GTK_VIEWPORT (inner->parent));
+		gdk_window_get_geometry (inner->parent->window, 0, 0, &viewport_size, 0, 0);
+	}
+
+	new_value = tab_search_offset (inner, adj->value, 0, cv->vertical);
+
+	if (new_value + viewport_size > adj->upper)
+		new_value = adj->upper - viewport_size;
+
+	if (!tab_left_is_moving)
+	{
+		tab_left_is_moving = 1;
+
+		for (i = adj->value; ((i > new_value) && (tab_left_is_moving)); i -= 0.1)
+		{
+			gtk_adjustment_set_value (adj, i);
+			while (g_main_pending ())
+				g_main_iteration (TRUE);
+		}
+
+		gtk_adjustment_set_value (adj, new_value);
+
+		tab_left_is_moving = 0;		/* hSP: set to false in case we didnt get stopped (the normal case) */
+	}
+	else
+	{
+		tab_left_is_moving = 0;		/* hSP: jump directly to next element if user is clicking faster than we can scroll.. */
+	}
+}
+
+static void
+tab_scroll_right_down_clicked (GtkWidget *widget, chanview *cv)
+{
+	GtkAdjustment *adj;
+	gint viewport_size;
+	gfloat new_value;
+	GtkWidget *inner;
+	gfloat i;
+
+	inner = ((tabview *)cv)->inner;
+
+	if (cv->vertical)
+	{
+		adj = gtk_viewport_get_vadjustment (GTK_VIEWPORT (inner->parent));
+		gdk_window_get_geometry (inner->parent->window, 0, 0, 0, &viewport_size, 0);
+	} else
+	{
+		adj = gtk_viewport_get_hadjustment (GTK_VIEWPORT (inner->parent));
+		gdk_window_get_geometry (inner->parent->window, 0, 0, &viewport_size, 0, 0);
+	}
+
+	new_value = tab_search_offset (inner, adj->value, 1, cv->vertical);
+
+	if (new_value == 0 || new_value + viewport_size > adj->upper)
+		new_value = adj->upper - viewport_size;
+
+	if (!tab_right_is_moving)
+	{
+		tab_right_is_moving = 1;
+
+		for (i = adj->value; ((i < new_value) && (tab_right_is_moving)); i += 0.1)
+		{
+			gtk_adjustment_set_value (adj, i);
+			while (g_main_pending ())
+				g_main_iteration (TRUE);
+		}
+
+		gtk_adjustment_set_value (adj, new_value);
+
+		tab_right_is_moving = 0;		/* hSP: set to false in case we didnt get stopped (the normal case) */
+	}
+	else
+	{
+		tab_right_is_moving = 0;		/* hSP: jump directly to next element if user is clicking faster than we can scroll.. */
+	}
+}
+
+static gboolean
+tab_scroll_cb (GtkWidget *widget, GdkEventScroll *event, gpointer cv)
+{
+	/* mouse wheel scrolling */
+	if (event->direction == GDK_SCROLL_UP)
+		tab_scroll_left_up_clicked (widget, cv);
+	else if (event->direction == GDK_SCROLL_DOWN)
+		tab_scroll_right_down_clicked (widget, cv);
+
+	return FALSE;
+}
+
+static void
+cv_tabs_xclick_cb (GtkWidget *button, chanview *cv)
+{
+	cv->cb_xbutton (cv, cv->focused, cv->focused->tag, cv->focused->userdata);
+}
+
+/* make a Scroll (arrow) button */
+
+static GtkWidget *
+make_sbutton (GtkArrowType type, void *click_cb, void *userdata)
+{
+	GtkWidget *button, *arrow;
+
+	button = gtk_button_new ();
+	arrow = gtk_arrow_new (type, GTK_SHADOW_NONE);
+	gtk_container_add (GTK_CONTAINER (button), arrow);
+	gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+	g_signal_connect (G_OBJECT (button), "clicked",
+							G_CALLBACK (click_cb), userdata);
+	g_signal_connect (G_OBJECT (button), "scroll_event",
+							G_CALLBACK (tab_scroll_cb), userdata);
+	gtk_widget_show (arrow);
+
+	return button;
+}
+
+static void
+cv_tabs_init (chanview *cv)
+{
+	GtkWidget *box, *hbox = NULL;
+	GtkWidget *viewport;
+	GtkWidget *outer;
+	GtkWidget *button;
+
+	if (cv->vertical)
+		outer = gtk_vbox_new (0, 0);
+	else
+		outer = gtk_hbox_new (0, 0);
+	((tabview *)cv)->outer = outer;
+	g_signal_connect (G_OBJECT (outer), "size_allocate",
+							G_CALLBACK (cv_tabs_sizealloc), cv);
+/*	gtk_container_set_border_width (GTK_CONTAINER (outer), 2);*/
+	gtk_widget_show (outer);
+
+	viewport = gtk_viewport_new (0, 0);
+	gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
+	g_signal_connect (G_OBJECT (viewport), "size_request",
+							G_CALLBACK (cv_tabs_sizerequest), cv);
+	g_signal_connect (G_OBJECT (viewport), "scroll_event",
+							G_CALLBACK (tab_scroll_cb), cv);
+	gtk_box_pack_start (GTK_BOX (outer), viewport, 1, 1, 0);
+	gtk_widget_show (viewport);
+
+	if (cv->vertical)
+		box = gtk_vbox_new (FALSE, 0);
+	else
+		box = gtk_hbox_new (FALSE, 0);
+	((tabview *)cv)->inner = box;
+	gtk_container_add (GTK_CONTAINER (viewport), box);
+	gtk_widget_show (box);
+
+	/* if vertical, the buttons can be side by side */
+	if (cv->vertical)
+	{
+		hbox = gtk_hbox_new (FALSE, 0);
+		gtk_box_pack_start (GTK_BOX (outer), hbox, 0, 0, 0);
+		gtk_widget_show (hbox);
+	}
+
+	/* make the Scroll buttons */
+	((tabview *)cv)->b2 = make_sbutton (cv->vertical ?
+													GTK_ARROW_UP : GTK_ARROW_LEFT,
+													tab_scroll_left_up_clicked,
+													cv);
+
+	((tabview *)cv)->b1 = make_sbutton (cv->vertical ?
+													GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
+													tab_scroll_right_down_clicked,
+													cv);
+
+	if (hbox)
+	{
+		gtk_container_add (GTK_CONTAINER (hbox), ((tabview *)cv)->b2);
+		gtk_container_add (GTK_CONTAINER (hbox), ((tabview *)cv)->b1);
+	} else
+	{
+		gtk_box_pack_start (GTK_BOX (outer), ((tabview *)cv)->b2, 0, 0, 0);
+		gtk_box_pack_start (GTK_BOX (outer), ((tabview *)cv)->b1, 0, 0, 0);
+	}
+
+	button = gtkutil_button (outer, GTK_STOCK_CLOSE, NULL, cv_tabs_xclick_cb,
+									 cv, 0);
+	gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+	GTK_WIDGET_UNSET_FLAGS (button, GTK_CAN_FOCUS);
+
+	gtk_container_add (GTK_CONTAINER (cv->box), outer);
+}
+
+static void
+cv_tabs_postinit (chanview *cv)
+{
+}
+
+static void
+tab_add_sorted (chanview *cv, GtkWidget *box, GtkWidget *tab, chan *ch)
+{
+	GList *list;
+	GtkBoxChild *child;
+	int i = 0;
+	void *b;
+
+	if (!cv->sorted)
+	{
+		gtk_box_pack_start (GTK_BOX (box), tab, 0, 0, 0);
+		gtk_widget_show (tab);
+		return;
+	}
+
+	/* sorting TODO:
+    *   - move tab if renamed (dialogs) */
+
+	/* userdata, passed to mg_tabs_compare() */
+	b = ch->userdata;
+
+	list = GTK_BOX (box)->children;
+	while (list)
+	{
+		child = list->data;
+		if (!GTK_IS_SEPARATOR (child->widget))
+		{
+			void *a = g_object_get_data (G_OBJECT (child->widget), "u");
+
+			if (ch->tag == 0 && cv->cb_compare (a, b) > 0)
+			{
+				gtk_box_pack_start (GTK_BOX (box), tab, 0, 0, 0);
+				gtk_box_reorder_child (GTK_BOX (box), tab, i);
+				gtk_widget_show (tab);
+				return;
+			}
+		}
+		i++;
+		list = list->next;
+	}
+
+	/* append */
+	gtk_box_pack_start (GTK_BOX (box), tab, 0, 0, 0);
+	gtk_box_reorder_child (GTK_BOX (box), tab, i);
+	gtk_widget_show (tab);
+}
+
+/* remove empty boxes and separators */
+
+static void
+cv_tabs_prune (chanview *cv)
+{
+	GList *boxes, *children;
+	GtkWidget *box, *inner;
+	GtkBoxChild *child;
+	int empty;
+
+	inner = ((tabview *)cv)->inner;
+	boxes = GTK_BOX (inner)->children;
+	while (boxes)
+	{
+		child = boxes->data;
+		box = child->widget;
+		boxes = boxes->next;
+
+		/* check if the box is empty (except a vseperator) */
+		empty = TRUE;
+		children = GTK_BOX (box)->children;
+		while (children)
+		{
+			if (!GTK_IS_SEPARATOR (((GtkBoxChild *)children->data)->widget))
+			{
+				empty = FALSE;
+				break;
+			}
+			children = children->next;
+		}
+
+		if (empty)
+			gtk_widget_destroy (box);
+	}
+}
+
+static void
+tab_add_real (chanview *cv, GtkWidget *tab, chan *ch)
+{
+	GList *boxes, *children;
+	GtkWidget *sep, *box, *inner;
+	GtkBoxChild *child;
+	int empty;
+
+	inner = ((tabview *)cv)->inner;
+	/* see if a family for this tab already exists */
+	boxes = GTK_BOX (inner)->children;
+	while (boxes)
+	{
+		child = boxes->data;
+		box = child->widget;
+
+		if (g_object_get_data (G_OBJECT (box), "f") == ch->family)
+		{
+			tab_add_sorted (cv, box, tab, ch);
+			gtk_widget_queue_resize (inner->parent);
+			return;
+		}
+
+		boxes = boxes->next;
+
+		/* check if the box is empty (except a vseperator) */
+		empty = TRUE;
+		children = GTK_BOX (box)->children;
+		while (children)
+		{
+			if (!GTK_IS_SEPARATOR (((GtkBoxChild *)children->data)->widget))
+			{
+				empty = FALSE;
+				break;
+			}
+			children = children->next;
+		}
+
+		if (empty)
+			gtk_widget_destroy (box);
+	}
+
+	/* create a new family box */
+	if (cv->vertical)
+	{
+		/* vertical */
+		box = gtk_vbox_new (FALSE, 0);
+		sep = gtk_hseparator_new ();
+	} else
+	{
+		/* horiz */
+		box = gtk_hbox_new (FALSE, 0);
+		sep = gtk_vseparator_new ();
+	}
+
+	gtk_box_pack_end (GTK_BOX (box), sep, 0, 0, 4);
+	gtk_widget_show (sep);
+	gtk_box_pack_start (GTK_BOX (inner), box, 0, 0, 0);
+	g_object_set_data (G_OBJECT (box), "f", ch->family);
+	gtk_box_pack_start (GTK_BOX (box), tab, 0, 0, 0);
+	gtk_widget_show (tab);
+	gtk_widget_show (box);
+	gtk_widget_queue_resize (inner->parent);
+}
+
+static gboolean
+tab_ignore_cb (GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
+{
+	return TRUE;
+}
+
+/* called when a tab is clicked (button down) */
+
+static void
+tab_pressed_cb (GtkToggleButton *tab, chan *ch)
+{
+	chan *old_tab;
+	int is_switching = TRUE;
+	chanview *cv = ch->cv;
+
+	ignore_toggle = TRUE;
+	/* de-activate the old tab */
+	old_tab = cv->focused;
+	if (old_tab && old_tab->impl)
+	{
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (old_tab->impl), FALSE);
+		if (old_tab == ch)
+			is_switching = FALSE;
+	}
+	gtk_toggle_button_set_active (tab, TRUE);
+	ignore_toggle = FALSE;
+	cv->focused = ch;
+
+	if (/*tab->active*/is_switching)
+		/* call the focus callback */
+		cv->cb_focus (cv, ch, ch->tag, ch->userdata);
+}
+
+/* called for keyboard tab toggles only */
+static void
+tab_toggled_cb (GtkToggleButton *tab, chan *ch)
+{
+	if (ignore_toggle)
+		return;
+
+	/* activated a tab via keyboard */
+	tab_pressed_cb (tab, ch);
+}
+
+static gboolean
+tab_click_cb (GtkWidget *wid, GdkEventButton *event, chan *ch)
+{
+	return ch->cv->cb_contextmenu (ch->cv, ch, ch->tag, ch->userdata, event);
+}
+
+static void *
+cv_tabs_add (chanview *cv, chan *ch, char *name, GtkTreeIter *parent)
+{
+	GtkWidget *but;
+
+	but = gtk_toggle_button_new_with_label (name);
+	gtk_widget_set_name (but, "xchat-tab");
+	g_object_set_data (G_OBJECT (but), "c", ch);
+	/* used to trap right-clicks */
+	g_signal_connect (G_OBJECT (but), "button_press_event",
+						 	G_CALLBACK (tab_click_cb), ch);
+	/* avoid prelights */
+	g_signal_connect (G_OBJECT (but), "enter_notify_event",
+						 	G_CALLBACK (tab_ignore_cb), NULL);
+	g_signal_connect (G_OBJECT (but), "leave_notify_event",
+						 	G_CALLBACK (tab_ignore_cb), NULL);
+	g_signal_connect (G_OBJECT (but), "pressed",
+							G_CALLBACK (tab_pressed_cb), ch);
+	/* for keyboard */
+	g_signal_connect (G_OBJECT (but), "toggled",
+						 	G_CALLBACK (tab_toggled_cb), ch);
+	g_object_set_data (G_OBJECT (but), "u", ch->userdata);
+
+	tab_add_real (cv, but, ch);
+
+	return but;
+}
+
+/* traverse all the family boxes of tabs 
+ *
+ * A "group" is basically:
+ * GtkV/HBox
+ * `-GtkViewPort
+ *   `-GtkV/HBox (inner box)
+ *     `- GtkBox (family box)
+ *        `- GtkToggleButton
+ *        `- GtkToggleButton
+ *        `- ...
+ *     `- GtkBox
+ *        `- GtkToggleButton
+ *        `- GtkToggleButton
+ *        `- ...
+ *     `- ...
+ *
+ * */
+
+static int
+tab_group_for_each_tab (chanview *cv,
+								int (*callback) (GtkWidget *tab, int num, int usernum),
+								int usernum)
+{
+	GList *tabs;
+	GList *boxes;
+	GtkBoxChild *child;
+	GtkBox *innerbox;
+	int i;
+
+	innerbox = (GtkBox *) ((tabview *)cv)->inner;
+	boxes = innerbox->children;
+	i = 0;
+	while (boxes)
+	{
+		child = boxes->data;
+		tabs = GTK_BOX (child->widget)->children;
+
+		while (tabs)
+		{
+			child = tabs->data;
+
+			if (!GTK_IS_SEPARATOR (child->widget))
+			{
+				if (callback (child->widget, i, usernum) != -1)
+					return i;
+				i++;
+			}
+			tabs = tabs->next;
+		}
+
+		boxes = boxes->next;
+	}
+
+	return i;
+}
+
+static int
+tab_check_focus_cb (GtkWidget *tab, int num, int unused)
+{
+	if (GTK_TOGGLE_BUTTON (tab)->active)
+		return num;
+
+	return -1;
+}
+
+/* returns the currently focused tab number */
+
+static int
+tab_group_get_cur_page (chanview *cv)
+{
+	return tab_group_for_each_tab (cv, tab_check_focus_cb, 0);
+}
+
+static void
+cv_tabs_focus (chan *ch)
+{
+	if (ch->impl)
+	/* focus the new one (tab_pressed_cb defocuses the old one) */
+		tab_pressed_cb (GTK_TOGGLE_BUTTON (ch->impl), ch);
+}
+
+static int
+tab_focus_num_cb (GtkWidget *tab, int num, int want)
+{
+	if (num == want)
+	{
+		cv_tabs_focus (g_object_get_data (G_OBJECT (tab), "c"));
+		return 1;
+	}
+
+	return -1;
+}
+
+static void
+cv_tabs_change_orientation (chanview *cv)
+{
+	/* cleanup the old one */
+	if (cv->func_cleanup)
+		cv->func_cleanup (cv);
+
+	/* now rebuild a new tabbar or tree */
+	cv->func_init (cv);
+	chanview_populate (cv);
+}
+
+/* switch to the tab number specified */
+
+static void
+cv_tabs_move_focus (chanview *cv, gboolean relative, int num)
+{
+	int i, max;
+
+	if (relative)
+	{
+		max = cv->size;
+		i = tab_group_get_cur_page (cv) + num;
+		/* make it wrap around at both ends */
+		if (i < 0)
+			i = max - 1;
+		if (i >= max)
+			i = 0;
+		tab_group_for_each_tab (cv, tab_focus_num_cb, i);
+		return;
+	}
+
+	tab_group_for_each_tab (cv, tab_focus_num_cb, num);
+}
+
+static void
+cv_tabs_remove (chan *ch)
+{
+	gtk_widget_destroy (ch->impl);
+	ch->impl = NULL;
+
+	cv_tabs_prune (ch->cv);
+}
+
+static void
+cv_tabs_move (chan *ch, int delta)
+{
+	int i, pos = 0;
+	GList *list;
+	GtkWidget *parent = ((GtkWidget *)ch->impl)->parent;
+
+	i = 0;
+	for (list = GTK_BOX (parent)->children; list; list = list->next)
+	{
+		GtkBoxChild *child_entry;
+
+		child_entry = list->data;
+		if (child_entry->widget == ch->impl)
+			pos = i;
+		i++;
+	}
+
+	pos = (pos - delta) % i;
+	gtk_box_reorder_child (GTK_BOX (parent), ch->impl, pos);
+}
+
+static void
+cv_tabs_move_family (chan *ch, int delta)
+{
+	int i, pos = 0;
+	GList *list;
+	GtkWidget *box = NULL;
+
+	/* find position of tab's family */
+	i = 0;
+	for (list = GTK_BOX (((tabview *)ch->cv)->inner)->children; list; list = list->next)
+	{
+		GtkBoxChild *child_entry;
+		void *fam;
+
+		child_entry = list->data;
+		fam = g_object_get_data (G_OBJECT (child_entry->widget), "f");
+		if (fam == ch->family)
+		{
+			box = child_entry->widget;
+			pos = i;
+		}
+		i++;
+	}
+
+	pos = (pos - delta) % i;
+	gtk_box_reorder_child (GTK_BOX (box->parent), box, pos);
+}
+
+static void
+cv_tabs_cleanup (chanview *cv)
+{
+	if (cv->box)
+		gtk_widget_destroy (((tabview *)cv)->outer);
+}
+
+static void
+cv_tabs_set_color (chan *ch, PangoAttrList *list)
+{
+	gtk_label_set_attributes (GTK_LABEL (GTK_BIN (ch->impl)->child), list);
+}
+
+static void
+cv_tabs_rename (chan *ch, char *name)
+{
+	PangoAttrList *attr;
+	GtkWidget *tab = ch->impl;
+
+	attr = gtk_label_get_attributes (GTK_LABEL (GTK_BIN (tab)->child));
+	if (attr)
+		pango_attr_list_ref (attr);
+
+	gtk_button_set_label (GTK_BUTTON (tab), name);
+	gtk_widget_queue_resize (tab->parent->parent->parent);
+
+	if (attr)
+	{
+		gtk_label_set_attributes (GTK_LABEL (GTK_BIN (tab)->child), attr);
+		pango_attr_list_unref (attr);
+	}
+}
+
+static gboolean
+cv_tabs_is_collapsed (chan *ch)
+{
+	return FALSE;
+}
+
+static chan *
+cv_tabs_get_parent (chan *ch)
+{
+	return NULL;
+}
diff --git a/src/fe-gtk/chanview-tree.c b/src/fe-gtk/chanview-tree.c
new file mode 100644
index 00000000..5373f21b
--- /dev/null
+++ b/src/fe-gtk/chanview-tree.c
@@ -0,0 +1,364 @@
+/* file included in chanview.c */
+
+typedef struct
+{
+	GtkTreeView *tree;
+	GtkWidget *scrollw;	/* scrolledWindow */
+} treeview;
+
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "fe-gtk.h"
+#include "maingui.h"
+
+#include <gdk/gdk.h>
+#include <gtk/gtktreeview.h>
+
+static void 	/* row-activated, when a row is double clicked */
+cv_tree_activated_cb (GtkTreeView *view, GtkTreePath *path,
+							 GtkTreeViewColumn *column, gpointer data)
+{
+	if (gtk_tree_view_row_expanded (view, path))
+		gtk_tree_view_collapse_row (view, path);
+	else
+		gtk_tree_view_expand_row (view, path, FALSE);
+}
+
+static void		/* row selected callback */
+cv_tree_sel_cb (GtkTreeSelection *sel, chanview *cv)
+{
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	chan *ch;
+
+	if (gtk_tree_selection_get_selected (sel, &model, &iter))
+	{
+		gtk_tree_model_get (model, &iter, COL_CHAN, &ch, -1);
+
+		cv->focused = ch;
+		cv->cb_focus (cv, ch, ch->tag, ch->userdata);
+	}
+}
+
+static gboolean
+cv_tree_click_cb (GtkTreeView *tree, GdkEventButton *event, chanview *cv)
+{
+	chan *ch;
+	GtkTreeSelection *sel;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+	int ret = FALSE;
+
+	if (event->button != 3 && event->state == 0)
+		return FALSE;
+
+	sel = gtk_tree_view_get_selection (tree);
+	if (gtk_tree_view_get_path_at_pos (tree, event->x, event->y, &path, 0, 0, 0))
+	{
+		if (event->button == 2)
+		{
+			gtk_tree_selection_unselect_all (sel);
+			gtk_tree_selection_select_path (sel, path);
+		}
+		if (gtk_tree_model_get_iter (GTK_TREE_MODEL (cv->store), &iter, path))
+		{
+			gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &iter, COL_CHAN, &ch, -1);
+			ret = cv->cb_contextmenu (cv, ch, ch->tag, ch->userdata, event);
+		}
+		gtk_tree_path_free (path);
+	}
+	return ret;
+}
+
+static void
+cv_tree_init (chanview *cv)
+{
+	GtkWidget *view, *win;
+	GtkCellRenderer *renderer;
+	static const GtkTargetEntry dnd_src_target[] =
+	{
+		{"XCHAT_CHANVIEW", GTK_TARGET_SAME_APP, 75 }
+	};
+	static const GtkTargetEntry dnd_dest_target[] =
+	{
+		{"XCHAT_USERLIST", GTK_TARGET_SAME_APP, 75 }
+	};
+
+	win = gtk_scrolled_window_new (0, 0);
+	/*gtk_container_set_border_width (GTK_CONTAINER (win), 1);*/
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (win),
+													 GTK_SHADOW_IN);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (win),
+											  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+	gtk_container_add (GTK_CONTAINER (cv->box), win);
+	gtk_widget_show (win);
+
+	view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (cv->store));
+	gtk_widget_set_name (view, "xchat-tree");
+	if (cv->style)
+		gtk_widget_set_style (view, cv->style);
+	/*gtk_widget_modify_base (view, GTK_STATE_NORMAL, &colors[COL_BG]);*/
+	GTK_WIDGET_UNSET_FLAGS (view, GTK_CAN_FOCUS);
+	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE);
+#if GTK_CHECK_VERSION(2,10,0)
+	if (!(prefs.gui_tweaks & 8))
+		gtk_tree_view_set_enable_tree_lines (GTK_TREE_VIEW (view), TRUE);
+#endif
+	gtk_container_add (GTK_CONTAINER (win), view);
+
+	/* icon column */
+	if (cv->use_icons)
+	{
+		renderer = gtk_cell_renderer_pixbuf_new ();
+		if (prefs.gui_tweaks & 32)
+			g_object_set (G_OBJECT (renderer), "ypad", 0, NULL);
+		gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view),
+																	-1, NULL, renderer,
+																	"pixbuf", COL_PIXBUF, NULL);
+	}
+
+	/* main column */
+	renderer = gtk_cell_renderer_text_new ();
+	if (prefs.gui_tweaks & 32)
+		g_object_set (G_OBJECT (renderer), "ypad", 0, NULL);
+	gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1);
+	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view),
+																-1, NULL, renderer,
+									"text", COL_NAME, "attributes", COL_ATTR, NULL);
+
+	g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (view))),
+							"changed", G_CALLBACK (cv_tree_sel_cb), cv);
+	g_signal_connect (G_OBJECT (view), "button-press-event",
+							G_CALLBACK (cv_tree_click_cb), cv);
+	g_signal_connect (G_OBJECT (view), "row-activated",
+							G_CALLBACK (cv_tree_activated_cb), NULL);
+
+	gtk_drag_dest_set (view, GTK_DEST_DEFAULT_ALL, dnd_dest_target, 1,
+							 GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK);
+	gtk_drag_source_set (view, GDK_BUTTON1_MASK, dnd_src_target, 1, GDK_ACTION_COPY);
+
+#ifndef WIN32
+	g_signal_connect (G_OBJECT (view), "drag_begin",
+							G_CALLBACK (mg_drag_begin_cb), NULL);
+	g_signal_connect (G_OBJECT (view), "drag_drop",
+							G_CALLBACK (mg_drag_drop_cb), NULL);
+	g_signal_connect (G_OBJECT (view), "drag_motion",
+							G_CALLBACK (mg_drag_motion_cb), NULL);
+	g_signal_connect (G_OBJECT (view), "drag_end",
+							G_CALLBACK (mg_drag_end_cb), NULL);
+#endif
+
+	((treeview *)cv)->tree = GTK_TREE_VIEW (view);
+	((treeview *)cv)->scrollw = win;
+	gtk_widget_show (view);
+}
+
+static void
+cv_tree_postinit (chanview *cv)
+{
+	gtk_tree_view_expand_all (((treeview *)cv)->tree);
+}
+
+static void *
+cv_tree_add (chanview *cv, chan *ch, char *name, GtkTreeIter *parent)
+{
+	GtkTreePath *path;
+
+	if (parent)
+	{
+		/* expand the parent node */
+		path = gtk_tree_model_get_path (GTK_TREE_MODEL (cv->store), parent);
+		if (path)
+		{
+			gtk_tree_view_expand_row (((treeview *)cv)->tree, path, FALSE);
+			gtk_tree_path_free (path);
+		}
+	}
+
+	return NULL;
+}
+
+static void
+cv_tree_change_orientation (chanview *cv)
+{
+}
+
+static void
+cv_tree_focus (chan *ch)
+{
+	GtkTreeView *tree = ((treeview *)ch->cv)->tree;
+	GtkTreeModel *model = gtk_tree_view_get_model (tree);
+	GtkTreePath *path;
+	GtkTreeIter parent;
+	GdkRectangle cell_rect;
+	GdkRectangle vis_rect;
+	gint dest_y;
+
+	/* expand the parent node */
+	if (gtk_tree_model_iter_parent (model, &parent, &ch->iter))
+	{
+		path = gtk_tree_model_get_path (model, &parent);
+		if (path)
+		{
+			/*if (!gtk_tree_view_row_expanded (tree, path))
+			{
+				gtk_tree_path_free (path);
+				return;
+			}*/
+			gtk_tree_view_expand_row (tree, path, FALSE);
+			gtk_tree_path_free (path);
+		}
+	}
+
+	path = gtk_tree_model_get_path (model, &ch->iter);
+	if (path)
+	{
+		/* This full section does what
+		 * gtk_tree_view_scroll_to_cell (tree, path, NULL, TRUE, 0.5, 0.5);
+		 * does, except it only scrolls the window if the provided cell is
+		 * not visible. Basic algorithm taken from gtktreeview.c */
+
+		/* obtain information to see if the cell is visible */
+		gtk_tree_view_get_background_area (tree, path, NULL, &cell_rect);
+		gtk_tree_view_get_visible_rect (tree, &vis_rect);
+
+		/* The cordinates aren't offset correctly */
+		gtk_tree_view_widget_to_tree_coords( tree, cell_rect.x, cell_rect.y, NULL, &cell_rect.y );
+
+		/* only need to scroll if out of bounds */
+		if (cell_rect.y < vis_rect.y ||
+				cell_rect.y + cell_rect.height > vis_rect.y + vis_rect.height)
+		{
+			dest_y = cell_rect.y - ((vis_rect.height - cell_rect.height) * 0.5);
+			if (dest_y < 0)
+				dest_y = 0;
+			gtk_tree_view_scroll_to_point (tree, -1, dest_y);
+		}
+		/* theft done, now make it focused like */
+		gtk_tree_view_set_cursor (tree, path, NULL, FALSE);
+		gtk_tree_path_free (path);
+	}
+}
+
+static void
+cv_tree_move_focus (chanview *cv, gboolean relative, int num)
+{
+	chan *ch;
+
+	if (relative)
+	{
+		num += cv_find_number_of_chan (cv, cv->focused);
+		num %= cv->size;
+		/* make it wrap around at both ends */
+		if (num < 0)
+			num = cv->size - 1;
+	}
+
+	ch = cv_find_chan_by_number (cv, num);
+	if (ch)
+		cv_tree_focus (ch);
+}
+
+static void
+cv_tree_remove (chan *ch)
+{
+}
+
+static void
+move_row (chan *ch, int delta, GtkTreeIter *parent)
+{
+	GtkTreeStore *store = ch->cv->store;
+	GtkTreeIter *src = &ch->iter;
+	GtkTreeIter dest = ch->iter;
+	GtkTreePath *dest_path;
+
+	if (delta < 0) /* down */
+	{
+		if (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &dest))
+			gtk_tree_store_swap (store, src, &dest);
+		else	/* move to top */
+			gtk_tree_store_move_after (store, src, NULL);
+
+	} else
+	{
+		dest_path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &dest);
+		if (gtk_tree_path_prev (dest_path))
+		{
+			gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &dest, dest_path);
+			gtk_tree_store_swap (store, src, &dest);
+		} else
+		{	/* move to bottom */
+			gtk_tree_store_move_before (store, src, NULL);
+		}
+
+		gtk_tree_path_free (dest_path);
+	}
+}
+
+static void
+cv_tree_move (chan *ch, int delta)
+{
+	GtkTreeIter parent;
+
+	/* do nothing if this is a server row */
+	if (gtk_tree_model_iter_parent (GTK_TREE_MODEL (ch->cv->store), &parent, &ch->iter))
+		move_row (ch, delta, &parent);
+}
+
+static void
+cv_tree_move_family (chan *ch, int delta)
+{
+	move_row (ch, delta, NULL);
+}
+
+static void
+cv_tree_cleanup (chanview *cv)
+{
+	if (cv->box)
+		/* kill the scrolled window */
+		gtk_widget_destroy (((treeview *)cv)->scrollw);
+}
+
+static void
+cv_tree_set_color (chan *ch, PangoAttrList *list)
+{
+	/* nothing to do, it's already set in the store */
+}
+
+static void
+cv_tree_rename (chan *ch, char *name)
+{
+	/* nothing to do, it's already renamed in the store */
+}
+
+static chan *
+cv_tree_get_parent (chan *ch)
+{
+	chan *parent_ch = NULL;
+	GtkTreeIter parent;
+
+	if (gtk_tree_model_iter_parent (GTK_TREE_MODEL (ch->cv->store), &parent, &ch->iter))
+	{
+		gtk_tree_model_get (GTK_TREE_MODEL (ch->cv->store), &parent, COL_CHAN, &parent_ch, -1);
+	}
+
+	return parent_ch;
+}
+
+static gboolean
+cv_tree_is_collapsed (chan *ch)
+{
+	chan *parent = cv_tree_get_parent (ch);
+	GtkTreePath *path = NULL;
+	gboolean ret;
+
+	if (parent == NULL)
+		return FALSE;
+
+	path = gtk_tree_model_get_path (GTK_TREE_MODEL (parent->cv->store),
+											  &parent->iter);
+	ret = !gtk_tree_view_row_expanded (((treeview *)parent->cv)->tree, path);
+	gtk_tree_path_free (path);
+	
+	return ret;
+}
diff --git a/src/fe-gtk/chanview.c b/src/fe-gtk/chanview.c
new file mode 100644
index 00000000..e90c4df8
--- /dev/null
+++ b/src/fe-gtk/chanview.c
@@ -0,0 +1,643 @@
+/* abstract channel view: tabs or tree or anything you like */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#include "chanview.h"
+#include "gtkutil.h"
+
+
+/* treeStore columns */
+
+#define COL_NAME 0		/* (char *) */
+#define COL_CHAN 1		/* (chan *) */
+#define COL_ATTR 2		/* (PangoAttrList *) */
+#define COL_PIXBUF 3		/* (GdkPixbuf *) */
+
+struct _chanview
+{
+	/* impl scratch area */
+	char implscratch[sizeof (void *) * 8];
+
+	GtkTreeStore *store;
+	int size;			/* number of channels in view */
+
+	GtkWidget *box;	/* the box we destroy when changing implementations */
+	GtkStyle *style;	/* style used for tree */
+	chan *focused;		/* currently focused channel */
+	int trunc_len;
+
+	/* callbacks */
+	void (*cb_focus) (chanview *, chan *, int tag, void *userdata);
+	void (*cb_xbutton) (chanview *, chan *, int tag, void *userdata);
+	gboolean (*cb_contextmenu) (chanview *, chan *, int tag, void *userdata, GdkEventButton *);
+	int (*cb_compare) (void *a, void *b);
+
+	/* impl */
+	void (*func_init) (chanview *);
+	void (*func_postinit) (chanview *);
+	void *(*func_add) (chanview *, chan *, char *, GtkTreeIter *);
+	void (*func_move_focus) (chanview *, gboolean, int);
+	void (*func_change_orientation) (chanview *);
+	void (*func_remove) (chan *);
+	void (*func_move) (chan *, int delta);
+	void (*func_move_family) (chan *, int delta);
+	void (*func_focus) (chan *);
+	void (*func_set_color) (chan *, PangoAttrList *);
+	void (*func_rename) (chan *, char *);
+	gboolean (*func_is_collapsed) (chan *);
+	chan *(*func_get_parent) (chan *);
+	void (*func_cleanup) (chanview *);
+
+	unsigned int sorted:1;
+	unsigned int vertical:1;
+	unsigned int use_icons:1;
+};
+
+struct _chan
+{
+	chanview *cv;	/* our owner */
+	GtkTreeIter iter;
+	void *userdata;	/* session * */
+	void *family;		/* server * or null */
+	void *impl;	/* togglebutton or null */
+	GdkPixbuf *icon;
+	short allow_closure;	/* allow it to be closed when it still has children? */
+	short tag;
+};
+
+static chan *cv_find_chan_by_number (chanview *cv, int num);
+static int cv_find_number_of_chan (chanview *cv, chan *find_ch);
+
+
+/* ======= TABS ======= */
+
+#include "chanview-tabs.c"
+
+
+/* ======= TREE ======= */
+
+#include "chanview-tree.c"
+
+
+/* ==== ABSTRACT CHANVIEW ==== */
+
+static char *
+truncate_tab_name (char *name, int max)
+{
+	char *buf;
+
+	if (max > 2 && g_utf8_strlen (name, -1) > max)
+	{
+		/* truncate long channel names */
+		buf = malloc (strlen (name) + 4);
+		strcpy (buf, name);
+		g_utf8_offset_to_pointer (buf, max)[0] = 0;
+		strcat (buf, "..");
+		return buf;
+	}
+
+	return name;
+}
+
+/* iterate through a model, into 1 depth of children */
+
+static void
+model_foreach_1 (GtkTreeModel *model, void (*func)(void *, GtkTreeIter *),
+					  void *userdata)
+{
+	GtkTreeIter iter, inner;
+
+	if (gtk_tree_model_get_iter_first (model, &iter))
+	{
+		do
+		{
+			func (userdata, &iter);
+			if (gtk_tree_model_iter_children (model, &inner, &iter))
+			{
+				do
+					func (userdata, &inner);
+				while (gtk_tree_model_iter_next (model, &inner));
+			}
+		}
+		while (gtk_tree_model_iter_next (model, &iter));
+	}
+}
+
+static void
+chanview_pop_cb (chanview *cv, GtkTreeIter *iter)
+{
+	chan *ch;
+	char *name;
+	PangoAttrList *attr;
+
+	gtk_tree_model_get (GTK_TREE_MODEL (cv->store), iter,
+							  COL_NAME, &name, COL_CHAN, &ch, COL_ATTR, &attr, -1);
+	ch->impl = cv->func_add (cv, ch, name, NULL);
+	if (attr)
+	{
+		cv->func_set_color (ch, attr);
+		pango_attr_list_unref (attr);
+	}
+	g_free (name);
+}
+
+static void
+chanview_populate (chanview *cv)
+{
+	model_foreach_1 (GTK_TREE_MODEL (cv->store), (void *)chanview_pop_cb, cv);
+}
+
+void
+chanview_set_impl (chanview *cv, int type)
+{
+	/* cleanup the old one */
+	if (cv->func_cleanup)
+		cv->func_cleanup (cv);
+
+	switch (type)
+	{
+	case 0:
+		cv->func_init = cv_tabs_init;
+		cv->func_postinit = cv_tabs_postinit;
+		cv->func_add = cv_tabs_add;
+		cv->func_move_focus = cv_tabs_move_focus;
+		cv->func_change_orientation = cv_tabs_change_orientation;
+		cv->func_remove = cv_tabs_remove;
+		cv->func_move = cv_tabs_move;
+		cv->func_move_family = cv_tabs_move_family;
+		cv->func_focus = cv_tabs_focus;
+		cv->func_set_color = cv_tabs_set_color;
+		cv->func_rename = cv_tabs_rename;
+		cv->func_is_collapsed = cv_tabs_is_collapsed;
+		cv->func_get_parent = cv_tabs_get_parent;
+		cv->func_cleanup = cv_tabs_cleanup;
+		break;
+
+	default:
+		cv->func_init = cv_tree_init;
+		cv->func_postinit = cv_tree_postinit;
+		cv->func_add = cv_tree_add;
+		cv->func_move_focus = cv_tree_move_focus;
+		cv->func_change_orientation = cv_tree_change_orientation;
+		cv->func_remove = cv_tree_remove;
+		cv->func_move = cv_tree_move;
+		cv->func_move_family = cv_tree_move_family;
+		cv->func_focus = cv_tree_focus;
+		cv->func_set_color = cv_tree_set_color;
+		cv->func_rename = cv_tree_rename;
+		cv->func_is_collapsed = cv_tree_is_collapsed;
+		cv->func_get_parent = cv_tree_get_parent;
+		cv->func_cleanup = cv_tree_cleanup;
+		break;
+	}
+
+	/* now rebuild a new tabbar or tree */
+	cv->func_init (cv);
+
+	chanview_populate (cv);
+
+	cv->func_postinit (cv);
+
+	/* force re-focus */
+	if (cv->focused)
+		cv->func_focus (cv->focused);
+}
+
+static void
+chanview_free_ch (chanview *cv, GtkTreeIter *iter)
+{
+	chan *ch;
+
+	gtk_tree_model_get (GTK_TREE_MODEL (cv->store), iter, COL_CHAN, &ch, -1);
+	free (ch);
+}
+
+static void
+chanview_destroy_store (chanview *cv)	/* free every (chan *) in the store */
+{
+	model_foreach_1 (GTK_TREE_MODEL (cv->store), (void *)chanview_free_ch, cv);
+	g_object_unref (cv->store);
+}
+
+static void
+chanview_destroy (chanview *cv)
+{
+	if (cv->func_cleanup)
+		cv->func_cleanup (cv);
+
+	if (cv->box)
+		gtk_widget_destroy (cv->box);
+
+	chanview_destroy_store (cv);
+	free (cv);
+}
+
+static void
+chanview_box_destroy_cb (GtkWidget *box, chanview *cv)
+{
+	cv->box = NULL;
+	chanview_destroy (cv);
+}
+
+chanview *
+chanview_new (int type, int trunc_len, gboolean sort, gboolean use_icons,
+				  GtkStyle *style)
+{
+	chanview *cv;
+
+	cv = calloc (1, sizeof (chanview));
+	cv->store = gtk_tree_store_new (4, G_TYPE_STRING, G_TYPE_POINTER,
+											  PANGO_TYPE_ATTR_LIST, GDK_TYPE_PIXBUF);
+	cv->style = style;
+	cv->box = gtk_hbox_new (0, 0);
+	cv->trunc_len = trunc_len;
+	cv->sorted = sort;
+	cv->use_icons = use_icons;
+	gtk_widget_show (cv->box);
+	chanview_set_impl (cv, type);
+
+	g_signal_connect (G_OBJECT (cv->box), "destroy",
+							G_CALLBACK (chanview_box_destroy_cb), cv);
+
+	return cv;
+}
+
+/* too lazy for signals */
+
+void
+chanview_set_callbacks (chanview *cv,
+	void (*cb_focus) (chanview *, chan *, int tag, void *userdata),
+	void (*cb_xbutton) (chanview *, chan *, int tag, void *userdata),
+	gboolean (*cb_contextmenu) (chanview *, chan *, int tag, void *userdata, GdkEventButton *),
+	int (*cb_compare) (void *a, void *b))
+{
+	cv->cb_focus = cb_focus;
+	cv->cb_xbutton = cb_xbutton;
+	cv->cb_contextmenu = cb_contextmenu;
+	cv->cb_compare = cb_compare;
+}
+
+/* find a place to insert this new entry, based on the compare function */
+
+static void
+chanview_insert_sorted (chanview *cv, GtkTreeIter *add_iter, GtkTreeIter *parent, void *ud)
+{
+	GtkTreeIter iter;
+	chan *ch;
+
+	if (cv->sorted && gtk_tree_model_iter_children (GTK_TREE_MODEL (cv->store), &iter, parent))
+	{
+		do
+		{
+			gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &iter, COL_CHAN, &ch, -1);
+			if (ch->tag == 0 && cv->cb_compare (ch->userdata, ud) > 0)
+			{
+				gtk_tree_store_insert_before (cv->store, add_iter, parent, &iter);
+				return;
+			}
+		}
+		while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &iter));
+	}
+
+	gtk_tree_store_append (cv->store, add_iter, parent);
+}
+
+/* find a parent node with the same "family" pointer (i.e. the Server tab) */
+
+static int
+chanview_find_parent (chanview *cv, void *family, GtkTreeIter *search_iter, chan *avoid)
+{
+	chan *search_ch;
+
+	/* find this new row's parent, if any */
+	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv->store), search_iter))
+	{
+		do
+		{
+			gtk_tree_model_get (GTK_TREE_MODEL (cv->store), search_iter, 
+									  COL_CHAN, &search_ch, -1);
+			if (family == search_ch->family && search_ch != avoid /*&&
+				 gtk_tree_store_iter_depth (cv->store, search_iter) == 0*/)
+				return TRUE;
+		}
+		while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), search_iter));
+	}
+
+	return FALSE;
+}
+
+static chan *
+chanview_add_real (chanview *cv, char *name, void *family, void *userdata,
+						 gboolean allow_closure, int tag, GdkPixbuf *icon,
+						 chan *ch, chan *avoid)
+{
+	GtkTreeIter parent_iter;
+	GtkTreeIter iter;
+	gboolean has_parent = FALSE;
+
+	if (chanview_find_parent (cv, family, &parent_iter, avoid))
+	{
+		chanview_insert_sorted (cv, &iter, &parent_iter, userdata);
+		has_parent = TRUE;
+	} else
+	{
+		gtk_tree_store_append (cv->store, &iter, NULL);
+	}
+
+	if (!ch)
+	{
+		ch = calloc (1, sizeof (chan));
+		ch->userdata = userdata;
+		ch->family = family;
+		ch->cv = cv;
+		ch->allow_closure = allow_closure;
+		ch->tag = tag;
+		ch->icon = icon;
+	}
+	memcpy (&(ch->iter), &iter, sizeof (iter));
+
+	gtk_tree_store_set (cv->store, &iter, COL_NAME, name, COL_CHAN, ch,
+							  COL_PIXBUF, icon, -1);
+
+	cv->size++;
+	if (!has_parent)
+		ch->impl = cv->func_add (cv, ch, name, NULL);
+	else
+		ch->impl = cv->func_add (cv, ch, name, &parent_iter);
+
+	return ch;
+}
+
+chan *
+chanview_add (chanview *cv, char *name, void *family, void *userdata, gboolean allow_closure, int tag, GdkPixbuf *icon)
+{
+	char *new_name;
+	chan *ret;
+
+	new_name = truncate_tab_name (name, cv->trunc_len);
+
+	ret = chanview_add_real (cv, new_name, family, userdata, allow_closure, tag, icon, NULL, NULL);
+
+	if (new_name != name)
+		free (new_name);
+
+	return ret;
+}
+
+int
+chanview_get_size (chanview *cv)
+{
+	return cv->size;
+}
+
+GtkWidget *
+chanview_get_box (chanview *cv)
+{
+	return cv->box;
+}
+
+void
+chanview_move_focus (chanview *cv, gboolean relative, int num)
+{
+	cv->func_move_focus (cv, relative, num);
+}
+
+GtkOrientation
+chanview_get_orientation (chanview *cv)
+{
+	return (cv->vertical ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL);
+}
+
+void
+chanview_set_orientation (chanview *cv, gboolean vertical)
+{
+	if (vertical != cv->vertical)
+	{
+		cv->vertical = vertical;
+		cv->func_change_orientation (cv);
+	}
+}
+
+int
+chan_get_tag (chan *ch)
+{
+	return ch->tag;
+}
+
+void *
+chan_get_userdata (chan *ch)
+{
+	return ch->userdata;
+}
+
+void
+chan_focus (chan *ch)
+{
+	if (ch->cv->focused == ch)
+		return;
+
+	ch->cv->func_focus (ch);
+}
+
+void
+chan_move (chan *ch, int delta)
+{
+	ch->cv->func_move (ch, delta);
+}
+
+void
+chan_move_family (chan *ch, int delta)
+{
+	ch->cv->func_move_family (ch, delta);
+}
+
+void
+chan_set_color (chan *ch, PangoAttrList *list)
+{
+	gtk_tree_store_set (ch->cv->store, &ch->iter, COL_ATTR, list, -1);	
+	ch->cv->func_set_color (ch, list);
+}
+
+void
+chan_rename (chan *ch, char *name, int trunc_len)
+{
+	char *new_name;
+
+	new_name = truncate_tab_name (name, trunc_len);
+
+	gtk_tree_store_set (ch->cv->store, &ch->iter, COL_NAME, new_name, -1);
+	ch->cv->func_rename (ch, new_name);
+	ch->cv->trunc_len = trunc_len;
+
+	if (new_name != name)
+		free (new_name);
+}
+
+/* this thing is overly complicated */
+
+static int
+cv_find_number_of_chan (chanview *cv, chan *find_ch)
+{
+	GtkTreeIter iter, inner;
+	chan *ch;
+	int i = 0;
+
+	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv->store), &iter))
+	{
+		do
+		{
+			gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &iter, COL_CHAN, &ch, -1);
+			if (ch == find_ch)
+				return i;
+			i++;
+
+			if (gtk_tree_model_iter_children (GTK_TREE_MODEL (cv->store), &inner, &iter))
+			{
+				do
+				{
+					gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &inner, COL_CHAN, &ch, -1);
+					if (ch == find_ch)
+						return i;
+					i++;
+				}
+				while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &inner));
+			}
+		}
+		while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &iter));
+	}
+
+	return 0;	/* WARNING */
+}
+
+/* this thing is overly complicated too */
+
+static chan *
+cv_find_chan_by_number (chanview *cv, int num)
+{
+	GtkTreeIter iter, inner;
+	chan *ch;
+	int i = 0;
+
+	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv->store), &iter))
+	{
+		do
+		{
+			if (i == num)
+			{
+				gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &iter, COL_CHAN, &ch, -1);
+				return ch;
+			}
+			i++;
+
+			if (gtk_tree_model_iter_children (GTK_TREE_MODEL (cv->store), &inner, &iter))
+			{
+				do
+				{
+					if (i == num)
+					{
+						gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &inner, COL_CHAN, &ch, -1);
+						return ch;
+					}
+					i++;
+				}
+				while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &inner));
+			}
+		}
+		while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &iter));
+	}
+
+	return NULL;
+}
+
+static void
+chan_emancipate_children (chan *ch)
+{
+	char *name;
+	chan *childch;
+	GtkTreeIter childiter;
+	PangoAttrList *attr;
+
+	while (gtk_tree_model_iter_children (GTK_TREE_MODEL (ch->cv->store), &childiter, &ch->iter))
+	{
+		/* remove and re-add all the children, but avoid using "ch" as parent */
+		gtk_tree_model_get (GTK_TREE_MODEL (ch->cv->store), &childiter,
+								  COL_NAME, &name, COL_CHAN, &childch, COL_ATTR, &attr, -1);
+		ch->cv->func_remove (childch);
+		gtk_tree_store_remove (ch->cv->store, &childiter);
+		ch->cv->size--;
+		chanview_add_real (childch->cv, name, childch->family, childch->userdata, childch->allow_closure, childch->tag, childch->icon, childch, ch);
+		if (attr)
+		{
+			childch->cv->func_set_color (childch, attr);
+			pango_attr_list_unref (attr);
+		}
+		g_free (name);
+	}
+}
+
+gboolean
+chan_remove (chan *ch, gboolean force)
+{
+	chan *new_ch;
+	int i, num;
+	extern int xchat_is_quitting;
+
+	if (xchat_is_quitting)	/* avoid lots of looping on exit */
+		return TRUE;
+
+	/* is this ch allowed to be closed while still having children? */
+	if (!force &&
+		 gtk_tree_model_iter_has_child (GTK_TREE_MODEL (ch->cv->store), &ch->iter) &&
+		 !ch->allow_closure)
+		return FALSE;
+
+	chan_emancipate_children (ch);
+	ch->cv->func_remove (ch);
+
+	/* is it the focused one? */
+	if (ch->cv->focused == ch)
+	{
+		ch->cv->focused = NULL;
+
+		/* try to move the focus to some other valid channel */
+		num = cv_find_number_of_chan (ch->cv, ch);
+		/* move to the one left of the closing tab */
+		new_ch = cv_find_chan_by_number (ch->cv, num - 1);
+		if (new_ch && new_ch != ch)
+		{
+			chan_focus (new_ch);	/* this'll will set ch->cv->focused for us too */
+		} else
+		{
+			/* if it fails, try focus from tab 0 and up */
+			for (i = 0; i < ch->cv->size; i++)
+			{
+				new_ch = cv_find_chan_by_number (ch->cv, i);
+				if (new_ch && new_ch != ch)
+				{
+					chan_focus (new_ch);	/* this'll will set ch->cv->focused for us too */
+					break;
+				}
+			}
+		}
+	}
+
+	ch->cv->size--;
+	gtk_tree_store_remove (ch->cv->store, &ch->iter);
+	free (ch);
+	return TRUE;
+}
+
+gboolean
+chan_is_collapsed (chan *ch)
+{
+	return ch->cv->func_is_collapsed (ch);
+}
+
+chan *
+chan_get_parent (chan *ch)
+{
+	return ch->cv->func_get_parent (ch);
+}
diff --git a/src/fe-gtk/chanview.h b/src/fe-gtk/chanview.h
new file mode 100644
index 00000000..75b5ef1f
--- /dev/null
+++ b/src/fe-gtk/chanview.h
@@ -0,0 +1,31 @@
+typedef struct _chanview chanview;
+typedef struct _chan chan;
+
+chanview *chanview_new (int type, int trunc_len, gboolean sort, gboolean use_icons, GtkStyle *style);
+void chanview_set_callbacks (chanview *cv,
+	void (*cb_focus) (chanview *, chan *, int tag, void *userdata),
+	void (*cb_xbutton) (chanview *, chan *, int tag, void *userdata),
+	gboolean (*cb_contextmenu) (chanview *, chan *, int tag, void *userdata, GdkEventButton *),
+	int (*cb_compare) (void *a, void *b));
+void chanview_set_impl (chanview *cv, int type);
+chan *chanview_add (chanview *cv, char *name, void *family, void *userdata, gboolean allow_closure, int tag, GdkPixbuf *icon);
+int chanview_get_size (chanview *cv);
+GtkWidget *chanview_get_box (chanview *cv);
+void chanview_move_focus (chanview *cv, gboolean relative, int num);
+GtkOrientation chanview_get_orientation (chanview *cv);
+void chanview_set_orientation (chanview *cv, gboolean vertical);
+
+int chan_get_tag (chan *ch);
+void *chan_get_userdata (chan *ch);
+void chan_focus (chan *ch);
+void chan_move (chan *ch, int delta);
+void chan_move_family (chan *ch, int delta);
+void chan_set_color (chan *ch, PangoAttrList *list);
+void chan_rename (chan *ch, char *new_name, int trunc_len);
+gboolean chan_remove (chan *ch, gboolean force);
+gboolean chan_is_collapsed (chan *ch);
+chan * chan_get_parent (chan *ch);
+
+#define FOCUS_NEW_ALL 1
+#define FOCUS_NEW_ONLY_ASKED 2
+#define FOCUS_NEW_NONE 0
diff --git a/src/fe-gtk/custom-list.c b/src/fe-gtk/custom-list.c
new file mode 100644
index 00000000..ac20e0ff
--- /dev/null
+++ b/src/fe-gtk/custom-list.c
@@ -0,0 +1,753 @@
+#include <string.h>
+#include <stdlib.h>
+#include "custom-list.h"
+
+/* indent -i3 -ci3 -ut -ts3 -bli0 -c0 custom-list.c */
+
+/* boring declarations of local functions */
+
+static void custom_list_init (CustomList * pkg_tree);
+
+static void custom_list_class_init (CustomListClass * klass);
+
+static void custom_list_tree_model_init (GtkTreeModelIface * iface);
+
+static void custom_list_finalize (GObject * object);
+
+static GtkTreeModelFlags custom_list_get_flags (GtkTreeModel * tree_model);
+
+static gint custom_list_get_n_columns (GtkTreeModel * tree_model);
+
+static GType custom_list_get_column_type (GtkTreeModel * tree_model,
+														gint index);
+
+static gboolean custom_list_get_iter (GtkTreeModel * tree_model,
+												  GtkTreeIter * iter, GtkTreePath * path);
+
+static GtkTreePath *custom_list_get_path (GtkTreeModel * tree_model,
+														GtkTreeIter * iter);
+
+static void custom_list_get_value (GtkTreeModel * tree_model,
+											  GtkTreeIter * iter,
+											  gint column, GValue * value);
+
+static gboolean custom_list_iter_next (GtkTreeModel * tree_model,
+													GtkTreeIter * iter);
+
+static gboolean custom_list_iter_children (GtkTreeModel * tree_model,
+														 GtkTreeIter * iter,
+														 GtkTreeIter * parent);
+
+static gboolean custom_list_iter_has_child (GtkTreeModel * tree_model,
+														  GtkTreeIter * iter);
+
+static gint custom_list_iter_n_children (GtkTreeModel * tree_model,
+													  GtkTreeIter * iter);
+
+static gboolean custom_list_iter_nth_child (GtkTreeModel * tree_model,
+														  GtkTreeIter * iter,
+														  GtkTreeIter * parent, gint n);
+
+static gboolean custom_list_iter_parent (GtkTreeModel * tree_model,
+													  GtkTreeIter * iter,
+													  GtkTreeIter * child);
+
+  /* -- GtkTreeSortable interface functions -- */
+
+static gboolean custom_list_sortable_get_sort_column_id (GtkTreeSortable *
+																			sortable,
+																			gint * sort_col_id,
+																			GtkSortType * order);
+
+static void custom_list_sortable_set_sort_column_id (GtkTreeSortable *
+																	  sortable,
+																	  gint sort_col_id,
+																	  GtkSortType order);
+
+static void custom_list_sortable_set_sort_func (GtkTreeSortable * sortable,
+																gint sort_col_id,
+																GtkTreeIterCompareFunc
+																sort_func, gpointer user_data,
+																GtkDestroyNotify
+																destroy_func);
+
+static void custom_list_sortable_set_default_sort_func (GtkTreeSortable *
+																		  sortable,
+																		  GtkTreeIterCompareFunc
+																		  sort_func,
+																		  gpointer user_data,
+																		  GtkDestroyNotify
+																		  destroy_func);
+
+static gboolean custom_list_sortable_has_default_sort_func (GtkTreeSortable *
+																				sortable);
+
+
+
+static GObjectClass *parent_class = NULL;	/* GObject stuff - nothing to worry about */
+
+
+static void
+custom_list_sortable_init (GtkTreeSortableIface * iface)
+{
+	iface->get_sort_column_id = custom_list_sortable_get_sort_column_id;
+	iface->set_sort_column_id = custom_list_sortable_set_sort_column_id;
+	iface->set_sort_func = custom_list_sortable_set_sort_func;	/* NOT SUPPORTED */
+	iface->set_default_sort_func = custom_list_sortable_set_default_sort_func;	/* NOT SUPPORTED */
+	iface->has_default_sort_func = custom_list_sortable_has_default_sort_func;	/* NOT SUPPORTED */
+}
+
+/*****************************************************************************
+ *
+ *  custom_list_get_type: here we register our new type and its interfaces
+ *                        with the type system. If you want to implement
+ *                        additional interfaces like GtkTreeSortable, you
+ *                        will need to do it here.
+ *
+ *****************************************************************************/
+
+static GType
+custom_list_get_type (void)
+{
+	static GType custom_list_type = 0;
+
+	if (custom_list_type)
+		return custom_list_type;
+
+	/* Some boilerplate type registration stuff */
+	if (1)
+	{
+		static const GTypeInfo custom_list_info = {
+			sizeof (CustomListClass),
+			NULL,	/* base_init */
+			NULL,	/* base_finalize */
+			(GClassInitFunc) custom_list_class_init,
+			NULL,	/* class finalize */
+			NULL,	/* class_data */
+			sizeof (CustomList),
+			0,	/* n_preallocs */
+			(GInstanceInitFunc) custom_list_init
+		};
+
+		custom_list_type =
+			g_type_register_static (G_TYPE_OBJECT, "CustomList",
+											&custom_list_info, (GTypeFlags) 0);
+	}
+
+	/* Here we register our GtkTreeModel interface with the type system */
+	if (1)
+	{
+		static const GInterfaceInfo tree_model_info = {
+			(GInterfaceInitFunc) custom_list_tree_model_init,
+			NULL,
+			NULL
+		};
+
+		g_type_add_interface_static (custom_list_type, GTK_TYPE_TREE_MODEL,
+											  &tree_model_info);
+	}
+
+	/* Add GtkTreeSortable interface */
+	if (1)
+	{
+		static const GInterfaceInfo tree_sortable_info = {
+			(GInterfaceInitFunc) custom_list_sortable_init,
+			NULL,
+			NULL
+		};
+
+		g_type_add_interface_static (custom_list_type,
+											  GTK_TYPE_TREE_SORTABLE,
+											  &tree_sortable_info);
+	}
+
+	return custom_list_type;
+}
+
+/*****************************************************************************
+ *
+ *  custom_list_class_init: more boilerplate GObject/GType stuff.
+ *                          Init callback for the type system,
+ *                          called once when our new class is created.
+ *
+ *****************************************************************************/
+
+static void
+custom_list_class_init (CustomListClass * klass)
+{
+	GObjectClass *object_class;
+
+	parent_class = (GObjectClass *) g_type_class_peek_parent (klass);
+	object_class = (GObjectClass *) klass;
+
+	object_class->finalize = custom_list_finalize;
+}
+
+/*****************************************************************************
+ *
+ *  custom_list_tree_model_init: init callback for the interface registration
+ *                               in custom_list_get_type. Here we override
+ *                               the GtkTreeModel interface functions that
+ *                               we implement.
+ *
+ *****************************************************************************/
+
+static void
+custom_list_tree_model_init (GtkTreeModelIface * iface)
+{
+	iface->get_flags = custom_list_get_flags;
+	iface->get_n_columns = custom_list_get_n_columns;
+	iface->get_column_type = custom_list_get_column_type;
+	iface->get_iter = custom_list_get_iter;
+	iface->get_path = custom_list_get_path;
+	iface->get_value = custom_list_get_value;
+	iface->iter_next = custom_list_iter_next;
+	iface->iter_children = custom_list_iter_children;
+	iface->iter_has_child = custom_list_iter_has_child;
+	iface->iter_n_children = custom_list_iter_n_children;
+	iface->iter_nth_child = custom_list_iter_nth_child;
+	iface->iter_parent = custom_list_iter_parent;
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_init: this is called everytime a new custom list object
+ *                    instance is created (we do that in custom_list_new).
+ *                    Initialise the list structure's fields here.
+ *
+ *****************************************************************************/
+
+static void
+custom_list_init (CustomList * custom_list)
+{
+	custom_list->n_columns = CUSTOM_LIST_N_COLUMNS;
+
+	custom_list->column_types[0] = G_TYPE_STRING;	/* CUSTOM_LIST_COL_NAME      */
+	custom_list->column_types[1] = G_TYPE_UINT;	/* CUSTOM_LIST_COL_USERS     */
+	custom_list->column_types[2] = G_TYPE_STRING;	/* CUSTOM_LIST_COL_TOPIC     */
+
+	custom_list->num_rows = 0;
+	custom_list->num_alloc = 0;
+	custom_list->rows = NULL;
+
+	custom_list->sort_id = SORT_ID_CHANNEL;
+	custom_list->sort_order = GTK_SORT_ASCENDING;
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_finalize: this is called just before a custom list is
+ *                        destroyed. Free dynamically allocated memory here.
+ *
+ *****************************************************************************/
+
+static void
+custom_list_finalize (GObject * object)
+{
+	custom_list_clear (CUSTOM_LIST (object));
+
+	/* must chain up - finalize parent */
+	(*parent_class->finalize) (object);
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_get_flags: tells the rest of the world whether our tree model
+ *                         has any special characteristics. In our case,
+ *                         we have a list model (instead of a tree), and each
+ *                         tree iter is valid as long as the row in question
+ *                         exists, as it only contains a pointer to our struct.
+ *
+ *****************************************************************************/
+
+static GtkTreeModelFlags
+custom_list_get_flags (GtkTreeModel * tree_model)
+{
+	return (GTK_TREE_MODEL_LIST_ONLY /*| GTK_TREE_MODEL_ITERS_PERSIST */ );
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_get_n_columns: tells the rest of the world how many data
+ *                             columns we export via the tree model interface
+ *
+ *****************************************************************************/
+
+static gint
+custom_list_get_n_columns (GtkTreeModel * tree_model)
+{
+	return 3;/*CUSTOM_LIST (tree_model)->n_columns;*/
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_get_column_type: tells the rest of the world which type of
+ *                               data an exported model column contains
+ *
+ *****************************************************************************/
+
+static GType
+custom_list_get_column_type (GtkTreeModel * tree_model, gint index)
+{
+	return CUSTOM_LIST (tree_model)->column_types[index];
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_get_iter: converts a tree path (physical position) into a
+ *                        tree iter structure (the content of the iter
+ *                        fields will only be used internally by our model).
+ *                        We simply store a pointer to our chanlistrow
+ *                        structure that represents that row in the tree iter.
+ *
+ *****************************************************************************/
+
+static gboolean
+custom_list_get_iter (GtkTreeModel * tree_model,
+							 GtkTreeIter * iter, GtkTreePath * path)
+{
+	CustomList *custom_list = CUSTOM_LIST (tree_model);
+	chanlistrow *record;
+	gint n;
+
+	n = gtk_tree_path_get_indices (path)[0];
+	if (n >= custom_list->num_rows || n < 0)
+		return FALSE;
+
+	record = custom_list->rows[n];
+
+	/* We simply store a pointer to our custom record in the iter */
+	iter->user_data = record;
+
+	return TRUE;
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_get_path: converts a tree iter into a tree path (ie. the
+ *                        physical position of that row in the list).
+ *
+ *****************************************************************************/
+
+static GtkTreePath *
+custom_list_get_path (GtkTreeModel * tree_model, GtkTreeIter * iter)
+{
+	GtkTreePath *path;
+	chanlistrow *record;
+
+	record = (chanlistrow *) iter->user_data;
+
+	path = gtk_tree_path_new ();
+	gtk_tree_path_append_index (path, record->pos);
+
+	return path;
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_get_value: Returns a row's exported data columns
+ *                         (_get_value is what gtk_tree_model_get uses)
+ *
+ *****************************************************************************/
+
+static void
+custom_list_get_value (GtkTreeModel * tree_model,
+							  GtkTreeIter * iter, gint column, GValue * value)
+{
+	chanlistrow *record;
+	CustomList *custom_list = CUSTOM_LIST (tree_model);
+
+	if (custom_list->num_rows == 0)
+		return;
+
+	g_value_init (value, custom_list->column_types[column]);
+
+	record = (chanlistrow *) iter->user_data;
+
+	switch (column)
+	{
+	case CUSTOM_LIST_COL_NAME:
+		g_value_set_static_string (value, GET_CHAN (record));
+		break;
+
+	case CUSTOM_LIST_COL_USERS:
+		g_value_set_uint (value, record->users);
+		break;
+
+	case CUSTOM_LIST_COL_TOPIC:
+		g_value_set_static_string (value, record->topic);
+		break;
+	}
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_iter_next: Takes an iter structure and sets it to point
+ *                         to the next row.
+ *
+ *****************************************************************************/
+
+static gboolean
+custom_list_iter_next (GtkTreeModel * tree_model, GtkTreeIter * iter)
+{
+	chanlistrow *record, *nextrecord;
+	CustomList *custom_list = CUSTOM_LIST (tree_model);
+
+	record = (chanlistrow *) iter->user_data;
+
+	/* Is this the last record in the list? */
+	if ((record->pos + 1) >= custom_list->num_rows)
+		return FALSE;
+
+	nextrecord = custom_list->rows[(record->pos + 1)];
+
+	g_assert (nextrecord != NULL);
+	g_assert (nextrecord->pos == (record->pos + 1));
+
+	iter->user_data = nextrecord;
+
+	return TRUE;
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_iter_children: Returns TRUE or FALSE depending on whether
+ *                             the row specified by 'parent' has any children.
+ *                             If it has children, then 'iter' is set to
+ *                             point to the first child. Special case: if
+ *                             'parent' is NULL, then the first top-level
+ *                             row should be returned if it exists.
+ *
+ *****************************************************************************/
+
+static gboolean
+custom_list_iter_children (GtkTreeModel * tree_model,
+									GtkTreeIter * iter, GtkTreeIter * parent)
+{
+	CustomList *custom_list = CUSTOM_LIST (tree_model);
+
+	/* this is a list, nodes have no children */
+	if (parent)
+		return FALSE;
+
+	/* parent == NULL is a special case; we need to return the first top-level row */
+	/* No rows => no first row */
+	if (custom_list->num_rows == 0)
+		return FALSE;
+
+	/* Set iter to first item in list */
+	iter->user_data = custom_list->rows[0];
+
+	return TRUE;
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_iter_has_child: Returns TRUE or FALSE depending on whether
+ *                              the row specified by 'iter' has any children.
+ *                              We only have a list and thus no children.
+ *
+ *****************************************************************************/
+
+static gboolean
+custom_list_iter_has_child (GtkTreeModel * tree_model, GtkTreeIter * iter)
+{
+	return FALSE;
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_iter_n_children: Returns the number of children the row
+ *                               specified by 'iter' has. This is usually 0,
+ *                               as we only have a list and thus do not have
+ *                               any children to any rows. A special case is
+ *                               when 'iter' is NULL, in which case we need
+ *                               to return the number of top-level nodes,
+ *                               ie. the number of rows in our list.
+ *
+ *****************************************************************************/
+
+static gint
+custom_list_iter_n_children (GtkTreeModel * tree_model, GtkTreeIter * iter)
+{
+	CustomList *custom_list = CUSTOM_LIST (tree_model);
+
+	/* special case: if iter == NULL, return number of top-level rows */
+	if (!iter)
+		return custom_list->num_rows;
+
+	return 0;	/* otherwise, this is easy again for a list */
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_iter_nth_child: If the row specified by 'parent' has any
+ *                              children, set 'iter' to the n-th child and
+ *                              return TRUE if it exists, otherwise FALSE.
+ *                              A special case is when 'parent' is NULL, in
+ *                              which case we need to set 'iter' to the n-th
+ *                              row if it exists.
+ *
+ *****************************************************************************/
+
+static gboolean
+custom_list_iter_nth_child (GtkTreeModel * tree_model,
+									 GtkTreeIter * iter, GtkTreeIter * parent, gint n)
+{
+	CustomList *custom_list = CUSTOM_LIST (tree_model);
+
+	/* a list has only top-level rows */
+	if (parent)
+		return FALSE;
+
+	/* special case: if parent == NULL, set iter to n-th top-level row */
+	if (n >= custom_list->num_rows)
+		return FALSE;
+
+	iter->user_data = custom_list->rows[n];
+	return TRUE;
+}
+
+
+/*****************************************************************************
+ *
+ *  custom_list_iter_parent: Point 'iter' to the parent node of 'child'. As
+ *                           we have a list and thus no children and no
+ *                           parents of children, we can just return FALSE.
+ *
+ *****************************************************************************/
+
+static gboolean
+custom_list_iter_parent (GtkTreeModel * tree_model,
+								 GtkTreeIter * iter, GtkTreeIter * child)
+{
+	return FALSE;
+}
+
+static gboolean
+custom_list_sortable_get_sort_column_id (GtkTreeSortable * sortable,
+													  gint * sort_col_id,
+													  GtkSortType * order)
+{
+	CustomList *custom_list = CUSTOM_LIST (sortable);
+
+	if (sort_col_id)
+		*sort_col_id = custom_list->sort_id;
+
+	if (order)
+		*order = custom_list->sort_order;
+
+	return TRUE;
+}
+
+
+static void
+custom_list_sortable_set_sort_column_id (GtkTreeSortable * sortable,
+													  gint sort_col_id, GtkSortType order)
+{
+	CustomList *custom_list = CUSTOM_LIST (sortable);
+
+	if (custom_list->sort_id == sort_col_id
+		 && custom_list->sort_order == order)
+		return;
+
+	custom_list->sort_id = sort_col_id;
+	custom_list->sort_order = order;
+
+	custom_list_resort (custom_list);
+
+	/* emit "sort-column-changed" signal to tell any tree views
+	 *  that the sort column has changed (so the little arrow
+	 *  in the column header of the sort column is drawn
+	 *  in the right column)                                     */
+
+	gtk_tree_sortable_sort_column_changed (sortable);
+}
+
+static void
+custom_list_sortable_set_sort_func (GtkTreeSortable * sortable,
+												gint sort_col_id,
+												GtkTreeIterCompareFunc sort_func,
+												gpointer user_data,
+												GtkDestroyNotify destroy_func)
+{
+}
+
+static void
+custom_list_sortable_set_default_sort_func (GtkTreeSortable * sortable,
+														  GtkTreeIterCompareFunc sort_func,
+														  gpointer user_data,
+														  GtkDestroyNotify destroy_func)
+{
+}
+
+static gboolean
+custom_list_sortable_has_default_sort_func (GtkTreeSortable * sortable)
+{
+	return FALSE;
+}
+
+/* fast as possible compare func for sorting.
+   TODO: If fast enough, use a unicode collation key and strcmp. */
+
+#define TOSML(c) (((c) >= 'A' && (c) <= 'Z') ? (c) - 'A' + 'a' : (c))
+
+static inline int
+fast_ascii_stricmp (const char *s1, const char *s2)
+{
+	int c1, c2;
+
+	while (*s1 && *s2)
+	{
+		c1 = (int) (unsigned char) TOSML (*s1);
+		c2 = (int) (unsigned char) TOSML (*s2);
+		if (c1 != c2)
+			return (c1 - c2);
+		s1++;
+		s2++;
+	}
+
+	return (((int) (unsigned char) *s1) - ((int) (unsigned char) *s2));
+}
+
+static gint
+custom_list_qsort_compare_func (chanlistrow ** a, chanlistrow ** b,
+										  CustomList * custom_list)
+{
+	if (custom_list->sort_order == GTK_SORT_DESCENDING)
+	{
+		chanlistrow **tmp = a;
+		a = b;
+		b = tmp;
+	}
+
+	if (custom_list->sort_id == SORT_ID_USERS)
+	{
+		return (*a)->users - (*b)->users;
+	}
+
+	if (custom_list->sort_id == SORT_ID_TOPIC)
+	{
+		return fast_ascii_stricmp ((*a)->topic, (*b)->topic);
+	}
+
+	return strcmp ((*a)->collation_key, (*b)->collation_key);
+}
+
+/*****************************************************************************
+ *
+ *  custom_list_new:  This is what you use in your own code to create a
+ *                    new custom list tree model for you to use.
+ *
+ *****************************************************************************/
+
+CustomList *
+custom_list_new (void)
+{
+	return (CustomList *) g_object_new (CUSTOM_TYPE_LIST, NULL);
+}
+
+void
+custom_list_append (CustomList * custom_list, chanlistrow * newrecord)
+{
+	GtkTreeIter iter;
+	GtkTreePath *path;
+	gulong newsize;
+	guint pos;
+
+	if (custom_list->num_rows >= custom_list->num_alloc)
+	{
+		custom_list->num_alloc += 64;
+		newsize = custom_list->num_alloc * sizeof (chanlistrow *);
+		custom_list->rows = g_realloc (custom_list->rows, newsize);
+	}
+
+	/* TODO: Binary search insert? */
+
+	pos = custom_list->num_rows;
+	custom_list->rows[pos] = newrecord;
+	custom_list->num_rows++;
+	newrecord->pos = pos;
+
+	/* inform the tree view and other interested objects
+	 *  (e.g. tree row references) that we have inserted
+	 *  a new row, and where it was inserted */
+
+	path = gtk_tree_path_new ();
+	gtk_tree_path_append_index (path, newrecord->pos);
+/*  custom_list_get_iter(GTK_TREE_MODEL(custom_list), &iter, path);*/
+	iter.user_data = newrecord;
+	gtk_tree_model_row_inserted (GTK_TREE_MODEL (custom_list), path, &iter);
+	gtk_tree_path_free (path);
+}
+
+void
+custom_list_resort (CustomList * custom_list)
+{
+	GtkTreePath *path;
+	gint *neworder, i;
+
+	if (custom_list->num_rows < 2)
+		return;
+
+	/* resort */
+	g_qsort_with_data (custom_list->rows,
+							 custom_list->num_rows,
+							 sizeof (chanlistrow *),
+							 (GCompareDataFunc) custom_list_qsort_compare_func,
+							 custom_list);
+
+	/* let other objects know about the new order */
+	neworder = malloc (sizeof (gint) * custom_list->num_rows);
+
+	for (i = custom_list->num_rows - 1; i >= 0; i--)
+	{
+		/* Note that the API reference might be wrong about
+		 * this, see bug number 124790 on bugs.gnome.org.
+		 * Both will work, but one will give you 'jumpy'
+		 * selections after row reordering. */
+		/* neworder[(custom_list->rows[i])->pos] = i; */
+		neworder[i] = (custom_list->rows[i])->pos;
+		(custom_list->rows[i])->pos = i;
+	}
+
+	path = gtk_tree_path_new ();
+	gtk_tree_model_rows_reordered (GTK_TREE_MODEL (custom_list), path, NULL,
+											 neworder);
+	gtk_tree_path_free (path);
+	free (neworder);
+}
+
+void
+custom_list_clear (CustomList * custom_list)
+{
+	int i, max = custom_list->num_rows - 1;
+	GtkTreePath *path;
+
+	for (i = max; i >= 0; i--)
+	{
+		path = gtk_tree_path_new ();
+		gtk_tree_path_append_index (path, custom_list->rows[i]->pos);
+		gtk_tree_model_row_deleted (GTK_TREE_MODEL (custom_list), path);
+		gtk_tree_path_free (path);
+	}
+
+	custom_list->num_rows = 0;
+	custom_list->num_alloc = 0;
+
+	g_free (custom_list->rows);
+	custom_list->rows = NULL;
+}
diff --git a/src/fe-gtk/custom-list.h b/src/fe-gtk/custom-list.h
new file mode 100644
index 00000000..d9e4f09e
--- /dev/null
+++ b/src/fe-gtk/custom-list.h
@@ -0,0 +1,85 @@
+#ifndef _custom_list_h_included_
+#define _custom_list_h_included_
+
+#include <gtk/gtk.h>
+
+/* Some boilerplate GObject defines. 'klass' is used
+ *   instead of 'class', because 'class' is a C++ keyword */
+
+#define CUSTOM_TYPE_LIST            (custom_list_get_type ())
+#define CUSTOM_LIST(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), CUSTOM_TYPE_LIST, CustomList))
+#define CUSTOM_LIST_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  CUSTOM_TYPE_LIST, CustomListClass))
+#define CUSTOM_IS_LIST(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CUSTOM_TYPE_LIST))
+#define CUSTOM_IS_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  CUSTOM_TYPE_LIST))
+#define CUSTOM_LIST_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  CUSTOM_TYPE_LIST, CustomListClass))
+
+/* The data columns that we export via the tree model interface */
+
+enum
+{
+	CUSTOM_LIST_COL_NAME,
+	CUSTOM_LIST_COL_USERS,
+	CUSTOM_LIST_COL_TOPIC,
+	CUSTOM_LIST_N_COLUMNS
+};
+
+enum
+{
+	SORT_ID_CHANNEL,
+	SORT_ID_USERS,
+	SORT_ID_TOPIC
+};
+
+typedef struct
+{
+	char *topic;
+	char *collation_key;
+	guint32 pos;						  /* pos within the array */
+	guint32 users;
+	/* channel string lives beyond "users" */
+#define GET_CHAN(row) (((char *)row)+sizeof(chanlistrow))
+}
+chanlistrow;
+
+typedef struct _CustomList CustomList;
+typedef struct _CustomListClass CustomListClass;
+
+
+
+/* CustomList: this structure contains everything we need for our
+ *             model implementation. You can add extra fields to
+ *             this structure, e.g. hashtables to quickly lookup
+ *             rows or whatever else you might need, but it is
+ *             crucial that 'parent' is the first member of the
+ *             structure.                                          */
+struct _CustomList
+{
+	GObject parent;
+
+	guint num_rows;				  /* number of rows that we have used */
+	guint num_alloc;					/* number of rows allocated */
+	chanlistrow **rows;			  /* a dynamically allocated array of pointers to the
+										   *  CustomRecord structure for each row */
+
+	gint n_columns;
+	GType column_types[CUSTOM_LIST_N_COLUMNS];
+
+	gint sort_id;
+	GtkSortType sort_order;
+};
+
+
+/* CustomListClass: more boilerplate GObject stuff */
+
+struct _CustomListClass
+{
+	GObjectClass parent_class;
+};
+
+
+CustomList *custom_list_new (void);
+void custom_list_append (CustomList *, chanlistrow *);
+void custom_list_resort (CustomList *);
+void custom_list_clear (CustomList *);
+
+#endif /* _custom_list_h_included_ */
diff --git a/src/fe-gtk/dccgui.c b/src/fe-gtk/dccgui.c
new file mode 100644
index 00000000..61f6d502
--- /dev/null
+++ b/src/fe-gtk/dccgui.c
@@ -0,0 +1,1098 @@
+/* X-Chat
+ * Copyright (C) 1998-2006 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <time.h>
+
+#define WANTSOCKET
+#define WANTARPA
+#include "../common/inet.h"
+#include "fe-gtk.h"
+
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkmessagedialog.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtkexpander.h>
+#include <gtk/gtkliststore.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtkcellrendererpixbuf.h>
+#include <gtk/gtkcellrenderertext.h>
+#include <gtk/gtkcheckmenuitem.h>
+#include <gtk/gtkradiobutton.h>
+#include <gtk/gtkversion.h>
+
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "../common/fe.h"
+#include "../common/util.h"
+#include "../common/network.h"
+#include "gtkutil.h"
+#include "palette.h"
+#include "maingui.h"
+
+
+enum	/* DCC SEND/RECV */
+{
+	COL_TYPE,
+	COL_STATUS,
+	COL_FILE,
+	COL_SIZE,
+	COL_POS,
+	COL_PERC,
+	COL_SPEED,
+	COL_ETA,
+	COL_NICK,
+	COL_DCC, /* struct DCC * */
+	COL_COLOR,	/* GdkColor */
+	N_COLUMNS
+};
+
+enum	/* DCC CHAT */
+{
+	CCOL_STATUS,
+	CCOL_NICK,
+	CCOL_RECV,
+	CCOL_SENT,
+	CCOL_START,
+	CCOL_DCC,	/* struct DCC * */
+	CCOL_COLOR,	/* GdkColor * */
+	CN_COLUMNS
+};
+
+struct dccwindow
+{
+	GtkWidget *window;
+
+	GtkWidget *list;
+	GtkListStore *store;
+	GtkTreeSelection *sel;
+
+	GtkWidget *abort_button;
+	GtkWidget *accept_button;
+	GtkWidget *resume_button;
+	GtkWidget *open_button;
+
+	GtkWidget *file_label;
+	GtkWidget *address_label;
+};
+
+struct my_dcc_send
+{
+	struct session *sess;
+	char *nick;
+	int maxcps;
+	int passive;
+};
+
+static struct dccwindow dccfwin = {NULL, };	/* file */
+static struct dccwindow dcccwin = {NULL, };	/* chat */
+static GdkPixbuf *pix_up = NULL;	/* down arrow */
+static GdkPixbuf *pix_dn = NULL;	/* up arrow */
+static int win_width = 600;
+static int win_height = 256;
+static short view_mode;	/* 1=download 2=upload 3=both */
+#define VIEW_DOWNLOAD 1
+#define VIEW_UPLOAD 2
+#define VIEW_BOTH 3
+
+#define KILOBYTE 1024
+#define MEGABYTE (KILOBYTE * 1024)
+#define GIGABYTE (MEGABYTE * 1024)
+
+
+static void
+proper_unit (DCC_SIZE size, char *buf, int buf_len)
+{
+	if (size <= KILOBYTE)
+	{
+		snprintf (buf, buf_len, "%"DCC_SFMT"B", size);
+	}
+	else if (size > KILOBYTE && size <= MEGABYTE)
+	{
+		snprintf (buf, buf_len, "%"DCC_SFMT"kB", size / KILOBYTE);
+	}
+	else
+	{
+		snprintf (buf, buf_len, "%.2fMB", (float)size / MEGABYTE);
+	}
+}
+
+static void
+dcc_send_filereq_file (struct my_dcc_send *mdc, char *file)
+{
+	if (file)
+		dcc_send (mdc->sess, mdc->nick, file, mdc->maxcps, mdc->passive);
+	else
+	{
+		free (mdc->nick);
+		free (mdc);
+	}
+}
+
+void
+fe_dcc_send_filereq (struct session *sess, char *nick, int maxcps, int passive)
+{
+	char tbuf[128];
+	struct my_dcc_send *mdc;
+	
+	mdc = malloc (sizeof (*mdc));
+	mdc->sess = sess;
+	mdc->nick = strdup (nick);
+	mdc->maxcps = maxcps;
+	mdc->passive = passive;
+
+	snprintf (tbuf, sizeof tbuf, _("Send file to %s"), nick);
+	gtkutil_file_req (tbuf, dcc_send_filereq_file, mdc, NULL, FRF_MULTIPLE);
+}
+
+static void
+dcc_prepare_row_chat (struct DCC *dcc, GtkListStore *store, GtkTreeIter *iter,
+							 gboolean update_only)
+{
+	static char pos[16], siz[16];
+	char *date;
+
+	date = ctime (&dcc->starttime);
+	date[strlen (date) - 1] = 0;	/* remove the \n */
+
+	proper_unit (dcc->pos, pos, sizeof (pos));
+	proper_unit (dcc->size, siz, sizeof (siz));
+
+	gtk_list_store_set (store, iter,
+							  CCOL_STATUS, _(dccstat[dcc->dccstat].name),
+							  CCOL_NICK, dcc->nick,
+							  CCOL_RECV, pos,
+							  CCOL_SENT, siz,
+							  CCOL_START, date,
+							  CCOL_DCC, dcc,
+							  CCOL_COLOR,
+							  dccstat[dcc->dccstat].color == 1 ?
+								NULL :
+								colors + dccstat[dcc->dccstat].color,
+							  -1);
+}
+
+static void
+dcc_prepare_row_send (struct DCC *dcc, GtkListStore *store, GtkTreeIter *iter,
+							 gboolean update_only)
+{
+	static char pos[16], size[16], kbs[14], perc[14], eta[14];
+	int to_go;
+	float per;
+
+	if (!pix_up)
+		pix_up = gtk_widget_render_icon (dccfwin.window, "gtk-go-up",
+													GTK_ICON_SIZE_MENU, NULL);
+
+	/* percentage ack'ed */
+	per = (float) ((dcc->ack * 100.00) / dcc->size);
+	proper_unit (dcc->size, size, sizeof (size));
+	proper_unit (dcc->pos, pos, sizeof (pos));
+	snprintf (kbs, sizeof (kbs), "%.1f", ((float)dcc->cps) / 1024);
+/*	proper_unit (dcc->ack, ack, sizeof (ack));*/
+	snprintf (perc, sizeof (perc), "%.0f%%", per);
+	if (dcc->cps != 0)
+	{
+		to_go = (dcc->size - dcc->ack) / dcc->cps;
+		snprintf (eta, sizeof (eta), "%.2d:%.2d:%.2d",
+					 to_go / 3600, (to_go / 60) % 60, to_go % 60);
+	} else
+		strcpy (eta, "--:--:--");
+
+	if (update_only)
+		gtk_list_store_set (store, iter,
+								  COL_STATUS, _(dccstat[dcc->dccstat].name),
+								  COL_POS, pos,
+								  COL_PERC, perc,
+								  COL_SPEED, kbs,
+								  COL_ETA, eta,
+								  COL_COLOR,
+								  dccstat[dcc->dccstat].color == 1 ?
+									NULL :
+									colors + dccstat[dcc->dccstat].color,
+									-1);
+	else
+		gtk_list_store_set (store, iter,
+								  COL_TYPE, pix_up,
+								  COL_STATUS, _(dccstat[dcc->dccstat].name),
+								  COL_FILE, file_part (dcc->file),
+								  COL_SIZE, size,
+								  COL_POS, pos,
+								  COL_PERC, perc,
+								  COL_SPEED, kbs,
+								  COL_ETA, eta,
+								  COL_NICK, dcc->nick,
+								  COL_DCC, dcc,
+								  COL_COLOR,
+								  dccstat[dcc->dccstat].color == 1 ?
+									NULL :
+									colors + dccstat[dcc->dccstat].color,
+									-1);
+}
+
+static void
+dcc_prepare_row_recv (struct DCC *dcc, GtkListStore *store, GtkTreeIter *iter,
+							 gboolean update_only)
+{
+	static char size[16], pos[16], kbs[16], perc[14], eta[16];
+	float per;
+	int to_go;
+
+	if (!pix_dn)
+		pix_dn = gtk_widget_render_icon (dccfwin.window, "gtk-go-down",
+													GTK_ICON_SIZE_MENU, NULL);
+
+	proper_unit (dcc->size, size, sizeof (size));
+	if (dcc->dccstat == STAT_QUEUED)
+		proper_unit (dcc->resumable, pos, sizeof (pos));
+	else
+		proper_unit (dcc->pos, pos, sizeof (pos));
+	snprintf (kbs, sizeof (kbs), "%.1f", ((float)dcc->cps) / 1024);
+	/* percentage recv'ed */
+	per = (float) ((dcc->pos * 100.00) / dcc->size);
+	snprintf (perc, sizeof (perc), "%.0f%%", per);
+	if (dcc->cps != 0)
+	{
+		to_go = (dcc->size - dcc->pos) / dcc->cps;
+		snprintf (eta, sizeof (eta), "%.2d:%.2d:%.2d",
+					 to_go / 3600, (to_go / 60) % 60, to_go % 60);
+	} else
+		strcpy (eta, "--:--:--");
+
+	if (update_only)
+		gtk_list_store_set (store, iter,
+								  COL_STATUS, _(dccstat[dcc->dccstat].name),
+								  COL_POS, pos,
+								  COL_PERC, perc,
+								  COL_SPEED, kbs,
+								  COL_ETA, eta,
+								  COL_COLOR,
+								  dccstat[dcc->dccstat].color == 1 ?
+									NULL :
+									colors + dccstat[dcc->dccstat].color,
+									-1);
+	else
+		gtk_list_store_set (store, iter,
+								  COL_TYPE, pix_dn,
+								  COL_STATUS, _(dccstat[dcc->dccstat].name),
+								  COL_FILE, file_part (dcc->file),
+								  COL_SIZE, size,
+								  COL_POS, pos,
+								  COL_PERC, perc,
+								  COL_SPEED, kbs,
+								  COL_ETA, eta,
+								  COL_NICK, dcc->nick,
+								  COL_DCC, dcc,
+								  COL_COLOR,
+								  dccstat[dcc->dccstat].color == 1 ?
+									NULL :
+									colors + dccstat[dcc->dccstat].color,
+									-1);
+}
+
+static gboolean
+dcc_find_row (struct DCC *find_dcc, GtkTreeModel *model, GtkTreeIter *iter, int col)
+{
+	struct DCC *dcc;
+
+	if (gtk_tree_model_get_iter_first (model, iter))
+	{
+		do
+		{
+			gtk_tree_model_get (model, iter, col, &dcc, -1);
+			if (dcc == find_dcc)
+				return TRUE;
+		}
+		while (gtk_tree_model_iter_next (model, iter));
+	}
+
+	return FALSE;
+}
+
+static void
+dcc_update_recv (struct DCC *dcc)
+{
+	GtkTreeIter iter;
+
+	if (!dccfwin.window)
+		return;
+
+	if (!dcc_find_row (dcc, GTK_TREE_MODEL (dccfwin.store), &iter, COL_DCC))
+		return;
+
+	dcc_prepare_row_recv (dcc, dccfwin.store, &iter, TRUE);
+}
+
+static void
+dcc_update_chat (struct DCC *dcc)
+{
+	GtkTreeIter iter;
+
+	if (!dcccwin.window)
+		return;
+
+	if (!dcc_find_row (dcc, GTK_TREE_MODEL (dcccwin.store), &iter, CCOL_DCC))
+		return;
+
+	dcc_prepare_row_chat (dcc, dcccwin.store, &iter, TRUE);
+}
+
+static void
+dcc_update_send (struct DCC *dcc)
+{
+	GtkTreeIter iter;
+
+	if (!dccfwin.window)
+		return;
+
+	if (!dcc_find_row (dcc, GTK_TREE_MODEL (dccfwin.store), &iter, COL_DCC))
+		return;
+
+	dcc_prepare_row_send (dcc, dccfwin.store, &iter, TRUE);
+}
+
+static void
+close_dcc_file_window (GtkWindow *win, gpointer data)
+{
+	dccfwin.window = NULL;
+}
+
+static void
+dcc_append (struct DCC *dcc, GtkListStore *store, gboolean prepend)
+{
+	GtkTreeIter iter;
+
+	if (prepend)
+		gtk_list_store_prepend (store, &iter);
+	else
+		gtk_list_store_append (store, &iter);
+
+	if (dcc->type == TYPE_RECV)
+		dcc_prepare_row_recv (dcc, store, &iter, FALSE);
+	else
+		dcc_prepare_row_send (dcc, store, &iter, FALSE);
+}
+
+static void
+dcc_fill_window (int flags)
+{
+	struct DCC *dcc;
+	GSList *list;
+	GtkTreeIter iter;
+	int i = 0;
+
+	gtk_list_store_clear (GTK_LIST_STORE (dccfwin.store));
+
+	if (flags & VIEW_UPLOAD)
+	{
+		list = dcc_list;
+		while (list)
+		{
+			dcc = list->data;
+			if (dcc->type == TYPE_SEND)
+			{
+				dcc_append (dcc, dccfwin.store, FALSE);
+				i++;
+			}
+			list = list->next;
+		}
+	}
+
+	if (flags & VIEW_DOWNLOAD)
+	{
+		list = dcc_list;
+		while (list)
+		{
+			dcc = list->data;
+			if (dcc->type == TYPE_RECV)
+			{
+				dcc_append (dcc, dccfwin.store, FALSE);
+				i++;
+			}
+			list = list->next;
+		}
+	}
+
+	/* if only one entry, select it (so Accept button can work) */
+	if (i == 1)
+	{
+		gtk_tree_model_get_iter_first (GTK_TREE_MODEL (dccfwin.store), &iter);
+		gtk_tree_selection_select_iter (dccfwin.sel, &iter);
+	}
+}
+
+/* return list of selected DCCs */
+
+static GSList *
+treeview_get_selected (GtkTreeModel *model, GtkTreeSelection *sel, int column)
+{
+	GtkTreeIter iter;
+	GSList *list = NULL;
+	void *ptr;
+
+	if (gtk_tree_model_get_iter_first (model, &iter))
+	{
+		do
+		{
+			if (gtk_tree_selection_iter_is_selected (sel, &iter))
+			{
+				gtk_tree_model_get (model, &iter, column, &ptr, -1);
+				list = g_slist_prepend (list, ptr);
+			}
+		}
+		while (gtk_tree_model_iter_next (model, &iter));
+	}
+
+	return g_slist_reverse (list);
+}
+
+static GSList *
+dcc_get_selected (void)
+{
+	return treeview_get_selected (GTK_TREE_MODEL (dccfwin.store),
+											dccfwin.sel, COL_DCC);
+}
+
+static void
+resume_clicked (GtkWidget * wid, gpointer none)
+{
+	struct DCC *dcc;
+	char buf[512];
+	GSList *list;
+
+	list = dcc_get_selected ();
+	if (!list)
+		return;
+	dcc = list->data;
+	g_slist_free (list);
+
+	if (dcc->type == TYPE_RECV && !dcc_resume (dcc))
+	{
+		switch (dcc->resume_error)
+		{
+		case 0:	/* unknown error */
+			fe_message (_("That file is not resumable."), FE_MSG_ERROR);
+			break;
+		case 1:
+			snprintf (buf, sizeof (buf),
+						_(	"Cannot access file: %s\n"
+							"%s.\n"
+							"Resuming not possible."), dcc->destfile,	
+							errorstring (dcc->resume_errno));
+			fe_message (buf, FE_MSG_ERROR);
+			break;
+		case 2:
+			fe_message (_("File in download directory is larger "
+							"than file offered. Resuming not possible."), FE_MSG_ERROR);
+			break;
+		case 3:
+			fe_message (_("Cannot resume the same file from two people."), FE_MSG_ERROR);
+		}
+	}
+}
+
+static void
+abort_clicked (GtkWidget * wid, gpointer none)
+{
+	struct DCC *dcc;
+	GSList *start, *list;
+
+	start = list = dcc_get_selected ();
+	for (; list; list = list->next)
+	{
+		dcc = list->data;
+		dcc_abort (dcc->serv->front_session, dcc);
+	}
+	g_slist_free (start);
+}
+
+static void
+accept_clicked (GtkWidget * wid, gpointer none)
+{
+	struct DCC *dcc;
+	GSList *start, *list;
+
+	start = list = dcc_get_selected ();
+	for (; list; list = list->next)
+	{
+		dcc = list->data;
+		if (dcc->type != TYPE_SEND)
+			dcc_get (dcc);
+	}
+	g_slist_free (start);
+}
+
+static void
+browse_folder (char *dir)
+{
+#ifdef WIN32
+	/* no need for file:// in ShellExecute() */
+	fe_open_url (dir);
+#else
+	char buf[512];
+
+	snprintf (buf, sizeof (buf), "file://%s", dir);
+	fe_open_url (buf);
+#endif
+}
+
+static void
+browse_dcc_folder (void)
+{
+	if (prefs.dcc_completed_dir[0])
+		browse_folder (prefs.dcc_completed_dir);
+	else
+		browse_folder (prefs.dccdir);
+}
+
+static void
+dcc_details_populate (struct DCC *dcc)
+{
+	char buf[128];
+
+	if (!dcc)
+	{
+		gtk_label_set_text (GTK_LABEL (dccfwin.file_label), NULL);
+		gtk_label_set_text (GTK_LABEL (dccfwin.address_label), NULL);
+		return;
+	}
+
+	/* full path */
+	if (dcc->type == TYPE_RECV)
+		gtk_label_set_text (GTK_LABEL (dccfwin.file_label), dcc->destfile);
+	else
+		gtk_label_set_text (GTK_LABEL (dccfwin.file_label), dcc->file);
+
+	/* address and port */
+	snprintf (buf, sizeof (buf), "%s : %d", net_ip (dcc->addr), dcc->port);
+	gtk_label_set_text (GTK_LABEL (dccfwin.address_label), buf);
+}
+
+static void
+dcc_row_cb (GtkTreeSelection *sel, gpointer user_data)
+{
+	struct DCC *dcc;
+	GSList *list;
+
+	list = dcc_get_selected ();
+	if (!list)
+	{
+		gtk_widget_set_sensitive (dccfwin.accept_button, FALSE);
+		gtk_widget_set_sensitive (dccfwin.resume_button, FALSE);
+		gtk_widget_set_sensitive (dccfwin.abort_button, FALSE);
+		dcc_details_populate (NULL);
+		return;
+	}
+
+	gtk_widget_set_sensitive (dccfwin.abort_button, TRUE);
+
+	if (list->next)	/* multi selection */
+	{
+		gtk_widget_set_sensitive (dccfwin.accept_button, TRUE);
+		gtk_widget_set_sensitive (dccfwin.resume_button, TRUE);
+		dcc_details_populate (list->data);
+	}
+	else
+	{
+		/* turn OFF/ON appropriate buttons */
+		dcc = list->data;
+		if (dcc->dccstat == STAT_QUEUED && dcc->type == TYPE_RECV)
+		{
+			gtk_widget_set_sensitive (dccfwin.accept_button, TRUE);
+			gtk_widget_set_sensitive (dccfwin.resume_button, TRUE);
+		}
+		else
+		{
+			gtk_widget_set_sensitive (dccfwin.accept_button, FALSE);
+			gtk_widget_set_sensitive (dccfwin.resume_button, FALSE);
+		}
+
+		dcc_details_populate (dcc);
+	}
+
+	g_slist_free (list);
+}
+
+static void
+dcc_dclick_cb (GtkTreeView *view, GtkTreePath *path,
+					GtkTreeViewColumn *column, gpointer data)
+{
+	struct DCC *dcc;
+	GSList *list;
+
+	list = dcc_get_selected ();
+	if (!list)
+		return;
+	dcc = list->data;
+	g_slist_free (list);
+
+	if (dcc->type == TYPE_RECV)
+	{
+		accept_clicked (0, 0);
+		return;
+	}
+
+	switch (dcc->dccstat)
+	{
+	case STAT_FAILED:
+	case STAT_ABORTED:
+	case STAT_DONE:
+		dcc_abort (dcc->serv->front_session, dcc);
+	}
+}
+
+static void
+dcc_add_column (GtkWidget *tree, int textcol, int colorcol, char *title, gboolean right_justified)
+{
+	GtkCellRenderer *renderer;
+
+	renderer = gtk_cell_renderer_text_new ();
+	if (right_justified)
+		g_object_set (G_OBJECT (renderer), "xalign", (float) 1.0, NULL);
+	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), -1, title, renderer,
+																"text", textcol, "foreground-gdk", colorcol,
+																NULL);
+	gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1);
+}
+
+static GtkWidget *
+dcc_detail_label (char *text, GtkWidget *box, int num)
+{
+	GtkWidget *label;
+	char buf[64];
+
+	label = gtk_label_new (NULL);
+	snprintf (buf, sizeof (buf), "<b>%s</b>", text);
+	gtk_label_set_markup (GTK_LABEL (label), buf);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+	gtk_table_attach (GTK_TABLE (box), label, 0, 1, 0 + num, 1 + num, GTK_FILL, GTK_FILL, 0, 0);
+
+	label = gtk_label_new (NULL);
+	gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+	gtk_table_attach (GTK_TABLE (box), label, 1, 2, 0 + num, 1 + num, GTK_FILL, GTK_FILL, 0, 0);
+
+	return label;
+}
+
+static void
+dcc_exp_cb (GtkWidget *exp, GtkWidget *box)
+{
+#if GTK_CHECK_VERSION(2,20,0)
+	if (gtk_widget_get_visible (box))
+#else
+	if (GTK_WIDGET_VISIBLE (box))
+#endif
+		gtk_widget_hide (box);
+	else
+		gtk_widget_show (box);
+}
+
+static void
+dcc_toggle (GtkWidget *item, gpointer data)
+{
+	if (GTK_TOGGLE_BUTTON (item)->active)
+	{
+		view_mode = GPOINTER_TO_INT (data);
+		dcc_fill_window (GPOINTER_TO_INT (data));
+	}
+}
+
+static gboolean
+dcc_configure_cb (GtkWindow *win, GdkEventConfigure *event, gpointer data)
+{
+	/* remember the window size */
+	gtk_window_get_size (win, &win_width, &win_height);
+	return FALSE;
+}
+
+int
+fe_dcc_open_recv_win (int passive)
+{
+	GtkWidget *radio, *table, *vbox, *bbox, *view, *exp, *detailbox;
+	GtkListStore *store;
+	GSList *group;
+
+	if (dccfwin.window)
+	{
+		if (!passive)
+			mg_bring_tofront (dccfwin.window);
+		return TRUE;
+	}
+	dccfwin.window = mg_create_generic_tab ("Transfers", _("XChat: Uploads and Downloads"),
+														 FALSE, TRUE, close_dcc_file_window, NULL,
+														 win_width, win_height, &vbox, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (dccfwin.window), 3);
+	gtk_box_set_spacing (GTK_BOX (vbox), 3);
+
+	store = gtk_list_store_new (N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING,
+										 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
+										 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
+										 G_TYPE_STRING, G_TYPE_POINTER, GDK_TYPE_COLOR);
+	view = gtkutil_treeview_new (vbox, GTK_TREE_MODEL (store), NULL, -1);
+	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE);
+	/* Up/Down Icon column */
+	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, NULL,
+																gtk_cell_renderer_pixbuf_new (),
+																"pixbuf", COL_TYPE, NULL);
+	dcc_add_column (view, COL_STATUS, COL_COLOR, _("Status"), FALSE);
+	dcc_add_column (view, COL_FILE,   COL_COLOR, _("File"), FALSE);
+	dcc_add_column (view, COL_SIZE,   COL_COLOR, _("Size"), TRUE);
+	dcc_add_column (view, COL_POS,    COL_COLOR, _("Position"), TRUE);
+	dcc_add_column (view, COL_PERC,   COL_COLOR, "%", TRUE);
+	dcc_add_column (view, COL_SPEED,  COL_COLOR, "KB/s", TRUE);
+	dcc_add_column (view, COL_ETA,    COL_COLOR, _("ETA"), FALSE);
+	dcc_add_column (view, COL_NICK,   COL_COLOR, _("Nick"), FALSE);
+
+	gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), COL_FILE), TRUE);
+	gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), COL_NICK), TRUE);
+
+	dccfwin.list = view;
+	dccfwin.store = store;
+	dccfwin.sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
+	view_mode = VIEW_BOTH;
+	gtk_tree_selection_set_mode (dccfwin.sel, GTK_SELECTION_MULTIPLE);
+
+	if (!prefs.windows_as_tabs)
+		g_signal_connect (G_OBJECT (dccfwin.window), "configure_event",
+								G_CALLBACK (dcc_configure_cb), 0);
+	g_signal_connect (G_OBJECT (dccfwin.sel), "changed",
+							G_CALLBACK (dcc_row_cb), NULL);
+	/* double click */
+	g_signal_connect (G_OBJECT (view), "row-activated",
+							G_CALLBACK (dcc_dclick_cb), NULL);
+
+	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);
+
+	radio = gtk_radio_button_new_with_mnemonic (NULL, _("Both"));
+	g_signal_connect (G_OBJECT (radio), "toggled",
+							G_CALLBACK (dcc_toggle), GINT_TO_POINTER (VIEW_BOTH));
+	gtk_table_attach (GTK_TABLE (table), radio, 3, 4, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
+	group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio));
+
+	radio = gtk_radio_button_new_with_mnemonic (group, _("Uploads"));
+	g_signal_connect (G_OBJECT (radio), "toggled",
+							G_CALLBACK (dcc_toggle), GINT_TO_POINTER (VIEW_UPLOAD));
+	gtk_table_attach (GTK_TABLE (table), radio, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
+	group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio));
+
+	radio = gtk_radio_button_new_with_mnemonic (group, _("Downloads"));
+	g_signal_connect (G_OBJECT (radio), "toggled",
+							G_CALLBACK (dcc_toggle), GINT_TO_POINTER (VIEW_DOWNLOAD));
+	gtk_table_attach (GTK_TABLE (table), radio, 2, 3, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
+
+	exp = gtk_expander_new (_("Details"));
+	gtk_table_attach (GTK_TABLE (table), exp, 0, 1, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
+
+	detailbox = gtk_table_new (3, 3, FALSE);
+	gtk_table_set_col_spacings (GTK_TABLE (detailbox), 6);
+	gtk_table_set_row_spacings (GTK_TABLE (detailbox), 2);
+	gtk_container_set_border_width (GTK_CONTAINER (detailbox), 6);
+	g_signal_connect (G_OBJECT (exp), "activate",
+							G_CALLBACK (dcc_exp_cb), detailbox);
+	gtk_table_attach (GTK_TABLE (table), detailbox, 0, 4, 1, 2, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
+
+	dccfwin.file_label = dcc_detail_label (_("File:"), detailbox, 0);
+	dccfwin.address_label = dcc_detail_label (_("Address:"), detailbox, 1);
+
+	bbox = gtk_hbutton_box_new ();
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD);
+	gtk_box_pack_end (GTK_BOX (vbox), bbox, FALSE, FALSE, 2);
+
+	dccfwin.abort_button = gtkutil_button (bbox, GTK_STOCK_CANCEL, 0, abort_clicked, 0, _("Abort"));
+	dccfwin.accept_button = gtkutil_button (bbox, GTK_STOCK_APPLY, 0, accept_clicked, 0, _("Accept"));
+	dccfwin.resume_button = gtkutil_button (bbox, GTK_STOCK_REFRESH, 0, resume_clicked, 0, _("Resume"));
+	dccfwin.open_button = gtkutil_button (bbox, 0, 0, browse_dcc_folder, 0, _("Open Folder..."));
+	gtk_widget_set_sensitive (dccfwin.accept_button, FALSE);
+	gtk_widget_set_sensitive (dccfwin.resume_button, FALSE);
+	gtk_widget_set_sensitive (dccfwin.abort_button, FALSE);
+
+	dcc_fill_window (3);
+	gtk_widget_show_all (dccfwin.window);
+	gtk_widget_hide (detailbox);
+
+	return FALSE;
+}
+
+int
+fe_dcc_open_send_win (int passive)
+{
+	/* combined send/recv GUI */
+	return fe_dcc_open_recv_win (passive);
+}
+
+
+/* DCC CHAT GUIs BELOW */
+
+static GSList *
+dcc_chat_get_selected (void)
+{
+	return treeview_get_selected (GTK_TREE_MODEL (dcccwin.store),
+											dcccwin.sel, CCOL_DCC);
+}
+
+static void
+accept_chat_clicked (GtkWidget * wid, gpointer none)
+{
+	struct DCC *dcc;
+	GSList *start, *list;
+
+	start = list = dcc_chat_get_selected ();
+	for (; list; list = list->next)
+	{
+		dcc = list->data;
+		dcc_get (dcc);
+	}
+	g_slist_free (start);
+}
+
+static void
+abort_chat_clicked (GtkWidget * wid, gpointer none)
+{
+	struct DCC *dcc;
+	GSList *start, *list;
+
+	start = list = dcc_chat_get_selected ();
+	for (; list; list = list->next)
+	{
+		dcc = list->data;
+		dcc_abort (dcc->serv->front_session, dcc);
+	}
+	g_slist_free (start);
+}
+
+static void
+dcc_chat_close_cb (void)
+{
+	dcccwin.window = NULL;
+}
+
+static void
+dcc_chat_append (struct DCC *dcc, GtkListStore *store, gboolean prepend)
+{
+	GtkTreeIter iter;
+
+	if (prepend)
+		gtk_list_store_prepend (store, &iter);
+	else
+		gtk_list_store_append (store, &iter);
+
+	dcc_prepare_row_chat (dcc, store, &iter, FALSE);
+}
+
+static void
+dcc_chat_fill_win (void)
+{
+	struct DCC *dcc;
+	GSList *list;
+	GtkTreeIter iter;
+	int i = 0;
+
+	gtk_list_store_clear (GTK_LIST_STORE (dcccwin.store));
+
+	list = dcc_list;
+	while (list)
+	{
+		dcc = list->data;
+		if (dcc->type == TYPE_CHATSEND || dcc->type == TYPE_CHATRECV)
+		{
+			dcc_chat_append (dcc, dcccwin.store, FALSE);
+			i++;
+		}
+		list = list->next;
+	}
+
+	/* if only one entry, select it (so Accept button can work) */
+	if (i == 1)
+	{
+		gtk_tree_model_get_iter_first (GTK_TREE_MODEL (dcccwin.store), &iter);
+		gtk_tree_selection_select_iter (dcccwin.sel, &iter);
+	}
+}
+
+static void
+dcc_chat_row_cb (GtkTreeSelection *sel, gpointer user_data)
+{
+	struct DCC *dcc;
+	GSList *list;
+
+	list = dcc_chat_get_selected ();
+	if (!list)
+	{
+		gtk_widget_set_sensitive (dcccwin.accept_button, FALSE);
+		gtk_widget_set_sensitive (dcccwin.abort_button, FALSE);
+		return;
+	}
+
+	gtk_widget_set_sensitive (dcccwin.abort_button, TRUE);
+
+	if (list->next)	/* multi selection */
+		gtk_widget_set_sensitive (dcccwin.accept_button, TRUE);
+	else
+	{
+		/* turn OFF/ON appropriate buttons */
+		dcc = list->data;
+		if (dcc->dccstat == STAT_QUEUED && dcc->type == TYPE_CHATRECV)
+			gtk_widget_set_sensitive (dcccwin.accept_button, TRUE);
+		else
+			gtk_widget_set_sensitive (dcccwin.accept_button, FALSE);
+	}
+
+	g_slist_free (list);
+}
+
+static void
+dcc_chat_dclick_cb (GtkTreeView *view, GtkTreePath *path,
+						  GtkTreeViewColumn *column, gpointer data)
+{
+	accept_chat_clicked (0, 0);
+}
+
+int
+fe_dcc_open_chat_win (int passive)
+{
+	GtkWidget *view, *vbox, *bbox;
+	GtkListStore *store;
+
+	if (dcccwin.window)
+	{
+		if (!passive)
+			mg_bring_tofront (dcccwin.window);
+		return TRUE;
+	}
+
+	dcccwin.window =
+			  mg_create_generic_tab ("DCCChat", _("XChat: DCC Chat List"),
+						FALSE, TRUE, dcc_chat_close_cb, NULL, 550, 180, &vbox, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (dcccwin.window), 3);
+	gtk_box_set_spacing (GTK_BOX (vbox), 3);
+
+	store = gtk_list_store_new (CN_COLUMNS, G_TYPE_STRING, G_TYPE_STRING,
+										 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
+										 G_TYPE_POINTER, GDK_TYPE_COLOR);
+	view = gtkutil_treeview_new (vbox, GTK_TREE_MODEL (store), NULL, -1);
+
+	dcc_add_column (view, CCOL_STATUS, CCOL_COLOR, _("Status"), FALSE);
+	dcc_add_column (view, CCOL_NICK,   CCOL_COLOR, _("Nick"), FALSE);
+	dcc_add_column (view, CCOL_RECV,   CCOL_COLOR, _("Recv"), TRUE);
+	dcc_add_column (view, CCOL_SENT,   CCOL_COLOR, _("Sent"), TRUE);
+	dcc_add_column (view, CCOL_START,  CCOL_COLOR, _("Start Time"), FALSE);
+
+	gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), 1), TRUE);
+	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE);
+
+	dcccwin.list = view;
+	dcccwin.store = store;
+	dcccwin.sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
+	gtk_tree_selection_set_mode (dcccwin.sel, GTK_SELECTION_MULTIPLE);
+
+	g_signal_connect (G_OBJECT (dcccwin.sel), "changed",
+							G_CALLBACK (dcc_chat_row_cb), NULL);
+	/* double click */
+	g_signal_connect (G_OBJECT (view), "row-activated",
+							G_CALLBACK (dcc_chat_dclick_cb), NULL);
+
+	bbox = gtk_hbutton_box_new ();
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD);
+	gtk_box_pack_end (GTK_BOX (vbox), bbox, FALSE, FALSE, 2);
+
+	dcccwin.abort_button = gtkutil_button (bbox, GTK_STOCK_CANCEL, 0, abort_chat_clicked, 0, _("Abort"));
+	dcccwin.accept_button = gtkutil_button (bbox, GTK_STOCK_APPLY, 0, accept_chat_clicked, 0, _("Accept"));
+	gtk_widget_set_sensitive (dcccwin.accept_button, FALSE);
+	gtk_widget_set_sensitive (dcccwin.abort_button, FALSE);
+
+	dcc_chat_fill_win ();
+	gtk_widget_show_all (dcccwin.window);
+
+	return FALSE;
+}
+
+void
+fe_dcc_add (struct DCC *dcc)
+{
+	switch (dcc->type)
+	{
+	case TYPE_RECV:
+		if (dccfwin.window && (view_mode & VIEW_DOWNLOAD))
+			dcc_append (dcc, dccfwin.store, TRUE);
+		break;
+
+	case TYPE_SEND:
+		if (dccfwin.window && (view_mode & VIEW_UPLOAD))
+			dcc_append (dcc, dccfwin.store, TRUE);
+		break;
+
+	default: /* chat */
+		if (dcccwin.window)
+			dcc_chat_append (dcc, dcccwin.store, TRUE);
+	}
+}
+
+void
+fe_dcc_update (struct DCC *dcc)
+{
+	switch (dcc->type)
+	{
+	case TYPE_SEND:
+		dcc_update_send (dcc);
+		break;
+
+	case TYPE_RECV:
+		dcc_update_recv (dcc);
+		break;
+
+	default:
+		dcc_update_chat (dcc);
+	}
+}
+
+void
+fe_dcc_remove (struct DCC *dcc)
+{
+	GtkTreeIter iter;
+
+	switch (dcc->type)
+	{
+	case TYPE_SEND:
+	case TYPE_RECV:
+		if (dccfwin.window)
+		{
+			if (dcc_find_row (dcc, GTK_TREE_MODEL (dccfwin.store), &iter, COL_DCC))
+				gtk_list_store_remove (dccfwin.store, &iter);
+		}
+		break;
+
+	default:	/* chat */
+		if (dcccwin.window)
+		{
+			if (dcc_find_row (dcc, GTK_TREE_MODEL (dcccwin.store), &iter, CCOL_DCC))
+				gtk_list_store_remove (dcccwin.store, &iter);
+		}
+		break;
+	}
+}
diff --git a/src/fe-gtk/editlist.c b/src/fe-gtk/editlist.c
new file mode 100644
index 00000000..5af67e32
--- /dev/null
+++ b/src/fe-gtk/editlist.c
@@ -0,0 +1,409 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "fe-gtk.h"
+
+#include <gtk/gtkstock.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkclist.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkvseparator.h>
+
+#include "../common/xchat.h"
+#include "../common/cfgfiles.h"
+#include "../common/xchatc.h"
+#include "../common/fe.h"
+#include "menu.h"
+#include "gtkutil.h"
+#include "maingui.h"
+#include "editlist.h"
+
+
+static GtkWidget *editlist_gui_entry_name;
+static GtkWidget *editlist_gui_entry_cmd;
+static GtkWidget *editlist_gui_window;
+static GtkWidget *editlist_gui_list;
+static GSList *editlist_list;
+static char *editlist_file;
+static char *editlist_help;
+
+
+
+static void
+editlist_gui_load (GtkWidget * listgad)
+{
+	gchar *nnew[2];
+	GSList *list = editlist_list;
+	struct popup *pop;
+
+	while (list)
+	{
+		pop = (struct popup *) list->data;
+		nnew[0] = pop->name;
+		nnew[1] = pop->cmd;
+		gtk_clist_append (GTK_CLIST (listgad), nnew);
+		list = list->next;
+	}
+}
+
+static void
+editlist_gui_row_unselected (GtkWidget * clist, gint row, gint column,
+									  GdkEventButton * even, gpointer none)
+{
+	gtk_entry_set_text (GTK_ENTRY (editlist_gui_entry_name), "");
+	gtk_entry_set_text (GTK_ENTRY (editlist_gui_entry_cmd), "");
+}
+
+static void
+editlist_gui_row_selected (GtkWidget * clist, gint row, gint column,
+									GdkEventButton * even, gpointer none)
+{
+	char *name, *cmd;
+
+	row = gtkutil_clist_selection (editlist_gui_list);
+	if (row != -1)
+	{
+		gtk_clist_get_text (GTK_CLIST (clist), row, 0, &name);
+		gtk_clist_get_text (GTK_CLIST (clist), row, 1, &cmd);
+
+		name = strdup (name);
+		cmd = strdup (cmd);
+
+		gtk_entry_set_text (GTK_ENTRY (editlist_gui_entry_name), name);
+		gtk_entry_set_text (GTK_ENTRY (editlist_gui_entry_cmd), cmd);
+
+		free (name);
+		free (cmd);
+	} else
+	{
+		editlist_gui_row_unselected (0, 0, 0, 0, 0);
+	}
+}
+
+static void
+editlist_gui_handle_cmd (GtkWidget * igad)
+{
+	int row;
+	const char *reply;
+
+	row = gtkutil_clist_selection (editlist_gui_list);
+	if (row != -1)
+	{
+		reply = gtk_entry_get_text (GTK_ENTRY (igad));
+		gtk_clist_set_text (GTK_CLIST (editlist_gui_list), row, 1, reply);
+	}
+}
+
+static void
+editlist_gui_handle_name (GtkWidget * igad)
+{
+	int row;
+	const char *ctcp;
+
+	row = gtkutil_clist_selection (editlist_gui_list);
+	if (row != -1)
+	{
+		ctcp = gtk_entry_get_text (GTK_ENTRY (igad));
+		gtk_clist_set_text (GTK_CLIST (editlist_gui_list), row, 0, ctcp);
+	}
+}
+
+static void
+editlist_gui_addnew (GtkWidget * igad)
+{
+	int i;
+	gchar *nnew[2];
+
+	nnew[0] = _("*NEW*");
+	nnew[1] = _("EDIT ME");
+
+	i = gtk_clist_append (GTK_CLIST (editlist_gui_list), nnew);
+	gtk_clist_select_row (GTK_CLIST (editlist_gui_list), i, 0);
+	gtk_clist_moveto (GTK_CLIST (editlist_gui_list), i, 0, 0.5, 0);
+}
+
+static void
+editlist_gui_delete (GtkWidget * igad)
+{
+	int row;
+
+	row = gtkutil_clist_selection (editlist_gui_list);
+	if (row != -1)
+	{
+		gtk_clist_unselect_all (GTK_CLIST (editlist_gui_list));
+		gtk_clist_remove (GTK_CLIST (editlist_gui_list), row);
+	}
+}
+
+static void
+editlist_gui_save (GtkWidget * igad)
+{
+	int fh, i = 0;
+	char buf[512];
+	char *a, *b;
+
+	fh = xchat_open_file (editlist_file, O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE);
+	if (fh != -1)
+	{
+		while (1)
+		{
+			if (!gtk_clist_get_text (GTK_CLIST (editlist_gui_list), i, 0, &a))
+				break;
+			gtk_clist_get_text (GTK_CLIST (editlist_gui_list), i, 1, &b);
+			snprintf (buf, sizeof (buf), "NAME %s\nCMD %s\n\n", a, b);
+			write (fh, buf, strlen (buf));
+			i++;
+		}
+		close (fh);
+		gtk_widget_destroy (editlist_gui_window);
+		if (editlist_list == replace_list)
+		{
+			list_free (&replace_list);
+			list_loadconf (editlist_file, &replace_list, 0);
+		} else if (editlist_list == popup_list)
+		{
+			list_free (&popup_list);
+			list_loadconf (editlist_file, &popup_list, 0);
+		} else if (editlist_list == button_list)
+		{
+			GSList *list = sess_list;
+			struct session *sess;
+			list_free (&button_list);
+			list_loadconf (editlist_file, &button_list, 0);
+			while (list)
+			{
+				sess = (struct session *) list->data;
+				fe_buttons_update (sess);
+				list = list->next;
+			}
+		} else if (editlist_list == dlgbutton_list)
+		{
+			GSList *list = sess_list;
+			struct session *sess;
+			list_free (&dlgbutton_list);
+			list_loadconf (editlist_file, &dlgbutton_list, 0);
+			while (list)
+			{
+				sess = (struct session *) list->data;
+				fe_dlgbuttons_update (sess);
+				list = list->next;
+			}
+		} else if (editlist_list == ctcp_list)
+		{
+			list_free (&ctcp_list);
+			list_loadconf (editlist_file, &ctcp_list, 0);
+		} else if (editlist_list == command_list)
+		{
+			list_free (&command_list);
+			list_loadconf (editlist_file, &command_list, 0);
+		} else if (editlist_list == usermenu_list)
+		{
+			list_free (&usermenu_list);
+			list_loadconf (editlist_file, &usermenu_list, 0);
+			usermenu_update ();
+		} else
+		{
+			list_free (&urlhandler_list);
+			list_loadconf (editlist_file, &urlhandler_list, 0);
+		}
+	}
+}
+
+static void
+editlist_gui_help (GtkWidget * igad)
+{
+/*	if (editlist_help)*/
+		fe_message (editlist_help, FE_MSG_INFO);
+}
+
+static void
+editlist_gui_sort (GtkWidget * igad)
+{
+	int row;
+
+	row = gtkutil_clist_selection (editlist_gui_list);
+	if (row != -1)
+		gtk_clist_unselect_row (GTK_CLIST (editlist_gui_list), row, 0);
+	gtk_clist_sort (GTK_CLIST (editlist_gui_list));
+}
+
+static void
+editlist_gui_movedown (GtkWidget * igad)
+{
+	int row;
+	char *temp;
+
+	row = gtkutil_clist_selection (editlist_gui_list);
+	if (row != -1)
+	{
+		if (!gtk_clist_get_text (GTK_CLIST (editlist_gui_list), row + 1, 0, &temp))
+			return;
+		gtk_clist_freeze (GTK_CLIST (editlist_gui_list));
+		gtk_clist_swap_rows (GTK_CLIST (editlist_gui_list), row, row + 1);
+		gtk_clist_thaw (GTK_CLIST (editlist_gui_list));
+		row++;
+		if (!gtk_clist_row_is_visible (GTK_CLIST (editlist_gui_list), row) !=
+			 GTK_VISIBILITY_FULL)
+			gtk_clist_moveto (GTK_CLIST (editlist_gui_list), row, 0, 0.9, 0);
+	}
+}
+
+static void
+editlist_gui_moveup (GtkWidget * igad)
+{
+	int row;
+
+	row = gtkutil_clist_selection (editlist_gui_list);
+	if (row != -1 && row > 0)
+	{
+		gtk_clist_freeze (GTK_CLIST (editlist_gui_list));
+		gtk_clist_swap_rows (GTK_CLIST (editlist_gui_list), row - 1, row);
+		gtk_clist_thaw (GTK_CLIST (editlist_gui_list));
+		row--;
+		if (gtk_clist_row_is_visible (GTK_CLIST (editlist_gui_list), row) !=
+			 GTK_VISIBILITY_FULL)
+			gtk_clist_moveto (GTK_CLIST (editlist_gui_list), row, 0, 0.1, 0);
+	}
+}
+
+static void
+editlist_gui_close (void)
+{
+	editlist_gui_window = 0;
+}
+
+void
+editlist_gui_open (char *title1, char *title2, GSList * list, char *title, char *wmclass,
+						 char *file, char *help)
+{
+	gchar *titles[2];
+	GtkWidget *vbox, *hbox, *button;
+
+	if (title1)
+	{
+		titles[0] = title1;
+		titles[1] = title2;
+	} else
+	{
+		titles[0] = _("Name");
+		titles[1] = _("Command");
+	}
+
+	if (editlist_gui_window)
+	{
+		mg_bring_tofront (editlist_gui_window);
+		return;
+	}
+
+	editlist_list = list;
+	editlist_file = file;
+	editlist_help = help;
+
+	editlist_gui_window =
+			  mg_create_generic_tab (wmclass, title, TRUE, FALSE,
+											 editlist_gui_close, NULL, 450, 250, &vbox, 0);
+
+	editlist_gui_list = gtkutil_clist_new (2, titles, vbox, GTK_POLICY_ALWAYS,
+														editlist_gui_row_selected, 0,
+														editlist_gui_row_unselected, 0,
+														GTK_SELECTION_BROWSE);
+	gtk_clist_set_column_width (GTK_CLIST (editlist_gui_list), 0, 90);
+
+	hbox = gtk_hbox_new (0, 2);
+	gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0);
+	gtk_widget_show (hbox);
+
+	button = gtkutil_button (hbox, GTK_STOCK_GO_UP, 0, editlist_gui_moveup,
+									 0, _("Move Up"));
+	gtk_widget_set_usize (button, 100, 0);
+
+	button = gtkutil_button (hbox, GTK_STOCK_GO_DOWN, 0, editlist_gui_movedown,
+									 0, _("Move Dn"));
+	gtk_widget_set_usize (button, 100, 0);
+
+	button = gtk_vseparator_new ();
+	gtk_container_add (GTK_CONTAINER (hbox), button);
+	gtk_widget_show (button);
+
+	button = gtkutil_button (hbox, GTK_STOCK_CANCEL, 0, gtkutil_destroy,
+									 editlist_gui_window, _("Cancel"));
+	gtk_widget_set_usize (button, 100, 0);
+
+	button = gtkutil_button (hbox, GTK_STOCK_SAVE, 0, editlist_gui_save,
+									 0, _("Save"));
+	gtk_widget_set_usize (button, 100, 0);
+
+	hbox = gtk_hbox_new (0, 2);
+	gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0);
+	gtk_widget_show (hbox);
+
+	button = gtkutil_button (hbox, GTK_STOCK_ADD, 0, editlist_gui_addnew,
+									 0, _("Add New"));
+	gtk_widget_set_usize (button, 100, 0);
+
+	button = gtkutil_button (hbox, GTK_STOCK_REMOVE, 0, editlist_gui_delete,
+									 0, _("Delete"));
+	gtk_widget_set_usize (button, 100, 0);
+
+	button = gtk_vseparator_new ();
+	gtk_container_add (GTK_CONTAINER (hbox), button);
+	gtk_widget_show (button);
+
+	button = gtkutil_button (hbox, GTK_STOCK_SORT_ASCENDING, 0, editlist_gui_sort,
+									 0, _("Sort"));
+	gtk_widget_set_usize (button, 100, 0);
+
+	button = gtkutil_button (hbox, GTK_STOCK_HELP, 0, editlist_gui_help,
+									 0, _("Help"));
+	gtk_widget_set_usize (button, 100, 0);
+
+	if (!help)
+		gtk_widget_set_sensitive (GTK_WIDGET (button), FALSE);
+
+	hbox = gtk_hbox_new (0, 2);
+	gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0);
+	gtk_widget_show (hbox);
+
+	editlist_gui_entry_name = gtk_entry_new_with_max_length (82);
+	gtk_widget_set_usize (editlist_gui_entry_name, 96, 0);
+	gtk_signal_connect (GTK_OBJECT (editlist_gui_entry_name), "changed",
+							  GTK_SIGNAL_FUNC (editlist_gui_handle_name), 0);
+	gtk_box_pack_start (GTK_BOX (hbox), editlist_gui_entry_name, 0, 0, 0);
+	gtk_widget_show (editlist_gui_entry_name);
+
+	editlist_gui_entry_cmd = gtk_entry_new_with_max_length (255);
+	gtk_signal_connect (GTK_OBJECT (editlist_gui_entry_cmd), "changed",
+							  GTK_SIGNAL_FUNC (editlist_gui_handle_cmd), 0);
+	gtk_container_add (GTK_CONTAINER (hbox), editlist_gui_entry_cmd);
+	gtk_widget_show (editlist_gui_entry_cmd);
+
+	hbox = gtk_hbox_new (0, 2);
+	gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0);
+	gtk_widget_show (hbox);
+
+	editlist_gui_load (editlist_gui_list);
+
+	gtk_widget_show (editlist_gui_window);
+}
diff --git a/src/fe-gtk/editlist.h b/src/fe-gtk/editlist.h
new file mode 100644
index 00000000..f17cc2e0
--- /dev/null
+++ b/src/fe-gtk/editlist.h
@@ -0,0 +1 @@
+void editlist_gui_open (char *title1, char *title2, GSList * list, char *title, char *wmclass, char *file, char *help);
diff --git a/src/fe-gtk/fe-gtk.c b/src/fe-gtk/fe-gtk.c
new file mode 100644
index 00000000..5efcaeec
--- /dev/null
+++ b/src/fe-gtk/fe-gtk.c
@@ -0,0 +1,1063 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkmain.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkprogressbar.h>
+#include <gtk/gtkbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtktogglebutton.h>
+#include <gtk/gtkmessagedialog.h>
+#include <gtk/gtkversion.h>
+
+#include "../common/xchat.h"
+#include "../common/fe.h"
+#include "../common/util.h"
+#include "../common/text.h"
+#include "../common/cfgfiles.h"
+#include "../common/xchatc.h"
+#include "../common/plugin.h"
+#include "gtkutil.h"
+#include "maingui.h"
+#include "pixmaps.h"
+#include "joind.h"
+#include "xtext.h"
+#include "palette.h"
+#include "menu.h"
+#include "notifygui.h"
+#include "textgui.h"
+#include "fkeys.h"
+#include "plugin-tray.h"
+#include "urlgrab.h"
+
+#ifdef USE_XLIB
+#include <gdk/gdkx.h>
+#include <gtk/gtkinvisible.h>
+#endif
+
+#ifdef USE_GTKSPELL
+#include <gtk/gtktextview.h>
+#endif
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+GdkPixmap *channelwin_pix;
+
+
+#ifdef USE_XLIB
+
+static void
+redraw_trans_xtexts (void)
+{
+	GSList *list = sess_list;
+	session *sess;
+	int done_main = FALSE;
+
+	while (list)
+	{
+		sess = list->data;
+		if (GTK_XTEXT (sess->gui->xtext)->transparent)
+		{
+			if (!sess->gui->is_tab || !done_main)
+				gtk_xtext_refresh (GTK_XTEXT (sess->gui->xtext), 1);
+			if (sess->gui->is_tab)
+				done_main = TRUE;
+		}
+		list = list->next;
+	}
+}
+
+static GdkFilterReturn
+root_event_cb (GdkXEvent *xev, GdkEventProperty *event, gpointer data)
+{
+	static Atom at = None;
+	XEvent *xevent = (XEvent *)xev;
+
+	if (xevent->type == PropertyNotify)
+	{
+		if (at == None)
+			at = XInternAtom (xevent->xproperty.display, "_XROOTPMAP_ID", True);
+
+		if (at == xevent->xproperty.atom)
+			redraw_trans_xtexts ();
+	}
+
+	return GDK_FILTER_CONTINUE;
+}
+
+#endif
+
+/* === command-line parameter parsing : requires glib 2.6 === */
+
+static char *arg_cfgdir = NULL;
+static gint arg_show_autoload = 0;
+static gint arg_show_config = 0;
+static gint arg_show_version = 0;
+static gint arg_minimize = 0;
+
+static const GOptionEntry gopt_entries[] = 
+{
+ {"no-auto",	'a', 0, G_OPTION_ARG_NONE,	&arg_dont_autoconnect, N_("Don't auto connect to servers"), NULL},
+ {"cfgdir",	'd', 0, G_OPTION_ARG_STRING,	&arg_cfgdir, N_("Use a different config directory"), "PATH"},
+ {"no-plugins",	'n', 0, G_OPTION_ARG_NONE,	&arg_skip_plugins, N_("Don't auto load any plugins"), NULL},
+ {"plugindir",	'p', 0, G_OPTION_ARG_NONE,	&arg_show_autoload, N_("Show plugin auto-load directory"), NULL},
+ {"configdir",	'u', 0, G_OPTION_ARG_NONE,	&arg_show_config, N_("Show user config directory"), NULL},
+ {"url",	 0,  0, G_OPTION_ARG_STRING,	&arg_url, N_("Open an irc://server:port/channel URL"), "URL"},
+#ifndef WIN32	/* uses DBUS */
+ {"command",	'c', 0, G_OPTION_ARG_STRING,	&arg_command, N_("Execute command:"), "COMMAND"},
+ {"existing",	'e', 0, G_OPTION_ARG_NONE,	&arg_existing, N_("Open URL or execute command in an existing XChat"), NULL},
+#endif
+ {"minimize",	 0,  0, G_OPTION_ARG_INT,	&arg_minimize, N_("Begin minimized. Level 0=Normal 1=Iconified 2=Tray"), N_("level")},
+ {"version",	'v', 0, G_OPTION_ARG_NONE,	&arg_show_version, N_("Show version information"), NULL},
+ {NULL}
+};
+
+int
+fe_args (int argc, char *argv[])
+{
+	GError *error = NULL;
+	GOptionContext *context;
+
+#ifdef ENABLE_NLS
+	bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+	textdomain (GETTEXT_PACKAGE);
+#endif
+
+	context = g_option_context_new (NULL);
+	g_option_context_add_main_entries (context, gopt_entries, GETTEXT_PACKAGE);
+	g_option_context_add_group (context, gtk_get_option_group (FALSE));
+	g_option_context_parse (context, &argc, &argv, &error);
+
+	if (error)
+	{
+		if (error->message)
+			printf ("%s\n", error->message);
+		return 1;
+	}
+
+	g_option_context_free (context);
+
+	if (arg_show_version)
+	{
+		printf (PACKAGE_TARNAME" "PACKAGE_VERSION"\n");
+		return 0;
+	}
+
+	if (arg_show_autoload)
+	{
+#ifdef WIN32
+		/* see the chdir() below */
+		char *sl, *exe = strdup (argv[0]);
+		sl = strrchr (exe, '\\');
+		if (sl)
+		{
+			*sl = 0;
+			printf ("%s\\plugins\n", exe);
+		}
+#else
+		printf ("%s\n", XCHATLIBDIR"/plugins");
+#endif
+		return 0;
+	}
+
+	if (arg_show_config)
+	{
+		printf ("%s\n", get_xdir_fs ());
+		return 0;
+	}
+
+#ifdef WIN32
+	/* this is mainly for irc:// URL handling. When windows calls us from */
+	/* I.E, it doesn't give an option of "Start in" directory, like short */
+	/* cuts can. So we have to set the current dir manually, to the path  */
+	/* of the exe. */
+	{
+		char *tmp = strdup (argv[0]);
+		char *sl;
+
+		sl = strrchr (tmp, '\\');
+		if (sl)
+		{
+			*sl = 0;
+			chdir (tmp);
+		}
+		free (tmp);
+	}
+#endif
+
+	if (arg_cfgdir)	/* we want filesystem encoding */
+	{
+		xdir_fs = strdup (arg_cfgdir);
+		if (xdir_fs[strlen (xdir_fs) - 1] == '/')
+			xdir_fs[strlen (xdir_fs) - 1] = 0;
+		g_free (arg_cfgdir);
+	}
+
+	gtk_init (&argc, &argv);
+
+#ifdef USE_XLIB
+	gdk_window_set_events (gdk_get_default_root_window (), GDK_PROPERTY_CHANGE_MASK);
+	gdk_window_add_filter (gdk_get_default_root_window (),
+								  (GdkFilterFunc)root_event_cb, NULL);
+#endif
+
+	return -1;
+}
+
+const char cursor_color_rc[] =
+	"style \"xc-ib-st\""
+	"{"
+#ifdef USE_GTKSPELL
+		"GtkTextView::cursor-color=\"#%02x%02x%02x\""
+#else
+		"GtkEntry::cursor-color=\"#%02x%02x%02x\""
+#endif
+	"}"
+	"widget \"*.xchat-inputbox\" style : application \"xc-ib-st\"";
+
+GtkStyle *
+create_input_style (GtkStyle *style)
+{
+	char buf[256];
+	static int done_rc = FALSE;
+
+	pango_font_description_free (style->font_desc);
+	style->font_desc = pango_font_description_from_string (prefs.font_normal);
+
+	/* fall back */
+	if (pango_font_description_get_size (style->font_desc) == 0)
+	{
+		snprintf (buf, sizeof (buf), _("Failed to open font:\n\n%s"), prefs.font_normal);
+		fe_message (buf, FE_MSG_ERROR);
+		pango_font_description_free (style->font_desc);
+		style->font_desc = pango_font_description_from_string ("sans 11");
+	}
+
+	if (prefs.style_inputbox && !done_rc)
+	{
+		done_rc = TRUE;
+		sprintf (buf, cursor_color_rc, (colors[COL_FG].red >> 8),
+			(colors[COL_FG].green >> 8), (colors[COL_FG].blue >> 8));
+		gtk_rc_parse_string (buf);
+	}
+
+	style->bg[GTK_STATE_NORMAL] = colors[COL_FG];
+	style->base[GTK_STATE_NORMAL] = colors[COL_BG];
+	style->text[GTK_STATE_NORMAL] = colors[COL_FG];
+
+	return style;
+}
+
+void
+fe_init (void)
+{
+	palette_load ();
+	key_init ();
+	pixmaps_init ();
+
+	channelwin_pix = pixmap_load_from_file (prefs.background);
+	input_style = create_input_style (gtk_style_new ());
+}
+
+void
+fe_main (void)
+{
+	gtk_main ();
+
+	/* sleep for 3 seconds so any QUIT messages are not lost. The  */
+	/* GUI is closed at this point, so the user doesn't even know! */
+	if (prefs.wait_on_exit)
+		sleep (3);
+}
+
+void
+fe_cleanup (void)
+{
+	/* it's saved when pressing OK in setup.c */
+	/*palette_save ();*/
+}
+
+void
+fe_exit (void)
+{
+	gtk_main_quit ();
+}
+
+int
+fe_timeout_add (int interval, void *callback, void *userdata)
+{
+	return g_timeout_add (interval, (GSourceFunc) callback, userdata);
+}
+
+void
+fe_timeout_remove (int tag)
+{
+	g_source_remove (tag);
+}
+
+#ifdef WIN32
+
+static void
+log_handler (const gchar   *log_domain,
+		       GLogLevelFlags log_level,
+		       const gchar   *message,
+		       gpointer	      unused_data)
+{
+	session *sess;
+
+	if (getenv ("XCHAT_WARNING_IGNORE"))
+		return;
+
+	sess = find_dialog (serv_list->data, "(warnings)");
+	if (!sess)
+		sess = new_ircwindow (serv_list->data, "(warnings)", SESS_DIALOG, 0);
+
+	PrintTextf (sess, "%s\t%s\n", log_domain, message);
+	if (getenv ("XCHAT_WARNING_ABORT"))
+		abort ();
+}
+
+#endif
+
+/* install tray stuff */
+
+static int
+fe_idle (gpointer data)
+{
+	session *sess = sess_list->data;
+
+	plugin_add (sess, NULL, NULL, tray_plugin_init, tray_plugin_deinit, NULL, FALSE);
+
+	if (arg_minimize == 1)
+		gtk_window_iconify (GTK_WINDOW (sess->gui->window));
+	else if (arg_minimize == 2)
+		tray_toggle_visibility (FALSE);
+
+	return 0;
+}
+
+void
+fe_new_window (session *sess, int focus)
+{
+	int tab = FALSE;
+
+	if (sess->type == SESS_DIALOG)
+	{
+		if (prefs.privmsgtab)
+			tab = TRUE;
+	} else
+	{
+		if (prefs.tabchannels)
+			tab = TRUE;
+	}
+
+	mg_changui_new (sess, NULL, tab, focus);
+
+#ifdef WIN32
+	g_log_set_handler ("GLib", G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING, (GLogFunc)log_handler, 0);
+	g_log_set_handler ("GLib-GObject", G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING, (GLogFunc)log_handler, 0);
+	g_log_set_handler ("Gdk", G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING, (GLogFunc)log_handler, 0);
+	g_log_set_handler ("Gtk", G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING, (GLogFunc)log_handler, 0);
+#endif
+
+	if (!sess_list->next)
+		g_idle_add (fe_idle, NULL);
+}
+
+void
+fe_new_server (struct server *serv)
+{
+	serv->gui = malloc (sizeof (struct server_gui));
+	memset (serv->gui, 0, sizeof (struct server_gui));
+}
+
+void
+fe_message (char *msg, int flags)
+{
+	GtkWidget *dialog;
+	int type = GTK_MESSAGE_WARNING;
+
+	if (flags & FE_MSG_ERROR)
+		type = GTK_MESSAGE_ERROR;
+	if (flags & FE_MSG_INFO)
+		type = GTK_MESSAGE_INFO;
+
+	dialog = gtk_message_dialog_new (GTK_WINDOW (parent_window), 0, type,
+												GTK_BUTTONS_OK, "%s", msg);
+	if (flags & FE_MSG_MARKUP)
+		gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dialog), msg);
+	g_signal_connect (G_OBJECT (dialog), "response",
+							G_CALLBACK (gtk_widget_destroy), 0);
+	gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+	gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
+	gtk_widget_show (dialog);
+
+	if (flags & FE_MSG_WAIT)
+		gtk_dialog_run (GTK_DIALOG (dialog));
+}
+
+void
+fe_idle_add (void *func, void *data)
+{
+	g_idle_add (func, data);
+}
+
+void
+fe_input_remove (int tag)
+{
+	g_source_remove (tag);
+}
+
+int
+fe_input_add (int sok, int flags, void *func, void *data)
+{
+	int tag, type = 0;
+	GIOChannel *channel;
+
+#ifdef WIN32
+	if (flags & FIA_FD)
+		channel = g_io_channel_win32_new_fd (sok);
+	else
+		channel = g_io_channel_win32_new_socket (sok);
+#else
+	channel = g_io_channel_unix_new (sok);
+#endif
+
+	if (flags & FIA_READ)
+		type |= G_IO_IN | G_IO_HUP | G_IO_ERR;
+	if (flags & FIA_WRITE)
+		type |= G_IO_OUT | G_IO_ERR;
+	if (flags & FIA_EX)
+		type |= G_IO_PRI;
+
+	tag = g_io_add_watch (channel, type, (GIOFunc) func, data);
+	g_io_channel_unref (channel);
+
+	return tag;
+}
+
+void
+fe_set_topic (session *sess, char *topic, char *stripped_topic)
+{
+	if (!sess->gui->is_tab || sess == current_tab)
+	{
+		gtk_entry_set_text (GTK_ENTRY (sess->gui->topic_entry), stripped_topic);
+		mg_set_topic_tip (sess);
+	} else
+	{
+		if (sess->res->topic_text)
+			free (sess->res->topic_text);
+		sess->res->topic_text = strdup (stripped_topic);
+	}
+}
+
+void
+fe_set_hilight (struct session *sess)
+{
+	if (sess->gui->is_tab)
+		fe_set_tab_color (sess, 3);	/* set tab to blue */
+
+	if (prefs.input_flash_hilight)
+		fe_flash_window (sess); /* taskbar flash */
+}
+
+static void
+fe_update_mode_entry (session *sess, GtkWidget *entry, char **text, char *new_text)
+{
+	if (!sess->gui->is_tab || sess == current_tab)
+	{
+		if (sess->gui->flag_wid[0])	/* channel mode buttons enabled? */
+			gtk_entry_set_text (GTK_ENTRY (entry), new_text);
+	} else
+	{
+		if (sess->gui->is_tab)
+		{
+			if (*text)
+				free (*text);
+			*text = strdup (new_text);
+		}
+	}
+}
+
+void
+fe_update_channel_key (struct session *sess)
+{
+	fe_update_mode_entry (sess, sess->gui->key_entry,
+								 &sess->res->key_text, sess->channelkey);
+	fe_set_title (sess);
+}
+
+void
+fe_update_channel_limit (struct session *sess)
+{
+	char tmp[16];
+
+	sprintf (tmp, "%d", sess->limit);
+	fe_update_mode_entry (sess, sess->gui->limit_entry,
+								 &sess->res->limit_text, tmp);
+	fe_set_title (sess);
+}
+
+int
+fe_is_chanwindow (struct server *serv)
+{
+	if (!serv->gui->chanlist_window)
+		return 0;
+	return 1;
+}
+
+int
+fe_is_banwindow (struct session *sess)
+{
+   if (!sess->res->banlist_window)
+     return 0;
+   return 1;
+}
+
+void
+fe_notify_update (char *name)
+{
+	if (!name)
+		notify_gui_update ();
+}
+
+void
+fe_text_clear (struct session *sess, int lines)
+{
+	gtk_xtext_clear (sess->res->buffer, lines);
+}
+
+void
+fe_close_window (struct session *sess)
+{
+	if (sess->gui->is_tab)
+		mg_tab_close (sess);
+	else
+		gtk_widget_destroy (sess->gui->window);
+}
+
+void
+fe_progressbar_start (session *sess)
+{
+	if (!sess->gui->is_tab || current_tab == sess)
+	/* if it's the focused tab, create it for real! */
+		mg_progressbar_create (sess->gui);
+	else
+	/* otherwise just remember to create on when it gets focused */
+		sess->res->c_graph = TRUE;
+}
+
+void
+fe_progressbar_end (server *serv)
+{
+	GSList *list = sess_list;
+	session *sess;
+
+	while (list)				  /* check all windows that use this server and  *
+									   * remove the connecting graph, if it has one. */
+	{
+		sess = list->data;
+		if (sess->server == serv)
+		{
+			if (sess->gui->bar)
+				mg_progressbar_destroy (sess->gui);
+			sess->res->c_graph = FALSE;
+		}
+		list = list->next;
+	}
+}
+
+void
+fe_print_text (struct session *sess, char *text, time_t stamp)
+{
+	PrintTextRaw (sess->res->buffer, (unsigned char *)text, prefs.indent_nicks, stamp);
+
+	if (!sess->new_data && sess != current_tab &&
+		 sess->gui->is_tab && !sess->nick_said && stamp == 0)
+	{
+		sess->new_data = TRUE;
+		if (sess->msg_said)
+			fe_set_tab_color (sess, 2);
+		else
+			fe_set_tab_color (sess, 1);
+	}
+}
+
+void
+fe_beep (void)
+{
+	gdk_beep ();
+}
+
+#ifndef WIN32
+static int
+lastlog_regex_cmp (char *a, regex_t *reg)
+{
+	return !regexec (reg, a, 1, NULL, REG_NOTBOL);
+}
+#endif
+
+void
+fe_lastlog (session *sess, session *lastlog_sess, char *sstr, gboolean regexp)
+{
+#ifndef WIN32
+	regex_t reg;
+#endif
+
+	if (gtk_xtext_is_empty (sess->res->buffer))
+	{
+		PrintText (lastlog_sess, _("Search buffer is empty.\n"));
+		return;
+	}
+
+	if (!regexp)
+	{
+		gtk_xtext_lastlog (lastlog_sess->res->buffer, sess->res->buffer,
+								 (void *) nocasestrstr, sstr);
+		return;
+	}
+
+#ifndef WIN32
+	if (regcomp (&reg, sstr, REG_ICASE | REG_EXTENDED | REG_NOSUB) == 0)
+	{
+		gtk_xtext_lastlog (lastlog_sess->res->buffer, sess->res->buffer,
+								 (void *) lastlog_regex_cmp, &reg);
+		regfree (&reg);
+	}
+#endif
+}
+
+void
+fe_set_lag (server *serv, int lag)
+{
+	GSList *list = sess_list;
+	session *sess;
+	gdouble per;
+	char lagtext[64];
+	char lagtip[128];
+	unsigned long nowtim;
+
+	if (lag == -1)
+	{
+		if (!serv->lag_sent)
+			return;
+		nowtim = make_ping_time ();
+		lag = (nowtim - serv->lag_sent) / 100000;
+	}
+
+	per = (double)((double)lag / (double)10);
+	if (per > 1.0)
+		per = 1.0;
+
+	snprintf (lagtext, sizeof (lagtext) - 1, "%s%d.%ds",
+				 serv->lag_sent ? "+" : "", lag / 10, lag % 10);
+	snprintf (lagtip, sizeof (lagtip) - 1, "Lag: %s%d.%d seconds",
+				 serv->lag_sent ? "+" : "", lag / 10, lag % 10);
+
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv)
+		{
+			if (sess->res->lag_tip)
+				free (sess->res->lag_tip);
+			sess->res->lag_tip = strdup (lagtip);
+
+			if (!sess->gui->is_tab || current_tab == sess)
+			{
+				if (sess->gui->lagometer)
+				{
+					gtk_progress_bar_set_fraction ((GtkProgressBar *) sess->gui->lagometer, per);
+					add_tip (sess->gui->lagometer->parent, lagtip);
+				}
+				if (sess->gui->laginfo)
+					gtk_label_set_text ((GtkLabel *) sess->gui->laginfo, lagtext);
+			} else
+			{
+				sess->res->lag_value = per;
+				if (sess->res->lag_text)
+					free (sess->res->lag_text);
+				sess->res->lag_text = strdup (lagtext);
+			}
+		}
+		list = list->next;
+	}
+}
+
+void
+fe_set_throttle (server *serv)
+{
+	GSList *list = sess_list;
+	struct session *sess;
+	float per;
+	char tbuf[96];
+	char tip[160];
+
+	per = (float) serv->sendq_len / 1024.0;
+	if (per > 1.0)
+		per = 1.0;
+
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv)
+		{
+			snprintf (tbuf, sizeof (tbuf) - 1, _("%d bytes"), serv->sendq_len);
+			snprintf (tip, sizeof (tip) - 1, _("Network send queue: %d bytes"), serv->sendq_len);
+
+			if (sess->res->queue_tip)
+				free (sess->res->queue_tip);
+			sess->res->queue_tip = strdup (tip);
+
+			if (!sess->gui->is_tab || current_tab == sess)
+			{
+				if (sess->gui->throttlemeter)
+				{
+					gtk_progress_bar_set_fraction ((GtkProgressBar *) sess->gui->throttlemeter, per);
+					add_tip (sess->gui->throttlemeter->parent, tip);
+				}
+				if (sess->gui->throttleinfo)
+					gtk_label_set_text ((GtkLabel *) sess->gui->throttleinfo, tbuf);
+			} else
+			{
+				sess->res->queue_value = per;
+				if (sess->res->queue_text)
+					free (sess->res->queue_text);
+				sess->res->queue_text = strdup (tbuf);
+			}
+		}
+		list = list->next;
+	}
+}
+
+void
+fe_ctrl_gui (session *sess, fe_gui_action action, int arg)
+{
+	switch (action)
+	{
+	case FE_GUI_HIDE:
+		gtk_widget_hide (sess->gui->window); break;
+	case FE_GUI_SHOW:
+		gtk_widget_show (sess->gui->window);
+		gtk_window_present (GTK_WINDOW (sess->gui->window));
+		break;
+	case FE_GUI_FOCUS:
+		mg_bring_tofront_sess (sess); break;
+	case FE_GUI_FLASH:
+		fe_flash_window (sess); break;
+	case FE_GUI_COLOR:
+		fe_set_tab_color (sess, arg); break;
+	case FE_GUI_ICONIFY:
+		gtk_window_iconify (GTK_WINDOW (sess->gui->window)); break;
+	case FE_GUI_MENU:
+		menu_bar_toggle ();	/* toggle menubar on/off */
+		break;
+	case FE_GUI_ATTACH:
+		mg_detach (sess, arg);	/* arg: 0=toggle 1=detach 2=attach */
+		break;
+	case FE_GUI_APPLY:
+		setup_apply_real (TRUE, TRUE);
+	}
+}
+
+static void
+dcc_saveas_cb (struct DCC *dcc, char *file)
+{
+	if (is_dcc (dcc))
+	{
+		if (dcc->dccstat == STAT_QUEUED)
+		{
+			if (file)
+				dcc_get_with_destfile (dcc, file);
+			else if (dcc->resume_sent == 0)
+				dcc_abort (dcc->serv->front_session, dcc);
+		}
+	}
+}
+
+void
+fe_confirm (const char *message, void (*yesproc)(void *), void (*noproc)(void *), void *ud)
+{
+	/* warning, assuming fe_confirm is used by DCC only! */
+	struct DCC *dcc = ud;
+
+	if (dcc->file)
+		gtkutil_file_req (message, dcc_saveas_cb, ud, dcc->file,
+								FRF_WRITE|FRF_FILTERISINITIAL|FRF_NOASKOVERWRITE);
+}
+
+int
+fe_gui_info (session *sess, int info_type)
+{
+	switch (info_type)
+	{
+	case 0:	/* window status */
+#if GTK_CHECK_VERSION(2,20,0)
+		if (!gtk_widget_get_visible (GTK_WIDGET (sess->gui->window)))
+#else
+		if (!GTK_WIDGET_VISIBLE (GTK_WIDGET (sess->gui->window)))
+#endif
+			return 2;	/* hidden (iconified or systray) */
+#if GTK_CHECK_VERSION(2,4,0)
+		if (gtk_window_is_active (GTK_WINDOW (sess->gui->window)))
+#else
+#if GTK_CHECK_VERSION(2,2,0)
+		if (GTK_WINDOW (sess->gui->window)->is_active)
+#endif
+#endif
+			return 1;	/* active/focused */
+
+		return 0;		/* normal (no keyboard focus or behind a window) */
+	}
+
+	return -1;
+}
+
+void *
+fe_gui_info_ptr (session *sess, int info_type)
+{
+	switch (info_type)
+	{
+	case 0:	/* native window pointer (for plugins) */
+#ifdef WIN32
+		return GDK_WINDOW_HWND (sess->gui->window->window);
+#else
+		return sess->gui->window;
+#endif
+		break;
+
+	case 1:	/* GtkWindow * (for plugins) */
+		return sess->gui->window;
+	}
+	return NULL;
+}
+
+char *
+fe_get_inputbox_contents (session *sess)
+{
+	/* not the current tab */
+	if (sess->res->input_text)
+		return sess->res->input_text;
+
+	/* current focused tab */
+	return SPELL_ENTRY_GET_TEXT (sess->gui->input_box);
+}
+
+int
+fe_get_inputbox_cursor (session *sess)
+{
+	/* not the current tab (we don't remember the cursor pos) */
+	if (sess->res->input_text)
+		return 0;
+
+	/* current focused tab */
+	return SPELL_ENTRY_GET_POS (sess->gui->input_box);
+}
+
+void
+fe_set_inputbox_cursor (session *sess, int delta, int pos)
+{
+	if (!sess->gui->is_tab || sess == current_tab)
+	{
+		if (delta)
+			pos += SPELL_ENTRY_GET_POS (sess->gui->input_box);
+		SPELL_ENTRY_SET_POS (sess->gui->input_box, pos);
+	} else
+	{
+		/* we don't support changing non-front tabs yet */
+	}
+}
+
+void
+fe_set_inputbox_contents (session *sess, char *text)
+{
+	if (!sess->gui->is_tab || sess == current_tab)
+	{
+		SPELL_ENTRY_SET_TEXT (sess->gui->input_box, text);
+	} else
+	{
+		if (sess->res->input_text)
+			free (sess->res->input_text);
+		sess->res->input_text = strdup (text);
+	}
+}
+
+#ifndef WIN32
+
+static gboolean
+try_browser (const char *browser, const char *arg, const char *url)
+{
+	const char *argv[4];
+	char *path;
+
+	path = g_find_program_in_path (browser);
+	if (!path)
+		return 0;
+
+	argv[0] = path;
+	argv[1] = url;
+	argv[2] = NULL;
+	if (arg)
+	{
+		argv[1] = arg;
+		argv[2] = url;
+		argv[3] = NULL;
+	}
+	xchat_execv (argv);
+	g_free (path);
+	return 1;
+}
+
+#endif
+
+static void
+fe_open_url_inner (const char *url)
+{
+#ifdef WIN32
+	ShellExecute (0, "open", url, NULL, NULL, SW_SHOWNORMAL);
+#else
+	/* universal desktop URL opener (from xdg-utils). Supports gnome,kde,xfce4. */
+	if (try_browser ("xdg-open", NULL, url))
+		return;
+
+	/* try to detect GNOME */
+	if (g_getenv ("GNOME_DESKTOP_SESSION_ID"))
+	{
+		if (try_browser ("gnome-open", NULL, url)) /* Gnome 2.4+ has this */
+			return;
+	}
+
+	/* try to detect KDE */
+	if (g_getenv ("KDE_FULL_SESSION"))
+	{
+		if (try_browser ("kfmclient", "exec", url))
+			return;
+	}
+
+	/* everything failed, what now? just try firefox */
+	if (try_browser ("firefox", NULL, url))
+		return;
+
+	/* fresh out of ideas... */
+	try_browser ("mozilla", NULL, url);
+#endif
+}
+
+static void
+fe_open_url_locale (const char *url)
+{
+#ifndef WIN32
+	if (url[0] != '/' && strchr (url, ':') == NULL)
+	{
+		url = g_strdup_printf ("http://%s", url);
+		fe_open_url_inner (url);
+		g_free ((char *)url);
+		return;
+	}
+#endif
+	fe_open_url_inner (url);
+}
+
+void
+fe_open_url (const char *url)
+{
+	char *loc;
+
+	if (prefs.utf8_locale)
+	{
+		fe_open_url_locale (url);
+		return;
+	}
+
+	/* the OS expects it in "locale" encoding. This makes it work on
+	   unix systems that use ISO-8859-x and Win32. */
+	loc = g_locale_from_utf8 (url, -1, 0, 0, 0);
+	if (loc)
+	{
+		fe_open_url_locale (loc);
+		g_free (loc);
+	}
+}
+
+void
+fe_server_event (server *serv, int type, int arg)
+{
+	GSList *list = sess_list;
+	session *sess;
+
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv && (current_tab == sess || !sess->gui->is_tab))
+		{
+			session_gui *gui = sess->gui;
+
+			switch (type)
+			{
+			case FE_SE_CONNECTING:	/* connecting in progress */
+			case FE_SE_RECONDELAY:	/* reconnect delay begun */
+				/* enable Disconnect item */
+				gtk_widget_set_sensitive (gui->menu_item[MENU_ID_DISCONNECT], 1);
+				break;
+
+			case FE_SE_CONNECT:
+				/* enable Disconnect and Away menu items */
+				gtk_widget_set_sensitive (gui->menu_item[MENU_ID_AWAY], 1);
+				gtk_widget_set_sensitive (gui->menu_item[MENU_ID_DISCONNECT], 1);
+				break;
+
+			case FE_SE_LOGGEDIN:	/* end of MOTD */
+				gtk_widget_set_sensitive (gui->menu_item[MENU_ID_JOIN], 1);
+				/* if number of auto-join channels is zero, open joind */
+				if (arg == 0)
+					joind_open (serv);
+				break;
+
+			case FE_SE_DISCONNECT:
+				/* disable Disconnect and Away menu items */
+				gtk_widget_set_sensitive (gui->menu_item[MENU_ID_AWAY], 0);
+				gtk_widget_set_sensitive (gui->menu_item[MENU_ID_DISCONNECT], 0);
+				gtk_widget_set_sensitive (gui->menu_item[MENU_ID_JOIN], 0);
+				/* close the join-dialog, if one exists */
+				joind_close (serv);
+			}
+		}
+		list = list->next;
+	}
+}
+
+void
+fe_get_file (const char *title, char *initial,
+				 void (*callback) (void *userdata, char *file), void *userdata,
+				 int flags)
+				
+{
+	/* OK: Call callback once per file, then once more with file=NULL. */
+	/* CANCEL: Call callback once with file=NULL. */
+	gtkutil_file_req (title, callback, userdata, initial, flags | FRF_FILTERISINITIAL);
+}
diff --git a/src/fe-gtk/fe-gtk.h b/src/fe-gtk/fe-gtk.h
new file mode 100644
index 00000000..12516259
--- /dev/null
+++ b/src/fe-gtk/fe-gtk.h
@@ -0,0 +1,197 @@
+#include "../../config.h"
+
+#ifdef WIN32
+/* If you're compiling this for Windows, your release is un-official
+ * and not condoned. Please don't use the XChat name. Make up your
+ * own name! */
+#define DISPLAY_NAME "XChat-Unofficial"
+#else
+#define DISPLAY_NAME "XChat"
+#endif
+
+#ifndef WIN32
+#include <sys/types.h>
+#include <regex.h>
+#endif
+
+#if defined(ENABLE_NLS) && !defined(_)
+#  include <libintl.h>
+#  define _(x) gettext(x)
+#  ifdef gettext_noop
+#    define N_(String) gettext_noop (String)
+#  else
+#    define N_(String) (String)
+#  endif
+#endif
+#if !defined(ENABLE_NLS) && defined(_)
+#  undef _
+#  define N_(String) (String)
+#  define _(x) (x)
+#endif
+
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkcontainer.h>
+#include <gtk/gtksignal.h>
+
+#undef gtk_signal_connect
+#define gtk_signal_connect g_signal_connect
+
+#define flag_t flag_wid[0]
+#define flag_n flag_wid[1]
+#define flag_s flag_wid[2]
+#define flag_i flag_wid[3]
+#define flag_p flag_wid[4]
+#define flag_m flag_wid[5]
+#define flag_l flag_wid[6]
+#define flag_k flag_wid[7]
+#define flag_b flag_wid[8]
+#define NUM_FLAG_WIDS 9
+
+struct server_gui
+{
+	GtkWidget *rawlog_window;
+	GtkWidget *rawlog_textlist;
+
+	/* join dialog */
+	GtkWidget *joind_win;
+	GtkWidget *joind_entry;
+	GtkWidget *joind_radio1;
+	GtkWidget *joind_radio2;
+	GtkWidget *joind_check;
+
+	/* chanlist variables */
+	GtkWidget *chanlist_wild;		/* GtkEntry */
+	GtkWidget *chanlist_window;
+	GtkWidget *chanlist_list;
+	GtkWidget *chanlist_label;
+	GtkWidget *chanlist_min_spin;	/* minusers GtkSpinButton */
+	GtkWidget *chanlist_refresh;	/* buttons */
+	GtkWidget *chanlist_join;
+	GtkWidget *chanlist_savelist;
+	GtkWidget *chanlist_search;
+
+	GSList *chanlist_data_stored_rows;	/* stored list so it can be resorted  */
+	GSList *chanlist_pending_rows;
+	gint chanlist_tag;
+	gint chanlist_flash_tag;
+
+	gboolean chanlist_match_wants_channel;	/* match in channel name */
+	gboolean chanlist_match_wants_topic;	/* match in topic */
+
+#ifndef WIN32
+	regex_t chanlist_match_regex;	/* compiled regular expression here */
+	unsigned int have_regex;
+#endif
+
+	guint chanlist_users_found_count;	/* users total for all channels */
+	guint chanlist_users_shown_count;	/* users total for displayed channels */
+	guint chanlist_channels_found_count;	/* channel total for /LIST operation */
+	guint chanlist_channels_shown_count;	/* total number of displayed 
+														   channels */
+
+	int chanlist_maxusers;
+	int chanlist_minusers;
+	int chanlist_minusers_downloaded;	/* used by LIST IRC command */
+	int chanlist_search_type;		/* 0=simple 1=pattern/wildcard 2=regexp */
+	gboolean chanlist_caption_is_stale;
+};
+
+/* this struct is persistant even when delinking/relinking */
+
+typedef struct restore_gui
+{
+	/* banlist stuff */
+	GtkWidget *banlist_window;
+	GtkWidget *banlist_treeview;
+	GtkWidget *banlist_butRefresh;
+
+	void *tab;			/* (chan *) */
+
+	/* information stored when this tab isn't front-most */
+	void *user_model;	/* for filling the GtkTreeView */
+	void *buffer;		/* xtext_Buffer */
+	char *input_text;	/* input text buffer (while not-front tab) */
+	char *topic_text;	/* topic GtkEntry buffer */
+	char *key_text;
+	char *limit_text;
+	gfloat old_ul_value;	/* old userlist value (for adj) */
+	gfloat lag_value;	/* lag-o-meter */
+	char *lag_text;	/* lag-o-meter text */
+	char *lag_tip;		/* lag-o-meter tooltip */
+	gfloat queue_value; /* outbound queue meter */
+	char *queue_text;		/* outbound queue text */
+	char *queue_tip;		/* outbound queue tooltip */
+	short flag_wid_state[NUM_FLAG_WIDS];
+	unsigned int c_graph:1;	/* connecting graph, is there one? */
+} restore_gui;
+
+typedef struct session_gui
+{
+	GtkWidget
+		*xtext,
+		*vscrollbar,
+		*window,	/* toplevel */
+		*topic_entry,
+		*note_book,
+		*main_table,
+		*user_tree,	/* GtkTreeView */
+		*user_box,	/* userlist box */
+		*button_box_parent,
+		*button_box,	/* userlist buttons' box */
+		*dialogbutton_box,
+		*topicbutton_box,
+		*meter_box,	/* all the meters inside this */
+		*lagometer,
+		*laginfo,
+		*throttlemeter,
+		*throttleinfo,
+		*topic_bar,
+		*hpane_left,
+		*hpane_right,
+		*vpane_left,
+		*vpane_right,
+		*menu,
+		*bar,				/* connecting progress bar */
+		*nick_box,		/* contains label to the left of input_box */
+		*nick_label,
+		*op_xpm,			/* icon to the left of nickname */
+		*namelistinfo,	/* label above userlist */
+		*input_box,
+		*flag_wid[NUM_FLAG_WIDS],		/* channelmode buttons */
+		*limit_entry,		  /* +l */
+		*key_entry;		  /* +k */
+
+#define MENU_ID_NUM 12
+	GtkWidget *menu_item[MENU_ID_NUM+1]; /* some items we may change state of */
+
+	void *chanview;	/* chanview.h */
+
+	int bartag;		/*connecting progressbar timeout */
+
+	int pane_left_size;	/*last position of the pane*/
+	int pane_right_size;
+
+	guint16 is_tab;	/* is tab or toplevel? */
+	guint16 ul_hidden;	/* userlist hidden? */
+
+} session_gui;
+
+extern GdkPixmap *channelwin_pix;
+extern GdkPixmap *dialogwin_pix;
+
+
+#ifdef USE_GTKSPELL
+char *SPELL_ENTRY_GET_TEXT (GtkWidget *entry);
+#define SPELL_ENTRY_SET_TEXT(e,txt) gtk_text_buffer_set_text (gtk_text_view_get_buffer(GTK_TEXT_VIEW(e)),txt,-1);
+#define SPELL_ENTRY_SET_EDITABLE(e,v) gtk_text_view_set_editable(GTK_TEXT_VIEW(e), v)
+int SPELL_ENTRY_GET_POS (GtkWidget *entry);
+void SPELL_ENTRY_SET_POS (GtkWidget *entry, int pos);
+void SPELL_ENTRY_INSERT (GtkWidget *entry, const char *text, int len, int *pos);
+#else
+#define SPELL_ENTRY_GET_TEXT(e) (GTK_ENTRY(e)->text)
+#define SPELL_ENTRY_SET_TEXT(e,txt) gtk_entry_set_text(GTK_ENTRY(e),txt)
+#define SPELL_ENTRY_SET_EDITABLE(e,v) gtk_editable_set_editable(GTK_EDITABLE(e),v)
+#define SPELL_ENTRY_GET_POS(e) gtk_editable_get_position(GTK_EDITABLE(e))
+#define SPELL_ENTRY_SET_POS(e,p) gtk_editable_set_position(GTK_EDITABLE(e),p);
+#define SPELL_ENTRY_INSERT(e,t,l,p) gtk_editable_insert_text(GTK_EDITABLE(e),t,l,p)
+#endif
diff --git a/src/fe-gtk/fkeys.c b/src/fe-gtk/fkeys.c
new file mode 100644
index 00000000..014b5cc3
--- /dev/null
+++ b/src/fe-gtk/fkeys.c
@@ -0,0 +1,1814 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtklabel.h>
+#include <gtk/gtkeditable.h>
+#include <gtk/gtkmenu.h>
+#include <gtk/gtkmenuitem.h>
+#include <gtk/gtkoptionmenu.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkclist.h>
+#include <gtk/gtknotebook.h>
+#include <gtk/gtkcheckbutton.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkvscrollbar.h>
+
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "../common/cfgfiles.h"
+#include "../common/fe.h"
+#include "../common/userlist.h"
+#include "../common/outbound.h"
+#include "../common/util.h"
+#include "../common/text.h"
+#include "../common/plugin.h"
+#include <gdk/gdkkeysyms.h>
+#include "gtkutil.h"
+#include "menu.h"
+#include "xtext.h"
+#include "palette.h"
+#include "maingui.h"
+#include "textgui.h"
+#include "fkeys.h"
+
+#ifdef USE_GTKSPELL
+#include <gtk/gtktextview.h>
+#endif
+
+static void replace_handle (GtkWidget * wid);
+void key_action_tab_clean (void);
+
+/***************** Key Binding Code ******************/
+
+/* NOTES:
+
+   To add a new action:
+   1) inc KEY_MAX_ACTIONS
+   2) write the function at the bottom of this file (with all the others)
+   FIXME: Write about calling and returning
+   3) Add it to key_actions
+
+   --AGL
+
+ */
+
+/* Remember that the *number* of actions is this *plus* 1 --AGL */
+#define KEY_MAX_ACTIONS 14
+/* These are cp'ed from history.c --AGL */
+#define STATE_SHIFT     GDK_SHIFT_MASK
+#define	STATE_ALT	GDK_MOD1_MASK
+#define STATE_CTRL	GDK_CONTROL_MASK
+
+struct key_binding
+{
+	int keyval;						  /* GDK keynumber */
+	char *keyname;					  /* String with the name of the function */
+	int action;						  /* Index into key_actions */
+	int mod;							  /* Flags of STATE_* above */
+	char *data1, *data2;			  /* Pointers to strings, these must be freed */
+	struct key_binding *next;
+};
+
+struct key_action
+{
+	int (*handler) (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
+						 struct session * sess);
+	char *name;
+	char *help;
+};
+
+struct gcomp_data
+{
+	char data[CHANLEN];
+	int elen;
+};
+
+static int key_load_kbs (char *);
+static void key_load_defaults ();
+static void key_save_kbs (char *);
+static int key_action_handle_command (GtkWidget * wid, GdkEventKey * evt,
+												  char *d1, char *d2,
+												  struct session *sess);
+static int key_action_page_switch (GtkWidget * wid, GdkEventKey * evt,
+											  char *d1, char *d2, struct session *sess);
+int key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
+							  struct session *sess);
+static int key_action_scroll_page (GtkWidget * wid, GdkEventKey * evt,
+											  char *d1, char *d2, struct session *sess);
+static int key_action_set_buffer (GtkWidget * wid, GdkEventKey * evt,
+											 char *d1, char *d2, struct session *sess);
+static int key_action_history_up (GtkWidget * wid, GdkEventKey * evt,
+											 char *d1, char *d2, struct session *sess);
+static int key_action_history_down (GtkWidget * wid, GdkEventKey * evt,
+												char *d1, char *d2, struct session *sess);
+static int key_action_tab_comp (GtkWidget * wid, GdkEventKey * evt, char *d1,
+										  char *d2, struct session *sess);
+static int key_action_comp_chng (GtkWidget * wid, GdkEventKey * evt, char *d1,
+                                                                                        char *d2, struct session *sess);
+static int key_action_replace (GtkWidget * wid, GdkEventKey * evt, char *d1,
+										 char *d2, struct session *sess);
+static int key_action_move_tab_left (GtkWidget * wid, GdkEventKey * evt,
+												 char *d1, char *d2,
+												 struct session *sess);
+static int key_action_move_tab_right (GtkWidget * wid, GdkEventKey * evt,
+												  char *d1, char *d2,
+												  struct session *sess);
+static int key_action_move_tab_family_left (GtkWidget * wid, GdkEventKey * evt,
+												 char *d1, char *d2,
+												 struct session *sess);
+static int key_action_move_tab_family_right (GtkWidget * wid, GdkEventKey * evt,
+												  char *d1, char *d2,
+												  struct session *sess);
+static int key_action_put_history (GtkWidget * wid, GdkEventKey * evt,
+												  char *d1, char *d2,
+												  struct session *sess);
+
+static GtkWidget *key_dialog;
+static struct key_binding *keys_root = NULL;
+
+static const struct key_action key_actions[KEY_MAX_ACTIONS + 1] = {
+
+	{key_action_handle_command, "Run Command",
+	 N_("The \002Run Command\002 action runs the data in Data 1 as if it has been typed into the entry box where you pressed the key sequence. Thus it can contain text (which will be sent to the channel/person), commands or user commands. When run all \002\\n\002 characters in Data 1 are used to deliminate seperate commands so it is possible to run more than one command. If you want a \002\\\002 in the actual text run then enter \002\\\\\002")},
+	{key_action_page_switch, "Change Page",
+	 N_("The \002Change Page\002 command switches between pages in the notebook. Set Data 1 to the page you want to switch to. If Data 2 is set to anything then the switch will be relative to the current position")},
+	{key_action_insert, "Insert in Buffer",
+	 N_("The \002Insert in Buffer\002 command will insert the contents of Data 1 into the entry where the key sequence was pressed at the current cursor position")},
+	{key_action_scroll_page, "Scroll Page",
+	 N_("The \002Scroll Page\002 command scrolls the text widget up or down one page or one line. Set Data 1 to either Up, Down, +1 or -1.")},
+	{key_action_set_buffer, "Set Buffer",
+	 N_("The \002Set Buffer\002 command sets the entry where the key sequence was entered to the contents of Data 1")},
+	{key_action_history_up, "Last Command",
+	 N_("The \002Last Command\002 command sets the entry to contain the last command entered - the same as pressing up in a shell")},
+	{key_action_history_down, "Next Command",
+	 N_("The \002Next Command\002 command sets the entry to contain the next command entered - the same as pressing down in a shell")},
+	{key_action_tab_comp, "Complete nick/command",
+	 N_("This command changes the text in the entry to finish an incomplete nickname or command. If Data 1 is set then double-tabbing in a string will select the last nick, not the next")},
+	{key_action_comp_chng, "Change Selected Nick",
+	 N_("This command scrolls up and down through the list of nicks. If Data 1 is set to anything it will scroll up, else it scrolls down")},
+	{key_action_replace, "Check For Replace",
+	 N_("This command checks the last word entered in the entry against the replace list and replaces it if it finds a match")},
+	{key_action_move_tab_left, "Move front tab left",
+	 N_("This command moves the front tab left by one")},
+	{key_action_move_tab_right, "Move front tab right",
+	 N_("This command moves the front tab right by one")},
+	{key_action_move_tab_family_left, "Move tab family left",
+	 N_("This command moves the current tab family to the left")},
+	{key_action_move_tab_family_right, "Move tab family right",
+	 N_("This command moves the current tab family to the right")},
+	{key_action_put_history, "Push input line into history",
+	 N_("Push input line into history but doesn't send to server")},
+};
+
+void
+key_init ()
+{
+	keys_root = NULL;
+	if (key_load_kbs (NULL) == 1)
+	{
+		key_load_defaults ();
+		if (key_load_kbs (NULL) == 1)
+			fe_message (_("There was an error loading key"
+							" bindings configuration"), FE_MSG_ERROR);
+	}
+}
+
+static char *
+key_get_key_name (int keyval)
+{
+	return gdk_keyval_name (gdk_keyval_to_lower (keyval));
+}
+
+/* Ok, here are the NOTES
+
+   key_handle_key_press now handles all the key presses and history_keypress is
+   now defunct. It goes thru the linked list keys_root and finds a matching
+   key. It runs the action func and switches on these values:
+   0) Return
+   1) Find next
+   2) stop signal and return
+
+   * history_keypress is now dead (and gone)
+   * key_handle_key_press now takes its role
+   * All the possible actions are in a struct called key_actions (in fkeys.c)
+   * it is made of {function, name, desc}
+   * key bindings can pass 2 *text* strings to the handler. If more options are nee
+   ded a format can be put on one of these options
+   * key actions are passed {
+   the entry widget
+   the Gdk event
+   data 1
+   data 2
+   session struct
+   }
+   * key bindings are stored in a linked list of key_binding structs
+   * which looks like {
+   int keyval;  GDK keynumber
+   char *keyname;  String with the name of the function 
+   int action;  Index into key_actions 
+   int mod; Flags of STATE_* above 
+   char *data1, *data2;  Pointers to strings, these must be freed 
+   struct key_binding *next;
+   }
+   * remember that is (data1 || data2) != NULL then they need to be free()'ed
+
+   --AGL
+
+ */
+
+gboolean
+key_handle_key_press (GtkWidget *wid, GdkEventKey *evt, session *sess)
+{
+	struct key_binding *kb, *last = NULL;
+	int keyval = evt->keyval;
+	int mod, n;
+	GSList *list;
+
+	/* where did this event come from? */
+	list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+		if (sess->gui->input_box == wid)
+		{
+			if (sess->gui->is_tab)
+				sess = current_tab;
+			break;
+		}
+		list = list->next;
+	}
+	if (!list)
+		return FALSE;
+	current_sess = sess;
+
+	if (plugin_emit_keypress (sess, evt->state, evt->keyval, evt->length, evt->string))
+		return 1;
+
+	/* maybe the plugin closed this tab? */
+	if (!is_session (sess))
+		return 1;
+
+	mod = evt->state & (STATE_CTRL | STATE_ALT | STATE_SHIFT);
+
+	kb = keys_root;
+	while (kb)
+	{
+		if (kb->keyval == keyval && kb->mod == mod)
+		{
+			if (kb->action < 0 || kb->action > KEY_MAX_ACTIONS)
+				return 0;
+
+			/* Bump this binding to the top of the list */
+			if (last != NULL)
+			{
+				last->next = kb->next;
+				kb->next = keys_root;
+				keys_root = kb;
+			}
+			/* Run the function */
+			n = key_actions[kb->action].handler (wid, evt, kb->data1,
+															 kb->data2, sess);
+			switch (n)
+			{
+			case 0:
+				return 1;
+			case 2:
+				g_signal_stop_emission_by_name (G_OBJECT (wid),
+														"key_press_event");
+				return 1;
+			}
+		}
+		last = kb;
+		kb = kb->next;
+	}
+
+	switch (keyval)
+	{
+	case GDK_space:
+		key_action_tab_clean ();
+		break;
+
+#if defined(USE_GTKSPELL) && !defined(WIN32)
+	/* gtktextview has no 'activate' event, so we trap ENTER here */
+	case GDK_Return:
+	case GDK_KP_Enter:
+		if (!(evt->state & GDK_CONTROL_MASK))
+		{
+			g_signal_stop_emission_by_name (G_OBJECT (wid), "key_press_event");
+			mg_inputbox_cb (wid, sess->gui);
+		}
+#endif
+	}
+
+	return 0;
+}
+
+/* Walks keys_root and free()'s everything */
+/*static void
+key_free_all ()
+{
+	struct key_binding *cur, *next;
+
+	cur = keys_root;
+	while (cur)
+	{
+		next = cur->next;
+		if (cur->data1)
+			free (cur->data1);
+		if (cur->data2)
+			free (cur->data2);
+		free (cur);
+		cur = next;
+	}
+	keys_root = NULL;
+}*/
+
+/* Turns mod flags into a C-A-S string */
+static char *
+key_make_mod_str (int mod, char *buf)
+{
+	int i = 0;
+
+	if (mod & STATE_CTRL)
+	{
+		if (i)
+			buf[i++] = '-';
+		buf[i++] = 'C';
+	}
+	if (mod & STATE_ALT)
+	{
+		if (i)
+			buf[i++] = '-';
+		buf[i++] = 'A';
+	}
+	if (mod & STATE_SHIFT)
+	{
+		if (i)
+			buf[i++] = '-';
+		buf[i++] = 'S';
+	}
+	buf[i] = 0;
+	return buf;
+}
+
+/* ***** GUI code here ******************* */
+
+/* NOTE: The key_dialog defin is above --AGL */
+static GtkWidget *key_dialog_act_menu, *key_dialog_kb_clist;
+static GtkWidget *key_dialog_tog_c, *key_dialog_tog_s, *key_dialog_tog_a;
+static GtkWidget *key_dialog_ent_key, *key_dialog_ent_d1, *key_dialog_ent_d2;
+static GtkWidget *key_dialog_text;
+
+static void
+key_load_defaults ()
+{
+		/* This is the default config */
+#define defcfg \
+		"C\nPrior\nChange Page\nD1:-1\nD2:Relative\n\n"\
+		"C\nNext\nChange Page\nD1:1\nD2:Relative\n\n"\
+		"A\n9\nChange Page\nD1:9\nD2!\n\n"\
+		"A\n8\nChange Page\nD1:8\nD2!\n\n"\
+		"A\n7\nChange Page\nD1:7\nD2!\n\n"\
+		"A\n6\nChange Page\nD1:6\nD2!\n\n"\
+		"A\n5\nChange Page\nD1:5\nD2!\n\n"\
+		"A\n4\nChange Page\nD1:4\nD2!\n\n"\
+		"A\n3\nChange Page\nD1:3\nD2!\n\n"\
+		"A\n2\nChange Page\nD1:2\nD2!\n\n"\
+		"A\n1\nChange Page\nD1:1\nD2!\n\n"\
+		"C\no\nInsert in Buffer\nD1:\nD2!\n\n"\
+		"C\nb\nInsert in Buffer\nD1:\nD2!\n\n"\
+		"C\nk\nInsert in Buffer\nD1:\nD2!\n\n"\
+		"S\nNext\nChange Selected Nick\nD1!\nD2!\n\n"\
+		"S\nPrior\nChange Selected Nick\nD1:Up\nD2!\n\n"\
+		"None\nNext\nScroll Page\nD1:Down\nD2!\n\n"\
+		"None\nPrior\nScroll Page\nD1:Up\nD2!\n\n"\
+		"S\nDown\nScroll Page\nD1:+1\nD2!\n\n"\
+		"S\nUp\nScroll Page\nD1:-1\nD2!\n\n"\
+		"None\nDown\nNext Command\nD1!\nD2!\n\n"\
+		"None\nUp\nLast Command\nD1!\nD2!\n\n"\
+		"None\nTab\nComplete nick/command\nD1!\nD2!\n\n"\
+		"None\nspace\nCheck For Replace\nD1!\nD2!\n\n"\
+		"None\nReturn\nCheck For Replace\nD1!\nD2!\n\n"\
+		"None\nKP_Enter\nCheck For Replace\nD1!\nD2!\n\n"\
+		"C\nTab\nComplete nick/command\nD1:Up\nD2!\n\n"\
+		"A\nLeft\nMove front tab left\nD1!\nD2!\n\n"\
+		"A\nRight\nMove front tab right\nD1!\nD2!\n\n"\
+		"CS\nPrior\nMove tab family left\nD1!\nD2!\n\n"\
+		"CS\nNext\nMove tab family right\nD1!\nD2!\n\n"\
+		"None\nF9\nRun Command\nD1:/GUI MENU TOGGLE\nD2!\n\n"
+	int fd;
+
+	fd = xchat_open_file ("keybindings.conf", O_CREAT | O_TRUNC | O_WRONLY, 0x180, XOF_DOMODE);
+	if (fd < 0)
+		/* ???!!! */
+		return;
+
+	write (fd, defcfg, strlen (defcfg));
+	close (fd);
+}
+
+static void
+key_dialog_close ()
+{
+	key_dialog = NULL;
+	key_save_kbs (NULL);
+}
+
+static void
+key_dialog_add_new (GtkWidget * button, GtkCList * list)
+{
+	gchar *strs[] = { "", NULL, NULL, NULL, NULL };
+	struct key_binding *kb;
+
+	strs[1] = _("<none>");
+	strs[2] = _("<none>");
+	strs[3] = _("<none>");
+	strs[4] = _("<none>");
+
+	kb = malloc (sizeof (struct key_binding));
+
+	kb->keyval = 0;
+	kb->keyname = NULL;
+	kb->action = -1;
+	kb->mod = 0;
+	kb->data1 = kb->data2 = NULL;
+	kb->next = keys_root;
+
+	keys_root = kb;
+
+	gtk_clist_set_row_data (GTK_CLIST (list),
+									gtk_clist_append (GTK_CLIST (list), strs), kb);
+
+}
+
+static void
+key_dialog_delete (GtkWidget * button, GtkCList * list)
+{
+	struct key_binding *kb, *cur, *last;
+	int row = gtkutil_clist_selection ((GtkWidget *) list);
+
+	if (row != -1)
+	{
+		kb = gtk_clist_get_row_data (list, row);
+		cur = keys_root;
+		last = NULL;
+		while (cur)
+		{
+			if (cur == kb)
+			{
+				if (last)
+					last->next = kb->next;
+				else
+					keys_root = kb->next;
+
+				if (kb->data1)
+					free (kb->data1);
+				if (kb->data2)
+					free (kb->data2);
+				free (kb);
+				gtk_clist_remove (list, row);
+				return;
+			}
+			last = cur;
+			cur = cur->next;
+		}
+		printf ("*** key_dialog_delete: couldn't find kb in list!\n");
+		/*if (getenv ("XCHAT_DEBUG"))
+			abort ();*/
+	}
+}
+
+static void
+key_print_text (GtkXText *xtext, char *text)
+{
+	unsigned int old = prefs.timestamp;
+	prefs.timestamp = 0;	/* temporarily disable stamps */
+	gtk_xtext_clear (GTK_XTEXT (xtext)->buffer, 0);
+	PrintTextRaw (GTK_XTEXT (xtext)->buffer, text, 0, 0);
+	prefs.timestamp = old;
+}
+
+static void
+key_dialog_sel_act (GtkWidget * un, int num)
+{
+	int row = gtkutil_clist_selection (key_dialog_kb_clist);
+	struct key_binding *kb;
+
+	if (row != -1)
+	{
+		kb = gtk_clist_get_row_data (GTK_CLIST (key_dialog_kb_clist), row);
+		kb->action = num;
+		gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 2,
+								  _(key_actions[num].name));
+		if (key_actions[num].help)
+		{
+			key_print_text (GTK_XTEXT (key_dialog_text), _(key_actions[num].help));
+		}
+	}
+}
+
+static void
+key_dialog_sel_row (GtkWidget * clist, gint row, gint column,
+						  GdkEventButton * evt, gpointer data)
+{
+	struct key_binding *kb = gtk_clist_get_row_data (GTK_CLIST (clist), row);
+
+	if (kb == NULL)
+	{
+		printf ("*** key_dialog_sel_row: kb == NULL\n");
+		abort ();
+	}
+	if (kb->action > -1 && kb->action <= KEY_MAX_ACTIONS)
+	{
+		gtk_option_menu_set_history (GTK_OPTION_MENU (key_dialog_act_menu),
+											  kb->action);
+		if (key_actions[kb->action].help)
+		{
+			key_print_text (GTK_XTEXT (key_dialog_text), _(key_actions[kb->action].help));
+		}
+	}
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (key_dialog_tog_c),
+										  (kb->mod & STATE_CTRL) == STATE_CTRL);
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (key_dialog_tog_s),
+										  (kb->mod & STATE_SHIFT) == STATE_SHIFT);
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (key_dialog_tog_a),
+										  (kb->mod & STATE_ALT) == STATE_ALT);
+
+	if (kb->data1)
+		gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_d1), kb->data1);
+	else
+		gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_d1), "");
+
+	if (kb->data2)
+		gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_d2), kb->data2);
+	else
+		gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_d2), "");
+
+	if (kb->keyname)
+		gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_key), kb->keyname);
+	else
+		gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_key), "");
+}
+
+static void
+key_dialog_tog_key (GtkWidget * tog, int kstate)
+{
+	int state = GTK_TOGGLE_BUTTON (tog)->active;
+	int row = gtkutil_clist_selection (key_dialog_kb_clist);
+	struct key_binding *kb;
+	char buf[32];
+
+	if (row == -1)
+		return;
+
+	kb = gtk_clist_get_row_data (GTK_CLIST (key_dialog_kb_clist), row);
+	if (state)
+		kb->mod |= kstate;
+	else
+		kb->mod &= ~kstate;
+
+	gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 0,
+							  key_make_mod_str (kb->mod, buf));
+}
+
+static GtkWidget *
+key_dialog_make_toggle (char *label, void *callback, void *option,
+								GtkWidget * box)
+{
+	GtkWidget *wid;
+
+	wid = gtk_check_button_new_with_label (label);
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), 0);
+	gtk_signal_connect (GTK_OBJECT (wid), "toggled",
+							  GTK_SIGNAL_FUNC (callback), option);
+	gtk_box_pack_end (GTK_BOX (box), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+
+	return wid;
+}
+
+static GtkWidget *
+key_dialog_make_entry (char *label, char *act, void *callback, void *option,
+							  GtkWidget * box)
+{
+	GtkWidget *wid, *hbox;;
+
+	hbox = gtk_hbox_new (0, 2);
+	if (label)
+	{
+		wid = gtk_label_new (label);
+		gtk_widget_show (wid);
+		gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
+	}
+	wid = gtk_entry_new ();
+	if (act)
+	{
+		gtk_signal_connect (GTK_OBJECT (wid), act, GTK_SIGNAL_FUNC (callback),
+								  option);
+	}
+	gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+	gtk_widget_show (hbox);
+
+	gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0);
+
+	return wid;
+}
+
+static void
+key_dialog_set_key (GtkWidget * entry, GdkEventKey * evt, void *none)
+{
+	int row = gtkutil_clist_selection (key_dialog_kb_clist);
+	struct key_binding *kb;
+
+	gtk_entry_set_text (GTK_ENTRY (entry), "");
+
+	if (row == -1)
+		return;
+
+	kb = gtk_clist_get_row_data (GTK_CLIST (key_dialog_kb_clist), row);
+	kb->keyval = evt->keyval;
+	kb->keyname = key_get_key_name (kb->keyval);
+	gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 1, kb->keyname);
+	gtk_entry_set_text (GTK_ENTRY (entry), kb->keyname);
+	g_signal_stop_emission_by_name (G_OBJECT (entry), "key_press_event");
+}
+
+static void
+key_dialog_set_data (GtkWidget * entry, int d)
+{
+	const char *text = gtk_entry_get_text (GTK_ENTRY (entry));
+	int row = gtkutil_clist_selection (key_dialog_kb_clist);
+	struct key_binding *kb;
+	char *buf;
+	int len = strlen (text);
+
+	len++;
+
+	if (row == -1)
+		return;
+
+	kb = gtk_clist_get_row_data (GTK_CLIST (key_dialog_kb_clist), row);
+	if (d == 0)
+	{									  /* using data1 */
+		if (kb->data1)
+			free (kb->data1);
+		buf = (char *) malloc (len);
+		memcpy (buf, text, len);
+		kb->data1 = buf;
+		gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 3, text);
+	} else
+	{
+		if (kb->data2)
+			free (kb->data2);
+		buf = (char *) malloc (len);
+		memcpy (buf, text, len);
+		kb->data2 = buf;
+		gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 4, text);
+	}
+}
+
+void
+key_dialog_show ()
+{
+	GtkWidget *vbox, *hbox, *list, *vbox2, *wid, *wid2, *wid3, *hbox2;
+	struct key_binding *kb;
+	gchar *titles[] = { NULL, NULL, NULL, "1", "2" };
+	char temp[32];
+	int i;
+
+	titles[0] = _("Mod");
+	titles[1] = _("Key");
+	titles[2] = _("Action");
+
+	if (key_dialog)
+	{
+		mg_bring_tofront (key_dialog);
+		return;
+	}
+
+	key_dialog =
+			  mg_create_generic_tab ("editkeys", _("XChat: Keyboard Shortcuts"),
+							TRUE, FALSE, key_dialog_close, NULL, 560, 330, &vbox, 0);
+
+	hbox = gtk_hbox_new (0, 2);
+	gtk_box_pack_start (GTK_BOX (vbox), hbox, 1, 1, 0);
+
+	list = gtkutil_clist_new (5, titles, hbox, 0, key_dialog_sel_row, 0, NULL,
+									  0, GTK_SELECTION_SINGLE);
+	gtk_widget_set_usize (list, 400, 0);
+	key_dialog_kb_clist = list;
+
+	gtk_widget_show (hbox);
+
+	kb = keys_root;
+
+	gtk_clist_set_column_width (GTK_CLIST (list), 1, 50);
+	gtk_clist_set_column_width (GTK_CLIST (list), 2, 120);
+	gtk_clist_set_column_width (GTK_CLIST (list), 3, 50);
+	gtk_clist_set_column_width (GTK_CLIST (list), 4, 50);
+
+	while (kb)
+	{
+		titles[0] = key_make_mod_str (kb->mod, temp);
+		titles[1] = kb->keyname;
+		if (kb->action < 0 || kb->action > KEY_MAX_ACTIONS)
+			titles[2] = _("<none>");
+		else
+			titles[2] = key_actions[kb->action].name;
+		if (kb->data1)
+			titles[3] = kb->data1;
+		else
+			titles[3] = _("<none>");
+
+		if (kb->data2)
+			titles[4] = kb->data2;
+		else
+			titles[4] = _("<none>");
+
+		gtk_clist_set_row_data (GTK_CLIST (list),
+										gtk_clist_append (GTK_CLIST (list), titles),
+										kb);
+
+		kb = kb->next;
+	}
+
+	vbox2 = gtk_vbox_new (0, 2);
+	gtk_box_pack_end (GTK_BOX (hbox), vbox2, 1, 1, 0);
+	wid = gtk_button_new_with_label (_("Add New"));
+	gtk_box_pack_start (GTK_BOX (vbox2), wid, 0, 0, 0);
+	gtk_signal_connect (GTK_OBJECT (wid), "clicked",
+							  GTK_SIGNAL_FUNC (key_dialog_add_new), list);
+	gtk_widget_show (wid);
+	wid = gtk_button_new_with_label (_("Delete"));
+	gtk_box_pack_start (GTK_BOX (vbox2), wid, 0, 0, 0);
+	gtk_signal_connect (GTK_OBJECT (wid), "clicked",
+							  GTK_SIGNAL_FUNC (key_dialog_delete), list);
+	gtk_widget_show (wid);
+	gtk_widget_show (vbox2);
+
+	wid = gtk_option_menu_new ();
+	wid2 = gtk_menu_new ();
+
+	for (i = 0; i <= KEY_MAX_ACTIONS; i++)
+	{
+		wid3 = gtk_menu_item_new_with_label (_(key_actions[i].name));
+		gtk_widget_show (wid3);
+		gtk_menu_shell_append (GTK_MENU_SHELL (wid2), wid3);
+		gtk_signal_connect (GTK_OBJECT (wid3), "activate",
+								  GTK_SIGNAL_FUNC (key_dialog_sel_act),
+								  GINT_TO_POINTER (i));
+	}
+
+	gtk_option_menu_set_menu (GTK_OPTION_MENU (wid), wid2);
+	gtk_option_menu_set_history (GTK_OPTION_MENU (wid), 0);
+	gtk_box_pack_end (GTK_BOX (vbox2), wid, 0, 0, 0);
+	gtk_widget_show (wid);
+	key_dialog_act_menu = wid;
+
+	key_dialog_tog_s = key_dialog_make_toggle (_("Shift"), key_dialog_tog_key,
+															 (void *) STATE_SHIFT, vbox2);
+	key_dialog_tog_a = key_dialog_make_toggle (_("Alt"), key_dialog_tog_key,
+															 (void *) STATE_ALT, vbox2);
+	key_dialog_tog_c = key_dialog_make_toggle (_("Ctrl"), key_dialog_tog_key,
+															 (void *) STATE_CTRL, vbox2);
+
+	key_dialog_ent_key = key_dialog_make_entry (_("Key"), "key_press_event",
+															  key_dialog_set_key, NULL,
+															  vbox2);
+
+	key_dialog_ent_d1 = key_dialog_make_entry (_("Data 1"), "activate",
+															 key_dialog_set_data, NULL,
+															 vbox2);
+	key_dialog_ent_d2 = key_dialog_make_entry (_("Data 2"), "activate",
+															 key_dialog_set_data,
+															 (void *) 1, vbox2);
+
+	hbox2 = gtk_hbox_new (0, 2);
+	gtk_box_pack_end (GTK_BOX (vbox), hbox2, 0, 0, 1);
+
+	wid = gtk_xtext_new (colors, 0);
+	gtk_xtext_set_tint (GTK_XTEXT (wid), prefs.tint_red, prefs.tint_green, prefs.tint_blue);
+	gtk_xtext_set_background (GTK_XTEXT (wid),
+									  channelwin_pix,
+									  prefs.transparent);
+	gtk_widget_set_usize (wid, 0, 75);
+	gtk_box_pack_start (GTK_BOX (hbox2), wid, 1, 1, 1);
+	gtk_xtext_set_font (GTK_XTEXT (wid), prefs.font_normal);
+	gtk_widget_show (wid);
+
+	wid2 = gtk_vscrollbar_new (GTK_XTEXT (wid)->adj);
+	gtk_box_pack_start (GTK_BOX (hbox2), wid2, 0, 0, 0);
+	gtk_widget_show (wid2);
+
+	gtk_widget_show (hbox2);
+	key_dialog_text = wid;
+
+	gtk_widget_show_all (key_dialog);
+}
+
+static void
+key_save_kbs (char *fn)
+{
+	int fd, i;
+	char buf[512];
+	struct key_binding *kb;
+
+	if (!fn)
+		fd = xchat_open_file ("keybindings.conf", O_CREAT | O_TRUNC | O_WRONLY,
+									 0x180, XOF_DOMODE);
+	else
+		fd = xchat_open_file (fn, O_CREAT | O_TRUNC | O_WRONLY,
+									 0x180, XOF_DOMODE | XOF_FULLPATH);
+	if (fd < 0)
+	{
+		fe_message (_("Error opening keys config file\n"), FE_MSG_ERROR);
+		return;
+	}
+	write (fd, buf,
+			 snprintf (buf, 510, "# XChat key bindings config file\n\n"));
+
+	kb = keys_root;
+	i = 0;
+
+	while (kb)
+	{
+		if (kb->keyval == -1 || kb->keyname == NULL || kb->action < 0)
+		{
+			kb = kb->next;
+			continue;
+		}
+		i = 0;
+		if (kb->mod & STATE_CTRL)
+		{
+			i++;
+			write (fd, "C", 1);
+		}
+		if (kb->mod & STATE_ALT)
+		{
+			i++;
+			write (fd, "A", 1);
+		}
+		if (kb->mod & STATE_SHIFT)
+		{
+			i++;
+			write (fd, "S", 1);
+		}
+		if (i == 0)
+			write (fd, "None\n", 5);
+		else
+			write (fd, "\n", 1);
+
+		write (fd, buf, snprintf (buf, 510, "%s\n%s\n", kb->keyname,
+										  key_actions[kb->action].name));
+		if (kb->data1 && kb->data1[0])
+			write (fd, buf, snprintf (buf, 510, "D1:%s\n", kb->data1));
+		else
+			write (fd, "D1!\n", 4);
+
+		if (kb->data2 && kb->data2[0])
+			write (fd, buf, snprintf (buf, 510, "D2:%s\n", kb->data2));
+		else
+			write (fd, "D2!\n", 4);
+
+		write (fd, "\n", 1);
+
+		kb = kb->next;
+	}
+
+	close (fd);
+}
+
+/* I just know this is going to be a nasty parse, if you think it's bugged
+   it almost certainly is so contact the XChat dev team --AGL */
+
+static inline int
+key_load_kbs_helper_mod (char *in, int *out)
+{
+	int n, len, mod = 0;
+
+	/* First strip off the fluff */
+	while (in[0] == ' ' || in[0] == '\t')
+		in++;
+	len = strlen (in);
+	while (in[len] == ' ' || in[len] == '\t')
+	{
+		in[len] = 0;
+		len--;
+	}
+
+	if (strcmp (in, "None") == 0)
+	{
+		*out = 0;
+		return 0;
+	}
+	for (n = 0; n < len; n++)
+	{
+		switch (in[n])
+		{
+		case 'C':
+			mod |= STATE_CTRL;
+			break;
+		case 'A':
+			mod |= STATE_ALT;
+			break;
+		case 'S':
+			mod |= STATE_SHIFT;
+			break;
+		default:
+			return 1;
+		}
+	}
+
+	*out = mod;
+	return 0;
+}
+
+/* These are just local defines to keep me sane --AGL */
+
+#define KBSTATE_MOD 0
+#define KBSTATE_KEY 1
+#define KBSTATE_ACT 2
+#define KBSTATE_DT1 3
+#define KBSTATE_DT2 4
+
+/* *** Warning, Warning! - massive function ahead! --AGL */
+
+static int
+key_load_kbs (char *filename)
+{
+	char *buf, *ibuf;
+	struct stat st;
+	struct key_binding *kb = NULL, *last = NULL;
+	int fd, len, pnt = 0, state = 0, n;
+
+	if (filename == NULL)
+		fd = xchat_open_file ("keybindings.conf", O_RDONLY, 0, 0);
+	else
+		fd = xchat_open_file (filename, O_RDONLY, 0, XOF_FULLPATH);
+	if (fd < 0)
+		return 1;
+	if (fstat (fd, &st) != 0)
+		return 1;
+	ibuf = malloc (st.st_size);
+	read (fd, ibuf, st.st_size);
+	close (fd);
+
+	while (buf_get_line (ibuf, &buf, &pnt, st.st_size))
+	{
+		if (buf[0] == '#')
+			continue;
+		if (strlen (buf) == 0)
+			continue;
+
+		switch (state)
+		{
+		case KBSTATE_MOD:
+			kb = (struct key_binding *) malloc (sizeof (struct key_binding));
+			if (key_load_kbs_helper_mod (buf, &kb->mod))
+				goto corrupt_file;
+			state = KBSTATE_KEY;
+			continue;
+		case KBSTATE_KEY:
+			/* First strip off the fluff */
+			while (buf[0] == ' ' || buf[0] == '\t')
+				buf++;
+			len = strlen (buf);
+			while (buf[len] == ' ' || buf[len] == '\t')
+			{
+				buf[len] = 0;
+				len--;
+			}
+
+			n = gdk_keyval_from_name (buf);
+			if (n == 0)
+			{
+				/* Unknown keyname, abort */
+				if (last)
+					last->next = NULL;
+				free (ibuf);
+				ibuf = malloc (1024);
+				snprintf (ibuf, 1024,
+							 _("Unknown keyname %s in key bindings config file\nLoad aborted, please fix %s/keybindings.conf\n"),
+							 buf, get_xdir_utf8 ());
+				fe_message (ibuf, FE_MSG_ERROR);
+				free (ibuf);
+				return 2;
+			}
+			kb->keyname = gdk_keyval_name (n);
+			kb->keyval = n;
+
+			state = KBSTATE_ACT;
+			continue;
+		case KBSTATE_ACT:
+			/* First strip off the fluff */
+			while (buf[0] == ' ' || buf[0] == '\t')
+				buf++;
+			len = strlen (buf);
+			while (buf[len] == ' ' || buf[len] == '\t')
+			{
+				buf[len] = 0;
+				len--;
+			}
+
+			for (n = 0; n < KEY_MAX_ACTIONS + 1; n++)
+			{
+				if (strcmp (key_actions[n].name, buf) == 0)
+				{
+					kb->action = n;
+					break;
+				}
+			}
+
+			if (n == KEY_MAX_ACTIONS + 1)
+			{
+				if (last)
+					last->next = NULL;
+				free (ibuf);
+				ibuf = malloc (1024);
+				snprintf (ibuf, 1024,
+							 _("Unknown action %s in key bindings config file\nLoad aborted, Please fix %s/keybindings\n"),
+							 buf, get_xdir_utf8 ());
+				fe_message (ibuf, FE_MSG_ERROR);
+				free (ibuf);
+				return 3;
+			}
+			state = KBSTATE_DT1;
+			continue;
+		case KBSTATE_DT1:
+		case KBSTATE_DT2:
+			if (state == KBSTATE_DT1)
+				kb->data1 = kb->data2 = NULL;
+
+			while (buf[0] == ' ' || buf[0] == '\t')
+				buf++;
+
+			if (buf[0] != 'D')
+			{
+				free (ibuf);
+				ibuf = malloc (1024);
+				snprintf (ibuf, 1024,
+							 _("Expecting Data line (beginning Dx{:|!}) but got:\n%s\n\nLoad aborted, Please fix %s/keybindings\n"),
+							 buf, get_xdir_utf8 ());
+				fe_message (ibuf, FE_MSG_ERROR);
+				free (ibuf);
+				return 4;
+			}
+			switch (buf[1])
+			{
+			case '1':
+				if (state != KBSTATE_DT1)
+					goto corrupt_file;
+				break;
+			case '2':
+				if (state != KBSTATE_DT2)
+					goto corrupt_file;
+				break;
+			default:
+				goto corrupt_file;
+			}
+
+			if (buf[2] == ':')
+			{
+				len = strlen (buf);
+				/* Add one for the NULL, subtract 3 for the "Dx:" */
+				len++;
+				len -= 3;
+				if (state == KBSTATE_DT1)
+				{
+					kb->data1 = malloc (len);
+					memcpy (kb->data1, &buf[3], len);
+				} else
+				{
+					kb->data2 = malloc (len);
+					memcpy (kb->data2, &buf[3], len);
+				}
+			} else if (buf[2] == '!')
+			{
+				if (state == KBSTATE_DT1)
+					kb->data1 = NULL;
+				else
+					kb->data2 = NULL;
+			}
+			if (state == KBSTATE_DT1)
+			{
+				state = KBSTATE_DT2;
+				continue;
+			} else
+			{
+				if (last)
+					last->next = kb;
+				else
+					keys_root = kb;
+				last = kb;
+
+				state = KBSTATE_MOD;
+			}
+
+			continue;
+		}
+	}
+	if (last)
+		last->next = NULL;
+	free (ibuf);
+	return 0;
+
+ corrupt_file:
+	/*if (getenv ("XCHAT_DEBUG"))
+		abort ();*/
+	snprintf (ibuf, 1024,
+						_("Key bindings config file is corrupt, load aborted\n"
+								 "Please fix %s/keybindings.conf\n"),
+						 get_xdir_utf8 ());
+	fe_message (ibuf, FE_MSG_ERROR);
+	free (ibuf);
+	return 5;
+}
+
+/* ***** Key actions start here *********** */
+
+/* See the NOTES above --AGL */
+
+/* "Run command" */
+static int
+key_action_handle_command (GtkWidget * wid, GdkEventKey * evt, char *d1,
+									char *d2, struct session *sess)
+{
+	int ii, oi, len;
+	char out[2048], d = 0;
+
+	if (!d1)
+		return 0;
+
+	len = strlen (d1);
+
+	/* Replace each "\n" substring with '\n' */
+	for (ii = oi = 0; ii < len; ii++)
+	{
+		d = d1[ii];
+		if (d == '\\')
+		{
+			ii++;
+			d = d1[ii];
+			if (d == 'n')
+				out[oi++] = '\n';
+			else if (d == '\\')
+				out[oi++] = '\\';
+			else
+			{
+				out[oi++] = '\\';
+				out[oi++] = d;
+			}
+			continue;
+		}
+		out[oi++] = d;
+	}
+	out[oi] = 0;
+
+	handle_multiline (sess, out, 0, 0);
+	return 0;
+}
+
+static int
+key_action_page_switch (GtkWidget * wid, GdkEventKey * evt, char *d1,
+								char *d2, struct session *sess)
+{
+	int len, i, num;
+
+	if (!d1)
+		return 1;
+
+	len = strlen (d1);
+	if (!len)
+		return 1;
+
+	for (i = 0; i < len; i++)
+	{
+		if (d1[i] < '0' || d1[i] > '9')
+		{
+			if (i == 0 && (d1[i] == '+' || d1[i] == '-'))
+				continue;
+			else
+				return 1;
+		}
+	}
+
+	num = atoi (d1);
+	if (!d2)
+		num--;
+	if (!d2 || d2[0] == 0)
+		mg_switch_page (FALSE, num);
+	else
+		mg_switch_page (TRUE, num);
+	return 0;
+}
+
+int
+key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
+						 struct session *sess)
+{
+	int tmp_pos;
+
+	if (!d1)
+		return 1;
+
+	tmp_pos = SPELL_ENTRY_GET_POS (wid);
+	SPELL_ENTRY_INSERT (wid, d1, strlen (d1), &tmp_pos);
+	SPELL_ENTRY_SET_POS (wid, tmp_pos);
+	return 2;
+}
+
+/* handles PageUp/Down keys */
+static int
+key_action_scroll_page (GtkWidget * wid, GdkEventKey * evt, char *d1,
+								char *d2, struct session *sess)
+{
+	int value, end;
+	GtkAdjustment *adj;
+	enum scroll_type { PAGE_UP, PAGE_DOWN, LINE_UP, LINE_DOWN };
+	int type = PAGE_DOWN;
+
+	if (d1)
+	{
+		if (!strcasecmp (d1, "up"))
+			type = PAGE_UP;
+		else if (!strcasecmp (d1, "+1"))
+			type = LINE_DOWN;
+		else if (!strcasecmp (d1, "-1"))
+			type = LINE_UP;
+	}
+
+	if (!sess)
+		return 0;
+
+	adj = GTK_RANGE (sess->gui->vscrollbar)->adjustment;
+	end = adj->upper - adj->lower - adj->page_size;
+
+	switch (type)
+	{
+	case LINE_UP:
+		value = adj->value - 1.0;
+		break;
+
+	case LINE_DOWN:
+		value = adj->value + 1.0;
+		break;
+
+	case PAGE_UP:
+		value = adj->value - (adj->page_size - 1);
+		break;
+
+	default:	/* PAGE_DOWN */
+		value = adj->value + (adj->page_size - 1);
+		break;
+	}
+
+	if (value < 0)
+		value = 0;
+	if (value > end)
+		value = end;
+
+	gtk_adjustment_set_value (adj, value);
+
+	return 0;
+}
+
+static int
+key_action_set_buffer (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
+							  struct session *sess)
+{
+	if (!d1)
+		return 1;
+	if (d1[0] == 0)
+		return 1;
+
+	SPELL_ENTRY_SET_TEXT (wid, d1);
+	SPELL_ENTRY_SET_POS (wid, -1);
+
+	return 2;
+}
+
+static int
+key_action_history_up (GtkWidget * wid, GdkEventKey * ent, char *d1, char *d2,
+							  struct session *sess)
+{
+	char *new_line;
+
+	new_line = history_up (&sess->history, SPELL_ENTRY_GET_TEXT (wid));
+	if (new_line)
+	{
+		SPELL_ENTRY_SET_TEXT (wid, new_line);
+		SPELL_ENTRY_SET_POS (wid, -1);
+	}
+
+	return 2;
+}
+
+static int
+key_action_history_down (GtkWidget * wid, GdkEventKey * ent, char *d1,
+								 char *d2, struct session *sess)
+{
+	char *new_line;
+
+	new_line = history_down (&sess->history);
+	if (new_line)
+	{
+		SPELL_ENTRY_SET_TEXT (wid, new_line);
+		SPELL_ENTRY_SET_POS (wid, -1);
+	}
+
+	return 2;
+}
+
+/* old data that we reuse */
+static struct gcomp_data old_gcomp;
+
+/* work on the data, ie return only channels */
+static int
+double_chan_cb (session *lsess, GList **list)
+{
+	if (lsess->type == SESS_CHANNEL)
+		*list = g_list_prepend(*list, lsess->channel);
+	return TRUE;
+}
+
+/* convert a slist -> list. */
+static GList *
+chanlist_double_list (GSList *inlist)
+{
+	GList *list = NULL;
+	g_slist_foreach(inlist, (GFunc)double_chan_cb, &list);
+	return list;
+}
+
+/* handle commands */
+static int
+double_cmd_cb (struct popup *pop, GList **list)
+{
+	*list = g_list_prepend(*list, pop->name);
+	return TRUE;
+}
+
+/* convert a slist -> list. */
+static GList *
+cmdlist_double_list (GSList *inlist)
+{
+	GList *list = NULL;
+	g_slist_foreach (inlist, (GFunc)double_cmd_cb, &list);
+	return list;
+}
+
+static char *
+gcomp_nick_func (char *data)
+{
+	if (data)
+		return ((struct User *)data)->nick;
+	return "";
+}
+
+void
+key_action_tab_clean(void)
+{
+	if (old_gcomp.elen)
+	{
+		old_gcomp.data[0] = 0;
+		old_gcomp.elen = 0;
+	}
+}
+
+/* Used in the followig completers */
+#define COMP_BUF 2048
+
+/* For use in sorting the user list for completion */
+static int
+talked_recent_cmp (struct User *a, struct User *b)
+{
+	if (a->lasttalk < b->lasttalk)
+		return -1;
+
+	if (a->lasttalk > b->lasttalk)
+		return 1;
+
+	return 0;
+}
+
+static int
+key_action_tab_comp (GtkWidget *t, GdkEventKey *entry, char *d1, char *d2,
+							struct session *sess)
+{
+	int len = 0, elen = 0, i = 0, cursor_pos, ent_start = 0, comp = 0, found = 0,
+	    prefix_len, skip_len = 0, is_nick, is_cmd = 0;
+	char buf[COMP_BUF], ent[CHANLEN], *postfix = NULL, *result, *ch;
+	GList *list = NULL, *tmp_list = NULL;
+	const char *text;
+	GCompletion *gcomp = NULL;
+
+	/* force the IM Context to reset */
+	SPELL_ENTRY_SET_EDITABLE (t, FALSE);
+	SPELL_ENTRY_SET_EDITABLE (t, TRUE);
+
+	text = SPELL_ENTRY_GET_TEXT (t);
+	if (text[0] == 0)
+		return 1;
+
+	len = g_utf8_strlen (text, -1); /* must be null terminated */
+
+	cursor_pos = SPELL_ENTRY_GET_POS (t);
+
+	buf[0] = 0; /* make sure we don't get garbage in the buffer */
+
+	/* handle "nick: " or "nick " or "#channel "*/
+	ch = g_utf8_find_prev_char(text, g_utf8_offset_to_pointer(text,cursor_pos));
+	if (ch && ch[0] == ' ')
+	{
+		skip_len++;
+		ch = g_utf8_find_prev_char(text, ch);
+		if (!ch)
+			return 2;
+
+		cursor_pos = g_utf8_pointer_to_offset(text, ch);
+		if (cursor_pos && (g_utf8_get_char_validated(ch, -1) == ':' || 
+					g_utf8_get_char_validated(ch, -1) == ',' ||
+					g_utf8_get_char_validated(ch, -1) == prefs.nick_suffix[0]))
+		{
+			skip_len++;
+		}
+		else
+			cursor_pos = g_utf8_pointer_to_offset(text, g_utf8_offset_to_pointer(ch, 1));
+	}
+
+	comp = skip_len;
+	
+	/* store the text following the cursor for reinsertion later */
+	if ((cursor_pos + skip_len) < len)
+		postfix = g_utf8_offset_to_pointer(text, cursor_pos + skip_len);
+
+	for (ent_start = cursor_pos; ; --ent_start)
+	{
+		if (ent_start == 0)
+			break;
+		ch = g_utf8_offset_to_pointer(text, ent_start - 1);
+		if (ch && ch[0] == ' ')
+			break;
+	}
+
+	if (ent_start == 0 && text[0] == prefs.cmdchar[0])
+	{
+		ent_start++;
+		is_cmd = 1;
+	}
+	
+	prefix_len = ent_start;
+	elen = cursor_pos - ent_start;
+
+	g_utf8_strncpy (ent, g_utf8_offset_to_pointer (text, prefix_len), elen);
+
+	is_nick = (ent[0] == '#' || ent[0] == '&' || is_cmd) ? 0 : 1;
+	
+	if (sess->type == SESS_DIALOG && is_nick)
+	{
+		/* tab in a dialog completes the other person's name */
+		if (rfc_ncasecmp (sess->channel, ent, elen) == 0)
+		{
+			result =  sess->channel;
+			is_nick = 0;
+		}
+		else
+			return 2;
+	}
+	else
+	{
+		if (is_nick)
+		{
+			gcomp = g_completion_new((GCompletionFunc)gcomp_nick_func);
+			tmp_list = userlist_double_list(sess); /* create a temp list so we can free the memory */
+			if (prefs.completion_sort == 1)	/* sort in last-talk order? */
+				tmp_list = g_list_sort (tmp_list, (void *)talked_recent_cmp);
+		}
+		else
+		{
+			gcomp = g_completion_new (NULL);
+			if (is_cmd)
+			{
+				tmp_list = cmdlist_double_list (command_list);
+				for(i = 0; xc_cmds[i].name != NULL ; i++)
+				{
+					tmp_list = g_list_prepend (tmp_list, xc_cmds[i].name);
+				}
+				tmp_list = plugin_command_list(tmp_list);
+			}
+			else
+				tmp_list = chanlist_double_list (sess_list);
+		}
+		tmp_list = g_list_reverse(tmp_list); /* make the comp entries turn up in the right order */
+		g_completion_set_compare (gcomp, (GCompletionStrncmpFunc)rfc_ncasecmp);
+		if (tmp_list)
+		{
+			g_completion_add_items (gcomp, tmp_list);
+			g_list_free (tmp_list);
+		}
+
+		if (comp && !(rfc_ncasecmp(old_gcomp.data, ent, old_gcomp.elen) == 0))
+		{
+			key_action_tab_clean ();
+			comp = 0;
+		}
+	
+#if GLIB_CHECK_VERSION(2,4,0)
+		list = g_completion_complete_utf8 (gcomp, comp ? old_gcomp.data : ent, &result);
+#else
+		list = g_completion_complete (gcomp, comp ? old_gcomp.data : ent, &result);
+#endif
+		
+		if (result == NULL) /* No matches found */
+		{
+			g_completion_free(gcomp);
+			return 2;
+		}
+
+		if (comp) /* existing completion */
+		{
+			while(list) /* find the current entry */
+			{
+				if(rfc_ncasecmp(list->data, ent, elen) == 0)
+				{
+					found = 1;
+					break;
+				}
+				list = list->next;
+			}
+
+			if (found)
+			{
+				if (!(d1 && d1[0])) /* not holding down shift */
+				{
+					if (g_list_next(list) == NULL)
+						list = g_list_first(list);
+					else
+						list = g_list_next(list);
+				}
+				else
+				{
+					if (g_list_previous(list) == NULL)
+						list = g_list_last(list);
+					else
+						list = g_list_previous(list);
+				}
+				g_free(result);
+				result = (char*)list->data;
+			}
+			else
+			{
+				g_free(result);
+				g_completion_free(gcomp);
+				return 2;
+			}
+		}
+		else
+		{
+			strcpy(old_gcomp.data, ent);
+			old_gcomp.elen = elen;
+
+			/* Get the first nick and put out the data for future nickcompletes */
+			if (prefs.completion_amount && g_list_length (list) <= prefs.completion_amount)
+			{
+				g_free(result);
+				result = (char*)list->data;
+			}
+			else
+			{
+				/* bash style completion */
+				if (g_list_next(list) != NULL)
+				{
+					if (strlen (result) > elen) /* the largest common prefix is larger than nick, change the data */
+					{
+						if (prefix_len)
+							g_utf8_strncpy (buf, text, prefix_len);
+						strncat (buf, result, COMP_BUF - prefix_len);
+						cursor_pos = strlen (buf);
+						g_free(result);
+#if !GLIB_CHECK_VERSION(2,4,0)
+						g_utf8_validate (buf, -1, (const gchar **)&result);
+						(*result) = 0;
+#endif
+						if (postfix)
+						{
+							strcat (buf, " ");
+							strncat (buf, postfix, COMP_BUF - cursor_pos -1);
+						}
+						SPELL_ENTRY_SET_TEXT (t, buf);
+						SPELL_ENTRY_SET_POS (t, g_utf8_pointer_to_offset(buf, buf + cursor_pos));
+						buf[0] = 0;
+					}
+					else
+						g_free(result);
+					while (list)
+					{
+						len = strlen (buf);	/* current buffer */
+						elen = strlen (list->data);	/* next item to add */
+						if (len + elen + 2 >= COMP_BUF) /* +2 is space + null */
+						{
+							PrintText (sess, buf);
+							buf[0] = 0;
+							len = 0;
+						}
+						strcpy (buf + len, (char *) list->data);
+						strcpy (buf + len + elen, " ");
+						list = list->next;
+					}
+					PrintText (sess, buf);
+					g_completion_free(gcomp);
+					return 2;
+				}
+				/* Only one matching entry */
+				g_free(result);
+				result = list->data;
+			}
+		}
+	}
+	
+	if(result)
+	{
+		if (prefix_len)
+			g_utf8_strncpy(buf, text, prefix_len);
+		strncat (buf, result, COMP_BUF - (prefix_len + 3)); /* make sure nicksuffix and space fits */
+		if(!prefix_len && is_nick)
+			strcat (buf, &prefs.nick_suffix[0]);
+		strcat (buf, " ");
+		cursor_pos = strlen (buf);
+		if (postfix)
+			strncat (buf, postfix, COMP_BUF - cursor_pos - 2);
+		SPELL_ENTRY_SET_TEXT (t, buf);
+		SPELL_ENTRY_SET_POS (t, g_utf8_pointer_to_offset(buf, buf + cursor_pos));
+	}
+	if (gcomp)
+		g_completion_free(gcomp);
+	return 2;
+}
+#undef COMP_BUF
+
+static int
+key_action_comp_chng (GtkWidget * wid, GdkEventKey * ent, char *d1, char *d2,
+		struct session *sess)
+{
+	key_action_tab_comp(wid, ent, d1, d2, sess);
+	return 2;
+}
+
+
+static int
+key_action_replace (GtkWidget * wid, GdkEventKey * ent, char *d1, char *d2,
+						  struct session *sess)
+{
+	replace_handle (wid);
+	return 1;
+}
+
+
+static int
+key_action_move_tab_left (GtkWidget * wid, GdkEventKey * ent, char *d1,
+								  char *d2, struct session *sess)
+{
+	mg_move_tab (sess, +1);
+	return 2;						  /* don't allow default action */
+}
+
+static int
+key_action_move_tab_right (GtkWidget * wid, GdkEventKey * ent, char *d1,
+									char *d2, struct session *sess)
+{
+	mg_move_tab (sess, -1);
+	return 2;						  /* -''- */
+}
+
+static int
+key_action_move_tab_family_left (GtkWidget * wid, GdkEventKey * ent, char *d1,
+								  char *d2, struct session *sess)
+{
+	mg_move_tab_family (sess, +1);
+	return 2;						  /* don't allow default action */
+}
+
+static int
+key_action_move_tab_family_right (GtkWidget * wid, GdkEventKey * ent, char *d1,
+									char *d2, struct session *sess)
+{
+	mg_move_tab_family (sess, -1);
+	return 2;						  /* -''- */
+}
+
+static int
+key_action_put_history (GtkWidget * wid, GdkEventKey * ent, char *d1,
+									char *d2, struct session *sess)
+{
+	history_add (&sess->history, SPELL_ENTRY_GET_TEXT (wid));
+	SPELL_ENTRY_SET_TEXT (wid, "");
+	return 2;						  /* -''- */
+}
+
+
+/* -------- */
+
+
+#define STATE_SHIFT	GDK_SHIFT_MASK
+#define STATE_ALT		GDK_MOD1_MASK
+#define STATE_CTRL	GDK_CONTROL_MASK
+
+static void
+replace_handle (GtkWidget *t)
+{
+	const char *text, *postfix_pnt;
+	struct popup *pop;
+	GSList *list = replace_list;
+	char word[256];
+	char postfix[256];
+	char outbuf[4096];
+	int c, len, xlen;
+
+	text = SPELL_ENTRY_GET_TEXT (t);
+
+	len = strlen (text);
+	if (len < 1)
+		return;
+
+	for (c = len - 1; c > 0; c--)
+	{
+		if (text[c] == ' ')
+			break;
+	}
+	if (text[c] == ' ')
+		c++;
+	xlen = c;
+	if (len - c >= (sizeof (word) - 12))
+		return;
+	if (len - c < 1)
+		return;
+	memcpy (word, &text[c], len - c);
+	word[len - c] = 0;
+	len = strlen (word);
+	if (word[0] == '\'' && word[len] == '\'')
+		return;
+	postfix_pnt = NULL;
+	for (c = 0; c < len; c++)
+	{
+		if (word[c] == '\'')
+		{
+			postfix_pnt = &word[c + 1];
+			word[c] = 0;
+			break;
+		}
+	}
+
+	if (postfix_pnt != NULL)
+	{
+		if (strlen (postfix_pnt) > sizeof (postfix) - 12)
+			return;
+		strcpy (postfix, postfix_pnt);
+	}
+	while (list)
+	{
+		pop = (struct popup *) list->data;
+		if (strcmp (pop->name, word) == 0)
+		{
+			memcpy (outbuf, text, xlen);
+			outbuf[xlen] = 0;
+			if (postfix_pnt == NULL)
+				snprintf (word, sizeof (word), "%s", pop->cmd);
+			else
+				snprintf (word, sizeof (word), "%s%s", pop->cmd, postfix);
+			strcat (outbuf, word);
+			SPELL_ENTRY_SET_TEXT (t, outbuf);
+			SPELL_ENTRY_SET_POS (t, -1);
+			return;
+		}
+		list = list->next;
+	}
+}
+
diff --git a/src/fe-gtk/fkeys.h b/src/fe-gtk/fkeys.h
new file mode 100644
index 00000000..20cd4c73
--- /dev/null
+++ b/src/fe-gtk/fkeys.h
@@ -0,0 +1,5 @@
+void key_init (void);
+void key_dialog_show (void);
+int key_handle_key_press (GtkWidget * wid, GdkEventKey * evt, session *sess);
+int key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
+						 session *sess);
diff --git a/src/fe-gtk/gtkutil.c b/src/fe-gtk/gtkutil.c
new file mode 100644
index 00000000..63ab491b
--- /dev/null
+++ b/src/fe-gtk/gtkutil.c
@@ -0,0 +1,675 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+#define _FILE_OFFSET_BITS 64 /* allow selection of large files */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include "fe-gtk.h"
+
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkclist.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkmessagedialog.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkimage.h>
+#include <gtk/gtktooltips.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkspinbutton.h>
+#include <gtk/gtkclipboard.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtkcellrenderertext.h>
+#include <gtk/gtkcellrenderertoggle.h>
+#include <gtk/gtkversion.h>
+#include <gtk/gtkfilechooserdialog.h>
+
+#include "../common/xchat.h"
+#include "../common/fe.h"
+#include "../common/util.h"
+#include "gtkutil.h"
+#include "pixmaps.h"
+
+/* gtkutil.c, just some gtk wrappers */
+
+extern void path_part (char *file, char *path, int pathlen);
+
+
+struct file_req
+{
+	GtkWidget *dialog;
+	void *userdata;
+	filereqcallback callback;
+	int flags;		/* FRF_* flags */
+};
+
+static char last_dir[256] = "";
+
+
+static void
+gtkutil_file_req_destroy (GtkWidget * wid, struct file_req *freq)
+{
+	freq->callback (freq->userdata, NULL);
+	free (freq);
+}
+
+static void
+gtkutil_check_file (char *file, struct file_req *freq)
+{
+	struct stat st;
+	int axs = FALSE;
+
+	path_part (file, last_dir, sizeof (last_dir));
+
+	/* check if the file is readable or writable */
+	if (freq->flags & FRF_WRITE)
+	{
+		if (access (last_dir, W_OK) == 0)
+			axs = TRUE;
+	} else
+	{
+		if (stat (file, &st) != -1)
+		{
+			if (!S_ISDIR (st.st_mode) || (freq->flags & FRF_CHOOSEFOLDER))
+				axs = TRUE;
+		}
+	}
+
+	if (axs)
+	{
+		char *utf8_file;
+		/* convert to UTF8. It might be converted back to locale by
+			server.c's g_convert */
+		utf8_file = xchat_filename_to_utf8 (file, -1, NULL, NULL, NULL);
+		if (utf8_file)
+		{
+			freq->callback (freq->userdata, utf8_file);
+			g_free (utf8_file);
+		} else
+		{
+			fe_message ("Filename encoding is corrupt.", FE_MSG_ERROR);
+		}
+	} else
+	{
+		if (freq->flags & FRF_WRITE)
+			fe_message (_("Cannot write to that file."), FE_MSG_ERROR);
+		else
+			fe_message (_("Cannot read that file."), FE_MSG_ERROR);
+	}
+}
+
+static void
+gtkutil_file_req_done (GtkWidget * wid, struct file_req *freq)
+{
+	GSList *files, *cur;
+	GtkFileChooser *fs = GTK_FILE_CHOOSER (freq->dialog);
+
+	if (freq->flags & FRF_MULTIPLE)
+	{
+		files = cur = gtk_file_chooser_get_filenames (fs);
+		while (cur)
+		{
+			gtkutil_check_file (cur->data, freq);
+			g_free (cur->data);
+			cur = cur->next;
+		}
+		if (files)
+			g_slist_free (files);
+	} else
+	{
+		if (freq->flags & FRF_CHOOSEFOLDER)
+			gtkutil_check_file (gtk_file_chooser_get_current_folder (fs), freq);
+		else
+			gtkutil_check_file (gtk_file_chooser_get_filename (fs), freq);
+	}
+
+	/* this should call the "destroy" cb, where we free(freq) */
+	gtk_widget_destroy (freq->dialog);
+}
+
+static void
+gtkutil_file_req_response (GtkWidget *dialog, gint res, struct file_req *freq)
+{
+	switch (res)
+	{
+	case GTK_RESPONSE_ACCEPT:
+		gtkutil_file_req_done (dialog, freq);
+		break;
+
+	case GTK_RESPONSE_CANCEL:
+		/* this should call the "destroy" cb, where we free(freq) */
+		gtk_widget_destroy (freq->dialog);
+	}
+}
+
+void
+gtkutil_file_req (const char *title, void *callback, void *userdata, char *filter,
+						int flags)
+{
+	struct file_req *freq;
+	GtkWidget *dialog;
+	extern char *get_xdir_fs (void);
+
+	if (flags & FRF_WRITE)
+	{
+		dialog = gtk_file_chooser_dialog_new (title, NULL,
+												GTK_FILE_CHOOSER_ACTION_SAVE,
+												GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+												GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
+												NULL);
+		if (filter && filter[0])	/* filter becomes initial name when saving */
+		{
+			char temp[1024];
+			path_part (filter, temp, sizeof (temp));
+			gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), temp);
+			gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), file_part (filter));
+		}
+#if GTK_CHECK_VERSION(2,8,0)
+		if (!(flags & FRF_NOASKOVERWRITE))
+			gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
+#endif
+	}
+	else
+		dialog = gtk_file_chooser_dialog_new (title, NULL,
+												GTK_FILE_CHOOSER_ACTION_OPEN,
+												GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+												GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+												NULL);
+	if (flags & FRF_MULTIPLE)
+		gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dialog), TRUE);
+	if (last_dir[0])
+		gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), last_dir);
+	if (flags & FRF_ADDFOLDER)
+		gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog),
+														  get_xdir_fs (), NULL);
+	if (flags & FRF_CHOOSEFOLDER)
+	{
+		gtk_file_chooser_set_action (GTK_FILE_CHOOSER (dialog), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
+		gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), filter);
+	}
+	else
+	{
+		if (filter && (flags & FRF_FILTERISINITIAL))
+			gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), filter);
+	}
+
+	freq = malloc (sizeof (struct file_req));
+	freq->dialog = dialog;
+	freq->flags = flags;
+	freq->callback = callback;
+	freq->userdata = userdata;
+
+	g_signal_connect (G_OBJECT (dialog), "response",
+							G_CALLBACK (gtkutil_file_req_response), freq);
+	g_signal_connect (G_OBJECT (dialog), "destroy",
+						   G_CALLBACK (gtkutil_file_req_destroy), (gpointer) freq);
+	gtk_widget_show (dialog);
+}
+
+void
+gtkutil_destroy (GtkWidget * igad, GtkWidget * dgad)
+{
+	gtk_widget_destroy (dgad);
+}
+
+static void
+gtkutil_get_str_response (GtkDialog *dialog, gint arg1, gpointer entry)
+{
+	void (*callback) (int cancel, char *text, void *user_data);
+	char *text;
+	void *user_data;
+
+	text = (char *) gtk_entry_get_text (GTK_ENTRY (entry));
+	callback = g_object_get_data (G_OBJECT (dialog), "cb");
+	user_data = g_object_get_data (G_OBJECT (dialog), "ud");
+
+	switch (arg1)
+	{
+	case GTK_RESPONSE_REJECT:
+		callback (TRUE, text, user_data);
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		break;
+	case GTK_RESPONSE_ACCEPT:
+		callback (FALSE, text, user_data);
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		break;
+	}
+}
+
+static void
+gtkutil_str_enter (GtkWidget *entry, GtkWidget *dialog)
+{
+	gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
+}
+
+void
+fe_get_str (char *msg, char *def, void *callback, void *userdata)
+{
+	GtkWidget *dialog;
+	GtkWidget *entry;
+	GtkWidget *hbox;
+	GtkWidget *label;
+
+	dialog = gtk_dialog_new_with_buttons (msg, NULL, 0,
+										GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+										GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+										NULL);
+	gtk_box_set_homogeneous (GTK_BOX (GTK_DIALOG (dialog)->vbox), TRUE);
+	gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
+	hbox = gtk_hbox_new (TRUE, 0);
+
+	g_object_set_data (G_OBJECT (dialog), "cb", callback);
+	g_object_set_data (G_OBJECT (dialog), "ud", userdata);
+
+	entry = gtk_entry_new ();
+	g_signal_connect (G_OBJECT (entry), "activate",
+						 	G_CALLBACK (gtkutil_str_enter), dialog);
+	gtk_entry_set_text (GTK_ENTRY (entry), def);
+	gtk_box_pack_end (GTK_BOX (hbox), entry, 0, 0, 0);
+
+	label = gtk_label_new (msg);
+	gtk_box_pack_end (GTK_BOX (hbox), label, 0, 0, 0);
+
+	g_signal_connect (G_OBJECT (dialog), "response",
+						   G_CALLBACK (gtkutil_get_str_response), entry);
+
+	gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), hbox);
+
+	gtk_widget_show_all (dialog);
+}
+
+static void
+gtkutil_get_number_response (GtkDialog *dialog, gint arg1, gpointer spin)
+{
+	void (*callback) (int cancel, int value, void *user_data);
+	int num;
+	void *user_data;
+
+	num = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (spin));
+	callback = g_object_get_data (G_OBJECT (dialog), "cb");
+	user_data = g_object_get_data (G_OBJECT (dialog), "ud");
+
+	switch (arg1)
+	{
+	case GTK_RESPONSE_REJECT:
+		callback (TRUE, num, user_data);
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		break;
+	case GTK_RESPONSE_ACCEPT:
+		callback (FALSE, num, user_data);
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		break;
+	}
+}
+
+void
+fe_get_int (char *msg, int def, void *callback, void *userdata)
+{
+	GtkWidget *dialog;
+	GtkWidget *spin;
+	GtkWidget *hbox;
+	GtkWidget *label;
+	GtkAdjustment *adj;
+
+	dialog = gtk_dialog_new_with_buttons (msg, NULL, 0,
+										GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+										GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+										NULL);
+	gtk_box_set_homogeneous (GTK_BOX (GTK_DIALOG (dialog)->vbox), TRUE);
+	gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
+	hbox = gtk_hbox_new (TRUE, 0);
+
+	g_object_set_data (G_OBJECT (dialog), "cb", callback);
+	g_object_set_data (G_OBJECT (dialog), "ud", userdata);
+
+	spin = gtk_spin_button_new (NULL, 1, 0);
+	adj = gtk_spin_button_get_adjustment ((GtkSpinButton*)spin);
+	adj->lower = 0;
+	adj->upper = 1024;
+	adj->step_increment = 1;
+	gtk_adjustment_changed (adj);
+	gtk_spin_button_set_value ((GtkSpinButton*)spin, def);
+	gtk_box_pack_end (GTK_BOX (hbox), spin, 0, 0, 0);
+
+	label = gtk_label_new (msg);
+	gtk_box_pack_end (GTK_BOX (hbox), label, 0, 0, 0);
+
+	g_signal_connect (G_OBJECT (dialog), "response",
+						   G_CALLBACK (gtkutil_get_number_response), spin);
+
+	gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), hbox);
+
+	gtk_widget_show_all (dialog);
+}
+
+GtkWidget *
+gtkutil_button (GtkWidget *box, char *stock, char *tip, void *callback,
+					 void *userdata, char *labeltext)
+{
+	GtkWidget *wid, *img, *bbox;
+
+	wid = gtk_button_new ();
+
+	if (labeltext)
+	{
+		gtk_button_set_label (GTK_BUTTON (wid), labeltext);
+		gtk_button_set_image (GTK_BUTTON (wid), gtk_image_new_from_stock (stock, GTK_ICON_SIZE_MENU));
+		gtk_button_set_use_underline (GTK_BUTTON (wid), TRUE);
+		if (box)
+			gtk_container_add (GTK_CONTAINER (box), wid);
+	}
+	else
+	{
+		bbox = gtk_hbox_new (0, 0);
+		gtk_container_add (GTK_CONTAINER (wid), bbox);
+		gtk_widget_show (bbox);
+
+		img = gtk_image_new_from_stock (stock, GTK_ICON_SIZE_MENU);
+		if (stock == GTK_STOCK_GOTO_LAST)
+			gtk_widget_set_usize (img, 10, 6);
+		gtk_container_add (GTK_CONTAINER (bbox), img);
+		gtk_widget_show (img);
+		gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0);
+	}
+
+	g_signal_connect (G_OBJECT (wid), "clicked",
+							G_CALLBACK (callback), userdata);
+	gtk_widget_show (wid);
+	if (tip)
+		add_tip (wid, tip);
+
+	return wid;
+}
+
+void
+gtkutil_label_new (char *text, GtkWidget * box)
+{
+	GtkWidget *label = gtk_label_new (text);
+	gtk_container_add (GTK_CONTAINER (box), label);
+	gtk_widget_show (label);
+}
+
+GtkWidget *
+gtkutil_entry_new (int max, GtkWidget * box, void *callback,
+						 gpointer userdata)
+{
+	GtkWidget *entry = gtk_entry_new_with_max_length (max);
+	gtk_container_add (GTK_CONTAINER (box), entry);
+	if (callback)
+		g_signal_connect (G_OBJECT (entry), "changed",
+								G_CALLBACK (callback), userdata);
+	gtk_widget_show (entry);
+	return entry;
+}
+
+GtkWidget *
+gtkutil_clist_new (int columns, char *titles[],
+						 GtkWidget * box, int policy,
+						 void *select_callback, gpointer select_userdata,
+						 void *unselect_callback,
+						 gpointer unselect_userdata, int selection_mode)
+{
+	GtkWidget *clist, *win;
+
+	win = gtk_scrolled_window_new (0, 0);
+	gtk_container_add (GTK_CONTAINER (box), win);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (win),
+											  GTK_POLICY_AUTOMATIC, policy);
+	gtk_widget_show (win);
+
+	if (titles)
+		clist = gtk_clist_new_with_titles (columns, titles);
+	else
+		clist = gtk_clist_new (columns);
+
+	gtk_clist_set_selection_mode (GTK_CLIST (clist), selection_mode);
+	gtk_clist_column_titles_passive (GTK_CLIST (clist));
+	gtk_container_add (GTK_CONTAINER (win), clist);
+	if (select_callback)
+	{
+		g_signal_connect (G_OBJECT (clist), "select_row",
+								G_CALLBACK (select_callback), select_userdata);
+	}
+	if (unselect_callback)
+	{
+		g_signal_connect (G_OBJECT (clist), "unselect_row",
+								G_CALLBACK (unselect_callback), unselect_userdata);
+	}
+	gtk_widget_show (clist);
+
+	return clist;
+}
+
+int
+gtkutil_clist_selection (GtkWidget * clist)
+{
+	if (GTK_CLIST (clist)->selection)
+		return GPOINTER_TO_INT(GTK_CLIST (clist)->selection->data);
+	return -1;
+}
+
+static int
+int_compare (const int * elem1, const int * elem2)
+{
+	return (*elem1) - (*elem2);
+}
+
+int
+gtkutil_clist_multiple_selection (GtkWidget * clist, int ** rows, const int max_rows)
+{
+	int i = 0;
+	GList *tmp_clist;
+	*rows = malloc (sizeof (int) * max_rows );
+	memset( *rows, -1, max_rows * sizeof(int) );
+
+	for( tmp_clist = GTK_CLIST(clist)->selection;
+			tmp_clist && i < max_rows; tmp_clist = tmp_clist->next, i++)
+	{
+		(*rows)[i] = GPOINTER_TO_INT( tmp_clist->data );
+	}
+	qsort(*rows, i, sizeof(int), (void *)int_compare);
+	return i;
+
+}
+
+void
+add_tip (GtkWidget * wid, char *text)
+{
+	static GtkTooltips *tip = NULL;
+	if (!tip)
+		tip = gtk_tooltips_new ();
+	gtk_tooltips_set_tip (tip, wid, text, 0);
+}
+
+void
+show_and_unfocus (GtkWidget * wid)
+{
+	GTK_WIDGET_UNSET_FLAGS (wid, GTK_CAN_FOCUS);
+	gtk_widget_show (wid);
+}
+
+void
+gtkutil_set_icon (GtkWidget *win)
+{
+	gtk_window_set_icon (GTK_WINDOW (win), pix_xchat);
+}
+
+extern GtkWidget *parent_window;	/* maingui.c */
+
+GtkWidget *
+gtkutil_window_new (char *title, char *role, int width, int height, int flags)
+{
+	GtkWidget *win;
+
+	win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+	gtkutil_set_icon (win);
+#ifdef WIN32
+	gtk_window_set_wmclass (GTK_WINDOW (win), "XChat", "xchat");
+#endif
+	gtk_window_set_title (GTK_WINDOW (win), title);
+	gtk_window_set_default_size (GTK_WINDOW (win), width, height);
+	gtk_window_set_role (GTK_WINDOW (win), role);
+	if (flags & 1)
+		gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_MOUSE);
+	if ((flags & 2) && parent_window)
+	{
+		gtk_window_set_type_hint (GTK_WINDOW (win), GDK_WINDOW_TYPE_HINT_DIALOG);
+		gtk_window_set_transient_for (GTK_WINDOW (win), GTK_WINDOW (parent_window));
+	}
+
+	return win;
+}
+
+/* pass NULL as selection to paste to both clipboard & X11 text */
+void
+gtkutil_copy_to_clipboard (GtkWidget *widget, GdkAtom selection,
+                           const gchar *str)
+{
+	GtkWidget *win;
+	GtkClipboard *clip, *clip2;
+
+	win = gtk_widget_get_toplevel (GTK_WIDGET (widget));
+	if (GTK_WIDGET_TOPLEVEL (win))
+	{
+		int len = strlen (str);
+
+		if (selection)
+		{
+			clip = gtk_widget_get_clipboard (win, selection);
+			gtk_clipboard_set_text (clip, str, len);
+		} else
+		{
+			/* copy to both primary X selection and clipboard */
+			clip = gtk_widget_get_clipboard (win, GDK_SELECTION_PRIMARY);
+			clip2 = gtk_widget_get_clipboard (win, GDK_SELECTION_CLIPBOARD);
+			gtk_clipboard_set_text (clip, str, len);
+			gtk_clipboard_set_text (clip2, str, len);
+		}
+	}
+}
+
+/* Treeview util functions */
+
+GtkWidget *
+gtkutil_treeview_new (GtkWidget *box, GtkTreeModel *model,
+                      GtkTreeCellDataFunc mapper, ...)
+{
+	GtkWidget *win, *view;
+	GtkCellRenderer *renderer = NULL;
+	GtkTreeViewColumn *col;
+	va_list args;
+	int col_id = 0;
+	GType type;
+	char *title, *attr;
+
+	win = gtk_scrolled_window_new (0, 0);
+	gtk_container_add (GTK_CONTAINER (box), win);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (win),
+											  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+	gtk_widget_show (win);
+
+	view = gtk_tree_view_new_with_model (model);
+	/* the view now has a ref on the model, we can unref it */
+	g_object_unref (G_OBJECT (model));
+	gtk_container_add (GTK_CONTAINER (win), view);
+
+	va_start (args, mapper);
+	for (col_id = va_arg (args, int); col_id != -1; col_id = va_arg (args, int))
+	{
+		type = gtk_tree_model_get_column_type (model, col_id);
+		switch (type)
+		{
+			case G_TYPE_BOOLEAN:
+				renderer = gtk_cell_renderer_toggle_new ();
+				attr = "active";
+				break;
+			case G_TYPE_STRING:	/* fall through */
+			default:
+				renderer = gtk_cell_renderer_text_new ();
+				attr = "text";
+				break;
+		}
+
+		title = va_arg (args, char *);
+		if (mapper)	/* user-specified function to set renderer attributes */
+		{
+			col = gtk_tree_view_column_new_with_attributes (title, renderer, NULL);
+			gtk_tree_view_column_set_cell_data_func (col, renderer, mapper,
+			                                         GINT_TO_POINTER (col_id), NULL);
+		} else
+		{
+			/* just set the typical attribute for this type of renderer */
+			col = gtk_tree_view_column_new_with_attributes (title, renderer,
+			                                                attr, col_id, NULL);
+		}
+		gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
+	}
+
+	va_end (args);
+
+	return view;
+}
+
+gboolean
+gtkutil_treemodel_string_to_iter (GtkTreeModel *model, gchar *pathstr, GtkTreeIter *iter_ret)
+{
+	GtkTreePath *path = gtk_tree_path_new_from_string (pathstr);
+	gboolean success;
+
+	success = gtk_tree_model_get_iter (model, iter_ret, path);
+	gtk_tree_path_free (path);
+	return success;
+}
+
+/*gboolean
+gtkutil_treeview_get_selected_iter (GtkTreeView *view, GtkTreeIter *iter_ret)
+{
+	GtkTreeModel *store;
+	GtkTreeSelection *select;
+	
+	select = gtk_tree_view_get_selection (view);
+	return gtk_tree_selection_get_selected (select, &store, iter_ret);
+}*/
+
+gboolean
+gtkutil_treeview_get_selected (GtkTreeView *view, GtkTreeIter *iter_ret, ...)
+{
+	GtkTreeModel *store;
+	GtkTreeSelection *select;
+	gboolean has_selected;
+	va_list args;
+	
+	select = gtk_tree_view_get_selection (view);
+	has_selected = gtk_tree_selection_get_selected (select, &store, iter_ret);
+
+	if (has_selected) {
+		va_start (args, iter_ret);
+		gtk_tree_model_get_valist (store, iter_ret, args);
+		va_end (args);
+	}
+
+	return has_selected;
+}
+
diff --git a/src/fe-gtk/gtkutil.h b/src/fe-gtk/gtkutil.h
new file mode 100644
index 00000000..9bf9e058
--- /dev/null
+++ b/src/fe-gtk/gtkutil.h
@@ -0,0 +1,39 @@
+#include <gtk/gtktreeview.h>
+#include <gtk/gtktreemodel.h>
+
+typedef void (*filereqcallback) (void *, char *file);
+
+#define FRF_WRITE 1
+#define FRF_MULTIPLE 2
+#define FRF_ADDFOLDER 4
+#define FRF_CHOOSEFOLDER 8
+#define FRF_FILTERISINITIAL 16
+#define FRF_NOASKOVERWRITE 32
+
+void gtkutil_file_req (const char *title, void *callback, void *userdata, char *filter, int flags);
+void gtkutil_destroy (GtkWidget * igad, GtkWidget * dgad);
+GtkWidget *gtkutil_button (GtkWidget *box, char *stock, char *tip, void *callback,
+				 void *userdata, char *labeltext);
+void gtkutil_label_new (char *text, GtkWidget * box);
+GtkWidget *gtkutil_entry_new (int max, GtkWidget * box, void *callback,
+										gpointer userdata);
+GtkWidget *gtkutil_clist_new (int columns, char *titles[], GtkWidget * box,
+										int policy, void *select_callback,
+										gpointer select_userdata,
+										void *unselect_callback,
+										gpointer unselect_userdata, int selection_mode);
+int gtkutil_clist_selection (GtkWidget * clist);
+int gtkutil_clist_multiple_selection (GtkWidget * clist,
+													int ** rows, const int max_rows);
+void add_tip (GtkWidget * wid, char *text);
+void show_and_unfocus (GtkWidget * wid);
+void gtkutil_set_icon (GtkWidget *win);
+GtkWidget *gtkutil_window_new (char *title, char *role, int width, int height, int flags);
+void gtkutil_copy_to_clipboard (GtkWidget *widget, GdkAtom selection,
+                                const gchar *str);
+GtkWidget *gtkutil_treeview_new (GtkWidget *box, GtkTreeModel *model,
+                                 GtkTreeCellDataFunc mapper, ...);
+gboolean gtkutil_treemodel_string_to_iter (GtkTreeModel *model, gchar *pathstr, GtkTreeIter *iter_ret);
+gboolean gtkutil_treeview_get_selected_iter (GtkTreeView *view, GtkTreeIter *iter_ret);
+gboolean gtkutil_treeview_get_selected (GtkTreeView *view, GtkTreeIter *iter_ret, ...);
+
diff --git a/src/fe-gtk/ignoregui.c b/src/fe-gtk/ignoregui.c
new file mode 100644
index 00000000..dc5fce9c
--- /dev/null
+++ b/src/fe-gtk/ignoregui.c
@@ -0,0 +1,449 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "fe-gtk.h"
+
+#include <gtk/gtkcheckbutton.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkframe.h>
+#include <gtk/gtkhseparator.h>
+#include <gtk/gtkversion.h>
+
+#include <gtk/gtkliststore.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtkcellrenderertext.h>
+#include <gtk/gtkcellrenderertoggle.h>
+
+#include "../common/xchat.h"
+#include "../common/ignore.h"
+#include "../common/cfgfiles.h"
+#include "../common/fe.h"
+#include "gtkutil.h"
+#include "maingui.h"
+
+enum
+{
+	MASK_COLUMN,
+	CHAN_COLUMN,
+	PRIV_COLUMN,
+	NOTICE_COLUMN,
+	CTCP_COLUMN,
+	DCC_COLUMN,
+	INVITE_COLUMN,
+	UNIGNORE_COLUMN,
+	N_COLUMNS
+};
+
+static GtkWidget *ignorewin = 0;
+
+static GtkWidget *num_ctcp;
+static GtkWidget *num_priv;
+static GtkWidget *num_chan;
+static GtkWidget *num_noti;
+static GtkWidget *num_invi;
+
+static GtkTreeModel *
+get_store (void)
+{
+	return gtk_tree_view_get_model (g_object_get_data (G_OBJECT (ignorewin), "view"));
+}
+
+static int
+ignore_get_flags (GtkTreeModel *model, GtkTreeIter *iter)
+{
+	gboolean chan, priv, noti, ctcp, dcc, invi, unig;
+	int flags = 0;
+
+	gtk_tree_model_get (model, iter, 1, &chan, 2, &priv, 3, &noti,
+	                    4, &ctcp, 5, &dcc, 6, &invi, 7, &unig, -1);
+	if (chan)
+		flags |= IG_CHAN;
+	if (priv)
+		flags |= IG_PRIV;
+	if (noti)
+		flags |= IG_NOTI;
+	if (ctcp)
+		flags |= IG_CTCP;
+	if (dcc)
+		flags |= IG_DCC;
+	if (invi)
+		flags |= IG_INVI;
+	if (unig)
+		flags |= IG_UNIG;
+	return flags;
+}
+
+static void
+mask_edited (GtkCellRendererText *render, gchar *path, gchar *new, gpointer dat)
+{
+	GtkListStore *store = GTK_LIST_STORE (get_store ());
+	GtkTreeIter iter;
+	char *old;
+	int flags;
+
+	gtkutil_treemodel_string_to_iter (GTK_TREE_MODEL (store), path, &iter);
+	gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 0, &old, -1);
+	
+	if (!strcmp (old, new))	/* no change */
+		;
+	else if (ignore_exists (new))	/* duplicate, ignore */
+		fe_message (_("That mask already exists."), FE_MSG_ERROR);
+	else
+	{
+		/* delete old mask, and add new one with original flags */
+		ignore_del (old, NULL);
+		flags = ignore_get_flags (GTK_TREE_MODEL (store), &iter);
+		ignore_add (new, flags);
+
+		/* update tree */
+		gtk_list_store_set (store, &iter, MASK_COLUMN, new, -1);
+	}
+	g_free (old);
+	
+}
+
+static void
+option_toggled (GtkCellRendererToggle *render, gchar *path, gpointer data)
+{
+	GtkListStore *store = GTK_LIST_STORE (get_store ());
+	GtkTreeIter iter;
+	int col_id = GPOINTER_TO_INT (data);
+	gboolean active;
+	char *mask;
+	int flags;
+
+	gtkutil_treemodel_string_to_iter (GTK_TREE_MODEL (store), path, &iter);
+
+	/* update model */
+	gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, col_id, &active, -1);
+	gtk_list_store_set (store, &iter, col_id, !active, -1);
+
+	/* update ignore list */
+	gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 0, &mask, -1);
+	flags = ignore_get_flags (GTK_TREE_MODEL (store), &iter);
+	if (ignore_add (mask, flags) != 2)
+		g_warning ("ignore treeview is out of sync!\n");
+	
+	g_free (mask);
+}
+
+static GtkWidget *
+ignore_treeview_new (GtkWidget *box)
+{
+	GtkListStore *store;
+	GtkWidget *view;
+	GtkTreeViewColumn *col;
+	GtkCellRenderer *render;
+	int col_id;
+
+	store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING,
+	                            G_TYPE_BOOLEAN, G_TYPE_BOOLEAN,
+	                            G_TYPE_BOOLEAN, G_TYPE_BOOLEAN,
+	                            G_TYPE_BOOLEAN, G_TYPE_BOOLEAN,
+	                            G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
+	g_return_val_if_fail (store != NULL, NULL);
+
+	view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store),
+	                             NULL,
+	                             MASK_COLUMN, _("Mask"),
+	                             CHAN_COLUMN, _("Channel"),
+	                             PRIV_COLUMN, _("Private"),
+	                             NOTICE_COLUMN, _("Notice"),
+	                             CTCP_COLUMN, _("CTCP"),
+	                             DCC_COLUMN, _("DCC"),
+	                             INVITE_COLUMN, _("Invite"),
+	                             UNIGNORE_COLUMN, _("Unignore"),
+	                             -1);
+
+	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE);
+	gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), 0), TRUE);
+
+	/* attach to signals and customise columns */
+	for (col_id=0; (col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), col_id));
+	     col_id++)
+	{
+		GList *list = gtk_tree_view_column_get_cell_renderers (col);
+		GList *tmp;
+
+		for (tmp = list; tmp; tmp = tmp->next)
+		{
+			render = tmp->data;
+			if (col_id > 0)	/* it's a toggle button column */
+			{
+				g_signal_connect (render, "toggled", G_CALLBACK (option_toggled),
+				                  GINT_TO_POINTER (col_id));
+			} else	/* mask column */
+			{
+				g_object_set (G_OBJECT (render), "editable", TRUE, NULL);
+				g_signal_connect (render, "edited", G_CALLBACK (mask_edited), NULL);
+				/* make this column sortable */
+				gtk_tree_view_column_set_sort_column_id (col, col_id);
+				gtk_tree_view_column_set_min_width (col, 272);
+			}
+			/* centre titles */
+			gtk_tree_view_column_set_alignment (col, 0.5);
+		}
+
+		g_list_free (list);
+	}
+	
+	gtk_widget_show (view);
+	return view;
+}
+
+static void
+ignore_delete_entry_clicked (GtkWidget * wid, struct session *sess)
+{
+	GtkTreeView *view = g_object_get_data (G_OBJECT (ignorewin), "view");
+	GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
+	GtkTreeIter iter;
+	GtkTreePath *path;
+	char *mask = NULL;
+
+	if (gtkutil_treeview_get_selected (view, &iter, 0, &mask, -1))
+	{
+		/* delete this row, select next one */
+#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0)
+		gtk_list_store_remove (store, &iter);
+#else
+		if (gtk_list_store_remove (store, &iter))
+		{
+			path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
+			gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, 1.0, 0.0);
+			gtk_tree_view_set_cursor (view, path, NULL, FALSE);
+			gtk_tree_path_free (path);
+		}
+#endif
+
+		ignore_del (mask, NULL);
+		g_free (mask);
+	}
+}
+
+static void
+ignore_store_new (int cancel, char *mask, gpointer data)
+{
+	GtkTreeView *view = g_object_get_data (G_OBJECT (ignorewin), "view");
+	GtkListStore *store = GTK_LIST_STORE (get_store ());
+	GtkTreeIter iter;
+	GtkTreePath *path;
+	int flags = IG_CHAN | IG_PRIV | IG_NOTI | IG_CTCP | IG_DCC | IG_INVI;
+
+	if (cancel)
+		return;
+	/* check if it already exists */
+	if (ignore_exists (mask))
+	{
+		fe_message (_("That mask already exists."), FE_MSG_ERROR);
+		return;
+	}
+
+	ignore_add (mask, flags);
+
+	gtk_list_store_append (store, &iter);
+	/* ignore everything by default */
+	gtk_list_store_set (store, &iter, 0, mask, 1, TRUE, 2, TRUE, 3, TRUE,
+	                    4, TRUE, 5, TRUE, 6, TRUE, 7, FALSE, -1);
+	/* make sure the new row is visible and selected */
+	path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
+	gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, 1.0, 0.0);
+	gtk_tree_view_set_cursor (view, path, NULL, FALSE);
+	gtk_tree_path_free (path);
+}
+
+static void
+ignore_clear_entry_clicked (GtkWidget * wid, gpointer unused)
+{
+	GtkListStore *store = GTK_LIST_STORE (get_store ());
+	GtkTreeIter iter;
+	char *mask;
+
+	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter))
+	{
+		/* remove from ignore_list */
+		do
+		{
+			mask = NULL;
+			gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, MASK_COLUMN, &mask, -1);
+			ignore_del (mask, NULL);
+			g_free (mask);
+		}
+		while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
+
+		/* remove from GUI */
+		gtk_list_store_clear (store);
+	}
+}
+
+static void
+ignore_new_entry_clicked (GtkWidget * wid, struct session *sess)
+{
+	fe_get_str (_("Enter mask to ignore:"), "nick!userid@host.com",
+	            ignore_store_new, NULL);
+
+}
+
+static void
+close_ignore_gui_callback ()
+{
+	ignore_save ();
+	ignorewin = 0;
+}
+
+static GtkWidget *
+ignore_stats_entry (GtkWidget * box, char *label, int value)
+{
+	GtkWidget *wid;
+	char buf[16];
+
+	sprintf (buf, "%d", value);
+	gtkutil_label_new (label, box);
+	wid = gtkutil_entry_new (16, box, 0, 0);
+	gtk_widget_set_size_request (wid, 30, -1);
+	gtk_editable_set_editable (GTK_EDITABLE (wid), FALSE);
+	gtk_widget_set_sensitive (GTK_WIDGET (wid), FALSE);
+	gtk_entry_set_text (GTK_ENTRY (wid), buf);
+
+	return wid;
+}
+
+void
+ignore_gui_open ()
+{
+	GtkWidget *vbox, *box, *stat_box, *frame;
+	GtkWidget *view;
+	GtkListStore *store;
+	GtkTreeIter iter;
+	GSList *temp = ignore_list;
+	char *mask;
+	gboolean private, chan, notice, ctcp, dcc, invite, unignore;
+
+	if (ignorewin)
+	{
+		mg_bring_tofront (ignorewin);
+		return;
+	}
+
+	ignorewin =
+			  mg_create_generic_tab ("IgnoreList", _("XChat: Ignore list"),
+											FALSE, TRUE, close_ignore_gui_callback,
+											NULL, 600, 256, &vbox, 0);
+
+	view = ignore_treeview_new (vbox);
+	g_object_set_data (G_OBJECT (ignorewin), "view", view);
+	
+	frame = gtk_frame_new (_("Ignore Stats:"));
+	gtk_widget_show (frame);
+
+	stat_box = gtk_hbox_new (0, 2);
+	gtk_container_set_border_width (GTK_CONTAINER (stat_box), 6);
+	gtk_container_add (GTK_CONTAINER (frame), stat_box);
+	gtk_widget_show (stat_box);
+
+	num_chan = ignore_stats_entry (stat_box, _("Channel:"), ignored_chan);
+	num_priv = ignore_stats_entry (stat_box, _("Private:"), ignored_priv);
+	num_noti = ignore_stats_entry (stat_box, _("Notice:"), ignored_noti);
+	num_ctcp = ignore_stats_entry (stat_box, _("CTCP:"), ignored_ctcp);
+	num_invi = ignore_stats_entry (stat_box, _("Invite:"), ignored_invi);
+
+	gtk_box_pack_start (GTK_BOX (vbox), frame, 0, 0, 5);
+
+	box = gtk_hbutton_box_new ();
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (box), GTK_BUTTONBOX_SPREAD);
+	gtk_box_pack_start (GTK_BOX (vbox), box, FALSE, FALSE, 2);
+	gtk_container_set_border_width (GTK_CONTAINER (box), 5);
+	gtk_widget_show (box);
+
+	gtkutil_button (box, GTK_STOCK_NEW, 0, ignore_new_entry_clicked, 0,
+						 _("Add..."));
+	gtkutil_button (box, GTK_STOCK_DELETE, 0, ignore_delete_entry_clicked,
+						 0, _("Delete"));
+	gtkutil_button (box, GTK_STOCK_CLEAR, 0, ignore_clear_entry_clicked,
+						 0, _("Clear"));
+
+	store = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (view)));
+
+	while (temp)
+	{
+		struct ignore *ignore = temp->data;
+
+		mask = ignore->mask;
+		chan = (ignore->type & IG_CHAN);
+		private = (ignore->type & IG_PRIV);
+		notice = (ignore->type & IG_NOTI);
+		ctcp = (ignore->type & IG_CTCP);
+		dcc = (ignore->type & IG_DCC);
+		invite = (ignore->type & IG_INVI);
+		unignore = (ignore->type & IG_UNIG);
+
+		gtk_list_store_append (store, &iter);
+		gtk_list_store_set (store, &iter,
+		                    MASK_COLUMN, mask,
+		                    CHAN_COLUMN, chan,
+		                    PRIV_COLUMN, private,
+		                    NOTICE_COLUMN, notice,
+		                    CTCP_COLUMN, ctcp,
+		                    DCC_COLUMN, dcc,
+		                    INVITE_COLUMN, invite,
+		                    UNIGNORE_COLUMN, unignore,
+		                    -1);
+		
+		temp = temp->next;
+	}
+	gtk_widget_show (ignorewin);
+}
+
+void
+fe_ignore_update (int level)
+{
+	/* some ignores have changed via /ignore, we should update
+	   the gui now */
+	/* level 1 = the list only. */
+	/* level 2 = the numbers only. */
+	/* for now, ignore level 1, since the ignore GUI isn't realtime,
+	   only saved when you click OK */
+	char buf[16];
+
+	if (level == 2 && ignorewin)
+	{
+		sprintf (buf, "%d", ignored_ctcp);
+		gtk_entry_set_text (GTK_ENTRY (num_ctcp), buf);
+
+		sprintf (buf, "%d", ignored_noti);
+		gtk_entry_set_text (GTK_ENTRY (num_noti), buf);
+
+		sprintf (buf, "%d", ignored_chan);
+		gtk_entry_set_text (GTK_ENTRY (num_chan), buf);
+
+		sprintf (buf, "%d", ignored_invi);
+		gtk_entry_set_text (GTK_ENTRY (num_invi), buf);
+
+		sprintf (buf, "%d", ignored_priv);
+		gtk_entry_set_text (GTK_ENTRY (num_priv), buf);
+	}
+}
diff --git a/src/fe-gtk/joind.c b/src/fe-gtk/joind.c
new file mode 100644
index 00000000..ee5c56d1
--- /dev/null
+++ b/src/fe-gtk/joind.c
@@ -0,0 +1,257 @@
+/* Copyright (c) 2005 Peter Zelezny
+   All Rights Reserved.
+
+   joind.c - The Join Dialog.
+
+   Popups up when you connect without any autojoin channels and helps you
+   to find or join a channel.
+*/
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <gtk/gtkbbox.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkdialog.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkimage.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkradiobutton.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkwindow.h>
+
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "../common/server.h"
+#include "../common/fe.h"
+#include "fe-gtk.h"
+#include "chanlist.h"
+
+
+static void
+joind_radio2_cb (GtkWidget *radio, server *serv)
+{
+	if (GTK_TOGGLE_BUTTON (radio)->active)
+	{
+		gtk_widget_grab_focus (serv->gui->joind_entry);
+		gtk_editable_set_position (GTK_EDITABLE (serv->gui->joind_entry), 999);
+	}
+}
+
+static void
+joind_entryenter_cb (GtkWidget *entry, GtkWidget *ok)
+{
+	gtk_widget_grab_focus (ok);
+}
+
+static void
+joind_entryfocus_cb (GtkWidget *entry, GdkEventFocus *event, server *serv)
+{
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (serv->gui->joind_radio2), TRUE);
+}
+
+static void
+joind_destroy_cb (GtkWidget *win, server *serv)
+{
+	if (is_server (serv))
+		serv->gui->joind_win = NULL;
+}
+
+static void
+joind_ok_cb (GtkWidget *ok, server *serv)
+{
+	if (!is_server (serv))
+	{
+		gtk_widget_destroy (gtk_widget_get_toplevel (ok));
+		return;
+	}
+
+	/* do nothing */
+	if (GTK_TOGGLE_BUTTON (serv->gui->joind_radio1)->active)
+		goto xit;
+
+	/* join specific channel */
+	if (GTK_TOGGLE_BUTTON (serv->gui->joind_radio2)->active)
+	{
+		char *text = GTK_ENTRY (serv->gui->joind_entry)->text;
+		if (strlen (text) < 2)
+		{
+			fe_message (_("Channel name too short, try again."), FE_MSG_ERROR);
+			return;
+		}
+		serv->p_join (serv, text, "");
+		goto xit;
+	}
+
+	/* channel list */
+	chanlist_opengui (serv, TRUE);
+
+xit:
+	prefs.gui_join_dialog = 0;
+	if (GTK_TOGGLE_BUTTON (serv->gui->joind_check)->active)
+		prefs.gui_join_dialog = 1;
+
+	gtk_widget_destroy (serv->gui->joind_win);
+	serv->gui->joind_win = NULL;
+}
+
+static void
+joind_show_dialog (server *serv)
+{
+	GtkWidget *dialog1;
+	GtkWidget *dialog_vbox1;
+	GtkWidget *vbox1;
+	GtkWidget *hbox1;
+	GtkWidget *image1;
+	GtkWidget *vbox2;
+	GtkWidget *label;
+	GtkWidget *radiobutton1;
+	GtkWidget *radiobutton2;
+	GtkWidget *radiobutton3;
+	GSList *radiobutton1_group;
+	GtkWidget *hbox2;
+	GtkWidget *entry1;
+	GtkWidget *checkbutton1;
+	GtkWidget *dialog_action_area1;
+	GtkWidget *okbutton1;
+	char buf[256];
+	char buf2[256];
+
+	serv->gui->joind_win = dialog1 = gtk_dialog_new ();
+	gtk_window_set_title (GTK_WINDOW (dialog1), _("XChat: Connection Complete"));
+	gtk_window_set_type_hint (GTK_WINDOW (dialog1), GDK_WINDOW_TYPE_HINT_DIALOG);
+	gtk_window_set_position (GTK_WINDOW (dialog1), GTK_WIN_POS_MOUSE);
+
+	dialog_vbox1 = GTK_DIALOG (dialog1)->vbox;
+	gtk_widget_show (dialog_vbox1);
+
+	vbox1 = gtk_vbox_new (FALSE, 0);
+	gtk_widget_show (vbox1);
+	gtk_box_pack_start (GTK_BOX (dialog_vbox1), vbox1, TRUE, TRUE, 0);
+
+	hbox1 = gtk_hbox_new (FALSE, 0);
+	gtk_widget_show (hbox1);
+	gtk_box_pack_start (GTK_BOX (vbox1), hbox1, TRUE, TRUE, 0);
+
+	image1 = gtk_image_new_from_stock ("gtk-yes", GTK_ICON_SIZE_DIALOG);
+	gtk_widget_show (image1);
+	gtk_box_pack_start (GTK_BOX (hbox1), image1, FALSE, TRUE, 24);
+	gtk_misc_set_alignment (GTK_MISC (image1), 0.5, 0.06);
+
+	vbox2 = gtk_vbox_new (FALSE, 10);
+	gtk_container_set_border_width (GTK_CONTAINER (vbox2), 6);
+	gtk_widget_show (vbox2);
+	gtk_box_pack_start (GTK_BOX (hbox1), vbox2, TRUE, TRUE, 0);
+
+	snprintf (buf2, sizeof (buf2), _("Connection to %s complete."),
+				 server_get_network (serv, TRUE));
+	snprintf (buf, sizeof (buf), "\n<b>%s</b>", buf2);
+	label = gtk_label_new (buf);
+	gtk_widget_show (label);
+	gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0);
+	gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+
+	label = gtk_label_new (_("In the Server-List window, no channel (chat room) has been entered to be automatically joined for this network."));
+	gtk_widget_show (label);
+	gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0);
+	GTK_LABEL (label)->wrap = TRUE;
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+
+	label = gtk_label_new (_("What would you like to do next?"));
+	gtk_widget_show (label);
+	gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+
+	serv->gui->joind_radio1 = radiobutton1 = gtk_radio_button_new_with_mnemonic (NULL, _("_Nothing, I'll join a channel later."));
+	gtk_widget_show (radiobutton1);
+	gtk_box_pack_start (GTK_BOX (vbox2), radiobutton1, FALSE, FALSE, 0);
+	radiobutton1_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radiobutton1));
+
+	hbox2 = gtk_hbox_new (FALSE, 0);
+	gtk_widget_show (hbox2);
+	gtk_box_pack_start (GTK_BOX (vbox2), hbox2, FALSE, FALSE, 0);
+
+	serv->gui->joind_radio2 = radiobutton2 = gtk_radio_button_new_with_mnemonic (NULL, _("_Join this channel:"));
+	gtk_widget_show (radiobutton2);
+	gtk_box_pack_start (GTK_BOX (hbox2), radiobutton2, FALSE, FALSE, 0);
+	gtk_radio_button_set_group (GTK_RADIO_BUTTON (radiobutton2), radiobutton1_group);
+	radiobutton1_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radiobutton2));
+
+	serv->gui->joind_entry = entry1 = gtk_entry_new ();
+	gtk_entry_set_text (GTK_ENTRY (entry1), "#");
+	gtk_widget_show (entry1);
+	gtk_box_pack_start (GTK_BOX (hbox2), entry1, TRUE, TRUE, 8);
+
+	snprintf (buf, sizeof (buf), "<small>     %s</small>",
+				 _("If you know the name of the channel you want to join, enter it here."));
+	label = gtk_label_new (buf);
+	gtk_widget_show (label);
+	gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0);
+	gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+
+	radiobutton3 = gtk_radio_button_new_with_mnemonic (NULL, _("O_pen the Channel-List window."));
+	gtk_widget_show (radiobutton3);
+	gtk_box_pack_start (GTK_BOX (vbox2), radiobutton3, FALSE, FALSE, 0);
+	gtk_radio_button_set_group (GTK_RADIO_BUTTON (radiobutton3), radiobutton1_group);
+	radiobutton1_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radiobutton3));
+
+	snprintf (buf, sizeof (buf), "<small>     %s</small>",
+				 _("Retrieving the Channel-List may take a minute or two."));
+	label = gtk_label_new (buf);
+	gtk_widget_show (label);
+	gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0);
+	gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+
+	serv->gui->joind_check = checkbutton1 = gtk_check_button_new_with_mnemonic (_("_Always show this dialog after connecting."));
+	if (prefs.gui_join_dialog)
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbutton1), TRUE);
+	gtk_widget_show (checkbutton1);
+	gtk_box_pack_start (GTK_BOX (vbox1), checkbutton1, FALSE, FALSE, 0);
+
+	dialog_action_area1 = GTK_DIALOG (dialog1)->action_area;
+	gtk_widget_show (dialog_action_area1);
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (dialog_action_area1), GTK_BUTTONBOX_END);
+
+	okbutton1 = gtk_button_new_from_stock ("gtk-ok");
+	gtk_widget_show (okbutton1);
+	gtk_box_pack_end (GTK_BOX (GTK_DIALOG (dialog1)->action_area), okbutton1, FALSE, TRUE, 0);
+	GTK_WIDGET_SET_FLAGS (okbutton1, GTK_CAN_DEFAULT);
+
+	g_signal_connect (G_OBJECT (dialog1), "destroy",
+							G_CALLBACK (joind_destroy_cb), serv);
+	g_signal_connect (G_OBJECT (entry1), "focus_in_event",
+							G_CALLBACK (joind_entryfocus_cb), serv);
+	g_signal_connect (G_OBJECT (entry1), "activate",
+							G_CALLBACK (joind_entryenter_cb), okbutton1);
+	g_signal_connect (G_OBJECT (radiobutton2), "toggled",
+							G_CALLBACK (joind_radio2_cb), serv);
+	g_signal_connect (G_OBJECT (okbutton1), "clicked",
+							G_CALLBACK (joind_ok_cb), serv);
+
+	gtk_widget_grab_focus (okbutton1);
+	gtk_widget_show_all (dialog1);
+}
+
+void
+joind_open (server *serv)
+{
+	if (prefs.gui_join_dialog)
+		joind_show_dialog (serv);
+}
+
+void
+joind_close (server *serv)
+{
+	if (serv->gui->joind_win)
+	{
+		gtk_widget_destroy (serv->gui->joind_win);
+		serv->gui->joind_win = NULL;
+	}
+}
diff --git a/src/fe-gtk/joind.h b/src/fe-gtk/joind.h
new file mode 100644
index 00000000..aa0fd0ad
--- /dev/null
+++ b/src/fe-gtk/joind.h
@@ -0,0 +1,2 @@
+void joind_open (server *serv);
+void joind_close (server *serv);
diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c
new file mode 100644
index 00000000..ef269f95
--- /dev/null
+++ b/src/fe-gtk/maingui.c
@@ -0,0 +1,3811 @@
+/* X-Chat
+ * Copyright (C) 1998-2005 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include <gtk/gtkarrow.h>
+#include <gtk/gtktogglebutton.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkeventbox.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhpaned.h>
+#include <gtk/gtkvpaned.h>
+#include <gtk/gtkframe.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkmenuitem.h>
+#include <gtk/gtkprogressbar.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtknotebook.h>
+#include <gtk/gtkimage.h>
+#include <gtk/gtkmessagedialog.h>
+#include <gtk/gtkcheckmenuitem.h>
+#include <gtk/gtkcheckbutton.h>
+#include <gtk/gtkbbox.h>
+#include <gtk/gtkvscrollbar.h>
+
+#include "../common/xchat.h"
+#include "../common/fe.h"
+#include "../common/server.h"
+#include "../common/xchatc.h"
+#include "../common/outbound.h"
+#include "../common/inbound.h"
+#include "../common/plugin.h"
+#include "../common/modes.h"
+#include "../common/url.h"
+#include "fe-gtk.h"
+#include "banlist.h"
+#include "gtkutil.h"
+#include "joind.h"
+#include "palette.h"
+#include "maingui.h"
+#include "menu.h"
+#include "fkeys.h"
+#include "userlistgui.h"
+#include "chanview.h"
+#include "pixmaps.h"
+#include "plugin-tray.h"
+#include "xtext.h"
+
+#ifdef USE_GTKSPELL
+#include <gtk/gtktextview.h>
+#include <gtkspell/gtkspell.h>
+#endif
+
+#ifdef USE_LIBSEXY
+#include "sexy-spell-entry.h"
+#endif
+
+#define GUI_SPACING (3)
+#define GUI_BORDER (0)
+#define SCROLLBAR_SPACING (2)
+
+enum
+{
+	POS_INVALID = 0,
+	POS_TOPLEFT = 1,
+	POS_BOTTOMLEFT = 2,
+	POS_TOPRIGHT = 3,
+	POS_BOTTOMRIGHT = 4,
+	POS_TOP = 5,	/* for tabs only */
+	POS_BOTTOM = 6,
+	POS_HIDDEN = 7
+};
+
+/* two different types of tabs */
+#define TAG_IRC 0		/* server, channel, dialog */
+#define TAG_UTIL 1	/* dcc, notify, chanlist */
+
+static void mg_create_entry (session *sess, GtkWidget *box);
+static void mg_link_irctab (session *sess, int focus);
+
+static session_gui static_mg_gui;
+static session_gui *mg_gui = NULL;	/* the shared irc tab */
+static int ignore_chanmode = FALSE;
+static const char chan_flags[] = { 't', 'n', 's', 'i', 'p', 'm', 'l', 'k' };
+
+static chan *active_tab = NULL;	/* active tab */
+GtkWidget *parent_window = NULL;			/* the master window */
+
+GtkStyle *input_style;
+
+static PangoAttrList *away_list;
+static PangoAttrList *newdata_list;
+static PangoAttrList *nickseen_list;
+static PangoAttrList *newmsg_list;
+static PangoAttrList *plain_list = NULL;
+
+
+#ifdef USE_GTKSPELL
+
+/* use these when it's a GtkTextView instead of GtkEntry */
+
+char *
+SPELL_ENTRY_GET_TEXT (GtkWidget *entry)
+{
+	static char *last = NULL;	/* warning: don't overlap 2 GET_TEXT calls! */
+	GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry));
+	GtkTextIter start_iter, end_iter;
+
+	gtk_text_buffer_get_iter_at_offset (buf, &start_iter, 0);
+	gtk_text_buffer_get_end_iter (buf, &end_iter);
+	g_free (last);
+	last = gtk_text_buffer_get_text (buf, &start_iter, &end_iter, FALSE);
+	return last;
+}
+
+void
+SPELL_ENTRY_SET_POS (GtkWidget *entry, int pos)
+{
+	GtkTextIter iter;
+	GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry));
+
+	gtk_text_buffer_get_iter_at_offset (buf, &iter, pos);
+	gtk_text_buffer_place_cursor (buf, &iter);
+}
+
+int
+SPELL_ENTRY_GET_POS (GtkWidget *entry)
+{
+	GtkTextIter cursor;
+	GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry));
+
+	gtk_text_buffer_get_iter_at_mark (buf, &cursor, gtk_text_buffer_get_insert (buf));
+	return gtk_text_iter_get_offset (&cursor);
+}
+
+void
+SPELL_ENTRY_INSERT (GtkWidget *entry, const char *text, int len, int *pos)
+{
+	GtkTextIter iter;
+	GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry));
+
+	/* len is bytes. pos is chars. */
+	gtk_text_buffer_get_iter_at_offset (buf, &iter, *pos);
+	gtk_text_buffer_insert (buf, &iter, text, len);
+	*pos += g_utf8_strlen (text, len);
+}
+
+#endif
+
+static PangoAttrList *
+mg_attr_list_create (GdkColor *col, int size)
+{
+	PangoAttribute *attr;
+	PangoAttrList *list;
+
+	list = pango_attr_list_new ();
+
+	if (col)
+	{
+		attr = pango_attr_foreground_new (col->red, col->green, col->blue);
+		attr->start_index = 0;
+		attr->end_index = 0xffff;
+		pango_attr_list_insert (list, attr);
+	}
+
+	if (size > 0)
+	{
+		attr = pango_attr_scale_new (size == 1 ? PANGO_SCALE_SMALL : PANGO_SCALE_X_SMALL);
+		attr->start_index = 0;
+		attr->end_index = 0xffff;
+		pango_attr_list_insert (list, attr);
+	}
+
+	return list;
+}
+
+static void
+mg_create_tab_colors (void)
+{
+	if (plain_list)
+	{
+		pango_attr_list_unref (plain_list);
+		pango_attr_list_unref (newmsg_list);
+		pango_attr_list_unref (newdata_list);
+		pango_attr_list_unref (nickseen_list);
+		pango_attr_list_unref (away_list);
+	}
+
+	plain_list = mg_attr_list_create (NULL, prefs.tab_small);
+	newdata_list = mg_attr_list_create (&colors[COL_NEW_DATA], prefs.tab_small);
+	nickseen_list = mg_attr_list_create (&colors[COL_HILIGHT], prefs.tab_small);
+	newmsg_list = mg_attr_list_create (&colors[COL_NEW_MSG], prefs.tab_small);
+	away_list = mg_attr_list_create (&colors[COL_AWAY], FALSE);
+}
+
+#ifdef WIN32
+#define WINVER 0x0501	/* needed for vc6? */
+#include <windows.h>
+#include <gdk/gdkwin32.h>
+
+/* Flash the taskbar button on Windows when there's a highlight event. */
+
+static void
+flash_window (GtkWidget *win)
+{
+	FLASHWINFO fi;
+	static HMODULE user = NULL;
+	static BOOL (*flash) (PFLASHWINFO) = NULL;
+
+	if (!user)
+	{
+		user = GetModuleHandleA ("USER32");
+		if (!user)
+			return;	/* this should never fail */
+	}
+
+	if (!flash)
+	{
+		flash = (void *)GetProcAddress (user, "FlashWindowEx");
+		if (!flash)
+			return;	/* this fails on NT4.0 and Win95 */
+	}
+
+	fi.cbSize = sizeof (fi);
+	fi.hwnd = GDK_WINDOW_HWND (win->window);
+	fi.dwFlags = FLASHW_ALL | FLASHW_TIMERNOFG;
+	fi.uCount = 0;
+	fi.dwTimeout = 500;
+	flash (&fi);
+	/*FlashWindowEx (&fi);*/
+}
+#else
+
+#ifdef USE_XLIB
+#include <gdk/gdkx.h>
+
+static void
+set_window_urgency (GtkWidget *win, gboolean set)
+{
+	XWMHints *hints;
+
+	hints = XGetWMHints(GDK_WINDOW_XDISPLAY(win->window), GDK_WINDOW_XWINDOW(win->window));
+	if (set)
+		hints->flags |= XUrgencyHint;
+	else
+		hints->flags &= ~XUrgencyHint;
+	XSetWMHints(GDK_WINDOW_XDISPLAY(win->window),
+	            GDK_WINDOW_XWINDOW(win->window), hints);
+	XFree(hints);
+}
+
+static void
+flash_window (GtkWidget *win)
+{
+	set_window_urgency (win, TRUE);
+}
+
+static void
+unflash_window (GtkWidget *win)
+{
+	set_window_urgency (win, FALSE);
+}
+#endif
+#endif
+
+/* flash the taskbar button */
+
+void
+fe_flash_window (session *sess)
+{
+#if defined(WIN32) || defined(USE_XLIB)
+	if (fe_gui_info (sess, 0) != 1)	/* only do it if not focused */
+		flash_window (sess->gui->window);
+#endif
+}
+
+/* set a tab plain, red, light-red, or blue */
+
+void
+fe_set_tab_color (struct session *sess, int col)
+{
+	struct session *server_sess = sess->server->server_session;
+	if (sess->gui->is_tab && (col == 0 || sess != current_tab))
+	{
+		switch (col)
+		{
+		case 0:	/* no particular color (theme default) */
+			sess->new_data = FALSE;
+			sess->msg_said = FALSE;
+			sess->nick_said = FALSE;
+			chan_set_color (sess->res->tab, plain_list);
+			break;
+		case 1:	/* new data has been displayed (dark red) */
+			sess->new_data = TRUE;
+			sess->msg_said = FALSE;
+			sess->nick_said = FALSE;
+			chan_set_color (sess->res->tab, newdata_list);
+
+			if (chan_is_collapsed (sess->res->tab))
+			{
+				server_sess->new_data = TRUE;
+				server_sess->msg_said = FALSE;
+				server_sess->nick_said = FALSE;
+				chan_set_color (chan_get_parent (sess->res->tab), newdata_list);
+			}
+				
+			break;
+		case 2:	/* new message arrived in channel (light red) */
+			sess->new_data = FALSE;
+			sess->msg_said = TRUE;
+			sess->nick_said = FALSE;
+			chan_set_color (sess->res->tab, newmsg_list);
+			
+			if (chan_is_collapsed (sess->res->tab))
+			{
+				server_sess->new_data = FALSE;
+				server_sess->msg_said = TRUE;
+				server_sess->nick_said = FALSE;
+				chan_set_color (chan_get_parent (sess->res->tab), newmsg_list);
+			}
+			
+			break;
+		case 3:	/* your nick has been seen (blue) */
+			sess->new_data = FALSE;
+			sess->msg_said = FALSE;
+			sess->nick_said = TRUE;
+			chan_set_color (sess->res->tab, nickseen_list);
+
+			if (chan_is_collapsed (sess->res->tab))
+			{
+				server_sess->new_data = FALSE;
+				server_sess->msg_said = FALSE;
+				server_sess->nick_said = TRUE;
+				chan_set_color (chan_get_parent (sess->res->tab), nickseen_list);
+			}
+				
+			break;
+		}
+	}
+}
+
+static void
+mg_set_myself_away (session_gui *gui, gboolean away)
+{
+	gtk_label_set_attributes (GTK_LABEL (GTK_BIN (gui->nick_label)->child),
+									  away ? away_list : NULL);
+}
+
+/* change the little icon to the left of your nickname */
+
+void
+mg_set_access_icon (session_gui *gui, GdkPixbuf *pix, gboolean away)
+{
+	if (gui->op_xpm)
+	{
+		if (pix == gtk_image_get_pixbuf (GTK_IMAGE (gui->op_xpm))) /* no change? */
+		{
+			mg_set_myself_away (gui, away);
+			return;
+		}
+
+		gtk_widget_destroy (gui->op_xpm);
+		gui->op_xpm = NULL;
+	}
+
+	if (pix)
+	{
+		gui->op_xpm = gtk_image_new_from_pixbuf (pix);
+		gtk_box_pack_start (GTK_BOX (gui->nick_box), gui->op_xpm, 0, 0, 0);
+		gtk_widget_show (gui->op_xpm);
+	}
+
+	mg_set_myself_away (gui, away);
+}
+
+static gboolean
+mg_inputbox_focus (GtkWidget *widget, GdkEventFocus *event, session_gui *gui)
+{
+	GSList *list;
+	session *sess;
+
+	if (gui->is_tab)
+		return FALSE;
+
+	list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+		if (sess->gui == gui)
+		{
+			current_sess = sess;
+			if (!sess->server->server_session)
+				sess->server->server_session = sess;
+			break;
+		}
+		list = list->next;
+	}
+
+	return FALSE;
+}
+
+void
+mg_inputbox_cb (GtkWidget *igad, session_gui *gui)
+{
+	char *cmd;
+	static int ignore = FALSE;
+	GSList *list;
+	session *sess = NULL;
+
+	if (ignore)
+		return;
+
+	cmd = SPELL_ENTRY_GET_TEXT (igad);
+	if (cmd[0] == 0)
+		return;
+
+	cmd = strdup (cmd);
+
+	/* avoid recursive loop */
+	ignore = TRUE;
+	SPELL_ENTRY_SET_TEXT (igad, "");
+	ignore = FALSE;
+
+	/* where did this event come from? */
+	if (gui->is_tab)
+	{
+		sess = current_tab;
+	} else
+	{
+		list = sess_list;
+		while (list)
+		{
+			sess = list->data;
+			if (sess->gui == gui)
+				break;
+			list = list->next;
+		}
+		if (!list)
+			sess = NULL;
+	}
+
+	if (sess)
+		handle_multiline (sess, cmd, TRUE, FALSE);
+
+	free (cmd);
+}
+
+static gboolean
+has_key (char *modes)
+{
+	if (!modes)
+		return FALSE;
+	/* this is a crude check, but "-k" can't exist, so it works. */
+	while (*modes)
+	{
+		if (*modes == 'k')
+			return TRUE;
+		if (*modes == ' ')
+			return FALSE;
+		modes++;
+	}
+	return FALSE;
+}
+
+void
+fe_set_title (session *sess)
+{
+	char tbuf[512];
+	int type;
+
+	if (sess->gui->is_tab && sess != current_tab)
+		return;
+
+	type = sess->type;
+
+	if (sess->server->connected == FALSE && sess->type != SESS_DIALOG)
+		goto def;
+
+	switch (type)
+	{
+	case SESS_DIALOG:
+		snprintf (tbuf, sizeof (tbuf), DISPLAY_NAME": %s %s @ %s",
+					 _("Dialog with"), sess->channel, server_get_network (sess->server, TRUE));
+		break;
+	case SESS_SERVER:
+		snprintf (tbuf, sizeof (tbuf), DISPLAY_NAME": %s @ %s",
+					 sess->server->nick, server_get_network (sess->server, TRUE));
+		break;
+	case SESS_CHANNEL:
+		/* don't display keys in the titlebar */
+		if ((!(prefs.gui_tweaks & 16)) && has_key (sess->current_modes))
+			snprintf (tbuf, sizeof (tbuf),
+						 DISPLAY_NAME": %s @ %s / %s",
+						 sess->server->nick, server_get_network (sess->server, TRUE),
+						 sess->channel);
+		else
+			snprintf (tbuf, sizeof (tbuf),
+						 DISPLAY_NAME": %s @ %s / %s (%s)",
+						 sess->server->nick, server_get_network (sess->server, TRUE),
+						 sess->channel, sess->current_modes ? sess->current_modes : "");
+		if (prefs.gui_tweaks & 1)
+			snprintf (tbuf + strlen (tbuf), 9, " (%d)", sess->total);
+		break;
+	case SESS_NOTICES:
+	case SESS_SNOTICES:
+		snprintf (tbuf, sizeof (tbuf), DISPLAY_NAME": %s @ %s (notices)",
+					 sess->server->nick, server_get_network (sess->server, TRUE));
+		break;
+	default:
+	def:
+		gtk_window_set_title (GTK_WINDOW (sess->gui->window), DISPLAY_NAME);
+		return;
+	}
+
+	gtk_window_set_title (GTK_WINDOW (sess->gui->window), tbuf);
+}
+
+static gboolean
+mg_windowstate_cb (GtkWindow *wid, GdkEventWindowState *event, gpointer userdata)
+{
+	prefs.gui_win_state = 0;
+	if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED)
+		prefs.gui_win_state = 1;
+
+	if ((event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) &&
+		 (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED) &&
+		 (prefs.gui_tray_flags & 4))
+	{
+		tray_toggle_visibility (TRUE);
+		gtk_window_deiconify (wid);
+	}
+
+	return FALSE;
+}
+
+static gboolean
+mg_configure_cb (GtkWidget *wid, GdkEventConfigure *event, session *sess)
+{
+	if (sess == NULL)			/* for the main_window */
+	{
+		if (mg_gui)
+		{
+			if (prefs.mainwindow_save)
+			{
+				sess = current_sess;
+				gtk_window_get_position (GTK_WINDOW (wid), &prefs.mainwindow_left,
+												 &prefs.mainwindow_top);
+				gtk_window_get_size (GTK_WINDOW (wid), &prefs.mainwindow_width,
+											&prefs.mainwindow_height);
+			}
+		}
+	}
+
+	if (sess)
+	{
+		if (sess->type == SESS_DIALOG && prefs.mainwindow_save)
+		{
+			gtk_window_get_position (GTK_WINDOW (wid), &prefs.dialog_left,
+											 &prefs.dialog_top);
+			gtk_window_get_size (GTK_WINDOW (wid), &prefs.dialog_width,
+										&prefs.dialog_height);
+		}
+
+		if (((GtkXText *) sess->gui->xtext)->transparent)
+			gtk_widget_queue_draw (sess->gui->xtext);
+	}
+
+	return FALSE;
+}
+
+/* move to a non-irc tab */
+
+static void
+mg_show_generic_tab (GtkWidget *box)
+{
+	int num;
+	GtkWidget *f = NULL;
+
+#if defined(GTK_WIDGET_HAS_FOCUS)
+	if (current_sess && GTK_WIDGET_HAS_FOCUS (current_sess->gui->input_box))
+#else
+	if (current_sess && gtk_widget_has_focus (current_sess->gui->input_box))
+#endif
+		f = current_sess->gui->input_box;
+
+	num = gtk_notebook_page_num (GTK_NOTEBOOK (mg_gui->note_book), box);
+	gtk_notebook_set_current_page (GTK_NOTEBOOK (mg_gui->note_book), num);
+	gtk_tree_view_set_model (GTK_TREE_VIEW (mg_gui->user_tree), NULL);
+	gtk_window_set_title (GTK_WINDOW (mg_gui->window),
+								 g_object_get_data (G_OBJECT (box), "title"));
+	gtk_widget_set_sensitive (mg_gui->menu, FALSE);
+
+	if (f)
+		gtk_widget_grab_focus (f);
+}
+
+/* a channel has been focused */
+
+static void
+mg_focus (session *sess)
+{
+	if (sess->gui->is_tab)
+		current_tab = sess;
+	current_sess = sess;
+
+	/* dirty trick to avoid auto-selection */
+	SPELL_ENTRY_SET_EDITABLE (sess->gui->input_box, FALSE);
+	gtk_widget_grab_focus (sess->gui->input_box);
+	SPELL_ENTRY_SET_EDITABLE (sess->gui->input_box, TRUE);
+
+	sess->server->front_session = sess;
+
+	if (sess->server->server_session != NULL)
+	{
+		if (sess->server->server_session->type != SESS_SERVER)
+			sess->server->server_session = sess;
+	} else
+	{
+		sess->server->server_session = sess;
+	}
+
+	if (sess->new_data || sess->nick_said || sess->msg_said)
+	{
+		sess->nick_said = FALSE;
+		sess->msg_said = FALSE;
+		sess->new_data = FALSE;
+		/* when called via mg_changui_new, is_tab might be true, but
+			sess->res->tab is still NULL. */
+		if (sess->res->tab)
+			fe_set_tab_color (sess, 0);
+	}
+}
+
+static int
+mg_progressbar_update (GtkWidget *bar)
+{
+	static int type = 0;
+	static float pos = 0;
+
+	pos += 0.05;
+	if (pos >= 0.99)
+	{
+		if (type == 0)
+		{
+			type = 1;
+			gtk_progress_bar_set_orientation ((GtkProgressBar *) bar,
+														 GTK_PROGRESS_RIGHT_TO_LEFT);
+		} else
+		{
+			type = 0;
+			gtk_progress_bar_set_orientation ((GtkProgressBar *) bar,
+														 GTK_PROGRESS_LEFT_TO_RIGHT);
+		}
+		pos = 0.05;
+	}
+	gtk_progress_bar_set_fraction ((GtkProgressBar *) bar, pos);
+	return 1;
+}
+
+void
+mg_progressbar_create (session_gui *gui)
+{
+	gui->bar = gtk_progress_bar_new ();
+	gtk_box_pack_start (GTK_BOX (gui->nick_box), gui->bar, 0, 0, 0);
+	gtk_widget_show (gui->bar);
+	gui->bartag = fe_timeout_add (50, mg_progressbar_update, gui->bar);
+}
+
+void
+mg_progressbar_destroy (session_gui *gui)
+{
+	fe_timeout_remove (gui->bartag);
+	gtk_widget_destroy (gui->bar);
+	gui->bar = 0;
+	gui->bartag = 0;
+}
+
+/* switching tabs away from this one, so remember some info about it! */
+
+static void
+mg_unpopulate (session *sess)
+{
+	restore_gui *res;
+	session_gui *gui;
+	int i;
+
+	gui = sess->gui;
+	res = sess->res;
+
+	res->input_text = strdup (SPELL_ENTRY_GET_TEXT (gui->input_box));
+	res->topic_text = strdup (GTK_ENTRY (gui->topic_entry)->text);
+	res->limit_text = strdup (GTK_ENTRY (gui->limit_entry)->text);
+	res->key_text = strdup (GTK_ENTRY (gui->key_entry)->text);
+	if (gui->laginfo)
+		res->lag_text = strdup (gtk_label_get_text (GTK_LABEL (gui->laginfo)));
+	if (gui->throttleinfo)
+		res->queue_text = strdup (gtk_label_get_text (GTK_LABEL (gui->throttleinfo)));
+
+	for (i = 0; i < NUM_FLAG_WIDS - 1; i++)
+		res->flag_wid_state[i] = GTK_TOGGLE_BUTTON (gui->flag_wid[i])->active;
+
+	res->old_ul_value = userlist_get_value (gui->user_tree);
+	if (gui->lagometer)
+		res->lag_value = gtk_progress_bar_get_fraction (
+													GTK_PROGRESS_BAR (gui->lagometer));
+	if (gui->throttlemeter)
+		res->queue_value = gtk_progress_bar_get_fraction (
+													GTK_PROGRESS_BAR (gui->throttlemeter));
+
+	if (gui->bar)
+	{
+		res->c_graph = TRUE;	/* still have a graph, just not visible now */
+		mg_progressbar_destroy (gui);
+	}
+}
+
+static void
+mg_restore_label (GtkWidget *label, char **text)
+{
+	if (!label)
+		return;
+
+	if (*text)
+	{
+		gtk_label_set_text (GTK_LABEL (label), *text);
+		free (*text);
+		*text = NULL;
+	} else
+	{
+		gtk_label_set_text (GTK_LABEL (label), "");
+	}
+}
+
+static void
+mg_restore_entry (GtkWidget *entry, char **text)
+{
+	if (*text)
+	{
+		gtk_entry_set_text (GTK_ENTRY (entry), *text);
+		free (*text);
+		*text = NULL;
+	} else
+	{
+		gtk_entry_set_text (GTK_ENTRY (entry), "");
+	}
+	gtk_editable_set_position (GTK_EDITABLE (entry), -1);
+}
+
+static void
+mg_restore_speller (GtkWidget *entry, char **text)
+{
+	if (*text)
+	{
+		SPELL_ENTRY_SET_TEXT (entry, *text);
+		free (*text);
+		*text = NULL;
+	} else
+	{
+		SPELL_ENTRY_SET_TEXT (entry, "");
+	}
+	SPELL_ENTRY_SET_POS (entry, -1);
+}
+
+void
+mg_set_topic_tip (session *sess)
+{
+	char *text;
+
+	switch (sess->type)
+	{
+	case SESS_CHANNEL:
+		if (sess->topic)
+		{
+			text = g_strdup_printf (_("Topic for %s is: %s"), sess->channel,
+						 sess->topic);
+			add_tip (sess->gui->topic_entry, text);
+			g_free (text);
+		} else
+			add_tip (sess->gui->topic_entry, _("No topic is set"));
+		break;
+	default:
+		if (GTK_ENTRY (sess->gui->topic_entry)->text &&
+			 GTK_ENTRY (sess->gui->topic_entry)->text[0])
+			add_tip (sess->gui->topic_entry, GTK_ENTRY (sess->gui->topic_entry)->text);
+		else
+			add_tip (sess->gui->topic_entry, NULL);
+	}
+}
+
+static void
+mg_hide_empty_pane (GtkPaned *pane)
+{
+#if defined(GTK_WIDGET_VISIBLE)
+	if ((pane->child1 == NULL || !GTK_WIDGET_VISIBLE (pane->child1)) &&
+		 (pane->child2 == NULL || !GTK_WIDGET_VISIBLE (pane->child2)))
+#else
+	if ((pane->child1 == NULL || !gtk_widget_get_visible (pane->child1)) &&
+		 (pane->child2 == NULL || !gtk_widget_get_visible (pane->child2)))
+#endif
+	{
+		gtk_widget_hide (GTK_WIDGET (pane));
+		return;
+	}
+
+	gtk_widget_show (GTK_WIDGET (pane));
+}
+
+static void
+mg_hide_empty_boxes (session_gui *gui)
+{
+	/* hide empty vpanes - so the handle is not shown */
+	mg_hide_empty_pane ((GtkPaned*)gui->vpane_right);
+	mg_hide_empty_pane ((GtkPaned*)gui->vpane_left);
+}
+
+static void
+mg_userlist_showhide (session *sess, int show)
+{
+	session_gui *gui = sess->gui;
+	int handle_size;
+
+	if (show)
+	{
+		gtk_widget_show (gui->user_box);
+		gui->ul_hidden = 0;
+
+		gtk_widget_style_get (GTK_WIDGET (gui->hpane_right), "handle-size", &handle_size, NULL);
+		gtk_paned_set_position (GTK_PANED (gui->hpane_right), GTK_WIDGET (gui->hpane_right)->allocation.width - (prefs.gui_pane_right_size + handle_size));
+	}
+	else
+	{
+		gtk_widget_hide (gui->user_box);
+		gui->ul_hidden = 1;
+	}
+
+	mg_hide_empty_boxes (gui);
+}
+
+static gboolean
+mg_is_userlist_and_tree_combined (void)
+{
+	if (prefs.tab_pos == POS_TOPLEFT && prefs.gui_ulist_pos == POS_BOTTOMLEFT)
+		return TRUE;
+	if (prefs.tab_pos == POS_BOTTOMLEFT && prefs.gui_ulist_pos == POS_TOPLEFT)
+		return TRUE;
+
+	if (prefs.tab_pos == POS_TOPRIGHT && prefs.gui_ulist_pos == POS_BOTTOMRIGHT)
+		return TRUE;
+	if (prefs.tab_pos == POS_BOTTOMRIGHT && prefs.gui_ulist_pos == POS_TOPRIGHT)
+		return TRUE;
+
+	return FALSE;
+}
+
+/* decide if the userlist should be shown or hidden for this tab */
+
+void
+mg_decide_userlist (session *sess, gboolean switch_to_current)
+{
+	/* when called from menu.c we need this */
+	if (sess->gui == mg_gui && switch_to_current)
+		sess = current_tab;
+
+	if (prefs.hideuserlist)
+	{
+		mg_userlist_showhide (sess, FALSE);
+		return;
+	}
+
+	switch (sess->type)
+	{
+	case SESS_SERVER:
+	case SESS_DIALOG:
+	case SESS_NOTICES:
+	case SESS_SNOTICES:
+		if (mg_is_userlist_and_tree_combined ())
+			mg_userlist_showhide (sess, TRUE);	/* show */
+		else
+			mg_userlist_showhide (sess, FALSE);	/* hide */
+		break;
+	default:		
+		mg_userlist_showhide (sess, TRUE);	/* show */
+	}
+}
+
+static void
+mg_userlist_toggle_cb (GtkWidget *button, gpointer userdata)
+{
+	prefs.hideuserlist = !prefs.hideuserlist;
+	mg_decide_userlist (current_sess, FALSE);
+	gtk_widget_grab_focus (current_sess->gui->input_box);
+}
+
+static int ul_tag = 0;
+
+static gboolean
+mg_populate_userlist (session *sess)
+{
+	session_gui *gui;
+
+	if (!sess)
+		sess = current_tab;
+
+	if (is_session (sess))
+	{
+		gui = sess->gui;
+		if (sess->type == SESS_DIALOG)
+			mg_set_access_icon (sess->gui, NULL, sess->server->is_away);
+		else
+			mg_set_access_icon (sess->gui, get_user_icon (sess->server, sess->me), sess->server->is_away);
+		userlist_show (sess);
+		userlist_set_value (sess->gui->user_tree, sess->res->old_ul_value);
+	}
+
+	ul_tag = 0;
+	return 0;
+}
+
+/* fill the irc tab with a new channel */
+
+static void
+mg_populate (session *sess)
+{
+	session_gui *gui = sess->gui;
+	restore_gui *res = sess->res;
+	int i, render = TRUE;
+	guint16 vis = gui->ul_hidden;
+
+	switch (sess->type)
+	{
+	case SESS_DIALOG:
+		/* show the dialog buttons */
+		gtk_widget_show (gui->dialogbutton_box);
+		/* hide the chan-mode buttons */
+		gtk_widget_hide (gui->topicbutton_box);
+		/* hide the userlist */
+		mg_decide_userlist (sess, FALSE);
+		/* shouldn't edit the topic */
+		gtk_editable_set_editable (GTK_EDITABLE (gui->topic_entry), FALSE);
+		break;
+	case SESS_SERVER:
+		if (prefs.chanmodebuttons)
+			gtk_widget_show (gui->topicbutton_box);
+		/* hide the dialog buttons */
+		gtk_widget_hide (gui->dialogbutton_box);
+		/* hide the userlist */
+		mg_decide_userlist (sess, FALSE);
+		/* shouldn't edit the topic */
+		gtk_editable_set_editable (GTK_EDITABLE (gui->topic_entry), FALSE);
+		break;
+	default:
+		/* hide the dialog buttons */
+		gtk_widget_hide (gui->dialogbutton_box);
+		if (prefs.chanmodebuttons)
+			gtk_widget_show (gui->topicbutton_box);
+		/* show the userlist */
+		mg_decide_userlist (sess, FALSE);
+		/* let the topic be editted */
+		gtk_editable_set_editable (GTK_EDITABLE (gui->topic_entry), TRUE);
+	}
+
+	/* move to THE irc tab */
+	if (gui->is_tab)
+		gtk_notebook_set_current_page (GTK_NOTEBOOK (gui->note_book), 0);
+
+	/* xtext size change? Then don't render, wait for the expose caused
+      by showing/hidding the userlist */
+	if (vis != gui->ul_hidden && gui->user_box->allocation.width > 1)
+		render = FALSE;
+
+	gtk_xtext_buffer_show (GTK_XTEXT (gui->xtext), res->buffer, render);
+
+	if (gui->is_tab)
+		gtk_widget_set_sensitive (gui->menu, TRUE);
+
+	/* restore all the GtkEntry's */
+	mg_restore_entry (gui->topic_entry, &res->topic_text);
+	mg_restore_speller (gui->input_box, &res->input_text);
+	mg_restore_entry (gui->key_entry, &res->key_text);
+	mg_restore_entry (gui->limit_entry, &res->limit_text);
+	mg_restore_label (gui->laginfo, &res->lag_text);
+	mg_restore_label (gui->throttleinfo, &res->queue_text);
+
+	mg_focus (sess);
+	fe_set_title (sess);
+
+	/* this one flickers, so only change if necessary */
+	if (strcmp (sess->server->nick, gtk_button_get_label (GTK_BUTTON (gui->nick_label))) != 0)
+		gtk_button_set_label (GTK_BUTTON (gui->nick_label), sess->server->nick);
+
+	/* this is slow, so make it a timeout event */
+	if (!gui->is_tab)
+	{
+		mg_populate_userlist (sess);
+	} else
+	{
+		if (ul_tag == 0)
+			ul_tag = g_idle_add ((GSourceFunc)mg_populate_userlist, NULL);
+	}
+
+	fe_userlist_numbers (sess);
+
+	/* restore all the channel mode buttons */
+	ignore_chanmode = TRUE;
+	for (i = 0; i < NUM_FLAG_WIDS - 1; i++)
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gui->flag_wid[i]),
+												res->flag_wid_state[i]);
+	ignore_chanmode = FALSE;
+
+	if (gui->lagometer)
+	{
+		gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (gui->lagometer),
+												 res->lag_value);
+		if (res->lag_tip)
+			add_tip (sess->gui->lagometer->parent, res->lag_tip);
+	}
+	if (gui->throttlemeter)
+	{
+		gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (gui->throttlemeter),
+												 res->queue_value);
+		if (res->queue_tip)
+			add_tip (sess->gui->throttlemeter->parent, res->queue_tip);
+	}
+
+	/* did this tab have a connecting graph? restore it.. */
+	if (res->c_graph)
+	{
+		res->c_graph = FALSE;
+		mg_progressbar_create (gui);
+	}
+
+	/* menu items */
+	GTK_CHECK_MENU_ITEM (gui->menu_item[MENU_ID_AWAY])->active = sess->server->is_away;
+	gtk_widget_set_sensitive (gui->menu_item[MENU_ID_AWAY], sess->server->connected);
+	gtk_widget_set_sensitive (gui->menu_item[MENU_ID_JOIN], sess->server->end_of_motd);
+	gtk_widget_set_sensitive (gui->menu_item[MENU_ID_DISCONNECT],
+									  sess->server->connected || sess->server->recondelay_tag);
+
+	mg_set_topic_tip (sess);
+
+	plugin_emit_dummy_print (sess, "Focus Tab");
+}
+
+void
+mg_bring_tofront_sess (session *sess)	/* IRC tab or window */
+{
+	if (sess->gui->is_tab)
+		chan_focus (sess->res->tab);
+	else
+		gtk_window_present (GTK_WINDOW (sess->gui->window));
+}
+
+void
+mg_bring_tofront (GtkWidget *vbox)	/* non-IRC tab or window */
+{
+	chan *ch;
+
+	ch = g_object_get_data (G_OBJECT (vbox), "ch");
+	if (ch)
+		chan_focus (ch);
+	else
+		gtk_window_present (GTK_WINDOW (gtk_widget_get_toplevel (vbox)));
+}
+
+void
+mg_switch_page (int relative, int num)
+{
+	if (mg_gui)
+		chanview_move_focus (mg_gui->chanview, relative, num);
+}
+
+/* a toplevel IRC window was destroyed */
+
+static void
+mg_topdestroy_cb (GtkWidget *win, session *sess)
+{
+/*	printf("enter mg_topdestroy. sess %p was destroyed\n", sess);*/
+
+	/* kill the text buffer */
+	gtk_xtext_buffer_free (sess->res->buffer);
+	/* kill the user list */
+	g_object_unref (G_OBJECT (sess->res->user_model));
+
+	session_free (sess);	/* tell xchat.c about it */
+}
+
+/* cleanup an IRC tab */
+
+static void
+mg_ircdestroy (session *sess)
+{
+	GSList *list;
+
+	/* kill the text buffer */
+	gtk_xtext_buffer_free (sess->res->buffer);
+	/* kill the user list */
+	g_object_unref (G_OBJECT (sess->res->user_model));
+
+	session_free (sess);	/* tell xchat.c about it */
+
+	if (mg_gui == NULL)
+	{
+/*		puts("-> mg_gui is already NULL");*/
+		return;
+	}
+
+	list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+		if (sess->gui->is_tab)
+		{
+/*			puts("-> some tabs still remain");*/
+			return;
+		}
+		list = list->next;
+	}
+
+/*	puts("-> no tabs left, killing main tabwindow");*/
+	gtk_widget_destroy (mg_gui->window);
+	active_tab = NULL;
+	mg_gui = NULL;
+	parent_window = NULL;
+}
+
+static void
+mg_tab_close_cb (GtkWidget *dialog, gint arg1, session *sess)
+{
+	GSList *list, *next;
+
+	gtk_widget_destroy (dialog);
+	if (arg1 == GTK_RESPONSE_OK && is_session (sess))
+	{
+		/* force it NOT to send individual PARTs */
+		sess->server->sent_quit = TRUE;
+
+		for (list = sess_list; list;)
+		{
+			next = list->next;
+			if (((session *)list->data)->server == sess->server &&
+				 ((session *)list->data) != sess)
+				fe_close_window ((session *)list->data);
+			list = next;
+		}
+
+		/* just send one QUIT - better for BNCs */
+		sess->server->sent_quit = FALSE;
+		fe_close_window (sess);
+	}
+}
+
+void
+mg_tab_close (session *sess)
+{
+	GtkWidget *dialog;
+	GSList *list;
+	int i;
+
+	if (chan_remove (sess->res->tab, FALSE))
+		mg_ircdestroy (sess);
+	else
+	{
+		for (i = 0, list = sess_list; list; list = list->next)
+			if (((session *)list->data)->server == sess->server)
+				i++;
+		dialog = gtk_message_dialog_new (GTK_WINDOW (parent_window), 0,
+						GTK_MESSAGE_WARNING, GTK_BUTTONS_OK_CANCEL,
+						_("This server still has %d channels or dialogs associated with it. "
+						  "Close them all?"), i);
+		g_signal_connect (G_OBJECT (dialog), "response",
+								G_CALLBACK (mg_tab_close_cb), sess);
+		if (prefs.tab_layout)
+		{
+			gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
+		}
+		else
+		{
+			gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER_ON_PARENT);		
+		}
+		gtk_widget_show (dialog);
+	}
+}
+
+static void
+mg_menu_destroy (GtkWidget *menu, gpointer userdata)
+{
+	gtk_widget_destroy (menu);
+	g_object_unref (menu);
+}
+
+void
+mg_create_icon_item (char *label, char *stock, GtkWidget *menu,
+							void *callback, void *userdata)
+{
+	GtkWidget *item;
+
+	item = create_icon_menu (label, stock, TRUE);
+	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+	g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (callback),
+							userdata);
+	gtk_widget_show (item);
+}
+
+static int
+mg_count_networks (void)
+{
+	int cons = 0;
+	GSList *list;
+
+	for (list = serv_list; list; list = list->next)
+	{
+		if (((server *)list->data)->connected)
+			cons++;
+	}
+	return cons;
+}
+
+static int
+mg_count_dccs (void)
+{
+	GSList *list;
+	struct DCC *dcc;
+	int dccs = 0;
+
+	list = dcc_list;
+	while (list)
+	{
+		dcc = list->data;
+		if ((dcc->type == TYPE_SEND || dcc->type == TYPE_RECV) &&
+			 dcc->dccstat == STAT_ACTIVE)
+			dccs++;
+		list = list->next;
+	}
+
+	return dccs;
+}
+
+void
+mg_open_quit_dialog (gboolean minimize_button)
+{
+	static GtkWidget *dialog = NULL;
+	GtkWidget *dialog_vbox1;
+	GtkWidget *table1;
+	GtkWidget *image;
+	GtkWidget *checkbutton1;
+	GtkWidget *label;
+	GtkWidget *dialog_action_area1;
+	GtkWidget *button;
+	char *text, *connecttext;
+	int cons;
+	int dccs;
+
+	if (dialog)
+	{
+		gtk_window_present (GTK_WINDOW (dialog));
+		return;
+	}
+
+	dccs = mg_count_dccs ();
+	cons = mg_count_networks ();
+	if (dccs + cons == 0 || !prefs.gui_quit_dialog)
+	{
+		xchat_exit ();
+		return;
+	}
+
+	dialog = gtk_dialog_new ();
+	gtk_container_set_border_width (GTK_CONTAINER (dialog), 6);
+	gtk_window_set_title (GTK_WINDOW (dialog), _("Quit XChat?"));
+	gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window));
+	gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+	gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
+
+	dialog_vbox1 = GTK_DIALOG (dialog)->vbox;
+	gtk_widget_show (dialog_vbox1);
+
+	table1 = gtk_table_new (2, 2, FALSE);
+	gtk_widget_show (table1);
+	gtk_box_pack_start (GTK_BOX (dialog_vbox1), table1, TRUE, TRUE, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (table1), 6);
+	gtk_table_set_row_spacings (GTK_TABLE (table1), 12);
+	gtk_table_set_col_spacings (GTK_TABLE (table1), 12);
+
+	image = gtk_image_new_from_stock ("gtk-dialog-warning", GTK_ICON_SIZE_DIALOG);
+	gtk_widget_show (image);
+	gtk_table_attach (GTK_TABLE (table1), image, 0, 1, 0, 1,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (GTK_FILL), 0, 0);
+
+	checkbutton1 = gtk_check_button_new_with_mnemonic (_("Don't ask next time."));
+	gtk_widget_show (checkbutton1);
+	gtk_table_attach (GTK_TABLE (table1), checkbutton1, 0, 2, 1, 2,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+							(GtkAttachOptions) (0), 0, 4);
+
+	connecttext = g_strdup_printf (_("You are connected to %i IRC networks."), cons);
+	text = g_strdup_printf ("<span weight=\"bold\" size=\"larger\">%s</span>\n\n%s\n%s",
+								_("Are you sure you want to quit?"),
+								cons ? connecttext : "",
+								dccs ? _("Some file transfers are still active.") : "");
+	g_free (connecttext);
+	label = gtk_label_new (text);
+	g_free (text);
+	gtk_widget_show (label);
+	gtk_table_attach (GTK_TABLE (table1), label, 1, 2, 0, 1,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_SHRINK | GTK_FILL),
+							(GtkAttachOptions) (GTK_EXPAND | GTK_SHRINK), 0, 0);
+	gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+
+	dialog_action_area1 = GTK_DIALOG (dialog)->action_area;
+	gtk_widget_show (dialog_action_area1);
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (dialog_action_area1),
+										GTK_BUTTONBOX_END);
+
+	if (minimize_button)
+	{
+		button = gtk_button_new_with_mnemonic (_("_Minimize to Tray"));
+		gtk_widget_show (button);
+		gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, 1);
+	}
+
+	button = gtk_button_new_from_stock ("gtk-cancel");
+	gtk_widget_show (button);
+	gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
+											GTK_RESPONSE_CANCEL);
+	gtk_widget_grab_focus (button);
+
+	button = gtk_button_new_from_stock ("gtk-quit");
+	gtk_widget_show (button);
+	gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, 0);
+
+	gtk_widget_show (dialog);
+
+	switch (gtk_dialog_run (GTK_DIALOG (dialog)))
+	{
+	case 0:
+		if (GTK_TOGGLE_BUTTON (checkbutton1)->active)
+			prefs.gui_quit_dialog = 0;
+		xchat_exit ();
+		break;
+	case 1: /* minimize to tray */
+		if (GTK_TOGGLE_BUTTON (checkbutton1)->active)
+		{
+			prefs.gui_tray_flags |= 1;
+			/*prefs.gui_quit_dialog = 0;*/
+		}
+		/* force tray icon ON, if not already */
+		if (!prefs.gui_tray)
+		{
+			prefs.gui_tray = 1;
+			tray_apply_setup ();
+		}
+		tray_toggle_visibility (TRUE);
+		break;
+	}
+
+	gtk_widget_destroy (dialog);
+	dialog = NULL;
+}
+
+void
+mg_close_sess (session *sess)
+{
+	if (sess_list->next == NULL)
+	{
+		mg_open_quit_dialog (FALSE);
+		return;
+	}
+
+	fe_close_window (sess);
+}
+
+static int
+mg_chan_remove (chan *ch)
+{
+	/* remove the tab from chanview */
+	chan_remove (ch, TRUE);
+	/* any tabs left? */
+	if (chanview_get_size (mg_gui->chanview) < 1)
+	{
+		/* if not, destroy the main tab window */
+		gtk_widget_destroy (mg_gui->window);
+		current_tab = NULL;
+		active_tab = NULL;
+		mg_gui = NULL;
+		parent_window = NULL;
+		return TRUE;
+	}
+	return FALSE;
+}
+
+/* destroy non-irc tab/window */
+
+static void
+mg_close_gen (chan *ch, GtkWidget *box)
+{
+	char *title = g_object_get_data (G_OBJECT (box), "title");
+
+	if (title)
+		free (title);
+	if (!ch)
+		ch = g_object_get_data (G_OBJECT (box), "ch");
+	if (ch)
+	{
+		/* remove from notebook */
+		gtk_widget_destroy (box);
+		/* remove the tab from chanview */
+		mg_chan_remove (ch);
+	} else
+	{
+		gtk_widget_destroy (gtk_widget_get_toplevel (box));
+	}
+}
+
+/* the "X" close button has been pressed (tab-view) */
+
+static void
+mg_xbutton_cb (chanview *cv, chan *ch, int tag, gpointer userdata)
+{
+	if (tag == TAG_IRC)	/* irc tab */
+		mg_close_sess (userdata);
+	else						/* non-irc utility tab */
+		mg_close_gen (ch, userdata);
+}
+
+static void
+mg_link_gentab (chan *ch, GtkWidget *box)
+{
+	int num;
+	GtkWidget *win;
+
+	g_object_ref (box);
+
+	num = gtk_notebook_page_num (GTK_NOTEBOOK (mg_gui->note_book), box);
+	gtk_notebook_remove_page (GTK_NOTEBOOK (mg_gui->note_book), num);
+	mg_chan_remove (ch);
+
+	win = gtkutil_window_new (g_object_get_data (G_OBJECT (box), "title"), "",
+									  GPOINTER_TO_INT (g_object_get_data (G_OBJECT (box), "w")),
+									  GPOINTER_TO_INT (g_object_get_data (G_OBJECT (box), "h")),
+									  3);
+	/* so it doesn't try to chan_remove (there's no tab anymore) */
+	g_object_steal_data (G_OBJECT (box), "ch");
+	gtk_container_set_border_width (GTK_CONTAINER (box), 0);
+	gtk_container_add (GTK_CONTAINER (win), box);
+	gtk_widget_show (win);
+
+	g_object_unref (box);
+}
+
+static void
+mg_detach_tab_cb (GtkWidget *item, chan *ch)
+{
+	if (chan_get_tag (ch) == TAG_IRC)	/* IRC tab */
+	{
+		/* userdata is session * */
+		mg_link_irctab (chan_get_userdata (ch), 1);
+		return;
+	}
+
+	/* userdata is GtkWidget * */
+	mg_link_gentab (ch, chan_get_userdata (ch));	/* non-IRC tab */
+}
+
+static void
+mg_destroy_tab_cb (GtkWidget *item, chan *ch)
+{
+	/* treat it just like the X button press */
+	mg_xbutton_cb (mg_gui->chanview, ch, chan_get_tag (ch), chan_get_userdata (ch));
+}
+
+static void
+mg_color_insert (GtkWidget *item, gpointer userdata)
+{
+	char buf[32];
+	char *text;
+	int num = GPOINTER_TO_INT (userdata);
+
+	if (num > 99)
+	{
+		switch (num)
+		{
+		case 100:
+			text = "\002"; break;
+		case 101:
+			text = "\037"; break;
+		case 102:
+			text = "\035"; break;
+		default:
+			text = "\017"; break;
+		}
+		key_action_insert (current_sess->gui->input_box, 0, text, 0, 0);
+	} else
+	{
+		sprintf (buf, "\003%02d", num);
+		key_action_insert (current_sess->gui->input_box, 0, buf, 0, 0);
+	}
+}
+
+static void
+mg_markup_item (GtkWidget *menu, char *text, int arg)
+{
+	GtkWidget *item;
+
+	item = gtk_menu_item_new_with_label ("");
+	gtk_label_set_markup (GTK_LABEL (GTK_BIN (item)->child), text);
+	g_signal_connect (G_OBJECT (item), "activate",
+							G_CALLBACK (mg_color_insert), GINT_TO_POINTER (arg));
+	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+	gtk_widget_show (item);
+}
+
+GtkWidget *
+mg_submenu (GtkWidget *menu, char *text)
+{
+	GtkWidget *submenu, *item;
+
+	item = gtk_menu_item_new_with_mnemonic (text);
+	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+	gtk_widget_show (item);
+
+	submenu = gtk_menu_new ();
+	gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
+	gtk_widget_show (submenu);
+
+	return submenu;
+}
+
+static void
+mg_create_color_menu (GtkWidget *menu, session *sess)
+{
+	GtkWidget *submenu;
+	GtkWidget *subsubmenu;
+	char buf[256];
+	int i;
+
+	submenu = mg_submenu (menu, _("Insert Attribute or Color Code"));
+
+	mg_markup_item (submenu, _("<b>Bold</b>"), 100);
+	mg_markup_item (submenu, _("<u>Underline</u>"), 101);
+	/*mg_markup_item (submenu, _("<i>Italic</i>"), 102);*/
+	mg_markup_item (submenu, _("Normal"), 103);
+
+	subsubmenu = mg_submenu (submenu, _("Colors 0-7"));
+
+	for (i = 0; i < 8; i++)
+	{
+		sprintf (buf, "<tt><sup>%02d</sup> <span background=\"#%02x%02x%02x\">"
+					"   </span></tt>",
+				i, colors[i].red >> 8, colors[i].green >> 8, colors[i].blue >> 8);
+		mg_markup_item (subsubmenu, buf, i);
+	}
+
+	subsubmenu = mg_submenu (submenu, _("Colors 8-15"));
+
+	for (i = 8; i < 16; i++)
+	{
+		sprintf (buf, "<tt><sup>%02d</sup> <span background=\"#%02x%02x%02x\">"
+					"   </span></tt>",
+				i, colors[i].red >> 8, colors[i].green >> 8, colors[i].blue >> 8);
+		mg_markup_item (subsubmenu, buf, i);
+	}
+}
+
+static void
+mg_set_guint8 (GtkCheckMenuItem *item, guint8 *setting)
+{
+	session *sess = current_sess;
+	guint8 logging = sess->text_logging;
+
+	*setting = SET_OFF;
+	if (item->active)
+		*setting = SET_ON;
+
+	/* has the logging setting changed? */
+	if (logging != sess->text_logging)
+		log_open_or_close (sess);
+}
+
+static void
+mg_perchan_menu_item (char *label, GtkWidget *menu, guint8 *setting, guint global)
+{
+	guint8 initial_value = *setting;
+
+	/* if it's using global value, use that as initial state */
+	if (initial_value == SET_DEFAULT)
+		initial_value = global;
+
+	menu_toggle_item (label, menu, mg_set_guint8, setting, initial_value);
+}
+
+static void
+mg_create_perchannelmenu (session *sess, GtkWidget *menu)
+{
+	GtkWidget *submenu;
+
+	submenu = menu_quick_sub (_("_Settings"), menu, NULL, XCMENU_MNEMONIC, -1);
+
+	mg_perchan_menu_item (_("_Log to Disk"), submenu, &sess->text_logging, prefs.logging);
+	mg_perchan_menu_item (_("_Reload Scrollback"), submenu, &sess->text_scrollback, prefs.text_replay);
+	if (sess->type == SESS_CHANNEL)
+		mg_perchan_menu_item (_("_Hide Join/Part Messages"), submenu, &sess->text_hidejoinpart, prefs.confmode);
+}
+
+static void
+mg_create_alertmenu (session *sess, GtkWidget *menu)
+{
+	GtkWidget *submenu;
+
+	submenu = menu_quick_sub (_("_Extra Alerts"), menu, NULL, XCMENU_MNEMONIC, -1);
+
+	mg_perchan_menu_item (_("Beep on _Message"), submenu, &sess->alert_beep, prefs.input_beep_chans);
+	mg_perchan_menu_item (_("Blink Tray _Icon"), submenu, &sess->alert_tray, prefs.input_tray_chans);
+	mg_perchan_menu_item (_("Blink Task _Bar"), submenu, &sess->alert_taskbar, prefs.input_flash_chans);
+}
+
+static void
+mg_create_tabmenu (session *sess, GdkEventButton *event, chan *ch)
+{
+	GtkWidget *menu, *item;
+	char buf[256];
+
+	menu = gtk_menu_new ();
+
+	if (sess)
+	{
+		char *name = g_markup_escape_text (sess->channel[0] ? sess->channel : _("<none>"), -1);
+		snprintf (buf, sizeof (buf), "<span foreground=\"#3344cc\"><b>%s</b></span>", name);
+		g_free (name);
+
+		item = gtk_menu_item_new_with_label ("");
+		gtk_label_set_markup (GTK_LABEL (GTK_BIN (item)->child), buf);
+		gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+		gtk_widget_show (item);
+
+		/* separator */
+		menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0);
+
+		/* per-channel alerts */
+		mg_create_alertmenu (sess, menu);
+
+		/* per-channel settings */
+		mg_create_perchannelmenu (sess, menu);
+
+		/* separator */
+		menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0);
+
+		if (sess->type == SESS_CHANNEL)
+			menu_addfavoritemenu (sess->server, menu, sess->channel);
+	}
+
+	mg_create_icon_item (_("_Detach"), GTK_STOCK_REDO, menu,
+								mg_detach_tab_cb, ch);
+	mg_create_icon_item (_("_Close"), GTK_STOCK_CLOSE, menu,
+								mg_destroy_tab_cb, ch);
+	if (sess && tabmenu_list)
+		menu_create (menu, tabmenu_list, sess->channel, FALSE);
+	menu_add_plugin_items (menu, "\x4$TAB", sess->channel);
+
+	if (event->window)
+		gtk_menu_set_screen (GTK_MENU (menu), gdk_drawable_get_screen (event->window));
+	g_object_ref (menu);
+	g_object_ref_sink (menu);
+	g_object_unref (menu);
+	g_signal_connect (G_OBJECT (menu), "selection-done",
+							G_CALLBACK (mg_menu_destroy), NULL);
+	gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 0, event->time);
+}
+
+static gboolean
+mg_tab_contextmenu_cb (chanview *cv, chan *ch, int tag, gpointer ud, GdkEventButton *event)
+{
+	/* shift-click to close a tab */
+	if ((event->state & GDK_SHIFT_MASK) && event->type == GDK_BUTTON_PRESS)
+	{
+		mg_xbutton_cb (cv, ch, tag, ud);
+		return FALSE;
+	}
+
+	if (event->button != 3)
+		return FALSE;
+
+	if (tag == TAG_IRC)
+		mg_create_tabmenu (ud, event, ch);
+	else
+		mg_create_tabmenu (NULL, event, ch);
+
+	return TRUE;
+}
+
+void
+mg_dnd_drop_file (session *sess, char *target, char *uri)
+{
+	char *p, *data, *next, *fname;
+
+	p = data = strdup (uri);
+	while (*p)
+	{
+		next = strchr (p, '\r');
+		if (strncasecmp ("file:", p, 5) == 0)
+		{
+			if (next)
+				*next = 0;
+			fname = g_filename_from_uri (p, NULL, NULL);
+			if (fname)
+			{
+				/* dcc_send() expects utf-8 */
+				p = xchat_filename_to_utf8 (fname, -1, 0, 0, 0);
+				if (p)
+				{
+					dcc_send (sess, target, p, prefs.dcc_max_send_cps, 0);
+					g_free (p);
+				}
+				g_free (fname);
+			}
+		}
+		if (!next)
+			break;
+		p = next + 1;
+		if (*p == '\n')
+			p++;
+	}
+	free (data);
+
+}
+
+static void
+mg_dialog_dnd_drop (GtkWidget * widget, GdkDragContext * context, gint x,
+						  gint y, GtkSelectionData * selection_data, guint info,
+						  guint32 time, gpointer ud)
+{
+	if (current_sess->type == SESS_DIALOG)
+		/* sess->channel is really the nickname of dialogs */
+		mg_dnd_drop_file (current_sess, current_sess->channel, selection_data->data);
+}
+
+/* add a tabbed channel */
+
+static void
+mg_add_chan (session *sess)
+{
+	GdkPixbuf *icon;
+	char *name = _("<none>");
+
+	if (sess->channel[0])
+		name = sess->channel;
+
+	switch (sess->type)
+	{
+	case SESS_CHANNEL:
+		icon = pix_channel;
+		break;
+	case SESS_SERVER:
+		icon = pix_server;
+		break;
+	default:
+		icon = pix_dialog;
+	}
+
+	sess->res->tab = chanview_add (sess->gui->chanview, name, sess->server, sess,
+											 sess->type == SESS_SERVER ? FALSE : TRUE,
+											 TAG_IRC, icon);
+	if (plain_list == NULL)
+		mg_create_tab_colors ();
+
+	chan_set_color (sess->res->tab, plain_list);
+
+	if (sess->res->buffer == NULL)
+	{
+		sess->res->buffer = gtk_xtext_buffer_new (GTK_XTEXT (sess->gui->xtext));
+		gtk_xtext_set_time_stamp (sess->res->buffer, prefs.timestamp);
+		sess->res->user_model = userlist_create_model ();
+	}
+}
+
+static void
+mg_userlist_button (GtkWidget * box, char *label, char *cmd,
+						  int a, int b, int c, int d)
+{
+	GtkWidget *wid = gtk_button_new_with_label (label);
+	g_signal_connect (G_OBJECT (wid), "clicked",
+							G_CALLBACK (userlist_button_cb), cmd);
+	gtk_table_attach_defaults (GTK_TABLE (box), wid, a, b, c, d);
+	show_and_unfocus (wid);
+}
+
+static GtkWidget *
+mg_create_userlistbuttons (GtkWidget *box)
+{
+	struct popup *pop;
+	GSList *list = button_list;
+	int a = 0, b = 0;
+	GtkWidget *tab;
+
+	tab = gtk_table_new (5, 2, FALSE);
+	gtk_box_pack_end (GTK_BOX (box), tab, FALSE, FALSE, 0);
+
+	while (list)
+	{
+		pop = list->data;
+		if (pop->cmd[0])
+		{
+			mg_userlist_button (tab, pop->name, pop->cmd, a, a + 1, b, b + 1);
+			a++;
+			if (a == 2)
+			{
+				a = 0;
+				b++;
+			}
+		}
+		list = list->next;
+	}
+
+	return tab;
+}
+
+static void
+mg_topic_cb (GtkWidget *entry, gpointer userdata)
+{
+	session *sess = current_sess;
+	char *text;
+
+	if (sess->channel[0] && sess->server->connected && sess->type == SESS_CHANNEL)
+	{
+		text = GTK_ENTRY (entry)->text;
+		if (text[0] == 0)
+			text = NULL;
+		sess->server->p_topic (sess->server, sess->channel, text);
+	} else
+		gtk_entry_set_text (GTK_ENTRY (entry), "");
+	/* restore focus to the input widget, where the next input will most
+likely be */
+	gtk_widget_grab_focus (sess->gui->input_box);
+}
+
+static void
+mg_tabwindow_kill_cb (GtkWidget *win, gpointer userdata)
+{
+	GSList *list, *next;
+	session *sess;
+
+/*	puts("enter mg_tabwindow_kill_cb");*/
+	xchat_is_quitting = TRUE;
+
+	/* see if there's any non-tab windows left */
+	list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+		next = list->next;
+		if (!sess->gui->is_tab)
+		{
+			xchat_is_quitting = FALSE;
+/*			puts("-> will not exit, some toplevel windows left");*/
+		} else
+		{
+			mg_ircdestroy (sess);
+		}
+		list = next;
+	}
+
+	current_tab = NULL;
+	active_tab = NULL;
+	mg_gui = NULL;
+	parent_window = NULL;
+}
+
+static GtkWidget *
+mg_changui_destroy (session *sess)
+{
+	GtkWidget *ret = NULL;
+
+	if (sess->gui->is_tab)
+	{
+		/* avoid calling the "destroy" callback */
+		g_signal_handlers_disconnect_by_func (G_OBJECT (sess->gui->window),
+														  mg_tabwindow_kill_cb, 0);
+		/* remove the tab from the chanview */
+		if (!mg_chan_remove (sess->res->tab))
+			/* if the window still exists, restore the signal handler */
+			g_signal_connect (G_OBJECT (sess->gui->window), "destroy",
+									G_CALLBACK (mg_tabwindow_kill_cb), 0);
+	} else
+	{
+		/* avoid calling the "destroy" callback */
+		g_signal_handlers_disconnect_by_func (G_OBJECT (sess->gui->window),
+														  mg_topdestroy_cb, sess);
+		/*gtk_widget_destroy (sess->gui->window);*/
+		/* don't destroy until the new one is created. Not sure why, but */
+		/* it fixes: Gdk-CRITICAL **: gdk_colormap_get_screen: */
+		/*           assertion `GDK_IS_COLORMAP (cmap)' failed */
+		ret = sess->gui->window;
+		free (sess->gui);
+		sess->gui = NULL;
+	}
+	return ret;
+}
+
+static void
+mg_link_irctab (session *sess, int focus)
+{
+	GtkWidget *win;
+
+	if (sess->gui->is_tab)
+	{
+		win = mg_changui_destroy (sess);
+		mg_changui_new (sess, sess->res, 0, focus);
+		mg_populate (sess);
+		xchat_is_quitting = FALSE;
+		if (win)
+			gtk_widget_destroy (win);
+		return;
+	}
+
+	mg_unpopulate (sess);
+	win = mg_changui_destroy (sess);
+	mg_changui_new (sess, sess->res, 1, focus);
+	/* the buffer is now attached to a different widget */
+	((xtext_buffer *)sess->res->buffer)->xtext = (GtkXText *)sess->gui->xtext;
+	if (win)
+		gtk_widget_destroy (win);
+}
+
+void
+mg_detach (session *sess, int mode)
+{
+	switch (mode)
+	{
+	/* detach only */
+	case 1:
+		if (sess->gui->is_tab)
+			mg_link_irctab (sess, 1);
+		break;
+	/* attach only */
+	case 2:
+		if (!sess->gui->is_tab)
+			mg_link_irctab (sess, 1);
+		break;
+	/* toggle */
+	default:
+		mg_link_irctab (sess, 1);
+	}
+}
+
+static int
+check_is_number (char *t)
+{
+	while (*t)
+	{
+		if (*t < '0' || *t > '9')
+			return FALSE;
+		t++;
+	}
+	return TRUE;
+}
+
+static void
+mg_change_flag (GtkWidget * wid, session *sess, char flag)
+{
+	server *serv = sess->server;
+	char mode[3];
+
+	mode[1] = flag;
+	mode[2] = '\0';
+	if (serv->connected && sess->channel[0])
+	{
+		if (GTK_TOGGLE_BUTTON (wid)->active)
+			mode[0] = '+';
+		else
+			mode[0] = '-';
+		serv->p_mode (serv, sess->channel, mode);
+		serv->p_join_info (serv, sess->channel);
+		sess->ignore_mode = TRUE;
+		sess->ignore_date = TRUE;
+	}
+}
+
+static void
+flagl_hit (GtkWidget * wid, struct session *sess)
+{
+	char modes[512];
+	const char *limit_str;
+	server *serv = sess->server;
+
+	if (GTK_TOGGLE_BUTTON (wid)->active)
+	{
+		if (serv->connected && sess->channel[0])
+		{
+			limit_str = gtk_entry_get_text (GTK_ENTRY (sess->gui->limit_entry));
+			if (check_is_number ((char *)limit_str) == FALSE)
+			{
+				fe_message (_("User limit must be a number!\n"), FE_MSG_ERROR);
+				gtk_entry_set_text (GTK_ENTRY (sess->gui->limit_entry), "");
+				gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), FALSE);
+				return;
+			}
+			snprintf (modes, sizeof (modes), "+l %d", atoi (limit_str));
+			serv->p_mode (serv, sess->channel, modes);
+			serv->p_join_info (serv, sess->channel);
+		}
+	} else
+		mg_change_flag (wid, sess, 'l');
+}
+
+static void
+flagk_hit (GtkWidget * wid, struct session *sess)
+{
+	char modes[512];
+	server *serv = sess->server;
+
+	if (serv->connected && sess->channel[0])
+	{
+		snprintf (modes, sizeof (modes), "-k %s", 
+			  gtk_entry_get_text (GTK_ENTRY (sess->gui->key_entry)));
+
+		if (GTK_TOGGLE_BUTTON (wid)->active)
+			modes[0] = '+';
+
+		serv->p_mode (serv, sess->channel, modes);
+	}
+}
+
+static void
+mg_flagbutton_cb (GtkWidget *but, char *flag)
+{
+	session *sess;
+	char mode;
+
+	if (ignore_chanmode)
+		return;
+
+	sess = current_sess;
+	mode = tolower ((unsigned char) flag[0]);
+
+	switch (mode)
+	{
+	case 'l':
+		flagl_hit (but, sess);
+		break;
+	case 'k':
+		flagk_hit (but, sess);
+		break;
+	case 'b':
+		ignore_chanmode = TRUE;
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sess->gui->flag_b), FALSE);
+		ignore_chanmode = FALSE;
+		banlist_opengui (sess);
+		break;
+	default:
+		mg_change_flag (but, sess, mode);
+	}
+}
+
+static GtkWidget *
+mg_create_flagbutton (char *tip, GtkWidget *box, char *face)
+{
+	GtkWidget *wid;
+
+	wid = gtk_toggle_button_new_with_label (face);
+	gtk_widget_set_size_request (wid, 18, 0);
+	add_tip (wid, tip);
+	gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0);
+	g_signal_connect (G_OBJECT (wid), "toggled",
+							G_CALLBACK (mg_flagbutton_cb), face);
+	show_and_unfocus (wid);
+
+	return wid;
+}
+
+static void
+mg_key_entry_cb (GtkWidget * igad, gpointer userdata)
+{
+	char modes[512];
+	session *sess = current_sess;
+	server *serv = sess->server;
+
+	if (serv->connected && sess->channel[0])
+	{
+		snprintf (modes, sizeof (modes), "+k %s",
+				gtk_entry_get_text (GTK_ENTRY (igad)));
+		serv->p_mode (serv, sess->channel, modes);
+		serv->p_join_info (serv, sess->channel);
+	}
+}
+
+static void
+mg_limit_entry_cb (GtkWidget * igad, gpointer userdata)
+{
+	char modes[512];
+	session *sess = current_sess;
+	server *serv = sess->server;
+
+	if (serv->connected && sess->channel[0])
+	{
+		if (check_is_number ((char *)gtk_entry_get_text (GTK_ENTRY (igad))) == FALSE)
+		{
+			gtk_entry_set_text (GTK_ENTRY (igad), "");
+			fe_message (_("User limit must be a number!\n"), FE_MSG_ERROR);
+			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sess->gui->flag_l), FALSE);
+			return;
+		}
+		snprintf (modes, sizeof(modes), "+l %d", 
+				atoi (gtk_entry_get_text (GTK_ENTRY (igad))));
+		serv->p_mode (serv, sess->channel, modes);
+		serv->p_join_info (serv, sess->channel);
+	}
+}
+
+static void
+mg_apply_entry_style (GtkWidget *entry)
+{
+	gtk_widget_modify_base (entry, GTK_STATE_NORMAL, &colors[COL_BG]);
+	gtk_widget_modify_text (entry, GTK_STATE_NORMAL, &colors[COL_FG]);
+	gtk_widget_modify_font (entry, input_style->font_desc);
+}
+
+static void
+mg_create_chanmodebuttons (session_gui *gui, GtkWidget *box)
+{
+	gui->flag_t = mg_create_flagbutton (_("Topic Protection"), box, "T");
+	gui->flag_n = mg_create_flagbutton (_("No outside messages"), box, "N");
+	gui->flag_s = mg_create_flagbutton (_("Secret"), box, "S");
+	gui->flag_i = mg_create_flagbutton (_("Invite Only"), box, "I");
+	gui->flag_p = mg_create_flagbutton (_("Private"), box, "P");
+	gui->flag_m = mg_create_flagbutton (_("Moderated"), box, "M");
+	gui->flag_b = mg_create_flagbutton (_("Ban List"), box, "B");
+
+	gui->flag_k = mg_create_flagbutton (_("Keyword"), box, "K");
+	gui->key_entry = gtk_entry_new ();
+	gtk_widget_set_name (gui->key_entry, "xchat-inputbox");
+	gtk_entry_set_max_length (GTK_ENTRY (gui->key_entry), 16);
+	gtk_widget_set_size_request (gui->key_entry, 30, -1);
+	gtk_box_pack_start (GTK_BOX (box), gui->key_entry, 0, 0, 0);
+	g_signal_connect (G_OBJECT (gui->key_entry), "activate",
+							G_CALLBACK (mg_key_entry_cb), NULL);
+
+	if (prefs.style_inputbox)
+		mg_apply_entry_style (gui->key_entry);
+
+	gui->flag_l = mg_create_flagbutton (_("User Limit"), box, "L");
+	gui->limit_entry = gtk_entry_new ();
+	gtk_widget_set_name (gui->limit_entry, "xchat-inputbox");
+	gtk_entry_set_max_length (GTK_ENTRY (gui->limit_entry), 10);
+	gtk_widget_set_size_request (gui->limit_entry, 30, -1);
+	gtk_box_pack_start (GTK_BOX (box), gui->limit_entry, 0, 0, 0);
+	g_signal_connect (G_OBJECT (gui->limit_entry), "activate",
+							G_CALLBACK (mg_limit_entry_cb), NULL);
+
+	if (prefs.style_inputbox)
+		mg_apply_entry_style (gui->limit_entry);
+}
+
+/*static void
+mg_create_link_buttons (GtkWidget *box, gpointer userdata)
+{
+	gtkutil_button (box, GTK_STOCK_CLOSE, _("Close this tab/window"),
+						 mg_x_click_cb, userdata, 0);
+
+	if (!userdata)
+	gtkutil_button (box, GTK_STOCK_REDO, _("Attach/Detach this tab"),
+						 mg_link_cb, userdata, 0);
+}*/
+
+static void
+mg_dialog_button_cb (GtkWidget *wid, char *cmd)
+{
+	/* the longest cmd is 12, and the longest nickname is 64 */
+	char buf[128];
+	char *host = "";
+	char *topic;
+
+	if (!current_sess)
+		return;
+
+	topic = (char *)(GTK_ENTRY (current_sess->gui->topic_entry)->text);
+	topic = strrchr (topic, '@');
+	if (topic)
+		host = topic + 1;
+
+	auto_insert (buf, sizeof (buf), cmd, 0, 0, "", "", "",
+					 server_get_network (current_sess->server, TRUE), host, "",
+					 current_sess->channel);
+
+	handle_command (current_sess, buf, TRUE);
+
+	/* dirty trick to avoid auto-selection */
+	SPELL_ENTRY_SET_EDITABLE (current_sess->gui->input_box, FALSE);
+	gtk_widget_grab_focus (current_sess->gui->input_box);
+	SPELL_ENTRY_SET_EDITABLE (current_sess->gui->input_box, TRUE);
+}
+
+static void
+mg_dialog_button (GtkWidget *box, char *name, char *cmd)
+{
+	GtkWidget *wid;
+
+	wid = gtk_button_new_with_label (name);
+	gtk_box_pack_start (GTK_BOX (box), wid, FALSE, FALSE, 0);
+	g_signal_connect (G_OBJECT (wid), "clicked",
+							G_CALLBACK (mg_dialog_button_cb), cmd);
+	gtk_widget_set_size_request (wid, -1, 0);
+}
+
+static void
+mg_create_dialogbuttons (GtkWidget *box)
+{
+	struct popup *pop;
+	GSList *list = dlgbutton_list;
+
+	while (list)
+	{
+		pop = list->data;
+		if (pop->cmd[0])
+			mg_dialog_button (box, pop->name, pop->cmd);
+		list = list->next;
+	}
+}
+
+static void
+mg_create_topicbar (session *sess, GtkWidget *box)
+{
+	GtkWidget *hbox, *topic, *bbox;
+	session_gui *gui = sess->gui;
+
+	gui->topic_bar = hbox = gtk_hbox_new (FALSE, 0);
+	gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0);
+
+	if (!gui->is_tab)
+		sess->res->tab = NULL;
+
+	gui->topic_entry = topic = gtk_entry_new ();
+	gtk_widget_set_name (topic, "xchat-inputbox");
+	gtk_container_add (GTK_CONTAINER (hbox), topic);
+	g_signal_connect (G_OBJECT (topic), "activate",
+							G_CALLBACK (mg_topic_cb), 0);
+
+	if (prefs.style_inputbox)
+		mg_apply_entry_style (topic);
+
+	gui->topicbutton_box = bbox = gtk_hbox_new (FALSE, 0);
+	gtk_box_pack_start (GTK_BOX (hbox), bbox, 0, 0, 0);
+	mg_create_chanmodebuttons (gui, bbox);
+
+	gui->dialogbutton_box = bbox = gtk_hbox_new (FALSE, 0);
+	gtk_box_pack_start (GTK_BOX (hbox), bbox, 0, 0, 0);
+	mg_create_dialogbuttons (bbox);
+
+	if (!prefs.paned_userlist)
+		gtkutil_button (hbox, GTK_STOCK_GOTO_LAST, _("Show/Hide userlist"),
+							 mg_userlist_toggle_cb, 0, 0);
+}
+
+/* check if a word is clickable */
+
+static int
+mg_word_check (GtkWidget * xtext, char *word, int len)
+{
+	session *sess = current_sess;
+	int ret;
+
+	ret = url_check_word (word, len);	/* common/url.c */
+	if (ret == 0)
+	{
+		if (( (word[0]=='@' || word[0]=='+' || word[0]=='%') && userlist_find (sess, word+1)) || userlist_find (sess, word))
+			return WORD_NICK;
+
+		if (sess->type == SESS_DIALOG)
+			return WORD_DIALOG;
+	}
+
+	return ret;
+}
+
+/* mouse click inside text area */
+
+static void
+mg_word_clicked (GtkWidget *xtext, char *word, GdkEventButton *even)
+{
+	session *sess = current_sess;
+
+	if (even->button == 1)			/* left button */
+	{
+		if (word == NULL)
+		{
+			mg_focus (sess);
+			return;
+		}
+
+		if ((even->state & 13) == prefs.gui_url_mod)
+		{
+			switch (mg_word_check (xtext, word, strlen (word)))
+			{
+			case WORD_URL:
+			case WORD_HOST:
+				fe_open_url (word);
+			}
+		}
+		return;
+	}
+
+	if (even->button == 2)
+	{
+		if (sess->type == SESS_DIALOG)
+			menu_middlemenu (sess, even);
+		else if (even->type == GDK_2BUTTON_PRESS)
+			userlist_select (sess, word);
+		return;
+	}
+
+	switch (mg_word_check (xtext, word, strlen (word)))
+	{
+	case 0:
+		menu_middlemenu (sess, even);
+		break;
+	case WORD_URL:
+	case WORD_HOST:
+		menu_urlmenu (even, word);
+		break;
+	case WORD_NICK:
+		menu_nickmenu (sess, even, (word[0]=='@' || word[0]=='+' || word[0]=='%') ?
+			word+1 : word, FALSE);
+		break;
+	case WORD_CHANNEL:
+		if (*word == '@' || *word == '+' || *word=='^' || *word=='%' || *word=='*')
+			word++;
+		menu_chanmenu (sess, even, word);
+		break;
+	case WORD_EMAIL:
+		{
+			char *newword = malloc (strlen (word) + 10);
+			if (*word == '~')
+				word++;
+			sprintf (newword, "mailto:%s", word);
+			menu_urlmenu (even, newword);
+			free (newword);
+		}
+		break;
+	case WORD_DIALOG:
+		menu_nickmenu (sess, even, sess->channel, FALSE);
+		break;
+	}
+}
+
+void
+mg_update_xtext (GtkWidget *wid)
+{
+	GtkXText *xtext = GTK_XTEXT (wid);
+
+	gtk_xtext_set_palette (xtext, colors);
+	gtk_xtext_set_max_lines (xtext, prefs.max_lines);
+	gtk_xtext_set_tint (xtext, prefs.tint_red, prefs.tint_green, prefs.tint_blue);
+	gtk_xtext_set_background (xtext, channelwin_pix, prefs.transparent);
+	gtk_xtext_set_wordwrap (xtext, prefs.wordwrap);
+	gtk_xtext_set_show_marker (xtext, prefs.show_marker);
+	gtk_xtext_set_show_separator (xtext, prefs.indent_nicks ? prefs.show_separator : 0);
+	gtk_xtext_set_indent (xtext, prefs.indent_nicks);
+	if (!gtk_xtext_set_font (xtext, prefs.font_normal))
+	{
+		fe_message ("Failed to open any font. I'm out of here!", FE_MSG_WAIT | FE_MSG_ERROR);
+		exit (1);
+	}
+
+	gtk_xtext_refresh (xtext, FALSE);
+}
+
+/* handle errors reported by xtext */
+
+static void
+mg_xtext_error (int type)
+{
+	switch (type)
+	{
+	case 0:
+		fe_message (_("Unable to set transparent background!\n\n"
+						"You may be using a non-compliant window\n"
+						"manager that is not currently supported.\n"), FE_MSG_WARN);
+		prefs.transparent = 0;
+		/* no others exist yet */
+	}
+}
+
+static void
+mg_create_textarea (session *sess, GtkWidget *box)
+{
+	GtkWidget *inbox, *vbox, *frame;
+	GtkXText *xtext;
+	session_gui *gui = sess->gui;
+	static const GtkTargetEntry dnd_targets[] =
+	{
+		{"text/uri-list", 0, 1}
+	};
+	static const GtkTargetEntry dnd_dest_targets[] =
+	{
+		{"XCHAT_CHANVIEW", GTK_TARGET_SAME_APP, 75 },
+		{"XCHAT_USERLIST", GTK_TARGET_SAME_APP, 75 }
+	};
+
+	vbox = gtk_vbox_new (FALSE, 0);
+	gtk_container_add (GTK_CONTAINER (box), vbox);
+
+	inbox = gtk_hbox_new (FALSE, SCROLLBAR_SPACING);
+	gtk_container_add (GTK_CONTAINER (vbox), inbox);
+
+	frame = gtk_frame_new (NULL);
+	gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+	gtk_container_add (GTK_CONTAINER (inbox), frame);
+
+	gui->xtext = gtk_xtext_new (colors, TRUE);
+	xtext = GTK_XTEXT (gui->xtext);
+	gtk_xtext_set_max_indent (xtext, prefs.max_auto_indent);
+	gtk_xtext_set_thin_separator (xtext, prefs.thin_separator);
+	gtk_xtext_set_error_function (xtext, mg_xtext_error);
+	gtk_xtext_set_urlcheck_function (xtext, mg_word_check);
+	gtk_xtext_set_max_lines (xtext, prefs.max_lines);
+	gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (xtext));
+	mg_update_xtext (GTK_WIDGET (xtext));
+
+	g_signal_connect (G_OBJECT (xtext), "word_click",
+							G_CALLBACK (mg_word_clicked), NULL);
+
+	gui->vscrollbar = gtk_vscrollbar_new (GTK_XTEXT (xtext)->adj);
+	gtk_box_pack_start (GTK_BOX (inbox), gui->vscrollbar, FALSE, TRUE, 0);
+#ifndef WIN32	/* needs more work */
+	gtk_drag_dest_set (gui->vscrollbar, 5, dnd_dest_targets, 2,
+							 GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK);
+	g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_begin",
+							G_CALLBACK (mg_drag_begin_cb), NULL);
+	g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_drop",
+							G_CALLBACK (mg_drag_drop_cb), NULL);
+	g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_motion",
+							G_CALLBACK (mg_drag_motion_cb), gui->vscrollbar);
+	g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_end",
+							G_CALLBACK (mg_drag_end_cb), NULL);
+
+	gtk_drag_dest_set (gui->xtext, GTK_DEST_DEFAULT_ALL, dnd_targets, 1,
+							 GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK);
+	g_signal_connect (G_OBJECT (gui->xtext), "drag_data_received",
+							G_CALLBACK (mg_dialog_dnd_drop), NULL);
+#endif
+}
+
+static GtkWidget *
+mg_create_infoframe (GtkWidget *box)
+{
+	GtkWidget *frame, *label, *hbox;
+
+	frame = gtk_frame_new (0);
+	gtk_frame_set_shadow_type ((GtkFrame*)frame, GTK_SHADOW_OUT);
+	gtk_container_add (GTK_CONTAINER (box), frame);
+
+	hbox = gtk_hbox_new (0, 0);
+	gtk_container_add (GTK_CONTAINER (frame), hbox);
+
+	label = gtk_label_new (NULL);
+	gtk_container_add (GTK_CONTAINER (hbox), label);
+
+	return label;
+}
+
+static void
+mg_create_meters (session_gui *gui, GtkWidget *parent_box)
+{
+	GtkWidget *infbox, *wid, *box;
+
+	gui->meter_box = infbox = box = gtk_vbox_new (0, 1);
+	gtk_box_pack_start (GTK_BOX (parent_box), box, 0, 0, 0);
+
+	if ((prefs.lagometer & 2) || (prefs.throttlemeter & 2))
+	{
+		infbox = gtk_hbox_new (0, 0);
+		gtk_box_pack_start (GTK_BOX (box), infbox, 0, 0, 0);
+	}
+
+	if (prefs.lagometer & 1)
+	{
+		gui->lagometer = wid = gtk_progress_bar_new ();
+#ifdef WIN32
+		gtk_widget_set_size_request (wid, 1, 10);
+#else
+		gtk_widget_set_size_request (wid, 1, 8);
+#endif
+
+		wid = gtk_event_box_new ();
+		gtk_container_add (GTK_CONTAINER (wid), gui->lagometer);
+		gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0);
+	}
+	if (prefs.lagometer & 2)
+	{
+		gui->laginfo = wid = mg_create_infoframe (infbox);
+		gtk_label_set_text ((GtkLabel *) wid, "Lag");
+	}
+
+	if (prefs.throttlemeter & 1)
+	{
+		gui->throttlemeter = wid = gtk_progress_bar_new ();
+#ifdef WIN32
+		gtk_widget_set_size_request (wid, 1, 10);
+#else
+		gtk_widget_set_size_request (wid, 1, 8);
+#endif
+
+		wid = gtk_event_box_new ();
+		gtk_container_add (GTK_CONTAINER (wid), gui->throttlemeter);
+		gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0);
+	}
+	if (prefs.throttlemeter & 2)
+	{
+		gui->throttleinfo = wid = mg_create_infoframe (infbox);
+		gtk_label_set_text ((GtkLabel *) wid, "Throttle");
+	}
+}
+
+void
+mg_update_meters (session_gui *gui)
+{
+	gtk_widget_destroy (gui->meter_box);
+	gui->lagometer = NULL;
+	gui->laginfo = NULL;
+	gui->throttlemeter = NULL;
+	gui->throttleinfo = NULL;
+
+	mg_create_meters (gui, gui->button_box_parent);
+	gtk_widget_show_all (gui->meter_box);
+}
+
+static void
+mg_create_userlist (session_gui *gui, GtkWidget *box)
+{
+	GtkWidget *frame, *ulist, *vbox;
+
+	vbox = gtk_vbox_new (0, 1);
+	gtk_container_add (GTK_CONTAINER (box), vbox);
+
+	frame = gtk_frame_new (NULL);
+	if (!(prefs.gui_tweaks & 1))
+		gtk_box_pack_start (GTK_BOX (vbox), frame, 0, 0, GUI_SPACING);
+
+	gui->namelistinfo = gtk_label_new (NULL);
+	gtk_container_add (GTK_CONTAINER (frame), gui->namelistinfo);
+
+	gui->user_tree = ulist = userlist_create (vbox);
+
+	if (prefs.style_namelistgad)
+	{
+		gtk_widget_set_style (ulist, input_style);
+		gtk_widget_modify_base (ulist, GTK_STATE_NORMAL, &colors[COL_BG]);
+	}
+
+	mg_create_meters (gui, vbox);
+
+	gui->button_box_parent = vbox;
+	gui->button_box = mg_create_userlistbuttons (vbox);
+}
+
+static void
+mg_leftpane_cb (GtkPaned *pane, GParamSpec *param, session_gui *gui)
+{
+	prefs.gui_pane_left_size = gtk_paned_get_position (pane);
+}
+
+static void
+mg_rightpane_cb (GtkPaned *pane, GParamSpec *param, session_gui *gui)
+{
+	int handle_size;
+
+/*	if (pane->child1 == NULL || (!GTK_WIDGET_VISIBLE (pane->child1)))
+		return;
+	if (pane->child2 == NULL || (!GTK_WIDGET_VISIBLE (pane->child2)))
+		return;*/
+
+	gtk_widget_style_get (GTK_WIDGET (pane), "handle-size", &handle_size, NULL);
+	/* record the position from the RIGHT side */
+	prefs.gui_pane_right_size = GTK_WIDGET (pane)->allocation.width - gtk_paned_get_position (pane) - handle_size;
+}
+
+static gboolean
+mg_add_pane_signals (session_gui *gui)
+{
+	g_signal_connect (G_OBJECT (gui->hpane_right), "notify::position",
+							G_CALLBACK (mg_rightpane_cb), gui);
+	g_signal_connect (G_OBJECT (gui->hpane_left), "notify::position",
+							G_CALLBACK (mg_leftpane_cb), gui);
+	return FALSE;
+}
+
+static void
+mg_create_center (session *sess, session_gui *gui, GtkWidget *box)
+{
+	GtkWidget *vbox, *hbox, *book;
+
+	/* sep between top and bottom of left side */
+	gui->vpane_left = gtk_vpaned_new ();
+
+	/* sep between top and bottom of right side */
+	gui->vpane_right = gtk_vpaned_new ();
+
+	/* sep between left and xtext */
+	gui->hpane_left = gtk_hpaned_new ();
+	gtk_paned_set_position (GTK_PANED (gui->hpane_left), prefs.gui_pane_left_size);
+
+	/* sep between xtext and right side */
+	gui->hpane_right = gtk_hpaned_new ();
+
+	if (prefs.gui_tweaks & 4)
+	{
+		gtk_paned_pack2 (GTK_PANED (gui->hpane_left), gui->vpane_left, FALSE, TRUE);
+		gtk_paned_pack1 (GTK_PANED (gui->hpane_left), gui->hpane_right, TRUE, TRUE);
+	}
+	else
+	{
+		gtk_paned_pack1 (GTK_PANED (gui->hpane_left), gui->vpane_left, FALSE, TRUE);
+		gtk_paned_pack2 (GTK_PANED (gui->hpane_left), gui->hpane_right, TRUE, TRUE);
+	}
+	gtk_paned_pack2 (GTK_PANED (gui->hpane_right), gui->vpane_right, FALSE, TRUE);
+
+	gtk_container_add (GTK_CONTAINER (box), gui->hpane_left);
+
+	gui->note_book = book = gtk_notebook_new ();
+	gtk_notebook_set_show_tabs (GTK_NOTEBOOK (book), FALSE);
+	gtk_notebook_set_show_border (GTK_NOTEBOOK (book), FALSE);
+	gtk_paned_pack1 (GTK_PANED (gui->hpane_right), book, TRUE, TRUE);
+
+	hbox = gtk_hbox_new (FALSE, 0);
+	gtk_paned_pack1 (GTK_PANED (gui->vpane_right), hbox, FALSE, TRUE);
+	mg_create_userlist (gui, hbox);
+
+	gui->user_box = hbox;
+
+	vbox = gtk_vbox_new (FALSE, 3);
+	gtk_notebook_append_page (GTK_NOTEBOOK (book), vbox, NULL);
+	mg_create_topicbar (sess, vbox);
+	mg_create_textarea (sess, vbox);
+	mg_create_entry (sess, vbox);
+
+	g_idle_add ((GSourceFunc)mg_add_pane_signals, gui);
+}
+
+static void
+mg_change_nick (int cancel, char *text, gpointer userdata)
+{
+	char buf[256];
+
+	if (!cancel)
+	{
+		snprintf (buf, sizeof (buf), "nick %s", text);
+		handle_command (current_sess, buf, FALSE);
+	}
+}
+
+static void
+mg_nickclick_cb (GtkWidget *button, gpointer userdata)
+{
+	fe_get_str (_("Enter new nickname:"), current_sess->server->nick,
+					mg_change_nick, NULL);
+}
+
+/* make sure chanview and userlist positions are sane */
+
+static void
+mg_sanitize_positions (int *cv, int *ul)
+{
+	if (prefs.tab_layout == 2)
+	{
+		/* treeview can't be on TOP or BOTTOM */
+		if (*cv == POS_TOP || *cv == POS_BOTTOM)
+			*cv = POS_TOPLEFT;
+	}
+
+	/* userlist can't be on TOP or BOTTOM */
+	if (*ul == POS_TOP || *ul == POS_BOTTOM)
+		*ul = POS_TOPRIGHT;
+
+	/* can't have both in the same place */
+	if (*cv == *ul)
+	{
+		*cv = POS_TOPRIGHT;
+		if (*ul == POS_TOPRIGHT)
+			*cv = POS_BOTTOMRIGHT;
+	}
+}
+
+static void
+mg_place_userlist_and_chanview_real (session_gui *gui, GtkWidget *userlist, GtkWidget *chanview)
+{
+	int unref_userlist = FALSE;
+	int unref_chanview = FALSE;
+
+	/* first, remove userlist/treeview from their containers */
+	if (userlist && userlist->parent)
+	{
+		g_object_ref (userlist);
+		gtk_container_remove (GTK_CONTAINER (userlist->parent), userlist);
+		unref_userlist = TRUE;
+	}
+
+	if (chanview && chanview->parent)
+	{
+		g_object_ref (chanview);
+		gtk_container_remove (GTK_CONTAINER (chanview->parent), chanview);
+		unref_chanview = TRUE;
+	}
+
+	if (chanview)
+	{
+		/* incase the previous pos was POS_HIDDEN */
+		gtk_widget_show (chanview);
+
+		gtk_table_set_row_spacing (GTK_TABLE (gui->main_table), 1, 0);
+		gtk_table_set_row_spacing (GTK_TABLE (gui->main_table), 2, 2);
+
+		/* then place them back in their new positions */
+		switch (prefs.tab_pos)
+		{
+		case POS_TOPLEFT:
+			gtk_paned_pack1 (GTK_PANED (gui->vpane_left), chanview, FALSE, TRUE);
+			break;
+		case POS_BOTTOMLEFT:
+			gtk_paned_pack2 (GTK_PANED (gui->vpane_left), chanview, FALSE, TRUE);
+			break;
+		case POS_TOPRIGHT:
+			gtk_paned_pack1 (GTK_PANED (gui->vpane_right), chanview, FALSE, TRUE);
+			break;
+		case POS_BOTTOMRIGHT:
+			gtk_paned_pack2 (GTK_PANED (gui->vpane_right), chanview, FALSE, TRUE);
+			break;
+		case POS_TOP:
+			gtk_table_set_row_spacing (GTK_TABLE (gui->main_table), 1, GUI_SPACING-1);
+			gtk_table_attach (GTK_TABLE (gui->main_table), chanview,
+									1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
+			break;
+		case POS_HIDDEN:
+			gtk_widget_hide (chanview);
+			/* always attach it to something to avoid ref_count=0 */
+			if (prefs.gui_ulist_pos == POS_TOP)
+				gtk_table_attach (GTK_TABLE (gui->main_table), chanview,
+										1, 2, 3, 4, GTK_FILL, GTK_FILL, 0, 0);
+
+			else
+				gtk_table_attach (GTK_TABLE (gui->main_table), chanview,
+										1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
+			break;
+		default:/* POS_BOTTOM */
+			gtk_table_set_row_spacing (GTK_TABLE (gui->main_table), 2, 3);
+			gtk_table_attach (GTK_TABLE (gui->main_table), chanview,
+									1, 2, 3, 4, GTK_FILL, GTK_FILL, 0, 0);
+		}
+	}
+
+	if (userlist)
+	{
+		switch (prefs.gui_ulist_pos)
+		{
+		case POS_TOPLEFT:
+			gtk_paned_pack1 (GTK_PANED (gui->vpane_left), userlist, FALSE, TRUE);
+			break;
+		case POS_BOTTOMLEFT:
+			gtk_paned_pack2 (GTK_PANED (gui->vpane_left), userlist, FALSE, TRUE);
+			break;
+		case POS_BOTTOMRIGHT:
+			gtk_paned_pack2 (GTK_PANED (gui->vpane_right), userlist, FALSE, TRUE);
+			break;
+		/*case POS_HIDDEN:
+			break;*/	/* Hide using the VIEW menu instead */
+		default:/* POS_TOPRIGHT */
+			gtk_paned_pack1 (GTK_PANED (gui->vpane_right), userlist, FALSE, TRUE);
+		}
+	}
+
+	if (unref_chanview)
+		g_object_unref (chanview);
+	if (unref_userlist)
+		g_object_unref (userlist);
+
+	mg_hide_empty_boxes (gui);
+}
+
+static void
+mg_place_userlist_and_chanview (session_gui *gui)
+{
+	GtkOrientation orientation;
+	GtkWidget *chanviewbox = NULL;
+	int pos;
+
+	mg_sanitize_positions (&prefs.tab_pos, &prefs.gui_ulist_pos);
+
+	if (gui->chanview)
+	{
+		pos = prefs.tab_pos;
+
+		orientation = chanview_get_orientation (gui->chanview);
+		if ((pos == POS_BOTTOM || pos == POS_TOP) && orientation == GTK_ORIENTATION_VERTICAL)
+			chanview_set_orientation (gui->chanview, FALSE);
+		else if ((pos == POS_TOPLEFT || pos == POS_BOTTOMLEFT || pos == POS_TOPRIGHT || pos == POS_BOTTOMRIGHT) && orientation == GTK_ORIENTATION_HORIZONTAL)
+			chanview_set_orientation (gui->chanview, TRUE);
+		chanviewbox = chanview_get_box (gui->chanview);
+	}
+
+	mg_place_userlist_and_chanview_real (gui, gui->user_box, chanviewbox);
+}
+
+void
+mg_change_layout (int type)
+{
+	if (mg_gui)
+	{
+		/* put tabs at the bottom */
+		if (type == 0 && prefs.tab_pos != POS_BOTTOM && prefs.tab_pos != POS_TOP)
+			prefs.tab_pos = POS_BOTTOM;
+
+		mg_place_userlist_and_chanview (mg_gui);
+		chanview_set_impl (mg_gui->chanview, type);
+	}
+}
+
+static void
+mg_inputbox_rightclick (GtkEntry *entry, GtkWidget *menu)
+{
+	mg_create_color_menu (menu, NULL);
+}
+
+static void
+mg_create_entry (session *sess, GtkWidget *box)
+{
+	GtkWidget *sw, *hbox, *but, *entry;
+	session_gui *gui = sess->gui;
+
+	hbox = gtk_hbox_new (FALSE, 0);
+	gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0);
+
+	gui->nick_box = gtk_hbox_new (FALSE, 0);
+	gtk_box_pack_start (GTK_BOX (hbox), gui->nick_box, 0, 0, 0);
+
+	gui->nick_label = but = gtk_button_new_with_label (sess->server->nick);
+	gtk_button_set_relief (GTK_BUTTON (but), GTK_RELIEF_NONE);
+	GTK_WIDGET_UNSET_FLAGS (but, GTK_CAN_FOCUS);
+	gtk_box_pack_end (GTK_BOX (gui->nick_box), but, 0, 0, 0);
+	g_signal_connect (G_OBJECT (but), "clicked",
+							G_CALLBACK (mg_nickclick_cb), NULL);
+
+#ifdef USE_GTKSPELL
+	gui->input_box = entry = gtk_text_view_new ();
+	gtk_widget_set_size_request (entry, 0, 1);
+	gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (entry), GTK_WRAP_NONE);
+	gtk_text_view_set_accepts_tab (GTK_TEXT_VIEW (entry), FALSE);
+	if (prefs.gui_input_spell)
+		gtkspell_new_attach (GTK_TEXT_VIEW (entry), NULL, NULL);
+
+	sw = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+													 GTK_SHADOW_IN);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+												GTK_POLICY_NEVER,
+												GTK_POLICY_NEVER);
+	gtk_container_add (GTK_CONTAINER (sw), entry);
+	gtk_container_add (GTK_CONTAINER (hbox), sw);
+#else
+#ifdef USE_LIBSEXY
+	gui->input_box = entry = sexy_spell_entry_new ();
+	sexy_spell_entry_set_checked ((SexySpellEntry *)entry, prefs.gui_input_spell);
+#else
+	gui->input_box = entry = gtk_entry_new ();
+#endif
+	gtk_entry_set_max_length (GTK_ENTRY (gui->input_box), 2048);
+	g_signal_connect (G_OBJECT (entry), "activate",
+							G_CALLBACK (mg_inputbox_cb), gui);
+	gtk_container_add (GTK_CONTAINER (hbox), entry);
+#endif
+
+	gtk_widget_set_name (entry, "xchat-inputbox");
+	g_signal_connect (G_OBJECT (entry), "key_press_event",
+							G_CALLBACK (key_handle_key_press), NULL);
+	g_signal_connect (G_OBJECT (entry), "focus_in_event",
+							G_CALLBACK (mg_inputbox_focus), gui);
+	g_signal_connect (G_OBJECT (entry), "populate_popup",
+							G_CALLBACK (mg_inputbox_rightclick), NULL);
+	gtk_widget_grab_focus (entry);
+
+	if (prefs.style_inputbox)
+		mg_apply_entry_style (entry);
+}
+
+static void
+mg_switch_tab_cb (chanview *cv, chan *ch, int tag, gpointer ud)
+{
+	chan *old;
+	session *sess = ud;
+
+	old = active_tab;
+	active_tab = ch;
+
+	if (tag == TAG_IRC)
+	{
+		if (active_tab != old)
+		{
+			if (old && current_tab)
+				mg_unpopulate (current_tab);
+			mg_populate (sess);
+		}
+	} else if (old != active_tab)
+	{
+		/* userdata for non-irc tabs is actually the GtkBox */
+		mg_show_generic_tab (ud);
+		if (!mg_is_userlist_and_tree_combined ())
+			mg_userlist_showhide (current_sess, FALSE);	/* hide */
+	}
+}
+
+/* compare two tabs (for tab sorting function) */
+
+static int
+mg_tabs_compare (session *a, session *b)
+{
+	/* server tabs always go first */
+	if (a->type == SESS_SERVER)
+		return -1;
+
+	/* then channels */
+	if (a->type == SESS_CHANNEL && b->type != SESS_CHANNEL)
+		return -1;
+	if (a->type != SESS_CHANNEL && b->type == SESS_CHANNEL)
+		return 1;
+
+	return strcasecmp (a->channel, b->channel);
+}
+
+static void
+mg_create_tabs (session_gui *gui)
+{
+	gboolean use_icons = FALSE;
+
+	/* if any one of these PNGs exist, the chanview will create
+	 * the extra column for icons. */
+	if (pix_channel || pix_dialog || pix_server || pix_util)
+		use_icons = TRUE;
+
+	gui->chanview = chanview_new (prefs.tab_layout, prefs.truncchans,
+											prefs.tab_sort, use_icons,
+											prefs.style_namelistgad ? input_style : NULL);
+	chanview_set_callbacks (gui->chanview, mg_switch_tab_cb, mg_xbutton_cb,
+									mg_tab_contextmenu_cb, (void *)mg_tabs_compare);
+	mg_place_userlist_and_chanview (gui);
+}
+
+static gboolean
+mg_tabwin_focus_cb (GtkWindow * win, GdkEventFocus *event, gpointer userdata)
+{
+	current_sess = current_tab;
+	if (current_sess)
+	{
+		gtk_xtext_check_marker_visibility (GTK_XTEXT (current_sess->gui->xtext));
+		plugin_emit_dummy_print (current_sess, "Focus Window");
+	}
+#ifndef WIN32
+#ifdef USE_XLIB
+	unflash_window (GTK_WIDGET (win));
+#endif
+#endif
+	return FALSE;
+}
+
+static gboolean
+mg_topwin_focus_cb (GtkWindow * win, GdkEventFocus *event, session *sess)
+{
+	current_sess = sess;
+	if (!sess->server->server_session)
+		sess->server->server_session = sess;
+	gtk_xtext_check_marker_visibility(GTK_XTEXT (current_sess->gui->xtext));
+#ifndef WIN32
+#ifdef USE_XLIB
+	unflash_window (GTK_WIDGET (win));
+#endif
+#endif
+	plugin_emit_dummy_print (sess, "Focus Window");
+	return FALSE;
+}
+
+static void
+mg_create_menu (session_gui *gui, GtkWidget *table, int away_state)
+{
+	GtkAccelGroup *accel_group;
+
+	accel_group = gtk_accel_group_new ();
+	gtk_window_add_accel_group (GTK_WINDOW (gtk_widget_get_toplevel (table)),
+										 accel_group);
+	g_object_unref (accel_group);
+
+	gui->menu = menu_create_main (accel_group, TRUE, away_state, !gui->is_tab,
+											gui->menu_item);
+	gtk_table_attach (GTK_TABLE (table), gui->menu, 0, 3, 0, 1,
+						   GTK_EXPAND | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+}
+
+static void
+mg_create_irctab (session *sess, GtkWidget *table)
+{
+	GtkWidget *vbox;
+	session_gui *gui = sess->gui;
+
+	vbox = gtk_vbox_new (FALSE, 0);
+	gtk_table_attach (GTK_TABLE (table), vbox, 1, 2, 2, 3,
+						   GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
+	mg_create_center (sess, gui, vbox);
+}
+
+static void
+mg_create_topwindow (session *sess)
+{
+	GtkWidget *win;
+	GtkWidget *table;
+
+	if (sess->type == SESS_DIALOG)
+		win = gtkutil_window_new ("XChat", NULL,
+										  prefs.dialog_width, prefs.dialog_height, 0);
+	else
+		win = gtkutil_window_new ("XChat", NULL,
+										  prefs.mainwindow_width,
+										  prefs.mainwindow_height, 0);
+	sess->gui->window = win;
+	gtk_container_set_border_width (GTK_CONTAINER (win), GUI_BORDER);
+
+	g_signal_connect (G_OBJECT (win), "focus_in_event",
+							G_CALLBACK (mg_topwin_focus_cb), sess);
+	g_signal_connect (G_OBJECT (win), "destroy",
+							G_CALLBACK (mg_topdestroy_cb), sess);
+	g_signal_connect (G_OBJECT (win), "configure_event",
+							G_CALLBACK (mg_configure_cb), sess);
+
+	palette_alloc (win);
+
+	table = gtk_table_new (4, 3, FALSE);
+	/* spacing under the menubar */
+	gtk_table_set_row_spacing (GTK_TABLE (table), 0, GUI_SPACING);
+	/* left and right borders */
+	gtk_table_set_col_spacing (GTK_TABLE (table), 0, 1);
+	gtk_table_set_col_spacing (GTK_TABLE (table), 1, 1);
+	gtk_container_add (GTK_CONTAINER (win), table);
+
+	mg_create_irctab (sess, table);
+	mg_create_menu (sess->gui, table, sess->server->is_away);
+
+	if (sess->res->buffer == NULL)
+	{
+		sess->res->buffer = gtk_xtext_buffer_new (GTK_XTEXT (sess->gui->xtext));
+		gtk_xtext_buffer_show (GTK_XTEXT (sess->gui->xtext), sess->res->buffer, TRUE);
+		gtk_xtext_set_time_stamp (sess->res->buffer, prefs.timestamp);
+		sess->res->user_model = userlist_create_model ();
+	}
+
+	userlist_show (sess);
+
+	gtk_widget_show_all (table);
+
+	if (prefs.hidemenu)
+		gtk_widget_hide (sess->gui->menu);
+
+	if (!prefs.topicbar)
+		gtk_widget_hide (sess->gui->topic_bar);
+
+	if (!prefs.userlistbuttons)
+		gtk_widget_hide (sess->gui->button_box);
+
+	if (prefs.gui_tweaks & 2)
+		gtk_widget_hide (sess->gui->nick_box);
+
+	mg_decide_userlist (sess, FALSE);
+
+	if (sess->type == SESS_DIALOG)
+	{
+		/* hide the chan-mode buttons */
+		gtk_widget_hide (sess->gui->topicbutton_box);
+	} else
+	{
+		gtk_widget_hide (sess->gui->dialogbutton_box);
+
+		if (!prefs.chanmodebuttons)
+			gtk_widget_hide (sess->gui->topicbutton_box);
+	}
+
+	mg_place_userlist_and_chanview (sess->gui);
+
+	gtk_widget_show (win);
+}
+
+static gboolean
+mg_tabwindow_de_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+	GSList *list;
+	session *sess;
+
+	if ((prefs.gui_tray_flags & 1) && tray_toggle_visibility (FALSE))
+		return TRUE;
+
+	/* check for remaining toplevel windows */
+	list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+		if (!sess->gui->is_tab)
+			return FALSE;
+		list = list->next;
+	}
+
+	mg_open_quit_dialog (TRUE);
+	return TRUE;
+}
+
+static void
+mg_create_tabwindow (session *sess)
+{
+	GtkWidget *win;
+	GtkWidget *table;
+
+	win = gtkutil_window_new ("XChat", NULL, prefs.mainwindow_width,
+									  prefs.mainwindow_height, 0);
+	sess->gui->window = win;
+	gtk_window_move (GTK_WINDOW (win), prefs.mainwindow_left,
+						  prefs.mainwindow_top);
+	if (prefs.gui_win_state)
+		gtk_window_maximize (GTK_WINDOW (win));
+	gtk_container_set_border_width (GTK_CONTAINER (win), GUI_BORDER);
+
+	g_signal_connect (G_OBJECT (win), "delete_event",
+						   G_CALLBACK (mg_tabwindow_de_cb), 0);
+	g_signal_connect (G_OBJECT (win), "destroy",
+						   G_CALLBACK (mg_tabwindow_kill_cb), 0);
+	g_signal_connect (G_OBJECT (win), "focus_in_event",
+							G_CALLBACK (mg_tabwin_focus_cb), NULL);
+	g_signal_connect (G_OBJECT (win), "configure_event",
+							G_CALLBACK (mg_configure_cb), NULL);
+	g_signal_connect (G_OBJECT (win), "window_state_event",
+							G_CALLBACK (mg_windowstate_cb), NULL);
+
+	palette_alloc (win);
+
+	sess->gui->main_table = table = gtk_table_new (4, 3, FALSE);
+	/* spacing under the menubar */
+	gtk_table_set_row_spacing (GTK_TABLE (table), 0, GUI_SPACING);
+	/* left and right borders */
+	gtk_table_set_col_spacing (GTK_TABLE (table), 0, 1);
+	gtk_table_set_col_spacing (GTK_TABLE (table), 1, 1);
+	gtk_container_add (GTK_CONTAINER (win), table);
+
+	mg_create_irctab (sess, table);
+	mg_create_tabs (sess->gui);
+	mg_create_menu (sess->gui, table, sess->server->is_away);
+
+	mg_focus (sess);
+
+	gtk_widget_show_all (table);
+
+	if (prefs.hidemenu)
+		gtk_widget_hide (sess->gui->menu);
+
+	mg_decide_userlist (sess, FALSE);
+
+	if (!prefs.topicbar)
+		gtk_widget_hide (sess->gui->topic_bar);
+
+	if (!prefs.chanmodebuttons)
+		gtk_widget_hide (sess->gui->topicbutton_box);
+
+	if (!prefs.userlistbuttons)
+		gtk_widget_hide (sess->gui->button_box);
+
+	if (prefs.gui_tweaks & 2)
+		gtk_widget_hide (sess->gui->nick_box);
+
+	mg_place_userlist_and_chanview (sess->gui);
+
+	gtk_widget_show (win);
+}
+
+void
+mg_apply_setup (void)
+{
+	GSList *list = sess_list;
+	session *sess;
+	int done_main = FALSE;
+
+	mg_create_tab_colors ();
+
+	while (list)
+	{
+		sess = list->data;
+		gtk_xtext_set_time_stamp (sess->res->buffer, prefs.timestamp);
+		((xtext_buffer *)sess->res->buffer)->needs_recalc = TRUE;
+		if (!sess->gui->is_tab || !done_main)
+			mg_place_userlist_and_chanview (sess->gui);
+		if (sess->gui->is_tab)
+			done_main = TRUE;
+		list = list->next;
+	}
+}
+
+static chan *
+mg_add_generic_tab (char *name, char *title, void *family, GtkWidget *box)
+{
+	chan *ch;
+
+	gtk_notebook_append_page (GTK_NOTEBOOK (mg_gui->note_book), box, NULL);
+	gtk_widget_show (box);
+
+	ch = chanview_add (mg_gui->chanview, name, NULL, box, TRUE, TAG_UTIL, pix_util);
+	chan_set_color (ch, plain_list);
+	/* FIXME: memory leak */
+	g_object_set_data (G_OBJECT (box), "title", strdup (title));
+	g_object_set_data (G_OBJECT (box), "ch", ch);
+
+	if (prefs.newtabstofront)
+		chan_focus (ch);
+
+	return ch;
+}
+
+void
+fe_buttons_update (session *sess)
+{
+	session_gui *gui = sess->gui;
+
+	gtk_widget_destroy (gui->button_box);
+	gui->button_box = mg_create_userlistbuttons (gui->button_box_parent);
+
+	if (prefs.userlistbuttons)
+		gtk_widget_show (sess->gui->button_box);
+	else
+		gtk_widget_hide (sess->gui->button_box);
+}
+
+void
+fe_clear_channel (session *sess)
+{
+	char tbuf[CHANLEN+6];
+	session_gui *gui = sess->gui;
+
+	if (sess->gui->is_tab)
+	{
+		if (sess->waitchannel[0])
+		{
+			if (prefs.truncchans > 2 && g_utf8_strlen (sess->waitchannel, -1) > prefs.truncchans)
+			{
+				/* truncate long channel names */
+				tbuf[0] = '(';
+				strcpy (tbuf + 1, sess->waitchannel);
+				g_utf8_offset_to_pointer(tbuf, prefs.truncchans)[0] = 0;
+				strcat (tbuf, "..)");
+			} else
+			{
+				sprintf (tbuf, "(%s)", sess->waitchannel);
+			}
+		}
+		else
+			strcpy (tbuf, _("<none>"));
+		chan_rename (sess->res->tab, tbuf, prefs.truncchans);
+	}
+
+	if (!sess->gui->is_tab || sess == current_tab)
+	{
+		gtk_entry_set_text (GTK_ENTRY (gui->topic_entry), "");
+
+		if (gui->op_xpm)
+		{
+			gtk_widget_destroy (gui->op_xpm);
+			gui->op_xpm = 0;
+		}
+	} else
+	{
+		if (sess->res->topic_text)
+		{
+			free (sess->res->topic_text);
+			sess->res->topic_text = NULL;
+		}
+	}
+}
+
+void
+fe_set_nonchannel (session *sess, int state)
+{
+}
+
+void
+fe_dlgbuttons_update (session *sess)
+{
+	GtkWidget *box;
+	session_gui *gui = sess->gui;
+
+	gtk_widget_destroy (gui->dialogbutton_box);
+
+	gui->dialogbutton_box = box = gtk_hbox_new (0, 0);
+	gtk_box_pack_start (GTK_BOX (gui->topic_bar), box, 0, 0, 0);
+	gtk_box_reorder_child (GTK_BOX (gui->topic_bar), box, 3);
+	mg_create_dialogbuttons (box);
+
+	gtk_widget_show_all (box);
+
+	if (current_tab && current_tab->type != SESS_DIALOG)
+		gtk_widget_hide (current_tab->gui->dialogbutton_box);
+}
+
+void
+fe_update_mode_buttons (session *sess, char mode, char sign)
+{
+	int state, i;
+
+	if (sign == '+')
+		state = TRUE;
+	else
+		state = FALSE;
+
+	for (i = 0; i < NUM_FLAG_WIDS - 1; i++)
+	{
+		if (chan_flags[i] == mode)
+		{
+			if (!sess->gui->is_tab || sess == current_tab)
+			{
+				ignore_chanmode = TRUE;
+				if (GTK_TOGGLE_BUTTON (sess->gui->flag_wid[i])->active != state)
+					gtk_toggle_button_set_active (
+							GTK_TOGGLE_BUTTON (sess->gui->flag_wid[i]), state);
+				ignore_chanmode = FALSE;
+			} else
+			{
+				sess->res->flag_wid_state[i] = state;
+			}
+			return;
+		}
+	}
+}
+
+void
+fe_set_nick (server *serv, char *newnick)
+{
+	GSList *list = sess_list;
+	session *sess;
+
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv)
+		{
+			if (current_tab == sess || !sess->gui->is_tab)
+				gtk_button_set_label (GTK_BUTTON (sess->gui->nick_label), newnick);
+		}
+		list = list->next;
+	}
+}
+
+void
+fe_set_away (server *serv)
+{
+	GSList *list = sess_list;
+	session *sess;
+
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv)
+		{
+			if (!sess->gui->is_tab || sess == current_tab)
+			{
+				GTK_CHECK_MENU_ITEM (sess->gui->menu_item[MENU_ID_AWAY])->active = serv->is_away;
+				/* gray out my nickname */
+				mg_set_myself_away (sess->gui, serv->is_away);
+			}
+		}
+		list = list->next;
+	}
+}
+
+void
+fe_set_channel (session *sess)
+{
+	if (sess->res->tab != NULL)
+		chan_rename (sess->res->tab, sess->channel, prefs.truncchans);
+}
+
+void
+mg_changui_new (session *sess, restore_gui *res, int tab, int focus)
+{
+	int first_run = FALSE;
+	session_gui *gui;
+	struct User *user = NULL;
+
+	if (!res)
+	{
+		res = malloc (sizeof (restore_gui));
+		memset (res, 0, sizeof (restore_gui));
+	}
+
+	sess->res = res;
+
+	if (!sess->server->front_session)
+		sess->server->front_session = sess;
+
+	if (!is_channel (sess->server, sess->channel))
+		user = userlist_find_global (sess->server, sess->channel);
+
+	if (!tab)
+	{
+		gui = malloc (sizeof (session_gui));
+		memset (gui, 0, sizeof (session_gui));
+		gui->is_tab = FALSE;
+		sess->gui = gui;
+		mg_create_topwindow (sess);
+		fe_set_title (sess);
+		if (user && user->hostname)
+			set_topic (sess, user->hostname, user->hostname);
+		return;
+	}
+
+	if (mg_gui == NULL)
+	{
+		first_run = TRUE;
+		gui = &static_mg_gui;
+		memset (gui, 0, sizeof (session_gui));
+		gui->is_tab = TRUE;
+		sess->gui = gui;
+		mg_create_tabwindow (sess);
+		mg_gui = gui;
+		parent_window = gui->window;
+	} else
+	{
+		sess->gui = gui = mg_gui;
+		gui->is_tab = TRUE;
+	}
+
+	if (user && user->hostname)
+		set_topic (sess, user->hostname, user->hostname);
+
+	mg_add_chan (sess);
+
+	if (first_run || (prefs.newtabstofront == FOCUS_NEW_ONLY_ASKED && focus)
+			|| prefs.newtabstofront == FOCUS_NEW_ALL )
+		chan_focus (res->tab);
+}
+
+GtkWidget *
+mg_create_generic_tab (char *name, char *title, int force_toplevel,
+							  int link_buttons,
+							  void *close_callback, void *userdata,
+							  int width, int height, GtkWidget **vbox_ret,
+							  void *family)
+{
+	GtkWidget *vbox, *win;
+
+	if (prefs.tab_pos == POS_HIDDEN && prefs.windows_as_tabs)
+		prefs.windows_as_tabs = 0;
+
+	if (force_toplevel || !prefs.windows_as_tabs)
+	{
+		win = gtkutil_window_new (title, name, width, height, 3);
+		vbox = gtk_vbox_new (0, 0);
+		*vbox_ret = vbox;
+		gtk_container_add (GTK_CONTAINER (win), vbox);
+		gtk_widget_show (vbox);
+		if (close_callback)
+			g_signal_connect (G_OBJECT (win), "destroy",
+									G_CALLBACK (close_callback), userdata);
+		return win;
+	}
+
+	vbox = gtk_vbox_new (0, 2);
+	g_object_set_data (G_OBJECT (vbox), "w", GINT_TO_POINTER (width));
+	g_object_set_data (G_OBJECT (vbox), "h", GINT_TO_POINTER (height));
+	gtk_container_set_border_width (GTK_CONTAINER (vbox), 3);
+	*vbox_ret = vbox;
+
+	if (close_callback)
+		g_signal_connect (G_OBJECT (vbox), "destroy",
+								G_CALLBACK (close_callback), userdata);
+
+	mg_add_generic_tab (name, title, family, vbox);
+
+/*	if (link_buttons)
+	{
+		hbox = gtk_hbox_new (FALSE, 0);
+		gtk_box_pack_start (GTK_BOX (vbox), hbox, 0, 0, 0);
+		mg_create_link_buttons (hbox, ch);
+		gtk_widget_show (hbox);
+	}*/
+
+	return vbox;
+}
+
+void
+mg_move_tab (session *sess, int delta)
+{
+	if (sess->gui->is_tab)
+		chan_move (sess->res->tab, delta);
+}
+
+void
+mg_move_tab_family (session *sess, int delta)
+{
+	if (sess->gui->is_tab)
+		chan_move_family (sess->res->tab, delta);
+}
+
+void
+mg_set_title (GtkWidget *vbox, char *title) /* for non-irc tab/window only */
+{
+	char *old;
+
+	old = g_object_get_data (G_OBJECT (vbox), "title");
+	if (old)
+	{
+		g_object_set_data (G_OBJECT (vbox), "title", strdup (title));
+		free (old);
+	} else
+	{
+		gtk_window_set_title (GTK_WINDOW (vbox), title);
+	}
+}
+
+void
+fe_server_callback (server *serv)
+{
+	joind_close (serv);
+
+	if (serv->gui->chanlist_window)
+		mg_close_gen (NULL, serv->gui->chanlist_window);
+
+	if (serv->gui->rawlog_window)
+		mg_close_gen (NULL, serv->gui->rawlog_window);
+
+	free (serv->gui);
+}
+
+/* called when a session is being killed */
+
+void
+fe_session_callback (session *sess)
+{
+	if (sess->res->banlist_window)
+		mg_close_gen (NULL, sess->res->banlist_window);
+
+	if (sess->res->input_text)
+		free (sess->res->input_text);
+
+	if (sess->res->topic_text)
+		free (sess->res->topic_text);
+
+	if (sess->res->limit_text)
+		free (sess->res->limit_text);
+
+	if (sess->res->key_text)
+		free (sess->res->key_text);
+
+	if (sess->res->queue_text)
+		free (sess->res->queue_text);
+	if (sess->res->queue_tip)
+		free (sess->res->queue_tip);
+
+	if (sess->res->lag_text)
+		free (sess->res->lag_text);
+	if (sess->res->lag_tip)
+		free (sess->res->lag_tip);
+
+	if (sess->gui->bartag)
+		fe_timeout_remove (sess->gui->bartag);
+
+	if (sess->gui != &static_mg_gui)
+		free (sess->gui);
+	free (sess->res);
+}
+
+/* ===== DRAG AND DROP STUFF ===== */
+
+static gboolean
+is_child_of (GtkWidget *widget, GtkWidget *parent)
+{
+	while (widget)
+	{
+		if (widget->parent == parent)
+			return TRUE;
+		widget = widget->parent;
+	}
+	return FALSE;
+}
+
+static void
+mg_handle_drop (GtkWidget *widget, int y, int *pos, int *other_pos)
+{
+	int height;
+	session_gui *gui = current_sess->gui;
+
+	gdk_drawable_get_size (widget->window, NULL, &height);
+
+	if (y < height / 2)
+	{
+		if (is_child_of (widget, gui->vpane_left))
+			*pos = 1;	/* top left */
+		else
+			*pos = 3;	/* top right */
+	}
+	else
+	{
+		if (is_child_of (widget, gui->vpane_left))
+			*pos = 2;	/* bottom left */
+		else
+			*pos = 4;	/* bottom right */
+	}
+
+	/* both in the same pos? must move one */
+	if (*pos == *other_pos)
+	{
+		switch (*other_pos)
+		{
+		case 1:
+			*other_pos = 2;
+			break;
+		case 2:
+			*other_pos = 1;
+			break;
+		case 3:
+			*other_pos = 4;
+			break;
+		case 4:
+			*other_pos = 3;
+			break;
+		}
+	}
+
+	mg_place_userlist_and_chanview (gui);
+}
+
+static gboolean
+mg_is_gui_target (GdkDragContext *context)
+{
+	char *target_name;
+
+	if (!context || !context->targets || !context->targets->data)
+		return FALSE;
+
+	target_name = gdk_atom_name (context->targets->data);
+	if (target_name)
+	{
+		/* if it's not XCHAT_CHANVIEW or XCHAT_USERLIST */
+		/* we should ignore it. */
+		if (target_name[0] != 'X')
+		{
+			g_free (target_name);
+			return FALSE;
+		}
+		g_free (target_name);
+	}
+
+	return TRUE;
+}
+
+/* this begin callback just creates an nice of the source */
+
+gboolean
+mg_drag_begin_cb (GtkWidget *widget, GdkDragContext *context, gpointer userdata)
+{
+#ifndef WIN32	/* leaks GDI pool memory - don't use on win32 */
+	int width, height;
+	GdkColormap *cmap;
+	GdkPixbuf *pix, *pix2;
+
+	/* ignore file drops */
+	if (!mg_is_gui_target (context))
+		return FALSE;
+
+	cmap = gtk_widget_get_colormap (widget);
+	gdk_drawable_get_size (widget->window, &width, &height);
+
+	pix = gdk_pixbuf_get_from_drawable (NULL, widget->window, cmap, 0, 0, 0, 0, width, height);
+	pix2 = gdk_pixbuf_scale_simple (pix, width * 4 / 5, height / 2, GDK_INTERP_HYPER);
+	g_object_unref (pix);
+
+	gtk_drag_set_icon_pixbuf (context, pix2, 0, 0);
+	g_object_set_data (G_OBJECT (widget), "ico", pix2);
+#endif
+
+	return TRUE;
+}
+
+void
+mg_drag_end_cb (GtkWidget *widget, GdkDragContext *context, gpointer userdata)
+{
+	/* ignore file drops */
+	if (!mg_is_gui_target (context))
+		return;
+
+#ifndef WIN32
+	g_object_unref (g_object_get_data (G_OBJECT (widget), "ico"));
+#endif
+}
+
+/* drop complete */
+
+gboolean
+mg_drag_drop_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, gpointer user_data)
+{
+	/* ignore file drops */
+	if (!mg_is_gui_target (context))
+		return FALSE;
+
+	switch (context->action)
+	{
+	case GDK_ACTION_MOVE:
+		/* from userlist */
+		mg_handle_drop (widget, y, &prefs.gui_ulist_pos, &prefs.tab_pos);
+		break;
+	case GDK_ACTION_COPY:
+		/* from tree - we use GDK_ACTION_COPY for the tree */
+		mg_handle_drop (widget, y, &prefs.tab_pos, &prefs.gui_ulist_pos);
+		break;
+	default:
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/* draw highlight rectangle in the destination */
+
+gboolean
+mg_drag_motion_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, gpointer scbar)
+{
+	GdkGC *gc;
+	GdkColor col;
+	GdkGCValues val;
+	int half, width, height;
+	int ox, oy;
+	GtkPaned *paned;
+	GdkDrawable *draw;
+
+	/* ignore file drops */
+	if (!mg_is_gui_target (context))
+		return FALSE;
+
+	if (scbar)	/* scrollbar */
+	{
+		ox = widget->allocation.x;
+		oy = widget->allocation.y;
+		width = widget->allocation.width;
+		height = widget->allocation.height;
+		draw = widget->window;
+	}
+	else
+	{
+		ox = oy = 0;
+		gdk_drawable_get_size (widget->window, &width, &height);
+		draw = widget->window;
+	}
+
+	val.subwindow_mode = GDK_INCLUDE_INFERIORS;
+	val.graphics_exposures = 0;
+	val.function = GDK_XOR;
+
+	gc = gdk_gc_new_with_values (widget->window, &val, GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW | GDK_GC_FUNCTION);
+	col.red = rand() % 0xffff;
+	col.green = rand() % 0xffff;
+	col.blue = rand() % 0xffff;
+	gdk_colormap_alloc_color (gtk_widget_get_colormap (widget), &col, FALSE, TRUE);
+	gdk_gc_set_foreground (gc, &col);
+
+	half = height / 2;
+
+#if 0
+	/* are both tree/userlist on the same side? */
+	paned = (GtkPaned *)widget->parent->parent;
+	if (paned->child1 != NULL && paned->child2 != NULL)
+	{
+		gdk_draw_rectangle (draw, gc, 0, 1, 2, width - 3, height - 4);
+		gdk_draw_rectangle (draw, gc, 0, 0, 1, width - 1, height - 2);
+		g_object_unref (gc);
+		return TRUE;
+	}
+#endif
+
+	if (y < half)
+	{
+		gdk_draw_rectangle (draw, gc, FALSE, 1 + ox, 2 + oy, width - 3, half - 4);
+		gdk_draw_rectangle (draw, gc, FALSE, 0 + ox, 1 + oy, width - 1, half - 2);
+		gtk_widget_queue_draw_area (widget, ox, half + oy, width, height - half);
+	}
+	else
+	{
+		gdk_draw_rectangle (draw, gc, FALSE, 0 + ox, half + 1 + oy, width - 1, half - 2);
+		gdk_draw_rectangle (draw, gc, FALSE, 1 + ox, half + 2 + oy, width - 3, half - 4);
+		gtk_widget_queue_draw_area (widget, ox, oy, width, half);
+	}
+
+	g_object_unref (gc);
+
+	return TRUE;
+}
diff --git a/src/fe-gtk/maingui.h b/src/fe-gtk/maingui.h
new file mode 100644
index 00000000..bc9aaefd
--- /dev/null
+++ b/src/fe-gtk/maingui.h
@@ -0,0 +1,33 @@
+extern GtkStyle *input_style;
+extern GtkWidget *parent_window;
+
+void mg_changui_new (session *sess, restore_gui *res, int tab, int focus);
+void mg_update_xtext (GtkWidget *wid);
+void mg_open_quit_dialog (gboolean minimize_button);
+void mg_switch_page (int relative, int num);
+void mg_move_tab (session *, int delta);
+void mg_move_tab_family (session *, int delta);
+void mg_bring_tofront (GtkWidget *vbox);
+void mg_bring_tofront_sess (session *sess);
+void mg_decide_userlist (session *sess, gboolean switch_to_current);
+void mg_set_topic_tip (session *sess);
+GtkWidget *mg_create_generic_tab (char *name, char *title, int force_toplevel, int link_buttons, void *close_callback, void *userdata, int width, int height, GtkWidget **vbox_ret, void *family);
+void mg_set_title (GtkWidget *button, char *title);
+void mg_set_access_icon (session_gui *gui, GdkPixbuf *pix, gboolean away);
+void mg_apply_setup (void);
+void mg_close_sess (session *);
+void mg_tab_close (session *sess);
+void mg_detach (session *sess, int mode);
+void mg_progressbar_create (session_gui *gui);
+void mg_progressbar_destroy (session_gui *gui);
+void mg_dnd_drop_file (session *sess, char *target, char *uri);
+void mg_change_layout (int type);
+void mg_update_meters (session_gui *);
+void mg_inputbox_cb (GtkWidget *igad, session_gui *gui);
+void mg_create_icon_item (char *label, char *stock, GtkWidget *menu, void *callback, void *userdata);
+GtkWidget *mg_submenu (GtkWidget *menu, char *text);
+/* DND */
+gboolean mg_drag_begin_cb (GtkWidget *widget, GdkDragContext *context, gpointer userdata);
+void mg_drag_end_cb (GtkWidget *widget, GdkDragContext *context, gpointer userdata);
+gboolean mg_drag_drop_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, gpointer user_data);
+gboolean mg_drag_motion_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, gpointer user_data); 
diff --git a/src/fe-gtk/menu.c b/src/fe-gtk/menu.c
new file mode 100644
index 00000000..d04be222
--- /dev/null
+++ b/src/fe-gtk/menu.c
@@ -0,0 +1,2270 @@
+/* X-Chat
+ * Copyright (C) 1998-2007 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkcheckmenuitem.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkimage.h>
+#include <gtk/gtkimagemenuitem.h>
+#include <gtk/gtkradiomenuitem.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkmessagedialog.h>
+#include <gtk/gtkmenu.h>
+#include <gtk/gtkmenubar.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkversion.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "../common/cfgfiles.h"
+#include "../common/outbound.h"
+#include "../common/ignore.h"
+#include "../common/fe.h"
+#include "../common/server.h"
+#include "../common/servlist.h"
+#include "../common/notify.h"
+#include "../common/util.h"
+#include "xtext.h"
+#include "about.h"
+#include "ascii.h"
+#include "banlist.h"
+#include "chanlist.h"
+#include "editlist.h"
+#include "fkeys.h"
+#include "gtkutil.h"
+#include "maingui.h"
+#include "notifygui.h"
+#include "pixmaps.h"
+#include "rawlog.h"
+#include "palette.h"
+#include "plugingui.h"
+#include "search.h"
+#include "textgui.h"
+#include "urlgrab.h"
+#include "userlistgui.h"
+#include "menu.h"
+
+static GSList *submenu_list;
+
+enum
+{
+	M_MENUITEM,
+	M_NEWMENU,
+	M_END,
+	M_SEP,
+	M_MENUTOG,
+	M_MENURADIO,
+	M_MENUSTOCK,
+	M_MENUPIX,
+	M_MENUSUB
+};
+
+struct mymenu
+{
+	char *text;
+	void *callback;
+	char *image;
+	unsigned char type;	/* M_XXX */
+	unsigned char id;		/* MENU_ID_XXX (menu.h) */
+	unsigned char state;	/* ticked or not? */
+	unsigned char sensitive;	/* shaded out? */
+	guint key;				/* GDK_x */
+};
+
+#define XCMENU_DOLIST 1
+#define XCMENU_SHADED 1
+#define XCMENU_MARKUP 2
+#define XCMENU_MNEMONIC 4
+
+/* execute a userlistbutton/popupmenu command */
+
+static void
+nick_command (session * sess, char *cmd)
+{
+	if (*cmd == '!')
+		xchat_exec (cmd + 1);
+	else
+		handle_command (sess, cmd, TRUE);
+}
+
+/* fill in the %a %s %n etc and execute the command */
+
+void
+nick_command_parse (session *sess, char *cmd, char *nick, char *allnick)
+{
+	char *buf;
+	char *host = _("Host unknown");
+	struct User *user;
+	int len;
+
+/*	if (sess->type == SESS_DIALOG)
+	{
+		buf = (char *)(GTK_ENTRY (sess->gui->topic_entry)->text);
+		buf = strrchr (buf, '@');
+		if (buf)
+			host = buf + 1;
+	} else*/
+	{
+		user = userlist_find (sess, nick);
+		if (user && user->hostname)
+			host = strchr (user->hostname, '@') + 1;
+	}
+
+	/* this can't overflow, since popup->cmd is only 256 */
+	len = strlen (cmd) + strlen (nick) + strlen (allnick) + 512;
+	buf = malloc (len);
+
+	auto_insert (buf, len, cmd, 0, 0, allnick, sess->channel, "",
+					 server_get_network (sess->server, TRUE), host,
+					 sess->server->nick, nick);
+
+	nick_command (sess, buf);
+
+	free (buf);
+}
+
+/* userlist button has been clicked */
+
+void
+userlist_button_cb (GtkWidget * button, char *cmd)
+{
+	int i, num_sel, using_allnicks = FALSE;
+	char **nicks, *allnicks;
+	char *nick = NULL;
+	session *sess;
+
+	sess = current_sess;
+
+	if (strstr (cmd, "%a"))
+		using_allnicks = TRUE;
+
+	if (sess->type == SESS_DIALOG)
+	{
+		/* fake a selection */
+		nicks = malloc (sizeof (char *) * 2);
+		nicks[0] = g_strdup (sess->channel);
+		nicks[1] = NULL;
+		num_sel = 1;
+	} else
+	{
+		/* find number of selected rows */
+		nicks = userlist_selection_list (sess->gui->user_tree, &num_sel);
+		if (num_sel < 1)
+		{
+			nick_command_parse (sess, cmd, "", "");
+			return;
+		}
+	}
+
+	/* create "allnicks" string */
+	allnicks = malloc (((NICKLEN + 1) * num_sel) + 1);
+	*allnicks = 0;
+
+	i = 0;
+	while (nicks[i])
+	{
+		if (i > 0)
+			strcat (allnicks, " ");
+		strcat (allnicks, nicks[i]);
+
+		if (!nick)
+			nick = nicks[0];
+
+		/* if not using "%a", execute the command once for each nickname */
+		if (!using_allnicks)
+			nick_command_parse (sess, cmd, nicks[i], "");
+
+		i++;
+	}
+
+	if (using_allnicks)
+	{
+		if (!nick)
+			nick = "";
+		nick_command_parse (sess, cmd, nick, allnicks);
+	}
+
+	while (num_sel)
+	{
+		num_sel--;
+		g_free (nicks[num_sel]);
+	}
+
+	free (nicks);
+	free (allnicks);
+}
+
+/* a popup-menu-item has been selected */
+
+static void
+popup_menu_cb (GtkWidget * item, char *cmd)
+{
+	char *nick;
+
+	/* the userdata is set in menu_quick_item() */
+	nick = g_object_get_data (G_OBJECT (item), "u");
+
+	if (!nick)	/* userlist popup menu */
+	{
+		/* treat it just like a userlist button */
+		userlist_button_cb (NULL, cmd);
+		return;
+	}
+
+	if (!current_sess)	/* for url grabber window */
+		nick_command_parse (sess_list->data, cmd, nick, nick);
+	else
+		nick_command_parse (current_sess, cmd, nick, nick);
+}
+
+GtkWidget *
+menu_toggle_item (char *label, GtkWidget *menu, void *callback, void *userdata,
+						int state)
+{
+	GtkWidget *item;
+
+	item = gtk_check_menu_item_new_with_mnemonic (label);
+	gtk_check_menu_item_set_active ((GtkCheckMenuItem*)item, state);
+	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+	g_signal_connect (G_OBJECT (item), "activate",
+							G_CALLBACK (callback), userdata);
+	gtk_widget_show (item);
+
+	return item;
+}
+
+GtkWidget *
+menu_quick_item (char *cmd, char *label, GtkWidget * menu, int flags,
+					  gpointer userdata, char *icon)
+{
+	GtkWidget *img, *item;
+	char *path;
+
+	if (!label)
+		item = gtk_menu_item_new ();
+	else
+	{
+		if (icon)
+		{
+			/*if (flags & XCMENU_MARKUP)
+				item = gtk_image_menu_item_new_with_markup (label);
+			else*/
+				item = gtk_image_menu_item_new_with_mnemonic (label);
+			img = NULL;
+			if (access (icon, R_OK) == 0)	/* try fullpath */
+				img = gtk_image_new_from_file (icon);
+			else
+			{
+				/* try relative to ~/.xchat2 */
+				path = g_strdup_printf ("%s/%s", get_xdir_fs (), icon);
+				if (access (path, R_OK) == 0)
+					img = gtk_image_new_from_file (path);
+				else
+					img = gtk_image_new_from_stock (icon, GTK_ICON_SIZE_MENU);
+				g_free (path);
+			}
+
+			if (img)
+				gtk_image_menu_item_set_image ((GtkImageMenuItem *)item, img);
+		}
+		else
+		{
+			if (flags & XCMENU_MARKUP)
+			{
+				item = gtk_menu_item_new_with_label ("");
+				if (flags & XCMENU_MNEMONIC)
+					gtk_label_set_markup_with_mnemonic (GTK_LABEL (GTK_BIN (item)->child), label);
+				else
+					gtk_label_set_markup (GTK_LABEL (GTK_BIN (item)->child), label);
+			} else
+			{
+				if (flags & XCMENU_MNEMONIC)
+					item = gtk_menu_item_new_with_mnemonic (label);
+				else
+					item = gtk_menu_item_new_with_label (label);
+			}
+		}
+	}
+	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+	g_object_set_data (G_OBJECT (item), "u", userdata);
+	if (cmd)
+		g_signal_connect (G_OBJECT (item), "activate",
+								G_CALLBACK (popup_menu_cb), cmd);
+	if (flags & XCMENU_SHADED)
+		gtk_widget_set_sensitive (GTK_WIDGET (item), FALSE);
+	gtk_widget_show_all (item);
+
+	return item;
+}
+
+static void
+menu_quick_item_with_callback (void *callback, char *label, GtkWidget * menu,
+										 void *arg)
+{
+	GtkWidget *item;
+
+	item = gtk_menu_item_new_with_label (label);
+	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+	g_signal_connect (G_OBJECT (item), "activate",
+							G_CALLBACK (callback), arg);
+	gtk_widget_show (item);
+}
+
+GtkWidget *
+menu_quick_sub (char *name, GtkWidget *menu, GtkWidget **sub_item_ret, int flags, int pos)
+{
+	GtkWidget *sub_menu;
+	GtkWidget *sub_item;
+
+	if (!name)
+		return menu;
+
+	/* Code to add a submenu */
+	sub_menu = gtk_menu_new ();
+	if (flags & XCMENU_MARKUP)
+	{
+		sub_item = gtk_menu_item_new_with_label ("");
+		gtk_label_set_markup (GTK_LABEL (GTK_BIN (sub_item)->child), name);
+	}
+	else
+	{
+		if (flags & XCMENU_MNEMONIC)
+			sub_item = gtk_menu_item_new_with_mnemonic (name);
+		else
+			sub_item = gtk_menu_item_new_with_label (name);
+	}
+	gtk_menu_shell_insert (GTK_MENU_SHELL (menu), sub_item, pos);
+	gtk_widget_show (sub_item);
+	gtk_menu_item_set_submenu (GTK_MENU_ITEM (sub_item), sub_menu);
+
+	if (sub_item_ret)
+		*sub_item_ret = sub_item;
+
+	if (flags & XCMENU_DOLIST)
+		/* We create a new element in the list */
+		submenu_list = g_slist_prepend (submenu_list, sub_menu);
+	return sub_menu;
+}
+
+static GtkWidget *
+menu_quick_endsub ()
+{
+	/* Just delete the first element in the linked list pointed to by first */
+	if (submenu_list)
+		submenu_list = g_slist_remove (submenu_list, submenu_list->data);
+
+	if (submenu_list)
+		return (submenu_list->data);
+	else
+		return NULL;
+}
+
+static void
+toggle_cb (GtkWidget *item, char *pref_name)
+{
+	char buf[256];
+
+	if (GTK_CHECK_MENU_ITEM (item)->active)
+		snprintf (buf, sizeof (buf), "set %s 1", pref_name);
+	else
+		snprintf (buf, sizeof (buf), "set %s 0", pref_name);
+
+	handle_command (current_sess, buf, FALSE);
+}
+
+static int
+is_in_path (char *cmd)
+{
+	char *prog = strdup (cmd + 1);	/* 1st char is "!" */
+	char *space, *path, *orig;
+
+	orig = prog; /* save for free()ing */
+	/* special-case these default entries. */
+	/*                  123456789012345678 */
+	if (strncmp (prog, "gnome-terminal -x ", 18) == 0)
+	/* don't check for gnome-terminal, but the thing it's executing! */
+		prog += 18;
+
+	space = strchr (prog, ' ');	/* this isn't 100% but good enuf */
+	if (space)
+		*space = 0;
+
+	path = g_find_program_in_path (prog);
+	if (path)
+	{
+		g_free (path);
+		g_free (orig);
+		return 1;
+	}
+
+	g_free (orig);
+	return 0;
+}
+
+/* syntax: "LABEL~ICON~STUFF~ADDED~LATER~" */
+
+static void
+menu_extract_icon (char *name, char **label, char **icon)
+{
+	char *p = name;
+	char *start = NULL;
+	char *end = NULL;
+
+	while (*p)
+	{
+		if (*p == '~')
+		{
+			/* escape \~ */
+			if (p == name || p[-1] != '\\')
+			{
+				if (!start)
+					start = p + 1;
+				else if (!end)
+					end = p + 1;
+			}
+		}
+		p++;
+	}
+
+	if (!end)
+		end = p;
+
+	if (start && start != end)
+	{
+		*label = g_strndup (name, (start - name) - 1);
+		*icon = g_strndup (start, (end - start) - 1);
+	}
+	else
+	{
+		*label = g_strdup (name);
+		*icon = NULL;
+	}
+}
+
+/* append items to "menu" using the (struct popup*) list provided */
+
+void
+menu_create (GtkWidget *menu, GSList *list, char *target, int check_path)
+{
+	struct popup *pop;
+	GtkWidget *tempmenu = menu, *subitem = NULL;
+	int childcount = 0;
+
+	submenu_list = g_slist_prepend (0, menu);
+	while (list)
+	{
+		pop = (struct popup *) list->data;
+
+		if (!strncasecmp (pop->name, "SUB", 3))
+		{
+			childcount = 0;
+			tempmenu = menu_quick_sub (pop->cmd, tempmenu, &subitem, XCMENU_DOLIST|XCMENU_MNEMONIC, -1);
+
+		} else if (!strncasecmp (pop->name, "TOGGLE", 6))
+		{
+			childcount++;
+			menu_toggle_item (pop->name + 7, tempmenu, toggle_cb, pop->cmd,
+									cfg_get_bool (pop->cmd));
+
+		} else if (!strncasecmp (pop->name, "ENDSUB", 6))
+		{
+			/* empty sub menu due to no programs in PATH? */
+			if (check_path && childcount < 1)
+				gtk_widget_destroy (subitem);
+			subitem = NULL;
+
+			if (tempmenu != menu)
+				tempmenu = menu_quick_endsub ();
+			/* If we get here and tempmenu equals menu that means we havent got any submenus to exit from */
+
+		} else if (!strncasecmp (pop->name, "SEP", 3))
+		{
+			menu_quick_item (0, 0, tempmenu, XCMENU_SHADED, 0, 0);
+
+		} else
+		{
+			char *icon, *label;
+
+			/* default command in xchat.c */
+			if (pop->cmd[0] == 'n' && !strcmp (pop->cmd, "notify -n ASK %s"))
+			{
+				/* don't create this item if already in notify list */
+				if (!target || notify_is_in_list (current_sess->server, target))
+				{
+					list = list->next;
+					continue;
+				}
+			}
+
+			menu_extract_icon (pop->name, &label, &icon);
+
+			if (!check_path || pop->cmd[0] != '!')
+			{
+				menu_quick_item (pop->cmd, label, tempmenu, 0, target, icon);
+			/* check if the program is in path, if not, leave it out! */
+			} else if (is_in_path (pop->cmd))
+			{
+				childcount++;
+				menu_quick_item (pop->cmd, label, tempmenu, 0, target, icon);
+			}
+
+			g_free (label);
+			g_free (icon);
+		}
+
+		list = list->next;
+	}
+
+	/* Let's clean up the linked list from mem */
+	while (submenu_list)
+		submenu_list = g_slist_remove (submenu_list, submenu_list->data);
+}
+
+static char *str_copy = NULL;		/* for all pop-up menus */
+static GtkWidget *nick_submenu = NULL;	/* user info submenu */
+
+static void
+menu_destroy (GtkWidget *menu, gpointer objtounref)
+{
+	gtk_widget_destroy (menu);
+	g_object_unref (menu);
+	if (objtounref)
+		g_object_unref (G_OBJECT (objtounref));
+	nick_submenu = NULL;
+}
+
+static void
+menu_popup (GtkWidget *menu, GdkEventButton *event, gpointer objtounref)
+{
+#if (GTK_MAJOR_VERSION != 2) || (GTK_MINOR_VERSION != 0)
+	if (event && event->window)
+		gtk_menu_set_screen (GTK_MENU (menu), gdk_drawable_get_screen (event->window));
+#endif
+
+	g_object_ref (menu);
+	g_object_ref_sink (menu);
+	g_object_unref (menu);
+	g_signal_connect (G_OBJECT (menu), "selection-done",
+							G_CALLBACK (menu_destroy), objtounref);
+	gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
+						 0, event ? event->time : 0);
+}
+
+static void
+menu_nickinfo_cb (GtkWidget *menu, session *sess)
+{
+	char buf[512];
+
+	if (!is_session (sess))
+		return;
+
+	/* issue a /WHOIS */
+	snprintf (buf, sizeof (buf), "WHOIS %s %s", str_copy, str_copy);
+	handle_command (sess, buf, FALSE);
+	/* and hide the output */
+	sess->server->skip_next_whois = 1;
+}
+
+static void
+copy_to_clipboard_cb (GtkWidget *item, char *url)
+{
+	gtkutil_copy_to_clipboard (item, NULL, url);
+}
+
+/* returns boolean: Some data is missing */
+
+static gboolean
+menu_create_nickinfo_menu (struct User *user, GtkWidget *submenu)
+{
+	char buf[512];
+	char unknown[96];
+	char *real, *fmt;
+	struct away_msg *away;
+	gboolean missing = FALSE;
+	GtkWidget *item;
+
+	/* let the translators tweak this if need be */
+	fmt = _("<tt><b>%-11s</b></tt> %s");
+	snprintf (unknown, sizeof (unknown), "<i>%s</i>", _("Unknown"));
+
+	if (user->realname)
+	{
+		real = strip_color (user->realname, -1, STRIP_ALL|STRIP_ESCMARKUP);
+		snprintf (buf, sizeof (buf), fmt, _("Real Name:"), real);
+		g_free (real);
+	} else
+	{
+		snprintf (buf, sizeof (buf), fmt, _("Real Name:"), unknown);
+	}
+	item = menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0);
+	g_signal_connect (G_OBJECT (item), "activate",
+							G_CALLBACK (copy_to_clipboard_cb), 
+							user->realname ? user->realname : unknown);
+
+	snprintf (buf, sizeof (buf), fmt, _("User:"),
+				 user->hostname ? user->hostname : unknown);
+	item = menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0);
+	g_signal_connect (G_OBJECT (item), "activate",
+							G_CALLBACK (copy_to_clipboard_cb), 
+							user->hostname ? user->hostname : unknown);
+
+	snprintf (buf, sizeof (buf), fmt, _("Country:"),
+				 user->hostname ? country(user->hostname) : unknown);
+	item = menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0);
+	g_signal_connect (G_OBJECT (item), "activate",
+							G_CALLBACK (copy_to_clipboard_cb), 
+							user->hostname ? country(user->hostname) : unknown);
+
+	snprintf (buf, sizeof (buf), fmt, _("Server:"),
+				 user->servername ? user->servername : unknown);
+	item = menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0);
+	g_signal_connect (G_OBJECT (item), "activate",
+							G_CALLBACK (copy_to_clipboard_cb), 
+							user->servername ? user->servername : unknown);
+
+	if (user->lasttalk)
+	{
+		char min[96];
+
+		snprintf (min, sizeof (min), _("%u minutes ago"),
+					(unsigned int) ((time (0) - user->lasttalk) / 60));
+		snprintf (buf, sizeof (buf), fmt, _("Last Msg:"), min);
+	} else
+	{
+		snprintf (buf, sizeof (buf), fmt, _("Last Msg:"), unknown);
+	}
+	menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0);
+
+	if (user->away)
+	{
+		away = server_away_find_message (current_sess->server, user->nick);
+		if (away)
+		{
+			char *msg = strip_color (away->message ? away->message : unknown, -1, STRIP_ALL|STRIP_ESCMARKUP);
+			snprintf (buf, sizeof (buf), fmt, _("Away Msg:"), msg);
+			g_free (msg);
+			item = menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0);
+			g_signal_connect (G_OBJECT (item), "activate",
+									G_CALLBACK (copy_to_clipboard_cb), 
+									away->message ? away->message : unknown);
+		}
+		else
+			missing = TRUE;
+	}
+
+	return missing;
+}
+
+void
+fe_userlist_update (session *sess, struct User *user)
+{
+	GList *items, *next;
+
+	if (!nick_submenu || !str_copy)
+		return;
+
+	/* not the same nick as the menu? */
+	if (sess->server->p_cmp (user->nick, str_copy))
+		return;
+
+	/* get rid of the "show" signal */
+	g_signal_handlers_disconnect_by_func (nick_submenu, menu_nickinfo_cb, sess);
+
+	/* destroy all the old items */
+	items = ((GtkMenuShell *) nick_submenu)->children;
+	while (items)
+	{
+		next = items->next;
+		gtk_widget_destroy (items->data);
+		items = next;
+	}
+
+	/* and re-create them with new info */
+	menu_create_nickinfo_menu (user, nick_submenu);
+}
+
+void
+menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel)
+{
+	char buf[512];
+	struct User *user;
+	GtkWidget *submenu, *menu = gtk_menu_new ();
+
+	if (str_copy)
+		free (str_copy);
+	str_copy = strdup (nick);
+
+	submenu_list = 0;	/* first time through, might not be 0 */
+
+	/* more than 1 nick selected? */
+	if (num_sel > 1)
+	{
+		snprintf (buf, sizeof (buf), _("%d nicks selected."), num_sel);
+		menu_quick_item (0, buf, menu, 0, 0, 0);
+		menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0);
+	} else
+	{
+		user = userlist_find (sess, nick);	/* lasttalk is channel specific */
+		if (!user)
+			user = userlist_find_global (current_sess->server, nick);
+		if (user)
+		{
+			nick_submenu = submenu = menu_quick_sub (nick, menu, NULL, XCMENU_DOLIST, -1);
+
+			if (menu_create_nickinfo_menu (user, submenu) ||
+				 !user->hostname || !user->realname || !user->servername)
+			{
+				g_signal_connect (G_OBJECT (submenu), "show", G_CALLBACK (menu_nickinfo_cb), sess);
+			}
+
+			menu_quick_endsub ();
+			menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0);
+		}
+	}
+
+	if (num_sel > 1)
+		menu_create (menu, popup_list, NULL, FALSE);
+	else
+		menu_create (menu, popup_list, str_copy, FALSE);
+
+	if (num_sel == 0)	/* xtext click */
+		menu_add_plugin_items (menu, "\x5$NICK", str_copy);
+	else	/* userlist treeview click */
+		menu_add_plugin_items (menu, "\x5$NICK", NULL);
+
+	menu_popup (menu, event, NULL);
+}
+
+/* stuff for the View menu */
+
+static void
+menu_showhide_cb (session *sess)
+{
+	if (prefs.hidemenu)
+		gtk_widget_hide (sess->gui->menu);
+	else
+		gtk_widget_show (sess->gui->menu);
+}
+
+static void
+menu_topic_showhide_cb (session *sess)
+{
+	if (prefs.topicbar)
+		gtk_widget_show (sess->gui->topic_bar);
+	else
+		gtk_widget_hide (sess->gui->topic_bar);
+}
+
+static void
+menu_userlist_showhide_cb (session *sess)
+{
+	mg_decide_userlist (sess, TRUE);
+}
+
+static void
+menu_ulbuttons_showhide_cb (session *sess)
+{
+	if (prefs.userlistbuttons)
+		gtk_widget_show (sess->gui->button_box);
+	else
+		gtk_widget_hide (sess->gui->button_box);
+}
+
+static void
+menu_cmbuttons_showhide_cb (session *sess)
+{
+	switch (sess->type)
+	{
+	case SESS_CHANNEL:
+		if (prefs.chanmodebuttons)
+			gtk_widget_show (sess->gui->topicbutton_box);
+		else
+			gtk_widget_hide (sess->gui->topicbutton_box);
+		break;
+	default:
+		gtk_widget_hide (sess->gui->topicbutton_box);
+	}
+}
+
+static void
+menu_setting_foreach (void (*callback) (session *), int id, guint state)
+{
+	session *sess;
+	GSList *list;
+	int maindone = FALSE;	/* do it only once for EVERY tab */
+
+	list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+
+		if (!sess->gui->is_tab || !maindone)
+		{
+			if (sess->gui->is_tab)
+				maindone = TRUE;
+			if (id != -1)
+				GTK_CHECK_MENU_ITEM (sess->gui->menu_item[id])->active = state;
+			if (callback)
+				callback (sess);
+		}
+
+		list = list->next;
+	}
+}
+
+void
+menu_bar_toggle (void)
+{
+	prefs.hidemenu = !prefs.hidemenu;
+	menu_setting_foreach (menu_showhide_cb, MENU_ID_MENUBAR, !prefs.hidemenu);
+}
+
+static void
+menu_bar_toggle_cb (void)
+{
+	menu_bar_toggle ();
+	if (prefs.hidemenu)
+		fe_message (_("The Menubar is now hidden. You can show it again"
+						  " by pressing F9 or right-clicking in a blank part of"
+						  " the main text area."), FE_MSG_INFO);
+}
+
+static void
+menu_topicbar_toggle (GtkWidget *wid, gpointer ud)
+{
+	prefs.topicbar = !prefs.topicbar;
+	menu_setting_foreach (menu_topic_showhide_cb, MENU_ID_TOPICBAR,
+								 prefs.topicbar);
+}
+
+static void
+menu_userlist_toggle (GtkWidget *wid, gpointer ud)
+{
+	prefs.hideuserlist = !prefs.hideuserlist;
+	menu_setting_foreach (menu_userlist_showhide_cb, MENU_ID_USERLIST,
+								 !prefs.hideuserlist);
+}
+
+static void
+menu_ulbuttons_toggle (GtkWidget *wid, gpointer ud)
+{
+	prefs.userlistbuttons = !prefs.userlistbuttons;
+	menu_setting_foreach (menu_ulbuttons_showhide_cb, MENU_ID_ULBUTTONS,
+								 prefs.userlistbuttons);
+}
+
+static void
+menu_cmbuttons_toggle (GtkWidget *wid, gpointer ud)
+{
+	prefs.chanmodebuttons = !prefs.chanmodebuttons;
+	menu_setting_foreach (menu_cmbuttons_showhide_cb, MENU_ID_MODEBUTTONS,
+								 prefs.chanmodebuttons);
+}
+
+void
+menu_middlemenu (session *sess, GdkEventButton *event)
+{
+	GtkWidget *menu;
+	GtkAccelGroup *accel_group;
+
+	accel_group = gtk_accel_group_new ();
+	menu = menu_create_main (accel_group, FALSE, sess->server->is_away, !sess->gui->is_tab, NULL);
+	menu_popup (menu, event, accel_group);
+}
+
+static void
+open_url_cb (GtkWidget *item, char *url)
+{
+	char buf[512];
+
+	/* pass this to /URL so it can handle irc:// */
+	snprintf (buf, sizeof (buf), "URL %s", url);
+	handle_command (current_sess, buf, FALSE);
+}
+
+void
+menu_urlmenu (GdkEventButton *event, char *url)
+{
+	GtkWidget *menu;
+	char *tmp, *chop;
+
+	if (str_copy)
+		free (str_copy);
+	str_copy = strdup (url);
+
+	menu = gtk_menu_new ();
+	/* more than 51 chars? Chop it */
+	if (g_utf8_strlen (str_copy, -1) >= 52)
+	{
+		tmp = strdup (str_copy);
+		chop = g_utf8_offset_to_pointer (tmp, 48);
+		chop[0] = chop[1] = chop[2] = '.';
+		chop[3] = 0;
+		menu_quick_item (0, tmp, menu, XCMENU_SHADED, 0, 0);
+		free (tmp);
+	} else
+	{
+		menu_quick_item (0, str_copy, menu, XCMENU_SHADED, 0, 0);
+	}
+	menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0);
+
+	/* Two hardcoded entries */
+	if (strncmp (str_copy, "irc://", 6) == 0 ||
+	    strncmp (str_copy, "ircs://",7) == 0)
+		menu_quick_item_with_callback (open_url_cb, _("Connect"), menu, str_copy);
+	else
+		menu_quick_item_with_callback (open_url_cb, _("Open Link in Browser"), menu, str_copy);
+	menu_quick_item_with_callback (copy_to_clipboard_cb, _("Copy Selected Link"), menu, str_copy);
+	/* custom ones from urlhandlers.conf */
+	menu_create (menu, urlhandler_list, str_copy, TRUE);
+	menu_add_plugin_items (menu, "\x4$URL", str_copy);
+	menu_popup (menu, event, NULL);
+}
+
+static void
+menu_chan_cycle (GtkWidget * menu, char *chan)
+{
+	char tbuf[256];
+
+	if (current_sess)
+	{
+		snprintf (tbuf, sizeof tbuf, "CYCLE %s", chan);
+		handle_command (current_sess, tbuf, FALSE);
+	}
+}
+
+static void
+menu_chan_part (GtkWidget * menu, char *chan)
+{
+	char tbuf[256];
+
+	if (current_sess)
+	{
+		snprintf (tbuf, sizeof tbuf, "part %s", chan);
+		handle_command (current_sess, tbuf, FALSE);
+	}
+}
+
+static void
+menu_chan_join (GtkWidget * menu, char *chan)
+{
+	char tbuf[256];
+
+	if (current_sess)
+	{
+		snprintf (tbuf, sizeof tbuf, "join %s", chan);
+		handle_command (current_sess, tbuf, FALSE);
+	}
+}
+
+void
+menu_chanmenu (struct session *sess, GdkEventButton * event, char *chan)
+{
+	GtkWidget *menu;
+	int is_joined = FALSE;
+
+	if (find_channel (sess->server, chan))
+		is_joined = TRUE;
+
+	if (str_copy)
+		free (str_copy);
+	str_copy = strdup (chan);
+
+	menu = gtk_menu_new ();
+
+	menu_quick_item (0, chan, menu, XCMENU_SHADED, str_copy, 0);
+	menu_quick_item (0, 0, menu, XCMENU_SHADED, str_copy, 0);
+
+	if (!is_joined)
+		menu_quick_item_with_callback (menu_chan_join, _("Join Channel"), menu,
+												 str_copy);
+	else
+	{
+		menu_quick_item_with_callback (menu_chan_part, _("Part Channel"), menu,
+												 str_copy);
+		menu_quick_item_with_callback (menu_chan_cycle, _("Cycle Channel"), menu,
+												 str_copy);
+	}
+
+	menu_addfavoritemenu (sess->server, menu, str_copy);
+
+	menu_add_plugin_items (menu, "\x5$CHAN", str_copy);
+	menu_popup (menu, event, NULL);
+}
+
+static void
+menu_delfav_cb (GtkWidget *item, server *serv)
+{
+	servlist_autojoinedit (serv->network, str_copy, FALSE);
+}
+
+static void
+menu_addfav_cb (GtkWidget *item, server *serv)
+{
+	servlist_autojoinedit (serv->network, str_copy, TRUE);
+}
+
+void
+menu_addfavoritemenu (server *serv, GtkWidget *menu, char *channel)
+{
+	if (!serv->network)
+		return;
+
+	if (channel != str_copy)
+	{
+		if (str_copy)
+			free (str_copy);
+		str_copy = strdup (channel);
+	}
+
+	if (joinlist_is_in_list (serv, channel))
+		mg_create_icon_item (_("_Remove from Favorites"), GTK_STOCK_REMOVE, menu, menu_delfav_cb, serv);
+	else
+		mg_create_icon_item (_("_Add to Favorites"), GTK_STOCK_ADD, menu, menu_addfav_cb, serv);
+}
+
+static void
+menu_open_server_list (GtkWidget *wid, gpointer none)
+{
+	fe_serverlist_open (current_sess);
+}
+
+static void
+menu_settings (GtkWidget * wid, gpointer none)
+{
+	extern void setup_open (void);
+	setup_open ();
+}
+
+static void
+menu_usermenu (void)
+{
+	editlist_gui_open (NULL, NULL, usermenu_list, _("XChat: User menu"),
+							 "usermenu", "usermenu.conf", 0);
+}
+
+static void
+usermenu_create (GtkWidget *menu)
+{
+	menu_create (menu, usermenu_list, "", FALSE);
+	menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0);	/* sep */
+	menu_quick_item_with_callback (menu_usermenu, _("Edit This Menu..."), menu, 0);
+}
+
+static void
+usermenu_destroy (GtkWidget * menu)
+{
+	GList *items = ((GtkMenuShell *) menu)->children;
+	GList *next;
+
+	while (items)
+	{
+		next = items->next;
+		gtk_widget_destroy (items->data);
+		items = next;
+	}
+}
+
+void
+usermenu_update (void)
+{
+	int done_main = FALSE;
+	GSList *list = sess_list;
+	session *sess;
+	GtkWidget *menu;
+
+	while (list)
+	{
+		sess = list->data;
+		menu = sess->gui->menu_item[MENU_ID_USERMENU];
+		if (sess->gui->is_tab)
+		{
+			if (!done_main && menu)
+			{
+				usermenu_destroy (menu);
+				usermenu_create (menu);
+				done_main = TRUE;
+			}
+		} else if (menu)
+		{
+			usermenu_destroy (menu);
+			usermenu_create (menu);
+		}
+		list = list->next;
+	}
+}
+
+static void
+menu_newserver_window (GtkWidget * wid, gpointer none)
+{
+	int old = prefs.tabchannels;
+
+	prefs.tabchannels = 0;
+	new_ircwindow (NULL, NULL, SESS_SERVER, 0);
+	prefs.tabchannels = old;
+}
+
+static void
+menu_newchannel_window (GtkWidget * wid, gpointer none)
+{
+	int old = prefs.tabchannels;
+
+	prefs.tabchannels = 0;
+	new_ircwindow (current_sess->server, NULL, SESS_CHANNEL, 0);
+	prefs.tabchannels = old;
+}
+
+static void
+menu_newserver_tab (GtkWidget * wid, gpointer none)
+{
+	int old = prefs.tabchannels;
+	int oldf = prefs.newtabstofront;
+
+	prefs.tabchannels = 1;
+	/* force focus if setting is "only requested tabs" */
+	if (prefs.newtabstofront == 2)
+		prefs.newtabstofront = 1;
+	new_ircwindow (NULL, NULL, SESS_SERVER, 0);
+	prefs.tabchannels = old;
+	prefs.newtabstofront = oldf;
+}
+
+static void
+menu_newchannel_tab (GtkWidget * wid, gpointer none)
+{
+	int old = prefs.tabchannels;
+
+	prefs.tabchannels = 1;
+	new_ircwindow (current_sess->server, NULL, SESS_CHANNEL, 0);
+	prefs.tabchannels = old;
+}
+
+static void
+menu_rawlog (GtkWidget * wid, gpointer none)
+{
+	open_rawlog (current_sess->server);
+}
+
+static void
+menu_detach (GtkWidget * wid, gpointer none)
+{
+	mg_detach (current_sess, 0);
+}
+
+static void
+menu_close (GtkWidget * wid, gpointer none)
+{
+	mg_close_sess (current_sess);
+}
+
+static void
+menu_quit (GtkWidget * wid, gpointer none)
+{
+	mg_open_quit_dialog (FALSE);
+}
+
+static void
+menu_search ()
+{
+	search_open (current_sess);
+}
+
+static void
+menu_resetmarker (GtkWidget * wid, gpointer none)
+{
+	gtk_xtext_reset_marker_pos (GTK_XTEXT (current_sess->gui->xtext));
+}
+
+static void
+menu_flushbuffer (GtkWidget * wid, gpointer none)
+{
+	fe_text_clear (current_sess, 0);
+}
+
+static void
+savebuffer_req_done (session *sess, char *file)
+{
+	int fh;
+
+	if (!file)
+		return;
+
+	fh = open (file, O_TRUNC | O_WRONLY | O_CREAT, 0600);
+	if (fh != -1)
+	{
+		gtk_xtext_save (GTK_XTEXT (sess->gui->xtext), fh);
+		close (fh);
+	}
+}
+
+static void
+menu_savebuffer (GtkWidget * wid, gpointer none)
+{
+	gtkutil_file_req (_("Select an output filename"), savebuffer_req_done,
+							current_sess, NULL, FRF_WRITE);
+}
+
+static void
+menu_disconnect (GtkWidget * wid, gpointer none)
+{
+	handle_command (current_sess, "DISCON", FALSE);
+}
+
+static void
+menu_reconnect (GtkWidget * wid, gpointer none)
+{
+	if (current_sess->server->hostname[0])
+		handle_command (current_sess, "RECONNECT", FALSE);
+	else
+		fe_serverlist_open (current_sess);
+}
+
+static void
+menu_join_cb (GtkWidget *dialog, gint response, GtkEntry *entry)
+{
+	switch (response)
+	{
+	case GTK_RESPONSE_ACCEPT:
+		menu_chan_join (NULL, entry->text);
+		break;
+
+	case GTK_RESPONSE_HELP:
+		chanlist_opengui (current_sess->server, TRUE);
+		break;
+	}
+
+	gtk_widget_destroy (dialog);
+}
+
+static void
+menu_join_entry_cb (GtkWidget *entry, GtkDialog *dialog)
+{
+	gtk_dialog_response (dialog, GTK_RESPONSE_ACCEPT);
+}
+
+static void
+menu_join (GtkWidget * wid, gpointer none)
+{
+	GtkWidget *hbox, *dialog, *entry, *label;
+
+	dialog = gtk_dialog_new_with_buttons (_("Join Channel"),
+									GTK_WINDOW (parent_window), 0,
+									_("Retrieve channel list..."), GTK_RESPONSE_HELP,
+									GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+									GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+									NULL);
+	gtk_box_set_homogeneous (GTK_BOX (GTK_DIALOG (dialog)->vbox), TRUE);
+	gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
+	hbox = gtk_hbox_new (TRUE, 0);
+
+	entry = gtk_entry_new ();
+	GTK_ENTRY (entry)->editable = 0;	/* avoid auto-selection */
+	gtk_entry_set_text (GTK_ENTRY (entry), "#");
+	g_signal_connect (G_OBJECT (entry), "activate",
+						 	G_CALLBACK (menu_join_entry_cb), dialog);
+	gtk_box_pack_end (GTK_BOX (hbox), entry, 0, 0, 0);
+
+	label = gtk_label_new (_("Enter Channel to Join:"));
+	gtk_box_pack_end (GTK_BOX (hbox), label, 0, 0, 0);
+
+	g_signal_connect (G_OBJECT (dialog), "response",
+						   G_CALLBACK (menu_join_cb), entry);
+
+	gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), hbox);
+
+	gtk_widget_show_all (dialog);
+
+	gtk_editable_set_editable (GTK_EDITABLE (entry), TRUE);
+	gtk_editable_set_position (GTK_EDITABLE (entry), 1);
+}
+
+static void
+menu_away (GtkCheckMenuItem *item, gpointer none)
+{
+	handle_command (current_sess, item->active ? "away" : "back", FALSE);
+}
+
+static void
+menu_chanlist (GtkWidget * wid, gpointer none)
+{
+	chanlist_opengui (current_sess->server, FALSE);
+}
+
+static void
+menu_banlist (GtkWidget * wid, gpointer none)
+{
+	banlist_opengui (current_sess);
+}
+
+#ifdef USE_PLUGIN
+
+static void
+menu_loadplugin (void)
+{
+	plugingui_load ();
+}
+
+static void
+menu_pluginlist (void)
+{
+	plugingui_open ();
+}
+
+#else
+
+#define menu_pluginlist 0
+#define menu_loadplugin 0
+
+#endif
+
+#define usercommands_help  _("User Commands - Special codes:\n\n"\
+                           "%c  =  current channel\n"\
+									"%e  =  current network name\n"\
+									"%m  =  machine info\n"\
+                           "%n  =  your nick\n"\
+									"%t  =  time/date\n"\
+                           "%v  =  xchat version\n"\
+                           "%2  =  word 2\n"\
+                           "%3  =  word 3\n"\
+                           "&2  =  word 2 to the end of line\n"\
+                           "&3  =  word 3 to the end of line\n\n"\
+                           "eg:\n"\
+                           "/cmd john hello\n\n"\
+                           "%2 would be \042john\042\n"\
+                           "&2 would be \042john hello\042.")
+
+#define ulbutton_help       _("Userlist Buttons - Special codes:\n\n"\
+                           "%a  =  all selected nicks\n"\
+                           "%c  =  current channel\n"\
+									"%e  =  current network name\n"\
+                           "%h  =  selected nick's hostname\n"\
+									"%m  =  machine info\n"\
+                           "%n  =  your nick\n"\
+                           "%s  =  selected nick\n"\
+									"%t  =  time/date\n")
+
+#define dlgbutton_help      _("Dialog Buttons - Special codes:\n\n"\
+                           "%a  =  all selected nicks\n"\
+                           "%c  =  current channel\n"\
+									"%e  =  current network name\n"\
+                           "%h  =  selected nick's hostname\n"\
+									"%m  =  machine info\n"\
+                           "%n  =  your nick\n"\
+                           "%s  =  selected nick\n"\
+									"%t  =  time/date\n")
+
+#define ctcp_help          _("CTCP Replies - Special codes:\n\n"\
+                           "%d  =  data (the whole ctcp)\n"\
+									"%e  =  current network name\n"\
+									"%m  =  machine info\n"\
+                           "%s  =  nick who sent the ctcp\n"\
+                           "%t  =  time/date\n"\
+                           "%2  =  word 2\n"\
+                           "%3  =  word 3\n"\
+                           "&2  =  word 2 to the end of line\n"\
+                           "&3  =  word 3 to the end of line\n\n")
+
+#define url_help           _("URL Handlers - Special codes:\n\n"\
+                           "%s  =  the URL string\n\n"\
+                           "Putting a ! infront of the command\n"\
+                           "indicates it should be sent to a\n"\
+                           "shell instead of XChat")
+
+static void
+menu_usercommands (void)
+{
+	editlist_gui_open (NULL, NULL, command_list, _("XChat: User Defined Commands"),
+							 "commands", "commands.conf", usercommands_help);
+}
+
+static void
+menu_ulpopup (void)
+{
+	editlist_gui_open (NULL, NULL, popup_list, _("XChat: Userlist Popup menu"), "popup",
+							 "popup.conf", ulbutton_help);
+}
+
+static void
+menu_rpopup (void)
+{
+	editlist_gui_open (_("Text"), _("Replace with"), replace_list, _("XChat: Replace"), "replace",
+							 "replace.conf", 0);
+}
+
+static void
+menu_urlhandlers (void)
+{
+	editlist_gui_open (NULL, NULL, urlhandler_list, _("XChat: URL Handlers"), "urlhandlers",
+							 "urlhandlers.conf", url_help);
+}
+
+static void
+menu_evtpopup (void)
+{
+	pevent_dialog_show ();
+}
+
+static void
+menu_keypopup (void)
+{
+	key_dialog_show ();
+}
+
+static void
+menu_ulbuttons (void)
+{
+	editlist_gui_open (NULL, NULL, button_list, _("XChat: Userlist buttons"), "buttons",
+							 "buttons.conf", ulbutton_help);
+}
+
+static void
+menu_dlgbuttons (void)
+{
+	editlist_gui_open (NULL, NULL, dlgbutton_list, _("XChat: Dialog buttons"), "dlgbuttons",
+							 "dlgbuttons.conf", dlgbutton_help);
+}
+
+static void
+menu_ctcpguiopen (void)
+{
+	editlist_gui_open (NULL, NULL, ctcp_list, _("XChat: CTCP Replies"), "ctcpreply",
+							 "ctcpreply.conf", ctcp_help);
+}
+
+static void
+menu_docs (GtkWidget *wid, gpointer none)
+{
+	fe_open_url ("http://xchat.org/docs/");
+}
+
+/*static void
+menu_webpage (GtkWidget *wid, gpointer none)
+{
+	fe_open_url ("http://xchat.org");
+}*/
+
+static void
+menu_dcc_win (GtkWidget *wid, gpointer none)
+{
+	fe_dcc_open_recv_win (FALSE);
+	fe_dcc_open_send_win (FALSE);
+}
+
+static void
+menu_dcc_chat_win (GtkWidget *wid, gpointer none)
+{
+	fe_dcc_open_chat_win (FALSE);
+}
+
+void
+menu_change_layout (void)
+{
+	if (prefs.tab_layout == 0)
+	{
+		menu_setting_foreach (NULL, MENU_ID_LAYOUT_TABS, 1);
+		menu_setting_foreach (NULL, MENU_ID_LAYOUT_TREE, 0);
+		mg_change_layout (0);
+	} else
+	{
+		menu_setting_foreach (NULL, MENU_ID_LAYOUT_TABS, 0);
+		menu_setting_foreach (NULL, MENU_ID_LAYOUT_TREE, 1);
+		mg_change_layout (2);
+	}
+}
+
+static void
+menu_layout_cb (GtkWidget *item, gpointer none)
+{
+	prefs.tab_layout = 2;
+	if (GTK_CHECK_MENU_ITEM (item)->active)
+		prefs.tab_layout = 0;
+
+	menu_change_layout ();
+}
+
+static void
+menu_apply_metres_cb (session *sess)
+{
+	mg_update_meters (sess->gui);
+}
+
+static void
+menu_metres_off (GtkWidget *item, gpointer none)
+{
+	if (GTK_CHECK_MENU_ITEM (item)->active)
+	{
+		prefs.lagometer = 0;
+		prefs.throttlemeter = 0;
+		menu_setting_foreach (menu_apply_metres_cb, -1, 0);
+	}
+}
+
+static void
+menu_metres_text (GtkWidget *item, gpointer none)
+{
+	if (GTK_CHECK_MENU_ITEM (item)->active)
+	{
+		prefs.lagometer = 2;
+		prefs.throttlemeter = 2;
+		menu_setting_foreach (menu_apply_metres_cb, -1, 0);
+	}
+}
+
+static void
+menu_metres_graph (GtkWidget *item, gpointer none)
+{
+	if (GTK_CHECK_MENU_ITEM (item)->active)
+	{
+		prefs.lagometer = 1;
+		prefs.throttlemeter = 1;
+		menu_setting_foreach (menu_apply_metres_cb, -1, 0);
+	}
+}
+
+static void
+menu_metres_both (GtkWidget *item, gpointer none)
+{
+	if (GTK_CHECK_MENU_ITEM (item)->active)
+	{
+		prefs.lagometer = 3;
+		prefs.throttlemeter = 3;
+		menu_setting_foreach (menu_apply_metres_cb, -1, 0);
+	}
+}
+
+static struct mymenu mymenu[] = {
+	{N_("_XChat"), 0, 0, M_NEWMENU, 0, 0, 1},
+	{N_("Network Li_st..."), menu_open_server_list, (char *)&pix_book, M_MENUPIX, 0, 0, 1, GDK_s},
+	{0, 0, 0, M_SEP, 0, 0, 0},
+
+	{N_("_New"), 0, GTK_STOCK_NEW, M_MENUSUB, 0, 0, 1},
+		{N_("Server Tab..."), menu_newserver_tab, 0, M_MENUITEM, 0, 0, 1, GDK_t},
+		{N_("Channel Tab..."), menu_newchannel_tab, 0, M_MENUITEM, 0, 0, 1},
+		{N_("Server Window..."), menu_newserver_window, 0, M_MENUITEM, 0, 0, 1},
+		{N_("Channel Window..."), menu_newchannel_window, 0, M_MENUITEM, 0, 0, 1},
+		{0, 0, 0, M_END, 0, 0, 0},
+	{0, 0, 0, M_SEP, 0, 0, 0},
+
+#ifdef USE_PLUGIN
+	{N_("_Load Plugin or Script..."), menu_loadplugin, GTK_STOCK_REVERT_TO_SAVED, M_MENUSTOCK, 0, 0, 1},
+#else
+	{N_("_Load Plugin or Script..."), 0, GTK_STOCK_REVERT_TO_SAVED, M_MENUSTOCK, 0, 0, 0},
+#endif
+	{0, 0, 0, M_SEP, 0, 0, 0},	/* 11 */
+#define DETACH_OFFSET (12)
+	{0, menu_detach, GTK_STOCK_REDO, M_MENUSTOCK, 0, 0, 1, GDK_I},	/* 12 */
+#define CLOSE_OFFSET (13)
+	{0, menu_close, GTK_STOCK_CLOSE, M_MENUSTOCK, 0, 0, 1, GDK_w},
+	{0, 0, 0, M_SEP, 0, 0, 0},
+	{N_("_Quit"), menu_quit, GTK_STOCK_QUIT, M_MENUSTOCK, 0, 0, 1, GDK_q},	/* 15 */
+
+	{N_("_View"), 0, 0, M_NEWMENU, 0, 0, 1},
+#define MENUBAR_OFFSET (17)
+	{N_("_Menu Bar"), menu_bar_toggle_cb, 0, M_MENUTOG, MENU_ID_MENUBAR, 0, 1, GDK_F9},
+	{N_("_Topic Bar"), menu_topicbar_toggle, 0, M_MENUTOG, MENU_ID_TOPICBAR, 0, 1},
+	{N_("_User List"), menu_userlist_toggle, 0, M_MENUTOG, MENU_ID_USERLIST, 0, 1, GDK_F7},
+	{N_("U_serlist Buttons"), menu_ulbuttons_toggle, 0, M_MENUTOG, MENU_ID_ULBUTTONS, 0, 1},
+	{N_("M_ode Buttons"), menu_cmbuttons_toggle, 0, M_MENUTOG, MENU_ID_MODEBUTTONS, 0, 1},
+	{0, 0, 0, M_SEP, 0, 0, 0},
+	{N_("_Channel Switcher"), 0, 0, M_MENUSUB, 0, 0, 1},	/* 23 */
+#define TABS_OFFSET (24)
+		{N_("_Tabs"), menu_layout_cb, 0, M_MENURADIO, MENU_ID_LAYOUT_TABS, 0, 1},
+		{N_("T_ree"), 0, 0, M_MENURADIO, MENU_ID_LAYOUT_TREE, 0, 1},
+		{0, 0, 0, M_END, 0, 0, 0},
+	{N_("_Network Meters"), 0, 0, M_MENUSUB, 0, 0, 1},	/* 27 */
+#define METRE_OFFSET (28)
+		{N_("Off"), menu_metres_off, 0, M_MENURADIO, 0, 0, 1},
+		{N_("Graph"), menu_metres_graph, 0, M_MENURADIO, 0, 0, 1},
+		{N_("Text"), menu_metres_text, 0, M_MENURADIO, 0, 0, 1},
+		{N_("Both"), menu_metres_both, 0, M_MENURADIO, 0, 0, 1},
+		{0, 0, 0, M_END, 0, 0, 0},	/* 32 */
+
+	{N_("_Server"), 0, 0, M_NEWMENU, 0, 0, 1},
+	{N_("_Disconnect"), menu_disconnect, GTK_STOCK_DISCONNECT, M_MENUSTOCK, MENU_ID_DISCONNECT, 0, 1},
+	{N_("_Reconnect"), menu_reconnect, GTK_STOCK_CONNECT, M_MENUSTOCK, MENU_ID_RECONNECT, 0, 1},
+	{N_("Join a Channel..."), menu_join, GTK_STOCK_JUMP_TO, M_MENUSTOCK, MENU_ID_JOIN, 0, 1},
+	{N_("List of Channels..."), menu_chanlist, GTK_STOCK_INDEX, M_MENUITEM, 0, 0, 1},
+	{0, 0, 0, M_SEP, 0, 0, 0},
+#define AWAY_OFFSET (39)
+	{N_("Marked Away"), menu_away, 0, M_MENUTOG, MENU_ID_AWAY, 0, 1, GDK_a},
+
+	{N_("_Usermenu"), 0, 0, M_NEWMENU, MENU_ID_USERMENU, 0, 1},	/* 40 */
+
+	{N_("S_ettings"), 0, 0, M_NEWMENU, 0, 0, 1},
+	{N_("_Preferences"), menu_settings, GTK_STOCK_PREFERENCES, M_MENUSTOCK, 0, 0, 1},
+
+	{N_("Advanced"), 0, GTK_STOCK_JUSTIFY_LEFT, M_MENUSUB, 0, 0, 1},
+		{N_("Auto Replace..."), menu_rpopup, 0, M_MENUITEM, 0, 0, 1},
+		{N_("CTCP Replies..."), menu_ctcpguiopen, 0, M_MENUITEM, 0, 0, 1},
+		{N_("Dialog Buttons..."), menu_dlgbuttons, 0, M_MENUITEM, 0, 0, 1},
+		{N_("Keyboard Shortcuts..."), menu_keypopup, 0, M_MENUITEM, 0, 0, 1},
+		{N_("Text Events..."), menu_evtpopup, 0, M_MENUITEM, 0, 0, 1},
+		{N_("URL Handlers..."), menu_urlhandlers, 0, M_MENUITEM, 0, 0, 1},
+		{N_("User Commands..."), menu_usercommands, 0, M_MENUITEM, 0, 0, 1},
+		{N_("Userlist Buttons..."), menu_ulbuttons, 0, M_MENUITEM, 0, 0, 1},
+		{N_("Userlist Popup..."), menu_ulpopup, 0, M_MENUITEM, 0, 0, 1},
+		{0, 0, 0, M_END, 0, 0, 0},		/* 53 */
+
+	{N_("_Window"), 0, 0, M_NEWMENU, 0, 0, 1},
+	{N_("Ban List..."), menu_banlist, 0, M_MENUITEM, 0, 0, 1},
+	{N_("Character Chart..."), ascii_open, 0, M_MENUITEM, 0, 0, 1},
+	{N_("Direct Chat..."), menu_dcc_chat_win, 0, M_MENUITEM, 0, 0, 1},
+	{N_("File Transfers..."), menu_dcc_win, 0, M_MENUITEM, 0, 0, 1},
+	{N_("Friends List..."), notify_opengui, 0, M_MENUITEM, 0, 0, 1},
+	{N_("Ignore List..."), ignore_gui_open, 0, M_MENUITEM, 0, 0, 1},
+	{N_("Plugins and Scripts..."), menu_pluginlist, 0, M_MENUITEM, 0, 0, 1},
+	{N_("Raw Log..."), menu_rawlog, 0, M_MENUITEM, 0, 0, 1},	/* 62 */
+	{N_("URL Grabber..."), url_opengui, 0, M_MENUITEM, 0, 0, 1},
+	{0, 0, 0, M_SEP, 0, 0, 0},
+	{N_("Reset Marker Line"), menu_resetmarker, 0, M_MENUITEM, 0, 0, 1, GDK_m},
+	{N_("C_lear Text"), menu_flushbuffer, GTK_STOCK_CLEAR, M_MENUSTOCK, 0, 0, 1, GDK_l},
+#define SEARCH_OFFSET 67
+	{N_("Search Text..."), menu_search, GTK_STOCK_FIND, M_MENUSTOCK, 0, 0, 1, GDK_f},
+	{N_("Save Text..."), menu_savebuffer, GTK_STOCK_SAVE, M_MENUSTOCK, 0, 0, 1},
+
+	{N_("_Help"), 0, 0, M_NEWMENU, 0, 0, 1},	/* 69 */
+	{N_("_Contents"), menu_docs, GTK_STOCK_HELP, M_MENUSTOCK, 0, 0, 1, GDK_F1},
+#if 0
+	{N_("Check for updates"), menu_update, 0, M_MENUITEM, 0, 1},
+#endif
+	{N_("_About"), menu_about, GTK_STOCK_ABOUT, M_MENUSTOCK, 0, 0, 1},
+
+	{0, 0, 0, M_END, 0, 0, 0},
+};
+
+GtkWidget *
+create_icon_menu (char *labeltext, void *stock_name, int is_stock)
+{
+	GtkWidget *item, *img;
+
+	if (is_stock)
+		img = gtk_image_new_from_stock (stock_name, GTK_ICON_SIZE_MENU);
+	else
+		img = gtk_image_new_from_pixbuf (*((GdkPixbuf **)stock_name));
+	item = gtk_image_menu_item_new_with_mnemonic (labeltext);
+	gtk_image_menu_item_set_image ((GtkImageMenuItem *)item, img);
+	gtk_widget_show (img);
+
+	return item;
+}
+
+#if GTK_CHECK_VERSION(2,4,0)
+
+/* Override the default GTK2.4 handler, which would make menu
+   bindings not work when the menu-bar is hidden. */
+static gboolean
+menu_canacaccel (GtkWidget *widget, guint signal_id, gpointer user_data)
+{
+	/* GTK2.2 behaviour */
+#if GTK_CHECK_VERSION(2,20,0)
+	return gtk_widget_is_sensitive (widget);
+#else
+	return GTK_WIDGET_IS_SENSITIVE (widget);
+#endif
+}
+
+#endif
+
+
+/* === STUFF FOR /MENU === */
+
+static GtkMenuItem *
+menu_find_item (GtkWidget *menu, char *name)
+{
+	GList *items = ((GtkMenuShell *) menu)->children;
+	GtkMenuItem *item;
+	GtkWidget *child;
+	const char *labeltext;
+
+	while (items)
+	{
+		item = items->data;
+		child = GTK_BIN (item)->child;
+		if (child)	/* separators arn't labels, skip them */
+		{
+			labeltext = g_object_get_data (G_OBJECT (item), "name");
+			if (!labeltext)
+				labeltext = gtk_label_get_text (GTK_LABEL (child));
+			if (!menu_streq (labeltext, name, 1))
+				return item;
+		} else if (name == NULL)
+		{
+			return item;
+		}
+		items = items->next;
+	}
+
+	return NULL;
+}
+
+static GtkWidget *
+menu_find_path (GtkWidget *menu, char *path)
+{
+	GtkMenuItem *item;
+	char *s;
+	char name[128];
+	int len;
+
+	/* grab the next part of the path */
+	s = strchr (path, '/');
+	len = s - path;
+	if (!s)
+		len = strlen (path);
+	len = MIN (len, sizeof (name) - 1);
+	memcpy (name, path, len);
+	name[len] = 0;
+
+	item = menu_find_item (menu, name);
+	if (!item)
+		return NULL;
+
+	menu = gtk_menu_item_get_submenu (item);
+	if (!menu)
+		return NULL;
+
+	path += len;
+	if (*path == 0)
+		return menu;
+
+	return menu_find_path (menu, path + 1);
+}
+
+static GtkWidget *
+menu_find (GtkWidget *menu, char *path, char *label)
+{
+	GtkWidget *item = NULL;
+
+	if (path[0] != 0)
+		menu = menu_find_path (menu, path);
+	if (menu)
+		item = (GtkWidget *)menu_find_item (menu, label);
+	return item;
+}
+
+static void
+menu_foreach_gui (menu_entry *me, void (*callback) (GtkWidget *, menu_entry *, char *))
+{
+	GSList *list = sess_list;
+	int tabdone = FALSE;
+	session *sess;
+
+	if (!me->is_main)
+		return;	/* not main menu */
+
+	while (list)
+	{
+		sess = list->data;
+		/* do it only once for tab sessions, since they share a GUI */
+		if (!sess->gui->is_tab || !tabdone)
+		{
+			callback (sess->gui->menu, me, NULL);
+			if (sess->gui->is_tab)
+				tabdone = TRUE;
+		}
+		list = list->next;
+	}
+}
+
+static void
+menu_update_cb (GtkWidget *menu, menu_entry *me, char *target)
+{
+	GtkWidget *item;
+
+	item = menu_find (menu, me->path, me->label);
+	if (item)
+	{
+		gtk_widget_set_sensitive (item, me->enable);
+		/* must do it without triggering the callback */
+		if (GTK_IS_CHECK_MENU_ITEM (item))
+			GTK_CHECK_MENU_ITEM (item)->active = me->state;
+	}
+}
+
+/* radio state changed via mouse click */
+static void
+menu_radio_cb (GtkCheckMenuItem *item, menu_entry *me)
+{
+	me->state = 0;
+	if (item->active)
+		me->state = 1;
+
+	/* update the state, incase this was changed via right-click. */
+	/* This will update all other windows and menu bars */
+	menu_foreach_gui (me, menu_update_cb);
+
+	if (me->state && me->cmd)
+		handle_command (current_sess, me->cmd, FALSE);
+}
+
+/* toggle state changed via mouse click */
+static void
+menu_toggle_cb (GtkCheckMenuItem *item, menu_entry *me)
+{
+	me->state = 0;
+	if (item->active)
+		me->state = 1;
+
+	/* update the state, incase this was changed via right-click. */
+	/* This will update all other windows and menu bars */
+	menu_foreach_gui (me, menu_update_cb);
+
+	if (me->state)
+		handle_command (current_sess, me->cmd, FALSE);
+	else
+		handle_command (current_sess, me->ucmd, FALSE);
+}
+
+static GtkWidget *
+menu_radio_item (char *label, GtkWidget *menu, void *callback, void *userdata,
+						int state, char *groupname)
+{
+	GtkWidget *item;
+	GtkMenuItem *parent;
+	GSList *grouplist = NULL;
+
+	parent = menu_find_item (menu, groupname);
+	if (parent)
+		grouplist = gtk_radio_menu_item_get_group ((GtkRadioMenuItem *)parent);
+
+	item = gtk_radio_menu_item_new_with_label (grouplist, label);
+	gtk_check_menu_item_set_active ((GtkCheckMenuItem*)item, state);
+	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+	g_signal_connect (G_OBJECT (item), "activate",
+							G_CALLBACK (callback), userdata);
+	gtk_widget_show (item);
+
+	return item;
+}
+
+static void
+menu_reorder (GtkMenu *menu, GtkWidget *item, int pos)
+{
+	if (pos == 0xffff)	/* outbound.c uses this default */
+		return;
+
+	if (pos < 0)	/* position offset from end/bottom */
+		gtk_menu_reorder_child (menu, item, (g_list_length (GTK_MENU_SHELL (menu)->children) + pos) - 1);
+	else
+		gtk_menu_reorder_child (menu, item, pos);
+}
+
+static GtkWidget *
+menu_add_radio (GtkWidget *menu, menu_entry *me)
+{
+	GtkWidget *item = NULL;
+	char *path = me->path + me->root_offset;
+
+	if (path[0] != 0)
+		menu = menu_find_path (menu, path);
+	if (menu)
+	{
+		item = menu_radio_item (me->label, menu, menu_radio_cb, me, me->state, me->group);
+		menu_reorder (GTK_MENU (menu), item, me->pos);
+	}
+	return item;
+}
+
+static GtkWidget *
+menu_add_toggle (GtkWidget *menu, menu_entry *me)
+{
+	GtkWidget *item = NULL;
+	char *path = me->path + me->root_offset;
+
+	if (path[0] != 0)
+		menu = menu_find_path (menu, path);
+	if (menu)
+	{
+		item = menu_toggle_item (me->label, menu, menu_toggle_cb, me, me->state);
+		menu_reorder (GTK_MENU (menu), item, me->pos);
+	}
+	return item;
+}
+
+static GtkWidget *
+menu_add_item (GtkWidget *menu, menu_entry *me, char *target)
+{
+	GtkWidget *item = NULL;
+	char *path = me->path + me->root_offset;
+
+	if (path[0] != 0)
+		menu = menu_find_path (menu, path);
+	if (menu)
+	{
+		item = menu_quick_item (me->cmd, me->label, menu, me->markup ? XCMENU_MARKUP|XCMENU_MNEMONIC : XCMENU_MNEMONIC, target, me->icon);
+		menu_reorder (GTK_MENU (menu), item, me->pos);
+	}
+	return item;
+}
+
+static GtkWidget *
+menu_add_sub (GtkWidget *menu, menu_entry *me)
+{
+	GtkWidget *item = NULL;
+	char *path = me->path + me->root_offset;
+	int pos;
+
+	if (path[0] != 0)
+		menu = menu_find_path (menu, path);
+	if (menu)
+	{
+		pos = me->pos;
+		if (pos < 0)	/* position offset from end/bottom */
+			pos = g_list_length (GTK_MENU_SHELL (menu)->children) + pos;
+		menu_quick_sub (me->label, menu, &item, me->markup ? XCMENU_MARKUP|XCMENU_MNEMONIC : XCMENU_MNEMONIC, pos);
+	}
+	return item;
+}
+
+static void
+menu_del_cb (GtkWidget *menu, menu_entry *me, char *target)
+{
+	GtkWidget *item = menu_find (menu, me->path + me->root_offset, me->label);
+	if (item)
+		gtk_widget_destroy (item);
+}
+
+static void
+menu_add_cb (GtkWidget *menu, menu_entry *me, char *target)
+{
+	GtkWidget *item;
+	GtkAccelGroup *accel_group;
+
+	if (me->group)	/* have a group name? Must be a radio item */
+		item = menu_add_radio (menu, me);
+	else if (me->ucmd)	/* have unselect-cmd? Must be a toggle item */
+		item = menu_add_toggle (menu, me);
+	else if (me->cmd || !me->label)	/* label=NULL for separators */
+		item = menu_add_item (menu, me, target);
+	else
+		item = menu_add_sub (menu, me);
+
+	if (item)
+	{
+		gtk_widget_set_sensitive (item, me->enable);
+		if (me->key)
+		{
+			accel_group = g_object_get_data (G_OBJECT (menu), "accel");
+			if (accel_group)	/* popup menus don't have them */
+				gtk_widget_add_accelerator (item, "activate", accel_group, me->key,
+													 me->modifier, GTK_ACCEL_VISIBLE);
+		}
+	}
+}
+
+char *
+fe_menu_add (menu_entry *me)
+{
+	char *text;
+
+	menu_foreach_gui (me, menu_add_cb);
+
+	if (!me->markup)
+		return NULL;
+
+	if (!pango_parse_markup (me->label, -1, 0, NULL, &text, NULL, NULL))
+		return NULL;
+
+	/* return the label with markup stripped */
+	return text;
+}
+
+void
+fe_menu_del (menu_entry *me)
+{
+	menu_foreach_gui (me, menu_del_cb);
+}
+
+void
+fe_menu_update (menu_entry *me)
+{
+	menu_foreach_gui (me, menu_update_cb);
+}
+
+/* used to add custom menus to the right-click menu */
+
+static void
+menu_add_plugin_mainmenu_items (GtkWidget *menu)
+{
+	GSList *list;
+	menu_entry *me;
+
+	list = menu_list;	/* outbound.c */
+	while (list)
+	{
+		me = list->data;
+		if (me->is_main)
+			menu_add_cb (menu, me, NULL);
+		list = list->next;
+	}
+}
+
+void
+menu_add_plugin_items (GtkWidget *menu, char *root, char *target)
+{
+	GSList *list;
+	menu_entry *me;
+
+	list = menu_list;	/* outbound.c */
+	while (list)
+	{
+		me = list->data;
+		if (!me->is_main && !strncmp (me->path, root + 1, root[0]))
+			menu_add_cb (menu, me, target);
+		list = list->next;
+	}
+}
+
+/* === END STUFF FOR /MENU === */
+
+GtkWidget *
+menu_create_main (void *accel_group, int bar, int away, int toplevel,
+						GtkWidget **menu_widgets)
+{
+	int i = 0;
+	GtkWidget *item;
+	GtkWidget *menu = 0;
+	GtkWidget *menu_item = 0;
+	GtkWidget *menu_bar;
+	GtkWidget *usermenu = 0;
+	GtkWidget *submenu = 0;
+	int close_mask = GDK_CONTROL_MASK;
+	int away_mask = GDK_MOD1_MASK;
+	char *key_theme = NULL;
+	GtkSettings *settings;
+	GSList *group = NULL;
+
+	if (bar)
+		menu_bar = gtk_menu_bar_new ();
+	else
+		menu_bar = gtk_menu_new ();
+
+	/* /MENU needs to know this later */
+	g_object_set_data (G_OBJECT (menu_bar), "accel", accel_group);
+
+#if GTK_CHECK_VERSION(2,4,0)
+	g_signal_connect (G_OBJECT (menu_bar), "can-activate-accel",
+							G_CALLBACK (menu_canacaccel), 0);
+#endif
+
+	/* set the initial state of toggles */
+	mymenu[MENUBAR_OFFSET].state = !prefs.hidemenu;
+	mymenu[MENUBAR_OFFSET+1].state = prefs.topicbar;
+	mymenu[MENUBAR_OFFSET+2].state = !prefs.hideuserlist;
+	mymenu[MENUBAR_OFFSET+3].state = prefs.userlistbuttons;
+	mymenu[MENUBAR_OFFSET+4].state = prefs.chanmodebuttons;
+
+	mymenu[AWAY_OFFSET].state = away;
+
+	switch (prefs.tab_layout)
+	{
+	case 0:
+		mymenu[TABS_OFFSET].state = 1;
+		mymenu[TABS_OFFSET+1].state = 0;
+		break;
+	default:
+		mymenu[TABS_OFFSET].state = 0;
+		mymenu[TABS_OFFSET+1].state = 1;
+	}
+
+	mymenu[METRE_OFFSET].state = 0;
+	mymenu[METRE_OFFSET+1].state = 0;
+	mymenu[METRE_OFFSET+2].state = 0;
+	mymenu[METRE_OFFSET+3].state = 0;
+	switch (prefs.lagometer)
+	{
+	case 0:
+		mymenu[METRE_OFFSET].state = 1;
+		break;
+	case 1:
+		mymenu[METRE_OFFSET+1].state = 1;
+		break;
+	case 2:
+		mymenu[METRE_OFFSET+2].state = 1;
+		break;
+	default:
+		mymenu[METRE_OFFSET+3].state = 1;
+	}
+
+	/* change Close binding to ctrl-shift-w when using emacs keys */
+	settings = gtk_widget_get_settings (menu_bar);
+	if (settings)
+	{
+		g_object_get (settings, "gtk-key-theme-name", &key_theme, NULL);
+		if (key_theme)
+		{
+			if (!strcasecmp (key_theme, "Emacs"))
+			{
+				close_mask = GDK_SHIFT_MASK | GDK_CONTROL_MASK;
+				mymenu[SEARCH_OFFSET].key = 0;
+			}
+			g_free (key_theme);
+		}
+	}
+
+	/* Away binding to ctrl-alt-a if the _Help menu conflicts (FR/PT/IT) */
+	{
+		char *help = _("_Help");
+		char *under = strchr (help, '_');
+		if (under && (under[1] == 'a' || under[1] == 'A'))
+			away_mask = GDK_MOD1_MASK | GDK_CONTROL_MASK;
+	}
+
+	if (!toplevel)
+	{
+		mymenu[DETACH_OFFSET].text = N_("_Detach");
+		mymenu[CLOSE_OFFSET].text = N_("_Close");
+	}
+	else
+	{
+		mymenu[DETACH_OFFSET].text = N_("_Attach");
+		mymenu[CLOSE_OFFSET].text = N_("_Close");
+	}
+
+	while (1)
+	{
+		item = NULL;
+		if (mymenu[i].id == MENU_ID_USERMENU && !prefs.gui_usermenu)
+		{
+			i++;
+			continue;
+		}
+
+		switch (mymenu[i].type)
+		{
+		case M_NEWMENU:
+			if (menu)
+				gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), menu);
+			item = menu = gtk_menu_new ();
+			if (mymenu[i].id == MENU_ID_USERMENU)
+				usermenu = menu;
+			menu_item = gtk_menu_item_new_with_mnemonic (_(mymenu[i].text));
+			/* record the English name for /menu */
+			g_object_set_data (G_OBJECT (menu_item), "name", mymenu[i].text);
+			gtk_menu_shell_append (GTK_MENU_SHELL (menu_bar), menu_item);
+			gtk_widget_show (menu_item);
+			break;
+
+		case M_MENUPIX:
+			item = create_icon_menu (_(mymenu[i].text), mymenu[i].image, FALSE);
+			goto normalitem;
+
+		case M_MENUSTOCK:
+			item = create_icon_menu (_(mymenu[i].text), mymenu[i].image, TRUE);
+			goto normalitem;
+
+		case M_MENUITEM:
+			item = gtk_menu_item_new_with_mnemonic (_(mymenu[i].text));
+normalitem:
+			if (mymenu[i].key != 0)
+				gtk_widget_add_accelerator (item, "activate", accel_group,
+										mymenu[i].key,
+										mymenu[i].key == GDK_F1 ? 0 :
+										mymenu[i].key == GDK_w ? close_mask :
+										GDK_CONTROL_MASK,
+										GTK_ACCEL_VISIBLE);
+			if (mymenu[i].callback)
+				g_signal_connect (G_OBJECT (item), "activate",
+										G_CALLBACK (mymenu[i].callback), 0);
+			if (submenu)
+				gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
+			else
+				gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+			gtk_widget_show (item);
+			break;
+
+		case M_MENUTOG:
+			item = gtk_check_menu_item_new_with_mnemonic (_(mymenu[i].text));
+togitem:
+			/* must avoid callback for Radio buttons */
+			GTK_CHECK_MENU_ITEM (item)->active = mymenu[i].state;
+			/*gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
+													 mymenu[i].state);*/
+			if (mymenu[i].key != 0)
+				gtk_widget_add_accelerator (item, "activate", accel_group,
+									mymenu[i].key, mymenu[i].id == MENU_ID_AWAY ?
+									away_mask : GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
+			if (mymenu[i].callback)
+				g_signal_connect (G_OBJECT (item), "toggled",
+										G_CALLBACK (mymenu[i].callback), 0);
+			if (submenu)
+				gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
+			else
+				gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+			gtk_widget_show (item);
+			gtk_widget_set_sensitive (item, mymenu[i].sensitive);
+			break;
+
+		case M_MENURADIO:
+			item = gtk_radio_menu_item_new_with_mnemonic (group, _(mymenu[i].text));
+			group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
+			goto togitem;
+
+		case M_SEP:
+			item = gtk_menu_item_new ();
+			gtk_widget_set_sensitive (item, FALSE);
+			gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+			gtk_widget_show (item);
+			break;
+
+		case M_MENUSUB:
+			group = NULL;
+			submenu = gtk_menu_new ();
+			item = create_icon_menu (_(mymenu[i].text), mymenu[i].image, TRUE);
+			/* record the English name for /menu */
+			g_object_set_data (G_OBJECT (item), "name", mymenu[i].text);
+			gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
+			gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+			gtk_widget_show (item);
+			break;
+
+		/*case M_END:*/ default:
+			if (!submenu)
+			{
+				if (menu)
+				{
+					gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), menu);
+					menu_add_plugin_mainmenu_items (menu_bar);
+				}
+				if (usermenu)
+					usermenu_create (usermenu);
+				return (menu_bar);
+			}
+			submenu = NULL;
+		}
+
+		/* record this GtkWidget * so it's state might be changed later */
+		if (mymenu[i].id != 0 && menu_widgets)
+			/* this ends up in sess->gui->menu_item[MENU_ID_XXX] */
+			menu_widgets[mymenu[i].id] = item;
+
+		i++;
+	}
+}
diff --git a/src/fe-gtk/menu.h b/src/fe-gtk/menu.h
new file mode 100644
index 00000000..7fef79cd
--- /dev/null
+++ b/src/fe-gtk/menu.h
@@ -0,0 +1,41 @@
+GtkWidget *menu_create_main (void *accel_group, int bar, int away, int toplevel, GtkWidget **menu_widgets);
+void menu_urlmenu (GdkEventButton * event, char *url);
+void menu_chanmenu (session *sess, GdkEventButton * event, char *chan);
+void menu_addfavoritemenu (server *serv, GtkWidget *menu, char *channel);
+void menu_nickmenu (session *sess, GdkEventButton * event, char *nick, int num_sel);
+void menu_middlemenu (session *sess, GdkEventButton *event);
+void userlist_button_cb (GtkWidget * button, char *cmd);
+void nick_command_parse (session *sess, char *cmd, char *nick, char *allnick);
+void usermenu_update (void);
+GtkWidget *menu_toggle_item (char *label, GtkWidget *menu, void *callback, void *userdata, int state);
+GtkWidget *menu_quick_item (char *cmd, char *label, GtkWidget * menu, int flags, gpointer userdata, char *icon);
+GtkWidget *menu_quick_sub (char *name, GtkWidget *menu, GtkWidget **sub_item_ret, int flags, int pos);
+GtkWidget *create_icon_menu (char *labeltext, void *stock_name, int is_stock);
+void menu_create (GtkWidget *menu, GSList *list, char *target, int check_path);
+void menu_bar_toggle (void);
+void menu_add_plugin_items (GtkWidget *menu, char *root, char *target);
+void menu_change_layout (void);
+
+/* for menu_quick functions */
+#define XCMENU_DOLIST 1
+#define XCMENU_SHADED 1
+#define XCMENU_MARKUP 2
+#define XCMENU_MNEMONIC 4
+
+/* menu items we keep a GtkWidget* for (to change their state) */
+#define MENU_ID_AWAY 1
+#define MENU_ID_MENUBAR 2
+#define MENU_ID_TOPICBAR 3
+#define MENU_ID_USERLIST 4
+#define MENU_ID_ULBUTTONS 5
+#define MENU_ID_MODEBUTTONS 6
+#define MENU_ID_LAYOUT_TABS 7
+#define MENU_ID_LAYOUT_TREE 8
+#define MENU_ID_DISCONNECT 9
+#define MENU_ID_RECONNECT 10
+#define MENU_ID_JOIN 11
+#define MENU_ID_USERMENU 12
+
+#if (MENU_ID_NUM < MENU_ID_USERMENU)
+#error MENU_ID_NUM is set wrong
+#endif
diff --git a/src/fe-gtk/mmx_cmod.S b/src/fe-gtk/mmx_cmod.S
new file mode 100644
index 00000000..12e866de
--- /dev/null
+++ b/src/fe-gtk/mmx_cmod.S
@@ -0,0 +1,530 @@
+/*
+ * Copyright (C) 1997-2001, Michael Jennings
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies of the Software, its documentation and marketing & publicity
+ * materials, and acknowledgment shall be given in the documentation, materials
+ * and software packages that this Software was used.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/* MMX routines for tinting XImages written by Willem Monsuwe <willem@stack.nl> */
+
+/* Function calling conventions:
+ *   shade_ximage_xx(void *data, int bpl, int w, int h, int rm, int gm, int bm);
+ */
+
+#define data	8(%ebp)
+#define bpl	12(%ebp)
+#define w	16(%ebp)
+#define h	20(%ebp)
+#define rm	24(%ebp)
+#define gm	28(%ebp)
+#define bm	32(%ebp)
+
+#ifdef UNDERSCORE_SYMBOLS /* need this to link with msvc */
+#define SHADE_XIMAGE_15 _shade_ximage_15_mmx
+#define SHADE_XIMAGE_16 _shade_ximage_16_mmx
+#define SHADE_XIMAGE_32 _shade_ximage_32_mmx
+#define HAVE_MMX _have_mmx
+#else
+#define SHADE_XIMAGE_15 shade_ximage_15_mmx
+#define SHADE_XIMAGE_16 shade_ximage_16_mmx
+#define SHADE_XIMAGE_32 shade_ximage_32_mmx
+#define HAVE_MMX have_mmx
+#endif
+
+.globl SHADE_XIMAGE_15
+.globl SHADE_XIMAGE_16
+.globl SHADE_XIMAGE_32
+.globl HAVE_MMX
+
+.bss
+.text
+.align 8
+
+#define ENTER                   \
+        pushl %ebp              ;\
+        movl %esp, %ebp         ;\
+        pushl %ebx              ;\
+        pushl %ecx              ;\
+        pushl %edx              ;\
+        pushl %edi              ;\
+        pushl %esi              ;\
+        movl data, %esi         ;\
+        movl w, %ebx            ;\
+        movl h, %edx
+
+#define LEAVE                   \
+4:                              ;\
+        emms                    ;\
+        popl %esi               ;\
+        popl %edi               ;\
+        popl %edx               ;\
+        popl %ecx               ;\
+        popl %ebx               ;\
+        movl %ebp, %esp         ;\
+        popl %ebp               ;\
+        ret
+
+
+SHADE_XIMAGE_15:
+        ENTER
+
+        leal -6(%esi, %ebx, 2), %esi
+        negl %ebx
+        jz 5f
+
+        /* Setup multipliers */
+        movd rm, %mm5
+        movd gm, %mm6
+        movd bm, %mm7
+        punpcklwd %mm5, %mm5    /* 00 00 00 00 rm rm rm rm */
+        punpcklwd %mm6, %mm6    /* 00 00 00 00 gm gm gm gm */
+        punpcklwd %mm7, %mm7    /* 00 00 00 00 bm bm bm bm */
+        punpckldq %mm5, %mm5    /* rm rm rm rm rm rm rm rm */
+        punpckldq %mm6, %mm6    /* gm gm gm gm gm gm gm gm */
+        punpckldq %mm7, %mm7    /* bm bm bm bm bm bm bm bm */
+
+        cmpl $256, rm
+        jg shade_ximage_15_mmx_saturate
+        cmpl $256, gm
+        jg shade_ximage_15_mmx_saturate
+        cmpl $256, bm
+        jg shade_ximage_15_mmx_saturate
+
+1:      movl %ebx, %ecx
+        addl $3, %ecx
+        jns 3f
+2:
+        movq (%esi, %ecx, 2), %mm0
+
+        movq %mm0, %mm1         /* rg gb */
+        movq %mm0, %mm2         /* rg gb */
+        psrlw $5, %mm1          /* 0r rg */
+        psrlw $10, %mm0         /* 00 0r */
+        psllw $11, %mm2         /* b0 00 */
+        psllw $11, %mm1         /* g0 00 */
+        psllw $8, %mm0          /* 0r 00 */
+        psrlw $3, %mm1          /* 0g 00 */
+        psrlw $3, %mm2          /* 0b 00 */
+
+        pmulhw %mm5, %mm0       /* 00 0r */
+        pmulhw %mm6, %mm1       /* 00 0g */
+        pmulhw %mm7, %mm2       /* 00 0b */
+
+        psllw $10, %mm0         /* r0 00 */
+        psllw $5, %mm1          /* 0g g0 */
+        por %mm2, %mm0          /* r0 0b */
+        por %mm1, %mm0          /* rg gb */
+        
+        movq %mm0, (%esi, %ecx, 2)
+
+        addl $4, %ecx
+        js 2b
+        jmp 4f
+3:
+        movw (%esi, %ecx, 2), %ax
+        movd %eax, %mm0
+
+        movq %mm0, %mm1         /* rg gb */
+        movq %mm0, %mm2         /* rg gb */
+        psrlw $5, %mm1          /* 0r rg */
+        psrlw $10, %mm0         /* 00 0r */
+        psllw $11, %mm2         /* b0 00 */
+        psllw $11, %mm1         /* g0 00 */
+        psllw $8, %mm0          /* 0r 00 */
+        psrlw $3, %mm1          /* 0g 00 */
+        psrlw $3, %mm2          /* 0b 00 */
+
+        pmulhw %mm5, %mm0       /* 00 0r */
+        pmulhw %mm6, %mm1       /* 00 0g */
+        pmulhw %mm7, %mm2       /* 00 0b */
+
+        psllw $10, %mm0         /* r0 00 */
+        psllw $5, %mm1          /* 0g g0 */
+        por %mm2, %mm0          /* r0 0b */
+        por %mm1, %mm0          /* rg gb */
+
+        movd %mm0, %eax
+        movw %ax, (%esi, %ecx, 2)
+
+        incl %ecx
+4:
+        cmpl $2, %ecx
+        jng 3b
+
+        addl bpl, %esi
+        decl %edx
+        jnz 1b
+5:
+        LEAVE
+
+
+shade_ximage_15_mmx_saturate:
+
+        pcmpeqw %mm3, %mm3
+        psllw $5, %mm3          /* ff e0 ff e0 ff e0 ff e0 */
+
+1:      movl %ebx, %ecx
+        addl $3, %ecx
+        jns 3f
+2:
+        movq (%esi, %ecx, 2), %mm0
+
+        movq %mm0, %mm1         /* rg gb */
+        movq %mm0, %mm2         /* rg gb */
+        psrlw $5, %mm1          /* 0r rg */
+        psrlw $10, %mm0         /* 00 0r */
+        psllw $11, %mm2         /* b0 00 */
+        psllw $11, %mm1         /* g0 00 */
+        psllw $8, %mm0          /* 0r 00 */
+        psrlw $3, %mm1          /* 0g 00 */
+        psrlw $3, %mm2          /* 0b 00 */
+
+        pmulhw %mm5, %mm0       /* xx xr */
+        pmulhw %mm6, %mm1       /* xx xg */
+        pmulhw %mm7, %mm2       /* xx xb */
+
+        /* Saturate upper */
+        paddusw %mm3, %mm0      /* ff er */
+        paddusw %mm3, %mm1      /* ff eg */
+        paddusw %mm3, %mm2      /* ff eb */
+
+        psubw %mm3, %mm0        /* 00 0r */
+        psubw %mm3, %mm1        /* 00 0g */
+        psubw %mm3, %mm2        /* 00 0b */
+        
+        psllw $10, %mm0         /* r0 00 */
+        psllw $5, %mm1          /* 0g g0 */
+        por %mm2, %mm0          /* r0 0b */
+        por %mm1, %mm0          /* rg gb */
+
+        movq %mm0, (%esi, %ecx, 2)
+
+        addl $4, %ecx
+        js 2b
+        jmp 4f
+3:
+        movw (%esi, %ecx, 2), %ax
+        movd %eax, %mm0
+
+        movq %mm0, %mm1         /* rg gb */
+        movq %mm0, %mm2         /* rg gb */
+        psrlw $5, %mm1          /* 0r rg */
+        psrlw $10, %mm0         /* 00 0r */
+        psllw $11, %mm2         /* b0 00 */
+        psllw $11, %mm1         /* g0 00 */
+        psllw $8, %mm0          /* 0r 00 */
+        psrlw $3, %mm1          /* 0g 00 */
+        psrlw $3, %mm2          /* 0b 00 */
+
+        pmulhw %mm5, %mm0       /* xx xr */
+        pmulhw %mm6, %mm1       /* xx xg */
+        pmulhw %mm7, %mm2       /* xx xb */
+
+        /* Saturate upper */
+        paddusw %mm3, %mm0      /* ff er */
+        paddusw %mm3, %mm1      /* ff eg */
+        paddusw %mm3, %mm2      /* ff eb */
+
+        psubw %mm3, %mm0        /* 00 0r */
+        psubw %mm3, %mm1        /* 00 0g */
+        psubw %mm3, %mm2        /* 00 0b */
+        
+        psllw $10, %mm0         /* r0 00 */
+        psllw $5, %mm1          /* 0g g0 */
+        por %mm2, %mm0          /* r0 0b */
+        por %mm1, %mm0          /* rg gb */
+
+        movd %mm0, %eax
+        movw %ax, (%esi, %ecx, 2)
+
+        incl %ecx
+4:
+        cmpl $2, %ecx
+        jng 3b
+
+        addl bpl, %esi
+        decl %edx
+        jnz 1b
+5:
+        LEAVE
+
+
+SHADE_XIMAGE_16:
+        ENTER
+
+        leal -6(%esi, %ebx, 2), %esi
+        negl %ebx
+        jz 5f
+
+        /* Setup multipliers */
+        movd rm, %mm5
+        movd gm, %mm6
+        movd bm, %mm7
+        punpcklwd %mm5, %mm5    /* 00 00 00 00 rm rm rm rm */
+        punpcklwd %mm6, %mm6    /* 00 00 00 00 gm gm gm gm */
+        punpcklwd %mm7, %mm7    /* 00 00 00 00 bm bm bm bm */
+        punpckldq %mm5, %mm5    /* rm rm rm rm rm rm rm rm */
+        punpckldq %mm6, %mm6    /* gm gm gm gm gm gm gm gm */
+        punpckldq %mm7, %mm7    /* bm bm bm bm bm bm bm bm */
+
+        cmpl $256, rm
+        jg shade_ximage_16_mmx_saturate
+        cmpl $256, gm
+        jg shade_ximage_16_mmx_saturate
+        cmpl $256, bm
+        jg shade_ximage_16_mmx_saturate
+
+1:      movl %ebx, %ecx
+        addl $3, %ecx
+        jns 3f
+2:
+        movq (%esi, %ecx, 2), %mm0
+
+        movq %mm0, %mm1         /* rg gb */
+        movq %mm0, %mm2         /* rg gb */
+        psrlw $5, %mm1          /* 0r rg */
+        psrlw $11, %mm0         /* 00 0r */
+        psllw $11, %mm2         /* b0 00 */
+        psllw $10, %mm1         /* g0 00 */
+        psllw $8, %mm0          /* 0r 00 */
+        psrlw $2, %mm1          /* 0g 00 */
+        psrlw $3, %mm2          /* 0b 00 */
+
+        pmulhw %mm5, %mm0       /* 00 0r */
+        pmulhw %mm6, %mm1       /* 00 0g */
+        pmulhw %mm7, %mm2       /* 00 0b */
+
+        psllw $11, %mm0         /* r0 00 */
+        psllw $5, %mm1          /* 0g g0 */
+        por %mm2, %mm0          /* r0 0b */
+        por %mm1, %mm0          /* rg gb */
+        
+        movq %mm0, (%esi, %ecx, 2)
+
+        addl $4, %ecx
+        js 2b
+	jmp 4f
+3:
+        movw (%esi, %ecx, 2), %ax
+        movd %eax, %mm0
+
+        movq %mm0, %mm1         /* rg gb */
+        movq %mm0, %mm2         /* rg gb */
+        psrlw $5, %mm1          /* 0r rg */
+        psrlw $11, %mm0         /* 00 0r */
+        psllw $11, %mm2         /* b0 00 */
+        psllw $10, %mm1         /* g0 00 */
+        psllw $8, %mm0          /* 0r 00 */
+        psrlw $2, %mm1          /* 0g 00 */
+        psrlw $3, %mm2          /* 0b 00 */
+
+        pmulhw %mm5, %mm0       /* 00 0r */
+        pmulhw %mm6, %mm1       /* 00 0g */
+        pmulhw %mm7, %mm2       /* 00 0b */
+
+        psllw $11, %mm0         /* r0 00 */
+        psllw $5, %mm1          /* 0g g0 */
+        por %mm2, %mm0          /* r0 0b */
+        por %mm1, %mm0          /* rg gb */
+
+        movd %mm0, %eax
+        movw %ax, (%esi, %ecx, 2)
+
+        incl %ecx
+4:
+        cmpl $2, %ecx
+        jng 3b
+
+        addl bpl, %esi
+        decl %edx
+        jnz 1b
+5:
+        LEAVE
+
+
+shade_ximage_16_mmx_saturate:
+
+        pcmpeqw %mm3, %mm3
+        movq %mm3, %mm4
+        psllw $5, %mm3          /* ff e0 ff e0 ff e0 ff e0 */
+        psllw $6, %mm4          /* ff c0 ff c0 ff c0 ff c0 */
+
+1:      movl %ebx, %ecx
+        addl $3, %ecx
+        jns 3f
+2:
+        movq (%esi, %ecx, 2), %mm0
+
+        movq %mm0, %mm1         /* rg gb */
+        movq %mm0, %mm2         /* rg gb */
+        psrlw $5, %mm1          /* 0r rg */
+        psrlw $11, %mm0         /* 00 0r */
+        psllw $11, %mm2         /* b0 00 */
+        psllw $10, %mm1         /* g0 00 */
+        psllw $8, %mm0          /* 0r 00 */
+        psrlw $2, %mm1          /* 0g 00 */
+        psrlw $3, %mm2          /* 0b 00 */
+
+        pmulhw %mm5, %mm0       /* xx xr */
+        pmulhw %mm6, %mm1       /* xx xg */
+        pmulhw %mm7, %mm2       /* xx xb */
+
+        /* Saturate upper */
+        paddusw %mm3, %mm0      /* ff er */
+        paddusw %mm4, %mm1      /* ff cg */
+        paddusw %mm3, %mm2      /* ff eb */
+
+        psubw %mm4, %mm1        /* 00 0g */
+        psubw %mm3, %mm2        /* 00 0b */
+        
+        psllw $11, %mm0         /* r0 00 */
+        psllw $5, %mm1          /* 0g g0 */
+        por %mm2, %mm0          /* r0 0b */
+        por %mm1, %mm0          /* rg gb */
+
+        movq %mm0, (%esi, %ecx, 2)
+
+        addl $4, %ecx
+        js 2b
+        jmp 4f
+3:
+        movw (%esi, %ecx, 2), %ax
+        movd %eax, %mm0
+
+        movq %mm0, %mm1         /* rg gb */
+        movq %mm0, %mm2         /* rg gb */
+        psrlw $5, %mm1          /* 0r rg */
+        psrlw $11, %mm0         /* 00 0r */
+        psllw $11, %mm2         /* b0 00 */
+        psllw $10, %mm1         /* g0 00 */
+        psllw $8, %mm0          /* 0r 00 */
+        psrlw $2, %mm1          /* 0g 00 */
+        psrlw $3, %mm2          /* 0b 00 */
+
+        pmulhw %mm5, %mm0       /* xx xr */
+        pmulhw %mm6, %mm1       /* xx xg */
+        pmulhw %mm7, %mm2       /* xx xb */
+
+        /* Saturate upper */
+        paddusw %mm3, %mm0      /* ff er */
+        paddusw %mm4, %mm1      /* ff cg */
+        paddusw %mm3, %mm2      /* ff eb */
+
+        psubw %mm4, %mm1        /* 00 0g */
+        psubw %mm3, %mm2        /* 00 0b */
+        
+        psllw $11, %mm0         /* r0 00 */
+        psllw $5, %mm1          /* 0g g0 */
+        por %mm2, %mm0          /* r0 0b */
+        por %mm1, %mm0          /* rg gb */
+
+        movd %mm0, %eax
+        movw %ax, (%esi, %ecx, 2)
+
+        incl %ecx
+4:
+        cmpl $2, %ecx
+        jng 3b
+
+        addl bpl, %esi
+        decl %edx
+        jnz 1b
+5:
+        LEAVE
+
+
+SHADE_XIMAGE_32:
+        ENTER
+
+        leal (%esi, %ebx, 4), %esi
+        negl %ebx
+        jz 3f
+
+        movd rm, %mm4
+        movd gm, %mm5
+        movd bm, %mm6
+        psllq $32, %mm4
+        psllq $16, %mm5
+        por %mm6, %mm4
+        por %mm5, %mm4
+
+        pcmpeqw %mm6, %mm6
+        psllw $15, %mm6                 /* 80 00 80 00 80 00 80 00 */
+        movq %mm6, %mm5
+        pmulhw %mm4, %mm5               /* Get correction factor */
+1:
+        movl %ebx, %ecx
+2:
+        movd (%esi, %ecx, 4), %mm1      /* 00 rr gg bb */
+        pxor %mm0, %mm0
+        punpcklbw %mm1, %mm0            /* 00 00 rr 00 gg 00 bb 00 */
+        pxor %mm6, %mm0                 /* Flip sign */
+
+        pmulhw %mm4, %mm0               /* 00 00 xx rr xx gg xx bb */
+        psubw %mm5, %mm0                /* Correct range */
+        packuswb %mm0, %mm0             /* 00 rr gg bb 00 rr gg bb */
+
+        movd %mm0, (%esi, %ecx, 4)
+
+        incl %ecx
+        jnz 2b
+
+        addl bpl, %esi
+        decl %edx
+        jnz 1b
+3:
+        LEAVE
+
+
+HAVE_MMX:
+	push	%ebx
+/* Check if bit 21 in flags word is writeable */
+	pushfl	
+	popl	%eax
+	movl	%eax,%ebx
+	xorl	$0x00200000, %eax
+	pushl	%eax
+	popfl
+	pushfl
+	popl	%eax
+
+	cmpl	%eax, %ebx
+	je	8f
+
+/* OK, we have CPUID */
+
+	movl	$1, %eax
+	cpuid
+	
+	test	$0x00800000, %edx
+	jz	8f
+
+	movl	$1, %eax	/* success, have mmx */
+	popl	%ebx
+	ret
+
+8:
+	xorl	%eax,%eax	/* failed, no mmx */
+	popl	%ebx
+	ret
+
+#if defined(__GNUC__) && !defined(_WIN32)
+.section .note.GNU-stack, "", @progbits
+.previous
+#endif
diff --git a/src/fe-gtk/mmx_cmod.h b/src/fe-gtk/mmx_cmod.h
new file mode 100644
index 00000000..52d07102
--- /dev/null
+++ b/src/fe-gtk/mmx_cmod.h
@@ -0,0 +1,4 @@
+void shade_ximage_15_mmx(void *data, int bpl, int w, int h, int rm, int gm, int bm);
+void shade_ximage_16_mmx(void *data, int bpl, int w, int h, int rm, int gm, int bm);
+void shade_ximage_32_mmx(void *data, int bpl, int w, int h, int rm, int gm, int bm);
+int have_mmx (void);
diff --git a/src/fe-gtk/notifygui.c b/src/fe-gtk/notifygui.c
new file mode 100644
index 00000000..5acb683a
--- /dev/null
+++ b/src/fe-gtk/notifygui.c
@@ -0,0 +1,442 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <time.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkscrolledwindow.h>
+
+#include <gtk/gtklabel.h>
+#include <gtk/gtkliststore.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkmessagedialog.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtkcellrenderertext.h>
+
+#include "../common/xchat.h"
+#include "../common/notify.h"
+#include "../common/cfgfiles.h"
+#include "../common/fe.h"
+#include "../common/server.h"
+#include "../common/util.h"
+#include "../common/userlist.h"
+#include "gtkutil.h"
+#include "maingui.h"
+#include "palette.h"
+#include "notifygui.h"
+
+
+/* model for the notify treeview */
+enum
+{
+	USER_COLUMN,
+	STATUS_COLUMN,
+	SERVER_COLUMN,
+	SEEN_COLUMN,
+	COLOUR_COLUMN,
+	NPS_COLUMN, 	/* struct notify_per_server * */
+	N_COLUMNS
+};
+
+
+static GtkWidget *notify_window = 0;
+static GtkWidget *notify_button_opendialog;
+static GtkWidget *notify_button_remove;
+
+
+static void
+notify_closegui (void)
+{
+	notify_window = 0;
+}
+
+/* Need this to be able to set the foreground colour property of a row
+ * from a GdkColor * in the model  -Vince
+ */
+static void
+notify_treecell_property_mapper (GtkTreeViewColumn *col, GtkCellRenderer *cell,
+                                 GtkTreeModel *model, GtkTreeIter *iter,
+                                 gpointer data)
+{
+	gchar *text;
+	GdkColor *colour;
+	int model_column = GPOINTER_TO_INT (data);
+
+	gtk_tree_model_get (GTK_TREE_MODEL (model), iter, 
+	                    COLOUR_COLUMN, &colour,
+	                    model_column, &text, -1);
+	g_object_set (G_OBJECT (cell), "text", text, NULL);
+	g_object_set (G_OBJECT (cell), "foreground-gdk", colour, NULL);
+	g_free (text);
+}
+
+static void
+notify_row_cb (GtkTreeSelection *sel, GtkTreeView *view)
+{
+	GtkTreeIter iter;
+	struct notify_per_server *servnot;
+
+	if (gtkutil_treeview_get_selected (view, &iter, NPS_COLUMN, &servnot, -1))
+	{
+		gtk_widget_set_sensitive (notify_button_opendialog, servnot ? servnot->ison : 0);
+		gtk_widget_set_sensitive (notify_button_remove, TRUE);
+		return;
+	}
+
+	gtk_widget_set_sensitive (notify_button_opendialog, FALSE);
+	gtk_widget_set_sensitive (notify_button_remove, FALSE);
+}
+
+static GtkWidget *
+notify_treeview_new (GtkWidget *box)
+{
+	GtkListStore *store;
+	GtkWidget *view;
+	GtkTreeViewColumn *col;
+	int col_id;
+
+	store = gtk_list_store_new (N_COLUMNS,
+	                            G_TYPE_STRING,
+	                            G_TYPE_STRING,
+	                            G_TYPE_STRING,
+	                            G_TYPE_STRING,
+	                            G_TYPE_POINTER,	/* can't specify colour! */
+										 G_TYPE_POINTER
+	                           );
+	g_return_val_if_fail (store != NULL, NULL);
+
+	view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store),
+	                             notify_treecell_property_mapper,
+	                             USER_COLUMN, _("Name"),
+	                             STATUS_COLUMN, _("Status"),
+	                             SERVER_COLUMN, _("Network"),
+	                             SEEN_COLUMN, _("Last Seen"), -1);
+	gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), 0), TRUE);
+
+	for (col_id=0; (col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), col_id));
+	     col_id++)
+			gtk_tree_view_column_set_alignment (col, 0.5);
+
+	g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (view))),
+							"changed", G_CALLBACK (notify_row_cb), view);
+
+	gtk_widget_show (view);
+	return view;
+}
+
+void
+notify_gui_update (void)
+{
+	struct notify *notify;
+	struct notify_per_server *servnot;
+	GSList *list = notify_list;
+	GSList *slist;
+	gchar *name, *status, *server, *seen;
+	int online, servcount;
+	time_t lastseen;
+	char agobuf[128];
+
+	GtkListStore *store;
+	GtkTreeView *view;
+	GtkTreeIter iter;
+	gboolean valid;	/* true if we don't need to append a new tree row */
+
+	if (!notify_window)
+		return;
+
+	view = g_object_get_data (G_OBJECT (notify_window), "view");
+	store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
+	valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter);
+
+	while (list)
+	{
+		notify = (struct notify *) list->data;
+		name = notify->name;
+		status = _("Offline");
+		server = "";
+
+		online = FALSE;
+		lastseen = 0;
+		/* First see if they're online on any servers */
+		slist = notify->server_list;
+		while (slist)
+		{
+			servnot = (struct notify_per_server *) slist->data;
+			if (servnot->ison)
+				online = TRUE;
+			if (servnot->lastseen > lastseen)
+				lastseen = servnot->lastseen;
+			slist = slist->next;
+		}
+
+		if (!online)				  /* Offline on all servers */
+		{
+			if (!lastseen)
+				seen = _("Never");
+			else
+			{
+				snprintf (agobuf, sizeof (agobuf), _("%d minutes ago"), (int)(time (0) - lastseen) / 60);
+				seen = agobuf;
+			}
+			if (!valid)	/* create new tree row if required */
+				gtk_list_store_append (store, &iter);
+			gtk_list_store_set (store, &iter, 0, name, 1, status,
+			                    2, server, 3, seen, 4, &colors[4], 5, NULL, -1);
+			if (valid)
+				valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter);
+
+		} else
+		{
+			/* Online - add one line per server */
+			servcount = 0;
+			slist = notify->server_list;
+			status = _("Online");
+			while (slist)
+			{
+				servnot = (struct notify_per_server *) slist->data;
+				if (servnot->ison)
+				{
+					if (servcount > 0)
+						name = "";
+					server = server_get_network (servnot->server, TRUE);
+
+					snprintf (agobuf, sizeof (agobuf), _("%d minutes ago"), (int)(time (0) - lastseen) / 60);
+					seen = agobuf;
+
+					if (!valid)	/* create new tree row if required */
+						gtk_list_store_append (store, &iter);
+					gtk_list_store_set (store, &iter, 0, name, 1, status,
+					                    2, server, 3, seen, 4, &colors[3], 5, servnot, -1);
+					if (valid)
+						valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter);
+
+					servcount++;
+				}
+				slist = slist->next;
+			}
+		}
+		
+		list = list->next;
+	}
+
+	while (valid)
+	{
+		GtkTreeIter old = iter;
+		/* get next iter now because removing invalidates old one */
+		valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store),
+                                      &iter);
+		gtk_list_store_remove (store, &old);
+	}
+}
+
+static void
+notify_opendialog_clicked (GtkWidget * igad)
+{
+	GtkTreeView *view;
+	GtkTreeIter iter;
+	struct notify_per_server *servnot;
+
+	view = g_object_get_data (G_OBJECT (notify_window), "view");
+	if (gtkutil_treeview_get_selected (view, &iter, NPS_COLUMN, &servnot, -1))
+	{
+		if (servnot)
+			open_query (servnot->server, servnot->notify->name, TRUE);
+	}
+}
+
+static void
+notify_remove_clicked (GtkWidget * igad)
+{
+	GtkTreeView *view;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	GtkTreePath *path = NULL;
+	gboolean found = FALSE;
+	char *name;
+
+	view = g_object_get_data (G_OBJECT (notify_window), "view");
+	if (gtkutil_treeview_get_selected (view, &iter, USER_COLUMN, &name, -1))
+	{
+		model = gtk_tree_view_get_model (view);
+		found = (*name != 0);
+		while (!found)	/* the real nick is some previous node */
+		{
+			g_free (name); /* it's useless to us */
+			if (!path)
+				path = gtk_tree_model_get_path (model, &iter);
+			if (!gtk_tree_path_prev (path))	/* arrgh! no previous node! */
+			{
+				g_warning ("notify list state is invalid\n");
+				break;
+			}
+			if (!gtk_tree_model_get_iter (model, &iter, path))
+				break;
+			gtk_tree_model_get (model, &iter, USER_COLUMN, &name, -1);
+			found = (*name != 0);
+		}
+		if (path)
+			gtk_tree_path_free (path);
+		if (!found)
+			return;
+
+		/* ok, now we can remove it */
+		gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+		notify_deluser (name);
+		g_free (name);
+	}
+}
+
+static void
+notifygui_add_cb (GtkDialog *dialog, gint response, gpointer entry)
+{
+	char *networks;
+	char *text;
+
+	text = GTK_ENTRY (entry)->text;
+	if (text[0] && response == GTK_RESPONSE_ACCEPT)
+	{
+		networks = GTK_ENTRY (g_object_get_data (G_OBJECT (entry), "net"))->text;
+		if (strcasecmp (networks, "ALL") == 0 || networks[0] == 0)
+			notify_adduser (text, NULL);
+		else
+			notify_adduser (text, networks);
+	}
+
+	gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+notifygui_add_enter (GtkWidget *entry, GtkWidget *dialog)
+{
+	gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
+}
+
+void
+fe_notify_ask (char *nick, char *networks)
+{
+	GtkWidget *dialog;
+	GtkWidget *entry;
+	GtkWidget *label;
+	GtkWidget *wid;
+	GtkWidget *table;
+	char *msg = _("Enter nickname to add:");
+	char buf[256];
+
+	dialog = gtk_dialog_new_with_buttons (msg, NULL, 0,
+										GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+										GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+										NULL);
+	if (parent_window)
+		gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window));
+	gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
+
+	table = gtk_table_new (2, 3, FALSE);
+	gtk_container_set_border_width (GTK_CONTAINER (table), 12);
+	gtk_table_set_row_spacings (GTK_TABLE (table), 3);
+	gtk_table_set_col_spacings (GTK_TABLE (table), 8);
+	gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), table);
+
+	label = gtk_label_new (msg);
+	gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, 0, 1);
+
+	entry = gtk_entry_new ();
+	gtk_entry_set_text (GTK_ENTRY (entry), nick);
+	g_signal_connect (G_OBJECT (entry), "activate",
+						 	G_CALLBACK (notifygui_add_enter), dialog);
+	gtk_table_attach_defaults (GTK_TABLE (table), entry, 1, 2, 0, 1);
+
+	g_signal_connect (G_OBJECT (dialog), "response",
+						   G_CALLBACK (notifygui_add_cb), entry);
+
+	label = gtk_label_new (_("Notify on these networks:"));
+	gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, 2, 3);
+
+	wid = gtk_entry_new ();
+	g_object_set_data (G_OBJECT (entry), "net", wid);
+	g_signal_connect (G_OBJECT (wid), "activate",
+						 	G_CALLBACK (notifygui_add_enter), dialog);
+	gtk_entry_set_text (GTK_ENTRY (wid), networks ? networks : "ALL");
+	gtk_table_attach_defaults (GTK_TABLE (table), wid, 1, 2, 2, 3);
+
+	label = gtk_label_new (NULL);
+	snprintf (buf, sizeof (buf), "<i><span size=\"smaller\">%s</span></i>", _("Comma separated list of networks is accepted."));
+	gtk_label_set_markup (GTK_LABEL (label), buf);
+	gtk_table_attach_defaults (GTK_TABLE (table), label, 1, 2, 3, 4);
+
+	gtk_widget_show_all (dialog);
+}
+
+static void
+notify_add_clicked (GtkWidget * igad)
+{
+	fe_notify_ask ("", NULL);
+}
+
+void
+notify_opengui (void)
+{
+	GtkWidget *vbox, *bbox;
+	GtkWidget *view;
+
+	if (notify_window)
+	{
+		mg_bring_tofront (notify_window);
+		return;
+	}
+
+	notify_window =
+		mg_create_generic_tab ("Notify", _("XChat: Friends List"), FALSE, TRUE,
+		                       notify_closegui, NULL, 400, 250, &vbox, 0);
+
+	view = notify_treeview_new (vbox);
+	g_object_set_data (G_OBJECT (notify_window), "view", view);
+  
+	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 (vbox), bbox, 0, 0, 0);
+	gtk_widget_show (bbox);
+
+	gtkutil_button (bbox, GTK_STOCK_NEW, 0, notify_add_clicked, 0,
+	                _("Add..."));
+
+	notify_button_remove =
+	gtkutil_button (bbox, GTK_STOCK_DELETE, 0, notify_remove_clicked, 0,
+	                _("Remove"));
+
+	notify_button_opendialog =
+	gtkutil_button (bbox, NULL, 0, notify_opendialog_clicked, 0,
+	                _("Open Dialog"));
+
+	gtk_widget_set_sensitive (notify_button_opendialog, FALSE);
+	gtk_widget_set_sensitive (notify_button_remove, FALSE);
+
+	notify_gui_update ();
+
+	gtk_widget_show (notify_window);
+}
diff --git a/src/fe-gtk/notifygui.h b/src/fe-gtk/notifygui.h
new file mode 100644
index 00000000..360834dc
--- /dev/null
+++ b/src/fe-gtk/notifygui.h
@@ -0,0 +1,2 @@
+void notify_gui_update (void);
+void notify_opengui (void);
diff --git a/src/fe-gtk/palette.c b/src/fe-gtk/palette.c
new file mode 100644
index 00000000..ebae92ff
--- /dev/null
+++ b/src/fe-gtk/palette.c
@@ -0,0 +1,226 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "fe-gtk.h"
+#include "palette.h"
+
+#include "../common/xchat.h"
+#include "../common/util.h"
+#include "../common/cfgfiles.h"
+
+
+GdkColor colors[] = {
+	/* colors for xtext */
+	{0, 0xcccc, 0xcccc, 0xcccc}, /* 16 white */
+	{0, 0x0000, 0x0000, 0x0000}, /* 17 black */
+	{0, 0x35c2, 0x35c2, 0xb332}, /* 18 blue */
+	{0, 0x2a3d, 0x8ccc, 0x2a3d}, /* 19 green */
+	{0, 0xc3c3, 0x3b3b, 0x3b3b}, /* 20 red */
+	{0, 0xc7c7, 0x3232, 0x3232}, /* 21 light red */
+	{0, 0x8000, 0x2666, 0x7fff}, /* 22 purple */
+	{0, 0x6666, 0x3636, 0x1f1f}, /* 23 orange */
+	{0, 0xd999, 0xa6d3, 0x4147}, /* 24 yellow */
+	{0, 0x3d70, 0xcccc, 0x3d70}, /* 25 green */
+	{0, 0x199a, 0x5555, 0x5555}, /* 26 aqua */
+	{0, 0x2eef, 0x8ccc, 0x74df}, /* 27 light aqua */
+	{0, 0x451e, 0x451e, 0xe666}, /* 28 blue */
+	{0, 0xb0b0, 0x3737, 0xb0b0}, /* 29 light purple */
+	{0, 0x4c4c, 0x4c4c, 0x4c4c}, /* 30 grey */
+	{0, 0x9595, 0x9595, 0x9595}, /* 31 light grey */
+
+	{0, 0xcccc, 0xcccc, 0xcccc}, /* 16 white */
+	{0, 0x0000, 0x0000, 0x0000}, /* 17 black */
+	{0, 0x35c2, 0x35c2, 0xb332}, /* 18 blue */
+	{0, 0x2a3d, 0x8ccc, 0x2a3d}, /* 19 green */
+	{0, 0xc3c3, 0x3b3b, 0x3b3b}, /* 20 red */
+	{0, 0xc7c7, 0x3232, 0x3232}, /* 21 light red */
+	{0, 0x8000, 0x2666, 0x7fff}, /* 22 purple */
+	{0, 0x6666, 0x3636, 0x1f1f}, /* 23 orange */
+	{0, 0xd999, 0xa6d3, 0x4147}, /* 24 yellow */
+	{0, 0x3d70, 0xcccc, 0x3d70}, /* 25 green */
+	{0, 0x199a, 0x5555, 0x5555}, /* 26 aqua */
+	{0, 0x2eef, 0x8ccc, 0x74df}, /* 27 light aqua */
+	{0, 0x451e, 0x451e, 0xe666}, /* 28 blue */
+	{0, 0xb0b0, 0x3737, 0xb0b0}, /* 29 light purple */
+	{0, 0x4c4c, 0x4c4c, 0x4c4c}, /* 30 grey */
+	{0, 0x9595, 0x9595, 0x9595}, /* 31 light grey */
+
+	{0, 0xffff, 0xffff, 0xffff}, /* 32 marktext Fore (white) */
+	{0, 0x3535, 0x6e6e, 0xc1c1}, /* 33 marktext Back (blue) */
+	{0, 0x0000, 0x0000, 0x0000}, /* 34 foreground (black) */
+	{0, 0xf0f0, 0xf0f0, 0xf0f0}, /* 35 background (white) */
+	{0, 0xcccc, 0x1010, 0x1010}, /* 36 marker line (red) */
+
+	/* colors for GUI */
+	{0, 0x9999, 0x0000, 0x0000}, /* 37 tab New Data (dark red) */
+	{0, 0x0000, 0x0000, 0xffff}, /* 38 tab Nick Mentioned (blue) */
+	{0, 0xffff, 0x0000, 0x0000}, /* 39 tab New Message (red) */
+	{0, 0x9595, 0x9595, 0x9595}, /* 40 away user (grey) */
+};
+#define MAX_COL 40
+
+
+void
+palette_alloc (GtkWidget * widget)
+{
+	int i;
+	static int done_alloc = FALSE;
+	GdkColormap *cmap;
+
+	if (!done_alloc)		  /* don't do it again */
+	{
+		done_alloc = TRUE;
+		cmap = gtk_widget_get_colormap (widget);
+		for (i = MAX_COL; i >= 0; i--)
+			gdk_colormap_alloc_color (cmap, &colors[i], FALSE, TRUE);
+	}
+}
+
+/* maps XChat 2.0.x colors to current */
+static const int remap[] =
+{
+	0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
+	33,	/* 16:marktextback */
+	32,	/* 17:marktextfore */
+	34,	/* 18: fg */
+	35,	/* 19: bg */
+	37,	/* 20: newdata */
+	38,	/* 21: blue */
+	39,	/* 22: newmsg */
+	40		/* 23: away */
+};
+
+void
+palette_load (void)
+{
+	int i, j, l, fh, res;
+	char prefname[256];
+	struct stat st;
+	char *cfg;
+	int red, green, blue;
+	int upgrade = FALSE;
+
+	fh = xchat_open_file ("colors.conf", O_RDONLY, 0, 0);
+	if (fh == -1)
+	{
+		fh = xchat_open_file ("palette.conf", O_RDONLY, 0, 0);
+		upgrade = TRUE;
+	}
+
+	if (fh != -1)
+	{
+		fstat (fh, &st);
+		cfg = malloc (st.st_size + 1);
+		if (cfg)
+		{
+			cfg[0] = '\0';
+			l = read (fh, cfg, st.st_size);
+			if (l >= 0)
+				cfg[l] = '\0';
+
+			if (!upgrade)
+			{
+				/* mIRC colors 0-31 are here */
+				for (i = 0; i < 32; i++)
+				{
+					snprintf (prefname, sizeof prefname, "color_%d", i);
+					cfg_get_color (cfg, prefname, &red, &green, &blue);
+					colors[i].red = red;
+					colors[i].green = green;
+					colors[i].blue = blue;
+				}
+
+				/* our special colors are mapped at 256+ */
+				for (i = 256, j = 32; j < MAX_COL+1; i++, j++)
+				{
+					snprintf (prefname, sizeof prefname, "color_%d", i);
+					cfg_get_color (cfg, prefname, &red, &green, &blue);
+					colors[j].red = red;
+					colors[j].green = green;
+					colors[j].blue = blue;
+				}
+
+			} else
+			{
+				/* loading 2.0.x palette.conf */
+				for (i = 0; i < MAX_COL+1; i++)
+				{
+					snprintf (prefname, sizeof prefname, "color_%d_red", i);
+					red = cfg_get_int (cfg, prefname);
+
+					snprintf (prefname, sizeof prefname, "color_%d_grn", i);
+					green = cfg_get_int (cfg, prefname);
+
+					snprintf (prefname, sizeof prefname, "color_%d_blu", i);
+					blue = cfg_get_int_with_result (cfg, prefname, &res);
+
+					if (res)
+					{
+						colors[remap[i]].red = red;
+						colors[remap[i]].green = green;
+						colors[remap[i]].blue = blue;
+					}
+				}
+
+				/* copy 0-15 to 16-31 */
+				for (i = 0; i < 16; i++)
+				{
+					colors[i+16].red = colors[i].red;
+					colors[i+16].green = colors[i].green;
+					colors[i+16].blue = colors[i].blue;
+				}
+			}
+			free (cfg);
+		}
+		close (fh);
+	}
+}
+
+void
+palette_save (void)
+{
+	int i, j, fh;
+	char prefname[256];
+
+	fh = xchat_open_file ("colors.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE);
+	if (fh != -1)
+	{
+		/* mIRC colors 0-31 are here */
+		for (i = 0; i < 32; i++)
+		{
+			snprintf (prefname, sizeof prefname, "color_%d", i);
+			cfg_put_color (fh, colors[i].red, colors[i].green, colors[i].blue, prefname);
+		}
+
+		/* our special colors are mapped at 256+ */
+		for (i = 256, j = 32; j < MAX_COL+1; i++, j++)
+		{
+			snprintf (prefname, sizeof prefname, "color_%d", i);
+			cfg_put_color (fh, colors[j].red, colors[j].green, colors[j].blue, prefname);
+		}
+
+		close (fh);
+	}
+}
diff --git a/src/fe-gtk/palette.h b/src/fe-gtk/palette.h
new file mode 100644
index 00000000..c97693bb
--- /dev/null
+++ b/src/fe-gtk/palette.h
@@ -0,0 +1,15 @@
+extern GdkColor colors[];
+
+#define COL_MARK_FG 32
+#define COL_MARK_BG 33
+#define COL_FG 34
+#define COL_BG 35
+#define COL_MARKER 36
+#define COL_NEW_DATA 37
+#define COL_HILIGHT 38
+#define COL_NEW_MSG 39
+#define COL_AWAY 40
+
+void palette_alloc (GtkWidget * widget);
+void palette_load (void);
+void palette_save (void);
diff --git a/src/fe-gtk/pixmaps.c b/src/fe-gtk/pixmaps.c
new file mode 100644
index 00000000..3d85c3b0
--- /dev/null
+++ b/src/fe-gtk/pixmaps.c
@@ -0,0 +1,123 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "fe-gtk.h"
+#include "../common/xchat.h"
+#include "../common/fe.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gdk-pixbuf/gdk-pixdata.h>
+#include <gtk/gtkstock.h>
+
+#include "../pixmaps/inline_pngs.h"
+
+GdkPixbuf *pix_xchat;
+GdkPixbuf *pix_book;
+
+GdkPixbuf *pix_purple;
+GdkPixbuf *pix_red;
+GdkPixbuf *pix_op;
+GdkPixbuf *pix_hop;
+GdkPixbuf *pix_voice;
+
+GdkPixbuf *pix_tray_msg;
+GdkPixbuf *pix_tray_hilight;
+GdkPixbuf *pix_tray_file;
+
+GdkPixbuf *pix_channel;
+GdkPixbuf *pix_dialog;
+GdkPixbuf *pix_server;
+GdkPixbuf *pix_util;
+
+
+static GdkPixmap *
+pixmap_load_from_file_real (char *file)
+{
+	GdkPixbuf *img;
+	GdkPixmap *pixmap;
+
+	img = gdk_pixbuf_new_from_file (file, 0);
+	if (!img)
+		return NULL;
+	gdk_pixbuf_render_pixmap_and_mask (img, &pixmap, NULL, 128);
+	gdk_pixbuf_unref (img);
+
+	return pixmap;
+}
+
+GdkPixmap *
+pixmap_load_from_file (char *filename)
+{
+	char buf[256];
+	GdkPixmap *pix;
+
+	if (filename[0] == '\0')
+		return NULL;
+
+	pix = pixmap_load_from_file_real (filename);
+	if (pix == NULL)
+	{
+		strcpy (buf, "Cannot open:\n\n");
+		strncpy (buf + 14, filename, sizeof (buf) - 14);
+		buf[sizeof (buf) - 1] = 0;
+		fe_message (buf, FE_MSG_ERROR);
+	}
+
+	return pix;
+}
+
+#define LOADPIX(vv,pp,ff) \
+	vv = gdk_pixbuf_new_from_file (XCHATSHAREDIR"/xchat/"ff, 0); \
+	if (!vv) \
+		vv = gdk_pixbuf_new_from_inline (-1, pp, FALSE, 0);
+
+#define LOADPIX_DISKONLY(vv,ff) \
+	vv = gdk_pixbuf_new_from_file (XCHATSHAREDIR"/xchat/"ff, 0);
+
+#define EXT ".png"
+
+void
+pixmaps_init (void)
+{
+	pix_book = gdk_pixbuf_new_from_inline (-1, bookpng, FALSE, 0);
+
+	/* used in About window, tray icon and WindowManager icon. */
+	LOADPIX (pix_xchat, xchatpng, "xchat"EXT);
+
+	/* userlist icons, with inlined defaults */
+	LOADPIX (pix_hop, hoppng, "hop"EXT);
+	LOADPIX (pix_purple, purplepng, "purple"EXT);
+	LOADPIX (pix_red, redpng, "red"EXT);
+	LOADPIX (pix_op, oppng, "op"EXT);
+	LOADPIX (pix_voice, voicepng, "voice"EXT);
+
+	/* tray icons, with inlined defaults */
+	LOADPIX (pix_tray_msg, traymsgpng, "message"EXT);
+	LOADPIX (pix_tray_hilight, trayhilightpng, "highlight"EXT);
+	LOADPIX (pix_tray_file, trayfilepng, "fileoffer"EXT);
+
+	/* treeview icons, no defaults, load from disk only */
+	LOADPIX_DISKONLY (pix_channel,	"channel"EXT);
+	LOADPIX_DISKONLY (pix_dialog,		"dialog"EXT);
+	LOADPIX_DISKONLY (pix_server,		"server"EXT);
+	LOADPIX_DISKONLY (pix_util,		"util"EXT);
+}
diff --git a/src/fe-gtk/pixmaps.h b/src/fe-gtk/pixmaps.h
new file mode 100644
index 00000000..91b9696e
--- /dev/null
+++ b/src/fe-gtk/pixmaps.h
@@ -0,0 +1,19 @@
+extern GdkPixbuf *pix_book;
+extern GdkPixbuf *pix_hop;
+extern GdkPixbuf *pix_purple;
+extern GdkPixbuf *pix_red;
+extern GdkPixbuf *pix_op;
+extern GdkPixbuf *pix_voice;
+extern GdkPixbuf *pix_xchat;
+
+extern GdkPixbuf *pix_tray_msg;
+extern GdkPixbuf *pix_tray_hilight;
+extern GdkPixbuf *pix_tray_file;
+
+extern GdkPixbuf *pix_channel;
+extern GdkPixbuf *pix_dialog;
+extern GdkPixbuf *pix_server;
+extern GdkPixbuf *pix_util;
+
+extern GdkPixmap *pixmap_load_from_file (char *file);
+extern void pixmaps_init (void);
diff --git a/src/fe-gtk/plugin-tray.c b/src/fe-gtk/plugin-tray.c
new file mode 100644
index 00000000..8603abf4
--- /dev/null
+++ b/src/fe-gtk/plugin-tray.c
@@ -0,0 +1,852 @@
+/* Copyright (C) 2006-2007 Peter Zelezny. */
+
+#include <string.h>
+#include <unistd.h>
+#include "../common/xchat-plugin.h"
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "../common/inbound.h"
+#include "../common/server.h"
+#include "../common/fe.h"
+#include "../common/util.h"
+#include "fe-gtk.h"
+#include "pixmaps.h"
+#include "maingui.h"
+#include "menu.h"
+#include <gtk/gtk.h>
+
+#define LIBNOTIFY
+
+typedef enum	/* current icon status */
+{
+	TS_NONE,
+	TS_MESSAGE,
+	TS_HIGHLIGHT,
+	TS_FILEOFFER,
+	TS_CUSTOM /* plugin */
+} TrayStatus;
+
+typedef enum
+{
+	WS_FOCUSED,
+	WS_NORMAL,
+	WS_HIDDEN
+} WinStatus;
+
+typedef GdkPixbuf* TrayIcon;
+#define tray_icon_from_file(f) gdk_pixbuf_new_from_file(f,NULL)
+#define tray_icon_free(i) g_object_unref(i)
+
+#define ICON_NORMAL pix_xchat
+#define ICON_MSG pix_tray_msg
+#define ICON_HILIGHT pix_tray_hilight
+#define ICON_FILE pix_tray_file
+#define TIMEOUT 500
+
+static GtkStatusIcon *sticon;
+static gint flash_tag;
+static TrayStatus tray_status;
+static xchat_plugin *ph;
+
+static TrayIcon custom_icon1;
+static TrayIcon custom_icon2;
+
+static int tray_priv_count = 0;
+static int tray_pub_count = 0;
+static int tray_hilight_count = 0;
+static int tray_file_count = 0;
+
+
+void tray_apply_setup (void);
+
+
+static WinStatus
+tray_get_window_status (void)
+{
+	const char *st;
+
+	st = xchat_get_info (ph, "win_status");
+
+	if (!st)
+		return WS_HIDDEN;
+
+	if (!strcmp (st, "active"))
+		return WS_FOCUSED;
+
+	if (!strcmp (st, "hidden"))
+		return WS_HIDDEN;
+
+	return WS_NORMAL;
+}
+
+static int
+tray_count_channels (void)
+{
+	int cons = 0;
+	GSList *list;
+	session *sess;
+
+	for (list = sess_list; list; list = list->next)
+	{
+		sess = list->data;
+		if (sess->server->connected && sess->channel[0] &&
+			 sess->type == SESS_CHANNEL)
+			cons++;
+	}
+	return cons;
+}
+
+static int
+tray_count_networks (void)
+{
+	int cons = 0;
+	GSList *list;
+
+	for (list = serv_list; list; list = list->next)
+	{
+		if (((server *)list->data)->connected)
+			cons++;
+	}
+	return cons;
+}
+
+void
+fe_tray_set_tooltip (const char *text)
+{
+	if (sticon)
+		gtk_status_icon_set_tooltip (sticon, text);
+}
+
+#ifdef LIBNOTIFY
+
+/* dynamic access to libnotify.so */
+
+static void *nn_mod = NULL;
+/* prototypes */
+static gboolean (*nn_init) (char *);
+static void (*nn_uninit) (void);
+/* recent versions of libnotify don't take the fourth GtkWidget argument, but passing an
+ * extra NULL argument will be fine */
+static void *(*nn_new) (const gchar *summary, const gchar *message, const gchar *icon, gpointer dummy);
+static gboolean (*nn_show) (void *noti, GError **error);
+static void (*nn_set_timeout) (void *noti, gint timeout);
+
+static void
+libnotify_cleanup (void)
+{
+	if (nn_mod)
+	{
+		nn_uninit ();
+		g_module_close (nn_mod);
+		nn_mod = NULL;
+	}
+}
+
+static gboolean
+libnotify_notify_new (const char *title, const char *text, GtkStatusIcon *icon)
+{
+	void *noti;
+
+	if (!nn_mod)
+	{
+		nn_mod = g_module_open ("libnotify", G_MODULE_BIND_LAZY);
+		if (!nn_mod)
+		{
+			nn_mod = g_module_open ("libnotify.so.1", G_MODULE_BIND_LAZY);
+			if (!nn_mod)
+				return FALSE;
+		}
+
+		if (!g_module_symbol (nn_mod, "notify_init", (gpointer)&nn_init))
+			goto bad;
+		if (!g_module_symbol (nn_mod, "notify_uninit", (gpointer)&nn_uninit))
+			goto bad;
+		if (!g_module_symbol (nn_mod, "notify_notification_new", (gpointer)&nn_new))
+			goto bad;
+		if (!g_module_symbol (nn_mod, "notify_notification_show", (gpointer)&nn_show))
+			goto bad;
+		if (!g_module_symbol (nn_mod, "notify_notification_set_timeout", (gpointer)&nn_set_timeout))
+			goto bad;
+		if (!nn_init (PACKAGE_NAME))
+			goto bad;
+	}
+
+	text = strip_color (text, -1, STRIP_ALL|STRIP_ESCMARKUP);
+	title = strip_color (title, -1, STRIP_ALL);
+	noti = nn_new (title, text, XCHATSHAREDIR"/pixmaps/xchat.png", NULL);
+	g_free ((char *)title);
+	g_free ((char *)text);
+
+	nn_set_timeout (noti, prefs.input_balloon_time*1000);
+	nn_show (noti, NULL);
+	g_object_unref (G_OBJECT (noti));
+
+	return TRUE;
+
+bad:
+	g_module_close (nn_mod);
+	nn_mod = NULL;
+	return FALSE;
+}
+
+#endif
+
+void
+fe_tray_set_balloon (const char *title, const char *text)
+{
+#ifndef WIN32
+	const char *argv[8];
+	const char *path;
+	char time[16];
+	WinStatus ws;
+
+	/* no balloons if the window is focused */
+	ws = tray_get_window_status ();
+	if (ws == WS_FOCUSED)
+		return;
+
+	/* bit 1 of flags means "no balloons unless hidden/iconified" */
+	if (ws != WS_HIDDEN && (prefs.gui_tray_flags & 2))
+		return;
+
+	/* FIXME: this should close the current balloon */
+	if (!text)
+		return;
+
+#ifdef LIBNOTIFY
+	/* try it via libnotify.so */
+	if (libnotify_notify_new (title, text, sticon))
+		return;	/* success */
+#endif
+
+	/* try it the crude way */
+	path = g_find_program_in_path ("notify-send");
+	if (path)
+	{
+		sprintf(time, "%d000",prefs.input_balloon_time);
+		argv[0] = path;
+		argv[1] = "-i";
+		argv[2] = "gtk-dialog-info";
+		if (access (XCHATSHAREDIR"/pixmaps/xchat.png", R_OK) == 0)
+			argv[2] = XCHATSHAREDIR"/pixmaps/xchat.png";
+		argv[3] = "-t";
+		argv[4] = time;
+		argv[5] = title;
+		text = strip_color (text, -1, STRIP_ALL|STRIP_ESCMARKUP);
+		argv[6] = text;
+		argv[7] = NULL;
+		xchat_execv (argv);
+		g_free ((char *)path);
+		g_free ((char *)text);
+	}
+	else
+	{
+		/* show this error only once */
+		static unsigned char said_it = FALSE;
+		if (!said_it)
+		{
+			said_it = TRUE;
+			fe_message (_("Cannot find 'notify-send' to open balloon alerts.\nPlease install libnotify."), FE_MSG_ERROR);
+		}
+	}
+#endif
+}
+
+static void
+tray_set_balloonf (const char *text, const char *format, ...)
+{
+	va_list args;
+	char *buf;
+
+	va_start (args, format);
+	buf = g_strdup_vprintf (format, args);
+	va_end (args);
+
+	fe_tray_set_balloon (buf, text);
+	g_free (buf);
+}
+
+static void
+tray_set_tipf (const char *format, ...)
+{
+	va_list args;
+	char *buf;
+
+	va_start (args, format);
+	buf = g_strdup_vprintf (format, args);
+	va_end (args);
+
+	fe_tray_set_tooltip (buf);
+	g_free (buf);
+}
+
+static void
+tray_stop_flash (void)
+{
+	int nets, chans;
+
+	if (flash_tag)
+	{
+		g_source_remove (flash_tag);
+		flash_tag = 0;
+	}
+
+	if (sticon)
+	{
+		gtk_status_icon_set_from_pixbuf (sticon, ICON_NORMAL);
+		nets = tray_count_networks ();
+		chans = tray_count_channels ();
+		if (nets)
+			tray_set_tipf (_("XChat: Connected to %u networks and %u channels"),
+								nets, chans);
+		else
+			tray_set_tipf ("XChat: %s", _("Not connected."));
+	}
+
+	if (custom_icon1)
+	{
+		tray_icon_free (custom_icon1);
+		custom_icon1 = NULL;
+	}
+
+	if (custom_icon2)
+	{
+		tray_icon_free (custom_icon2);
+		custom_icon2 = NULL;
+	}
+
+	tray_status = TS_NONE;
+}
+
+static void
+tray_reset_counts (void)
+{
+	tray_priv_count = 0;
+	tray_pub_count = 0;
+	tray_hilight_count = 0;
+	tray_file_count = 0;
+}
+
+static int
+tray_timeout_cb (TrayIcon icon)
+{
+	if (custom_icon1)
+	{
+		if (gtk_status_icon_get_pixbuf (sticon) == custom_icon1)
+		{
+			if (custom_icon2)
+				gtk_status_icon_set_from_pixbuf (sticon, custom_icon2);
+			else
+				gtk_status_icon_set_from_pixbuf (sticon, ICON_NORMAL);
+		}
+		else
+		{
+			gtk_status_icon_set_from_pixbuf (sticon, custom_icon1);
+		}
+	}
+	else
+	{
+		if (gtk_status_icon_get_pixbuf (sticon) == ICON_NORMAL)
+			gtk_status_icon_set_from_pixbuf (sticon, icon);
+		else
+			gtk_status_icon_set_from_pixbuf (sticon, ICON_NORMAL);
+	}
+	return 1;
+}
+
+static void
+tray_set_flash (TrayIcon icon)
+{
+	if (!sticon)
+		return;
+
+	/* already flashing the same icon */
+	if (flash_tag && gtk_status_icon_get_pixbuf (sticon) == icon)
+		return;
+
+	/* no flashing if window is focused */
+	if (tray_get_window_status () == WS_FOCUSED)
+		return;
+
+	tray_stop_flash ();
+
+	gtk_status_icon_set_from_pixbuf (sticon, icon);
+	flash_tag = g_timeout_add (TIMEOUT, (GSourceFunc) tray_timeout_cb, icon);
+}
+
+void
+fe_tray_set_flash (const char *filename1, const char *filename2, int tout)
+{
+	tray_apply_setup ();
+	if (!sticon)
+		return;
+
+	tray_stop_flash ();
+
+	if (tout == -1)
+		tout = TIMEOUT;
+
+	custom_icon1 = tray_icon_from_file (filename1);
+	if (filename2)
+		custom_icon2 = tray_icon_from_file (filename2);
+
+	gtk_status_icon_set_from_pixbuf (sticon, custom_icon1);
+	flash_tag = g_timeout_add (tout, (GSourceFunc) tray_timeout_cb, NULL);
+	tray_status = TS_CUSTOM;
+}
+
+void
+fe_tray_set_icon (feicon icon)
+{
+	tray_apply_setup ();
+	if (!sticon)
+		return;
+
+	tray_stop_flash ();
+
+	switch (icon)
+	{
+	case FE_ICON_NORMAL:
+		break;
+	case FE_ICON_MESSAGE:
+		tray_set_flash (ICON_MSG);
+		break;
+	case FE_ICON_HIGHLIGHT:
+	case FE_ICON_PRIVMSG:
+		tray_set_flash (ICON_HILIGHT);
+		break;
+	case FE_ICON_FILEOFFER:
+		tray_set_flash (ICON_FILE);
+	}
+}
+
+void
+fe_tray_set_file (const char *filename)
+{
+	tray_apply_setup ();
+	if (!sticon)
+		return;
+
+	tray_stop_flash ();
+
+	if (filename)
+	{
+		custom_icon1 = tray_icon_from_file (filename);
+		gtk_status_icon_set_from_pixbuf (sticon, custom_icon1);
+		tray_status = TS_CUSTOM;
+	}
+}
+
+gboolean
+tray_toggle_visibility (gboolean force_hide)
+{
+	static int x, y;
+	static GdkScreen *screen;
+	GtkWindow *win;
+
+	if (!sticon)
+		return FALSE;
+
+	/* ph may have an invalid context now */
+	xchat_set_context (ph, xchat_find_context (ph, NULL, NULL));
+
+	win = (GtkWindow *)xchat_get_info (ph, "win_ptr");
+
+	tray_stop_flash ();
+	tray_reset_counts ();
+
+	if (!win)
+		return FALSE;
+
+#if GTK_CHECK_VERSION(2,20,0)
+	if (force_hide || gtk_widget_get_visible (GTK_WIDGET (win)))
+#else
+	if (force_hide || GTK_WIDGET_VISIBLE (win))
+#endif
+	{
+		gtk_window_get_position (win, &x, &y);
+		screen = gtk_window_get_screen (win);
+		gtk_widget_hide (GTK_WIDGET (win));
+	}
+	else
+	{
+		gtk_window_set_screen (win, screen);
+		gtk_window_move (win, x, y);
+		gtk_widget_show (GTK_WIDGET (win));
+		gtk_window_present (win);
+	}
+
+	return TRUE;
+}
+
+static void
+tray_menu_restore_cb (GtkWidget *item, gpointer userdata)
+{
+	tray_toggle_visibility (FALSE);
+}
+
+static void
+tray_menu_quit_cb (GtkWidget *item, gpointer userdata)
+{
+	mg_open_quit_dialog (FALSE);
+}
+
+/* returns 0-mixed 1-away 2-back */
+
+static int
+tray_find_away_status (void)
+{
+	GSList *list;
+	server *serv;
+	int away = 0;
+	int back = 0;
+
+	for (list = serv_list; list; list = list->next)
+	{
+		serv = list->data;
+
+		if (serv->is_away || serv->reconnect_away)
+			away++;
+		else
+			back++;
+	}
+
+	if (away && back)
+		return 0;
+
+	if (away)
+		return 1;
+
+	return 2;
+}
+
+static void
+tray_foreach_server (GtkWidget *item, char *cmd)
+{
+	GSList *list;
+	server *serv;
+
+	for (list = serv_list; list; list = list->next)
+	{
+		serv = list->data;
+		if (serv->connected)
+			handle_command (serv->server_session, cmd, FALSE);
+	}
+}
+
+static GtkWidget *
+tray_make_item (GtkWidget *menu, char *label, void *callback, void *userdata)
+{
+	GtkWidget *item;
+
+	if (label)
+		item = gtk_menu_item_new_with_mnemonic (label);
+	else
+		item = gtk_menu_item_new ();
+	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+	g_signal_connect (G_OBJECT (item), "activate",
+							G_CALLBACK (callback), userdata);
+	gtk_widget_show (item);
+
+	return item;
+}
+
+static void
+tray_toggle_cb (GtkCheckMenuItem *item, unsigned int *setting)
+{
+	*setting = item->active;
+}
+
+static void
+blink_item (unsigned int *setting, GtkWidget *menu, char *label)
+{
+	menu_toggle_item (label, menu, tray_toggle_cb, setting, *setting);
+}
+
+static void
+tray_menu_destroy (GtkWidget *menu, gpointer userdata)
+{
+	gtk_widget_destroy (menu);
+	g_object_unref (menu);
+}
+
+static void
+tray_menu_cb (GtkWidget *widget, guint button, guint time, gpointer userdata)
+{
+	GtkWidget *menu;
+	GtkWidget *submenu;
+	GtkWidget *item;
+	int away_status;
+
+	/* ph may have an invalid context now */
+	xchat_set_context (ph, xchat_find_context (ph, NULL, NULL));
+
+	menu = gtk_menu_new ();
+	/*gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget));*/
+
+	if (tray_get_window_status () == WS_HIDDEN)
+		tray_make_item (menu, _("_Restore"), tray_menu_restore_cb, NULL);
+	else
+		tray_make_item (menu, _("_Hide"), tray_menu_restore_cb, NULL);
+	tray_make_item (menu, NULL, tray_menu_quit_cb, NULL);
+
+	submenu = mg_submenu (menu, _("_Blink on"));
+	blink_item (&prefs.input_tray_chans, submenu, _("Channel Message"));
+	blink_item (&prefs.input_tray_priv, submenu, _("Private Message"));
+	blink_item (&prefs.input_tray_hilight, submenu, _("Highlighted Message"));
+	/*blink_item (BIT_FILEOFFER, submenu, _("File Offer"));*/
+
+	submenu = mg_submenu (menu, _("_Change status"));
+	away_status = tray_find_away_status ();
+	item = tray_make_item (submenu, _("_Away"), tray_foreach_server, "away");
+	if (away_status == 1)
+		gtk_widget_set_sensitive (item, FALSE);
+	item = tray_make_item (submenu, _("_Back"), tray_foreach_server, "back");
+	if (away_status == 2)
+		gtk_widget_set_sensitive (item, FALSE);
+
+	tray_make_item (menu, NULL, tray_menu_quit_cb, NULL);
+	mg_create_icon_item (_("_Quit"), GTK_STOCK_QUIT, menu, tray_menu_quit_cb, NULL);
+
+	menu_add_plugin_items (menu, "\x5$TRAY", NULL);
+
+	g_object_ref (menu);
+	g_object_ref_sink (menu);
+	g_object_unref (menu);
+	g_signal_connect (G_OBJECT (menu), "selection-done",
+							G_CALLBACK (tray_menu_destroy), NULL);
+
+	gtk_menu_popup (GTK_MENU (menu), NULL, NULL, gtk_status_icon_position_menu,
+						 userdata, button, time);
+}
+
+static void
+tray_init (void)
+{
+	flash_tag = 0;
+	tray_status = TS_NONE;
+	custom_icon1 = NULL;
+	custom_icon2 = NULL;
+
+	sticon = gtk_status_icon_new_from_pixbuf (ICON_NORMAL);
+	if (!sticon)
+		return;
+	g_signal_connect (G_OBJECT (sticon), "popup-menu",
+							G_CALLBACK (tray_menu_cb), sticon);
+	g_signal_connect (G_OBJECT (sticon), "activate",
+							G_CALLBACK (tray_menu_restore_cb), NULL);
+}
+
+static int
+tray_hilight_cb (char *word[], void *userdata)
+{
+	/*if (tray_status == TS_HIGHLIGHT)
+		return XCHAT_EAT_NONE;*/
+
+	if (prefs.input_tray_hilight)
+	{
+		tray_set_flash (ICON_HILIGHT);
+
+		/* FIXME: hides any previous private messages */
+		tray_hilight_count++;
+		if (tray_hilight_count == 1)
+			tray_set_tipf (_("XChat: Highlighted message from: %s (%s)"),
+								word[1], xchat_get_info (ph, "channel"));
+		else
+			tray_set_tipf (_("XChat: %u highlighted messages, latest from: %s (%s)"),
+								tray_hilight_count, word[1], xchat_get_info (ph, "channel"));
+	}
+
+	if (prefs.input_balloon_hilight)
+		tray_set_balloonf (word[2], _("XChat: Highlighted message from: %s (%s)"),
+								 word[1], xchat_get_info (ph, "channel"));
+
+	return XCHAT_EAT_NONE;
+}
+
+static int
+tray_message_cb (char *word[], void *userdata)
+{
+	if (/*tray_status == TS_MESSAGE ||*/ tray_status == TS_HIGHLIGHT)
+		return XCHAT_EAT_NONE;
+
+	if (prefs.input_tray_chans)
+	{
+		tray_set_flash (ICON_MSG);
+
+		tray_pub_count++;
+		if (tray_pub_count == 1)
+			tray_set_tipf (_("XChat: New public message from: %s (%s)"),
+								word[1], xchat_get_info (ph, "channel"));
+		else
+			tray_set_tipf (_("XChat: %u new public messages."), tray_pub_count);
+	}
+
+	if (prefs.input_balloon_chans)
+		tray_set_balloonf (word[2], _("XChat: New public message from: %s (%s)"),
+								 word[1], xchat_get_info (ph, "channel"));
+
+	return XCHAT_EAT_NONE;
+}
+
+static void
+tray_priv (char *from, char *text)
+{
+	const char *network;
+
+	if (alert_match_word (from, prefs.irc_no_hilight))
+		return;
+
+	tray_set_flash (ICON_HILIGHT);
+
+	network = xchat_get_info (ph, "network");
+	if (!network)
+		network = xchat_get_info (ph, "server");
+
+	tray_priv_count++;
+	if (tray_priv_count == 1)
+		tray_set_tipf (_("XChat: Private message from: %s (%s)"),
+							from, network);
+	else
+		tray_set_tipf (_("XChat: %u private messages, latest from: %s (%s)"),
+							tray_priv_count, from, network);
+
+	if (prefs.input_balloon_priv)
+		tray_set_balloonf (text, _("XChat: Private message from: %s (%s)"),
+								 from, network);
+}
+
+static int
+tray_priv_cb (char *word[], void *userdata)
+{
+	/*if (tray_status == TS_HIGHLIGHT)
+		return XCHAT_EAT_NONE;*/
+
+	if (prefs.input_tray_priv)
+		tray_priv (word[1], word[2]);
+
+	return XCHAT_EAT_NONE;
+}
+
+static int
+tray_invited_cb (char *word[], void *userdata)
+{
+	/*if (tray_status == TS_HIGHLIGHT)
+		return XCHAT_EAT_NONE;*/
+
+	if (prefs.input_tray_priv)
+		tray_priv (word[2], "Invited");
+
+	return XCHAT_EAT_NONE;
+}
+
+static int
+tray_dcc_cb (char *word[], void *userdata)
+{
+	const char *network;
+
+/*	if (tray_status == TS_FILEOFFER)
+		return XCHAT_EAT_NONE;*/
+
+	network = xchat_get_info (ph, "network");
+	if (!network)
+		network = xchat_get_info (ph, "server");
+
+	if (prefs.input_tray_priv)
+	{
+		tray_set_flash (ICON_FILE);
+
+		tray_file_count++;
+		if (tray_file_count == 1)
+			tray_set_tipf (_("XChat: File offer from: %s (%s)"),
+								word[1], network);
+		else
+			tray_set_tipf (_("XChat: %u file offers, latest from: %s (%s)"),
+								tray_file_count, word[1], network);
+	}
+
+	if (prefs.input_balloon_priv)
+		tray_set_balloonf ("", _("XChat: File offer from: %s (%s)"),
+								word[1], network);
+
+	return XCHAT_EAT_NONE;
+}
+
+static int
+tray_focus_cb (char *word[], void *userdata)
+{
+	tray_stop_flash ();
+	tray_reset_counts ();
+	return XCHAT_EAT_NONE;
+}
+
+static void
+tray_cleanup (void)
+{
+	tray_stop_flash ();
+
+	if (sticon)
+	{
+		g_object_unref ((GObject *)sticon);
+		sticon = NULL;
+	}
+}
+
+void
+tray_apply_setup (void)
+{
+	if (sticon)
+	{
+		if (!prefs.gui_tray)
+			tray_cleanup ();
+	}
+	else
+	{
+		if (prefs.gui_tray)
+			tray_init ();
+	}
+}
+
+int
+tray_plugin_init (xchat_plugin *plugin_handle, char **plugin_name,
+				char **plugin_desc, char **plugin_version, char *arg)
+{
+	/* we need to save this for use with any xchat_* functions */
+	ph = plugin_handle;
+
+	*plugin_name = "";
+	*plugin_desc = "";
+	*plugin_version = "";
+
+	xchat_hook_print (ph, "Channel Msg Hilight", -1, tray_hilight_cb, NULL);
+	xchat_hook_print (ph, "Channel Action Hilight", -1, tray_hilight_cb, NULL);
+
+	xchat_hook_print (ph, "Channel Message", -1, tray_message_cb, NULL);
+	xchat_hook_print (ph, "Channel Action", -1, tray_message_cb, NULL);
+	xchat_hook_print (ph, "Channel Notice", -1, tray_message_cb, NULL);
+
+	xchat_hook_print (ph, "Private Message", -1, tray_priv_cb, NULL);
+	xchat_hook_print (ph, "Private Message to Dialog", -1, tray_priv_cb, NULL);
+	xchat_hook_print (ph, "Notice", -1, tray_priv_cb, NULL);
+	xchat_hook_print (ph, "Invited", -1, tray_invited_cb, NULL);
+
+	xchat_hook_print (ph, "DCC Offer", -1, tray_dcc_cb, NULL);
+
+	xchat_hook_print (ph, "Focus Window", -1, tray_focus_cb, NULL);
+
+	if (prefs.gui_tray)
+		tray_init ();
+
+	return 1;       /* return 1 for success */
+}
+
+int
+tray_plugin_deinit (xchat_plugin *plugin_handle)
+{
+#ifdef WIN32
+	tray_cleanup ();
+#elif defined(LIBNOTIFY)
+	libnotify_cleanup ();
+#endif
+	return 1;
+}
diff --git a/src/fe-gtk/plugin-tray.h b/src/fe-gtk/plugin-tray.h
new file mode 100644
index 00000000..d54be5a4
--- /dev/null
+++ b/src/fe-gtk/plugin-tray.h
@@ -0,0 +1,4 @@
+int tray_plugin_init (void *, char **, char **, char **, char *);
+int tray_plugin_deinit (void *);
+gboolean tray_toggle_visibility (gboolean force_hide);
+void tray_apply_setup (void);
diff --git a/src/fe-gtk/plugingui.c b/src/fe-gtk/plugingui.c
new file mode 100644
index 00000000..de59e649
--- /dev/null
+++ b/src/fe-gtk/plugingui.c
@@ -0,0 +1,239 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkdialog.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkbox.h>
+#include <gtk/gtkscrolledwindow.h>
+
+#include <gtk/gtkliststore.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtkcellrenderertext.h>
+
+#include "../common/xchat.h"
+#define PLUGIN_C
+typedef struct session xchat_context;
+#include "../common/xchat-plugin.h"
+#include "../common/plugin.h"
+#include "../common/util.h"
+#include "../common/outbound.h"
+#include "../common/fe.h"
+#include "../common/xchatc.h"
+#include "gtkutil.h"
+
+/* model for the plugin treeview */
+enum
+{
+	NAME_COLUMN,
+	VERSION_COLUMN,
+	FILE_COLUMN,
+	DESC_COLUMN,
+	N_COLUMNS
+};
+
+static GtkWidget *plugin_window = NULL;
+
+
+static GtkWidget *
+plugingui_treeview_new (GtkWidget *box)
+{
+	GtkListStore *store;
+	GtkWidget *view;
+	GtkTreeViewColumn *col;
+	int col_id;
+
+	store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING,
+	                            G_TYPE_STRING, G_TYPE_STRING);
+	g_return_val_if_fail (store != NULL, NULL);
+	view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store), NULL,
+	                             NAME_COLUMN, _("Name"),
+	                             VERSION_COLUMN, _("Version"),
+	                             FILE_COLUMN, _("File"),
+	                             DESC_COLUMN, _("Description"), -1);
+	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE);
+	for (col_id=0; (col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), col_id));
+	     col_id++)
+			gtk_tree_view_column_set_alignment (col, 0.5);
+
+	gtk_widget_show (view);
+	return view;
+}
+
+static void
+plugingui_close_button (GtkWidget * wid, gpointer a)
+{
+	gtk_widget_destroy (plugin_window);
+}
+
+static void
+plugingui_close (GtkWidget * wid, gpointer a)
+{
+	plugin_window = NULL;
+}
+
+extern GSList *plugin_list;
+
+void
+fe_pluginlist_update (void)
+{
+	xchat_plugin *pl;
+	GSList *list;
+	GtkTreeView *view;
+	GtkListStore *store;
+	GtkTreeIter iter;
+
+	if (!plugin_window)
+		return;
+
+	view = g_object_get_data (G_OBJECT (plugin_window), "view");
+	store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
+	gtk_list_store_clear (store);
+
+	list = plugin_list;
+	while (list)
+	{
+		pl = list->data;
+		if (pl->version[0] != 0)
+		{
+			gtk_list_store_append (store, &iter);
+			gtk_list_store_set (store, &iter, NAME_COLUMN, pl->name,
+			                    VERSION_COLUMN, pl->version,
+			                    FILE_COLUMN, file_part (pl->filename),
+			                    DESC_COLUMN, pl->desc, -1);
+		}
+		list = list->next;
+	}
+}
+
+static void
+plugingui_load_cb (session *sess, char *file)
+{
+	if (file)
+	{
+		char *buf = malloc (strlen (file) + 9);
+
+		if (strchr (file, ' '))
+			sprintf (buf, "LOAD \"%s\"", file);
+		else
+			sprintf (buf, "LOAD %s", file);
+		handle_command (sess, buf, FALSE);
+		free (buf);
+	}
+}
+
+void
+plugingui_load (void)
+{
+	gtkutil_file_req (_("Select a Plugin or Script to load"), plugingui_load_cb,
+							current_sess, NULL, FRF_ADDFOLDER);
+}
+
+static void
+plugingui_loadbutton_cb (GtkWidget * wid, gpointer unused)
+{
+	plugingui_load ();
+}
+
+static void
+plugingui_unload (GtkWidget * wid, gpointer unused)
+{
+	int len;
+	char *modname, *file, *buf;
+	GtkTreeView *view;
+	GtkTreeIter iter;
+	
+	view = g_object_get_data (G_OBJECT (plugin_window), "view");
+	if (!gtkutil_treeview_get_selected (view, &iter, NAME_COLUMN, &modname,
+	                                    FILE_COLUMN, &file, -1))
+		return;
+
+	len = strlen (file);
+#ifdef WIN32
+	if (len > 4 && strcasecmp (file + len - 4, ".dll") == 0)
+#else
+#if defined(__hpux)
+	if (len > 3 && strcasecmp (file + len - 3, ".sl") == 0)
+#else
+	if (len > 3 && strcasecmp (file + len - 3, ".so") == 0)
+#endif
+#endif
+	{
+		if (plugin_kill (modname, FALSE) == 2)
+			fe_message (_("That plugin is refusing to unload.\n"), FE_MSG_ERROR);
+	} else
+	{
+		/* let python.so or perl.so handle it */
+		buf = malloc (strlen (file) + 10);
+		if (strchr (file, ' '))
+			sprintf (buf, "UNLOAD \"%s\"", file);
+		else
+			sprintf (buf, "UNLOAD %s", file);
+		handle_command (current_sess, buf, FALSE);
+		free (buf);
+	}
+
+	g_free (modname);
+	g_free (file);
+}
+
+void
+plugingui_open (void)
+{
+	GtkWidget *view;
+	GtkWidget *vbox, *action_area;
+
+	if (plugin_window)
+	{
+		gtk_window_present (GTK_WINDOW (plugin_window));
+		return;
+	}
+
+	plugin_window = gtk_dialog_new ();
+	g_signal_connect (G_OBJECT (plugin_window), "destroy",
+							G_CALLBACK (plugingui_close), 0);
+	gtk_window_set_default_size (GTK_WINDOW (plugin_window), 500, 250);
+	vbox = GTK_DIALOG (plugin_window)->vbox;
+	action_area = GTK_DIALOG (plugin_window)->action_area;
+	gtk_container_set_border_width (GTK_CONTAINER (vbox), 4);
+	gtk_window_set_position (GTK_WINDOW (plugin_window), GTK_WIN_POS_CENTER);
+	gtk_window_set_title (GTK_WINDOW (plugin_window), _("XChat: Plugins and Scripts"));
+
+	view = plugingui_treeview_new (vbox);
+	g_object_set_data (G_OBJECT (plugin_window), "view", view);
+
+	gtkutil_button (action_area, GTK_STOCK_REVERT_TO_SAVED, NULL,
+	                plugingui_loadbutton_cb, NULL, _("_Load..."));
+
+	gtkutil_button (action_area, GTK_STOCK_DELETE, NULL,
+	                plugingui_unload, NULL, _("_UnLoad"));
+
+	gtkutil_button (action_area,
+						 GTK_STOCK_CLOSE, NULL, plugingui_close_button,
+						 NULL, _("_Close"));
+ 
+	fe_pluginlist_update ();
+
+	gtk_widget_show (plugin_window);
+}
diff --git a/src/fe-gtk/plugingui.h b/src/fe-gtk/plugingui.h
new file mode 100644
index 00000000..945d9a01
--- /dev/null
+++ b/src/fe-gtk/plugingui.h
@@ -0,0 +1,2 @@
+void plugingui_open (void);
+void plugingui_load (void);
diff --git a/src/fe-gtk/rawlog.c b/src/fe-gtk/rawlog.c
new file mode 100644
index 00000000..56ca0510
--- /dev/null
+++ b/src/fe-gtk/rawlog.c
@@ -0,0 +1,152 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkvscrollbar.h>
+#include <gtk/gtkstock.h>
+
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "../common/cfgfiles.h"
+#include "../common/server.h"
+#include "gtkutil.h"
+#include "palette.h"
+#include "maingui.h"
+#include "rawlog.h"
+#include "xtext.h"
+
+
+static void
+close_rawlog (GtkWidget *wid, server *serv)
+{
+	if (is_server (serv))
+		serv->gui->rawlog_window = 0;
+}
+
+static void
+rawlog_save (server *serv, char *file)
+{
+	int fh = -1;
+
+	if (file)
+	{
+		if (serv->gui->rawlog_window)
+			fh = xchat_open_file (file, O_TRUNC | O_WRONLY | O_CREAT,
+										 0600, XOF_DOMODE | XOF_FULLPATH);
+		if (fh != -1)
+		{
+			gtk_xtext_save (GTK_XTEXT (serv->gui->rawlog_textlist), fh);
+			close (fh);
+		}
+	}
+}
+
+static int
+rawlog_clearbutton (GtkWidget * wid, server *serv)
+{
+	gtk_xtext_clear (GTK_XTEXT (serv->gui->rawlog_textlist)->buffer, 0);
+	return FALSE;
+}
+
+static int
+rawlog_savebutton (GtkWidget * wid, server *serv)
+{
+	gtkutil_file_req (_("Save As..."), rawlog_save, serv, NULL, FRF_WRITE);
+	return FALSE;
+}
+
+void
+open_rawlog (struct server *serv)
+{
+	GtkWidget *hbox, *vscrollbar, *vbox;
+	char tbuf[256];
+
+	if (serv->gui->rawlog_window)
+	{
+		mg_bring_tofront (serv->gui->rawlog_window);
+		return;
+	}
+
+	snprintf (tbuf, sizeof tbuf, _("XChat: Rawlog (%s)"), serv->servername);
+	serv->gui->rawlog_window =
+		mg_create_generic_tab ("RawLog", tbuf, FALSE, TRUE, close_rawlog, serv,
+							 640, 320, &vbox, serv);
+
+	hbox = gtk_hbox_new (FALSE, 2);
+	gtk_container_add (GTK_CONTAINER (vbox), hbox);
+	gtk_container_set_border_width (GTK_CONTAINER (hbox), 4);
+	gtk_widget_show (hbox);
+
+	serv->gui->rawlog_textlist = gtk_xtext_new (colors, 0);
+	gtk_xtext_set_tint (GTK_XTEXT (serv->gui->rawlog_textlist), prefs.tint_red, prefs.tint_green, prefs.tint_blue);
+	gtk_xtext_set_background (GTK_XTEXT (serv->gui->rawlog_textlist),
+									  channelwin_pix, prefs.transparent);
+
+	gtk_container_add (GTK_CONTAINER (hbox), serv->gui->rawlog_textlist);
+	gtk_xtext_set_font (GTK_XTEXT (serv->gui->rawlog_textlist), prefs.font_normal);
+	GTK_XTEXT (serv->gui->rawlog_textlist)->ignore_hidden = 1;
+	gtk_widget_show (serv->gui->rawlog_textlist);
+
+	vscrollbar = gtk_vscrollbar_new (GTK_XTEXT (serv->gui->rawlog_textlist)->adj);
+	gtk_box_pack_start (GTK_BOX (hbox), vscrollbar, FALSE, FALSE, 0);
+	show_and_unfocus (vscrollbar);
+
+	hbox = gtk_hbutton_box_new ();
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_SPREAD);
+	gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0);
+	gtk_widget_show (hbox);
+
+	gtkutil_button (hbox, GTK_STOCK_CLEAR, NULL, rawlog_clearbutton,
+						 serv, _("Clear rawlog"));
+
+	gtkutil_button (hbox, GTK_STOCK_SAVE_AS, NULL, rawlog_savebutton,
+						 serv, _("Save As..."));
+
+	gtk_widget_show (serv->gui->rawlog_window);
+}
+
+void
+fe_add_rawlog (server *serv, char *text, int len, int outbound)
+{
+	char *new_text;
+
+	if (!serv->gui->rawlog_window)
+		return;
+
+	new_text = malloc (len + 7);
+
+	len = sprintf (new_text, "\0033>>\017 %s", text);
+	if (outbound)
+	{
+		new_text[1] = '4';
+		new_text[2] = '<';
+		new_text[3] = '<';
+	}
+	gtk_xtext_append (GTK_XTEXT (serv->gui->rawlog_textlist)->buffer, new_text, len);
+	free (new_text);
+}
diff --git a/src/fe-gtk/rawlog.h b/src/fe-gtk/rawlog.h
new file mode 100644
index 00000000..db41e2a7
--- /dev/null
+++ b/src/fe-gtk/rawlog.h
@@ -0,0 +1 @@
+void open_rawlog (server *serv);
diff --git a/src/fe-gtk/search.c b/src/fe-gtk/search.c
new file mode 100644
index 00000000..d62e79c7
--- /dev/null
+++ b/src/fe-gtk/search.c
@@ -0,0 +1,159 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkhseparator.h>
+#include <gtk/gtkvseparator.h>
+#include <gtk/gtkradiobutton.h>
+#include <gtk/gtktogglebutton.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "../common/xchat.h"
+#include "../common/fe.h"
+#include "../common/util.h"
+#include "../common/xchatc.h"
+#include "gtkutil.h"
+#include "xtext.h"
+#include "maingui.h"
+
+
+static textentry *last;	/* our last search pos */
+static int case_match = 0;
+static int search_backward = 0;
+
+
+static void
+search_search (session * sess, const gchar *text)
+{
+	if (!is_session (sess))
+	{
+		fe_message (_("The window you opened this Search "
+						"for doesn't exist anymore."), FE_MSG_ERROR);
+		return;
+	}
+
+	last = gtk_xtext_search (GTK_XTEXT (sess->gui->xtext), text,
+									 last, case_match, search_backward);
+	if (!last)
+		fe_message (_("Search hit end, not found."), FE_MSG_ERROR);
+}
+
+static void
+search_find_cb (GtkWidget * button, session * sess)
+{
+	GtkEntry *entry;
+	const gchar *text;
+
+	entry = g_object_get_data (G_OBJECT (button), "e");
+	text = gtk_entry_get_text (entry);
+	search_search (sess, text);
+}
+
+static void
+search_close_cb (GtkWidget * button, GtkWidget * win)
+{
+	gtk_widget_destroy (win);
+}
+
+static void
+search_entry_cb (GtkWidget * entry, session * sess)
+{
+	search_search (sess, gtk_entry_get_text (GTK_ENTRY (entry)));
+}
+
+static gboolean 
+search_key_cb (GtkWidget * window, GdkEventKey * key, gpointer userdata)
+{
+	if (key->keyval == GDK_Escape)
+		gtk_widget_destroy (window);
+	return FALSE;
+}
+
+static void
+search_caseign_cb (GtkToggleButton * but, session * sess)
+{
+	case_match = (but->active)? 1: 0;
+}
+
+static void
+search_dirbwd_cb (GtkToggleButton * but, session * sess)
+{
+	search_backward = (but->active)? 1: 0;
+}
+
+void
+search_open (session * sess)
+{
+	GtkWidget *win, *hbox, *vbox, *entry, *wid;
+
+	last = NULL;
+	win = mg_create_generic_tab ("search", _("XChat: Search"), TRUE, FALSE,
+								 NULL, NULL, 0, 0, &vbox, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (win), 12);
+	gtk_box_set_spacing (GTK_BOX (vbox), 4);
+
+	hbox = gtk_hbox_new (0, 10);
+	gtk_container_add (GTK_CONTAINER (vbox), hbox);
+	gtk_widget_show (hbox);
+
+	gtkutil_label_new (_("Find:"), hbox);
+
+	entry = gtk_entry_new ();
+	g_signal_connect (G_OBJECT (entry), "activate",
+							G_CALLBACK (search_entry_cb), sess);
+	gtk_container_add (GTK_CONTAINER (hbox), entry);
+	gtk_widget_show (entry);
+	gtk_widget_grab_focus (entry);
+
+	wid = gtk_check_button_new_with_mnemonic (_("_Match case"));
+	GTK_TOGGLE_BUTTON (wid)->active = case_match;
+	g_signal_connect (G_OBJECT (wid), "toggled", G_CALLBACK (search_caseign_cb), sess);
+	gtk_container_add (GTK_CONTAINER (vbox), wid);
+	gtk_widget_show (wid);
+
+	wid = gtk_check_button_new_with_mnemonic (_("Search _backwards"));
+	GTK_TOGGLE_BUTTON (wid)->active = search_backward;
+	g_signal_connect (G_OBJECT (wid), "toggled", G_CALLBACK (search_dirbwd_cb), sess);
+	gtk_container_add (GTK_CONTAINER (vbox), wid);
+	gtk_widget_show (wid);
+
+	hbox = gtk_hbutton_box_new ();
+	gtk_box_pack_start (GTK_BOX (vbox), hbox, 0, 0, 4);
+	gtk_widget_show (hbox);
+
+	gtkutil_button (hbox, GTK_STOCK_CLOSE, 0, search_close_cb, win,
+						_("_Close"));
+	wid = gtkutil_button (hbox, GTK_STOCK_FIND, 0, search_find_cb, sess,
+								_("_Find"));
+	g_object_set_data (G_OBJECT (wid), "e", entry);
+
+	g_signal_connect (G_OBJECT (win), "key-press-event", G_CALLBACK (search_key_cb), win);
+
+	gtk_widget_show (win);
+}
diff --git a/src/fe-gtk/search.h b/src/fe-gtk/search.h
new file mode 100644
index 00000000..8fa1b628
--- /dev/null
+++ b/src/fe-gtk/search.h
@@ -0,0 +1 @@
+void search_open (session * sess);
diff --git a/src/fe-gtk/servlistgui.c b/src/fe-gtk/servlistgui.c
new file mode 100644
index 00000000..2ac0e6c9
--- /dev/null
+++ b/src/fe-gtk/servlistgui.c
@@ -0,0 +1,1918 @@
+/* X-Chat
+ * Copyright (C) 2004-2008 Peter Zelezny.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <gtk/gtkversion.h>
+#include <gtk/gtkcheckbutton.h>
+#include <gtk/gtkcellrenderertext.h>
+#include <gtk/gtkcomboboxentry.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkhseparator.h>
+#include <gtk/gtkimage.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkliststore.h>
+#include <gtk/gtkmenuitem.h>
+#include <gtk/gtkmessagedialog.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtktogglebutton.h>
+#include <gtk/gtktree.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtkvbbox.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkwindow.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "../common/servlist.h"
+#include "../common/cfgfiles.h"
+#include "../common/fe.h"
+
+#include "fe-gtk.h"
+#include "gtkutil.h"
+#include "menu.h"
+#include "pixmaps.h"
+
+
+/* servlistgui.c globals */
+static GtkWidget *serverlist_win = NULL;
+static GtkWidget *networks_tree;	/* network TreeView */
+static int ignore_changed = FALSE;
+#ifdef WIN32
+static int win_width = 324;
+static int win_height = 426;
+#else
+static int win_width = 364;
+static int win_height = 478;
+#endif
+
+/* global user info */
+static GtkWidget *entry_nick1;
+static GtkWidget *entry_nick2;
+static GtkWidget *entry_nick3;
+static GtkWidget *entry_guser;
+static GtkWidget *entry_greal;
+
+/* edit area */
+static GtkWidget *edit_win;
+static GtkWidget *edit_entry_nick;
+static GtkWidget *edit_entry_nick2;
+static GtkWidget *edit_entry_user;
+static GtkWidget *edit_entry_real;
+static GtkWidget *edit_entry_join;
+static GtkWidget *edit_entry_pass;
+static GtkWidget *edit_entry_cmd;
+static GtkWidget *edit_entry_nickserv;
+static GtkWidget *edit_label_nick;
+static GtkWidget *edit_label_nick2;
+static GtkWidget *edit_label_real;
+static GtkWidget *edit_label_user;
+static GtkWidget *edit_tree;
+
+static ircnet *selected_net = NULL;
+static ircserver *selected_serv = NULL;
+static ircnet *fav_add_net = NULL; /* used in Add/Remove fav context menus */
+static session *servlist_sess;
+
+static void servlist_network_row_cb (GtkTreeSelection *sel, gpointer user_data);
+static GtkWidget *servlist_open_edit (GtkWidget *parent, ircnet *net);
+
+
+static const char *pages[]=
+{
+	"UTF-8 (Unicode)",
+	"IRC (Latin/Unicode Hybrid)",
+	"ISO-8859-15 (Western Europe)",
+	"ISO-8859-2 (Central Europe)",
+	"ISO-8859-7 (Greek)",
+	"ISO-8859-8 (Hebrew)",
+	"ISO-8859-9 (Turkish)",
+	"ISO-2022-JP (Japanese)",
+	"SJIS (Japanese)",
+	"CP949 (Korean)",
+	"KOI8-R (Cyrillic)",
+	"CP1251 (Cyrillic)",
+	"CP1256 (Arabic)",
+	"CP1257 (Baltic)",
+	"GB18030 (Chinese)",
+	"TIS-620 (Thai)",
+	NULL
+};
+
+static void
+servlist_select_and_show (GtkTreeView *treeview, GtkTreeIter *iter,
+								  GtkListStore *store)
+{
+	GtkTreePath *path;
+	GtkTreeSelection *sel;
+
+	sel = gtk_tree_view_get_selection (treeview);
+
+	/* select this network */
+	gtk_tree_selection_select_iter (sel, iter);
+	/* and make sure it's visible */
+	path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter);
+	if (path)
+	{
+		gtk_tree_view_scroll_to_cell (treeview, path, NULL, TRUE, 0.5, 0.5);
+		gtk_tree_view_set_cursor (treeview, path, NULL, FALSE);
+		gtk_tree_path_free (path);
+	}
+}
+
+static void
+servlist_servers_populate (ircnet *net, GtkWidget *treeview)
+{
+	GtkListStore *store;
+	GtkTreeIter iter;
+	int i;
+	ircserver *serv;
+	GSList *list = net->servlist;
+
+	store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (treeview));
+	gtk_list_store_clear (store);
+
+	i = 0;
+	while (list)
+	{
+		serv = list->data;
+		gtk_list_store_append (store, &iter);
+		gtk_list_store_set (store, &iter, 0, serv->hostname, 1, 1, -1);
+
+		if (net->selected == i)
+			/* select this server */
+			servlist_select_and_show (GTK_TREE_VIEW (treeview), &iter, store);
+
+		i++;
+		list = list->next;
+	}
+}
+
+static void
+servlist_networks_populate_ (GtkWidget *treeview, GSList *netlist, gboolean favorites)
+{
+	GtkListStore *store;
+	GtkTreeIter iter;
+	int i;
+	ircnet *net;
+
+	if (!netlist)
+	{
+		net = servlist_net_add (_("New Network"), "", FALSE);
+		servlist_server_add (net, "newserver/6667");
+		netlist = network_list;
+	}
+	store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (treeview));
+	gtk_list_store_clear (store);
+
+	i = 0;
+	while (netlist)
+	{
+		net = netlist->data;
+		if (!favorites || (net->flags & FLAG_FAVORITE))
+		{
+			if (favorites)
+				gtk_list_store_insert_with_values (store, &iter, 0x7fffffff, 0, net->name, 1, 1, 2, 400, -1);
+			else
+				gtk_list_store_insert_with_values (store, &iter, 0x7fffffff, 0, net->name, 1, 1, 2, (net->flags & FLAG_FAVORITE) ? 800 : 400, -1);
+			if (i == prefs.slist_select)
+			{
+				/* select this network */
+				servlist_select_and_show (GTK_TREE_VIEW (treeview), &iter, store);
+				selected_net = net;
+			}
+		}
+		i++;
+		netlist = netlist->next;
+	}
+}
+
+static void
+servlist_networks_populate (GtkWidget *treeview, GSList *netlist)
+{
+	servlist_networks_populate_ (treeview, netlist, prefs.slist_fav);
+}
+
+static void
+servlist_server_row_cb (GtkTreeSelection *sel, gpointer user_data)
+{
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	ircserver *serv;
+	char *servname;
+	int pos;
+
+	if (!selected_net)
+		return;
+
+	if (gtk_tree_selection_get_selected (sel, &model, &iter))
+	{
+		gtk_tree_model_get (model, &iter, 0, &servname, -1);
+		serv = servlist_server_find (selected_net, servname, &pos);
+		g_free (servname);
+		if (serv)
+			selected_net->selected = pos;
+		selected_serv = serv;
+	}
+}
+
+static void
+servlist_start_editing (GtkTreeView *tree)
+{
+	GtkTreeSelection *sel;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	GtkTreePath *path;
+
+	sel = gtk_tree_view_get_selection (tree);
+
+	if (gtk_tree_selection_get_selected (sel, &model, &iter))
+	{
+		path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+		if (path)
+		{
+			gtk_tree_view_set_cursor (tree, path,
+									gtk_tree_view_get_column (tree, 0), TRUE);
+			gtk_tree_path_free (path);
+		}
+	}
+}
+
+static void
+servlist_addserver_cb (GtkWidget *item, GtkWidget *treeview)
+{
+	GtkTreeIter iter;
+	GtkListStore *store;
+
+	if (!selected_net)
+		return;
+
+	store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (treeview));
+	servlist_server_add (selected_net, "newserver/6667");
+
+	gtk_list_store_append (store, &iter);
+	gtk_list_store_set (store, &iter, 0, "newserver/6667", 1, 1, -1);
+
+	/* select this server */
+	servlist_select_and_show (GTK_TREE_VIEW (treeview), &iter, store);
+	/*servlist_start_editing (GTK_TREE_VIEW (treeview));*/
+
+	servlist_server_row_cb (gtk_tree_view_get_selection (GTK_TREE_VIEW (networks_tree)), NULL);
+}
+
+static void
+servlist_addnet_cb (GtkWidget *item, GtkTreeView *treeview)
+{
+	GtkTreeIter iter;
+	GtkListStore *store;
+	ircnet *net;
+
+	net = servlist_net_add (_("New Network"), "", TRUE);
+	net->encoding = strdup ("IRC (Latin/Unicode Hybrid)");
+	servlist_server_add (net, "newserver/6667");
+
+	store = (GtkListStore *)gtk_tree_view_get_model (treeview);
+	gtk_list_store_prepend (store, &iter);
+	gtk_list_store_set (store, &iter, 0, net->name, 1, 1, -1);
+
+	/* select this network */
+	servlist_select_and_show (GTK_TREE_VIEW (networks_tree), &iter, store);
+	servlist_start_editing (GTK_TREE_VIEW (networks_tree));
+
+	servlist_network_row_cb (gtk_tree_view_get_selection (GTK_TREE_VIEW (networks_tree)), NULL);
+}
+
+static void
+servlist_deletenetwork (ircnet *net)
+{
+	GtkTreeSelection *sel;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+
+	/* remove from GUI */
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (networks_tree));
+	if (gtk_tree_selection_get_selected (sel, &model, &iter))
+		gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+
+	/* remove from list */
+	servlist_net_remove (net);
+
+	/* force something to be selected */
+	gtk_tree_model_get_iter_first (model, &iter);
+	servlist_select_and_show (GTK_TREE_VIEW (networks_tree), &iter,
+									  GTK_LIST_STORE (model));
+	servlist_network_row_cb (sel, NULL);
+}
+
+static void
+servlist_deletenetdialog_cb (GtkDialog *dialog, gint arg1, ircnet *net)
+{
+	gtk_widget_destroy (GTK_WIDGET (dialog));
+	if (arg1 == GTK_RESPONSE_OK)
+		servlist_deletenetwork (net);
+}
+
+static void
+servlist_move_server (ircserver *serv, int delta)
+{
+	int pos;
+
+	pos = g_slist_index (selected_net->servlist, serv);
+	if (pos >= 0)
+	{
+		pos += delta;
+		if (pos >= 0)
+		{
+			selected_net->servlist = g_slist_remove (selected_net->servlist, serv);
+			selected_net->servlist = g_slist_insert (selected_net->servlist, serv, pos);
+			servlist_servers_populate (selected_net, edit_tree);
+		}
+	}
+}
+
+static void
+servlist_move_network (ircnet *net, int delta)
+{
+	int pos;
+
+	pos = g_slist_index (network_list, net);
+	if (pos >= 0)
+	{
+		pos += delta;
+		if (pos >= 0)
+		{
+			/*prefs.slist_select += delta;*/
+			network_list = g_slist_remove (network_list, net);
+			network_list = g_slist_insert (network_list, net, pos);
+			servlist_networks_populate (networks_tree, network_list);
+		}
+	}
+}
+
+static gboolean
+servlist_net_keypress_cb (GtkWidget *wid, GdkEventKey *evt, gpointer tree)
+{
+	if (!selected_net)
+		return FALSE;
+
+	if (evt->state & GDK_SHIFT_MASK)
+	{
+		if (evt->keyval == GDK_Up)
+		{
+			servlist_move_network (selected_net, -1);
+		}
+		else if (evt->keyval == GDK_Down)
+		{
+			servlist_move_network (selected_net, +1);
+		}
+	}
+
+	return FALSE;
+}
+
+static gboolean
+servlist_serv_keypress_cb (GtkWidget *wid, GdkEventKey *evt, gpointer userdata)
+{
+	if (!selected_net || !selected_serv)
+		return FALSE;
+
+	if (evt->state & GDK_SHIFT_MASK)
+	{
+		if (evt->keyval == GDK_Up)
+		{
+			servlist_move_server (selected_serv, -1);
+		}
+		else if (evt->keyval == GDK_Down)
+		{
+			servlist_move_server (selected_serv, +1);
+		}
+	}
+
+	return FALSE;
+}
+
+static gint
+servlist_compare (ircnet *net1, ircnet *net2)
+{
+	gchar *net1_casefolded, *net2_casefolded;
+	int result=0;
+
+	net1_casefolded=g_utf8_casefold(net1->name,-1),
+	net2_casefolded=g_utf8_casefold(net2->name,-1),
+
+	result=g_utf8_collate(net1_casefolded,net2_casefolded);
+
+	g_free(net1_casefolded);
+	g_free(net2_casefolded);
+
+	return result;
+
+}
+
+static void
+servlist_sort (GtkWidget *button, gpointer none)
+{
+	network_list=g_slist_sort(network_list,(GCompareFunc)servlist_compare);
+	servlist_networks_populate (networks_tree, network_list);
+}
+
+static gboolean
+servlist_has_selection (GtkTreeView *tree)
+{
+	GtkTreeSelection *sel;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+
+	/* make sure something is selected */
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
+	return gtk_tree_selection_get_selected (sel, &model, &iter);
+}
+
+static void
+servlist_favor (GtkWidget *button, gpointer none)
+{
+	GtkTreeSelection *sel;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+
+	if (!selected_net)
+		return;
+
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (networks_tree));
+	if (gtk_tree_selection_get_selected (sel, &model, &iter))
+	{
+		if (selected_net->flags & FLAG_FAVORITE)
+		{
+			gtk_list_store_set (GTK_LIST_STORE (model), &iter, 2, 400, -1);
+			selected_net->flags &= ~FLAG_FAVORITE;
+		}
+		else
+		{
+			gtk_list_store_set (GTK_LIST_STORE (model), &iter, 2, 800, -1);
+			selected_net->flags |= FLAG_FAVORITE;
+		}
+	}
+}
+
+static void
+servlist_update_from_entry (char **str, GtkWidget *entry)
+{
+	if (*str)
+		free (*str);
+
+	if (GTK_ENTRY (entry)->text[0] == 0)
+		*str = NULL;
+	else
+		*str = strdup (GTK_ENTRY (entry)->text);
+}
+
+static void
+servlist_edit_update (ircnet *net)
+{
+	servlist_update_from_entry (&net->nick, edit_entry_nick);
+	servlist_update_from_entry (&net->nick2, edit_entry_nick2);
+	servlist_update_from_entry (&net->user, edit_entry_user);
+	servlist_update_from_entry (&net->real, edit_entry_real);
+
+	servlist_update_from_entry (&net->autojoin, edit_entry_join);
+	servlist_update_from_entry (&net->command, edit_entry_cmd);
+	servlist_update_from_entry (&net->nickserv, edit_entry_nickserv);
+	servlist_update_from_entry (&net->pass, edit_entry_pass);
+}
+
+static void
+servlist_edit_close_cb (GtkWidget *button, gpointer userdata)
+{
+	if (selected_net)
+		servlist_edit_update (selected_net);
+
+	gtk_widget_destroy (edit_win);
+	edit_win = NULL;
+}
+
+static gint
+servlist_editwin_delete_cb (GtkWidget *win, GdkEventAny *event, gpointer none)
+{
+	servlist_edit_close_cb (NULL, NULL);
+	return FALSE;
+}
+
+static gboolean
+servlist_configure_cb (GtkWindow *win, GdkEventConfigure *event, gpointer none)
+{
+	/* remember the window size */
+	gtk_window_get_size (win, &win_width, &win_height);
+	return FALSE;
+}
+
+static void
+servlist_edit_cb (GtkWidget *but, gpointer none)
+{
+	if (!servlist_has_selection (GTK_TREE_VIEW (networks_tree)))
+		return;
+
+	edit_win = servlist_open_edit (serverlist_win, selected_net);
+	gtkutil_set_icon (edit_win);
+	servlist_servers_populate (selected_net, edit_tree);
+	g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (edit_tree))),
+							"changed", G_CALLBACK (servlist_server_row_cb), NULL);
+	g_signal_connect (G_OBJECT (edit_win), "delete_event",
+						 	G_CALLBACK (servlist_editwin_delete_cb), 0);
+	g_signal_connect (G_OBJECT (edit_tree), "key_press_event",
+							G_CALLBACK (servlist_serv_keypress_cb), 0);
+	gtk_widget_show (edit_win);
+}
+
+static void
+servlist_deletenet_cb (GtkWidget *item, ircnet *net)
+{
+	GtkWidget *dialog;
+
+	if (!servlist_has_selection (GTK_TREE_VIEW (networks_tree)))
+		return;
+
+	net = selected_net;
+	if (!net)
+		return;
+	dialog = gtk_message_dialog_new (GTK_WINDOW (serverlist_win),
+												GTK_DIALOG_DESTROY_WITH_PARENT |
+												GTK_DIALOG_MODAL,
+												GTK_MESSAGE_QUESTION,
+												GTK_BUTTONS_OK_CANCEL,
+							_("Really remove network \"%s\" and all its servers?"),
+												net->name);
+	g_signal_connect (dialog, "response",
+							G_CALLBACK (servlist_deletenetdialog_cb), net);
+	gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
+	gtk_widget_show (dialog);
+}
+
+static void
+servlist_deleteserver (ircserver *serv, GtkTreeModel *model)
+{
+	GtkTreeSelection *sel;
+	GtkTreeIter iter;
+
+	/* don't remove the last server */
+	if (selected_net && g_slist_length (selected_net->servlist) < 2)
+		return;
+
+	/* remove from GUI */
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (edit_tree));
+	if (gtk_tree_selection_get_selected (sel, &model, &iter))
+		gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+
+	/* remove from list */
+	if (selected_net)
+		servlist_server_remove (selected_net, serv);
+}
+
+static void
+servlist_editserverbutton_cb (GtkWidget *item, gpointer none)
+{
+	servlist_start_editing (GTK_TREE_VIEW (edit_tree));
+}
+
+static void
+servlist_deleteserver_cb (GtkWidget *item, gpointer none)
+{
+	GtkTreeSelection *sel;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	char *servname;
+	ircserver *serv;
+	int pos;
+
+	/* find the selected item in the GUI */
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (edit_tree));
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (edit_tree));
+
+	if (gtk_tree_selection_get_selected (sel, &model, &iter))
+	{
+		gtk_tree_model_get (model, &iter, 0, &servname, -1);
+		serv = servlist_server_find (selected_net, servname, &pos);
+		g_free (servname);
+		if (serv)
+			servlist_deleteserver (serv, model);
+	}
+}
+
+static ircnet *
+servlist_find_selected_net (GtkTreeSelection *sel)
+{
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	char *netname;
+	int pos;
+	ircnet *net = NULL;
+
+	if (gtk_tree_selection_get_selected (sel, &model, &iter))
+	{
+		gtk_tree_model_get (model, &iter, 0, &netname, -1);
+		net = servlist_net_find (netname, &pos, strcmp);
+		g_free (netname);
+		if (net)
+			prefs.slist_select = pos;
+	}
+
+	return net;
+}
+
+static void
+servlist_network_row_cb (GtkTreeSelection *sel, gpointer user_data)
+{
+	ircnet *net;
+
+	selected_net = NULL;
+
+	net = servlist_find_selected_net (sel);
+	if (net)
+		selected_net = net;
+}
+
+static int
+servlist_savegui (void)
+{
+	char *sp;
+
+	/* check for blank username, ircd will not allow this */
+	if (GTK_ENTRY (entry_guser)->text[0] == 0)
+		return 1;
+
+	if (GTK_ENTRY (entry_greal)->text[0] == 0)
+		return 1;
+
+	strcpy (prefs.nick1, GTK_ENTRY (entry_nick1)->text);
+	strcpy (prefs.nick2, GTK_ENTRY (entry_nick2)->text);
+	strcpy (prefs.nick3, GTK_ENTRY (entry_nick3)->text);
+	strcpy (prefs.username, GTK_ENTRY (entry_guser)->text);
+	sp = strchr (prefs.username, ' ');
+	if (sp)
+		sp[0] = 0;	/* spaces will break the login */
+	strcpy (prefs.realname, GTK_ENTRY (entry_greal)->text);
+	servlist_save ();
+
+	return 0;
+}
+
+static gboolean
+servlist_get_iter_from_name (GtkTreeModel *model, gchar *name, GtkTreeIter *iter)
+{
+	GtkTreePath *path = gtk_tree_path_new_from_string (name);
+
+	if (!gtk_tree_model_get_iter (model, iter, path))
+	{
+		gtk_tree_path_free (path);
+		return FALSE;
+	}
+
+	gtk_tree_path_free (path);
+	return TRUE;
+}
+
+static void
+servlist_editchannel_cb (GtkCellRendererText *cell, gchar *name, gchar *newval, GtkTreeModel *model)
+{
+	GtkTreeIter iter;
+	static int loop_guard = FALSE;
+
+	if (loop_guard)
+		return;
+
+	if (!servlist_get_iter_from_name (model, name, &iter))
+		return;
+
+	loop_guard = TRUE;
+	/* delete empty item */
+	if (newval[0] == 0)
+		gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+	else
+		gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, newval, -1);
+	loop_guard = FALSE;
+}
+
+static void
+servlist_editkey_cb (GtkCellRendererText *cell, gchar *name, gchar *newval, GtkTreeModel *model)
+{
+	GtkTreeIter iter;
+
+	if (!servlist_get_iter_from_name (model, name, &iter))
+		return;
+
+	gtk_list_store_set (GTK_LIST_STORE (model), &iter, 1, newval, -1);
+}
+
+static void
+servlist_addchannel (GtkWidget *tree, char *channel)
+{
+	GtkTreeIter iter;
+	GtkListStore *store;
+
+	store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (tree));
+
+	gtk_list_store_append (store, &iter);
+	gtk_list_store_set (store, &iter, 0, channel, 1, "", 2, TRUE, -1);
+
+	/* select this server */
+	servlist_select_and_show (GTK_TREE_VIEW (tree), &iter, store);
+	servlist_start_editing (GTK_TREE_VIEW (tree));
+}
+
+static void
+servlist_addchannel_cb (GtkWidget *item, GtkWidget *tree)
+{
+	servlist_addchannel (tree, _("#channel"));
+}
+
+static void
+servlist_deletechannel_cb (GtkWidget *item, GtkWidget *tree)
+{
+	GtkTreeSelection *sel;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+
+	/* find the selected item in the GUI */
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree));
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
+
+	if (gtk_tree_selection_get_selected (sel, &model, &iter))
+		gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+}
+
+static void
+servlist_editchannelbutton_cb (GtkWidget *item, GtkWidget *tree)
+{
+	servlist_start_editing (GTK_TREE_VIEW (tree));
+}
+
+/* save everything from the GUI to the GtkEntry */
+
+static void
+servlist_autojoineditok_cb (GtkWidget *button, GtkWidget *tree)
+{
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	char *channel, *key;
+	char *autojoin;
+	GSList *channels = NULL, *keys = NULL;
+
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree));
+
+	if (gtk_tree_model_get_iter_first (model, &iter))
+	{
+		do
+		{
+			gtk_tree_model_get (model, &iter, 0, &channel, 1, &key, -1);
+			channels = g_slist_append (channels, channel);
+			if (key && key[0] == 0)
+			{
+				/* NULL out empty keys */
+				g_free (key);
+				keys = g_slist_append (keys, NULL);				
+			}
+			else
+				keys = g_slist_append (keys, key);
+		}
+		while (gtk_tree_model_iter_next (model, &iter));
+	}
+
+	gtk_widget_destroy (gtk_widget_get_toplevel (button));
+
+	autojoin = joinlist_merge (channels, keys);
+	if (autojoin)
+	{
+		if (edit_win && selected_net)
+			gtk_entry_set_text (GTK_ENTRY (edit_entry_join), autojoin);
+		else
+		{
+			if (fav_add_net->autojoin)
+				free (fav_add_net->autojoin);
+			fav_add_net->autojoin = strdup (autojoin);
+		}
+		g_free (autojoin);
+	}
+
+	/* this does g_free too */
+	joinlist_free (channels, keys);
+
+	if (fav_add_net)
+		servlist_save ();
+}
+
+void
+servlist_autojoinedit (ircnet *net, char *channel, gboolean add)
+{
+	GtkWidget *win;
+	GtkWidget *scrolledwindow;
+	GtkTreeModel *model;
+	GtkListStore *store;
+	GtkCellRenderer *renderer;
+	GtkWidget *tree;
+	GtkWidget *table;
+	GtkWidget *label;
+	GtkWidget *label2;
+	GtkWidget *bbox;
+	GtkWidget *wid;
+
+	GtkWidget *vbuttonbox1;
+	GtkWidget *buttonadd;
+	GtkWidget *buttonremove;
+	GtkWidget *buttonedit;
+
+	char buf[128];
+	char lab[128];
+	GSList *channels, *keys;
+	GSList *clist, *klist;
+	GtkTreeIter iter;
+
+	if (edit_win && selected_net)
+		/* update net->autojoin */
+		servlist_edit_update (selected_net);
+
+	win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+	gtk_container_set_border_width (GTK_CONTAINER (win), 4);
+	gtk_window_set_title (GTK_WINDOW (win), _("XChat: Favorite Channels (Auto-Join List)"));
+	gtk_window_set_default_size (GTK_WINDOW (win), 354, 256);
+	gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_MOUSE);
+	if (edit_win)
+		gtk_window_set_transient_for (GTK_WINDOW (win), GTK_WINDOW (edit_win));
+	gtk_window_set_modal (GTK_WINDOW (win), TRUE);
+	gtk_window_set_type_hint (GTK_WINDOW (win), GDK_WINDOW_TYPE_HINT_DIALOG);
+	gtk_window_set_role (GTK_WINDOW (win), "editserv");
+
+	table = gtk_table_new (1, 1, FALSE);
+	gtk_container_add (GTK_CONTAINER (win), table);
+	gtk_widget_show (table);
+
+	snprintf (buf, sizeof (buf), _("These channels will be joined whenever you connect to %s."), net->name);
+	label = gtk_label_new (buf);
+	gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER);
+	gtk_table_attach (GTK_TABLE (table), label, 0, 2, 0, 1, GTK_FILL|GTK_EXPAND, GTK_FILL, 3, 3);
+	gtk_widget_show (label);
+
+	label2 = gtk_label_new ("");
+	gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_CENTER);
+	gtk_table_attach (GTK_TABLE (table), label2, 0, 2, 1, 2, GTK_FILL, 0, 3, 3);
+	gtk_widget_show (label2);
+
+	scrolledwindow = gtk_scrolled_window_new (NULL, NULL);
+	gtk_table_attach (GTK_TABLE (table), scrolledwindow, 0, 1, 2, 3, GTK_FILL|GTK_EXPAND, GTK_FILL|GTK_EXPAND, 0, 0);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow),
+											  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow),
+													 GTK_SHADOW_IN);
+	gtk_widget_show (scrolledwindow);
+
+	store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
+	model = GTK_TREE_MODEL (store);
+
+	tree = gtk_tree_view_new_with_model (model);
+	g_object_unref (model);
+	gtk_container_add (GTK_CONTAINER (scrolledwindow), tree);
+	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree), TRUE);
+	gtk_widget_show (tree);
+
+	renderer = gtk_cell_renderer_text_new ();
+	g_signal_connect (G_OBJECT (renderer), "edited",
+							G_CALLBACK (servlist_editchannel_cb), model);
+	gtk_tree_view_insert_column_with_attributes (
+								GTK_TREE_VIEW (tree), -1,
+						 		_("Channel"), renderer,
+						 		"text", 0,
+								"editable", 2,
+								NULL);
+
+	renderer = gtk_cell_renderer_text_new ();
+	g_signal_connect (G_OBJECT (renderer), "edited",
+							G_CALLBACK (servlist_editkey_cb), model);
+	gtk_tree_view_insert_column_with_attributes (
+								GTK_TREE_VIEW (tree), -1,
+						 		_("Key (Password)"), renderer,
+						 		"text", 1,
+								"editable", 2,
+								NULL);
+
+	gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (tree), 0), TRUE);
+	gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (tree), 1), TRUE);
+
+	gtk_tree_sortable_set_sort_column_id ((GtkTreeSortable *)model, 0, GTK_SORT_ASCENDING);
+
+	/* ===BUTTONS=== */
+	vbuttonbox1 = gtk_vbutton_box_new ();
+	gtk_box_set_spacing (GTK_BOX (vbuttonbox1), 3);
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (vbuttonbox1), GTK_BUTTONBOX_START);
+	gtk_widget_show (vbuttonbox1);
+	gtk_table_attach (GTK_TABLE (table), vbuttonbox1, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 3, 0);
+
+	buttonadd = gtk_button_new_from_stock ("gtk-add");
+	g_signal_connect (G_OBJECT (buttonadd), "clicked",
+							G_CALLBACK (servlist_addchannel_cb), tree);
+	gtk_widget_show (buttonadd);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonadd);
+	GTK_WIDGET_SET_FLAGS (buttonadd, GTK_CAN_DEFAULT);
+
+	buttonremove = gtk_button_new_from_stock ("gtk-remove");
+	g_signal_connect (G_OBJECT (buttonremove), "clicked",
+							G_CALLBACK (servlist_deletechannel_cb), tree);
+	gtk_widget_show (buttonremove);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonremove);
+	GTK_WIDGET_SET_FLAGS (buttonremove, GTK_CAN_DEFAULT);
+
+	buttonedit = gtk_button_new_with_mnemonic (_("_Edit"));
+	g_signal_connect (G_OBJECT (buttonedit), "clicked",
+							G_CALLBACK (servlist_editchannelbutton_cb), tree);
+	gtk_widget_show (buttonedit);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonedit);
+	GTK_WIDGET_SET_FLAGS (buttonedit, GTK_CAN_DEFAULT);
+
+	bbox = gtk_hbutton_box_new ();
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_END);
+	gtk_box_set_spacing (GTK_BOX (bbox), 4);
+	gtk_table_attach (GTK_TABLE (table), bbox, 0, 1, 3, 4, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 4);
+	gtk_widget_show (bbox);
+
+	wid = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
+	g_signal_connect (G_OBJECT (wid), "clicked", G_CALLBACK (gtkutil_destroy), win);
+	gtk_container_add (GTK_CONTAINER (bbox), wid);
+	gtk_widget_show (wid);
+
+	wid = gtk_button_new_from_stock (GTK_STOCK_OK);
+	g_signal_connect (G_OBJECT (wid), "clicked", G_CALLBACK (servlist_autojoineditok_cb), tree);
+	gtk_container_add (GTK_CONTAINER (bbox), wid);
+	gtk_widget_show (wid);
+	gtk_widget_grab_focus (wid);
+	/* =========== */
+
+	if (net->autojoin)
+	{
+		joinlist_split (net->autojoin, &channels, &keys);
+
+		clist = channels;
+		klist = keys;
+
+		while (clist)
+		{
+			if (channel && !add && !rfc_casecmp (channel, clist->data))
+			{
+				snprintf (buf, sizeof (buf), _("%s has been removed."), channel);
+				snprintf (lab, sizeof (lab), "<span foreground=\"#2222DD\">%s</span>", buf);
+				gtk_label_set_markup (GTK_LABEL (label2), lab);
+			}
+			else
+			{
+				gtk_list_store_append (store, &iter);
+				gtk_list_store_set (store, &iter, 0, clist->data, 1, klist->data, 2, TRUE, -1);
+			}
+
+			klist = klist->next;
+			clist = clist->next;
+		}
+
+		joinlist_free (channels, keys);
+	}
+
+	if (channel && add)
+	{
+		servlist_addchannel (tree, channel);
+		snprintf (buf, sizeof (buf), _("%s has been added."), channel);
+		snprintf (lab, sizeof (lab), "<span foreground=\"#2222DD\">%s</span>", buf);
+		gtk_label_set_markup (GTK_LABEL (label2), lab);
+	}
+
+	fav_add_net = net;
+
+	gtk_widget_show (win);
+}
+
+static void
+servlist_autojoinedit_cb (GtkWidget *button, ircnet *net)
+{
+	servlist_autojoinedit (net, NULL, FALSE);
+}
+
+static void
+servlist_connect_cb (GtkWidget *button, gpointer userdata)
+{
+	if (!selected_net)
+		return;
+
+	if (servlist_savegui () != 0)
+	{
+		fe_message (_("User name and Real name cannot be left blank."), FE_MSG_ERROR);
+		return;
+	}
+
+ 	if (!is_session (servlist_sess))
+		servlist_sess = NULL;	/* open a new one */
+
+	{
+		GSList *list;
+		session *sess;
+		session *chosen = servlist_sess;
+
+		servlist_sess = NULL;	/* open a new one */
+
+		for (list = sess_list; list; list = list->next)
+		{
+			sess = list->data;
+			if (sess->server->network == selected_net)
+			{
+				servlist_sess = sess;
+				if (sess->server->connected)
+					servlist_sess = NULL;	/* open a new one */
+				break;
+			}
+		}
+
+		/* use the chosen one, if it's empty */
+		if (!servlist_sess &&
+			  chosen &&
+			 !chosen->server->connected &&
+			  chosen->server->server_session->channel[0] == 0)
+		{
+			servlist_sess = chosen;
+		}
+	}
+
+	servlist_connect (servlist_sess, selected_net, TRUE);
+
+	gtk_widget_destroy (serverlist_win);
+	serverlist_win = NULL;
+	selected_net = NULL;
+}
+
+static void
+servlist_celledit_cb (GtkCellRendererText *cell, gchar *arg1, gchar *arg2,
+							 gpointer user_data)
+{
+	GtkTreeModel *model = (GtkTreeModel *)user_data;
+	GtkTreeIter iter;
+	GtkTreePath *path;
+	char *netname;
+	ircnet *net;
+
+	if (!arg1 || !arg2)
+		return;
+
+	path = gtk_tree_path_new_from_string (arg1);
+	if (!path)
+		return;
+
+	if (!gtk_tree_model_get_iter (model, &iter, path))
+	{
+		gtk_tree_path_free (path);
+		return;
+	}
+	gtk_tree_model_get (model, &iter, 0, &netname, -1);
+
+	net = servlist_net_find (netname, NULL, strcmp);
+	g_free (netname);
+	if (net)
+	{
+		/* delete empty item */
+		if (arg2[0] == 0)
+		{
+			servlist_deletenetwork (net);
+			gtk_tree_path_free (path);
+			return;
+		}
+
+		netname = net->name;
+		net->name = strdup (arg2);
+		gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, net->name, -1);
+		free (netname);
+	}
+
+	gtk_tree_path_free (path);
+}
+
+static void
+servlist_check_cb (GtkWidget *but, gpointer num_p)
+{
+	int num = GPOINTER_TO_INT (num_p);
+
+	if (!selected_net)
+		return;
+
+	if ((1 << num) == FLAG_CYCLE || (1 << num) == FLAG_USE_PROXY)
+	{
+		/* these ones are reversed, so it's compat with 2.0.x */
+		if (GTK_TOGGLE_BUTTON (but)->active)
+			selected_net->flags &= ~(1 << num);
+		else
+			selected_net->flags |= (1 << num);
+	} else
+	{
+		if (GTK_TOGGLE_BUTTON (but)->active)
+			selected_net->flags |= (1 << num);
+		else
+			selected_net->flags &= ~(1 << num);
+	}
+
+	if ((1 << num) == FLAG_USE_GLOBAL)
+	{
+		if (GTK_TOGGLE_BUTTON (but)->active)
+		{
+			gtk_widget_hide (edit_label_nick);
+			gtk_widget_hide (edit_entry_nick);
+
+			gtk_widget_hide (edit_label_nick2);
+			gtk_widget_hide (edit_entry_nick2);
+
+			gtk_widget_hide (edit_label_user);
+			gtk_widget_hide (edit_entry_user);
+
+			gtk_widget_hide (edit_label_real);
+			gtk_widget_hide (edit_entry_real);
+		} else
+		{
+			gtk_widget_show (edit_label_nick);
+			gtk_widget_show (edit_entry_nick);
+
+			gtk_widget_show (edit_label_nick2);
+			gtk_widget_show (edit_entry_nick2);
+
+			gtk_widget_show (edit_label_user);
+			gtk_widget_show (edit_entry_user);
+
+			gtk_widget_show (edit_label_real);
+			gtk_widget_show (edit_entry_real);
+		}
+	}
+}
+
+static GtkWidget *
+servlist_create_check (int num, int state, GtkWidget *table, int row, int col, char *labeltext)
+{
+	GtkWidget *but;
+
+	but = gtk_check_button_new_with_label (labeltext);
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (but), state);
+	g_signal_connect (G_OBJECT (but), "toggled",
+							G_CALLBACK (servlist_check_cb), GINT_TO_POINTER (num));
+	gtk_table_attach (GTK_TABLE (table), but, col, col+2, row, row+1,
+						   GTK_FILL|GTK_EXPAND, 0, 0, 0);
+	gtk_widget_show (but);
+
+	return but;
+}
+
+static GtkWidget *
+servlist_create_entry (GtkWidget *table, char *labeltext, int row,
+							  char *def, GtkWidget **label_ret, char *tip)
+{
+	GtkWidget *label, *entry;
+
+	label = gtk_label_new_with_mnemonic (labeltext);
+	if (label_ret)
+		*label_ret = label;
+	gtk_widget_show (label);
+	gtk_table_attach (GTK_TABLE (table), label, 1, 2, row, row+1,
+							GTK_FILL, 0, 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+
+	entry = gtk_entry_new ();
+	add_tip (entry, tip);
+	gtk_widget_show (entry);
+	gtk_entry_set_text (GTK_ENTRY (entry), def ? def : "");
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
+
+	if (row == 15)	/* for "Channels to Join:" */
+	{
+		GtkWidget *button, *box;
+
+		box = gtk_hbox_new (0, 0);
+		button = gtk_button_new_with_label ("...");
+		g_signal_connect (G_OBJECT (button), "clicked",
+								G_CALLBACK (servlist_autojoinedit_cb), selected_net);
+
+		gtk_box_pack_start (GTK_BOX (box), entry, TRUE, TRUE, 0);
+		gtk_box_pack_end (GTK_BOX (box), button, 0, 0, 0);
+		gtk_widget_show_all (box);
+
+		gtk_table_attach (GTK_TABLE (table), box, 2, 3, row, row+1,
+								GTK_FILL|GTK_EXPAND, 0, 0, 0);
+	}
+	else
+	{
+		gtk_table_attach (GTK_TABLE (table), entry, 2, 3, row, row+1,
+								GTK_FILL|GTK_EXPAND, 0, 0, 0);
+	}
+
+	return entry;
+}
+
+static gint
+servlist_delete_cb (GtkWidget *win, GdkEventAny *event, gpointer userdata)
+{
+	servlist_savegui ();
+	serverlist_win = NULL;
+	selected_net = NULL;
+
+	if (sess_list == NULL)
+		xchat_exit ();
+
+	return FALSE;
+}
+
+static void
+servlist_close_cb (GtkWidget *button, gpointer userdata)
+{
+	servlist_savegui ();
+	gtk_widget_destroy (serverlist_win);
+	serverlist_win = NULL;
+	selected_net = NULL;
+
+	if (sess_list == NULL)
+		xchat_exit ();
+}
+
+/* convert "host:port" format to "host/port" */
+
+static char *
+servlist_sanitize_hostname (char *host)
+{
+	char *ret, *c, *e;
+
+	ret = strdup (host);
+
+	c = strchr  (ret, ':');
+	e = strrchr (ret, ':');
+
+	/* if only one colon exists it's probably not IPv6 */
+	if (c && c == e)
+		*c = '/';
+
+	return ret;
+}
+
+static void
+servlist_editserver_cb (GtkCellRendererText *cell, gchar *arg1, gchar *arg2,
+								gpointer user_data)
+{
+	GtkTreeModel *model = (GtkTreeModel *)user_data;
+	GtkTreeIter iter;
+	GtkTreePath *path;
+	char *servname;
+	ircserver *serv;
+
+	if (!selected_net)
+		return;
+
+	path = gtk_tree_path_new_from_string (arg1);
+
+	if (!gtk_tree_model_get_iter (model, &iter, path))
+	{
+		gtk_tree_path_free (path);
+		return;
+	}
+
+	gtk_tree_model_get (model, &iter, 0, &servname, -1);
+	serv = servlist_server_find (selected_net, servname, NULL);
+	g_free (servname);
+
+	if (serv)
+	{
+		/* delete empty item */
+		if (arg2[0] == 0)
+		{
+			servlist_deleteserver (serv, model);
+			gtk_tree_path_free (path);
+			return;
+		}
+
+		servname = serv->hostname;
+		serv->hostname = servlist_sanitize_hostname (arg2);
+		gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, serv->hostname, -1);
+		free (servname);
+	}
+
+	gtk_tree_path_free (path);
+}
+
+static void
+servlist_combo_cb (GtkEntry *entry, gpointer userdata)
+{
+	if (!selected_net)
+		return;
+
+	if (!ignore_changed)
+	{
+		if (selected_net->encoding)
+			free (selected_net->encoding);
+		selected_net->encoding = strdup (entry->text);
+	}
+}
+
+static GtkWidget *
+servlist_create_charsetcombo (void)
+{
+	GtkWidget *cb;
+	int i;
+
+	cb = gtk_combo_box_entry_new_text ();
+	gtk_combo_box_append_text (GTK_COMBO_BOX (cb), "System default");
+	i = 0;
+	while (pages[i])
+	{
+		gtk_combo_box_append_text (GTK_COMBO_BOX (cb), (char *)pages[i]);
+		i++;
+	}
+	g_signal_connect (G_OBJECT (GTK_BIN (cb)->child), "changed",
+							G_CALLBACK (servlist_combo_cb), NULL);
+
+	return cb;
+}
+
+static void
+no_servlist (GtkWidget * igad, gpointer serv)
+{
+	if (GTK_TOGGLE_BUTTON (igad)->active)
+		prefs.slist_skip = TRUE;
+	else
+		prefs.slist_skip = FALSE;
+}
+
+static void
+fav_servlist (GtkWidget * igad, gpointer serv)
+{
+	if (GTK_TOGGLE_BUTTON (igad)->active)
+		prefs.slist_fav = TRUE;
+	else
+		prefs.slist_fav = FALSE;
+
+	servlist_networks_populate (networks_tree, network_list);
+}
+
+static GtkWidget *
+bold_label (char *text)
+{
+	char buf[128];
+	GtkWidget *label;
+
+	snprintf (buf, sizeof (buf), "<b>%s</b>", text);
+	label = gtk_label_new (buf);
+	gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+	gtk_widget_show (label);
+
+	return label;
+}
+
+static GtkWidget *
+servlist_open_edit (GtkWidget *parent, ircnet *net)
+{
+	GtkWidget *editwindow;
+	GtkWidget *vbox5;
+	GtkWidget *table3;
+	GtkWidget *label17;
+	GtkWidget *label16;
+	GtkWidget *label21;
+	GtkWidget *label34;
+	GtkWidget *comboboxentry_charset;
+	GtkWidget *hbox1;
+	GtkWidget *scrolledwindow2;
+	GtkWidget *treeview_servers;
+	GtkWidget *vbuttonbox1;
+	GtkWidget *buttonadd;
+	GtkWidget *buttonremove;
+	GtkWidget *buttonedit;
+	GtkWidget *hseparator2;
+	GtkWidget *hbuttonbox4;
+	GtkWidget *button10;
+	GtkWidget *check;
+	GtkTreeModel *model;
+	GtkListStore *store;
+	GtkCellRenderer *renderer;
+	char buf[128];
+	char buf2[128 + 8];
+
+	editwindow = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+	gtk_container_set_border_width (GTK_CONTAINER (editwindow), 4);
+	snprintf (buf, sizeof (buf), _("XChat: Edit %s"), net->name);
+	gtk_window_set_title (GTK_WINDOW (editwindow), buf);
+	gtk_window_set_default_size (GTK_WINDOW (editwindow), 354, 0);
+	gtk_window_set_position (GTK_WINDOW (editwindow), GTK_WIN_POS_MOUSE);
+	gtk_window_set_transient_for (GTK_WINDOW (editwindow), GTK_WINDOW (parent));
+	gtk_window_set_modal (GTK_WINDOW (editwindow), TRUE);
+	gtk_window_set_type_hint (GTK_WINDOW (editwindow), GDK_WINDOW_TYPE_HINT_DIALOG);
+	gtk_window_set_role (GTK_WINDOW (editwindow), "editserv");
+
+	vbox5 = gtk_vbox_new (FALSE, 0);
+	gtk_widget_show (vbox5);
+	gtk_container_add (GTK_CONTAINER (editwindow), vbox5);
+
+	table3 = gtk_table_new (17, 3, FALSE);
+	gtk_widget_show (table3);
+	gtk_box_pack_start (GTK_BOX (vbox5), table3, TRUE, TRUE, 0);
+	gtk_table_set_row_spacings (GTK_TABLE (table3), 2);
+	gtk_table_set_col_spacings (GTK_TABLE (table3), 8);
+
+	snprintf (buf, sizeof (buf), _("Servers for %s"), net->name);
+	snprintf (buf2, sizeof (buf2), "<b>%s</b>", buf);
+	label16 = gtk_label_new (buf2);
+	gtk_widget_show (label16);
+	gtk_table_attach (GTK_TABLE (table3), label16, 0, 3, 0, 1,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 3);
+	gtk_label_set_use_markup (GTK_LABEL (label16), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (label16), 0, 0.5);
+
+	check = servlist_create_check (0, !(net->flags & FLAG_CYCLE), table3,
+								  2, 1, _("Connect to selected server only"));
+	add_tip (check, _("Don't cycle through all the servers when the connection fails."));
+
+	label17 = bold_label (_("Your Details"));
+	gtk_table_attach (GTK_TABLE (table3), label17, 0, 3, 3, 4,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 3);
+
+	servlist_create_check (1, net->flags & FLAG_USE_GLOBAL, table3,
+								  4, 1, _("Use global user information"));
+
+	edit_entry_nick =
+		servlist_create_entry (table3, _("_Nick name:"), 5, net->nick,
+									  &edit_label_nick, 0);
+
+	edit_entry_nick2 =
+		servlist_create_entry (table3, _("Second choice:"), 6, net->nick2,
+									  &edit_label_nick2, 0);
+
+	edit_entry_user =
+		servlist_create_entry (table3, _("_User name:"), 7, net->user,
+									  &edit_label_user, 0);
+
+	edit_entry_real =
+		servlist_create_entry (table3, _("Rea_l name:"), 8, net->real,
+									  &edit_label_real, 0);
+
+	label21 = bold_label (_("Connecting"));
+	gtk_table_attach (GTK_TABLE (table3), label21, 0, 3, 9, 10,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 3);
+
+	servlist_create_check (3, net->flags & FLAG_AUTO_CONNECT, table3,
+								  11, 1, _("Auto connect to this network at startup"));
+	servlist_create_check (4, !(net->flags & FLAG_USE_PROXY), table3,
+								  12, 1, _("Bypass proxy server"));
+	check = servlist_create_check (2, net->flags & FLAG_USE_SSL, table3,
+								  13, 1, _("Use SSL for all the servers on this network"));
+#ifndef USE_OPENSSL
+	gtk_widget_set_sensitive (check, FALSE);
+#endif
+	check = servlist_create_check (5, net->flags & FLAG_ALLOW_INVALID, table3,
+								  14, 1, _("Accept invalid SSL certificate"));
+#ifndef USE_OPENSSL
+	gtk_widget_set_sensitive (check, FALSE);
+#endif
+
+	edit_entry_join =
+		servlist_create_entry (table3, _("_Favorite channels:"), 15,
+									  net->autojoin, 0,
+				  _("Channels to join, separated by commas, but not spaces!"));
+
+	edit_entry_cmd =
+		servlist_create_entry (table3, _("Connect command:"), 16,
+									  net->command, 0,
+					_("Extra command to execute after connecting. If you need more than one, set this to LOAD -e <filename>, where <filename> is a text-file full of commands to execute."));
+
+	edit_entry_nickserv =
+		servlist_create_entry (table3, _("Nickserv password:"), 17,
+									  net->nickserv, 0,
+					_("If your nickname requires a password, enter it here. Not all IRC networks support this."));
+	gtk_entry_set_visibility (GTK_ENTRY (edit_entry_nickserv), FALSE);
+
+	edit_entry_pass =
+		servlist_create_entry (table3, _("Server password:"), 18,
+									  net->pass, 0,
+					_("Password for the server, if in doubt, leave blank."));
+	gtk_entry_set_visibility (GTK_ENTRY (edit_entry_pass), FALSE);
+
+	label34 = gtk_label_new (_("Character set:"));
+	gtk_widget_show (label34);
+	gtk_table_attach (GTK_TABLE (table3), label34, 1, 2, 19, 20,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (label34), 0, 0.5);
+
+	comboboxentry_charset = servlist_create_charsetcombo ();
+	ignore_changed = TRUE;
+	gtk_entry_set_text (GTK_ENTRY (GTK_BIN (comboboxentry_charset)->child), net->encoding ? net->encoding : "System default");
+	ignore_changed = FALSE;
+	gtk_widget_show (comboboxentry_charset);
+	gtk_table_attach (GTK_TABLE (table3), comboboxentry_charset, 2, 3, 19, 20,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (GTK_FILL), 0, 0);
+
+	hbox1 = gtk_hbox_new (FALSE, 0);
+	gtk_widget_show (hbox1);
+	gtk_table_attach (GTK_TABLE (table3), hbox1, 1, 3, 1, 2,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0);
+
+	scrolledwindow2 = gtk_scrolled_window_new (NULL, NULL);
+	gtk_widget_show (scrolledwindow2);
+	gtk_box_pack_start (GTK_BOX (hbox1), scrolledwindow2, TRUE, TRUE, 0);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow2),
+											  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow2),
+													 GTK_SHADOW_IN);
+
+	store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN);
+	model = GTK_TREE_MODEL (store);
+
+	edit_tree = treeview_servers = gtk_tree_view_new_with_model (model);
+	g_object_unref (model);
+	gtk_widget_show (treeview_servers);
+	gtk_container_add (GTK_CONTAINER (scrolledwindow2), treeview_servers);
+	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview_servers),
+												  FALSE);
+
+	renderer = gtk_cell_renderer_text_new ();
+	g_signal_connect (G_OBJECT (renderer), "edited",
+							G_CALLBACK (servlist_editserver_cb), model);
+	gtk_tree_view_insert_column_with_attributes (
+								GTK_TREE_VIEW (treeview_servers), -1,
+						 		0, renderer,
+						 		"text", 0,
+								"editable", 1,
+								NULL);
+
+	vbuttonbox1 = gtk_vbutton_box_new ();
+	gtk_box_set_spacing (GTK_BOX (vbuttonbox1), 3);
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (vbuttonbox1), GTK_BUTTONBOX_START);
+	gtk_widget_show (vbuttonbox1);
+	gtk_box_pack_start (GTK_BOX (hbox1), vbuttonbox1, FALSE, FALSE, 3);
+
+	buttonadd = gtk_button_new_from_stock ("gtk-add");
+	g_signal_connect (G_OBJECT (buttonadd), "clicked",
+							G_CALLBACK (servlist_addserver_cb), edit_tree);
+	gtk_widget_show (buttonadd);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonadd);
+	GTK_WIDGET_SET_FLAGS (buttonadd, GTK_CAN_DEFAULT);
+
+	buttonremove = gtk_button_new_from_stock ("gtk-remove");
+	g_signal_connect (G_OBJECT (buttonremove), "clicked",
+							G_CALLBACK (servlist_deleteserver_cb), NULL);
+	gtk_widget_show (buttonremove);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonremove);
+	GTK_WIDGET_SET_FLAGS (buttonremove, GTK_CAN_DEFAULT);
+
+	buttonedit = gtk_button_new_with_mnemonic (_("_Edit"));
+	g_signal_connect (G_OBJECT (buttonedit), "clicked",
+							G_CALLBACK (servlist_editserverbutton_cb), NULL);
+	gtk_widget_show (buttonedit);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonedit);
+	GTK_WIDGET_SET_FLAGS (buttonedit, GTK_CAN_DEFAULT);
+
+	hseparator2 = gtk_hseparator_new ();
+	gtk_widget_show (hseparator2);
+	gtk_box_pack_start (GTK_BOX (vbox5), hseparator2, FALSE, FALSE, 8);
+
+	hbuttonbox4 = gtk_hbutton_box_new ();
+	gtk_widget_show (hbuttonbox4);
+	gtk_box_pack_start (GTK_BOX (vbox5), hbuttonbox4, FALSE, FALSE, 0);
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (hbuttonbox4),
+										GTK_BUTTONBOX_END);
+
+	button10 = gtk_button_new_from_stock ("gtk-close");
+	g_signal_connect (G_OBJECT (button10), "clicked",
+							G_CALLBACK (servlist_edit_close_cb), 0);
+	gtk_widget_show (button10);
+	gtk_container_add (GTK_CONTAINER (hbuttonbox4), button10);
+	GTK_WIDGET_SET_FLAGS (button10, GTK_CAN_DEFAULT);
+
+	if (net->flags & FLAG_USE_GLOBAL)
+	{
+		gtk_widget_hide (edit_label_nick);
+		gtk_widget_hide (edit_entry_nick);
+
+		gtk_widget_hide (edit_label_nick2);
+		gtk_widget_hide (edit_entry_nick2);
+
+		gtk_widget_hide (edit_label_user);
+		gtk_widget_hide (edit_entry_user);
+
+		gtk_widget_hide (edit_label_real);
+		gtk_widget_hide (edit_entry_real);
+	}
+
+	gtk_widget_grab_focus (button10);
+	gtk_widget_grab_default (button10);
+
+	return editwindow;
+}
+
+static GtkWidget *
+servlist_open_networks (void)
+{
+	GtkWidget *servlist;
+	GtkWidget *vbox1;
+	GtkWidget *label2;
+	GtkWidget *table1;
+	GtkWidget *label3;
+	GtkWidget *label4;
+	GtkWidget *label5;
+	GtkWidget *label6;
+	GtkWidget *label7;
+	GtkWidget *entry1;
+	GtkWidget *entry2;
+	GtkWidget *entry3;
+	GtkWidget *entry4;
+	GtkWidget *entry5;
+	GtkWidget *vbox2;
+	GtkWidget *label1;
+	GtkWidget *table4;
+	GtkWidget *scrolledwindow3;
+	GtkWidget *treeview_networks;
+	GtkWidget *checkbutton_skip;
+	GtkWidget *checkbutton_fav;
+	GtkWidget *hbox;
+	GtkWidget *vbuttonbox2;
+	GtkWidget *button_add;
+	GtkWidget *button_remove;
+	GtkWidget *button_edit;
+	GtkWidget *button_sort;
+	GtkWidget *hseparator1;
+	GtkWidget *hbuttonbox1;
+	GtkWidget *button_connect;
+	GtkWidget *button_close;
+	GtkTreeModel *model;
+	GtkListStore *store;
+	GtkCellRenderer *renderer;
+
+	servlist = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+	gtk_container_set_border_width (GTK_CONTAINER (servlist), 4);
+	gtk_window_set_title (GTK_WINDOW (servlist), _("XChat: Network List"));
+	gtk_window_set_default_size (GTK_WINDOW (servlist), win_width, win_height);
+	gtk_window_set_position (GTK_WINDOW (servlist), GTK_WIN_POS_MOUSE);
+	gtk_window_set_role (GTK_WINDOW (servlist), "servlist");
+	gtk_window_set_type_hint (GTK_WINDOW (servlist), GDK_WINDOW_TYPE_HINT_DIALOG);
+	if (current_sess)
+		gtk_window_set_transient_for (GTK_WINDOW (servlist), GTK_WINDOW (current_sess->gui->window));
+
+	vbox1 = gtk_vbox_new (FALSE, 0);
+	gtk_widget_show (vbox1);
+	gtk_container_add (GTK_CONTAINER (servlist), vbox1);
+
+	label2 = bold_label (_("User Information"));
+	gtk_box_pack_start (GTK_BOX (vbox1), label2, FALSE, FALSE, 0);
+
+	table1 = gtk_table_new (5, 2, FALSE);
+	gtk_widget_show (table1);
+	gtk_box_pack_start (GTK_BOX (vbox1), table1, FALSE, FALSE, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (table1), 8);
+	gtk_table_set_row_spacings (GTK_TABLE (table1), 2);
+	gtk_table_set_col_spacings (GTK_TABLE (table1), 4);
+
+	label3 = gtk_label_new_with_mnemonic (_("_Nick name:"));
+	gtk_widget_show (label3);
+	gtk_table_attach (GTK_TABLE (table1), label3, 0, 1, 0, 1,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (label3), 0, 0.5);
+
+	label4 = gtk_label_new (_("Second choice:"));
+	gtk_widget_show (label4);
+	gtk_table_attach (GTK_TABLE (table1), label4, 0, 1, 1, 2,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (label4), 0, 0.5);
+
+	label5 = gtk_label_new (_("Third choice:"));
+	gtk_widget_show (label5);
+	gtk_table_attach (GTK_TABLE (table1), label5, 0, 1, 2, 3,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (label5), 0, 0.5);
+
+	label6 = gtk_label_new_with_mnemonic (_("_User name:"));
+	gtk_widget_show (label6);
+	gtk_table_attach (GTK_TABLE (table1), label6, 0, 1, 3, 4,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (label6), 0, 0.5);
+
+	label7 = gtk_label_new_with_mnemonic (_("Rea_l name:"));
+	gtk_widget_show (label7);
+	gtk_table_attach (GTK_TABLE (table1), label7, 0, 1, 4, 5,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (label7), 0, 0.5);
+
+	entry_nick1 = entry1 = gtk_entry_new ();
+	gtk_entry_set_text (GTK_ENTRY (entry1), prefs.nick1);
+	gtk_widget_show (entry1);
+	gtk_table_attach (GTK_TABLE (table1), entry1, 1, 2, 0, 1,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+
+	entry_nick2 = entry2 = gtk_entry_new ();
+	gtk_entry_set_text (GTK_ENTRY (entry2), prefs.nick2);
+	gtk_widget_show (entry2);
+	gtk_table_attach (GTK_TABLE (table1), entry2, 1, 2, 1, 2,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+
+	entry_nick3 = entry3 = gtk_entry_new ();
+	gtk_entry_set_text (GTK_ENTRY (entry3), prefs.nick3);
+	gtk_widget_show (entry3);
+	gtk_table_attach (GTK_TABLE (table1), entry3, 1, 2, 2, 3,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+
+	entry_guser = entry4 = gtk_entry_new ();
+	gtk_entry_set_text (GTK_ENTRY (entry4), prefs.username);
+	gtk_widget_show (entry4);
+	gtk_table_attach (GTK_TABLE (table1), entry4, 1, 2, 3, 4,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+
+	entry_greal = entry5 = gtk_entry_new ();
+	gtk_entry_set_text (GTK_ENTRY (entry5), prefs.realname);
+	gtk_widget_show (entry5);
+	gtk_table_attach (GTK_TABLE (table1), entry5, 1, 2, 4, 5,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+
+	vbox2 = gtk_vbox_new (FALSE, 0);
+	gtk_widget_show (vbox2);
+	gtk_box_pack_start (GTK_BOX (vbox1), vbox2, TRUE, TRUE, 0);
+
+	label1 = bold_label (_("Networks"));
+	gtk_box_pack_start (GTK_BOX (vbox2), label1, FALSE, FALSE, 0);
+
+	table4 = gtk_table_new (2, 2, FALSE);
+	gtk_widget_show (table4);
+	gtk_box_pack_start (GTK_BOX (vbox2), table4, TRUE, TRUE, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (table4), 8);
+	gtk_table_set_row_spacings (GTK_TABLE (table4), 2);
+	gtk_table_set_col_spacings (GTK_TABLE (table4), 3);
+
+	scrolledwindow3 = gtk_scrolled_window_new (NULL, NULL);
+	gtk_widget_show (scrolledwindow3);
+	gtk_table_attach (GTK_TABLE (table4), scrolledwindow3, 0, 1, 0, 1,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow3),
+											  GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow3),
+													 GTK_SHADOW_IN);
+
+	store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_INT);
+	model = GTK_TREE_MODEL (store);
+
+	networks_tree = treeview_networks = gtk_tree_view_new_with_model (model);
+	g_object_unref (model);
+	gtk_widget_show (treeview_networks);
+	gtk_container_add (GTK_CONTAINER (scrolledwindow3), treeview_networks);
+	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview_networks),
+												  FALSE);
+
+	renderer = gtk_cell_renderer_text_new ();
+	g_signal_connect (G_OBJECT (renderer), "edited",
+							G_CALLBACK (servlist_celledit_cb), model);
+	gtk_tree_view_insert_column_with_attributes (
+								GTK_TREE_VIEW (treeview_networks), -1,
+						 		0, renderer,
+						 		"text", 0,
+								"editable", 1,
+								"weight", 2,
+								NULL);
+
+	hbox = gtk_hbox_new (0, FALSE);
+	gtk_table_attach (GTK_TABLE (table4), hbox, 0, 2, 1, 2,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_widget_show (hbox);
+
+	checkbutton_skip =
+		gtk_check_button_new_with_mnemonic (_("Skip network list on startup"));
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbutton_skip),
+											prefs.slist_skip);
+	gtk_container_add (GTK_CONTAINER (hbox), checkbutton_skip);
+	g_signal_connect (G_OBJECT (checkbutton_skip), "toggled",
+							G_CALLBACK (no_servlist), 0);
+	gtk_widget_show (checkbutton_skip);
+
+	checkbutton_fav =
+		gtk_check_button_new_with_mnemonic (_("Show favorites only"));
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbutton_fav),
+											prefs.slist_fav);
+	gtk_container_add (GTK_CONTAINER (hbox), checkbutton_fav);
+	g_signal_connect (G_OBJECT (checkbutton_fav), "toggled",
+							G_CALLBACK (fav_servlist), 0);
+	gtk_widget_show (checkbutton_fav);
+
+	vbuttonbox2 = gtk_vbutton_box_new ();
+	gtk_box_set_spacing (GTK_BOX (vbuttonbox2), 3);
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (vbuttonbox2), GTK_BUTTONBOX_START);
+	gtk_widget_show (vbuttonbox2);
+	gtk_table_attach (GTK_TABLE (table4), vbuttonbox2, 1, 2, 0, 1,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (GTK_FILL), 0, 0);
+
+	button_add = gtk_button_new_from_stock ("gtk-add");
+	g_signal_connect (G_OBJECT (button_add), "clicked",
+							G_CALLBACK (servlist_addnet_cb), networks_tree);
+	gtk_widget_show (button_add);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox2), button_add);
+	GTK_WIDGET_SET_FLAGS (button_add, GTK_CAN_DEFAULT);
+
+	button_remove = gtk_button_new_from_stock ("gtk-remove");
+	g_signal_connect (G_OBJECT (button_remove), "clicked",
+							G_CALLBACK (servlist_deletenet_cb), 0);
+	gtk_widget_show (button_remove);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox2), button_remove);
+	GTK_WIDGET_SET_FLAGS (button_remove, GTK_CAN_DEFAULT);
+
+	button_edit = gtk_button_new_with_mnemonic (_("_Edit..."));
+	g_signal_connect (G_OBJECT (button_edit), "clicked",
+							G_CALLBACK (servlist_edit_cb), 0);
+	gtk_widget_show (button_edit);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox2), button_edit);
+	GTK_WIDGET_SET_FLAGS (button_edit, GTK_CAN_DEFAULT);
+
+	button_sort = gtk_button_new_with_mnemonic (_("_Sort"));
+	add_tip (button_sort, _("Sorts the network list in alphabetical order. "
+				"Use SHIFT-UP and SHIFT-DOWN keys to move a row."));
+	g_signal_connect (G_OBJECT (button_sort), "clicked",
+							G_CALLBACK (servlist_sort), 0);
+	gtk_widget_show (button_sort);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox2), button_sort);
+	GTK_WIDGET_SET_FLAGS (button_sort, GTK_CAN_DEFAULT);
+
+	button_sort = gtk_button_new_with_mnemonic (_("_Favor"));
+	add_tip (button_sort, _("Mark or unmark this network as a favorite."));
+	g_signal_connect (G_OBJECT (button_sort), "clicked",
+							G_CALLBACK (servlist_favor), 0);
+	gtk_widget_show (button_sort);
+	gtk_container_add (GTK_CONTAINER (vbuttonbox2), button_sort);
+	GTK_WIDGET_SET_FLAGS (button_sort, GTK_CAN_DEFAULT);
+
+	hseparator1 = gtk_hseparator_new ();
+	gtk_widget_show (hseparator1);
+	gtk_box_pack_start (GTK_BOX (vbox1), hseparator1, FALSE, TRUE, 4);
+
+	hbuttonbox1 = gtk_hbutton_box_new ();
+	gtk_widget_show (hbuttonbox1);
+	gtk_box_pack_start (GTK_BOX (vbox1), hbuttonbox1, FALSE, TRUE, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (hbuttonbox1), 8);
+
+	button_close = gtk_button_new_from_stock ("gtk-close");
+	gtk_widget_show (button_close);
+	g_signal_connect (G_OBJECT (button_close), "clicked",
+							G_CALLBACK (servlist_close_cb), 0);
+	gtk_container_add (GTK_CONTAINER (hbuttonbox1), button_close);
+	GTK_WIDGET_SET_FLAGS (button_close, GTK_CAN_DEFAULT);
+
+	button_connect = gtkutil_button (hbuttonbox1, GTK_STOCK_CONNECT, NULL,
+												servlist_connect_cb, NULL, _("C_onnect"));
+	GTK_WIDGET_SET_FLAGS (button_connect, GTK_CAN_DEFAULT);
+
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label3), entry1);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label6), entry4);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label7), entry5);
+
+	gtk_widget_grab_focus (networks_tree);
+	gtk_widget_grab_default (button_close);
+	return servlist;
+}
+
+void
+fe_serverlist_open (session *sess)
+{
+	if (serverlist_win)
+	{
+		gtk_window_present (GTK_WINDOW (serverlist_win));
+		return;
+	}
+
+	servlist_sess = sess;
+
+	serverlist_win = servlist_open_networks ();
+	gtkutil_set_icon (serverlist_win);
+
+	servlist_networks_populate (networks_tree, network_list);
+
+	g_signal_connect (G_OBJECT (serverlist_win), "delete_event",
+						 	G_CALLBACK (servlist_delete_cb), 0);
+	g_signal_connect (G_OBJECT (serverlist_win), "configure_event",
+							G_CALLBACK (servlist_configure_cb), 0);
+	g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (networks_tree))),
+							"changed", G_CALLBACK (servlist_network_row_cb), NULL);
+	g_signal_connect (G_OBJECT (networks_tree), "key_press_event",
+							G_CALLBACK (servlist_net_keypress_cb), networks_tree);
+
+	gtk_widget_show (serverlist_win);
+}
diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c
new file mode 100644
index 00000000..517e0944
--- /dev/null
+++ b/src/fe-gtk/setup.c
@@ -0,0 +1,2137 @@
+/* X-Chat
+ * Copyright (C) 2004-2007 Peter Zelezny.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "../common/xchat.h"
+#include "../common/cfgfiles.h"
+#include "../common/fe.h"
+#include "../common/text.h"
+#include "../common/userlist.h"
+#include "../common/util.h"
+#include "../common/xchatc.h"
+#include "fe-gtk.h"
+#include "gtkutil.h"
+#include "maingui.h"
+#include "palette.h"
+#include "pixmaps.h"
+#include "menu.h"
+
+#include <gtk/gtkcolorseldialog.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkmisc.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkalignment.h>
+#include <gtk/gtknotebook.h>
+#include <gtk/gtkframe.h>
+#include <gtk/gtkfontsel.h>
+#include <gtk/gtkcheckbutton.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkspinbutton.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkhseparator.h>
+#include <gtk/gtkradiobutton.h>
+#include <gtk/gtkcombobox.h>
+#include <gtk/gtkliststore.h>
+#include <gtk/gtktreestore.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtkcellrenderertext.h>
+#include <gtk/gtkhscale.h>
+#ifdef WIN32
+#include "../common/fe.h"
+#endif
+#ifdef USE_GTKSPELL
+#include <gtk/gtktextview.h>
+#include <gtkspell/gtkspell.h>
+#endif
+#ifdef USE_LIBSEXY
+#include "sexy-spell-entry.h"
+#endif
+
+GtkStyle *create_input_style (GtkStyle *);
+
+#define LABEL_INDENT 12
+
+static int last_selected_page = 0;
+static int last_selected_row = 0; /* sound row */
+static gboolean color_change;
+static struct xchatprefs setup_prefs;
+static GtkWidget *cancel_button;
+static GtkWidget *font_dialog = NULL;
+
+enum
+{
+	ST_END,
+	ST_TOGGLE,
+	ST_TOGGLR,
+	ST_3OGGLE,
+	ST_ENTRY,
+	ST_EFONT,
+	ST_EFILE,
+	ST_EFOLDER,
+	ST_MENU,
+	ST_RADIO,
+	ST_NUMBER,
+	ST_HSCALE,
+	ST_HEADER,
+	ST_LABEL,
+	ST_ALERTHEAD
+};
+
+typedef struct
+{
+	int type;
+	char *label;
+	int offset;
+	char *tooltip;
+	char const *const *list;
+	int extra;
+} setting;
+
+
+static const setting textbox_settings[] =
+{
+	{ST_HEADER,	N_("Text Box Appearance"),0,0,0},
+	{ST_EFONT,  N_("Font:"), P_OFFSETNL(font_normal), 0, 0, sizeof prefs.font_normal},
+	{ST_EFILE,  N_("Background image:"), P_OFFSETNL(background), 0, 0, sizeof prefs.background},
+	{ST_NUMBER,	N_("Scrollback lines:"), P_OFFINTNL(max_lines),0,0,100000},
+	{ST_TOGGLE, N_("Colored nick names"), P_OFFINTNL(colorednicks),
+					N_("Give each person on IRC a different color"),0,0},
+	{ST_TOGGLR, N_("Indent nick names"), P_OFFINTNL(indent_nicks),
+					N_("Make nick names right-justified"),0,0},
+	{ST_TOGGLE, N_("Transparent background"), P_OFFINTNL(transparent),0,0,0},
+	{ST_TOGGLR, N_("Show marker line"), P_OFFINTNL(show_marker),
+					N_("Insert a red line after the last read text."),0,0},
+	{ST_HEADER, N_("Transparency Settings"), 0,0,0},
+	{ST_HSCALE, N_("Red:"), P_OFFINTNL(tint_red),0,0,0},
+	{ST_HSCALE, N_("Green:"), P_OFFINTNL(tint_green),0,0,0},
+	{ST_HSCALE, N_("Blue:"), P_OFFINTNL(tint_blue),0,0,0},
+
+	{ST_HEADER,	N_("Time Stamps"),0,0,0},
+	{ST_TOGGLE, N_("Enable time stamps"), P_OFFINTNL(timestamp),0,0,2},
+	{ST_ENTRY,  N_("Time stamp format:"), P_OFFSETNL(stamp_format),
+					N_("See strftime manpage for details."),0,sizeof prefs.stamp_format},
+
+	{ST_END, 0, 0, 0, 0, 0}
+};
+
+static const char *const tabcompmenu[] = 
+{
+	N_("A-Z"),
+	N_("Last-spoke order"),
+	NULL
+};
+
+static const setting inputbox_settings[] =
+{
+	{ST_HEADER, N_("Input box"),0,0,0},
+	{ST_TOGGLE, N_("Use the Text box font and colors"), P_OFFINTNL(style_inputbox),0,0,0},
+#if defined(USE_GTKSPELL) || defined(USE_LIBSEXY)
+	{ST_TOGGLE, N_("Spell checking"), P_OFFINTNL(gui_input_spell),0,0,0},
+#endif
+
+	{ST_HEADER, N_("Nick Completion"),0,0,0},
+	{ST_TOGGLE, N_("Automatic nick completion (without TAB key)"), P_OFFINTNL(nickcompletion),
+					0,0,0},
+	{ST_ENTRY,	N_("Nick completion suffix:"), P_OFFSETNL(nick_suffix),0,0,sizeof prefs.nick_suffix},
+	{ST_MENU,	N_("Nick completion sorted:"), P_OFFINTNL(completion_sort), 0, tabcompmenu, 0},
+
+#if 0	/* obsolete */
+	{ST_HEADER, N_("Input Box Codes"),0,0,0},
+	{ST_TOGGLE, N_("Interpret %nnn as an ASCII value"), P_OFFINTNL(perc_ascii),0,0,0},
+	{ST_TOGGLE, N_("Interpret %C, %B as Color, Bold etc"), P_OFFINTNL(perc_color),0,0,0},
+#endif
+
+	{ST_END, 0, 0, 0, 0, 0}
+};
+
+/*static const char *const lagmenutext[] = 
+{
+	N_("Off"),
+	N_("Graph"),
+	N_("Info text"),
+	N_("Both"),
+	NULL
+};*/
+
+static const char *const ulmenutext[] = 
+{
+	N_("A-Z, Ops first"),
+	N_("A-Z"),
+	N_("Z-A, Ops last"),
+	N_("Z-A"),
+	N_("Unsorted"),
+	NULL
+};
+
+static const char *const cspos[] =
+{
+	N_("Left (Upper)"),
+	N_("Left (Lower)"),
+	N_("Right (Upper)"),
+	N_("Right (Lower)"),
+	N_("Top"),
+	N_("Bottom"),
+	N_("Hidden"),
+	NULL
+};
+
+static const char *const ulpos[] =
+{
+	N_("Left (Upper)"),
+	N_("Left (Lower)"),
+	N_("Right (Upper)"),
+	N_("Right (Lower)"),
+	NULL
+};
+
+static const setting userlist_settings[] =
+{
+	{ST_HEADER,	N_("User List"),0,0,0},
+	{ST_TOGGLE, N_("Show hostnames in user list"), P_OFFINTNL(showhostname_in_userlist), 0, 0, 0},
+	{ST_TOGGLE, N_("Use the Text box font and colors"), P_OFFINTNL(style_namelistgad),0,0,0},
+/*	{ST_TOGGLE, N_("Resizable user list"), P_OFFINTNL(paned_userlist),0,0,0},*/
+	{ST_MENU,	N_("User list sorted by:"), P_OFFINTNL(userlist_sort), 0, ulmenutext, 0},
+	{ST_MENU,	N_("Show user list at:"), P_OFFINTNL(gui_ulist_pos), 0, ulpos, 1},
+
+	{ST_HEADER,	N_("Away tracking"),0,0,0},
+	{ST_TOGGLE,	N_("Track the Away status of users and mark them in a different color"), P_OFFINTNL(away_track),0,0,2},
+	{ST_NUMBER, N_("On channels smaller than:"), P_OFFINTNL(away_size_max),0,0,10000},
+
+	{ST_HEADER,	N_("Action Upon Double Click"),0,0,0},
+	{ST_ENTRY,	N_("Execute command:"), P_OFFSETNL(doubleclickuser), 0, 0, sizeof prefs.doubleclickuser},
+
+/*	{ST_HEADER,	N_("Extra Gadgets"),0,0,0},
+	{ST_MENU,	N_("Lag meter:"), P_OFFINTNL(lagometer), 0, lagmenutext, 0},
+	{ST_MENU,	N_("Throttle meter:"), P_OFFINTNL(throttlemeter), 0, lagmenutext, 0},*/
+
+	{ST_END, 0, 0, 0, 0, 0}
+};
+
+static const char *const tabwin[] =
+{
+	N_("Windows"),
+	N_("Tabs"),
+	NULL
+};
+
+#if 0
+static const char *const focusnewtabsmenu[] =
+{
+	N_("Never"),
+	N_("Always"),
+	N_("Only requested tabs"),
+	NULL
+};
+#endif
+
+static const char *const swtype[] =
+{
+	N_("Tabs"),	/* 0 tabs */
+	"",			/* 1 reserved */
+	N_("Tree"),	/* 2 tree */
+	NULL
+};
+
+static const setting tabs_settings[] =
+{
+	/*{ST_HEADER,	N_("Channel Switcher"),0,0,0},*/
+	{ST_RADIO,  N_("Switcher type:"),P_OFFINTNL(tab_layout), 0, swtype, 0},
+	{ST_TOGGLE, N_("Open an extra tab for server messages"), P_OFFINTNL(use_server_tab), 0, 0, 0},
+	{ST_TOGGLE, N_("Open an extra tab for server notices"), P_OFFINTNL(notices_tabs), 0, 0, 0},
+	{ST_TOGGLE, N_("Open a new tab when you receive a private message"), P_OFFINTNL(autodialog), 0, 0, 0},
+	{ST_TOGGLE, N_("Sort tabs in alphabetical order"), P_OFFINTNL(tab_sort), 0, 0, 0},
+	{ST_TOGGLE, N_("Smaller text"), P_OFFINTNL(tab_small), 0, 0, 0},
+#if 0
+	{ST_MENU,	N_("Focus new tabs:"), P_OFFINTNL(newtabstofront), 0, focusnewtabsmenu, 0},
+#endif
+	{ST_MENU,	N_("Show channel switcher at:"), P_OFFINTNL(tab_pos), 0, cspos, 1},
+	{ST_NUMBER,	N_("Shorten tab labels to:"), P_OFFINTNL(truncchans), 0, (const char **)N_("letters."), 99},
+
+	{ST_HEADER,	N_("Tabs or Windows"),0,0,0},
+	{ST_MENU,	N_("Open channels in:"), P_OFFINTNL(tabchannels), 0, tabwin, 0},
+	{ST_MENU,	N_("Open dialogs in:"), P_OFFINTNL(privmsgtab), 0, tabwin, 0},
+	{ST_MENU,	N_("Open utilities in:"), P_OFFINTNL(windows_as_tabs), N_("Open DCC, Ignore, Notify etc, in tabs or windows?"), tabwin, 0},
+
+	{ST_END, 0, 0, 0, 0, 0}
+};
+
+static const char *const dccaccept[] =
+{
+	N_("No"),
+	N_("Yes"),
+	N_("Browse for save folder every time"),
+	NULL
+};
+
+static const setting filexfer_settings[] =
+{
+	{ST_HEADER, N_("Files and Directories"), 0, 0, 0},
+	{ST_MENU,	N_("Auto accept file offers:"), P_OFFINTNL(autodccsend), 0, dccaccept, 0},
+	{ST_EFOLDER,N_("Download files to:"), P_OFFSETNL(dccdir), 0, 0, sizeof prefs.dccdir},
+	{ST_EFOLDER,N_("Move completed files to:"), P_OFFSETNL(dcc_completed_dir), 0, 0, sizeof prefs.dcc_completed_dir},
+	{ST_TOGGLE, N_("Save nick name in filenames"), P_OFFINTNL(dccwithnick), 0, 0, 0},
+
+	{ST_HEADER, N_("Network Settings"), 0, 0, 0},
+	{ST_TOGGLE, N_("Get my address from the IRC server"), P_OFFINTNL(ip_from_server),
+					N_("Asks the IRC server for your real address. Use this if you have a 192.168.*.* address!"), 0, 0},
+	{ST_ENTRY,	N_("DCC IP address:"), P_OFFSETNL(dcc_ip_str),
+					N_("Claim you are at this address when offering files."), 0, sizeof prefs.dcc_ip_str},
+	{ST_NUMBER,	N_("First DCC send port:"), P_OFFINTNL(first_dcc_send_port), 0, 0, 65535},
+	{ST_NUMBER,	N_("Last DCC send port:"), P_OFFINTNL(last_dcc_send_port), 0, 
+		(const char **)N_("!Leave ports at zero for full range."), 65535},
+
+	{ST_HEADER, N_("Maximum File Transfer Speeds (bytes per second)"), 0, 0, 0},
+	{ST_NUMBER,	N_("One upload:"), P_OFFINTNL(dcc_max_send_cps), 
+					N_("Maximum speed for one transfer"), 0, 1000000},
+	{ST_NUMBER,	N_("One download:"), P_OFFINTNL(dcc_max_get_cps),
+					N_("Maximum speed for one transfer"), 0, 1000000},
+	{ST_NUMBER,	N_("All uploads combined:"), P_OFFINTNL(dcc_global_max_send_cps),
+					N_("Maximum speed for all files"), 0, 1000000},
+	{ST_NUMBER,	N_("All downloads combined:"), P_OFFINTNL(dcc_global_max_get_cps),
+					N_("Maximum speed for all files"), 0, 1000000},
+
+	{ST_END, 0, 0, 0, 0, 0}
+};
+
+static const int balloonlist[3] =
+{
+	P_OFFINTNL(input_balloon_chans), P_OFFINTNL(input_balloon_priv), P_OFFINTNL(input_balloon_hilight)
+};
+
+static const int trayblinklist[3] =
+{
+	P_OFFINTNL(input_tray_chans), P_OFFINTNL(input_tray_priv), P_OFFINTNL(input_tray_hilight)
+};
+
+static const int taskbarlist[3] =
+{
+	P_OFFINTNL(input_flash_chans), P_OFFINTNL(input_flash_priv), P_OFFINTNL(input_flash_hilight)
+};
+
+static const int beeplist[3] =
+{
+	P_OFFINTNL(input_beep_chans), P_OFFINTNL(input_beep_priv), P_OFFINTNL(input_beep_hilight)
+};
+
+static const setting alert_settings[] =
+{
+	{ST_HEADER,	N_("Alerts"),0,0,0},
+
+	{ST_ALERTHEAD},
+#ifndef WIN32
+	{ST_3OGGLE, N_("Show tray balloons on:"), 0, 0, (void *)balloonlist, 0},
+#endif
+	{ST_3OGGLE, N_("Blink tray icon on:"), 0, 0, (void *)trayblinklist, 0},
+	{ST_3OGGLE, N_("Blink task bar on:"), 0, 0, (void *)taskbarlist, 0},
+	{ST_3OGGLE, N_("Make a beep sound on:"), 0, 0, (void *)beeplist, 0},
+
+	{ST_TOGGLE,	N_("Enable system tray icon"), P_OFFINTNL(gui_tray), 0, 0, 0},
+
+	{ST_HEADER,	N_("Highlighted Messages"),0,0,0},
+	{ST_LABEL,	N_("Highlighted messages are ones where your nickname is mentioned, but also:"), 0, 0, 0, 1},
+
+	{ST_ENTRY,	N_("Extra words to highlight:"), P_OFFSETNL(irc_extra_hilight), 0, 0, sizeof prefs.irc_extra_hilight},
+	{ST_ENTRY,	N_("Nick names not to highlight:"), P_OFFSETNL(irc_no_hilight), 0, 0, sizeof prefs.irc_no_hilight},
+	{ST_ENTRY,	N_("Nick names to always highlight:"), P_OFFSETNL(irc_nick_hilight), 0, 0, sizeof prefs.irc_nick_hilight},
+	{ST_LABEL,	N_("Separate multiple words with commas.\nWildcards are accepted.")},
+	{ST_END, 0, 0, 0, 0, 0}
+};
+
+static const setting general_settings[] =
+{
+	{ST_HEADER,	N_("Default Messages"),0,0,0},
+	{ST_ENTRY,	N_("Quit:"), P_OFFSETNL(quitreason), 0, 0, sizeof prefs.quitreason},
+	{ST_ENTRY,	N_("Leave channel:"), P_OFFSETNL(partreason), 0, 0, sizeof prefs.partreason},
+	{ST_ENTRY,	N_("Away:"), P_OFFSETNL(awayreason), 0, 0, sizeof prefs.awayreason},
+
+	{ST_HEADER,	N_("Away"),0,0,0},
+	{ST_TOGGLE,	N_("Announce away messages"), P_OFFINTNL(show_away_message),
+					N_("Announce your away messages to all channels"), 0, 0},
+	{ST_TOGGLE,	N_("Show away once"), P_OFFINTNL(show_away_once), N_("Show identical away messages only once"), 0, 0},
+	{ST_TOGGLE,	N_("Automatically unmark away"), P_OFFINTNL(auto_unmark_away), N_("Unmark yourself as away before sending messages"), 0, 0},
+	{ST_END, 0, 0, 0, 0, 0}
+};
+
+#if 0
+static const setting advanced_settings[] =
+{
+	{ST_HEADER,	N_("Advanced Settings"),0,0,0},
+	{ST_NUMBER,	N_("Auto reconnect delay:"), P_OFFINTNL(recon_delay), 0, 0, 9999},
+	{ST_TOGGLE,	N_("Display MODEs in raw form"), P_OFFINTNL(raw_modes), 0, 0, 0},
+	{ST_TOGGLE,	N_("Whois on notify"), P_OFFINTNL(whois_on_notifyonline), N_("Sends a /WHOIS when a user comes online in your notify list"), 0, 0},
+	{ST_TOGGLE,	N_("Hide join and part messages"), P_OFFINTNL(confmode), N_("Hide channel join/part messages by default"), 0, 0},
+	{ST_HEADER,	N_("Auto Open DCC Windows"),0,0,0},
+	{ST_TOGGLE, N_("Send window"), P_OFFINTNL(autoopendccsendwindow), 0, 0, 0},
+	{ST_TOGGLE, N_("Receive window"), P_OFFINTNL(autoopendccrecvwindow), 0, 0, 0},
+	{ST_TOGGLE, N_("Chat window"), P_OFFINTNL(autoopendccchatwindow), 0, 0, 0},
+
+	{ST_END, 0, 0, 0, 0, 0}
+};
+#endif
+
+static const setting logging_settings[] =
+{
+	{ST_HEADER,	N_("Logging"),0,0,0},
+	{ST_TOGGLE,	N_("Display scrollback from previous session"), P_OFFINTNL(text_replay), 0, 0, 0},
+	{ST_TOGGLE,	N_("Enable logging of conversations to disk"), P_OFFINTNL(logging), 0, 0, 2},
+	{ST_ENTRY,	N_("Log filename:"), P_OFFSETNL(logmask), 0, 0, sizeof prefs.logmask},
+	{ST_LABEL,	N_("%s=Server %c=Channel %n=Network.")},
+
+	{ST_HEADER,	N_("Time Stamps"),0,0,0},
+	{ST_TOGGLE,	N_("Insert timestamps in logs"), P_OFFINTNL(timestamp_logs), 0, 0, 2},
+	{ST_ENTRY,	N_("Log timestamp format:"), P_OFFSETNL(timestamp_log_format), 0, 0, sizeof prefs.timestamp_log_format},
+	{ST_LABEL,	N_("See strftime manpage for details.")},
+
+	{ST_END, 0, 0, 0, 0, 0}
+};
+
+static const char *const proxytypes[] =
+{
+	N_("(Disabled)"),
+	N_("Wingate"),
+	N_("Socks4"),
+	N_("Socks5"),
+	N_("HTTP"),
+#ifdef USE_MSPROXY
+	N_("MS Proxy (ISA)"),
+#endif
+#ifdef USE_LIBPROXY
+	N_("Auto"),
+#endif
+	NULL
+};
+
+static const char *const proxyuse[] =
+{
+	N_("All Connections"),
+	N_("IRC Server Only"),
+	N_("DCC Get Only"),
+	NULL
+};
+
+static const setting network_settings[] =
+{
+	{ST_HEADER,	N_("Your Address"), 0, 0, 0, 0},
+	{ST_ENTRY,	N_("Bind to:"), P_OFFSETNL(hostname), 0, 0, sizeof prefs.hostname},
+	{ST_LABEL,	N_("Only useful for computers with multiple addresses.")},
+
+	{ST_HEADER,	N_("Proxy Server"), 0, 0, 0, 0},
+	{ST_ENTRY,	N_("Hostname:"), P_OFFSETNL(proxy_host), 0, 0, sizeof prefs.proxy_host},
+	{ST_NUMBER,	N_("Port:"), P_OFFINTNL(proxy_port), 0, 0, 65535},
+	{ST_MENU,	N_("Type:"), P_OFFINTNL(proxy_type), 0, proxytypes, 0},
+	{ST_MENU,	N_("Use proxy for:"), P_OFFINTNL(proxy_use), 0, proxyuse, 0},
+
+	{ST_HEADER,	N_("Proxy Authentication"), 0, 0, 0, 0},
+#ifdef USE_MSPROXY
+	{ST_TOGGLE,	N_("Use Authentication (MS Proxy, HTTP or Socks5 only)"), P_OFFINTNL(proxy_auth), 0, 0, 0},
+#else
+	{ST_TOGGLE,	N_("Use Authentication (HTTP or Socks5 only)"), P_OFFINTNL(proxy_auth), 0, 0, 0},
+#endif
+	{ST_ENTRY,	N_("Username:"), P_OFFSETNL(proxy_user), 0, 0, sizeof prefs.proxy_user},
+	{ST_ENTRY,	N_("Password:"), P_OFFSETNL(proxy_pass), 0, GINT_TO_POINTER(1), sizeof prefs.proxy_pass},
+
+	{ST_END, 0, 0, 0, 0, 0}
+};
+
+#define setup_get_str(pr,set) (((char *)pr)+set->offset)
+#define setup_get_int(pr,set) *(((int *)pr)+set->offset)
+#define setup_get_int3(pr,off) *(((int *)pr)+off) 
+
+#define setup_set_int(pr,set,num) *((int *)pr+set->offset)=num
+#define setup_set_str(pr,set,str) strcpy(((char *)pr)+set->offset,str)
+
+
+static void
+setup_3oggle_cb (GtkToggleButton *but, unsigned int *setting)
+{
+	*setting = but->active;
+}
+
+static void
+setup_headlabel (GtkWidget *tab, int row, int col, char *text)
+{
+	GtkWidget *label;
+	char buf[128];
+	char *sp;
+
+	snprintf (buf, sizeof (buf), "<b><span size=\"smaller\">%s</span></b>", text);
+	sp = strchr (buf + 17, ' ');
+	if (sp)
+		*sp = '\n';
+
+	label = gtk_label_new (NULL);
+	gtk_label_set_markup (GTK_LABEL (label), buf);
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (tab), label, col, col + 1, row, row + 1, 0, 0, 4, 0);
+}
+
+static void
+setup_create_alert_header (GtkWidget *tab, int row, const setting *set)
+{
+	setup_headlabel (tab, row, 3, _("Channel Message"));
+	setup_headlabel (tab, row, 4, _("Private Message"));
+	setup_headlabel (tab, row, 5, _("Highlighted Message"));
+}
+
+/* makes 3 toggles side-by-side */
+
+static void
+setup_create_3oggle (GtkWidget *tab, int row, const setting *set)
+{
+	GtkWidget *label, *wid;
+	int *offsets = (int *)set->list;
+
+	label = gtk_label_new (_(set->label));
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (tab), label, 2, 3, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+
+	wid = gtk_check_button_new ();
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid),
+											setup_get_int3 (&setup_prefs, offsets[0]));
+	g_signal_connect (G_OBJECT (wid), "toggled",
+							G_CALLBACK (setup_3oggle_cb), ((int *)&setup_prefs) + offsets[0]);
+	gtk_table_attach (GTK_TABLE (tab), wid, 3, 4, row, row + 1, 0, 0, 0, 0);
+
+	wid = gtk_check_button_new ();
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid),
+											setup_get_int3 (&setup_prefs, offsets[1]));
+	g_signal_connect (G_OBJECT (wid), "toggled",
+							G_CALLBACK (setup_3oggle_cb), ((int *)&setup_prefs) + offsets[1]);
+	gtk_table_attach (GTK_TABLE (tab), wid, 4, 5, row, row + 1, 0, 0, 0, 0);
+
+	wid = gtk_check_button_new ();
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid),
+											setup_get_int3 (&setup_prefs, offsets[2]));
+	g_signal_connect (G_OBJECT (wid), "toggled",
+							G_CALLBACK (setup_3oggle_cb), ((int *)&setup_prefs) + offsets[2]);
+	gtk_table_attach (GTK_TABLE (tab), wid, 5, 6, row, row + 1, 0, 0, 0, 0);
+}
+
+static void
+setup_toggle_cb (GtkToggleButton *but, const setting *set)
+{
+	GtkWidget *label, *disable_wid;
+
+	setup_set_int (&setup_prefs, set, but->active ? 1 : 0);
+
+	/* does this toggle also enable/disable another widget? */
+	disable_wid = g_object_get_data (G_OBJECT (but), "nxt");
+	if (disable_wid)
+	{
+		gtk_widget_set_sensitive (disable_wid, but->active);
+		label = g_object_get_data (G_OBJECT (disable_wid), "lbl");
+		gtk_widget_set_sensitive (label, but->active);
+	}
+}
+
+static void
+setup_create_toggleR (GtkWidget *tab, int row, const setting *set)
+{
+	GtkWidget *wid;
+
+	wid = gtk_check_button_new_with_label (_(set->label));
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid),
+											setup_get_int (&setup_prefs, set));
+	g_signal_connect (G_OBJECT (wid), "toggled",
+							G_CALLBACK (setup_toggle_cb), (gpointer)set);
+	if (set->tooltip)
+		add_tip (wid, _(set->tooltip));
+	gtk_table_attach (GTK_TABLE (tab), wid, 4, 5, row, row + 1,
+							GTK_EXPAND | GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+}
+
+static GtkWidget *
+setup_create_toggleL (GtkWidget *tab, int row, const setting *set)
+{
+	GtkWidget *wid;
+
+	wid = gtk_check_button_new_with_label (_(set->label));
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid),
+											setup_get_int (&setup_prefs, set));
+	g_signal_connect (G_OBJECT (wid), "toggled",
+							G_CALLBACK (setup_toggle_cb), (gpointer)set);
+	if (set->tooltip)
+		add_tip (wid, _(set->tooltip));
+	gtk_table_attach (GTK_TABLE (tab), wid, 2, row==6 ? 6 : 4, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+
+	return wid;
+}
+
+#if 0
+static void
+setup_create_toggle (GtkWidget *box, int row, const setting *set)
+{
+	GtkWidget *wid;
+
+	wid = gtk_check_button_new_with_label (_(set->label));
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid),
+											setup_get_int (&setup_prefs, set));
+	g_signal_connect (G_OBJECT (wid), "toggled",
+							G_CALLBACK (setup_toggle_cb), (gpointer)set);
+	if (set->tooltip)
+		add_tip (wid, _(set->tooltip));
+	gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0);
+}
+#endif
+
+static GtkWidget *
+setup_create_italic_label (char *text)
+{
+	GtkWidget *label;
+	char buf[256];
+
+	label = gtk_label_new (NULL);
+	snprintf (buf, sizeof (buf), "<i><span size=\"smaller\">%s</span></i>", text);
+	gtk_label_set_markup (GTK_LABEL (label), buf);
+	gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER);
+
+	return label;
+}
+
+static void
+setup_spin_cb (GtkSpinButton *spin, const setting *set)
+{
+	setup_set_int (&setup_prefs, set, gtk_spin_button_get_value_as_int (spin));
+}
+
+static GtkWidget *
+setup_create_spin (GtkWidget *table, int row, const setting *set)
+{
+	GtkWidget *label, *wid, *rbox, *align;
+	char *text;
+
+	label = gtk_label_new (_(set->label));
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), label, 2, 3, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+
+	align = gtk_alignment_new (0.0, 0.5, 0.0, 0.0);
+	gtk_table_attach (GTK_TABLE (table), align, 3, 4, row, row + 1,
+							GTK_EXPAND | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+
+	rbox = gtk_hbox_new (0, 0);
+	gtk_container_add (GTK_CONTAINER (align), rbox);
+
+	wid = gtk_spin_button_new_with_range (0, set->extra, 1);
+	g_object_set_data (G_OBJECT (wid), "lbl", label);
+	if (set->tooltip)
+		add_tip (wid, _(set->tooltip));
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (wid),
+										setup_get_int (&setup_prefs, set));
+	g_signal_connect (G_OBJECT (wid), "value_changed",
+							G_CALLBACK (setup_spin_cb), (gpointer)set);
+	gtk_box_pack_start (GTK_BOX (rbox), wid, 0, 0, 0);
+
+	if (set->list)
+	{
+		text = _((char *)set->list);
+		if (text[0] == '!')
+			label = setup_create_italic_label (text + 1);
+		else
+			label = gtk_label_new (text);
+		gtk_box_pack_start (GTK_BOX (rbox), label, 0, 0, 6);
+	}
+
+	return wid;
+}
+
+static gint
+setup_apply_tint (int *tag)
+{
+	prefs.tint_red = setup_prefs.tint_red;
+	prefs.tint_green = setup_prefs.tint_green;
+	prefs.tint_blue = setup_prefs.tint_blue;
+	mg_update_xtext (current_sess->gui->xtext);
+	*tag = 0;
+	return 0;
+}
+
+static void
+setup_hscale_cb (GtkHScale *wid, const setting *set)
+{
+	static int tag = 0;
+
+	setup_set_int (&setup_prefs, set, gtk_range_get_value(GTK_RANGE(wid)));
+	if(tag == 0)
+		tag = g_idle_add ((GSourceFunc)setup_apply_tint, &tag);
+}
+
+static void
+setup_create_hscale (GtkWidget *table, int row, const setting *set)
+{
+	GtkWidget *wid;
+
+	wid = gtk_label_new (_(set->label));
+	gtk_misc_set_alignment (GTK_MISC (wid), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), wid, 2, 3, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+
+	wid = gtk_hscale_new_with_range (0., 255., 1.);
+	gtk_scale_set_value_pos (GTK_SCALE (wid), GTK_POS_RIGHT);
+	gtk_range_set_value (GTK_RANGE (wid), setup_get_int (&setup_prefs, set));
+	g_signal_connect (G_OBJECT(wid), "value_changed",
+							G_CALLBACK (setup_hscale_cb), (gpointer)set);
+	gtk_table_attach (GTK_TABLE (table), wid, 3, 6, row, row + 1,
+							GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
+}
+
+
+static GtkWidget *proxy_user; 	/* username GtkEntry */
+static GtkWidget *proxy_pass; 	/* password GtkEntry */
+
+static void
+setup_menu_cb (GtkWidget *cbox, const setting *set)
+{
+	int n = gtk_combo_box_get_active (GTK_COMBO_BOX (cbox));
+
+	/* set the prefs.<field> */
+	setup_set_int (&setup_prefs, set, n + set->extra);
+
+	if (set->list == proxytypes)
+	{
+		/* only HTTP and Socks5 can use a username/pass */
+		gtk_widget_set_sensitive (proxy_user, (n == 3 || n == 4 || n == 5));
+		gtk_widget_set_sensitive (proxy_pass, (n == 3 || n == 4 || n == 5));
+	}
+}
+
+static void
+setup_radio_cb (GtkWidget *item, const setting *set)
+{
+	if (GTK_TOGGLE_BUTTON (item)->active)
+	{
+		int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "n"));
+		/* set the prefs.<field> */
+		setup_set_int (&setup_prefs, set, n);
+	}
+}
+
+static int
+setup_create_radio (GtkWidget *table, int row, const setting *set)
+{
+	GtkWidget *wid, *hbox;
+	int i;
+	const char **text = (const char **)set->list;
+	GSList *group;
+
+	wid = gtk_label_new (_(set->label));
+	gtk_misc_set_alignment (GTK_MISC (wid), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), wid, 2, 3, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+
+	hbox = gtk_hbox_new (0, 0);
+	gtk_table_attach (GTK_TABLE (table), hbox, 3, 4, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+
+	i = 0;
+	group = NULL;
+	while (text[i])
+	{
+		if (text[i][0] != 0)
+		{
+			wid = gtk_radio_button_new_with_mnemonic (group, _(text[i]));
+			/*if (set->tooltip)
+				add_tip (wid, _(set->tooltip));*/
+			group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (wid));
+			gtk_container_add (GTK_CONTAINER (hbox), wid);
+			if (i == setup_get_int (&setup_prefs, set))
+				gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), TRUE);
+			g_object_set_data (G_OBJECT (wid), "n", GINT_TO_POINTER (i));
+			g_signal_connect (G_OBJECT (wid), "toggled",
+									G_CALLBACK (setup_radio_cb), (gpointer)set);
+		}
+		i++;
+		row++;
+	}
+
+	return i;
+}
+
+/*
+static const char *id_strings[] =
+{
+	"",
+	"*",
+	"%C4*%C18%B%B",
+	"%U"
+};
+
+static void
+setup_id_menu_cb (GtkWidget *item, char *dest)
+{
+	int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "n"));
+
+	strcpy (dest, id_strings[n]);
+}
+
+static void
+setup_create_id_menu (GtkWidget *table, char *label, int row, char *dest)
+{
+	GtkWidget *wid, *menu, *item;
+	int i, def = 0;
+	static const char *text[] =
+	{
+		("(disabled)"),
+		("A star (*)"),
+		("A red star (*)"),
+		("Underlined")
+	};
+
+	wid = gtk_label_new (label);
+	gtk_misc_set_alignment (GTK_MISC (wid), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), wid, 2, 3, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+
+	wid = gtk_option_menu_new ();
+	menu = gtk_menu_new ();
+
+	for (i = 0; i < 4; i++)
+	{
+		if (strcmp (id_strings[i], dest) == 0)
+		{
+			def = i;
+			break;
+		}
+	}
+
+	i = 0;
+	while (text[i])
+	{
+		item = gtk_menu_item_new_with_label (_(text[i]));
+		g_object_set_data (G_OBJECT (item), "n", GINT_TO_POINTER (i));
+
+		gtk_widget_show (item);
+		gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+		g_signal_connect (G_OBJECT (item), "activate",
+								G_CALLBACK (setup_id_menu_cb), dest);
+		i++;
+	}
+
+	gtk_option_menu_set_menu (GTK_OPTION_MENU (wid), menu);
+	gtk_option_menu_set_history (GTK_OPTION_MENU (wid), def);
+
+	gtk_table_attach (GTK_TABLE (table), wid, 3, 4, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+}
+
+*/
+
+static void
+setup_create_menu (GtkWidget *table, int row, const setting *set)
+{
+	GtkWidget *wid, *cbox, *box;
+	const char **text = (const char **)set->list;
+	int i;
+
+	wid = gtk_label_new (_(set->label));
+	gtk_misc_set_alignment (GTK_MISC (wid), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), wid, 2, 3, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+
+	cbox = gtk_combo_box_new_text ();
+
+	for (i = 0; text[i]; i++)
+		gtk_combo_box_append_text (GTK_COMBO_BOX (cbox), _(text[i]));
+
+	gtk_combo_box_set_active (GTK_COMBO_BOX (cbox),
+									  setup_get_int (&setup_prefs, set) - set->extra);
+	g_signal_connect (G_OBJECT (cbox), "changed",
+							G_CALLBACK (setup_menu_cb), (gpointer)set);
+
+	box = gtk_hbox_new (0, 0);
+	gtk_box_pack_start (GTK_BOX (box), cbox, 0, 0, 0);
+	gtk_table_attach (GTK_TABLE (table), box, 3, 4, row, row + 1,
+							GTK_EXPAND | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+}
+
+static void
+setup_filereq_cb (GtkWidget *entry, char *file)
+{
+	if (file)
+	{
+		if (file[0])
+			gtk_entry_set_text (GTK_ENTRY (entry), file);
+	}
+}
+
+static void
+setup_browsefile_cb (GtkWidget *button, GtkWidget *entry)
+{
+	gtkutil_file_req (_("Select an Image File"), setup_filereq_cb, entry, NULL, 0);
+}
+
+static void
+setup_fontsel_destroy (GtkWidget *button, GtkFontSelectionDialog *dialog)
+{
+	font_dialog = NULL;
+}
+
+static void
+setup_fontsel_cb (GtkWidget *button, GtkFontSelectionDialog *dialog)
+{
+	GtkWidget *entry;
+	char *font_name;
+
+	entry = g_object_get_data (G_OBJECT (button), "e");
+	font_name = gtk_font_selection_dialog_get_font_name (dialog);
+
+	gtk_entry_set_text (GTK_ENTRY (entry), font_name);
+
+	g_free (font_name);
+	gtk_widget_destroy (GTK_WIDGET (dialog));
+	font_dialog = NULL;
+}
+
+static void
+setup_fontsel_cancel (GtkWidget *button, GtkFontSelectionDialog *dialog)
+{
+	gtk_widget_destroy (GTK_WIDGET (dialog));
+	font_dialog = NULL;
+}
+
+static void
+setup_browsefolder_cb (GtkWidget *button, GtkEntry *entry)
+{
+	gtkutil_file_req (_("Select Download Folder"), setup_filereq_cb, entry, entry->text, FRF_CHOOSEFOLDER);
+}
+
+static void
+setup_browsefont_cb (GtkWidget *button, GtkWidget *entry)
+{
+	GtkFontSelection *sel;
+	GtkFontSelectionDialog *dialog;
+
+	dialog = (GtkFontSelectionDialog *) gtk_font_selection_dialog_new (_("Select font"));
+	font_dialog = (GtkWidget *)dialog;	/* global var */
+
+	sel = (GtkFontSelection *) dialog->fontsel;
+
+	if (GTK_ENTRY (entry)->text[0])
+		gtk_font_selection_set_font_name (sel, GTK_ENTRY (entry)->text);
+
+	g_object_set_data (G_OBJECT (dialog->ok_button), "e", entry);
+
+	g_signal_connect (G_OBJECT (dialog), "destroy",
+							G_CALLBACK (setup_fontsel_destroy), dialog);
+	g_signal_connect (G_OBJECT (dialog->ok_button), "clicked",
+							G_CALLBACK (setup_fontsel_cb), dialog);
+	g_signal_connect (G_OBJECT (dialog->cancel_button), "clicked",
+							G_CALLBACK (setup_fontsel_cancel), dialog);
+
+	gtk_widget_show (GTK_WIDGET (dialog));
+}
+
+static void
+setup_entry_cb (GtkEntry *entry, setting *set)
+{
+	int size;
+	int pos;
+	int len = strlen (entry->text);
+	unsigned char *p = entry->text;
+
+	/* need to truncate? */
+	if (len >= set->extra)
+	{
+		len = pos = 0;
+		while (1)
+		{
+			size = g_utf8_skip [*p];
+			len += size;
+			p += size;
+			/* truncate to "set->extra" BYTES */
+			if (len >= set->extra)
+			{
+				gtk_editable_delete_text (GTK_EDITABLE (entry), pos, -1);
+				break;
+			}
+			pos++;
+		}
+	}
+	else
+	{
+		setup_set_str (&setup_prefs, set, entry->text);
+	}
+}
+
+static void
+setup_create_label (GtkWidget *table, int row, const setting *set)
+{
+	gtk_table_attach (GTK_TABLE (table), setup_create_italic_label (_(set->label)),
+							set->extra ? 1 : 3, 5, row, row + 1, GTK_FILL,
+							GTK_SHRINK | GTK_FILL, 0, 0);
+}
+
+static GtkWidget *
+setup_create_entry (GtkWidget *table, int row, const setting *set)
+{
+	GtkWidget *label;
+	GtkWidget *wid, *bwid;
+
+	label = gtk_label_new (_(set->label));
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), label, 2, 3, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+
+	wid = gtk_entry_new ();
+	g_object_set_data (G_OBJECT (wid), "lbl", label);
+	if (set->list)
+		gtk_entry_set_visibility (GTK_ENTRY (wid), FALSE);
+	if (set->tooltip)
+		add_tip (wid, _(set->tooltip));
+	gtk_entry_set_max_length (GTK_ENTRY (wid), set->extra - 1);
+	gtk_entry_set_text (GTK_ENTRY (wid), setup_get_str (&setup_prefs, set));
+	g_signal_connect (G_OBJECT (wid), "changed",
+							G_CALLBACK (setup_entry_cb), (gpointer)set);
+
+	if (set->offset == P_OFFSETNL(proxy_user))
+		proxy_user = wid;
+	if (set->offset == P_OFFSETNL(proxy_pass))
+		proxy_pass = wid; 
+
+	/* only http and Socks5 can auth */
+	if ( (set->offset == P_OFFSETNL(proxy_pass) ||
+			set->offset == P_OFFSETNL(proxy_user)) &&
+	     (setup_prefs.proxy_type != 4 && setup_prefs.proxy_type != 3 && setup_prefs.proxy_type != 5) )
+		gtk_widget_set_sensitive (wid, FALSE);
+
+	if (set->type == ST_ENTRY)
+		gtk_table_attach (GTK_TABLE (table), wid, 3, 6, row, row + 1,
+								GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
+	else
+	{
+		gtk_table_attach (GTK_TABLE (table), wid, 3, 5, row, row + 1,
+								GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
+		bwid = gtk_button_new_with_label (_("Browse..."));
+		gtk_table_attach (GTK_TABLE (table), bwid, 5, 6, row, row + 1,
+								GTK_SHRINK | GTK_FILL, GTK_FILL, 0, 0);
+		if (set->type == ST_EFILE)
+			g_signal_connect (G_OBJECT (bwid), "clicked",
+									G_CALLBACK (setup_browsefile_cb), wid);
+		if (set->type == ST_EFONT)
+			g_signal_connect (G_OBJECT (bwid), "clicked",
+									G_CALLBACK (setup_browsefont_cb), wid);
+		if (set->type == ST_EFOLDER)
+			g_signal_connect (G_OBJECT (bwid), "clicked",
+									G_CALLBACK (setup_browsefolder_cb), wid);
+	}
+
+	return wid;
+}
+
+static void
+setup_create_header (GtkWidget *table, int row, char *labeltext)
+{
+	GtkWidget *label;
+	char buf[128];
+
+	if (row == 0)
+		snprintf (buf, sizeof (buf), "<b>%s</b>", _(labeltext));
+	else
+		snprintf (buf, sizeof (buf), "\n<b>%s</b>", _(labeltext));
+
+	label = gtk_label_new (NULL);
+	gtk_label_set_markup (GTK_LABEL (label), buf);
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (table), label, 0, 4, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 5);
+}
+
+static GtkWidget *
+setup_create_frame (GtkWidget **left, GtkWidget *box)
+{
+	GtkWidget *tab, *hbox, *inbox = box;
+
+	tab = gtk_table_new (3, 2, FALSE);
+	gtk_container_set_border_width (GTK_CONTAINER (tab), 6);
+	gtk_table_set_row_spacings (GTK_TABLE (tab), 2);
+	gtk_table_set_col_spacings (GTK_TABLE (tab), 3);
+	gtk_container_add (GTK_CONTAINER (inbox), tab);
+
+	hbox = gtk_hbox_new (FALSE, 0);
+	gtk_container_add (GTK_CONTAINER (inbox), hbox);
+
+	*left = gtk_vbox_new (FALSE, 0);
+	gtk_box_pack_start (GTK_BOX (hbox), *left, 0, 0, 0);
+
+	return tab;
+}
+
+static void
+open_data_cb (GtkWidget *button, gpointer data)
+{
+	fe_open_url (get_xdir_utf8 ());
+}
+
+static GtkWidget *
+setup_create_page (const setting *set)
+{
+	int i, row, do_disable;
+	GtkWidget *tab, *box, *left;
+	GtkWidget *wid = NULL, *prev;
+
+	box = gtk_vbox_new (FALSE, 1);
+	gtk_container_set_border_width (GTK_CONTAINER (box), 6);
+
+	tab = setup_create_frame (&left, box);
+
+	i = row = do_disable = 0;
+	while (set[i].type != ST_END)
+	{
+		prev = wid;
+
+		switch (set[i].type)
+		{
+		case ST_HEADER:
+			setup_create_header (tab, row, set[i].label);
+			break;
+		case ST_EFONT:
+		case ST_ENTRY:
+		case ST_EFILE:
+		case ST_EFOLDER:
+			wid = setup_create_entry (tab, row, &set[i]);
+			break;
+		case ST_TOGGLR:
+			row--;
+			setup_create_toggleR (tab, row, &set[i]);
+			break;
+		case ST_TOGGLE:
+			wid = setup_create_toggleL (tab, row, &set[i]);
+			do_disable = set[i].extra;
+			break;
+		case ST_3OGGLE:
+			setup_create_3oggle (tab, row, &set[i]);
+			break;
+		case ST_MENU:
+			setup_create_menu (tab, row, &set[i]);
+			break;
+		case ST_RADIO:
+			row += setup_create_radio (tab, row, &set[i]);
+			break;
+		case ST_NUMBER:
+			wid = setup_create_spin (tab, row, &set[i]);
+			break;
+		case ST_HSCALE:
+			setup_create_hscale (tab, row, &set[i]);
+			break;
+		case ST_LABEL:
+			setup_create_label (tab, row, &set[i]);
+			break;
+		case ST_ALERTHEAD:
+			setup_create_alert_header (tab, row, &set[i]);
+		}
+
+		/* will this toggle disable the "next" widget? */
+		do_disable--;
+		if (do_disable == 0)
+		{
+			/* setup_toggle_cb uses this data */
+			g_object_set_data (G_OBJECT (prev), "nxt", wid);
+			/* force initial sensitive state */
+			gtk_widget_set_sensitive (wid, GTK_TOGGLE_BUTTON (prev)->active);
+			gtk_widget_set_sensitive (g_object_get_data (G_OBJECT (wid), "lbl"),
+											  GTK_TOGGLE_BUTTON (prev)->active);
+		}
+
+		i++;
+		row++;
+	}
+
+#if 0
+	if (set == general_settings)
+	{
+		setup_create_id_menu (tab, _("Mark identified users with:"),	
+									 row, setup_prefs.irc_id_ytext);
+		setup_create_id_menu (tab, _("Mark not-identified users with:"),	
+									 row + 1, setup_prefs.irc_id_ntext);
+	}
+#endif
+
+	if (set == logging_settings)
+	{
+		GtkWidget *but = gtk_button_new_with_label (_("Open Data Folder"));
+		gtk_box_pack_start (GTK_BOX (left), but, 0, 0, 0);
+		g_signal_connect (G_OBJECT (but), "clicked",
+								G_CALLBACK (open_data_cb), 0);
+	}
+
+	return box;
+}
+
+static void
+setup_color_ok_cb (GtkWidget *button, GtkWidget *dialog)
+{
+	GtkColorSelectionDialog *cdialog = GTK_COLOR_SELECTION_DIALOG (dialog);
+	GdkColor *col;
+	GdkColor old_color;
+	GtkStyle *style;
+
+	col = g_object_get_data (G_OBJECT (button), "c");
+	old_color = *col;
+
+	button = g_object_get_data (G_OBJECT (button), "b");
+
+	if (!GTK_IS_WIDGET (button))
+	{
+		gtk_widget_destroy (dialog);
+		return;
+	}
+
+	color_change = TRUE;
+
+	gtk_color_selection_get_current_color (GTK_COLOR_SELECTION (cdialog->colorsel), col);
+
+	gdk_colormap_alloc_color (gtk_widget_get_colormap (button), col, TRUE, TRUE);
+
+	style = gtk_style_new ();
+	style->bg[0] = *col;
+	gtk_widget_set_style (button, style);
+	g_object_unref (style);
+
+	/* is this line correct?? */
+	gdk_colormap_free_colors (gtk_widget_get_colormap (button), &old_color, 1);
+
+	gtk_widget_destroy (dialog);
+}
+
+static void
+setup_color_cb (GtkWidget *button, gpointer userdata)
+{
+	GtkWidget *dialog;
+	GtkColorSelectionDialog *cdialog;
+	GdkColor *color;
+
+	color = &colors[GPOINTER_TO_INT (userdata)];
+
+	dialog = gtk_color_selection_dialog_new (_("Select color"));
+	cdialog = GTK_COLOR_SELECTION_DIALOG (dialog);
+
+	gtk_widget_hide (cdialog->help_button);
+	g_signal_connect (G_OBJECT (cdialog->ok_button), "clicked",
+							G_CALLBACK (setup_color_ok_cb), dialog);
+	g_signal_connect (G_OBJECT (cdialog->cancel_button), "clicked",
+							G_CALLBACK (gtkutil_destroy), dialog);
+	g_object_set_data (G_OBJECT (cdialog->ok_button), "c", color);
+	g_object_set_data (G_OBJECT (cdialog->ok_button), "b", button);
+	gtk_widget_set_sensitive (cdialog->help_button, FALSE);
+	gtk_color_selection_set_current_color (GTK_COLOR_SELECTION (cdialog->colorsel), color);
+	gtk_widget_show (dialog);
+}
+
+static void
+setup_create_color_button (GtkWidget *table, int num, int row, int col)
+{
+	GtkWidget *but;
+	GtkStyle *style;
+	char buf[64];
+
+	if (num > 31)
+		strcpy (buf, "<span size=\"x-small\"> </span>");
+	else
+						/* 12345678901 23456789 01  23456789 */
+		sprintf (buf, "<span size=\"x-small\">%d</span>", num);
+	but = gtk_button_new_with_label (" ");
+	gtk_label_set_markup (GTK_LABEL (GTK_BIN (but)->child), buf);
+	/* win32 build uses this to turn off themeing */
+	g_object_set_data (G_OBJECT (but), "xchat-color", (gpointer)1);
+	gtk_table_attach (GTK_TABLE (table), but, col, col+1, row, row+1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
+	g_signal_connect (G_OBJECT (but), "clicked",
+							G_CALLBACK (setup_color_cb), GINT_TO_POINTER (num));
+	style = gtk_style_new ();
+	style->bg[GTK_STATE_NORMAL] = colors[num];
+	gtk_widget_set_style (but, style);
+	g_object_unref (style);
+}
+
+static void
+setup_create_other_colorR (char *text, int num, int row, GtkWidget *tab)
+{
+	GtkWidget *label;
+
+	label = gtk_label_new (text);
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (tab), label, 5, 9, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+	setup_create_color_button (tab, num, row, 9);
+}
+
+static void
+setup_create_other_color (char *text, int num, int row, GtkWidget *tab)
+{
+	GtkWidget *label;
+
+	label = gtk_label_new (text);
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (tab), label, 2, 3, row, row + 1,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+	setup_create_color_button (tab, num, row, 3);
+}
+
+static GtkWidget *
+setup_create_color_page (void)
+{
+	GtkWidget *tab, *box, *label;
+	int i;
+
+	box = gtk_vbox_new (FALSE, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (box), 6);
+
+	tab = gtk_table_new (9, 2, FALSE);
+	gtk_container_set_border_width (GTK_CONTAINER (tab), 6);
+	gtk_table_set_row_spacings (GTK_TABLE (tab), 2);
+	gtk_table_set_col_spacings (GTK_TABLE (tab), 3);
+	gtk_container_add (GTK_CONTAINER (box), tab);
+
+	setup_create_header (tab, 0, N_("Text Colors"));
+
+	label = gtk_label_new (_("mIRC colors:"));
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (tab), label, 2, 3, 1, 2,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+
+	for (i = 0; i < 16; i++)
+		setup_create_color_button (tab, i, 1, i+3);
+
+	label = gtk_label_new (_("Local colors:"));
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+	gtk_table_attach (GTK_TABLE (tab), label, 2, 3, 2, 3,
+							GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0);
+
+	for (i = 16; i < 32; i++)
+		setup_create_color_button (tab, i, 2, (i+3) - 16);
+
+	setup_create_other_color (_("Foreground:"), COL_FG, 3, tab);
+	setup_create_other_colorR (_("Background:"), COL_BG, 3, tab);
+
+	setup_create_header (tab, 5, N_("Marking Text"));
+
+	setup_create_other_color (_("Foreground:"), COL_MARK_FG, 6, tab);
+	setup_create_other_colorR (_("Background:"), COL_MARK_BG, 6, tab);
+
+	setup_create_header (tab, 8, N_("Interface Colors"));
+
+	setup_create_other_color (_("New data:"), COL_NEW_DATA, 9, tab);
+	setup_create_other_colorR (_("Marker line:"), COL_MARKER, 9, tab);
+	setup_create_other_color (_("New message:"), COL_NEW_MSG, 10, tab);
+	setup_create_other_colorR (_("Away user:"), COL_AWAY, 10, tab);
+	setup_create_other_color (_("Highlight:"), COL_HILIGHT, 11, tab);
+
+	return box;
+}
+
+/* === GLOBALS for sound GUI === */
+
+static GtkWidget *sndprog_entry;
+static GtkWidget *sndfile_entry;
+static GtkWidget *snddir_entry;
+static int ignore_changed = FALSE;
+
+extern struct text_event te[]; /* text.c */
+extern char *sound_files[];
+
+static void
+setup_snd_apply (void)
+{
+	strcpy (setup_prefs.sounddir, GTK_ENTRY (snddir_entry)->text);
+	strcpy (setup_prefs.soundcmd, GTK_ENTRY (sndprog_entry)->text);
+}
+
+static void
+setup_snd_populate (GtkTreeView * treeview)
+{
+	GtkListStore *store;
+	GtkTreeIter iter;
+	GtkTreeSelection *sel;
+	GtkTreePath *path;
+	int i;
+
+	sel = gtk_tree_view_get_selection (treeview);
+	store = (GtkListStore *)gtk_tree_view_get_model (treeview);
+
+	for (i = NUM_XP-1; i >= 0; i--)
+	{
+		gtk_list_store_prepend (store, &iter);
+		if (sound_files[i])
+			gtk_list_store_set (store, &iter, 0, te[i].name, 1, sound_files[i], 2, i, -1);
+		else
+			gtk_list_store_set (store, &iter, 0, te[i].name, 1, "", 2, i, -1);
+		if (i == last_selected_row)
+		{
+			gtk_tree_selection_select_iter (sel, &iter);
+			path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
+			if (path)
+			{
+				gtk_tree_view_scroll_to_cell (treeview, path, NULL, TRUE, 0.5, 0.5);
+				gtk_tree_view_set_cursor (treeview, path, NULL, FALSE);
+				gtk_tree_path_free (path);
+			}
+		}
+	}
+}
+
+static int
+setup_snd_get_selected (GtkTreeSelection *sel, GtkTreeIter *iter)
+{
+	int n;
+	GtkTreeModel *model;
+
+	if (!gtk_tree_selection_get_selected (sel, &model, iter))
+		return -1;
+
+	gtk_tree_model_get (model, iter, 2, &n, -1);
+	return n;
+}
+
+static void
+setup_snd_row_cb (GtkTreeSelection *sel, gpointer user_data)
+{
+	int n;
+	GtkTreeIter iter;
+
+	n = setup_snd_get_selected (sel, &iter);
+	if (n == -1)
+		return;
+	last_selected_row = n;
+
+	ignore_changed = TRUE;
+	if (sound_files[n])
+		gtk_entry_set_text (GTK_ENTRY (sndfile_entry), sound_files[n]);
+	else
+		gtk_entry_set_text (GTK_ENTRY (sndfile_entry), "");
+	ignore_changed = FALSE;
+}
+
+static void
+setup_snd_add_columns (GtkTreeView * treeview)
+{
+	GtkCellRenderer *renderer;
+	GtkTreeModel *model;
+
+	/* event column */
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
+																-1, _("Event"), renderer,
+																"text", 0, NULL);
+
+	/* file column */
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
+																-1, _("Sound file"), renderer,
+																"text", 1, NULL);
+
+	model = GTK_TREE_MODEL (gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT));
+	gtk_tree_view_set_model (GTK_TREE_VIEW (treeview), model);
+	g_object_unref (model);
+}
+
+static void
+setup_autotoggle_cb (GtkToggleButton *but, GtkToggleButton *ext)
+{
+	if (but->active)
+	{
+		setup_prefs.soundcmd[0] = 0;
+		gtk_entry_set_text (GTK_ENTRY (sndprog_entry), "");
+		gtk_widget_set_sensitive (sndprog_entry, FALSE);
+	} else
+	{
+		gtk_widget_set_sensitive (sndprog_entry, TRUE);
+	}
+}
+
+static void
+setup_snd_filereq_cb (GtkWidget *entry, char *file)
+{
+	if (file)
+	{
+		if (file[0])
+			gtk_entry_set_text (GTK_ENTRY (entry), file);
+	}
+}
+
+static void
+setup_snd_browse_cb (GtkWidget *button, GtkEntry *entry)
+{
+	gtkutil_file_req (_("Select a sound file"), setup_snd_filereq_cb, entry, NULL, 0);
+}
+
+static void
+setup_snd_play_cb (GtkWidget *button, GtkEntry *entry)
+{
+	sound_play (entry->text, FALSE);
+}
+
+static void
+setup_snd_changed_cb (GtkEntry *ent, GtkTreeView *tree)
+{
+	int n;
+	GtkTreeIter iter;
+	GtkListStore *store;
+	GtkTreeSelection *sel;
+
+	if (ignore_changed)
+		return;
+
+	sel = gtk_tree_view_get_selection (tree);
+	n = setup_snd_get_selected (sel, &iter);
+	if (n == -1)
+		return;
+
+	/* get the new sound file */
+	if (sound_files[n])
+		free (sound_files[n]);
+	sound_files[n] = strdup (GTK_ENTRY (ent)->text);
+
+	/* update the TreeView list */
+	store = (GtkListStore *)gtk_tree_view_get_model (tree);
+	gtk_list_store_set (store, &iter, 1, sound_files[n], -1);
+
+	gtk_widget_set_sensitive (cancel_button, FALSE);
+}
+
+static GtkWidget *
+setup_create_sound_page (void)
+{
+	GtkWidget *vbox1;
+	GtkWidget *vbox2;
+	GtkWidget *table2;
+	GtkWidget *label2;
+	GtkWidget *label3;
+	GtkWidget *radio_external;
+	GSList *radio_group = NULL;
+	GtkWidget *radio_auto;
+	GtkWidget *label4;
+	GtkWidget *entry3;
+	GtkWidget *scrolledwindow1;
+	GtkWidget *sound_tree;
+	GtkWidget *table1;
+	GtkWidget *sound_label;
+	GtkWidget *sound_browse;
+	GtkWidget *sound_play;
+	GtkTreeSelection *sel;
+
+	vbox1 = gtk_vbox_new (FALSE, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (vbox1), 6);
+	gtk_widget_show (vbox1);
+
+	vbox2 = gtk_vbox_new (FALSE, 0);
+	gtk_widget_show (vbox2);
+	gtk_container_add (GTK_CONTAINER (vbox1), vbox2);
+
+	table2 = gtk_table_new (4, 3, FALSE);
+	gtk_widget_show (table2);
+	gtk_box_pack_start (GTK_BOX (vbox2), table2, FALSE, TRUE, 8);
+	gtk_table_set_row_spacings (GTK_TABLE (table2), 2);
+	gtk_table_set_col_spacings (GTK_TABLE (table2), 4);
+
+	label2 = gtk_label_new (_("Sound playing method:"));
+	gtk_widget_show (label2);
+	gtk_table_attach (GTK_TABLE (table2), label2, 0, 1, 0, 1,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (label2), 0, 0.5);
+
+	label3 =
+		gtk_label_new_with_mnemonic (_("External sound playing _program:"));
+	gtk_widget_show (label3);
+	gtk_table_attach (GTK_TABLE (table2), label3, 0, 1, 2, 3,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (label3), 0, 0.5);
+
+	sndprog_entry = gtk_entry_new ();
+	if (setup_prefs.soundcmd[0] == 0)
+		gtk_widget_set_sensitive (sndprog_entry, FALSE);
+	else
+		gtk_entry_set_text (GTK_ENTRY (sndprog_entry), setup_prefs.soundcmd);
+	gtk_widget_show (sndprog_entry);
+	gtk_table_attach (GTK_TABLE (table2), sndprog_entry, 1, 3, 2, 3,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+
+	radio_external =
+		gtk_radio_button_new_with_mnemonic (NULL, _("_External program"));
+	gtk_widget_show (radio_external);
+	gtk_table_attach (GTK_TABLE (table2), radio_external, 1, 3, 1, 2,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_radio_button_set_group (GTK_RADIO_BUTTON (radio_external),
+										 radio_group);
+	radio_group =
+		gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio_external));
+
+	radio_auto = gtk_radio_button_new_with_mnemonic (NULL, _("_Automatic"));
+	g_signal_connect (G_OBJECT (radio_auto), "toggled",
+							G_CALLBACK (setup_autotoggle_cb), radio_external);
+	gtk_widget_show (radio_auto);
+	gtk_table_attach (GTK_TABLE (table2), radio_auto, 1, 3, 0, 1,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_radio_button_set_group (GTK_RADIO_BUTTON (radio_auto),
+										 radio_group);
+	radio_group =
+		gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio_auto));
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio_auto), setup_prefs.soundcmd[0] == 0);
+
+	label4 = gtk_label_new_with_mnemonic (_("Sound files _directory:"));
+	gtk_widget_show (label4);
+	gtk_table_attach (GTK_TABLE (table2), label4, 0, 1, 3, 4,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (label4), 0, 0.5);
+
+	snddir_entry = entry3 = gtk_entry_new ();
+	gtk_entry_set_text (GTK_ENTRY (entry3), setup_prefs.sounddir);
+	gtk_widget_show (entry3);
+	gtk_table_attach (GTK_TABLE (table2), entry3, 1, 3, 3, 4,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+
+	scrolledwindow1 = gtk_scrolled_window_new (NULL, NULL);
+	gtk_widget_show (scrolledwindow1);
+	gtk_container_add (GTK_CONTAINER (vbox2), scrolledwindow1);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow1),
+											  GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow1),
+													 GTK_SHADOW_IN);
+
+	sound_tree = gtk_tree_view_new ();
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (sound_tree));
+	gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
+	setup_snd_add_columns (GTK_TREE_VIEW (sound_tree));
+	setup_snd_populate (GTK_TREE_VIEW (sound_tree));
+	g_signal_connect (G_OBJECT (sel), "changed",
+							G_CALLBACK (setup_snd_row_cb), NULL);
+	gtk_widget_show (sound_tree);
+	gtk_container_add (GTK_CONTAINER (scrolledwindow1), sound_tree);
+	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (sound_tree), TRUE);
+
+	table1 = gtk_table_new (2, 3, FALSE);
+	gtk_widget_show (table1);
+	gtk_box_pack_start (GTK_BOX (vbox2), table1, FALSE, TRUE, 8);
+	gtk_table_set_row_spacings (GTK_TABLE (table1), 2);
+	gtk_table_set_col_spacings (GTK_TABLE (table1), 4);
+
+	sound_label = gtk_label_new_with_mnemonic (_("Sound file:"));
+	gtk_widget_show (sound_label);
+	gtk_table_attach (GTK_TABLE (table1), sound_label, 0, 1, 0, 1,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+	gtk_misc_set_alignment (GTK_MISC (sound_label), 0, 0.5);
+
+	sndfile_entry = gtk_entry_new ();
+	g_signal_connect (G_OBJECT (sndfile_entry), "changed",
+							G_CALLBACK (setup_snd_changed_cb), sound_tree);
+	gtk_widget_show (sndfile_entry);
+	gtk_table_attach (GTK_TABLE (table1), sndfile_entry, 0, 1, 1, 2,
+							(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+
+	sound_browse = gtk_button_new_with_mnemonic (_("_Browse..."));
+	g_signal_connect (G_OBJECT (sound_browse), "clicked",
+							G_CALLBACK (setup_snd_browse_cb), sndfile_entry);
+	gtk_widget_show (sound_browse);
+	gtk_table_attach (GTK_TABLE (table1), sound_browse, 1, 2, 1, 2,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+
+#ifdef GTK_STOCK_MEDIA_PLAY
+	sound_play = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PLAY);
+#else
+	sound_play = gtk_button_new_with_mnemonic (_("_Play"));
+#endif
+	g_signal_connect (G_OBJECT (sound_play), "clicked",
+							G_CALLBACK (setup_snd_play_cb), sndfile_entry);
+	gtk_widget_show (sound_play);
+	gtk_table_attach (GTK_TABLE (table1), sound_play, 2, 3, 1, 2,
+							(GtkAttachOptions) (GTK_FILL),
+							(GtkAttachOptions) (0), 0, 0);
+
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label3), sndprog_entry);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label4), entry3);
+	setup_snd_row_cb (sel, NULL);
+
+	return vbox1;
+}
+
+static void
+setup_add_page (const char *title, GtkWidget *book, GtkWidget *tab)
+{
+	GtkWidget *oframe, *frame, *label, *vvbox;
+	char buf[128];
+
+	/* frame for whole page */
+	oframe = gtk_frame_new (NULL);
+	gtk_frame_set_shadow_type (GTK_FRAME (oframe), GTK_SHADOW_IN);
+
+	vvbox = gtk_vbox_new (FALSE, 0);
+	gtk_container_add (GTK_CONTAINER (oframe), vvbox);
+
+	/* border for the label */
+	frame = gtk_frame_new (NULL);
+	gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
+	gtk_box_pack_start (GTK_BOX (vvbox), frame, FALSE, TRUE, 0);
+
+	/* label */
+	label = gtk_label_new (NULL);
+	snprintf (buf, sizeof (buf), "<b><big>%s</big></b>", _(title));
+	gtk_label_set_markup (GTK_LABEL (label), buf);
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+	gtk_misc_set_padding (GTK_MISC (label), 2, 1);
+	gtk_container_add (GTK_CONTAINER (frame), label);
+
+	gtk_container_add (GTK_CONTAINER (vvbox), tab);
+
+	gtk_notebook_append_page (GTK_NOTEBOOK (book), oframe, NULL);
+}
+
+static const char *const cata[] =
+{
+	N_("Interface"),
+		N_("Text box"),
+		N_("Input box"),
+		N_("User list"),
+		N_("Channel switcher"),
+		N_("Colors"),
+		NULL,
+	N_("Chatting"),
+		N_("Alerts"),
+		N_("General"),
+		N_("Logging"),
+		N_("Sound"),
+/*		N_("Advanced"),*/
+		NULL,
+	N_("Network"),
+		N_("Network setup"),
+		N_("File transfers"),
+		NULL,
+	NULL
+};
+
+static GtkWidget *
+setup_create_pages (GtkWidget *box)
+{
+	GtkWidget *book;
+
+	book = gtk_notebook_new ();
+
+	setup_add_page (cata[1], book, setup_create_page (textbox_settings));
+	setup_add_page (cata[2], book, setup_create_page (inputbox_settings));
+	setup_add_page (cata[3], book, setup_create_page (userlist_settings));
+	setup_add_page (cata[4], book, setup_create_page (tabs_settings));
+	setup_add_page (cata[5], book, setup_create_color_page ());
+	setup_add_page (cata[8], book, setup_create_page (alert_settings));
+	setup_add_page (cata[9], book, setup_create_page (general_settings));
+	setup_add_page (cata[10], book, setup_create_page (logging_settings));
+	setup_add_page (cata[11], book, setup_create_sound_page ());
+	setup_add_page (cata[14], book, setup_create_page (network_settings));
+	setup_add_page (cata[15], book, setup_create_page (filexfer_settings));
+
+	gtk_notebook_set_show_tabs (GTK_NOTEBOOK (book), FALSE);
+	gtk_notebook_set_show_border (GTK_NOTEBOOK (book), FALSE);
+	gtk_container_add (GTK_CONTAINER (box), book);
+
+	return book;
+}
+
+static void
+setup_tree_cb (GtkTreeView *treeview, GtkWidget *book)
+{
+	GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+	int page;
+
+	if (gtk_tree_selection_get_selected (selection, &model, &iter))
+	{
+		gtk_tree_model_get (model, &iter, 1, &page, -1);
+		if (page != -1)
+		{
+			gtk_notebook_set_current_page (GTK_NOTEBOOK (book), page);
+			last_selected_page = page;
+		}
+	}
+}
+
+static gboolean
+setup_tree_select_filter (GtkTreeSelection *selection, GtkTreeModel *model,
+								  GtkTreePath *path, gboolean path_selected,
+								  gpointer data)
+{
+	if (gtk_tree_path_get_depth (path) > 1)
+		return TRUE;
+	return FALSE;
+}
+
+static void
+setup_create_tree (GtkWidget *box, GtkWidget *book)
+{
+	GtkWidget *tree;
+	GtkWidget *frame;
+	GtkTreeStore *model;
+	GtkTreeIter iter;
+	GtkTreeIter child_iter;
+	GtkTreeIter *sel_iter = NULL;
+	GtkCellRenderer *renderer;
+	GtkTreeSelection *sel;
+	int i, page;
+
+	model = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_INT);
+
+	i = 0;
+	page = 0;
+	do
+	{
+		gtk_tree_store_append (model, &iter, NULL);
+		gtk_tree_store_set (model, &iter, 0, _(cata[i]), 1, -1, -1);
+		i++;
+
+		do
+		{
+			gtk_tree_store_append (model, &child_iter, &iter);
+			gtk_tree_store_set (model, &child_iter, 0, _(cata[i]), 1, page, -1);
+			if (page == last_selected_page)
+				sel_iter = gtk_tree_iter_copy (&child_iter);
+			page++;
+			i++;
+		} while (cata[i]);
+
+		i++;
+
+	} while (cata[i]);
+
+	tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model));
+	g_object_unref (G_OBJECT (model));
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
+	gtk_tree_selection_set_mode (sel, GTK_SELECTION_BROWSE);
+	gtk_tree_selection_set_select_function (sel, setup_tree_select_filter,
+														 NULL, NULL);
+	g_signal_connect (G_OBJECT (tree), "cursor_changed",
+							G_CALLBACK (setup_tree_cb), book);
+
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree),
+							    -1, _("Categories"), renderer, "text", 0, NULL);
+	gtk_tree_view_expand_all (GTK_TREE_VIEW (tree));
+
+	frame = gtk_frame_new (NULL);
+	gtk_container_add (GTK_CONTAINER (frame), tree);
+	gtk_box_pack_start (GTK_BOX (box), frame, 0, 0, 0);
+	gtk_box_reorder_child (GTK_BOX (box), frame, 0);
+
+	if (sel_iter)
+	{
+		gtk_tree_selection_select_iter (sel, sel_iter);
+		gtk_tree_iter_free (sel_iter);
+	}
+}
+
+static void
+setup_apply_entry_style (GtkWidget *entry)
+{
+	gtk_widget_modify_base (entry, GTK_STATE_NORMAL, &colors[COL_BG]);
+	gtk_widget_modify_text (entry, GTK_STATE_NORMAL, &colors[COL_FG]);
+	gtk_widget_modify_font (entry, input_style->font_desc);
+}
+
+static void
+setup_apply_to_sess (session_gui *gui)
+{
+#ifdef USE_GTKSPELL
+	GtkSpell *spell;
+#endif
+
+	mg_update_xtext (gui->xtext);
+
+	if (prefs.style_namelistgad)
+		gtk_widget_set_style (gui->user_tree, input_style);
+
+	if (prefs.style_inputbox)
+	{
+		extern char cursor_color_rc[];
+		char buf[256];
+		sprintf (buf, cursor_color_rc,
+				(colors[COL_FG].red >> 8),
+				(colors[COL_FG].green >> 8),
+				(colors[COL_FG].blue >> 8));
+		gtk_rc_parse_string (buf);
+
+		setup_apply_entry_style (gui->input_box);
+		setup_apply_entry_style (gui->limit_entry);
+		setup_apply_entry_style (gui->key_entry);
+		setup_apply_entry_style (gui->topic_entry);
+	}
+
+	if (prefs.userlistbuttons)
+		gtk_widget_show (gui->button_box);
+	else
+		gtk_widget_hide (gui->button_box);
+
+#ifdef USE_GTKSPELL
+	spell = gtkspell_get_from_text_view (GTK_TEXT_VIEW (gui->input_box));
+	if (prefs.gui_input_spell)
+	{
+		if (!spell)
+			gtkspell_new_attach (GTK_TEXT_VIEW (gui->input_box), NULL, NULL);
+	}
+	else
+	{
+		if (spell)
+			gtkspell_detach (spell);
+	}
+#endif
+
+#ifdef USE_LIBSEXY
+	sexy_spell_entry_set_checked ((SexySpellEntry *)gui->input_box, prefs.gui_input_spell);
+#endif
+}
+
+static void
+unslash (char *dir)
+{
+	if (dir[0])
+	{
+		int len = strlen (dir) - 1;
+#ifdef WIN32
+		if (dir[len] == '/' || dir[len] == '\\')
+#else
+		if (dir[len] == '/')
+#endif
+			dir[len] = 0;
+	}
+}
+
+void
+setup_apply_real (int new_pix, int do_ulist, int do_layout)
+{
+	GSList *list;
+	session *sess;
+	int done_main = FALSE;
+
+	/* remove trailing slashes */
+	unslash (prefs.dccdir);
+	unslash (prefs.dcc_completed_dir);
+
+	mkdir_utf8 (prefs.dccdir);
+	mkdir_utf8 (prefs.dcc_completed_dir);
+
+	if (new_pix)
+	{
+		if (channelwin_pix)
+			g_object_unref (channelwin_pix);
+		channelwin_pix = pixmap_load_from_file (prefs.background);
+	}
+
+	input_style = create_input_style (input_style);
+
+	list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+		if (sess->gui->is_tab)
+		{
+			/* only apply to main tabwindow once */
+			if (!done_main)
+			{
+				done_main = TRUE;
+				setup_apply_to_sess (sess->gui);
+			}
+		} else
+		{
+			setup_apply_to_sess (sess->gui);
+		}
+
+		log_open_or_close (sess);
+
+		if (do_ulist)
+			userlist_rehash (sess);
+
+		list = list->next;
+	}
+
+	mg_apply_setup ();
+	tray_apply_setup ();
+
+	if (do_layout)
+		menu_change_layout ();
+}
+
+static void
+setup_apply (struct xchatprefs *pr)
+{
+	int new_pix = FALSE;
+	int noapply = FALSE;
+	int do_ulist = FALSE;
+	int do_layout = FALSE;
+
+	if (strcmp (pr->background, prefs.background) != 0)
+		new_pix = TRUE;
+
+#define DIFF(a) (pr->a != prefs.a)
+
+	if (DIFF (paned_userlist))
+		noapply = TRUE;
+	if (DIFF (lagometer))
+		noapply = TRUE;
+	if (DIFF (throttlemeter))
+		noapply = TRUE;
+	if (DIFF (showhostname_in_userlist))
+		noapply = TRUE;
+	if (DIFF (tab_small))
+		noapply = TRUE;
+	if (DIFF (tab_sort))
+		noapply = TRUE;
+	if (DIFF (use_server_tab))
+		noapply = TRUE;
+	if (DIFF (style_namelistgad))
+		noapply = TRUE;
+	if (DIFF (truncchans))
+		noapply = TRUE;
+	if (DIFF (tab_layout))
+		do_layout = TRUE;
+
+	if (color_change || (DIFF (away_size_max)) || (DIFF (away_track)))
+		do_ulist = TRUE;
+
+	if ((pr->tab_pos == 5 || pr->tab_pos == 6) &&
+		 pr->tab_layout == 2 && pr->tab_pos != prefs.tab_pos)
+		fe_message (_("You cannot place the tree on the top or bottom!\n"
+						"Please change to the <b>Tabs</b> layout in the <b>View</b>"
+						" menu first."),
+						FE_MSG_WARN | FE_MSG_MARKUP);
+
+	memcpy (&prefs, pr, sizeof (prefs));
+
+	setup_apply_real (new_pix, do_ulist, do_layout);
+
+	if (noapply)
+		fe_message (_("Some settings were changed that require a"
+						" restart to take full effect."), FE_MSG_WARN);
+
+#ifndef WIN32
+	if (prefs.autodccsend == 1)
+	{
+		if (!strcmp ((char *)g_get_home_dir (), prefs.dccdir))
+		{
+			fe_message (_("*WARNING*\n"
+							 "Auto accepting DCC to your home directory\n"
+							 "can be dangerous and is exploitable. Eg:\n"
+							 "Someone could send you a .bash_profile"), FE_MSG_WARN);
+		}
+	}
+#endif
+}
+
+#if 0
+static void
+setup_apply_cb (GtkWidget *but, GtkWidget *win)
+{
+	/* setup_prefs -> xchat */
+	setup_apply (&setup_prefs);
+}
+#endif
+
+static void
+setup_ok_cb (GtkWidget *but, GtkWidget *win)
+{
+	setup_snd_apply ();
+	gtk_widget_destroy (win);
+	setup_apply (&setup_prefs);
+	save_config ();
+	palette_save ();
+}
+
+static GtkWidget *
+setup_window_open (void)
+{
+	GtkWidget *wid, *win, *vbox, *hbox, *hbbox;
+
+	win = gtkutil_window_new (_("XChat: Preferences"), "prefs", 0, 0, 3);
+
+	vbox = gtk_vbox_new (FALSE, 5);
+	gtk_container_set_border_width (GTK_CONTAINER (vbox), 6);
+	gtk_container_add (GTK_CONTAINER (win), vbox);
+
+	hbox = gtk_hbox_new (FALSE, 4);
+	gtk_container_add (GTK_CONTAINER (vbox), hbox);
+
+	setup_create_tree (hbox, setup_create_pages (hbox));
+
+	hbox = gtk_hbox_new (FALSE, 0);
+	gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+
+	/* prepare the button box */
+	hbbox = gtk_hbutton_box_new ();
+	gtk_box_set_spacing (GTK_BOX (hbbox), 4);
+	gtk_box_pack_end (GTK_BOX (hbox), hbbox, FALSE, FALSE, 0);
+
+	/* standard buttons */
+	/* GNOME doesn't like apply */
+#if 0
+	wid = gtk_button_new_from_stock (GTK_STOCK_APPLY);
+	g_signal_connect (G_OBJECT (wid), "clicked",
+							G_CALLBACK (setup_apply_cb), win);
+	gtk_box_pack_start (GTK_BOX (hbbox), wid, FALSE, FALSE, 0);
+#endif
+
+	cancel_button = wid = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
+	g_signal_connect (G_OBJECT (wid), "clicked",
+							G_CALLBACK (gtkutil_destroy), win);
+	gtk_box_pack_start (GTK_BOX (hbbox), wid, FALSE, FALSE, 0);
+
+	wid = gtk_button_new_from_stock (GTK_STOCK_OK);
+	g_signal_connect (G_OBJECT (wid), "clicked",
+							G_CALLBACK (setup_ok_cb), win);
+	gtk_box_pack_start (GTK_BOX (hbbox), wid, FALSE, FALSE, 0);
+
+	wid = gtk_hseparator_new ();
+	gtk_box_pack_end (GTK_BOX (vbox), wid, FALSE, FALSE, 0);
+
+	gtk_widget_show_all (win);
+
+	return win;
+}
+
+static void
+setup_close_cb (GtkWidget *win, GtkWidget **swin)
+{
+	*swin = NULL;
+
+	if (font_dialog)
+	{
+		gtk_widget_destroy (font_dialog);
+		font_dialog = NULL;
+	}
+}
+
+void
+setup_open (void)
+{
+	static GtkWidget *setup_window = NULL;
+
+	if (setup_window)
+	{
+		gtk_window_present (GTK_WINDOW (setup_window));
+		return;
+	}
+
+	memcpy (&setup_prefs, &prefs, sizeof (prefs));
+
+	color_change = FALSE;
+	setup_window = setup_window_open ();
+
+	g_signal_connect (G_OBJECT (setup_window), "destroy",
+							G_CALLBACK (setup_close_cb), &setup_window);
+}
diff --git a/src/fe-gtk/sexy-spell-entry.c b/src/fe-gtk/sexy-spell-entry.c
new file mode 100644
index 00000000..d67ffe2d
--- /dev/null
+++ b/src/fe-gtk/sexy-spell-entry.c
@@ -0,0 +1,1329 @@
+/*
+ * @file libsexy/sexy-icon-entry.c Entry widget
+ *
+ * @Copyright (C) 2004-2006 Christian Hammond.
+ * Some of this code is from gtkspell, Copyright (C) 2002 Evan Martin.
+ *
+ * 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 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.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <gtk/gtk.h>
+#include "sexy-spell-entry.h"
+#include <string.h>
+#include <glib/gi18n.h>
+#include <sys/types.h>
+/*#include "gtkspell-iso-codes.h"
+#include "sexy-marshal.h"*/
+
+/*
+ * Bunch of poop to make enchant into a runtime dependency rather than a
+ * compile-time dependency.  This makes it so I don't have to hear the
+ * complaints from people with binary distributions who don't get spell
+ * checking because they didn't check their configure output.
+ */
+struct EnchantDict;
+struct EnchantBroker;
+
+typedef void (*EnchantDictDescribeFn) (const char * const lang_tag,
+                                       const char * const provider_name,
+                                       const char * const provider_desc,
+                                       const char * const provider_file,
+                                       void * user_data);
+
+static struct EnchantBroker * (*enchant_broker_init) (void);
+static void (*enchant_broker_free) (struct EnchantBroker * broker);
+static void (*enchant_broker_free_dict) (struct EnchantBroker * broker, struct EnchantDict * dict);
+static void (*enchant_broker_list_dicts) (struct EnchantBroker * broker, EnchantDictDescribeFn fn, void * user_data);
+static struct EnchantDict * (*enchant_broker_request_dict) (struct EnchantBroker * broker, const char *const tag);
+
+static void (*enchant_dict_add_to_personal) (struct EnchantDict * dict, const char *const word, ssize_t len);
+static void (*enchant_dict_add_to_session) (struct EnchantDict * dict, const char *const word, ssize_t len);
+static int (*enchant_dict_check) (struct EnchantDict * dict, const char *const word, ssize_t len);
+static void (*enchant_dict_describe) (struct EnchantDict * dict, EnchantDictDescribeFn fn, void * user_data);
+static void (*enchant_dict_free_suggestions) (struct EnchantDict * dict, char **suggestions);
+static void (*enchant_dict_store_replacement) (struct EnchantDict * dict, const char *const mis, ssize_t mis_len, const char *const cor, ssize_t cor_len);
+static char ** (*enchant_dict_suggest) (struct EnchantDict * dict, const char *const word, ssize_t len, size_t * out_n_suggs);
+static gboolean have_enchant = FALSE;
+
+struct _SexySpellEntryPriv
+{
+	struct EnchantBroker *broker;
+	PangoAttrList        *attr_list;
+	gint                  mark_character;
+	GHashTable           *dict_hash;
+	GSList               *dict_list;
+	gchar               **words;
+	gint                 *word_starts;
+	gint                 *word_ends;
+	gboolean              checked;
+};
+
+static void sexy_spell_entry_class_init(SexySpellEntryClass *klass);
+static void sexy_spell_entry_editable_init (GtkEditableClass *iface);
+static void sexy_spell_entry_init(SexySpellEntry *entry);
+static void sexy_spell_entry_finalize(GObject *obj);
+static void sexy_spell_entry_destroy(GtkObject *obj);
+static gint sexy_spell_entry_expose(GtkWidget *widget, GdkEventExpose *event);
+static gint sexy_spell_entry_button_press(GtkWidget *widget, GdkEventButton *event);
+
+/* GtkEditable handlers */
+static void sexy_spell_entry_changed(GtkEditable *editable, gpointer data);
+
+/* Other handlers */
+static gboolean sexy_spell_entry_popup_menu(GtkWidget *widget, SexySpellEntry *entry);
+
+/* Internal utility functions */
+static gint       gtk_entry_find_position                     (GtkEntry             *entry,
+                                                               gint                  x);
+static gboolean   word_misspelled                             (SexySpellEntry       *entry,
+                                                               int                   start,
+                                                               int                   end);
+static gboolean   default_word_check                          (SexySpellEntry       *entry,
+                                                               const gchar          *word);
+static gboolean   sexy_spell_entry_activate_language_internal (SexySpellEntry       *entry,
+                                                               const gchar          *lang,
+                                                               GError              **error);
+static gchar     *get_lang_from_dict                          (struct EnchantDict   *dict);
+static void       sexy_spell_entry_recheck_all                (SexySpellEntry       *entry);
+static void       entry_strsplit_utf8                         (GtkEntry             *entry,
+                                                               gchar              ***set,
+                                                               gint                **starts,
+                                                               gint                **ends);
+
+static GtkEntryClass *parent_class = NULL;
+
+G_DEFINE_TYPE_EXTENDED(SexySpellEntry, sexy_spell_entry, GTK_TYPE_ENTRY, 0, G_IMPLEMENT_INTERFACE(GTK_TYPE_EDITABLE, sexy_spell_entry_editable_init));
+
+enum
+{
+	WORD_CHECK,
+	LAST_SIGNAL
+};
+static guint signals[LAST_SIGNAL] = {0};
+
+static gboolean
+spell_accumulator(GSignalInvocationHint *hint, GValue *return_accu, const GValue *handler_return, gpointer data)
+{
+	gboolean ret = g_value_get_boolean(handler_return);
+	/* Handlers return TRUE if the word is misspelled.  In this
+	 * case, it means that we want to stop if the word is checked
+	 * as correct */
+	g_value_set_boolean (return_accu, ret);
+	return ret;
+}
+
+static void
+initialize_enchant ()
+{
+	GModule *enchant;
+	gpointer funcptr;
+
+	enchant = g_module_open("libenchant", 0);
+	if (enchant == NULL)
+	{
+		enchant = g_module_open("libenchant.so.1", 0);
+		if (enchant == NULL)
+			return;
+	}
+
+	have_enchant = TRUE;
+
+#define MODULE_SYMBOL(name, func) \
+	g_module_symbol(enchant, (name), &funcptr); \
+	(func) = funcptr;
+
+	MODULE_SYMBOL("enchant_broker_init", enchant_broker_init)
+	MODULE_SYMBOL("enchant_broker_free", enchant_broker_free)
+	MODULE_SYMBOL("enchant_broker_free_dict", enchant_broker_free_dict)
+	MODULE_SYMBOL("enchant_broker_list_dicts", enchant_broker_list_dicts)
+	MODULE_SYMBOL("enchant_broker_request_dict", enchant_broker_request_dict)
+
+	MODULE_SYMBOL("enchant_dict_add_to_personal", enchant_dict_add_to_personal)
+	MODULE_SYMBOL("enchant_dict_add_to_session", enchant_dict_add_to_session)
+	MODULE_SYMBOL("enchant_dict_check", enchant_dict_check)
+	MODULE_SYMBOL("enchant_dict_describe", enchant_dict_describe)
+	MODULE_SYMBOL("enchant_dict_free_suggestions",
+				  enchant_dict_free_suggestions)
+	MODULE_SYMBOL("enchant_dict_store_replacement",
+				  enchant_dict_store_replacement)
+	MODULE_SYMBOL("enchant_dict_suggest", enchant_dict_suggest)
+
+#undef MODULE_SYMBOL
+}
+
+static void
+sexy_spell_entry_class_init(SexySpellEntryClass *klass)
+{
+	GObjectClass *gobject_class;
+	GtkObjectClass *object_class;
+	GtkWidgetClass *widget_class;
+	GtkEntryClass *entry_class;
+
+	initialize_enchant();
+
+	parent_class = g_type_class_peek_parent(klass);
+
+	gobject_class = G_OBJECT_CLASS(klass);
+	object_class  = GTK_OBJECT_CLASS(klass);
+	widget_class  = GTK_WIDGET_CLASS(klass);
+	entry_class   = GTK_ENTRY_CLASS(klass);
+
+	if (have_enchant)
+		klass->word_check = default_word_check;
+
+	gobject_class->finalize = sexy_spell_entry_finalize;
+
+	object_class->destroy = sexy_spell_entry_destroy;
+
+	widget_class->expose_event = sexy_spell_entry_expose;
+	widget_class->button_press_event = sexy_spell_entry_button_press;
+
+	/**
+	 * SexySpellEntry::word-check:
+	 * @entry: The entry on which the signal is emitted.
+	 * @word: The word to check.
+	 *
+	 * The ::word-check signal is emitted whenever the entry has to check
+	 * a word.  This allows the application to mark words as correct even
+	 * if none of the active dictionaries contain it, such as nicknames in
+	 * a chat client.
+	 *
+	 * Returns: %FALSE to indicate that the word should be marked as
+	 * correct.
+	 */
+/*	signals[WORD_CHECK] = g_signal_new("word_check",
+					   G_TYPE_FROM_CLASS(object_class),
+					   G_SIGNAL_RUN_LAST,
+					   G_STRUCT_OFFSET(SexySpellEntryClass, word_check),
+					   (GSignalAccumulator) spell_accumulator, NULL,
+					   sexy_marshal_BOOLEAN__STRING,
+					   G_TYPE_BOOLEAN,
+					   1, G_TYPE_STRING);*/
+}
+
+static void
+sexy_spell_entry_editable_init (GtkEditableClass *iface)
+{
+}
+
+static gint
+gtk_entry_find_position (GtkEntry *entry, gint x)
+{
+	PangoLayout *layout;
+	PangoLayoutLine *line;
+	const gchar *text;
+	gint cursor_index;
+	gint index;
+	gint pos;
+	gboolean trailing;
+
+	x = x + entry->scroll_offset;
+
+	layout = gtk_entry_get_layout(entry);
+	text = pango_layout_get_text(layout);
+	cursor_index = g_utf8_offset_to_pointer(text, entry->current_pos) - text;
+
+	line = pango_layout_get_lines(layout)->data;
+	pango_layout_line_x_to_index(line, x * PANGO_SCALE, &index, &trailing);
+
+	if (index >= cursor_index && entry->preedit_length) {
+		if (index >= cursor_index + entry->preedit_length) {
+			index -= entry->preedit_length;
+		} else {
+			index = cursor_index;
+			trailing = FALSE;
+		}
+	}
+
+	pos = g_utf8_pointer_to_offset (text, text + index);
+	pos += trailing;
+
+	return pos;
+}
+
+static void
+insert_underline(SexySpellEntry *entry, guint start, guint end)
+{
+	PangoAttribute *ucolor = pango_attr_underline_color_new (65535, 0, 0);
+	PangoAttribute *unline = pango_attr_underline_new (PANGO_UNDERLINE_ERROR);
+
+	ucolor->start_index = start;
+	unline->start_index = start;
+
+	ucolor->end_index = end;
+	unline->end_index = end;
+
+	pango_attr_list_insert (entry->priv->attr_list, ucolor);
+	pango_attr_list_insert (entry->priv->attr_list, unline);
+}
+
+static void
+get_word_extents_from_position(SexySpellEntry *entry, gint *start, gint *end, guint position)
+{
+	const gchar *text;
+	gint i, bytes_pos;
+
+	*start = -1;
+	*end = -1;
+
+	if (entry->priv->words == NULL)
+		return;
+
+	text = gtk_entry_get_text(GTK_ENTRY(entry));
+	bytes_pos = (gint) (g_utf8_offset_to_pointer(text, position) - text);
+
+	for (i = 0; entry->priv->words[i]; i++) {
+		if (bytes_pos >= entry->priv->word_starts[i] &&
+		    bytes_pos <= entry->priv->word_ends[i]) {
+			*start = entry->priv->word_starts[i];
+			*end   = entry->priv->word_ends[i];
+			return;
+		}
+	}
+}
+
+static void
+add_to_dictionary(GtkWidget *menuitem, SexySpellEntry *entry)
+{
+	char *word;
+	gint start, end;
+	struct EnchantDict *dict;
+
+	if (!have_enchant)
+		return;
+
+	get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character);
+	word = gtk_editable_get_chars(GTK_EDITABLE(entry), start, end);
+
+	dict = (struct EnchantDict *) g_object_get_data(G_OBJECT(menuitem), "enchant-dict");
+	if (dict)
+		enchant_dict_add_to_personal(dict, word, -1);
+
+	g_free(word);
+
+	if (entry->priv->words) {
+		g_strfreev(entry->priv->words);
+		g_free(entry->priv->word_starts);
+		g_free(entry->priv->word_ends);
+	}
+	entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
+	sexy_spell_entry_recheck_all(entry);
+}
+
+static void
+ignore_all(GtkWidget *menuitem, SexySpellEntry *entry)
+{
+	char *word;
+	gint start, end;
+	GSList *li;
+
+	if (!have_enchant)
+		return;
+
+	get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character);
+	word = gtk_editable_get_chars(GTK_EDITABLE(entry), start, end);
+
+	for (li = entry->priv->dict_list; li; li = g_slist_next (li)) {
+		struct EnchantDict *dict = (struct EnchantDict *) li->data;
+		enchant_dict_add_to_session(dict, word, -1);
+	}
+
+	g_free(word);
+
+	if (entry->priv->words) {
+		g_strfreev(entry->priv->words);
+		g_free(entry->priv->word_starts);
+		g_free(entry->priv->word_ends);
+	}
+	entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
+	sexy_spell_entry_recheck_all(entry);
+}
+
+static void
+replace_word(GtkWidget *menuitem, SexySpellEntry *entry)
+{
+	char *oldword;
+	const char *newword;
+	gint start, end;
+	gint cursor;
+	struct EnchantDict *dict;
+
+	if (!have_enchant)
+		return;
+
+	get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character);
+	oldword = gtk_editable_get_chars(GTK_EDITABLE(entry), start, end);
+	newword = gtk_label_get_text(GTK_LABEL(GTK_BIN(menuitem)->child));
+
+	cursor = gtk_editable_get_position(GTK_EDITABLE(entry));
+	/* is the cursor at the end? If so, restore it there */
+	if (g_utf8_strlen(gtk_entry_get_text(GTK_ENTRY(entry)), -1) == cursor)
+		cursor = -1;
+	else if(cursor > start && cursor <= end)
+		cursor = start;
+
+	gtk_editable_delete_text(GTK_EDITABLE(entry), start, end);
+	gtk_editable_set_position(GTK_EDITABLE(entry), start);
+	gtk_editable_insert_text(GTK_EDITABLE(entry), newword, strlen(newword),
+							 &start);
+	gtk_editable_set_position(GTK_EDITABLE(entry), cursor);
+
+	dict = (struct EnchantDict *) g_object_get_data(G_OBJECT(menuitem), "enchant-dict");
+
+        if (dict)
+		enchant_dict_store_replacement(dict,
+					       oldword, -1,
+					       newword, -1);
+
+	g_free(oldword);
+}
+
+static void
+build_suggestion_menu(SexySpellEntry *entry, GtkWidget *menu, struct EnchantDict *dict, const gchar *word)
+{
+	GtkWidget *mi;
+	gchar **suggestions;
+	size_t n_suggestions, i;
+
+	if (!have_enchant)
+		return;
+
+	suggestions = enchant_dict_suggest(dict, word, -1, &n_suggestions);
+
+	if (suggestions == NULL || n_suggestions == 0) {
+		/* no suggestions.  put something in the menu anyway... */
+		GtkWidget *label = gtk_label_new("");
+		gtk_label_set_markup(GTK_LABEL(label), _("<i>(no suggestions)</i>"));
+
+		mi = gtk_separator_menu_item_new();
+		gtk_container_add(GTK_CONTAINER(mi), label);
+		gtk_widget_show_all(mi);
+		gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
+	} else {
+		/* build a set of menus with suggestions */
+		for (i = 0; i < n_suggestions; i++) {
+			if ((i != 0) && (i % 10 == 0)) {
+				mi = gtk_separator_menu_item_new();
+				gtk_widget_show(mi);
+				gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
+
+				mi = gtk_menu_item_new_with_label(_("More..."));
+				gtk_widget_show(mi);
+				gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
+
+				menu = gtk_menu_new();
+				gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), menu);
+			}
+
+			mi = gtk_menu_item_new_with_label(suggestions[i]);
+			g_object_set_data(G_OBJECT(mi), "enchant-dict", dict);
+			g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(replace_word), entry);
+			gtk_widget_show(mi);
+			gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
+		}
+	}
+
+	enchant_dict_free_suggestions(dict, suggestions);
+}
+
+static GtkWidget *
+build_spelling_menu(SexySpellEntry *entry, const gchar *word)
+{
+	struct EnchantDict *dict;
+	GtkWidget *topmenu, *mi;
+	gchar *label;
+
+	if (!have_enchant)
+		return NULL;
+
+	topmenu = gtk_menu_new();
+
+	if (entry->priv->dict_list == NULL)
+		return topmenu;
+
+#if 1
+	dict = (struct EnchantDict *) entry->priv->dict_list->data;
+	build_suggestion_menu(entry, topmenu, dict, word);
+#else
+	/* Suggestions */
+	if (g_slist_length(entry->priv->dict_list) == 1) {
+		dict = (struct EnchantDict *) entry->priv->dict_list->data;
+		build_suggestion_menu(entry, topmenu, dict, word);
+	} else {
+		GSList *li;
+		GtkWidget *menu;
+		gchar *lang, *lang_name;
+
+		for (li = entry->priv->dict_list; li; li = g_slist_next (li)) {
+			dict = (struct EnchantDict *) li->data;
+			lang = get_lang_from_dict(dict);
+			lang_name = gtkspell_iso_codes_lookup_name_for_code(lang);
+			if (lang_name) {
+				mi = gtk_menu_item_new_with_label(lang_name);
+				g_free(lang_name);
+			} else {
+				mi = gtk_menu_item_new_with_label(lang);
+			}
+			g_free(lang);
+
+			gtk_widget_show(mi);
+			gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi);
+			menu = gtk_menu_new();
+			gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), menu);
+			build_suggestion_menu(entry, menu, dict, word);
+		}
+	}
+#endif
+
+	/* Separator */
+	mi = gtk_separator_menu_item_new ();
+	gtk_widget_show(mi);
+	gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi);
+
+	/* + Add to Dictionary */
+	label = g_strdup_printf(_("Add \"%s\" to Dictionary"), word);
+	mi = gtk_image_menu_item_new_with_label(label);
+	g_free(label);
+
+	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU));
+
+#if 1
+	dict = (struct EnchantDict *) entry->priv->dict_list->data;
+	g_object_set_data(G_OBJECT(mi), "enchant-dict", dict);
+	g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(add_to_dictionary), entry);
+#else
+	if (g_slist_length(entry->priv->dict_list) == 1) {
+		dict = (struct EnchantDict *) entry->priv->dict_list->data;
+		g_object_set_data(G_OBJECT(mi), "enchant-dict", dict);
+		g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(add_to_dictionary), entry);
+	} else {
+		GSList *li;
+		GtkWidget *menu, *submi;
+		gchar *lang, *lang_name;
+
+		menu = gtk_menu_new();
+		gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), menu);
+
+		for (li = entry->priv->dict_list; li; li = g_slist_next(li)) {
+			dict = (struct EnchantDict *)li->data;
+			lang = get_lang_from_dict(dict);
+			lang_name = gtkspell_iso_codes_lookup_name_for_code(lang);
+			if (lang_name) {
+				submi = gtk_menu_item_new_with_label(lang_name);
+				g_free(lang_name);
+			} else {
+				submi = gtk_menu_item_new_with_label(lang);
+			}
+			g_free(lang);
+			g_object_set_data(G_OBJECT(submi), "enchant-dict", dict);
+
+			g_signal_connect(G_OBJECT(submi), "activate", G_CALLBACK(add_to_dictionary), entry);
+
+			gtk_widget_show(submi);
+			gtk_menu_shell_append(GTK_MENU_SHELL(menu), submi);
+		}
+	}
+#endif
+
+	gtk_widget_show_all(mi);
+	gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi);
+
+	/* - Ignore All */
+	mi = gtk_image_menu_item_new_with_label(_("Ignore All"));
+	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), gtk_image_new_from_stock(GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU));
+	g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(ignore_all), entry);
+	gtk_widget_show_all(mi);
+	gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi);
+
+	return topmenu;
+}
+
+static void
+sexy_spell_entry_populate_popup(SexySpellEntry *entry, GtkMenu *menu, gpointer data)
+{
+	GtkWidget *icon, *mi;
+	gint start, end;
+	gchar *word;
+
+	if ((have_enchant == FALSE) || (entry->priv->checked == FALSE))
+		return;
+
+	if (g_slist_length(entry->priv->dict_list) == 0)
+		return;
+
+	get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character);
+	if (start == end)
+		return;
+	if (!word_misspelled(entry, start, end))
+		return;
+
+	/* separator */
+	mi = gtk_separator_menu_item_new();
+	gtk_widget_show(mi);
+	gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
+
+	/* Above the separator, show the suggestions menu */
+	icon = gtk_image_new_from_stock(GTK_STOCK_SPELL_CHECK, GTK_ICON_SIZE_MENU);
+	mi = gtk_image_menu_item_new_with_label(_("Spelling Suggestions"));
+	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), icon);
+
+	word = gtk_editable_get_chars(GTK_EDITABLE(entry), start, end);
+	g_assert(word != NULL);
+	gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), build_spelling_menu(entry, word));
+	g_free(word);
+
+	gtk_widget_show_all(mi);
+	gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
+}
+
+static void
+sexy_spell_entry_init(SexySpellEntry *entry)
+{
+	entry->priv = g_new0(SexySpellEntryPriv, 1);
+
+	entry->priv->dict_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
+	if (have_enchant)
+		sexy_spell_entry_activate_default_languages(entry);
+
+	entry->priv->attr_list = pango_attr_list_new();
+
+	entry->priv->checked = TRUE;
+
+	g_signal_connect(G_OBJECT(entry), "popup-menu", G_CALLBACK(sexy_spell_entry_popup_menu), entry);
+	g_signal_connect(G_OBJECT(entry), "populate-popup", G_CALLBACK(sexy_spell_entry_populate_popup), NULL);
+	g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(sexy_spell_entry_changed), NULL);
+}
+
+static void
+sexy_spell_entry_finalize(GObject *obj)
+{
+	SexySpellEntry *entry;
+
+	g_return_if_fail(obj != NULL);
+	g_return_if_fail(SEXY_IS_SPELL_ENTRY(obj));
+
+	entry = SEXY_SPELL_ENTRY(obj);
+
+	if (entry->priv->attr_list)
+		pango_attr_list_unref(entry->priv->attr_list);
+	if (entry->priv->dict_hash)
+		g_hash_table_destroy(entry->priv->dict_hash);
+	if (entry->priv->words)
+		g_strfreev(entry->priv->words);
+	if (entry->priv->word_starts)
+		g_free(entry->priv->word_starts);
+	if (entry->priv->word_ends)
+		g_free(entry->priv->word_ends);
+
+	if (have_enchant) {
+		if (entry->priv->broker) {
+			GSList *li;
+			for (li = entry->priv->dict_list; li; li = g_slist_next(li)) {
+				struct EnchantDict *dict = (struct EnchantDict*) li->data;
+				enchant_broker_free_dict (entry->priv->broker, dict);
+			}
+			g_slist_free (entry->priv->dict_list);
+
+			enchant_broker_free(entry->priv->broker);
+		}
+	}
+
+	g_free(entry->priv);
+
+	if (G_OBJECT_CLASS(parent_class)->finalize)
+		G_OBJECT_CLASS(parent_class)->finalize(obj);
+}
+
+static void
+sexy_spell_entry_destroy(GtkObject *obj)
+{
+	SexySpellEntry *entry;
+
+	entry = SEXY_SPELL_ENTRY(obj);
+
+	if (GTK_OBJECT_CLASS(parent_class)->destroy)
+		GTK_OBJECT_CLASS(parent_class)->destroy(obj);
+}
+
+/**
+ * sexy_spell_entry_new
+ *
+ * Creates a new SexySpellEntry widget.
+ *
+ * Returns: a new #SexySpellEntry.
+ */
+GtkWidget *
+sexy_spell_entry_new(void)
+{
+	return GTK_WIDGET(g_object_new(SEXY_TYPE_SPELL_ENTRY, NULL));
+}
+
+GQuark
+sexy_spell_error_quark(void)
+{
+	static GQuark q = 0;
+	if (q == 0)
+		q = g_quark_from_static_string("sexy-spell-error-quark");
+	return q;
+}
+
+static gboolean
+default_word_check(SexySpellEntry *entry, const gchar *word)
+{
+	gboolean result = TRUE;
+	GSList *li;
+
+	if (!have_enchant)
+		return result;
+
+	if (g_unichar_isalpha(*word) == FALSE) {
+		/* We only want to check words */
+		return FALSE;
+	}
+	for (li = entry->priv->dict_list; li; li = g_slist_next (li)) {
+		struct EnchantDict *dict = (struct EnchantDict *) li->data;
+		if (enchant_dict_check(dict, word, strlen(word)) == 0) {
+			result = FALSE;
+			break;
+		}
+	}
+	return result;
+}
+
+static gboolean
+word_misspelled(SexySpellEntry *entry, int start, int end)
+{
+	const gchar *text;
+	gchar *word;
+	gboolean ret;
+
+	if (start == end)
+		return FALSE;
+	text = gtk_entry_get_text(GTK_ENTRY(entry));
+	word = g_new0(gchar, end - start + 2);
+
+	g_strlcpy(word, text + start, end - start + 1);
+
+#if 0
+	g_signal_emit(entry, signals[WORD_CHECK], 0, word, &ret);
+#else
+	ret = default_word_check (entry, word);
+#endif
+
+	g_free(word);
+	return ret;
+}
+
+static void
+check_word(SexySpellEntry *entry, int start, int end)
+{
+	PangoAttrIterator *it;
+
+	/* Check to see if we've got any attributes at this position.
+	 * If so, free them, since we'll readd it if the word is misspelled */
+	it = pango_attr_list_get_iterator(entry->priv->attr_list);
+	if (it == NULL)
+		return;
+	do {
+		gint s, e;
+		pango_attr_iterator_range(it, &s, &e);
+		if (s == start) {
+			GSList *attrs = pango_attr_iterator_get_attrs(it);
+			g_slist_foreach(attrs, (GFunc) pango_attribute_destroy, NULL);
+			g_slist_free(attrs);
+		}
+	} while (pango_attr_iterator_next(it));
+	pango_attr_iterator_destroy(it);
+
+	if (word_misspelled(entry, start, end))
+		insert_underline(entry, start, end);
+}
+
+static void
+sexy_spell_entry_recheck_all(SexySpellEntry *entry)
+{
+	GdkRectangle rect;
+	GtkWidget *widget = GTK_WIDGET(entry);
+	PangoLayout *layout;
+	int length, i;
+
+	if ((have_enchant == FALSE) || (entry->priv->checked == FALSE))
+		return;
+
+	if (g_slist_length(entry->priv->dict_list) == 0)
+		return;
+
+	/* Remove all existing pango attributes.  These will get readded as we check */
+	pango_attr_list_unref(entry->priv->attr_list);
+	entry->priv->attr_list = pango_attr_list_new();
+
+	/* Loop through words */
+	for (i = 0; entry->priv->words[i]; i++) {
+		length = strlen(entry->priv->words[i]);
+		if (length == 0)
+			continue;
+		check_word(entry, entry->priv->word_starts[i], entry->priv->word_ends[i]);
+	}
+
+	layout = gtk_entry_get_layout(GTK_ENTRY(entry));
+	pango_layout_set_attributes(layout, entry->priv->attr_list);
+
+	if (GTK_WIDGET_REALIZED(GTK_WIDGET(entry))) {
+		rect.x = 0; rect.y = 0;
+		rect.width  = widget->allocation.width;
+		rect.height = widget->allocation.height;
+		gdk_window_invalidate_rect(widget->window, &rect, TRUE);
+	}
+}
+
+static gint
+sexy_spell_entry_expose(GtkWidget *widget, GdkEventExpose *event)
+{
+	SexySpellEntry *entry = SEXY_SPELL_ENTRY(widget);
+	GtkEntry *gtk_entry = GTK_ENTRY(widget);
+	PangoLayout *layout;
+
+	if (entry->priv->checked) {
+		layout = gtk_entry_get_layout(gtk_entry);
+		pango_layout_set_attributes(layout, entry->priv->attr_list);
+	}
+
+	return GTK_WIDGET_CLASS(parent_class)->expose_event (widget, event);
+}
+
+static gint
+sexy_spell_entry_button_press(GtkWidget *widget, GdkEventButton *event)
+{
+	SexySpellEntry *entry = SEXY_SPELL_ENTRY(widget);
+	GtkEntry *gtk_entry = GTK_ENTRY(widget);
+	gint pos;
+
+	pos = gtk_entry_find_position(gtk_entry, event->x);
+	entry->priv->mark_character = pos;
+
+	return GTK_WIDGET_CLASS(parent_class)->button_press_event (widget, event);
+}
+
+static gboolean
+sexy_spell_entry_popup_menu(GtkWidget *widget, SexySpellEntry *entry)
+{
+	/* Menu popped up from a keybinding (menu key or <shift>+F10). Use
+	 * the cursor position as the mark position */
+	entry->priv->mark_character = gtk_editable_get_position (GTK_EDITABLE (entry));
+	return FALSE;
+}
+
+static void
+entry_strsplit_utf8(GtkEntry *entry, gchar ***set, gint **starts, gint **ends)
+{
+	PangoLayout   *layout;
+	PangoLogAttr  *log_attrs;
+	const gchar   *text;
+	gint           n_attrs, n_strings, i, j;
+
+	layout = gtk_entry_get_layout(GTK_ENTRY(entry));
+	text = gtk_entry_get_text(GTK_ENTRY(entry));
+	pango_layout_get_log_attrs(layout, &log_attrs, &n_attrs);
+
+	/* Find how many words we have */
+	n_strings = 0;
+	for (i = 0; i < n_attrs; i++)
+		if (log_attrs[i].is_word_start)
+			n_strings++;
+
+	*set    = g_new0(gchar *, n_strings + 1);
+	*starts = g_new0(gint, n_strings);
+	*ends   = g_new0(gint, n_strings);
+
+	/* Copy out strings */
+	for (i = 0, j = 0; i < n_attrs; i++) {
+		if (log_attrs[i].is_word_start) {
+			gint cend, bytes;
+			gchar *start;
+
+			/* Find the end of this string */
+			cend = i;
+			while (!(log_attrs[cend].is_word_end))
+				cend++;
+
+			/* Copy sub-string */
+			start = g_utf8_offset_to_pointer(text, i);
+			bytes = (gint) (g_utf8_offset_to_pointer(text, cend) - start);
+			(*set)[j]    = g_new0(gchar, bytes + 1);
+			(*starts)[j] = (gint) (start - text);
+			(*ends)[j]   = (gint) (start - text + bytes);
+			g_utf8_strncpy((*set)[j], start, cend - i);
+
+			/* Move on to the next word */
+			j++;
+		}
+	}
+
+	g_free (log_attrs);
+}
+
+static void
+sexy_spell_entry_changed(GtkEditable *editable, gpointer data)
+{
+	SexySpellEntry *entry = SEXY_SPELL_ENTRY(editable);
+	if (entry->priv->checked == FALSE)
+		return;
+	if (g_slist_length(entry->priv->dict_list) == 0)
+		return;
+
+	if (entry->priv->words) {
+		g_strfreev(entry->priv->words);
+		g_free(entry->priv->word_starts);
+		g_free(entry->priv->word_ends);
+	}
+	entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
+	sexy_spell_entry_recheck_all(entry);
+}
+
+static gboolean
+enchant_has_lang(const gchar *lang, GSList *langs) {
+	GSList *i;
+	for (i = langs; i; i = g_slist_next(i)) {
+		if (strcmp(lang, i->data) == 0) {
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+/**
+ * sexy_spell_entry_activate_default_languages:
+ * @entry: A #SexySpellEntry.
+ *
+ * Activate spell checking for languages specified in the $LANG
+ * or $LANGUAGE environment variables.  These languages are
+ * activated by default, so this function need only be called
+ * if they were previously deactivated.
+ */
+void
+sexy_spell_entry_activate_default_languages(SexySpellEntry *entry)
+{
+#if GLIB_CHECK_VERSION (2, 6, 0)
+	const gchar* const *langs;
+	int i;
+	gchar *lastprefix = NULL;
+	GSList *enchant_langs;
+
+	if (!have_enchant)
+		return;
+
+	if (!entry->priv->broker)
+		entry->priv->broker = enchant_broker_init();
+
+
+	langs = g_get_language_names ();
+
+	if (langs == NULL)
+		return;
+
+	enchant_langs = sexy_spell_entry_get_languages(entry);
+
+	for (i = 0; langs[i]; i++) {
+		if ((g_strncasecmp(langs[i], "C", 1) != 0) &&
+		    (strlen(langs[i]) >= 2) &&
+		    enchant_has_lang(langs[i], enchant_langs)) {
+			if ((lastprefix == NULL) || (g_str_has_prefix(langs[i], lastprefix) == FALSE))
+				sexy_spell_entry_activate_language_internal(entry, langs[i], NULL);
+			if (lastprefix != NULL)
+				g_free(lastprefix);
+			lastprefix = g_strndup(langs[i], 2);
+		}
+	}
+	if (lastprefix != NULL)
+		g_free(lastprefix);
+
+	g_slist_foreach(enchant_langs, (GFunc) g_free, NULL);
+	g_slist_free(enchant_langs);
+
+	/* If we don't have any languages activated, use "en" */
+	if (entry->priv->dict_list == NULL)
+		sexy_spell_entry_activate_language_internal(entry, "en", NULL);
+#else
+	gchar *lang;
+
+	if (!have_enchant)
+		return;
+
+	lang = (gchar *) g_getenv("LANG");
+
+	if (lang != NULL) {
+		if (g_strncasecmp(lang, "C", 1) == 0)
+			lang = NULL;
+		else if (lang[0] == '\0')
+			lang = NULL;
+	}
+
+	if (lang == NULL)
+		lang = "en";
+
+	sexy_spell_entry_activate_language_internal(entry, lang, NULL);
+#endif
+}
+
+static void
+get_lang_from_dict_cb(const char * const lang_tag,
+		      const char * const provider_name,
+		      const char * const provider_desc,
+		      const char * const provider_file,
+		      void * user_data) {
+	gchar **lang = (gchar **)user_data;
+	*lang = g_strdup(lang_tag);
+}
+
+static gchar *
+get_lang_from_dict(struct EnchantDict *dict)
+{
+	gchar *lang;
+
+	if (!have_enchant)
+		return NULL;
+
+	enchant_dict_describe(dict, get_lang_from_dict_cb, &lang);
+	return lang;
+}
+
+static gboolean
+sexy_spell_entry_activate_language_internal(SexySpellEntry *entry, const gchar *lang, GError **error)
+{
+	struct EnchantDict *dict;
+
+	if (!have_enchant)
+		return FALSE;
+
+	if (!entry->priv->broker)
+		entry->priv->broker = enchant_broker_init();
+
+	if (g_hash_table_lookup(entry->priv->dict_hash, lang))
+		return TRUE;
+
+	dict = enchant_broker_request_dict(entry->priv->broker, lang);
+
+	if (!dict) {
+		g_set_error(error, SEXY_SPELL_ERROR, SEXY_SPELL_ERROR_BACKEND, _("enchant error for language: %s"), lang);
+		return FALSE;
+	}
+
+	entry->priv->dict_list = g_slist_append(entry->priv->dict_list, (gpointer) dict);
+	g_hash_table_insert(entry->priv->dict_hash, get_lang_from_dict(dict), (gpointer) dict);
+
+	return TRUE;
+}
+
+static void
+dict_describe_cb(const char * const lang_tag,
+		 const char * const provider_name,
+		 const char * const provider_desc,
+		 const char * const provider_file,
+		 void * user_data)
+{
+	GSList **langs = (GSList **)user_data;
+
+	*langs = g_slist_append(*langs, (gpointer)g_strdup(lang_tag));
+}
+
+/**
+ * sexy_spell_entry_get_languages:
+ * @entry: A #SexySpellEntry.
+ *
+ * Retrieve a list of language codes for which dictionaries are available.
+ *
+ * Returns: a new #GList object, or %NULL on error.
+ */
+GSList *
+sexy_spell_entry_get_languages(const SexySpellEntry *entry)
+{
+	GSList *langs = NULL;
+
+	g_return_val_if_fail(entry != NULL, NULL);
+	g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry), NULL);
+
+	if (enchant_broker_list_dicts == NULL)
+		return NULL;
+
+	if (!entry->priv->broker)
+		return NULL;
+
+	enchant_broker_list_dicts(entry->priv->broker, dict_describe_cb, &langs);
+
+	return langs;
+}
+
+/**
+ * sexy_spell_entry_get_language_name:
+ * @entry: A #SexySpellEntry.
+ * @lang: The language code to lookup a friendly name for.
+ *
+ * Get a friendly name for a given locale.
+ *
+ * Returns: The name of the locale. Should be freed with g_free()
+ */
+gchar *
+sexy_spell_entry_get_language_name(const SexySpellEntry *entry,
+								   const gchar *lang)
+{
+	/*if (have_enchant)
+		return gtkspell_iso_codes_lookup_name_for_code(lang);*/
+	return NULL;
+}
+
+/**
+ * sexy_spell_entry_language_is_active:
+ * @entry: A #SexySpellEntry.
+ * @lang: The language to use, in a form enchant understands.
+ *
+ * Determine if a given language is currently active.
+ *
+ * Returns: TRUE if the language is active.
+ */
+gboolean
+sexy_spell_entry_language_is_active(const SexySpellEntry *entry,
+									const gchar *lang)
+{
+	return (g_hash_table_lookup(entry->priv->dict_hash, lang) != NULL);
+}
+
+/**
+ * sexy_spell_entry_activate_language:
+ * @entry: A #SexySpellEntry
+ * @lang: The language to use in a form Enchant understands. Typically either
+ *        a two letter language code or a locale code in the form xx_XX.
+ * @error: Return location for error.
+ *
+ * Activate spell checking for the language specifed.
+ *
+ * Returns: FALSE if there was an error.
+ */
+gboolean
+sexy_spell_entry_activate_language(SexySpellEntry *entry, const gchar *lang, GError **error)
+{
+	gboolean ret;
+
+	g_return_val_if_fail(entry != NULL, FALSE);
+	g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry), FALSE);
+	g_return_val_if_fail(lang != NULL && lang != '\0', FALSE);
+
+	if (!have_enchant)
+		return FALSE;
+
+	if (error)
+		g_return_val_if_fail(*error == NULL, FALSE);
+
+	ret = sexy_spell_entry_activate_language_internal(entry, lang, error);
+
+	if (ret) {
+		if (entry->priv->words) {
+			g_strfreev(entry->priv->words);
+			g_free(entry->priv->word_starts);
+			g_free(entry->priv->word_ends);
+		}
+		entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
+		sexy_spell_entry_recheck_all(entry);
+	}
+
+	return ret;
+}
+
+/**
+ * sexy_spell_entry_deactivate_language:
+ * @entry: A #SexySpellEntry.
+ * @lang: The language in a form Enchant understands. Typically either
+ *        a two letter language code or a locale code in the form xx_XX.
+ *
+ * Deactivate spell checking for the language specifed.
+ */
+void
+sexy_spell_entry_deactivate_language(SexySpellEntry *entry, const gchar *lang)
+{
+	g_return_if_fail(entry != NULL);
+	g_return_if_fail(SEXY_IS_SPELL_ENTRY(entry));
+
+	if (!have_enchant)
+		return;
+
+	if (!entry->priv->dict_list)
+		return;
+
+	if (lang) {
+		struct EnchantDict *dict;
+
+		dict = g_hash_table_lookup(entry->priv->dict_hash, lang);
+		if (!dict)
+			return;
+		enchant_broker_free_dict(entry->priv->broker, dict);
+		entry->priv->dict_list = g_slist_remove(entry->priv->dict_list, dict);
+		g_hash_table_remove (entry->priv->dict_hash, lang);
+	} else {
+		/* deactivate all */
+		GSList *li;
+		struct EnchantDict *dict;
+
+		for (li = entry->priv->dict_list; li; li = g_slist_next(li)) {
+			dict = (struct EnchantDict *)li->data;
+			enchant_broker_free_dict(entry->priv->broker, dict);
+		}
+
+		g_slist_free (entry->priv->dict_list);
+		g_hash_table_destroy (entry->priv->dict_hash);
+		entry->priv->dict_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+		entry->priv->dict_list = NULL;
+	}
+
+	if (entry->priv->words) {
+		g_strfreev(entry->priv->words);
+		g_free(entry->priv->word_starts);
+		g_free(entry->priv->word_ends);
+	}
+	entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
+	sexy_spell_entry_recheck_all(entry);
+}
+
+/**
+ * sexy_spell_entry_set_active_languages:
+ * @entry: A #SexySpellEntry
+ * @langs: A list of language codes to activate, in a form Enchant understands.
+ *         Typically either a two letter language code or a locale code in the
+ *         form xx_XX.
+ * @error: Return location for error.
+ *
+ * Activate spell checking for only the languages specified.
+ *
+ * Returns: FALSE if there was an error.
+ */
+gboolean
+sexy_spell_entry_set_active_languages(SexySpellEntry *entry, GSList *langs, GError **error)
+{
+	GSList *li;
+
+	g_return_val_if_fail(entry != NULL, FALSE);
+	g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry), FALSE);
+	g_return_val_if_fail(langs != NULL, FALSE);
+
+	if (!have_enchant)
+		return FALSE;
+
+	/* deactivate all languages first */
+	sexy_spell_entry_deactivate_language(entry, NULL);
+
+	for (li = langs; li; li = g_slist_next(li)) {
+		if (sexy_spell_entry_activate_language_internal(entry,
+		    (const gchar *) li->data, error) == FALSE)
+			return FALSE;
+	}
+	if (entry->priv->words) {
+		g_strfreev(entry->priv->words);
+		g_free(entry->priv->word_starts);
+		g_free(entry->priv->word_ends);
+	}
+	entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
+	sexy_spell_entry_recheck_all(entry);
+	return TRUE;
+}
+
+/**
+ * sexy_spell_entry_get_active_languages:
+ * @entry: A #SexySpellEntry
+ *
+ * Retrieve a list of the currently active languages.
+ *
+ * Returns: A GSList of char* values with language codes (en, fr, etc).  Both
+ *          the data and the list must be freed by the user.
+ */
+GSList *
+sexy_spell_entry_get_active_languages(SexySpellEntry *entry)
+{
+	GSList *ret = NULL, *li;
+	struct EnchantDict *dict;
+	gchar *lang;
+
+	g_return_val_if_fail(entry != NULL, NULL);
+	g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry), NULL);
+
+	if (!have_enchant)
+		return NULL;
+
+	for (li = entry->priv->dict_list; li; li = g_slist_next(li)) {
+		dict = (struct EnchantDict *) li->data;
+		lang = get_lang_from_dict(dict);
+		ret = g_slist_append(ret, lang);
+	}
+	return ret;
+}
+
+/**
+ * sexy_spell_entry_is_checked:
+ * @entry: A #SexySpellEntry.
+ *
+ * Queries a #SexySpellEntry and returns whether the entry has spell-checking enabled.
+ *
+ * Returns: TRUE if the entry has spell-checking enabled.
+ */
+gboolean
+sexy_spell_entry_is_checked(SexySpellEntry *entry)
+{
+	return entry->priv->checked;
+}
+
+/**
+ * sexy_spell_entry_set_checked:
+ * @entry: A #SexySpellEntry.
+ * @checked: Whether to enable spell-checking
+ *
+ * Sets whether the entry has spell-checking enabled.
+ */
+void
+sexy_spell_entry_set_checked(SexySpellEntry *entry, gboolean checked)
+{
+	GtkWidget *widget;
+
+	if (entry->priv->checked == checked)
+		return;
+
+	entry->priv->checked = checked;
+	widget = GTK_WIDGET(entry);
+
+	if (checked == FALSE && GTK_WIDGET_REALIZED(widget)) {
+		PangoLayout *layout;
+		GdkRectangle rect;
+
+		pango_attr_list_unref(entry->priv->attr_list);
+		entry->priv->attr_list = pango_attr_list_new();
+
+		layout = gtk_entry_get_layout(GTK_ENTRY(entry));
+		pango_layout_set_attributes(layout, entry->priv->attr_list);
+
+		rect.x = 0; rect.y = 0;
+		rect.width  = widget->allocation.width;
+		rect.height = widget->allocation.height;
+		gdk_window_invalidate_rect(widget->window, &rect, TRUE);
+	} else {
+		if (entry->priv->words) {
+			g_strfreev(entry->priv->words);
+			g_free(entry->priv->word_starts);
+			g_free(entry->priv->word_ends);
+		}
+		entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
+		sexy_spell_entry_recheck_all(entry);
+	}
+}
diff --git a/src/fe-gtk/sexy-spell-entry.h b/src/fe-gtk/sexy-spell-entry.h
new file mode 100644
index 00000000..61d1b795
--- /dev/null
+++ b/src/fe-gtk/sexy-spell-entry.h
@@ -0,0 +1,87 @@
+/*
+ * @file libsexy/sexy-spell-entry.h Entry widget
+ *
+ * @Copyright (C) 2004-2006 Christian Hammond.
+ *
+ * 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 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.
+ */
+#ifndef _SEXY_SPELL_ENTRY_H_
+#define _SEXY_SPELL_ENTRY_H_
+
+typedef struct _SexySpellEntry      SexySpellEntry;
+typedef struct _SexySpellEntryClass SexySpellEntryClass;
+typedef struct _SexySpellEntryPriv  SexySpellEntryPriv;
+
+#include <gtk/gtkentry.h>
+
+#define SEXY_TYPE_SPELL_ENTRY            (sexy_spell_entry_get_type())
+#define SEXY_SPELL_ENTRY(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SEXY_TYPE_SPELL_ENTRY, SexySpellEntry))
+#define SEXY_SPELL_ENTRY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SEXY_TYPE_SPELL_ENTRY, SexySpellEntryClass))
+#define SEXY_IS_SPELL_ENTRY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SEXY_TYPE_SPELL_ENTRY))
+#define SEXY_IS_SPELL_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SEXY_TYPE_SPELL_ENTRY))
+#define SEXY_SPELL_ENTRY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SEXY_TYPE_SPELL_ENTRY, SexySpellEntryClass))
+
+#define SEXY_SPELL_ERROR                 (sexy_spell_error_quark())
+
+typedef enum {
+	SEXY_SPELL_ERROR_BACKEND,
+} SexySpellError;
+
+struct _SexySpellEntry
+{
+	GtkEntry parent_object;
+
+	SexySpellEntryPriv *priv;
+
+	void (*gtk_reserved1)(void);
+	void (*gtk_reserved2)(void);
+	void (*gtk_reserved3)(void);
+	void (*gtk_reserved4)(void);
+};
+
+struct _SexySpellEntryClass
+{
+	GtkEntryClass parent_class;
+
+	/* Signals */
+	gboolean (*word_check)(SexySpellEntry *entry, const gchar *word);
+
+	void (*gtk_reserved1)(void);
+	void (*gtk_reserved2)(void);
+	void (*gtk_reserved3)(void);
+	void (*gtk_reserved4)(void);
+};
+
+G_BEGIN_DECLS
+
+GType      sexy_spell_entry_get_type(void);
+GtkWidget *sexy_spell_entry_new(void);
+GQuark     sexy_spell_error_quark(void);
+
+GSList    *sexy_spell_entry_get_languages(const SexySpellEntry *entry);
+gchar     *sexy_spell_entry_get_language_name(const SexySpellEntry *entry, const gchar *lang);
+gboolean   sexy_spell_entry_language_is_active(const SexySpellEntry *entry, const gchar *lang);
+gboolean   sexy_spell_entry_activate_language(SexySpellEntry *entry, const gchar *lang, GError **error);
+void       sexy_spell_entry_deactivate_language(SexySpellEntry *entry, const gchar *lang);
+gboolean   sexy_spell_entry_set_active_languages(SexySpellEntry *entry, GSList *langs, GError **error);
+GSList    *sexy_spell_entry_get_active_languages(SexySpellEntry *entry);
+gboolean   sexy_spell_entry_is_checked(SexySpellEntry *entry);
+void       sexy_spell_entry_set_checked(SexySpellEntry *entry, gboolean checked);
+void       sexy_spell_entry_activate_default_languages(SexySpellEntry *entry);
+
+G_END_DECLS
+
+#endif
diff --git a/src/fe-gtk/textgui.c b/src/fe-gtk/textgui.c
new file mode 100644
index 00000000..604da44b
--- /dev/null
+++ b/src/fe-gtk/textgui.c
@@ -0,0 +1,456 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkliststore.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkvpaned.h>
+#include <gtk/gtkvscrollbar.h>
+
+#include "../common/xchat.h"
+#include "../common/xchatc.h"
+#include "../common/cfgfiles.h"
+#include "../common/outbound.h"
+#include "../common/fe.h"
+#include "../common/text.h"
+#include "gtkutil.h"
+#include "xtext.h"
+#include "maingui.h"
+#include "palette.h"
+#include "textgui.h"
+
+extern struct text_event te[];
+extern char *pntevts_text[];
+extern char *pntevts[];
+
+static GtkWidget *pevent_dialog = NULL, *pevent_dialog_twid,
+	*pevent_dialog_entry,
+	*pevent_dialog_list, *pevent_dialog_hlist;
+
+enum
+{
+	COL_EVENT_NAME,
+	COL_EVENT_TEXT,
+	COL_ROW,
+	N_COLUMNS
+};
+
+
+/* this is only used in xtext.c for indented timestamping */
+int
+xtext_get_stamp_str (time_t tim, char **ret)
+{
+	return get_stamp_str (prefs.stamp_format, tim, ret);
+}
+
+static void
+PrintTextLine (xtext_buffer *xtbuf, unsigned char *text, int len, int indent, time_t timet)
+{
+	unsigned char *tab, *new_text;
+	int leftlen;
+
+	if (len == 0)
+		len = 1;
+
+	if (!indent)
+	{
+		if (prefs.timestamp)
+		{
+			int stamp_size;
+			char *stamp;
+
+			if (timet == 0)
+				timet = time (0);
+
+			stamp_size = get_stamp_str (prefs.stamp_format, timet, &stamp);
+			new_text = malloc (len + stamp_size + 1);
+			memcpy (new_text, stamp, stamp_size);
+			g_free (stamp);
+			memcpy (new_text + stamp_size, text, len);
+			gtk_xtext_append (xtbuf, new_text, len + stamp_size);
+			free (new_text);
+		} else
+			gtk_xtext_append (xtbuf, text, len);
+		return;
+	}
+
+	tab = strchr (text, '\t');
+	if (tab && tab < (text + len))
+	{
+		leftlen = tab - text;
+		gtk_xtext_append_indent (xtbuf,
+										 text, leftlen, tab + 1, len - (leftlen + 1), timet);
+	} else
+		gtk_xtext_append_indent (xtbuf, 0, 0, text, len, timet);
+}
+
+void
+PrintTextRaw (void *xtbuf, unsigned char *text, int indent, time_t stamp)
+{
+	char *last_text = text;
+	int len = 0;
+	int beep_done = FALSE;
+
+	/* split the text into separate lines */
+	while (1)
+	{
+		switch (*text)
+		{
+		case 0:
+			PrintTextLine (xtbuf, last_text, len, indent, stamp);
+			return;
+		case '\n':
+			PrintTextLine (xtbuf, last_text, len, indent, stamp);
+			text++;
+			if (*text == 0)
+				return;
+			last_text = text;
+			len = 0;
+			break;
+		case ATTR_BEEP:
+			*text = ' ';
+			if (!beep_done) /* beeps may be slow, so only do 1 per line */
+			{
+				beep_done = TRUE;
+				if (!prefs.filterbeep)
+					gdk_beep ();
+			}
+		default:
+			text++;
+			len++;
+		}
+	}
+}
+
+static void
+pevent_dialog_close (GtkWidget *wid, gpointer arg)
+{
+	pevent_dialog = NULL;
+	pevent_save (NULL);
+}
+
+static void
+pevent_dialog_update (GtkWidget * wid, GtkWidget * twid)
+{
+	int len, m;
+	const char *text;
+	char *out;
+	int sig;
+	GtkTreeIter iter;
+	GtkListStore *store;
+
+	if (!gtkutil_treeview_get_selected (GTK_TREE_VIEW (pevent_dialog_list),
+													&iter, COL_ROW, &sig, -1))
+		return;
+
+	text = gtk_entry_get_text (GTK_ENTRY (wid));
+	len = strlen (text);
+
+	if (pevt_build_string (text, &out, &m) != 0)
+	{
+		fe_message (_("There was an error parsing the string"), FE_MSG_ERROR);
+		return;
+	}
+	if (m > (te[sig].num_args & 0x7f))
+	{
+		free (out);
+		out = malloc (4096);
+		snprintf (out, 4096,
+					 _("This signal is only passed %d args, $%d is invalid"),
+					 te[sig].num_args & 0x7f, m);
+		fe_message (out, FE_MSG_WARN);
+		free (out);
+		return;
+	}
+
+	store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (pevent_dialog_list));
+	gtk_list_store_set (store, &iter, COL_EVENT_TEXT, text, -1);
+
+	if (pntevts_text[sig])
+		free (pntevts_text[sig]);
+	if (pntevts[sig])
+		free (pntevts[sig]);
+
+	pntevts_text[sig] = malloc (len + 1);
+	memcpy (pntevts_text[sig], text, len + 1);
+	pntevts[sig] = out;
+
+	out = malloc (len + 2);
+	memcpy (out, text, len + 1);
+	out[len] = '\n';
+	out[len + 1] = 0;
+	check_special_chars (out, TRUE);
+
+	PrintTextRaw (GTK_XTEXT (twid)->buffer, out, 0, 0);
+	free (out);
+
+	/* save this when we exit */
+	prefs.save_pevents = 1;
+}
+
+static void
+pevent_dialog_hfill (GtkWidget * list, int e)
+{
+	int i = 0;
+	char *text;
+	GtkTreeIter iter;
+	GtkListStore *store;
+
+	store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (pevent_dialog_hlist));
+	gtk_list_store_clear (store);
+	while (i < (te[e].num_args & 0x7f))
+	{
+		text = _(te[e].help[i]);
+		i++;
+		if (text[0] == '\001')
+			text++;
+		gtk_list_store_insert_with_values (store, &iter, -1,
+													  0, i,
+													  1, text, -1);
+	}
+}
+
+static void
+pevent_dialog_unselect (void)
+{
+	gtk_entry_set_text (GTK_ENTRY (pevent_dialog_entry), "");
+	gtk_list_store_clear ((GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (pevent_dialog_hlist)));
+}
+
+static void
+pevent_dialog_select (GtkTreeSelection *sel, gpointer store)
+{
+	char *text;
+	int sig;
+	GtkTreeIter iter;
+
+	if (!gtkutil_treeview_get_selected (GTK_TREE_VIEW (pevent_dialog_list),
+													&iter, COL_ROW, &sig, -1))
+	{
+		pevent_dialog_unselect ();
+	}
+	else
+	{
+		gtk_tree_model_get (store, &iter, COL_EVENT_TEXT, &text, -1);
+		gtk_entry_set_text (GTK_ENTRY (pevent_dialog_entry), text);
+		g_free (text);
+		pevent_dialog_hfill (pevent_dialog_hlist, sig);
+	}
+}
+
+static void
+pevent_dialog_fill (GtkWidget * list)
+{
+	int i;
+	GtkListStore *store;
+	GtkTreeIter iter;
+
+	store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+	gtk_list_store_clear (store);
+
+	i = NUM_XP;
+	do
+	{
+		i--;
+		gtk_list_store_insert_with_values (store, &iter, 0,
+													  COL_EVENT_NAME, te[i].name,
+													  COL_EVENT_TEXT, pntevts_text[i],
+													  COL_ROW, i, -1);
+	}
+	while (i != 0);
+}
+
+static void
+pevent_save_req_cb (void *arg1, char *file)
+{
+	if (file)
+		pevent_save (file);
+}
+
+static void
+pevent_save_cb (GtkWidget * wid, void *data)
+{
+	if (data)
+	{
+		gtkutil_file_req (_("Print Texts File"), pevent_save_req_cb, NULL,
+								NULL, FRF_WRITE);
+		return;
+	}
+	pevent_save (NULL);
+}
+
+static void
+pevent_load_req_cb (void *arg1, char *file)
+{
+	if (file)
+	{
+		pevent_load (file);
+		pevent_make_pntevts ();
+		pevent_dialog_fill (pevent_dialog_list);
+		pevent_dialog_unselect ();
+		prefs.save_pevents = 1;
+	}
+}
+
+static void
+pevent_load_cb (GtkWidget * wid, void *data)
+{
+	gtkutil_file_req (_("Print Texts File"), pevent_load_req_cb, NULL, NULL, 0);
+}
+
+static void
+pevent_ok_cb (GtkWidget * wid, void *data)
+{
+	gtk_widget_destroy (pevent_dialog);
+}
+
+static void
+pevent_test_cb (GtkWidget * wid, GtkWidget * twid)
+{
+	int len, n;
+	char *out, *text;
+
+	for (n = 0; n < NUM_XP; n++)
+	{
+		text = _(pntevts_text[n]);
+		len = strlen (text);
+
+		out = malloc (len + 2);
+		memcpy (out, text, len + 1);
+		out[len] = '\n';
+		out[len + 1] = 0;
+		check_special_chars (out, TRUE);
+
+		PrintTextRaw (GTK_XTEXT (twid)->buffer, out, 0, 0);
+		free (out);
+	}
+}
+
+void
+pevent_dialog_show ()
+{
+	GtkWidget *vbox, *hbox, *tbox, *wid, *bh, *th;
+	GtkListStore *store, *hstore;
+	GtkTreeSelection *sel;
+
+	if (pevent_dialog)
+	{
+		mg_bring_tofront (pevent_dialog);
+		return;
+	}
+
+	pevent_dialog =
+			  mg_create_generic_tab ("edit events", _("Edit Events"),
+											 TRUE, FALSE, pevent_dialog_close, NULL,
+											 600, 455, &vbox, 0);
+
+	wid = gtk_vpaned_new ();
+	th = gtk_vbox_new (0, 2);
+	bh = gtk_vbox_new (0, 2);
+	gtk_widget_show (th);
+	gtk_widget_show (bh);
+	gtk_paned_pack1 (GTK_PANED (wid), th, 1, 1);
+	gtk_paned_pack2 (GTK_PANED (wid), bh, 0, 1);
+	gtk_box_pack_start (GTK_BOX (vbox), wid, 1, 1, 0);
+	gtk_widget_show (wid);
+
+	store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING,
+	                            G_TYPE_STRING, G_TYPE_INT);
+	pevent_dialog_list = gtkutil_treeview_new (th, GTK_TREE_MODEL (store), NULL,
+															 COL_EVENT_NAME, _("Event"),
+															 COL_EVENT_TEXT, _("Text"), -1);
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (pevent_dialog_list));
+	g_signal_connect (G_OBJECT (sel), "changed",
+							G_CALLBACK (pevent_dialog_select), store);
+
+	pevent_dialog_twid = gtk_xtext_new (colors, 0);
+	gtk_xtext_set_tint (GTK_XTEXT (pevent_dialog_twid), prefs.tint_red, prefs.tint_green, prefs.tint_blue);
+	gtk_xtext_set_background (GTK_XTEXT (pevent_dialog_twid),
+									  channelwin_pix, prefs.transparent);
+
+	pevent_dialog_entry = gtk_entry_new_with_max_length (255);
+	g_signal_connect (G_OBJECT (pevent_dialog_entry), "activate",
+							G_CALLBACK (pevent_dialog_update), pevent_dialog_twid);
+	gtk_box_pack_start (GTK_BOX (bh), pevent_dialog_entry, 0, 0, 0);
+	gtk_widget_show (pevent_dialog_entry);
+
+	tbox = gtk_hbox_new (0, 0);
+	gtk_container_add (GTK_CONTAINER (bh), tbox);
+	gtk_widget_show (tbox);
+
+	gtk_widget_set_usize (pevent_dialog_twid, 150, 20);
+	gtk_container_add (GTK_CONTAINER (tbox), pevent_dialog_twid);
+	gtk_xtext_set_font (GTK_XTEXT (pevent_dialog_twid), prefs.font_normal);
+
+	wid = gtk_vscrollbar_new (GTK_XTEXT (pevent_dialog_twid)->adj);
+	gtk_box_pack_start (GTK_BOX (tbox), wid, FALSE, FALSE, 0);
+	show_and_unfocus (wid);
+
+	gtk_widget_show (pevent_dialog_twid);
+
+	hstore = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
+	pevent_dialog_hlist = gtkutil_treeview_new (bh, GTK_TREE_MODEL (hstore),
+															  NULL,
+															  0, _("$ Number"),
+															  1, _("Description"), -1);
+	gtk_widget_show (pevent_dialog_hlist);
+
+	pevent_dialog_fill (pevent_dialog_list);
+	gtk_widget_show (pevent_dialog_list);
+
+	hbox = gtk_hbutton_box_new ();
+	gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 2);
+	/*wid = gtk_button_new_with_label (_("Save"));
+	gtk_box_pack_end (GTK_BOX (hbox), wid, 0, 0, 0);
+	gtk_signal_connect (GTK_OBJECT (wid), "clicked",
+							  GTK_SIGNAL_FUNC (pevent_save_cb), NULL);
+	gtk_widget_show (wid);*/
+	gtkutil_button (hbox, GTK_STOCK_SAVE_AS, NULL, pevent_save_cb,
+						 (void *) 1, _("Save As..."));
+	gtkutil_button (hbox, GTK_STOCK_OPEN, NULL, pevent_load_cb,
+						 (void *) 0, _("Load From..."));
+	wid = gtk_button_new_with_label (_("Test All"));
+	gtk_box_pack_end (GTK_BOX (hbox), wid, 0, 0, 0);
+	g_signal_connect (G_OBJECT (wid), "clicked",
+							G_CALLBACK (pevent_test_cb), pevent_dialog_twid);
+	gtk_widget_show (wid);
+
+	wid = gtk_button_new_from_stock (GTK_STOCK_OK);
+	gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
+	g_signal_connect (G_OBJECT (wid), "clicked",
+							G_CALLBACK (pevent_ok_cb), NULL);
+	gtk_widget_show (wid);
+
+	gtk_widget_show (hbox);
+
+	gtk_widget_show (pevent_dialog);
+}
diff --git a/src/fe-gtk/textgui.h b/src/fe-gtk/textgui.h
new file mode 100644
index 00000000..23db5848
--- /dev/null
+++ b/src/fe-gtk/textgui.h
@@ -0,0 +1,2 @@
+void PrintTextRaw (void *xtbuf, unsigned char *text, int indent, time_t stamp);
+void pevent_dialog_show (void);
diff --git a/src/fe-gtk/urlgrab.c b/src/fe-gtk/urlgrab.c
new file mode 100644
index 00000000..6e5f1e0d
--- /dev/null
+++ b/src/fe-gtk/urlgrab.c
@@ -0,0 +1,208 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkscrolledwindow.h>
+
+#include <gtk/gtkliststore.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtkcellrenderertext.h>
+
+#include "../common/xchat.h"
+#include "../common/cfgfiles.h"
+#include "../common/fe.h"
+#include "../common/url.h"
+#include "../common/tree.h"
+#include "gtkutil.h"
+#include "menu.h"
+#include "maingui.h"
+#include "urlgrab.h"
+
+/* model for the URL treeview */
+enum
+{
+	URL_COLUMN,
+	N_COLUMNS
+};
+
+static GtkWidget *urlgrabberwindow = 0;
+
+
+static gboolean
+url_treeview_url_clicked_cb (GtkWidget *view, GdkEventButton *event,
+                             gpointer data)
+{
+	GtkTreeIter iter;
+	gchar *url;
+
+	if (!event ||
+	    !gtkutil_treeview_get_selected (GTK_TREE_VIEW (view), &iter,
+	                                    URL_COLUMN, &url, -1))
+	{
+		return FALSE;
+	}
+	
+	switch (event->button)
+	{
+		case 1:
+			if (event->type == GDK_2BUTTON_PRESS)
+				fe_open_url (url);
+			break;
+		case 3:
+			menu_urlmenu (event, url);
+			break;
+		default:
+			break;
+	}
+	g_free (url);
+
+	return FALSE;
+}
+
+static GtkWidget *
+url_treeview_new (GtkWidget *box)
+{
+	GtkListStore *store;
+	GtkWidget *view;
+
+	store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING);
+	g_return_val_if_fail (store != NULL, NULL);
+
+	view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store), NULL,
+	                             URL_COLUMN, _("URL"), -1);
+	g_signal_connect (G_OBJECT (view), "button_press_event",
+	                  G_CALLBACK (url_treeview_url_clicked_cb), NULL);
+	/* don't want column headers */
+	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE);
+	gtk_widget_show (view);
+	return view;
+}
+
+static void
+url_closegui (GtkWidget *wid, gpointer userdata)
+{
+	urlgrabberwindow = 0;
+}
+
+static void
+url_button_clear (void)
+{
+	GtkListStore *store;
+	
+	url_clear ();
+	store = GTK_LIST_STORE (g_object_get_data (G_OBJECT (urlgrabberwindow),
+	                                           "model"));
+	gtk_list_store_clear (store);
+}
+
+static void
+url_button_copy (GtkWidget *widget, gpointer data)
+{
+	GtkTreeView *view = GTK_TREE_VIEW (data);
+	GtkTreeIter iter;
+	gchar *url = NULL;
+
+	if (gtkutil_treeview_get_selected (view, &iter, URL_COLUMN, &url, -1))
+	{
+		gtkutil_copy_to_clipboard (GTK_WIDGET (view), NULL, url);
+		g_free (url);
+	}
+}
+
+static void
+url_save_callback (void *arg1, char *file)
+{
+	if (file)
+		url_save (file, "w", TRUE);
+}
+
+static void
+url_button_save (void)
+{
+	gtkutil_file_req (_("Select an output filename"),
+							url_save_callback, NULL, NULL, FRF_WRITE);
+}
+
+void
+fe_url_add (const char *urltext)
+{
+	GtkListStore *store;
+	GtkTreeIter iter;
+	
+	if (urlgrabberwindow)
+	{
+		store = GTK_LIST_STORE (g_object_get_data (G_OBJECT (urlgrabberwindow),
+		                                           "model"));
+		gtk_list_store_prepend (store, &iter);
+		gtk_list_store_set (store, &iter,
+		                    URL_COLUMN, urltext,
+		                    -1);
+	}
+}
+
+static int
+populate_cb (char *urltext, gpointer userdata)
+{
+	fe_url_add (urltext);
+	return TRUE;
+}
+
+void
+url_opengui ()
+{
+	GtkWidget *vbox, *hbox, *view;
+
+	if (urlgrabberwindow)
+	{
+		mg_bring_tofront (urlgrabberwindow);
+		return;
+	}
+
+	urlgrabberwindow =
+		mg_create_generic_tab ("UrlGrabber", _("XChat: URL Grabber"), FALSE,
+							 TRUE, url_closegui, NULL, 400, 256, &vbox, 0);
+	view = url_treeview_new (vbox);
+	g_object_set_data (G_OBJECT (urlgrabberwindow), "model",
+	                   gtk_tree_view_get_model (GTK_TREE_VIEW (view)));
+
+	hbox = gtk_hbutton_box_new ();
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_SPREAD);
+	gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
+	gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0);
+	gtk_widget_show (hbox);
+
+	gtkutil_button (hbox, GTK_STOCK_CLEAR,
+						 _("Clear list"), url_button_clear, 0, _("Clear"));
+	gtkutil_button (hbox, GTK_STOCK_COPY,
+						 _("Copy selected URL"), url_button_copy, view, _("Copy"));
+	gtkutil_button (hbox, GTK_STOCK_SAVE_AS,
+						 _("Save list to a file"), url_button_save, 0, _("Save As..."));
+
+	gtk_widget_show (urlgrabberwindow);
+
+	tree_foreach (url_tree, (tree_traverse_func *)populate_cb, NULL);
+}
diff --git a/src/fe-gtk/urlgrab.h b/src/fe-gtk/urlgrab.h
new file mode 100644
index 00000000..cc534241
--- /dev/null
+++ b/src/fe-gtk/urlgrab.h
@@ -0,0 +1,2 @@
+void url_autosave (void);
+void url_opengui (void);
diff --git a/src/fe-gtk/userlistgui.c b/src/fe-gtk/userlistgui.c
new file mode 100644
index 00000000..f040a6a1
--- /dev/null
+++ b/src/fe-gtk/userlistgui.c
@@ -0,0 +1,718 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "fe-gtk.h"
+
+#include <gtk/gtkbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkdnd.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkcellrendererpixbuf.h>
+#include <gtk/gtkcellrenderertext.h>
+#include <gtk/gtkliststore.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "../common/xchat.h"
+#include "../common/util.h"
+#include "../common/userlist.h"
+#include "../common/modes.h"
+#include "../common/notify.h"
+#include "../common/xchatc.h"
+#include "gtkutil.h"
+#include "palette.h"
+#include "maingui.h"
+#include "menu.h"
+#include "pixmaps.h"
+#include "userlistgui.h"
+
+#ifdef USE_GTKSPELL
+#include <gtk/gtktextview.h>
+#endif
+
+
+enum
+{
+	COL_PIX=0,		// GdkPixbuf *
+	COL_NICK=1,		// char *
+	COL_HOST=2,		// char *
+	COL_USER=3,		// struct User *
+	COL_GDKCOLOR=4	// GdkColor *
+};
+
+
+GdkPixbuf *
+get_user_icon (server *serv, struct User *user)
+{
+	char *pre;
+	int level;
+
+	if (!user)
+		return NULL;
+
+	/* these ones are hardcoded */
+	switch (user->prefix[0])
+	{
+	case 0: return NULL;
+	case '@': return pix_op;
+	case '%': return pix_hop;
+	case '+': return pix_voice;
+	}
+
+	/* find out how many levels above Op this user is */
+	pre = strchr (serv->nick_prefixes, '@');
+	if (pre && pre != serv->nick_prefixes)
+	{
+		pre--;
+		level = 0;
+		while (1)
+		{
+			if (pre[0] == user->prefix[0])
+			{
+				switch (level)
+				{
+				case 0: return pix_red;	/* 1 level above op */
+				case 1: return pix_purple;	 /* 2 levels above op */
+				}
+				break;	/* 3+, no icons */
+			}
+			level++;
+			if (pre == serv->nick_prefixes)
+				break;
+			pre--;
+		}
+	}
+
+	return NULL;
+}
+
+void
+fe_userlist_numbers (session *sess)
+{
+	char tbuf[256];
+
+	if (sess == current_tab || !sess->gui->is_tab)
+	{
+		if (sess->total)
+		{
+			snprintf (tbuf, sizeof (tbuf), _("%d ops, %d total"), sess->ops, sess->total);
+			tbuf[sizeof (tbuf) - 1] = 0;
+			gtk_label_set_text (GTK_LABEL (sess->gui->namelistinfo), tbuf);
+		} else
+		{
+			gtk_label_set_text (GTK_LABEL (sess->gui->namelistinfo), NULL);
+		}
+
+		if (sess->type == SESS_CHANNEL && prefs.gui_tweaks & 1)
+			fe_set_title (sess);
+	}
+}
+
+static void
+scroll_to_iter (GtkTreeIter *iter, GtkTreeView *treeview, GtkTreeModel *model)
+{
+	GtkTreePath *path = gtk_tree_model_get_path (model, iter);
+	if (path)
+	{
+		gtk_tree_view_scroll_to_cell (treeview, path, NULL, TRUE, 0.5, 0.5);
+		gtk_tree_path_free (path);
+	}
+}
+
+/* select a row in the userlist by nick-name */
+
+void
+userlist_select (session *sess, char *name)
+{
+	GtkTreeIter iter;
+	GtkTreeView *treeview = GTK_TREE_VIEW (sess->gui->user_tree);
+	GtkTreeModel *model = gtk_tree_view_get_model (treeview);
+	GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
+	struct User *row_user;
+
+	if (gtk_tree_model_get_iter_first (model, &iter))
+	{
+		do
+		{
+			gtk_tree_model_get (model, &iter, COL_USER, &row_user, -1);
+			if (sess->server->p_cmp (row_user->nick, name) == 0)
+			{
+				if (gtk_tree_selection_iter_is_selected (selection, &iter))
+					gtk_tree_selection_unselect_iter (selection, &iter);
+				else
+					gtk_tree_selection_select_iter (selection, &iter);
+
+				/* and make sure it's visible */
+				scroll_to_iter (&iter, treeview, model);
+				return;
+			}
+		}
+		while (gtk_tree_model_iter_next (model, &iter));
+	}
+}
+
+char **
+userlist_selection_list (GtkWidget *widget, int *num_ret)
+{
+	GtkTreeIter iter;
+	GtkTreeView *treeview = (GtkTreeView *) widget;
+	GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
+	GtkTreeModel *model = gtk_tree_view_get_model (treeview);
+	struct User *user;
+	int i, num_sel;
+	char **nicks;
+
+	*num_ret = 0;
+	/* first, count the number of selections */
+	num_sel = 0;
+	if (gtk_tree_model_get_iter_first (model, &iter))
+	{
+		do
+		{
+			if (gtk_tree_selection_iter_is_selected (selection, &iter))
+				num_sel++;
+		}
+		while (gtk_tree_model_iter_next (model, &iter));
+	}
+
+	if (num_sel < 1)
+		return NULL;
+
+	nicks = malloc (sizeof (char *) * (num_sel + 1));
+
+	i = 0;
+	gtk_tree_model_get_iter_first (model, &iter);
+	do
+	{
+		if (gtk_tree_selection_iter_is_selected (selection, &iter))
+		{
+			gtk_tree_model_get (model, &iter, COL_USER, &user, -1);
+			nicks[i] = g_strdup (user->nick);
+			i++;
+			nicks[i] = NULL;
+		}
+	}
+	while (gtk_tree_model_iter_next (model, &iter));
+
+	*num_ret = i;
+	return nicks;
+}
+
+void
+fe_userlist_set_selected (struct session *sess)
+{
+	GtkListStore *store = sess->res->user_model;
+	GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (sess->gui->user_tree));
+	GtkTreeIter iter;
+	struct User *user;
+
+	/* if it's not front-most tab it doesn't own the GtkTreeView! */
+	if (store != (GtkListStore*) gtk_tree_view_get_model (GTK_TREE_VIEW (sess->gui->user_tree)))
+		return;
+
+	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter))
+	{
+		do
+		{
+			gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, COL_USER, &user, -1);
+
+			if (gtk_tree_selection_iter_is_selected (selection, &iter))
+				user->selected = 1;
+			else
+				user->selected = 0;
+				
+		} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
+	}
+}
+
+static GtkTreeIter *
+find_row (GtkTreeView *treeview, GtkTreeModel *model, struct User *user,
+			 int *selected)
+{
+	static GtkTreeIter iter;
+	struct User *row_user;
+
+	*selected = FALSE;
+	if (gtk_tree_model_get_iter_first (model, &iter))
+	{
+		do
+		{
+			gtk_tree_model_get (model, &iter, COL_USER, &row_user, -1);
+			if (row_user == user)
+			{
+				if (gtk_tree_view_get_model (treeview) == model)
+				{
+					if (gtk_tree_selection_iter_is_selected (gtk_tree_view_get_selection (treeview), &iter))
+						*selected = TRUE;
+				}
+				return &iter;
+			}
+		}
+		while (gtk_tree_model_iter_next (model, &iter));
+	}
+
+	return NULL;
+}
+
+void
+userlist_set_value (GtkWidget *treeview, gfloat val)
+{
+	gtk_adjustment_set_value (
+			gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (treeview)), val);
+}
+
+gfloat
+userlist_get_value (GtkWidget *treeview)
+{
+	return gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (treeview))->value;
+}
+
+int
+fe_userlist_remove (session *sess, struct User *user)
+{
+	GtkTreeIter *iter;
+/*	GtkAdjustment *adj;
+	gfloat val, end;*/
+	int sel;
+
+	iter = find_row (GTK_TREE_VIEW (sess->gui->user_tree),
+						  sess->res->user_model, user, &sel);
+	if (!iter)
+		return 0;
+
+/*	adj = gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (sess->gui->user_tree));
+	val = adj->value;*/
+
+	gtk_list_store_remove (sess->res->user_model, iter);
+
+	/* is it the front-most tab? */
+/*	if (gtk_tree_view_get_model (GTK_TREE_VIEW (sess->gui->user_tree))
+		 == sess->res->user_model)
+	{
+		end = adj->upper - adj->lower - adj->page_size;
+		if (val > end)
+			val = end;
+		gtk_adjustment_set_value (adj, val);
+	}*/
+
+	return sel;
+}
+
+void
+fe_userlist_rehash (session *sess, struct User *user)
+{
+	GtkTreeIter *iter;
+	int sel;
+	int do_away = TRUE;
+
+	iter = find_row (GTK_TREE_VIEW (sess->gui->user_tree),
+						  sess->res->user_model, user, &sel);
+	if (!iter)
+		return;
+
+	if (prefs.away_size_max < 1 || !prefs.away_track)
+		do_away = FALSE;
+
+	gtk_list_store_set (GTK_LIST_STORE (sess->res->user_model), iter,
+							  COL_HOST, user->hostname,
+							  COL_GDKCOLOR, (do_away)
+									?	(user->away ? &colors[COL_AWAY] : NULL)
+									:	(NULL),
+							  -1);
+}
+
+void
+fe_userlist_insert (session *sess, struct User *newuser, int row, int sel)
+{
+	GtkTreeModel *model = sess->res->user_model;
+	GdkPixbuf *pix = get_user_icon (sess->server, newuser);
+	GtkTreeIter iter;
+	int do_away = TRUE;
+	char *nick;
+
+	if (prefs.away_size_max < 1 || !prefs.away_track)
+		do_away = FALSE;
+
+	nick = newuser->nick;
+	if (prefs.gui_tweaks & 64)
+	{
+		nick = malloc (strlen (newuser->nick) + 2);
+		nick[0] = newuser->prefix[0];
+		if (!nick[0] || nick[0] == ' ')
+			strcpy (nick, newuser->nick);
+		else
+			strcpy (nick + 1, newuser->nick);
+		pix = NULL;
+	}
+
+	gtk_list_store_insert_with_values (GTK_LIST_STORE (model), &iter, row,
+									COL_PIX, pix,
+									COL_NICK, nick,
+									COL_HOST, newuser->hostname,
+									COL_USER, newuser,
+									COL_GDKCOLOR, (do_away)
+										?	(newuser->away ? &colors[COL_AWAY] : NULL)
+										:	(NULL),
+								  -1);
+
+	if (prefs.gui_tweaks & 64)
+		free (nick);
+
+	/* is it me? */
+	if (newuser->me && sess->gui->nick_box)
+	{
+		if (!sess->gui->is_tab || sess == current_tab)
+			mg_set_access_icon (sess->gui, pix, sess->server->is_away);
+	}
+
+#if 0
+	if (prefs.hilitenotify && notify_isnotify (sess, newuser->nick))
+	{
+		gtk_clist_set_foreground ((GtkCList *) sess->gui->user_clist, row,
+										  &colors[prefs.nu_color]);
+	}
+#endif
+
+	/* is it the front-most tab? */
+	if (gtk_tree_view_get_model (GTK_TREE_VIEW (sess->gui->user_tree))
+		 == model)
+	{
+		if (sel)
+			gtk_tree_selection_select_iter (gtk_tree_view_get_selection
+										(GTK_TREE_VIEW (sess->gui->user_tree)), &iter);
+	}
+}
+
+void
+fe_userlist_move (session *sess, struct User *user, int new_row)
+{
+	fe_userlist_insert (sess, user, new_row, fe_userlist_remove (sess, user));
+}
+
+void
+fe_userlist_clear (session *sess)
+{
+	gtk_list_store_clear (sess->res->user_model);
+}
+
+static void
+userlist_dnd_drop (GtkTreeView *widget, GdkDragContext *context,
+						 gint x, gint y, GtkSelectionData *selection_data,
+						 guint info, guint ttime, gpointer userdata)
+{
+	struct User *user;
+	GtkTreePath *path;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+
+	if (!gtk_tree_view_get_path_at_pos (widget, x, y, &path, NULL, NULL, NULL))
+		return;
+
+	model = gtk_tree_view_get_model (widget);
+	if (!gtk_tree_model_get_iter (model, &iter, path))
+		return;
+	gtk_tree_model_get (model, &iter, COL_USER, &user, -1);
+
+	mg_dnd_drop_file (current_sess, user->nick, selection_data->data);
+}
+
+static gboolean
+userlist_dnd_motion (GtkTreeView *widget, GdkDragContext *context, gint x,
+							gint y, guint ttime, gpointer tree)
+{
+	GtkTreePath *path;
+	GtkTreeSelection *sel;
+
+	if (!tree)
+		return FALSE;
+
+	if (gtk_tree_view_get_path_at_pos (widget, x, y, &path, NULL, NULL, NULL))
+	{
+		sel = gtk_tree_view_get_selection (widget);
+		gtk_tree_selection_unselect_all (sel);
+		gtk_tree_selection_select_path (sel, path);
+	}
+
+	return FALSE;
+}
+
+static gboolean
+userlist_dnd_leave (GtkTreeView *widget, GdkDragContext *context, guint ttime)
+{
+	gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (widget));
+	return TRUE;
+}
+
+void *
+userlist_create_model (void)
+{
+	return gtk_list_store_new (5, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING,
+										G_TYPE_POINTER, GDK_TYPE_COLOR);
+}
+
+static void
+userlist_add_columns (GtkTreeView * treeview)
+{
+	GtkCellRenderer *renderer;
+
+	/* icon column */
+	renderer = gtk_cell_renderer_pixbuf_new ();
+	if (prefs.gui_tweaks & 32)
+		g_object_set (G_OBJECT (renderer), "ypad", 0, NULL);
+	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
+																-1, NULL, renderer,
+																"pixbuf", 0, NULL);
+
+	/* nick column */
+	renderer = gtk_cell_renderer_text_new ();
+	if (prefs.gui_tweaks & 32)
+		g_object_set (G_OBJECT (renderer), "ypad", 0, NULL);
+	gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1);
+	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
+																-1, NULL, renderer,
+													"text", 1, "foreground-gdk", 4, NULL);
+
+	if (prefs.showhostname_in_userlist)
+	{
+		/* hostname column */
+		renderer = gtk_cell_renderer_text_new ();
+		if (prefs.gui_tweaks & 32)
+			g_object_set (G_OBJECT (renderer), "ypad", 0, NULL);
+		gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1);
+		gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
+																	-1, NULL, renderer,
+																	"text", 2, NULL);
+	}
+}
+
+static gint
+userlist_click_cb (GtkWidget *widget, GdkEventButton *event, gpointer userdata)
+{
+	char **nicks;
+	int i;
+	GtkTreeSelection *sel;
+	GtkTreePath *path;
+
+	if (!event)
+		return FALSE;
+
+	if (!(event->state & GDK_CONTROL_MASK) &&
+		event->type == GDK_2BUTTON_PRESS && prefs.doubleclickuser[0])
+	{
+		nicks = userlist_selection_list (widget, &i);
+		if (nicks)
+		{
+			nick_command_parse (current_sess, prefs.doubleclickuser, nicks[0],
+									  nicks[0]);
+			while (i)
+			{
+				i--;
+				g_free (nicks[i]);
+			}
+			free (nicks);
+		}
+		return TRUE;
+	}
+
+	if (event->button == 3)
+	{
+		/* do we have a multi-selection? */
+		nicks = userlist_selection_list (widget, &i);
+		if (nicks && i > 1)
+		{
+			menu_nickmenu (current_sess, event, nicks[0], i);
+			while (i)
+			{
+				i--;
+				g_free (nicks[i]);
+			}
+			free (nicks);
+			return TRUE;
+		}
+		if (nicks)
+		{
+			g_free (nicks[0]);
+			free (nicks);
+		}
+
+		sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
+		if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
+			 event->x, event->y, &path, 0, 0, 0))
+		{
+			gtk_tree_selection_unselect_all (sel);
+			gtk_tree_selection_select_path (sel, path);
+			gtk_tree_path_free (path);
+			nicks = userlist_selection_list (widget, &i);
+			if (nicks)
+			{
+				menu_nickmenu (current_sess, event, nicks[0], i);
+				while (i)
+				{
+					i--;
+					g_free (nicks[i]);
+				}
+				free (nicks);
+			}
+		} else
+		{
+			gtk_tree_selection_unselect_all (sel);
+		}
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+userlist_key_cb (GtkWidget *wid, GdkEventKey *evt, gpointer userdata)
+{
+	if (evt->keyval >= GDK_asterisk && evt->keyval <= GDK_z)
+	{
+		/* dirty trick to avoid auto-selection */
+		SPELL_ENTRY_SET_EDITABLE (current_sess->gui->input_box, FALSE);
+		gtk_widget_grab_focus (current_sess->gui->input_box);
+		SPELL_ENTRY_SET_EDITABLE (current_sess->gui->input_box, TRUE);
+		gtk_widget_event (current_sess->gui->input_box, (GdkEvent *)evt);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+GtkWidget *
+userlist_create (GtkWidget *box)
+{
+	GtkWidget *sw, *treeview;
+	static const GtkTargetEntry dnd_dest_targets[] =
+	{
+		{"text/uri-list", 0, 1},
+		{"XCHAT_CHANVIEW", GTK_TARGET_SAME_APP, 75 }
+	};
+	static const GtkTargetEntry dnd_src_target[] =
+	{
+		{"XCHAT_USERLIST", GTK_TARGET_SAME_APP, 75 }
+	};
+
+	sw = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+													 GTK_SHADOW_ETCHED_IN);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+											  prefs.showhostname_in_userlist ?
+												GTK_POLICY_AUTOMATIC :
+												GTK_POLICY_NEVER,
+											  GTK_POLICY_AUTOMATIC);
+	gtk_box_pack_start (GTK_BOX (box), sw, TRUE, TRUE, 0);
+	gtk_widget_show (sw);
+
+	treeview = gtk_tree_view_new ();
+	gtk_widget_set_name (treeview, "xchat-userlist");
+	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
+	gtk_tree_selection_set_mode (gtk_tree_view_get_selection
+										  (GTK_TREE_VIEW (treeview)),
+										  GTK_SELECTION_MULTIPLE);
+
+	/* set up drops */
+	gtk_drag_dest_set (treeview, GTK_DEST_DEFAULT_ALL, dnd_dest_targets, 2,
+							 GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK);
+	gtk_drag_source_set (treeview, GDK_BUTTON1_MASK, dnd_src_target, 1, GDK_ACTION_MOVE);
+
+	/* file DND (for DCC) */
+	g_signal_connect (G_OBJECT (treeview), "drag_motion",
+							G_CALLBACK (userlist_dnd_motion), treeview);
+	g_signal_connect (G_OBJECT (treeview), "drag_leave",
+							G_CALLBACK (userlist_dnd_leave), 0);
+	g_signal_connect (G_OBJECT (treeview), "drag_data_received",
+							G_CALLBACK (userlist_dnd_drop), treeview);
+
+	g_signal_connect (G_OBJECT (treeview), "button_press_event",
+							G_CALLBACK (userlist_click_cb), 0);
+	g_signal_connect (G_OBJECT (treeview), "key_press_event",
+							G_CALLBACK (userlist_key_cb), 0);
+
+	/* tree/chanview DND */
+#ifndef WIN32	/* leaks GDI pool memory, don't enable */
+	g_signal_connect (G_OBJECT (treeview), "drag_begin",
+							G_CALLBACK (mg_drag_begin_cb), NULL);
+	g_signal_connect (G_OBJECT (treeview), "drag_drop",
+							G_CALLBACK (mg_drag_drop_cb), NULL);
+	g_signal_connect (G_OBJECT (treeview), "drag_motion",
+							G_CALLBACK (mg_drag_motion_cb), NULL);
+	g_signal_connect (G_OBJECT (treeview), "drag_end",
+							G_CALLBACK (mg_drag_end_cb), NULL);
+#endif
+
+	userlist_add_columns (GTK_TREE_VIEW (treeview));
+
+	gtk_container_add (GTK_CONTAINER (sw), treeview);
+	gtk_widget_show (treeview);
+
+	return treeview;
+}
+
+void
+userlist_show (session *sess)
+{
+	gtk_tree_view_set_model (GTK_TREE_VIEW (sess->gui->user_tree),
+									 sess->res->user_model);
+}
+
+void
+fe_uselect (session *sess, char *word[], int do_clear, int scroll_to)
+{
+	int thisname;
+	char *name;
+	GtkTreeIter iter;
+	GtkTreeView *treeview = GTK_TREE_VIEW (sess->gui->user_tree);
+	GtkTreeModel *model = gtk_tree_view_get_model (treeview);
+	GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
+	struct User *row_user;
+
+	if (gtk_tree_model_get_iter_first (model, &iter))
+	{
+		if (do_clear)
+			gtk_tree_selection_unselect_all (selection);
+
+		do
+		{
+			if (*word[0])
+			{
+				gtk_tree_model_get (model, &iter, COL_USER, &row_user, -1);
+				thisname = 0;
+				while ( *(name = word[thisname++]) )
+				{
+					if (sess->server->p_cmp (row_user->nick, name) == 0)
+					{
+						gtk_tree_selection_select_iter (selection, &iter);
+						if (scroll_to)
+							scroll_to_iter (&iter, treeview, model);
+						break;
+					}
+				}
+			}
+
+		}
+		while (gtk_tree_model_iter_next (model, &iter));
+	}
+}
diff --git a/src/fe-gtk/userlistgui.h b/src/fe-gtk/userlistgui.h
new file mode 100644
index 00000000..b49e2b9b
--- /dev/null
+++ b/src/fe-gtk/userlistgui.h
@@ -0,0 +1,8 @@
+void userlist_set_value (GtkWidget *treeview, gfloat val);
+gfloat userlist_get_value (GtkWidget *treeview);
+GtkWidget *userlist_create (GtkWidget *box);
+void *userlist_create_model (void);
+void userlist_show (session *sess);
+void userlist_select (session *sess, char *name);
+char **userlist_selection_list (GtkWidget *widget, int *num_ret);
+GdkPixbuf *get_user_icon (server *serv, struct User *user);
diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c
new file mode 100644
index 00000000..fa9803c7
--- /dev/null
+++ b/src/fe-gtk/xtext.c
@@ -0,0 +1,5478 @@
+/* X-Chat
+ * Copyright (C) 1998 Peter Zelezny.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * =========================================================================
+ *
+ * xtext, the text widget used by X-Chat.
+ * By Peter Zelezny <zed@xchat.org>.
+ *
+ */
+
+#define XCHAT							/* using xchat */
+#define TINT_VALUE 195				/* 195/255 of the brightness. */
+#define MOTION_MONITOR				/* URL hilights. */
+#define SMOOTH_SCROLL				/* line-by-line or pixel scroll? */
+#define SCROLL_HACK					/* use XCopyArea scroll, or full redraw? */
+#undef COLOR_HILIGHT				/* Color instead of underline? */
+/* Italic is buggy because it assumes drawing an italic string will have
+   identical extents to the normal font. This is only true some of the
+   time, so we can't use this hack yet. */
+#undef ITALIC							/* support Italic? */
+#define GDK_MULTIHEAD_SAFE
+#define USE_DB							/* double buffer */
+
+#define MARGIN 2						/* dont touch. */
+#define REFRESH_TIMEOUT 20
+#define WORDWRAP_LIMIT 24
+
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkselection.h>
+#include <gtk/gtkclipboard.h>
+#include <gtk/gtkversion.h>
+#include <gtk/gtkwindow.h>
+
+#ifdef XCHAT
+#include "../../config.h"			/* can define USE_XLIB here */
+#else
+#define USE_XLIB
+#endif
+
+#ifdef USE_XLIB
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#endif
+
+#ifdef USE_MMX
+#include "mmx_cmod.h"
+#endif
+
+#include "xtext.h"
+
+#define charlen(str) g_utf8_skip[*(guchar *)(str)]
+
+#ifdef WIN32
+#include <windows.h>
+#include <gdk/gdkwin32.h>
+#endif
+
+/* is delimiter */
+#define is_del(c) \
+	(c == ' ' || c == '\n' || c == ')' || c == '(' || \
+	 c == '>' || c == '<' || c == ATTR_RESET || c == ATTR_BOLD || c == 0)
+
+#ifdef SCROLL_HACK
+/* force scrolling off */
+#define dontscroll(buf) (buf)->last_pixel_pos = 0x7fffffff
+#else
+#define dontscroll(buf)
+#endif
+
+static GtkWidgetClass *parent_class = NULL;
+
+struct textentry
+{
+	struct textentry *next;
+	struct textentry *prev;
+	unsigned char *str;
+	time_t stamp;
+	gint16 str_width;
+	gint16 str_len;
+	gint16 mark_start;
+	gint16 mark_end;
+	gint16 indent;
+	gint16 left_len;
+	gint16 lines_taken;
+#define RECORD_WRAPS 4
+	guint16 wrap_offset[RECORD_WRAPS];
+	guchar mb;		/* boolean: is multibyte? */
+	guchar tag;
+	guchar pad1;
+	guchar pad2;	/* 32-bit align : 44 bytes total */
+};
+
+enum
+{
+	WORD_CLICK,
+	LAST_SIGNAL
+};
+
+/* values for selection info */
+enum
+{
+	TARGET_UTF8_STRING,
+	TARGET_STRING,
+	TARGET_TEXT,
+	TARGET_COMPOUND_TEXT
+};
+
+static guint xtext_signals[LAST_SIGNAL];
+
+#ifdef XCHAT
+char *nocasestrstr (const char *text, const char *tofind);	/* util.c */
+int xtext_get_stamp_str (time_t, char **);
+#endif
+static void gtk_xtext_render_page (GtkXText * xtext);
+static void gtk_xtext_calc_lines (xtext_buffer *buf, int);
+#if defined(USE_XLIB) || defined(WIN32)
+static void gtk_xtext_load_trans (GtkXText * xtext);
+static void gtk_xtext_free_trans (GtkXText * xtext);
+#endif
+static char *gtk_xtext_selection_get_text (GtkXText *xtext, int *len_ret);
+static textentry *gtk_xtext_nth (GtkXText *xtext, int line, int *subline);
+static void gtk_xtext_adjustment_changed (GtkAdjustment * adj,
+														GtkXText * xtext);
+static int gtk_xtext_render_ents (GtkXText * xtext, textentry *, textentry *);
+static void gtk_xtext_recalc_widths (xtext_buffer *buf, int);
+static void gtk_xtext_fix_indent (xtext_buffer *buf);
+static int gtk_xtext_find_subline (GtkXText *xtext, textentry *ent, int line);
+static char *gtk_xtext_conv_color (unsigned char *text, int len, int *newlen);
+static unsigned char *
+gtk_xtext_strip_color (unsigned char *text, int len, unsigned char *outbuf,
+							  int *newlen, int *mb_ret, int strip_hidden);
+static gboolean gtk_xtext_check_ent_visibility (GtkXText * xtext, textentry *find_ent, int add);
+static int gtk_xtext_render_page_timeout (GtkXText * xtext);
+
+/* some utility functions first */
+
+#ifndef XCHAT	/* xchat has this in util.c */
+
+static char *
+nocasestrstr (const char *s, const char *tofind)
+{
+   register const size_t len = strlen (tofind);
+
+   if (len == 0)
+     return (char *)s;
+   while (toupper(*s) != toupper(*tofind) || strncasecmp (s, tofind, len))
+     if (*s++ == '\0')
+       return (char *)NULL;
+   return (char *)s;   
+}
+
+#endif
+
+/* gives width of a 8bit string - with no mIRC codes in it */
+
+static int
+gtk_xtext_text_width_8bit (GtkXText *xtext, unsigned char *str, int len)
+{
+	int width = 0;
+
+	while (len)
+	{
+		width += xtext->fontwidth[*str];
+		str++;
+		len--;
+	}
+
+	return width;
+}
+
+#ifdef WIN32
+
+static void
+win32_draw_bg (GtkXText *xtext, int x, int y, int width, int height)
+{
+	HDC hdc;
+	HWND hwnd;
+	HRGN rgn;
+
+	if (xtext->shaded)
+	{
+		/* xtext->pixmap is really a GdkImage, created in win32_tint() */
+		gdk_draw_image (xtext->draw_buf, xtext->bgc, (GdkImage*)xtext->pixmap,
+							 x, y, x, y, width, height);
+	} else
+	{
+		hwnd = GDK_WINDOW_HWND (xtext->draw_buf);
+		hdc = GetDC (hwnd);
+
+		rgn = CreateRectRgn (x, y, x + width, y + height);
+		SelectClipRgn (hdc, rgn);
+
+		PaintDesktop (hdc);
+
+		ReleaseDC (hwnd, hdc);
+		DeleteObject (rgn);
+	}
+}
+
+static void
+xtext_draw_bg (GtkXText *xtext, int x, int y, int width, int height)
+{
+	if (xtext->transparent)
+		win32_draw_bg (xtext, x, y, width, height);
+	else
+		gdk_draw_rectangle (xtext->draw_buf, xtext->bgc, 1, x, y, width, height);
+}
+
+#else
+
+#define xtext_draw_bg(xt,x,y,w,h) gdk_draw_rectangle(xt->draw_buf, xt->bgc, \
+																	  1,x,y,w,h);
+
+#endif
+
+/* ========================================= */
+/* ========== XFT 1 and 2 BACKEND ========== */
+/* ========================================= */
+
+#ifdef USE_XFT
+
+static void
+backend_font_close (GtkXText *xtext)
+{
+	XftFontClose (GDK_WINDOW_XDISPLAY (xtext->draw_buf), xtext->font);
+#ifdef ITALIC
+	XftFontClose (GDK_WINDOW_XDISPLAY (xtext->draw_buf), xtext->ifont);
+#endif
+}
+
+static void
+backend_init (GtkXText *xtext)
+{
+	if (xtext->xftdraw == NULL)
+	{
+		xtext->xftdraw = XftDrawCreate (
+			GDK_WINDOW_XDISPLAY (xtext->draw_buf),
+			GDK_WINDOW_XWINDOW (xtext->draw_buf),
+			GDK_VISUAL_XVISUAL (gdk_drawable_get_visual (xtext->draw_buf)),
+			GDK_COLORMAP_XCOLORMAP (gdk_drawable_get_colormap (xtext->draw_buf)));
+		XftDrawSetSubwindowMode (xtext->xftdraw, IncludeInferiors);
+	}
+}
+
+static void
+backend_deinit (GtkXText *xtext)
+{
+	if (xtext->xftdraw)
+	{
+		XftDrawDestroy (xtext->xftdraw);
+		xtext->xftdraw = NULL;
+	}
+}
+
+static XftFont *
+backend_font_open_real (Display *xdisplay, char *name, gboolean italics)
+{
+	XftFont *font = NULL;
+	PangoFontDescription *fontd;
+	int weight, slant, screen = DefaultScreen (xdisplay);
+
+	fontd = pango_font_description_from_string (name);
+
+	if (pango_font_description_get_size (fontd) != 0)
+	{
+		weight = pango_font_description_get_weight (fontd);
+		/* from pangoft2-fontmap.c */
+		if (weight < (PANGO_WEIGHT_NORMAL + PANGO_WEIGHT_LIGHT) / 2)
+			weight = XFT_WEIGHT_LIGHT;
+		else if (weight < (PANGO_WEIGHT_NORMAL + 600) / 2)
+			weight = XFT_WEIGHT_MEDIUM;
+		else if (weight < (600 + PANGO_WEIGHT_BOLD) / 2)
+			weight = XFT_WEIGHT_DEMIBOLD;
+		else if (weight < (PANGO_WEIGHT_BOLD + PANGO_WEIGHT_ULTRABOLD) / 2)
+			weight = XFT_WEIGHT_BOLD;
+		else
+			weight = XFT_WEIGHT_BLACK;
+
+		slant = pango_font_description_get_style (fontd);
+		if (slant == PANGO_STYLE_ITALIC)
+			slant = XFT_SLANT_ITALIC;
+		else if (slant == PANGO_STYLE_OBLIQUE)
+			slant = XFT_SLANT_OBLIQUE;
+		else
+			slant = XFT_SLANT_ROMAN;
+
+		font = XftFontOpen (xdisplay, screen,
+						XFT_FAMILY, XftTypeString, pango_font_description_get_family (fontd),
+						XFT_CORE, XftTypeBool, False,
+						XFT_SIZE, XftTypeDouble, (double)pango_font_description_get_size (fontd)/PANGO_SCALE,
+						XFT_WEIGHT, XftTypeInteger, weight,
+						XFT_SLANT, XftTypeInteger, italics ? XFT_SLANT_ITALIC : slant,
+						NULL);
+	}
+	pango_font_description_free (fontd);
+
+	if (font == NULL)
+	{
+		font = XftFontOpenName (xdisplay, screen, name);
+		if (font == NULL)
+			font = XftFontOpenName (xdisplay, screen, "sans-11");
+	}
+
+	return font;
+}
+
+static void
+backend_font_open (GtkXText *xtext, char *name)
+{
+	Display *dis = GDK_WINDOW_XDISPLAY (xtext->draw_buf);
+
+	xtext->font = backend_font_open_real (dis, name, FALSE);
+#ifdef ITALIC
+	xtext->ifont = backend_font_open_real (dis, name, TRUE);
+#endif
+}
+
+inline static int
+backend_get_char_width (GtkXText *xtext, unsigned char *str, int *mbl_ret)
+{
+	XGlyphInfo ext;
+
+	if (*str < 128)
+	{
+		*mbl_ret = 1;
+		return xtext->fontwidth[*str];
+	}
+
+	*mbl_ret = charlen (str);
+	XftTextExtentsUtf8 (GDK_WINDOW_XDISPLAY (xtext->draw_buf), xtext->font, str, *mbl_ret, &ext);
+
+	return ext.xOff;
+}
+
+static int
+backend_get_text_width (GtkXText *xtext, guchar *str, int len, int is_mb)
+{
+	XGlyphInfo ext;
+
+	if (!is_mb)
+		return gtk_xtext_text_width_8bit (xtext, str, len);
+
+	XftTextExtentsUtf8 (GDK_WINDOW_XDISPLAY (xtext->draw_buf), xtext->font, str, len, &ext);
+	return ext.xOff;
+}
+
+static void
+backend_draw_text (GtkXText *xtext, int dofill, GdkGC *gc, int x, int y,
+						 char *str, int len, int str_width, int is_mb)
+{
+	/*Display *xdisplay = GDK_WINDOW_XDISPLAY (xtext->draw_buf);*/
+	void (*draw_func) (XftDraw *, XftColor *, XftFont *, int, int, XftChar8 *, int) = (void *)XftDrawString8;
+	XftFont *font;
+
+	/* if all ascii, use String8 to avoid the conversion penalty */
+	if (is_mb)
+		draw_func = (void *)XftDrawStringUtf8;
+
+	if (dofill)
+	{
+/*		register GC xgc = GDK_GC_XGC (gc);
+		XSetForeground (xdisplay, xgc, xtext->xft_bg->pixel);
+		XFillRectangle (xdisplay, GDK_WINDOW_XWINDOW (xtext->draw_buf), xgc, x,
+							 y - xtext->font->ascent, str_width, xtext->fontsize);*/
+		XftDrawRect (xtext->xftdraw, xtext->xft_bg, x,
+						 y - xtext->font->ascent, str_width, xtext->fontsize);
+	}
+
+	font = xtext->font;
+#ifdef ITALIC
+	if (xtext->italics)
+		font = xtext->ifont;
+#endif
+
+	draw_func (xtext->xftdraw, xtext->xft_fg, font, x, y, str, len);
+
+	if (xtext->overdraw)
+		draw_func (xtext->xftdraw, xtext->xft_fg, font, x, y, str, len);
+
+	if (xtext->bold)
+		draw_func (xtext->xftdraw, xtext->xft_fg, font, x + 1, y, str, len);
+}
+
+/*static void
+backend_set_clip (GtkXText *xtext, GdkRectangle *area)
+{
+	gdk_gc_set_clip_rectangle (xtext->fgc, area);
+	gdk_gc_set_clip_rectangle (xtext->bgc, area);
+}
+
+static void
+backend_clear_clip (GtkXText *xtext)
+{
+	gdk_gc_set_clip_rectangle (xtext->fgc, NULL);
+	gdk_gc_set_clip_rectangle (xtext->bgc, NULL);
+}*/
+
+/*static void
+backend_set_clip (GtkXText *xtext, GdkRectangle *area)
+{
+	Region reg;
+	XRectangle rect;
+
+	rect.x = area->x;
+	rect.y = area->y;
+	rect.width = area->width;
+	rect.height = area->height;
+
+	reg = XCreateRegion ();
+	XUnionRectWithRegion (&rect, reg, reg);
+	XftDrawSetClip (xtext->xftdraw, reg);
+	XDestroyRegion (reg);
+
+	gdk_gc_set_clip_rectangle (xtext->fgc, area);
+}
+
+static void
+backend_clear_clip (GtkXText *xtext)
+{
+	XftDrawSetClip (xtext->xftdraw, NULL);
+	gdk_gc_set_clip_rectangle (xtext->fgc, NULL);
+}
+*/
+#else	/* !USE_XFT */
+
+/* ======================================= */
+/* ============ PANGO BACKEND ============ */
+/* ======================================= */
+
+static void
+backend_font_close (GtkXText *xtext)
+{
+	pango_font_description_free (xtext->font->font);
+#ifdef ITALIC
+	pango_font_description_free (xtext->font->ifont);
+#endif
+}
+
+static void
+backend_init (GtkXText *xtext)
+{
+	if (xtext->layout == NULL)
+	{
+		xtext->layout = gtk_widget_create_pango_layout (GTK_WIDGET (xtext), 0); 
+		if (xtext->font)
+			pango_layout_set_font_description (xtext->layout, xtext->font->font);
+	}
+}
+
+static void
+backend_deinit (GtkXText *xtext)
+{
+	if (xtext->layout)
+	{
+		g_object_unref (xtext->layout);
+		xtext->layout = NULL;
+	}
+}
+
+static PangoFontDescription *
+backend_font_open_real (char *name)
+{
+	PangoFontDescription *font;
+
+	font = pango_font_description_from_string (name);
+	if (font && pango_font_description_get_size (font) == 0)
+	{
+		pango_font_description_free (font);
+		font = pango_font_description_from_string ("sans 11");
+	}
+	if (!font)
+		font = pango_font_description_from_string ("sans 11");
+
+	return font;
+}
+
+static void
+backend_font_open (GtkXText *xtext, char *name)
+{
+	PangoLanguage *lang;
+	PangoContext *context;
+	PangoFontMetrics *metrics;
+
+	xtext->font = &xtext->pango_font;
+	xtext->font->font = backend_font_open_real (name);
+	if (!xtext->font->font)
+	{
+		xtext->font = NULL;
+		return;
+	}
+#ifdef ITALIC
+	xtext->font->ifont = backend_font_open_real (name);
+	pango_font_description_set_style (xtext->font->ifont, PANGO_STYLE_ITALIC);
+#endif
+
+	backend_init (xtext);
+	pango_layout_set_font_description (xtext->layout, xtext->font->font);
+
+	/* vte does it this way */
+	context = gtk_widget_get_pango_context (GTK_WIDGET (xtext));
+	lang = pango_context_get_language (context);
+	metrics = pango_context_get_metrics (context, xtext->font->font, lang);
+	xtext->font->ascent = pango_font_metrics_get_ascent (metrics) / PANGO_SCALE;
+	xtext->font->descent = pango_font_metrics_get_descent (metrics) / PANGO_SCALE;
+	pango_font_metrics_unref (metrics);
+}
+
+static int
+backend_get_text_width (GtkXText *xtext, guchar *str, int len, int is_mb)
+{
+	int width;
+
+	if (!is_mb)
+		return gtk_xtext_text_width_8bit (xtext, str, len);
+
+	if (*str == 0)
+		return 0;
+
+	pango_layout_set_text (xtext->layout, str, len);
+	pango_layout_get_pixel_size (xtext->layout, &width, NULL);
+
+	return width;
+}
+
+inline static int
+backend_get_char_width (GtkXText *xtext, unsigned char *str, int *mbl_ret)
+{
+	int width;
+
+	if (*str < 128)
+	{
+		*mbl_ret = 1;
+		return xtext->fontwidth[*str];
+	}
+
+	*mbl_ret = charlen (str);
+	pango_layout_set_text (xtext->layout, str, *mbl_ret);
+	pango_layout_get_pixel_size (xtext->layout, &width, NULL);
+
+	return width;
+}
+
+/* simplified version of gdk_draw_layout_line_with_colors() */
+
+static void 
+xtext_draw_layout_line (GdkDrawable      *drawable,
+								GdkGC            *gc,
+								gint              x, 
+								gint              y,
+								PangoLayoutLine  *line)
+{
+	GSList *tmp_list = line->runs;
+	PangoRectangle logical_rect;
+	gint x_off = 0;
+
+	while (tmp_list)
+	{
+		PangoLayoutRun *run = tmp_list->data;
+
+		pango_glyph_string_extents (run->glyphs, run->item->analysis.font,
+											 NULL, &logical_rect);
+
+		gdk_draw_glyphs (drawable, gc, run->item->analysis.font,
+							  x + x_off / PANGO_SCALE, y, run->glyphs);
+
+		x_off += logical_rect.width;
+		tmp_list = tmp_list->next;
+	}
+}
+
+static void
+backend_draw_text (GtkXText *xtext, int dofill, GdkGC *gc, int x, int y,
+						 char *str, int len, int str_width, int is_mb)
+{
+	GdkGCValues val;
+	GdkColor col;
+	PangoLayoutLine *line;
+
+#ifdef ITALIC
+	if (xtext->italics)
+		pango_layout_set_font_description (xtext->layout, xtext->font->ifont);
+#endif
+
+	pango_layout_set_text (xtext->layout, str, len);
+
+	if (dofill)
+	{
+#ifdef WIN32
+		if (xtext->transparent && !xtext->backcolor)
+			win32_draw_bg (xtext, x, y - xtext->font->ascent, str_width,
+								xtext->fontsize);
+		else
+#endif
+		{
+			gdk_gc_get_values (gc, &val);
+			col.pixel = val.background.pixel;
+			gdk_gc_set_foreground (gc, &col);
+			gdk_draw_rectangle (xtext->draw_buf, gc, 1, x, y -
+									  xtext->font->ascent, str_width, xtext->fontsize);
+			col.pixel = val.foreground.pixel;
+			gdk_gc_set_foreground (gc, &col);
+		}
+	}
+
+	line = pango_layout_get_lines (xtext->layout)->data;
+
+	xtext_draw_layout_line (xtext->draw_buf, gc, x, y, line);
+
+	if (xtext->overdraw)
+		xtext_draw_layout_line (xtext->draw_buf, gc, x, y, line);
+
+	if (xtext->bold)
+		xtext_draw_layout_line (xtext->draw_buf, gc, x + 1, y, line);
+
+#ifdef ITALIC
+	if (xtext->italics)
+		pango_layout_set_font_description (xtext->layout, xtext->font->font);
+#endif
+}
+
+/*static void
+backend_set_clip (GtkXText *xtext, GdkRectangle *area)
+{
+	gdk_gc_set_clip_rectangle (xtext->fgc, area);
+	gdk_gc_set_clip_rectangle (xtext->bgc, area);
+}
+
+static void
+backend_clear_clip (GtkXText *xtext)
+{
+	gdk_gc_set_clip_rectangle (xtext->fgc, NULL);
+	gdk_gc_set_clip_rectangle (xtext->bgc, NULL);
+}*/
+
+#endif /* !USE_PANGO */
+
+static void
+xtext_set_fg (GtkXText *xtext, GdkGC *gc, int index)
+{
+	GdkColor col;
+
+	col.pixel = xtext->palette[index];
+	gdk_gc_set_foreground (gc, &col);
+
+#ifdef USE_XFT
+	if (gc == xtext->fgc)
+		xtext->xft_fg = &xtext->color[index];
+	else
+		xtext->xft_bg = &xtext->color[index];
+#endif
+}
+
+#ifdef USE_XFT
+
+#define xtext_set_bg(xt,gc,index) xt->xft_bg = &xt->color[index]
+
+#else
+
+static void
+xtext_set_bg (GtkXText *xtext, GdkGC *gc, int index)
+{
+	GdkColor col;
+
+	col.pixel = xtext->palette[index];
+	gdk_gc_set_background (gc, &col);
+}
+
+#endif
+
+static void
+gtk_xtext_init (GtkXText * xtext)
+{
+	xtext->pixmap = NULL;
+	xtext->io_tag = 0;
+	xtext->add_io_tag = 0;
+	xtext->scroll_tag = 0;
+	xtext->max_lines = 0;
+	xtext->col_back = XTEXT_BG;
+	xtext->col_fore = XTEXT_FG;
+	xtext->nc = 0;
+	xtext->pixel_offset = 0;
+	xtext->bold = FALSE;
+	xtext->underline = FALSE;
+	xtext->italics = FALSE;
+	xtext->hidden = FALSE;
+	xtext->font = NULL;
+#ifdef USE_XFT
+	xtext->xftdraw = NULL;
+#else
+	xtext->layout = NULL;
+#endif
+	xtext->jump_out_offset = 0;
+	xtext->jump_in_offset = 0;
+	xtext->ts_x = 0;
+	xtext->ts_y = 0;
+	xtext->clip_x = 0;
+	xtext->clip_x2 = 1000000;
+	xtext->clip_y = 0;
+	xtext->clip_y2 = 1000000;
+	xtext->error_function = NULL;
+	xtext->urlcheck_function = NULL;
+	xtext->color_paste = FALSE;
+	xtext->skip_border_fills = FALSE;
+	xtext->skip_stamp = FALSE;
+	xtext->render_hilights_only = FALSE;
+	xtext->un_hilight = FALSE;
+	xtext->recycle = FALSE;
+	xtext->dont_render = FALSE;
+	xtext->dont_render2 = FALSE;
+	xtext->overdraw = FALSE;
+	xtext->tint_red = xtext->tint_green = xtext->tint_blue = TINT_VALUE;
+
+	xtext->adj = (GtkAdjustment *) gtk_adjustment_new (0, 0, 1, 1, 1, 1);
+	g_object_ref (G_OBJECT (xtext->adj));
+	g_object_ref_sink (G_OBJECT (xtext->adj));
+	g_object_unref (G_OBJECT (xtext->adj));
+
+	xtext->vc_signal_tag = g_signal_connect (G_OBJECT (xtext->adj),
+				"value_changed", G_CALLBACK (gtk_xtext_adjustment_changed), xtext);
+	{
+		static const GtkTargetEntry targets[] = {
+			{ "UTF8_STRING", 0, TARGET_UTF8_STRING },
+			{ "STRING", 0, TARGET_STRING },
+			{ "TEXT",   0, TARGET_TEXT }, 
+			{ "COMPOUND_TEXT", 0, TARGET_COMPOUND_TEXT }
+		};
+		static const gint n_targets = sizeof (targets) / sizeof (targets[0]);
+
+		gtk_selection_add_targets (GTK_WIDGET (xtext), GDK_SELECTION_PRIMARY,
+											targets, n_targets);
+	}
+
+	if (getenv ("XCHAT_OVERDRAW"))
+		xtext->overdraw = TRUE;
+}
+
+static void
+gtk_xtext_adjustment_set (xtext_buffer *buf, int fire_signal)
+{
+	GtkAdjustment *adj = buf->xtext->adj;
+
+	if (buf->xtext->buffer == buf)
+	{
+		adj->lower = 0;
+		adj->upper = buf->num_lines;
+
+		if (adj->upper == 0)
+			adj->upper = 1;
+
+		adj->page_size =
+			(GTK_WIDGET (buf->xtext)->allocation.height -
+			 buf->xtext->font->descent) / buf->xtext->fontsize;
+		adj->page_increment = adj->page_size;
+
+		if (adj->value > adj->upper - adj->page_size)
+			adj->value = adj->upper - adj->page_size;
+
+		if (adj->value < 0)
+			adj->value = 0;
+
+		if (fire_signal)
+			gtk_adjustment_changed (adj);
+	}
+}
+
+static gint
+gtk_xtext_adjustment_timeout (GtkXText * xtext)
+{
+	gtk_xtext_render_page (xtext);
+	xtext->io_tag = 0;
+	return 0;
+}
+
+static void
+gtk_xtext_adjustment_changed (GtkAdjustment * adj, GtkXText * xtext)
+{
+#ifdef SMOOTH_SCROLL
+	if (xtext->buffer->old_value != xtext->adj->value)
+#else
+	if ((int) xtext->buffer->old_value != (int) xtext->adj->value)
+#endif
+	{
+		if (xtext->adj->value >= xtext->adj->upper - xtext->adj->page_size)
+			xtext->buffer->scrollbar_down = TRUE;
+		else
+			xtext->buffer->scrollbar_down = FALSE;
+
+		if (xtext->adj->value + 1 == xtext->buffer->old_value ||
+			 xtext->adj->value - 1 == xtext->buffer->old_value)	/* clicked an arrow? */
+		{
+			if (xtext->io_tag)
+			{
+				g_source_remove (xtext->io_tag);
+				xtext->io_tag = 0;
+			}
+			gtk_xtext_render_page (xtext);
+		} else
+		{
+			if (!xtext->io_tag)
+				xtext->io_tag = g_timeout_add (REFRESH_TIMEOUT,
+															(GSourceFunc)
+															gtk_xtext_adjustment_timeout,
+															xtext);
+		}
+	}
+	xtext->buffer->old_value = adj->value;
+}
+
+GtkWidget *
+gtk_xtext_new (GdkColor palette[], int separator)
+{
+	GtkXText *xtext;
+
+	xtext = g_object_new (gtk_xtext_get_type (), NULL);
+	xtext->separator = separator;
+	xtext->wordwrap = TRUE;
+	xtext->buffer = gtk_xtext_buffer_new (xtext);
+	xtext->orig_buffer = xtext->buffer;
+
+	gtk_widget_set_double_buffered (GTK_WIDGET (xtext), FALSE);
+	gtk_xtext_set_palette (xtext, palette);
+
+	return GTK_WIDGET (xtext);
+}
+
+static void
+gtk_xtext_destroy (GtkObject * object)
+{
+	GtkXText *xtext = GTK_XTEXT (object);
+
+	if (xtext->add_io_tag)
+	{
+		g_source_remove (xtext->add_io_tag);
+		xtext->add_io_tag = 0;
+	}
+
+	if (xtext->scroll_tag)
+	{
+		g_source_remove (xtext->scroll_tag);
+		xtext->scroll_tag = 0;
+	}
+
+	if (xtext->io_tag)
+	{
+		g_source_remove (xtext->io_tag);
+		xtext->io_tag = 0;
+	}
+
+	if (xtext->pixmap)
+	{
+#if defined(USE_XLIB) || defined(WIN32)
+		if (xtext->transparent)
+			gtk_xtext_free_trans (xtext);
+		else
+#endif
+			g_object_unref (xtext->pixmap);
+		xtext->pixmap = NULL;
+	}
+
+	if (xtext->font)
+	{
+		backend_font_close (xtext);
+		xtext->font = NULL;
+	}
+
+	if (xtext->adj)
+	{
+		g_signal_handlers_disconnect_matched (G_OBJECT (xtext->adj),
+					G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, xtext);
+	/*	gtk_signal_disconnect_by_data (GTK_OBJECT (xtext->adj), xtext);*/
+		g_object_unref (G_OBJECT (xtext->adj));
+		xtext->adj = NULL;
+	}
+
+	if (xtext->bgc)
+	{
+		g_object_unref (xtext->bgc);
+		xtext->bgc = NULL;
+	}
+
+	if (xtext->fgc)
+	{
+		g_object_unref (xtext->fgc);
+		xtext->fgc = NULL;
+	}
+
+	if (xtext->light_gc)
+	{
+		g_object_unref (xtext->light_gc);
+		xtext->light_gc = NULL;
+	}
+
+	if (xtext->dark_gc)
+	{
+		g_object_unref (xtext->dark_gc);
+		xtext->dark_gc = NULL;
+	}
+
+	if (xtext->thin_gc)
+	{
+		g_object_unref (xtext->thin_gc);
+		xtext->thin_gc = NULL;
+	}
+
+	if (xtext->marker_gc)
+	{
+		g_object_unref (xtext->marker_gc);
+		xtext->marker_gc = NULL;
+	}
+
+	if (xtext->hand_cursor)
+	{
+		gdk_cursor_unref (xtext->hand_cursor);
+		xtext->hand_cursor = NULL;
+	}
+
+	if (xtext->resize_cursor)
+	{
+		gdk_cursor_unref (xtext->resize_cursor);
+		xtext->resize_cursor = NULL;
+	}
+
+	if (xtext->orig_buffer)
+	{
+		gtk_xtext_buffer_free (xtext->orig_buffer);
+		xtext->orig_buffer = NULL;
+	}
+
+	if (GTK_OBJECT_CLASS (parent_class)->destroy)
+		(*GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+static void
+gtk_xtext_unrealize (GtkWidget * widget)
+{
+	backend_deinit (GTK_XTEXT (widget));
+
+	/* if there are still events in the queue, this'll avoid segfault */
+	gdk_window_set_user_data (widget->window, NULL);
+
+	if (parent_class->unrealize)
+		(* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
+}
+
+static void
+gtk_xtext_realize (GtkWidget * widget)
+{
+	GtkXText *xtext;
+	GdkWindowAttr attributes;
+	GdkGCValues val;
+	GdkColor col;
+	GdkColormap *cmap;
+
+	GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+	xtext = GTK_XTEXT (widget);
+
+	attributes.x = widget->allocation.x;
+	attributes.y = widget->allocation.y;
+	attributes.width = widget->allocation.width;
+	attributes.height = widget->allocation.height;
+	attributes.wclass = GDK_INPUT_OUTPUT;
+	attributes.window_type = GDK_WINDOW_CHILD;
+	attributes.event_mask = gtk_widget_get_events (widget) |
+		GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
+#ifdef MOTION_MONITOR
+		| GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK;
+#else
+		| GDK_POINTER_MOTION_MASK;
+#endif
+
+	cmap = gtk_widget_get_colormap (widget);
+	attributes.colormap = cmap;
+	attributes.visual = gtk_widget_get_visual (widget);
+
+	widget->window = gdk_window_new (widget->parent->window, &attributes,
+												GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL |
+												GDK_WA_COLORMAP);
+
+	gdk_window_set_user_data (widget->window, widget);
+
+	xtext->depth = gdk_drawable_get_visual (widget->window)->depth;
+
+	val.subwindow_mode = GDK_INCLUDE_INFERIORS;
+	val.graphics_exposures = 0;
+
+	xtext->bgc = gdk_gc_new_with_values (widget->window, &val,
+													 GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW);
+	xtext->fgc = gdk_gc_new_with_values (widget->window, &val,
+													 GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW);
+	xtext->light_gc = gdk_gc_new_with_values (widget->window, &val,
+											GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW);
+	xtext->dark_gc = gdk_gc_new_with_values (widget->window, &val,
+											GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW);
+	xtext->thin_gc = gdk_gc_new_with_values (widget->window, &val,
+											GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW);
+	xtext->marker_gc = gdk_gc_new_with_values (widget->window, &val,
+											GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW);
+
+	/* for the separator bar (light) */
+	col.red = 0xffff; col.green = 0xffff; col.blue = 0xffff;
+	gdk_colormap_alloc_color (cmap, &col, FALSE, TRUE);
+	gdk_gc_set_foreground (xtext->light_gc, &col);
+
+	/* for the separator bar (dark) */
+	col.red = 0x1111; col.green = 0x1111; col.blue = 0x1111;
+	gdk_colormap_alloc_color (cmap, &col, FALSE, TRUE);
+	gdk_gc_set_foreground (xtext->dark_gc, &col);
+
+	/* for the separator bar (thinline) */
+	col.red = 0x8e38; col.green = 0x8e38; col.blue = 0x9f38;
+	gdk_colormap_alloc_color (cmap, &col, FALSE, TRUE);
+	gdk_gc_set_foreground (xtext->thin_gc, &col);
+
+	/* for the marker bar (marker) */
+	col.pixel = xtext->palette[XTEXT_MARKER];
+	gdk_gc_set_foreground (xtext->marker_gc, &col);
+
+	xtext_set_fg (xtext, xtext->fgc, XTEXT_FG);
+	xtext_set_bg (xtext, xtext->fgc, XTEXT_BG);
+	xtext_set_fg (xtext, xtext->bgc, XTEXT_BG);
+
+	/* draw directly to window */
+	xtext->draw_buf = widget->window;
+
+#if defined(USE_XLIB) || defined(WIN32)
+	if (xtext->transparent)
+	{
+		gtk_xtext_load_trans (xtext);
+	} else
+#endif
+	if (xtext->pixmap)
+	{
+		gdk_gc_set_tile (xtext->bgc, xtext->pixmap);
+		gdk_gc_set_ts_origin (xtext->bgc, 0, 0);
+		xtext->ts_x = xtext->ts_y = 0;
+		gdk_gc_set_fill (xtext->bgc, GDK_TILED);
+	}
+
+#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0)
+	xtext->hand_cursor = gdk_cursor_new (GDK_HAND1);
+	xtext->resize_cursor = gdk_cursor_new (GDK_LEFT_SIDE);
+#else
+	xtext->hand_cursor = gdk_cursor_new_for_display (gdk_drawable_get_display (widget->window), GDK_HAND1);
+	xtext->resize_cursor = gdk_cursor_new_for_display (gdk_drawable_get_display (widget->window), GDK_LEFT_SIDE);
+#endif
+
+	gdk_window_set_back_pixmap (widget->window, NULL, FALSE);
+	widget->style = gtk_style_attach (widget->style, widget->window);
+
+	backend_init (xtext);
+}
+
+static void
+gtk_xtext_size_request (GtkWidget * widget, GtkRequisition * requisition)
+{
+	requisition->width = 200;
+	requisition->height = 90;
+}
+
+static void
+gtk_xtext_size_allocate (GtkWidget * widget, GtkAllocation * allocation)
+{
+	GtkXText *xtext = GTK_XTEXT (widget);
+	int height_only = FALSE;
+	int do_trans = TRUE;
+
+	if (allocation->width == xtext->buffer->window_width)
+		height_only = TRUE;
+
+	if (allocation->x == widget->allocation.x &&
+		 allocation->y == widget->allocation.y && xtext->avoid_trans)
+		do_trans = FALSE;
+
+	xtext->avoid_trans = FALSE;
+
+	widget->allocation = *allocation;
+	if (GTK_WIDGET_REALIZED (widget))
+	{
+		xtext->buffer->window_width = allocation->width;
+		xtext->buffer->window_height = allocation->height;
+
+		gdk_window_move_resize (widget->window, allocation->x, allocation->y,
+										allocation->width, allocation->height);
+		dontscroll (xtext->buffer);	/* force scrolling off */
+		if (!height_only)
+			gtk_xtext_calc_lines (xtext->buffer, FALSE);
+		else
+		{
+			xtext->buffer->pagetop_ent = NULL;
+			gtk_xtext_adjustment_set (xtext->buffer, FALSE);
+		}
+#if defined(USE_XLIB) || defined(WIN32)
+		if (do_trans && xtext->transparent && xtext->shaded)
+		{
+			gtk_xtext_free_trans (xtext);
+			gtk_xtext_load_trans (xtext);
+		}
+#endif
+		if (xtext->buffer->scrollbar_down)
+			gtk_adjustment_set_value (xtext->adj, xtext->adj->upper -
+											  xtext->adj->page_size);
+	}
+}
+
+static void
+gtk_xtext_selection_clear_full (xtext_buffer *buf)
+{
+	textentry *ent = buf->text_first;
+	while (ent)
+	{
+		ent->mark_start = -1;
+		ent->mark_end = -1;
+		ent = ent->next;
+	}
+}
+
+static int
+gtk_xtext_selection_clear (xtext_buffer *buf)
+{
+	textentry *ent;
+	int ret = 0;
+
+	ent = buf->last_ent_start;
+	while (ent)
+	{
+		if (ent->mark_start != -1)
+			ret = 1;
+		ent->mark_start = -1;
+		ent->mark_end = -1;
+		if (ent == buf->last_ent_end)
+			break;
+		ent = ent->next;
+	}
+
+	return ret;
+}
+
+static int
+find_x (GtkXText *xtext, textentry *ent, unsigned char *text, int x, int indent)
+{
+	int xx = indent;
+	int i = 0;
+	int rcol = 0, bgcol = 0;
+	int hidden = FALSE;
+	unsigned char *orig = text;
+	int mbl;
+	int char_width;
+
+	while (*text)
+	{
+		mbl = 1;
+		if (rcol > 0 && (isdigit (*text) || (*text == ',' && isdigit (text[1]) && !bgcol)))
+		{
+			if (text[1] != ',') rcol--;
+			if (*text == ',')
+			{
+				rcol = 2;
+				bgcol = 1;
+			}
+			text++;
+		} else
+		{
+			rcol = bgcol = 0;
+			switch (*text)
+			{
+			case ATTR_COLOR:
+				rcol = 2;
+			case ATTR_BEEP:
+			case ATTR_RESET:
+			case ATTR_REVERSE:
+			case ATTR_BOLD:
+			case ATTR_UNDERLINE:
+			case ATTR_ITALICS:
+				text++;
+				break;
+			case ATTR_HIDDEN:
+				if (xtext->ignore_hidden)
+					goto def;
+				hidden = !hidden;
+				text++;
+				break;
+			default:
+			def:
+				char_width = backend_get_char_width (xtext, text, &mbl);
+				if (!hidden) xx += char_width;
+				text += mbl;
+				if (xx >= x)
+					return i + (orig - ent->str);
+			}
+		}
+
+		i += mbl;
+		if (text - orig >= ent->str_len)
+			return ent->str_len;
+	}
+
+	return ent->str_len;
+}
+
+static int
+gtk_xtext_find_x (GtkXText * xtext, int x, textentry * ent, int subline,
+						int line, int *out_of_bounds)
+{
+	int indent;
+	unsigned char *str;
+
+	if (subline < 1)
+		indent = ent->indent;
+	else
+		indent = xtext->buffer->indent;
+
+	if (line > xtext->adj->page_size || line < 0)
+		return 0;
+
+	if (xtext->buffer->grid_dirty || line > 255)
+	{
+		str = ent->str + gtk_xtext_find_subline (xtext, ent, subline);
+		if (str >= ent->str + ent->str_len)
+			return 0;
+	} else
+	{
+		if (xtext->buffer->grid_offset[line] > ent->str_len)
+			return 0;
+		str = ent->str + xtext->buffer->grid_offset[line];
+	}
+
+	if (x < indent)
+	{
+		*out_of_bounds = 1;
+		return (str - ent->str);
+	}
+
+	*out_of_bounds = 0;
+
+	return find_x (xtext, ent, str, x, indent);
+}
+
+static textentry *
+gtk_xtext_find_char (GtkXText * xtext, int x, int y, int *off,
+							int *out_of_bounds)
+{
+	textentry *ent;
+	int line;
+	int subline;
+
+	line = (y + xtext->pixel_offset) / xtext->fontsize;
+	ent = gtk_xtext_nth (xtext, line + (int)xtext->adj->value, &subline);
+	if (!ent)
+		return 0;
+
+	if (off)
+		*off = gtk_xtext_find_x (xtext, x, ent, subline, line, out_of_bounds);
+
+	return ent;
+}
+
+static void
+gtk_xtext_draw_sep (GtkXText * xtext, int y)
+{
+	int x, height;
+	GdkGC *light, *dark;
+
+	if (y == -1)
+	{
+		y = 0;
+		height = GTK_WIDGET (xtext)->allocation.height;
+	} else
+	{
+		height = xtext->fontsize;
+	}
+
+	/* draw the separator line */
+	if (xtext->separator && xtext->buffer->indent)
+	{
+		light = xtext->light_gc;
+		dark = xtext->dark_gc;
+
+		x = xtext->buffer->indent - ((xtext->space_width + 1) / 2);
+		if (x < 1)
+			return;
+
+		if (xtext->thinline)
+		{
+			if (xtext->moving_separator)
+				gdk_draw_line (xtext->draw_buf, light, x, y, x, y + height);
+			else
+				gdk_draw_line (xtext->draw_buf, xtext->thin_gc, x, y, x, y + height);
+		} else
+		{
+			if (xtext->moving_separator)
+			{
+				gdk_draw_line (xtext->draw_buf, light, x - 1, y, x - 1, y + height);
+				gdk_draw_line (xtext->draw_buf, dark, x, y, x, y + height);
+			} else
+			{
+				gdk_draw_line (xtext->draw_buf, dark, x - 1, y, x - 1, y + height);
+				gdk_draw_line (xtext->draw_buf, light, x, y, x, y + height);
+			}
+		}
+	}
+}
+
+static void
+gtk_xtext_draw_marker (GtkXText * xtext, textentry * ent, int y)
+{
+	int x, width, render_y;
+
+	if (!xtext->marker) return;
+
+	if (xtext->buffer->marker_pos == ent)
+	{
+		render_y = y + xtext->font->descent;
+	}
+	else if (xtext->buffer->marker_pos == ent->next && ent->next != NULL)
+	{
+		render_y = y + xtext->font->descent + xtext->fontsize * ent->lines_taken;
+	}
+	else return;
+
+	x = 0;
+	width = GTK_WIDGET (xtext)->allocation.width;
+
+	gdk_draw_line (xtext->draw_buf, xtext->marker_gc, x, render_y, x + width, render_y);
+
+#if GTK_CHECK_VERSION(2,4,0)
+	if (gtk_window_has_toplevel_focus (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (xtext)))))
+#else
+	if (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (xtext)))->has_focus)
+#endif
+	{
+		xtext->buffer->marker_seen = TRUE;
+	}
+}
+
+#ifdef USE_SHM
+static int
+have_shm_pixmaps(Display *dpy)
+{
+	int major, minor;
+	static int checked = 0;
+	static int have = FALSE;
+
+	if (!checked)
+	{
+		XShmQueryVersion (dpy, &major, &minor, &have);
+		checked = 1;
+	}
+
+	return have;
+}
+#endif
+
+static void
+gtk_xtext_paint (GtkWidget *widget, GdkRectangle *area)
+{
+	GtkXText *xtext = GTK_XTEXT (widget);
+	textentry *ent_start, *ent_end;
+	int x, y;
+
+#if defined(USE_XLIB) || defined(WIN32)
+	if (xtext->transparent)
+	{
+		gdk_window_get_origin (widget->window, &x, &y);
+		/* update transparency only if it moved */
+		if (xtext->last_win_x != x || xtext->last_win_y != y)
+		{
+			xtext->last_win_x = x;
+			xtext->last_win_y = y;
+#ifndef WIN32
+#ifdef USE_SHM
+			if (xtext->shaded && !have_shm_pixmaps(GDK_WINDOW_XDISPLAY (xtext->draw_buf)))
+#else
+			if (xtext->shaded)
+#endif
+			{
+				xtext->recycle = TRUE;
+				gtk_xtext_load_trans (xtext);
+				xtext->recycle = FALSE;
+			} else
+#endif
+			{
+				gtk_xtext_free_trans (xtext);
+				gtk_xtext_load_trans (xtext);
+			}
+		}
+	}
+#endif
+
+	if (area->x == 0 && area->y == 0 &&
+		 area->height == widget->allocation.height &&
+		 area->width == widget->allocation.width)
+	{
+		dontscroll (xtext->buffer);	/* force scrolling off */
+		gtk_xtext_render_page (xtext);
+		return;
+	}
+
+	ent_start = gtk_xtext_find_char (xtext, area->x, area->y, NULL, NULL);
+	if (!ent_start)
+	{
+		xtext_draw_bg (xtext, area->x, area->y, area->width, area->height);
+		goto xit;
+	}
+	ent_end = gtk_xtext_find_char (xtext, area->x + area->width,
+											 area->y + area->height, NULL, NULL);
+	if (!ent_end)
+		ent_end = xtext->buffer->text_last;
+
+	/* can't set a clip here, because fgc/bgc are used to draw the DB too */
+/*	backend_set_clip (xtext, area);*/
+	xtext->clip_x = area->x;
+	xtext->clip_x2 = area->x + area->width;
+	xtext->clip_y = area->y;
+	xtext->clip_y2 = area->y + area->height;
+
+	/* y is the last pixel y location it rendered text at */
+	y = gtk_xtext_render_ents (xtext, ent_start, ent_end);
+
+	if (y && y < widget->allocation.height && !ent_end->next)
+	{
+		GdkRectangle rect;
+
+		rect.x = 0;
+		rect.y = y;
+		rect.width = widget->allocation.width;
+		rect.height = widget->allocation.height - y;
+
+		/* fill any space below the last line that also intersects with
+			the exposure rectangle */
+		if (gdk_rectangle_intersect (area, &rect, &rect))
+		{
+			xtext_draw_bg (xtext, rect.x, rect.y, rect.width, rect.height);
+		}
+	}
+
+	/*backend_clear_clip (xtext);*/
+	xtext->clip_x = 0;
+	xtext->clip_x2 = 1000000;
+	xtext->clip_y = 0;
+	xtext->clip_y2 = 1000000;
+
+xit:
+	x = xtext->buffer->indent - ((xtext->space_width + 1) / 2);
+	if (area->x <= x)
+		gtk_xtext_draw_sep (xtext, -1);
+}
+
+static gboolean
+gtk_xtext_expose (GtkWidget * widget, GdkEventExpose * event)
+{
+	gtk_xtext_paint (widget, &event->area);
+	return FALSE;
+}
+
+/* render a selection that has extended or contracted upward */
+
+static void
+gtk_xtext_selection_up (GtkXText *xtext, textentry *start, textentry *end,
+								int start_offset)
+{
+	/* render all the complete lines */
+	if (start->next == end)
+		gtk_xtext_render_ents (xtext, end, NULL);
+	else
+		gtk_xtext_render_ents (xtext, start->next, end);
+
+	/* now the incomplete upper line */
+	if (start == xtext->buffer->last_ent_start)
+		xtext->jump_in_offset = xtext->buffer->last_offset_start;
+	else
+		xtext->jump_in_offset = start_offset;
+	gtk_xtext_render_ents (xtext, start, NULL);
+	xtext->jump_in_offset = 0;
+}
+
+/* render a selection that has extended or contracted downward */
+
+static void
+gtk_xtext_selection_down (GtkXText *xtext, textentry *start, textentry *end,
+								  int end_offset)
+{
+	/* render all the complete lines */
+	if (end->prev == start)
+		gtk_xtext_render_ents (xtext, start, NULL);
+	else
+		gtk_xtext_render_ents (xtext, start, end->prev);
+
+	/* now the incomplete bottom line */
+	if (end == xtext->buffer->last_ent_end)
+		xtext->jump_out_offset = xtext->buffer->last_offset_end;
+	else
+		xtext->jump_out_offset = end_offset;
+	gtk_xtext_render_ents (xtext, end, NULL);
+	xtext->jump_out_offset = 0;
+}
+
+static void
+gtk_xtext_selection_render (GtkXText *xtext,
+									 textentry *start_ent, int start_offset,
+									 textentry *end_ent, int end_offset)
+{
+	textentry *ent;
+	int start, end;
+
+	xtext->skip_border_fills = TRUE;
+	xtext->skip_stamp = TRUE;
+
+	/* force an optimized render if there was no previous selection */
+	if (xtext->buffer->last_ent_start == NULL && start_ent == end_ent)
+	{
+		xtext->buffer->last_offset_start = start_offset;
+		xtext->buffer->last_offset_end = end_offset;
+		goto lamejump;
+	}
+
+	/* mark changed within 1 ent only? */
+	if (xtext->buffer->last_ent_start == start_ent &&
+		 xtext->buffer->last_ent_end == end_ent)
+	{
+		/* when only 1 end of the selection is changed, we can really
+			save on rendering */
+		if (xtext->buffer->last_offset_start == start_offset ||
+			 xtext->buffer->last_offset_end == end_offset)
+		{
+lamejump:
+			ent = end_ent;
+			/* figure out where to start and end the rendering */
+			if (end_offset > xtext->buffer->last_offset_end)
+			{
+				end = end_offset;
+				start = xtext->buffer->last_offset_end;
+			} else if (end_offset < xtext->buffer->last_offset_end)
+			{
+				end = xtext->buffer->last_offset_end;
+				start = end_offset;
+			} else if (start_offset < xtext->buffer->last_offset_start)
+			{
+				end = xtext->buffer->last_offset_start;
+				start = start_offset;
+				ent = start_ent;
+			} else if (start_offset > xtext->buffer->last_offset_start)
+			{
+				end = start_offset;
+				start = xtext->buffer->last_offset_start;
+				ent = start_ent;
+			} else
+			{	/* WORD selects end up here */
+				end = end_offset;
+				start = start_offset;
+			}
+		} else
+		{
+			/* LINE selects end up here */
+			/* so which ent actually changed? */
+			ent = start_ent;
+			if (xtext->buffer->last_offset_start == start_offset)
+				ent = end_ent;
+
+			end = MAX (xtext->buffer->last_offset_end, end_offset);
+			start = MIN (xtext->buffer->last_offset_start, start_offset);
+		}
+
+		xtext->jump_out_offset = end;
+		xtext->jump_in_offset = start;
+		gtk_xtext_render_ents (xtext, ent, NULL);
+		xtext->jump_out_offset = 0;
+		xtext->jump_in_offset = 0;
+	}
+	/* marking downward? */
+	else if (xtext->buffer->last_ent_start == start_ent &&
+				xtext->buffer->last_offset_start == start_offset)
+	{
+		/* find the range that covers both old and new selection */
+		ent = start_ent;
+		while (ent)
+		{
+			if (ent == xtext->buffer->last_ent_end)
+			{
+				gtk_xtext_selection_down (xtext, ent, end_ent, end_offset);
+				/*gtk_xtext_render_ents (xtext, ent, end_ent);*/
+				break;
+			}
+			if (ent == end_ent)
+			{
+				gtk_xtext_selection_down (xtext, ent, xtext->buffer->last_ent_end, end_offset);
+				/*gtk_xtext_render_ents (xtext, ent, xtext->buffer->last_ent_end);*/
+				break;
+			}
+			ent = ent->next;
+		}
+	}
+	/* marking upward? */
+	else if (xtext->buffer->last_ent_end == end_ent &&
+				xtext->buffer->last_offset_end == end_offset)
+	{
+		ent = end_ent;
+		while (ent)
+		{
+			if (ent == start_ent)
+			{
+				gtk_xtext_selection_up (xtext, xtext->buffer->last_ent_start, ent, start_offset);
+				/*gtk_xtext_render_ents (xtext, xtext->buffer->last_ent_start, ent);*/
+				break;
+			}
+			if (ent == xtext->buffer->last_ent_start)
+			{
+				gtk_xtext_selection_up (xtext, start_ent, ent, start_offset);
+				/*gtk_xtext_render_ents (xtext, start_ent, ent);*/
+				break;
+			}
+			ent = ent->prev;
+		}
+	}
+	else	/* cross-over mark (stretched or shrunk at both ends) */
+	{
+		/* unrender the old mark */
+		gtk_xtext_render_ents (xtext, xtext->buffer->last_ent_start, xtext->buffer->last_ent_end);
+		/* now render the new mark, but skip overlaps */
+		if (start_ent == xtext->buffer->last_ent_start)
+		{
+			/* if the new mark is a sub-set of the old, do nothing */
+			if (start_ent != end_ent)
+				gtk_xtext_render_ents (xtext, start_ent->next, end_ent);
+		} else if (end_ent == xtext->buffer->last_ent_end)
+		{
+			/* if the new mark is a sub-set of the old, do nothing */
+			if (start_ent != end_ent)
+				gtk_xtext_render_ents (xtext, start_ent, end_ent->prev);
+		} else
+			gtk_xtext_render_ents (xtext, start_ent, end_ent);
+	}
+
+	xtext->buffer->last_ent_start = start_ent;
+	xtext->buffer->last_ent_end = end_ent;
+	xtext->buffer->last_offset_start = start_offset;
+	xtext->buffer->last_offset_end = end_offset;
+
+	xtext->skip_border_fills = FALSE;
+	xtext->skip_stamp = FALSE;
+}
+
+static void
+gtk_xtext_selection_draw (GtkXText * xtext, GdkEventMotion * event, gboolean render)
+{
+	textentry *ent;
+	textentry *ent_end;
+	textentry *ent_start;
+	int offset_start;
+	int offset_end;
+	int low_x;
+	int low_y;
+	int high_x;
+	int high_y;
+	int tmp;
+
+	if (xtext->select_start_y > xtext->select_end_y)
+	{
+		low_x = xtext->select_end_x;
+		low_y = xtext->select_end_y;
+		high_x = xtext->select_start_x;
+		high_y = xtext->select_start_y;
+	} else
+	{
+		low_x = xtext->select_start_x;
+		low_y = xtext->select_start_y;
+		high_x = xtext->select_end_x;
+		high_y = xtext->select_end_y;
+	}
+
+	ent_start = gtk_xtext_find_char (xtext, low_x, low_y, &offset_start, &tmp);
+	if (!ent_start)
+	{
+		if (xtext->adj->value != xtext->buffer->old_value)
+			gtk_xtext_render_page (xtext);
+		return;
+	}
+
+	ent_end = gtk_xtext_find_char (xtext, high_x, high_y, &offset_end, &tmp);
+	if (!ent_end)
+	{
+		ent_end = xtext->buffer->text_last;
+		if (!ent_end)
+		{
+			if (xtext->adj->value != xtext->buffer->old_value)
+				gtk_xtext_render_page (xtext);
+			return;
+		}
+		offset_end = ent_end->str_len;
+	}
+
+	/* marking less than a complete line? */
+	/* make sure "start" is smaller than "end" (swap them if need be) */
+	if (ent_start == ent_end && offset_start > offset_end)
+	{
+		tmp = offset_start;
+		offset_start = offset_end;
+		offset_end = tmp;
+	}
+
+	/* has the selection changed? Dont render unless necessary */
+	if (xtext->buffer->last_ent_start == ent_start &&
+		 xtext->buffer->last_ent_end == ent_end &&
+		 xtext->buffer->last_offset_start == offset_start &&
+		 xtext->buffer->last_offset_end == offset_end)
+		return;
+
+	/* set all the old mark_ fields to -1 */
+	gtk_xtext_selection_clear (xtext->buffer);
+
+	ent_start->mark_start = offset_start;
+	ent_start->mark_end = offset_end;
+
+	if (ent_start != ent_end)
+	{
+		ent_start->mark_end = ent_start->str_len;
+		if (offset_end != 0)
+		{
+			ent_end->mark_start = 0;
+			ent_end->mark_end = offset_end;
+		}
+
+		/* set all the mark_ fields of the ents within the selection */
+		ent = ent_start->next;
+		while (ent && ent != ent_end)
+		{
+			ent->mark_start = 0;
+			ent->mark_end = ent->str_len;
+			ent = ent->next;
+		}
+	}
+
+	if (render)
+		gtk_xtext_selection_render (xtext, ent_start, offset_start, ent_end, offset_end);
+}
+
+static gint
+gtk_xtext_scrolldown_timeout (GtkXText * xtext)
+{
+	int p_y, win_height;
+
+	gdk_window_get_pointer (GTK_WIDGET (xtext)->window, 0, &p_y, 0);
+	gdk_drawable_get_size (GTK_WIDGET (xtext)->window, 0, &win_height);
+
+	if (p_y > win_height &&
+		 xtext->adj->value < (xtext->adj->upper - xtext->adj->page_size))
+	{
+		xtext->adj->value++;
+		gtk_adjustment_changed (xtext->adj);
+		gtk_xtext_render_page (xtext);
+		return 1;
+	}
+
+	xtext->scroll_tag = 0;
+	return 0;
+}
+
+static gint
+gtk_xtext_scrollup_timeout (GtkXText * xtext)
+{
+	int p_y;
+
+	gdk_window_get_pointer (GTK_WIDGET (xtext)->window, 0, &p_y, 0);
+
+	if (p_y < 0 && xtext->adj->value > 0.0)
+	{
+		xtext->adj->value--;
+		gtk_adjustment_changed (xtext->adj);
+		gtk_xtext_render_page (xtext);
+		return 1;
+	}
+
+	xtext->scroll_tag = 0;
+	return 0;
+}
+
+static void
+gtk_xtext_selection_update (GtkXText * xtext, GdkEventMotion * event, int p_y, gboolean render)
+{
+	int win_height;
+	int moved;
+
+	gdk_drawable_get_size (GTK_WIDGET (xtext)->window, 0, &win_height);
+
+	/* selecting past top of window, scroll up! */
+	if (p_y < 0 && xtext->adj->value >= 0)
+	{
+		if (!xtext->scroll_tag)
+			xtext->scroll_tag = g_timeout_add (100,
+															(GSourceFunc)
+														 	gtk_xtext_scrollup_timeout,
+															xtext);
+		return;
+	}
+
+	/* selecting past bottom of window, scroll down! */
+	if (p_y > win_height &&
+		 xtext->adj->value < (xtext->adj->upper - xtext->adj->page_size))
+	{
+		if (!xtext->scroll_tag)
+			xtext->scroll_tag = g_timeout_add (100,
+															(GSourceFunc)
+															gtk_xtext_scrolldown_timeout,
+															xtext);
+		return;
+	}
+
+	moved = (int)xtext->adj->value - xtext->select_start_adj;
+	xtext->select_start_y -= (moved * xtext->fontsize);
+	xtext->select_start_adj = xtext->adj->value;
+	gtk_xtext_selection_draw (xtext, event, render);
+}
+
+static char *
+gtk_xtext_get_word (GtkXText * xtext, int x, int y, textentry ** ret_ent,
+						  int *ret_off, int *ret_len)
+{
+	textentry *ent;
+	int offset;
+	unsigned char *str;
+	unsigned char *word;
+	int len;
+	int out_of_bounds = 0;
+
+	ent = gtk_xtext_find_char (xtext, x, y, &offset, &out_of_bounds);
+	if (!ent)
+		return 0;
+
+	if (out_of_bounds)
+		return 0;
+
+	if (offset == ent->str_len)
+		return 0;
+
+	if (offset < 1)
+		return 0;
+
+	/*offset--;*/	/* FIXME: not all chars are 1 byte */
+
+	str = ent->str + offset;
+
+	while (!is_del (*str) && str != ent->str)
+		str--;
+	word = str + 1;
+
+	len = 0;
+	str = word;
+	while (!is_del (*str) && len != ent->str_len)
+	{
+		str++;
+		len++;
+	}
+
+	if (len > 0 && word[len-1]=='.')
+	{
+		len--;
+		str--;
+	}
+
+	if (ret_ent)
+		*ret_ent = ent;
+	if (ret_off)
+		*ret_off = word - ent->str;
+	if (ret_len)
+		*ret_len = str - word;
+
+	return gtk_xtext_strip_color (word, len, xtext->scratch_buffer, NULL, NULL, FALSE);
+}
+
+#ifdef MOTION_MONITOR
+
+static void
+gtk_xtext_unrender_hilight (GtkXText *xtext)
+{
+	xtext->render_hilights_only = TRUE;
+	xtext->skip_border_fills = TRUE;
+	xtext->skip_stamp = TRUE;
+	xtext->un_hilight = TRUE;
+
+	gtk_xtext_render_ents (xtext, xtext->hilight_ent, NULL);
+
+	xtext->render_hilights_only = FALSE;
+	xtext->skip_border_fills = FALSE;
+	xtext->skip_stamp = FALSE;
+	xtext->un_hilight = FALSE;
+}
+
+static gboolean
+gtk_xtext_leave_notify (GtkWidget * widget, GdkEventCrossing * event)
+{
+	GtkXText *xtext = GTK_XTEXT (widget);
+
+	if (xtext->cursor_hand)
+	{
+		gtk_xtext_unrender_hilight (xtext);
+		xtext->hilight_start = -1;
+		xtext->hilight_end = -1;
+		xtext->cursor_hand = FALSE;
+		gdk_window_set_cursor (widget->window, 0);
+		xtext->hilight_ent = NULL;
+	}
+
+	if (xtext->cursor_resize)
+	{
+		gtk_xtext_unrender_hilight (xtext);
+		xtext->hilight_start = -1;
+		xtext->hilight_end = -1;
+		xtext->cursor_resize = FALSE;
+		gdk_window_set_cursor (widget->window, 0);
+		xtext->hilight_ent = NULL;
+	}
+
+	return FALSE;
+}
+
+#endif
+
+/* check if we should mark time stamps, and if a redraw is needed */
+
+static gboolean
+gtk_xtext_check_mark_stamp (GtkXText *xtext, GdkModifierType mask)
+{
+	gboolean redraw = FALSE;
+
+	if ((mask & GDK_SHIFT_MASK))
+	{
+		if (!xtext->mark_stamp)
+		{
+			redraw = TRUE;	/* must redraw all */
+			xtext->mark_stamp = TRUE;
+		}
+	} else
+	{
+		if (xtext->mark_stamp)
+		{
+			redraw = TRUE;	/* must redraw all */
+			xtext->mark_stamp = FALSE;
+		}
+	}
+	return redraw;
+}
+
+static gboolean
+gtk_xtext_motion_notify (GtkWidget * widget, GdkEventMotion * event)
+{
+	GtkXText *xtext = GTK_XTEXT (widget);
+	GdkModifierType mask;
+	int redraw, tmp, x, y, offset, len, line_x;
+	unsigned char *word;
+	textentry *word_ent;
+
+	gdk_window_get_pointer (widget->window, &x, &y, &mask);
+
+	if (xtext->moving_separator)
+	{
+		if (x < (3 * widget->allocation.width) / 5 && x > 15)
+		{
+			tmp = xtext->buffer->indent;
+			xtext->buffer->indent = x;
+			gtk_xtext_fix_indent (xtext->buffer);
+			if (tmp != xtext->buffer->indent)
+			{
+				gtk_xtext_recalc_widths (xtext->buffer, FALSE);
+				if (xtext->buffer->scrollbar_down)
+					gtk_adjustment_set_value (xtext->adj, xtext->adj->upper -
+													  xtext->adj->page_size);
+				if (!xtext->io_tag)
+					xtext->io_tag = g_timeout_add (REFRESH_TIMEOUT,
+																(GSourceFunc)
+																gtk_xtext_adjustment_timeout,
+																xtext);
+			}
+		}
+		return FALSE;
+	}
+
+	if (xtext->button_down)
+	{
+		redraw = gtk_xtext_check_mark_stamp (xtext, mask);
+		gtk_grab_add (widget);
+		/*gdk_pointer_grab (widget->window, TRUE,
+									GDK_BUTTON_RELEASE_MASK |
+									GDK_BUTTON_MOTION_MASK, NULL, NULL, 0);*/
+		xtext->select_end_x = x;
+		xtext->select_end_y = y;
+		gtk_xtext_selection_update (xtext, event, y, !redraw);
+		xtext->hilighting = TRUE;
+
+		/* user has pressed or released SHIFT, must redraw entire selection */
+		if (redraw)
+		{
+			xtext->force_stamp = TRUE;
+			gtk_xtext_render_ents (xtext, xtext->buffer->last_ent_start,
+										  xtext->buffer->last_ent_end);
+			xtext->force_stamp = FALSE;
+		}
+		return FALSE;
+	}
+#ifdef MOTION_MONITOR
+
+	if (xtext->separator && xtext->buffer->indent)
+	{
+		line_x = xtext->buffer->indent - ((xtext->space_width + 1) / 2);
+		if (line_x == x || line_x == x + 1 || line_x == x - 1)
+		{
+			if (!xtext->cursor_resize)
+			{
+				gdk_window_set_cursor (GTK_WIDGET (xtext)->window,
+										  		xtext->resize_cursor);
+				xtext->cursor_resize = TRUE;
+			}
+			return FALSE;
+		}
+	}
+
+	if (xtext->urlcheck_function == NULL)
+		return FALSE;
+
+	word = gtk_xtext_get_word (xtext, x, y, &word_ent, &offset, &len);
+	if (word)
+	{
+		if (xtext->urlcheck_function (GTK_WIDGET (xtext), word, len) > 0)
+		{
+			if (!xtext->cursor_hand ||
+				 xtext->hilight_ent != word_ent ||
+				 xtext->hilight_start != offset ||
+				 xtext->hilight_end != offset + len)
+			{
+				if (!xtext->cursor_hand)
+				{
+					gdk_window_set_cursor (GTK_WIDGET (xtext)->window,
+											  		xtext->hand_cursor);
+					xtext->cursor_hand = TRUE;
+				}
+
+				/* un-render the old hilight */
+				if (xtext->hilight_ent)
+					gtk_xtext_unrender_hilight (xtext);
+
+				xtext->hilight_ent = word_ent;
+				xtext->hilight_start = offset;
+				xtext->hilight_end = offset + len;
+
+				xtext->skip_border_fills = TRUE;
+				xtext->render_hilights_only = TRUE;
+				xtext->skip_stamp = TRUE;
+
+				gtk_xtext_render_ents (xtext, word_ent, NULL);
+
+				xtext->skip_border_fills = FALSE;
+				xtext->render_hilights_only = FALSE;
+				xtext->skip_stamp = FALSE;
+			}
+			return FALSE;
+		}
+	}
+
+	gtk_xtext_leave_notify (widget, NULL);
+
+#endif
+
+	return FALSE;
+}
+
+static void
+gtk_xtext_set_clip_owner (GtkWidget * xtext, GdkEventButton * event)
+{
+	char *str;
+	int len;
+
+	if (GTK_XTEXT (xtext)->selection_buffer &&
+		GTK_XTEXT (xtext)->selection_buffer != GTK_XTEXT (xtext)->buffer)
+		gtk_xtext_selection_clear (GTK_XTEXT (xtext)->selection_buffer);
+
+	GTK_XTEXT (xtext)->selection_buffer = GTK_XTEXT (xtext)->buffer;
+
+	str = gtk_xtext_selection_get_text (GTK_XTEXT (xtext), &len);
+	if (str)
+	{
+#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0)
+		gtk_clipboard_set_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD),
+										str, len);
+#else
+		gtk_clipboard_set_text (gtk_widget_get_clipboard (xtext, GDK_SELECTION_CLIPBOARD),
+										str, len);
+#endif
+		free (str);
+	}
+
+	gtk_selection_owner_set (xtext, GDK_SELECTION_PRIMARY, event->time);
+}
+
+static void
+gtk_xtext_unselect (GtkXText *xtext)
+{
+	xtext_buffer *buf = xtext->buffer;
+
+	xtext->skip_border_fills = TRUE;
+	xtext->skip_stamp = TRUE;
+
+	xtext->jump_in_offset = buf->last_ent_start->mark_start;
+	/* just a single ent was marked? */
+	if (buf->last_ent_start == buf->last_ent_end)
+	{
+		xtext->jump_out_offset = buf->last_ent_start->mark_end;
+		buf->last_ent_end = NULL;
+	}
+
+	gtk_xtext_selection_clear (xtext->buffer);
+
+	/* FIXME: use jump_out on multi-line selects too! */
+	gtk_xtext_render_ents (xtext, buf->last_ent_start, buf->last_ent_end);
+
+	xtext->jump_in_offset = 0;
+	xtext->jump_out_offset = 0;
+
+	xtext->skip_border_fills = FALSE;
+	xtext->skip_stamp = FALSE;
+
+	xtext->buffer->last_ent_start = NULL;
+	xtext->buffer->last_ent_end = NULL;
+}
+
+static gboolean
+gtk_xtext_button_release (GtkWidget * widget, GdkEventButton * event)
+{
+	GtkXText *xtext = GTK_XTEXT (widget);
+	unsigned char *word;
+	int old;
+
+	if (xtext->moving_separator)
+	{
+		xtext->moving_separator = FALSE;
+		old = xtext->buffer->indent;
+		if (event->x < (4 * widget->allocation.width) / 5 && event->x > 15)
+			xtext->buffer->indent = event->x;
+		gtk_xtext_fix_indent (xtext->buffer);
+		if (xtext->buffer->indent != old)
+		{
+			gtk_xtext_recalc_widths (xtext->buffer, FALSE);
+			gtk_xtext_adjustment_set (xtext->buffer, TRUE);
+			gtk_xtext_render_page (xtext);
+		} else
+			gtk_xtext_draw_sep (xtext, -1);
+		return FALSE;
+	}
+
+	if (xtext->word_or_line_select)
+	{
+		xtext->word_or_line_select = FALSE;
+		xtext->button_down = FALSE;
+		return FALSE;
+	}
+
+	if (event->button == 1)
+	{
+		xtext->button_down = FALSE;
+
+		gtk_grab_remove (widget);
+		/*gdk_pointer_ungrab (0);*/
+
+		/* got a new selection? */
+		if (xtext->buffer->last_ent_start)
+		{
+			xtext->color_paste = FALSE;
+			if (event->state & GDK_CONTROL_MASK)
+				xtext->color_paste = TRUE;
+			gtk_xtext_set_clip_owner (GTK_WIDGET (xtext), event);
+		}
+
+		if (xtext->select_start_x == event->x &&
+			 xtext->select_start_y == event->y &&
+			 xtext->buffer->last_ent_start)
+		{
+			gtk_xtext_unselect (xtext);
+			xtext->mark_stamp = FALSE;
+			return FALSE;
+		}
+
+		if (!xtext->hilighting)
+		{
+			word = gtk_xtext_get_word (xtext, event->x, event->y, 0, 0, 0);
+			g_signal_emit (G_OBJECT (xtext), xtext_signals[WORD_CLICK], 0, word ? word : NULL, event);
+		} else
+		{
+			xtext->hilighting = FALSE;
+		}
+	}
+
+
+	return FALSE;
+}
+
+static gboolean
+gtk_xtext_button_press (GtkWidget * widget, GdkEventButton * event)
+{
+	GtkXText *xtext = GTK_XTEXT (widget);
+	GdkModifierType mask;
+	textentry *ent;
+	unsigned char *word;
+	int line_x, x, y, offset, len;
+
+	gdk_window_get_pointer (widget->window, &x, &y, &mask);
+
+	if (event->button == 3 || event->button == 2) /* right/middle click */
+	{
+		word = gtk_xtext_get_word (xtext, x, y, 0, 0, 0);
+		if (word)
+		{
+			g_signal_emit (G_OBJECT (xtext), xtext_signals[WORD_CLICK], 0,
+								word, event);
+		} else
+			g_signal_emit (G_OBJECT (xtext), xtext_signals[WORD_CLICK], 0,
+								"", event);
+		return FALSE;
+	}
+
+	if (event->button != 1)		  /* we only want left button */
+		return FALSE;
+
+	if (event->type == GDK_2BUTTON_PRESS)	/* WORD select */
+	{
+		gtk_xtext_check_mark_stamp (xtext, mask);
+		if (gtk_xtext_get_word (xtext, x, y, &ent, &offset, &len))
+		{
+			if (len == 0)
+				return FALSE;
+			gtk_xtext_selection_clear (xtext->buffer);
+			ent->mark_start = offset;
+			ent->mark_end = offset + len;
+			gtk_xtext_selection_render (xtext, ent, offset, ent, offset + len);
+			xtext->word_or_line_select = TRUE;
+			gtk_xtext_set_clip_owner (GTK_WIDGET (xtext), event);
+		}
+
+		return FALSE;
+	}
+
+	if (event->type == GDK_3BUTTON_PRESS)	/* LINE select */
+	{
+		gtk_xtext_check_mark_stamp (xtext, mask);
+		if (gtk_xtext_get_word (xtext, x, y, &ent, 0, 0))
+		{
+			gtk_xtext_selection_clear (xtext->buffer);
+			ent->mark_start = 0;
+			ent->mark_end = ent->str_len;
+			gtk_xtext_selection_render (xtext, ent, 0, ent, ent->str_len);
+			xtext->word_or_line_select = TRUE;
+			gtk_xtext_set_clip_owner (GTK_WIDGET (xtext), event);
+		}
+
+		return FALSE;
+	}
+
+	/* check if it was a separator-bar click */
+	if (xtext->separator && xtext->buffer->indent)
+	{
+		line_x = xtext->buffer->indent - ((xtext->space_width + 1) / 2);
+		if (line_x == x || line_x == x + 1 || line_x == x - 1)
+		{
+			xtext->moving_separator = TRUE;
+			/* draw the separator line */
+			gtk_xtext_draw_sep (xtext, -1);
+			return FALSE;
+		}
+	}
+
+	xtext->button_down = TRUE;
+	xtext->select_start_x = x;
+	xtext->select_start_y = y;
+	xtext->select_start_adj = xtext->adj->value;
+
+	return FALSE;
+}
+
+/* another program has claimed the selection */
+
+static gboolean
+gtk_xtext_selection_kill (GtkXText *xtext, GdkEventSelection *event)
+{
+#ifndef WIN32
+	if (xtext->buffer->last_ent_start)
+		gtk_xtext_unselect (xtext);
+#endif
+	return TRUE;
+}
+
+static char *
+gtk_xtext_selection_get_text (GtkXText *xtext, int *len_ret)
+{
+	textentry *ent;
+	char *txt;
+	char *pos;
+	char *stripped;
+	int len;
+	int first = TRUE;
+	xtext_buffer *buf;
+
+	buf = xtext->selection_buffer;
+	if (!buf)
+		return NULL;
+
+	/* first find out how much we need to malloc ... */
+	len = 0;
+	ent = buf->last_ent_start;
+	while (ent)
+	{
+		if (ent->mark_start != -1)
+		{
+			/* include timestamp? */
+			if (ent->mark_start == 0 && xtext->mark_stamp)
+			{
+				char *time_str;
+				int stamp_size = xtext_get_stamp_str (ent->stamp, &time_str);
+				g_free (time_str);
+				len += stamp_size;
+			}
+
+			if (ent->mark_end - ent->mark_start > 0)
+				len += (ent->mark_end - ent->mark_start) + 1;
+			else
+				len++;
+		}
+		if (ent == buf->last_ent_end)
+			break;
+		ent = ent->next;
+	}
+
+	if (len < 1)
+		return NULL;
+
+	/* now allocate mem and copy buffer */
+	pos = txt = malloc (len);
+	ent = buf->last_ent_start;
+	while (ent)
+	{
+		if (ent->mark_start != -1)
+		{
+			if (!first)
+			{
+				*pos = '\n';
+				pos++;
+			}
+			first = FALSE;
+			if (ent->mark_end - ent->mark_start > 0)
+			{
+				/* include timestamp? */
+				if (ent->mark_start == 0 && xtext->mark_stamp)
+				{
+					char *time_str;
+					int stamp_size = xtext_get_stamp_str (ent->stamp, &time_str);
+					memcpy (pos, time_str, stamp_size);
+					g_free (time_str);
+					pos += stamp_size;
+				}
+
+				memcpy (pos, ent->str + ent->mark_start,
+						  ent->mark_end - ent->mark_start);
+				pos += ent->mark_end - ent->mark_start;
+			}
+		}
+		if (ent == buf->last_ent_end)
+			break;
+		ent = ent->next;
+	}
+	*pos = 0;
+
+	if (xtext->color_paste)
+	{
+		/*stripped = gtk_xtext_conv_color (txt, strlen (txt), &len);*/
+		stripped = txt;
+		len = strlen (txt);
+	} else
+	{
+		stripped = gtk_xtext_strip_color (txt, strlen (txt), NULL, &len, 0, FALSE);
+		free (txt);
+	}
+
+	*len_ret = len;
+	return stripped;
+}
+
+/* another program is asking for our selection */
+
+static void
+gtk_xtext_selection_get (GtkWidget * widget,
+								 GtkSelectionData * selection_data_ptr,
+								 guint info, guint time)
+{
+	GtkXText *xtext = GTK_XTEXT (widget);
+	char *stripped;
+	guchar *new_text;
+	int len;
+	gsize glen;
+
+	stripped = gtk_xtext_selection_get_text (xtext, &len);
+	if (!stripped)
+		return;
+
+	switch (info)
+	{
+	case TARGET_UTF8_STRING:
+		/* it's already in utf8 */
+		gtk_selection_data_set_text (selection_data_ptr, stripped, len);
+		break;
+	case TARGET_TEXT:
+	case TARGET_COMPOUND_TEXT:
+		{
+			GdkAtom encoding;
+			gint format;
+			gint new_length;
+
+#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0)
+			gdk_string_to_compound_text (
+#else
+			gdk_string_to_compound_text_for_display (
+												gdk_drawable_get_display (widget->window),
+#endif
+												stripped, &encoding, &format, &new_text,
+												&new_length);
+			gtk_selection_data_set (selection_data_ptr, encoding, format,
+											new_text, new_length);
+			gdk_free_compound_text (new_text);
+		}
+		break;
+	default:
+		new_text = g_locale_from_utf8 (stripped, len, NULL, &glen, NULL);
+		gtk_selection_data_set (selection_data_ptr, GDK_SELECTION_TYPE_STRING,
+										8, new_text, glen);
+		g_free (new_text);
+	}
+
+	free (stripped);
+}
+
+static gboolean
+gtk_xtext_scroll (GtkWidget *widget, GdkEventScroll *event)
+{
+	GtkXText *xtext = GTK_XTEXT (widget);
+	gfloat new_value;
+
+	if (event->direction == GDK_SCROLL_UP)		/* mouse wheel pageUp */
+	{
+		new_value = xtext->adj->value - (xtext->adj->page_increment / 10);
+		if (new_value < xtext->adj->lower)
+			new_value = xtext->adj->lower;
+		gtk_adjustment_set_value (xtext->adj, new_value);
+	}
+	else if (event->direction == GDK_SCROLL_DOWN)	/* mouse wheel pageDn */
+	{
+		new_value = xtext->adj->value + (xtext->adj->page_increment / 10);
+		if (new_value > (xtext->adj->upper - xtext->adj->page_size))
+			new_value = xtext->adj->upper - xtext->adj->page_size;
+		gtk_adjustment_set_value (xtext->adj, new_value);
+	}
+
+	return FALSE;
+}
+
+static void
+gtk_xtext_class_init (GtkXTextClass * class)
+{
+	GtkObjectClass *object_class;
+	GtkWidgetClass *widget_class;
+	GtkXTextClass *xtext_class;
+
+	object_class = (GtkObjectClass *) class;
+	widget_class = (GtkWidgetClass *) class;
+	xtext_class = (GtkXTextClass *) class;
+
+	parent_class = gtk_type_class (gtk_widget_get_type ());
+
+	xtext_signals[WORD_CLICK] =
+		g_signal_new ("word_click",
+							G_TYPE_FROM_CLASS (object_class),
+							G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+							G_STRUCT_OFFSET (GtkXTextClass, word_click),
+							NULL, NULL,
+							gtk_marshal_VOID__POINTER_POINTER,
+							G_TYPE_NONE,
+							2, G_TYPE_POINTER, G_TYPE_POINTER);
+	object_class->destroy = gtk_xtext_destroy;
+
+	widget_class->realize = gtk_xtext_realize;
+	widget_class->unrealize = gtk_xtext_unrealize;
+	widget_class->size_request = gtk_xtext_size_request;
+	widget_class->size_allocate = gtk_xtext_size_allocate;
+	widget_class->button_press_event = gtk_xtext_button_press;
+	widget_class->button_release_event = gtk_xtext_button_release;
+	widget_class->motion_notify_event = gtk_xtext_motion_notify;
+	widget_class->selection_clear_event = (void *)gtk_xtext_selection_kill;
+	widget_class->selection_get = gtk_xtext_selection_get;
+	widget_class->expose_event = gtk_xtext_expose;
+	widget_class->scroll_event = gtk_xtext_scroll;
+#ifdef MOTION_MONITOR
+	widget_class->leave_notify_event = gtk_xtext_leave_notify;
+#endif
+
+	xtext_class->word_click = NULL;
+}
+
+GType
+gtk_xtext_get_type (void)
+{
+	static GType xtext_type = 0;
+
+	if (!xtext_type)
+	{
+		static const GTypeInfo xtext_info =
+		{
+			sizeof (GtkXTextClass),
+			NULL,		/* base_init */
+			NULL,		/* base_finalize */
+			(GClassInitFunc) gtk_xtext_class_init,
+			NULL,		/* class_finalize */
+			NULL,		/* class_data */
+			sizeof (GtkXText),
+			0,		/* n_preallocs */
+			(GInstanceInitFunc) gtk_xtext_init,
+		};
+
+		xtext_type = g_type_register_static (GTK_TYPE_WIDGET, "GtkXText",
+														 &xtext_info, 0);
+	}
+
+	return xtext_type;
+}
+
+/* strip MIRC colors and other attribs. */
+
+/* CL: needs to strip hidden when called by gtk_xtext_text_width, but not when copying text */
+
+static unsigned char *
+gtk_xtext_strip_color (unsigned char *text, int len, unsigned char *outbuf,
+							  int *newlen, int *mb_ret, int strip_hidden)
+{
+	int i = 0;
+	int rcol = 0, bgcol = 0;
+	int hidden = FALSE;
+	unsigned char *new_str;
+	int mb = FALSE;
+
+	if (outbuf == NULL)
+		new_str = malloc (len + 2);
+	else
+		new_str = outbuf;
+
+	while (len > 0)
+	{
+		if (*text >= 128)
+			mb = TRUE;
+
+		if (rcol > 0 && (isdigit (*text) || (*text == ',' && isdigit (text[1]) && !bgcol)))
+		{
+			if (text[1] != ',') rcol--;
+			if (*text == ',')
+			{
+				rcol = 2;
+				bgcol = 1;
+			}
+		} else
+		{
+			rcol = bgcol = 0;
+			switch (*text)
+			{
+			case ATTR_COLOR:
+				rcol = 2;
+				break;
+			case ATTR_BEEP:
+			case ATTR_RESET:
+			case ATTR_REVERSE:
+			case ATTR_BOLD:
+			case ATTR_UNDERLINE:
+			case ATTR_ITALICS:
+				break;
+			case ATTR_HIDDEN:
+				hidden = !hidden;
+				break;
+			default:
+				if (!(hidden && strip_hidden))
+					new_str[i++] = *text;
+			}
+		}
+		text++;
+		len--;
+	}
+
+	new_str[i] = 0;
+
+	if (newlen != NULL)
+		*newlen = i;
+
+	if (mb_ret != NULL)
+		*mb_ret = mb;
+
+	return new_str;
+}
+
+/* GeEkMaN: converts mIRC control codes to literal control codes */
+
+static char *
+gtk_xtext_conv_color (unsigned char *text, int len, int *newlen)
+{
+	int i, j = 2;
+	char cchar = 0;
+	char *new_str;
+	int mbl;
+
+	for (i = 0; i < len;)
+	{
+		switch (text[i])
+		{
+		case ATTR_COLOR:
+		case ATTR_RESET:
+		case ATTR_REVERSE:
+		case ATTR_BOLD:
+		case ATTR_UNDERLINE:
+		case ATTR_ITALICS:
+		case ATTR_HIDDEN:
+			j += 3;
+			i++;
+			break;
+		default:
+			mbl = charlen (text + i);
+			j += mbl;
+			i += mbl;
+		}
+	}
+
+	new_str = malloc (j);
+	j = 0;
+
+	for (i = 0; i < len;)
+	{
+		switch (text[i])
+		{
+		case ATTR_COLOR:
+			cchar = 'C';
+			break;
+		case ATTR_RESET:
+			cchar = 'O';
+			break;
+		case ATTR_REVERSE:
+			cchar = 'R';
+			break;
+		case ATTR_BOLD:
+			cchar = 'B';
+			break;
+		case ATTR_UNDERLINE:
+			cchar = 'U';
+			break;
+		case ATTR_ITALICS:
+			cchar = 'I';
+			break;
+		case ATTR_HIDDEN:
+			cchar = 'H';
+			break;
+		case ATTR_BEEP:
+			break;
+		default:
+			mbl = charlen (text + i);
+			if (mbl == 1)
+			{
+				new_str[j] = text[i];
+				j++;
+				i++;
+			} else
+			{
+				/* invalid utf8 safe guard */
+				if (i + mbl > len)
+					mbl = len - i;
+				memcpy (new_str + j, text + i, mbl);
+				j += mbl;
+				i += mbl;
+			}
+		}
+		if (cchar != 0)
+		{
+			new_str[j++] = '%';
+			new_str[j++] = cchar;
+			cchar = 0;
+			i++;
+		}
+	}
+
+	new_str[j] = 0;
+	*newlen = j;
+
+	return new_str;
+}
+
+/* gives width of a string, excluding the mIRC codes */
+
+static int
+gtk_xtext_text_width (GtkXText *xtext, unsigned char *text, int len,
+							 int *mb_ret)
+{
+	unsigned char *new_buf;
+	int new_len, mb;
+
+	new_buf = gtk_xtext_strip_color (text, len, xtext->scratch_buffer,
+												&new_len, &mb, !xtext->ignore_hidden);
+
+	if (mb_ret)
+		*mb_ret = mb;
+
+	return backend_get_text_width (xtext, new_buf, new_len, mb);
+}
+
+/* actually draw text to screen (one run with the same color/attribs) */
+
+static int
+gtk_xtext_render_flush (GtkXText * xtext, int x, int y, unsigned char *str,
+								int len, GdkGC *gc, int is_mb)
+{
+	int str_width, dofill;
+	GdkDrawable *pix = NULL;
+	int dest_x, dest_y;
+
+	if (xtext->dont_render || len < 1 || xtext->hidden)
+		return 0;
+
+	str_width = backend_get_text_width (xtext, str, len, is_mb);
+
+	if (xtext->dont_render2)
+		return str_width;
+
+	/* roll-your-own clipping (avoiding XftDrawString is always good!) */
+	if (x > xtext->clip_x2 || x + str_width < xtext->clip_x)
+		return str_width;
+	if (y - xtext->font->ascent > xtext->clip_y2 || (y - xtext->font->ascent) + xtext->fontsize < xtext->clip_y)
+		return str_width;
+
+	if (xtext->render_hilights_only)
+	{
+		if (!xtext->in_hilight)	/* is it a hilight prefix? */
+			return str_width;
+#ifndef COLOR_HILIGHT
+		if (!xtext->un_hilight)	/* doing a hilight? no need to draw the text */
+			goto dounder;
+#endif
+	}
+
+#ifdef USE_DB
+#ifdef WIN32
+	if (!xtext->transparent)
+#endif
+	{
+		pix = gdk_pixmap_new (xtext->draw_buf, str_width, xtext->fontsize, xtext->depth);
+		if (pix)
+		{
+#ifdef USE_XFT
+			XftDrawChange (xtext->xftdraw, GDK_WINDOW_XWINDOW (pix));
+#endif
+			dest_x = x;
+			dest_y = y - xtext->font->ascent;
+
+			gdk_gc_set_ts_origin (xtext->bgc, xtext->ts_x - x, xtext->ts_y - dest_y);
+
+			x = 0;
+			y = xtext->font->ascent;
+			xtext->draw_buf = pix;
+		}
+	}
+#endif
+
+	dofill = TRUE;
+
+	/* backcolor is always handled by XDrawImageString */
+	if (!xtext->backcolor && xtext->pixmap)
+	{
+	/* draw the background pixmap behind the text - CAUSES FLICKER HERE!! */
+		xtext_draw_bg (xtext, x, y - xtext->font->ascent, str_width,
+							xtext->fontsize);
+		dofill = FALSE;	/* already drawn the background */
+	}
+
+	backend_draw_text (xtext, dofill, gc, x, y, str, len, str_width, is_mb);
+
+#ifdef USE_DB
+	if (pix)
+	{
+		GdkRectangle clip;
+		GdkRectangle dest;
+
+		gdk_gc_set_ts_origin (xtext->bgc, xtext->ts_x, xtext->ts_y);
+		xtext->draw_buf = GTK_WIDGET (xtext)->window;
+#ifdef USE_XFT
+		XftDrawChange (xtext->xftdraw, GDK_WINDOW_XWINDOW (xtext->draw_buf));
+#endif
+#if 0
+		gdk_draw_drawable (xtext->draw_buf, xtext->bgc, pix, 0, 0, dest_x,
+								 dest_y, str_width, xtext->fontsize);
+#else
+		clip.x = xtext->clip_x;
+		clip.y = xtext->clip_y;
+		clip.width = xtext->clip_x2 - xtext->clip_x;
+		clip.height = xtext->clip_y2 - xtext->clip_y;
+
+		dest.x = dest_x;
+		dest.y = dest_y;
+		dest.width = str_width;
+		dest.height = xtext->fontsize;
+
+		if (gdk_rectangle_intersect (&clip, &dest, &dest))
+			/* dump the DB to window, but only within the clip_x/x2/y/y2 */
+			gdk_draw_drawable (xtext->draw_buf, xtext->bgc, pix,
+									 dest.x - dest_x, dest.y - dest_y,
+									 dest.x, dest.y, dest.width, dest.height);
+#endif
+		g_object_unref (pix);
+	}
+#endif
+
+	if (xtext->underline)
+	{
+#ifdef USE_XFT
+		GdkColor col;
+#endif
+
+#ifndef COLOR_HILIGHT
+dounder:
+#endif
+
+#ifdef USE_XFT
+		col.pixel = xtext->xft_fg->pixel;
+		gdk_gc_set_foreground (gc, &col);
+#endif
+		if (pix)
+			y = dest_y + xtext->font->ascent + 1;
+		else
+		{
+			y++;
+			dest_x = x;
+		}
+		/* draw directly to window, it's out of the range of our DB */
+		gdk_draw_line (xtext->draw_buf, gc, dest_x, y, dest_x + str_width - 1, y);
+	}
+
+	return str_width;
+}
+
+static void
+gtk_xtext_reset (GtkXText * xtext, int mark, int attribs)
+{
+	if (attribs)
+	{
+		xtext->underline = FALSE;
+		xtext->bold = FALSE;
+		xtext->italics = FALSE;
+		xtext->hidden = FALSE;
+	}
+	if (!mark)
+	{
+		xtext->backcolor = FALSE;
+		if (xtext->col_fore != XTEXT_FG)
+			xtext_set_fg (xtext, xtext->fgc, XTEXT_FG);
+		if (xtext->col_back != XTEXT_BG)
+			xtext_set_bg (xtext, xtext->fgc, XTEXT_BG);
+	}
+	xtext->col_fore = XTEXT_FG;
+	xtext->col_back = XTEXT_BG;
+	xtext->parsing_color = FALSE;
+	xtext->parsing_backcolor = FALSE;
+	xtext->nc = 0;
+}
+
+/* render a single line, which WONT wrap, and parse mIRC colors */
+
+static int
+gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent,
+							 unsigned char *str, int len, int win_width, int indent,
+							 int line, int left_only, int *x_size_ret)
+{
+	GdkGC *gc;
+	int i = 0, x = indent, j = 0;
+	unsigned char *pstr = str;
+	int col_num, tmp;
+	int offset;
+	int mark = FALSE;
+	int ret = 1;
+
+	xtext->in_hilight = FALSE;
+
+	offset = str - ent->str;
+
+	if (line < 255 && line >= 0)
+		xtext->buffer->grid_offset[line] = offset;
+
+	gc = xtext->fgc;				  /* our foreground GC */
+
+	if (ent->mark_start != -1 &&
+		 ent->mark_start <= i + offset && ent->mark_end > i + offset)
+	{
+		xtext_set_bg (xtext, gc, XTEXT_MARK_BG);
+		xtext_set_fg (xtext, gc, XTEXT_MARK_FG);
+		xtext->backcolor = TRUE;
+		mark = TRUE;
+	}
+#ifdef MOTION_MONITOR
+	if (xtext->hilight_ent == ent &&
+		 xtext->hilight_start <= i + offset && xtext->hilight_end > i + offset)
+	{
+		if (!xtext->un_hilight)
+		{
+#ifdef COLOR_HILIGHT
+			xtext_set_bg (xtext, gc, 2);
+#else
+			xtext->underline = TRUE;
+#endif
+		}
+		xtext->in_hilight = TRUE;
+	}
+#endif
+
+	if (!xtext->skip_border_fills && !xtext->dont_render)
+	{
+		/* draw background to the left of the text */
+		if (str == ent->str && indent > MARGIN && xtext->buffer->time_stamp)
+		{
+			/* don't overwrite the timestamp */
+			if (indent > xtext->stamp_width)
+			{
+				xtext_draw_bg (xtext, xtext->stamp_width, y - xtext->font->ascent,
+									indent - xtext->stamp_width, xtext->fontsize);
+			}
+		} else
+		{
+			/* fill the indent area with background gc */
+			if (indent >= xtext->clip_x)
+			{
+				xtext_draw_bg (xtext, 0, y - xtext->font->ascent,
+									MIN (indent, xtext->clip_x2), xtext->fontsize);
+			}
+		}
+	}
+
+	if (xtext->jump_in_offset > 0 && offset < xtext->jump_in_offset)
+		xtext->dont_render2 = TRUE;
+
+	while (i < len)
+	{
+
+#ifdef MOTION_MONITOR
+		if (xtext->hilight_ent == ent && xtext->hilight_start == (i + offset))
+		{
+			x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+			pstr += j;
+			j = 0;
+			if (!xtext->un_hilight)
+			{
+#ifdef COLOR_HILIGHT
+				xtext_set_bg (xtext, gc, 2);
+#else
+				xtext->underline = TRUE;
+#endif
+			}
+
+			xtext->in_hilight = TRUE;
+		}
+#endif
+
+		if ((xtext->parsing_color && isdigit (str[i]) && xtext->nc < 2) ||
+			 (xtext->parsing_color && str[i] == ',' && isdigit (str[i+1]) && xtext->nc < 3 && !xtext->parsing_backcolor))
+		{
+			pstr++;
+			if (str[i] == ',')
+			{
+				xtext->parsing_backcolor = TRUE;
+				if (xtext->nc)
+				{
+					xtext->num[xtext->nc] = 0;
+					xtext->nc = 0;
+					col_num = atoi (xtext->num);
+					if (col_num == 99)	/* mIRC lameness */
+						col_num = XTEXT_FG;
+					else
+						col_num = col_num % XTEXT_MIRC_COLS;
+					xtext->col_fore = col_num;
+					if (!mark)
+						xtext_set_fg (xtext, gc, col_num);
+				}
+			} else
+			{
+				xtext->num[xtext->nc] = str[i];
+				if (xtext->nc < 7)
+					xtext->nc++;
+			}
+		} else
+		{
+			if (xtext->parsing_color)
+			{
+				xtext->parsing_color = FALSE;
+				if (xtext->nc)
+				{
+					xtext->num[xtext->nc] = 0;
+					xtext->nc = 0;
+					col_num = atoi (xtext->num);
+					if (xtext->parsing_backcolor)
+					{
+						if (col_num == 99)	/* mIRC lameness */
+							col_num = XTEXT_BG;
+						else
+							col_num = col_num % XTEXT_MIRC_COLS;
+						if (col_num == XTEXT_BG)
+							xtext->backcolor = FALSE;
+						else
+							xtext->backcolor = TRUE;
+						if (!mark)
+							xtext_set_bg (xtext, gc, col_num);
+						xtext->col_back = col_num;
+					} else
+					{
+						if (col_num == 99)	/* mIRC lameness */
+							col_num = XTEXT_FG;
+						else
+							col_num = col_num % XTEXT_MIRC_COLS;
+						if (!mark)
+							xtext_set_fg (xtext, gc, col_num);
+						xtext->col_fore = col_num;
+					}
+					xtext->parsing_backcolor = FALSE;
+				} else
+				{
+					/* got a \003<non-digit>... i.e. reset colors */
+					x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+					pstr += j;
+					j = 0;
+					gtk_xtext_reset (xtext, mark, FALSE);
+				}
+			}
+
+			switch (str[i])
+			{
+			case '\n':
+			/*case ATTR_BEEP:*/
+				break;
+			case ATTR_REVERSE:
+				x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+				pstr += j + 1;
+				j = 0;
+				tmp = xtext->col_fore;
+				xtext->col_fore = xtext->col_back;
+				xtext->col_back = tmp;
+				if (!mark)
+				{
+					xtext_set_fg (xtext, gc, xtext->col_fore);
+					xtext_set_bg (xtext, gc, xtext->col_back);
+				}
+				if (xtext->col_back != XTEXT_BG)
+					xtext->backcolor = TRUE;
+				else
+					xtext->backcolor = FALSE;
+				break;
+			case ATTR_BOLD:
+				x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+				xtext->bold = !xtext->bold;
+				pstr += j + 1;
+				j = 0;
+				break;
+			case ATTR_UNDERLINE:
+				x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+				xtext->underline = !xtext->underline;
+				pstr += j + 1;
+				j = 0;
+				break;
+			case ATTR_ITALICS:
+				x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+				xtext->italics = !xtext->italics;
+				pstr += j + 1;
+				j = 0;
+				break;
+			case ATTR_HIDDEN:
+				x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+				xtext->hidden = (!xtext->hidden) & (!xtext->ignore_hidden);
+				pstr += j + 1;
+				j = 0;
+				break;
+			case ATTR_RESET:
+				x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+				pstr += j + 1;
+				j = 0;
+				gtk_xtext_reset (xtext, mark, !xtext->in_hilight);
+				break;
+			case ATTR_COLOR:
+				x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+				xtext->parsing_color = TRUE;
+				pstr += j + 1;
+				j = 0;
+				break;
+			default:
+				tmp = charlen (str + i);
+				/* invalid utf8 safe guard */
+				if (tmp + i > len)
+					tmp = len - i;
+				j += tmp;	/* move to the next utf8 char */
+			}
+		}
+		i += charlen (str + i);	/* move to the next utf8 char */
+		/* invalid utf8 safe guard */
+		if (i > len)
+			i = len;
+
+		/* Separate the left part, the space and the right part
+		   into separate runs, and reset bidi state inbetween.
+		   Perform this only on the first line of the message.
+                */
+		if (offset == 0)
+		{
+			/* we've reached the end of the left part? */
+			if ((pstr-str)+j == ent->left_len)
+			{
+				x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+				pstr += j;
+				j = 0;
+			}
+			else if ((pstr-str)+j == ent->left_len+1)
+			{
+				x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+				pstr += j;
+				j = 0;
+			}
+		}
+
+		/* have we been told to stop rendering at this point? */
+		if (xtext->jump_out_offset > 0 && xtext->jump_out_offset <= (i + offset))
+		{
+			gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+			ret = 0;	/* skip the rest of the lines, we're done. */
+			j = 0;
+			break;
+		}
+
+		if (xtext->jump_in_offset > 0 && xtext->jump_in_offset == (i + offset))
+		{
+			x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+			pstr += j;
+			j = 0;
+			xtext->dont_render2 = FALSE;
+		}
+
+#ifdef MOTION_MONITOR
+		if (xtext->hilight_ent == ent && xtext->hilight_end == (i + offset))
+		{
+			x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+			pstr += j;
+			j = 0;
+#ifdef COLOR_HILIGHT
+			if (mark)
+			{
+				xtext_set_bg (xtext, gc, XTEXT_MARK_BG);
+				xtext->backcolor = TRUE;
+			} else
+			{
+				xtext_set_bg (xtext, gc, xtext->col_back);
+				if (xtext->col_back != XTEXT_BG)
+					xtext->backcolor = TRUE;
+				else
+					xtext->backcolor = FALSE;
+			}
+#else
+			xtext->underline = FALSE;
+#endif
+			xtext->in_hilight = FALSE;
+			if (xtext->render_hilights_only)
+			{
+				/* stop drawing this ent */
+				ret = 0;
+				break;
+			}
+		}
+#endif
+
+		if (!mark && ent->mark_start == (i + offset))
+		{
+			x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+			pstr += j;
+			j = 0;
+			xtext_set_bg (xtext, gc, XTEXT_MARK_BG);
+			xtext_set_fg (xtext, gc, XTEXT_MARK_FG);
+			xtext->backcolor = TRUE;
+			mark = TRUE;
+		}
+
+		if (mark && ent->mark_end == (i + offset))
+		{
+			x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+			pstr += j;
+			j = 0;
+			xtext_set_bg (xtext, gc, xtext->col_back);
+			xtext_set_fg (xtext, gc, xtext->col_fore);
+			if (xtext->col_back != XTEXT_BG)
+				xtext->backcolor = TRUE;
+			else
+				xtext->backcolor = FALSE;
+			mark = FALSE;
+		}
+
+	}
+
+	if (j)
+		x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+
+	if (mark)
+	{
+		xtext_set_bg (xtext, gc, xtext->col_back);
+		xtext_set_fg (xtext, gc, xtext->col_fore);
+		if (xtext->col_back != XTEXT_BG)
+			xtext->backcolor = TRUE;
+		else
+			xtext->backcolor = FALSE;
+	}
+
+	/* draw background to the right of the text */
+	if (!left_only && !xtext->dont_render)
+	{
+		/* draw separator now so it doesn't appear to flicker */
+		gtk_xtext_draw_sep (xtext, y - xtext->font->ascent);
+		if (!xtext->skip_border_fills && xtext->clip_x2 >= x)
+		{
+			int xx = MAX (x, xtext->clip_x);
+
+			xtext_draw_bg (xtext,
+								xx,	/* x */
+								y - xtext->font->ascent, /* y */
+				MIN (xtext->clip_x2 - xx, (win_width + MARGIN) - xx), /* width */
+								xtext->fontsize);		/* height */
+		}
+	}
+
+	xtext->dont_render2 = FALSE;
+
+	/* return how much we drew in the x direction */
+	if (x_size_ret)
+		*x_size_ret = x - indent;
+
+	return ret;
+}
+
+#ifdef USE_XLIB
+
+/* get the desktop/root window */
+
+static Window desktop_window = None;
+
+static Window
+get_desktop_window (Display *xdisplay, Window the_window)
+{
+	Atom prop, type;
+	int format;
+	unsigned long length, after;
+	unsigned char *data;
+	unsigned int nchildren;
+	Window w, root, *children, parent;
+
+	prop = XInternAtom (xdisplay, "_XROOTPMAP_ID", True);
+	if (prop == None)
+	{
+		prop = XInternAtom (xdisplay, "_XROOTCOLOR_PIXEL", True);
+		if (prop == None)
+			return None;
+	}
+
+	for (w = the_window; w; w = parent)
+	{
+		if ((XQueryTree (xdisplay, w, &root, &parent, &children,
+				&nchildren)) == False)
+			return None;
+
+		if (nchildren)
+			XFree (children);
+
+		XGetWindowProperty (xdisplay, w, prop, 0L, 1L, False,
+								  AnyPropertyType, &type, &format, &length, &after,
+								  &data);
+		if (data)
+			XFree (data);
+
+		if (type != None)
+			return (desktop_window = w);
+	}
+
+	return (desktop_window = None);
+}
+
+/* find the root window (backdrop) Pixmap */
+
+static Pixmap
+get_pixmap_prop (Display *xdisplay, Window the_window)
+{
+	Atom type;
+	int format;
+	unsigned long length, after;
+	unsigned char *data;
+	Pixmap pix = None;
+	static Atom prop = None;
+
+	if (desktop_window == None)
+		desktop_window = get_desktop_window (xdisplay, the_window);
+	if (desktop_window == None)
+		desktop_window = DefaultRootWindow (xdisplay);
+
+	if (prop == None)
+		prop = XInternAtom (xdisplay, "_XROOTPMAP_ID", True);
+	if (prop == None)
+		return None;
+
+	XGetWindowProperty (xdisplay, desktop_window, prop, 0L, 1L, False,
+							  AnyPropertyType, &type, &format, &length, &after,
+							  &data);
+	if (data)
+	{
+		if (type == XA_PIXMAP)
+			pix = *((Pixmap *) data);
+
+		XFree (data);
+	}
+
+	return pix;
+}
+
+/* slow generic routine, for the depths/bpp we don't know about */
+
+static void
+shade_ximage_generic (GdkVisual *visual, XImage *ximg, int bpl, int w, int h, int rm, int gm, int bm, int bg)
+{
+	int x, y;
+	int bgr = (256 - rm) * (bg & visual->red_mask);
+	int bgg = (256 - gm) * (bg & visual->green_mask);
+	int bgb = (256 - bm) * (bg & visual->blue_mask);
+
+	for (x = 0; x < w; x++)
+	{
+		for (y = 0; y < h; y++)
+		{
+			unsigned long pixel = XGetPixel (ximg, x, y);
+			int r, g, b;
+
+			r = rm * (pixel & visual->red_mask) + bgr;
+			g = gm * (pixel & visual->green_mask) + bgg;
+			b = bm * (pixel & visual->blue_mask) + bgb;
+
+			XPutPixel (ximg, x, y,
+							((r >> 8) & visual->red_mask) |
+							((g >> 8) & visual->green_mask) |
+							((b >> 8) & visual->blue_mask));
+		}
+	}
+}
+
+#endif
+
+/* Fast shading routine. Based on code by Willem Monsuwe <willem@stack.nl> */
+
+#define SHADE_IMAGE(bytes, type, rmask, gmask, bmask) \
+	unsigned char *ptr; \
+	int x, y; \
+	int bgr = (256 - rm) * (bg & rmask); \
+	int bgg = (256 - gm) * (bg & gmask); \
+	int bgb = (256 - bm) * (bg & bmask); \
+	ptr = (unsigned char *) data + (w * bytes); \
+	for (y = h; --y >= 0;) \
+	{ \
+		for (x = -w; x < 0; x++) \
+		{ \
+			int r, g, b; \
+			b = ((type *) ptr)[x]; \
+			r = rm * (b & rmask) + bgr; \
+			g = gm * (b & gmask) + bgg; \
+			b = bm * (b & bmask) + bgb; \
+			((type *) ptr)[x] = ((r >> 8) & rmask) \
+										| ((g >> 8) & gmask) \
+										| ((b >> 8) & bmask); \
+		} \
+		ptr += bpl; \
+    }
+
+/* RGB 15 */
+static void
+shade_ximage_15 (void *data, int bpl, int w, int h, int rm, int gm, int bm, int bg)
+{
+	SHADE_IMAGE (2, guint16, 0x7c00, 0x3e0, 0x1f);
+}
+
+/* RGB 16 */
+static void
+shade_ximage_16 (void *data, int bpl, int w, int h, int rm, int gm, int bm, int bg)
+{
+	SHADE_IMAGE (2, guint16, 0xf800, 0x7e0, 0x1f);
+}
+
+/* RGB 24 */
+static void
+shade_ximage_24 (void *data, int bpl, int w, int h, int rm, int gm, int bm, int bg)
+{
+	/* 24 has to be a special case, there's no guint24, or 24bit MOV :) */
+	unsigned char *ptr;
+	int x, y;
+	int bgr = (256 - rm) * ((bg & 0xff0000) >> 16);
+	int bgg = (256 - gm) * ((bg & 0xff00) >> 8);
+	int bgb = (256 - bm) * (bg & 0xff);
+
+	ptr = (unsigned char *) data + (w * 3);
+	for (y = h; --y >= 0;)
+	{
+		for (x = -(w * 3); x < 0; x += 3)
+		{
+			int r, g, b;
+
+#if (G_BYTE_ORDER == G_BIG_ENDIAN)
+			r = (ptr[x + 0] * rm + bgr) >> 8;
+			g = (ptr[x + 1] * gm + bgg) >> 8;
+			b = (ptr[x + 2] * bm + bgb) >> 8;
+			ptr[x + 0] = r;
+			ptr[x + 1] = g;
+			ptr[x + 2] = b;
+#else
+			r = (ptr[x + 2] * rm + bgr) >> 8;
+			g = (ptr[x + 1] * gm + bgg) >> 8;
+			b = (ptr[x + 0] * bm + bgb) >> 8;
+			ptr[x + 2] = r;
+			ptr[x + 1] = g;
+			ptr[x + 0] = b;
+#endif
+		}
+		ptr += bpl;
+	}
+}
+
+/* RGB 32 */
+static void
+shade_ximage_32 (void *data, int bpl, int w, int h, int rm, int gm, int bm, int bg)
+{
+	SHADE_IMAGE (4, guint32, 0xff0000, 0xff00, 0xff);
+}
+
+static void
+shade_image (GdkVisual *visual, void *data, int bpl, int bpp, int w, int h,
+				 int rm, int gm, int bm, int bg, int depth)
+{
+	int bg_r, bg_g, bg_b;
+
+	bg_r = bg & visual->red_mask;
+	bg_g = bg & visual->green_mask;
+	bg_b = bg & visual->blue_mask;
+
+#ifdef USE_MMX
+	/* the MMX routines are about 50% faster at 16-bit. */
+	/* only use MMX routines with a pure black background */
+	if (bg_r == 0 && bg_g == 0 && bg_b == 0 && have_mmx ())	/* do a runtime check too! */
+	{
+		switch (depth)
+		{
+		case 15:
+			shade_ximage_15_mmx (data, bpl, w, h, rm, gm, bm);
+			break;
+		case 16:
+			shade_ximage_16_mmx (data, bpl, w, h, rm, gm, bm);
+			break;
+		case 24:
+			if (bpp != 32)
+				goto generic;
+		case 32:
+			shade_ximage_32_mmx (data, bpl, w, h, rm, gm, bm);
+			break;
+		default:
+			goto generic;
+		}
+	} else
+	{
+generic:
+#endif
+		switch (depth)
+		{
+		case 15:
+			shade_ximage_15 (data, bpl, w, h, rm, gm, bm, bg);
+			break;
+		case 16:
+			shade_ximage_16 (data, bpl, w, h, rm, gm, bm, bg);
+			break;
+		case 24:
+			if (bpp != 32)
+			{
+				shade_ximage_24 (data, bpl, w, h, rm, gm, bm, bg);
+				break;
+			}
+		case 32:
+			shade_ximage_32 (data, bpl, w, h, rm, gm, bm, bg);
+		}
+#ifdef USE_MMX
+	}
+#endif
+}
+
+#ifdef USE_XLIB
+
+#ifdef USE_SHM
+
+static XImage *
+get_shm_image (Display *xdisplay, XShmSegmentInfo *shminfo, int x, int y,
+					int w, int h, int depth, Pixmap pix)
+{
+	XImage *ximg;
+
+	shminfo->shmid = -1;
+	shminfo->shmaddr = (char*) -1;
+	ximg = XShmCreateImage (xdisplay, 0, depth, ZPixmap, 0, shminfo, w, h);
+	if (!ximg)
+		return NULL;
+
+	shminfo->shmid = shmget (IPC_PRIVATE, ximg->bytes_per_line * ximg->height,
+									 IPC_CREAT|0600);
+	if (shminfo->shmid == -1)
+	{
+		XDestroyImage (ximg);
+		return NULL;
+	}
+
+	shminfo->readOnly = False;
+	ximg->data = shminfo->shmaddr = (char *)shmat (shminfo->shmid, 0, 0);
+	if (shminfo->shmaddr == ((char *)-1))
+	{
+		shmctl (shminfo->shmid, IPC_RMID, 0);
+		XDestroyImage (ximg);
+		return NULL;
+	}
+
+	XShmAttach (xdisplay, shminfo);
+	XSync (xdisplay, False);
+	shmctl (shminfo->shmid, IPC_RMID, 0);
+	XShmGetImage (xdisplay, pix, ximg, x, y, AllPlanes);
+
+	return ximg;
+}
+
+static XImage *
+get_image (GtkXText *xtext, Display *xdisplay, XShmSegmentInfo *shminfo,
+			  int x, int y, int w, int h, int depth, Pixmap pix)
+{
+	XImage *ximg;
+
+	xtext->shm = 1;
+	ximg = get_shm_image (xdisplay, shminfo, x, y, w, h, depth, pix);
+	if (!ximg)
+	{
+		xtext->shm = 0;
+		ximg = XGetImage (xdisplay, pix, x, y, w, h, -1, ZPixmap);
+	}
+
+	return ximg;
+}
+
+#endif
+
+static GdkPixmap *
+shade_pixmap (GtkXText * xtext, Pixmap p, int x, int y, int w, int h)
+{
+	unsigned int dummy, width, height, depth;
+	GdkPixmap *shaded_pix;
+	Window root;
+	Pixmap tmp;
+	XImage *ximg;
+	XGCValues gcv;
+	GC tgc;
+	Display *xdisplay = GDK_WINDOW_XDISPLAY (xtext->draw_buf);
+
+#ifdef USE_SHM
+	int shm_pixmaps;
+	shm_pixmaps = have_shm_pixmaps(xdisplay);
+#endif
+
+	XGetGeometry (xdisplay, p, &root, &dummy, &dummy, &width, &height,
+					  &dummy, &depth);
+
+	if (width < x + w || height < y + h || x < 0 || y < 0)
+	{
+		gcv.subwindow_mode = IncludeInferiors;
+		gcv.graphics_exposures = False;
+		tgc = XCreateGC (xdisplay, p, GCGraphicsExposures|GCSubwindowMode,
+							  &gcv);
+		tmp = XCreatePixmap (xdisplay, p, w, h, depth);
+		XSetTile (xdisplay, tgc, p);
+		XSetFillStyle (xdisplay, tgc, FillTiled);
+		XSetTSOrigin (xdisplay, tgc, -x, -y);
+		XFillRectangle (xdisplay, tmp, tgc, 0, 0, w, h);
+		XFreeGC (xdisplay, tgc);
+
+#ifdef USE_SHM
+		if (shm_pixmaps)
+			ximg = get_image (xtext, xdisplay, &xtext->shminfo, 0, 0, w, h, depth, tmp);
+		else
+#endif
+			ximg = XGetImage (xdisplay, tmp, 0, 0, w, h, -1, ZPixmap);
+		XFreePixmap (xdisplay, tmp);
+	} else
+	{
+#ifdef USE_SHM
+		if (shm_pixmaps)
+			ximg = get_image (xtext, xdisplay, &xtext->shminfo, x, y, w, h, depth, p);
+		else
+#endif
+			ximg = XGetImage (xdisplay, p, x, y, w, h, -1, ZPixmap);
+	}
+
+	if (!ximg)
+		return NULL;
+
+	if (depth <= 14)
+	{
+		shade_ximage_generic (gdk_drawable_get_visual (GTK_WIDGET (xtext)->window),
+									 ximg, ximg->bytes_per_line, w, h, xtext->tint_red,
+									 xtext->tint_green, xtext->tint_blue,
+									 xtext->palette[XTEXT_BG]);
+	} else
+	{
+		shade_image (gdk_drawable_get_visual (GTK_WIDGET (xtext)->window),
+						 ximg->data, ximg->bytes_per_line, ximg->bits_per_pixel,
+						 w, h, xtext->tint_red, xtext->tint_green, xtext->tint_blue,
+						 xtext->palette[XTEXT_BG], depth);
+	}
+
+	if (xtext->recycle)
+		shaded_pix = xtext->pixmap;
+	else
+	{
+#ifdef USE_SHM
+		if (xtext->shm && shm_pixmaps)
+		{
+#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0)
+			shaded_pix = gdk_pixmap_foreign_new (
+				XShmCreatePixmap (xdisplay, p, ximg->data, &xtext->shminfo, w, h, depth));
+#else
+			shaded_pix = gdk_pixmap_foreign_new_for_display (
+				gdk_drawable_get_display (xtext->draw_buf),
+				XShmCreatePixmap (xdisplay, p, ximg->data, &xtext->shminfo, w, h, depth));
+#endif
+		} else
+#endif
+		{
+			shaded_pix = gdk_pixmap_new (GTK_WIDGET (xtext)->window, w, h, depth);
+		}
+	}
+
+#ifdef USE_SHM
+	if (!xtext->shm || !shm_pixmaps)
+#endif
+		XPutImage (xdisplay, GDK_WINDOW_XWINDOW (shaded_pix),
+					  GDK_GC_XGC (xtext->fgc), ximg, 0, 0, 0, 0, w, h);
+	XDestroyImage (ximg);
+
+	return shaded_pix;
+}
+
+#endif /* !USE_XLIB */
+
+/* free transparency xtext->pixmap */
+#if defined(USE_XLIB) || defined(WIN32)
+
+static void
+gtk_xtext_free_trans (GtkXText * xtext)
+{
+	if (xtext->pixmap)
+	{
+#ifdef USE_SHM
+		if (xtext->shm && have_shm_pixmaps(GDK_WINDOW_XDISPLAY (xtext->draw_buf)))
+		{
+			XFreePixmap (GDK_WINDOW_XDISPLAY (xtext->pixmap),
+							 GDK_WINDOW_XWINDOW (xtext->pixmap));
+			XShmDetach (GDK_WINDOW_XDISPLAY (xtext->draw_buf), &xtext->shminfo);
+			shmdt (xtext->shminfo.shmaddr);
+		}
+#endif
+		g_object_unref (xtext->pixmap);
+		xtext->pixmap = NULL;
+		xtext->shm = 0;
+	}
+}
+
+#endif
+
+#ifdef WIN32
+
+static GdkPixmap *
+win32_tint (GtkXText *xtext, GdkImage *img, int width, int height)
+{
+	guchar *pixelp;
+	int x, y;
+	GdkPixmap *pix;
+	GdkVisual *visual = gdk_drawable_get_visual (GTK_WIDGET (xtext)->window);
+	guint32 pixel;
+	int r, g, b;
+
+	if (img->depth <= 14)
+	{
+		/* slow generic routine */
+		for (y = 0; y < height; y++)
+		{
+			for (x = 0; x < width; x++)
+			{
+				if (img->depth == 1)
+				{
+					pixel = (((guchar *) img->mem)[y * img->bpl + (x >> 3)] & (1 << (7 - (x & 0x7)))) != 0;
+					goto here;
+				}
+
+				if (img->depth == 4)
+				{
+					pixelp = (guchar *) img->mem + y * img->bpl + (x >> 1);
+					if (x&1)
+					{
+						pixel = (*pixelp) & 0x0F;
+						goto here;
+					}
+
+					pixel = (*pixelp) >> 4;
+					goto here;
+				}
+
+				pixelp = (guchar *) img->mem + y * img->bpl + x * img->bpp;
+
+				switch (img->bpp)
+				{
+				case 1:
+					pixel = *pixelp; break;
+
+				/* Windows is always LSB, no need to check img->byte_order. */
+				case 2:
+					pixel = pixelp[0] | (pixelp[1] << 8); break;
+
+				case 3:
+					pixel = pixelp[0] | (pixelp[1] << 8) | (pixelp[2] << 16); break;
+
+				case 4:
+					pixel = pixelp[0] | (pixelp[1] << 8) | (pixelp[2] << 16); break;
+				}
+
+here:
+				r = (pixel & visual->red_mask) >> visual->red_shift;
+				g = (pixel & visual->green_mask) >> visual->green_shift;
+				b = (pixel & visual->blue_mask) >> visual->blue_shift;
+
+				/* actual tinting is only these 3 lines */
+				pixel = ((r * xtext->tint_red) >> 8) << visual->red_shift |
+							((g * xtext->tint_green) >> 8) << visual->green_shift |
+							((b * xtext->tint_blue) >> 8) << visual->blue_shift;
+
+				if (img->depth == 1)
+					if (pixel & 1)
+						((guchar *) img->mem)[y * img->bpl + (x >> 3)] |= (1 << (7 - (x & 0x7)));
+					else
+						((guchar *) img->mem)[y * img->bpl + (x >> 3)] &= ~(1 << (7 - (x & 0x7)));
+				else if (img->depth == 4)
+				{
+					pixelp = (guchar *) img->mem + y * img->bpl + (x >> 1);
+
+					if (x&1)
+					{
+						*pixelp &= 0xF0;
+						*pixelp |= (pixel & 0x0F);
+					} else
+					{
+						*pixelp &= 0x0F;
+						*pixelp |= (pixel << 4);
+					}
+				} else
+				{
+					pixelp = (guchar *) img->mem + y * img->bpl + x * img->bpp;
+
+					/* Windows is always LSB, no need to check img->byte_order. */
+					switch (img->bpp)
+					{
+					case 4:
+						pixelp[3] = 0;
+					case 3:
+						pixelp[2] = ((pixel >> 16) & 0xFF);
+					case 2:
+						pixelp[1] = ((pixel >> 8) & 0xFF);
+					case 1:
+						pixelp[0] = (pixel & 0xFF);
+					}
+				}
+			}
+		}
+	} else
+	{
+		shade_image (visual, img->mem, img->bpl, img->bpp, width, height,
+						 xtext->tint_red, xtext->tint_green, xtext->tint_blue,
+						 xtext->palette[XTEXT_BG], visual->depth);
+	}
+
+	/* no need to dump it to a Pixmap, it's one and the same on win32 */
+	pix = (GdkPixmap *)img;
+
+	return pix;
+}
+
+#endif /* !WIN32 */
+
+/* grab pixmap from root window and set xtext->pixmap */
+#if defined(USE_XLIB) || defined(WIN32)
+
+static void
+gtk_xtext_load_trans (GtkXText * xtext)
+{
+#ifdef WIN32
+	GdkImage *img;
+	int width, height;
+	HDC hdc;
+	HWND hwnd;
+
+	/* if not shaded, we paint directly with PaintDesktop() */
+	if (!xtext->shaded)
+		return;
+
+	hwnd = GDK_WINDOW_HWND (GTK_WIDGET (xtext)->window);
+	hdc = GetDC (hwnd);
+	PaintDesktop (hdc);
+	ReleaseDC (hwnd, hdc);
+
+	gdk_window_get_size (GTK_WIDGET (xtext)->window, &width, &height);
+	img = gdk_image_get (GTK_WIDGET (xtext)->window, 0, 0, width+128, height);
+	xtext->pixmap = win32_tint (xtext, img, img->width, img->height);
+
+#else
+
+	Pixmap rootpix;
+	GtkWidget *widget = GTK_WIDGET (xtext);
+	int x, y;
+
+	rootpix = get_pixmap_prop (GDK_WINDOW_XDISPLAY (widget->window), GDK_WINDOW_XWINDOW (widget->window));
+	if (rootpix == None)
+	{
+		if (xtext->error_function)
+			xtext->error_function (0);
+		xtext->transparent = FALSE;
+		return;
+	}
+
+	gdk_window_get_origin (widget->window, &x, &y);
+
+	if (xtext->shaded)
+	{
+		int width, height;
+		gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &width, &height);
+		xtext->pixmap = shade_pixmap (xtext, rootpix, x, y, width+105, height);
+		if (xtext->pixmap == NULL)
+		{
+			xtext->shaded = 0;
+			goto noshade;
+		}
+		gdk_gc_set_tile (xtext->bgc, xtext->pixmap);
+		gdk_gc_set_ts_origin (xtext->bgc, 0, 0);
+		xtext->ts_x = xtext->ts_y = 0;
+	} else
+	{
+noshade:
+#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0)
+		xtext->pixmap = gdk_pixmap_foreign_new (rootpix);
+#else
+		xtext->pixmap = gdk_pixmap_foreign_new_for_display (gdk_drawable_get_display (GTK_WIDGET (xtext)->window), rootpix);
+#endif
+		gdk_gc_set_tile (xtext->bgc, xtext->pixmap);
+		gdk_gc_set_ts_origin (xtext->bgc, -x, -y);
+		xtext->ts_x = -x;
+		xtext->ts_y = -y;
+	}
+	gdk_gc_set_fill (xtext->bgc, GDK_TILED);
+#endif /* !WIN32 */
+}
+
+#endif /* ! XLIB || WIN32 */
+
+/* walk through str until this line doesn't fit anymore */
+
+static int
+find_next_wrap (GtkXText * xtext, textentry * ent, unsigned char *str,
+					 int win_width, int indent)
+{
+	unsigned char *last_space = str;
+	unsigned char *orig_str = str;
+	int str_width = indent;
+	int rcol = 0, bgcol = 0;
+	int hidden = FALSE;
+	int mbl;
+	int char_width;
+	int ret;
+	int limit_offset = 0;
+
+	/* single liners */
+	if (win_width >= ent->str_width + ent->indent)
+		return ent->str_len;
+
+	/* it does happen! */
+	if (win_width < 1)
+	{
+		ret = ent->str_len - (str - ent->str);
+		goto done;
+	}
+
+	while (1)
+	{
+		if (rcol > 0 && (isdigit (*str) || (*str == ',' && isdigit (str[1]) && !bgcol)))
+		{
+			if (str[1] != ',') rcol--;
+			if (*str == ',')
+			{
+				rcol = 2;
+				bgcol = 1;
+			}
+			limit_offset++;
+			str++;
+		} else
+		{
+			rcol = bgcol = 0;
+			switch (*str)
+			{
+			case ATTR_COLOR:
+				rcol = 2;
+			case ATTR_BEEP:
+			case ATTR_RESET:
+			case ATTR_REVERSE:
+			case ATTR_BOLD:
+			case ATTR_UNDERLINE:
+			case ATTR_ITALICS:
+				limit_offset++;
+				str++;
+				break;
+			case ATTR_HIDDEN:
+				if (xtext->ignore_hidden)
+					goto def;
+				hidden = !hidden;
+				limit_offset++;
+				str++;
+				break;
+			default:
+			def:
+				char_width = backend_get_char_width (xtext, str, &mbl);
+				if (!hidden) str_width += char_width;
+				if (str_width > win_width)
+				{
+					if (xtext->wordwrap)
+					{
+						if (str - last_space > WORDWRAP_LIMIT + limit_offset)
+							ret = str - orig_str; /* fall back to character wrap */
+						else
+						{
+							if (*last_space == ' ')
+								last_space++;
+							ret = last_space - orig_str;
+							if (ret == 0) /* fall back to character wrap */
+								ret = str - orig_str;
+						}
+						goto done;
+					}
+					ret = str - orig_str;
+					goto done;
+				}
+
+				/* keep a record of the last space, for wordwrapping */
+				if (is_del (*str))
+				{
+					last_space = str;
+					limit_offset = 0;
+				}
+
+				/* progress to the next char */
+				str += mbl;
+
+			}
+		}
+
+		if (str >= ent->str + ent->str_len)
+		{
+			ret = str - orig_str;
+			goto done;
+		}
+	}
+
+done:
+
+	/* must make progress */
+	if (ret < 1)
+		ret = 1;
+
+	return ret;
+}
+
+/* find the offset, in bytes, that wrap number 'line' starts at */
+
+static int
+gtk_xtext_find_subline (GtkXText *xtext, textentry *ent, int line)
+{
+	int win_width;
+	unsigned char *str;
+	int indent, str_pos, line_pos, len;
+
+	if (ent->lines_taken < 2 || line < 1)
+		return 0;
+
+	/* we record the first 4 lines' wraps, so take a shortcut */
+	if (line <= RECORD_WRAPS)
+		return ent->wrap_offset[line - 1];
+
+	gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &win_width, 0);
+	win_width -= MARGIN;
+
+/*	indent = ent->indent;
+	str = ent->str;
+	line_pos = str_pos = 0;*/
+
+	/* start from the last recorded wrap, and move forward */
+	indent = xtext->buffer->indent;
+	str_pos = ent->wrap_offset[RECORD_WRAPS-1];
+	str = str_pos + ent->str;
+	line_pos = RECORD_WRAPS;
+
+	do
+	{
+		len = find_next_wrap (xtext, ent, str, win_width, indent);
+		indent = xtext->buffer->indent;
+		str += len;
+		str_pos += len;
+		line_pos++;
+		if (line_pos >= line)
+			return str_pos;
+	}
+	while (str < ent->str + ent->str_len);
+
+	return 0;
+}
+
+/* horrible hack for drawing time stamps */
+
+static void
+gtk_xtext_render_stamp (GtkXText * xtext, textentry * ent,
+								char *text, int len, int line, int win_width)
+{
+	textentry tmp_ent;
+	int jo, ji, hs;
+	int xsize, y;
+
+	/* trashing ent here, so make a backup first */
+	memcpy (&tmp_ent, ent, sizeof (tmp_ent));
+	ent->mb = TRUE;	/* make non-english days of the week work */
+	jo = xtext->jump_out_offset;	/* back these up */
+	ji = xtext->jump_in_offset;
+	hs = xtext->hilight_start;
+	xtext->jump_out_offset = 0;
+	xtext->jump_in_offset = 0;
+	xtext->hilight_start = 0xffff;	/* temp disable */
+
+	if (xtext->mark_stamp)
+	{
+		/* if this line is marked, mark this stamp too */
+		if (ent->mark_start == 0)	
+		{
+			ent->mark_start = 0;
+			ent->mark_end = len;
+		}
+		else
+		{
+			ent->mark_start = -1;
+			ent->mark_end = -1;
+		}
+		ent->str = text;
+	}
+
+	y = (xtext->fontsize * line) + xtext->font->ascent - xtext->pixel_offset;
+	gtk_xtext_render_str (xtext, y, ent, text, len,
+								 win_width, 2, line, TRUE, &xsize);
+
+	/* restore everything back to how it was */
+	memcpy (ent, &tmp_ent, sizeof (tmp_ent));
+	xtext->jump_out_offset = jo;
+	xtext->jump_in_offset = ji;
+	xtext->hilight_start = hs;
+
+	/* with a non-fixed-width font, sometimes we don't draw enough
+		background i.e. when this stamp is shorter than xtext->stamp_width */
+	xsize += MARGIN;
+	if (xsize < xtext->stamp_width)
+	{
+		y -= xtext->font->ascent;
+		xtext_draw_bg (xtext,
+							xsize,	/* x */
+							y,			/* y */
+							xtext->stamp_width - xsize,	/* width */
+							xtext->fontsize					/* height */);
+	}
+}
+
+/* render a single line, which may wrap to more lines */
+
+static int
+gtk_xtext_render_line (GtkXText * xtext, textentry * ent, int line,
+							  int lines_max, int subline, int win_width)
+{
+	unsigned char *str;
+	int indent, taken, entline, len, y, start_subline;
+
+	entline = taken = 0;
+	str = ent->str;
+	indent = ent->indent;
+	start_subline = subline;
+
+#ifdef XCHAT
+	/* draw the timestamp */
+	if (xtext->auto_indent && xtext->buffer->time_stamp &&
+		 (!xtext->skip_stamp || xtext->mark_stamp || xtext->force_stamp))
+	{
+		char *time_str;
+		int len;
+
+		len = xtext_get_stamp_str (ent->stamp, &time_str);
+		gtk_xtext_render_stamp (xtext, ent, time_str, len, line, win_width);
+		g_free (time_str);
+	}
+#endif
+
+	/* draw each line one by one */
+	do
+	{
+		/* if it's one of the first 4 wraps, we don't need to calculate it, it's
+			recorded in ->wrap_offset. This saves us a loop. */
+		if (entline < RECORD_WRAPS)
+		{
+			if (ent->lines_taken < 2)
+				len = ent->str_len;
+			else
+			{
+				if (entline > 0)
+					len = ent->wrap_offset[entline] - ent->wrap_offset[entline-1];
+				else
+					len = ent->wrap_offset[0];
+			}
+		} else
+			len = find_next_wrap (xtext, ent, str, win_width, indent);
+
+		entline++;
+
+		y = (xtext->fontsize * line) + xtext->font->ascent - xtext->pixel_offset;
+		if (!subline)
+		{
+			if (!gtk_xtext_render_str (xtext, y, ent, str, len, win_width,
+												indent, line, FALSE, NULL))
+			{
+				/* small optimization */
+				gtk_xtext_draw_marker (xtext, ent, y - xtext->fontsize * (taken + start_subline + 1));
+				return ent->lines_taken - subline;
+			}
+		} else
+		{
+			xtext->dont_render = TRUE;
+			gtk_xtext_render_str (xtext, y, ent, str, len, win_width,
+										 indent, line, FALSE, NULL);
+			xtext->dont_render = FALSE;
+			subline--;
+			line--;
+			taken--;
+		}
+
+		indent = xtext->buffer->indent;
+		line++;
+		taken++;
+		str += len;
+
+		if (line >= lines_max)
+			break;
+
+	}
+	while (str < ent->str + ent->str_len);
+
+	gtk_xtext_draw_marker (xtext, ent, y - xtext->fontsize * (taken + start_subline));
+
+	return taken;
+}
+
+void
+gtk_xtext_set_palette (GtkXText * xtext, GdkColor palette[])
+{
+	int i;
+	GdkColor col;
+
+	for (i = (XTEXT_COLS-1); i >= 0; i--)
+	{
+#ifdef USE_XFT
+		xtext->color[i].color.red = palette[i].red;
+		xtext->color[i].color.green = palette[i].green;
+		xtext->color[i].color.blue = palette[i].blue;
+		xtext->color[i].color.alpha = 0xffff;
+		xtext->color[i].pixel = palette[i].pixel;
+#endif
+		xtext->palette[i] = palette[i].pixel;
+	}
+
+	if (GTK_WIDGET_REALIZED (xtext))
+	{
+		xtext_set_fg (xtext, xtext->fgc, XTEXT_FG);
+		xtext_set_bg (xtext, xtext->fgc, XTEXT_BG);
+		xtext_set_fg (xtext, xtext->bgc, XTEXT_BG);
+
+		col.pixel = xtext->palette[XTEXT_MARKER];
+		gdk_gc_set_foreground (xtext->marker_gc, &col);
+	}
+	xtext->col_fore = XTEXT_FG;
+	xtext->col_back = XTEXT_BG;
+}
+
+static void
+gtk_xtext_fix_indent (xtext_buffer *buf)
+{
+	int j;
+
+	/* make indent a multiple of the space width */
+	if (buf->indent && buf->xtext->space_width)
+	{
+		j = 0;
+		while (j < buf->indent)
+		{
+			j += buf->xtext->space_width;
+		}
+		buf->indent = j;
+	}
+
+	dontscroll (buf);	/* force scrolling off */
+}
+
+static void
+gtk_xtext_recalc_widths (xtext_buffer *buf, int do_str_width)
+{
+	textentry *ent;
+
+	/* since we have a new font, we have to recalc the text widths */
+	ent = buf->text_first;
+	while (ent)
+	{
+		if (do_str_width)
+		{
+			ent->str_width = gtk_xtext_text_width (buf->xtext, ent->str,
+														ent->str_len, NULL);
+		}
+		if (ent->left_len != -1)
+		{
+			ent->indent =
+				(buf->indent -
+				 gtk_xtext_text_width (buf->xtext, ent->str,
+										ent->left_len, NULL)) - buf->xtext->space_width;
+			if (ent->indent < MARGIN)
+				ent->indent = MARGIN;
+		}
+		ent = ent->next;
+	}
+
+	gtk_xtext_calc_lines (buf, FALSE);
+}
+
+int
+gtk_xtext_set_font (GtkXText *xtext, char *name)
+{
+	int i;
+	unsigned char c;
+
+	if (xtext->font)
+		backend_font_close (xtext);
+
+	/* realize now, so that font_open has a XDisplay */
+	gtk_widget_realize (GTK_WIDGET (xtext));
+
+	backend_font_open (xtext, name);
+	if (xtext->font == NULL)
+		return FALSE;
+
+	/* measure the width of every char;  only the ASCII ones for XFT */
+	for (i = 0; i < sizeof(xtext->fontwidth)/sizeof(xtext->fontwidth[0]); i++)
+	{
+		c = i;
+		xtext->fontwidth[i] = backend_get_text_width (xtext, &c, 1, TRUE);
+	}
+	xtext->space_width = xtext->fontwidth[' '];
+	xtext->fontsize = xtext->font->ascent + xtext->font->descent;
+
+#ifdef XCHAT
+	{
+		char *time_str;
+		int stamp_size = xtext_get_stamp_str (time(0), &time_str);
+		xtext->stamp_width =
+			gtk_xtext_text_width (xtext, time_str, stamp_size, NULL) + MARGIN;
+		g_free (time_str);
+	}
+#endif
+
+	gtk_xtext_fix_indent (xtext->buffer);
+
+	if (GTK_WIDGET_REALIZED (xtext))
+		gtk_xtext_recalc_widths (xtext->buffer, TRUE);
+
+	return TRUE;
+}
+
+void
+gtk_xtext_set_background (GtkXText * xtext, GdkPixmap * pixmap, gboolean trans)
+{
+	GdkGCValues val;
+	gboolean shaded = FALSE;
+
+	if (trans && (xtext->tint_red != 255 || xtext->tint_green != 255 || xtext->tint_blue != 255))
+		shaded = TRUE;
+
+#if !defined(USE_XLIB) && !defined(WIN32)
+	shaded = FALSE;
+	trans = FALSE;
+#endif
+
+	if (xtext->pixmap)
+	{
+#if defined(USE_XLIB) || defined(WIN32)
+		if (xtext->transparent)
+			gtk_xtext_free_trans (xtext);
+		else
+#endif
+			g_object_unref (xtext->pixmap);
+		xtext->pixmap = NULL;
+	}
+
+	xtext->transparent = trans;
+
+#if defined(USE_XLIB) || defined(WIN32)
+	if (trans)
+	{
+		xtext->shaded = shaded;
+		if (GTK_WIDGET_REALIZED (xtext))
+			gtk_xtext_load_trans (xtext);
+		return;
+	}
+#endif
+
+	dontscroll (xtext->buffer);
+	xtext->pixmap = pixmap;
+
+	if (pixmap != 0)
+	{
+		g_object_ref (pixmap);
+		if (GTK_WIDGET_REALIZED (xtext))
+		{
+			gdk_gc_set_tile (xtext->bgc, pixmap);
+			gdk_gc_set_ts_origin (xtext->bgc, 0, 0);
+			xtext->ts_x = xtext->ts_y = 0;
+			gdk_gc_set_fill (xtext->bgc, GDK_TILED);
+		}
+	} else if (GTK_WIDGET_REALIZED (xtext))
+	{
+		g_object_unref (xtext->bgc);
+		val.subwindow_mode = GDK_INCLUDE_INFERIORS;
+		val.graphics_exposures = 0;
+		xtext->bgc = gdk_gc_new_with_values (GTK_WIDGET (xtext)->window,
+								&val, GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW);
+		xtext_set_fg (xtext, xtext->bgc, XTEXT_BG);
+	}
+}
+
+void
+gtk_xtext_save (GtkXText * xtext, int fh)
+{
+	textentry *ent;
+	int newlen;
+	char *buf;
+
+	ent = xtext->buffer->text_first;
+	while (ent)
+	{
+		buf = gtk_xtext_strip_color (ent->str, ent->str_len, NULL,
+											  &newlen, NULL, FALSE);
+		write (fh, buf, newlen);
+		write (fh, "\n", 1);
+		free (buf);
+		ent = ent->next;
+	}
+}
+
+/* count how many lines 'ent' will take (with wraps) */
+
+static int
+gtk_xtext_lines_taken (xtext_buffer *buf, textentry * ent)
+{
+	unsigned char *str;
+	int indent, taken, len;
+	int win_width;
+
+	win_width = buf->window_width - MARGIN;
+
+	if (ent->str_width + ent->indent < win_width)
+		return 1;
+
+	indent = ent->indent;
+	str = ent->str;
+	taken = 0;
+
+	do
+	{
+		len = find_next_wrap (buf->xtext, ent, str, win_width, indent);
+		if (taken < RECORD_WRAPS)
+			ent->wrap_offset[taken] = (str + len) - ent->str;
+		indent = buf->indent;
+		taken++;
+		str += len;
+	}
+	while (str < ent->str + ent->str_len);
+
+	return taken;
+}
+
+/* Calculate number of actual lines (with wraps), to set adj->lower. *
+ * This should only be called when the window resizes.               */
+
+static void
+gtk_xtext_calc_lines (xtext_buffer *buf, int fire_signal)
+{
+	textentry *ent;
+	int width;
+	int height;
+	int lines;
+
+	gdk_drawable_get_size (GTK_WIDGET (buf->xtext)->window, &width, &height);
+	width -= MARGIN;
+
+	if (width < 30 || height < buf->xtext->fontsize || width < buf->indent + 30)
+		return;
+
+	lines = 0;
+	ent = buf->text_first;
+	while (ent)
+	{
+		ent->lines_taken = gtk_xtext_lines_taken (buf, ent);
+		lines += ent->lines_taken;
+		ent = ent->next;
+	}
+
+	buf->pagetop_ent = NULL;
+	buf->num_lines = lines;
+	gtk_xtext_adjustment_set (buf, fire_signal);
+}
+
+/* find the n-th line in the linked list, this includes wrap calculations */
+
+static textentry *
+gtk_xtext_nth (GtkXText *xtext, int line, int *subline)
+{
+	int lines = 0;
+	textentry *ent;
+
+	ent = xtext->buffer->text_first;
+
+	/* -- optimization -- try to make a short-cut using the pagetop ent */
+	if (xtext->buffer->pagetop_ent)
+	{
+		if (line == xtext->buffer->pagetop_line)
+		{
+			*subline = xtext->buffer->pagetop_subline;
+			return xtext->buffer->pagetop_ent;
+		}
+		if (line > xtext->buffer->pagetop_line)
+		{
+			/* lets start from the pagetop instead of the absolute beginning */
+			ent = xtext->buffer->pagetop_ent;
+			lines = xtext->buffer->pagetop_line - xtext->buffer->pagetop_subline;
+		}
+		else if (line > xtext->buffer->pagetop_line - line)
+		{
+			/* move backwards from pagetop */
+			ent = xtext->buffer->pagetop_ent;
+			lines = xtext->buffer->pagetop_line - xtext->buffer->pagetop_subline;
+			while (1)
+			{
+				if (lines <= line)
+				{
+					*subline = line - lines;
+					return ent;
+				}
+				ent = ent->prev;
+				if (!ent)
+					break;
+				lines -= ent->lines_taken;
+			}
+			return 0;
+		}
+	}
+	/* -- end of optimization -- */
+
+	while (ent)
+	{
+		lines += ent->lines_taken;
+		if (lines > line)
+		{
+			*subline = ent->lines_taken - (lines - line);
+			return ent;
+		}
+		ent = ent->next;
+	}
+	return 0;
+}
+
+/* render enta (or an inclusive range enta->entb) */
+
+static int
+gtk_xtext_render_ents (GtkXText * xtext, textentry * enta, textentry * entb)
+{
+	textentry *ent, *orig_ent, *tmp_ent;
+	int line;
+	int lines_max;
+	int width;
+	int height;
+	int subline;
+	int drawing = FALSE;
+
+	if (xtext->buffer->indent < MARGIN)
+		xtext->buffer->indent = MARGIN;	  /* 2 pixels is our left margin */
+
+	gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &width, &height);
+	width -= MARGIN;
+
+	if (width < 32 || height < xtext->fontsize || width < xtext->buffer->indent + 30)
+		return 0;
+
+	lines_max = ((height + xtext->pixel_offset) / xtext->fontsize) + 1;
+	line = 0;
+	orig_ent = xtext->buffer->pagetop_ent;
+	subline = xtext->buffer->pagetop_subline;
+
+	/* used before a complete page is in buffer */
+	if (orig_ent == NULL)
+		orig_ent = xtext->buffer->text_first;
+
+	/* check if enta is before the start of this page */
+	if (entb)
+	{
+		tmp_ent = orig_ent;
+		while (tmp_ent)
+		{
+			if (tmp_ent == enta)
+				break;
+			if (tmp_ent == entb)
+			{
+				drawing = TRUE;
+				break;
+			}
+			tmp_ent = tmp_ent->next;
+		}
+	}
+
+	ent = orig_ent;
+	while (ent)
+	{
+		if (entb && ent == enta)
+			drawing = TRUE;
+
+		if (drawing || ent == entb || ent == enta)
+		{
+			gtk_xtext_reset (xtext, FALSE, TRUE);
+			line += gtk_xtext_render_line (xtext, ent, line, lines_max,
+													 subline, width);
+			subline = 0;
+			xtext->jump_in_offset = 0;	/* jump_in_offset only for the 1st */
+		} else
+		{
+			if (ent == orig_ent)
+			{
+				line -= subline;
+				subline = 0;
+			}
+			line += ent->lines_taken;
+		}
+
+		if (ent == entb)
+			break;
+
+		if (line >= lines_max)
+			break;
+
+		ent = ent->next;
+	}
+
+	/* space below last line */
+	return (xtext->fontsize * line) - xtext->pixel_offset;
+}
+
+/* render a whole page/window, starting from 'startline' */
+
+static void
+gtk_xtext_render_page (GtkXText * xtext)
+{
+	textentry *ent;
+	int line;
+	int lines_max;
+	int width;
+	int height;
+	int subline;
+	int startline = xtext->adj->value;
+
+	if(!GTK_WIDGET_REALIZED(xtext))
+	  return;
+
+	if (xtext->buffer->indent < MARGIN)
+		xtext->buffer->indent = MARGIN;	  /* 2 pixels is our left margin */
+
+	gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &width, &height);
+
+	if (width < 34 || height < xtext->fontsize || width < xtext->buffer->indent + 32)
+		return;
+
+#ifdef SMOOTH_SCROLL
+	xtext->pixel_offset = (xtext->adj->value - startline) * xtext->fontsize;
+#else
+	xtext->pixel_offset = 0;
+#endif
+
+	subline = line = 0;
+	ent = xtext->buffer->text_first;
+
+	if (startline > 0)
+		ent = gtk_xtext_nth (xtext, startline, &subline);
+
+	xtext->buffer->pagetop_ent = ent;
+	xtext->buffer->pagetop_subline = subline;
+	xtext->buffer->pagetop_line = startline;
+
+#ifdef SCROLL_HACK
+{
+	int pos, overlap;
+	GdkRectangle area;
+
+	if (xtext->buffer->num_lines <= xtext->adj->page_size)
+		dontscroll (xtext->buffer);
+
+#ifdef SMOOTH_SCROLL
+	pos = xtext->adj->value * xtext->fontsize;
+#else
+	pos = startline * xtext->fontsize;
+#endif
+	overlap = xtext->buffer->last_pixel_pos - pos;
+	xtext->buffer->last_pixel_pos = pos;
+
+#ifdef USE_DB
+#ifdef WIN32
+	if (!xtext->transparent && !xtext->pixmap && abs (overlap) < height)
+#else
+	if (!xtext->pixmap && abs (overlap) < height)
+#endif
+#else
+	/* dont scroll PageUp/Down without a DB, it looks ugly */
+#ifdef WIN32
+	if (!xtext->transparent && !xtext->pixmap && abs (overlap) < height - (3*xtext->fontsize))
+#else
+	if (!xtext->pixmap && abs (overlap) < height - (3*xtext->fontsize))
+#endif
+#endif
+	{
+		/* so the obscured regions are exposed */
+		gdk_gc_set_exposures (xtext->fgc, TRUE);
+		if (overlap < 1)	/* DOWN */
+		{
+			int remainder;
+
+			gdk_draw_drawable (xtext->draw_buf, xtext->fgc, xtext->draw_buf,
+									 0, -overlap, 0, 0, width, height + overlap);
+			remainder = ((height - xtext->font->descent) % xtext->fontsize) +
+							xtext->font->descent;
+			area.y = (height + overlap) - remainder;
+			area.height = remainder - overlap;
+		} else
+		{
+			gdk_draw_drawable (xtext->draw_buf, xtext->fgc, xtext->draw_buf,
+									 0, 0, 0, overlap, width, height - overlap);
+			area.y = 0;
+			area.height = overlap;
+		}
+		gdk_gc_set_exposures (xtext->fgc, FALSE);
+
+		if (area.height > 0)
+		{
+			area.x = 0;
+			area.width = width;
+			gtk_xtext_paint (GTK_WIDGET (xtext), &area);
+		}
+		xtext->buffer->grid_dirty = TRUE;
+
+		return;
+	}
+}
+#endif
+
+	xtext->buffer->grid_dirty = FALSE;
+	width -= MARGIN;
+	lines_max = ((height + xtext->pixel_offset) / xtext->fontsize) + 1;
+
+	while (ent)
+	{
+		gtk_xtext_reset (xtext, FALSE, TRUE);
+		line += gtk_xtext_render_line (xtext, ent, line, lines_max,
+												 subline, width);
+		subline = 0;
+
+		if (line >= lines_max)
+			break;
+
+		ent = ent->next;
+	}
+
+	line = (xtext->fontsize * line) - xtext->pixel_offset;
+	/* fill any space below the last line with our background GC */
+	xtext_draw_bg (xtext, 0, line, width + MARGIN, height - line);
+
+	/* draw the separator line */
+	gtk_xtext_draw_sep (xtext, -1);
+}
+
+void
+gtk_xtext_refresh (GtkXText * xtext, int do_trans)
+{
+	if (GTK_WIDGET_REALIZED (GTK_WIDGET (xtext)))
+	{
+#if defined(USE_XLIB) || defined(WIN32)
+		if (xtext->transparent && do_trans)
+		{
+			gtk_xtext_free_trans (xtext);
+			gtk_xtext_load_trans (xtext);
+		}
+#endif
+		gtk_xtext_render_page (xtext);
+	}
+}
+
+static int
+gtk_xtext_kill_ent (xtext_buffer *buffer, textentry *ent)
+{
+	int visible;
+
+	/* Set visible to TRUE if this is the current buffer */
+	/* and this ent shows up on the screen now */
+	visible = buffer->xtext->buffer == buffer &&
+				 gtk_xtext_check_ent_visibility (buffer->xtext, ent, 0);
+
+	if (ent == buffer->pagetop_ent)
+		buffer->pagetop_ent = NULL;
+
+	if (ent == buffer->last_ent_start)
+	{
+		buffer->last_ent_start = ent->next;
+		buffer->last_offset_start = 0;
+	}
+
+	if (ent == buffer->last_ent_end)
+	{
+		buffer->last_ent_start = NULL;
+		buffer->last_ent_end = NULL;
+	}
+
+	if (buffer->marker_pos == ent) buffer->marker_pos = NULL;
+
+	free (ent);
+	return visible;
+}
+
+/* remove the topline from the list */
+
+static void
+gtk_xtext_remove_top (xtext_buffer *buffer)
+{
+	textentry *ent;
+
+	ent = buffer->text_first;
+	if (!ent)
+		return;
+	buffer->num_lines -= ent->lines_taken;
+	buffer->pagetop_line -= ent->lines_taken;
+	buffer->last_pixel_pos -= (ent->lines_taken * buffer->xtext->fontsize);
+	buffer->text_first = ent->next;
+	if (buffer->text_first)
+		buffer->text_first->prev = NULL;
+	else
+		buffer->text_last = NULL;
+
+	buffer->old_value -= ent->lines_taken;
+	if (buffer->xtext->buffer == buffer)	/* is it the current buffer? */
+	{
+		buffer->xtext->adj->value -= ent->lines_taken;
+		buffer->xtext->select_start_adj -= ent->lines_taken;
+	}
+
+	if (gtk_xtext_kill_ent (buffer, ent))
+	{
+		if (!buffer->xtext->add_io_tag)
+		{
+			/* remove scrolling events */
+			if (buffer->xtext->io_tag)
+			{
+				g_source_remove (buffer->xtext->io_tag);
+				buffer->xtext->io_tag = 0;
+			}
+			buffer->xtext->force_render = TRUE;
+			buffer->xtext->add_io_tag = g_timeout_add (REFRESH_TIMEOUT * 2,
+														(GSourceFunc)
+														gtk_xtext_render_page_timeout,
+														buffer->xtext);
+		}
+	}
+}
+
+static void
+gtk_xtext_remove_bottom (xtext_buffer *buffer)
+{
+	textentry *ent;
+
+	ent = buffer->text_last;
+	if (!ent)
+		return;
+	buffer->num_lines -= ent->lines_taken;
+	buffer->text_last = ent->prev;
+	if (buffer->text_last)
+		buffer->text_last->next = NULL;
+	else
+		buffer->text_first = NULL;
+
+	gtk_xtext_kill_ent (buffer, ent);
+}
+
+/* If lines=0 => clear all */
+
+void
+gtk_xtext_clear (xtext_buffer *buf, int lines)
+{
+	textentry *next;
+
+	if (lines != 0)
+	{
+		if (lines < 0)
+		{
+			/* delete lines from bottom */
+			lines *= -1;
+			while (lines)
+			{
+				gtk_xtext_remove_bottom (buf);
+				lines--;
+			}
+		}
+		else
+		{
+			/* delete lines from top */
+			while (lines)
+			{
+				gtk_xtext_remove_top (buf);
+				lines--;
+			}
+		}
+	}
+	else
+	{
+		/* delete all */
+		if (buf->xtext->auto_indent)
+			buf->indent = MARGIN;
+		buf->scrollbar_down = TRUE;
+		buf->last_ent_start = NULL;
+		buf->last_ent_end = NULL;
+		buf->marker_pos = NULL;
+		dontscroll (buf);
+
+		while (buf->text_first)
+		{
+			next = buf->text_first->next;
+			free (buf->text_first);
+			buf->text_first = next;
+		}
+		buf->text_last = NULL;
+	}
+
+	if (buf->xtext->buffer == buf)
+	{
+		gtk_xtext_calc_lines (buf, TRUE);
+		gtk_xtext_refresh (buf->xtext, 0);
+	} else
+	{
+		gtk_xtext_calc_lines (buf, FALSE);
+	}
+}
+
+static gboolean
+gtk_xtext_check_ent_visibility (GtkXText * xtext, textentry *find_ent, int add)
+{
+	textentry *ent;
+	int lines_max;
+	int line = 0;
+	int width;
+	int height;
+
+	gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &width, &height);
+
+	lines_max = ((height + xtext->pixel_offset) / xtext->fontsize) + add;
+	ent = xtext->buffer->pagetop_ent;
+
+	while (ent && line < lines_max)
+	{
+		if (find_ent == ent)
+			return TRUE;
+		line += ent->lines_taken;
+		ent = ent->next;
+	}
+
+	return FALSE;
+}
+
+void
+gtk_xtext_check_marker_visibility (GtkXText * xtext)
+{
+	if (gtk_xtext_check_ent_visibility (xtext, xtext->buffer->marker_pos, 1))
+		xtext->buffer->marker_seen = TRUE;
+}
+
+textentry *
+gtk_xtext_search (GtkXText * xtext, const gchar *text, textentry *start, gboolean case_match, gboolean backward)
+{
+	textentry *ent, *fent;
+	int line;
+	gchar *str, *nee, *hay;	/* needle in haystack */
+
+	gtk_xtext_selection_clear_full (xtext->buffer);
+	xtext->buffer->last_ent_start = NULL;
+	xtext->buffer->last_ent_end = NULL;
+
+	/* set up text comparand for Case Match or Ignore */
+	if (case_match)
+		nee = g_strdup (text);
+	else
+		nee = g_utf8_casefold (text, strlen (text));
+
+	/* Validate that start gives a currently valid ent pointer */
+	ent = xtext->buffer->text_first;
+	while (ent)
+	{
+		if (ent == start)
+			break;
+		ent = ent->next;
+	}
+	if (!ent)
+		start = NULL;
+
+	/* Choose first ent to look at */
+	if (start)
+		ent = backward? start->prev: start->next;
+	else
+		ent = backward? xtext->buffer->text_last: xtext->buffer->text_first;
+
+	/* Search from there to one end or the other until found */
+	while (ent)
+	{
+		/* If Case Ignore, fold before & free after calling strstr */
+		if (case_match)
+			hay = g_strdup (ent->str);
+		else
+			hay = g_utf8_casefold (ent->str, strlen (ent->str));
+		/* Try to find the needle in this haystack */
+		str = g_strstr_len (hay, strlen (hay), nee);
+		g_free (hay);
+		if (str)
+			break;
+		ent = backward? ent->prev: ent->next;
+	}
+	fent = ent;
+
+	/* Save distance to start, end of found string */
+	if (ent)
+	{
+		ent->mark_start = str - hay;
+		ent->mark_end = ent->mark_start + strlen (nee);
+
+		/* is the match visible? Might need to scroll */
+		if (!gtk_xtext_check_ent_visibility (xtext, ent, 0))
+		{
+			ent = xtext->buffer->text_first;
+			line = 0;
+			while (ent)
+			{
+				line += ent->lines_taken;
+				ent = ent->next;
+				if (ent == fent)
+					break;
+			}
+			while (line > xtext->adj->upper - xtext->adj->page_size)
+				line--;
+			if (backward)
+				line -= xtext->adj->page_size - ent->lines_taken;
+			xtext->adj->value = line;
+			xtext->buffer->scrollbar_down = FALSE;
+			gtk_adjustment_changed (xtext->adj);
+		}
+	}
+
+	g_free (nee);
+	gtk_widget_queue_draw (GTK_WIDGET (xtext));
+
+	return fent;
+}
+
+static int
+gtk_xtext_render_page_timeout (GtkXText * xtext)
+{
+	GtkAdjustment *adj = xtext->adj;
+
+	xtext->add_io_tag = 0;
+
+	/* less than a complete page? */
+	if (xtext->buffer->num_lines <= adj->page_size)
+	{
+		xtext->buffer->old_value = 0;
+		adj->value = 0;
+		gtk_xtext_render_page (xtext);
+	} else if (xtext->buffer->scrollbar_down)
+	{
+		g_signal_handler_block (xtext->adj, xtext->vc_signal_tag);
+		gtk_xtext_adjustment_set (xtext->buffer, FALSE);
+		gtk_adjustment_set_value (adj, adj->upper - adj->page_size);
+		g_signal_handler_unblock (xtext->adj, xtext->vc_signal_tag);
+		xtext->buffer->old_value = adj->value;
+		gtk_xtext_render_page (xtext);
+	} else
+	{
+		gtk_xtext_adjustment_set (xtext->buffer, TRUE);
+		if (xtext->force_render)
+		{
+			xtext->force_render = FALSE;
+			gtk_xtext_render_page (xtext);
+		}
+	}
+
+	return 0;
+}
+
+/* append a textentry to our linked list */
+
+static void
+gtk_xtext_append_entry (xtext_buffer *buf, textentry * ent, time_t stamp)
+{
+	unsigned int mb;
+	int i;
+
+	/* we don't like tabs */
+	i = 0;
+	while (i < ent->str_len)
+	{
+		if (ent->str[i] == '\t')
+			ent->str[i] = ' ';
+		i++;
+	}
+
+	ent->stamp = stamp;
+	if (stamp == 0)
+		ent->stamp = time (0);
+	ent->str_width = gtk_xtext_text_width (buf->xtext, ent->str, ent->str_len, &mb);
+	ent->mb = FALSE;
+	if (mb)
+		ent->mb = TRUE;
+	ent->mark_start = -1;
+	ent->mark_end = -1;
+	ent->next = NULL;
+
+	if (ent->indent < MARGIN)
+		ent->indent = MARGIN;	  /* 2 pixels is the left margin */
+
+	/* append to our linked list */
+	if (buf->text_last)
+		buf->text_last->next = ent;
+	else
+		buf->text_first = ent;
+	ent->prev = buf->text_last;
+	buf->text_last = ent;
+
+	ent->lines_taken = gtk_xtext_lines_taken (buf, ent);
+	buf->num_lines += ent->lines_taken;
+
+	if (buf->reset_marker_pos || 
+		((buf->marker_pos == NULL || buf->marker_seen) && (buf->xtext->buffer != buf || 
+#if GTK_CHECK_VERSION(2,4,0)
+		!gtk_window_has_toplevel_focus (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (buf->xtext)))))))
+#else
+		!(GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (buf->xtext))))->has_focus)))
+#endif
+	{
+		buf->marker_pos = ent;
+		dontscroll (buf); /* force scrolling off */
+		buf->marker_seen = FALSE;
+		buf->reset_marker_pos = FALSE;
+	}
+
+	if (buf->xtext->max_lines > 2 && buf->xtext->max_lines < buf->num_lines)
+	{
+		gtk_xtext_remove_top (buf);
+	}
+
+	if (buf->xtext->buffer == buf)
+	{
+#ifdef SCROLL_HACK
+		/* this could be improved */
+		if ((buf->num_lines - 1) <= buf->xtext->adj->page_size)
+			dontscroll (buf);
+#endif
+
+		if (!buf->xtext->add_io_tag)
+		{
+			/* remove scrolling events */
+			if (buf->xtext->io_tag)
+			{
+				g_source_remove (buf->xtext->io_tag);
+				buf->xtext->io_tag = 0;
+			}
+			buf->xtext->add_io_tag = g_timeout_add (REFRESH_TIMEOUT * 2,
+															(GSourceFunc)
+															gtk_xtext_render_page_timeout,
+															buf->xtext);
+		}
+	} else if (buf->scrollbar_down)
+	{
+		buf->old_value = buf->num_lines - buf->xtext->adj->page_size;
+		if (buf->old_value < 0)
+			buf->old_value = 0;
+	}
+}
+
+/* the main two public functions */
+
+void
+gtk_xtext_append_indent (xtext_buffer *buf,
+								 unsigned char *left_text, int left_len,
+								 unsigned char *right_text, int right_len,
+								 time_t stamp)
+{
+	textentry *ent;
+	unsigned char *str;
+	int space;
+	int tempindent;
+	int left_width;
+
+	if (left_len == -1)
+		left_len = strlen (left_text);
+
+	if (right_len == -1)
+		right_len = strlen (right_text);
+
+	if (right_len >= sizeof (buf->xtext->scratch_buffer))
+		right_len = sizeof (buf->xtext->scratch_buffer) - 1;
+
+	if (right_text[right_len-1] == '\n')
+		right_len--;
+
+	ent = malloc (left_len + right_len + 2 + sizeof (textentry));
+	str = (unsigned char *) ent + sizeof (textentry);
+
+	memcpy (str, left_text, left_len);
+	str[left_len] = ' ';
+	memcpy (str + left_len + 1, right_text, right_len);
+	str[left_len + 1 + right_len] = 0;
+
+	left_width = gtk_xtext_text_width (buf->xtext, left_text, left_len, NULL);
+
+	ent->left_len = left_len;
+	ent->str = str;
+	ent->str_len = left_len + 1 + right_len;
+	ent->indent = (buf->indent - left_width) - buf->xtext->space_width;
+
+	if (buf->time_stamp)
+		space = buf->xtext->stamp_width;
+	else
+		space = 0;
+
+	/* do we need to auto adjust the separator position? */
+	if (buf->xtext->auto_indent && ent->indent < MARGIN + space)
+	{
+		tempindent = MARGIN + space + buf->xtext->space_width + left_width;
+
+		if (tempindent > buf->indent)
+			buf->indent = tempindent;
+
+		if (buf->indent > buf->xtext->max_auto_indent)
+			buf->indent = buf->xtext->max_auto_indent;
+
+		gtk_xtext_fix_indent (buf);
+		gtk_xtext_recalc_widths (buf, FALSE);
+
+		ent->indent = (buf->indent - left_width) - buf->xtext->space_width;
+		buf->xtext->force_render = TRUE;
+	}
+
+	gtk_xtext_append_entry (buf, ent, stamp);
+}
+
+void
+gtk_xtext_append (xtext_buffer *buf, unsigned char *text, int len)
+{
+	textentry *ent;
+
+	if (len == -1)
+		len = strlen (text);
+
+	if (text[len-1] == '\n')
+		len--;
+
+	if (len >= sizeof (buf->xtext->scratch_buffer))
+		len = sizeof (buf->xtext->scratch_buffer) - 1;
+
+	ent = malloc (len + 1 + sizeof (textentry));
+	ent->str = (unsigned char *) ent + sizeof (textentry);
+	ent->str_len = len;
+	if (len)
+		memcpy (ent->str, text, len);
+	ent->str[len] = 0;
+	ent->indent = 0;
+	ent->left_len = -1;
+
+	gtk_xtext_append_entry (buf, ent, 0);
+}
+
+gboolean
+gtk_xtext_is_empty (xtext_buffer *buf)
+{
+	return buf->text_first == NULL;
+}
+
+int
+gtk_xtext_lastlog (xtext_buffer *out, xtext_buffer *search_area,
+						 int (*cmp_func) (char *, void *), void *userdata)
+{
+	textentry *ent = search_area->text_first;
+	int matches = 0;
+
+	while (ent)
+	{
+		if (cmp_func (ent->str, userdata))
+		{
+			matches++;
+			/* copy the text over */
+			if (search_area->xtext->auto_indent)
+				gtk_xtext_append_indent (out, ent->str, ent->left_len,
+												 ent->str + ent->left_len + 1,
+												 ent->str_len - ent->left_len - 1, 0);
+			else
+				gtk_xtext_append (out, ent->str, ent->str_len);
+			/* copy the timestamp over */
+			out->text_last->stamp = ent->stamp;
+		}
+		ent = ent->next;
+	}
+
+	return matches;
+}
+
+void
+gtk_xtext_foreach (xtext_buffer *buf, GtkXTextForeach func, void *data)
+{
+	textentry *ent = buf->text_first;
+
+	while (ent)
+	{
+		(*func) (buf->xtext, ent->str, data);
+		ent = ent->next;
+	}
+}
+
+void
+gtk_xtext_set_error_function (GtkXText *xtext, void (*error_function) (int))
+{
+	xtext->error_function = error_function;
+}
+
+void
+gtk_xtext_set_indent (GtkXText *xtext, gboolean indent)
+{
+	xtext->auto_indent = indent;
+}
+
+void
+gtk_xtext_set_max_indent (GtkXText *xtext, int max_auto_indent)
+{
+	xtext->max_auto_indent = max_auto_indent;
+}
+
+void
+gtk_xtext_set_max_lines (GtkXText *xtext, int max_lines)
+{
+	xtext->max_lines = max_lines;
+}
+
+void
+gtk_xtext_set_show_marker (GtkXText *xtext, gboolean show_marker)
+{
+	xtext->marker = show_marker;
+}
+
+void
+gtk_xtext_set_show_separator (GtkXText *xtext, gboolean show_separator)
+{
+	xtext->separator = show_separator;
+}
+
+void
+gtk_xtext_set_thin_separator (GtkXText *xtext, gboolean thin_separator)
+{
+	xtext->thinline = thin_separator;
+}
+
+void
+gtk_xtext_set_time_stamp (xtext_buffer *buf, gboolean time_stamp)
+{
+	buf->time_stamp = time_stamp;
+}
+
+void
+gtk_xtext_set_tint (GtkXText *xtext, int tint_red, int tint_green, int tint_blue)
+{
+	xtext->tint_red = tint_red;
+	xtext->tint_green = tint_green;
+	xtext->tint_blue = tint_blue;
+
+	/*if (xtext->tint_red != 255 || xtext->tint_green != 255 || xtext->tint_blue != 255)
+		shaded = TRUE;*/
+}
+
+void
+gtk_xtext_set_urlcheck_function (GtkXText *xtext, int (*urlcheck_function) (GtkWidget *, char *, int))
+{
+	xtext->urlcheck_function = urlcheck_function;
+}
+
+void
+gtk_xtext_set_wordwrap (GtkXText *xtext, gboolean wordwrap)
+{
+	xtext->wordwrap = wordwrap;
+}
+
+void
+gtk_xtext_reset_marker_pos (GtkXText *xtext)
+{
+	xtext->buffer->marker_pos = NULL;
+	dontscroll (xtext->buffer); /* force scrolling off */
+	gtk_xtext_render_page (xtext);
+	xtext->buffer->reset_marker_pos = TRUE;
+}
+
+void
+gtk_xtext_buffer_show (GtkXText *xtext, xtext_buffer *buf, int render)
+{
+	int w, h;
+
+	buf->xtext = xtext;
+
+	if (xtext->buffer == buf)
+		return;
+
+/*printf("text_buffer_show: xtext=%p buffer=%p\n", xtext, buf);*/
+
+	if (xtext->add_io_tag)
+	{
+		g_source_remove (xtext->add_io_tag);
+		xtext->add_io_tag = 0;
+	}
+
+	if (xtext->io_tag)
+	{
+		g_source_remove (xtext->io_tag);
+		xtext->io_tag = 0;
+	}
+
+	if (!GTK_WIDGET_REALIZED (GTK_WIDGET (xtext)))
+		gtk_widget_realize (GTK_WIDGET (xtext));
+
+	gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &w, &h);
+
+	/* after a font change */
+	if (buf->needs_recalc)
+	{
+		buf->needs_recalc = FALSE;
+		gtk_xtext_recalc_widths (buf, TRUE);
+	}
+
+	/* now change to the new buffer */
+	xtext->buffer = buf;
+	dontscroll (buf);	/* force scrolling off */
+	xtext->adj->value = buf->old_value;
+	xtext->adj->upper = buf->num_lines;
+	if (xtext->adj->upper == 0)
+		xtext->adj->upper = 1;
+	/* sanity check */
+	else if (xtext->adj->value > xtext->adj->upper - xtext->adj->page_size)
+	{
+		/*buf->pagetop_ent = NULL;*/
+		xtext->adj->value = xtext->adj->upper - xtext->adj->page_size;
+		if (xtext->adj->value < 0)
+			xtext->adj->value = 0;
+	}
+
+	if (render)
+	{
+		/* did the window change size since this buffer was last shown? */
+		if (buf->window_width != w)
+		{
+			buf->window_width = w;
+			gtk_xtext_calc_lines (buf, FALSE);
+			if (buf->scrollbar_down)
+				gtk_adjustment_set_value (xtext->adj, xtext->adj->upper -
+												  xtext->adj->page_size);
+		} else if (buf->window_height != h)
+		{
+			buf->window_height = h;
+			buf->pagetop_ent = NULL;
+			gtk_xtext_adjustment_set (buf, FALSE);
+		}
+
+		gtk_xtext_render_page (xtext);
+		gtk_adjustment_changed (xtext->adj);
+	} else
+	{
+		/* avoid redoing the transparency */
+		xtext->avoid_trans = TRUE;
+	}
+}
+
+xtext_buffer *
+gtk_xtext_buffer_new (GtkXText *xtext)
+{
+	xtext_buffer *buf;
+
+	buf = malloc (sizeof (xtext_buffer));
+	memset (buf, 0, sizeof (xtext_buffer));
+	buf->old_value = -1;
+	buf->xtext = xtext;
+	buf->scrollbar_down = TRUE;
+	buf->indent = xtext->space_width * 2;
+	dontscroll (buf);
+
+	return buf;
+}
+
+void
+gtk_xtext_buffer_free (xtext_buffer *buf)
+{
+	textentry *ent, *next;
+
+	if (buf->xtext->buffer == buf)
+		buf->xtext->buffer = buf->xtext->orig_buffer;
+
+	if (buf->xtext->selection_buffer == buf)
+		buf->xtext->selection_buffer = NULL;
+
+	ent = buf->text_first;
+	while (ent)
+	{
+		next = ent->next;
+		free (ent);
+		ent = next;
+	}
+
+	free (buf);
+}
diff --git a/src/fe-gtk/xtext.h b/src/fe-gtk/xtext.h
new file mode 100644
index 00000000..a37ddc32
--- /dev/null
+++ b/src/fe-gtk/xtext.h
@@ -0,0 +1,275 @@
+#ifndef __XTEXT_H__
+#define __XTEXT_H__
+
+#include <gtk/gtkadjustment.h>
+#ifdef USE_XFT
+#include <X11/Xft/Xft.h>
+#endif
+
+#ifdef USE_SHM
+#include <X11/Xlib.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <X11/extensions/XShm.h>
+#endif
+
+#define GTK_TYPE_XTEXT              (gtk_xtext_get_type ())
+#define GTK_XTEXT(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), GTK_TYPE_XTEXT, GtkXText))
+#define GTK_XTEXT_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_XTEXT, GtkXTextClass))
+#define GTK_IS_XTEXT(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), GTK_TYPE_XTEXT))
+#define GTK_IS_XTEXT_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_XTEXT))
+#define GTK_XTEXT_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_XTEXT, GtkXTextClass))
+
+#define ATTR_BOLD			'\002'
+#define ATTR_COLOR		'\003'
+#define ATTR_BLINK		'\006'
+#define ATTR_BEEP			'\007'
+#define ATTR_HIDDEN		'\010'
+#define ATTR_ITALICS2	'\011'
+#define ATTR_RESET		'\017'
+#define ATTR_REVERSE		'\026'
+#define ATTR_ITALICS		'\035'
+#define ATTR_UNDERLINE	'\037'
+
+/* these match palette.h */
+#define XTEXT_MIRC_COLS 32
+#define XTEXT_COLS 37		/* 32 plus 5 for extra stuff below */
+#define XTEXT_MARK_FG 32	/* for marking text */
+#define XTEXT_MARK_BG 33
+#define XTEXT_FG 34
+#define XTEXT_BG 35
+#define XTEXT_MARKER 36		/* for marker line */
+
+typedef struct _GtkXText GtkXText;
+typedef struct _GtkXTextClass GtkXTextClass;
+typedef struct textentry textentry;
+
+typedef struct {
+	GtkXText *xtext;					/* attached to this widget */
+
+	gfloat old_value;					/* last known adj->value */
+	textentry *text_first;
+	textentry *text_last;
+	guint16 grid_offset[256];
+
+	textentry *last_ent_start;	  /* this basically describes the last rendered */
+	textentry *last_ent_end;	  /* selection. */
+	int last_offset_start;
+	int last_offset_end;
+
+	int last_pixel_pos;
+
+	int pagetop_line;
+	int pagetop_subline;
+	textentry *pagetop_ent;			/* what's at xtext->adj->value */
+
+	int num_lines;
+	int indent;						  /* position of separator (pixels) from left */
+
+	textentry *marker_pos;
+
+	int window_width;				/* window size when last rendered. */
+	int window_height;
+
+	unsigned int time_stamp:1;
+	unsigned int scrollbar_down:1;
+	unsigned int needs_recalc:1;
+	unsigned int grid_dirty:1;
+	unsigned int marker_seen:1;
+	unsigned int reset_marker_pos:1;
+} xtext_buffer;
+
+struct _GtkXText
+{
+	GtkWidget widget;
+
+	xtext_buffer *buffer;
+	xtext_buffer *orig_buffer;
+	xtext_buffer *selection_buffer;
+
+#ifdef USE_SHM
+	XShmSegmentInfo shminfo;
+#endif
+
+	GtkAdjustment *adj;
+	GdkPixmap *pixmap;				/* 0 = use palette[19] */
+	GdkDrawable *draw_buf;			/* points to ->window */
+	GdkCursor *hand_cursor;
+	GdkCursor *resize_cursor;
+
+	int pixel_offset;					/* amount of pixels the top line is chopped by */
+
+	int last_win_x;
+	int last_win_y;
+	int last_win_h;
+	int last_win_w;
+
+	int tint_red;
+	int tint_green;
+	int tint_blue;
+
+	GdkGC *bgc;						  /* backing pixmap */
+	GdkGC *fgc;						  /* text foreground color */
+	GdkGC *light_gc;				  /* sep bar */
+	GdkGC *dark_gc;
+	GdkGC *thin_gc;
+	GdkGC *marker_gc;
+	gulong palette[XTEXT_COLS];
+
+	gint io_tag;					  /* for delayed refresh events */
+	gint add_io_tag;				  /* "" when adding new text */
+	gint scroll_tag;				  /* marking-scroll timeout */
+	gulong vc_signal_tag;        /* signal handler for "value_changed" adj */
+
+	int select_start_adj;		  /* the adj->value when the selection started */
+	int select_start_x;
+	int select_start_y;
+	int select_end_x;
+	int select_end_y;
+
+	int max_lines;
+
+	int col_fore;
+	int col_back;
+
+	int depth;						  /* gdk window depth */
+
+	char num[8];					  /* for parsing mirc color */
+	int nc;							  /* offset into xtext->num */
+
+	textentry *hilight_ent;
+	int hilight_start;
+	int hilight_end;
+
+	guint16 fontwidth[128];	  /* each char's width, only the ASCII ones */
+
+#ifdef USE_XFT
+	XftColor color[XTEXT_COLS];
+	XftColor *xft_fg;
+	XftColor *xft_bg;				/* both point into color[20] */
+	XftDraw *xftdraw;
+	XftFont *font;
+	XftFont *ifont;				/* italics */
+#else
+	struct pangofont
+	{
+		PangoFontDescription *font;
+		PangoFontDescription *ifont;	/* italics */
+		int ascent;
+		int descent;
+	} *font, pango_font;
+	PangoLayout *layout;
+#endif
+
+	int fontsize;
+	int space_width;				  /* width (pixels) of the space " " character */
+	int stamp_width;				  /* width of "[88:88:88]" */
+	int max_auto_indent;
+
+	unsigned char scratch_buffer[4096];
+
+	void (*error_function) (int type);
+	int (*urlcheck_function) (GtkWidget * xtext, char *word, int len);
+
+	int jump_out_offset;	/* point at which to stop rendering */
+	int jump_in_offset;	/* "" start rendering */
+
+	int ts_x;			/* ts origin for ->bgc GC */
+	int ts_y;
+
+	int clip_x;			/* clipping (x directions) */
+	int clip_x2;		/* from x to x2 */
+
+	int clip_y;			/* clipping (y directions) */
+	int clip_y2;		/* from y to y2 */
+
+	/* current text states */
+	unsigned int bold:1;
+	unsigned int underline:1;
+	unsigned int italics:1;
+	unsigned int hidden:1;
+
+	/* text parsing states */
+	unsigned int parsing_backcolor:1;
+	unsigned int parsing_color:1;
+	unsigned int backcolor:1;
+
+	/* various state information */
+	unsigned int moving_separator:1;
+	unsigned int word_or_line_select:1;
+	unsigned int button_down:1;
+	unsigned int hilighting:1;
+	unsigned int dont_render:1;
+	unsigned int dont_render2:1;
+	unsigned int cursor_hand:1;
+	unsigned int cursor_resize:1;
+	unsigned int skip_border_fills:1;
+	unsigned int skip_stamp:1;
+	unsigned int mark_stamp:1;	/* Cut&Paste with stamps? */
+	unsigned int force_stamp:1;	/* force redrawing it */
+	unsigned int render_hilights_only:1;
+	unsigned int in_hilight:1;
+	unsigned int un_hilight:1;
+	unsigned int recycle:1;
+	unsigned int avoid_trans:1;
+	unsigned int force_render:1;
+	unsigned int shm:1;
+	unsigned int color_paste:1; /* CTRL was pressed when selection finished */
+
+	/* settings/prefs */
+	unsigned int auto_indent:1;
+	unsigned int thinline:1;
+	unsigned int transparent:1;
+	unsigned int shaded:1;
+	unsigned int marker:1;
+	unsigned int separator:1;
+	unsigned int wordwrap:1;
+	unsigned int overdraw:1;
+	unsigned int ignore_hidden:1;	/* rawlog uses this */
+};
+
+struct _GtkXTextClass
+{
+	GtkWidgetClass parent_class;
+	void (*word_click) (GtkXText * xtext, char *word, GdkEventButton * event);
+};
+
+GtkWidget *gtk_xtext_new (GdkColor palette[], int separator);
+void gtk_xtext_append (xtext_buffer *buf, unsigned char *text, int len);
+void gtk_xtext_append_indent (xtext_buffer *buf,
+										unsigned char *left_text, int left_len,
+										unsigned char *right_text, int right_len,
+										time_t stamp);
+int gtk_xtext_set_font (GtkXText *xtext, char *name);
+void gtk_xtext_set_background (GtkXText * xtext, GdkPixmap * pixmap, gboolean trans);
+void gtk_xtext_set_palette (GtkXText * xtext, GdkColor palette[]);
+void gtk_xtext_clear (xtext_buffer *buf, int lines);
+void gtk_xtext_save (GtkXText * xtext, int fh);
+void gtk_xtext_refresh (GtkXText * xtext, int do_trans);
+int gtk_xtext_lastlog (xtext_buffer *out, xtext_buffer *search_area, int (*cmp_func) (char *, void *userdata), void *userdata);
+textentry *gtk_xtext_search (GtkXText * xtext, const gchar *text, textentry *start, gboolean case_match, gboolean backward);
+void gtk_xtext_reset_marker_pos (GtkXText *xtext);
+void gtk_xtext_check_marker_visibility(GtkXText *xtext);
+
+gboolean gtk_xtext_is_empty (xtext_buffer *buf);
+typedef void (*GtkXTextForeach) (GtkXText *xtext, unsigned char *text, void *data);
+void gtk_xtext_foreach (xtext_buffer *buf, GtkXTextForeach func, void *data);
+
+void gtk_xtext_set_error_function (GtkXText *xtext, void (*error_function) (int));
+void gtk_xtext_set_indent (GtkXText *xtext, gboolean indent);
+void gtk_xtext_set_max_indent (GtkXText *xtext, int max_auto_indent);
+void gtk_xtext_set_max_lines (GtkXText *xtext, int max_lines);
+void gtk_xtext_set_show_marker (GtkXText *xtext, gboolean show_marker);
+void gtk_xtext_set_show_separator (GtkXText *xtext, gboolean show_separator);
+void gtk_xtext_set_thin_separator (GtkXText *xtext, gboolean thin_separator);
+void gtk_xtext_set_time_stamp (xtext_buffer *buf, gboolean timestamp);
+void gtk_xtext_set_tint (GtkXText *xtext, int tint_red, int tint_green, int tint_blue);
+void gtk_xtext_set_urlcheck_function (GtkXText *xtext, int (*urlcheck_function) (GtkWidget *, char *, int));
+void gtk_xtext_set_wordwrap (GtkXText *xtext, gboolean word_wrap);
+
+xtext_buffer *gtk_xtext_buffer_new (GtkXText *xtext);
+void gtk_xtext_buffer_free (xtext_buffer *buf);
+void gtk_xtext_buffer_show (GtkXText *xtext, xtext_buffer *buf, int render);
+GType gtk_xtext_get_type (void);
+
+#endif