/*
 *  Copyright (C) 2000 Marco Pesenti Gritti
 *
 *  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, 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.
 */

/* Galeon includes */
#include "galeon.h"
#include "history.h"
#include "window.h"
#include "bookmarks.h"
#include "autocompletion.h"
#include "misc.h"
#include "state.h"
#include "prefs.h"
#include "glade.h"
#include "dialog.h"
#include "eel-gconf-extensions.h"
#include "mozilla.h"

#include <string.h>
#include <gtk/gtkoptionmenu.h>
#include <gtk/gtkselection.h>
#include <gtk/gtkhbox.h>
#include <gtk/gtktogglebutton.h>
#include <libgnomeui/gtkpixmapmenuitem.h>
#include <libgnomeui/gnome-app.h>
#include <libgnomeui/gnome-preferences.h>
#include <libgnome/gnome-util.h>
#include <libgnome/gnome-config.h>
#include <libgnome/gnome-i18n.h>
#include <libgnomevfs/gnome-vfs.h>
#include <libgnomevfs/gnome-vfs-mime.h>

#include <time.h>

/**
 * The history hash table. A hash table is used because you have to make 
 * an url lookup in history each time you try to add or update a record
 */
static GHashTable *history = NULL;

/**
 * The history hosts hash table. A mapping from hostname to a HistoryHost
 * structure.
 */
static GHashTable *history_hosts = NULL;

/**
 * is the history dirty, i.e. needs saving to disk
 */
static gboolean history_dirty = FALSE;

/**
 * All the currently built HistoryViews
 */
static GList *history_views = NULL;

static GList *history_notifiers_id;

/* sorting functions for each history column */
static const GCompareFunc history_compare[5] =
{
	history_compare_title_cb,
	history_compare_url_cb,
	history_compare_last_cb,
	history_compare_first_cb,
	history_compare_visits_cb
};

/* number of days until we expire history entries (set from prefs) */
static gint expire_days = 100;

/* when parsing using SAX, elements 0 and 1 of the attribute array are
 * the key and value respectively */
enum
{
	SAX_KEY   = 0,
	SAX_VALUE = 1
};

/** Where does the recent menu start? */
#define GO_RECENT_POS 7

/* local function prototypes */
static void history_load (void);
static GtkWidget *history_create_recent_menu_item (HistoryItem *hi, gint num,
						   GtkWidget *menu);
static void history_add_recent_menu (GaleonWindow *window, HistoryItem *hi);
static void history_make_host_strings (HistoryHost *hh, gchar **text);
static void history_make_item_strings (HistoryItem *hi, gchar **text);
static void history_clear_recent_menu (void);
static HistoryItem *history_add_item (gchar *url, gchar *title_utf8, 
				      GTime first, GTime last, gint visits);
static void history_look_for_newer (gpointer key, gpointer value, 
				    gpointer user_data);
static gint calculate_age (GTime last);
static HistoryView *history_build_view (GaleonWindow *window,
					const char *xml_root);
static void history_show_dialog_add_item (gpointer key, gpointer value,
					  gpointer user_data);
static HistoryHost *history_get_host (const gchar *url);
static HistoryHost *history_add_host (const gchar *host);
static void history_fill_ctree (HistoryView *hv);
static void history_destroy_view_structure (HistoryView *hv);
static void history_update_item_views (HistoryItem *hi);
static void history_update_item_view (HistoryView *hv, HistoryItem *hi);
static void history_host_scrub_node (gpointer key, gpointer value,
				     gpointer user_data);
static void history_empty_ctree (HistoryView *hv);
static gboolean history_filter_text (HistoryView *hv, gchar *url);
static gboolean history_filter_date (HistoryView *hv, GTime atime);
static void history_start_element (void *ctx, const xmlChar *fullname, 
				   const xmlChar **atts);
static void history_parse_host_element (const xmlChar **attrs);
static void history_parse_item_element (const xmlChar **attrs);
static void  history_save_host (gpointer key, gpointer value, 
				gpointer user_data);
static void history_save_item (gpointer key, gpointer value,
			       gpointer user_data);
static void eputs (guchar *text, FILE *fp);
static void history_add_server_to_list (gpointer key, HistoryHost *host,
					GList **list);
static void tree_sort (GtkCTree *ctree, GtkCTreeNode *node, gpointer data);
static void local_gtk_ctree_sort_recursive (GtkCTree *ctree,
					    GtkCTreeNode *node);
static gboolean history_item_free_cb (gpointer key, gpointer value, 
				      gpointer user_data);
static void history_drag_data_get_cb (GtkWidget *widget, 
				      GdkDragContext *context,
				      GtkSelectionData *selection_data, 
				      guint info, guint time, 
				      HistoryView *hv);
void 
window_menu_recent_activate_cb (GtkMenuItem *menuitem, gpointer user_data);
void 
window_menu_recent_release_cb (GtkMenuItem *menuitem, GdkEventButton *event,
			       gpointer user_data);
static void history_notifiers_init(HistoryView *hv);

/**
 * history_init: initialise the history subsystem
 */
void
history_init (void)
{
	/* load the history */
	history_load ();

	/* setup the periodic history saving callback */
	g_timeout_add (HISTORY_SAVE_INTERVAL, history_periodic_save_cb, NULL);
}

/**
 * history_exit: shut down the history structures
 */
void
history_exit (void)
{
	/* save the history */
	history_save ();

	/* destroy the views */
	g_list_foreach (history_views, (GFunc)(history_destroy_view_structure),
			NULL);
	g_list_free (history_views);
	history_views = NULL;

	/* destroy hosts hash table */
	g_hash_table_freeze (history_hosts);
	g_hash_table_foreach_remove (history_hosts, (GHRFunc)
				     history_item_free_cb, NULL);
	g_hash_table_thaw (history_hosts);
	g_hash_table_destroy (history_hosts);
	history_hosts = NULL;

	/* destroy history hash table */
	g_hash_table_freeze (history);
	g_hash_table_foreach_remove (history, (GHRFunc)history_item_free_cb,
				     NULL);
	g_hash_table_thaw (history);
	g_hash_table_destroy (history);
	history = NULL;
}

/**
 * history_clear: clear the history
 */
