/*
 *  Copyright (C) 2003  Tommi Komulainen
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation. 
 *
 *  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
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "galeon-prefs-dialog.h"

#include "eel-gconf-extensions.h"
#include "galeon-embed-prefs.h"
#include "galeon-embed-shell.h"
#include "galeon-language-editor.h"
#include "galeon-marshal.h"
#include "gul-general.h"
#include "gul-glade.h"
#include "gul-glade-gconf.h"
#include "gul-gobject-misc.h"
#include "gul-gui-option.h"
#include "gul-gui.h"
#include "pixbuf-cache.h"
#include "prefs-strings.h"
#include "hig-alert.h"

#include <glib/gi18n.h>
#include <gtk/gtkcelllayout.h>
#include <gtk/gtkcellrendererpixbuf.h>
#include <gtk/gtkcellrenderertext.h>
#include <gtk/gtkcombobox.h>
#include <gtk/gtkcomboboxentry.h>
#include <gtk/gtkentry.h>
#include <gtk/gtkiconfactory.h>
#include <gtk/gtkliststore.h>
#include <gtk/gtkmain.h>
#include <gtk/gtknotebook.h>
#include <gtk/gtkspinbutton.h>
#include <gtk/gtkstock.h>
#include <gtk/gtktreemodelsort.h>
#include <gtk/gtktreeselection.h>
#include <gtk/gtktreeview.h>
#include <gtk/gtktogglebutton.h>
#include <gtk/gtklabel.h>
#include <libgnome/gnome-config.h>

#include <string.h>

/* name of the proxy configuration program */
#ifndef GNOME_NETWORK_PREFERENCES
#define GNOME_NETWORK_PREFERENCES       "gnome-network-preferences"
#endif

/* delay (ms) before applying changes in font size spinbuttons */
#define FONTS_SPIN_TIMEOUT	500

/* gnome-config keys for saving state */
#define STATE_FONTS_ENCODING "Preferences/fonts_encoding"

/* stock items */
#define GALEON_STOCK_PREFS_GENERAL     "galeon-prefs-general"
#define GALEON_STOCK_PREFS_LANGUAGE    "galeon-prefs-language"
#define GALEON_STOCK_PREFS_FONTS       "galeon-prefs-fonts"
#define GALEON_STOCK_PREFS_TABS        "galeon-prefs-tabs"
#define GALEON_STOCK_PREFS_MOUSE       "galeon-prefs-mouse"
#define GALEON_STOCK_PREFS_WEB_CONTENT "galeon-prefs-web-content"
#define GALEON_STOCK_PREFS_PRIVACY     "galeon-prefs-privacy"
#define GALEON_STOCK_PREFS_NETWORK     "galeon-prefs-network"

struct _GaleonPrefsDialogPrivate {
	guint       timeout_id;
	GtkWidget  *timeout_widget;

	GtkWidget  *general_homepage;

	GtkWidget  *language_entry;
	GtkWidget  *language_editor;

	gboolean    fonts_ignore_updates;
	char       *fonts_encoding;
	GtkWidget  *fonts_serif;
	GtkWidget  *fonts_sans;
	GtkWidget  *fonts_cursive;
	GtkWidget  *fonts_fantasy;
	GtkWidget  *fonts_monospace;
	GtkWidget  *fonts_proportional_size;
	GtkWidget  *fonts_monospace_size;
	GtkWidget  *fonts_minimum_size;
};

