/*
 *  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 "mozilla_i18n.h"
#include "misc.h"
#include "prefs.h"
#include "history.h"
#include "window.h"
#include "mime.h"
#include "session.h"
#include "bookmarks.h"
#include "mozilla.h"

/* system includes */
#include <errno.h>
#include <dirent.h>
#include <limits.h>
#include <ctype.h>

#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <gtk/gtkmain.h>
#include <gtk/gtkrc.h>
#include <gtk/gtkcheckmenuitem.h>
#include <gtk/gtkselection.h>
#include <libgnomeui/gnome-dialog-util.h>
#include <libgnomeui/gnome-uidefs.h>
#include <libgnome/gnome-i18n.h>
#include <libgnome/gnome-url.h>
#include <libgnome/gnome-util.h>
#include <libgnome/gnome-exec.h>
#include <libgnome/gnome-config.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include "gtkmozembed.h"

/* GNOME includes */
#include <libgnomevfs/gnome-vfs.h>
#include <libgnomevfs/gnome-vfs-mime.h>
#include <libgnomevfs/gnome-vfs-handle.h>

/** Styles for tab labels */
GtkStyle *loading_text_style = NULL;
GtkStyle *new_text_style = NULL;

/* Local prototypes */
static gboolean is_punctuation (gchar c);
static gchar* get_first_directory (const gchar *start_dir);
static void pixbuf_threshold_alpha (GdkPixbuf *pixbuf, gboolean prelight);

/**
 * user_file: returns the pathname of galeon shared files (e.g., galeon.glade)
 *
 * fname: just the filename with no path information
 * critical: critical file? (halt if not found)
 */
gchar *
user_file (gchar *fname, gboolean critical)
{
	static GHashTable *already_found = NULL;
	gchar *alternative[6];
	gchar *file;
	gint i;
	
	/* create cache hash table if it doesnt already exist */
	if (already_found == NULL)
	{
		already_found = g_hash_table_new (g_str_hash, g_str_equal);
	}

        /* Have we already found this? */
	file = g_hash_table_lookup (already_found, fname);
	if (file != NULL) 
	{
		return g_strdup (file);
	}

	/* try the default */
	file = g_strconcat (g_get_home_dir (), "/.galeon/", fname, NULL);
	
	/* success? */
	if (g_file_exists (file))
	{
		return file;
	}
	g_free(file);

	/* specify alternate locations in order of precedence */
	alternative[0] = g_strdup (fname);
	alternative[1] = g_strconcat ("../", fname, NULL);
	alternative[2] = g_strconcat ("ui/", fname, NULL);
	alternative[3] = g_strconcat ("../ui/", fname, NULL);
	alternative[4] = g_strconcat (SHARE_DIR "/", fname, NULL);
	alternative[5] = NULL;  /* NULL terminator needed */
	
	/* select one of the alternatives */
	file = NULL;
	for (i = 0; alternative[i] != NULL; i++)
	{
		if (file == NULL && g_file_exists (alternative[i])) 
		{
			file = alternative[i];
                        /* don't warn for the install default */
			if (i != 4)
			{
				g_message ("Using %s (usually OK)\n", file);
			}
		}
		else
		{
			/* free unused string */
			g_free (alternative[i]);
		}
	}

	/* check for success */
	if (file != NULL)
	{
		/* add it to the set of found files */
		g_hash_table_insert (already_found, g_strdup (fname), 
				     g_strdup (file));
	}
	/* if nothing then theres an error */
	else if (critical)
	{
		g_error(_("%s not found"), fname);
	}

	/* return result */
	return file;
}

/**
 * Frees an array of strings
 */ 
void 
free_string_array (char *array[], int size)
{
	int i;
	if (array)
	{
		for (i = 0; i < size; i++)
			if (array[i])
				g_free (array[i]);
		g_free (array);
	}
}

/**
 * Creates a string with a numbered/lettered accel (caller must free)
 * returns NULL if num is out of the range of acceleratable nums/letters
 */
gchar *
new_num_accel_str (gint num, gchar *text, gboolean lettersok)
{
	gchar *label = NULL;
	if (num < 9)
		label = g_strdup_printf	("_%i. %s", num+1, text);
	else if (num == 9)
		label = g_strdup_printf	("_%i. %s", 0, text);
	else if (num < 36 && lettersok)
		label = g_strdup_printf	("_%c. %s", 'a'+num-10, text);
	return label;
}

/**
 * Sets a labels text with shortcut key handling in the 
 * standard _<key> accel way
 */
void
label_set_accel_text (const gchar *text, GtkWidget *label, GtkWidget *menu, 
		      GtkWidget *item)
{
	gint tmp_key = gtk_label_parse_uline (GTK_LABEL (label), text);
	GtkAccelGroup *menu_accels = 
		gtk_menu_ensure_uline_accel_group (GTK_MENU (menu));
	gtk_widget_add_accelerator (item, "activate_item", menu_accels,
			tmp_key, 0, 0);
}

/**
 * Strip the _ out of a string like gtk_label_parse_uline would do
 * caller is responsible for freeing the returned string
 */
gchar *
strip_uline_accel (const gchar *text)
{
	GString *out;
	const gchar *u, *cur = text;
	out = g_string_new (NULL);
	while ((u = strchr (cur, '_')))
	{
		if (*(u+1) == '_')
		{
			/* __ in the string is equal to _ in the output 
			 * so include the _ in the output, skip over the 
			 * second _ and continue scanning. */
			g_string_sprintfa (out, "%.*s", u - cur + 1, cur);
			cur = u + 2;
		} else {
			/* copy everything before the _ and skip over it */
			g_string_sprintfa (out, "%.*s", u - cur , cur);
			cur = u + 1;
			/* only one accel per string, so we are done now */
			break;
		}
	}
	if (cur && *cur)
	{
		/* attach rest of string */
		g_string_append (out, cur);
	}

	u = out->str;
	g_string_free (out, FALSE); /* don't free char data, caller must */
	return (gchar *)u;
}

/**
 * Escape _'s in string such that the gtk_label_parse_uline will display as
 *  normal
 * caller is responsible for freeing the returned string
 */
/* FIXME: utf8 */
gchar *
escape_uline_accel (const gchar *text)
{
	GString *out;
	const gchar *u, *cur = text;
	out = g_string_new (NULL);
	while ((u = strchr (cur, '_')))
	{
		/* All we need to do is double every _, so include the _ in 
		 * the output, add another _, and continue scanning. */
		g_string_sprintfa (out, "%.*s_", u - cur + 1, cur);
		cur = u + 1;
	}
	if (cur && *cur)
	{
		/* attach rest of string */
		g_string_append (out, cur);
	}

	u = out->str;
	g_string_free (out, FALSE); /* don't free char data, caller must */
	return (gchar *)u;
}

