/*
 Copyright (C) 2013 Stephan Haller <nomad@froevel.de>

 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.1 of the License, or (at your option) any later version.

 See the file COPYING for the full license text.
*/

#include "cookie-permission-manager-preferences-window.h"

/* Define this class in GObject system */
G_DEFINE_TYPE(CookiePermissionManagerPreferencesWindow,
				cookie_permission_manager_preferences_window,
				GTK_TYPE_DIALOG)

/* Properties */
enum
{
	PROP_0,

	PROP_MANAGER,

	PROP_LAST
};

static GParamSpec* CookiePermissionManagerPreferencesWindowProperties[PROP_LAST]={ 0, };

/* Private structure - access only by public API if needed */
#define COOKIE_PERMISSION_MANAGER_PREFERENCES_WINDOW_GET_PRIVATE(obj) \
	(G_TYPE_INSTANCE_GET_PRIVATE((obj), TYPE_COOKIE_PERMISSION_MANAGER_PREFERENCES_WINDOW, CookiePermissionManagerPreferencesWindowPrivate))

struct _CookiePermissionManagerPreferencesWindowPrivate
{
	/* Extension related */
	CookiePermissionManager	*manager;
	sqlite3					*database;

	/* Dialog related */
	GtkWidget				*contentArea;
	GtkListStore			*listStore;
	GtkWidget				*list;
	GtkTreeSelection		*listSelection;
	GtkWidget				*editingCombo;
	GtkWidget				*deleteButton;
	GtkWidget				*deleteAllButton;
	GtkWidget				*unknownPolicyCombo;
	GtkWidget				*addDomainEntry;
	GtkWidget				*addDomainPolicyCombo;
	GtkWidget				*addDomainButton;

	gint					signalManagerChangedDatabaseID;
	gint					signalManagerUnknownPolicyID;
	gint					signalUnknownPolicyID;
};

enum
{
	DOMAIN_COLUMN,
	POLICY_COLUMN,
	N_COLUMN
};


/* IMPLEMENTATION: Private variables and methods */

/* "Add domain"-button was pressed */
static void _cookie_permission_manager_preferences_on_add_domain_clicked(CookiePermissionManagerPreferencesWindow *self,
																			gpointer *inUserData)
{
	CookiePermissionManagerPreferencesWindowPrivate	*priv=self->priv;
	gchar											*domain;
	const gchar										*domainStart, *domainEnd;
	gchar											*realDomain;
	GtkTreeIter										policyIter;

	g_return_if_fail(priv->database);

	/* Get domain name entered */
	domain=g_hostname_to_ascii(gtk_entry_get_text(GTK_ENTRY(priv->addDomainEntry)));

	/* Trim whitespaces from start and end of entered domain name */
	domainStart=domain;
	while(*domainStart && g_ascii_isspace(*domainStart)) domainStart++;

	domainEnd=domain+strlen(domain)-1;
	while(*domainEnd && g_ascii_isspace(*domainEnd)) domainEnd--;
	if(domainEnd<=domainStart) return;

	/* Seperate domain name from whitespaces */
	realDomain=g_strndup(domain, domainEnd-domainStart+1);
	if(!realDomain) return;

	/* Get policy from combo box */
	if(gtk_combo_box_get_active_iter(GTK_COMBO_BOX(priv->addDomainPolicyCombo), &policyIter))
	{
		gchar	*sql;
		gchar	*error=NULL;
		gint	success;
		gint	policy;
		gchar	*policyName;

		/* Get policy value to set for domain */
		gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(priv->addDomainPolicyCombo)),
													&policyIter,
													0, &policy,
													1, &policyName,
													-1);

		/* Add domain name and the selected policy to database */
		sql=sqlite3_mprintf("INSERT OR REPLACE INTO policies (domain, value) VALUES ('%q', %d);",
								realDomain,
								policy);
		success=sqlite3_exec(priv->database, sql, NULL, NULL, &error);

		/* Show error message if any */
		if(success==SQLITE_OK)
		{
			gtk_list_store_append(priv->listStore, &policyIter);
			gtk_list_store_set(priv->listStore,
								&policyIter,
								DOMAIN_COLUMN, realDomain,
								POLICY_COLUMN, policyName,
								-1);
		}
			else g_warning(_("SQL fails: %s"), error);


		if(error) sqlite3_free(error);

		/* Free allocated resources */
		sqlite3_free(sql);
	}

	/* Free allocated resources */
	g_free(realDomain);
	g_free(domain);
}