enum {
	REQUEST,	/* request a page from PDM, cert manager, etc. */
	LOCATION,	/* request the current location for home page  */
	LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

static void
begin_busy (GaleonPrefsDialog *self)
{
	static GdkCursor *cursor = NULL;

	if (cursor == NULL) cursor = gdk_cursor_new (GDK_WATCH);

	if (!GTK_WIDGET_REALIZED(self)) gtk_widget_realize(GTK_WIDGET(self));

	gdk_window_set_cursor (GTK_WIDGET(self)->window, cursor);
	while (gtk_events_pending ()) gtk_main_iteration ();
}

static void
end_busy (GaleonPrefsDialog *self)
{
	gdk_window_set_cursor (GTK_WIDGET(self)->window, NULL);
}

static void
fill_combo_box_from_array (GtkWidget          *combobox,
			   const GulGuiOption *options,
			   guint               n_options)
{
	gul_gui_option_combobox_populate(GTK_COMBO_BOX(combobox),
					 options, n_options, FALSE);
}

static void
on_combobox_changed (GtkComboBox *combobox, const char *gconf_key)
{
	gchar *value = gul_gui_option_combobox_get_value(combobox);

	if (value && value[0]) {
		eel_gconf_set_integer(gconf_key, atoi(value));
	}
	g_free(value);
}

/** connect_combobox:
 * 
 * Connect combo box options to GConf key.  Specialized version for handling
 * enums-as-ints as inherited from Galeon 1.2.*
 */
static void
connect_combobox(GtkWidget          *combobox,
		 const char         *gconf_key,
		 const GulGuiOption *options,
		 guint               n_options)
{
	int  intval;
	char strval[8];

	g_return_if_fail(GTK_IS_COMBO_BOX(combobox));
	g_return_if_fail(gconf_key != NULL);

	fill_combo_box_from_array(combobox, options, n_options);

	intval = eel_gconf_get_integer(gconf_key);
	g_snprintf(strval, sizeof(strval), "%u", intval);
	gul_gui_option_combobox_set_value (GTK_COMBO_BOX(combobox), strval);

	g_signal_connect_data(G_OBJECT(combobox), "changed",
			      G_CALLBACK(on_combobox_changed),
			      g_strdup(gconf_key), (GClosureNotify)g_free, 0);
}

/************************************************************************
 * General page
 */
static void
galeon_prefs_general_construct (GaleonPrefsDialog *self, GladeXML *gxml)
{
	self->priv->general_homepage =
		glade_xml_get_widget (gxml, "general_homepage_entry");

	gul_glade_gconf_connect_simple (gxml,
		"general_homepage_entry", CONF_GENERAL_HOMEPAGE,
		"general_session",	  CONF_GENERAL_ALWAYS_SAVE_SESSION,
		NULL);

	gul_glade_gconf_connect_radio (gxml, CONF_GENERAL_NEWPAGE_TYPE,
		"general_new_home",  "0",
		"general_new_last",  "1",
		"general_new_blank", "2",
		NULL);
}

/* Glade callbacks (twice to shut up gcc...) */
void on_prefs_general_current_button_clicked (GtkWidget *button, GaleonPrefsDialog *dialog);
void on_prefs_general_reset_button_clicked   (GtkWidget *button, GaleonPrefsDialog *dialog);

void
on_prefs_general_reset_button_clicked (GtkWidget *button, GaleonPrefsDialog *self)
{
	char *homepage;

	g_return_if_fail (GALEON_IS_PREFS_DIALOG(self));
	eel_gconf_unset (CONF_GENERAL_HOMEPAGE);

	/* the above would be sufficient had we connected gconf listeners... */
	homepage = eel_gconf_get_string (CONF_GENERAL_HOMEPAGE);
	gtk_entry_set_text (GTK_ENTRY(self->priv->general_homepage), homepage);
	g_free (homepage);
}

void
on_prefs_general_current_button_clicked (GtkWidget *button, GaleonPrefsDialog *self)
{
	char *homepage;

	homepage = NULL;
	g_signal_emit (G_OBJECT(self), signals[LOCATION], 0, &homepage);
	g_return_if_fail (homepage != NULL);

	gtk_entry_set_text (GTK_ENTRY(self->priv->general_homepage), homepage);
	g_free (homepage);
}

/************************************************************************
 * Language page
 */

static const GulGuiOption language_languages[] = {
	{ N_("Afrikaans"), "ak" },
	{ N_("Albanian"), "sq" },
	{ N_("Arabic"), "ar" },
	{ N_("Azerbaijani"), "az" },
	{ N_("Basque"), "eu" },
	{ N_("Breton"), "br" },
	{ N_("Bulgarian"), "bg" },
	{ N_("Byelorussian"), "be" },
	{ N_("Catalan"), "ca" },
	{ N_("Chinese"), "zh" },
	{ N_("Croatian"), "hr" },
	{ N_("Czech"), "cs" },
	{ N_("Danish"), "da" },
	{ N_("Dutch"), "nl" },
	{ N_("English"), "en" },
	{ N_("English (British)"), "en-GB" },
	{ N_("Esperanto"), "eo" },
	{ N_("Estonian"), "et" },
	{ N_("Faeroese"), "fo" },
	{ N_("Finnish"), "fi" },
	{ N_("French"), "fr" },
	{ N_("Galician"), "gl" },
	{ N_("German"), "de" },
	{ N_("Greek"), "el" },
	{ N_("Hebrew"), "he" },
	{ N_("Hungarian"), "hu" },
	{ N_("Icelandic"), "is" },
	{ N_("Indonesian"), "id" },
	{ N_("Irish"), "ga" },
	{ N_("Italian"), "it" },
	{ N_("Japanese"), "ja" },
	{ N_("Korean"), "ko" },
	{ N_("Latvian"), "lv" },
	{ N_("Lithuanian"), "lt" },
	{ N_("Macedonian"), "mk" },
	{ N_("Malay"), "ms" },
	{ N_("Norwegian/Nynorsk"), "nn" },
	{ N_("Norwegian/Bokmaal"), "nb" },
	{ N_("Norwegian"), "no" },
	{ N_("Occitan"), "oc" },
	{ N_("Polish"), "pl" },
	{ N_("Portuguese"), "pt" },
	{ N_("Portuguese of Brazil"), "pt-BR" },
	{ N_("Romanian"), "ro" },
	{ N_("Russian"), "ru" },
	{ N_("Scottish"), "gd" },
	{ N_("Serbian"), "sr" },
	{ N_("Slovak"), "sk" },
	{ N_("Slovenian"), "sl" },
	{ N_("Spanish"), "es" },
	{ N_("Swedish"), "sv" },
	{ N_("Tamil"), "ta" },
	{ N_("Turkish"), "tr" },
	{ N_("Ukrainian"), "uk" },
	{ N_("Vietnamese"), "vi" },
	{ N_("Walloon"), "wa" },
};

void on_prefs_language_edit_button_clicked (GtkWidget *button, GaleonPrefsDialog *self);

static void
language_entry_update (GtkWidget *entry, GSList *titles)
{
	GString *str;
	GSList  *l;

	g_return_if_fail (GTK_IS_ENTRY(entry));

	str = g_string_new ("");
	for (l = titles; l != NULL; l = l->next)
	{
		const char *title = l->data;

		g_string_append (str, title);
		if (l->next) g_string_append (str, ", ");
	}

	gtk_entry_set_text (GTK_ENTRY(entry), str->str);
	g_string_free (str, TRUE);
}

static char *
language_get_custom_title (const char *value)
{
	g_return_val_if_fail (value != NULL, NULL);
	return g_strdup_printf (_("Custom [%s]"), value);
}

static char *
language_get_title (const char *value)
{
	guint i;

	g_return_val_if_fail (value != NULL, NULL);

	for (i = 0; i < G_N_ELEMENTS(language_languages); i++)
	{
		const GulGuiOption *lang = &language_languages[i];

		if (!strcmp (lang->value, value))
		{
			return g_strdup (_(lang->title));
		}
	}

	return language_get_custom_title (value);
}

static char *
on_language_editor_title (GaleonLanguageEditor *editor, const char *value)
{
	return language_get_custom_title (value);
}

static GSList *
language_get_titles (GSList *values)
{
	GSList *titles, *l;

	titles = NULL;
	for (l = values; l != NULL; l = l->next)
	{
		const char *value = l->data;
		char       *title;

		title = language_get_title (value);
		titles = g_slist_prepend (titles, title);
	}

	return g_slist_reverse (titles);
}

static void
language_entry_update_from_values (GtkWidget *entry, GSList *values)
{
	GSList *titles;

	titles = language_get_titles (values);

	language_entry_update (entry, titles);

	g_slist_foreach (titles, (GFunc)g_free, NULL);
	g_slist_free (titles);
}

static void
on_language_editor_changed (GaleonLanguageEditor *editor, GaleonPrefsDialog *self)
{
	GSList *values;
	GSList *titles;

	galeon_language_editor_get_selected (editor, &values, &titles);

	eel_gconf_set_string_list (CONF_RENDERING_LANGUAGE, values);
	language_entry_update (self->priv->language_entry, titles);

	g_slist_foreach (values, (GFunc)g_free, NULL);
	g_slist_free (values);
	g_slist_foreach (titles, (GFunc)g_free, NULL);
	g_slist_free (titles);
}

void
on_prefs_language_edit_button_clicked (GtkWidget *button, GaleonPrefsDialog *self)
{
	GSList *values;
	gulong  handler_id;
	int     ret;

	g_return_if_fail (GALEON_IS_PREFS_DIALOG(self));

	values = eel_gconf_get_string_list (CONF_RENDERING_LANGUAGE);
	galeon_language_editor_set_selected 
		(GALEON_LANGUAGE_EDITOR(self->priv->language_editor), values);
	g_slist_foreach (values, (GFunc)g_free, NULL);
	g_slist_free (values);

	gtk_window_set_transient_for (GTK_WINDOW(self->priv->language_editor),
			              GTK_WINDOW(self));

	handler_id = g_signal_connect (G_OBJECT(self->priv->language_editor), 
			               "changed",
			               G_CALLBACK(on_language_editor_changed),
				       self);

	while (42)
	{
		ret = gtk_dialog_run (GTK_DIALOG(self->priv->language_editor));
		if (ret != GTK_RESPONSE_HELP) break;

		/* FIXME need docs */
	}

	gtk_widget_hide (self->priv->language_editor);

	g_signal_handler_disconnect (G_OBJECT(self->priv->language_editor),
			             handler_id);
}

static const GulGuiOption language_autodetects[] = {
	{ N_("Off"),                 "0" },
	{ N_("Chinese"),             "1" },
	{ N_("East Asian"),          "2" },
	{ N_("Japanese"),            "3" },
	{ N_("Korean"),              "4" },
	{ N_("Russian"),             "5" },
	{ N_("Simplified Chinese"),  "6" },
	{ N_("Traditional Chinese"), "7" },
	{ N_("Ukrainian"),           "8" },
};

static void
on_language_default_charset_changed (GtkComboBox *combobox, GaleonPrefsDialog *self)
{
	char *value;

	value = gul_gui_option_combobox_get_value (combobox);
	g_return_if_fail (value != NULL);

	eel_gconf_set_string (CONF_LANGUAGE_DEFAULT_CHARSET, value);

	g_free(value);
}

static void
language_fill_charsets (GtkWidget *combobox, GList *charsets)
{
	GList     *l;
	GtkListStore *store;

	g_return_if_fail (GTK_IS_COMBO_BOX(combobox));

	/* 
	 * Adding rows produces a warning if this is not done. *sigh*
	 */
	gtk_widget_realize(combobox);

	gul_gui_option_combobox_populate(GTK_COMBO_BOX(combobox),
					 NULL, 0, TRUE);

	store = GTK_LIST_STORE(gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(
			gtk_combo_box_get_model(GTK_COMBO_BOX(combobox)))));