/**
 * Creates a label with a numbered/lettered accel
 */
GtkWidget *
new_num_accel_label (gint num, gchar *origtext, gboolean lettersok, 
		     GtkWidget *menu, GtkWidget *item)
{
	gchar *text = new_num_accel_str(num, origtext, lettersok);
	if (text == NULL)
		return gtk_label_new (origtext);
	else
	{
		GtkWidget *label = gtk_label_new ("");
		label_set_accel_text (text, label, menu, item);
		g_free(text);
		return label;
	}
}

void
menu_position_under_widget (GtkMenu *menu, gint *x, gint *y, 
			    gpointer user_data)
{
	GtkWidget *w = GTK_WIDGET(user_data);
	gint width, height;
	gint screen_width, screen_height;
	GtkRequisition requisition;

	gdk_window_get_size (w->window, &width, &height);
	gdk_window_get_deskrelative_origin(w->window, x, y);
	*y = *y + height;

	gtk_widget_size_request (GTK_WIDGET (menu), &requisition);
      
	screen_width = gdk_screen_width ();
	screen_height = gdk_screen_height ();
	  
	*x = CLAMP (*x, 0, MAX (0, screen_width - requisition.width));
	*y = CLAMP (*y, 0, MAX (0, screen_height - requisition.height));
}

/**
 * copy_to_clipboard: Copies some text into the clipboard
 **/
void
copy_to_clipboard (gchar *text, GaleonEmbed *embed)
{
	gint have_selection;
	GtkWidget *window = embed->parent_window->WMain;

	/* FIXME: free previous data? */
	gtk_object_set_data (GTK_OBJECT (window),
			     "selection", g_strdup (text));
	have_selection = gtk_selection_owner_set (GTK_WIDGET (window),
					 gdk_atom_intern("CLIPBOARD",FALSE), 
					  GDK_CURRENT_TIME)&&
		         gtk_selection_owner_set (window,
					 GDK_SELECTION_PRIMARY,
					 GDK_CURRENT_TIME);
	if (!have_selection)
	{
		g_warning("Selection not found");
	}
} 

static gboolean
is_punctuation (gchar c)
{
	return (c == ' ' || c == '.' || c == '!' || c == '|' ||
		c == ',' || c == ':' || c == ';');
}

/**
 * shorten_name: try to shorten a page title (ideally to target_length or 
 * less). The heurstics here seems to give pretty good results even down
 * to quite small lengths, generally remaining comprehensible down to
 * around six to eight characters.
 */
/* FIXME: utf8? */
gchar *
shorten_name(const gchar *input_name, gint target_length)
{
	gint i, j, length;
	gchar *name;
	char c;

	if (input_name == NULL)
		return (g_strdup (_("Untitled")));
	
	/* copy and clean name */
	name = g_strdup (input_name);
	g_strstrip (name);
	length = strlen (name);

	/* look for some common prefixes -- should these be translated? */
	if (eel_gconf_get_boolean (CONF_TABS_TABBED_PREFIX))
	{
		/* prefixes that only come by themselves */
		if (strncasecmp (name, "index of ", 9) == 0)
		{
			length -= 9;
			memmove(name, name + 9, length + 1);
		}
		else if (strncasecmp (name, "re: ", 4) == 0)
		{
			length -= 4;
			memmove(name, name + 4, length + 1);
		}
		else if (strncasecmp (name, "fwd: ", 5) == 0)
		{
			length -= 5;
			memmove(name, name + 5, length + 1);
		}
		else if (strncasecmp (name, "www.", 4) == 0)
		{
			length -= 4;
			memmove(name, name + 4, length + 1);
		}
		else 
		{
			/* prefixes that can be followed by other
			 * prefixes */
			if (strncasecmp (name, "welcome to ", 11) == 0)
			{
				length -= 11;
				memmove(name, name + 11, length + 1);
			}

			/* prefixes that follow the ones in the
			 * previous group */
			if (strncasecmp (name, "a ", 2) == 0)
			{
				length -= 2;
				memmove(name, name + 2, length + 1);
			}
			else if (strncasecmp (name, "my ", 3) == 0)
			{
				length -= 3;
				memmove(name, name + 3, length + 1);
			}
			else if (strncasecmp (name, "the ", 4) == 0)
			{
				length -= 4;
				memmove(name, name + 4, length + 1);
			}
		}

		/* remove any leading whitespace */
		g_strchug (name);
	}

	/* check length */
	length = strlen (name);
	if (length <= target_length)
	{
		return name;
	}

	/* find in name the first occurence of one of
	 * several common separators, and set it to '\0' */
	if (eel_gconf_get_boolean (CONF_TABS_TABBED_SEPARATOR))
	{
		gchar *first;
		gchar *str;

		/* set first to initially point to the terminating '\0'
		 * character */
		first = name + sizeof (gchar) * strlen (name);

		/* search for various separators... we can now search
		 * for ": ", becuase because we have stripped "re:" and
		 * "fwd: " in an earlier test */
		str = strstr (name, " - ");
		if (str != NULL && str < first) first = str;
		str = strstr (name, " -- ");
		if (str != NULL && str < first) first = str;
		str = strstr (name, " | ");
		if (str != NULL && str < first) first = str;
		str = strstr (name, " || ");
		if (str != NULL && str < first) first = str;
		str = strstr (name, ": ");
		if (str != NULL && str < first) first = str;
		str = strstr (name, " :: ");
		if (str != NULL && str < first) first = str;

		*first = '\0';

		/* check length */
		g_strchomp (name);
		length = strlen (name);
		if (length <= target_length)
		{
			return name;
		}
	}

	/* getting tricky, try losing a few vowels */
	/* yes, this is very anglocentric -- I don't know of any strategies
	 * for other languages (particularly non-European) -- MattA */
	if (eel_gconf_get_boolean (CONF_TABS_TABBED_VOWEL))
	{
		for (i = length - 1, j = length - 1; i >= 0; i--)
		{
			c = name[i];
			/* keep the letter if the string is short enough
			 * already, if the letter is at the beginning of a
			 * word, or if it's not a lowercase vowel */
			if (length <= target_length || i == 0 ||
 			    (i > 0 && name[i - 1] == ' ') ||
 			    /* TRANSLATOR: Put in this string those characters
 			     that can be skipped without making a word totally
 			     irrecognizable. In most languages, those are the
 			     vowels (with and without accents) */
 			    !strchr (_("aeiouyAEIOUY"), c))
			{
				name[j] = c;
				j--;
			}
			else
			{
				length--;
			}
		}
		/* shift */
		memmove(name, name + j + 1, length + 1);

		/* short enough yet? */
		if (length <= target_length)
		{
			return name;
		}
	}

	/* argh -- try chopping out whole words */
	for (i = target_length; i > 0; i--)
	{
		if (is_punctuation (name[i]))
		{
			while (is_punctuation (name[i]))
			{
				i--;
			}
			/* this will do */
			name[i + 1] = '\0';
			g_strchomp (name);
			return name;
		}
	}

	/* just chop off and add ellipsis */
	for (i = 0; i < 3; i++)
	{
		/* don't overflow string length */
		if (name[target_length + i] == '\0')
			break;
		
		name[target_length + i] = '.';
	}
	
	/* terminate string */
	name[target_length + i] = '\0';
	
	/* return it */
	return name;
}