/* Entry containing domain name which may be added to list has changed */
static void _cookie_permission_manager_preferences_on_add_domain_entry_changed(CookiePermissionManagerPreferencesWindow *self,
																				GtkEditable *inEditable)
{
	CookiePermissionManagerPreferencesWindowPrivate	*priv=self->priv;
	gchar											*asciiDomain, *checkAsciiDomain;
	gchar											*asciiDomainStart, *asciiDomainEnd;
	gint											dots;
	gboolean										isValid=FALSE;

	/* Get ASCII representation of domain name entered */
	asciiDomain=g_hostname_to_ascii(gtk_entry_get_text(GTK_ENTRY(priv->addDomainEntry)));

	/* Trim whitespaces from start and end of entered domain name */
	asciiDomainStart=asciiDomain;
	while(*asciiDomainStart && g_ascii_isspace(*asciiDomainStart)) asciiDomainStart++;

	asciiDomainEnd=asciiDomain+strlen(asciiDomain)-1;
	while(*asciiDomainEnd && g_ascii_isspace(*asciiDomainEnd)) asciiDomainEnd--;

	/* We allow only domain names and not cookie domain name so entered name
	 * must not start with a dot
	 */
	checkAsciiDomain=asciiDomainStart;
	isValid=(*asciiDomainStart!='.' && *asciiDomainEnd!='.');

	/* Now check if ASCII domain name is valid (very very simple check)
	 * and contains a hostname besides TLD
	 */
	dots=0;

	while(*checkAsciiDomain &&
			checkAsciiDomain<=asciiDomainEnd &&
			isValid)
	{
		/* Check for dot as (at least the first one) seperates hostname from TLD */
		if(*checkAsciiDomain=='.') dots++;
			else
			{
				/* Check for valid characters in domain name.
				 * Valid domain name can only contain ASCII alphabetic letters,
				 * digits (0-9) and hyphens ('-')
				 */
				isValid=(g_ascii_isalpha(*checkAsciiDomain) ||
							g_ascii_isdigit(*checkAsciiDomain) ||
							*checkAsciiDomain=='-');
			}

		checkAsciiDomain++;
	}

	/* If we have not reached the trimmed end of string something must have gone wrong 
	 * and domain entered is invalid. If domain name entered excluding dots is longer
	 * than 255 character it is also invalid.
	 */
	if(checkAsciiDomain<asciiDomainEnd) isValid=FALSE;
		else if((checkAsciiDomain-asciiDomainStart-dots)>255) isValid=FALSE;

	/* We need at least one dot in domain name (minimum number of dots to seperate
	 * hostname from TLD)
	 */
	isValid=(isValid && dots>0);

	/* Activate "add" button if hostname (equal to domain name here) is valid */
	gtk_widget_set_sensitive(priv->addDomainButton, isValid);

	/* Free allocated resources */
	g_free(asciiDomain);
}

/* Fill domain list with stored policies */
static void _cookie_permission_manager_preferences_window_fill(CookiePermissionManagerPreferencesWindow *self)
{
	CookiePermissionManagerPreferencesWindowPrivate	*priv=self->priv;
	gint											success;
	sqlite3_stmt									*statement=NULL;

	/* Clear tree/list view */
	gtk_list_store_clear(priv->listStore);

	/* If no database is present return here */
	if(!priv->database) return;

	/* Fill list store with policies from database */
	success=sqlite3_prepare_v2(priv->database,
								"SELECT domain, value FROM policies;",
								-1,
								&statement,
								NULL);
	if(statement && success==SQLITE_OK)
	{
		gchar		*domain;
		gint		policy;
		gchar		*policyName;
		GtkTreeIter	iter;

		while(sqlite3_step(statement)==SQLITE_ROW)
		{
			/* Get values */
			domain=(gchar*)sqlite3_column_text(statement, 0);
			policy=sqlite3_column_int(statement, 1);

			switch(policy)
			{
				case COOKIE_PERMISSION_MANAGER_POLICY_ACCEPT:
					policyName=_("Accept");
					break;

				case COOKIE_PERMISSION_MANAGER_POLICY_ACCEPT_FOR_SESSION:
					policyName=_("Accept for session");
					break;

				case COOKIE_PERMISSION_MANAGER_POLICY_BLOCK:
					policyName=_("Block");
					break;

				default:
					policyName=NULL;
					break;
			}

			if(policyName)
			{
				gtk_list_store_append(priv->listStore, &iter);
				gtk_list_store_set(priv->listStore,
									&iter,
									DOMAIN_COLUMN, domain,
									POLICY_COLUMN, policyName,
									-1);
			}
		}
	}
		else g_warning(_("SQL fails: %s"), sqlite3_errmsg(priv->database));

	sqlite3_finalize(statement);
}