void
history_clear (void)
{
	extern GCompletion *URLCompletion;

	/* destroy hosts hash table */
	g_hash_table_freeze (history_hosts);
	g_hash_table_foreach_remove (history_hosts, (GHRFunc) 
				     history_item_free_cb, NULL);
	g_hash_table_thaw (history_hosts);

	/* destroy history hash table */
	g_hash_table_freeze (history);
	g_hash_table_foreach_remove (history, (GHRFunc)history_item_free_cb, 
				     NULL);
	g_hash_table_thaw (history);

	/* clear the autocompletion history (FIXME refactor) */
	g_completion_clear_items (URLCompletion);

	/* destroy all views */
	g_list_foreach (history_views, (GFunc)history_refresh_view, NULL);

	/* clear the go_menu */
	history_clear_recent_menu ();

	/* clear the file */
	history_save ();
}

/**
 * history_show_dialog: show the history dialog
 */
void 
history_show_dialog (GaleonWindow *window)
{
	HistoryView *hv;

	/* check args */
	return_if_not_window (window);

	/* build and show the history dialog */
	hv = history_build_view (window, "history_dialog");

	state_load_window_state (hv->view, "history");
	state_load_column_widths (hv->ctree, "history");

	/* initialize notifiers */
	history_notifiers_init (hv);

	/* show the widget */
	dialog_set_parent (hv->view, window->WMain);
}

/**
 * history_hide_dialog: hide the history dialog
 */
void
history_hide_dialog (HistoryView *hv)
{
	state_save_window_state (hv->view->window, "history");
	state_save_column_widths (hv->ctree, "history");

	/* remove notifiers */
	history_notifiers_remove ();

	gtk_widget_destroy (GTK_WIDGET (hv->view));
	history_destroy_view (hv);
}

/**
 * history_show_dock: show the history dock
 */
void
history_show_dock (GaleonWindow *window)
{
	HistoryView *hv;

	/* check args */
	return_if_not_window (window);

	/* undock any existing dock */
	window_undock (window);

	/* build and pack the history dock */
	hv = history_build_view (window, "history_dock");

	/* initialize notifiers */
	history_notifiers_init (hv);

	/* add as a dock */
	window_dock (window, hv->view, CONF_STATE_HISTORY_DOCK_WIDTH);

	/* load dock column widths */
	state_load_column_widths (hv->ctree, "history_dock");	

	/* show the widget */
	gtk_widget_show (hv->view);

	/* set dock type */
	window->dock_type = DOCK_HISTORY;

	/* update toolbar buttons */
	window_update_bm_and_hist_buttons (window, FALSE, TRUE);	
}

/**
 * history_refresh_view: update the tree urls, applying current criteria
 */
void
history_refresh_view (HistoryView *hv)
{
	/* stop showing updates */
	gtk_clist_freeze (GTK_CLIST (hv->ctree));

	/* blow away existing tree */
	history_empty_ctree (hv);

	/* destroy hashes */
	if (hv->host_to_node != NULL)
	{
		g_hash_table_destroy (hv->host_to_node);
		g_hash_table_destroy (hv->item_to_node);
	}

	/* build new hashtables */
	hv->host_to_node = g_hash_table_new (NULL, NULL);
	hv->item_to_node = g_hash_table_new (NULL, NULL);

	/* rebuild tree */
	history_fill_ctree (hv);

	/* sort it */
	history_resort_view (hv);

	/* show updated tree */
	gtk_clist_thaw (GTK_CLIST (hv->ctree));
}

/**
 * history_resort_view: sort the ctree in the dialog/dock
 */
void
history_resort_view (HistoryView *hv)
{
	/* do sort */
  	local_gtk_ctree_sort_recursive (GTK_CTREE (hv->ctree), NULL);
}

/**
 * history_destroy_view: destroy a view structure
 */
void
history_destroy_view (HistoryView *hv)
{
	/* remove from list of views */
	history_views = g_list_remove (history_views, hv);

	/* destory structure */
	history_destroy_view_structure (hv);
}

/**
 * history_destroy_view_structure: destroy a history view structure without
 * removing it from the global list (for destroying all of them we need to
 * retain the list structure).
 */
static void
history_destroy_view_structure (HistoryView *hv)
{
	/* free allocated storage */
	g_hash_table_destroy (hv->host_to_node);
	g_hash_table_destroy (hv->item_to_node);
	g_free (hv->search);

	/* blank structure */
	memset (hv, 0, sizeof (HistoryView));
	
	/* free it */
	g_free (hv);
}

/**
 * history_is_visited: test if the URL is already in the history
 */
gboolean
history_is_visited (const gchar *url)
{
        return (g_hash_table_lookup (history, url) != NULL);
}

/**
 * history_visited: adds (or updates) a history item
 */
void
history_visited (const gchar *url)
{
        HistoryItem *hi;
        GTime now;

	/* check arguments */
	g_return_if_fail (url != NULL);

	/* don't add blank pages into history */
	if (!strcmp (url, "about:blank"))
	{
		return;
	}

	/* get current time */
        now = time (NULL);

	/* lookup in history */
        hi = g_hash_table_lookup (history, url);
        if (hi != NULL)
	{
		/* update other info */
                hi->last = now;
                hi->visits++;
		
		/* update host data as well */
		hi->host->last = now;
		hi->host->visits++;
		if (hi->visits > hi->host->dominant_item->visits)
		{
			hi->host->dominant_item = hi;
		}
        }
	else
	{
		/* create a new item to add to the view */
		hi = history_add_item (g_strdup (url), 
				       g_strdup (_("Untitled")),
				       now, now, 1);
        }

	/* update all the views */
	history_update_item_views (hi);

	/* we have changed the history, so save at next checkpoint */
	history_dirty = TRUE;
}

/**
 * history_visited: called to update the title of a page in the history
 */
void
history_set_page_title (const gchar *url, 
			gchar *title_locale,
			gchar *title_utf8)
{
	HistoryItem *hi;

	/* lookup in history */
        hi = g_hash_table_lookup (history, url);

	/* check its there */
	if (hi == NULL)
	{
		/* FIXME: this certainly happens, should we add the
		 * URL to the history at this point? I don't know -MattA */
		return;
	}

	/* check supplied title */
	g_return_if_fail (title_locale != NULL);
	g_return_if_fail (title_utf8 != NULL);
	if (strcmp (title_utf8, hi->title_utf8) == 0)
	{
		/* identical */
		/* add to the Go menu of each window (FIXME?) */
		g_list_foreach (all_windows, (GFunc)history_add_recent_menu, 
				hi);
		
		g_free (title_locale);
		g_free (title_utf8);
		return;
	}
      
	/* set utf8 title */
	g_free (hi->title_utf8);
	hi->title_utf8 = title_utf8;

        /* free old locale title */
	if (hi->title_locale != NULL)
	{
		g_free (hi->title_locale);
	}

	/* store new one if different */
	if (strcmp (title_utf8, title_locale) != 0)
	{
		hi->title_locale = title_locale;
	}
	else
	{
		hi->title_locale = NULL;
		g_free (title_locale);
	}

	/* update all the views */
	history_update_item_views (hi);

	/* add to the Go menu of each window (FIXME?) */
	g_list_foreach (all_windows, (GFunc)history_add_recent_menu, hi);

	/* we have changed the history, so save at next checkpoint */
	history_dirty = TRUE;
}