/**
 * initialise_colours: initialise (or update) global colour styles
 */
void
initialise_colours (void)
{
	GtkWidget *label;
	GtkStyle  *rcstyle;
	char *tmp;

	/* create a dummy label so we can fetch the associated rc style */
	label = gtk_label_new ("");
	rcstyle = gtk_rc_get_style (label);

	/* this is needed for some (broken?) themes */
	if (rcstyle == NULL)
	{
		rcstyle = gtk_style_new ();
	}
	else
	{
		gtk_style_ref (rcstyle);
	}

	/* free previous styles (if any) */
	if (loading_text_style) gtk_style_unref (loading_text_style);
	if (new_text_style) gtk_style_unref (new_text_style);
	
	/* make styles */
	loading_text_style = gtk_style_copy (rcstyle);
	new_text_style = gtk_style_copy (rcstyle);

	/* load colours */
	tmp = eel_gconf_get_string (CONF_TABS_TABBED_LOADING_COLOR);
	gdk_color_parse (tmp, &(loading_text_style->fg[0]));
	g_free (tmp);

	tmp = eel_gconf_get_string (CONF_TABS_TABBED_NEW_COLOR);
	gdk_color_parse (tmp, &(new_text_style->fg[0]));
	g_free (tmp);

	/* free */
	gtk_widget_destroy (label);
	gtk_style_unref (rcstyle);
}

/**
 * read_line_from_file: reads a line from an opened file and returns it in a 
 * new allocated string
 */
gchar *
read_line_from_file (FILE *f)
{
	gchar *line = g_strdup ("");
	gchar *t;
	gchar *buf = g_new0 (gchar, 256);
	while ( ! ( strchr (buf, '\n') || feof (f) ) ) {
		fgets(buf, 256, f);
		t = line;
		line = g_strconcat (line, buf, NULL);
		g_free (t);
	}
	return line;
}

/**
 * g_strcasestr: test if a string b is a substring of string a, independent
 * of case.
 */
const gchar *
g_strcasestr (const gchar *a, const gchar *b)
{
	gchar *down_a;
	gchar *down_b;
	gchar *ptr;

	/* copy and lower case the strings */
	down_a = g_strdup (a);
	down_b = g_strdup (b);
	g_strdown (down_a);
	g_strdown (down_b);

	/* compare */
	ptr = strstr (down_a, down_b);

	/* free allocated strings */
	g_free (down_a);
	g_free (down_b);
	
	/* return result of comparison */
	return ptr == NULL ? NULL : (a + (ptr - down_a));
}

/**
 * str_dup_replace: returns a new string (which must be freed later)
 * identical to str, but with all occurrences of a replaced by b.  if any
 * of the given strings are null, or if a is empty, null is returned. */
gchar
*str_dup_replace (const gchar *str, const gchar *a, const gchar *b)
{
	gchar *new_str, *start_str, *end_str, *dest_str;
	gint str_len, a_len, b_len, new_str_len;

	/* return if any of the strings are null, or if a is empty */
	if (!str || !a || !b || a[0] == '\0') return NULL;

	/* get the lengths of the user-supplied strings */
	str_len = new_str_len = strlen (str);
	a_len = strlen (a);
	b_len = strlen (b);

	/* find the length of the new string */
	start_str = (gchar *) str;
	while ((start_str = strstr (start_str, a)))
	{
		new_str_len -= a_len;
		new_str_len += b_len;
		start_str = &(start_str[a_len]);
	}

	/* allocate memory for the new string */
	new_str = g_new0 (gchar, new_str_len + 1);
	g_return_val_if_fail (new_str, NULL);

	/* walk through the string, replacing a with b */
	start_str = (gchar *) str;
	dest_str = new_str;
	while ((end_str = strstr (start_str, a)))
	{
		/* copy the stuff preceding a */
		memcpy (dest_str, start_str, end_str - start_str);
		dest_str = &(dest_str[end_str - start_str]);
		/* copy b in the place of a */
		memcpy (dest_str, b, b_len);
		dest_str = &(dest_str[b_len]);
		/* move to the end of the occurrence of a */
		start_str = &(end_str[a_len]);
	}

	/* copy the last part of the string and terminate it */
	memcpy (dest_str, start_str, strlen (start_str));
	dest_str[strlen (start_str)] = '\0';

	return new_str;
}

static gchar*
get_first_directory (const gchar *start_dir) 
{
	GnomeVFSResult rc;
	GList *list = NULL;
	GList *node;
	GnomeVFSFileInfo *info;
	gchar *prefs_dir = NULL;

	rc = gnome_vfs_directory_list_load(&list,
					   start_dir,
					   (GNOME_VFS_FILE_INFO_GET_MIME_TYPE
					    | GNOME_VFS_FILE_INFO_FORCE_FAST_MIME_TYPE
					    | GNOME_VFS_FILE_INFO_FOLLOW_LINKS),
					   NULL);

	for (node = list; node; node = node->next) {
		info = node->data;

		if (info->type == GNOME_VFS_FILE_TYPE_DIRECTORY && 
				strcmp (info->name, ".") &&
				strcmp (info->name, ".."))
		{
			prefs_dir = g_strdup_printf("%s/%s/",start_dir, info->name);
			break;
		}
	}

	gnome_vfs_file_info_list_free(list);

	return prefs_dir;
}

gchar* get_mozilla_prefs_directory () 
{
	gchar *prefs_dir = NULL;
	gchar *default_dir;

	default_dir = g_strconcat (g_get_home_dir(), MOZILLA_PREFS_DIR, NULL);

	/* descend first dir (_usually_ .mozilla/default) */
	prefs_dir = get_first_directory (default_dir);
	g_free (default_dir);
	if (!prefs_dir) return NULL;
	default_dir = prefs_dir;
	
	/* descend second dir something like (.mozilla/default/n8u3dbaou.slt) */
	prefs_dir = get_first_directory (default_dir);
	g_free (default_dir);

	return prefs_dir;
}