	for (l = charsets; l != NULL; l = l->next)
	{
		GtkTreeIter iter;
		const GaleonEncodingInfo *info = l->data;

		gtk_list_store_append(store, &iter);
		gtk_list_store_set(store, &iter,
				   OPTION_COL_TITLE, _(info->title),
				   OPTION_COL_VALUE, info->encoding,
				   -1);
	}
}

static void
galeon_prefs_language_construct (GaleonPrefsDialog *self, GladeXML *gxml)
{
	GaleonPrefsDialogPrivate *p = self->priv;
	GSList    *languages;
	GtkWidget *autodetect;
	GtkWidget *defcharset;
	GList     *charsets;
	char      *strval;
	GaleonEncodings *encodings;

	p->language_editor = galeon_language_editor_new ();
	galeon_language_editor_set_available
		(GALEON_LANGUAGE_EDITOR(p->language_editor),
		 language_languages, G_N_ELEMENTS(language_languages));
	g_signal_connect (G_OBJECT(p->language_editor), "title",
			  G_CALLBACK(on_language_editor_title), NULL);

	gul_glade_get_widgets (gxml,
		"language_languages",  &p->language_entry,
		"language_autodetect", &autodetect,
		"language_default",    &defcharset,
		NULL);

	g_return_if_fail (GTK_IS_ENTRY(p->language_entry));
	g_return_if_fail (GTK_IS_COMBO_BOX(autodetect));
	g_return_if_fail (GTK_IS_COMBO_BOX(defcharset));

	/* languages */

	languages = eel_gconf_get_string_list (CONF_RENDERING_LANGUAGE);
	language_entry_update_from_values (p->language_entry, languages);
	g_slist_foreach (languages, (GFunc)g_free, NULL);
	g_slist_free (languages);

	/* autodetect charsets */

	connect_combobox (autodetect, 
			  CONF_LANGUAGE_AUTODETECT_CHARSET,
			  language_autodetects,
			  G_N_ELEMENTS(language_autodetects));

	/* default charsets */
	galeon_embed_shell_get_encodings (embed_shell, &encodings);
	charsets = galeon_encodings_get_encodings (encodings, LG_ALL);
	language_fill_charsets (defcharset, charsets);
	g_list_free (charsets);

	strval = eel_gconf_get_string (CONF_LANGUAGE_DEFAULT_CHARSET);
	gul_gui_option_combobox_set_value (GTK_COMBO_BOX(defcharset), strval);
	g_free (strval);

	g_signal_connect (G_OBJECT(defcharset), "changed",
			  G_CALLBACK(on_language_default_charset_changed), self);
}

/************************************************************************
 * Tabs page
 */
static void
galeon_prefs_tabs_construct (GaleonPrefsDialog *self, GladeXML *gxml)
{
	gul_glade_gconf_connect_simple (gxml,
		"tabs_tabbed", CONF_TABS_TABBED,
		"tabs_jump",   CONF_TABS_TABBED_AUTOJUMP,
		"tabs_popups", CONF_TABS_TABBED_POPUPS,
		"tabs_always", CONF_TABS_TABBED_ALWAYS_SHOW,
		NULL);
}

/************************************************************************
 * Web Content page
 */
void on_prefs_content_popups_manage_button_clicked (GtkWidget *button, GaleonPrefsDialog *self);
void on_prefs_content_images_manage_button_clicked (GtkWidget *button, GaleonPrefsDialog *self);

void
on_prefs_content_popups_manage_button_clicked (GtkWidget *button, GaleonPrefsDialog *self)
{
	g_return_if_fail (GALEON_IS_PREFS_DIALOG(self));
	g_signal_emit (G_OBJECT(self), signals[REQUEST], 0, GALEON_PREFS_MANAGE_POPUPS);
}

void
on_prefs_content_images_manage_button_clicked (GtkWidget *button, GaleonPrefsDialog *self)
{
	g_return_if_fail (GALEON_IS_PREFS_DIALOG(self));
	g_signal_emit (G_OBJECT(self), signals[REQUEST], 0, GALEON_PREFS_MANAGE_IMAGES);
}

static void
galeon_prefs_web_content_construct (GaleonPrefsDialog *self, GladeXML *gxml)
{
	gul_glade_gconf_connect_simple (gxml,
		"content_java",       CONF_FILTERING_JAVA_ENABLED,
		"content_javascript", CONF_FILTERING_JAVASCRIPT_ENABLED,
		"content_popups",     CONF_FILTERING_ALLOW_POPUPS,
		NULL);
	gul_glade_gconf_connect_radio (gxml, CONF_FILTERING_IMAGE_LOADING_TYPE,
		"content_images_all",     "0",
		"content_images_visited", "1",
		"content_images_none",    "2",
		NULL);
	gul_glade_gconf_connect_radio (gxml, CONF_FILTERING_ANIMATE_TYPE,
		"content_animation_repeat", "0",
		"content_animation_once",   "1",
		"content_animation_none",   "2",
		NULL);
}