/* Database instance in manager changed */
static void _cookie_permission_manager_preferences_window_manager_database_changed(CookiePermissionManagerPreferencesWindow *self,
																					GParamSpec *inSpec,
																					gpointer inUserData)
{
	CookiePermissionManagerPreferencesWindowPrivate	*priv=self->priv;
	CookiePermissionManager							*manager=COOKIE_PERMISSION_MANAGER(inUserData);
	const gchar										*databaseFilename;

	/* Close connection to any open database */
	if(priv->database) sqlite3_close(priv->database);
	priv->database=NULL;

	/* Get pointer to new database and open database */
	g_object_get(manager, "database-filename", &databaseFilename, NULL);
	if(databaseFilename)
	{
		gint										success;

		success=sqlite3_open(databaseFilename, &priv->database);
		if(success!=SQLITE_OK)
		{
			g_warning(_("Could not open database of extenstion: %s"), sqlite3_errmsg(priv->database));

			if(priv->database) sqlite3_close(priv->database);
			priv->database=NULL;
		}
	}

	/* Fill list with new database */
	_cookie_permission_manager_preferences_window_fill(self);

	/* Set up availability of management buttons */
	gtk_widget_set_sensitive(priv->deleteAllButton, priv->database!=NULL);
	gtk_widget_set_sensitive(priv->list, priv->database!=NULL);

	return;
}

/* unknown-policy in manager changed or drop-down changed */
static void _cookie_permission_manager_preferences_window_manager_unknown_policy_changed(CookiePermissionManagerPreferencesWindow *self,
																							GParamSpec *inSpec,
																							gpointer inUserData)
{
	CookiePermissionManagerPreferencesWindowPrivate	*priv=self->priv;
	CookiePermissionManager							*manager=COOKIE_PERMISSION_MANAGER(inUserData);
	CookiePermissionManagerPolicy					policy;

	/* Get new unknown-policy value */
	g_object_get(manager, "unknown-policy", &policy, NULL);

	/* Set value in combobox (blocking signal to avoid loops) */
	g_signal_handler_block(priv->unknownPolicyCombo, priv->signalUnknownPolicyID);
	gtk_combo_box_set_active(GTK_COMBO_BOX(priv->unknownPolicyCombo), policy);
	g_signal_handler_unblock(priv->unknownPolicyCombo, priv->signalUnknownPolicyID);
}

static void _cookie_permission_manager_preferences_window_unknown_policy_changed(CookiePermissionManagerPreferencesWindow *self,
																					gpointer *inUserData)
{
	CookiePermissionManagerPreferencesWindowPrivate	*priv=self->priv;
	CookiePermissionManagerPolicy					policy;
	GtkTreeIter										policyIter;

	if(!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(priv->unknownPolicyCombo), &policyIter))
		return;

	gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(priv->unknownPolicyCombo)),
												&policyIter,
												0, &policy,
												-1);

	/* Get toogle state of widget (but block signal for manager) and set in manager */
	g_signal_handler_block(priv->manager, priv->signalManagerUnknownPolicyID);
	g_object_set(priv->manager, "unknown-policy", policy, NULL);
	g_signal_handler_unblock(priv->manager, priv->signalManagerUnknownPolicyID);
}

static void _cookie_permission_manager_preferences_on_policy_editing_started(CookiePermissionManagerPreferencesWindow *self,
																				GtkCellEditable *editable,
																				gchar *path,
																				gpointer *inUserData)
{
	CookiePermissionManagerPreferencesWindowPrivate	*priv=self->priv;

	priv->editingCombo=NULL;

	if(!GTK_IS_COMBO_BOX(editable)) return;

	priv->editingCombo=GTK_WIDGET(editable);
}

static void _cookie_permission_manager_preferences_on_policy_editing_canceled(CookiePermissionManagerPreferencesWindow *self,
																				gpointer *inUserData)
{
	CookiePermissionManagerPreferencesWindowPrivate	*priv=self->priv;

	priv->editingCombo=NULL;
}