void
launch_external_viewer (const gchar *filename)
{
	gchar *prog, *command;
	int pid;

	g_return_if_fail(filename != NULL);

	/* mark the file to be deleted upon galeon exit. */
	mozilla_delete_temp_file_on_exit (filename);

	/* get the program to execute */
	prog = eel_gconf_get_string (CONF_PROGRAMS_EXTERNAL_SOURCE_VIEWER);
	if (!prog) return;

	command = g_strconcat (prog," ", filename, NULL);

	/* execute the viewer */
	pid = gnome_execute_shell (NULL, command);

	/* free allocated string */
	g_free (command);
	g_free (prog);
}

/**
 * xmlSetRawProp: set a property in an XML file which has value encoded.
 * This works around the newline-losingness and broken entitiy handling 
 * of libxml1. (see bug #56487)
 */
xmlAttrPtr
xmlSetRawProp (xmlNodePtr node, const xmlChar *name, const xmlChar *value)
{
	xmlAttrPtr attr;
	gchar *escaped = escape_xml_prop (value);
	attr = xmlSetProp (node, name, escaped);
	g_free (escaped);
	return attr;
}
/**
 * xmlGetRawProp: get a property in an XML file which has been encoded
 * This works around the newline-losingness and broken entitiy handling 
 * of libxml1. (see bug #56487)
 */
xmlChar *
xmlGetRawProp (xmlNodePtr node, const xmlChar *name)
{
	xmlChar *value;
	gchar *unescaped;
	value = xmlGetProp (node, name);
	if (value == NULL)
	{
		return NULL;
	}
	unescaped = unescape_hexed_string (value);
	xmlFree (value);
	return unescaped;
}
/**
 * Escape chars in string such that xmlGetProp doesn't screw it up
 * caller is responsible for freeing the returned string
 */
gchar *
escape_xml_prop (const gchar *text)
{
	GString *out = g_string_new (NULL);
	guchar c;
	while ((c = *text))
	{
		if (c<0x20 || c>0x7F || c=='&' || c=='<' || c=='>' || c=='%' ||
				c=='"') //Should anything else be escaped?
			g_string_sprintfa (out, "%%%02X", c);
		else
			g_string_append_c (out, c);
		++text;
	}
	text = out->str;
	g_string_free (out, FALSE); /* don't free char data, caller must */
	return (gchar *)text;
}

gchar *
escape_path (const gchar *text)
{
	GString *out = g_string_new (NULL);
	guchar c;
	while ((c = *text))
	{
		if (c=='/' ||  c==' ' || c=='?' || c=='#' || c=='%') //Should anything else be escaped?
			g_string_sprintfa (out, "%%%02X", c);
		else
			g_string_append_c (out, c);
		++text;
	}
	text = out->str;
	g_string_free (out, FALSE); /* don't free char data, caller must */
	return (gchar *)text;
}

gchar *
unescape_hexed_string (const gchar *text)
{
	GString *out = g_string_new (NULL);
	const gchar *u, *cur = text;
	gchar decodebuf[3];
	decodebuf[2] = '\0';
	while ((u = strchr (cur, '%')))
	{
		decodebuf[0]=u[1];
		decodebuf[1]=u[2];
		g_string_sprintfa (out, "%.*s%c", u-cur, cur, 
					(char)strtol(decodebuf, NULL, 16));
		cur = u + 3;
	}
	if (cur && *cur)
	{
		/* attach rest of string */
		g_string_append (out, cur);
	}

	u = out->str;
	g_string_free (out, FALSE); /* don't free char data, caller must */
	return (gchar *)u;
}

LinkState key_state_to_link_state (guint evstate)
{
	return ((evstate & GDK_CONTROL_MASK) ? 1 : 0) |
		((evstate & GDK_SHIFT_MASK) ? LINKSTATE_SHIFTED : 0);

}

LinkState mouse_state_to_link_state (guint button, guint evstate)
{
	return ((button - 1) |
		((evstate & GDK_SHIFT_MASK)   ? LINKSTATE_SHIFTED : 0) |
		((evstate & GDK_CONTROL_MASK) ? LINKSTATE_CTRLED  : 0) |
		((evstate & GDK_MOD1_MASK)    ? LINKSTATE_ALTED   : 0));
}

PixmapData *
pixmap_data_new_from_file (const gchar *file, gboolean prelight)
{
	GdkPixbuf *pixbuf;
	PixmapData *pixmap_data;

	/* load pixbuf from disk */
	pixbuf = gdk_pixbuf_new_from_file (file);

	/* check... */
	if (pixbuf == NULL)
	{
		/* warn */
		g_warning ("`%s' does not exist or is not "
			   "a valid image file\n", file);

		/* callers should handle this */
		return NULL;
	}

	/* threshold out alpha if possible */
	if (gdk_pixbuf_get_has_alpha       (pixbuf) &&
	    gdk_pixbuf_get_bits_per_sample (pixbuf) == 8 &&
	    gdk_pixbuf_get_colorspace      (pixbuf) == GDK_COLORSPACE_RGB &&
	    gdk_pixbuf_get_n_channels      (pixbuf) == 4)
	{
		pixbuf_threshold_alpha (pixbuf, prelight);
	}
	
	/* allocate space for a new pixmap */
	pixmap_data = g_new0 (PixmapData, 1);

	/* render the pixbuf into the pixmap data */
	gdk_pixbuf_render_pixmap_and_mask (pixbuf, &(pixmap_data->pixmap),
					   &(pixmap_data->mask), 100);

	/* lose the pixbuf */
	gdk_pixbuf_unref (pixbuf);
	
	/* return completed structure */
	return pixmap_data; 
}

/**
 * pixbuf_threshold_alpha: this function does the job of thresolding alpha
 * in a pixbuf, by compositing with the default theme background colour,
 * and marking alpha < 0.125 as alpha = 0 -- this looks nice enough against
 * most flat, gradient and pixmap themes
 */