/**
 * history_build_view: build a history widget (either docked or dialoged)
 */
static HistoryView *
history_build_view (GaleonWindow *window, const char *xml_root)
{
	HistoryView *hv = g_new0 (HistoryView, 1);
	GladeXML *gxml;

	/* widgets to be looked up */
	WidgetLookup lookup_table[] =
	{
		{ "history_ctree",              &(hv->ctree)              },
		{ "history_host_checkbutton",   &(hv->host_checkbutton)   },
		{ "history_time_optionmenu",    &(hv->time_optionmenu)    },
		{ "history_entry",              &(hv->search_entry)       },
		{ "history_dock_close_button",  &(hv->dock_close_button)  },
		{ "history_dock_detach_button", &(hv->dock_detach_button) },
		{ NULL, NULL } /* terminator, must be last */
	};

	/* build the widgets */
	gxml = glade_widget_new ("galeon.glade", xml_root, &(hv->view), hv);
	hv->window = window;

	/* lookup useful widgets and dispose of used XML structure */
        lookup_widgets (gxml, lookup_table);
        gtk_object_unref (GTK_OBJECT (gxml));

	/* only for dock... */
	if (hv->dock_close_button != NULL)
	{
		/* fix up glade brokeness */
		gtk_button_set_relief (GTK_BUTTON (hv->dock_close_button),
				       GTK_RELIEF_NONE);
		gtk_button_set_relief (GTK_BUTTON (hv->dock_detach_button),
				       GTK_RELIEF_NONE);
	}

	/* get the last state */
	hv->group = eel_gconf_get_boolean (CONF_HISTORY_HOST_GROUP);
	hv->search = eel_gconf_get_string (CONF_HISTORY_SEARCH_TEXT);
	if (hv->search == NULL)
		hv->search = g_strdup("");
	hv->time = eel_gconf_get_integer (CONF_HISTORY_SEARCH_TIME);

	/* set in the dialog */
	GTK_TOGGLE_BUTTON (hv->host_checkbutton)->active = hv->group;
	gtk_entry_set_text (GTK_ENTRY (hv->search_entry), hv->search);

	gtk_option_menu_set_history (GTK_OPTION_MENU (hv->time_optionmenu),
				     hv->time);

	/* connect the optionmenu deactivate signal */
	gtk_signal_connect
		(GTK_OBJECT (GTK_OPTION_MENU (hv->time_optionmenu)->menu),
		 "deactivate", history_time_optionmenu_deactivate_cb, hv);

	/* setup drag and drop for dragging from the history */
	gtk_signal_connect (GTK_OBJECT (hv->ctree), "drag_data_get",
			    history_drag_data_get_cb, hv);
	gtk_drag_source_set (GTK_WIDGET (hv->ctree),
			     GDK_BUTTON1_MASK, url_drag_types, 
			     url_drag_types_num_items,
			     GDK_ACTION_COPY | GDK_ACTION_LINK);

	/* fill the tree */
	history_refresh_view (hv);

	/* add to the list of views */
	history_views = g_list_append (history_views, hv);

	/* return view structure */
	return hv;
}

/**
 * history_update_item_views: update all the views of a given HistoryItem
 */
static void
history_update_item_views (HistoryItem *hi)
{
	g_list_foreach (history_views, (GFunc)history_update_item_view, hi);
}

/**
 * history_update_item_view: update a view of a given HistoryItem
 */
static void
history_update_item_view (HistoryView *hv, HistoryItem *hi)
{
	gchar last[256], first[256], visits[30];
	gchar *text[6] = { NULL, NULL, last, first, visits, NULL };
	GtkCTreeNode *item_node, *host_node;
	const PixmapData *bm_icon;
	gint i;

	/* freeze the tree */
	gtk_clist_freeze (GTK_CLIST (hv->ctree));

	/* find host node */
	host_node = g_hash_table_lookup (hv->host_to_node, hi->host);

	/* valid host? */
	if (host_node != NULL)
	{
		/* make strings for host */
		history_make_host_strings (hi->host, text);

		/* update in tree */
		for (i = 1; i < 5; i++)
		{
			gtk_ctree_node_set_text (GTK_CTREE (hv->ctree),
						 host_node, i, text[i]);
		}
	}

	/* find the tree node in this view */
	item_node = g_hash_table_lookup (hv->item_to_node, hi);

	/* valid item? */
	if (item_node != NULL)
	{
		/* make strings for item */
		history_make_item_strings (hi, text);

		/* make pixmap for item */
		bm_icon = bookmarks_get_siteicon (hi->url);
		
		/* update in tree */
		gtk_ctree_node_set_pixtext (GTK_CTREE (hv->ctree),
					    item_node, 0, text[0], 2,
					    bm_icon->pixmap,
					    bm_icon->mask);
		for (i = 1; i < 5; i++)
		{
			gtk_ctree_node_set_text (GTK_CTREE (hv->ctree),
						 item_node, i, text[i]);
		}
	}
	else
	{
		/* make new node */
		hi->host->node = host_node;
		history_show_dialog_add_item (NULL, hi, hv);
		hi->host->node = NULL;
	}

	/* resort this view (FIXME: does this take too long?) */
	history_resort_view (hv);

	/* thaw the tree */
	gtk_clist_thaw (GTK_CLIST (hv->ctree));
}

/**
 * history_host_scrub_node: invalidate the node pointers so we don't
 * attempt to reuse them if the tree is destroyed
 */
static void
history_host_scrub_node (gpointer key, gpointer value, gpointer user_data)
{
	((HistoryHost *)value)->node = NULL;
}

/**
 * history_empty_ctree: remove all the tree urls
 */
static void
history_empty_ctree (HistoryView *hv)
{
	/* junk the whole tree */
	gtk_clist_clear (GTK_CLIST (hv->ctree));
}

/**
 * history_fill_ctree: fill the tree with the list of urls
 */