static void _cookie_permission_manager_preferences_on_policy_edited(CookiePermissionManagerPreferencesWindow *self,
																		gchar *path,
																		gchar *newText,
																		gpointer *inUserData)
{
	CookiePermissionManagerPreferencesWindowPrivate	*priv=self->priv;
	gchar											*domain;
	GtkTreeIter										iter;
	GtkTreeIter										policyIter;

	g_return_if_fail(priv->database);

	if (priv->editingCombo == NULL) return;

	gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(priv->listStore), &iter, path);

	gtk_tree_model_get(GTK_TREE_MODEL(priv->listStore),
						&iter,
						DOMAIN_COLUMN, &domain,
						-1);

	/* Get policy from combo box */
	if(gtk_combo_box_get_active_iter(GTK_COMBO_BOX(priv->editingCombo), &policyIter))
	{
		gchar	*sql;
		gchar	*error=NULL;
		gint	success;
		gint	policy;
		gchar	*policyName;

		/* Get policy value to set for domain */
		gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(priv->editingCombo)),
													&policyIter,
													0, &policy,
													1, &policyName,
													-1);

		g_return_if_fail(g_strcmp0(policyName, newText)==0);

		/* Add domain name and the selected policy to database */
		sql=sqlite3_mprintf("UPDATE policies SET value = %d WHERE domain = '%q';",
								policy,
								domain);
		success=sqlite3_exec(priv->database, sql, NULL, NULL, &error);

		/* Show error message if any */
		if(success==SQLITE_OK)
		{
			gtk_list_store_set(priv->listStore,
								&iter,
								POLICY_COLUMN, newText,
								-1);
		}
			else g_warning(_("SQL fails: %s"), error);


		if(error) sqlite3_free(error);

		/* Free allocated resources */
		sqlite3_free(sql);
	}

	priv->editingCombo=NULL;
}

/* Selection in list changed */
void _cookie_permission_manager_preferences_changed_selection(CookiePermissionManagerPreferencesWindow *self,
																GtkTreeSelection *inSelection)
{
	gboolean									selected=(gtk_tree_selection_count_selected_rows(inSelection)>0 ? TRUE: FALSE);

	gtk_widget_set_sensitive(self->priv->deleteButton, selected);
}

/* Delete button was clicked on selection */
void _cookie_permission_manager_preferences_on_delete_selection(CookiePermissionManagerPreferencesWindow *self,
																	GtkButton *inButton)
{
	CookiePermissionManagerPreferencesWindowPrivate	*priv=self->priv;
	GList											*rows, *row, *refs=NULL;
	GtkTreeRowReference								*ref;
	GtkTreeModel									*model=GTK_TREE_MODEL(priv->listStore);
	GtkTreeIter										iter;
	GtkTreePath										*path;
	gchar											*domain;
	gchar											*sql;
	gint											success;
	gchar											*error;

	/* Get selected rows in list and create a row reference because
	 * we will modify the model while iterating through selected rows
	 */
	rows=gtk_tree_selection_get_selected_rows(priv->listSelection, &model);
	for(row=rows; row; row=row->next)
	{
		ref=gtk_tree_row_reference_new(model, (GtkTreePath*)row->data);
		refs=g_list_prepend(refs, ref);
	}
	g_list_foreach(rows,(GFunc)gtk_tree_path_free, NULL);
	g_list_free(rows);

	/* Delete each selected row by its reference */
	for(row=refs; row; row=row->next)
	{
		/* Get domain from selected row */
		path=gtk_tree_row_reference_get_path((GtkTreeRowReference*)row->data);
		gtk_tree_model_get_iter(model, &iter, path);
		gtk_tree_model_get(model, &iter, DOMAIN_COLUMN, &domain, -1);

		/* Delete domain from database */
		sql=sqlite3_mprintf("DELETE FROM policies WHERE domain='%q';", domain);
		success=sqlite3_exec(priv->database,
								sql,
								NULL,
								NULL,
								&error);
		if(success!=SQLITE_OK || error)
		{
			if(error)
			{
				g_critical(_("Failed to execute database statement: %s"), error);
				sqlite3_free(error);
			}
				else g_critical(_("Failed to execute database statement: %s"), sqlite3_errmsg(priv->database));
		}
		sqlite3_free(sql);

		/* Delete row from model */
		gtk_list_store_remove(priv->listStore, &iter);
	}
	g_list_foreach(refs,(GFunc)gtk_tree_row_reference_free, NULL);
	g_list_free(refs);
}