/************************************************************************
 * Privacy page
 */
void on_prefs_privacy_certificates_manage_button_clicked (GtkWidget *button, GaleonPrefsDialog *self);
void on_prefs_privacy_cookies_manage_button_clicked      (GtkWidget *button, GaleonPrefsDialog *self);
void on_prefs_privacy_passwords_manage_button_clicked    (GtkWidget *button, GaleonPrefsDialog *self);

void
on_prefs_privacy_certificates_manage_button_clicked (GtkWidget *button, GaleonPrefsDialog *self)
{
	g_return_if_fail (GALEON_IS_PREFS_DIALOG(self));
	g_signal_emit (G_OBJECT(self), signals[REQUEST], 0, GALEON_PREFS_MANAGE_CERTIFICATES);
}
void
on_prefs_privacy_cookies_manage_button_clicked (GtkWidget *button, GaleonPrefsDialog *self)
{
	g_return_if_fail (GALEON_IS_PREFS_DIALOG(self));
	g_signal_emit (G_OBJECT(self), signals[REQUEST], 0, GALEON_PREFS_MANAGE_COOKIES);
}
void
on_prefs_privacy_passwords_manage_button_clicked (GtkWidget *button, GaleonPrefsDialog *self)
{
	g_return_if_fail (GALEON_IS_PREFS_DIALOG(self));
	g_signal_emit (G_OBJECT(self), signals[REQUEST], 0, GALEON_PREFS_MANAGE_PASSWORDS);
}

static void
galeon_prefs_privacy_construct (GaleonPrefsDialog *self, GladeXML *gxml)
{
	gul_glade_gconf_connect_radio (gxml, CONF_PERSISTENT_COOKIES_BEHAVIOR,
		"privacy_cookies_all",     "0",
		"privacy_cookies_visited", "1",
		"privacy_cookies_none",    "2",
		NULL);

	gul_glade_gconf_connect_simple (gxml,
		"privacy_cookies_expire", CONF_PERSISTENT_COOKIE_LIFETIME,
		"privacy_cookies_warn",   CONF_PERSISTENT_COOKIE_WARN,
		"privacy_passwords",      CONF_PERSISTENT_PASSWORDS_SAVE,
		NULL);
}

/************************************************************************
 * Fonts page
 */

static const GulGuiOption fonts_encodings[] = {
	{ N_("Arabic"),              "ar"             },
	{ N_("Greek"),               "el"             },
	{ N_("Hebrew"),              "he"             },
	{ N_("Japanese"),            "ja"             },
	{ N_("Korean"),              "ko"             },
	{ N_("Thai"),                "th"             },
	{ N_("Turkish"),             "tr"             },
	{ N_("Baltic"),              "x-baltic"       },
	{ N_("Central European"),    "x-central-euro" },
	{ N_("Cyrillic"),            "x-cyrillic"     },
	{ N_("Devanagari"),	     "x-devanagari"   },
	{ N_("Tamil"),               "x-tamil"        },
	{ N_("Unicode"),             "x-unicode"      },
	{ N_("User Defined"),        "x-user-def"     },
	{ N_("Western"),             "x-western"      },
	{ N_("Simplified Chinese"),  "zh-CN"          },
	{ N_("Traditional Chinese"), "zh-TW"          },
};

static void
fonts_make_gconf_key (char *buf, size_t bufsize,
		      const char *type, const char *encoding)
{
	g_snprintf (buf, bufsize, "%s_%s_%s", CONF_RENDERING_FONT, type, encoding);
}

static void
fonts_load_font (GaleonPrefsDialog *self, const char *type, GtkWidget *combo)
{
	GaleonPrefsDialogPrivate *p = self->priv;
	char     gconf_key[256];
	char    *font;
	GList   *fonts = NULL, *rest = NULL, *l;
	gint index = -1;
	gresult  rv;

	g_return_if_fail (GTK_IS_COMBO_BOX(combo));

	/* list all available fonts, show first those that are "good" for the
	 * given encoding, after those show the remaining fonts */
	rv = galeon_embed_shell_get_font_list (embed_shell, p->fonts_encoding,
			                       type, &fonts, NULL);
	g_return_if_fail (G_OK == rv);

	rv = galeon_embed_shell_get_font_list (embed_shell, "", type,
			                       &rest, NULL);
	g_return_if_fail (G_OK == rv);

	for (l = rest; l != NULL; /* nothing */)
	{
		GList *next = l->next;

		if (g_list_find_custom (fonts, l->data, (GCompareFunc)strcmp))
		{
			g_free (l->data);
			rest = g_list_delete_link (rest, l);
		}
		l = next;
	}

	fonts = g_list_concat (fonts, rest);

	fonts_make_gconf_key (gconf_key, sizeof(gconf_key),
			      type, p->fonts_encoding);
	font = eel_gconf_get_string (gconf_key);
	if (font == NULL || !g_list_find_custom (fonts, font,
				                 (GCompareFunc)strcmp))
	{
		g_free (font);
		font = g_strdup (fonts != NULL ? fonts->data : "");
	}

	/* Clear out the old list before populating the new one */
	gtk_list_store_clear(GTK_LIST_STORE(
			gtk_combo_box_get_model(GTK_COMBO_BOX(combo))));

	if (fonts == NULL)
	{
		gtk_widget_set_sensitive (combo, FALSE);
	}
	else
	{
		gint i = 0;

		gtk_widget_set_sensitive (combo, TRUE);
		for (l = fonts; l; l = l->next, i++)
		{
			gtk_combo_box_append_text(GTK_COMBO_BOX(combo),
						  (char *)l->data);
			if (strcmp(font, (char *)l->data) == 0)
			{
				index = i;
			}
		}
	}
	g_list_foreach (fonts, (GFunc)g_free, NULL);
	g_list_free (fonts);

	/* 
	 * If nothing was active, index defaults to -1 which does
	 * the right now and selects nothing.
	 */
	gtk_combo_box_set_active(GTK_COMBO_BOX(combo), index);
	g_free (font);
}

static void
fonts_load_font_size (GaleonPrefsDialog *self,
		      const char        *type,
		      GtkWidget         *spin_button,
		      int                default_size)
{
	GaleonPrefsDialogPrivate *p = self->priv;
	char gconf_key[256];
	int  size;

	g_return_if_fail (GTK_IS_SPIN_BUTTON(spin_button));

	fonts_make_gconf_key (gconf_key, sizeof(gconf_key),
			      type, p->fonts_encoding);
	size = eel_gconf_get_integer (gconf_key);
	if (size == 0 && default_size > 0)
	{
		size = default_size;
	}

	gtk_spin_button_set_value (GTK_SPIN_BUTTON(spin_button), size);
}