static void
history_fill_ctree (HistoryView *hv)
{
	/* add every history item */	
	hv->sibling = NULL;
	g_hash_table_foreach (history, history_show_dialog_add_item, hv);

	/* host ctree nodes can't be trusted to be valid after this time */
	g_hash_table_foreach (history_hosts, history_host_scrub_node, NULL);
}

/**
 * history_add_host: add an host to the list
 */
static HistoryHost *
history_add_host (const gchar *name)
{
	HistoryHost *host;
	
	/* allocate */
	host = g_new0 (HistoryHost, 1);

	/* fill in structure */
	host->title_locale       = g_strdup (name);
	host->title_utf8         = g_strdup (name);
	host->url                = g_strdup (name);
	host->node               = NULL;
	host->sibling            = NULL;
	host->zoom               = 0;
	host->selected_authorcss = -1;
	host->first              = 0;
	host->last               = 0;
	host->visits             = 0;
	host->dominant_item      = NULL;

	/* add to the hosts hashtable */
	g_hash_table_insert (history_hosts, host->url, host);

	/* return completed structure */
	return host;
}

/*
 * history_get_host: get the host folder of the url
 */
static HistoryHost *
history_get_host (const gchar *url)
{
	GnomeVFSURI *vfs_uri = NULL;
	const gchar *name = NULL;
	HistoryHost *host;

	/* check args */
	g_assert (url != NULL);

 	/* check if it's a local file */
 	if (!g_strncasecmp (url, "file://", 7))
 	{
 		/* if so, put it in a folder named "Local files" */
 		name = _("Local files");
  	}
	else
	{
		/* parse the url as a GnomeVFS uri */
		vfs_uri = gnome_vfs_uri_new (url);
		if (vfs_uri != NULL)
		{
			name = gnome_vfs_uri_get_host_name (vfs_uri);
		}

		/* handle failure gracefully */
		if (name == NULL)
		{
			/* seems to happen with https URLs, GnomeVFS bug? */
			name = _("Other");
		}
	}

	/* lookup in table */
	host = g_hash_table_lookup (history_hosts, name);
	if (host == NULL)
	{
		/* new host */
		host = history_add_host (name);
	}

	/* free uri -- this must be done here since the hostname returned
	 * is a constant string and part of the vfs_uri structure */
	if (vfs_uri != NULL)
	{
		gnome_vfs_uri_unref (vfs_uri);
	}

	/* return the appropriate folder */
	return host;
}

/*
 * history_filter_text: filter by search text 
 */
static gboolean
history_filter_text (HistoryView *hv, gchar *url)
{
	if (url == NULL)
	{
		return FALSE;
	}
	if (hv->search == NULL || hv->search[0] == '\0')
	{
		return TRUE;
	}

	return (g_strcasestr (url, hv->search) != NULL);
}

/*
 * history_filter_date: filter by date
 */
static gboolean
history_filter_date (HistoryView *hv, GTime atime)
{
	GDate date, current_date;
	gboolean result;

	g_date_clear (&current_date, 1);
	g_date_set_time (&current_date, time (NULL));

	g_date_clear (&date, 1);
	g_date_set_time (&date, atime);

	switch (hv->time)
	{
		/* Always */
	case 0:
		return TRUE;
		/* Today */
	case 1:
		break;
		/* Yesterday */
	case 2:
		g_date_subtract_days (&current_date, 1);
		break;
		/* Two days ago */
	case 3:
		g_date_subtract_days (&current_date, 2);
		break;
		/* Week */
	case 4:
		g_date_subtract_days (&current_date, 7);
		break;
		/* Month */
	case 5:
		g_date_subtract_months (&current_date, 1);
		break;
	default:
		break;
	}

	result = (g_date_compare (&date, &current_date) >= 0);

	return result;
}

/**
 * history_show_dialog_add_item: add an item to tree, used when filling it
 */
static void 
history_show_dialog_add_item (gpointer key, gpointer value, gpointer user_data)
{
	HistoryItem *hi = (HistoryItem *) value;
	HistoryView *hv = (HistoryView *) user_data;
	GtkCTreeNode **sibling = (GtkCTreeNode **)&(hv->sibling);
	gchar last[256], first[256], visits[30];
	gchar *text[6] = { NULL, NULL, last, first, visits, NULL };
	GtkCTreeNode *node, *parent_node;
	GtkCTreeNode *real_sibling;
	HistoryHost *host = NULL;
	gchar *title_locale;
	const PixmapData *bm_icon;

	/* check if this url passes the filter */
	title_locale = (hi->title_locale != NULL ? 
			hi->title_locale : hi->title_utf8);
	if (!(history_filter_text (hv, hi->url) ||
	      history_filter_text (hv, title_locale)) ||
	    !(history_filter_date (hv, hi->last)))
	{
		return;
	}

	/* group by host? */
	if (hv->group)
	{
		/* get the host */
		host = hi->host;

		/* build a node if necessary */
		if (host->node == NULL)
		{
			/* make strings */
			history_make_host_strings (host, text);

			/* make node */
			host->node = gtk_ctree_insert_node 
				(GTK_CTREE (hv->ctree), NULL, 
				 *sibling, 
				 text, 2,
				 folder_pixmap_data->pixmap,
				 folder_pixmap_data->mask, 
				 folder_open_pixmap_data->pixmap,
				 folder_open_pixmap_data->mask, 
				 FALSE, FALSE);

			/* set data so we can find the item when do compares */
			gtk_ctree_node_set_row_data (GTK_CTREE (hv->ctree),
						     host->node, 
						     host);

			/* add to mapping */
			g_hash_table_insert (hv->host_to_node, host,
					     host->node);

			/* where to gather children */
			host->sibling = NULL;

			/* next folder goes alongside this one */
			*sibling = host->node;
		}

		/* setup for this child */
		parent_node = host->node;
		real_sibling = host->sibling;
	}
	else
	{
		/* flat */
		parent_node = NULL;
	}

	/* make strings */
	history_make_item_strings (hi, text);

	/* fetch icon */
	bm_icon = bookmarks_get_siteicon (hi->url);
	
	/* add node to tree */
	node = gtk_ctree_insert_node (GTK_CTREE (hv->ctree), parent_node,
				      parent_node ? NULL : *sibling, text, 2, 
				      bm_icon->pixmap,
				      bm_icon->mask, 
				      bm_icon->pixmap,
				      bm_icon->mask, 
				      TRUE, TRUE);

	/* set data so we can find the item when we dbl click / do compares */
	gtk_ctree_node_set_row_data (GTK_CTREE (hv->ctree), node, hi);

	/* add to mapping */
	g_hash_table_insert (hv->item_to_node, hi, node);

	/* add the next one alongside this for speed */
	if (hv->group)
	{
		host->sibling = node;
	}
	else
	{
		*sibling = node;
	}
}