/* Delete all button was clicked */
void _cookie_permission_manager_preferences_on_delete_all(CookiePermissionManagerPreferencesWindow *self,
																	GtkButton *inButton)
{
	CookiePermissionManagerPreferencesWindowPrivate	*priv=self->priv;
	gint											success;
	gchar											*error=NULL;
	GtkWidget										*dialog;
	gint											dialogResponse;

	/* Ask user if he really wants to delete all permissions */
	dialog=gtk_message_dialog_new(GTK_WINDOW(self),
									GTK_DIALOG_MODAL,
									GTK_MESSAGE_QUESTION,
									GTK_BUTTONS_YES_NO,
									_("Do you really want to delete all cookie permissions?"));

	gtk_window_set_title(GTK_WINDOW(dialog), _("Delete all cookie permissions?"));
	gtk_window_set_icon_name(GTK_WINDOW(dialog), GTK_STOCK_PROPERTIES);

	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
												_("This action will delete all cookie permissions. "
												  "You will be asked for permissions again for each web site visited."));

	dialogResponse=gtk_dialog_run(GTK_DIALOG(dialog));
	gtk_widget_destroy(dialog);

	if(dialogResponse==GTK_RESPONSE_NO) return;

	/* Delete all permission */
	success=sqlite3_exec(priv->database,
							"DELETE FROM policies;",
							NULL,
							NULL,
							&error);

	if(success!=SQLITE_OK || error)
	{
		if(error)
		{
			g_critical(_("Failed to execute database statement: %s"), error);
			sqlite3_free(error);
		}
	}

	/* Re-setup list */
	_cookie_permission_manager_preferences_window_fill(self);
}

/* Sorting callbacks */
static gint _cookie_permission_manager_preferences_sort_string_callback(GtkTreeModel *inModel,
																		GtkTreeIter *inLeft,
																		GtkTreeIter *inRight,
																		gpointer inUserData)
{
	gchar		*left, *right;
	gint		column=GPOINTER_TO_INT(inUserData);
	gint		result;

	gtk_tree_model_get(inModel, inLeft, column, &left, -1);
	gtk_tree_model_get(inModel, inRight, column, &right, -1);

	result=g_strcmp0(left, right);

	g_free(left);
	g_free(right);

	return(result);
}

/* IMPLEMENTATION: GObject */

/* Finalize this object */
static void cookie_permission_manager_preferences_window_finalize(GObject *inObject)
{
	CookiePermissionManagerPreferencesWindowPrivate	*priv=COOKIE_PERMISSION_MANAGER_PREFERENCES_WINDOW(inObject)->priv;

	/* Dispose allocated resources */
	if(priv->database) sqlite3_close(priv->database);
	priv->database=NULL;

	if(priv->manager)
	{
		if(priv->signalManagerChangedDatabaseID) g_signal_handler_disconnect(priv->manager, priv->signalManagerChangedDatabaseID);
		priv->signalManagerChangedDatabaseID=0;

		if(priv->signalManagerUnknownPolicyID) g_signal_handler_disconnect(priv->manager, priv->signalManagerUnknownPolicyID);
		priv->signalManagerUnknownPolicyID=0;

		g_object_unref(priv->manager);
		priv->manager=NULL;
	}

	/* Call parent's class finalize method */
	G_OBJECT_CLASS(cookie_permission_manager_preferences_window_parent_class)->finalize(inObject);
}

/* Set/get properties */
static void cookie_permission_manager_preferences_window_set_property(GObject *inObject,
																		guint inPropID,
																		const GValue *inValue,
																		GParamSpec *inSpec)
{
	CookiePermissionManagerPreferencesWindow		*self=COOKIE_PERMISSION_MANAGER_PREFERENCES_WINDOW(inObject);
	CookiePermissionManagerPreferencesWindowPrivate	*priv=self->priv;
	GObject											*manager;

	switch(inPropID)
	{
		/* Construct-only properties */
		case PROP_MANAGER:
			/* Release old manager if available and disconnect signals */
			if(priv->manager)
			{
				if(priv->signalManagerChangedDatabaseID) g_signal_handler_disconnect(priv->manager, priv->signalManagerChangedDatabaseID);
				priv->signalManagerChangedDatabaseID=0;

				if(priv->signalManagerUnknownPolicyID) g_signal_handler_disconnect(priv->manager, priv->signalManagerUnknownPolicyID);
				priv->signalManagerUnknownPolicyID=0;

				g_object_unref(priv->manager);
				priv->manager=NULL;
			}

			/* Set new cookie permission manager and
			 * listen to changes in database property
			 */
			manager=g_value_get_object(inValue);
			if(manager)
			{
				priv->manager=g_object_ref(manager);

				priv->signalManagerChangedDatabaseID=
					g_signal_connect_swapped(priv->manager,
												"notify::database-filename",
												G_CALLBACK(_cookie_permission_manager_preferences_window_manager_database_changed),
												self);
				_cookie_permission_manager_preferences_window_manager_database_changed(self, NULL, priv->manager);

				priv->signalManagerUnknownPolicyID=
					g_signal_connect_swapped(priv->manager,
												"notify::unknown-policy",
												G_CALLBACK(_cookie_permission_manager_preferences_window_manager_unknown_policy_changed),
												self);
				_cookie_permission_manager_preferences_window_manager_unknown_policy_changed(self, NULL, priv->manager);
			}
			break;

		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
			break;
	}
}