static void
fonts_update_widgets (GaleonPrefsDialog *self)
{
	GaleonPrefsDialogPrivate *p = self->priv;

	/* this is going to take a while... */
	begin_busy (self);

	p->fonts_ignore_updates = TRUE;

	fonts_load_font (self, "serif",      p->fonts_serif);
	fonts_load_font (self, "sans-serif", p->fonts_sans);
	fonts_load_font (self, "cursive",    p->fonts_cursive);
	fonts_load_font (self, "fantasy",    p->fonts_fantasy);
	fonts_load_font (self, "monospace",  p->fonts_monospace);

	fonts_load_font_size (self, "variable_size",   p->fonts_proportional_size, 11);
	fonts_load_font_size (self, "monospace_size",  p->fonts_monospace_size,    10);
	fonts_load_font_size (self, "minimum_size",    p->fonts_minimum_size,      -1);

	p->fonts_ignore_updates = FALSE;

	end_busy (self);
}

static void
on_fonts_encoding_changed (GtkComboBox *combobox, GaleonPrefsDialog *self)
{
	GaleonPrefsDialogPrivate *p = self->priv;

	p->fonts_encoding = gul_gui_option_combobox_get_value (combobox);
	fonts_update_widgets (self);

	gnome_config_set_string (STATE_FONTS_ENCODING, p->fonts_encoding);
}

static void
fonts_set_type_in_combo(GtkWidget *combo, const char *value)
{
	g_return_if_fail(GTK_IS_COMBO_BOX(combo));
	g_object_set_data(G_OBJECT(combo), "fonts-combo::type",
	                  (gpointer)value);
}

static const char *
fonts_get_type_from_combo(GtkWidget *combo)
{
	g_return_val_if_fail(GTK_IS_COMBO_BOX(combo), NULL);
	return g_object_get_data(G_OBJECT(combo), "fonts-combo::type");
}

static void
on_fonts_combo_changed(GtkWidget *combo, GaleonPrefsDialog *self)
{
	GaleonPrefsDialogPrivate *p = self->priv;
	const char *type, *font;
	char        gconf_key[256];
	GtkTreeIter iter;
	GtkTreeModel *model;

	g_return_if_fail(GTK_IS_COMBO_BOX(combo));

	if (p->fonts_ignore_updates) return;

	type = fonts_get_type_from_combo(combo);
	g_return_if_fail (type != NULL);

	model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
	if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter))
	{
		gtk_tree_model_get(model, &iter,
		                   0, &font,
		                   -1);
	        g_return_if_fail(font || font[0]);
	}

	fonts_make_gconf_key(gconf_key, sizeof(gconf_key),
			     type, p->fonts_encoding);
	eel_gconf_set_string(gconf_key, font);
}

static void
fonts_connect_combo (GaleonPrefsDialog *self, GladeXML *gxml, 
		     const char *type,
		     const char *widget_name, GtkWidget **widget)
{
	*widget = glade_xml_get_widget(gxml, widget_name);
	g_return_if_fail(GTK_IS_COMBO_BOX(*widget));

	/*
	 * HACK: Glade 2.5.x doesn't yet allow setting useful
	 * properties on a GtkComboBoxEntry or the entry child
	 * so we have to do it in code.
	 */
	fonts_set_type_in_combo(*widget, type);

	g_signal_connect(G_OBJECT(*widget), "changed",
			 G_CALLBACK(on_fonts_combo_changed), self);
}

static void
fonts_set_type_in_spinbutton(GtkWidget *spin, const char *value)
{
	g_return_if_fail(GTK_IS_SPIN_BUTTON(spin));
	g_object_set_data(G_OBJECT(spin), "fonts-spinbutton::type",
			  (gpointer)value);
}

static const char *
fonts_get_type_from_spinbutton(GtkWidget *spin)
{
	g_return_val_if_fail(GTK_IS_SPIN_BUTTON(spin), NULL);
	return g_object_get_data(G_OBJECT(spin), "fonts-spinbutton::type");
}

static gboolean
fonts_timeout_func (gpointer data)
{
	GaleonPrefsDialog *self = data;
	GaleonPrefsDialogPrivate *p = self->priv;
	GtkWidget  *widget = p->timeout_widget;
	const char *type;
	char        gconf_key[256];
	int         size;

	g_return_val_if_fail (GTK_IS_SPIN_BUTTON(widget), FALSE);

	type = fonts_get_type_from_spinbutton(widget);
	g_return_val_if_fail (type != NULL, FALSE);

	size = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON(widget));
	fonts_make_gconf_key (gconf_key, sizeof(gconf_key),
			      type, p->fonts_encoding);
	eel_gconf_set_integer (gconf_key, size);

	p->timeout_id = 0;
	p->timeout_widget = NULL;

	return FALSE;
}

static void
fonts_timeout_start (GaleonPrefsDialog *self, GtkWidget *widget)
{
	GaleonPrefsDialogPrivate *p = self->priv;

	g_return_if_fail (p->timeout_id == 0);

	p->timeout_widget = widget;
	p->timeout_id = 
		g_timeout_add (FONTS_SPIN_TIMEOUT, fonts_timeout_func, self);
}

static void
fonts_timeout_stop (GaleonPrefsDialog *self, gboolean update)
{
	GaleonPrefsDialogPrivate *p = self->priv;

	if (p->timeout_id) 
	{
		g_source_remove (p->timeout_id);
		if (update)
		{
			fonts_timeout_func (self);
		}
	}
	p->timeout_id = 0;
	p->timeout_widget = NULL;
}

static void
on_fonts_spinbutton_changed (GtkWidget *spin, GaleonPrefsDialog *self)
{
	GaleonPrefsDialogPrivate *p = self->priv;
	const char *type;

	g_return_if_fail(GTK_IS_SPIN_BUTTON(spin));

	if (p->fonts_ignore_updates) return;

	type = fonts_get_type_from_spinbutton(spin);
	g_return_if_fail(type != NULL);

	/* we have to wait for a while before updating the gconf or the
	 * spinbuttons will be ridiculously unresponsive thanks to
	 * mozilla taking its time to reflow the page
	 */
	fonts_timeout_stop(self, FALSE);
	fonts_timeout_start(self, spin);
}

static void
fonts_connect_spin_button (GaleonPrefsDialog *self, GladeXML *gxml, 
		           const char *type,
			   const char *widget_name, GtkWidget **widget)
{
	*widget = glade_xml_get_widget(gxml, widget_name);
	g_return_if_fail(GTK_IS_SPIN_BUTTON(*widget));

	fonts_set_type_in_spinbutton(*widget, type);

	g_signal_connect(G_OBJECT(*widget), "value-changed",
			 G_CALLBACK(on_fonts_spinbutton_changed), self);
}