static void
pixbuf_threshold_alpha (GdkPixbuf *pixbuf, gboolean prelight)
{
	gint width, height, rowstride;
	guchar r, g, b, a;
	guchar tr, tg, tb;
	guchar *buffer;
	gint x, y, i;
	GdkColor bg;
	GtkWidget *dummy = gtk_button_new ();
	GtkStyle *rcstyle = gtk_rc_get_style (dummy);

	/* this is needed for some (broken?) themes */
	if (rcstyle == NULL)
	{
		rcstyle = gtk_style_new ();
	}
	else
	{
		gtk_style_ref (rcstyle);
	}

	/* get pixbuf properties */
	width  = gdk_pixbuf_get_width (pixbuf);
	height = gdk_pixbuf_get_height (pixbuf);
	buffer = gdk_pixbuf_get_pixels (pixbuf);
	rowstride = gdk_pixbuf_get_rowstride (pixbuf);
	
	/* get target colour to blend with based on style background */
	bg = prelight ? rcstyle->bg[2]: rcstyle->bg[0];
	tr = bg.red   >> 8;
	tg = bg.green >> 8;
	tb = bg.blue  >> 8;

	/* blend whole image */
	for (i = 0, y = 0; y < height; y++, i += rowstride - (width * 4))
	{
		for (x = 0; x < width; x++, i += 4)
		{
			/* get pixel */
			r = buffer[i + 0];
			g = buffer[i + 1];
			b = buffer[i + 2];
			a = buffer[i + 3];

			/* blend pixel */
			r = ((a * r) + ((255 ^ a) * tr)) >> 8;
			g = ((a * g) + ((255 ^ a) * tg)) >> 8;
			b = ((a * b) + ((255 ^ a) * tb)) >> 8;

			/* threshold alpha */
			if (a > 12)
			{
				a = 255;
			}
			else
			{
				a = 0;
			}
			
			/* writeback pixel */
			buffer[i + 0] = r;
			buffer[i + 1] = g;
			buffer[i + 2] = b;
			buffer[i + 3] = a;
		}
	}

	gtk_widget_destroy (dummy);
	gtk_style_unref (rcstyle);

}

/* NOTE: str must be at least 256 chars long */
void
store_time_in_string (GTime t, gchar *str)
{
	struct tm stm;
	int length;

	if (t > 0)
	{
		/* convert to local time */
		localtime_r ((time_t *)&t, &stm);

		/* format into string */
		/* this is used whenever a brief date is needed, like
		 * in the history (for last visited, first time visited) */
		length = strftime (str, 255, _("%Y-%m-%d"), &stm);
		str[length] = '\0';
	}
	else
	{
		str[0] = '\0';
	}
}

void
store_full_time_in_string (GTime t, gchar *str)
{
	struct tm stm;
	int length;

	if (t > 0)
	{
		/* convert to local time */
		localtime_r ((time_t *)&t, &stm);

		/* format into string */
		/* this is used when querying overwriting, as in "are you
		 * sure you want to overwrite file foo, last modified ..." */
		length = strftime (str, 255, _("%Y-%m-%d %H:%M:%S"), &stm);
		str[length] = '\0';
	}
	else
	{
		str[0] = '\0';
	}
}


gchar *
time_to_string (GTime t)
{
	gchar str[256];

	/* write into stack string */
	store_time_in_string (t, str);

	/* copy in heap and return */
	return g_strdup (str);
}

gint
xmlGetIntProp (xmlNodePtr node, const gchar *attribute)
{
	gchar *string_value;
	gint value;

	/* get the attribute as a string */
	string_value = xmlGetProp (node, attribute);

	/* convert to integer */
	/* FIXME: atoi is crap */
	value = atoi (string_value);

	/* free allocated string */
	xmlFree (string_value);

	/* return discovered value */
	return value;
}

gchar *
misc_parse_uri (const gchar *uri)
{
	const gchar *filename;
	const gchar *mime_type;
	

	/* check it's a file uri */
	if (strncmp (uri, "file:", 5) != 0)
	{
		return NULL;
	}

	/* check it exists */
	filename = uri + 5;
	if (!(g_file_exists (filename)))
	{
		return NULL;
	}

	/* get the mime type and check its a nautilus link */
	mime_type = gnome_vfs_get_file_mime_type (filename, NULL, FALSE);
	/* old style */
	if (strcmp (mime_type, "application/x-nautilus-link") == 0)
	{
		xmlDocPtr doc;
		gchar *url;
		
		/* attempt to parse it */
		doc = xmlParseFile (filename);
		
		/* check the document */
		if (doc == NULL)
		{
			return NULL;
		}
		
		/* check the structure */
		if (doc->root == NULL || doc->root->name == NULL ||
		    g_strcasecmp (doc->root->name, "nautilus_object") != 0)
		{
			xmlFreeDoc (doc);
			return NULL;
		}
		
		/* get the url */
		url = xmlGetProp (doc->root, "link");
		if (url == NULL || strlen (url) == 0)
		{
			xmlFreeDoc (doc);
			return NULL;
		}
		
		/* return the URL */
		xmlFreeDoc (doc);
		return url;
	}
	
	return NULL;
}

/**
 * gtk_radio_button_get: get the active member of a radiobutton group 
 * from one of the buttons in the group. This should be in GTK+!
 */
gint
gtk_radio_button_get (GtkRadioButton *radio_button)
{
	GtkToggleButton *toggle_button;
	gint i, length;
        GSList *list;
	
	/* get group list */
        list = gtk_radio_button_group (GTK_RADIO_BUTTON (radio_button));
        length = g_slist_length (list);

	/* iterate over list to find active button */
	for (i = 0; list != NULL; i++, list = g_slist_next (list))
	{
		/* get button and text */
		toggle_button = GTK_TOGGLE_BUTTON (list->data);
		if (gtk_toggle_button_get_active (toggle_button))
		{
			break;
		}
	}

	/* check we didn't run off end */
	g_assert (list != NULL);

	/* return index (reverse order!) */
	return (length - 1) - i;
}

/**
 * gtk_radio_button_set: set the active member of a radiobutton group
 * from one of the buttons in the group. This should be in GTK+!
 */
void
gtk_radio_button_set (GtkRadioButton *radio_button, gint index)
{
	GtkToggleButton *button;
	GSList *list;
	gint length;

	/* get the list */
        list = gtk_radio_button_group (GTK_RADIO_BUTTON (radio_button));

	/* check out the length */
        length = g_slist_length (list);

        /* new buttons are *preppended* to the list, so button added as first 
         * has last position in the list */
        index = (length - 1) - index;

	/* find the right button */
        button = GTK_TOGGLE_BUTTON (g_slist_nth_data (list, index));

	/* set it... this will de-activate the others in the group */
	if (gtk_toggle_button_get_active (button) == FALSE)
	{
		gtk_toggle_button_set_active (button, TRUE);
	}
}

/**
 * simplify_uri: Simplify a local filesystem uri, eg /home/jorn/blah/../txt
 * to /home/jorn/txt. Caller should free string.
 */