static void cookie_permission_manager_preferences_window_get_property(GObject *inObject,
																		guint inPropID,
																		GValue *outValue,
																		GParamSpec *inSpec)
{
	CookiePermissionManagerPreferencesWindow	*self=COOKIE_PERMISSION_MANAGER_PREFERENCES_WINDOW(inObject);

	switch(inPropID)
	{
		case PROP_MANAGER:
			g_value_set_object(outValue, self->priv->manager);
			break;

		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
			break;
	}
}

/* Class initialization
 * Override functions in parent classes and define properties and signals
 */
static void cookie_permission_manager_preferences_window_class_init(CookiePermissionManagerPreferencesWindowClass *klass)
{
	GObjectClass		*gobjectClass=G_OBJECT_CLASS(klass);

	/* Override functions */
	gobjectClass->finalize=cookie_permission_manager_preferences_window_finalize;
	gobjectClass->set_property=cookie_permission_manager_preferences_window_set_property;
	gobjectClass->get_property=cookie_permission_manager_preferences_window_get_property;

	/* Set up private structure */
	g_type_class_add_private(klass, sizeof(CookiePermissionManagerPreferencesWindowPrivate));

	/* Define properties */
	CookiePermissionManagerPreferencesWindowProperties[PROP_MANAGER]=
		g_param_spec_object("manager",
								_("Cookie permission manager"),
								_("Instance of current cookie permission manager"),
								TYPE_COOKIE_PERMISSION_MANAGER,
								G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);

	g_object_class_install_properties(gobjectClass, PROP_LAST, CookiePermissionManagerPreferencesWindowProperties);
}

/* Object initialization
 * Create private structure and set up default values
 */
static void cookie_permission_manager_preferences_window_init(CookiePermissionManagerPreferencesWindow *self)
{
	CookiePermissionManagerPreferencesWindowPrivate		*priv;
	GtkTreeSortable										*sortableList;
	GtkCellRenderer										*renderer;
	GtkTreeViewColumn									*column;
	GtkWidget											*widget;
	gchar												*text;
	gchar												*dialogTitle;
	GtkWidget											*scrolled;
	GtkWidget											*vbox;
	GtkWidget											*hbox;
	gint												width, height;
	GtkListStore										*list;
	GtkTreeIter											listIter;

	priv=self->priv=COOKIE_PERMISSION_MANAGER_PREFERENCES_WINDOW_GET_PRIVATE(self);

	/* Set up default values */
	priv->manager=NULL;

	/* Get content area to add gui controls to */
	priv->contentArea=gtk_dialog_get_content_area(GTK_DIALOG(self));
#ifdef HAVE_GTK3
	vbox=gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
	gtk_box_set_homogeneous(GTK_BOX(vbox), FALSE);
#else
	vbox=gtk_vbox_new(FALSE, 0);
#endif

	/* Set up dialog */
	dialogTitle=_("Configure cookie permission");

	gtk_window_set_title(GTK_WINDOW(self), dialogTitle);
	gtk_window_set_icon_name(GTK_WINDOW(self), GTK_STOCK_PROPERTIES);

	sokoke_widget_get_text_size(GTK_WIDGET(self), "M", &width, &height);
	gtk_window_set_default_size(GTK_WINDOW(self), width*52, -1);

	widget=sokoke_xfce_header_new(gtk_window_get_icon_name(GTK_WINDOW(self)), dialogTitle);
	if(widget) gtk_box_pack_start(GTK_BOX(priv->contentArea), widget, FALSE, FALSE, 0);

	gtk_dialog_add_button(GTK_DIALOG(self), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);

	/* Set up description */
	widget=gtk_label_new(NULL);
	text=g_strdup_printf(_("Below is a list of all web sites and the policy set for them. "
							"You can delete policies by marking the entries and clicking on <i>Delete</i>."
							"You can also add a policy for a domain manually by entering the domain below, "
							"choosing the policy and clicking on <i>Add</i>."));
	gtk_label_set_markup(GTK_LABEL(widget), text);
	g_free(text);
	gtk_label_set_line_wrap(GTK_LABEL(widget), TRUE);
	gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, FALSE, 4);

	/* Set up model for cookie domain list */
	priv->listStore=gtk_list_store_new(N_COLUMN,
										G_TYPE_STRING,	/* DOMAIN_COLUMN */
										G_TYPE_STRING	/* POLICY_COLUMN */);

	sortableList=GTK_TREE_SORTABLE(priv->listStore);
	gtk_tree_sortable_set_sort_func(sortableList,
										DOMAIN_COLUMN,
										(GtkTreeIterCompareFunc)_cookie_permission_manager_preferences_sort_string_callback,
										GINT_TO_POINTER(DOMAIN_COLUMN),
										NULL);
	gtk_tree_sortable_set_sort_func(sortableList,
										POLICY_COLUMN,
										(GtkTreeIterCompareFunc)_cookie_permission_manager_preferences_sort_string_callback,
										GINT_TO_POINTER(POLICY_COLUMN),
										NULL);
	gtk_tree_sortable_set_sort_column_id(sortableList, DOMAIN_COLUMN, GTK_SORT_ASCENDING);

	/* Set up domain addition widgets */