/**
 * history_make_host_strings: make (static) node strings for a host
 */
static void
history_make_host_strings (HistoryHost *hh, gchar **text)
{
	/* print into strings */
	text[0] = hh->url;
	text[1] = hh->url;
	store_time_in_string (hh->last, text[2]);
	store_time_in_string (hh->first, text[3]);
	sprintf (text[4], "%d", hh->visits);
}

/**
 * history_make_item_strings: make (static) node strings for an item
 */
static void
history_make_item_strings (HistoryItem *hi, gchar **text)
{
	/* print into strings */
	text[0] = (hi->title_locale != NULL ? 
		   hi->title_locale : hi->title_utf8);		
	text[1] = hi->url;
	store_time_in_string (hi->last, text[2]);
	store_time_in_string (hi->first, text[3]);
	sprintf (text[4], "%d", hi->visits);
}

/**
 * Clears the recent menu
 */
static void
history_clear_recent_menu (void)
{
	GList *w;
	gint go_recent_pos = GO_RECENT_POS;
	
	if (gnome_preferences_get_menus_have_tearoff ())
	{
		go_recent_pos++;
	}

	for (w = all_windows; w != NULL; w = g_list_next(w))
	{
		GaleonWindow *window = (GaleonWindow *)(w->data);
		GtkWidget *menu;
		GList *children;
		GList *tmplist;

		menu = window->go_menu;
		children = gtk_container_children (GTK_CONTAINER(menu));
		tmplist = children;
		children = g_list_nth (children, go_recent_pos);
		
		while (children != NULL)
		{ 
			gtk_container_remove (GTK_CONTAINER (menu),
					      GTK_WIDGET (children->data));
			gtk_widget_destroy (GTK_WIDGET (children->data));
			children = g_list_next (children);
		}
		
		g_list_free (tmplist);
	}
}

/**
 * Adds a new history item to the hashtable and to the history dialog
 */
static HistoryItem *
history_add_item (gchar *url, gchar *title_utf8, GTime first, 
		  GTime last, gint visits)
{
	HistoryItem *hi;
	HistoryHost *host;
	gchar *title_locale;

	/* allocate */
	hi = g_new0 (HistoryItem, 1);

	/* find the parent host */
	host = history_get_host (url);

	/* fill in the fields */
	hi->host               = host;
	hi->url                = url;
	hi->title_utf8         = title_utf8;
	hi->first              = first;
	hi->last               = last;
	hi->visits             = visits;
	hi->selected_authorcss = -1;

	
	/* convert to locale if different */
	title_locale = mozilla_utf8_to_locale (title_utf8);
	if (strcmp (title_locale, title_utf8) != 0)
	{
		/* keep this */
		hi->title_locale = title_locale;
	}
	else
	{
		/* not needed */
		g_free (title_locale);
	}

	/* update the host */
	host->visits += visits;
	if (host->dominant_item == NULL)
	{
		/* first item of host */
		host->first = first;
		host->last = last;
		host->dominant_item = hi;
	}
	else
	{
		host->first = MIN (host->first, first);
		host->last = MAX (host->last, last);
		if (visits > host->dominant_item->visits)
		{
			host->dominant_item = hi;
		}
	}

	/* add to the table */
        g_hash_table_insert (history, hi->url, hi);

	/* add a completion for it */
        auto_completion_add_url (hi->url);

	/* return the finished item */
	return hi;
}

/** 
 * Returns the url of the last visited page
 */ 
const gchar *
history_get_last_url (void)
{
        HistoryItem *last = NULL;

        g_hash_table_foreach (history, history_look_for_newer, &last);
        if (last != NULL)
	{
                return last->url;
        } 
	else
	{
                /* we should return some "default last page" */
                return GALEON_HOMEPAGE_URL;
        }
}

/**
 * Helper function to locate the newer HistoryItem
 * FIXME: we can do this better by constantly tracking the most
 * recent history item
 */
void 
history_look_for_newer (gpointer key, gpointer value, gpointer user_data)
{
        static GTime maxtime = 0;
        HistoryItem *hi = (HistoryItem *) value;
        if (hi->last >= maxtime)
	{
                HistoryItem **last = (HistoryItem **) user_data;
                *last = hi;
                maxtime = hi->last;
        }
}

/** 
 * Returns the number of days since the history item was visited for the last time
 */
gint
calculate_age (GTime last)
{
        GTime now = time (NULL);
        gint days = (now - last) / (24 * 60 * 60);
        return days;
}

/**
 * Adds an item to the recent menu of a given window or moves it to the start.
 */

/* FIXME: this function looks awful */
static void
history_add_recent_menu (GaleonWindow *window, HistoryItem *hi)
{
	GtkWidget *menu;
	GtkWidget *menuitem;
	GList *l;
	GList *li;
	gint go_recent_pos = GO_RECENT_POS;
	gint count = 1 - go_recent_pos;
	gint max = 10;  /* should we add a preference for this? */
	HistoryItem *mhi;

	/* check if the menu exist */
	if (GNOME_APP (window->WMain)->menubar == NULL)
	{
		return;
	}
	
 	menu = window->go_menu;

	if (gnome_preferences_get_menus_have_tearoff ())
	{
		go_recent_pos++;
		count--;
	}

	l = gtk_container_children (GTK_CONTAINER(menu));

        for (li = l; li != NULL; li = li->next)
	{
		if (count>0)
		{
			mhi = gtk_object_get_user_data (GTK_OBJECT (li->data));
			g_assert (mhi != NULL);
			gtk_widget_destroy (GTK_WIDGET (li->data));
			if (mhi != hi)
			{
				if (count < max) 
				{
					menuitem =
						history_create_recent_menu_item 
							(mhi, count, menu);
					gtk_menu_insert (GTK_MENU (menu),
						menuitem, go_recent_pos+count);
					gtk_widget_show (menuitem);
				}
				count++;
			}
		}
		else 
			count++;
	}
        g_list_free (l);
        menuitem = history_create_recent_menu_item (hi, 0, menu);
        gtk_menu_insert (GTK_MENU (menu), menuitem, go_recent_pos);
        gtk_widget_show (menuitem);
}