static void
galeon_prefs_fonts_construct (GaleonPrefsDialog *self, GladeXML *gxml)
{
	GaleonPrefsDialogPrivate *p = self->priv;
	GtkWidget *encoding;
	char      *tmp;

	gul_glade_size_group_widgets (gxml, GTK_SIZE_GROUP_HORIZONTAL,
		"fonts_proportional_label",
		"fonts_encoding_label",
		NULL);

	gul_glade_gconf_connect_radio (gxml, CONF_RENDERING_DEFAULT_FONT,
		"fonts_proportional_serif", "0",
		"fonts_proportional_sans",  "1",
		NULL);

	fonts_connect_combo (self, gxml, "serif",      "fonts_serif",
			     &p->fonts_serif);
	fonts_connect_combo (self, gxml, "sans-serif", "fonts_sans",
			     &p->fonts_sans);
	fonts_connect_combo (self, gxml, "cursive",    "fonts_cursive",
			     &p->fonts_cursive);
	fonts_connect_combo (self, gxml, "fantasy",    "fonts_fantasy",
			     &p->fonts_fantasy);
	fonts_connect_combo (self, gxml, "monospace",  "fonts_monospace",
			     &p->fonts_monospace);

	fonts_connect_spin_button (self, gxml, "variable_size",   
			           "fonts_proportional_size",
				   &p->fonts_proportional_size);
	fonts_connect_spin_button (self, gxml, "monospace_size",
			           "fonts_monospace_size",
				   &p->fonts_monospace_size);
	fonts_connect_spin_button (self, gxml, "minimum_size",
			           "fonts_minimum_size",
				   &p->fonts_minimum_size);

	encoding = glade_xml_get_widget (gxml, "fonts_encoding");
	fill_combo_box_from_array (encoding,
			           fonts_encodings,
				   G_N_ELEMENTS(fonts_encodings));

	tmp = gnome_config_get_string (STATE_FONTS_ENCODING "=x-western");
	gul_gui_option_combobox_set_value (GTK_COMBO_BOX(encoding), tmp);
	g_free (tmp);

	g_signal_connect (G_OBJECT(encoding), "changed",
			  G_CALLBACK(on_fonts_encoding_changed), self);
	on_fonts_encoding_changed(GTK_COMBO_BOX(encoding), self);

	gul_glade_gconf_connect_simple (gxml,
		"fonts_always", CONF_RENDERING_USE_OWN_FONTS,
		NULL);
}

/************************************************************************
 * Mouse page
 */
static void
on_toggle_button_toggled_invert (GtkToggleButton *button, const char *gconf_key)
{
	eel_gconf_set_boolean (gconf_key, !gtk_toggle_button_get_active (button));
}

static const GulGuiOption mouse_middle_actions[] = {
//	{ N_("Bookmarks menu"),           NULL /* "0" */ },
	{ N_("Paste URL in new tab"),     "1" },
//	{ N_("Go Back"),                  NULL /* "2" */ },
	{ N_("Gestures"),                 "3" },
	{ N_("Automatic scroll"),         "4" },
	{ N_("Manual scroll"),            "5" },
	{ N_("Paste URL in current tab"), "6" },
};

static void
galeon_prefs_mouse_construct (GaleonPrefsDialog *self, GladeXML *gxml)
{
	GtkWidget *widget;

	/* middle button */

	widget = glade_xml_get_widget (gxml, "mouse_middle");
	connect_combobox (widget,
			  CONF_MOUSE_MIDDLE_BUTTON_ACTION,
			  mouse_middle_actions,
			  G_N_ELEMENTS(mouse_middle_actions));

	/* right button */

	gul_glade_gconf_connect_radio (gxml, CONF_MOUSE_RIGHT_BUTTON_ACTION,
		"mouse_right_context", "0",
		"mouse_right_gesture", "1",
		NULL);

	/* wheel */

	gul_glade_group_sensitive (gxml,
		"mouse_wheel_lines", GUL_SENSITIVE_FOLLOW,
		"mouse_wheel_override_hbox", NULL);
	gul_glade_group_sensitive (gxml,
		"mouse_wheel_override", GUL_SENSITIVE_FOLLOW,
		"mouse_wheel_num_lines_hbox", NULL);

	gul_glade_gconf_connect_radio (gxml, CONF_MOUSE_WHEEL_NOKEY_ACTION,
		"mouse_wheel_page",  "1",
		"mouse_wheel_lines", "0",
		NULL);
	gul_glade_gconf_connect_simple (gxml,
		"mouse_wheel_num_lines", CONF_MOUSE_WHEEL_NOKEY_STEP,
		NULL);

	widget = glade_xml_get_widget (gxml, "mouse_wheel_override");
	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(widget),
		!eel_gconf_get_boolean (CONF_MOUSE_WHEEL_NOKEY_DEF_STEP));
	g_signal_connect (G_OBJECT(widget), "toggled",
			  G_CALLBACK(on_toggle_button_toggled_invert),
			  CONF_MOUSE_WHEEL_NOKEY_DEF_STEP);
}

/************************************************************************
 * Network page
 */
void on_prefs_network_clear_cache_button_clicked (GtkWidget *button, GaleonPrefsDialog *self);
void on_prefs_network_proxy_button_clicked (GtkWidget *button, GaleonPrefsDialog *self);

void
on_prefs_network_clear_cache_button_clicked (GtkWidget *button, GaleonPrefsDialog *self)
{
	g_return_if_fail (GALEON_IS_PREFS_DIALOG(self));
	g_signal_emit (G_OBJECT(self), signals[REQUEST], 0, GALEON_PREFS_CLEAR_CACHE);
}

void
on_prefs_network_proxy_button_clicked (GtkWidget *button, GaleonPrefsDialog *self)
{
	GtkWidget *parent;
	GtkWidget *dialog;
	GError 	  *err = NULL;

	if (g_spawn_command_line_async (GNOME_NETWORK_PREFERENCES, &err))
		return;

	parent = gtk_widget_get_toplevel (button);
	dialog = hig_alert_new (GTK_WINDOW(parent),
				GTK_DIALOG_MODAL,
				HIG_ALERT_ERROR,
				_("Cannot start network proxy "
				  "configuration program."),
				err->message,
				GTK_STOCK_OK,
				GTK_RESPONSE_OK,
				NULL);
	g_clear_error (&err);

	gtk_dialog_run (GTK_DIALOG(dialog));
	gtk_widget_destroy (dialog);
}

static void
on_network_cache_size_changed (GtkSpinButton *spinbutton)
{
	int cache_mb;

	cache_mb = gtk_spin_button_get_value_as_int (spinbutton);
	eel_gconf_set_integer (CONF_NETWORK_DISK_CACHE, cache_mb*1024);
}