#ifdef HAVE_GTK3
	hbox=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
	gtk_box_set_homogeneous(GTK_BOX(hbox), FALSE);
#else
	hbox=gtk_hbox_new(FALSE, 0);
#endif

	priv->addDomainEntry=gtk_entry_new();
	gtk_entry_set_max_length(GTK_ENTRY(priv->addDomainEntry), 64);
	gtk_container_add(GTK_CONTAINER(hbox), priv->addDomainEntry);
	g_signal_connect_swapped(priv->addDomainEntry, "changed", G_CALLBACK(_cookie_permission_manager_preferences_on_add_domain_entry_changed), self);

	list=gtk_list_store_new(2, G_TYPE_INT, G_TYPE_STRING);
	gtk_list_store_append(list, &listIter);
	gtk_list_store_set(list, &listIter, 0, COOKIE_PERMISSION_MANAGER_POLICY_ACCEPT, 1, _("Accept"), -1);
	gtk_list_store_append(list, &listIter);
	gtk_list_store_set(list, &listIter, 0, COOKIE_PERMISSION_MANAGER_POLICY_ACCEPT_FOR_SESSION, 1, _("Accept for session"), -1);
	gtk_list_store_append(list, &listIter);
	gtk_list_store_set(list, &listIter, 0, COOKIE_PERMISSION_MANAGER_POLICY_BLOCK, 1, _("Block"), -1);

	priv->addDomainPolicyCombo=gtk_combo_box_new_with_model(GTK_TREE_MODEL(list));
	gtk_combo_box_set_active(GTK_COMBO_BOX(priv->addDomainPolicyCombo), 0);
	gtk_container_add(GTK_CONTAINER(hbox), priv->addDomainPolicyCombo);

	renderer=gtk_cell_renderer_text_new();
	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(priv->addDomainPolicyCombo), renderer, TRUE);
	gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(priv->addDomainPolicyCombo), renderer, "text", 1);

	priv->addDomainButton=gtk_button_new_from_stock(GTK_STOCK_ADD);
	gtk_widget_set_sensitive(priv->addDomainButton, FALSE);
	gtk_container_add(GTK_CONTAINER(hbox), priv->addDomainButton);
	g_signal_connect_swapped(priv->addDomainButton, "clicked", G_CALLBACK(_cookie_permission_manager_preferences_on_add_domain_clicked), self);

	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 5);

	/* Set up cookie domain list */
	priv->list=gtk_tree_view_new_with_model(GTK_TREE_MODEL(priv->listStore));

#ifndef HAVE_GTK3
	gtk_widget_set_size_request(priv->list, -1, 300);
#endif

	priv->listSelection=gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->list));
	gtk_tree_selection_set_mode(priv->listSelection, GTK_SELECTION_MULTIPLE);
	g_signal_connect_swapped(priv->listSelection, "changed", G_CALLBACK(_cookie_permission_manager_preferences_changed_selection), self);

	renderer=gtk_cell_renderer_text_new();
	column=gtk_tree_view_column_new_with_attributes(_("Domain"),
													renderer,
													"text", DOMAIN_COLUMN,
													NULL);
	gtk_tree_view_column_set_sort_column_id(column, DOMAIN_COLUMN);
	gtk_tree_view_append_column(GTK_TREE_VIEW(priv->list), column);

	renderer=gtk_cell_renderer_combo_new();
	g_object_set(G_OBJECT(renderer), "model", list, "text-column", 1, "has-entry", false, "editable", true, NULL);
	g_signal_connect_swapped(renderer, "editing-started", G_CALLBACK(_cookie_permission_manager_preferences_on_policy_editing_started), self);
	g_signal_connect_swapped(renderer, "editing-canceled", G_CALLBACK(_cookie_permission_manager_preferences_on_policy_editing_canceled), self);
	g_signal_connect_swapped(renderer, "edited", G_CALLBACK(_cookie_permission_manager_preferences_on_policy_edited), self);
	column=gtk_tree_view_column_new_with_attributes(_("Policy"),
													renderer,
													"text", POLICY_COLUMN,
													NULL);
	gtk_tree_view_column_set_sort_column_id(column, POLICY_COLUMN);
	gtk_tree_view_append_column(GTK_TREE_VIEW(priv->list), column);

	scrolled=gtk_scrolled_window_new(NULL, NULL);