gchar *
simplify_uri (const gchar *uri)
{
	gchar **chars, *ret;
	GString *gstring;
	gint i;
	GList *l, *list = NULL;
	gboolean leading_slash = FALSE;

	if (uri[0] == '/') leading_slash = TRUE;
	
	chars = g_strsplit (uri, "/", 0);

	for (i = 0; chars[i] != NULL; i++)
	{
		if (strcmp (chars[i], ".") == 0)
		{
			g_free (chars[i]);
			chars[i] = g_strdup ("");
		}
		else if (strcmp (chars[i], "..") == 0)
		{
		 	GList *l;
			gint c;

			for (c = i - 1; c > 0 && chars[c] != NULL; c--)
			{
				if (strcmp (chars[c], "..") != 0 &&
				    strlen (chars[c]) > 0)
				{
					break;
				}
			}

			g_assert (chars[c] != NULL);

			l = g_list_find (list, chars[c]);
			if (l)
			{
				list = g_list_remove (list, l->data);
				g_free (chars[c]);
				chars[c] = g_strdup ("");
			}
		}
		else
		{
			list = g_list_append (list, chars[i]);
		}
	}

	if (leading_slash)
		gstring = g_string_new ("/");
	else
		gstring = g_string_new ("");

	for (l = list; l; l = g_list_next (l))
	{
		if (strcmp ((gchar *) l->data, "") == 0) continue;

		if (strcmp (gstring->str,  "") != 0 &&
		    strcmp (gstring->str, "/") != 0)
		{
			g_string_append_c (gstring, '/');
		}
		g_string_append (gstring, (gchar *) l->data);
	}

	ret = gstring->str;
	
	g_string_free (gstring, FALSE);
	g_list_free (list);
	g_strfreev (chars);
	  
	return ret;
}

/**
 * xml_decode_entity: translate &amp; et al and return a new expanded string 
 */
gchar *
xml_decode_entity (const unsigned char *encoded)
{
	gchar *buffer;
	gint i, j, length;
	
	if (encoded == NULL)
	{
		return NULL;
	}

	buffer = g_strdup (encoded);

	/* Optimize for case where no escape codes are found, otherwise
	 * use the first found code as the starting index */
	if ((i = (char *) strstr (buffer, "&") - buffer) < 0)
	{
		return buffer;
	}
	
	length = strlen (buffer);
	for (j = i; i < length; i++)
	{
		if (encoded[i] == '&')
		{
			if (strncmp (encoded + i + 1, "amp;", 4) == 0)
			{
				buffer[j++] = '&';
				i += 4;
			}
			else if (strncmp (encoded + i + 1, "lt;", 3) == 0)
			{
				buffer[j++] = '<';
				i += 3;
			}
			else if (strncmp (encoded + i + 1, "gt;", 3) == 0)
			{
				buffer[j++] = '>';
				i += 3;
			}
			else if (strncmp (encoded + i + 1, "quot;", 5) == 0)
			{
				buffer[j++] = '"';
				i += 5;
			}
			else if (strncmp (encoded + i + 1, "apos;", 5) == 0)
			{
				buffer[j++] = '\'';
				i += 5;
			}
			else if (encoded[i + 1] == '#')
			{
				buffer[j++] = atoi (encoded + i + 2);
				i += 5;
			}
		}
		else
		{
			buffer[j++] = encoded[i];
		}
	}

	buffer[j] = '\0';
	return buffer;
}

/**
 * toolbar_append_separator: build a separator with correct dimensions
 */
GtkWidget *
toolbar_append_separator (GtkToolbar *toolbar)
{
	GtkWidget *vsep = gtk_vseparator_new (), *hsep = gtk_hseparator_new ();

	gtk_toolbar_append_widget (GTK_TOOLBAR (toolbar), vsep, NULL, NULL);
	gtk_toolbar_append_widget (GTK_TOOLBAR (toolbar), hsep, NULL, NULL);
	
	gtk_signal_connect_after (GTK_OBJECT (toolbar), "orientation_changed", 
			    GTK_SIGNAL_FUNC
			   	(separator_toolbar_orientation_changed_cb),
			    vsep);
	gtk_signal_connect_after (GTK_OBJECT (toolbar), "orientation_changed", 
			    GTK_SIGNAL_FUNC
			   	(separator_toolbar_orientation_changed_cb),
			    hsep);
	
	if (GTK_TOOLBAR (toolbar)->orientation == GTK_ORIENTATION_HORIZONTAL)
	{
		gtk_widget_show (vsep);
		return vsep;
	}
	else
	{
		gtk_widget_show (hsep);
		return hsep;
	}
}

/*************************************************************************/
/* original code stolen from gtkaccellabel code, GTK+ CREDITS            */
/* modification necessary because gtkaccellabel doesn't work for what we */
/* want to do in the bookmark menu code                     -- jorn      */
/* N.B. Strings are not translated here as they aren't in original gtk   */
/* either                                                                */
/*************************************************************************/ 
gchar *
string_from_accel_data (GtkWidget *widget, guint accel_key, 
			GdkModifierType accel_mods)
{
	GtkAccelLabel *al = GTK_ACCEL_LABEL (gtk_accel_label_new (""));
	GtkAccelLabelClass *class = 
		GTK_ACCEL_LABEL_CLASS (GTK_OBJECT (al)->klass);
	gchar *str = NULL;
      
	GString *gstring;
	gboolean had_mod = FALSE;
	
	gstring = g_string_new ("   ");

	if (accel_mods & GDK_SHIFT_MASK)
	{
		g_string_append (gstring, class->mod_name_shift);
		had_mod = TRUE;
	}
	if (accel_mods & GDK_CONTROL_MASK)
	{
		if (had_mod)
	    		g_string_append (gstring, class->mod_separator);
		g_string_append (gstring, class->mod_name_control);
		had_mod = TRUE;
	}
	if (accel_mods & GDK_MOD1_MASK)
	{
		if (had_mod)
			g_string_append (gstring, class->mod_separator);
		g_string_append (gstring, class->mod_name_alt);
		had_mod = TRUE;
	}
	      
	if (had_mod)
		g_string_append (gstring, class->mod_separator);
      	if (accel_key < 0x80 || (accel_key > 0x80 && 
	    accel_key <= 0xff && class->latin1_to_char))
	{
		switch (accel_key)
	    	{
		case ' ':
			g_string_append (gstring, "Space");
			break;
		case '\\':
			g_string_append (gstring, "Backslash");
			break;
		default:
			g_string_append_c (gstring, 
					   toupper (accel_key));
			break;
		}
	}
	else
	{
		gchar *tmp;
	  
		tmp = gtk_accelerator_name (accel_key, 0);
		if (tmp[0] != 0 && tmp[1] == 0)
			tmp[0] = toupper (tmp[0]);
		g_string_append (gstring, tmp);
		g_free (tmp);
	}

	str = gstring->str;
	g_string_free (gstring, FALSE);

	return str;
}