GtkWidget *
history_create_recent_menu_item (HistoryItem *hi, gint num, GtkWidget *menu)
{
	GtkWidget *hb = gtk_hbox_new (FALSE, 0);
	const PixmapData *pixmap_data = bookmarks_get_siteicon (hi->url);
	GtkWidget *pixmap = gtk_pixmap_new (pixmap_data->pixmap,
		pixmap_data->mask);
	GtkWidget *menuitem = gtk_pixmap_menu_item_new ();
	gchar *title_locale, *s, *tmp = NULL;
	GtkWidget *l;
	gint length;

	s = (hi->title_locale != NULL ? 
	     hi->title_locale : hi->title_utf8);
	/* if the title comes up empty, use the url */
	if (s[0] == '\0')
	{
		s = hi->url;
	}
	
	length = strlen (s);
	
	/* shorten if needed */
	if (length > 40)
	{
		tmp = g_strdup_printf ("%.29s...%s", s, s + length - 8);
	}
	
	title_locale = escape_uline_accel (tmp ? tmp : s);
	
	l = new_num_accel_label (num, title_locale, FALSE, menu, menuitem);

	if (tmp) g_free (tmp);
	g_free (title_locale);
	
	gtk_box_pack_start (GTK_BOX (hb), l, FALSE, FALSE, 2);
	gtk_container_add (GTK_CONTAINER(menuitem), hb);
	gtk_pixmap_menu_item_set_pixmap (GTK_PIXMAP_MENU_ITEM(menuitem),
		pixmap);
	gtk_widget_lock_accelerators (menuitem);
	
	gtk_widget_show_all (hb);
	if (gnome_preferences_get_menus_have_icons ())
		gtk_widget_show (pixmap);
	
	gtk_object_set_user_data (GTK_OBJECT (menuitem), hi);
	gtk_signal_connect (GTK_OBJECT(menuitem), "activate",
			    GTK_SIGNAL_FUNC (window_menu_recent_activate_cb),
			    NULL);
	gtk_signal_connect (GTK_OBJECT(menuitem), "button-release-event",
			    GTK_SIGNAL_FUNC (window_menu_recent_release_cb),
			    NULL);

	return menuitem;
}

/**
 * history_set_zoom: set the zoom level of the server referenced by the
 * given url
 */
void
history_set_zoom (const gchar *url, gint zoom)
{
	HistoryHost *host;

	/* check URL is valid */
	if (url == NULL)
	{
		return;
	}

	/* store zoom level in host */
	host = history_get_host (url);
	if (host != NULL)
	{
		host->zoom = zoom;
	}
}

/**
 * history_get_zoom: get the zoom level of the server referenced by the
 * given url
 */
gint
history_get_zoom (const gchar *url)
{
	HistoryHost *host;

	/* check URL is valid */
	if (url == NULL)
	{
		return 0;
	}

	/* find a host */
	host = history_get_host (url);

	/* our result */
	return (host == NULL ? 0 : host->zoom);
}

/**
 * history_set_selected_authorcss: set the selected author css of the server 
 * referenced by the given url
 */
void
history_set_selected_authorcss (const gchar *url, gint n)
{
	HistoryHost *host;

	/* check URL is valid */
	if (url == NULL)
	{
		return;
	}

	/* store selected css in host */
	host = history_get_host (url);
	if (host != NULL)
		host->selected_authorcss = n;
}

/**
 * history_get_selected_authorcss: get the selected author css of the server 
 * referenced by the given url
 */
gint
history_get_selected_authorcss (const gchar *url)
{
	HistoryHost *host;

	/* check URL is valid */
	if (url == NULL)
	{
		return -1;
	}

	/* find a host */
	host = history_get_host (url);
	
	/* our result */
	return (host == NULL ? -1 : host->selected_authorcss);
}

/**
 * history_get_host_list: get a fresh linked list of all the hosts
 */
GList *
history_get_host_list (void)
{
	GList *list = NULL;

	/* add each host */
	g_hash_table_foreach (history_hosts, 
			      (GHFunc)history_add_server_to_list, &list);

	/* return complete list */
	return list;
}

/**
 * history_add_server_to_list: callback to add server structure to a list
 */
static void 
history_add_server_to_list (gpointer key, HistoryHost *host, GList **list)
{
	/* add it to the list */
	*list = g_list_prepend (*list, host);
}

/**
 * history_item_free_cb: free one history item
 */
static gboolean
history_item_free_cb (gpointer key, gpointer value, gpointer user_data)
{
	HistoryItem *hi = (HistoryItem *)value;

	/* NB the key is either the URL, in which case it is free'd here
	 * or if this is actually a host then it's just a pointer, so it
	 * doesn't need free'ing */

	/* free the item */
	g_free (hi->url);
	if (hi->title_locale != NULL)
	{
		g_free (hi->title_locale);
	}
	g_free (hi->title_utf8);
	g_free (hi);

	return TRUE;
}

/**
 * local_gtk_ctree_sort_recursive: sort the history ctree
 */
static void
local_gtk_ctree_sort_recursive (GtkCTree *ctree, GtkCTreeNode *node)
{
	GtkCList *clist;
	GtkCTreeNode *focus_node = NULL;
	
	g_return_if_fail (ctree != NULL);
	g_return_if_fail (GTK_IS_CTREE (ctree));
	
	clist = GTK_CLIST (ctree);
	
	gtk_clist_freeze (clist);
	if (!node || (node && gtk_ctree_is_viewable (ctree, node)))
	{
		focus_node =
			GTK_CTREE_NODE (g_list_nth (clist->row_list, 
						    clist->focus_row));
	}
	gtk_ctree_post_recursive (ctree, node, 
				  GTK_CTREE_FUNC (tree_sort), NULL);
	
	if (!node)
	{
		tree_sort (ctree, NULL, NULL);
	}
	if (focus_node)
	{
		clist->focus_row = g_list_position (clist->row_list,
						    (GList *)focus_node);
		clist->undo_anchor = clist->focus_row;
	}	
	gtk_clist_thaw (clist);
}

/**
 * tree_sort: utility function for local_gtk_ctree_sort_recursively
 */