#ifdef HAVE_GTK3
	gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(scrolled), height*10);
#endif
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	gtk_container_add(GTK_CONTAINER(scrolled), priv->list);
	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
	gtk_box_pack_start(GTK_BOX(vbox), scrolled, TRUE, TRUE, 5);

	/* Set up cookie domain list management buttons */
#ifdef HAVE_GTK3
	hbox=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
	gtk_box_set_homogeneous(GTK_BOX(hbox), FALSE);
#else
	hbox=gtk_hbox_new(FALSE, 0);
#endif

	priv->deleteButton=gtk_button_new_from_stock(GTK_STOCK_DELETE);
	gtk_widget_set_sensitive(priv->deleteButton, FALSE);
	gtk_container_add(GTK_CONTAINER(hbox), priv->deleteButton);
	g_signal_connect_swapped(priv->deleteButton, "clicked", G_CALLBACK(_cookie_permission_manager_preferences_on_delete_selection), self);

	priv->deleteAllButton=gtk_button_new_with_mnemonic(_("Delete _all"));
	gtk_button_set_image(GTK_BUTTON(priv->deleteAllButton), gtk_image_new_from_stock(GTK_STOCK_DELETE, GTK_ICON_SIZE_BUTTON));
	gtk_widget_set_sensitive(priv->deleteAllButton, FALSE);
	gtk_container_add(GTK_CONTAINER(hbox), priv->deleteAllButton);
	g_signal_connect_swapped(priv->deleteAllButton, "clicked", G_CALLBACK(_cookie_permission_manager_preferences_on_delete_all), self);

	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 5);

	/* Add "unknown-policy" combo */
#ifdef HAVE_GTK3
	hbox=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
	gtk_box_set_homogeneous(GTK_BOX(hbox), FALSE);
#else
	hbox=gtk_hbox_new(FALSE, 0);
#endif
	widget=gtk_label_new(_("Policy for cookies from domains not in the list: "));
	gtk_container_add(GTK_CONTAINER(hbox), widget);
	
	list=gtk_list_store_new(2, G_TYPE_INT, G_TYPE_STRING);
	gtk_list_store_append(list, &listIter);
	gtk_list_store_set(list, &listIter, 0, COOKIE_PERMISSION_MANAGER_POLICY_UNDETERMINED, 1, _("Ask for a decision"), -1);
	gtk_list_store_append(list, &listIter);
	gtk_list_store_set(list, &listIter, 0, COOKIE_PERMISSION_MANAGER_POLICY_ACCEPT, 1, _("Accept"), -1);
	gtk_list_store_append(list, &listIter);
	gtk_list_store_set(list, &listIter, 0, COOKIE_PERMISSION_MANAGER_POLICY_ACCEPT_FOR_SESSION, 1, _("Accept for session"), -1);
	gtk_list_store_append(list, &listIter);
	gtk_list_store_set(list, &listIter, 0, COOKIE_PERMISSION_MANAGER_POLICY_BLOCK, 1, _("Block"), -1);

	priv->unknownPolicyCombo=gtk_combo_box_new_with_model(GTK_TREE_MODEL(list));
	gtk_combo_box_set_active(GTK_COMBO_BOX(priv->unknownPolicyCombo), 0);
	gtk_container_add(GTK_CONTAINER(hbox), priv->unknownPolicyCombo);

	renderer=gtk_cell_renderer_text_new();
	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(priv->unknownPolicyCombo), renderer, TRUE);
	gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(priv->unknownPolicyCombo), renderer, "text", 1);

	priv->signalUnknownPolicyID=g_signal_connect_swapped(priv->unknownPolicyCombo,
															"changed",
															G_CALLBACK(_cookie_permission_manager_preferences_window_unknown_policy_changed),
															self);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 5);

	/* Finalize setup of content area */
	gtk_box_pack_start(GTK_BOX(priv->contentArea), vbox, TRUE, TRUE, 0);
}

/* Implementation: Public API */

/* Create new object */
GtkWidget* cookie_permission_manager_preferences_window_new(CookiePermissionManager *inManager)
{
	return(g_object_new(TYPE_COOKIE_PERMISSION_MANAGER_PREFERENCES_WINDOW,
							"manager", inManager,
							NULL));
}