/*************************************************************************/
/* gnome_dialog_run code stolen from libgnomeui, modified because the    */
/* original is buggy.                        -- jorn                     */
/*************************************************************************/

struct GnomeDialogRunInfo {
  gint button_number;
  gint close_id, clicked_id, destroy_id;
  gboolean destroyed;
};

static void
gnome_dialog_shutdown_run(GnomeDialog* dialog,
                          struct GnomeDialogRunInfo* runinfo)
{
  if (!runinfo->destroyed) 
    {
      
      gtk_signal_disconnect(GTK_OBJECT(dialog),
                            runinfo->close_id);
      gtk_signal_disconnect(GTK_OBJECT(dialog),
                            runinfo->clicked_id);
  
      runinfo->close_id = runinfo->clicked_id = -1;
    }

    gtk_main_quit();
}

static void
gnome_dialog_setbutton_callback(GnomeDialog *dialog,
				gint button_number,
				struct GnomeDialogRunInfo *runinfo)
{
  if(runinfo->close_id < 0)
    return;

  runinfo->button_number = button_number;

  gnome_dialog_shutdown_run(dialog, runinfo);
}

static gboolean
gnome_dialog_quit_run(GnomeDialog *dialog,
		      struct GnomeDialogRunInfo *runinfo)
{
  if(runinfo->close_id < 0)
    return FALSE;

  gnome_dialog_shutdown_run(dialog, runinfo);

  return FALSE;
}

static void
gnome_dialog_mark_destroy(GnomeDialog* dialog,
                          struct GnomeDialogRunInfo* runinfo)
{
  runinfo->destroyed = TRUE;

  if(runinfo->close_id < 0)
    return;
  else gnome_dialog_shutdown_run(dialog, runinfo);
}

gint
dialog_run(GnomeDialog* dialog, gboolean close_after)
{
  struct GnomeDialogRunInfo ri = {-1,-1,-1,-1,FALSE};

  g_return_val_if_fail(dialog != NULL, -1);
  g_return_val_if_fail(GNOME_IS_DIALOG(dialog), -1);

  /* There are several things that could happen to the dialog, and we
     need to handle them all: click, delete_event, close, destroy */

  ri.clicked_id =
    gtk_signal_connect(GTK_OBJECT(dialog), "clicked",
		       GTK_SIGNAL_FUNC(gnome_dialog_setbutton_callback),
		       &ri);

  ri.close_id =
    gtk_signal_connect(GTK_OBJECT(dialog), "close",
		       GTK_SIGNAL_FUNC(gnome_dialog_quit_run),
		       &ri);

  ri.destroy_id = 
    gtk_signal_connect(GTK_OBJECT(dialog), "destroy",
                       GTK_SIGNAL_FUNC(gnome_dialog_mark_destroy),
                       &ri);

  if ( ! GTK_WIDGET_VISIBLE(GTK_WIDGET(dialog)) )
    gtk_widget_show(GTK_WIDGET(dialog));

  gtk_main();
  
  if(!ri.destroyed) {

    gtk_signal_disconnect(GTK_OBJECT(dialog), ri.destroy_id);

    if(ri.close_id >= 0) /* We didn't shut down the run? */
      {
	gtk_signal_disconnect(GTK_OBJECT(dialog), ri.close_id);
	gtk_signal_disconnect(GTK_OBJECT(dialog), ri.clicked_id);
      }

    if (close_after)
      {
        gnome_dialog_close(dialog);
      }
  }

  return ri.button_number;
}

/*************************************************************************/
/* tree utility functions stolen from gtkctree code, GTK+ CREDITS        */
/* these are necessary because the original GtkCTree is a total f*ck-up, */
/* and has O(n^2) sorting complexity :-(                    -- MattA     */
/*************************************************************************/

static GtkCTreeNode *
gtk_ctree_last_visible (GtkCTree     *ctree,
			GtkCTreeNode *node)
{
  GtkCTreeNode *work;
  
  if (!node)
    return NULL;

  work = GTK_CTREE_ROW (node)->children;

  if (!work || !GTK_CTREE_ROW (node)->expanded)
    return node;

  while (GTK_CTREE_ROW (work)->sibling)
    work = GTK_CTREE_ROW (work)->sibling;

  return gtk_ctree_last_visible (ctree, work);
}

void
gtk_ctree_link (GtkCTree     *ctree,
		GtkCTreeNode *node,
		GtkCTreeNode *parent,
		GtkCTreeNode *sibling,
		gboolean      update_focus_row)
{
  GtkCList *clist;
  GList *list_end;
  GList *list;
  GList *work;
  gboolean visible = FALSE;
  gint rows = 0;
  
  clist = GTK_CLIST (ctree);

  for (rows = 1, list_end = (GList *)node; list_end->next;
       list_end = list_end->next)
    rows++;

  GTK_CTREE_ROW (node)->parent = parent;
  GTK_CTREE_ROW (node)->sibling = sibling;

  if (!parent || (parent && (gtk_ctree_is_viewable (ctree, parent) &&
			     GTK_CTREE_ROW (parent)->expanded)))
    {
      visible = TRUE;
      clist->rows += rows;
    }

  if (parent)
    work = (GList *)(GTK_CTREE_ROW (parent)->children);
  else
    work = clist->row_list;

  if (sibling)
    {
      if (work != (GList *)sibling)
	{
	  while (GTK_CTREE_ROW (work)->sibling != sibling)
	    work = (GList *)(GTK_CTREE_ROW (work)->sibling);
	  GTK_CTREE_ROW (work)->sibling = node;
	}

      if (sibling == GTK_CTREE_NODE (clist->row_list))
	clist->row_list = (GList *) node;
      if (GTK_CTREE_NODE_PREV (sibling) &&
	  GTK_CTREE_NODE_NEXT (GTK_CTREE_NODE_PREV (sibling)) == sibling)
	{
	  list = (GList *)GTK_CTREE_NODE_PREV (sibling);
	  list->next = (GList *)node;
	}
      
      list = (GList *)node;
      list->prev = (GList *)GTK_CTREE_NODE_PREV (sibling);
      list_end->next = (GList *)sibling;
      list = (GList *)sibling;
      list->prev = list_end;
      if (parent && GTK_CTREE_ROW (parent)->children == sibling)
	GTK_CTREE_ROW (parent)->children = node;
    }
  else
    {
      if (work)
	{
	  /* find sibling */
	  while (GTK_CTREE_ROW (work)->sibling)
	    work = (GList *)(GTK_CTREE_ROW (work)->sibling);
	  GTK_CTREE_ROW (work)->sibling = node;
	  
	  /* find last visible child of sibling */
	  work = (GList *) gtk_ctree_last_visible (ctree,
						   GTK_CTREE_NODE (work));
	  
	  list_end->next = work->next;
	  if (work->next)
	    list = work->next->prev = list_end;
	  work->next = (GList *)node;
	  list = (GList *)node;
	  list->prev = work;
	}
      else
	{
	  if (parent)
	    {
	      GTK_CTREE_ROW (parent)->children = node;
	      list = (GList *)node;
	      list->prev = (GList *)parent;
	      if (GTK_CTREE_ROW (parent)->expanded)
		{
		  list_end->next = (GList *)GTK_CTREE_NODE_NEXT (parent);
		  if (GTK_CTREE_NODE_NEXT(parent))
		    {
		      list = (GList *)GTK_CTREE_NODE_NEXT (parent);
		      list->prev = list_end;
		    }
		  list = (GList *)parent;
		  list->next = (GList *)node;
		}
	      else
		list_end->next = NULL;
	    }
	  else
	    {
	      clist->row_list = (GList *)node;
	      list = (GList *)node;
	      list->prev = NULL;
	      list_end->next = NULL;
	    }
	}
    }

  if (clist->row_list_end == NULL ||
      clist->row_list_end->next == (GList *)node)
    clist->row_list_end = list_end;

  if (visible && update_focus_row)
    {
      gint pos;
	  
      pos = g_list_position (clist->row_list, (GList *)node);
  
      if (pos <= clist->focus_row)
	{
	  clist->focus_row += rows;
	  clist->undo_anchor = clist->focus_row;
	}
    }
}