static void
tree_sort (GtkCTree *ctree, GtkCTreeNode *node, gpointer data)
{
	GtkCTreeNode *list_start;
	GtkCTreeNode *work;
	GtkCList *clist;
	GList *list, *sorted;
	gint sort_column;
	gint sort_order;

	/* get the list */
	clist = GTK_CLIST (ctree);

	/* find out the sort column and ordering */
	sort_column = eel_gconf_get_integer (CONF_HISTORY_SORT_COLUMN);
	sort_order = eel_gconf_get_integer (CONF_HISTORY_SORT_ORDER);

	/* find starting node */
	if (node != NULL)
	{
		list_start = GTK_CTREE_ROW (node)->children;
	}
	else
	{
		list_start = GTK_CTREE_NODE (clist->row_list);
	}
	
	/* make a list of the nodes to be sorted & remove them from the tree */
	sorted = NULL;
	for (work = list_start; work != NULL; 
	     work = GTK_CTREE_ROW(work)->sibling)
	{
		sorted = g_list_prepend (sorted, work);
		gtk_ctree_unlink (ctree, work, FALSE);
	}
	
	/* do the sort */
	sorted = g_list_sort (sorted, history_compare[sort_column]);
	if (sort_order == GTK_SORT_ASCENDING)
	{
		sorted = g_list_reverse (sorted);
	}
	
	/* restock the tree from the sorted list */
	list_start = NULL;
	for (list = sorted; list != NULL; list = g_list_next (list))
	{
		GtkCTreeNode *this = list->data;
		
		/* add in the right place */
		if (this == list_start)
		{
			list_start = GTK_CTREE_ROW (this)->sibling;
		}
		else
		{
			gtk_ctree_link (ctree, this, node, list_start, FALSE);
			list_start = this;
		}
	} 
	
	/* free allocated list */
	g_list_free (sorted);
}

/**
 * history_load: Loads the history from the history file (if it
 * exists). Creates the history dialog. Does not load expired entries
 */
static void
history_load (void)
{
        /* definition of SAX parser for reading documents */
	static xmlSAXHandler parser =
	{
		NULL, /* internalSubset        */
		NULL, /* isStandalone          */
		NULL, /* hasInternalSubset     */
		NULL, /* hasExternalSubset     */
		NULL, /* resolveEntity         */
		NULL, /* getEntity             */
		NULL, /* entityDecl            */
		NULL, /* notationDecl          */
		NULL, /* attributeDecl         */
		NULL, /* elementDecl           */
		NULL, /* unparsedEntityDecl    */
		NULL, /* setDocumentLocator    */
		NULL, /* startDocument         */
		NULL, /* endDocument           */
		(startElementSAXFunc) history_start_element, /* startElement */
		NULL, /* endElement            */
		NULL, /* reference             */
		NULL, /* characters            */
		NULL, /* ignorableWhitespace   */
		NULL, /* processingInstruction */
		NULL, /* comment               */
		NULL, /* warning    (FIXME)    */
		NULL, /* error      (FIXME)    */
		NULL, /* fatalError (FIXME)    */
	};
        gchar *confdir;
        gchar *histfile;

	/* build the filename */
        confdir = g_concat_dir_and_file (g_get_home_dir (), ".galeon");
	histfile = g_concat_dir_and_file (confdir, "history.xml");
	
	/* build the initial history hash table */
        history = g_hash_table_new (g_str_hash, g_str_equal);

	/* build the initial history hosts hashtable */
	history_hosts = g_hash_table_new (g_str_hash, g_str_equal);

	/* find out how many days to keep before expiring elements */
	expire_days = eel_gconf_get_integer (CONF_HISTORY_EXPIRE);

	/* load the file if we can access it */
	if (access (histfile, F_OK) != -1)
	{
		xmlSAXParseFile (&parser, histfile, TRUE);
	}
	
	/* free allocated strings */
	g_free (confdir);
	g_free (histfile);
}

/**
 * history_start_element: callback called to parse a single element of the
 * history XML file.
 */
static void 
history_start_element (void *ctx, const xmlChar *fullname, 
		       const xmlChar **attrs)
{
	/* parse element */
	if (xmlStrcmp (fullname, "ordering") == 0 && attrs != NULL)
	{
		/* ignore (FIXME: remove after a few versions) */
	}
	else if (xmlStrcmp (fullname, "host") == 0 && attrs != NULL)
	{
		/* <host> element */
		history_parse_host_element (attrs);
	}
	else if (xmlStrcmp (fullname, "item") == 0 && attrs != NULL)
	{
		/* <item> element */
		history_parse_item_element (attrs);
	}
	else if (xmlStrcmp (fullname, "history") == 0)
	{
		/* we recurse into this automatically */
	}
	else
	{
		g_warning ("unknown history element '%s'\n", fullname);
	}
}

/**
 * history_parse_host_element: parse <host> elements
 */
static void
history_parse_host_element (const xmlChar **attrs)
{
	HistoryHost *host;
	const xmlChar **attr;
	gchar *name = NULL;
	gint zoom = 0;
	gint css = -1;

	/* parse each attribute */
	for (attr = attrs; *attr != NULL; attr += 2)
	{
		if (xmlStrcmp (attr[SAX_KEY], "name") == 0)
		{
			name = xml_decode_entity (attr[SAX_VALUE]);
		}
		else if (xmlStrcmp (attr[SAX_KEY], "zoom") == 0)
		{
			zoom = atoi (attr[SAX_VALUE]);
		}
		else if (xmlStrcmp (attr[SAX_KEY], "authorcss") == 0)
		{
			css = atoi (attr[SAX_VALUE]);
		}
		else
		{
			g_warning ("Unsupported attribute for "
				   "history.xml::%s: %s", 
				   attr[SAX_KEY], attr[SAX_VALUE]);
		}
	}

	/* create a host structure and store */
	host = history_add_host (name);
	host->zoom = zoom;
	host->selected_authorcss = css;
	g_free (name);
}

/**
 * history_parse_item_element: parse <item> elements
 */
static void
history_parse_item_element (const xmlChar **attrs)
{
	const xmlChar **attr;
	gchar *title_utf8 = NULL;
	gchar *url = NULL;
	GTime first = 0;
	GTime last = 0;
	gint visits = 0;

	/* parse each attribute */
	for (attr = attrs; *attr != NULL; attr += 2)
	{
		if (xmlStrcmp(attr[SAX_KEY], "title") == 0)
		{
			title_utf8 = xml_decode_entity (attr[SAX_VALUE]);
		}
		else if (xmlStrcmp(attr[SAX_KEY], "url") == 0)
		{
			url = xml_decode_entity (attr[SAX_VALUE]);
		}
		else if (xmlStrcmp(attr[SAX_KEY], "first_time") == 0)
		{
			first = strtol (attr[SAX_VALUE], NULL, 10);
		}
		else if (xmlStrcmp(attr[SAX_KEY], "last_time") == 0)
		{
			last = strtol (attr[SAX_VALUE], NULL, 10);
		}
		else if (xmlStrcmp(attr[SAX_KEY], "visits") == 0)
		{
			visits = atoi (attr[SAX_VALUE]);
		}
		else
		{
			g_warning("Unsupported attribute for "
				  "history.xml::%s: %s", 
				  attr[SAX_KEY], attr[SAX_VALUE]);
		}
	}

	/* check age */
	if (calculate_age (last) <= expire_days)
	{
		/* add to the history */
		history_add_item (url, title_utf8, first, last, visits);
	}
	else
	{
		if (url) g_free (url);
		if (title_utf8) g_free (title_utf8);			
	}
}