static void
galeon_prefs_network_construct (GaleonPrefsDialog *self, GladeXML *gxml)
{
	GtkWidget *spinbutton;
	GtkWidget *button;
	GtkWidget *warning;
	char      *path;
	int        size_kb;

	/* proxy */

	gul_glade_get_widgets (gxml,
		"prefs_network_proxy_button", &button,
		"proxy_warning",              &warning,
		NULL);

	path = g_find_program_in_path (GNOME_NETWORK_PREFERENCES);
	if (path != NULL)
	{
		gtk_widget_set_sensitive (button, TRUE);
		gtk_widget_hide (warning);
		g_free (path);
	}
	else
	{
		gtk_widget_set_sensitive (button, FALSE);
		gtk_widget_show (warning);
	}

	/* cache size */

	spinbutton = glade_xml_get_widget (gxml, "network_cache_size");
	g_return_if_fail (GTK_IS_SPIN_BUTTON(spinbutton));

	size_kb = eel_gconf_get_integer (CONF_NETWORK_DISK_CACHE);
	gtk_spin_button_set_value (GTK_SPIN_BUTTON(spinbutton), size_kb/1024);

	g_signal_connect (G_OBJECT(spinbutton), "value-changed",
			  G_CALLBACK(on_network_cache_size_changed), NULL);
}

/************************************************************************
 * GaleonPrefsDialog
 */

static GtkDialogClass *parent_class;

/* normally stock item labels should have mnemonics, but unlike with buttons
 * the underline would be visible in the treeview
 */
static const GtkStockItem stock_items[] = {
        { GALEON_STOCK_PREFS_GENERAL,     N_("General"),     0, 0, NULL },
        { GALEON_STOCK_PREFS_LANGUAGE,    N_("Language"),    0, 0, NULL },
        { GALEON_STOCK_PREFS_FONTS,       N_("Fonts"),       0, 0, NULL },
        { GALEON_STOCK_PREFS_TABS,        N_("Tabs"),        0, 0, NULL },
        { GALEON_STOCK_PREFS_MOUSE,       N_("Mouse"),       0, 0, NULL },
        { GALEON_STOCK_PREFS_WEB_CONTENT, N_("Web Content"), 0, 0, NULL },
        { GALEON_STOCK_PREFS_PRIVACY,     N_("Privacy"),     0, 0, NULL },
        { GALEON_STOCK_PREFS_NETWORK,     N_("Network"),     0, 0, NULL },
};

/* stock items */

static void
register_stock_icons (void)
{
	static gboolean been_there_done_that = FALSE;
	GtkIconFactory *factory;
	guint           i;

	if (been_there_done_that) return;
	been_there_done_that = TRUE;

	gtk_stock_add (stock_items, G_N_ELEMENTS(stock_items));

	factory = gtk_icon_factory_new ();
	gtk_icon_factory_add_default (factory);

	for (i = 0; i < G_N_ELEMENTS(stock_items); i++)
	{
		const GtkStockItem *item = &stock_items[i];
		GdkPixbuf  *pixbuf;
		GtkIconSet *icon_set;
		char       *fname;

		fname = g_strconcat (item->stock_id, ".png", NULL);
		pixbuf = gul_pixbuf_cache_get (fname);
		g_free (fname);

		icon_set = gtk_icon_set_new_from_pixbuf (pixbuf);
		gtk_icon_factory_add (factory, item->stock_id, icon_set);
		gtk_icon_set_unref (icon_set);

		g_object_unref (pixbuf);
	}

	g_object_unref (factory);
}

/* Sidebar */

enum {
	SIDEBAR_COL_ICON,
	SIDEBAR_COL_TEXT,
	SIDEBAR_COL_PAGE_NUM
};

static const char *sidebar_stock_ids[] = {
	GALEON_STOCK_PREFS_GENERAL,
	GALEON_STOCK_PREFS_LANGUAGE,
	GALEON_STOCK_PREFS_FONTS,
	GALEON_STOCK_PREFS_TABS,
	GALEON_STOCK_PREFS_MOUSE,
	GALEON_STOCK_PREFS_WEB_CONTENT,
	GALEON_STOCK_PREFS_PRIVACY,
	GALEON_STOCK_PREFS_NETWORK,
};

static void
sidebar_construct (GtkWidget *treeview)
{
	GtkTreeViewColumn *column;
	GtkCellRenderer   *renderer;
	GtkListStore      *store;
	guint              i;

	g_return_if_fail (GTK_IS_TREE_VIEW(treeview));

	/* model */

	store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT);

	for (i = 0; i < G_N_ELEMENTS(sidebar_stock_ids); i++)
	{
		GtkStockItem item;
		GtkTreeIter  iter;

		g_assert (gtk_stock_lookup (sidebar_stock_ids[i], &item));

		gtk_list_store_append (store, &iter);
		gtk_list_store_set (store, &iter,
				    SIDEBAR_COL_ICON,     item.stock_id,
				    SIDEBAR_COL_TEXT,     _(item.label),
				    SIDEBAR_COL_PAGE_NUM, i,
				    -1);
	}

	/* view */

	column = gtk_tree_view_column_new ();

	renderer = gtk_cell_renderer_pixbuf_new ();
	gtk_tree_view_column_pack_start (column, renderer, FALSE);
	gtk_tree_view_column_add_attribute (column, renderer,
			                    "stock_id", SIDEBAR_COL_ICON);

	renderer = gtk_cell_renderer_text_new ();
	gtk_tree_view_column_pack_start (column, renderer, TRUE);
	gtk_tree_view_column_add_attribute (column, renderer,
			                    "text", SIDEBAR_COL_TEXT);

	gtk_tree_view_append_column (GTK_TREE_VIEW(treeview), column);

	gtk_tree_view_set_model (GTK_TREE_VIEW(treeview),
			         GTK_TREE_MODEL(store));
}

static void
on_sidebar_selection_changed (GtkTreeSelection *selection,
		              GtkNotebook      *notebook)
{
	GtkTreeModel *model;
	GtkTreeIter   iter;

	if (gtk_tree_selection_get_selected (selection, &model, &iter))
	{
		gint page_num;
		gtk_tree_model_get (model, &iter,
				    SIDEBAR_COL_PAGE_NUM, &page_num,
				    -1);
		gtk_notebook_set_current_page (notebook, page_num);
	}
}