void
gtk_ctree_unlink (GtkCTree     *ctree, 
		  GtkCTreeNode *node,
                  gboolean      update_focus_row)
{
  GtkCList *clist;
  gint rows;
  gint level;
  gint visible;
  GtkCTreeNode *work;
  GtkCTreeNode *parent;
  GList *list;

  g_return_if_fail (ctree != NULL);
  g_return_if_fail (GTK_IS_CTREE (ctree));
  g_return_if_fail (node != NULL);

  clist = GTK_CLIST (ctree);
  
  if (update_focus_row && clist->selection_mode == GTK_SELECTION_EXTENDED)
    {
//    GTK_CLIST_GET_CLASS (clist)->resync_selection (clist, NULL);
      
      g_list_free (clist->undo_selection);
      g_list_free (clist->undo_unselection);
      clist->undo_selection = NULL;
      clist->undo_unselection = NULL;
    }

  visible = gtk_ctree_is_viewable (ctree, node);

  /* clist->row_list_end unlinked ? */
  if (visible &&
      (GTK_CTREE_NODE_NEXT (node) == NULL ||
       (GTK_CTREE_ROW (node)->children &&
	gtk_ctree_is_ancestor (ctree, node,
			       GTK_CTREE_NODE (clist->row_list_end)))))
    clist->row_list_end = (GList *) (GTK_CTREE_NODE_PREV (node));

  /* update list */
  rows = 0;
  level = GTK_CTREE_ROW (node)->level;
  work = GTK_CTREE_NODE_NEXT (node);
  while (work && GTK_CTREE_ROW (work)->level > level)
    {
      work = GTK_CTREE_NODE_NEXT (work);
      rows++;
    }

  if (visible)
    {
      clist->rows -= (rows + 1);

      if (update_focus_row)
	{
	  gint pos;
	  
	  pos = g_list_position (clist->row_list, (GList *)node);
	  if (pos + rows < clist->focus_row)
	    clist->focus_row -= (rows + 1);
	  else if (pos <= clist->focus_row)
	    {
	      if (!GTK_CTREE_ROW (node)->sibling)
		clist->focus_row = MAX (pos - 1, 0);
	      else
		clist->focus_row = pos;
	      
	      clist->focus_row = MIN (clist->focus_row, clist->rows - 1);
	    }
	  clist->undo_anchor = clist->focus_row;
	}
    }

  if (work)
    {
      list = (GList *)GTK_CTREE_NODE_PREV (work);
      list->next = NULL;
      list = (GList *)work;
      list->prev = (GList *)GTK_CTREE_NODE_PREV (node);
    }

  if (GTK_CTREE_NODE_PREV (node) &&
      GTK_CTREE_NODE_NEXT (GTK_CTREE_NODE_PREV (node)) == node)
    {
      list = (GList *)GTK_CTREE_NODE_PREV (node);
      list->next = (GList *)work;
    }

  /* update tree */
  parent = GTK_CTREE_ROW (node)->parent;
  if (parent)
    {
      if (GTK_CTREE_ROW (parent)->children == node)
	{
	  GTK_CTREE_ROW (parent)->children = GTK_CTREE_ROW (node)->sibling;
	  if (!GTK_CTREE_ROW (parent)->children)
	  {
//	    gtk_ctree_collapse (ctree, parent);
	  }
	}
      else
	{
	  GtkCTreeNode *sibling;

	  sibling = GTK_CTREE_ROW (parent)->children;
	  while (GTK_CTREE_ROW (sibling)->sibling != node)
	    sibling = GTK_CTREE_ROW (sibling)->sibling;
	  GTK_CTREE_ROW (sibling)->sibling = GTK_CTREE_ROW (node)->sibling;
	}
    }
  else
    {
      if (clist->row_list == (GList *)node)
	clist->row_list = (GList *) (GTK_CTREE_ROW (node)->sibling);
      else
	{
	  GtkCTreeNode *sibling;

	  sibling = GTK_CTREE_NODE (clist->row_list);
	  while (GTK_CTREE_ROW (sibling)->sibling != node)
	    sibling = GTK_CTREE_ROW (sibling)->sibling;
	  GTK_CTREE_ROW (sibling)->sibling = GTK_CTREE_ROW (node)->sibling;
	}
    }
}

void galeon_exit (void)
{
	extern GList *mozilla_notifiers;

	eel_gconf_set_integer (CONF_CRASH_CRASHED, FALSE);

	/* FIXME: uncatch GDK errors which happen on theme changes */
	gdk_error_trap_pop ();

	/* shut down galeon subsystems */
	preferences_save ();
	mime_db_shutdown ();
	bookmarks_exit ();
	history_exit ();
	mozilla_save_prefs ();

	/* check... */
	g_assert (g_list_length (all_embeds) == 0);
	g_assert (g_list_length (all_windows) == 0);

	/* shut down GNOME subsystems */
	gnome_vfs_shutdown ();

	/* remove notifiers for mozilla prefs */
	galeon_notification_remove (&mozilla_notifiers);

	/* end our mainloop */
	gtk_main_quit();
}

/*************************************************************************/