/** 
 * history_save: saves the history out to the default XML file
 */
void 
history_save (void)
{
        gchar *histfile;
	FILE *fp;

	if (!history_dirty)
	{
		return;
	}

	histfile = g_strconcat (g_get_home_dir (), 
				"/.galeon/history.xml", NULL);
	fp = fopen (histfile, "wb");
	g_free (histfile);
	g_return_if_fail (fp != NULL);

	fputs ("<?xml version=\"1.0\"?>\n", fp);
	fputs ("<history>\n", fp);
        g_hash_table_foreach (history_hosts, history_save_host, fp);
        g_hash_table_foreach (history, history_save_item, fp);
	fputs ("</history>\n", fp);
	fclose (fp);
	history_dirty = FALSE;
}

/**
 * Saves one host of the history
 */
static void 
history_save_host (gpointer key, gpointer value, gpointer user_data)
{
        HistoryHost *hh = (HistoryHost *) value;
	FILE *fp = (FILE *) user_data;

	if (hh->zoom != 0 || hh->selected_authorcss != -1)
	{
		fputs ("  <host name=\"", fp);
		fputs (hh->url, fp);
	}
	
	if (hh->zoom != 0)
	{
		fputs ("\" zoom=\"", fp);
		fprintf (fp, "%d", hh->zoom);
	}

	if (hh->selected_authorcss != -1)
	{
		fputs ("\" authorcss=\"", fp);
		fprintf (fp, "%d", hh->selected_authorcss);
	}

	if (hh->zoom != 0 || hh->selected_authorcss != -1)
	{
		fputs ("\"/>\n", fp);
	}
}

/**
 * Saves one item of the history
 */
static void 
history_save_item (gpointer key, gpointer value, gpointer user_data)
{
        HistoryItem *hi = (HistoryItem *) value;
	FILE *fp = (FILE *) user_data;

	fputs ("  <item title=\"", fp);
	eputs (hi->title_utf8, fp);
	fputs ("\" url=\"", fp);
	eputs (hi->url, fp);
	fputs ("\" first_time=\"", fp);
	fprintf (fp, "%d", hi->first);
	fputs ("\" last_time=\"", fp);
	fprintf (fp, "%d", hi->last);
	fputs ("\" visits=\"", fp);
	fprintf (fp, "%d", hi->visits);
	fputs ("\"/>\n", fp);
}

/**
 * eputs: an efficient "attribute encoding and writing to file" function
 */
static void
eputs (guchar *text, FILE *fp)
{
	gint i, length, last;
	
	last = 0;
	length = strlen (text);
	for (i = 0; i < length; i++)
	{
                /**
		 * This method is not good. In XML spec, &#xxx refers to
		 * a specific character in the ISO/IEC 10646, not UTF-8.
		 * [ yes, but this is for attributes which need encoding
		 *   whatever the charset is being used -- MattA ]
		 */
		if (text[i] >= 127)
		{
			fwrite (text + last, i - last, 1, fp);
			last = i + 1;
			fprintf (fp, "&#%03d;", text[i]);
			continue;
		}
		switch (text[i])
		{
		case '&':
			fwrite (text + last, i - last, 1, fp);
			last = i + 1;
			fputs ("&amp;", fp);
			break;

		case '<':
			fwrite (text + last, i - last, 1, fp);
			last = i + 1;
			fputs ("&lt;", fp);
			break;

		case '>':
			fwrite (text + last, i - last, 1, fp);
			last = i + 1;
			fputs ("&gt;", fp);
			break;

		case '\"':
			fwrite (text + last, i - last, 1, fp);
			last = i + 1;
			fputs ("&quot;", fp);
			break;

		case '\'':
			fwrite (text + last, i - last, 1, fp);
			last = i + 1;
			fputs ("&apos;", fp);
			break;
		}
	}

	/* write out remainder */
	fwrite (text + last, i - last, 1, fp);
}

static void
history_drag_data_get_cb (GtkWidget *widget, GdkDragContext *context,
			  GtkSelectionData *selection_data, guint info,
			  guint time, HistoryView *hv)
{
	BookmarkItem *b;
	char *url;
	gchar *mem;

	g_return_if_fail (hv->selected_item != NULL);
	url = hv->selected_item->url;

	if (!url) return;

	switch (info)
	{
	case DND_TARGET_GALEON_BOOKMARK:
		b = bookmarks_new_bookmark (BM_SITE, TRUE, url,
					    url,
					    NULL, NULL, NULL);
		mem = bookmarks_item_to_string (b);
		gtk_selection_data_set 
			(selection_data, selection_data->target,
			 8, mem, strlen (mem));
		g_free (mem);
		break;
	case DND_TARGET_STRING:
	case DND_TARGET_NETSCAPE_URL:
	case DND_TARGET_GALEON_URL:
		gtk_selection_data_set 
			(selection_data, selection_data->target,
			 8, url, 
			 strlen (url));
		break;
	default:
		g_warning ("Unknown DND type");
		break;
	}
}

static const struct 
{
	char *gconf_key;
	GConfClientNotifyFunc func;
}
notifiers [] =
{
	{ CONF_HISTORY_SEARCH_TEXT, history_properties_notifier },
	{ CONF_HISTORY_SEARCH_TIME, history_properties_notifier },
	{ CONF_HISTORY_HOST_GROUP, history_properties_notifier },
	{ CONF_HISTORY_SORT_COLUMN, history_sort_notifier },
	{ CONF_HISTORY_SORT_ORDER, history_sort_notifier },
	{ NULL, NULL}
};

static
void history_notifiers_init(HistoryView *hv) 
{
	int i;

	for (i = 0; notifiers[i].gconf_key != NULL; i++)
	{
		galeon_notification_add(
			notifiers[i].gconf_key,
			(GConfClientNotifyFunc)notifiers[i].func,
			(gpointer)hv,
			&history_notifiers_id);
	}
}


void history_notifiers_remove(void) 
{
	galeon_notification_remove(&history_notifiers_id);
}