static void
on_notebook_switch_page (GtkNotebook      *notebook,
		         GtkNotebookPage  *page,
			 guint             page_num,
			 GtkTreeSelection *selection)
{
	GtkTreeModel *model;
	GtkTreeIter   iter;

	gtk_tree_selection_get_selected (selection, &model, &iter);
	g_return_if_fail (gtk_tree_model_get_iter_first (model, &iter));

	do {
		guint i;
		gtk_tree_model_get (model, &iter, SIDEBAR_COL_PAGE_NUM, &i, -1);
		if (i == page_num)
		{
			gtk_tree_selection_select_iter (selection, &iter);
			return;
		}
	} while (gtk_tree_model_iter_next (model, &iter));

	g_return_if_reached ();
}

/** sidebar_link_with_notebook:
 * @treeview: a #GtkTreeView
 * @notebook: a #GtkNotebook
 *
 * Link sidebar treeview with the notebook so that the visible page always
 * matches the treeview selection.
 */
static void
sidebar_link_with_notebook (GtkWidget *treeview, GtkWidget *notebook)
{
	GtkTreeSelection *selection;

	g_return_if_fail (GTK_IS_TREE_VIEW(treeview));
	g_return_if_fail (GTK_IS_NOTEBOOK(notebook));

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
	g_signal_connect (selection, "changed",
			  G_CALLBACK(on_sidebar_selection_changed), notebook);
	g_signal_connect (notebook, "switch_page",
			  G_CALLBACK(on_notebook_switch_page), selection);
}

/* GtkDialog */

static void
galeon_prefs_dialog_response (GtkDialog *dialog, gint response_id)
{
	if (response_id == GTK_RESPONSE_HELP)
	{
		gul_gui_help (GTK_WINDOW (dialog), "galeon.xml", "preferences");
		return;
	}

	gtk_widget_destroy (GTK_WIDGET(dialog));
}

/* GObject boilerplate */

GtkWidget *
galeon_prefs_dialog_new (void)
{
	GaleonPrefsDialog *self;

	self = GALEON_PREFS_DIALOG(g_object_new (
			GALEON_TYPE_PREFS_DIALOG, 
			"title",         _("Galeon Preferences"),
			"has-separator", FALSE,
			"resizable",     FALSE,
			"border-width",  6,
			NULL));
	gtk_container_set_border_width (GTK_CONTAINER(GTK_DIALOG(self)->vbox), 0);
	gtk_container_set_border_width (GTK_CONTAINER(GTK_DIALOG(self)->action_area), 6);

	gtk_dialog_add_buttons (GTK_DIALOG(self), 
				GTK_STOCK_HELP,  GTK_RESPONSE_HELP,
			        GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
				NULL);

	return GTK_WIDGET(self);
}

static void
galeon_prefs_dialog_init (GaleonPrefsDialog *self)
{
	GladeXML  *gxml;
	GtkWidget *content;
	GtkWidget *treeview;
	GtkWidget *notebook;

	self->priv = g_new0(GaleonPrefsDialogPrivate, 1);

	self->priv->fonts_encoding = NULL;

	gxml = gul_glade_widget_new ("prefs-dialog.glade", "preferences_dialog", NULL, self);
	g_return_if_fail (gxml != NULL);

	gul_glade_get_widgets (gxml,
	     "content_area",     &content,
	     "sidebar_treeview", &treeview,
	     "notebook",         &notebook,
	     NULL);

	gtk_widget_reparent (content, GTK_DIALOG(self)->vbox);

	sidebar_construct (treeview);
	sidebar_link_with_notebook (treeview, notebook);

	/* Keeping the notebook tabs visible in the glade file makes it easier
	 * to edit, and keeping the HIGgy borders inside the notebook pages
	 * makes it easier to use the notebook standalone.
	 */
	gtk_notebook_set_show_tabs (GTK_NOTEBOOK(notebook), FALSE);
	g_list_foreach (gtk_container_get_children (GTK_CONTAINER(notebook)),
			(GFunc)gtk_container_set_border_width,
			GUINT_TO_POINTER(0));

	gul_glade_size_group_widgets (gxml, GTK_SIZE_GROUP_HORIZONTAL,
		"prefs_content_popups_manage_button",
		"prefs_content_images_manage_button",
		"prefs_privacy_passwords_manage_button",
		"prefs_privacy_cookies_manage_button",
		"prefs_privacy_certificates_manage_button",
		"prefs_network_clear_cache_button",
		NULL);

	galeon_prefs_general_construct (self, gxml);
	galeon_prefs_language_construct (self, gxml);
	galeon_prefs_fonts_construct (self, gxml);
	galeon_prefs_tabs_construct (self, gxml);
	galeon_prefs_mouse_construct (self, gxml);
	galeon_prefs_web_content_construct (self, gxml);
	galeon_prefs_privacy_construct (self, gxml);
	galeon_prefs_network_construct (self, gxml);

	g_object_unref (gxml);
}

static void
galeon_prefs_dialog_destroy (GtkObject *object)
{
	GaleonPrefsDialog *self = GALEON_PREFS_DIALOG(object);

	fonts_timeout_stop (self, TRUE);

	GTK_OBJECT_CLASS(parent_class)->destroy (object);
}

static void
galeon_prefs_dialog_finalize (GObject *object)
{
	GaleonPrefsDialog *self = GALEON_PREFS_DIALOG(object);

	g_free (self->priv->fonts_encoding);
	gtk_widget_destroy (self->priv->language_editor);
	g_free (self->priv);

	G_OBJECT_CLASS(parent_class)->finalize (object);
}

static void
galeon_prefs_dialog_class_init (GaleonPrefsDialogClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS(klass);
	GtkObjectClass *gtkobj_class = GTK_OBJECT_CLASS(klass);
	GtkDialogClass *dialog_class = GTK_DIALOG_CLASS(klass);

	parent_class = g_type_class_peek_parent (klass);

	object_class->finalize = galeon_prefs_dialog_finalize;
	gtkobj_class->destroy = galeon_prefs_dialog_destroy;
	dialog_class->response = galeon_prefs_dialog_response;

	signals[REQUEST] =
		g_signal_new ("request",
			G_OBJECT_CLASS_TYPE (klass),
			G_SIGNAL_RUN_FIRST,
			G_STRUCT_OFFSET (GaleonPrefsDialogClass, request),
			NULL, NULL,
			g_cclosure_marshal_VOID__UINT,
			G_TYPE_NONE, 1,
			G_TYPE_UINT);

	signals[LOCATION] =
		g_signal_new ("location",
			G_OBJECT_CLASS_TYPE (klass),
			G_SIGNAL_RUN_LAST,
			G_STRUCT_OFFSET (GaleonPrefsDialogClass, location),
			NULL, NULL,
			galeon_marshal_STRING__VOID,
			G_TYPE_STRING, 0);

	register_stock_icons ();
}

GUL_TYPE_FUNC(galeon_prefs_dialog, GaleonPrefsDialog, GTK_TYPE_DIALOG)
