aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorMarco Pesenti Gritti <mpeseng@src.gnome.org>2002-12-31 03:29:24 +0800
committerMarco Pesenti Gritti <mpeseng@src.gnome.org>2002-12-31 03:29:24 +0800
commit6876ede98282c7db318089bfefb292aa59e55d48 (patch)
tree76b23252d04da232d0ebf22e53bfe3e022686af9 /lib
downloadgsoc2013-epiphany-6876ede98282c7db318089bfefb292aa59e55d48.tar.gz
gsoc2013-epiphany-6876ede98282c7db318089bfefb292aa59e55d48.tar.zst
gsoc2013-epiphany-6876ede98282c7db318089bfefb292aa59e55d48.zip
Initial revision
Diffstat (limited to 'lib')
-rw-r--r--lib/.cvsignore6
-rw-r--r--lib/Makefile.am73
-rw-r--r--lib/eel-gconf-extensions.c556
-rw-r--r--lib/eel-gconf-extensions.h87
-rw-r--r--lib/ephy-autocompletion-source.c85
-rw-r--r--lib/ephy-autocompletion-source.h80
-rw-r--r--lib/ephy-autocompletion.c664
-rw-r--r--lib/ephy-autocompletion.h107
-rw-r--r--lib/ephy-bonobo-extensions.c679
-rw-r--r--lib/ephy-bonobo-extensions.h121
-rw-r--r--lib/ephy-dialog.c943
-rw-r--r--lib/ephy-dialog.h119
-rw-r--r--lib/ephy-dnd.c109
-rw-r--r--lib/ephy-dnd.h67
-rw-r--r--lib/ephy-file-helpers.c326
-rw-r--r--lib/ephy-file-helpers.h48
-rw-r--r--lib/ephy-filesystem-autocompletion.c343
-rw-r--r--lib/ephy-filesystem-autocompletion.h70
-rw-r--r--lib/ephy-glade.c134
-rw-r--r--lib/ephy-glade.h41
-rw-r--r--lib/ephy-gobject-misc.h77
-rw-r--r--lib/ephy-gui.c352
-rw-r--r--lib/ephy-gui.h79
-rw-r--r--lib/ephy-marshal.list16
-rw-r--r--lib/ephy-node-filter.c461
-rw-r--r--lib/ephy-node-filter.h102
-rw-r--r--lib/ephy-node.c1439
-rw-r--r--lib/ephy-node.h144
-rw-r--r--lib/ephy-prefs-utils.c285
-rw-r--r--lib/ephy-prefs-utils.h61
-rw-r--r--lib/ephy-prefs.h58
-rw-r--r--lib/ephy-state.c157
-rw-r--r--lib/ephy-state.h44
-rw-r--r--lib/ephy-stock-icons.c62
-rw-r--r--lib/ephy-stock-icons.h33
-rw-r--r--lib/ephy-string.c446
-rw-r--r--lib/ephy-string.h63
-rw-r--r--lib/ephy-thread-helpers.c35
-rw-r--r--lib/ephy-thread-helpers.h34
-rw-r--r--lib/ephy-types.h35
-rw-r--r--lib/toolbar/.cvsignore6
-rw-r--r--lib/toolbar/Makefile.am41
-rw-r--r--lib/toolbar/ephy-tbi-favicon.c188
-rw-r--r--lib/toolbar/ephy-tbi-favicon.h66
-rw-r--r--lib/toolbar/ephy-tbi-location.c205
-rw-r--r--lib/toolbar/ephy-tbi-location.h66
-rw-r--r--lib/toolbar/ephy-tbi-navigation-history.c342
-rw-r--r--lib/toolbar/ephy-tbi-navigation-history.h81
-rw-r--r--lib/toolbar/ephy-tbi-separator.c179
-rw-r--r--lib/toolbar/ephy-tbi-separator.h68
-rw-r--r--lib/toolbar/ephy-tbi-spinner.c188
-rw-r--r--lib/toolbar/ephy-tbi-spinner.h66
-rw-r--r--lib/toolbar/ephy-tbi-std-toolitem.c467
-rw-r--r--lib/toolbar/ephy-tbi-std-toolitem.h86
-rw-r--r--lib/toolbar/ephy-tbi-zoom.c304
-rw-r--r--lib/toolbar/ephy-tbi-zoom.h66
-rw-r--r--lib/toolbar/ephy-toolbar-bonobo-view.c193
-rw-r--r--lib/toolbar/ephy-toolbar-bonobo-view.h75
-rw-r--r--lib/toolbar/ephy-toolbar-editor.c634
-rw-r--r--lib/toolbar/ephy-toolbar-editor.h80
-rw-r--r--lib/toolbar/ephy-toolbar-item-factory.c158
-rw-r--r--lib/toolbar/ephy-toolbar-item-factory.h31
-rw-r--r--lib/toolbar/ephy-toolbar-item.c142
-rw-r--r--lib/toolbar/ephy-toolbar-item.h91
-rw-r--r--lib/toolbar/ephy-toolbar-tree-model.c784
-rw-r--r--lib/toolbar/ephy-toolbar-tree-model.h74
-rw-r--r--lib/toolbar/ephy-toolbar.c420
-rw-r--r--lib/toolbar/ephy-toolbar.h80
-rw-r--r--lib/widgets/.cvsignore6
-rw-r--r--lib/widgets/Makefile.am30
-rw-r--r--lib/widgets/eggtreemodelfilter.c2560
-rw-r--r--lib/widgets/eggtreemodelfilter.h123
-rw-r--r--lib/widgets/eggtreemultidnd.c415
-rw-r--r--lib/widgets/eggtreemultidnd.h78
-rw-r--r--lib/widgets/ephy-autocompletion-window.c854
-rw-r--r--lib/widgets/ephy-autocompletion-window.h87
-rw-r--r--lib/widgets/ephy-ellipsizing-label.c774
-rw-r--r--lib/widgets/ephy-ellipsizing-label.h71
-rw-r--r--lib/widgets/ephy-location-entry.c700
-rw-r--r--lib/widgets/ephy-location-entry.h74
-rw-r--r--lib/widgets/ephy-notebook.c843
-rw-r--r--lib/widgets/ephy-notebook.h99
-rw-r--r--lib/widgets/ephy-spinner.c897
-rw-r--r--lib/widgets/ephy-spinner.h78
-rw-r--r--lib/widgets/ephy-tree-model-sort.c240
-rw-r--r--lib/widgets/ephy-tree-model-sort.h59
86 files changed, 21810 insertions, 0 deletions
diff --git a/lib/.cvsignore b/lib/.cvsignore
new file mode 100644
index 000000000..20e4cb0c8
--- /dev/null
+++ b/lib/.cvsignore
@@ -0,0 +1,6 @@
+Makefile
+Makefile.in
+*.lo
+.deps
+.libs
+*.la
diff --git a/lib/Makefile.am b/lib/Makefile.am
new file mode 100644
index 000000000..60092472f
--- /dev/null
+++ b/lib/Makefile.am
@@ -0,0 +1,73 @@
+SUBDIRS = widgets toolbar
+
+INCLUDES = \
+ $(WARN_CFLAGS) \
+ $(EPIPHANY_DEPENDENCY_CFLAGS) \
+ -DSHARE_DIR=\"$(pkgdatadir)\" \
+ -DG_DISABLE_DEPRECATED \
+ -DGDK_DISABLE_DEPRECATED \
+ -DGTK_DISABLE_DEPRECATED \
+ -DGDK_PIXBUF_DISABLE_DEPRECATED \
+ -DGNOME_DISABLE_DEPRECATED
+
+noinst_LTLIBRARIES = libephy.la
+
+libephy_la_SOURCES = \
+ ephy-types.h \
+ ephy-prefs.h \
+ ephy-gobject-misc.h \
+ eel-gconf-extensions.c \
+ eel-gconf-extensions.h \
+ ephy-dialog.c \
+ ephy-dialog.h \
+ ephy-dnd.c \
+ ephy-dnd.h \
+ ephy-marshal.c \
+ ephy-marshal.h \
+ ephy-types.h \
+ ephy-bonobo-extensions.h \
+ ephy-bonobo-extensions.c \
+ ephy-file-helpers.c \
+ ephy-file-helpers.h \
+ ephy-glade.c \
+ ephy-glade.h \
+ ephy-gui.c \
+ ephy-gui.h \
+ ephy-prefs-utils.c \
+ ephy-prefs-utils.h \
+ ephy-state.c \
+ ephy-state.h \
+ ephy-string.c \
+ ephy-string.h \
+ ephy-autocompletion.c \
+ ephy-autocompletion.h \
+ ephy-autocompletion-source.c \
+ ephy-autocompletion-source.h \
+ ephy-stock-icons.c \
+ ephy-stock-icons.h \
+ ephy-filesystem-autocompletion.c \
+ ephy-filesystem-autocompletion.h \
+ ephy-thread-helpers.c \
+ ephy-thread-helpers.h \
+ ephy-node.c \
+ ephy-node.h \
+ ephy-node-filter.c \
+ ephy-node-filter.h
+
+libephy_la_LIBADD = \
+ $(top_builddir)/lib/widgets/libephywidgets.la \
+ $(top_builddir)/lib/toolbar/libephytoolbar.la
+
+BUILT_SOURCES=ephy-marshal.c ephy-marshal.h
+
+CLEAN_FILES = $(BUILT_SOURCES)
+
+ephy-marshal.c: ephy-marshal.list
+ @GLIB_GENMARSHAL@ --prefix=ephy_marshal $(srcdir)/ephy-marshal.list --header --body > ephy-marshal.c
+
+ephy-marshal.h: ephy-marshal.list
+ @GLIB_GENMARSHAL@ --prefix=ephy_marshal $(srcdir)/ephy-marshal.list --header > ephy-marshal.h
+
+EXTRA_DIST = \
+ ephy-marshal.list
+
diff --git a/lib/eel-gconf-extensions.c b/lib/eel-gconf-extensions.c
new file mode 100644
index 000000000..e6593219a
--- /dev/null
+++ b/lib/eel-gconf-extensions.c
@@ -0,0 +1,556 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* eel-gconf-extensions.c - Stuff to make GConf easier to use.
+
+ Copyright (C) 2000, 2001 Eazel, Inc.
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Ramiro Estrugo <ramiro@eazel.com>
+*/
+
+#include <stdlib.h>
+#include <config.h>
+#include "eel-gconf-extensions.h"
+
+#include <gconf/gconf-client.h>
+#include <gconf/gconf.h>
+#include <gtk/gtkwidget.h>
+#include <libgnome/gnome-i18n.h>
+#include <gtk/gtkmessagedialog.h>
+
+static GConfClient *global_gconf_client = NULL;
+
+static void
+global_client_free (void)
+{
+ if (global_gconf_client == NULL) {
+ return;
+ }
+
+ g_object_unref (G_OBJECT (global_gconf_client));
+ global_gconf_client = NULL;
+}
+
+/* Public */
+GConfClient *
+eel_gconf_client_get_global (void)
+{
+ /* Initialize gconf if needed */
+ if (!gconf_is_initialized ()) {
+ char *argv[] = { "eel-preferences", NULL };
+ GError *error = NULL;
+
+ if (!gconf_init (1, argv, &error)) {
+ if (eel_gconf_handle_error (&error)) {
+ return NULL;
+ }
+ }
+
+ }
+
+ if (global_gconf_client == NULL) {
+ global_gconf_client = gconf_client_get_default ();
+ g_atexit (global_client_free);
+ }
+
+ return global_gconf_client;
+}
+
+gboolean
+eel_gconf_handle_error (GError **error)
+{
+ g_return_val_if_fail (error != NULL, FALSE);
+
+ if (*error != NULL) {
+ g_warning (_("GConf error:\n %s"), (*error)->message);
+ g_error_free (*error);
+ *error = NULL;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+eel_gconf_set_boolean (const char *key,
+ gboolean boolean_value)
+{
+ GConfClient *client;
+ GError *error = NULL;
+
+ g_return_if_fail (key != NULL);
+
+ client = eel_gconf_client_get_global ();
+ g_return_if_fail (client != NULL);
+
+ gconf_client_set_bool (client, key, boolean_value, &error);
+ eel_gconf_handle_error (&error);
+}
+
+gboolean
+eel_gconf_get_boolean (const char *key)
+{
+ gboolean result;
+ GConfClient *client;
+ GError *error = NULL;
+
+ g_return_val_if_fail (key != NULL, FALSE);
+
+ client = eel_gconf_client_get_global ();
+ g_return_val_if_fail (client != NULL, FALSE);
+
+ result = gconf_client_get_bool (client, key, &error);
+
+ if (eel_gconf_handle_error (&error)) {
+ result = FALSE;
+ }
+
+ return result;
+}
+
+void
+eel_gconf_set_integer (const char *key,
+ int int_value)
+{
+ GConfClient *client;
+ GError *error = NULL;
+
+ g_return_if_fail (key != NULL);
+
+ client = eel_gconf_client_get_global ();
+ g_return_if_fail (client != NULL);
+
+ gconf_client_set_int (client, key, int_value, &error);
+ eel_gconf_handle_error (&error);
+}
+
+int
+eel_gconf_get_integer (const char *key)
+{
+ int result;
+ GConfClient *client;
+ GError *error = NULL;
+
+ g_return_val_if_fail (key != NULL, 0);
+
+ client = eel_gconf_client_get_global ();
+ g_return_val_if_fail (client != NULL, 0);
+
+ result = gconf_client_get_int (client, key, &error);
+
+ if (eel_gconf_handle_error (&error)) {
+ result = 0;
+ }
+
+ return result;
+}
+
+void
+eel_gconf_set_float (const char *key,
+ gfloat float_value)
+{
+ GConfClient *client;
+ GError *error = NULL;
+
+ g_return_if_fail (key != NULL);
+
+ client = eel_gconf_client_get_global ();
+ g_return_if_fail (client != NULL);
+
+ gconf_client_set_float (client, key, float_value, &error);
+ eel_gconf_handle_error (&error);
+}
+
+gfloat
+eel_gconf_get_float (const char *key)
+{
+ gfloat result;
+ GConfClient *client;
+ GError *error = NULL;
+
+ g_return_val_if_fail (key != NULL, 0);
+
+ client = eel_gconf_client_get_global ();
+ g_return_val_if_fail (client != NULL, 0);
+
+ result = gconf_client_get_float (client, key, &error);
+
+ if (eel_gconf_handle_error (&error)) {
+ result = 0;
+ }
+
+ return result;
+}
+
+void
+eel_gconf_set_string (const char *key,
+ const char *string_value)
+{
+ GConfClient *client;
+ GError *error = NULL;
+
+ g_return_if_fail (key != NULL);
+ g_return_if_fail (string_value != NULL);
+
+ client = eel_gconf_client_get_global ();
+ g_return_if_fail (client != NULL);
+
+ gconf_client_set_string (client, key, string_value, &error);
+ eel_gconf_handle_error (&error);
+}
+
+void
+eel_gconf_unset (const char *key)
+{
+ GConfClient *client;
+ GError *error = NULL;
+
+ g_return_if_fail (key != NULL);
+
+ client = eel_gconf_client_get_global ();
+ g_return_if_fail (client != NULL);
+
+ gconf_client_unset (client, key, &error);
+ eel_gconf_handle_error (&error);
+}
+
+char *
+eel_gconf_get_string (const char *key)
+{
+ char *result;
+ GConfClient *client;
+ GError *error = NULL;
+
+ g_return_val_if_fail (key != NULL, NULL);
+
+ client = eel_gconf_client_get_global ();
+ g_return_val_if_fail (client != NULL, NULL);
+
+ result = gconf_client_get_string (client, key, &error);
+
+ if (eel_gconf_handle_error (&error)) {
+ result = g_strdup ("");
+ }
+
+ return result;
+}
+
+void
+eel_gconf_set_string_list (const char *key,
+ const GSList *slist)
+{
+ GConfClient *client;
+ GError *error;
+
+ g_return_if_fail (key != NULL);
+
+ client = eel_gconf_client_get_global ();
+ g_return_if_fail (client != NULL);
+
+ error = NULL;
+ gconf_client_set_list (client, key, GCONF_VALUE_STRING,
+ /* Need cast cause of GConf api bug */
+ (GSList *) slist,
+ &error);
+ eel_gconf_handle_error (&error);
+}
+
+GSList *
+eel_gconf_get_string_list (const char *key)
+{
+ GSList *slist;
+ GConfClient *client;
+ GError *error;
+
+ g_return_val_if_fail (key != NULL, NULL);
+
+ client = eel_gconf_client_get_global ();
+ g_return_val_if_fail (client != NULL, NULL);
+
+ error = NULL;
+ slist = gconf_client_get_list (client, key, GCONF_VALUE_STRING, &error);
+ if (eel_gconf_handle_error (&error)) {
+ slist = NULL;
+ }
+
+ return slist;
+}
+
+/* This code wasn't part of the original eel-gconf-extensions.c */
+void
+eel_gconf_set_integer_list (const char *key,
+ const GSList *slist)
+{
+ GConfClient *client;
+ GError *error;
+
+ g_return_if_fail (key != NULL);
+
+ client = eel_gconf_client_get_global ();
+ g_return_if_fail (client != NULL);
+
+ error = NULL;
+ gconf_client_set_list (client, key, GCONF_VALUE_INT,
+ /* Need cast cause of GConf api bug */
+ (GSList *) slist,
+ &error);
+ eel_gconf_handle_error (&error);
+}
+
+GSList *
+eel_gconf_get_integer_list (const char *key)
+{
+ GSList *slist;
+ GConfClient *client;
+ GError *error;
+
+ g_return_val_if_fail (key != NULL, NULL);
+
+ client = eel_gconf_client_get_global ();
+ g_return_val_if_fail (client != NULL, NULL);
+
+ error = NULL;
+ slist = gconf_client_get_list (client, key, GCONF_VALUE_INT, &error);
+ if (eel_gconf_handle_error (&error)) {
+ slist = NULL;
+ }
+
+ return slist;
+}
+/* End of added code */
+
+gboolean
+eel_gconf_is_default (const char *key)
+{
+ gboolean result;
+ GConfValue *value;
+ GError *error = NULL;
+
+ g_return_val_if_fail (key != NULL, FALSE);
+
+ value = gconf_client_get_without_default (eel_gconf_client_get_global (), key, &error);
+
+ if (eel_gconf_handle_error (&error)) {
+ if (value != NULL) {
+ gconf_value_free (value);
+ }
+ return FALSE;
+ }
+
+ result = (value == NULL);
+
+ if (value != NULL) {
+ gconf_value_free (value);
+ }
+
+
+ return result;
+}
+
+gboolean
+eel_gconf_monitor_add (const char *directory)
+{
+ GError *error = NULL;
+ GConfClient *client;
+
+ g_return_val_if_fail (directory != NULL, FALSE);
+
+ client = eel_gconf_client_get_global ();
+ g_return_val_if_fail (client != NULL, FALSE);
+
+ gconf_client_add_dir (client,
+ directory,
+ GCONF_CLIENT_PRELOAD_NONE,
+ &error);
+
+ if (eel_gconf_handle_error (&error)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+eel_gconf_monitor_remove (const char *directory)
+{
+ GError *error = NULL;
+ GConfClient *client;
+
+ if (directory == NULL) {
+ return FALSE;
+ }
+
+ client = eel_gconf_client_get_global ();
+ g_return_val_if_fail (client != NULL, FALSE);
+
+ gconf_client_remove_dir (client,
+ directory,
+ &error);
+
+ if (eel_gconf_handle_error (&error)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+eel_gconf_suggest_sync (void)
+{
+ GConfClient *client;
+ GError *error = NULL;
+
+ client = eel_gconf_client_get_global ();
+ g_return_if_fail (client != NULL);
+
+ gconf_client_suggest_sync (client, &error);
+ eel_gconf_handle_error (&error);
+}
+
+GConfValue*
+eel_gconf_get_value (const char *key)
+{
+ GConfValue *value = NULL;
+ GConfClient *client;
+ GError *error = NULL;
+
+ g_return_val_if_fail (key != NULL, NULL);
+
+ client = eel_gconf_client_get_global ();
+ g_return_val_if_fail (client != NULL, NULL);
+
+ value = gconf_client_get (client, key, &error);
+
+ if (eel_gconf_handle_error (&error)) {
+ if (value != NULL) {
+ gconf_value_free (value);
+ value = NULL;
+ }
+ }
+
+ return value;
+}
+
+void
+eel_gconf_set_value (const char *key, GConfValue *value)
+{
+ GConfClient *client;
+ GError *error = NULL;
+
+ g_return_if_fail (key != NULL);
+
+ client = eel_gconf_client_get_global ();
+ g_return_if_fail (client != NULL);
+
+ gconf_client_set (client, key, value, &error);
+
+ if (eel_gconf_handle_error (&error)) {
+ return;
+ }
+}
+
+void
+eel_gconf_value_free (GConfValue *value)
+{
+ if (value == NULL) {
+ return;
+ }
+
+ gconf_value_free (value);
+}
+
+guint
+eel_gconf_notification_add (const char *key,
+ GConfClientNotifyFunc notification_callback,
+ gpointer callback_data)
+{
+ guint notification_id;
+ GConfClient *client;
+ GError *error = NULL;
+
+ g_return_val_if_fail (key != NULL, EEL_GCONF_UNDEFINED_CONNECTION);
+ g_return_val_if_fail (notification_callback != NULL, EEL_GCONF_UNDEFINED_CONNECTION);
+
+ client = eel_gconf_client_get_global ();
+ g_return_val_if_fail (client != NULL, EEL_GCONF_UNDEFINED_CONNECTION);
+
+ notification_id = gconf_client_notify_add (client,
+ key,
+ notification_callback,
+ callback_data,
+ NULL,
+ &error);
+
+ if (eel_gconf_handle_error (&error)) {
+ if (notification_id != EEL_GCONF_UNDEFINED_CONNECTION) {
+ gconf_client_notify_remove (client, notification_id);
+ notification_id = EEL_GCONF_UNDEFINED_CONNECTION;
+ }
+ }
+
+ return notification_id;
+}
+
+void
+eel_gconf_notification_remove (guint notification_id)
+{
+ GConfClient *client;
+
+ if (notification_id == EEL_GCONF_UNDEFINED_CONNECTION) {
+ return;
+ }
+
+ client = eel_gconf_client_get_global ();
+ g_return_if_fail (client != NULL);
+
+ gconf_client_notify_remove (client, notification_id);
+}
+
+/* Simple wrapper for eel_gconf_notifier_add which
+ * adds the notifier id to the GList given as argument
+ * so that a call to ephy_notification_free can remove the notifiers
+ */
+void
+ephy_notification_add (const char *key,
+ GConfClientNotifyFunc notification_callback,
+ gpointer callback_data,
+ GList **notifiers)
+{
+ guint id = 0;
+
+ id = eel_gconf_notification_add(key,
+ notification_callback,
+ callback_data);
+ if (notifiers != NULL) {
+ *notifiers = g_list_append(*notifiers,
+ (gpointer)id);
+ }
+}
+
+/* Removes all the notifiers listed in notifiers */
+/* Frees the notifiers list */
+void
+ephy_notification_remove (GList **notifiers)
+{
+ g_list_foreach(*notifiers,
+ (GFunc)eel_gconf_notification_remove,
+ NULL);
+ g_list_free(*notifiers);
+ *notifiers = NULL;
+}
+
diff --git a/lib/eel-gconf-extensions.h b/lib/eel-gconf-extensions.h
new file mode 100644
index 000000000..df51afaa1
--- /dev/null
+++ b/lib/eel-gconf-extensions.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* eel-gconf-extensions.h - Stuff to make GConf easier to use.
+
+ Copyright (C) 2000, 2001 Eazel, Inc.
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Ramiro Estrugo <ramiro@eazel.com>
+*/
+
+#ifndef EEL_GCONF_EXTENSIONS_H
+#define EEL_GCONF_EXTENSIONS_H
+
+#include <glib/gerror.h>
+#include <gconf/gconf.h>
+#include <gconf/gconf-client.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define EEL_GCONF_UNDEFINED_CONNECTION 0
+
+GConfClient *eel_gconf_client_get_global (void);
+gboolean eel_gconf_handle_error (GError **error);
+void eel_gconf_set_boolean (const char *key,
+ gboolean boolean_value);
+gboolean eel_gconf_get_boolean (const char *key);
+int eel_gconf_get_integer (const char *key);
+void eel_gconf_set_integer (const char *key,
+ int int_value);
+gfloat eel_gconf_get_float (const char *key);
+void eel_gconf_set_float (const char *key,
+ gfloat float_value);
+char * eel_gconf_get_string (const char *key);
+void eel_gconf_set_string (const char *key,
+ const char *string_value);
+GSList * eel_gconf_get_string_list (const char *key);
+void eel_gconf_set_string_list (const char *key,
+ const GSList *string_list_value);
+gboolean eel_gconf_is_default (const char *key);
+gboolean eel_gconf_monitor_add (const char *directory);
+gboolean eel_gconf_monitor_remove (const char *directory);
+void eel_gconf_suggest_sync (void);
+GConfValue* eel_gconf_get_value (const char *key);
+gboolean eel_gconf_value_is_equal (const GConfValue *a,
+ const GConfValue *b);
+void eel_gconf_set_value (const char *key, GConfValue *value);
+void eel_gconf_value_free (GConfValue *value);
+void eel_gconf_unset (const char *key);
+
+/* Functions which weren't part of the eel-gconf-extensions.h file from eel */
+GSList *eel_gconf_get_integer_list (const char *key);
+void eel_gconf_set_integer_list (const char *key,
+ const GSList *slist);
+guint eel_gconf_notification_add (const char *key,
+ GConfClientNotifyFunc notification_callback,
+ gpointer callback_data);
+void eel_gconf_notification_remove (guint notification_id);
+
+void ephy_notification_add (const char *key,
+ GConfClientNotifyFunc notification_callback,
+ gpointer callback_data,
+ GList **notifiers);
+
+void ephy_notification_remove (GList **notifiers);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* EEL_GCONF_EXTENSIONS_H */
diff --git a/lib/ephy-autocompletion-source.c b/lib/ephy-autocompletion-source.c
new file mode 100644
index 000000000..717b9924e
--- /dev/null
+++ b/lib/ephy-autocompletion-source.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernándezs Pascual <ric@users.sourceforge.net>
+ *
+ * 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.
+ */
+
+#include "ephy-autocompletion-source.h"
+#include "ephy-marshal.h"
+
+static void ephy_autocompletion_source_base_init (gpointer g_class);
+
+GType
+ephy_autocompletion_source_get_type (void)
+{
+ static GType autocompletion_source_type = 0;
+
+ if (! autocompletion_source_type)
+ {
+ static const GTypeInfo autocompletion_source_info =
+ {
+ sizeof (EphyAutocompletionSourceIface), /* class_size */
+ ephy_autocompletion_source_base_init, /* base_init */
+ NULL, /* base_finalize */
+ NULL,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ 0,
+ 0, /* n_preallocs */
+ NULL
+ };
+
+ autocompletion_source_type = g_type_register_static
+ (G_TYPE_INTERFACE, "EphyAutocompletionSource", &autocompletion_source_info, 0);
+ g_type_interface_add_prerequisite (autocompletion_source_type, G_TYPE_OBJECT);
+ }
+
+ return autocompletion_source_type;
+}
+
+static void
+ephy_autocompletion_source_base_init (gpointer g_class)
+{
+ static gboolean initialized = FALSE;
+
+ if (!initialized)
+ {
+ g_signal_new ("data-changed",
+ EPHY_TYPE_AUTOCOMPLETION_SOURCE,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EphyAutocompletionSourceIface, data_changed),
+ NULL, NULL,
+ ephy_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ initialized = TRUE;
+ }
+}
+
+
+void
+ephy_autocompletion_source_foreach (EphyAutocompletionSource *source,
+ const gchar *basic_key,
+ EphyAutocompletionSourceForeachFunc func,
+ gpointer data)
+{
+ (* EPHY_AUTOCOMPLETION_SOURCE_GET_IFACE (source)->foreach) (source, basic_key, func, data);
+}
+
+void
+ephy_autocompletion_source_set_basic_key (EphyAutocompletionSource *source,
+ const gchar *basic_key)
+{
+ (* EPHY_AUTOCOMPLETION_SOURCE_GET_IFACE (source)->set_basic_key) (source, basic_key);
+}
diff --git a/lib/ephy-autocompletion-source.h b/lib/ephy-autocompletion-source.h
new file mode 100644
index 000000000..595708b3a
--- /dev/null
+++ b/lib/ephy-autocompletion-source.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernándezs Pascual <ric@users.sourceforge.net>
+ *
+ * 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.
+ */
+
+#ifndef EPHY_AUTOCOMPLETION_SOUCE_H
+#define EPHY_AUTOCOMPLETION_SOUCE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_AUTOCOMPLETION_SOURCE (ephy_autocompletion_source_get_type ())
+#define EPHY_AUTOCOMPLETION_SOURCE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ EPHY_TYPE_AUTOCOMPLETION_SOURCE, \
+ EphyAutocompletionSource))
+#define EPHY_IS_AUTOCOMPLETION_SOURCE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ EPHY_TYPE_AUTOCOMPLETION_SOURCE))
+#define EPHY_AUTOCOMPLETION_SOURCE_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), \
+ EPHY_TYPE_AUTOCOMPLETION_SOURCE, \
+ EphyAutocompletionSourceIface))
+
+
+typedef struct _EphyAutocompletionSource EphyAutocompletionSource;
+typedef struct _EphyAutocompletionSourceIface EphyAutocompletionSourceIface;
+typedef void (* EphyAutocompletionSourceForeachFunc) (EphyAutocompletionSource *source,
+ const char *item,
+ const char *title,
+ const char *target,
+ gboolean is_action,
+ gboolean substring,
+ guint32 score,
+ gpointer data);
+
+struct _EphyAutocompletionSourceIface
+{
+ GTypeInterface g_iface;
+
+ /* Signals */
+
+ /**
+ * Sources MUST emit this signal when theirs data changes, expecially if the
+ * strings are freed / modified. Otherwise, things will crash.
+ */
+ void (* data_changed) (EphyAutocompletionSource *source);
+
+ /* Virtual Table */
+ void (* foreach) (EphyAutocompletionSource *source,
+ const gchar *basic_key,
+ EphyAutocompletionSourceForeachFunc func,
+ gpointer data);
+ void (* set_basic_key) (EphyAutocompletionSource *source,
+ const gchar *basic_key);
+};
+
+GType ephy_autocompletion_source_get_type (void);
+void ephy_autocompletion_source_foreach (EphyAutocompletionSource *source,
+ const gchar *basic_key,
+ EphyAutocompletionSourceForeachFunc func,
+ gpointer data);
+void ephy_autocompletion_source_set_basic_key (EphyAutocompletionSource *source,
+ const gchar *basic_key);
+
+G_END_DECLS
+
+#endif
+
diff --git a/lib/ephy-autocompletion.c b/lib/ephy-autocompletion.c
new file mode 100644
index 000000000..a3f3348c1
--- /dev/null
+++ b/lib/ephy-autocompletion.c
@@ -0,0 +1,664 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "ephy-autocompletion.h"
+#include "ephy-gobject-misc.h"
+#include "ephy-marshal.h"
+
+#define NOT_IMPLEMENTED g_warning ("not implemented: " G_STRLOC);
+
+//#define DEBUG_MSG(x) g_print x
+#define DEBUG_MSG(x)
+
+//#define DEBUG_TIME
+
+#ifdef DEBUG_TIME
+#include <glib/gtimer.h>
+#endif
+
+/**
+ * Private data
+ */
+
+typedef enum {
+ GAS_NEEDS_REFINE,
+ GAS_NEEDS_FULL_UPDATE,
+ GAS_UPDATED
+} EphyAutocompletionStatus;
+
+typedef struct {
+ EphyAutocompletionMatch *array;
+ guint num_matches;
+ guint num_action_matches;
+ guint array_size;
+} ACMatchArray;
+
+#define ACMA_BASE_SIZE 10240
+
+struct _EphyAutocompletionPrivate {
+ GSList *sources;
+
+ guint nkeys;
+ gchar **keys;
+ guint *key_lengths;
+ gchar **prefixes;
+ guint *prefix_lengths;
+
+ gchar *common_prefix;
+ ACMatchArray matches;
+ EphyAutocompletionStatus status;
+ gboolean sorted;
+ gboolean changed;
+
+ gboolean sort_alpha;
+};
+
+/**
+ * Private functions, only availble from this file
+ */
+static void ephy_autocompletion_class_init (EphyAutocompletionClass *klass);
+static void ephy_autocompletion_init (EphyAutocompletion *e);
+static void ephy_autocompletion_finalize_impl (GObject *o);
+static void ephy_autocompletion_reset (EphyAutocompletion *ac);
+static void ephy_autocompletion_update_matches (EphyAutocompletion *ac);
+static void ephy_autocompletion_update_matches_full (EphyAutocompletion *ac);
+static gboolean ephy_autocompletion_sort_by_score (EphyAutocompletion *ac);
+static void ephy_autocompletion_data_changed_cb (EphyAutocompletionSource *s,
+ EphyAutocompletion *ac);
+
+static void acma_init (ACMatchArray *a);
+static void acma_destroy (ACMatchArray *a);
+static inline void acma_append (ACMatchArray *a,
+ EphyAutocompletionMatch *m,
+ gboolean action);
+static void acma_grow (ACMatchArray *a);
+
+
+static gpointer g_object_class;
+
+/**
+ * Signals enums and ids
+ */
+enum EphyAutocompletionSignalsEnum {
+ EPHY_AUTOCOMPLETION_SOURCES_CHANGED,
+ EPHY_AUTOCOMPLETION_LAST_SIGNAL
+};
+static gint EphyAutocompletionSignals[EPHY_AUTOCOMPLETION_LAST_SIGNAL];
+
+/**
+ * Autocompletion object
+ */
+
+MAKE_GET_TYPE (ephy_autocompletion, "EphyAutocompletion", EphyAutocompletion,
+ ephy_autocompletion_class_init, ephy_autocompletion_init, G_TYPE_OBJECT);
+
+static void
+ephy_autocompletion_class_init (EphyAutocompletionClass *klass)
+{
+ G_OBJECT_CLASS (klass)->finalize = ephy_autocompletion_finalize_impl;
+ g_object_class = g_type_class_peek_parent (klass);
+
+ EphyAutocompletionSignals[EPHY_AUTOCOMPLETION_SOURCES_CHANGED] = g_signal_new (
+ "sources-changed", G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
+ G_STRUCT_OFFSET (EphyAutocompletionClass, sources_changed),
+ NULL, NULL,
+ ephy_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+ephy_autocompletion_init (EphyAutocompletion *ac)
+{
+ EphyAutocompletionPrivate *p = g_new0 (EphyAutocompletionPrivate, 1);
+ ac->priv = p;
+ p->sources = NULL;
+ p->common_prefix = NULL;
+ acma_init (&p->matches);
+ p->status = GAS_NEEDS_FULL_UPDATE;
+
+ p->nkeys = 1;
+
+ p->keys = g_new0 (gchar *, 2);
+ p->keys[0] = g_strdup ("");
+ p->key_lengths = g_new0 (guint, 2);
+ p->key_lengths[0] = 0;
+
+ p->prefixes = g_new0 (gchar *, 2);
+ p->prefixes[0] = g_strdup ("");
+ p->prefix_lengths = g_new0 (guint, 2);
+ p->prefix_lengths[0] = 0;
+
+ p->sort_alpha = TRUE;
+}
+
+static void
+ephy_autocompletion_finalize_impl (GObject *o)
+{
+ EphyAutocompletion *ac = EPHY_AUTOCOMPLETION (o);
+ EphyAutocompletionPrivate *p = ac->priv;
+ GSList *li;
+
+ ephy_autocompletion_reset (ac);
+
+ for (li = p->sources; li; li = li->next)
+ {
+ g_signal_handlers_disconnect_by_func (li->data,
+ ephy_autocompletion_data_changed_cb, ac);
+ g_object_unref (li->data);
+ }
+
+ g_slist_free (p->sources);
+
+ g_strfreev (p->keys);
+ g_free (p->key_lengths);
+ g_strfreev (p->prefixes);
+ g_free (p->prefix_lengths);
+
+ G_OBJECT_CLASS (g_object_class)->finalize (o);
+
+}
+
+static void
+ephy_autocompletion_reset (EphyAutocompletion *ac)
+{
+ EphyAutocompletionPrivate *p = ac->priv;
+#ifdef DEBUG_TIME
+ GTimer *timer = g_timer_new ();
+ g_timer_start (timer);
+#endif
+ acma_destroy (&p->matches);
+ g_free (p->common_prefix);
+ p->common_prefix = NULL;
+ p->status = GAS_NEEDS_FULL_UPDATE;
+#ifdef DEBUG_TIME
+ DEBUG_MSG (("AC: %f elapsed resetting\n", g_timer_elapsed (timer, NULL)));
+ g_timer_destroy (timer);
+#endif
+}
+
+EphyAutocompletion *
+ephy_autocompletion_new (void)
+{
+ EphyAutocompletion *ret = g_object_new (EPHY_TYPE_AUTOCOMPLETION, NULL);
+ return ret;
+}
+void
+ephy_autocompletion_add_source (EphyAutocompletion *ac,
+ EphyAutocompletionSource *s)
+{
+ EphyAutocompletionPrivate *p = ac->priv;
+ g_object_ref (G_OBJECT (s));
+ p->sources = g_slist_prepend (p->sources, s);
+ ephy_autocompletion_reset (ac);
+ g_signal_connect (s, "data-changed", G_CALLBACK (ephy_autocompletion_data_changed_cb), ac);
+
+ g_signal_emit (ac, EphyAutocompletionSignals[EPHY_AUTOCOMPLETION_SOURCES_CHANGED], 0);
+}
+
+void
+ephy_autocompletion_set_key (EphyAutocompletion *ac,
+ const gchar *key)
+{
+ EphyAutocompletionPrivate *p = ac->priv;
+ guint i;
+ guint keylen = strlen (key);
+
+ if (strcmp (key, p->keys[0]))
+ {
+ GSList *li;
+ for (li = p->sources; li; li = li->next)
+ {
+ ephy_autocompletion_source_set_basic_key
+ (EPHY_AUTOCOMPLETION_SOURCE (li->data), key);
+ }
+ }
+
+ if (keylen >= p->key_lengths[0]
+ && !strncmp (p->keys[0], key, p->key_lengths[0]))
+ {
+ if (!strcmp (key, p->keys[0]))
+ {
+ return;
+ }
+ if (p->status != GAS_NEEDS_FULL_UPDATE)
+ {
+ p->status = GAS_NEEDS_REFINE;
+ }
+ if (p->common_prefix)
+ {
+ if (strncmp (p->common_prefix, key, keylen))
+ {
+ g_free (p->common_prefix);
+ p->common_prefix = NULL;
+ }
+ }
+ }
+ else
+ {
+ p->status = GAS_NEEDS_FULL_UPDATE;
+ g_free (p->common_prefix);
+ p->common_prefix = NULL;
+ }
+
+ for (i = 0; p->prefixes[i]; ++i)
+ {
+ g_free (p->keys[i]);
+ p->keys[i] = g_strconcat (p->prefixes[i], key, NULL);
+ p->key_lengths[i] = keylen + p->prefix_lengths[i];
+ }
+
+}
+
+gchar *
+ephy_autocompletion_get_common_prefix (EphyAutocompletion *ac)
+{
+ EphyAutocompletionPrivate *p = ac->priv;
+ ephy_autocompletion_update_matches (ac);
+ if (!p->common_prefix)
+ {
+ guint common_length = 0;
+ guint i;
+#ifdef DEBUG_TIME
+ GTimer *timer = g_timer_new ();
+ g_timer_start (timer);
+#endif
+ for (i = 0; i < p->matches.num_matches; i++)
+ {
+ EphyAutocompletionMatch *mi = &p->matches.array[i];
+ const gchar *realmatch = mi->title + mi->offset;
+ if (!p->common_prefix)
+ {
+ p->common_prefix = g_strdup (realmatch);
+ common_length = strlen (p->common_prefix);
+ continue;
+ }
+ else if (!strncmp (realmatch, p->common_prefix, common_length))
+ {
+ continue;
+ }
+ else
+ {
+ common_length = 0;
+ while (realmatch[common_length]
+ && realmatch[common_length] == p->common_prefix[common_length])
+ {
+ ++common_length;
+ }
+ g_free (p->common_prefix);
+ p->common_prefix = g_strndup (realmatch, common_length);
+ }
+ }
+#ifdef DEBUG_TIME
+ DEBUG_MSG (("AC: %f elapsed calculating common prefix\n", g_timer_elapsed (timer, NULL)));
+ g_timer_destroy (timer);
+#endif
+ }
+ return g_strdup (p->common_prefix);
+}
+
+const EphyAutocompletionMatch *
+ephy_autocompletion_get_matches (EphyAutocompletion *ac)
+{
+ ephy_autocompletion_update_matches (ac);
+ return ac->priv->matches.array;
+}
+
+const EphyAutocompletionMatch *
+ephy_autocompletion_get_matches_sorted_by_score (EphyAutocompletion *ac,
+ gboolean *changed)
+{
+ *changed = ephy_autocompletion_sort_by_score (ac);
+ return ac->priv->matches.array;
+}
+
+guint
+ephy_autocompletion_get_num_matches (EphyAutocompletion *ac)
+{
+ ephy_autocompletion_update_matches (ac);
+
+ return ac->priv->matches.num_matches;
+}
+
+guint
+ephy_autocompletion_get_num_action_matches (EphyAutocompletion *ac)
+{
+ return ac->priv->matches.num_matches -
+ ac->priv->matches.num_action_matches;
+}
+
+static void
+ephy_autocompletion_refine_matches (EphyAutocompletion *ac)
+{
+ EphyAutocompletionPrivate *p = ac->priv;
+ ACMatchArray oldmatches = p->matches;
+ ACMatchArray newmatches;
+ guint i;
+ gchar *key0 = p->keys[0];
+ guint key0l = p->key_lengths[0];
+#ifdef DEBUG_TIME
+ GTimer *timer = g_timer_new ();
+#endif
+ DEBUG_MSG (("AC: refining\n"));
+
+#ifdef DEBUG_TIME
+ g_timer_start (timer);
+#endif
+ acma_init (&newmatches);
+
+ p->changed = FALSE;
+
+ for (i = 0; i < oldmatches.num_matches; i++)
+ {
+ EphyAutocompletionMatch *mi = &oldmatches.array[i];
+ if (mi->is_action ||
+ (mi->substring && g_strrstr (mi->match, p->keys[0])) ||
+ !strncmp (key0, mi->title + mi->offset, key0l))
+ {
+ acma_append (&newmatches, mi, mi->is_action);
+ }
+ else
+ {
+ p->changed = TRUE;
+ }
+ }
+
+ acma_destroy (&oldmatches);
+ p->matches = newmatches;
+
+#ifdef DEBUG_TIME
+ DEBUG_MSG (("AC: %f elapsed refining\n", g_timer_elapsed (timer, NULL)));
+ g_timer_destroy (timer);
+#endif
+ DEBUG_MSG (("AC: %d matches\n", p->matches.num_matches));
+}
+
+static void
+ephy_autocompletion_update_matches (EphyAutocompletion *ac)
+{
+ EphyAutocompletionPrivate *p = ac->priv;
+ if (p->status == GAS_UPDATED)
+ {
+ return;
+ }
+ if (p->status == GAS_NEEDS_FULL_UPDATE)
+ {
+ ephy_autocompletion_update_matches_full (ac);
+ }
+ if (p->status == GAS_NEEDS_REFINE)
+ {
+ /* FIXME we do full update for now */
+ ephy_autocompletion_refine_matches (ac);
+ }
+
+ g_free (p->common_prefix);
+ p->common_prefix = NULL;
+ p->status = GAS_UPDATED;
+}
+
+static void
+ephy_autocompletion_update_matches_full_item (EphyAutocompletionSource *source,
+ const char *item,
+ const char *title,
+ const char *target,
+ gboolean is_action,
+ gboolean substring,
+ guint32 score,
+ EphyAutocompletionPrivate *p)
+{
+ if (is_action ||
+ (substring && g_strrstr (item, p->keys[0])))
+ {
+ EphyAutocompletionMatch m;
+ m.match = item;
+ m.title = title;
+ m.target = target;
+ m.is_action = is_action;
+ m.substring = substring;
+ m.offset = 0;
+ m.score = score;
+ acma_append (&p->matches, &m, is_action);
+ }
+ else
+ {
+ guint i;
+ for (i = 0; p->keys[i]; ++i)
+ {
+ if (!strncmp (item, p->keys[i], p->key_lengths[i]))
+ {
+ EphyAutocompletionMatch m;
+ m.match = item;
+ m.title = title;
+ m.target = target;
+ m.is_action = is_action;
+ m.substring = substring;
+ m.offset = p->prefix_lengths[i];
+ m.score = score;
+ acma_append (&p->matches, &m, is_action);
+ }
+ }
+ }
+}
+
+static void
+ephy_autocompletion_update_matches_full (EphyAutocompletion *ac)
+{
+ EphyAutocompletionPrivate *p = ac->priv;
+ GSList *li;
+#ifdef DEBUG_TIME
+ GTimer *timer = g_timer_new ();
+#endif
+
+ DEBUG_MSG (("AC: fully updating\n"));
+ ephy_autocompletion_reset (ac);
+
+#ifdef DEBUG_TIME
+ g_timer_start (timer);
+#endif
+ for (li = p->sources; li; li = li->next)
+ {
+ EphyAutocompletionSource *s = EPHY_AUTOCOMPLETION_SOURCE (li->data);
+ g_assert (s);
+ ephy_autocompletion_source_foreach (s, p->keys[0],
+ (EphyAutocompletionSourceForeachFunc)
+ ephy_autocompletion_update_matches_full_item,
+ p);
+ }
+#ifdef DEBUG_TIME
+ DEBUG_MSG (("AC: %f elapsed fully updating\n", g_timer_elapsed (timer, NULL)));
+ g_timer_destroy (timer);
+#endif
+
+ p->sorted = FALSE;
+ p->changed = TRUE;
+
+ DEBUG_MSG (("AC: %d matches, fully updated\n", p->matches.num_matches));
+}
+
+static gint
+ephy_autocompletion_compare_scores (EphyAutocompletionMatch *a, EphyAutocompletionMatch *b)
+{
+ /* higher scores first */
+ return b->score - a->score;
+}
+
+static gint
+ephy_autocompletion_compare_scores_and_alpha (EphyAutocompletionMatch *a, EphyAutocompletionMatch *b)
+{
+ if (a->score == b->score)
+ {
+ return strcmp (a->title, b->title);
+ }
+ else
+ {
+ /* higher scores first */
+ return b->score - a->score;
+ }
+}
+
+static gboolean
+ephy_autocompletion_sort_by_score (EphyAutocompletion *ac)
+{
+ EphyAutocompletionPrivate *p = ac->priv;
+#ifdef DEBUG_TIME
+ GTimer *timer;
+#endif
+ gint (*comparer) (EphyAutocompletionMatch *m1, EphyAutocompletionMatch *m2);
+
+ if (p->sort_alpha)
+ {
+ comparer = ephy_autocompletion_compare_scores_and_alpha;
+ }
+ else
+ {
+ comparer = ephy_autocompletion_compare_scores;
+ }
+
+ ephy_autocompletion_update_matches (ac);
+ if (p->changed == FALSE) return FALSE;
+
+ DEBUG_MSG (("AC: sorting\n"));
+#ifdef DEBUG_TIME
+ timer = g_timer_new ();
+ g_timer_start (timer);
+#endif
+ if (p->matches.num_matches > 0)
+ {
+ qsort (p->matches.array, p->matches.num_matches,
+ sizeof (EphyAutocompletionMatch),
+ (void *) comparer);
+ }
+
+ p->sorted = TRUE;
+
+#ifdef DEBUG_TIME
+ DEBUG_MSG (("AC: %f elapsed sorting by score\n", g_timer_elapsed (timer, NULL)));
+ g_timer_destroy (timer);
+#endif
+ return TRUE;
+}
+
+static void
+ephy_autocompletion_data_changed_cb (EphyAutocompletionSource *s,
+ EphyAutocompletion *ac)
+{
+ DEBUG_MSG (("AC: data changed, reseting\n"));
+ ephy_autocompletion_reset (ac);
+
+ g_signal_emit (ac, EphyAutocompletionSignals[EPHY_AUTOCOMPLETION_SOURCES_CHANGED], 0);
+}
+
+void
+ephy_autocompletion_set_prefixes (EphyAutocompletion *ac,
+ const gchar **prefixes)
+{
+ EphyAutocompletionPrivate *p = ac->priv;
+ guint nprefixes = 0;
+ gchar *oldkey = g_strdup (p->keys[0]);
+ guint i;
+
+ /* count prefixes */
+ while (prefixes[nprefixes])
+ {
+ ++nprefixes;
+ }
+
+ nprefixes--;
+
+ g_strfreev (p->keys);
+ g_free (p->key_lengths);
+ g_strfreev (p->prefixes);
+ g_free (p->prefix_lengths);
+
+ p->prefixes = g_new0 (gchar *, nprefixes + 2);
+ p->prefix_lengths = g_new0 (guint, nprefixes + 2);
+ p->keys = g_new0 (gchar *, nprefixes + 2);
+ p->key_lengths = g_new0 (guint, nprefixes + 2);
+
+ p->prefixes[0] = g_strdup ("");
+ p->prefix_lengths[0] = 0;
+ p->keys[0] = oldkey;
+ p->key_lengths[0] = strlen (p->keys[0]);
+
+ for (i = 0; i < nprefixes; ++i)
+ {
+ p->prefixes[i + 1] = g_strdup (prefixes[i]);
+ p->prefix_lengths[i + 1] = strlen (prefixes[i]);
+ p->keys[i + 1] = g_strconcat (p->prefixes[i + 1], p->keys[0], NULL);
+ p->key_lengths[i + 1] = p->prefix_lengths[i + 1] + p->key_lengths[0];
+ }
+
+ p->nkeys = nprefixes;
+}
+
+
+/* ACMatchArray */
+
+static void
+acma_init (ACMatchArray *a)
+{
+ a->array = NULL;
+ a->array_size = 0;
+ a->num_matches = 0;
+}
+
+/**
+ * Does not free the struct itself, only its contents
+ */
+static void
+acma_destroy (ACMatchArray *a)
+{
+ g_free (a->array);
+ a->array = NULL;
+ a->array_size = 0;
+ a->num_matches = 0;
+ a->num_action_matches = 0;
+}
+
+static inline void
+acma_append (ACMatchArray *a,
+ EphyAutocompletionMatch *m,
+ gboolean action)
+{
+ if (a->array_size == a->num_matches)
+ {
+ acma_grow (a);
+ }
+
+ a->array[a->num_matches] = *m;
+ a->num_matches++;
+ if (action) a->num_action_matches++;
+}
+
+static void
+acma_grow (ACMatchArray *a)
+{
+ gint new_size;
+ EphyAutocompletionMatch *new_array;
+
+ new_size = a->array_size + ACMA_BASE_SIZE;
+ new_array = g_renew (EphyAutocompletionMatch, a->array, new_size);
+
+ a->array_size = new_size;
+ a->array = new_array;
+}
+
+
diff --git a/lib/ephy-autocompletion.h b/lib/ephy-autocompletion.h
new file mode 100644
index 000000000..06dcc71dc
--- /dev/null
+++ b/lib/ephy-autocompletion.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifndef EPHY_AUTOCOMPLETION_H
+#define EPHY_AUTOCOMPLETION_H
+
+#include <glib-object.h>
+#include "ephy-autocompletion-source.h"
+
+G_BEGIN_DECLS
+
+/* object forward declarations */
+
+typedef struct _EphyAutocompletion EphyAutocompletion;
+typedef struct _EphyAutocompletionClass EphyAutocompletionClass;
+typedef struct _EphyAutocompletionPrivate EphyAutocompletionPrivate;
+typedef struct _EphyAutocompletionMatch EphyAutocompletionMatch;
+
+/**
+ * EphyAutocompletion object
+ */
+
+#define EPHY_TYPE_AUTOCOMPLETION (ephy_autocompletion_get_type())
+#define EPHY_AUTOCOMPLETION(object) (G_TYPE_CHECK_INSTANCE_CAST((object), \
+ EPHY_TYPE_AUTOCOMPLETION,\
+ EphyAutocompletion))
+#define EPHY_AUTOCOMPLETION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \
+ EPHY_TYPE_AUTOCOMPLETION,\
+ EphyAutocompletionClass))
+#define EPHY_IS_AUTOCOMPLETION(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), \
+ EPHY_TYPE_AUTOCOMPLETION))
+#define EPHY_IS_AUTOCOMPLETION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ EPHY_TYPE_AUTOCOMPLETION))
+#define EPHY_AUTOCOMPLETION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \
+ EPHY_TYPE_AUTOCOMPLETION,\
+ EphyAutocompletionClass))
+
+struct _EphyAutocompletionClass
+{
+ GObjectClass parent_class;
+
+ /* signals */
+ void (*sources_changed) (EphyAutocompletion *ac);
+};
+
+/* Remember: fields are public read-only */
+struct _EphyAutocompletion
+{
+ GObject parent_object;
+
+ EphyAutocompletionPrivate *priv;
+};
+
+struct _EphyAutocompletionMatch
+{
+ const char *match;
+ const char *title;
+ const char *target;
+ guint offset;
+ gint32 score;
+ gboolean is_action;
+ gboolean substring;
+};
+
+/* this is a set of usual prefixes for web browsing */
+#define EPHY_AUTOCOMPLETION_USUAL_WEB_PREFIXES \
+ "http://www.", \
+ "http://", \
+ "https://www.", \
+ "https://", \
+ "file://", \
+ "www."
+
+GType ephy_autocompletion_get_type (void);
+EphyAutocompletion * ephy_autocompletion_new (void);
+void ephy_autocompletion_add_source (EphyAutocompletion *ac,
+ EphyAutocompletionSource *s);
+void ephy_autocompletion_set_prefixes (EphyAutocompletion *ac,
+ const gchar **prefixes);
+void ephy_autocompletion_set_key (EphyAutocompletion *ac,
+ const gchar *key);
+gchar * ephy_autocompletion_get_common_prefix (EphyAutocompletion *ac);
+const EphyAutocompletionMatch *ephy_autocompletion_get_matches (EphyAutocompletion *ac);
+const EphyAutocompletionMatch *ephy_autocompletion_get_matches_sorted_by_score
+ (EphyAutocompletion *ac,
+ gboolean *changed);
+guint ephy_autocompletion_get_num_matches (EphyAutocompletion *ac);
+guint ephy_autocompletion_get_num_action_matches (EphyAutocompletion *ac);
+
+G_END_DECLS
+
+#endif
diff --git a/lib/ephy-bonobo-extensions.c b/lib/ephy-bonobo-extensions.c
new file mode 100644
index 000000000..e40b377cd
--- /dev/null
+++ b/lib/ephy-bonobo-extensions.c
@@ -0,0 +1,679 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* gul-bonobo-extensions.c - implementation of new functions that conceptually
+ belong in bonobo. Perhaps some of these will be
+ actually rolled into bonobo someday.
+
+ This file is based on nautilus-bonobo-extensions.c from
+ libnautilus-private.
+
+ Copyright (C) 2000, 2001 Eazel, Inc.
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: John Sullivan <sullivan@eazel.com>
+ Darin Adler <darin@bentspoon.com>
+*/
+
+#include <config.h>
+
+#include "ephy-bonobo-extensions.h"
+#include "ephy-string.h"
+#include <string.h>
+
+#include <bonobo/bonobo-ui-util.h>
+#include <gtk/gtkmain.h>
+#include <libgnomevfs/gnome-vfs-utils.h>
+#include <bonobo/bonobo-control.h>
+
+typedef enum {
+ NUMBERED_MENU_ITEM_PLAIN,
+ NUMBERED_MENU_ITEM_TOGGLE,
+ NUMBERED_MENU_ITEM_RADIO
+} NumberedMenuItemType;
+
+void
+ephy_bonobo_set_accelerator (BonoboUIComponent *ui,
+ const char *path,
+ const char *accelerator)
+{
+ if (bonobo_ui_component_get_container (ui)) /* should not do this here... */
+ {
+ bonobo_ui_component_set_prop (ui, path, "accel", accelerator, NULL);
+ }
+}
+
+void
+ephy_bonobo_set_label (BonoboUIComponent *ui,
+ const char *path,
+ const char *label)
+{
+ if (bonobo_ui_component_get_container (ui)) /* should not do this here... */
+ {
+ bonobo_ui_component_set_prop (ui, path, "label", label, NULL);
+ }
+}
+
+void
+ephy_bonobo_set_tip (BonoboUIComponent *ui,
+ const char *path,
+ const char *tip)
+{
+ if (bonobo_ui_component_get_container (ui)) /* should not do this here... */
+ {
+ bonobo_ui_component_set_prop (ui, path, "tip", tip, NULL);
+ }
+}
+
+void
+ephy_bonobo_set_sensitive (BonoboUIComponent *ui,
+ const char *path,
+ gboolean sensitive)
+{
+ if (bonobo_ui_component_get_container (ui)) /* should not do this here... */
+ {
+ bonobo_ui_component_set_prop (ui, path, "sensitive", sensitive ? "1" : "0", NULL);
+ }
+}
+
+void
+ephy_bonobo_set_toggle_state (BonoboUIComponent *ui,
+ const char *path,
+ gboolean state)
+{
+ if (bonobo_ui_component_get_container (ui)) /* should not do this here... */
+ {
+ bonobo_ui_component_set_prop (ui, path, "state", state ? "1" : "0", NULL);
+ }
+}
+
+void
+ephy_bonobo_set_hidden (BonoboUIComponent *ui,
+ const char *path,
+ gboolean hidden)
+{
+ if (bonobo_ui_component_get_container (ui)) /* should not do this here... */
+ {
+ bonobo_ui_component_set_prop (ui, path, "hidden", hidden ? "1" : "0", NULL);
+ }
+}
+
+char *
+ephy_bonobo_get_label (BonoboUIComponent *ui,
+ const char *path)
+{
+ if (bonobo_ui_component_get_container (ui)) /* should not do this here... */
+ {
+ return bonobo_ui_component_get_prop (ui, path, "label", NULL);
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+gboolean
+ephy_bonobo_get_hidden (BonoboUIComponent *ui,
+ const char *path)
+{
+ char *value;
+ gboolean hidden;
+ CORBA_Environment ev;
+
+ g_return_val_if_fail (BONOBO_IS_UI_COMPONENT (ui), FALSE);
+ g_return_val_if_fail (path != NULL, FALSE);
+
+ CORBA_exception_init (&ev);
+ value = bonobo_ui_component_get_prop (ui, path, "hidden", &ev);
+ CORBA_exception_free (&ev);
+
+ if (value == NULL) {
+ /* No hidden attribute means not hidden. */
+ hidden = FALSE;
+ } else {
+ /* Anything other than "0" counts as TRUE */
+ hidden = strcmp (value, "0") != 0;
+ g_free (value);
+ }
+
+ return hidden;
+}
+
+static char *
+get_numbered_menu_item_name (guint index)
+{
+ return g_strdup_printf ("%u", index);
+}
+
+char *
+ephy_bonobo_get_numbered_menu_item_path (BonoboUIComponent *ui,
+ const char *container_path,
+ guint index)
+{
+ char *item_name;
+ char *item_path;
+
+ g_return_val_if_fail (BONOBO_IS_UI_COMPONENT (ui), NULL);
+ g_return_val_if_fail (container_path != NULL, NULL);
+
+ item_name = get_numbered_menu_item_name (index);
+ item_path = g_strconcat (container_path, "/", item_name, NULL);
+ g_free (item_name);
+
+ return item_path;
+}
+
+char *
+ephy_bonobo_get_numbered_menu_item_command (BonoboUIComponent *ui,
+ const char *container_path,
+ guint index)
+{
+ char *command_name;
+ char *path;
+
+ g_return_val_if_fail (BONOBO_IS_UI_COMPONENT (ui), NULL);
+ g_return_val_if_fail (container_path != NULL, NULL);
+
+ path = ephy_bonobo_get_numbered_menu_item_path (ui, container_path, index);
+ command_name = gnome_vfs_escape_string (path);
+ g_free (path);
+
+ return command_name;
+}
+
+guint
+ephy_bonobo_get_numbered_menu_item_index_from_command (const char *command)
+{
+ char *path;
+ char *index_string;
+ int index;
+ gboolean got_index;
+
+ path = gnome_vfs_unescape_string (command, NULL);
+ index_string = strrchr (path, '/');
+
+ if (index_string == NULL) {
+ got_index = FALSE;
+ } else {
+ got_index = ephy_str_to_int (index_string + 1, &index);
+ }
+ g_free (path);
+
+ g_return_val_if_fail (got_index, 0);
+
+ return index;
+}
+
+char *
+ephy_bonobo_get_numbered_menu_item_container_path_from_command (const char *command)
+{
+ char *path;
+ char *index_string;
+ char *container_path;
+
+ path = gnome_vfs_unescape_string (command, NULL);
+ index_string = strrchr (path, '/');
+
+ container_path = index_string == NULL
+ ? NULL
+ : g_strndup (path, index_string - path);
+ g_free (path);
+
+ return container_path;
+}
+
+static char *
+ephy_bonobo_add_numbered_menu_item_internal (BonoboUIComponent *ui,
+ const char *container_path,
+ guint index,
+ const char *label,
+ NumberedMenuItemType type,
+ GdkPixbuf *pixbuf,
+ const char *radio_group_name)
+{
+ char *xml_item, *xml_command;
+ char *command_name;
+ char *item_name, *pixbuf_data;
+ char *path;
+
+ g_assert (BONOBO_IS_UI_COMPONENT (ui));
+ g_assert (container_path != NULL);
+ g_assert (label != NULL);
+ g_assert (type == NUMBERED_MENU_ITEM_PLAIN || pixbuf == NULL);
+ g_assert (type == NUMBERED_MENU_ITEM_RADIO || radio_group_name == NULL);
+ g_assert (type != NUMBERED_MENU_ITEM_RADIO || radio_group_name != NULL);
+
+ item_name = get_numbered_menu_item_name (index);
+ command_name = ephy_bonobo_get_numbered_menu_item_command
+ (ui, container_path, index);
+
+ switch (type) {
+ case NUMBERED_MENU_ITEM_TOGGLE:
+ xml_item = g_strdup_printf ("<menuitem name=\"%s\" id=\"%s\" type=\"toggle\"/>\n",
+ item_name, command_name);
+ break;
+ case NUMBERED_MENU_ITEM_RADIO:
+ xml_item = g_strdup_printf ("<menuitem name=\"%s\" id=\"%s\" "
+ "type=\"radio\" group=\"%s\"/>\n",
+ item_name, command_name, radio_group_name);
+ break;
+ case NUMBERED_MENU_ITEM_PLAIN:
+ if (pixbuf != NULL) {
+ pixbuf_data = bonobo_ui_util_pixbuf_to_xml (pixbuf);
+ xml_item = g_strdup_printf ("<menuitem name=\"%s\" verb=\"%s\" "
+ "pixtype=\"pixbuf\" pixname=\"%s\"/>\n",
+ item_name, command_name, pixbuf_data);
+ g_free (pixbuf_data);
+ } else {
+ xml_item = g_strdup_printf ("<menuitem name=\"%s\" verb=\"%s\"/>\n",
+ item_name, command_name);
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ xml_item = NULL; /* keep compiler happy */
+ }
+
+ g_free (item_name);
+
+ bonobo_ui_component_set (ui, container_path, xml_item, NULL);
+
+ g_free (xml_item);
+
+ path = ephy_bonobo_get_numbered_menu_item_path (ui, container_path, index);
+ ephy_bonobo_set_label (ui, path, label);
+ g_free (path);
+
+ /* Make the command node here too, so callers can immediately set
+ * properties on it (otherwise it doesn't get created until some
+ * time later).
+ */
+ xml_command = g_strdup_printf ("<cmd name=\"%s\"/>\n", command_name);
+ bonobo_ui_component_set (ui, "/commands", xml_command, NULL);
+ g_free (xml_command);
+
+ g_free (command_name);
+
+ return item_name;
+}
+
+/* Add a menu item specified by number into a given path. Used for
+ * dynamically creating a related series of menu items. Each index
+ * must be unique (normal use is to call this in a loop, and
+ * increment the index for each item).
+ */
+void
+ephy_bonobo_add_numbered_menu_item (BonoboUIComponent *ui,
+ const char *container_path,
+ guint index,
+ const char *label,
+ GdkPixbuf *pixbuf)
+{
+ g_return_if_fail (BONOBO_IS_UI_COMPONENT (ui));
+ g_return_if_fail (container_path != NULL);
+ g_return_if_fail (label != NULL);
+
+ ephy_bonobo_add_numbered_menu_item_internal (ui, container_path, index, label,
+ NUMBERED_MENU_ITEM_PLAIN, pixbuf, NULL);
+}
+
+/* Add a menu item specified by number into a given path. Used for
+ * dynamically creating a related series of toggle menu items. Each index
+ * must be unique (normal use is to call this in a loop, and
+ * increment the index for each item).
+ */
+void
+ephy_bonobo_add_numbered_toggle_menu_item (BonoboUIComponent *ui,
+ const char *container_path,
+ guint index,
+ const char *label)
+{
+ g_return_if_fail (BONOBO_IS_UI_COMPONENT (ui));
+ g_return_if_fail (container_path != NULL);
+ g_return_if_fail (label != NULL);
+
+ ephy_bonobo_add_numbered_menu_item_internal (ui, container_path, index, label,
+ NUMBERED_MENU_ITEM_TOGGLE, NULL, NULL);
+}
+
+/* Add a menu item specified by number into a given path. Used for
+ * dynamically creating a related series of radio menu items. Each index
+ * must be unique (normal use is to call this in a loop, and
+ * increment the index for each item).
+ */
+void
+ephy_bonobo_add_numbered_radio_menu_item (BonoboUIComponent *ui,
+ const char *container_path,
+ guint index,
+ const char *label,
+ const char *radio_group_name)
+{
+ g_return_if_fail (BONOBO_IS_UI_COMPONENT (ui));
+ g_return_if_fail (container_path != NULL);
+ g_return_if_fail (label != NULL);
+
+ ephy_bonobo_add_numbered_menu_item_internal (ui, container_path, index, label,
+ NUMBERED_MENU_ITEM_RADIO, NULL, radio_group_name);
+}
+
+void
+ephy_bonobo_add_numbered_submenu (BonoboUIComponent *ui,
+ const char *container_path,
+ guint index,
+ const char *label,
+ GdkPixbuf *pixbuf)
+{
+ char *xml_string, *item_name, *pixbuf_data, *submenu_path, *command_name;
+
+ g_return_if_fail (BONOBO_IS_UI_COMPONENT (ui));
+ g_return_if_fail (container_path != NULL);
+ g_return_if_fail (label != NULL);
+ g_return_if_fail (pixbuf == NULL || GDK_IS_PIXBUF (pixbuf));
+
+ item_name = get_numbered_menu_item_name (index);
+ command_name = ephy_bonobo_get_numbered_menu_item_command (ui, container_path, index);
+
+ if (pixbuf != NULL) {
+ pixbuf_data = bonobo_ui_util_pixbuf_to_xml (pixbuf);
+ xml_string = g_strdup_printf ("<submenu name=\"%s\" pixtype=\"pixbuf\" pixname=\"%s\" "
+ "verb=\"%s\"/>\n",
+ item_name, pixbuf_data, command_name);
+ g_free (pixbuf_data);
+ } else {
+ xml_string = g_strdup_printf ("<submenu name=\"%s\" verb=\"%s\"/>\n", item_name,
+ command_name);
+ }
+
+ bonobo_ui_component_set (ui, container_path, xml_string, NULL);
+
+ g_free (xml_string);
+
+ submenu_path = ephy_bonobo_get_numbered_menu_item_path (ui, container_path, index);
+ ephy_bonobo_set_label (ui, submenu_path, label);
+ g_free (submenu_path);
+
+ g_free (item_name);
+ g_free (command_name);
+}
+
+void
+ephy_bonobo_add_numbered_submenu_no_verb (BonoboUIComponent *ui,
+ const char *container_path,
+ guint index,
+ const char *label,
+ GdkPixbuf *pixbuf)
+{
+ char *xml_string, *item_name, *pixbuf_data, *submenu_path;
+
+ g_return_if_fail (BONOBO_IS_UI_COMPONENT (ui));
+ g_return_if_fail (container_path != NULL);
+ g_return_if_fail (label != NULL);
+ g_return_if_fail (pixbuf == NULL || GDK_IS_PIXBUF (pixbuf));
+
+ item_name = get_numbered_menu_item_name (index);
+
+ if (pixbuf != NULL) {
+ pixbuf_data = bonobo_ui_util_pixbuf_to_xml (pixbuf);
+ xml_string = g_strdup_printf ("<submenu name=\"%s\" pixtype=\"pixbuf\" pixname=\"%s\" "
+ "/>\n",
+ item_name, pixbuf_data);
+ g_free (pixbuf_data);
+ } else {
+ xml_string = g_strdup_printf ("<submenu name=\"%s\"/>\n", item_name);
+ }
+
+ bonobo_ui_component_set (ui, container_path, xml_string, NULL);
+
+ g_free (xml_string);
+
+ submenu_path = ephy_bonobo_get_numbered_menu_item_path (ui, container_path, index);
+ ephy_bonobo_set_label (ui, submenu_path, label);
+ g_free (submenu_path);
+
+ g_free (item_name);
+}
+
+void
+ephy_bonobo_add_submenu (BonoboUIComponent *ui,
+ const char *path,
+ const char *label,
+ GdkPixbuf *pixbuf)
+{
+ char *xml_string, *name, *pixbuf_data, *submenu_path;
+
+ g_return_if_fail (BONOBO_IS_UI_COMPONENT (ui));
+ g_return_if_fail (path != NULL);
+ g_return_if_fail (label != NULL);
+ g_return_if_fail (pixbuf == NULL || GDK_IS_PIXBUF (pixbuf));
+
+ /* Labels may contain characters that are illegal in names. So
+ * we create the name by URI-encoding the label.
+ */
+ name = gnome_vfs_escape_string (label);
+
+ if (pixbuf != NULL) {
+ pixbuf_data = bonobo_ui_util_pixbuf_to_xml (pixbuf);
+ xml_string = g_strdup_printf ("<submenu name=\"%s\" pixtype=\"pixbuf\" pixname=\"%s\"/>\n",
+ name, pixbuf_data);
+ g_free (pixbuf_data);
+ } else {
+ xml_string = g_strdup_printf ("<submenu name=\"%s\"/>\n", name);
+ }
+
+ bonobo_ui_component_set (ui, path, xml_string, NULL);
+
+ g_free (xml_string);
+
+ submenu_path = g_strconcat (path, "/", name, NULL);
+ ephy_bonobo_set_label (ui, submenu_path, label);
+ g_free (submenu_path);
+
+ g_free (name);
+}
+
+void
+ephy_bonobo_add_menu_separator (BonoboUIComponent *ui, const char *path)
+{
+ static gint hack = 0;
+ gchar *xml;
+
+ g_return_if_fail (BONOBO_IS_UI_COMPONENT (ui));
+ g_return_if_fail (path != NULL);
+
+ xml = g_strdup_printf ("<separator name=\"sep%d\"/>", ++hack);
+ bonobo_ui_component_set (ui, path, xml, NULL);
+ g_free (xml);
+}
+
+static void
+remove_commands (BonoboUIComponent *ui, const char *container_path)
+{
+ BonoboUINode *path_node;
+ BonoboUINode *child_node;
+ char *verb_name;
+ char *id_name;
+
+ path_node = bonobo_ui_component_get_tree (ui, container_path, TRUE, NULL);
+ if (path_node == NULL) {
+ return;
+ }
+
+ bonobo_ui_component_freeze (ui, NULL);
+
+ for (child_node = bonobo_ui_node_children (path_node);
+ child_node != NULL;
+ child_node = bonobo_ui_node_next (child_node)) {
+ verb_name = bonobo_ui_node_get_attr (child_node, "verb");
+ if (verb_name != NULL) {
+ bonobo_ui_component_remove_verb (ui, verb_name);
+ bonobo_ui_node_free_string (verb_name);
+ } else {
+ /* Only look for an id if there's no verb */
+ id_name = bonobo_ui_node_get_attr (child_node, "id");
+ if (id_name != NULL) {
+ bonobo_ui_component_remove_listener (ui, id_name);
+ bonobo_ui_node_free_string (id_name);
+ }
+ }
+ }
+
+ bonobo_ui_component_thaw (ui, NULL);
+
+ bonobo_ui_node_free (path_node);
+}
+
+/**
+ * ephy_bonobo_remove_menu_items_and_verbs
+ *
+ * Removes all menu items contained in a menu or placeholder, and
+ * their verbs.
+ *
+ * @uih: The BonoboUIHandler for this menu item.
+ * @container_path: The standard bonobo-style path specifier for this placeholder or submenu.
+ */
+void
+ephy_bonobo_remove_menu_items_and_commands (BonoboUIComponent *ui,
+ const char *container_path)
+{
+ char *remove_wildcard;
+
+ g_return_if_fail (BONOBO_IS_UI_COMPONENT (ui));
+ g_return_if_fail (container_path != NULL);
+
+ remove_commands (ui, container_path);
+
+ /* For speed, remove menu items themselves all in one fell swoop,
+ * though we removed the verbs one-by-one.
+ */
+ remove_wildcard = g_strdup_printf ("%s/*", container_path);
+ bonobo_ui_component_rm (ui, remove_wildcard, NULL);
+ g_free (remove_wildcard);
+}
+
+/* Call to set the user-visible label of a menu item to a string
+ * containing an underscore accelerator. The underscore is stripped
+ * off before setting the label of the command, because pop-up menu
+ * and toolbar button labels shouldn't have the underscore.
+ */
+void
+ephy_bonobo_set_label_for_menu_item_and_command (BonoboUIComponent *ui,
+ const char *menu_item_path,
+ const char *command_path,
+ const char *label_with_underscore)
+{
+ char *label_no_underscore;
+
+ g_return_if_fail (BONOBO_IS_UI_COMPONENT (ui));
+ g_return_if_fail (menu_item_path != NULL);
+ g_return_if_fail (command_path != NULL);
+ g_return_if_fail (label_with_underscore != NULL);
+
+ label_no_underscore = ephy_str_strip_chr (label_with_underscore, '_');
+ ephy_bonobo_set_label (ui,
+ menu_item_path,
+ label_with_underscore);
+ ephy_bonobo_set_label (ui,
+ command_path,
+ label_no_underscore);
+
+ g_free (label_no_underscore);
+}
+
+gchar *
+ephy_bonobo_add_dockitem (BonoboUIComponent *uic,
+ const char *name,
+ int band_num)
+{
+ gchar *xml;
+ gchar *sname;
+ gchar *path;
+
+ sname = gnome_vfs_escape_string (name);
+ xml = g_strdup_printf ("<dockitem name=\"%s\" band_num=\"%d\" "
+ "config=\"0\" behavior=\"exclusive\"/>",
+ sname, band_num);
+ path = g_strdup_printf ("/%s", sname);
+ bonobo_ui_component_set (uic, "", xml, NULL);
+
+ g_free (sname);
+ g_free (xml);
+ return path;
+}
+
+BonoboControl *
+ephy_bonobo_add_numbered_control (BonoboUIComponent *uic, GtkWidget *w,
+ guint index, const char *container_path)
+{
+ BonoboControl *control;
+ char *xml_string, *item_name, *control_path;
+
+ g_return_val_if_fail (BONOBO_IS_UI_COMPONENT (uic), NULL);
+ g_return_val_if_fail (container_path != NULL, NULL);
+
+ item_name = get_numbered_menu_item_name (index);
+ xml_string = g_strdup_printf ("<control name=\"%s\"/>", item_name);
+
+ bonobo_ui_component_set (uic, container_path, xml_string, NULL);
+
+ g_free (xml_string);
+
+ control_path = ephy_bonobo_get_numbered_menu_item_path (uic, container_path, index);
+
+ control = bonobo_control_new (w);
+ bonobo_ui_component_object_set (uic, control_path, BONOBO_OBJREF (control), NULL);
+ bonobo_object_unref (control);
+
+ g_free (control_path);
+ g_free (item_name);
+
+ return control;
+}
+
+void
+ephy_bonobo_replace_path (BonoboUIComponent *uic, const gchar *path_src,
+ const char *path_dst)
+{
+ BonoboUINode *node;
+ const char *name;
+ char *path_dst_folder;
+
+ name = strrchr (path_dst, '/');
+ g_return_if_fail (name != NULL);
+ path_dst_folder = g_strndup (path_dst, name - path_dst);
+ name++;
+
+ node = bonobo_ui_component_get_tree (uic, path_src, TRUE, NULL);
+ bonobo_ui_node_set_attr (node, "name", name);
+
+ ephy_bonobo_clear_path (uic, path_dst);
+
+ bonobo_ui_component_set_tree (uic, path_dst_folder, node, NULL);
+
+ g_free (path_dst_folder);
+ bonobo_ui_node_free (node);
+}
+
+void
+ephy_bonobo_clear_path (BonoboUIComponent *uic,
+ const gchar *path)
+{
+ if (bonobo_ui_component_path_exists (uic, path, NULL))
+ {
+ char *remove_wildcard = g_strdup_printf ("%s/*", path);
+ bonobo_ui_component_rm (uic, remove_wildcard, NULL);
+ g_free (remove_wildcard);
+ }
+}
diff --git a/lib/ephy-bonobo-extensions.h b/lib/ephy-bonobo-extensions.h
new file mode 100644
index 000000000..86bb977f9
--- /dev/null
+++ b/lib/ephy-bonobo-extensions.h
@@ -0,0 +1,121 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* gul-bonobo-extensions.h - interface for new functions that conceptually
+ belong in bonobo. Perhaps some of these will be
+ actually rolled into bonobo someday.
+
+
+ This file is based on nautilus-bonobo-extensions.h from
+ libnautilus-private.
+
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: John Sullivan <sullivan@eazel.com>
+*/
+
+#ifndef EPHY_BONOBO_EXTENSIONS_H
+#define EPHY_BONOBO_EXTENSIONS_H
+
+#include <bonobo/bonobo-ui-component.h>
+#include <bonobo/bonobo-control.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gtk/gtkwidget.h>
+
+void ephy_bonobo_set_accelerator (BonoboUIComponent *ui,
+ const char *path,
+ const char *accelerator);
+char *ephy_bonobo_get_label (BonoboUIComponent *ui,
+ const char *path);
+void ephy_bonobo_set_label (BonoboUIComponent *ui,
+ const char *path,
+ const char *label);
+void ephy_bonobo_set_tip (BonoboUIComponent *ui,
+ const char *path,
+ const char *tip);
+void ephy_bonobo_set_sensitive (BonoboUIComponent *ui,
+ const char *path,
+ gboolean sensitive);
+void ephy_bonobo_set_toggle_state (BonoboUIComponent *ui,
+ const char *path,
+ gboolean state);
+void ephy_bonobo_set_hidden (BonoboUIComponent *ui,
+ const char *path,
+ gboolean hidden);
+gboolean ephy_bonobo_get_hidden (BonoboUIComponent *ui,
+ const char *path);
+void ephy_bonobo_add_numbered_menu_item (BonoboUIComponent *ui,
+ const char *container_path,
+ guint index,
+ const char *label,
+ GdkPixbuf *pixbuf);
+void ephy_bonobo_add_numbered_toggle_menu_item (BonoboUIComponent *ui,
+ const char *container_path,
+ guint index,
+ const char *label);
+void ephy_bonobo_add_numbered_radio_menu_item (BonoboUIComponent *ui,
+ const char *container_path,
+ guint index,
+ const char *label,
+ const char *radio_group_name);
+char *ephy_bonobo_get_numbered_menu_item_command (BonoboUIComponent *ui,
+ const char *container_path,
+ guint index);
+char *ephy_bonobo_get_numbered_menu_item_path (BonoboUIComponent *ui,
+ const char *container_path,
+ guint index);
+guint ephy_bonobo_get_numbered_menu_item_index_from_command (const char *command);
+char *ephy_bonobo_get_numbered_menu_item_container_path_from_command (const char *command);
+void ephy_bonobo_add_submenu (BonoboUIComponent *ui,
+ const char *container_path,
+ const char *label,
+ GdkPixbuf *pixbuf);
+void ephy_bonobo_add_numbered_submenu (BonoboUIComponent *ui,
+ const char *container_path,
+ guint index,
+ const char *label,
+ GdkPixbuf *pixbuf);
+void ephy_bonobo_add_numbered_submenu_no_verb (BonoboUIComponent *ui,
+ const char *container_path,
+ guint index,
+ const char *label,
+ GdkPixbuf *pixbuf);
+void ephy_bonobo_add_menu_separator (BonoboUIComponent *ui,
+ const char *path);
+void ephy_bonobo_remove_menu_items_and_commands (BonoboUIComponent *ui,
+ const char *container_path);
+void ephy_bonobo_set_label_for_menu_item_and_command (BonoboUIComponent *ui,
+ const char *menu_item_path,
+ const char *command_path,
+ const char *label_with_underscore);
+void ephy_bonobo_set_icon (BonoboUIComponent *ui,
+ const char *path,
+ const char *icon_relative_path);
+gchar *ephy_bonobo_add_dockitem (BonoboUIComponent *uic,
+ const char *name,
+ int band_num);
+BonoboControl *ephy_bonobo_add_numbered_control (BonoboUIComponent *uic,
+ GtkWidget *w, guint index,
+ const char *path);
+void ephy_bonobo_clear_path (BonoboUIComponent *uic,
+ const gchar *path);
+void ephy_bonobo_replace_path (BonoboUIComponent *uic,
+ const gchar *path_src,
+ const char *path_dst);
+
+#endif /* EPHY_BONOBO_EXTENSIONS_H */
diff --git a/lib/ephy-dialog.c b/lib/ephy-dialog.c
new file mode 100644
index 000000000..8c8344dda
--- /dev/null
+++ b/lib/ephy-dialog.c
@@ -0,0 +1,943 @@
+/*
+ * Copyright (C) 2000, 2001, 2002 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.
+ */
+
+#include "ephy-dialog.h"
+#include "ephy-glade.h"
+#include "ephy-state.h"
+#include "ephy-prefs-utils.h"
+#include "ephy-gui.h"
+
+#include <string.h>
+#include <gtk/gtktogglebutton.h>
+
+static void
+ephy_dialog_class_init (EphyDialogClass *klass);
+static void
+ephy_dialog_init (EphyDialog *window);
+static void
+ephy_dialog_finalize (GObject *object);
+static void
+ephy_dialog_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void
+ephy_dialog_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+
+static void
+ephy_dialog_set_parent (EphyDialog *dialog,
+ GtkWidget *parent);
+
+static void
+impl_construct (EphyDialog *dialog,
+ const EphyDialogProperty *properties,
+ const char *file,
+ const char *name);
+
+static void
+impl_destruct (EphyDialog *dialog);
+
+static GtkWidget *
+impl_get_control (EphyDialog *dialog,
+ int property_id);
+static void
+impl_get_value (EphyDialog *dialog,
+ int property_id,
+ GValue *value);
+static gint
+impl_run (EphyDialog *dialog);
+static void
+impl_show (EphyDialog *dialog);
+void
+ephy_dialog_destroy_cb (GtkWidget *widget,
+ EphyDialog *dialog);
+
+enum
+{
+ PROP_0,
+ PROP_PARENT_WINDOW,
+ PROP_MODAL
+};
+
+typedef enum
+{
+ PT_TOGGLEBUTTON,
+ PT_RADIOBUTTON,
+ PT_SPINBUTTON,
+ PT_COLOR,
+ PT_OPTIONMENU,
+ PT_ENTRY,
+ PT_UNKNOWN
+} PrefType;
+
+typedef struct
+{
+ int id;
+ GtkWidget *widget;
+ const char *pref;
+ int *sg;
+ PropertyType type;
+} PropertyInfo;
+
+struct EphyDialogPrivate
+{
+ GtkWidget *parent;
+ GtkWidget *dialog;
+ GtkWidget *container;
+ PropertyInfo *props;
+ gboolean modal;
+ char *name;
+
+ int spin_item_id;
+ GTimer *spin_timer;
+};
+
+#define SPIN_DELAY 0.20
+
+static GObjectClass *parent_class = NULL;
+
+GType
+ephy_dialog_get_type (void)
+{
+ static GType ephy_dialog_type = 0;
+
+ if (ephy_dialog_type == 0)
+ {
+ static const GTypeInfo our_info =
+ {
+ sizeof (EphyDialogClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) ephy_dialog_class_init,
+ NULL,
+ NULL, /* class_data */
+ sizeof (EphyDialog),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) ephy_dialog_init
+ };
+
+ ephy_dialog_type = g_type_register_static (G_TYPE_OBJECT,
+ "EphyDialog",
+ &our_info, 0);
+ }
+
+ return ephy_dialog_type;
+}
+
+static void
+ephy_dialog_class_init (EphyDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->finalize = ephy_dialog_finalize;
+ object_class->set_property = ephy_dialog_set_property;
+ object_class->get_property = ephy_dialog_get_property;
+
+ klass->construct = impl_construct;
+ klass->get_control = impl_get_control;
+ klass->get_value = impl_get_value;
+ klass->run = impl_run;
+ klass->show = impl_show;
+ klass->destruct = impl_destruct;
+
+ g_object_class_install_property (object_class,
+ PROP_PARENT_WINDOW,
+ g_param_spec_object ("ParentWindow",
+ "ParentWindow",
+ "Parent window",
+ GTK_TYPE_WIDGET,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_MODAL,
+ g_param_spec_boolean ("Modal",
+ "Modal",
+ "Modal dialog",
+ FALSE,
+ G_PARAM_READWRITE));
+}
+
+static PrefType
+get_pref_type_from_widget (GtkWidget *widget)
+{
+ if (GTK_IS_OPTION_MENU (widget))
+ {
+ return PT_OPTIONMENU;
+ }
+ else if (GTK_IS_SPIN_BUTTON (widget))
+ {
+ return PT_SPINBUTTON;
+ }
+ else if (GTK_IS_RADIO_BUTTON (widget))
+ {
+ return PT_RADIOBUTTON;
+ }
+ else if (GTK_IS_TOGGLE_BUTTON (widget))
+ {
+ return PT_TOGGLEBUTTON;
+ }
+ else if (GTK_IS_ENTRY (widget))
+ {
+ return PT_ENTRY;
+ }
+ else if (GNOME_IS_COLOR_PICKER (widget))
+ {
+ return PT_COLOR;
+ }
+
+ return PT_UNKNOWN;
+}
+
+static int *
+set_controls_sensitivity (EphyDialog *dialog,
+ int *sg, gboolean s)
+{
+ GtkWidget *widget;
+
+ while (*sg != SY_END_GROUP)
+ {
+ widget = ephy_dialog_get_control (dialog,
+ *sg);
+ gtk_widget_set_sensitive (widget, s);
+
+ sg++;
+ }
+
+ return sg;
+}
+
+static void
+prefs_set_group_sensitivity (GtkWidget *widget,
+ PrefType type, int *sg)
+{
+ int group = -1;
+ EphyDialog *dialog;
+ int i = 0;
+
+ if (sg == NULL) return;
+
+ dialog = EPHY_DIALOG (g_object_get_data
+ (G_OBJECT(widget), "dialog"));
+
+ if (GTK_IS_RADIO_BUTTON (widget))
+ {
+ group = ephy_gui_gtk_radio_button_get
+ (GTK_RADIO_BUTTON(widget));
+ }
+ else if (GTK_IS_TOGGLE_BUTTON (widget))
+ {
+ group = !gtk_toggle_button_get_active
+ (GTK_TOGGLE_BUTTON(widget));
+ }
+ else
+ {
+ g_assert (FALSE);
+ }
+
+ while (*sg != SY_END)
+ {
+ if ((*sg == SY_BEGIN_GROUP) ||
+ (*sg == SY_BEGIN_GROUP_INVERSE))
+ {
+ gboolean b;
+
+ b = (i == group);
+ if (*sg == SY_BEGIN_GROUP_INVERSE) b = !b;
+
+ sg++;
+ sg = set_controls_sensitivity
+ (dialog, sg, b);
+ }
+
+ i++;
+ sg++;
+ }
+}
+
+static void
+prefs_togglebutton_clicked_cb (GtkWidget *widget, PropertyInfo *pi)
+{
+ if (pi->type == PT_AUTOAPPLY)
+ {
+ ephy_pu_set_config_from_togglebutton (widget, pi->pref);
+ }
+
+ prefs_set_group_sensitivity (widget, pi->type, pi->sg);
+}
+
+static void
+prefs_radiobutton_clicked_cb (GtkWidget *widget, PropertyInfo *pi)
+{
+ if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widget)))
+ {
+ return;
+ }
+
+ if (pi->type == PT_AUTOAPPLY)
+ {
+ ephy_pu_set_config_from_radiobuttongroup (widget, pi->pref);
+ }
+
+ prefs_set_group_sensitivity (widget, pi->type, pi->sg);
+}
+
+static gint
+prefs_spinbutton_timeout_cb (EphyDialog *dialog)
+{
+ PropertyInfo pi = dialog->priv->props[dialog->priv->spin_item_id];
+
+ /* timer still valid? */
+ if (dialog->priv->spin_timer == NULL)
+ {
+ return FALSE;
+ }
+
+ /* okay, we're ready to set */
+ if (g_timer_elapsed (dialog->priv->spin_timer, NULL) >= SPIN_DELAY)
+ {
+ /* kill off the timer */
+ g_timer_destroy (dialog->priv->spin_timer);
+ dialog->priv->spin_timer = NULL;
+
+ /* HACK update the spinbutton here so that the
+ * changes made directly in the entry are accepted
+ * and set in the pref. Otherwise the old value is used */
+ gtk_spin_button_update (GTK_SPIN_BUTTON(pi.widget));
+
+ /* set */
+ ephy_pu_set_config_from_spin_button (pi.widget,
+ pi.pref);
+
+ /* done now */
+ return FALSE;
+ }
+
+ /* call me again */
+ return TRUE;
+}
+
+static void
+prefs_spinbutton_changed_cb (GtkWidget *widget, PropertyInfo *pi)
+{
+ EphyDialog *dialog;
+
+ if (pi->type != PT_AUTOAPPLY) return;
+
+ dialog = EPHY_DIALOG (g_object_get_data
+ (G_OBJECT(widget), "dialog"));
+
+ dialog->priv->spin_item_id = pi->id;
+
+ /* destroy any existing timer */
+ if (dialog->priv->spin_timer != NULL)
+ {
+ g_timer_destroy (dialog->priv->spin_timer);
+ }
+
+ /* start the new one */
+ dialog->priv->spin_timer = g_timer_new();
+ g_timer_start (dialog->priv->spin_timer);
+ g_timeout_add (50, (GSourceFunc) prefs_spinbutton_timeout_cb,
+ dialog);
+}
+
+static void
+prefs_color_changed_cb (GtkWidget *widget, guint r, guint g,
+ guint b, guint a, const PropertyInfo *pi)
+{
+ if (pi->type == PT_AUTOAPPLY)
+ {
+ ephy_pu_set_config_from_color (widget, pi->pref);
+ }
+}
+
+static void
+prefs_entry_changed_cb (GtkWidget *widget, PropertyInfo *pi)
+{
+ if (pi->type == PT_AUTOAPPLY)
+ {
+ ephy_pu_set_config_from_editable (widget, pi->pref);
+ }
+}
+
+static void
+prefs_optionmenu_selected_cb (GtkWidget *widget, PropertyInfo *pi)
+{
+ if (pi->type == PT_AUTOAPPLY)
+ {
+ ephy_pu_set_config_from_optionmenu (widget, pi->pref);
+ }
+}
+
+static void
+prefs_connect_signals (EphyDialog *dialog)
+{
+ int i;
+ GSList *list;
+ PropertyInfo *props = dialog->priv->props;
+
+ for (i = 0; props[i].widget != NULL; i++)
+ {
+ PrefType type;
+ PropertyInfo *info;
+
+ if ((props[i].type != PT_AUTOAPPLY) &&
+ (props[i].sg == NULL))
+ continue;
+
+ info = &dialog->priv->props[i];
+ type = get_pref_type_from_widget
+ (dialog->priv->props[i].widget);
+
+ switch (type)
+ {
+ case PT_TOGGLEBUTTON:
+ g_object_set_data (G_OBJECT(info->widget), "dialog", dialog);
+ g_signal_connect (G_OBJECT (info->widget), "clicked",
+ G_CALLBACK(prefs_togglebutton_clicked_cb),
+ (gpointer)info);
+ break;
+ case PT_RADIOBUTTON:
+ list = gtk_radio_button_get_group
+ (GTK_RADIO_BUTTON(info->widget));
+ for (; list != NULL; list = list->next)
+ {
+ g_object_set_data (G_OBJECT(list->data),
+ "dialog", dialog);
+ g_signal_connect
+ (G_OBJECT (list->data), "clicked",
+ G_CALLBACK(prefs_radiobutton_clicked_cb),
+ (gpointer)info);
+ }
+ break;
+ case PT_SPINBUTTON:
+ g_object_set_data (G_OBJECT(info->widget), "dialog", dialog);
+ g_signal_connect (G_OBJECT (info->widget), "changed",
+ G_CALLBACK(prefs_spinbutton_changed_cb),
+ (gpointer)info);
+ break;
+ case PT_COLOR:
+ g_signal_connect (G_OBJECT (info->widget), "color_set",
+ G_CALLBACK(prefs_color_changed_cb),
+ (gpointer)info);
+ break;
+ case PT_OPTIONMENU:
+ g_signal_connect (G_OBJECT (info->widget),
+ "changed",
+ G_CALLBACK(prefs_optionmenu_selected_cb),
+ (gpointer)info);
+ break;
+ case PT_ENTRY:
+ g_signal_connect (G_OBJECT (info->widget), "changed",
+ G_CALLBACK(prefs_entry_changed_cb),
+ (gpointer)info);
+ break;
+ case PT_UNKNOWN:
+ break;
+ }
+ }
+}
+
+static void
+ephy_dialog_init (EphyDialog *dialog)
+{
+ dialog->priv = g_new0 (EphyDialogPrivate, 1);
+
+ dialog->priv->parent = NULL;
+ dialog->priv->dialog = NULL;
+ dialog->priv->props = NULL;
+ dialog->priv->spin_timer = NULL;
+ dialog->priv->name = NULL;
+}
+
+static void
+prefs_set_sensitivity (PropertyInfo *props)
+{
+ int i;
+
+ for (i=0 ; props[i].id >= 0; i++)
+ {
+ if (props[i].sg == NULL) continue;
+
+ g_return_if_fail (props[i].widget != NULL);
+
+ if (GTK_IS_RADIO_BUTTON(props[i].widget) ||
+ GTK_IS_TOGGLE_BUTTON(props[i].widget))
+ {
+ prefs_set_group_sensitivity (props[i].widget,
+ props[i].type,
+ props[i].sg);
+ }
+ }
+}
+
+static void
+load_props (PropertyInfo *props)
+{
+ int i;
+
+ for (i=0 ; props[i].id >= 0; i++)
+ {
+ if (props[i].pref == NULL) continue;
+
+ g_return_if_fail (props[i].widget != NULL);
+
+ if (GTK_IS_SPIN_BUTTON(props[i].widget))
+ {
+ ephy_pu_set_spin_button_from_config (props[i].widget,
+ props[i].pref);
+ }
+ else if (GTK_IS_RADIO_BUTTON(props[i].widget))
+ {
+ ephy_pu_set_radiobuttongroup_from_config (props[i].widget,
+ props[i].pref);
+ }
+ else if (GTK_IS_TOGGLE_BUTTON(props[i].widget))
+ {
+ ephy_pu_set_togglebutton_from_config (props[i].widget,
+ props[i].pref);
+ }
+ else if (GTK_IS_EDITABLE(props[i].widget))
+ {
+ ephy_pu_set_editable_from_config (props[i].widget,
+ props[i].pref);
+ }
+ else if (GTK_IS_OPTION_MENU(props[i].widget))
+ {
+ ephy_pu_set_optionmenu_from_config (props[i].widget,
+ props[i].pref);
+ }
+ else if (GNOME_IS_COLOR_PICKER(props[i].widget))
+ {
+ ephy_pu_set_color_from_config (props[i].widget,
+ props[i].pref);
+ }
+ }
+
+}
+
+static void
+save_props (PropertyInfo *props)
+{
+ int i;
+
+ for (i=0 ; props[i].id >= 0; i++)
+ {
+ if ((props[i].pref == NULL) ||
+ (props[i].type != PT_NORMAL)) continue;
+ g_return_if_fail (props[i].widget != NULL);
+
+ if (GTK_IS_SPIN_BUTTON(props[i].widget))
+ {
+ ephy_pu_set_config_from_spin_button (props[i].widget,
+ props[i].pref);
+ }
+ else if (GTK_IS_RADIO_BUTTON(props[i].widget))
+ {
+ ephy_pu_set_config_from_radiobuttongroup (props[i].widget,
+ props[i].pref);
+ }
+ else if (GTK_IS_TOGGLE_BUTTON(props[i].widget))
+ {
+ ephy_pu_set_config_from_togglebutton (props[i].widget,
+ props[i].pref);
+ }
+ else if (GTK_IS_EDITABLE(props[i].widget))
+ {
+ ephy_pu_set_config_from_editable (props[i].widget,
+ props[i].pref);
+ }
+ else if (GTK_IS_OPTION_MENU(props[i].widget))
+ {
+ ephy_pu_set_config_from_optionmenu (props[i].widget,
+ props[i].pref);
+ }
+ else if (GNOME_IS_COLOR_PICKER(props[i].widget))
+ {
+ ephy_pu_set_config_from_color (props[i].widget,
+ props[i].pref);
+ }
+ }
+}
+
+static void
+ephy_dialog_finalize (GObject *object)
+{
+ EphyDialog *dialog;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (IS_EPHY_DIALOG (object));
+
+ dialog = EPHY_DIALOG (object);
+
+ g_return_if_fail (dialog->priv != NULL);
+
+ if (dialog->priv->dialog)
+ {
+ ephy_dialog_destruct (dialog);
+ }
+
+ g_free (dialog->priv->name);
+ g_free (dialog->priv->props);
+ g_free (dialog->priv);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+ephy_dialog_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EphyDialog *d = EPHY_DIALOG (object);
+
+ switch (prop_id)
+ {
+ case PROP_PARENT_WINDOW:
+ ephy_dialog_set_parent (d, g_value_get_object (value));
+ break;
+ case PROP_MODAL:
+ ephy_dialog_set_modal (d, g_value_get_boolean (value));
+ break;
+ }
+}
+
+static void
+ephy_dialog_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EphyDialog *d = EPHY_DIALOG (object);
+
+ switch (prop_id)
+ {
+ case PROP_PARENT_WINDOW:
+ g_value_set_object (value, d->priv->parent);
+ break;
+ case PROP_MODAL:
+ g_value_set_boolean (value, d->priv->modal);
+ }
+}
+
+static void
+ephy_dialog_set_parent (EphyDialog *dialog,
+ GtkWidget *parent)
+{
+ g_return_if_fail (dialog->priv->parent == NULL);
+ g_return_if_fail (GTK_IS_WINDOW(parent));
+ g_return_if_fail (GTK_IS_WINDOW(dialog->priv->dialog));
+
+ dialog->priv->parent = parent;
+
+ gtk_window_set_transient_for (GTK_WINDOW (dialog->priv->dialog),
+ GTK_WINDOW (parent));
+}
+
+EphyDialog *
+ephy_dialog_new (void)
+{
+ return EPHY_DIALOG (g_object_new (EPHY_DIALOG_TYPE, NULL));
+}
+
+EphyDialog *
+ephy_dialog_new_with_parent (GtkWidget *parent_window)
+{
+ return EPHY_DIALOG (g_object_new (EPHY_DIALOG_TYPE,
+ "ParentWindow", parent_window,
+ NULL));
+}
+
+void ephy_dialog_show_embedded (EphyDialog *dialog,
+ GtkWidget *container)
+{
+ dialog->priv->container = container;
+ gtk_container_add (GTK_CONTAINER(container),
+ dialog->priv->dialog);
+ gtk_widget_show (dialog->priv->dialog);
+}
+
+void
+ephy_dialog_remove_embedded (EphyDialog *dialog)
+{
+ g_object_ref (G_OBJECT(dialog->priv->dialog));
+ gtk_container_remove (GTK_CONTAINER(dialog->priv->container),
+ dialog->priv->dialog);
+}
+
+static PropertyInfo *
+init_props (const EphyDialogProperty *properties, GladeXML *gxml)
+{
+ PropertyInfo *props;
+ int i;
+
+ for (i=0 ; properties[i].control_name != NULL; i++);
+
+ props = g_new0 (PropertyInfo, i+1);
+
+ for (i=0 ; properties[i].control_name != NULL; i++)
+ {
+ props[i].id = properties[i].id;
+ props[i].widget = glade_xml_get_widget
+ (gxml, properties[i].control_name);
+ props[i].pref = properties[i].state_pref;
+ props[i].sg = properties[i].sg;
+ props[i].type = properties[i].type;
+ }
+
+ props[i].id = -1;
+ props[i].widget = NULL;
+ props[i].pref = NULL;
+ props[i].sg = NULL;
+ props[i].type = 0;
+
+ return props;
+}
+
+static void
+dialog_destruction_notify (EphyDialog *dialog,
+ GObject *where_the_object_was)
+{
+ dialog->priv->dialog = NULL;
+ ephy_dialog_destruct (dialog);
+ g_object_unref (dialog);
+}
+
+static void
+dialog_destroy_cb (GtkWidget *widget, EphyDialog *dialog)
+{
+ if (dialog->priv->props &&
+ dialog->priv->dialog)
+ {
+ save_props (dialog->priv->props);
+ }
+}
+
+static void
+impl_construct (EphyDialog *dialog,
+ const EphyDialogProperty *properties,
+ const char *file,
+ const char *name)
+{
+ GladeXML *gxml;
+
+ gxml = ephy_glade_widget_new
+ (file, name, &(dialog->priv->dialog), dialog);
+
+ if (dialog->priv->name == NULL)
+ {
+ dialog->priv->name = g_strdup (name);
+ }
+
+ if (properties)
+ {
+ dialog->priv->props = init_props (properties, gxml);
+ }
+
+ g_signal_connect (dialog->priv->dialog,
+ "destroy",
+ G_CALLBACK(dialog_destroy_cb),
+ dialog);
+
+ g_object_unref (gxml);
+
+ g_object_weak_ref (G_OBJECT(dialog->priv->dialog),
+ (GWeakNotify)dialog_destruction_notify,
+ dialog);
+
+ if (dialog->priv->props)
+ {
+ load_props (dialog->priv->props);
+ prefs_connect_signals (dialog);
+ prefs_set_sensitivity (dialog->priv->props);
+ }
+}
+
+static void
+impl_destruct (EphyDialog *dialog)
+{
+ if (dialog->priv->dialog)
+ {
+ g_object_weak_unref (G_OBJECT(dialog->priv->dialog),
+ (GWeakNotify)dialog_destruction_notify,
+ dialog);
+ gtk_widget_destroy (dialog->priv->dialog);
+ dialog->priv->dialog = NULL;
+ }
+}
+
+static GtkWidget *
+impl_get_control (EphyDialog *dialog,
+ int property_id)
+{
+ return dialog->priv->props[property_id].widget;
+}
+
+static void
+impl_get_value (EphyDialog *dialog,
+ int property_id,
+ GValue *value)
+{
+ GtkWidget *widget = ephy_dialog_get_control (dialog,
+ property_id);
+
+ if (GTK_IS_SPIN_BUTTON (widget))
+ {
+ float val;
+ g_value_init (value, G_TYPE_FLOAT);
+ val = gtk_spin_button_get_value (GTK_SPIN_BUTTON(widget));
+ g_value_set_float (value, val);
+ }
+ else if (GTK_IS_RADIO_BUTTON (widget))
+ {
+ int val;
+ g_value_init (value, G_TYPE_INT);
+ val = ephy_gui_gtk_radio_button_get (GTK_RADIO_BUTTON(widget));
+ g_value_set_int (value, val);
+ }
+ else if (GTK_IS_TOGGLE_BUTTON (widget))
+ {
+ g_value_init (value, G_TYPE_BOOLEAN);
+ g_value_set_boolean (value, gtk_toggle_button_get_active
+ (GTK_TOGGLE_BUTTON(widget)));
+ }
+ else if (GTK_IS_EDITABLE (widget))
+ {
+ g_value_init (value, G_TYPE_STRING);
+ g_value_set_string (value, gtk_editable_get_chars
+ (GTK_EDITABLE (widget), 0, -1));
+ }
+ else if (GTK_IS_OPTION_MENU (widget))
+ {
+ int val;
+ g_value_init (value, G_TYPE_INT);
+ val = gtk_option_menu_get_history (GTK_OPTION_MENU(widget));
+ g_value_set_int (value, val);
+ }
+}
+
+static gboolean
+ephy_dialog_configure_event_cb (GtkWidget *widget,
+ GdkEventConfigure *event,
+ EphyDialog *dialog)
+{
+ ephy_state_save_window (widget, dialog->priv->name);
+
+ return FALSE;
+}
+
+static void
+setup_default_size (EphyDialog *dialog)
+{
+ ephy_state_load_window (dialog->priv->dialog,
+ dialog->priv->name, -1, -1, FALSE);
+
+ g_signal_connect (dialog->priv->dialog,
+ "configure_event",
+ G_CALLBACK (ephy_dialog_configure_event_cb),
+ dialog);
+}
+
+static gint
+impl_run (EphyDialog *dialog)
+{
+ setup_default_size (dialog);
+
+ return gtk_dialog_run (GTK_DIALOG(dialog->priv->dialog));
+}
+
+static void
+impl_show (EphyDialog *dialog)
+{
+ setup_default_size (dialog);
+
+ if (dialog->priv->parent)
+ {
+ /* make the dialog transient again, because it seems to get
+ * forgotten after gtk_widget_hide
+ */
+ gtk_window_set_transient_for (GTK_WINDOW (dialog->priv->dialog),
+ GTK_WINDOW (dialog->priv->parent));
+ }
+ gtk_window_present (GTK_WINDOW(dialog->priv->dialog));
+}
+
+void
+ephy_dialog_set_modal (EphyDialog *dialog,
+ gboolean is_modal)
+{
+ dialog->priv->modal = is_modal;
+
+ gtk_window_set_modal (GTK_WINDOW(dialog->priv->dialog),
+ is_modal);
+}
+
+void
+ephy_dialog_construct (EphyDialog *dialog,
+ const EphyDialogProperty *properties,
+ const char *file,
+ const char *name)
+{
+ EphyDialogClass *klass = EPHY_DIALOG_GET_CLASS (dialog);
+ return klass->construct (dialog, properties, file, name);
+}
+
+void
+ephy_dialog_destruct (EphyDialog *dialog)
+{
+ EphyDialogClass *klass = EPHY_DIALOG_GET_CLASS (dialog);
+ klass->destruct (dialog);
+}
+
+gint
+ephy_dialog_run (EphyDialog *dialog)
+{
+ EphyDialogClass *klass = EPHY_DIALOG_GET_CLASS (dialog);
+ return klass->run (dialog);
+}
+
+void
+ephy_dialog_show (EphyDialog *dialog)
+{
+ EphyDialogClass *klass = EPHY_DIALOG_GET_CLASS (dialog);
+ klass->show (dialog);
+}
+
+GtkWidget *
+ephy_dialog_get_control (EphyDialog *dialog,
+ int property_id)
+{
+ EphyDialogClass *klass = EPHY_DIALOG_GET_CLASS (dialog);
+ return klass->get_control (dialog, property_id);
+}
+
+void
+ephy_dialog_get_value (EphyDialog *dialog,
+ int property_id,
+ GValue *value)
+{
+ EphyDialogClass *klass = EPHY_DIALOG_GET_CLASS (dialog);
+ return klass->get_value (dialog, property_id, value);
+}
+
diff --git a/lib/ephy-dialog.h b/lib/ephy-dialog.h
new file mode 100644
index 000000000..24cb2cf5c
--- /dev/null
+++ b/lib/ephy-dialog.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2000, 2001, 2002 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.
+ */
+
+#ifndef EPHY_DIALOG_H
+#define EPHY_DIALOG_H
+
+#include <glib-object.h>
+#include <glib.h>
+#include <gtk/gtkwidget.h>
+
+G_BEGIN_DECLS
+
+typedef struct EphyDialogClass EphyDialogClass;
+
+#define EPHY_DIALOG_TYPE (ephy_dialog_get_type ())
+#define EPHY_DIALOG(obj) (GTK_CHECK_CAST ((obj), EPHY_DIALOG_TYPE, EphyDialog))
+#define EPHY_DIALOG_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), EPHY_DIALOG_TYPE, EphyDialogClass))
+#define IS_EPHY_DIALOG(obj) (GTK_CHECK_TYPE ((obj), EPHY_DIALOG_TYPE))
+#define IS_EPHY_DIALOG_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), EPHY_DIALOG))
+#define EPHY_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EPHY_DIALOG_TYPE, EphyDialogClass))
+
+typedef struct EphyDialog EphyDialog;
+typedef struct EphyDialogPrivate EphyDialogPrivate;
+
+#define SY_BEGIN_GROUP -20
+#define SY_END_GROUP -21
+#define SY_END -22
+#define SY_BEGIN_GROUP_INVERSE -23
+
+struct EphyDialog
+{
+ GObject parent;
+ EphyDialogPrivate *priv;
+};
+
+typedef enum
+{
+ PT_NORMAL,
+ PT_AUTOAPPLY
+} PropertyType;
+
+typedef struct
+{
+ int id;
+ const char *control_name;
+ const char *state_pref;
+ PropertyType type;
+ int *sg;
+} EphyDialogProperty;
+
+struct EphyDialogClass
+{
+ GObjectClass parent_class;
+
+ void (* construct) (EphyDialog *dialog,
+ const EphyDialogProperty *properties,
+ const char *file,
+ const char *name);
+ void (* destruct) (EphyDialog *dialog);
+ gint (* run) (EphyDialog *dialog);
+ void (* show) (EphyDialog *dialog);
+ GtkWidget * (* get_control) (EphyDialog *dialog,
+ int property_id);
+ void (* get_value) (EphyDialog *dialog,
+ int property_id,
+ GValue *value);
+};
+
+GType ephy_dialog_get_type (void);
+
+EphyDialog *ephy_dialog_new (void);
+
+EphyDialog *ephy_dialog_new_with_parent (GtkWidget *parent_window);
+
+void ephy_dialog_construct (EphyDialog *dialog,
+ const EphyDialogProperty *properties,
+ const char *file,
+ const char *name);
+
+void ephy_dialog_destruct (EphyDialog *dialog);
+
+gint ephy_dialog_run (EphyDialog *dialog);
+
+void ephy_dialog_show (EphyDialog *dialog);
+
+void ephy_dialog_show_embedded (EphyDialog *dialog,
+ GtkWidget *container);
+
+void ephy_dialog_remove_embedded (EphyDialog *dialog);
+
+void ephy_dialog_set_modal (EphyDialog *dialog,
+ gboolean is_modal);
+
+GtkWidget *ephy_dialog_get_control (EphyDialog *dialog,
+ int property_id);
+
+void ephy_dialog_get_value (EphyDialog *dialog,
+ int property_id,
+ GValue *value);
+
+G_END_DECLS
+
+#endif
+
diff --git a/lib/ephy-dnd.c b/lib/ephy-dnd.c
new file mode 100644
index 000000000..b70fa284a
--- /dev/null
+++ b/lib/ephy-dnd.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2000, 2001, 2002 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.
+ */
+
+#include "ephy-dnd.h"
+
+#include <gtk/gtkselection.h>
+#include <gtk/gtktreeview.h>
+
+static GtkTargetEntry url_drag_types [] =
+{
+ { EPHY_DND_URI_LIST_TYPE, 0, EPHY_DND_URI_LIST },
+ { EPHY_DND_TEXT_TYPE, 0, EPHY_DND_TEXT },
+ { EPHY_DND_URL_TYPE, 0, EPHY_DND_URL }
+};
+
+/* Encode a "_NETSCAPE_URL_" selection.
+ * As far as I can tell, Netscape is expecting a single
+ * URL to be returned. I cannot discover a way to construct
+ * a list to be returned that Netscape can understand.
+ * GMC also fails to do this as well.
+ */
+static void
+add_one_netscape_url (const char *url, int x, int y, int w, int h, gpointer data)
+{
+ GString *result;
+
+ result = (GString *) data;
+ if (result->len == 0) {
+ g_string_append (result, url);
+ }
+}
+
+static void
+add_one_uri (const char *uri, int x, int y, int w, int h, gpointer data)
+{
+ GString *result;
+
+ result = (GString *) data;
+
+ g_string_append (result, uri);
+ g_string_append (result, "\r\n");
+}
+
+gboolean
+ephy_dnd_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint32 time,
+ gpointer container_context,
+ EphyDragEachSelectedItemIterator each_selected_item_iterator)
+{
+ GString *result;
+
+ switch (info) {
+ case EPHY_DND_URI_LIST:
+ case EPHY_DND_TEXT:
+ result = g_string_new (NULL);
+ (* each_selected_item_iterator) (add_one_uri, container_context, result);
+ break;
+ case EPHY_DND_URL:
+ result = g_string_new (NULL);
+ (* each_selected_item_iterator) (add_one_netscape_url, container_context, result);
+ break;
+ default:
+ return FALSE;
+ }
+
+ gtk_selection_data_set (selection_data,
+ selection_data->target,
+ 8, result->str, result->len);
+
+ return TRUE;
+}
+
+void
+ephy_dnd_url_drag_source_set (GtkWidget *widget)
+{
+ gtk_drag_source_set (widget,
+ GDK_BUTTON1_MASK,
+ url_drag_types,
+ G_N_ELEMENTS (url_drag_types),
+ GDK_ACTION_COPY);
+}
+
+void
+ephy_dnd_enable_model_drag_source (GtkWidget *treeview)
+{
+ gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (treeview),
+ GDK_BUTTON1_MASK,
+ url_drag_types, G_N_ELEMENTS (url_drag_types),
+ GDK_ACTION_COPY);
+}
+
diff --git a/lib/ephy-dnd.h b/lib/ephy-dnd.h
new file mode 100644
index 000000000..87b6008d9
--- /dev/null
+++ b/lib/ephy-dnd.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2000, 2001, 2002 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.
+ */
+
+#ifndef EPHY_DND_H
+#define EPHY_DND_H
+
+#include <glib.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkdnd.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_DND_NODE_PROPERTY 3
+
+/* Drag & Drop target names. */
+#define EPHY_DND_URI_LIST_TYPE "text/uri-list"
+#define EPHY_DND_TEXT_TYPE "text/plain"
+#define EPHY_DND_URL_TYPE "_NETSCAPE_URL"
+#define EPHY_DND_EPHY_URL_TYPE "EPHY_URL"
+#define EPHY_DND_EPHY_BOOKMARK_TYPE "EPHY_BOOKMARK"
+
+/* Standard Drag & Drop types. */
+typedef enum {
+ EPHY_DND_URI_LIST,
+ EPHY_DND_URL,
+ EPHY_DND_TEXT,
+} EphyIconDndTargetType;
+
+typedef void (* EphyDragEachSelectedItemDataGet) (const char *url,
+ int x, int y, int w, int h,
+ gpointer data);
+
+typedef void (* EphyDragEachSelectedItemIterator) (EphyDragEachSelectedItemDataGet iteratee,
+ gpointer iterator_context,
+ gpointer data);
+
+gboolean ephy_dnd_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint32 time,
+ gpointer container_context,
+ EphyDragEachSelectedItemIterator each_selected_item_iterator);
+
+void ephy_dnd_url_drag_source_set (GtkWidget *widget);
+
+void ephy_dnd_enable_model_drag_source (GtkWidget *treeview);
+
+
+G_END_DECLS
+
+#endif
diff --git a/lib/ephy-file-helpers.c b/lib/ephy-file-helpers.c
new file mode 100644
index 000000000..2e867e3f5
--- /dev/null
+++ b/lib/ephy-file-helpers.c
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2002 Jorn Baayen
+ *
+ * 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.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <glib.h>
+#include <libgnome/gnome-i18n.h>
+#include <libgnome/gnome-init.h>
+#include <libgnome/gnome-exec.h>
+
+#include "ephy-file-helpers.h"
+
+static GHashTable *files = NULL;
+
+static char *dot_dir = NULL;
+
+char *
+ephy_file_tmp_filename (const char *base,
+ const char *extension)
+{
+ int fd;
+ char *name = g_strdup (base);
+
+ fd = mkstemp (name);
+
+ if (fd != -1)
+ {
+ unlink (name);
+ close (fd);
+ }
+ else
+ {
+ return NULL;
+ }
+
+ if (extension)
+ {
+ char *tmp;
+ tmp = g_strconcat (name, ".",
+ extension, NULL);
+ g_free (name);
+ name = tmp;
+ }
+
+ return name;
+}
+
+const char *
+ephy_file (const char *filename)
+{
+ char *ret;
+ int i;
+
+ static char *paths[] =
+ {
+ SHARE_DIR "/",
+ SHARE_DIR "/glade/",
+ SHARE_DIR "/art/",
+ SHARE_UNINSTALLED_DIR "/",
+ SHARE_UNINSTALLED_DIR "/glade/",
+ SHARE_UNINSTALLED_DIR "/art/"
+ };
+
+ g_assert (files != NULL);
+
+ ret = g_hash_table_lookup (files, filename);
+ if (ret != NULL)
+ return ret;
+
+ for (i = 0; i < (int) G_N_ELEMENTS (paths); i++)
+ {
+ ret = g_strconcat (paths[i], filename, NULL);
+ if (g_file_test (ret, G_FILE_TEST_EXISTS) == TRUE)
+ {
+ g_hash_table_insert (files, g_strdup (filename), ret);
+ return (const char *) ret;
+ }
+ g_free (ret);
+ }
+
+ g_warning (_("Failed to find %s"), filename);
+
+ return NULL;
+}
+
+const char *
+ephy_dot_dir (void)
+{
+ if (dot_dir == NULL)
+ {
+ dot_dir = g_build_filename (g_get_home_dir (),
+ GNOME_DOT_GNOME,
+ "epiphany",
+ NULL);
+ }
+
+ return dot_dir;
+}
+
+void
+ephy_file_helpers_init (void)
+{
+ files = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+}
+
+void
+ephy_file_helpers_shutdown (void)
+{
+ g_hash_table_destroy (files);
+
+ g_free (dot_dir);
+}
+
+static void
+gul_general_gnome_shell_execute (const char *command)
+{
+ GError *error = NULL;
+ if (!g_spawn_command_line_async (command, &error)) {
+ g_warning ("Error starting command '%s': %s\n", command, error->message);
+ g_error_free (error);
+ }
+}
+
+/* Return a command string containing the path to a terminal on this system. */
+
+static char *
+try_terminal_command (const char *program,
+ const char *args)
+{
+ char *program_in_path, *quoted, *result;
+
+ if (program == NULL) {
+ return NULL;
+ }
+
+ program_in_path = g_find_program_in_path (program);
+ if (program_in_path == NULL) {
+ return NULL;
+ }
+
+ quoted = g_shell_quote (program_in_path);
+ if (args == NULL || args[0] == '\0') {
+ return quoted;
+ }
+ result = g_strconcat (quoted, " ", args, NULL);
+ g_free (quoted);
+ return result;
+}
+
+static char *
+try_terminal_command_argv (int argc,
+ char **argv)
+{
+ GString *string;
+ int i;
+ char *quoted, *result;
+
+ if (argc == 0) {
+ return NULL;
+ }
+
+ if (argc == 1) {
+ return try_terminal_command (argv[0], NULL);
+ }
+
+ string = g_string_new (argv[1]);
+ for (i = 2; i < argc; i++) {
+ quoted = g_shell_quote (argv[i]);
+ g_string_append_c (string, ' ');
+ g_string_append (string, quoted);
+ g_free (quoted);
+ }
+ result = try_terminal_command (argv[0], string->str);
+ g_string_free (string, TRUE);
+
+ return result;
+}
+
+static char *
+get_terminal_command_prefix (gboolean for_command)
+{
+ int argc;
+ char **argv;
+ char *command;
+ guint i;
+ static const char *const commands[][3] = {
+ { "gnome-terminal", "-x", "" },
+ { "dtterm", "-e", "-ls" },
+ { "nxterm", "-e", "-ls" },
+ { "color-xterm", "-e", "-ls" },
+ { "rxvt", "-e", "-ls" },
+ { "xterm", "-e", "-ls" },
+ };
+
+ /* Try the terminal from preferences. Use without any
+ * arguments if we are just doing a standalone terminal.
+ */
+ argc = 0;
+ argv = g_new0 (char *, 1);
+ gnome_prepend_terminal_to_vector (&argc, &argv);
+
+ command = NULL;
+ if (argc != 0) {
+ if (for_command) {
+ command = try_terminal_command_argv (argc, argv);
+ } else {
+ /* Strip off the arguments in a lame attempt
+ * to make it be an interactive shell.
+ */
+ command = try_terminal_command (argv[0], NULL);
+ }
+ }
+
+ while (argc != 0) {
+ g_free (argv[--argc]);
+ }
+ g_free (argv);
+
+ if (command != NULL) {
+ return command;
+ }
+
+ /* Try well-known terminal applications in same order that gmc did. */
+ for (i = 0; i < G_N_ELEMENTS (commands); i++) {
+ command = try_terminal_command (commands[i][0],
+ commands[i][for_command ? 1 : 2]);
+ if (command != NULL) {
+ break;
+ }
+ }
+
+ return command;
+}
+
+static char *
+gul_general_gnome_make_terminal_command (const char *command)
+{
+ char *prefix, *quoted, *terminal_command;
+
+ if (command == NULL) {
+ return get_terminal_command_prefix (FALSE);
+ }
+ prefix = get_terminal_command_prefix (TRUE);
+ quoted = g_shell_quote (command);
+ terminal_command = g_strconcat (prefix, " /bin/sh -c ", quoted, NULL);
+ g_free (prefix);
+ g_free (quoted);
+ return terminal_command;
+}
+
+static void
+gul_general_gnome_open_terminal (const char *command)
+{
+ char *command_line;
+
+ command_line = gul_general_gnome_make_terminal_command (command);
+ if (command_line == NULL) {
+ g_message ("Could not start a terminal");
+ return;
+ }
+ gul_general_gnome_shell_execute (command_line);
+ g_free (command_line);
+}
+
+void
+ephy_file_launch_application (const char *command_string,
+ const char *parameter,
+ gboolean use_terminal)
+{
+ char *full_command;
+ char *quoted_parameter;
+
+ if (parameter != NULL) {
+ quoted_parameter = g_shell_quote (parameter);
+ full_command = g_strconcat (command_string, " ", quoted_parameter, NULL);
+ g_free (quoted_parameter);
+ } else {
+ full_command = g_strdup (command_string);
+ }
+
+ if (use_terminal) {
+ gul_general_gnome_open_terminal (full_command);
+ } else {
+ gul_general_gnome_shell_execute (full_command);
+ }
+
+ g_free (full_command);
+}
+
+void
+ephy_ensure_dir_exists (const char *dir)
+{
+ if (g_file_test (dir, G_FILE_TEST_IS_DIR) == FALSE)
+ {
+ if (g_file_test (dir, G_FILE_TEST_EXISTS) == TRUE)
+ g_error (_("%s exists, please move it out of the way."), dir);
+
+ if (mkdir (dir, 488) != 0)
+ g_error (_("Failed to create directory %s."), dir);
+ }
+}
diff --git a/lib/ephy-file-helpers.h b/lib/ephy-file-helpers.h
new file mode 100644
index 000000000..75f6a0e9e
--- /dev/null
+++ b/lib/ephy-file-helpers.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2002 Jorn Baayen
+ *
+ * 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.
+ *
+ * $Id$
+ */
+
+#ifndef EPHY_FILE_HELPERS_H
+#define EPHY_FILE_HELPERS_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+const char *ephy_file (const char *filename);
+
+const char *ephy_dot_dir (void);
+
+void ephy_file_helpers_init (void);
+
+void ephy_file_helpers_shutdown (void);
+
+void ephy_file_launch_application (const char *command_string,
+ const char *parameter,
+ gboolean use_terminal);
+
+char *ephy_file_tmp_filename (const char *base,
+ const char *extension);
+
+void ephy_ensure_dir_exists (const char *dir);
+
+
+G_END_DECLS
+
+#endif /* EPHY_FILE_HELPERS_H */
diff --git a/lib/ephy-filesystem-autocompletion.c b/lib/ephy-filesystem-autocompletion.c
new file mode 100644
index 000000000..8b1cb84fb
--- /dev/null
+++ b/lib/ephy-filesystem-autocompletion.c
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "ephy-autocompletion-source.h"
+#include "ephy-filesystem-autocompletion.h"
+#include "ephy-gobject-misc.h"
+#include <string.h>
+
+#include <libgnomevfs/gnome-vfs-async-ops.h>
+
+#define NOT_IMPLEMENTED g_warning ("not implemented: " G_STRLOC);
+
+//#define DEBUG_MSG(x) g_print x
+#define DEBUG_MSG(x)
+
+/**
+ * Private data
+ */
+struct _EphyFilesystemAutocompletionPrivate {
+ gchar *current_dir;
+ gchar *base_dir;
+ GnomeVFSURI *base_dir_uri;
+ gchar *basic_key;
+ gchar *basic_key_dir;
+ GSList *files;
+
+ guint score;
+ GnomeVFSAsyncHandle *load_handle;
+};
+
+/**
+ * Private functions, only availble from this file
+ */
+static void ephy_filesystem_autocompletion_class_init (EphyFilesystemAutocompletionClass *klass);
+static void ephy_filesystem_autocompletion_init (EphyFilesystemAutocompletion *as);
+static void ephy_filesystem_autocompletion_finalize_impl (GObject *o);
+static void ephy_filesystem_autocompletion_autocompletion_source_init (EphyAutocompletionSourceIface *iface);
+static void ephy_filesystem_autocompletion_autocompletion_source_foreach (EphyAutocompletionSource *source,
+ const gchar *current_text,
+ EphyAutocompletionSourceForeachFunc func,
+ gpointer data);
+void ephy_filesystem_autocompletion_autocompletion_source_set_basic_key (EphyAutocompletionSource *source,
+ const gchar *basic_key);
+static void ephy_filesystem_autocompletion_emit_autocompletion_source_data_changed (EphyFilesystemAutocompletion *gh);
+static void ephy_filesystem_autocompletion_set_current_dir (EphyFilesystemAutocompletion *fa, const gchar *d);
+
+
+static gpointer g_object_class;
+
+/**
+ * FilesystemAutocompletion object
+ */
+MAKE_GET_TYPE_IFACE (ephy_filesystem_autocompletion, "EphyFilesystemAutocompletion", EphyFilesystemAutocompletion,
+ ephy_filesystem_autocompletion_class_init, ephy_filesystem_autocompletion_init, G_TYPE_OBJECT,
+ ephy_filesystem_autocompletion_autocompletion_source_init, EPHY_TYPE_AUTOCOMPLETION_SOURCE);
+
+static void
+ephy_filesystem_autocompletion_autocompletion_source_init (EphyAutocompletionSourceIface *iface)
+{
+ iface->foreach = ephy_filesystem_autocompletion_autocompletion_source_foreach;
+ iface->set_basic_key = ephy_filesystem_autocompletion_autocompletion_source_set_basic_key;
+}
+
+static void
+ephy_filesystem_autocompletion_class_init (EphyFilesystemAutocompletionClass *klass)
+{
+ G_OBJECT_CLASS (klass)->finalize = ephy_filesystem_autocompletion_finalize_impl;
+
+ g_object_class = g_type_class_peek_parent (klass);
+}
+
+static void
+ephy_filesystem_autocompletion_init (EphyFilesystemAutocompletion *e)
+{
+ EphyFilesystemAutocompletionPrivate *p = g_new0 (EphyFilesystemAutocompletionPrivate, 1);
+ e->priv = p;
+
+ p->score = G_MAXINT / 2;
+ p->base_dir = g_strdup ("");
+}
+
+static void
+ephy_filesystem_autocompletion_finalize_impl (GObject *o)
+{
+ EphyFilesystemAutocompletion *as = GUL_FILESYSTEM_AUTOCOMPLETION (o);
+ EphyFilesystemAutocompletionPrivate *p = as->priv;
+
+ DEBUG_MSG (("in ephy_filesystem_autocompletion_finalize_impl\n"));
+
+ g_free (p->basic_key);
+ g_free (p->basic_key_dir);
+ g_free (p->current_dir);
+ g_free (p->base_dir);
+ if (p->base_dir_uri)
+ {
+ gnome_vfs_uri_unref (p->base_dir_uri);
+ }
+
+ g_free (p);
+
+ G_OBJECT_CLASS (g_object_class)->finalize (o);
+}
+
+EphyFilesystemAutocompletion *
+ephy_filesystem_autocompletion_new (void)
+{
+ EphyFilesystemAutocompletion *ret = g_object_new (GUL_TYPE_FILESYSTEM_AUTOCOMPLETION, NULL);
+ return ret;
+}
+
+
+static gchar *
+gfa_get_nearest_dir (const gchar *path)
+{
+ gchar *ret;
+ const gchar *lastslash = rindex (path, '/');
+
+ if (lastslash)
+ {
+ if (!strcmp (path, "file://"))
+ {
+ /* without this, gnome-vfs does not recognize it as a dir */
+ ret = g_strdup ("file:///");
+ }
+ else
+ {
+ ret = g_strndup (path, lastslash - path + 1);
+ }
+ }
+ else
+ {
+ ret = g_strdup ("");
+ }
+
+ return ret;
+}
+
+static void
+ephy_filesystem_autocompletion_autocompletion_source_foreach (EphyAutocompletionSource *source,
+ const gchar *basic_key,
+ EphyAutocompletionSourceForeachFunc func,
+ gpointer data)
+{
+ EphyFilesystemAutocompletion *fa = GUL_FILESYSTEM_AUTOCOMPLETION (source);
+ EphyFilesystemAutocompletionPrivate *p = fa->priv;
+ GSList *li;
+
+ ephy_filesystem_autocompletion_autocompletion_source_set_basic_key (source, basic_key);
+
+ for (li = p->files; li; li = li->next)
+ {
+ func (source, li->data, li->data, li->data, FALSE, FALSE, p->score, data);
+ }
+
+}
+
+static void
+ephy_filesystem_autocompletion_emit_autocompletion_source_data_changed (EphyFilesystemAutocompletion *fa)
+{
+ g_signal_emit_by_name (fa, "data-changed");
+}
+
+static void
+gfa_load_directory_cb (GnomeVFSAsyncHandle *handle,
+ GnomeVFSResult result,
+ GList *list,
+ guint entries_read,
+ gpointer callback_data)
+{
+ EphyFilesystemAutocompletion *fa = callback_data;
+ EphyFilesystemAutocompletionPrivate *p = fa->priv;
+ GList *li;
+ gchar *cd;
+
+ g_return_if_fail (p->load_handle == handle);
+
+ DEBUG_MSG (("gfa_load_directory_cb, entries_read == %d\n", entries_read));
+
+ if (entries_read <= 0)
+ {
+ return;
+ }
+
+ if (p->basic_key_dir[strlen (p->basic_key_dir) - 1] == G_DIR_SEPARATOR
+ || p->basic_key_dir[0] == '\0')
+ {
+ cd = g_strdup (p->basic_key_dir);
+ }
+ else
+ {
+ cd = g_strconcat (p->basic_key_dir, G_DIR_SEPARATOR_S, NULL);
+ }
+
+ for (li = list; li; li = li->next)
+ {
+ GnomeVFSFileInfo *i = li->data;
+ if (!(i->name[0] == '.'
+ && (i->name[1] == '\0'
+ || (i->name[1] == '.'
+ && i->name[2] == '\0'))))
+ {
+ gchar *f = g_strconcat (cd, i->name, NULL);
+ p->files = g_slist_prepend (p->files, f);
+
+ DEBUG_MSG (("+ %s\n", f));
+ }
+ }
+
+ g_free (cd);
+
+ ephy_filesystem_autocompletion_emit_autocompletion_source_data_changed (fa);
+}
+
+static void
+ephy_filesystem_autocompletion_set_current_dir (EphyFilesystemAutocompletion *fa, const gchar *d)
+{
+ EphyFilesystemAutocompletionPrivate *p = fa->priv;
+ GnomeVFSURI *cd_uri;
+
+ if (p->base_dir_uri)
+ {
+ cd_uri = gnome_vfs_uri_append_path (p->base_dir_uri, d);
+ }
+ else
+ {
+ cd_uri = gnome_vfs_uri_new (d);
+ }
+
+ if (p->load_handle)
+ {
+ gnome_vfs_async_cancel (p->load_handle);
+ p->load_handle = NULL;
+ }
+
+ if (p->files)
+ {
+ g_slist_foreach (p->files, (GFunc) g_free, NULL);
+ g_slist_free (p->files);
+ p->files = NULL;
+
+ ephy_filesystem_autocompletion_emit_autocompletion_source_data_changed (fa);
+ }
+
+ if (!cd_uri)
+ {
+ DEBUG_MSG (("Can't load dir %s\n", d));
+ return;
+ }
+
+ g_free (p->current_dir);
+ p->current_dir = gnome_vfs_uri_to_string (cd_uri, GNOME_VFS_URI_HIDE_NONE);
+
+ DEBUG_MSG (("Loading dir: %s\n", p->current_dir));
+
+ gnome_vfs_async_load_directory_uri (&p->load_handle,
+ cd_uri,
+ GNOME_VFS_FILE_INFO_DEFAULT,
+ 100,
+ 0,
+ gfa_load_directory_cb,
+ fa);
+
+ gnome_vfs_uri_unref (cd_uri);
+}
+
+void
+ephy_filesystem_autocompletion_autocompletion_source_set_basic_key (EphyAutocompletionSource *source,
+ const gchar *basic_key)
+{
+ EphyFilesystemAutocompletion *fa = GUL_FILESYSTEM_AUTOCOMPLETION (source);
+ EphyFilesystemAutocompletionPrivate *p = fa->priv;
+ gchar *new_basic_key_dir;
+
+ if (p->basic_key && !strcmp (p->basic_key, basic_key))
+ {
+ return;
+ }
+
+ g_free (p->basic_key);
+ p->basic_key = g_strdup (basic_key);
+
+ new_basic_key_dir = gfa_get_nearest_dir (basic_key);
+ if (p->basic_key_dir && !strcmp (p->basic_key_dir, new_basic_key_dir))
+ {
+ g_free (new_basic_key_dir);
+ }
+ else
+ {
+ g_free (p->basic_key_dir);
+ p->basic_key_dir = new_basic_key_dir;
+ ephy_filesystem_autocompletion_set_current_dir (fa, p->basic_key_dir);
+ }
+}
+
+void
+ephy_filesystem_autocompletion_set_base_dir (EphyFilesystemAutocompletion *fa, const gchar *d)
+{
+ EphyFilesystemAutocompletionPrivate *p = fa->priv;
+
+ g_free (p->base_dir);
+ p->base_dir = g_strdup (d);
+
+ if (p->base_dir_uri)
+ {
+ gnome_vfs_uri_unref (p->base_dir_uri);
+ }
+
+ if (p->base_dir[0])
+ {
+ p->base_dir_uri = gnome_vfs_uri_new (p->base_dir);
+ }
+ else
+ {
+ p->base_dir_uri = NULL;
+ }
+
+ if (p->base_dir_uri)
+ {
+ gchar *t = gnome_vfs_uri_to_string (p->base_dir_uri, GNOME_VFS_URI_HIDE_NONE);
+ DEBUG_MSG (("base_dir: %s\n", t));
+ g_free (t);
+ }
+}
+
diff --git a/lib/ephy-filesystem-autocompletion.h b/lib/ephy-filesystem-autocompletion.h
new file mode 100644
index 000000000..ec047282f
--- /dev/null
+++ b/lib/ephy-filesystem-autocompletion.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifndef EPHY_FILESYSTEM_AUTOCOMPLETION_H
+#define EPHY_FILESYSTEM_AUTOCOMPLETION_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/* object forward declarations */
+
+typedef struct _EphyFilesystemAutocompletion EphyFilesystemAutocompletion;
+typedef struct _EphyFilesystemAutocompletionClass EphyFilesystemAutocompletionClass;
+typedef struct _EphyFilesystemAutocompletionPrivate EphyFilesystemAutocompletionPrivate;
+
+/**
+ * FilesystemAutocompletion object
+ */
+
+#define GUL_TYPE_FILESYSTEM_AUTOCOMPLETION (ephy_filesystem_autocompletion_get_type())
+#define GUL_FILESYSTEM_AUTOCOMPLETION(object) (G_TYPE_CHECK_INSTANCE_CAST((object), \
+ GUL_TYPE_FILESYSTEM_AUTOCOMPLETION,\
+ EphyFilesystemAutocompletion))
+#define GUL_FILESYSTEM_AUTOCOMPLETION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GUL_TYPE_FILESYSTEM_AUTOCOMPLETION,\
+ EphyFilesystemAutocompletionClass))
+#define GUL_IS_FILESYSTEM_AUTOCOMPLETION(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), \
+ GUL_TYPE_FILESYSTEM_AUTOCOMPLETION))
+#define GUL_IS_FILESYSTEM_AUTOCOMPLETION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ GUL_TYPE_FILESYSTEM_AUTOCOMPLETION))
+#define GUL_FILESYSTEM_AUTOCOMPLETION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \
+ GUL_TYPE_FILESYSTEM_AUTOCOMPLETION,\
+ EphyFilesystemAutocompletionClass))
+
+struct _EphyFilesystemAutocompletionClass
+{
+ GObjectClass parent_class;
+
+};
+
+struct _EphyFilesystemAutocompletion
+{
+ GObject parent_object;
+ EphyFilesystemAutocompletionPrivate *priv;
+};
+
+GType ephy_filesystem_autocompletion_get_type (void);
+EphyFilesystemAutocompletion * ephy_filesystem_autocompletion_new (void);
+void ephy_filesystem_autocompletion_set_base_dir (EphyFilesystemAutocompletion *fa,
+ const gchar *d);
+
+G_END_DECLS
+
+#endif
diff --git a/lib/ephy-glade.c b/lib/ephy-glade.c
new file mode 100644
index 000000000..beb91827f
--- /dev/null
+++ b/lib/ephy-glade.c
@@ -0,0 +1,134 @@
+/*
+ * 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.
+ */
+
+#include "ephy-glade.h"
+#include "ephy-file-helpers.h"
+
+#include <glade/glade-xml.h>
+#include <gtk/gtkmenu.h>
+#include <gmodule.h>
+
+static void
+glade_signal_connect_func (const gchar *cb_name, GObject *obj,
+ const gchar *signal_name, const gchar *signal_data,
+ GObject *conn_obj, gboolean conn_after,
+ gpointer user_data);
+
+/**
+ * ephy_widget_new: build a new widget of the provided name, with all
+ * signals attached and data set to the provided parameter.
+ */
+GladeXML *
+ephy_glade_widget_new (const char *file, const char *widget_name,
+ GtkWidget **root, gpointer data)
+{
+ GladeXML *gxml;
+ const char *glade_file;
+
+ glade_file = ephy_file (file);
+ g_return_val_if_fail (glade_file != NULL, NULL);
+
+ /* build the widget */
+ /* note that libglade automatically caches the parsed file,
+ * so we don't need to worry about the efficiency of this */
+ gxml = glade_xml_new (glade_file, widget_name, NULL);
+ g_return_val_if_fail (gxml != NULL, NULL);
+
+ /* lookup the root widget if requested */
+ if (root != NULL)
+ {
+ *root = glade_xml_get_widget (gxml, widget_name);
+ }
+
+ /* connect signals and data */
+ glade_xml_signal_autoconnect_full
+ (gxml, (GladeXMLConnectFunc)glade_signal_connect_func, data);
+
+ /* return xml document for subsequent widget lookups */
+ return gxml;
+}
+
+/*
+ * glade_signal_connect_func: used by glade_xml_signal_autoconnect_full
+ */
+static void
+glade_signal_connect_func (const gchar *cb_name, GObject *obj,
+ const gchar *signal_name, const gchar *signal_data,
+ GObject *conn_obj, gboolean conn_after,
+ gpointer user_data)
+{
+ /** Module with all the symbols of the program */
+ static GModule *mod_self = NULL;
+ gpointer handler_func;
+
+ /* initialize gmodule */
+ if (mod_self == NULL)
+ {
+ mod_self = g_module_open (NULL, 0);
+ g_assert (mod_self != NULL);
+ }
+
+ /*g_print( "glade_signal_connect_func: cb_name = '%s', signal_name = '%s', signal_data = '%s'\n",
+ cb_name, signal_name, signal_data ); */
+
+ if (g_module_symbol (mod_self, cb_name, &handler_func))
+ {
+ /* found callback */
+ if (conn_obj)
+ {
+ if (conn_after)
+ {
+ g_signal_connect_object
+ (obj, signal_name,
+ handler_func, conn_obj,
+ G_CONNECT_AFTER);
+ }
+ else
+ {
+ g_signal_connect_object
+ (obj, signal_name,
+ handler_func, conn_obj,
+ G_CONNECT_SWAPPED);
+ }
+ }
+ else
+ {
+ /* no conn_obj; use standard connect */
+ gpointer data = NULL;
+
+ data = user_data;
+
+ if (conn_after)
+ {
+ g_signal_connect_after
+ (obj, signal_name,
+ handler_func, data);
+ }
+ else
+ {
+ g_signal_connect
+ (obj, signal_name,
+ handler_func, data);
+ }
+ }
+ }
+ else
+ {
+ g_warning("callback function not found: %s", cb_name);
+ }
+}
diff --git a/lib/ephy-glade.h b/lib/ephy-glade.h
new file mode 100644
index 000000000..cccf22f24
--- /dev/null
+++ b/lib/ephy-glade.h
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+#ifndef EPHY_GLADE_H
+#define EPHY_GLADE_H
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <glade/glade-xml.h>
+
+typedef struct
+{
+ const gchar *name;
+ GtkWidget **ptr;
+} WidgetLookup;
+
+G_BEGIN_DECLS
+
+GladeXML *ephy_glade_widget_new (const char *file,
+ const char *widget_name,
+ GtkWidget **root,
+ gpointer data);
+
+G_END_DECLS
+
+#endif
diff --git a/lib/ephy-gobject-misc.h b/lib/ephy-gobject-misc.h
new file mode 100644
index 000000000..c14ef1ae7
--- /dev/null
+++ b/lib/ephy-gobject-misc.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#define MAKE_GET_TYPE(l,str,t,ci,i,parent) \
+GType l##_get_type(void)\
+{\
+ static GType type = 0; \
+ if (!type) { \
+ static GTypeInfo const object_info = { \
+ sizeof (t##Class), \
+ \
+ (GBaseInitFunc) NULL, \
+ (GBaseFinalizeFunc) NULL, \
+ \
+ (GClassInitFunc) ci, \
+ (GClassFinalizeFunc) NULL, \
+ NULL, /* class_data */ \
+ \
+ sizeof (t), \
+ 0, /* n_preallocs */ \
+ (GInstanceInitFunc) i, \
+ }; \
+ type = g_type_register_static (parent, str, &object_info, 0); \
+ } \
+ return type; \
+}
+
+#define MAKE_GET_TYPE_IFACE(l,str,t,ci,i,parent,ii,iparent) \
+GType l##_get_type(void)\
+{\
+ static GType type = 0; \
+ if (!type) { \
+ static GTypeInfo const object_info = { \
+ sizeof (t##Class), \
+ \
+ (GBaseInitFunc) NULL, \
+ (GBaseFinalizeFunc) NULL, \
+ \
+ (GClassInitFunc) ci, \
+ (GClassFinalizeFunc) NULL, \
+ NULL, /* class_data */ \
+ \
+ sizeof (t), \
+ 0, /* n_preallocs */ \
+ (GInstanceInitFunc) i, \
+ }; \
+ \
+ static const GInterfaceInfo iface_info = { \
+ (GInterfaceInitFunc) ii, \
+ NULL, \
+ NULL \
+ }; \
+ \
+ type = g_type_register_static (parent, str, &object_info, (GTypeFlags)0); \
+ \
+ g_type_add_interface_static (type, \
+ iparent, \
+ &iface_info); \
+ } \
+ return type; \
+}
+
diff --git a/lib/ephy-gui.c b/lib/ephy-gui.c
new file mode 100644
index 000000000..fe4018d38
--- /dev/null
+++ b/lib/ephy-gui.c
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2002 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.
+ */
+
+#include "ephy-gui.h"
+#include "eel-gconf-extensions.h"
+
+#include <ctype.h>
+#include <string.h>
+#include <libgnome/gnome-i18n.h>
+#include <gtk/gtktreemodel.h>
+
+/* Styles for tab labels */
+GtkStyle *loading_text_style = NULL;
+GtkStyle *new_text_style = NULL;
+
+/**
+ * gul_gui_menu_position_under_widget:
+ */
+void
+ephy_gui_menu_position_under_widget (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ gpointer user_data)
+{
+ GtkWidget *w = GTK_WIDGET (user_data);
+ gint width, height;
+ gint screen_width, screen_height;
+ GtkRequisition requisition;
+
+ gdk_drawable_get_size (w->window, &width, &height);
+ gdk_window_get_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));
+}
+
+/**
+ * gul_gui_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
+ephy_gui_gtk_radio_button_get (GtkRadioButton *radio_button)
+{
+ GtkToggleButton *toggle_button;
+ gint i, length;
+ GSList *list;
+
+ /* get group list */
+ list = gtk_radio_button_get_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;
+}
+
+/**
+ * gul_gui_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
+ephy_gui_gtk_radio_button_set (GtkRadioButton *radio_button, gint index)
+{
+ GtkToggleButton *button;
+ GSList *list;
+ gint length;
+
+ /* get the list */
+ list = gtk_radio_button_get_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);
+ }
+}
+
+GtkWidget *
+ephy_gui_append_new_menuitem (GtkWidget *menu,
+ const char *mnemonic,
+ GCallback callback,
+ gpointer data)
+{
+ GtkWidget *menu_item;
+
+ menu_item = gtk_menu_item_new_with_mnemonic (mnemonic);
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu),
+ menu_item);
+
+ if (callback)
+ {
+ g_signal_connect (G_OBJECT (menu_item),
+ "activate",
+ callback, data);
+ }
+
+ return menu_item;
+}
+
+GtkWidget *
+ephy_gui_append_new_menuitem_stock (GtkWidget *menu,
+ const char *stock_id,
+ GCallback callback,
+ gpointer data)
+{
+ GtkWidget *menu_item;
+
+ menu_item = gtk_image_menu_item_new_from_stock (stock_id, NULL);
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu),
+ menu_item);
+
+ if (callback)
+ {
+ g_signal_connect (G_OBJECT (menu_item),
+ "activate",
+ callback, data);
+ }
+
+ return menu_item;
+}
+
+GtkWidget *
+ephy_gui_append_new_menuitem_stock_icon (GtkWidget *menu,
+ const char *stock_id,
+ const char *mnemonic,
+ GCallback callback,
+ gpointer data)
+{
+ GtkWidget *menu_item;
+ GtkWidget *image;
+
+ menu_item = gtk_image_menu_item_new_with_mnemonic (mnemonic);
+ image = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_MENU);
+ gtk_widget_show (image);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), image);
+
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu),
+ menu_item);
+
+ if (callback)
+ {
+ g_signal_connect (G_OBJECT (menu_item),
+ "activate",
+ callback, data);
+ }
+
+ return menu_item;
+}
+
+GtkWidget *
+ephy_gui_append_new_check_menuitem (GtkWidget *menu,
+ const char *mnemonic,
+ gboolean value,
+ GCallback callback,
+ gpointer data)
+{
+ GtkWidget *menu_item;
+
+ menu_item = gtk_check_menu_item_new_with_mnemonic (mnemonic);
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu),
+ menu_item);
+
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), value);
+
+ if (callback)
+ {
+ g_signal_connect (G_OBJECT (menu_item),
+ "activate",
+ callback, data);
+ }
+
+ return menu_item;
+}
+
+GtkWidget *
+ephy_gui_append_separator (GtkWidget *menu)
+{
+ GtkWidget *menu_item;
+
+ menu_item = gtk_menu_item_new ();
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu),
+ menu_item);
+
+ return menu_item;
+}
+
+gboolean
+ephy_gui_confirm_overwrite_file (GtkWidget *parent, const char *filename)
+{
+ char *question;
+ GtkWidget *dialog;
+ gboolean res;
+
+ if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+ {
+ return TRUE;
+ }
+
+ question = g_strdup_printf (_("File %s will be overwritten.\n"
+ "If you choose yes, the contents will be lost.\n\n"
+ "Do you want to continue?"), filename);
+ dialog = gtk_message_dialog_new (parent ? GTK_WINDOW(parent) : NULL,
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_YES_NO,
+ question);
+ res = (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_YES);
+ gtk_widget_destroy (dialog);
+ g_free (question);
+
+ return res;
+}
+
+static guint32
+shift_color_component (guchar component, float shift_by)
+{
+ guint32 result;
+ if (shift_by > 1.0) {
+ result = component * (2 - shift_by);
+ } else {
+ result = 0xff - shift_by * (0xff - component);
+ }
+
+ return result & 0xff;
+}
+
+/**
+ * ephy_gui_rgb_shift_color
+ * @color: A color.
+ * @shift_by: darken or lighten factor.
+ * Returns: An darkened or lightened rgb value.
+ *
+ * Darkens (@shift_by > 1) or lightens (@shift_by < 1)
+ * @color.
+ */
+guint32
+ephy_gui_rgb_shift_color (guint32 color, float shift_by)
+{
+ guint32 result;
+
+ /* shift red by shift_by */
+ result = shift_color_component((color & 0x00ff0000) >> 16, shift_by);
+ result <<= 8;
+ /* shift green by shift_by */
+ result |= shift_color_component((color & 0x0000ff00) >> 8, shift_by);
+ result <<= 8;
+ /* shift blue by shift_by */
+ result |= shift_color_component((color & 0x000000ff), shift_by);
+
+ /* alpha doesn't change */
+ result |= (0xff000000 & color);
+
+ return result;
+}
+
+static guint32
+rgb16_to_rgb (gushort r, gushort g, gushort b)
+{
+ guint32 result;
+
+ result = (0xff0000 | (r & 0xff00));
+ result <<= 8;
+ result |= ((g & 0xff00) | (b >> 8));
+
+ return result;
+}
+
+/**
+ * ephy_gui_gdk_color_to_rgb
+ * @color: A GdkColor style color.
+ * Returns: An rgb value.
+ *
+ * Converts from a GdkColor stlye color to a gdk_rgb one.
+ * Alpha gets set to fully opaque
+ */
+guint32
+ephy_gui_gdk_color_to_rgb (const GdkColor *color)
+{
+ return rgb16_to_rgb (color->red, color->green, color->blue);
+}
+
+/**
+ * ephy_gui_rgb_to_color
+ * @color: a gdk_rgb style value.
+ *
+ * Converts from a gdk_rgb value style to a GdkColor one.
+ * The gdk_rgb color alpha channel is ignored.
+ *
+ * Return value: A GdkColor structure version of the given RGB color.
+ */
+GdkColor
+ephy_gui_gdk_rgb_to_color (guint32 color)
+{
+ GdkColor result;
+
+ result.red = ((color >> 16) & 0xFF) * 0x101;
+ result.green = ((color >> 8) & 0xFF) * 0x101;
+ result.blue = (color & 0xff) * 0x101;
+ result.pixel = 0;
+
+ return result;
+}
diff --git a/lib/ephy-gui.h b/lib/ephy-gui.h
new file mode 100644
index 000000000..0d736592f
--- /dev/null
+++ b/lib/ephy-gui.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2002 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.
+ */
+
+#ifndef EPHY_GUI_H
+#define EPHY_GUI_H
+
+/* system includes */
+#include <gtk/gtk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <libgnomeui/gnome-dialog.h>
+#include <gnome.h>
+
+G_BEGIN_DECLS
+
+void ephy_gui_menu_position_under_widget (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ gpointer user_data);
+
+gint ephy_gui_gtk_radio_button_get (GtkRadioButton *radio_button);
+
+void ephy_gui_gtk_radio_button_set (GtkRadioButton *radio_button,
+ gint index);
+
+GList *ephy_gui_treeview_get_selection_refs (GtkTreeView *treeview);
+
+GtkWidget *ephy_gui_append_new_menuitem (GtkWidget *menu,
+ const char *mnemonic,
+ GCallback callback,
+ gpointer data);
+
+GtkWidget *ephy_gui_append_new_menuitem_stock (GtkWidget *menu,
+ const char *stock_id,
+ GCallback callback,
+ gpointer data);
+
+GtkWidget *ephy_gui_append_new_menuitem_stock_icon (GtkWidget *menu,
+ const char *stock_id,
+ const char *mnemonic,
+ GCallback callback,
+ gpointer data);
+
+GtkWidget *ephy_gui_append_new_check_menuitem (GtkWidget *menu,
+ const char *mnemonic,
+ gboolean value,
+ GCallback callback,
+ gpointer data);
+
+GtkWidget *ephy_gui_append_separator (GtkWidget *menu);
+
+gboolean ephy_gui_confirm_overwrite_file (GtkWidget *parent,
+ const char *filename);
+
+guint32 ephy_gui_rgb_shift_color (guint32 color,
+ float shift_by);
+
+guint32 ephy_gui_gdk_color_to_rgb (const GdkColor *color);
+
+GdkColor ephy_gui_gdk_rgb_to_color (guint32 color);
+
+G_END_DECLS
+
+#endif
diff --git a/lib/ephy-marshal.list b/lib/ephy-marshal.list
new file mode 100644
index 000000000..9082cae25
--- /dev/null
+++ b/lib/ephy-marshal.list
@@ -0,0 +1,16 @@
+VOID:VOID
+VOID:STRING
+VOID:OBJECT
+VOID:OBJECT,OBJECT,INT
+VOID:OBJECT,STRING,INT
+VOID:OBJECT,INT
+VOID:OBJECT,INT,INT
+VOID:POINTER,INT
+VOID:STRING,INT,INT
+VOID:STRING,INT
+VOID:STRING,STRING
+INT:STRING
+VOID:INT,INT
+VOID:INT,INT,INT
+INT:OBJECT
+VOID:POINTER,POINTER
diff --git a/lib/ephy-node-filter.c b/lib/ephy-node-filter.c
new file mode 100644
index 000000000..8f0bddbc0
--- /dev/null
+++ b/lib/ephy-node-filter.c
@@ -0,0 +1,461 @@
+/*
+ * Copyright (C) 2002 Olivier Martin <omartin@ifrance.com>
+ * (C) 2002 Jorn Baayen <jorn@nl.linux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Id$
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "ephy-node-filter.h"
+
+static void ephy_node_filter_class_init (EphyNodeFilterClass *klass);
+static void ephy_node_filter_init (EphyNodeFilter *node);
+static void ephy_node_filter_finalize (GObject *object);
+static gboolean ephy_node_filter_expression_evaluate (EphyNodeFilterExpression *expression,
+ EphyNode *node);
+
+enum
+{
+ CHANGED,
+ LAST_SIGNAL
+};
+
+struct EphyNodeFilterPrivate
+{
+ GPtrArray *levels;
+};
+
+struct EphyNodeFilterExpression
+{
+ EphyNodeFilterExpressionType type;
+
+ union
+ {
+ struct
+ {
+ EphyNode *a;
+ EphyNode *b;
+ } node_args;
+
+ struct
+ {
+ int prop_id;
+
+ union
+ {
+ EphyNode *node;
+ char *string;
+ int number;
+ } second_arg;
+ } prop_args;
+ } args;
+};
+
+static GObjectClass *parent_class = NULL;
+
+static guint ephy_node_filter_signals[LAST_SIGNAL] = { 0 };
+
+GType
+ephy_node_filter_get_type (void)
+{
+ static GType ephy_node_filter_type = 0;
+
+ if (ephy_node_filter_type == 0)
+ {
+ static const GTypeInfo our_info =
+ {
+ sizeof (EphyNodeFilterClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) ephy_node_filter_class_init,
+ NULL,
+ NULL,
+ sizeof (EphyNodeFilter),
+ 0,
+ (GInstanceInitFunc) ephy_node_filter_init
+ };
+
+ ephy_node_filter_type = g_type_register_static (G_TYPE_OBJECT,
+ "EphyNodeFilter",
+ &our_info, 0);
+ }
+
+ return ephy_node_filter_type;
+}
+
+static void
+ephy_node_filter_class_init (EphyNodeFilterClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->finalize = ephy_node_filter_finalize;
+
+ ephy_node_filter_signals[CHANGED] =
+ g_signal_new ("changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EphyNodeFilterClass, changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+static void
+ephy_node_filter_init (EphyNodeFilter *filter)
+{
+ filter->priv = g_new0 (EphyNodeFilterPrivate, 1);
+
+ filter->priv->levels = g_ptr_array_new ();
+}
+
+static void
+ephy_node_filter_finalize (GObject *object)
+{
+ EphyNodeFilter *filter;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (EPHY_IS_NODE_FILTER (object));
+
+ filter = EPHY_NODE_FILTER (object);
+
+ g_return_if_fail (filter->priv != NULL);
+
+ ephy_node_filter_empty (filter);
+
+ g_ptr_array_free (filter->priv->levels, FALSE);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+EphyNodeFilter *
+ephy_node_filter_new (void)
+{
+ EphyNodeFilter *filter;
+
+ filter = EPHY_NODE_FILTER (g_object_new (EPHY_TYPE_NODE_FILTER,
+ NULL));
+
+ g_return_val_if_fail (filter->priv != NULL, NULL);
+
+ return filter;
+}
+
+void
+ephy_node_filter_add_expression (EphyNodeFilter *filter,
+ EphyNodeFilterExpression *exp,
+ int level)
+{
+ while (level >= filter->priv->levels->len)
+ g_ptr_array_add (filter->priv->levels, NULL);
+
+ g_ptr_array_index (filter->priv->levels, level) =
+ g_list_append (g_ptr_array_index (filter->priv->levels, level), exp);
+}
+
+void
+ephy_node_filter_empty (EphyNodeFilter *filter)
+{
+ int i;
+
+ for (i = filter->priv->levels->len - 1; i >= 0; i--)
+ {
+ GList *list, *l;
+
+ list = g_ptr_array_index (filter->priv->levels, i);
+
+ for (l = list; l != NULL; l = g_list_next (l))
+ {
+ EphyNodeFilterExpression *exp;
+
+ exp = (EphyNodeFilterExpression *) l->data;
+
+ ephy_node_filter_expression_free (exp);
+ }
+
+ g_list_free (list);
+
+ g_ptr_array_remove_index (filter->priv->levels, i);
+ }
+}
+
+void
+ephy_node_filter_done_changing (EphyNodeFilter *filter)
+{
+ g_signal_emit (G_OBJECT (filter), ephy_node_filter_signals[CHANGED], 0);
+}
+
+/*
+ * We go through each level evaluating the filter expressions.
+ * Every time we get a match we immediately do a break and jump
+ * to the next level. We'll return FALSE if we arrive to a level
+ * without matches, TRUE otherwise.
+ */
+gboolean
+ephy_node_filter_evaluate (EphyNodeFilter *filter,
+ EphyNode *node)
+{
+ int i;
+
+ for (i = 0; i < filter->priv->levels->len; i++) {
+ GList *l, *list;
+ gboolean handled;
+
+ handled = FALSE;
+
+ list = g_ptr_array_index (filter->priv->levels, i);
+
+ for (l = list; l != NULL; l = g_list_next (l)) {
+ if (ephy_node_filter_expression_evaluate (l->data, node) == TRUE) {
+ handled = TRUE;
+ break;
+ }
+ }
+
+ if (handled == FALSE)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+EphyNodeFilterExpression *
+ephy_node_filter_expression_new (EphyNodeFilterExpressionType type,
+ ...)
+{
+ EphyNodeFilterExpression *exp;
+ va_list valist;
+
+ va_start (valist, type);
+
+ exp = g_new0 (EphyNodeFilterExpression, 1);
+
+ exp->type = type;
+
+ switch (type)
+ {
+ case EPHY_NODE_FILTER_EXPRESSION_NODE_EQUALS:
+ exp->args.node_args.a = va_arg (valist, EphyNode *);
+ exp->args.node_args.b = va_arg (valist, EphyNode *);
+ break;
+ case EPHY_NODE_FILTER_EXPRESSION_EQUALS:
+ case EPHY_NODE_FILTER_EXPRESSION_HAS_PARENT:
+ case EPHY_NODE_FILTER_EXPRESSION_HAS_CHILD:
+ exp->args.node_args.a = va_arg (valist, EphyNode *);
+ break;
+ case EPHY_NODE_FILTER_EXPRESSION_NODE_PROP_EQUALS:
+ case EPHY_NODE_FILTER_EXPRESSION_CHILD_PROP_EQUALS:
+ exp->args.prop_args.prop_id = va_arg (valist, int);
+ exp->args.prop_args.second_arg.node = va_arg (valist, EphyNode *);
+ break;
+ case EPHY_NODE_FILTER_EXPRESSION_STRING_PROP_CONTAINS:
+ case EPHY_NODE_FILTER_EXPRESSION_STRING_PROP_EQUALS:
+ exp->args.prop_args.prop_id = va_arg (valist, int);
+ exp->args.prop_args.second_arg.string = g_utf8_casefold (va_arg (valist, char *), -1);
+ break;
+ case EPHY_NODE_FILTER_EXPRESSION_KEY_PROP_CONTAINS:
+ case EPHY_NODE_FILTER_EXPRESSION_KEY_PROP_EQUALS:
+ {
+ char *folded;
+
+ exp->args.prop_args.prop_id = va_arg (valist, int);
+
+ folded = g_utf8_casefold (va_arg (valist, char *), -1);
+ exp->args.prop_args.second_arg.string = g_utf8_collate_key (folded, -1);
+ g_free (folded);
+ break;
+ }
+ case EPHY_NODE_FILTER_EXPRESSION_INT_PROP_EQUALS:
+ case EPHY_NODE_FILTER_EXPRESSION_INT_PROP_BIGGER_THAN:
+ case EPHY_NODE_FILTER_EXPRESSION_INT_PROP_LESS_THAN:
+ exp->args.prop_args.prop_id = va_arg (valist, int);
+ exp->args.prop_args.second_arg.number = va_arg (valist, int);
+ break;
+ default:
+ break;
+ }
+
+ va_end (valist);
+
+ return exp;
+}
+
+void
+ephy_node_filter_expression_free (EphyNodeFilterExpression *exp)
+{
+ switch (exp->type)
+ {
+ case EPHY_NODE_FILTER_EXPRESSION_STRING_PROP_CONTAINS:
+ case EPHY_NODE_FILTER_EXPRESSION_STRING_PROP_EQUALS:
+ case EPHY_NODE_FILTER_EXPRESSION_KEY_PROP_CONTAINS:
+ case EPHY_NODE_FILTER_EXPRESSION_KEY_PROP_EQUALS:
+ g_free (exp->args.prop_args.second_arg.string);
+ break;
+ default:
+ break;
+ }
+
+ g_free (exp);
+}
+
+static gboolean
+ephy_node_filter_expression_evaluate (EphyNodeFilterExpression *exp,
+ EphyNode *node)
+{
+ switch (exp->type)
+ {
+ case EPHY_NODE_FILTER_EXPRESSION_ALWAYS_TRUE:
+ return TRUE;
+ case EPHY_NODE_FILTER_EXPRESSION_NODE_EQUALS:
+ return (exp->args.node_args.a == exp->args.node_args.b);
+ case EPHY_NODE_FILTER_EXPRESSION_EQUALS:
+ return (exp->args.node_args.a == node);
+ case EPHY_NODE_FILTER_EXPRESSION_HAS_PARENT:
+ return ephy_node_has_child (exp->args.node_args.a, node);
+ case EPHY_NODE_FILTER_EXPRESSION_HAS_CHILD:
+ return ephy_node_has_child (node, exp->args.node_args.a);
+ case EPHY_NODE_FILTER_EXPRESSION_NODE_PROP_EQUALS:
+ {
+ EphyNode *prop;
+
+ prop = ephy_node_get_property_node (node,
+ exp->args.prop_args.prop_id);
+
+ return (prop == exp->args.prop_args.second_arg.node);
+ }
+ case EPHY_NODE_FILTER_EXPRESSION_CHILD_PROP_EQUALS:
+ {
+ EphyNode *prop;
+ GPtrArray *children;
+ int i;
+
+ children = ephy_node_get_children (node);
+ for (i = 0; i < children->len; i++)
+ {
+ EphyNode *child;
+
+ child = g_ptr_array_index (children, i);
+ prop = ephy_node_get_property_node
+ (child, exp->args.prop_args.prop_id);
+
+ if (prop == exp->args.prop_args.second_arg.node)
+ {
+ ephy_node_thaw (node);
+ return TRUE;
+ }
+ }
+
+ ephy_node_thaw (node);
+ return FALSE;
+ }
+ case EPHY_NODE_FILTER_EXPRESSION_STRING_PROP_CONTAINS:
+ {
+ const char *prop;
+ char *folded_case;
+ gboolean ret;
+
+ prop = ephy_node_get_property_string (node,
+ exp->args.prop_args.prop_id);
+ if (prop == NULL)
+ return FALSE;
+
+ folded_case = g_utf8_casefold (prop, -1);
+ ret = (strstr (folded_case, exp->args.prop_args.second_arg.string) != NULL);
+ g_free (folded_case);
+
+ return ret;
+ }
+ case EPHY_NODE_FILTER_EXPRESSION_STRING_PROP_EQUALS:
+ {
+ const char *prop;
+ char *folded_case;
+ gboolean ret;
+
+ prop = ephy_node_get_property_string (node,
+ exp->args.prop_args.prop_id);
+
+ if (prop == NULL)
+ return FALSE;
+
+ folded_case = g_utf8_casefold (prop, -1);
+ ret = (strcmp (folded_case, exp->args.prop_args.second_arg.string) == 0);
+ g_free (folded_case);
+
+ return ret;
+ }
+ case EPHY_NODE_FILTER_EXPRESSION_KEY_PROP_CONTAINS:
+ {
+ const char *prop;
+
+ prop = ephy_node_get_property_string (node,
+ exp->args.prop_args.prop_id);
+
+ if (prop == NULL)
+ return FALSE;
+
+ return (strstr (prop, exp->args.prop_args.second_arg.string) != NULL);
+ }
+ case EPHY_NODE_FILTER_EXPRESSION_KEY_PROP_EQUALS:
+ {
+ const char *prop;
+
+ prop = ephy_node_get_property_string (node,
+ exp->args.prop_args.prop_id);
+
+ if (prop == NULL)
+ return FALSE;
+
+ return (strcmp (prop, exp->args.prop_args.second_arg.string) == 0);
+ }
+ case EPHY_NODE_FILTER_EXPRESSION_INT_PROP_EQUALS:
+ {
+ int prop;
+
+ prop = ephy_node_get_property_int (node,
+ exp->args.prop_args.prop_id);
+
+ return (prop == exp->args.prop_args.second_arg.number);
+ }
+ case EPHY_NODE_FILTER_EXPRESSION_INT_PROP_BIGGER_THAN:
+ {
+ int prop;
+
+ prop = ephy_node_get_property_int (node,
+ exp->args.prop_args.prop_id);
+
+ return (prop > exp->args.prop_args.second_arg.number);
+ }
+ case EPHY_NODE_FILTER_EXPRESSION_INT_PROP_LESS_THAN:
+ {
+ int prop;
+
+ prop = ephy_node_get_property_int (node,
+ exp->args.prop_args.prop_id);
+ g_print ("%d %d\n", prop, exp->args.prop_args.second_arg.number);
+ return (prop < exp->args.prop_args.second_arg.number);
+ }
+ default:
+ break;
+ }
+
+ return FALSE;
+}
diff --git a/lib/ephy-node-filter.h b/lib/ephy-node-filter.h
new file mode 100644
index 000000000..1aedc16c6
--- /dev/null
+++ b/lib/ephy-node-filter.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2002 Olivier Martin <omartin@ifrance.com>
+ * (C) 2002 Jorn Baayen <jorn@nl.linux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Id$
+ */
+
+#ifndef EPHY_NODE_FILTER_H
+#define EPHY_NODE_FILTER_H
+
+#include <glib-object.h>
+
+#include "ephy-node.h"
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_NODE_FILTER (ephy_node_filter_get_type ())
+#define EPHY_NODE_FILTER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EPHY_TYPE_NODE_FILTER, EphyNodeFilter))
+#define EPHY_NODE_FILTER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EPHY_TYPE_NODE_FILTER, EphyNodeFilterClass))
+#define EPHY_IS_NODE_FILTER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EPHY_TYPE_NODE_FILTER))
+#define EPHY_IS_NODE_FILTER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EPHY_TYPE_NODE_FILTER))
+#define EPHY_NODE_FILTER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EPHY_TYPE_NODE_FILTER, EphyNodeFilterClass))
+
+typedef struct EphyNodeFilterPrivate EphyNodeFilterPrivate;
+
+typedef struct
+{
+ GObject parent;
+
+ EphyNodeFilterPrivate *priv;
+} EphyNodeFilter;
+
+typedef struct
+{
+ GObjectClass parent;
+
+ void (*changed) (EphyNodeFilter *filter);
+} EphyNodeFilterClass;
+
+typedef enum
+{
+ EPHY_NODE_FILTER_EXPRESSION_ALWAYS_TRUE, /* args: none */
+ EPHY_NODE_FILTER_EXPRESSION_NODE_EQUALS, /* args: EphyNode *a, EphyNode *b */
+ EPHY_NODE_FILTER_EXPRESSION_EQUALS, /* args: EphyNode *node */
+ EPHY_NODE_FILTER_EXPRESSION_HAS_PARENT, /* args: EphyNode *parent */
+ EPHY_NODE_FILTER_EXPRESSION_HAS_CHILD, /* args: EphyNode *child */
+ EPHY_NODE_FILTER_EXPRESSION_NODE_PROP_EQUALS, /* args: int prop_id, EphyNode *node */
+ EPHY_NODE_FILTER_EXPRESSION_CHILD_PROP_EQUALS, /* args: int prop_id, EphyNode *node */
+ EPHY_NODE_FILTER_EXPRESSION_STRING_PROP_CONTAINS, /* args: int prop_id, const char *string */
+ EPHY_NODE_FILTER_EXPRESSION_STRING_PROP_EQUALS, /* args: int prop_id, const char *string */
+ EPHY_NODE_FILTER_EXPRESSION_KEY_PROP_CONTAINS, /* args: int prop_id, const char *string */
+ EPHY_NODE_FILTER_EXPRESSION_KEY_PROP_EQUALS, /* args: int prop_id, const char *string */
+ EPHY_NODE_FILTER_EXPRESSION_INT_PROP_EQUALS, /* args: int prop_id, int int */
+ EPHY_NODE_FILTER_EXPRESSION_INT_PROP_BIGGER_THAN, /* args: int prop_id, int int */
+ EPHY_NODE_FILTER_EXPRESSION_INT_PROP_LESS_THAN /* args: int prop_id, int int */
+} EphyNodeFilterExpressionType;
+
+typedef struct EphyNodeFilterExpression EphyNodeFilterExpression;
+
+/* The filter starts iterating over all expressions at level 0,
+ * if one of them is TRUE it continues to level 1, etc.
+ * If it still has TRUE when there are no more expressions at the
+ * next level, the result is TRUE. Otherwise, it's FALSE.
+ */
+
+GType ephy_node_filter_get_type (void);
+
+EphyNodeFilter *ephy_node_filter_new (void);
+
+void ephy_node_filter_add_expression (EphyNodeFilter *filter,
+ EphyNodeFilterExpression *expression,
+ int level);
+
+void ephy_node_filter_empty (EphyNodeFilter *filter);
+
+void ephy_node_filter_done_changing (EphyNodeFilter *filter);
+
+gboolean ephy_node_filter_evaluate (EphyNodeFilter *filter,
+ EphyNode *node);
+
+EphyNodeFilterExpression *ephy_node_filter_expression_new (EphyNodeFilterExpressionType,
+ ...);
+/* no need to free unless you didn't add the expression to a filter */
+void ephy_node_filter_expression_free (EphyNodeFilterExpression *expression);
+
+G_END_DECLS
+
+#endif /* EPHY_NODE_FILTER_H */
diff --git a/lib/ephy-node.c b/lib/ephy-node.c
new file mode 100644
index 000000000..b75df9349
--- /dev/null
+++ b/lib/ephy-node.c
@@ -0,0 +1,1439 @@
+/*
+ * Copyright (C) 2002 Jorn Baayen <jorn@nl.linux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Id$
+ */
+
+#include <config.h>
+#include <libgnome/gnome-i18n.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <gdk/gdk.h>
+#include <time.h>
+
+#include "ephy-node.h"
+#include "ephy-string.h"
+#include "ephy-thread-helpers.h"
+
+static void ephy_node_class_init (EphyNodeClass *klass);
+static void ephy_node_init (EphyNode *node);
+static void ephy_node_finalize (GObject *object);
+static void ephy_node_dispose (GObject *object);
+static void ephy_node_set_object_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void ephy_node_get_object_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+static inline void id_factory_set_to (gulong new_factory_pos);
+static inline void real_set_property (EphyNode *node,
+ guint property_id,
+ GValue *value);
+static inline void real_remove_child (EphyNode *node,
+ EphyNode *child,
+ gboolean remove_from_parent,
+ gboolean remove_from_child);
+static inline void real_add_child (EphyNode *node,
+ EphyNode *child);
+static inline void read_lock_to_write_lock (EphyNode *node);
+static inline void write_lock_to_read_lock (EphyNode *node);
+static inline void lock_gdk (void);
+static inline void unlock_gdk (void);
+static inline EphyNode *node_from_id_real (gulong id);
+static inline int get_child_index_real (EphyNode *node,
+ EphyNode *child);
+
+typedef struct
+{
+ EphyNode *node;
+ guint index;
+} EphyNodeParent;
+
+struct EphyNodePrivate
+{
+ GStaticRWLock *lock;
+
+ int ref_count;
+
+ gulong id;
+
+ GPtrArray *properties;
+
+ GHashTable *parents;
+ GPtrArray *children;
+};
+
+enum
+{
+ PROP_0,
+ PROP_ID
+};
+
+enum
+{
+ DESTROYED,
+ RESTORED,
+ CHILD_ADDED,
+ CHILD_CHANGED,
+ CHILD_REMOVED,
+ LAST_SIGNAL
+};
+
+static GObjectClass *parent_class = NULL;
+
+static guint ephy_node_signals[LAST_SIGNAL] = { 0 };
+
+static GMutex *id_factory_lock = NULL;
+static long id_factory = 0;
+
+static GStaticRWLock *id_to_node_lock = NULL;
+static GPtrArray *id_to_node;
+
+GType
+ephy_node_get_type (void)
+{
+ static GType ephy_node_type = 0;
+
+ if (ephy_node_type == 0) {
+ static const GTypeInfo our_info = {
+ sizeof (EphyNodeClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) ephy_node_class_init,
+ NULL,
+ NULL,
+ sizeof (EphyNode),
+ 0,
+ (GInstanceInitFunc) ephy_node_init
+ };
+
+ ephy_node_type = g_type_register_static (G_TYPE_OBJECT,
+ "EphyNode",
+ &our_info, 0);
+ }
+
+ return ephy_node_type;
+}
+
+static void
+ephy_node_class_init (EphyNodeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->finalize = ephy_node_finalize;
+ object_class->dispose = ephy_node_dispose;
+
+ object_class->set_property = ephy_node_set_object_property;
+ object_class->get_property = ephy_node_get_object_property;
+
+ g_object_class_install_property (object_class,
+ PROP_ID,
+ g_param_spec_long ("id",
+ "Node ID",
+ "Node ID",
+ 0, G_MAXLONG, 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ ephy_node_signals[DESTROYED] =
+ g_signal_new ("destroyed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EphyNodeClass, destroyed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+ ephy_node_signals[RESTORED] =
+ g_signal_new ("restored",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EphyNodeClass, restored),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ ephy_node_signals[CHILD_ADDED] =
+ g_signal_new ("child_added",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EphyNodeClass, child_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ EPHY_TYPE_NODE);
+ ephy_node_signals[CHILD_CHANGED] =
+ g_signal_new ("child_changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EphyNodeClass, child_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ EPHY_TYPE_NODE);
+ ephy_node_signals[CHILD_REMOVED] =
+ g_signal_new ("child_removed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EphyNodeClass, child_removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ EPHY_TYPE_NODE);
+}
+
+static gboolean
+int_equal (gconstpointer a,
+ gconstpointer b)
+{
+ return GPOINTER_TO_INT (a) == GPOINTER_TO_INT (b);
+}
+
+static guint
+int_hash (gconstpointer a)
+{
+ return GPOINTER_TO_INT (a);
+}
+
+static void
+ephy_node_init (EphyNode *node)
+{
+ node->priv = g_new0 (EphyNodePrivate, 1);
+
+ node->priv->lock = g_new0 (GStaticRWLock, 1);
+ g_static_rw_lock_init (node->priv->lock);
+
+ node->priv->ref_count = 0;
+
+ node->priv->id = -1;
+
+ node->priv->properties = g_ptr_array_new ();
+
+ node->priv->parents = g_hash_table_new_full (int_hash,
+ int_equal,
+ NULL,
+ g_free);
+
+ node->priv->children = g_ptr_array_new ();
+}
+
+static void
+ephy_node_finalize (GObject *object)
+{
+ EphyNode *node;
+ guint i;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (EPHY_IS_NODE (object));
+
+ node = EPHY_NODE (object);
+
+ g_return_if_fail (node->priv != NULL);
+
+ for (i = 0; i < node->priv->properties->len; i++) {
+ GValue *val;
+
+ val = g_ptr_array_index (node->priv->properties, i);
+
+ if (val != NULL) {
+ g_value_unset (val);
+ g_free (val);
+ }
+ }
+ g_ptr_array_free (node->priv->properties, FALSE);
+
+ g_hash_table_destroy (node->priv->parents);
+
+ g_ptr_array_free (node->priv->children, FALSE);
+
+ g_static_rw_lock_free (node->priv->lock);
+
+ g_free (node->priv);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+remove_child (long id,
+ EphyNodeParent *node_info,
+ EphyNode *node)
+{
+ g_static_rw_lock_writer_lock (node_info->node->priv->lock);
+
+ real_remove_child (node_info->node, node, TRUE, FALSE);
+
+ g_static_rw_lock_writer_unlock (node_info->node->priv->lock);
+}
+
+static void
+ephy_node_dispose (GObject *object)
+{
+ EphyNode *node;
+ guint i;
+
+ node = EPHY_NODE (object);
+
+ /* remove from id table */
+ g_static_rw_lock_writer_lock (id_to_node_lock);
+
+ g_ptr_array_index (id_to_node, node->priv->id) = NULL;
+
+ g_static_rw_lock_writer_unlock (id_to_node_lock);
+
+ lock_gdk ();
+
+ /* remove from DAG */
+ g_hash_table_foreach (node->priv->parents,
+ (GHFunc) remove_child,
+ node);
+
+ for (i = 0; i < node->priv->children->len; i++) {
+ EphyNode *child;
+
+ child = g_ptr_array_index (node->priv->children, i);
+
+ g_static_rw_lock_writer_lock (child->priv->lock);
+
+ real_remove_child (node, child, FALSE, TRUE);
+
+ g_static_rw_lock_writer_unlock (child->priv->lock);
+ }
+
+ g_static_rw_lock_writer_unlock (node->priv->lock);
+
+ g_signal_emit (G_OBJECT (node), ephy_node_signals[DESTROYED], 0);
+
+ unlock_gdk ();
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+ephy_node_set_object_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EphyNode *node = EPHY_NODE (object);
+
+ switch (prop_id)
+ {
+ case PROP_ID:
+ node->priv->id = g_value_get_long (value);
+
+ g_static_rw_lock_writer_lock (id_to_node_lock);
+
+ /* resize array if needed */
+ if (node->priv->id >= id_to_node->len)
+ g_ptr_array_set_size (id_to_node, node->priv->id + 1);
+
+ g_ptr_array_index (id_to_node, node->priv->id) = node;
+
+ g_static_rw_lock_writer_unlock (id_to_node_lock);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+ephy_node_get_object_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EphyNode *node = EPHY_NODE (object);
+
+ switch (prop_id)
+ {
+ case PROP_ID:
+ g_value_set_long (value, node->priv->id);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+EphyNode *
+ephy_node_new (void)
+{
+ EphyNode *node;
+
+ node = EPHY_NODE (g_object_new (EPHY_TYPE_NODE,
+ "id", ephy_node_new_id (),
+ NULL));
+
+ g_return_val_if_fail (node->priv != NULL, NULL);
+
+ return node;
+}
+
+long
+ephy_node_get_id (EphyNode *node)
+{
+ long ret;
+
+ g_return_val_if_fail (EPHY_IS_NODE (node), -1);
+
+ g_static_rw_lock_reader_lock (node->priv->lock);
+
+ ret = node->priv->id;
+
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+
+ return ret;
+}
+
+static inline EphyNode *
+node_from_id_real (gulong id)
+{
+ EphyNode *ret = NULL;
+
+ if (id < id_to_node->len)
+ ret = g_ptr_array_index (id_to_node, id);;
+
+ return ret;
+}
+
+EphyNode *
+ephy_node_get_from_id (gulong id)
+{
+ EphyNode *ret = NULL;
+
+ g_return_val_if_fail (id > 0, NULL);
+
+ g_static_rw_lock_reader_lock (id_to_node_lock);
+
+ ret = node_from_id_real (id);
+
+ g_static_rw_lock_reader_unlock (id_to_node_lock);
+
+ return ret;
+}
+
+void
+ephy_node_ref (EphyNode *node)
+{
+ g_return_if_fail (EPHY_IS_NODE (node));
+
+ g_static_rw_lock_writer_lock (node->priv->lock);
+
+ node->priv->ref_count++;
+
+ g_static_rw_lock_writer_unlock (node->priv->lock);
+}
+
+void
+ephy_node_unref (EphyNode *node)
+{
+ g_return_if_fail (EPHY_IS_NODE (node));
+
+ g_static_rw_lock_writer_lock (node->priv->lock);
+
+ node->priv->ref_count--;
+
+ if (node->priv->ref_count <= 0) {
+ g_object_unref (G_OBJECT (node));
+ } else {
+ g_static_rw_lock_writer_unlock (node->priv->lock);
+ }
+}
+
+void
+ephy_node_freeze (EphyNode *node)
+{
+ g_return_if_fail (EPHY_IS_NODE (node));
+
+ g_static_rw_lock_reader_lock (node->priv->lock);
+}
+
+void
+ephy_node_thaw (EphyNode *node)
+{
+ g_return_if_fail (EPHY_IS_NODE (node));
+
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+}
+
+static void
+child_changed (gulong id,
+ EphyNodeParent *node_info,
+ EphyNode *node)
+{
+ g_static_rw_lock_reader_lock (node_info->node->priv->lock);
+
+ g_signal_emit (G_OBJECT (node_info->node), ephy_node_signals[CHILD_CHANGED], 0, node);
+
+ g_static_rw_lock_reader_unlock (node_info->node->priv->lock);
+}
+
+static inline void
+real_set_property (EphyNode *node,
+ guint property_id,
+ GValue *value)
+{
+ GValue *old;
+
+ if (property_id >= node->priv->properties->len) {
+ g_ptr_array_set_size (node->priv->properties, property_id + 1);
+ }
+
+ old = g_ptr_array_index (node->priv->properties, property_id);
+ if (old != NULL) {
+ g_value_unset (old);
+ g_free (old);
+ }
+
+ g_ptr_array_index (node->priv->properties, property_id) = value;
+}
+
+void
+ephy_node_set_property (EphyNode *node,
+ guint property_id,
+ const GValue *value)
+{
+ GValue *new;
+
+ g_return_if_fail (EPHY_IS_NODE (node));
+ g_return_if_fail (property_id >= 0);
+ g_return_if_fail (value != NULL);
+
+ lock_gdk ();
+
+ g_static_rw_lock_writer_lock (node->priv->lock);
+
+ new = g_new0 (GValue, 1);
+ g_value_init (new, G_VALUE_TYPE (value));
+ g_value_copy (value, new);
+
+ real_set_property (node, property_id, new);
+
+ write_lock_to_read_lock (node);
+
+ g_hash_table_foreach (node->priv->parents,
+ (GHFunc) child_changed,
+ node);
+
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+
+ unlock_gdk ();
+}
+
+gboolean
+ephy_node_get_property (EphyNode *node,
+ guint property_id,
+ GValue *value)
+{
+ GValue *ret;
+
+ g_return_val_if_fail (EPHY_IS_NODE (node), FALSE);
+ g_return_val_if_fail (property_id >= 0, FALSE);
+ g_return_val_if_fail (value != NULL, FALSE);
+
+ g_static_rw_lock_reader_lock (node->priv->lock);
+
+ if (property_id >= node->priv->properties->len) {
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ return FALSE;
+ }
+
+ ret = g_ptr_array_index (node->priv->properties, property_id);
+ if (ret == NULL) {
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ return FALSE;
+ }
+
+ g_value_init (value, G_VALUE_TYPE (ret));
+ g_value_copy (ret, value);
+
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+
+ return TRUE;
+}
+
+const char *
+ephy_node_get_property_string (EphyNode *node,
+ guint property_id)
+{
+ GValue *ret;
+ const char *retval;
+
+ g_return_val_if_fail (EPHY_IS_NODE (node), NULL);
+ g_return_val_if_fail (property_id >= 0, NULL);
+
+ g_static_rw_lock_reader_lock (node->priv->lock);
+
+ if (property_id >= node->priv->properties->len) {
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ return NULL;
+ }
+
+ ret = g_ptr_array_index (node->priv->properties, property_id);
+ if (ret == NULL) {
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ return NULL;
+ }
+
+ retval = g_value_get_string (ret);
+
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+
+ return retval;
+}
+
+gboolean
+ephy_node_get_property_boolean (EphyNode *node,
+ guint property_id)
+{
+ GValue *ret;
+ gboolean retval;
+
+ g_return_val_if_fail (EPHY_IS_NODE (node), FALSE);
+ g_return_val_if_fail (property_id >= 0, FALSE);
+
+ g_static_rw_lock_reader_lock (node->priv->lock);
+
+ if (property_id >= node->priv->properties->len) {
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ return FALSE;
+ }
+
+ ret = g_ptr_array_index (node->priv->properties, property_id);
+ if (ret == NULL) {
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ return FALSE;
+ }
+
+ retval = g_value_get_boolean (ret);
+
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+
+ return retval;
+}
+
+long
+ephy_node_get_property_long (EphyNode *node,
+ guint property_id)
+{
+ GValue *ret;
+ long retval;
+
+ g_return_val_if_fail (EPHY_IS_NODE (node), -1);
+ g_return_val_if_fail (property_id >= 0, -1);
+
+ g_static_rw_lock_reader_lock (node->priv->lock);
+
+ if (property_id >= node->priv->properties->len) {
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ return -1;
+ }
+
+ ret = g_ptr_array_index (node->priv->properties, property_id);
+ if (ret == NULL) {
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ return -1;
+ }
+
+ retval = g_value_get_long (ret);
+
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+
+ return retval;
+}
+
+int
+ephy_node_get_property_int (EphyNode *node,
+ guint property_id)
+{
+ GValue *ret;
+ int retval;
+
+ g_return_val_if_fail (EPHY_IS_NODE (node), -1);
+ g_return_val_if_fail (property_id >= 0, -1);
+
+ g_static_rw_lock_reader_lock (node->priv->lock);
+
+ if (property_id >= node->priv->properties->len) {
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ return -1;
+ }
+
+ ret = g_ptr_array_index (node->priv->properties, property_id);
+ if (ret == NULL) {
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ return -1;
+ }
+
+ retval = g_value_get_int (ret);
+
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+
+ return retval;
+}
+
+double
+ephy_node_get_property_double (EphyNode *node,
+ guint property_id)
+{
+ GValue *ret;
+ double retval;
+
+ g_return_val_if_fail (EPHY_IS_NODE (node), -1);
+ g_return_val_if_fail (property_id >= 0, -1);
+
+ g_static_rw_lock_reader_lock (node->priv->lock);
+
+ if (property_id >= node->priv->properties->len) {
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ return -1;
+ }
+
+ ret = g_ptr_array_index (node->priv->properties, property_id);
+ if (ret == NULL) {
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ return -1;
+ }
+
+ retval = g_value_get_double (ret);
+
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+
+ return retval;
+}
+
+float
+ephy_node_get_property_float (EphyNode *node,
+ guint property_id)
+{
+ GValue *ret;
+ float retval;
+
+ g_return_val_if_fail (EPHY_IS_NODE (node), -1);
+ g_return_val_if_fail (property_id >= 0, -1);
+
+ g_static_rw_lock_reader_lock (node->priv->lock);
+
+ if (property_id >= node->priv->properties->len) {
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ return -1;
+ }
+
+ ret = g_ptr_array_index (node->priv->properties, property_id);
+ if (ret == NULL) {
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ return -1;
+ }
+
+ retval = g_value_get_float (ret);
+
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+
+ return retval;
+}
+
+EphyNode *
+ephy_node_get_property_node (EphyNode *node,
+ guint property_id)
+{
+ GValue *ret;
+ EphyNode *retval;
+
+ g_return_val_if_fail (EPHY_IS_NODE (node), NULL);
+ g_return_val_if_fail (property_id >= 0, NULL);
+
+ g_static_rw_lock_reader_lock (node->priv->lock);
+
+ if (property_id >= node->priv->properties->len) {
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ return NULL;
+ }
+
+ ret = g_ptr_array_index (node->priv->properties, property_id);
+ if (ret == NULL) {
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ return NULL;
+ }
+
+ retval = g_value_get_pointer (ret);
+
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+
+ return retval;
+}
+
+char *
+ephy_node_get_property_time (EphyNode *node,
+ guint property_id)
+{
+ GValue *ret;
+ long mtime;
+ char *retval;
+
+ g_return_val_if_fail (EPHY_IS_NODE (node), NULL);
+ g_return_val_if_fail (property_id >= 0, NULL);
+
+ g_static_rw_lock_reader_lock (node->priv->lock);
+
+ if (property_id >= node->priv->properties->len) {
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ return g_strdup (_("Never"));
+ }
+
+ ret = g_ptr_array_index (node->priv->properties, property_id);
+ if (ret == NULL) {
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ return g_strdup (_("Never"));
+ }
+
+ mtime = g_value_get_long (ret);
+
+ if (retval >= 0) {
+ GDate *now, *file_date;
+ guint32 file_date_age;
+ const char *format = NULL;
+
+ now = g_date_new ();
+ g_date_set_time (now, time (NULL));
+
+ file_date = g_date_new ();
+ g_date_set_time (file_date, mtime);
+
+ file_date_age = (g_date_get_julian (now) - g_date_get_julian (file_date));
+
+ g_date_free (file_date);
+ g_date_free (now);
+
+ if (file_date_age == 0) {
+ format = _("Today at %-H:%M");
+ } else if (file_date_age == 1) {
+ format = _("Yesterday at %-H:%M");
+ } else {
+ format = _("%A, %B %-d %Y at %-H:%M");
+ }
+
+ retval = ephy_string_time_to_string (file_date, format);
+ } else {
+ retval = g_strdup (_("Never"));
+ }
+
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+
+ return retval;
+}
+
+static void
+save_parent (gulong id,
+ EphyNodeParent *node_info,
+ xmlNodePtr xml_node)
+{
+ xmlNodePtr parent_xml_node;
+ char *xml;
+
+ parent_xml_node = xmlNewChild (xml_node, NULL, "parent", NULL);
+
+ g_static_rw_lock_reader_lock (node_info->node->priv->lock);
+
+ xml = g_strdup_printf ("%ld", node_info->node->priv->id);
+ xmlSetProp (parent_xml_node, "id", xml);
+ g_free (xml);
+
+ g_static_rw_lock_reader_unlock (node_info->node->priv->lock);
+}
+
+void
+ephy_node_save_to_xml (EphyNode *node,
+ xmlNodePtr parent_xml_node)
+{
+ xmlNodePtr xml_node;
+ char *xml;
+ guint i;
+
+ g_return_if_fail (EPHY_IS_NODE (node));
+ g_return_if_fail (parent_xml_node != NULL);
+
+ g_static_rw_lock_reader_lock (node->priv->lock);
+
+ xml_node = xmlNewChild (parent_xml_node, NULL, "node", NULL);
+
+ xml = g_strdup_printf ("%ld", node->priv->id);
+ xmlSetProp (xml_node, "id", xml);
+ g_free (xml);
+
+ xmlSetProp (xml_node, "type", G_OBJECT_TYPE_NAME (node));
+
+ for (i = 0; i < node->priv->properties->len; i++) {
+ GValue *value;
+ xmlNodePtr value_xml_node;
+
+ value = g_ptr_array_index (node->priv->properties, i);
+ if (value == NULL)
+ continue;
+
+ value_xml_node = xmlNewChild (xml_node, NULL, "property", NULL);
+
+ xml = g_strdup_printf ("%d", i);
+ xmlSetProp (value_xml_node, "id", xml);
+ g_free (xml);
+
+ xmlSetProp (value_xml_node, "value_type", g_type_name (G_VALUE_TYPE (value)));
+
+ switch (G_VALUE_TYPE (value))
+ {
+ case G_TYPE_STRING:
+ xml = xmlEncodeEntitiesReentrant (NULL,
+ g_value_get_string (value));
+ xmlNodeSetContent (value_xml_node, xml);
+ g_free (xml);
+ break;
+ case G_TYPE_BOOLEAN:
+ xml = g_strdup_printf ("%d", g_value_get_boolean (value));
+ xmlNodeSetContent (value_xml_node, xml);
+ g_free (xml);
+ break;
+ case G_TYPE_INT:
+ xml = g_strdup_printf ("%d", g_value_get_int (value));
+ xmlNodeSetContent (value_xml_node, xml);
+ g_free (xml);
+ break;
+ case G_TYPE_LONG:
+ xml = g_strdup_printf ("%ld", g_value_get_long (value));
+ xmlNodeSetContent (value_xml_node, xml);
+ g_free (xml);
+ break;
+ case G_TYPE_FLOAT:
+ xml = g_strdup_printf ("%f", g_value_get_float (value));
+ xmlNodeSetContent (value_xml_node, xml);
+ g_free (xml);
+ break;
+ case G_TYPE_DOUBLE:
+ xml = g_strdup_printf ("%f", g_value_get_double (value));
+ xmlNodeSetContent (value_xml_node, xml);
+ g_free (xml);
+ break;
+ case G_TYPE_POINTER:
+ {
+ EphyNode *prop_node;
+
+ prop_node = g_value_get_pointer (value);
+
+ g_assert (prop_node != NULL);
+
+ g_static_rw_lock_reader_lock (prop_node->priv->lock);
+
+ xml = g_strdup_printf ("%ld", prop_node->priv->id);
+ xmlNodeSetContent (value_xml_node, xml);
+ g_free (xml);
+
+ g_static_rw_lock_reader_unlock (prop_node->priv->lock);
+ break;
+ }
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+
+ g_hash_table_foreach (node->priv->parents,
+ (GHFunc) save_parent,
+ xml_node);
+
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+}
+
+/* this function assumes it's safe to not lock anything while loading,
+ * this is at least true for the case where we're loading the library xml file
+ * from the main loop */
+EphyNode *
+ephy_node_new_from_xml (xmlNodePtr xml_node)
+{
+ EphyNode *node;
+ xmlNodePtr xml_child;
+ char *xml;
+ long id;
+ GType type;
+
+ g_return_val_if_fail (xml_node != NULL, NULL);
+
+ xml = xmlGetProp (xml_node, "id");
+ if (xml == NULL)
+ return NULL;
+ id = atol (xml);
+ g_free (xml);
+
+ id_factory_set_to (id);
+
+ xml = xmlGetProp (xml_node, "type");
+ type = g_type_from_name (xml);
+ g_free (xml);
+
+ node = EPHY_NODE (g_object_new (type,
+ "id", id,
+ NULL));
+
+ g_return_val_if_fail (node->priv != NULL, NULL);
+
+ for (xml_child = xml_node->children; xml_child != NULL; xml_child = xml_child->next) {
+ if (strcmp (xml_child->name, "parent") == 0) {
+ EphyNode *parent;
+ long parent_id;
+
+ xml = xmlGetProp (xml_child, "id");
+ g_assert (xml != NULL);
+ parent_id = atol (xml);
+ g_free (xml);
+
+ parent = node_from_id_real (parent_id);
+
+ if (parent != NULL)
+ {
+ real_add_child (parent, node);
+
+ g_signal_emit (G_OBJECT (parent), ephy_node_signals[CHILD_ADDED],
+ 0, node);
+ }
+ } else if (strcmp (xml_child->name, "property") == 0) {
+ GType value_type;
+ GValue *value;
+ int property_id;
+
+ xml = xmlGetProp (xml_child, "id");
+ property_id = atoi (xml);
+ g_free (xml);
+
+ xml = xmlGetProp (xml_child, "value_type");
+ value_type = g_type_from_name (xml);
+ g_free (xml);
+
+ xml = xmlNodeGetContent (xml_child);
+ value = g_new0 (GValue, 1);
+ g_value_init (value, value_type);
+
+ switch (value_type)
+ {
+ case G_TYPE_STRING:
+ g_value_set_string (value, xml);
+ break;
+ case G_TYPE_INT:
+ g_value_set_int (value, atoi (xml));
+ break;
+ case G_TYPE_BOOLEAN:
+ g_value_set_boolean (value, atoi (xml));
+ break;
+ case G_TYPE_LONG:
+ g_value_set_long (value, atol (xml));
+ break;
+ case G_TYPE_FLOAT:
+ g_value_set_float (value, atof (xml));
+ break;
+ case G_TYPE_DOUBLE:
+ g_value_set_double (value, atof (xml));
+ break;
+ case G_TYPE_POINTER:
+ {
+ EphyNode *property_node;
+
+ property_node = node_from_id_real (atol (xml));
+
+ g_value_set_pointer (value, property_node);
+ break;
+ }
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ real_set_property (node, property_id, value);
+
+ g_free (xml);
+ }
+ }
+
+ g_signal_emit (G_OBJECT (node), ephy_node_signals[RESTORED], 0);
+
+ return node;
+}
+
+static inline void
+real_add_child (EphyNode *node,
+ EphyNode *child)
+{
+ EphyNodeParent *node_info;
+
+ if (g_hash_table_lookup (child->priv->parents,
+ GINT_TO_POINTER (node->priv->id)) != NULL) {
+ return;
+ }
+
+ g_ptr_array_add (node->priv->children, child);
+
+ node_info = g_new0 (EphyNodeParent, 1);
+ node_info->node = node;
+ node_info->index = node->priv->children->len - 1;
+
+ g_hash_table_insert (child->priv->parents,
+ GINT_TO_POINTER (node->priv->id),
+ node_info);
+}
+
+void
+ephy_node_add_child (EphyNode *node,
+ EphyNode *child)
+{
+ g_return_if_fail (EPHY_IS_NODE (node));
+ g_return_if_fail (EPHY_IS_NODE (child));
+
+ lock_gdk ();
+
+ g_static_rw_lock_writer_lock (node->priv->lock);
+ g_static_rw_lock_writer_lock (child->priv->lock);
+
+ real_add_child (node, child);
+
+ write_lock_to_read_lock (node);
+ write_lock_to_read_lock (child);
+
+ g_signal_emit (G_OBJECT (node), ephy_node_signals[CHILD_ADDED], 0, child);
+
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ g_static_rw_lock_reader_unlock (child->priv->lock);
+
+ unlock_gdk ();
+}
+
+static inline void
+real_remove_child (EphyNode *node,
+ EphyNode *child,
+ gboolean remove_from_parent,
+ gboolean remove_from_child)
+{
+ EphyNodeParent *node_info;
+
+ write_lock_to_read_lock (node);
+ write_lock_to_read_lock (child);
+
+ g_signal_emit (G_OBJECT (node), ephy_node_signals[CHILD_REMOVED], 0, child);
+
+ read_lock_to_write_lock (node);
+ read_lock_to_write_lock (child);
+
+ node_info = g_hash_table_lookup (child->priv->parents,
+ GINT_TO_POINTER (node->priv->id));
+
+ if (remove_from_parent) {
+ guint i;
+
+ g_ptr_array_remove_index (node->priv->children,
+ node_info->index);
+
+ /* correct indices on kids */
+ for (i = node_info->index; i < node->priv->children->len; i++) {
+ EphyNode *borked_node;
+ EphyNodeParent *borked_node_info;
+
+ borked_node = g_ptr_array_index (node->priv->children, i);
+
+ g_static_rw_lock_writer_lock (borked_node->priv->lock);
+
+ borked_node_info = g_hash_table_lookup (borked_node->priv->parents,
+ GINT_TO_POINTER (node->priv->id));
+ borked_node_info->index--;
+
+ g_static_rw_lock_writer_unlock (borked_node->priv->lock);
+ }
+ }
+
+ if (remove_from_child) {
+ g_hash_table_remove (child->priv->parents,
+ GINT_TO_POINTER (node->priv->id));
+ }
+}
+
+void
+ephy_node_remove_child (EphyNode *node,
+ EphyNode *child)
+{
+ g_return_if_fail (EPHY_IS_NODE (node));
+ g_return_if_fail (EPHY_IS_NODE (child));
+
+ lock_gdk ();
+
+ g_static_rw_lock_writer_lock (node->priv->lock);
+ g_static_rw_lock_writer_lock (child->priv->lock);
+
+ real_remove_child (node, child, TRUE, TRUE);
+
+ g_static_rw_lock_writer_unlock (node->priv->lock);
+ g_static_rw_lock_writer_unlock (child->priv->lock);
+
+ unlock_gdk ();
+}
+
+gboolean
+ephy_node_has_child (EphyNode *node,
+ EphyNode *child)
+{
+ gboolean ret;
+
+ g_return_val_if_fail (EPHY_IS_NODE (node), FALSE);
+ g_return_val_if_fail (EPHY_IS_NODE (child), FALSE);
+
+ g_static_rw_lock_reader_lock (node->priv->lock);
+ g_static_rw_lock_reader_lock (child->priv->lock);
+
+ ret = (g_hash_table_lookup (child->priv->parents,
+ GINT_TO_POINTER (node->priv->id)) != NULL);
+
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ g_static_rw_lock_reader_unlock (child->priv->lock);
+
+ return ret;
+}
+
+GPtrArray *
+ephy_node_get_children (EphyNode *node)
+{
+ g_return_val_if_fail (EPHY_IS_NODE (node), NULL);
+
+ g_static_rw_lock_reader_lock (node->priv->lock);
+
+ return node->priv->children;
+}
+
+int
+ephy_node_get_n_children (EphyNode *node)
+{
+ int ret;
+
+ g_return_val_if_fail (EPHY_IS_NODE (node), -1);
+
+ g_static_rw_lock_reader_lock (node->priv->lock);
+
+ ret = node->priv->children->len;
+
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+
+ return ret;
+}
+
+EphyNode *
+ephy_node_get_nth_child (EphyNode *node,
+ guint n)
+{
+ EphyNode *ret;
+
+ g_return_val_if_fail (EPHY_IS_NODE (node), NULL);
+ g_return_val_if_fail (n >= 0, NULL);
+
+ g_static_rw_lock_reader_lock (node->priv->lock);
+
+ if (n < node->priv->children->len) {
+ ret = g_ptr_array_index (node->priv->children, n);
+ } else {
+ ret = NULL;
+ }
+
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+
+ return ret;
+}
+
+static inline int
+get_child_index_real (EphyNode *node,
+ EphyNode *child)
+{
+ EphyNodeParent *node_info;
+
+ node_info = g_hash_table_lookup (child->priv->parents,
+ GINT_TO_POINTER (node->priv->id));
+
+ if (node_info == NULL)
+ return -1;
+
+ return node_info->index;
+}
+
+int
+ephy_node_get_child_index (EphyNode *node,
+ EphyNode *child)
+{
+ EphyNodeParent *node_info;
+ int ret;
+
+ g_return_val_if_fail (EPHY_IS_NODE (node), -1);
+ g_return_val_if_fail (EPHY_IS_NODE (child), -1);
+
+ g_static_rw_lock_reader_lock (node->priv->lock);
+ g_static_rw_lock_reader_lock (child->priv->lock);
+
+ node_info = g_hash_table_lookup (child->priv->parents,
+ GINT_TO_POINTER (node->priv->id));
+
+ if (node_info == NULL)
+ return -1;
+
+ ret = node_info->index;
+
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ g_static_rw_lock_reader_unlock (child->priv->lock);
+
+ return ret;
+}
+
+EphyNode *
+ephy_node_get_next_child (EphyNode *node,
+ EphyNode *child)
+{
+ EphyNode *ret;
+ guint idx;
+
+ g_return_val_if_fail (EPHY_IS_NODE (node), NULL);
+ g_return_val_if_fail (EPHY_IS_NODE (child), NULL);
+
+ g_static_rw_lock_reader_lock (node->priv->lock);
+ g_static_rw_lock_reader_lock (child->priv->lock);
+
+ idx = get_child_index_real (node, child);
+
+ if ((idx + 1) < node->priv->children->len) {
+ ret = g_ptr_array_index (node->priv->children, idx + 1);
+ } else {
+ ret = NULL;
+ }
+
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ g_static_rw_lock_reader_unlock (child->priv->lock);
+
+ return ret;
+}
+
+EphyNode *
+ephy_node_get_previous_child (EphyNode *node,
+ EphyNode *child)
+{
+ EphyNode *ret;
+ int idx;
+
+ g_return_val_if_fail (EPHY_IS_NODE (node), NULL);
+ g_return_val_if_fail (EPHY_IS_NODE (child), NULL);
+
+ g_static_rw_lock_reader_lock (node->priv->lock);
+ g_static_rw_lock_reader_lock (child->priv->lock);
+
+ idx = get_child_index_real (node, child);
+
+ if ((idx - 1) >= 0) {
+ ret = g_ptr_array_index (node->priv->children, idx - 1);
+ } else {
+ ret = NULL;
+ }
+
+ g_static_rw_lock_reader_unlock (node->priv->lock);
+ g_static_rw_lock_reader_unlock (child->priv->lock);
+
+ return ret;
+}
+
+void
+ephy_node_system_init (void)
+{
+ /* id to node */
+ id_to_node = g_ptr_array_new ();
+
+ id_to_node_lock = g_new0 (GStaticRWLock, 1);
+ g_static_rw_lock_init (id_to_node_lock);
+
+ /* id factory */
+ id_factory = 0;
+ id_factory_lock = g_mutex_new ();
+}
+
+void
+ephy_node_system_shutdown (void)
+{
+ g_ptr_array_free (id_to_node, FALSE);
+
+ g_static_rw_lock_free (id_to_node_lock);
+
+ g_mutex_free (id_factory_lock);
+}
+
+long
+ephy_node_new_id (void)
+{
+ long ret;
+
+ g_mutex_lock (id_factory_lock);
+
+ id_factory++;
+
+ ret = id_factory;
+
+ g_mutex_unlock (id_factory_lock);
+
+ return ret;
+}
+
+static void
+id_factory_set_to (gulong new_factory_pos)
+{
+ id_factory = new_factory_pos + 1;
+}
+
+/* evillish hacks to temporarily readlock->writelock and v.v. */
+static inline void
+write_lock_to_read_lock (EphyNode *node)
+{
+ g_static_mutex_lock (&node->priv->lock->mutex);
+ node->priv->lock->read_counter++;
+ g_static_mutex_unlock (&node->priv->lock->mutex);
+
+ g_static_rw_lock_writer_unlock (node->priv->lock);
+}
+
+static inline void
+read_lock_to_write_lock (EphyNode *node)
+{
+ g_static_mutex_lock (&node->priv->lock->mutex);
+ node->priv->lock->read_counter--;
+ g_static_mutex_unlock (&node->priv->lock->mutex);
+
+ g_static_rw_lock_writer_lock (node->priv->lock);
+}
+
+static inline void
+lock_gdk (void)
+{
+ if (ephy_thread_helpers_in_main_thread () == FALSE)
+ GDK_THREADS_ENTER ();
+}
+
+static inline void
+unlock_gdk (void)
+{
+ if (ephy_thread_helpers_in_main_thread () == FALSE)
+ GDK_THREADS_LEAVE ();
+}
diff --git a/lib/ephy-node.h b/lib/ephy-node.h
new file mode 100644
index 000000000..2e5f92210
--- /dev/null
+++ b/lib/ephy-node.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2002 Jorn Baayen <jorn@nl.linux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Id$
+ */
+
+#ifndef EPHY_NODE_H
+#define EPHY_NODE_H
+
+#include <glib-object.h>
+
+#include <libxml/tree.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_NODE (ephy_node_get_type ())
+#define EPHY_NODE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EPHY_TYPE_NODE, EphyNode))
+#define EPHY_NODE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EPHY_TYPE_NODE, EphyNodeClass))
+#define EPHY_IS_NODE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EPHY_TYPE_NODE))
+#define EPHY_IS_NODE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EPHY_TYPE_NODE))
+#define EPHY_NODE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EPHY_TYPE_NODE, EphyNodeClass))
+
+typedef struct EphyNodePrivate EphyNodePrivate;
+
+typedef struct
+{
+ GObject parent;
+
+ EphyNodePrivate *priv;
+} EphyNode;
+
+typedef struct
+{
+ GObjectClass parent;
+
+ /* signals */
+ void (*destroyed) (EphyNode *node);
+ void (*restored) (EphyNode *node);
+
+ void (*child_added) (EphyNode *node, EphyNode *child);
+ void (*child_changed) (EphyNode *node, EphyNode *child);
+ void (*child_reordered) (EphyNode *node, EphyNode *child,
+ int old_index, int new_index);
+ void (*child_removed) (EphyNode *node, EphyNode *child);
+} EphyNodeClass;
+
+GType ephy_node_get_type (void);
+
+EphyNode *ephy_node_new (void);
+
+/* unique node ID */
+long ephy_node_get_id (EphyNode *node);
+
+EphyNode *ephy_node_get_from_id (gulong id);
+
+/* refcounting */
+void ephy_node_ref (EphyNode *node);
+void ephy_node_unref (EphyNode *node);
+
+/* locking */
+void ephy_node_freeze (EphyNode *node);
+void ephy_node_thaw (EphyNode *node);
+
+/* property interface */
+enum
+{
+ EPHY_NODE_PROP_NAME = 0,
+ EPHY_NODE_PROP_NAME_SORT_KEY = 1
+};
+
+void ephy_node_set_property (EphyNode *node,
+ guint property_id,
+ const GValue *value);
+gboolean ephy_node_get_property (EphyNode *node,
+ guint property_id,
+ GValue *value);
+
+const char *ephy_node_get_property_string (EphyNode *node,
+ guint property_id);
+gboolean ephy_node_get_property_boolean (EphyNode *node,
+ guint property_id);
+long ephy_node_get_property_long (EphyNode *node,
+ guint property_id);
+int ephy_node_get_property_int (EphyNode *node,
+ guint property_id);
+double ephy_node_get_property_double (EphyNode *node,
+ guint property_id);
+float ephy_node_get_property_float (EphyNode *node,
+ guint property_id);
+EphyNode *ephy_node_get_property_node (EphyNode *node,
+ guint property_id);
+/* free return value */
+char *ephy_node_get_property_time (EphyNode *node,
+ guint property_id);
+
+/* xml storage */
+void ephy_node_save_to_xml (EphyNode *node,
+ xmlNodePtr parent_xml_node);
+EphyNode *ephy_node_new_from_xml (xmlNodePtr xml_node);
+
+/* DAG structure */
+void ephy_node_add_child (EphyNode *node,
+ EphyNode *child);
+void ephy_node_remove_child (EphyNode *node,
+ EphyNode *child);
+gboolean ephy_node_has_child (EphyNode *node,
+ EphyNode *child);
+
+/* Note that ephy_node_get_children freezes the node; you'll have to thaw it when done.
+ * This is to prevent the data getting changed from another thread. */
+GPtrArray *ephy_node_get_children (EphyNode *node);
+int ephy_node_get_n_children (EphyNode *node);
+EphyNode *ephy_node_get_nth_child (EphyNode *node,
+ guint n);
+int ephy_node_get_child_index (EphyNode *node,
+ EphyNode *child);
+EphyNode *ephy_node_get_next_child (EphyNode *node,
+ EphyNode *child);
+EphyNode *ephy_node_get_previous_child (EphyNode *node,
+ EphyNode *child);
+
+/* node id services */
+void ephy_node_system_init (void);
+void ephy_node_system_shutdown (void);
+
+long ephy_node_new_id (void);
+
+G_END_DECLS
+
+#endif /* __EPHY_NODE_H */
diff --git a/lib/ephy-prefs-utils.c b/lib/ephy-prefs-utils.c
new file mode 100644
index 000000000..0e0cf4a50
--- /dev/null
+++ b/lib/ephy-prefs-utils.c
@@ -0,0 +1,285 @@
+/*
+ * 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.
+ */
+
+#include "ephy-prefs-utils.h"
+#include "ephy-gui.h"
+#include "eel-gconf-extensions.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <gtk/gtkmenu.h>
+#include <gtk/gtkmenushell.h>
+#include <gtk/gtkspinbutton.h>
+#include <gtk/gtktogglebutton.h>
+#include <gtk/gtkoptionmenu.h>
+#include <gtk/gtklist.h>
+#include <libgnomeui/gnome-color-picker.h>
+
+void
+ephy_pu_set_config_from_editable (GtkWidget *editable, const char *config_name)
+{
+ GConfValue *gcvalue = eel_gconf_get_value (config_name);
+ GConfValueType value_type;
+ char *value;
+ gint ivalue;
+ gfloat fvalue;
+
+ if (gcvalue == NULL) {
+ /* ugly hack around what appears to be a gconf bug
+ * it returns a NULL GConfValue for a valid string pref
+ * which is "" by default */
+ value_type = GCONF_VALUE_STRING;
+ } else {
+ value_type = gcvalue->type;
+ gconf_value_free (gcvalue);
+ }
+
+ /* get all the text into a new string */
+ value = gtk_editable_get_chars (GTK_EDITABLE(editable), 0, -1);
+
+ switch (value_type) {
+ case GCONF_VALUE_STRING:
+ eel_gconf_set_string (config_name,
+ value);
+ break;
+ /* FIXME : handle possible errors in the input for int and float */
+ case GCONF_VALUE_INT:
+ ivalue = atoi (value);
+ eel_gconf_set_integer (config_name, ivalue);
+ break;
+ case GCONF_VALUE_FLOAT:
+ fvalue = strtod (value, (char**)NULL);
+ eel_gconf_set_float (config_name, fvalue);
+ break;
+ default:
+ break;
+ }
+
+ /* free the allocated strings */
+ g_free (value);
+}
+
+void
+ephy_pu_set_config_from_optionmenu (GtkWidget *optionmenu, const char *config_name)
+{
+ int index = ephy_pu_get_int_from_optionmenu (optionmenu);
+
+ eel_gconf_set_integer (config_name, index);
+}
+
+void
+ephy_pu_set_config_from_radiobuttongroup (GtkWidget *radiobutton, const char *config_name)
+{
+ gint index;
+
+ /* get value from radio button group */
+ index = ephy_gui_gtk_radio_button_get (GTK_RADIO_BUTTON (radiobutton));
+
+ eel_gconf_set_integer (config_name, index);
+}
+
+void
+ephy_pu_set_config_from_spin_button (GtkWidget *spinbutton, const char *config_name)
+{
+ gdouble value;
+ gboolean use_int;
+
+ /* read the value as an integer */
+ value = gtk_spin_button_get_value (GTK_SPIN_BUTTON(spinbutton));
+
+ use_int = (gtk_spin_button_get_digits (GTK_SPIN_BUTTON(spinbutton)) == 0);
+
+ if (use_int)
+ {
+ eel_gconf_set_integer (config_name, value);
+ }
+ else
+ {
+ eel_gconf_set_float (config_name, value);
+ }
+}
+
+void
+ephy_pu_set_config_from_togglebutton (GtkWidget *togglebutton, const char *config_name)
+{
+ gboolean value;
+
+ /* read the value */
+ value = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(togglebutton));
+
+ eel_gconf_set_boolean (config_name, value);
+}
+
+void
+ephy_pu_set_config_from_color (GtkWidget *colorpicker, const char *config_name)
+{
+ guint8 r, g, b, a;
+ gchar color_string[9];
+
+ /* get color values from color picker */
+ gnome_color_picker_get_i8 (GNOME_COLOR_PICKER (colorpicker),
+ &r, &g, &b, &a);
+
+ /* write into string (bounded size) */
+ snprintf (color_string, 9, "#%02X%02X%02X", r, g, b);
+
+ /* set the configuration value */
+ eel_gconf_set_string (config_name, color_string);
+}
+
+void
+ephy_pu_set_editable_from_config (GtkWidget *editable, const char *config_name)
+{
+ GConfValue *gcvalue = eel_gconf_get_value (config_name);
+ GConfValueType value_type;
+ gchar *value;
+
+ if (gcvalue == NULL)
+ {
+ /* ugly hack around what appears to be a gconf bug
+ * it returns a NULL GConfValue for a valid string pref
+ * which is "" by default */
+ value_type = GCONF_VALUE_STRING;
+ }
+ else
+ {
+ value_type = gcvalue->type;
+ gconf_value_free (gcvalue);
+ }
+
+ switch (value_type)
+ {
+ case GCONF_VALUE_STRING:
+ value = eel_gconf_get_string (config_name);
+ break;
+ case GCONF_VALUE_INT:
+ value = g_strdup_printf ("%d",eel_gconf_get_integer (config_name));
+ break;
+ case GCONF_VALUE_FLOAT:
+ value = g_strdup_printf ("%.2f",eel_gconf_get_float (config_name));
+ break;
+ default:
+ value = NULL;
+ }
+
+ /* set this string value in the widget */
+ if (value)
+ {
+ gtk_entry_set_text(GTK_ENTRY(editable), value);
+ }
+
+ /* free the allocated string */
+ g_free (value);
+}
+
+void
+ephy_pu_set_optionmenu_from_config (GtkWidget *optionmenu, const char *config_name)
+{
+ gint index;
+
+ /* get the current value from the configuration space */
+ index = eel_gconf_get_integer (config_name);
+
+ /* set this option value in the widget */
+ gtk_option_menu_set_history (GTK_OPTION_MENU (optionmenu), index);
+}
+
+void
+ephy_pu_set_radiobuttongroup_from_config (GtkWidget *radiobutton, const char *config_name)
+{
+ gint index;
+
+ /* get the current value from the configuration space */
+ index = eel_gconf_get_integer (config_name);
+
+ /* set it (finds the group for us) */
+ ephy_gui_gtk_radio_button_set (GTK_RADIO_BUTTON (radiobutton), index);
+}
+
+void
+ephy_pu_set_spin_button_from_config (GtkWidget *spinbutton, const char *config_name)
+{
+ gdouble value;
+ gint use_int;
+
+ use_int = (gtk_spin_button_get_digits (GTK_SPIN_BUTTON(spinbutton)) == 0);
+
+ if (use_int)
+ {
+ /* get the current value from the configuration space */
+ value = eel_gconf_get_integer (config_name);
+ }
+ else
+ {
+ /* get the current value from the configuration space */
+ value = eel_gconf_get_float (config_name);
+ }
+
+ /* set this option value in the widget */
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(spinbutton), value);
+}
+
+void
+ephy_pu_set_togglebutton_from_config (GtkWidget *togglebutton, const char *config_name)
+{
+ gboolean value;
+
+ /* get the current value from the configuration space */
+ value = eel_gconf_get_boolean (config_name);
+
+ /* set this option value in the widget */
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (togglebutton), value);
+}
+
+void
+ephy_pu_set_color_from_config (GtkWidget *colorpicker, const char *config_name)
+{
+ gchar *color_string;
+ guint r, g, b;
+
+ /* get the string from config */
+ color_string = eel_gconf_get_string (config_name);
+
+ if (color_string)
+ {
+ /* parse it and setup the color picker */
+ sscanf (color_string, "#%2X%2X%2X", &r, &g, &b);
+ gnome_color_picker_set_i8 (GNOME_COLOR_PICKER (colorpicker),
+ r, g, b, 0);
+ /* free the string */
+ g_free (color_string);
+ }
+}
+
+int
+ephy_pu_get_int_from_optionmenu (GtkWidget *optionmenu)
+{
+ GtkWidget *menu;
+ GList *list;
+ gpointer item;
+ gint index;
+
+ /* extract the selection */
+ menu = GTK_OPTION_MENU(optionmenu)->menu;
+ list = GTK_MENU_SHELL(menu)->children;
+ item = gtk_menu_get_active (GTK_MENU(menu));
+ index = g_list_index (list, item);
+
+ return index;
+}
diff --git a/lib/ephy-prefs-utils.h b/lib/ephy-prefs-utils.h
new file mode 100644
index 000000000..13ac1e4d7
--- /dev/null
+++ b/lib/ephy-prefs-utils.h
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+#include <gtk/gtkwidget.h>
+
+G_BEGIN_DECLS
+
+void ephy_pu_set_config_from_editable (GtkWidget *editable,
+ const char *config_name);
+
+void ephy_pu_set_config_from_optionmenu (GtkWidget *optionmenu,
+ const char *config_name);
+
+void ephy_pu_set_config_from_radiobuttongroup (GtkWidget *radiobutton,
+ const char *config_name);
+
+void ephy_pu_set_config_from_spin_button (GtkWidget *spinbutton,
+ const char *config_name);
+
+void ephy_pu_set_config_from_togglebutton (GtkWidget *togglebutton,
+ const char *config_name);
+
+void ephy_pu_set_config_from_color (GtkWidget *colorpicker,
+ const char *config_name);
+
+void ephy_pu_set_editable_from_config (GtkWidget *editable,
+ const char *config_name);
+
+void ephy_pu_set_optionmenu_from_config (GtkWidget *optionmenu,
+ const char *config_name);
+
+void ephy_pu_set_radiobuttongroup_from_config (GtkWidget *radiobutton,
+ const char *config_name);
+
+void ephy_pu_set_togglebutton_from_config (GtkWidget *togglebutton,
+ const char *config_name);
+
+void ephy_pu_set_spin_button_from_config (GtkWidget *spinbutton,
+ const char *config_name);
+
+void ephy_pu_set_color_from_config (GtkWidget *colorpicker,
+ const char *config_name);
+
+int ephy_pu_get_int_from_optionmenu (GtkWidget *optionmenu);
+
+G_END_DECLS
diff --git a/lib/ephy-prefs.h b/lib/ephy-prefs.h
new file mode 100644
index 000000000..3c0562a1f
--- /dev/null
+++ b/lib/ephy-prefs.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2000-2002 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.
+ */
+
+#ifndef EPHY_PREFS_H
+#define EPHY_PREFS_H
+
+G_BEGIN_DECLS
+
+/* General */
+#define CONF_GENERAL_HOMEPAGE "/apps/epiphany/general/start_page"
+#define CONF_GENERAL_NEWPAGE_TYPE "/apps/epiphany/general/newpage_type"
+
+/* Interface */
+#define CONF_TABS_TABBED "/apps/epiphany/interface/open_in_tab"
+#define CONF_TABS_TABBED_POPUPS "/apps/epiphany/interface/popups_in_tab"
+#define CONF_TABS_TABBED_AUTOJUMP "/apps/epiphany/interface/jumpto_tab"
+#define CONF_WINDOWS_SIDEBAR_PAGE "/apps/epiphany/interface/sidebar_page"
+#define CONF_WINDOWS_SIDEBAR_SIZE "/apps/epiphany/interface/sidebar_size"
+#define CONF_WINDOWS_FS_SHOW_SIDEBAR "/apps/epiphany/interface/show_sidebar_in_fullscreen"
+#define CONF_WINDOWS_FS_SHOW_TOOLBARS "/apps/epiphany/interface/show_toolbars_in_fullscreen"
+#define CONF_WINDOWS_FS_SHOW_STATUSBAR "/apps/epiphany/interface/show_statusbar_in_fullscreen"
+#define CONF_WINDOWS_SHOW_SIDEBAR "/apps/epiphany/interface/show_sidebar"
+#define CONF_WINDOWS_SHOW_TOOLBARS "/apps/epiphany/interface/show_toolbars"
+#define CONF_WINDOWS_SHOW_STATUSBAR "/apps/epiphany/interface/show_statusbar"
+#define CONF_TOOLBAR_SETUP "/apps/epiphany/interface/toolbar_setup"
+#define CONF_TOOLBAR_SPINNER_THEME "/apps/epiphany/interface/spinner_theme"
+
+/* Downloader */
+#define CONF_DOWNLOADING_SHOW_DETAILS "/apps/epiphany/downloader/show_details"
+
+/* Directories */
+#define CONF_STATE_SAVE_DIR "/apps/epiphany/directories/save"
+#define CONF_STATE_SAVE_IMAGE_DIR "/apps/epiphany/directories/saveimage"
+#define CONF_STATE_OPEN_DIR "/apps/epiphany/directories/open"
+#define CONF_STATE_DOWNLOADING_DIR "/apps/epiphany/directories/downloading"
+
+/* System prefs */
+#define CONF_DESKTOP_FTP_HANDLER "/desktop/gnome/url-handlers/ftp/command"
+#define CONF_DESKTOP_TOOLBAR_STYLE "/desktop/gnome/interface/toolbar_style"
+
+G_END_DECLS
+
+#endif
diff --git a/lib/ephy-state.c b/lib/ephy-state.c
new file mode 100644
index 000000000..245cc38b1
--- /dev/null
+++ b/lib/ephy-state.c
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2001 Matthew Mueller
+ * (C) 2002 Jorn Baayen <jorn@nl.linux.org>
+ *
+ * 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.
+ *
+ * $Id$
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gtk/gtkpaned.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtktreeview.h>
+#include <libgnomeui/gnome-app.h>
+#include <bonobo/bonobo-dock.h>
+#include <bonobo/bonobo-dock-layout.h>
+
+#include "ephy-state.h"
+#include "eel-gconf-extensions.h"
+
+#define CONF_GUL_STATE_PATH "/apps/epiphany/state"
+
+static void
+window_save_size (GtkWidget *window, const gchar *name)
+{
+ int width, height;
+ gchar *buf;
+
+ gtk_window_get_size (GTK_WINDOW(window),
+ &width, &height);
+
+ buf = g_strdup_printf (CONF_GUL_STATE_PATH "/%s/width",name);
+ eel_gconf_set_integer (buf, width);
+ g_free (buf);
+
+ buf = g_strdup_printf (CONF_GUL_STATE_PATH "/%s/height",name);
+ eel_gconf_set_integer (buf, height);
+ g_free (buf);
+}
+
+/**
+ * ephy_state_save_window: save the window dimensions
+ */
+void
+ephy_state_save_window (GtkWidget *window,
+ const gchar *name)
+{
+ GdkWindowState state;
+ gboolean maximized;
+ gchar *buf;
+
+ state = gdk_window_get_state (GTK_WIDGET (window)->window);
+ maximized = state && GDK_WINDOW_STATE_MAXIMIZED;
+
+ buf = g_strdup_printf (CONF_GUL_STATE_PATH "/%s/maximized",name);
+ eel_gconf_set_boolean (buf, maximized);
+ g_free (buf);
+
+ if (!maximized)
+ {
+ window_save_size (window, name);
+ }
+}
+
+/**
+ * ephy_window_load_state: load the window state
+ */
+void
+ephy_state_load_window (GtkWidget *window,
+ const gchar *name,
+ int default_width,
+ int default_height,
+ gboolean position)
+{
+ gchar *buf;
+ gint width, height;
+ gboolean maximized;
+
+ buf = g_strdup_printf (CONF_GUL_STATE_PATH "/%s/maximized",name);
+ maximized = eel_gconf_get_boolean (buf);
+ g_free (buf);
+
+ buf = g_strdup_printf (CONF_GUL_STATE_PATH "/%s/width",name);
+ width = eel_gconf_get_integer (buf);
+ g_free (buf);
+
+ buf = g_strdup_printf (CONF_GUL_STATE_PATH "/%s/height",name);
+ height = eel_gconf_get_integer (buf);
+ g_free (buf);
+
+ /* try default size */
+ if (width == 0 && height == 0 &&
+ default_width != -1 && default_height != -1)
+ {
+ width = default_width;
+ height = default_height;
+ }
+
+ if (width != 0 && height != 0)
+ {
+ gtk_window_set_default_size
+ (GTK_WINDOW (window), width, height);
+ }
+
+ if (maximized)
+ {
+ gtk_window_maximize (GTK_WINDOW(window));
+ }
+}
+
+/**
+ * ephy_state_load_pane_pos: load the paned position
+ */
+void
+ephy_state_load_pane_pos (GtkWidget *pane,
+ const gchar *name)
+{
+ if (pane != NULL)
+ {
+ gint pane_pos;
+ gchar *buf = g_strdup_printf (CONF_GUL_STATE_PATH "/%s", name);
+ pane_pos = eel_gconf_get_integer (buf);
+ g_free (buf);
+
+ if (pane_pos != -1)
+ gtk_paned_set_position (GTK_PANED (pane), pane_pos);
+ }
+}
+
+/**
+ * gul_state_save_pane_pos: save the paned position
+ */
+void
+ephy_state_save_pane_pos (GtkWidget *pane,
+ const gchar *name)
+{
+ if (pane != NULL)
+ {
+ gchar *buf = g_strdup_printf (CONF_GUL_STATE_PATH "/%s", name);
+ eel_gconf_set_integer (buf, GTK_PANED (pane)->child1_size);
+ g_free (buf);
+ }
+}
diff --git a/lib/ephy-state.h b/lib/ephy-state.h
new file mode 100644
index 000000000..508e986be
--- /dev/null
+++ b/lib/ephy-state.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2001 Matthew Mueller
+ * (C) 2002 Jorn Baayen <jorn@nl.linux.org>
+ *
+ * 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.
+ *
+ * $Id$
+ */
+
+#ifndef EPHY_STATE_H
+#define EPHY_STATE_H
+
+G_BEGIN_DECLS
+
+void ephy_state_save_window (GtkWidget *window,
+ const gchar *name);
+
+void ephy_state_load_window (GtkWidget *window,
+ const gchar *name,
+ int default_width,
+ int default_heigth,
+ gboolean position);
+
+void ephy_state_save_pane_pos (GtkWidget *pane,
+ const gchar *name);
+
+void ephy_state_load_pane_pos (GtkWidget *pane,
+ const gchar *name);
+
+G_END_DECLS
+
+#endif /* EPHY_STATE_H */
diff --git a/lib/ephy-stock-icons.c b/lib/ephy-stock-icons.c
new file mode 100644
index 000000000..0d9ff902c
--- /dev/null
+++ b/lib/ephy-stock-icons.c
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2002 Jorn Baayen
+ *
+ * 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.
+ *
+ * $Id$
+ */
+
+#include <gtk/gtk.h>
+#include <glib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "ephy-file-helpers.h"
+#include "ephy-stock-icons.h"
+
+void
+ephy_stock_icons_init (void)
+{
+ GtkIconFactory *factory;
+ int i;
+
+ static const char *items[] =
+ {
+ EPHY_STOCK_SECURE,
+ EPHY_STOCK_UNSECURE
+ };
+
+ factory = gtk_icon_factory_new ();
+ gtk_icon_factory_add_default (factory);
+
+ for (i = 0; i < (int) G_N_ELEMENTS (items); i++)
+ {
+ GtkIconSet *icon_set;
+ GdkPixbuf *pixbuf;
+ char *fn;
+
+ fn = g_strconcat (items[i], ".png", NULL);
+ pixbuf = gdk_pixbuf_new_from_file (ephy_file (fn), NULL);
+ g_free (fn);
+
+ icon_set = gtk_icon_set_new_from_pixbuf (pixbuf);
+ gtk_icon_factory_add (factory, items[i], icon_set);
+ gtk_icon_set_unref (icon_set);
+
+ g_object_unref (G_OBJECT (pixbuf));
+ }
+
+ g_object_unref (G_OBJECT (factory));
+}
diff --git a/lib/ephy-stock-icons.h b/lib/ephy-stock-icons.h
new file mode 100644
index 000000000..c68be46c9
--- /dev/null
+++ b/lib/ephy-stock-icons.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2002 Jorn Baayen
+ *
+ * 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.
+ *
+ * $Id$
+ */
+
+#ifndef EPHY_STOCK_ICONS_H
+#define EPHY_STOCK_ICONS_H
+
+G_BEGIN_DECLS
+
+#define EPHY_STOCK_SECURE "epiphany-secure"
+#define EPHY_STOCK_UNSECURE "epiphany-unsecure"
+
+void ephy_stock_icons_init (void);
+
+G_END_DECLS
+
+#endif /* __RB_STOCK_ICONS_H */
diff --git a/lib/ephy-string.c b/lib/ephy-string.c
new file mode 100644
index 000000000..7ca28cf1f
--- /dev/null
+++ b/lib/ephy-string.c
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2002 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.
+ */
+
+#include "ephy-string.h"
+
+#include <string.h>
+#include <glib.h>
+#include <libgnome/libgnome.h>
+#include <libgnome/gnome-i18n.h>
+#include <libgnomevfs/gnome-vfs-mime.h>
+#include <libxml/parser.h>
+
+/**
+ * ephy_string_shorten: returns a newly allocated shortened version of str.
+ * the new string will be no longer than target_length characters, and will
+ * be of the form "http://blahblah...blahblah.html".
+ */
+gchar *
+ephy_string_shorten (const gchar *str, gint target_length)
+{
+ gchar *new_str;
+ gint actual_length, first_length, second_length;
+
+ if (!str) return NULL;
+
+ actual_length = strlen (str);
+
+ /* if the string is already short enough, or if it's too short for
+ * us to shorten it, return a new copy */
+ if (actual_length <= target_length ||
+ actual_length <= 3)
+ return g_strdup (str);
+
+ /* allocate new string */
+ new_str = g_new (gchar, target_length + 1);
+
+ /* calc lengths to take from beginning and ending of str */
+ second_length = (target_length - 3) / 2;
+ first_length = target_length - 3 - second_length;
+
+ /* create string */
+ strncpy (new_str, str, first_length);
+ strncpy (new_str + first_length, "...", 3);
+ strncpy (new_str + first_length + 3,
+ str + actual_length - second_length, second_length);
+ new_str[target_length] = '\0';
+
+ return new_str;
+}
+
+char *
+ephy_string_double_underscores (const char *string)
+{
+ int underscores;
+ const char *p;
+ char *q;
+ char *escaped;
+
+ if (string == NULL) {
+ return NULL;
+ }
+
+ underscores = 0;
+ for (p = string; *p != '\0'; p++) {
+ underscores += (*p == '_');
+ }
+
+ if (underscores == 0) {
+ return g_strdup (string);
+ }
+
+ escaped = g_new (char, strlen (string) + underscores + 1);
+ for (p = string, q = escaped; *p != '\0'; p++, q++) {
+ /* Add an extra underscore. */
+ if (*p == '_') {
+ *q++ = '_';
+ }
+ *q = *p;
+ }
+ *q = '\0';
+
+ return escaped;
+}
+
+/**
+ * ephy_string_store_time_in_string:
+ * NOTE: str must be at least 256 chars long
+ */
+void
+ephy_string_store_time_in_string (GDate *t, gchar *str, const char *format)
+{
+ int length;
+
+ if (t > 0)
+ {
+ /* format into string */
+ /* this is used whenever a brief date is needed, like
+ * in the history (for last visited, first time visited) */
+ length = g_date_strftime (str, 255,
+ format ? format : _("%Y-%m-%d"), t);
+ str[length] = '\0';
+ }
+ else
+ {
+ str[0] = '\0';
+ }
+}
+
+/**
+ * ephy_string_time_to_string:
+ */
+gchar *
+ephy_string_time_to_string (GDate *t,
+ const char *format)
+{
+ gchar str[256];
+
+ /* write into stack string */
+ ephy_string_store_time_in_string (t, str, format);
+
+ /* copy in heap and return */
+ return g_strdup (str);
+}
+
+gboolean
+ephy_str_to_int (const char *string, int *integer)
+{
+ long result;
+ char *parse_end;
+
+ /* Check for the case of an empty string. */
+ if (string == NULL || *string == '\0') {
+ return FALSE;
+ }
+
+ /* Call the standard library routine to do the conversion. */
+ errno = 0;
+ result = strtol (string, &parse_end, 0);
+
+ /* Check that the result is in range. */
+ if ((result == G_MINLONG || result == G_MAXLONG) && errno == ERANGE) {
+ return FALSE;
+ }
+ if (result < G_MININT || result > G_MAXINT) {
+ return FALSE;
+ }
+
+ /* Check that all the trailing characters are spaces. */
+ while (*parse_end != '\0') {
+ if (!g_ascii_isspace (*parse_end++)) {
+ return FALSE;
+ }
+ }
+
+ /* Return the result. */
+ *integer = result;
+ return TRUE;
+}
+
+/**
+ * ephy_str_strip_chr:
+ * Remove all occurrences of a character from a string.
+ *
+ * @source: The string to be stripped.
+ * @remove_this: The char to remove from @source
+ *
+ * Return value: A copy of @source, after removing all occurrences
+ * of @remove_this.
+ */
+char *
+ephy_str_strip_chr (const char *source, char remove_this)
+{
+ char *result, *out;
+ const char *in;
+
+ if (source == NULL) {
+ return NULL;
+ }
+
+ result = g_new (char, strlen (source) + 1);
+ in = source;
+ out = result;
+ do {
+ if (*in != remove_this) {
+ *out++ = *in;
+ }
+ } while (*in++ != '\0');
+
+ return result;
+}
+
+int
+ephy_strcasecmp (const char *string_a, const char *string_b)
+{
+ return g_ascii_strcasecmp (string_a == NULL ? "" : string_a,
+ string_b == NULL ? "" : string_b);
+}
+
+int
+ephy_strcasecmp_compare_func (gconstpointer string_a, gconstpointer string_b)
+{
+ return ephy_strcasecmp ((const char *) string_a,
+ (const char *) string_b);
+}
+
+/**
+ * like strpbrk but ignores chars preceded by slashes, unless the
+ * slash is also preceded by a slash unless that later slash is
+ * preceded by another slash... ;-)
+ */
+static char *
+ephy_strpbrk_unescaped (const char *s, const char *accept)
+{
+ gchar *ret = strpbrk (s, accept);
+
+ if (!ret || ret == s || *(ret - 1) != '\\')
+ {
+ return ret;
+ }
+ else
+ {
+ gchar *c = ret - 1;
+ g_assert (*c == '\\');
+
+ while (c >= s && *c == '\\') c--;
+
+ if ((ret - c) % 2 == 0)
+ {
+ return ephy_strpbrk_unescaped (ret + 1, accept);
+ }
+ else
+ {
+ return ret;
+ }
+ }
+}
+
+/**
+ * like strstr but supports quoting, ignoring matches inside quoted text
+ */
+static char *
+ephy_strstr_with_quotes (const char *haystack, const char *needle,
+ const char *quotes)
+{
+ gchar *quot = ephy_strpbrk_unescaped (haystack, quotes);
+ gchar *ret = strstr (haystack, needle);
+
+ if (!quot || !ret || ret < quot)
+ {
+ return ret;
+ }
+
+ quot = ephy_strpbrk_unescaped (quot + 1, quotes);
+
+ if (quot)
+ {
+ return ephy_strstr_with_quotes (quot + 1, needle, quotes);
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+/**
+ * like strpbrk but supports quoting, ignoring matches inside quoted text
+ */
+static char *
+ephy_strpbrk_with_quotes (const char *haystack, const char *needles,
+ const char *quotes)
+{
+ gchar *quot = ephy_strpbrk_unescaped (haystack, quotes);
+ gchar *ret = strpbrk (haystack, needles);
+
+ if (!quot || !ret || ret < quot)
+ {
+ return ret;
+ }
+
+ quot = ephy_strpbrk_unescaped (quot + 1, quotes);
+
+ if (quot)
+ {
+ return ephy_strpbrk_with_quotes (quot + 1, needles, quotes);
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+/**
+ * Like g_strsplit, but does not split tokens betwen quotes. Ignores
+ * quotes preceded by '\'.
+ */
+gchar **
+ephy_strsplit_with_quotes (const gchar *string,
+ const gchar *delimiter,
+ gint max_tokens,
+ const gchar *quotes)
+{
+ GSList *string_list = NULL, *slist;
+ gchar **str_array, *s;
+ guint n = 0;
+ const gchar *remainder;
+
+ g_return_val_if_fail (string != NULL, NULL);
+ g_return_val_if_fail (delimiter != NULL, NULL);
+ g_return_val_if_fail (delimiter[0] != '\0', NULL);
+
+ if (quotes == NULL)
+ {
+ return g_strsplit (string, delimiter, max_tokens);
+ }
+
+ if (max_tokens < 1)
+ {
+ max_tokens = G_MAXINT;
+ }
+
+ remainder = string;
+ s = ephy_strstr_with_quotes (remainder, delimiter, quotes);
+ if (s)
+ {
+ gsize delimiter_len = strlen (delimiter);
+
+ while (--max_tokens && s)
+ {
+ gsize len;
+ gchar *new_string;
+
+ len = s - remainder;
+ new_string = g_new (gchar, len + 1);
+ strncpy (new_string, remainder, len);
+ new_string[len] = 0;
+ string_list = g_slist_prepend (string_list, new_string);
+ n++;
+ remainder = s + delimiter_len;
+ s = ephy_strstr_with_quotes (remainder, delimiter, quotes);
+ }
+ }
+ if (*string)
+ {
+ n++;
+ string_list = g_slist_prepend (string_list, g_strdup (remainder));
+ }
+
+ str_array = g_new (gchar*, n + 1);
+
+ str_array[n--] = NULL;
+ for (slist = string_list; slist; slist = slist->next)
+ {
+ str_array[n--] = slist->data;
+ }
+
+ g_slist_free (string_list);
+
+ return str_array;
+}
+
+/**
+ * like ephy_strsplit_with_quotes, but matches any char in 'delimiters' as delimiter
+ * and does not return empty tokens
+ */
+gchar **
+ephy_strsplit_multiple_delimiters_with_quotes (const gchar *string,
+ const gchar *delimiters,
+ gint max_tokens,
+ const gchar *quotes)
+{
+ GSList *string_list = NULL, *slist;
+ gchar **str_array, *s;
+ guint n = 0;
+ const gchar *remainder;
+
+ g_return_val_if_fail (string != NULL, NULL);
+ g_return_val_if_fail (delimiters != NULL, NULL);
+ g_return_val_if_fail (delimiters[0] != '\0', NULL);
+
+ if (quotes == NULL)
+ {
+ quotes = "";
+ }
+
+ if (max_tokens < 1)
+ {
+ max_tokens = G_MAXINT;
+ }
+
+ remainder = string;
+ s = ephy_strpbrk_with_quotes (remainder, delimiters, quotes);
+ if (s)
+ {
+ const gsize delimiter_len = 1; /* only chars */
+
+ while (--max_tokens && s)
+ {
+ gsize len;
+ gchar *new_string;
+
+ len = s - remainder;
+ if (len > 0) /* ignore empty strings */
+ {
+ new_string = g_new (gchar, len + 1);
+ strncpy (new_string, remainder, len);
+ new_string[len] = 0;
+ string_list = g_slist_prepend (string_list, new_string);
+ n++;
+ }
+ remainder = s + delimiter_len;
+ s = ephy_strpbrk_with_quotes (remainder, delimiters, quotes);
+ }
+ }
+ if (*string)
+ {
+ n++;
+ string_list = g_slist_prepend (string_list, g_strdup (remainder));
+ }
+
+ str_array = g_new (gchar*, n + 1);
+
+ str_array[n--] = NULL;
+ for (slist = string_list; slist; slist = slist->next)
+ {
+ str_array[n--] = slist->data;
+ }
+
+ g_slist_free (string_list);
+
+ return str_array;
+}
diff --git a/lib/ephy-string.h b/lib/ephy-string.h
new file mode 100644
index 000000000..cc60bd638
--- /dev/null
+++ b/lib/ephy-string.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2002 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.
+ */
+
+#ifndef EPHY_STRING_H
+#define EPHY_STRING_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+char * ephy_string_double_underscores (const char *string);
+
+void ephy_string_store_time_in_string (GDate *t,
+ gchar *str,
+ const char *format);
+
+gchar *ephy_string_time_to_string (GDate *t,
+ const char *format);
+
+gboolean ephy_str_to_int (const char *string,
+ int *integer);
+
+char *ephy_str_strip_chr (const char *source,
+ char remove_this);
+
+int ephy_strcasecmp (const char *string_a,
+ const char *string_b);
+
+int ephy_strcasecmp_compare_func (gconstpointer string_a,
+ gconstpointer string_b);
+
+char **ephy_strsplit_with_quotes (const gchar *string,
+ const gchar *delimiter,
+ gint max_tokens,
+ const gchar *quotes);
+
+gchar *ephy_string_shorten (const gchar *str,
+ gint target_length);
+
+char **ephy_strsplit_multiple_delimiters_with_quotes (const gchar *string,
+ const gchar *delimiters,
+ gint max_tokens,
+ const gchar *quotes);
+
+
+G_END_DECLS
+
+#endif
diff --git a/lib/ephy-thread-helpers.c b/lib/ephy-thread-helpers.c
new file mode 100644
index 000000000..720358968
--- /dev/null
+++ b/lib/ephy-thread-helpers.c
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2002 Jorn Baayen
+ *
+ * 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.
+ *
+ * $Id$
+ */
+
+#include "ephy-thread-helpers.h"
+
+static GThread *main_thread = NULL;
+
+void
+ephy_thread_helpers_init (void)
+{
+ main_thread = g_thread_self ();
+}
+
+gboolean
+ephy_thread_helpers_in_main_thread (void)
+{
+ return (main_thread == g_thread_self ());
+}
diff --git a/lib/ephy-thread-helpers.h b/lib/ephy-thread-helpers.h
new file mode 100644
index 000000000..2b23156a3
--- /dev/null
+++ b/lib/ephy-thread-helpers.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2002 Jorn Baayen
+ *
+ * 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.
+ *
+ * $Id$
+ */
+
+#include <glib/gthread.h>
+
+#ifndef EPHY_THREAD_HELPERS_H
+#define EPHY_THREAD_HELPERS_H
+
+G_BEGIN_DECLS
+
+void ephy_thread_helpers_init (void);
+
+gboolean ephy_thread_helpers_in_main_thread (void);
+
+G_END_DECLS
+
+#endif /* EPHY_THREAD_HELPERS_H */
diff --git a/lib/ephy-types.h b/lib/ephy-types.h
new file mode 100644
index 000000000..70ce681e0
--- /dev/null
+++ b/lib/ephy-types.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2000, 2001, 2002 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.
+ */
+
+#ifndef EPHY_TYPES_H
+#define EPHY_TYPES_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ G_OK,
+ G_FAILED,
+ G_NOT_IMPLEMENTED
+} gresult;
+
+G_END_DECLS
+
+#endif
diff --git a/lib/toolbar/.cvsignore b/lib/toolbar/.cvsignore
new file mode 100644
index 000000000..20e4cb0c8
--- /dev/null
+++ b/lib/toolbar/.cvsignore
@@ -0,0 +1,6 @@
+Makefile
+Makefile.in
+*.lo
+.deps
+.libs
+*.la
diff --git a/lib/toolbar/Makefile.am b/lib/toolbar/Makefile.am
new file mode 100644
index 000000000..a6affaa04
--- /dev/null
+++ b/lib/toolbar/Makefile.am
@@ -0,0 +1,41 @@
+INCLUDES = \
+ -I$(top_srcdir)/lib \
+ -I$(top_srcdir)/lib/widgets \
+ $(WARN_CFLAGS) \
+ $(EPIPHANY_DEPENDENCY_CFLAGS) \
+ -DSHARE_DIR=\"$(pkgdatadir)\" \
+ -DG_DISABLE_DEPRECATED \
+ -DGDK_DISABLE_DEPRECATED \
+ -DGTK_DISABLE_DEPRECATED \
+ -DGDK_PIXBUF_DISABLE_DEPRECATED \
+ -DGNOME_DISABLE_DEPRECATED
+
+noinst_LTLIBRARIES = libephytoolbar.la
+
+libephytoolbar_la_SOURCES = \
+ ephy-toolbar.h \
+ ephy-toolbar.c \
+ ephy-toolbar-item.h \
+ ephy-toolbar-item.c \
+ ephy-toolbar-item-factory.h \
+ ephy-toolbar-item-factory.c \
+ ephy-tbi-zoom.h \
+ ephy-tbi-zoom.c \
+ ephy-tbi-separator.h \
+ ephy-tbi-separator.c \
+ ephy-tbi-favicon.h \
+ ephy-tbi-favicon.c \
+ ephy-tbi-spinner.h \
+ ephy-tbi-spinner.c \
+ ephy-tbi-location.h \
+ ephy-tbi-location.c \
+ ephy-tbi-navigation-history.h \
+ ephy-tbi-navigation-history.c \
+ ephy-tbi-std-toolitem.h \
+ ephy-tbi-std-toolitem.c \
+ ephy-toolbar-bonobo-view.c \
+ ephy-toolbar-bonobo-view.h \
+ ephy-toolbar-tree-model.h \
+ ephy-toolbar-tree-model.c \
+ ephy-toolbar-editor.h \
+ ephy-toolbar-editor.c
diff --git a/lib/toolbar/ephy-tbi-favicon.c b/lib/toolbar/ephy-tbi-favicon.c
new file mode 100644
index 000000000..d072e3ec6
--- /dev/null
+++ b/lib/toolbar/ephy-tbi-favicon.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <libgnome/gnome-i18n.h>
+#include <gtk/gtkeventbox.h>
+
+#include "ephy-gobject-misc.h"
+#include "ephy-marshal.h"
+#include "ephy-bonobo-extensions.h"
+#include "ephy-tbi-favicon.h"
+
+#define NOT_IMPLEMENTED g_warning ("not implemented: " G_STRLOC);
+//#define DEBUG_MSG(x) g_print x
+#define DEBUG_MSG(x)
+
+/**
+ * Private data
+ */
+struct _EphyTbiFaviconPrivate
+{
+ GtkWidget *widget;
+};
+
+/**
+ * Private functions, only availble from this file
+ */
+static void ephy_tbi_favicon_class_init (EphyTbiFaviconClass *klass);
+static void ephy_tbi_favicon_init (EphyTbiFavicon *tb);
+static void ephy_tbi_favicon_finalize_impl (GObject *o);
+static GtkWidget * ephy_tbi_favicon_get_widget_impl (EphyTbItem *i);
+static GdkPixbuf * ephy_tbi_favicon_get_icon_impl (EphyTbItem *i);
+static gchar * ephy_tbi_favicon_get_name_human_impl (EphyTbItem *i);
+static gchar * ephy_tbi_favicon_to_string_impl (EphyTbItem *i);
+static gboolean ephy_tbi_favicon_is_unique_impl (EphyTbItem *i);
+static EphyTbItem * ephy_tbi_favicon_clone_impl (EphyTbItem *i);
+static void ephy_tbi_favicon_parse_properties_impl (EphyTbItem *i, const gchar *props);
+static void ephy_tbi_favicon_add_to_bonobo_tb_impl (EphyTbItem *i,
+ BonoboUIComponent *ui,
+ const char *container_path,
+ guint index);
+
+static gpointer ephy_tb_item_class;
+
+/**
+ * TbiFavicon object
+ */
+
+MAKE_GET_TYPE (ephy_tbi_favicon, "EphyTbiFavicon", EphyTbiFavicon, ephy_tbi_favicon_class_init,
+ ephy_tbi_favicon_init, EPHY_TYPE_TB_ITEM);
+
+static void
+ephy_tbi_favicon_class_init (EphyTbiFaviconClass *klass)
+{
+ G_OBJECT_CLASS (klass)->finalize = ephy_tbi_favicon_finalize_impl;
+
+ EPHY_TB_ITEM_CLASS (klass)->get_widget = ephy_tbi_favicon_get_widget_impl;
+ EPHY_TB_ITEM_CLASS (klass)->get_icon = ephy_tbi_favicon_get_icon_impl;
+ EPHY_TB_ITEM_CLASS (klass)->get_name_human = ephy_tbi_favicon_get_name_human_impl;
+ EPHY_TB_ITEM_CLASS (klass)->to_string = ephy_tbi_favicon_to_string_impl;
+ EPHY_TB_ITEM_CLASS (klass)->is_unique = ephy_tbi_favicon_is_unique_impl;
+ EPHY_TB_ITEM_CLASS (klass)->clone = ephy_tbi_favicon_clone_impl;
+ EPHY_TB_ITEM_CLASS (klass)->parse_properties = ephy_tbi_favicon_parse_properties_impl;
+ EPHY_TB_ITEM_CLASS (klass)->add_to_bonobo_tb = ephy_tbi_favicon_add_to_bonobo_tb_impl;
+
+ ephy_tb_item_class = g_type_class_peek_parent (klass);
+}
+
+static void
+ephy_tbi_favicon_init (EphyTbiFavicon *tb)
+{
+ EphyTbiFaviconPrivate *p = g_new0 (EphyTbiFaviconPrivate, 1);
+ tb->priv = p;
+}
+
+EphyTbiFavicon *
+ephy_tbi_favicon_new (void)
+{
+ EphyTbiFavicon *ret = g_object_new (EPHY_TYPE_TBI_FAVICON, NULL);
+ return ret;
+}
+
+static void
+ephy_tbi_favicon_finalize_impl (GObject *o)
+{
+ EphyTbiFavicon *it = EPHY_TBI_FAVICON (o);
+ EphyTbiFaviconPrivate *p = it->priv;
+
+ if (p->widget)
+ {
+ g_object_unref (p->widget);
+ }
+
+ g_free (p);
+
+ DEBUG_MSG (("EphyTbiFavicon finalized\n"));
+
+ G_OBJECT_CLASS (ephy_tb_item_class)->finalize (o);
+}
+
+static GtkWidget *
+ephy_tbi_favicon_get_widget_impl (EphyTbItem *i)
+{
+ EphyTbiFavicon *iz = EPHY_TBI_FAVICON (i);
+ EphyTbiFaviconPrivate *p = iz->priv;
+
+ if (!p->widget)
+ {
+ /* here, we create only the event_box. */
+ p->widget = gtk_event_box_new ();
+ g_object_ref (p->widget);
+ gtk_object_sink (GTK_OBJECT (p->widget));
+ }
+
+ return p->widget;
+}
+
+static GdkPixbuf *
+ephy_tbi_favicon_get_icon_impl (EphyTbItem *i)
+{
+ /* need an icon for this */
+ return NULL;
+}
+
+static gchar *
+ephy_tbi_favicon_get_name_human_impl (EphyTbItem *i)
+{
+ return g_strdup (_("Drag Handle"));
+}
+
+static gchar *
+ephy_tbi_favicon_to_string_impl (EphyTbItem *i)
+{
+ /* if it had any properties, the string should include them */
+ return g_strdup_printf ("%s=favicon", i->id);
+}
+
+static gboolean
+ephy_tbi_favicon_is_unique_impl (EphyTbItem *i)
+{
+ return TRUE;
+}
+
+static EphyTbItem *
+ephy_tbi_favicon_clone_impl (EphyTbItem *i)
+{
+ EphyTbItem *ret = EPHY_TB_ITEM (ephy_tbi_favicon_new ());
+
+ ephy_tb_item_set_id (ret, i->id);
+
+ /* should copy properties too, if any */
+
+ return ret;
+}
+
+static void
+ephy_tbi_favicon_add_to_bonobo_tb_impl (EphyTbItem *i, BonoboUIComponent *ui,
+ const char *container_path, guint index)
+{
+ GtkWidget *w = ephy_tb_item_get_widget (i);
+ gtk_widget_show (w);
+ ephy_bonobo_add_numbered_control (ui, w, index, container_path);
+}
+
+static void
+ephy_tbi_favicon_parse_properties_impl (EphyTbItem *it, const gchar *props)
+{
+ /* we have no properties */
+}
+
diff --git a/lib/toolbar/ephy-tbi-favicon.h b/lib/toolbar/ephy-tbi-favicon.h
new file mode 100644
index 000000000..7cea6f634
--- /dev/null
+++ b/lib/toolbar/ephy-tbi-favicon.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifndef EPHY_TBI_FAVICON_H
+#define EPHY_TBI_FAVICON_H
+
+#include "ephy-toolbar-item.h"
+
+G_BEGIN_DECLS
+
+/* object forward declarations */
+
+typedef struct _EphyTbiFavicon EphyTbiFavicon;
+typedef struct _EphyTbiFaviconClass EphyTbiFaviconClass;
+typedef struct _EphyTbiFaviconPrivate EphyTbiFaviconPrivate;
+
+/**
+ * TbiFavicon object
+ */
+
+#define EPHY_TYPE_TBI_FAVICON (ephy_tbi_favicon_get_type())
+#define EPHY_TBI_FAVICON(object) (G_TYPE_CHECK_INSTANCE_CAST((object), EPHY_TYPE_TBI_FAVICON,\
+ EphyTbiFavicon))
+#define EPHY_TBI_FAVICON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EPHY_TYPE_TBI_FAVICON,\
+ EphyTbiFaviconClass))
+#define EPHY_IS_TBI_FAVICON(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), EPHY_TYPE_TBI_FAVICON))
+#define EPHY_IS_TBI_FAVICON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EPHY_TYPE_TBI_FAVICON))
+#define EPHY_TBI_FAVICON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPHY_TYPE_TBI_FAVICON,\
+ EphyTbiFaviconClass))
+
+struct _EphyTbiFaviconClass
+{
+ EphyTbItemClass parent_class;
+};
+
+/* Remember: fields are public read-only */
+struct _EphyTbiFavicon
+{
+ EphyTbItem parent_object;
+
+ EphyTbiFaviconPrivate *priv;
+};
+
+/* this class is abstract */
+
+GType ephy_tbi_favicon_get_type (void);
+EphyTbiFavicon * ephy_tbi_favicon_new (void);
+
+G_END_DECLS
+
+#endif
diff --git a/lib/toolbar/ephy-tbi-location.c b/lib/toolbar/ephy-tbi-location.c
new file mode 100644
index 000000000..0ccbaf83a
--- /dev/null
+++ b/lib/toolbar/ephy-tbi-location.c
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <libgnome/gnome-i18n.h>
+#include "ephy-gobject-misc.h"
+#include "ephy-marshal.h"
+#include "ephy-bonobo-extensions.h"
+#include "ephy-tbi-location.h"
+#include "ephy-location-entry.h"
+
+#define NOT_IMPLEMENTED g_warning ("not implemented: " G_STRLOC);
+//#define DEBUG_MSG(x) g_print x
+#define DEBUG_MSG(x)
+
+/**
+ * Private data
+ */
+struct _EphyTbiLocationPrivate
+{
+ GtkWidget *widget;
+};
+
+/**
+ * Private functions, only availble from this file
+ */
+static void ephy_tbi_location_class_init (EphyTbiLocationClass *klass);
+static void ephy_tbi_location_init (EphyTbiLocation *tb);
+static void ephy_tbi_location_finalize_impl (GObject *o);
+static GtkWidget * ephy_tbi_location_get_widget_impl (EphyTbItem *i);
+static GdkPixbuf * ephy_tbi_location_get_icon_impl (EphyTbItem *i);
+static gchar * ephy_tbi_location_get_name_human_impl (EphyTbItem *i);
+static gchar * ephy_tbi_location_to_string_impl (EphyTbItem *i);
+static gboolean ephy_tbi_location_is_unique_impl (EphyTbItem *i);
+static EphyTbItem * ephy_tbi_location_clone_impl (EphyTbItem *i);
+static void ephy_tbi_location_parse_properties_impl (EphyTbItem *i, const gchar *props);
+static void ephy_tbi_location_add_to_bonobo_tb_impl (EphyTbItem *i,
+ BonoboUIComponent *ui,
+ const char *container_path,
+ guint index);
+
+static gpointer ephy_tb_item_class;
+
+/**
+ * TbiLocation object
+ */
+
+MAKE_GET_TYPE (ephy_tbi_location, "EphyTbiLocation", EphyTbiLocation, ephy_tbi_location_class_init,
+ ephy_tbi_location_init, EPHY_TYPE_TB_ITEM);
+
+static void
+ephy_tbi_location_class_init (EphyTbiLocationClass *klass)
+{
+ G_OBJECT_CLASS (klass)->finalize = ephy_tbi_location_finalize_impl;
+
+ EPHY_TB_ITEM_CLASS (klass)->get_widget = ephy_tbi_location_get_widget_impl;
+ EPHY_TB_ITEM_CLASS (klass)->get_icon = ephy_tbi_location_get_icon_impl;
+ EPHY_TB_ITEM_CLASS (klass)->get_name_human = ephy_tbi_location_get_name_human_impl;
+ EPHY_TB_ITEM_CLASS (klass)->to_string = ephy_tbi_location_to_string_impl;
+ EPHY_TB_ITEM_CLASS (klass)->is_unique = ephy_tbi_location_is_unique_impl;
+ EPHY_TB_ITEM_CLASS (klass)->clone = ephy_tbi_location_clone_impl;
+ EPHY_TB_ITEM_CLASS (klass)->parse_properties = ephy_tbi_location_parse_properties_impl;
+ EPHY_TB_ITEM_CLASS (klass)->add_to_bonobo_tb = ephy_tbi_location_add_to_bonobo_tb_impl;
+
+ ephy_tb_item_class = g_type_class_peek_parent (klass);
+}
+
+static void
+ephy_tbi_location_init (EphyTbiLocation *tb)
+{
+ EphyTbiLocationPrivate *p = g_new0 (EphyTbiLocationPrivate, 1);
+ tb->priv = p;
+}
+
+EphyTbiLocation *
+ephy_tbi_location_new (void)
+{
+ EphyTbiLocation *ret = g_object_new (EPHY_TYPE_TBI_LOCATION, NULL);
+ return ret;
+}
+
+static void
+ephy_tbi_location_finalize_impl (GObject *o)
+{
+ EphyTbiLocation *it = EPHY_TBI_LOCATION (o);
+ EphyTbiLocationPrivate *p = it->priv;
+
+ if (p->widget)
+ {
+ g_object_unref (p->widget);
+ }
+
+ g_free (p);
+
+ DEBUG_MSG (("EphyTbiLocation finalized\n"));
+
+ G_OBJECT_CLASS (ephy_tb_item_class)->finalize (o);
+}
+
+static GtkWidget *
+ephy_tbi_location_get_widget_impl (EphyTbItem *i)
+{
+ EphyTbiLocation *iz = EPHY_TBI_LOCATION (i);
+ EphyTbiLocationPrivate *p = iz->priv;
+
+ if (!p->widget)
+ {
+ p->widget = GTK_WIDGET (ephy_location_entry_new ());
+ g_object_ref (p->widget);
+ gtk_object_sink (GTK_OBJECT (p->widget));
+ }
+
+ return p->widget;
+}
+
+static GdkPixbuf *
+ephy_tbi_location_get_icon_impl (EphyTbItem *i)
+{
+ return NULL;
+}
+
+static gchar *
+ephy_tbi_location_get_name_human_impl (EphyTbItem *i)
+{
+ return g_strdup (_("Location entry"));
+}
+
+static gchar *
+ephy_tbi_location_to_string_impl (EphyTbItem *i)
+{
+ /* if it had any properties, the string should include them */
+ return g_strdup_printf ("%s=location", i->id);
+}
+
+static gboolean
+ephy_tbi_location_is_unique_impl (EphyTbItem *i)
+{
+ return TRUE;
+}
+
+static EphyTbItem *
+ephy_tbi_location_clone_impl (EphyTbItem *i)
+{
+ EphyTbItem *ret = EPHY_TB_ITEM (ephy_tbi_location_new ());
+
+ ephy_tb_item_set_id (ret, i->id);
+
+ /* should copy properties too, if any */
+ /* the location value is not copied, not sure if it should... */
+
+ return ret;
+}
+
+static void
+ephy_tbi_location_add_to_bonobo_tb_impl (EphyTbItem *i, BonoboUIComponent *uic,
+ const char *container_path, guint index)
+{
+ GtkWidget *w = ephy_tb_item_get_widget (i);
+ BonoboControl *control;
+ char *xml_string, *control_path;
+
+ gtk_widget_show (w);
+
+ g_return_if_fail (BONOBO_IS_UI_COMPONENT (uic));
+ g_return_if_fail (container_path != NULL);
+
+ xml_string = g_strdup_printf ("<control name=\"location\" behavior=\"expandable\"/>");
+
+ bonobo_ui_component_set (uic, container_path, xml_string, NULL);
+
+ g_free (xml_string);
+
+ control_path = g_strconcat (container_path, "/location", NULL);
+
+ control = bonobo_control_new (w);
+ bonobo_ui_component_object_set (uic, control_path, BONOBO_OBJREF (control), NULL);
+ bonobo_object_unref (control);
+
+ g_free (control_path);
+}
+
+static void
+ephy_tbi_location_parse_properties_impl (EphyTbItem *it, const gchar *props)
+{
+ /* we have no properties */
+}
+
diff --git a/lib/toolbar/ephy-tbi-location.h b/lib/toolbar/ephy-tbi-location.h
new file mode 100644
index 000000000..9188463f6
--- /dev/null
+++ b/lib/toolbar/ephy-tbi-location.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifndef EPHY_TBI_LOCATION_H
+#define EPHY_TBI_LOCATION_H
+
+#include "ephy-toolbar-item.h"
+
+G_BEGIN_DECLS
+
+/* object forward declarations */
+
+typedef struct _EphyTbiLocation EphyTbiLocation;
+typedef struct _EphyTbiLocationClass EphyTbiLocationClass;
+typedef struct _EphyTbiLocationPrivate EphyTbiLocationPrivate;
+
+/**
+ * TbiLocation object
+ */
+
+#define EPHY_TYPE_TBI_LOCATION (ephy_tbi_location_get_type())
+#define EPHY_TBI_LOCATION(object) (G_TYPE_CHECK_INSTANCE_CAST((object), EPHY_TYPE_TBI_LOCATION,\
+ EphyTbiLocation))
+#define EPHY_TBI_LOCATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EPHY_TYPE_TBI_LOCATION,\
+ EphyTbiLocationClass))
+#define EPHY_IS_TBI_LOCATION(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), EPHY_TYPE_TBI_LOCATION))
+#define EPHY_IS_TBI_LOCATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EPHY_TYPE_TBI_LOCATION))
+#define EPHY_TBI_LOCATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPHY_TYPE_TBI_LOCATION,\
+ EphyTbiLocationClass))
+
+struct _EphyTbiLocationClass
+{
+ EphyTbItemClass parent_class;
+};
+
+/* Remember: fields are public read-only */
+struct _EphyTbiLocation
+{
+ EphyTbItem parent_object;
+
+ EphyTbiLocationPrivate *priv;
+};
+
+/* this class is abstract */
+
+GType ephy_tbi_location_get_type (void);
+EphyTbiLocation * ephy_tbi_location_new (void);
+
+G_END_DECLS
+
+#endif
diff --git a/lib/toolbar/ephy-tbi-navigation-history.c b/lib/toolbar/ephy-tbi-navigation-history.c
new file mode 100644
index 000000000..c6edc9865
--- /dev/null
+++ b/lib/toolbar/ephy-tbi-navigation-history.c
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <libgnome/gnome-i18n.h>
+#include <bonobo/bonobo-ui-toolbar-button-item.h>
+#include <bonobo/bonobo-property-bag.h>
+#include <gtk/gtktogglebutton.h>
+#include <gtk/gtkstock.h>
+#include <string.h>
+
+#include "ephy-tbi-navigation-history.h"
+#include "ephy-gobject-misc.h"
+#include "ephy-marshal.h"
+#include "ephy-bonobo-extensions.h"
+
+#define NOT_IMPLEMENTED g_warning ("not implemented: " G_STRLOC);
+//#define DEBUG_MSG(x) g_print x
+#define DEBUG_MSG(x)
+
+/**
+ * Private data
+ */
+struct _EphyTbiNavigationHistoryPrivate
+{
+ GtkWidget *widget;
+
+ EphyTbiNavigationHistoryDirection direction;
+};
+
+enum
+{
+ TOOLBAR_ITEM_STYLE_PROP,
+ TOOLBAR_ITEM_ORIENTATION_PROP,
+ TOOLBAR_ITEM_PRIORITY_PROP
+};
+
+/**
+ * Private functions, only availble from this file
+ */
+static void ephy_tbi_navigation_history_class_init (EphyTbiNavigationHistoryClass *klass);
+static void ephy_tbi_navigation_history_init (EphyTbiNavigationHistory *tb);
+static void ephy_tbi_navigation_history_finalize_impl (GObject *o);
+static GtkWidget * ephy_tbi_navigation_history_get_widget_impl (EphyTbItem *i);
+static GdkPixbuf * ephy_tbi_navigation_history_get_icon_impl (EphyTbItem *i);
+static gchar * ephy_tbi_navigation_history_get_name_human_impl (EphyTbItem *i);
+static gchar * ephy_tbi_navigation_history_to_string_impl (EphyTbItem *i);
+static gboolean ephy_tbi_navigation_history_is_unique_impl (EphyTbItem *i);
+static EphyTbItem * ephy_tbi_navigation_history_clone_impl (EphyTbItem *i);
+static void ephy_tbi_navigation_history_parse_properties_impl (EphyTbItem *i, const gchar *props);
+static void ephy_tbi_navigation_history_add_to_bonobo_tb_impl (EphyTbItem *i,
+ BonoboUIComponent *ui,
+ const char *container_path,
+ guint index);
+
+static gpointer ephy_tb_item_class;
+
+/**
+ * TbiNavigationHistory object
+ */
+
+MAKE_GET_TYPE (ephy_tbi_navigation_history, "EphyTbiNavigationHistory", EphyTbiNavigationHistory,
+ ephy_tbi_navigation_history_class_init,
+ ephy_tbi_navigation_history_init, EPHY_TYPE_TB_ITEM);
+
+static void
+ephy_tbi_navigation_history_class_init (EphyTbiNavigationHistoryClass *klass)
+{
+ G_OBJECT_CLASS (klass)->finalize = ephy_tbi_navigation_history_finalize_impl;
+
+ EPHY_TB_ITEM_CLASS (klass)->get_widget = ephy_tbi_navigation_history_get_widget_impl;
+ EPHY_TB_ITEM_CLASS (klass)->get_icon = ephy_tbi_navigation_history_get_icon_impl;
+ EPHY_TB_ITEM_CLASS (klass)->get_name_human = ephy_tbi_navigation_history_get_name_human_impl;
+ EPHY_TB_ITEM_CLASS (klass)->to_string = ephy_tbi_navigation_history_to_string_impl;
+ EPHY_TB_ITEM_CLASS (klass)->is_unique = ephy_tbi_navigation_history_is_unique_impl;
+ EPHY_TB_ITEM_CLASS (klass)->clone = ephy_tbi_navigation_history_clone_impl;
+ EPHY_TB_ITEM_CLASS (klass)->parse_properties = ephy_tbi_navigation_history_parse_properties_impl;
+ EPHY_TB_ITEM_CLASS (klass)->add_to_bonobo_tb = ephy_tbi_navigation_history_add_to_bonobo_tb_impl;
+
+ ephy_tb_item_class = g_type_class_peek_parent (klass);
+}
+
+static void
+ephy_tbi_navigation_history_init (EphyTbiNavigationHistory *tb)
+{
+ EphyTbiNavigationHistoryPrivate *p = g_new0 (EphyTbiNavigationHistoryPrivate, 1);
+ tb->priv = p;
+
+ p->direction = EPHY_TBI_NAVIGATION_HISTORY_BACK;
+}
+
+EphyTbiNavigationHistory *
+ephy_tbi_navigation_history_new (void)
+{
+ EphyTbiNavigationHistory *ret = g_object_new (EPHY_TYPE_TBI_NAVIGATION_HISTORY, NULL);
+ return ret;
+}
+
+static void
+ephy_tbi_navigation_history_finalize_impl (GObject *o)
+{
+ EphyTbiNavigationHistory *it = EPHY_TBI_NAVIGATION_HISTORY (o);
+ EphyTbiNavigationHistoryPrivate *p = it->priv;
+
+ if (p->widget)
+ {
+ g_object_unref (p->widget);
+ }
+
+ g_free (p);
+
+ DEBUG_MSG (("EphyTbiNavigationHistory finalized\n"));
+
+ G_OBJECT_CLASS (ephy_tb_item_class)->finalize (o);
+}
+
+static GtkWidget *
+ephy_tbi_navigation_history_get_widget_impl (EphyTbItem *i)
+{
+ EphyTbiNavigationHistory *iz = EPHY_TBI_NAVIGATION_HISTORY (i);
+ EphyTbiNavigationHistoryPrivate *p = iz->priv;
+
+ DEBUG_MSG (("in ephy_tbi_navigation_history_get_widget_impl\n"));
+ if (!p->widget)
+ {
+ DEBUG_MSG (("in ephy_tbi_navigation_history_get_widget_impl, really\n"));
+
+ p->widget = gtk_toggle_button_new ();
+ gtk_button_set_relief (GTK_BUTTON (p->widget), GTK_RELIEF_NONE);
+
+ gtk_container_add (GTK_CONTAINER (p->widget),
+ gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_OUT));
+
+ g_object_ref (p->widget);
+ gtk_object_sink (GTK_OBJECT (p->widget));
+ }
+
+ return p->widget;
+}
+
+static GdkPixbuf *
+ephy_tbi_navigation_history_get_icon_impl (EphyTbItem *i)
+{
+ return NULL;
+}
+
+static gchar *
+ephy_tbi_navigation_history_get_name_human_impl (EphyTbItem *i)
+{
+ EphyTbiNavigationHistoryPrivate *p = EPHY_TBI_NAVIGATION_HISTORY (i)->priv;
+ const gchar *ret;
+
+ switch (p->direction)
+ {
+ case EPHY_TBI_NAVIGATION_HISTORY_BACK:
+ ret = _("Back History");
+ break;
+ case EPHY_TBI_NAVIGATION_HISTORY_FORWARD:
+ ret = _("Forward History");
+ break;
+ case EPHY_TBI_NAVIGATION_HISTORY_UP:
+ ret = _("Up Several Levels");
+ break;
+ default:
+ g_assert_not_reached ();
+ ret = "unknown";
+ }
+
+ return g_strdup (ret);
+}
+
+static gchar *
+ephy_tbi_navigation_history_to_string_impl (EphyTbItem *i)
+{
+ EphyTbiNavigationHistoryPrivate *p = EPHY_TBI_NAVIGATION_HISTORY (i)->priv;
+
+ /* if it had any properties, the string should include them */
+ const char *sdir;
+
+ switch (p->direction)
+ {
+ case EPHY_TBI_NAVIGATION_HISTORY_BACK:
+ sdir = "back";
+ break;
+ case EPHY_TBI_NAVIGATION_HISTORY_FORWARD:
+ sdir = "forward";
+ break;
+ case EPHY_TBI_NAVIGATION_HISTORY_UP:
+ sdir = "up";
+ break;
+ default:
+ g_assert_not_reached ();
+ sdir = "unknown";
+ }
+
+ return g_strdup_printf ("%s=navigation_history(direction=%s)", i->id, sdir);
+}
+
+static gboolean
+ephy_tbi_navigation_history_is_unique_impl (EphyTbItem *i)
+{
+ return TRUE;
+}
+
+static EphyTbItem *
+ephy_tbi_navigation_history_clone_impl (EphyTbItem *i)
+{
+ EphyTbiNavigationHistoryPrivate *p = EPHY_TBI_NAVIGATION_HISTORY (i)->priv;
+ EphyTbItem *ret = EPHY_TB_ITEM (ephy_tbi_navigation_history_new ());
+
+ ephy_tb_item_set_id (ret, i->id);
+
+ /* should copy properties too, if any */
+ ephy_tbi_navigation_history_set_direction (EPHY_TBI_NAVIGATION_HISTORY (ret), p->direction);
+
+ return ret;
+}
+
+static void
+ephy_tbi_navigation_history_property_set_cb (BonoboPropertyBag *bag,
+ const BonoboArg *arg,
+ guint arg_id,
+ CORBA_Environment *ev,
+ gpointer user_data)
+{
+ BonoboControl *control;
+ BonoboUIToolbarItem *item;
+ GtkOrientation orientation;
+ BonoboUIToolbarItemStyle style;
+
+ control = BONOBO_CONTROL (user_data);
+ item = BONOBO_UI_TOOLBAR_ITEM (bonobo_control_get_widget (control));
+
+ switch (arg_id) {
+ case TOOLBAR_ITEM_ORIENTATION_PROP:
+ orientation = BONOBO_ARG_GET_INT (arg);
+ bonobo_ui_toolbar_item_set_orientation (item, orientation);
+
+ if (GTK_WIDGET (item)->parent) {
+ gtk_widget_queue_resize (GTK_WIDGET (item)->parent);
+ }
+ break;
+ case TOOLBAR_ITEM_STYLE_PROP:
+ style = BONOBO_ARG_GET_INT (arg);
+ bonobo_ui_toolbar_item_set_style (item, style);
+ break;
+ }
+}
+
+static void
+ephy_tbi_navigation_history_add_to_bonobo_tb_impl (EphyTbItem *i, BonoboUIComponent *ui,
+ const char *container_path, guint index)
+{
+ BonoboPropertyBag *pb;
+ BonoboControl *wrapper;
+ BonoboUIToolbarItem *item;
+ GtkWidget *button;
+
+ DEBUG_MSG (("in ephy_tbi_navigation_history_add_to_bonobo_tb_impl\n"));
+
+ item = BONOBO_UI_TOOLBAR_ITEM (bonobo_ui_toolbar_item_new ());
+
+ button = ephy_tb_item_get_widget (i);
+ gtk_container_add (GTK_CONTAINER (item), button);
+ gtk_widget_show_all (GTK_WIDGET (item));
+
+ wrapper = ephy_bonobo_add_numbered_control (ui, GTK_WIDGET (item), index, container_path);
+
+ pb = bonobo_property_bag_new
+ (NULL, ephy_tbi_navigation_history_property_set_cb, wrapper);
+ bonobo_property_bag_add (pb, "style",
+ TOOLBAR_ITEM_STYLE_PROP,
+ BONOBO_ARG_INT, NULL, NULL,
+ Bonobo_PROPERTY_WRITEABLE);
+ bonobo_property_bag_add (pb, "orientation",
+ TOOLBAR_ITEM_ORIENTATION_PROP,
+ BONOBO_ARG_INT, NULL, NULL,
+ Bonobo_PROPERTY_WRITEABLE);
+ bonobo_control_set_properties (wrapper, BONOBO_OBJREF (pb), NULL);
+ bonobo_object_unref (pb);
+}
+
+static void
+ephy_tbi_navigation_history_parse_properties_impl (EphyTbItem *it, const gchar *props)
+{
+ EphyTbiNavigationHistory *a = EPHY_TBI_NAVIGATION_HISTORY (it);
+
+ /* yes, this is quite hacky, but works */
+
+ /* we have aproperty, the direction */
+ const gchar *direc_prop;
+
+ direc_prop = strstr (props, "direction=");
+ if (direc_prop)
+ {
+ direc_prop += strlen ("direction=");
+ if (!strncmp (direc_prop, "back", 4))
+ {
+ ephy_tbi_navigation_history_set_direction (a, EPHY_TBI_NAVIGATION_HISTORY_BACK);
+ }
+ else if (!strncmp (direc_prop, "forward", 4))
+ {
+ ephy_tbi_navigation_history_set_direction (a, EPHY_TBI_NAVIGATION_HISTORY_FORWARD);
+ }
+ else if (!strncmp (direc_prop, "up", 2))
+ {
+ ephy_tbi_navigation_history_set_direction (a, EPHY_TBI_NAVIGATION_HISTORY_UP);
+ }
+ }
+}
+
+void
+ephy_tbi_navigation_history_set_direction (EphyTbiNavigationHistory *a, EphyTbiNavigationHistoryDirection d)
+{
+ EphyTbiNavigationHistoryPrivate *p = a->priv;
+
+ g_return_if_fail (d == EPHY_TBI_NAVIGATION_HISTORY_UP
+ || d == EPHY_TBI_NAVIGATION_HISTORY_BACK
+ || d == EPHY_TBI_NAVIGATION_HISTORY_FORWARD);
+
+ p->direction = d;
+
+}
+
diff --git a/lib/toolbar/ephy-tbi-navigation-history.h b/lib/toolbar/ephy-tbi-navigation-history.h
new file mode 100644
index 000000000..29cb6c32a
--- /dev/null
+++ b/lib/toolbar/ephy-tbi-navigation-history.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifndef EPHY_TBI_NAVIGATION_HISTORY_H
+#define EPHY_TBI_NAVIGATION_HISTORY_H
+
+#include "ephy-toolbar-item.h"
+
+G_BEGIN_DECLS
+
+/* object forward declarations */
+
+typedef struct _EphyTbiNavigationHistory EphyTbiNavigationHistory;
+typedef struct _EphyTbiNavigationHistoryClass EphyTbiNavigationHistoryClass;
+typedef struct _EphyTbiNavigationHistoryPrivate EphyTbiNavigationHistoryPrivate;
+
+/**
+ * TbiNavigationHistory object
+ */
+
+#define EPHY_TYPE_TBI_NAVIGATION_HISTORY (ephy_tbi_navigation_history_get_type())
+#define EPHY_TBI_NAVIGATION_HISTORY(object) (G_TYPE_CHECK_INSTANCE_CAST((object), \
+ EPHY_TYPE_TBI_NAVIGATION_HISTORY,\
+ EphyTbiNavigationHistory))
+#define EPHY_TBI_NAVIGATION_HISTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \
+ EPHY_TYPE_TBI_NAVIGATION_HISTORY,\
+ EphyTbiNavigationHistoryClass))
+#define EPHY_IS_TBI_NAVIGATION_HISTORY(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), \
+ EPHY_TYPE_TBI_NAVIGATION_HISTORY))
+#define EPHY_IS_TBI_NAVIGATION_HISTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ EPHY_TYPE_TBI_NAVIGATION_HISTORY))
+#define EPHY_TBI_NAVIGATION_HISTORY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \
+ EPHY_TYPE_TBI_NAVIGATION_HISTORY,\
+ EphyTbiNavigationHistoryClass))
+typedef enum
+{
+ EPHY_TBI_NAVIGATION_HISTORY_UP,
+ EPHY_TBI_NAVIGATION_HISTORY_BACK,
+ EPHY_TBI_NAVIGATION_HISTORY_FORWARD
+} EphyTbiNavigationHistoryDirection;
+
+
+struct _EphyTbiNavigationHistoryClass
+{
+ EphyTbItemClass parent_class;
+};
+
+/* Remember: fields are public read-only */
+struct _EphyTbiNavigationHistory
+{
+ EphyTbItem parent_object;
+
+ EphyTbiNavigationHistoryPrivate *priv;
+};
+
+/* this class is abstract */
+
+GType ephy_tbi_navigation_history_get_type (void);
+EphyTbiNavigationHistory *ephy_tbi_navigation_history_new (void);
+void ephy_tbi_navigation_history_set_direction (EphyTbiNavigationHistory *a,
+ EphyTbiNavigationHistoryDirection d);
+
+G_END_DECLS
+
+#endif
+
diff --git a/lib/toolbar/ephy-tbi-separator.c b/lib/toolbar/ephy-tbi-separator.c
new file mode 100644
index 000000000..43f69bd96
--- /dev/null
+++ b/lib/toolbar/ephy-tbi-separator.c
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <libgnome/gnome-i18n.h>
+#include <gtk/gtkstock.h>
+
+#include "ephy-tbi-separator.h"
+#include "ephy-gobject-misc.h"
+#include "ephy-marshal.h"
+#include "ephy-bonobo-extensions.h"
+
+#define NOT_IMPLEMENTED g_warning ("not implemented: " G_STRLOC);
+//#define DEBUG_MSG(x) g_print x
+#define DEBUG_MSG(x)
+
+/**
+ * Private data
+ */
+struct _EphyTbiSeparatorPrivate
+{
+ GtkWidget *widget;
+};
+
+/**
+ * Private functions, only availble from this file
+ */
+static void ephy_tbi_separator_class_init (EphyTbiSeparatorClass *klass);
+static void ephy_tbi_separator_init (EphyTbiSeparator *tb);
+static void ephy_tbi_separator_finalize_impl (GObject *o);
+static GtkWidget * ephy_tbi_separator_get_widget_impl (EphyTbItem *i);
+static GdkPixbuf * ephy_tbi_separator_get_icon_impl (EphyTbItem *i);
+static gchar * ephy_tbi_separator_get_name_human_impl (EphyTbItem *i);
+static gchar * ephy_tbi_separator_to_string_impl (EphyTbItem *i);
+static gboolean ephy_tbi_separator_is_unique_impl (EphyTbItem *i);
+static EphyTbItem * ephy_tbi_separator_clone_impl (EphyTbItem *i);
+static void ephy_tbi_separator_parse_properties_impl(EphyTbItem *i, const gchar *props);
+static void ephy_tbi_separator_add_to_bonobo_tb_impl(EphyTbItem *i,
+ BonoboUIComponent *ui,
+ const char *container_path,
+ guint index);
+
+static gpointer ephy_tb_item_class;
+
+/**
+ * TbiSeparator object
+ */
+
+MAKE_GET_TYPE (ephy_tbi_separator, "EphyTbiSeparator", EphyTbiSeparator, ephy_tbi_separator_class_init,
+ ephy_tbi_separator_init, EPHY_TYPE_TB_ITEM);
+
+static void
+ephy_tbi_separator_class_init (EphyTbiSeparatorClass *klass)
+{
+ G_OBJECT_CLASS (klass)->finalize = ephy_tbi_separator_finalize_impl;
+
+ EPHY_TB_ITEM_CLASS (klass)->get_widget = ephy_tbi_separator_get_widget_impl;
+ EPHY_TB_ITEM_CLASS (klass)->get_icon = ephy_tbi_separator_get_icon_impl;
+ EPHY_TB_ITEM_CLASS (klass)->get_name_human = ephy_tbi_separator_get_name_human_impl;
+ EPHY_TB_ITEM_CLASS (klass)->to_string = ephy_tbi_separator_to_string_impl;
+ EPHY_TB_ITEM_CLASS (klass)->is_unique = ephy_tbi_separator_is_unique_impl;
+ EPHY_TB_ITEM_CLASS (klass)->clone = ephy_tbi_separator_clone_impl;
+ EPHY_TB_ITEM_CLASS (klass)->parse_properties = ephy_tbi_separator_parse_properties_impl;
+ EPHY_TB_ITEM_CLASS (klass)->add_to_bonobo_tb = ephy_tbi_separator_add_to_bonobo_tb_impl;
+
+ ephy_tb_item_class = g_type_class_peek_parent (klass);
+}
+
+static void
+ephy_tbi_separator_init (EphyTbiSeparator *tb)
+{
+ EphyTbiSeparatorPrivate *p = g_new0 (EphyTbiSeparatorPrivate, 1);
+ tb->priv = p;
+}
+
+EphyTbiSeparator *
+ephy_tbi_separator_new (void)
+{
+ EphyTbiSeparator *ret = g_object_new (EPHY_TYPE_TBI_SEPARATOR, NULL);
+ return ret;
+}
+
+static void
+ephy_tbi_separator_finalize_impl (GObject *o)
+{
+ EphyTbiSeparator *it = EPHY_TBI_SEPARATOR (o);
+ EphyTbiSeparatorPrivate *p = it->priv;
+
+ if (p->widget)
+ {
+ g_object_unref (p->widget);
+ }
+
+ g_free (p);
+
+ DEBUG_MSG (("EphyTbiSeparator finalized\n"));
+
+ G_OBJECT_CLASS (ephy_tb_item_class)->finalize (o);
+}
+
+static GtkWidget *
+ephy_tbi_separator_get_widget_impl (EphyTbItem *i)
+{
+ return NULL;
+}
+
+static GdkPixbuf *
+ephy_tbi_separator_get_icon_impl (EphyTbItem *i)
+{
+ return NULL;
+}
+
+static gchar *
+ephy_tbi_separator_get_name_human_impl (EphyTbItem *i)
+{
+ return g_strdup (_("Separator"));
+}
+
+static gchar *
+ephy_tbi_separator_to_string_impl (EphyTbItem *i)
+{
+ /* if it had any properties, the string should include them */
+ return g_strdup_printf ("%s=separator", i->id);
+}
+
+static gboolean
+ephy_tbi_separator_is_unique_impl (EphyTbItem *i)
+{
+ return FALSE;
+}
+
+static EphyTbItem *
+ephy_tbi_separator_clone_impl (EphyTbItem *i)
+{
+ EphyTbItem *ret = EPHY_TB_ITEM (ephy_tbi_separator_new ());
+
+ ephy_tb_item_set_id (ret, i->id);
+
+ /* should copy properties too, if any */
+
+ return ret;
+}
+
+static void
+ephy_tbi_separator_add_to_bonobo_tb_impl (EphyTbItem *i, BonoboUIComponent *ui,
+ const char *container_path, guint index)
+{
+ static gint hack = 0;
+ gchar *xml;
+
+ xml = g_strdup_printf ("<separator name=\"sep%d\"/>", ++hack);
+ bonobo_ui_component_set (ui, container_path, xml, NULL);
+ g_free (xml);
+}
+
+static void
+ephy_tbi_separator_parse_properties_impl (EphyTbItem *it, const gchar *props)
+{
+ /* we have no properties */
+}
+
diff --git a/lib/toolbar/ephy-tbi-separator.h b/lib/toolbar/ephy-tbi-separator.h
new file mode 100644
index 000000000..754d85fec
--- /dev/null
+++ b/lib/toolbar/ephy-tbi-separator.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifndef EPHY_TBI_SEPARATOR_H
+#define EPHY_TBI_SEPARATOR_H
+
+#include "ephy-toolbar-item.h"
+
+G_BEGIN_DECLS
+
+/* object forward declarations */
+
+typedef struct _EphyTbiSeparator EphyTbiSeparator;
+typedef struct _EphyTbiSeparatorClass EphyTbiSeparatorClass;
+typedef struct _EphyTbiSeparatorPrivate EphyTbiSeparatorPrivate;
+
+/**
+ * TbiSeparator object
+ */
+
+#define EPHY_TYPE_TBI_SEPARATOR (ephy_tbi_separator_get_type())
+#define EPHY_TBI_SEPARATOR(object) (G_TYPE_CHECK_INSTANCE_CAST((object), \
+ EPHY_TYPE_TBI_SEPARATOR,\
+ EphyTbiSeparator))
+#define EPHY_TBI_SEPARATOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EPHY_TYPE_TBI_SEPARATOR,\
+ EphyTbiSeparatorClass))
+#define EPHY_IS_TBI_SEPARATOR(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), \
+ EPHY_TYPE_TBI_SEPARATOR))
+#define EPHY_IS_TBI_SEPARATOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EPHY_TYPE_TBI_SEPARATOR))
+#define EPHY_TBI_SEPARATOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPHY_TYPE_TBI_SEPARATOR,\
+ EphyTbiSeparatorClass))
+
+struct _EphyTbiSeparatorClass
+{
+ EphyTbItemClass parent_class;
+};
+
+/* Remember: fields are public read-only */
+struct _EphyTbiSeparator
+{
+ EphyTbItem parent_object;
+
+ EphyTbiSeparatorPrivate *priv;
+};
+
+/* this class is abstract */
+
+GType ephy_tbi_separator_get_type (void);
+EphyTbiSeparator * ephy_tbi_separator_new (void);
+
+G_END_DECLS
+
+#endif
diff --git a/lib/toolbar/ephy-tbi-spinner.c b/lib/toolbar/ephy-tbi-spinner.c
new file mode 100644
index 000000000..6f37764ec
--- /dev/null
+++ b/lib/toolbar/ephy-tbi-spinner.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <libgnome/gnome-i18n.h>
+#include <gtk/gtkhbox.h>
+
+#include "ephy-tbi-spinner.h"
+#include "ephy-gobject-misc.h"
+#include "ephy-marshal.h"
+#include "ephy-bonobo-extensions.h"
+
+#define NOT_IMPLEMENTED g_warning ("not implemented: " G_STRLOC);
+//#define DEBUG_MSG(x) g_print x
+#define DEBUG_MSG(x)
+
+/**
+ * Private data
+ */
+struct _EphyTbiSpinnerPrivate
+{
+ GtkWidget *widget;
+};
+
+/**
+ * Private functions, only availble from this file
+ */
+static void ephy_tbi_spinner_class_init (EphyTbiSpinnerClass *klass);
+static void ephy_tbi_spinner_init (EphyTbiSpinner *tb);
+static void ephy_tbi_spinner_finalize_impl (GObject *o);
+static GtkWidget * ephy_tbi_spinner_get_widget_impl (EphyTbItem *i);
+static GdkPixbuf * ephy_tbi_spinner_get_icon_impl (EphyTbItem *i);
+static gchar * ephy_tbi_spinner_get_name_human_impl (EphyTbItem *i);
+static gchar * ephy_tbi_spinner_to_string_impl (EphyTbItem *i);
+static gboolean ephy_tbi_spinner_is_unique_impl (EphyTbItem *i);
+static EphyTbItem * ephy_tbi_spinner_clone_impl (EphyTbItem *i);
+static void ephy_tbi_spinner_parse_properties_impl (EphyTbItem *i, const gchar *props);
+static void ephy_tbi_spinner_add_to_bonobo_tb_impl (EphyTbItem *i,
+ BonoboUIComponent *ui,
+ const char *container_path,
+ guint index);
+
+static gpointer ephy_tb_item_class;
+
+/**
+ * TbiSpinner object
+ */
+
+MAKE_GET_TYPE (ephy_tbi_spinner, "EphyTbiSpinner", EphyTbiSpinner, ephy_tbi_spinner_class_init,
+ ephy_tbi_spinner_init, EPHY_TYPE_TB_ITEM);
+
+static void
+ephy_tbi_spinner_class_init (EphyTbiSpinnerClass *klass)
+{
+ G_OBJECT_CLASS (klass)->finalize = ephy_tbi_spinner_finalize_impl;
+
+ EPHY_TB_ITEM_CLASS (klass)->get_widget = ephy_tbi_spinner_get_widget_impl;
+ EPHY_TB_ITEM_CLASS (klass)->get_icon = ephy_tbi_spinner_get_icon_impl;
+ EPHY_TB_ITEM_CLASS (klass)->get_name_human = ephy_tbi_spinner_get_name_human_impl;
+ EPHY_TB_ITEM_CLASS (klass)->to_string = ephy_tbi_spinner_to_string_impl;
+ EPHY_TB_ITEM_CLASS (klass)->is_unique = ephy_tbi_spinner_is_unique_impl;
+ EPHY_TB_ITEM_CLASS (klass)->clone = ephy_tbi_spinner_clone_impl;
+ EPHY_TB_ITEM_CLASS (klass)->parse_properties = ephy_tbi_spinner_parse_properties_impl;
+ EPHY_TB_ITEM_CLASS (klass)->add_to_bonobo_tb = ephy_tbi_spinner_add_to_bonobo_tb_impl;
+
+ ephy_tb_item_class = g_type_class_peek_parent (klass);
+}
+
+static void
+ephy_tbi_spinner_init (EphyTbiSpinner *tb)
+{
+ EphyTbiSpinnerPrivate *p = g_new0 (EphyTbiSpinnerPrivate, 1);
+ tb->priv = p;
+}
+
+EphyTbiSpinner *
+ephy_tbi_spinner_new (void)
+{
+ EphyTbiSpinner *ret = g_object_new (EPHY_TYPE_TBI_SPINNER, NULL);
+ return ret;
+}
+
+static void
+ephy_tbi_spinner_finalize_impl (GObject *o)
+{
+ EphyTbiSpinner *it = EPHY_TBI_SPINNER (o);
+ EphyTbiSpinnerPrivate *p = it->priv;
+
+ if (p->widget)
+ {
+ g_object_unref (p->widget);
+ }
+
+ g_free (p);
+
+ DEBUG_MSG (("EphyTbiSpinner finalized\n"));
+
+ G_OBJECT_CLASS (ephy_tb_item_class)->finalize (o);
+}
+
+static GtkWidget *
+ephy_tbi_spinner_get_widget_impl (EphyTbItem *i)
+{
+ EphyTbiSpinner *iz = EPHY_TBI_SPINNER (i);
+ EphyTbiSpinnerPrivate *p = iz->priv;
+
+ if (!p->widget)
+ {
+ /* here, we create only a box */
+ p->widget = gtk_hbox_new (FALSE, 0);
+ g_object_ref (p->widget);
+ gtk_object_sink (GTK_OBJECT (p->widget));
+ }
+
+ return p->widget;
+}
+
+static GdkPixbuf *
+ephy_tbi_spinner_get_icon_impl (EphyTbItem *i)
+{
+ /* need an icon for this */
+ return NULL;
+}
+
+static gchar *
+ephy_tbi_spinner_get_name_human_impl (EphyTbItem *i)
+{
+ return g_strdup (_("Spinner"));
+}
+
+static gchar *
+ephy_tbi_spinner_to_string_impl (EphyTbItem *i)
+{
+ /* if it had any properties, the string should include them */
+ return g_strdup_printf ("%s=spinner", i->id);
+}
+
+static gboolean
+ephy_tbi_spinner_is_unique_impl (EphyTbItem *i)
+{
+ return TRUE;
+}
+
+static EphyTbItem *
+ephy_tbi_spinner_clone_impl (EphyTbItem *i)
+{
+ EphyTbItem *ret = EPHY_TB_ITEM (ephy_tbi_spinner_new ());
+
+ ephy_tb_item_set_id (ret, i->id);
+
+ /* should copy properties too, if any */
+
+ return ret;
+}
+
+static void
+ephy_tbi_spinner_add_to_bonobo_tb_impl (EphyTbItem *i, BonoboUIComponent *ui,
+ const char *container_path, guint index)
+{
+ GtkWidget *w = ephy_tb_item_get_widget (i);
+ gtk_widget_show (w);
+ ephy_bonobo_add_numbered_control (ui, w, index, container_path);
+}
+
+static void
+ephy_tbi_spinner_parse_properties_impl (EphyTbItem *it, const gchar *props)
+{
+ /* we have no properties */
+}
+
diff --git a/lib/toolbar/ephy-tbi-spinner.h b/lib/toolbar/ephy-tbi-spinner.h
new file mode 100644
index 000000000..1bb04f277
--- /dev/null
+++ b/lib/toolbar/ephy-tbi-spinner.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifndef EPHY_TBI_SPINNER_H
+#define EPHY_TBI_SPINNER_H
+
+#include "ephy-toolbar-item.h"
+
+G_BEGIN_DECLS
+
+/* object forward declarations */
+
+typedef struct _EphyTbiSpinner EphyTbiSpinner;
+typedef struct _EphyTbiSpinnerClass EphyTbiSpinnerClass;
+typedef struct _EphyTbiSpinnerPrivate EphyTbiSpinnerPrivate;
+
+/**
+ * TbiSpinner object
+ */
+
+#define EPHY_TYPE_TBI_SPINNER (ephy_tbi_spinner_get_type())
+#define EPHY_TBI_SPINNER(object) (G_TYPE_CHECK_INSTANCE_CAST((object), EPHY_TYPE_TBI_SPINNER,\
+ EphyTbiSpinner))
+#define EPHY_TBI_SPINNER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EPHY_TYPE_TBI_SPINNER,\
+ EphyTbiSpinnerClass))
+#define EPHY_IS_TBI_SPINNER(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), EPHY_TYPE_TBI_SPINNER))
+#define EPHY_IS_TBI_SPINNER_CLASS(klass)(G_TYPE_CHECK_CLASS_TYPE((klass), EPHY_TYPE_TBI_SPINNER))
+#define EPHY_TBI_SPINNER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPHY_TYPE_TBI_SPINNER,\
+ EphyTbiSpinnerClass))
+
+struct _EphyTbiSpinnerClass
+{
+ EphyTbItemClass parent_class;
+};
+
+/* Remember: fields are public read-only */
+struct _EphyTbiSpinner
+{
+ EphyTbItem parent_object;
+
+ EphyTbiSpinnerPrivate *priv;
+};
+
+/* this class is abstract */
+
+GType ephy_tbi_spinner_get_type (void);
+EphyTbiSpinner * ephy_tbi_spinner_new (void);
+
+G_END_DECLS
+
+#endif
diff --git a/lib/toolbar/ephy-tbi-std-toolitem.c b/lib/toolbar/ephy-tbi-std-toolitem.c
new file mode 100644
index 000000000..deb468ecb
--- /dev/null
+++ b/lib/toolbar/ephy-tbi-std-toolitem.c
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <libgnome/gnome-i18n.h>
+#include <bonobo/bonobo-ui-toolbar-button-item.h>
+#include <bonobo/bonobo-property-bag.h>
+#include <gtk/gtkstock.h>
+#include <string.h>
+
+#include "ephy-tbi-std-toolitem.h"
+#include "ephy-gobject-misc.h"
+#include "ephy-marshal.h"
+#include "ephy-bonobo-extensions.h"
+
+#define NOT_IMPLEMENTED g_warning ("not implemented: " G_STRLOC);
+//#define DEBUG_MSG(x) g_print x
+#define DEBUG_MSG(x)
+
+/**
+ * Private data
+ */
+struct _EphyTbiStdToolitemPrivate
+{
+ GtkWidget *widget;
+
+ EphyTbiStdToolitemItem item;
+};
+
+
+/**
+ * Private functions, only availble from this file
+ */
+static void ephy_tbi_std_toolitem_class_init (EphyTbiStdToolitemClass *klass);
+static void ephy_tbi_std_toolitem_init (EphyTbiStdToolitem *tb);
+static void ephy_tbi_std_toolitem_finalize_impl (GObject *o);
+static GtkWidget * ephy_tbi_std_toolitem_get_widget_impl (EphyTbItem *i);
+static GdkPixbuf * ephy_tbi_std_toolitem_get_icon_impl (EphyTbItem *i);
+static gchar * ephy_tbi_std_toolitem_get_name_human_impl (EphyTbItem *i);
+static gchar * ephy_tbi_std_toolitem_to_string_impl (EphyTbItem *i);
+static gboolean ephy_tbi_std_toolitem_is_unique_impl (EphyTbItem *i);
+static EphyTbItem * ephy_tbi_std_toolitem_clone_impl (EphyTbItem *i);
+static void ephy_tbi_std_toolitem_parse_properties_impl (EphyTbItem *i, const gchar *props);
+static void ephy_tbi_std_toolitem_add_to_bonobo_tb_impl (EphyTbItem *i,
+ BonoboUIComponent *ui,
+ const char *container_path,
+ guint index);
+
+static gpointer ephy_tb_item_class;
+
+/**
+ * TbiStdToolitem object
+ */
+
+MAKE_GET_TYPE (ephy_tbi_std_toolitem, "EphyTbiStdToolitem", EphyTbiStdToolitem,
+ ephy_tbi_std_toolitem_class_init,
+ ephy_tbi_std_toolitem_init, EPHY_TYPE_TB_ITEM);
+
+static void
+ephy_tbi_std_toolitem_class_init (EphyTbiStdToolitemClass *klass)
+{
+ G_OBJECT_CLASS (klass)->finalize = ephy_tbi_std_toolitem_finalize_impl;
+
+ EPHY_TB_ITEM_CLASS (klass)->get_widget = ephy_tbi_std_toolitem_get_widget_impl;
+ EPHY_TB_ITEM_CLASS (klass)->get_icon = ephy_tbi_std_toolitem_get_icon_impl;
+ EPHY_TB_ITEM_CLASS (klass)->get_name_human = ephy_tbi_std_toolitem_get_name_human_impl;
+ EPHY_TB_ITEM_CLASS (klass)->to_string = ephy_tbi_std_toolitem_to_string_impl;
+ EPHY_TB_ITEM_CLASS (klass)->is_unique = ephy_tbi_std_toolitem_is_unique_impl;
+ EPHY_TB_ITEM_CLASS (klass)->clone = ephy_tbi_std_toolitem_clone_impl;
+ EPHY_TB_ITEM_CLASS (klass)->parse_properties = ephy_tbi_std_toolitem_parse_properties_impl;
+ EPHY_TB_ITEM_CLASS (klass)->add_to_bonobo_tb = ephy_tbi_std_toolitem_add_to_bonobo_tb_impl;
+
+ ephy_tb_item_class = g_type_class_peek_parent (klass);
+}
+
+static void
+ephy_tbi_std_toolitem_init (EphyTbiStdToolitem *tb)
+{
+ EphyTbiStdToolitemPrivate *p = g_new0 (EphyTbiStdToolitemPrivate, 1);
+ tb->priv = p;
+
+ p->item = EPHY_TBI_STD_TOOLITEM_BACK;
+}
+
+EphyTbiStdToolitem *
+ephy_tbi_std_toolitem_new (void)
+{
+ EphyTbiStdToolitem *ret = g_object_new (EPHY_TYPE_TBI_STD_TOOLITEM, NULL);
+ return ret;
+}
+
+static void
+ephy_tbi_std_toolitem_finalize_impl (GObject *o)
+{
+ EphyTbiStdToolitem *it = EPHY_TBI_STD_TOOLITEM (o);
+ EphyTbiStdToolitemPrivate *p = it->priv;
+
+ if (p->widget)
+ {
+ g_object_unref (p->widget);
+ }
+
+ g_free (p);
+
+ DEBUG_MSG (("EphyTbiStdToolitem finalized\n"));
+
+ G_OBJECT_CLASS (ephy_tb_item_class)->finalize (o);
+}
+
+static GtkWidget *
+ephy_tbi_std_toolitem_get_widget_impl (EphyTbItem *i)
+{
+ /* no widget avaible ... */
+ return NULL;
+}
+
+static GdkPixbuf *
+ephy_tbi_std_toolitem_get_icon_impl (EphyTbItem *i)
+{
+ EphyTbiStdToolitemPrivate *p = EPHY_TBI_STD_TOOLITEM (i)->priv;
+
+ static GdkPixbuf *pb_up = NULL;
+ static GdkPixbuf *pb_back = NULL;
+ static GdkPixbuf *pb_forward = NULL;
+ static GdkPixbuf *pb_stop = NULL;
+ static GdkPixbuf *pb_reload = NULL;
+ static GdkPixbuf *pb_home = NULL;
+ static GdkPixbuf *pb_go = NULL;
+ static GdkPixbuf *pb_new = NULL;
+
+ if (!pb_up)
+ {
+ /* what's the easier way? */
+ GtkWidget *b = gtk_spin_button_new_with_range (0, 1, 0.5);
+ pb_up = gtk_widget_render_icon (b,
+ GTK_STOCK_GO_UP,
+ GTK_ICON_SIZE_SMALL_TOOLBAR,
+ NULL);
+ pb_back = gtk_widget_render_icon (b,
+ GTK_STOCK_GO_BACK,
+ GTK_ICON_SIZE_SMALL_TOOLBAR,
+ NULL);
+ pb_forward = gtk_widget_render_icon (b,
+ GTK_STOCK_GO_FORWARD,
+ GTK_ICON_SIZE_SMALL_TOOLBAR,
+ NULL);
+ pb_stop = gtk_widget_render_icon (b,
+ GTK_STOCK_STOP,
+ GTK_ICON_SIZE_SMALL_TOOLBAR,
+ NULL);
+ pb_reload = gtk_widget_render_icon (b,
+ GTK_STOCK_REFRESH,
+ GTK_ICON_SIZE_SMALL_TOOLBAR,
+ NULL);
+ pb_home = gtk_widget_render_icon (b,
+ GTK_STOCK_HOME,
+ GTK_ICON_SIZE_SMALL_TOOLBAR,
+ NULL);
+ pb_go = gtk_widget_render_icon (b,
+ GTK_STOCK_JUMP_TO,
+ GTK_ICON_SIZE_SMALL_TOOLBAR,
+ NULL);
+ pb_new = gtk_widget_render_icon (b,
+ GTK_STOCK_NEW,
+ GTK_ICON_SIZE_SMALL_TOOLBAR,
+ NULL);
+ gtk_widget_destroy (b);
+ }
+
+ switch (p->item)
+ {
+ case EPHY_TBI_STD_TOOLITEM_BACK:
+ return g_object_ref (pb_back);
+ break;
+ case EPHY_TBI_STD_TOOLITEM_FORWARD:
+ return g_object_ref (pb_forward);
+ break;
+ case EPHY_TBI_STD_TOOLITEM_UP:
+ return g_object_ref (pb_up);
+ break;
+ case EPHY_TBI_STD_TOOLITEM_STOP:
+ return g_object_ref (pb_stop);
+ break;
+ case EPHY_TBI_STD_TOOLITEM_RELOAD:
+ return g_object_ref (pb_reload);
+ break;
+ case EPHY_TBI_STD_TOOLITEM_HOME:
+ return g_object_ref (pb_home);
+ break;
+ case EPHY_TBI_STD_TOOLITEM_GO:
+ return g_object_ref (pb_go);
+ break;
+ case EPHY_TBI_STD_TOOLITEM_NEW:
+ return g_object_ref (pb_new);
+ break;
+ default:
+ g_assert_not_reached ();
+ return NULL;
+ }
+}
+
+static gchar *
+ephy_tbi_std_toolitem_get_name_human_impl (EphyTbItem *i)
+{
+ EphyTbiStdToolitemPrivate *p = EPHY_TBI_STD_TOOLITEM (i)->priv;
+ const gchar *ret;
+
+ switch (p->item)
+ {
+ case EPHY_TBI_STD_TOOLITEM_BACK:
+ ret = _("Back");
+ break;
+ case EPHY_TBI_STD_TOOLITEM_FORWARD:
+ ret = _("Forward");
+ break;
+ case EPHY_TBI_STD_TOOLITEM_UP:
+ ret = _("Up");
+ break;
+ case EPHY_TBI_STD_TOOLITEM_STOP:
+ ret = _("Stop");
+ break;
+ case EPHY_TBI_STD_TOOLITEM_RELOAD:
+ ret = _("Reload");
+ break;
+ case EPHY_TBI_STD_TOOLITEM_HOME:
+ ret = _("Home");
+ break;
+ case EPHY_TBI_STD_TOOLITEM_GO:
+ ret = _("Go");
+ break;
+ case EPHY_TBI_STD_TOOLITEM_NEW:
+ ret = _("New");
+ break;
+ default:
+ g_assert_not_reached ();
+ ret = "unknown";
+ }
+
+ return g_strdup (ret);
+}
+
+static gchar *
+ephy_tbi_std_toolitem_to_string_impl (EphyTbItem *i)
+{
+ EphyTbiStdToolitemPrivate *p = EPHY_TBI_STD_TOOLITEM (i)->priv;
+
+ /* if it had any properties, the string should include them */
+ const char *sitem;
+
+ switch (p->item)
+ {
+ case EPHY_TBI_STD_TOOLITEM_BACK:
+ sitem = "back";
+ break;
+ case EPHY_TBI_STD_TOOLITEM_FORWARD:
+ sitem = "forward";
+ break;
+ case EPHY_TBI_STD_TOOLITEM_UP:
+ sitem = "up";
+ break;
+ case EPHY_TBI_STD_TOOLITEM_STOP:
+ sitem = "stop";
+ break;
+ case EPHY_TBI_STD_TOOLITEM_RELOAD:
+ sitem = "reload";
+ break;
+ case EPHY_TBI_STD_TOOLITEM_HOME:
+ sitem = "home";
+ break;
+ case EPHY_TBI_STD_TOOLITEM_GO:
+ sitem = "go";
+ break;
+ case EPHY_TBI_STD_TOOLITEM_NEW:
+ sitem = "new";
+ break;
+ default:
+ g_assert_not_reached ();
+ sitem = "unknown";
+ }
+
+ return g_strdup_printf ("%s=std_toolitem(item=%s)", i->id, sitem);
+}
+
+static gboolean
+ephy_tbi_std_toolitem_is_unique_impl (EphyTbItem *i)
+{
+ return TRUE;
+}
+
+static EphyTbItem *
+ephy_tbi_std_toolitem_clone_impl (EphyTbItem *i)
+{
+ EphyTbiStdToolitemPrivate *p = EPHY_TBI_STD_TOOLITEM (i)->priv;
+
+ EphyTbItem *ret = EPHY_TB_ITEM (ephy_tbi_std_toolitem_new ());
+
+ ephy_tb_item_set_id (ret, i->id);
+
+ /* should copy properties too, if any */
+ ephy_tbi_std_toolitem_set_item (EPHY_TBI_STD_TOOLITEM (ret), p->item);
+
+ return ret;
+}
+
+
+static void
+ephy_tbi_std_toolitem_add_to_bonobo_tb_impl (EphyTbItem *i, BonoboUIComponent *ui,
+ const char *container_path, guint index)
+{
+ EphyTbiStdToolitemPrivate *p = EPHY_TBI_STD_TOOLITEM (i)->priv;
+ gchar *xml_item;
+
+ switch (p->item)
+ {
+ case EPHY_TBI_STD_TOOLITEM_BACK:
+ xml_item = g_strdup_printf
+ ("<toolitem name=\"Back\" "
+ "label=\"%s\" "
+ "pixtype=\"stock\" pixname=\"gtk-go-back\" "
+ "priority=\"1\" "
+ "verb=\"GoBack\"/>", _("Back"));;
+ break;
+ case EPHY_TBI_STD_TOOLITEM_FORWARD:
+ xml_item = g_strdup_printf
+ ("<toolitem name=\"Forward\" "
+ "label=\"%s\" "
+ "pixtype=\"stock\" pixname=\"gtk-go-forward\" "
+ "verb=\"GoForward\"/>", _("Forward"));
+ break;
+ case EPHY_TBI_STD_TOOLITEM_UP:
+ xml_item = g_strdup_printf
+ ("<toolitem name=\"Up\" "
+ "label=\"%s\" "
+ "pixtype=\"stock\" pixname=\"gtk-go-up\" "
+ "verb=\"GoUp\"/>", _("Up"));;
+ break;
+ case EPHY_TBI_STD_TOOLITEM_STOP:
+ xml_item = g_strdup_printf
+ ("<toolitem name=\"Stop\" "
+ "label=\"%s\" "
+ "pixtype=\"stock\" pixname=\"gtk-stop\" "
+ "verb=\"GoStop\"/>", _("Stop"));
+ break;
+ case EPHY_TBI_STD_TOOLITEM_RELOAD:
+ xml_item = g_strdup_printf
+ ("<toolitem name=\"Reload\" "
+ "label=\"%s\" "
+ "pixtype=\"stock\" pixname=\"gtk-refresh\" "
+ "verb=\"GoReload\"/>", _("Reload"));
+ break;
+ case EPHY_TBI_STD_TOOLITEM_HOME:
+ xml_item = g_strdup_printf
+ ("<toolitem name=\"Home\" "
+ "label=\"%s\" "
+ "pixtype=\"stock\" pixname=\"gtk-home\" "
+ "priority=\"1\" "
+ "verb=\"GoHome\"/>", _("Home"));;
+ break;
+ case EPHY_TBI_STD_TOOLITEM_GO:
+ xml_item = g_strdup_printf
+ ("<toolitem name=\"Go\" "
+ "label=\"%s\" "
+ "pixtype=\"stock\" pixname=\"gtk-jump-to\" "
+ "verb=\"GoGo\"/>", _("Go"));;
+ break;
+ case EPHY_TBI_STD_TOOLITEM_NEW:
+ xml_item = g_strdup_printf
+ ("<toolitem name=\"New\" "
+ "label=\"%s\" "
+ "pixtype=\"stock\" pixname=\"gtk-new\" "
+ "verb=\"FileNew\"/>", _("New"));;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ xml_item = g_strdup ("");
+ }
+
+ bonobo_ui_component_set (ui, container_path, xml_item, NULL);
+ g_free (xml_item);
+}
+
+static void
+ephy_tbi_std_toolitem_parse_properties_impl (EphyTbItem *it, const gchar *props)
+{
+ EphyTbiStdToolitem *a = EPHY_TBI_STD_TOOLITEM (it);
+
+ /* yes, this is quite hacky, but works */
+
+ /* we have one property */
+ const gchar *item_prop;
+
+ item_prop = strstr (props, "item=");
+ if (item_prop)
+ {
+ item_prop += strlen ("item=");
+ if (!strncmp (item_prop, "back", 4))
+ {
+ ephy_tbi_std_toolitem_set_item (a, EPHY_TBI_STD_TOOLITEM_BACK);
+ }
+ else if (!strncmp (item_prop, "forward", 4))
+ {
+ ephy_tbi_std_toolitem_set_item (a, EPHY_TBI_STD_TOOLITEM_FORWARD);
+ }
+ else if (!strncmp (item_prop, "up", 2))
+ {
+ ephy_tbi_std_toolitem_set_item (a, EPHY_TBI_STD_TOOLITEM_UP);
+ }
+ else if (!strncmp (item_prop, "stop", 4))
+ {
+ ephy_tbi_std_toolitem_set_item (a, EPHY_TBI_STD_TOOLITEM_STOP);
+ }
+ else if (!strncmp (item_prop, "home", 4))
+ {
+ ephy_tbi_std_toolitem_set_item (a, EPHY_TBI_STD_TOOLITEM_HOME);
+ }
+ else if (!strncmp (item_prop, "go", 2))
+ {
+ ephy_tbi_std_toolitem_set_item (a, EPHY_TBI_STD_TOOLITEM_GO);
+ }
+ else if (!strncmp (item_prop, "reload", 6))
+ {
+ ephy_tbi_std_toolitem_set_item (a, EPHY_TBI_STD_TOOLITEM_RELOAD);
+ }
+ else if (!strncmp (item_prop, "new", 3))
+ {
+ ephy_tbi_std_toolitem_set_item (a, EPHY_TBI_STD_TOOLITEM_NEW);
+ }
+
+ }
+}
+
+void
+ephy_tbi_std_toolitem_set_item (EphyTbiStdToolitem *a, EphyTbiStdToolitemItem i)
+{
+ EphyTbiStdToolitemPrivate *p = a->priv;
+
+ g_return_if_fail (i == EPHY_TBI_STD_TOOLITEM_UP
+ || i == EPHY_TBI_STD_TOOLITEM_BACK
+ || i == EPHY_TBI_STD_TOOLITEM_FORWARD
+ || i == EPHY_TBI_STD_TOOLITEM_STOP
+ || i == EPHY_TBI_STD_TOOLITEM_RELOAD
+ || i == EPHY_TBI_STD_TOOLITEM_GO
+ || i == EPHY_TBI_STD_TOOLITEM_HOME
+ || i == EPHY_TBI_STD_TOOLITEM_NEW);
+
+ p->item = i;
+}
+
diff --git a/lib/toolbar/ephy-tbi-std-toolitem.h b/lib/toolbar/ephy-tbi-std-toolitem.h
new file mode 100644
index 000000000..67073041f
--- /dev/null
+++ b/lib/toolbar/ephy-tbi-std-toolitem.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifndef EPHY_TBI_STD_TOOLITEM_H
+#define EPHY_TBI_STD_TOOLITEM_H
+
+#include "ephy-toolbar-item.h"
+
+G_BEGIN_DECLS
+
+/* object forward declarations */
+
+typedef struct _EphyTbiStdToolitem EphyTbiStdToolitem;
+typedef struct _EphyTbiStdToolitemClass EphyTbiStdToolitemClass;
+typedef struct _EphyTbiStdToolitemPrivate EphyTbiStdToolitemPrivate;
+
+/**
+ * TbiStdToolitem object
+ */
+
+#define EPHY_TYPE_TBI_STD_TOOLITEM (ephy_tbi_std_toolitem_get_type())
+#define EPHY_TBI_STD_TOOLITEM(object) (G_TYPE_CHECK_INSTANCE_CAST((object), \
+ EPHY_TYPE_TBI_STD_TOOLITEM,\
+ EphyTbiStdToolitem))
+#define EPHY_TBI_STD_TOOLITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \
+ EPHY_TYPE_TBI_STD_TOOLITEM,\
+ EphyTbiStdToolitemClass))
+#define EPHY_IS_TBI_STD_TOOLITEM(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), \
+ EPHY_TYPE_TBI_STD_TOOLITEM))
+#define EPHY_IS_TBI_STD_TOOLITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ EPHY_TYPE_TBI_STD_TOOLITEM))
+#define EPHY_TBI_STD_TOOLITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \
+ EPHY_TYPE_TBI_STD_TOOLITEM,\
+ EphyTbiStdToolitemClass))
+typedef enum
+{
+ EPHY_TBI_STD_TOOLITEM_BACK,
+ EPHY_TBI_STD_TOOLITEM_FORWARD,
+ EPHY_TBI_STD_TOOLITEM_UP,
+ EPHY_TBI_STD_TOOLITEM_STOP,
+ EPHY_TBI_STD_TOOLITEM_RELOAD,
+ EPHY_TBI_STD_TOOLITEM_HOME,
+ EPHY_TBI_STD_TOOLITEM_GO,
+ EPHY_TBI_STD_TOOLITEM_NEW
+} EphyTbiStdToolitemItem;
+
+
+struct _EphyTbiStdToolitemClass
+{
+ EphyTbItemClass parent_class;
+};
+
+/* Remember: fields are public read-only */
+struct _EphyTbiStdToolitem
+{
+ EphyTbItem parent_object;
+
+ EphyTbiStdToolitemPrivate *priv;
+};
+
+/* this class is abstract */
+
+GType ephy_tbi_std_toolitem_get_type (void);
+EphyTbiStdToolitem * ephy_tbi_std_toolitem_new (void);
+void ephy_tbi_std_toolitem_set_item (EphyTbiStdToolitem *sit,
+ EphyTbiStdToolitemItem it);
+
+G_END_DECLS
+
+#endif
+
diff --git a/lib/toolbar/ephy-tbi-zoom.c b/lib/toolbar/ephy-tbi-zoom.c
new file mode 100644
index 000000000..578799b24
--- /dev/null
+++ b/lib/toolbar/ephy-tbi-zoom.c
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <libgnome/gnome-i18n.h>
+#include <gtk/gtkspinbutton.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkvbox.h>
+#include <string.h>
+
+#include "ephy-tbi-zoom.h"
+#include "ephy-prefs.h"
+#include "eel-gconf-extensions.h"
+#include "ephy-gobject-misc.h"
+#include "ephy-marshal.h"
+#include "ephy-bonobo-extensions.h"
+
+#define NOT_IMPLEMENTED g_warning ("not implemented: " G_STRLOC);
+//#define DEBUG_MSG(x) g_print x
+#define DEBUG_MSG(x)
+
+/**
+ * Private data
+ */
+struct _EphyTbiZoomPrivate
+{
+ GtkWidget *widget;
+ GtkWidget *label;
+ GtkWidget *hbox;
+ GtkWidget *vbox;
+ guint notification;
+};
+
+enum
+{
+ TOOLBAR_ITEM_STYLE_PROP,
+ TOOLBAR_ITEM_ORIENTATION_PROP,
+ TOOLBAR_ITEM_WANT_LABEL_PROP
+};
+
+/**
+ * Private functions, only availble from this file
+ */
+static void ephy_tbi_zoom_class_init (EphyTbiZoomClass *klass);
+static void ephy_tbi_zoom_init (EphyTbiZoom *tb);
+static void ephy_tbi_zoom_finalize_impl (GObject *o);
+static GtkWidget * ephy_tbi_zoom_get_widget_impl (EphyTbItem *i);
+static GdkPixbuf * ephy_tbi_zoom_get_icon_impl (EphyTbItem *i);
+static gchar * ephy_tbi_zoom_get_name_human_impl (EphyTbItem *i);
+static gchar * ephy_tbi_zoom_to_string_impl (EphyTbItem *i);
+static gboolean ephy_tbi_zoom_is_unique_impl (EphyTbItem *i);
+static EphyTbItem * ephy_tbi_zoom_clone_impl (EphyTbItem *i);
+static void ephy_tbi_zoom_parse_properties_impl (EphyTbItem *i, const gchar *props);
+static void ephy_tbi_zoom_add_to_bonobo_tb_impl (EphyTbItem *i,
+ BonoboUIComponent *ui,
+ const char *container_path,
+ guint index);
+static void ephy_tbi_zoom_setup_label (EphyTbiZoom *it);
+static void ephy_tbi_zoom_notification_cb (GConfClient* client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ gpointer user_data);
+
+
+static gpointer ephy_tb_item_class;
+
+/**
+ * TbiZoom object
+ */
+
+MAKE_GET_TYPE (ephy_tbi_zoom, "EphyTbiZoom", EphyTbiZoom, ephy_tbi_zoom_class_init,
+ ephy_tbi_zoom_init, EPHY_TYPE_TB_ITEM);
+
+static void
+ephy_tbi_zoom_class_init (EphyTbiZoomClass *klass)
+{
+ G_OBJECT_CLASS (klass)->finalize = ephy_tbi_zoom_finalize_impl;
+
+ EPHY_TB_ITEM_CLASS (klass)->get_widget = ephy_tbi_zoom_get_widget_impl;
+ EPHY_TB_ITEM_CLASS (klass)->get_icon = ephy_tbi_zoom_get_icon_impl;
+ EPHY_TB_ITEM_CLASS (klass)->get_name_human = ephy_tbi_zoom_get_name_human_impl;
+ EPHY_TB_ITEM_CLASS (klass)->to_string = ephy_tbi_zoom_to_string_impl;
+ EPHY_TB_ITEM_CLASS (klass)->is_unique = ephy_tbi_zoom_is_unique_impl;
+ EPHY_TB_ITEM_CLASS (klass)->clone = ephy_tbi_zoom_clone_impl;
+ EPHY_TB_ITEM_CLASS (klass)->parse_properties = ephy_tbi_zoom_parse_properties_impl;
+ EPHY_TB_ITEM_CLASS (klass)->add_to_bonobo_tb = ephy_tbi_zoom_add_to_bonobo_tb_impl;
+
+ ephy_tb_item_class = g_type_class_peek_parent (klass);
+}
+
+static void
+ephy_tbi_zoom_init (EphyTbiZoom *tbi)
+{
+ EphyTbiZoomPrivate *p = g_new0 (EphyTbiZoomPrivate, 1);
+ tbi->priv = p;
+
+ p->notification = eel_gconf_notification_add (CONF_DESKTOP_TOOLBAR_STYLE,
+ ephy_tbi_zoom_notification_cb,
+ tbi);
+}
+
+EphyTbiZoom *
+ephy_tbi_zoom_new (void)
+{
+ EphyTbiZoom *ret = g_object_new (EPHY_TYPE_TBI_ZOOM, NULL);
+ return ret;
+}
+
+static void
+ephy_tbi_zoom_finalize_impl (GObject *o)
+{
+ EphyTbiZoom *it = EPHY_TBI_ZOOM (o);
+ EphyTbiZoomPrivate *p = it->priv;
+
+ if (p->widget)
+ {
+ g_object_unref (p->widget);
+ }
+
+ if (p->label)
+ {
+ g_object_unref (p->label);
+ }
+
+ if (p->vbox)
+ {
+ g_object_unref (p->vbox);
+ }
+
+ if (p->hbox)
+ {
+ g_object_unref (p->hbox);
+ }
+
+ if (p->notification)
+ {
+ eel_gconf_notification_remove (p->notification);
+ }
+
+ g_free (p);
+
+ DEBUG_MSG (("EphyTbiZoom finalized\n"));
+
+ G_OBJECT_CLASS (ephy_tb_item_class)->finalize (o);
+}
+
+static GtkWidget *
+ephy_tbi_zoom_get_widget_impl (EphyTbItem *i)
+{
+ EphyTbiZoom *iz = EPHY_TBI_ZOOM (i);
+ EphyTbiZoomPrivate *p = iz->priv;
+
+ if (!p->widget)
+ {
+ p->widget = gtk_spin_button_new_with_range (1, 999, 10);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (p->widget), 100);
+ g_object_ref (p->widget);
+ gtk_object_sink (GTK_OBJECT (p->widget));
+ p->label = gtk_label_new (_("Zoom"));
+ g_object_ref (p->label);
+ gtk_object_sink (GTK_OBJECT (p->label));
+ p->vbox = gtk_vbox_new (FALSE, 0);
+ g_object_ref (p->vbox);
+ gtk_object_sink (GTK_OBJECT (p->vbox));
+ p->hbox = gtk_hbox_new (FALSE, 0);
+ g_object_ref (p->hbox);
+ gtk_object_sink (GTK_OBJECT (p->hbox));
+
+ gtk_box_pack_start_defaults (GTK_BOX (p->hbox), p->vbox);
+ gtk_box_pack_start_defaults (GTK_BOX (p->vbox), p->widget);
+ gtk_widget_show (p->vbox);
+ gtk_widget_show (p->hbox);
+ }
+
+ return p->widget;
+}
+
+static GdkPixbuf *
+ephy_tbi_zoom_get_icon_impl (EphyTbItem *i)
+{
+ static GdkPixbuf *pb = NULL;
+ if (!pb)
+ {
+ /* what's the easier way? */
+ GtkWidget *b = gtk_spin_button_new_with_range (0, 1, 0.5);
+ pb = gtk_widget_render_icon (b,
+ GTK_STOCK_ZOOM_IN,
+ GTK_ICON_SIZE_SMALL_TOOLBAR,
+ NULL);
+ gtk_widget_destroy (b);
+ }
+ return g_object_ref (pb);
+}
+
+static gchar *
+ephy_tbi_zoom_get_name_human_impl (EphyTbItem *i)
+{
+ return g_strdup (_("Zoom"));
+}
+
+static gchar *
+ephy_tbi_zoom_to_string_impl (EphyTbItem *i)
+{
+ /* if it had any properties, the string should include them */
+ return g_strdup_printf ("%s=zoom", i->id);
+}
+
+static gboolean
+ephy_tbi_zoom_is_unique_impl (EphyTbItem *i)
+{
+ return TRUE;
+}
+
+static EphyTbItem *
+ephy_tbi_zoom_clone_impl (EphyTbItem *i)
+{
+ EphyTbItem *ret = EPHY_TB_ITEM (ephy_tbi_zoom_new ());
+
+ ephy_tb_item_set_id (ret, i->id);
+
+ /* should copy properties too, if any */
+ /* the zoom value is not copied, not sure if it should... */
+
+ return ret;
+}
+
+static void
+ephy_tbi_zoom_add_to_bonobo_tb_impl (EphyTbItem *i, BonoboUIComponent *ui,
+ const char *container_path, guint index)
+{
+ GtkWidget *w = ephy_tb_item_get_widget (i);
+ EphyTbiZoomPrivate *p = EPHY_TBI_ZOOM (i)->priv;
+ gtk_widget_show (w);
+ ephy_bonobo_add_numbered_control (ui, p->hbox, index, container_path);
+ ephy_tbi_zoom_setup_label (EPHY_TBI_ZOOM (i));
+}
+
+static void
+ephy_tbi_zoom_parse_properties_impl (EphyTbItem *it, const gchar *props)
+{
+ /* we have no properties */
+}
+
+static void
+ephy_tbi_zoom_setup_label (EphyTbiZoom *it)
+{
+ EphyTbiZoomPrivate *p = it->priv;
+ gchar *style = eel_gconf_get_string (CONF_DESKTOP_TOOLBAR_STYLE);
+ ephy_tb_item_get_widget (EPHY_TB_ITEM (it));
+
+ g_object_ref (p->label);
+ if (p->label->parent)
+ {
+ gtk_container_remove (GTK_CONTAINER (p->label->parent), p->label);
+ }
+
+ if (!strcmp (style, "both_horiz") || !strcmp (style, "text"))
+ {
+ gtk_widget_show (p->label);
+ gtk_box_pack_start_defaults (GTK_BOX (p->hbox), p->label);
+ }
+ else if (!strcmp (style, "both"))
+ {
+ gtk_widget_show (p->label);
+ gtk_box_pack_start_defaults (GTK_BOX (p->vbox), p->label);
+ }
+ else
+ {
+ gtk_widget_hide (p->label);
+ }
+
+ g_free (style);
+ g_object_unref (p->label);
+}
+
+static void
+ephy_tbi_zoom_notification_cb (GConfClient* client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ gpointer user_data)
+{
+ ephy_tbi_zoom_setup_label (user_data);
+}
+
diff --git a/lib/toolbar/ephy-tbi-zoom.h b/lib/toolbar/ephy-tbi-zoom.h
new file mode 100644
index 000000000..03f0ed61e
--- /dev/null
+++ b/lib/toolbar/ephy-tbi-zoom.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifndef EPHY_TBI_ZOOM_H
+#define EPHY_TBI_ZOOM_H
+
+#include "ephy-toolbar-item.h"
+
+G_BEGIN_DECLS
+
+/* object forward declarations */
+
+typedef struct _EphyTbiZoom EphyTbiZoom;
+typedef struct _EphyTbiZoomClass EphyTbiZoomClass;
+typedef struct _EphyTbiZoomPrivate EphyTbiZoomPrivate;
+
+/**
+ * TbiZoom object
+ */
+
+#define EPHY_TYPE_TBI_ZOOM (ephy_tbi_zoom_get_type())
+#define EPHY_TBI_ZOOM(object) (G_TYPE_CHECK_INSTANCE_CAST((object), EPHY_TYPE_TBI_ZOOM,\
+ EphyTbiZoom))
+#define EPHY_TBI_ZOOM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EPHY_TYPE_TBI_ZOOM,\
+ EphyTbiZoomClass))
+#define EPHY_IS_TBI_ZOOM(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), EPHY_TYPE_TBI_ZOOM))
+#define EPHY_IS_TBI_ZOOM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EPHY_TYPE_TBI_ZOOM))
+#define EPHY_TBI_ZOOM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPHY_TYPE_TBI_ZOOM,\
+ EphyTbiZoomClass))
+
+struct _EphyTbiZoomClass
+{
+ EphyTbItemClass parent_class;
+};
+
+/* Remember: fields are public read-only */
+struct _EphyTbiZoom
+{
+ EphyTbItem parent_object;
+
+ EphyTbiZoomPrivate *priv;
+};
+
+/* this class is abstract */
+
+GType ephy_tbi_zoom_get_type (void);
+EphyTbiZoom * ephy_tbi_zoom_new (void);
+
+G_END_DECLS
+
+#endif
diff --git a/lib/toolbar/ephy-toolbar-bonobo-view.c b/lib/toolbar/ephy-toolbar-bonobo-view.c
new file mode 100644
index 000000000..a22356e1e
--- /dev/null
+++ b/lib/toolbar/ephy-toolbar-bonobo-view.c
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <libgnome/gnome-i18n.h>
+
+#include "ephy-gobject-misc.h"
+#include "ephy-marshal.h"
+#include "ephy-toolbar-bonobo-view.h"
+#include "ephy-bonobo-extensions.h"
+
+#define NOT_IMPLEMENTED g_warning ("not implemented: " G_STRLOC);
+//#define DEBUG_MSG(x) g_print x
+#define DEBUG_MSG(x)
+
+/**
+ * Private data
+ */
+struct _EphyTbBonoboViewPrivate
+{
+ EphyToolbar *tb;
+ BonoboUIComponent *ui;
+ gchar *path;
+};
+
+/**
+ * Private functions, only availble from this file
+ */
+static void ephy_tb_bonobo_view_class_init (EphyTbBonoboViewClass *klass);
+static void ephy_tb_bonobo_view_init (EphyTbBonoboView *tb);
+static void ephy_tb_bonobo_view_finalize_impl (GObject *o);
+static void ephy_tb_bonobo_view_rebuild (EphyTbBonoboView *tbv);
+static void ephy_tb_bonobo_view_tb_changed (EphyToolbar *tb, EphyTbBonoboView *tbv);
+
+static gpointer g_object_class;
+
+/**
+ * TbBonoboView object
+ */
+
+MAKE_GET_TYPE (ephy_tb_bonobo_view, "EphyTbBonoboView", EphyTbBonoboView, ephy_tb_bonobo_view_class_init,
+ ephy_tb_bonobo_view_init, G_TYPE_OBJECT);
+
+static void
+ephy_tb_bonobo_view_class_init (EphyTbBonoboViewClass *klass)
+{
+ G_OBJECT_CLASS (klass)->finalize = ephy_tb_bonobo_view_finalize_impl;
+
+ g_object_class = g_type_class_peek_parent (klass);
+}
+
+static void
+ephy_tb_bonobo_view_init (EphyTbBonoboView *tb)
+{
+ EphyTbBonoboViewPrivate *p = g_new0 (EphyTbBonoboViewPrivate, 1);
+ tb->priv = p;
+}
+
+static void
+ephy_tb_bonobo_view_finalize_impl (GObject *o)
+{
+ EphyTbBonoboView *tbv = EPHY_TB_BONOBO_VIEW (o);
+ EphyTbBonoboViewPrivate *p = tbv->priv;
+
+ if (p->tb)
+ {
+ g_signal_handlers_disconnect_matched (p->tb, G_SIGNAL_MATCH_DATA, 0, 0,
+ NULL, NULL, tbv);
+ g_object_unref (p->tb);
+ }
+ if (p->ui)
+ {
+ g_object_unref (p->ui);
+ }
+ if (p->path)
+ {
+ g_free (p->path);
+ }
+
+ g_free (p);
+
+ DEBUG_MSG (("EphyTbBonoboView finalized\n"));
+
+ G_OBJECT_CLASS (g_object_class)->finalize (o);
+}
+
+EphyTbBonoboView *
+ephy_tb_bonobo_view_new (void)
+{
+ EphyTbBonoboView *ret = g_object_new (EPHY_TYPE_TB_BONOBO_VIEW, NULL);
+ return ret;
+}
+
+void
+ephy_tb_bonobo_view_set_toolbar (EphyTbBonoboView *tbv, EphyToolbar *tb)
+{
+ EphyTbBonoboViewPrivate *p = tbv->priv;
+
+ if (p->tb)
+ {
+ g_signal_handlers_disconnect_matched (p->tb, G_SIGNAL_MATCH_DATA, 0, 0,
+ NULL, NULL, tbv);
+ g_object_unref (p->tb);
+ }
+
+ p->tb = g_object_ref (tb);
+ g_signal_connect (p->tb, "changed", G_CALLBACK (ephy_tb_bonobo_view_tb_changed), tbv);
+
+ if (p->ui)
+ {
+ ephy_tb_bonobo_view_rebuild (tbv);
+ }
+}
+
+static void
+ephy_tb_bonobo_view_tb_changed (EphyToolbar *tb, EphyTbBonoboView *tbv)
+{
+ EphyTbBonoboViewPrivate *p = tbv->priv;
+ if (p->ui)
+ {
+ ephy_tb_bonobo_view_rebuild (tbv);
+ }
+}
+
+void
+ephy_tb_bonobo_view_set_path (EphyTbBonoboView *tbv,
+ BonoboUIComponent *ui,
+ const gchar *path)
+{
+ EphyTbBonoboViewPrivate *p = tbv->priv;
+
+ if (p->ui)
+ {
+ g_object_unref (p->ui);
+ }
+
+ if (p->path)
+ {
+ g_free (p->path);
+ }
+
+ p->ui = g_object_ref (ui);
+ p->path = g_strdup (path);
+
+ if (p->tb)
+ {
+ ephy_tb_bonobo_view_rebuild (tbv);
+ }
+}
+
+static void
+ephy_tb_bonobo_view_rebuild (EphyTbBonoboView *tbv)
+{
+ EphyTbBonoboViewPrivate *p = tbv->priv;
+ GSList *items;
+ GSList *li;
+ uint index = 0;
+
+ g_return_if_fail (EPHY_IS_TOOLBAR (p->tb));
+ g_return_if_fail (BONOBO_IS_UI_COMPONENT (p->ui));
+ g_return_if_fail (p->path);
+
+ DEBUG_MSG (("Rebuilding EphyTbBonoboView\n"));
+
+ ephy_bonobo_clear_path (p->ui, p->path);
+
+ items = (GSList *) ephy_toolbar_get_item_list (p->tb);
+ for (li = items; li; li = li->next)
+ {
+ ephy_tb_item_add_to_bonobo_tb (li->data, p->ui, p->path, index++);
+ }
+
+ DEBUG_MSG (("Rebuilt EphyTbBonoboView\n"));
+}
+
diff --git a/lib/toolbar/ephy-toolbar-bonobo-view.h b/lib/toolbar/ephy-toolbar-bonobo-view.h
new file mode 100644
index 000000000..a914fc201
--- /dev/null
+++ b/lib/toolbar/ephy-toolbar-bonobo-view.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifndef EPHY_TOOLBAR_BONOBO_VIEW_H
+#define EPHY_TOOLBAR_BONOBO_VIEW_H
+
+#include <glib-object.h>
+
+#include <bonobo/bonobo-ui-component.h>
+
+#include "ephy-toolbar.h"
+
+G_BEGIN_DECLS
+
+/* object forward declarations */
+
+typedef struct _EphyTbBonoboView EphyTbBonoboView;
+typedef struct _EphyTbBonoboViewClass EphyTbBonoboViewClass;
+typedef struct _EphyTbBonoboViewPrivate EphyTbBonoboViewPrivate;
+
+/**
+ * TbBonoboView object
+ */
+
+#define EPHY_TYPE_TB_BONOBO_VIEW (ephy_tb_bonobo_view_get_type())
+#define EPHY_TB_BONOBO_VIEW(object) (G_TYPE_CHECK_INSTANCE_CAST((object), \
+ EPHY_TYPE_TB_BONOBO_VIEW,\
+ EphyTbBonoboView))
+#define EPHY_TB_BONOBO_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EPHY_TYPE_TB_BONOBO_VIEW,\
+ EphyTbBonoboViewClass))
+#define EPHY_IS_TB_BONOBO_VIEW(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), \
+ EPHY_TYPE_TB_BONOBO_VIEW))
+#define EPHY_IS_TB_BONOBO_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EPHY_TYPE_TB_BONOBO_VIEW))
+#define EPHY_TB_BONOBO_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPHY_TYPE_TB_BONOBO_VIEW,\
+ EphyTbBonoboViewClass))
+
+struct _EphyTbBonoboViewClass
+{
+ GObjectClass parent_class;
+};
+
+/* Remember: fields are public read-only */
+struct _EphyTbBonoboView
+{
+ GObject parent_object;
+
+ EphyTbBonoboViewPrivate *priv;
+};
+
+/* this class is abstract */
+
+GType ephy_tb_bonobo_view_get_type (void);
+EphyTbBonoboView * ephy_tb_bonobo_view_new (void);
+void ephy_tb_bonobo_view_set_toolbar (EphyTbBonoboView *tbv, EphyToolbar *tb);
+void ephy_tb_bonobo_view_set_path (EphyTbBonoboView *tbv,
+ BonoboUIComponent *ui,
+ const gchar *path);
+
+#endif
+
diff --git a/lib/toolbar/ephy-toolbar-editor.c b/lib/toolbar/ephy-toolbar-editor.c
new file mode 100644
index 000000000..e44767bad
--- /dev/null
+++ b/lib/toolbar/ephy-toolbar-editor.c
@@ -0,0 +1,634 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <libgnome/gnome-i18n.h>
+
+#include "ephy-gobject-misc.h"
+#include "ephy-marshal.h"
+#include "ephy-toolbar-editor.h"
+#include "ephy-toolbar-tree-model.h"
+#include "ephy-glade.h"
+
+#define NOT_IMPLEMENTED g_warning ("not implemented: " G_STRLOC);
+//#define DEBUG_MSG(x) g_print x
+#define DEBUG_MSG(x)
+
+/**
+ * Private data
+ */
+struct _EphyTbEditorPrivate
+{
+ EphyToolbar *tb;
+ EphyToolbar *available;
+
+ gchar *tb_undo_string;
+ gchar *available_undo_string;
+
+ gboolean in_toolbar_changed;
+
+ GtkWidget *window;
+ GtkWidget *available_view;
+ GtkWidget *current_view;
+ GtkWidget *close_button;
+ GtkWidget *undo_button;
+ GtkWidget *revert_button;
+ GtkWidget *up_button;
+ GtkWidget *down_button;
+ GtkWidget *left_button;
+ GtkWidget *right_button;
+};
+
+/**
+ * Private functions, only available from this file
+ */
+static void ephy_tb_editor_class_init (EphyTbEditorClass *klass);
+static void ephy_tb_editor_init (EphyTbEditor *tbe);
+static void ephy_tb_editor_finalize_impl (GObject *o);
+static void ephy_tb_editor_init_widgets (EphyTbEditor *tbe);
+static void ephy_tb_editor_set_treeview_toolbar (EphyTbEditor *tbe,
+ GtkTreeView *tv, EphyToolbar *tb);
+static void ephy_tb_editor_setup_treeview (EphyTbEditor *tbe, GtkTreeView *tv);
+static EphyTbItem * ephy_tb_editor_get_selected (EphyTbEditor *tbe, GtkTreeView *tv);
+static gint ephy_tb_editor_get_selected_index (EphyTbEditor *tbe, GtkTreeView *tv);
+static void ephy_tb_editor_select_index (EphyTbEditor *tbe, GtkTreeView *tv,
+ gint index);
+static void ephy_tb_editor_remove_used_items (EphyTbEditor *tbe);
+
+static void ephy_tb_editor_undo_clicked_cb (GtkWidget *b, EphyTbEditor *tbe);
+static void ephy_tb_editor_close_clicked_cb (GtkWidget *b, EphyTbEditor *tbe);
+static void ephy_tb_editor_up_clicked_cb (GtkWidget *b, EphyTbEditor *tbe);
+static void ephy_tb_editor_down_clicked_cb (GtkWidget *b, EphyTbEditor *tbe);
+static void ephy_tb_editor_left_clicked_cb (GtkWidget *b, EphyTbEditor *tbe);
+static void ephy_tb_editor_right_clicked_cb (GtkWidget *b, EphyTbEditor *tbe);
+static void ephy_tb_editor_toolbar_changed_cb (EphyToolbar *tb, EphyTbEditor *tbe);
+static gboolean ephy_tb_editor_treeview_button_press_event_cb (GtkWidget *widget,
+ GdkEventButton *event,
+ EphyTbEditor *tbe);
+
+
+static gpointer g_object_class;
+
+/* treeview dnd */
+enum
+{
+ TARGET_GTK_TREE_MODEL_ROW
+};
+static GtkTargetEntry tree_view_row_targets[] = {
+ { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP, TARGET_GTK_TREE_MODEL_ROW }
+};
+
+/**
+ * TbEditor object
+ */
+
+MAKE_GET_TYPE (ephy_tb_editor, "EphyTbEditor", EphyTbEditor, ephy_tb_editor_class_init,
+ ephy_tb_editor_init, G_TYPE_OBJECT);
+
+static void
+ephy_tb_editor_class_init (EphyTbEditorClass *klass)
+{
+ G_OBJECT_CLASS (klass)->finalize = ephy_tb_editor_finalize_impl;
+
+ g_object_class = g_type_class_peek_parent (klass);
+}
+
+static void
+ephy_tb_editor_init (EphyTbEditor *tb)
+{
+ EphyTbEditorPrivate *p = g_new0 (EphyTbEditorPrivate, 1);
+ tb->priv = p;
+
+ ephy_tb_editor_init_widgets (tb);
+}
+
+static void
+update_arrows_sensitivity (EphyTbEditor *tbe)
+{
+ GtkTreeSelection *selection;
+ gboolean current_sel;
+ gboolean avail_sel;
+ gboolean first = FALSE;
+ gboolean last = FALSE;
+ GtkTreeModel *tm;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ selection = gtk_tree_view_get_selection
+ (GTK_TREE_VIEW (tbe->priv->current_view));
+ current_sel = gtk_tree_selection_get_selected (selection, &tm, &iter);
+ if (current_sel)
+ {
+ path = gtk_tree_model_get_path (tm, &iter);
+ first = !gtk_tree_path_prev (path);
+ last = !gtk_tree_model_iter_next (tm, &iter);
+ }
+
+ selection = gtk_tree_view_get_selection
+ (GTK_TREE_VIEW (tbe->priv->available_view));
+ avail_sel = gtk_tree_selection_get_selected (selection, &tm, &iter);
+
+ gtk_widget_set_sensitive (tbe->priv->right_button,
+ avail_sel);
+ gtk_widget_set_sensitive (tbe->priv->left_button,
+ current_sel);
+ gtk_widget_set_sensitive (tbe->priv->up_button,
+ current_sel && !first);
+ gtk_widget_set_sensitive (tbe->priv->down_button,
+ current_sel && !last);
+}
+
+static void
+ephy_tb_editor_treeview_selection_changed_cb (GtkTreeSelection *selection,
+ EphyTbEditor *tbe)
+{
+ update_arrows_sensitivity (tbe);
+}
+
+static void
+ephy_tb_editor_init_widgets (EphyTbEditor *tbe)
+{
+ EphyTbEditorPrivate *p = tbe->priv;
+
+ GladeXML *gxml = ephy_glade_widget_new ("toolbar-editor.glade", "toolbar-editor-dialog",
+ NULL, tbe);
+ p->window = glade_xml_get_widget (gxml, "toolbar-editor-dialog");
+ p->available_view = glade_xml_get_widget (gxml, "toolbar-editor-available-view");
+ p->current_view = glade_xml_get_widget (gxml, "toolbar-editor-current-view");
+ p->close_button = glade_xml_get_widget (gxml, "toolbar-editor-close-button");
+ p->undo_button = glade_xml_get_widget (gxml, "toolbar-editor-undo-button");
+ p->revert_button = glade_xml_get_widget (gxml, "toolbar-editor-revert-button");
+ p->up_button = glade_xml_get_widget (gxml, "toolbar-editor-up-button");
+ p->down_button = glade_xml_get_widget (gxml, "toolbar-editor-down-button");
+ p->left_button = glade_xml_get_widget (gxml, "toolbar-editor-left-button");
+ p->right_button = glade_xml_get_widget (gxml, "toolbar-editor-right-button");
+ g_object_unref (gxml);
+
+ g_signal_connect_swapped (p->window, "delete_event", G_CALLBACK (g_object_unref), tbe);
+ g_signal_connect (p->undo_button, "clicked", G_CALLBACK (ephy_tb_editor_undo_clicked_cb), tbe);
+ g_signal_connect (p->close_button, "clicked", G_CALLBACK (ephy_tb_editor_close_clicked_cb), tbe);
+ g_signal_connect (p->up_button, "clicked", G_CALLBACK (ephy_tb_editor_up_clicked_cb), tbe);
+ g_signal_connect (p->down_button, "clicked", G_CALLBACK (ephy_tb_editor_down_clicked_cb), tbe);
+ g_signal_connect (p->left_button, "clicked", G_CALLBACK (ephy_tb_editor_left_clicked_cb), tbe);
+ g_signal_connect (p->right_button, "clicked", G_CALLBACK (ephy_tb_editor_right_clicked_cb), tbe);
+
+ ephy_tb_editor_setup_treeview (tbe, GTK_TREE_VIEW (p->current_view));
+ ephy_tb_editor_setup_treeview (tbe, GTK_TREE_VIEW (p->available_view));
+}
+
+static void
+ephy_tb_editor_undo_clicked_cb (GtkWidget *b, EphyTbEditor *tbe)
+{
+ EphyTbEditorPrivate *p = tbe->priv;
+ if (p->available_undo_string && p->available)
+ {
+ ephy_toolbar_parse (p->available, p->available_undo_string);
+ }
+
+ if (p->tb_undo_string && p->tb)
+ {
+ ephy_toolbar_parse (p->tb, p->tb_undo_string);
+ }
+}
+
+static void
+ephy_tb_editor_close_clicked_cb (GtkWidget *b, EphyTbEditor *tbe)
+{
+ gtk_widget_hide (tbe->priv->window);
+ g_object_unref (tbe);
+}
+
+static void
+ephy_tb_editor_up_clicked_cb (GtkWidget *b, EphyTbEditor *tbe)
+{
+ EphyTbEditorPrivate *p = tbe->priv;
+ EphyTbItem *item = ephy_tb_editor_get_selected (tbe, GTK_TREE_VIEW (p->current_view));
+ gint index = ephy_tb_editor_get_selected_index (tbe, GTK_TREE_VIEW (p->current_view));
+ if (item && index > 0)
+ {
+ g_object_ref (item);
+ ephy_toolbar_remove_item (p->tb, item);
+ ephy_toolbar_add_item (p->tb, item, index - 1);
+ ephy_tb_editor_select_index (tbe, GTK_TREE_VIEW (p->current_view), index - 1);
+ g_object_unref (item);
+ }
+}
+
+static void
+ephy_tb_editor_down_clicked_cb (GtkWidget *b, EphyTbEditor *tbe)
+{
+ EphyTbEditorPrivate *p = tbe->priv;
+ EphyTbItem *item = ephy_tb_editor_get_selected (tbe, GTK_TREE_VIEW (p->current_view));
+ gint index = ephy_tb_editor_get_selected_index (tbe, GTK_TREE_VIEW (p->current_view));
+ if (item)
+ {
+ g_object_ref (item);
+ ephy_toolbar_remove_item (p->tb, item);
+ ephy_toolbar_add_item (p->tb, item, index + 1);
+ ephy_tb_editor_select_index (tbe, GTK_TREE_VIEW (p->current_view), index + 1);
+ g_object_unref (item);
+ }
+}
+
+static void
+ephy_tb_editor_left_clicked_cb (GtkWidget *b, EphyTbEditor *tbe)
+{
+ EphyTbEditorPrivate *p = tbe->priv;
+ EphyTbItem *item = ephy_tb_editor_get_selected (tbe, GTK_TREE_VIEW (p->current_view));
+ /* probably is better not allowing reordering the available_view */
+ gint index = ephy_tb_editor_get_selected_index (tbe, GTK_TREE_VIEW (p->available_view));
+ if (item)
+ {
+ g_object_ref (item);
+ ephy_toolbar_remove_item (p->tb, item);
+ if (ephy_tb_item_is_unique (item))
+ {
+ ephy_toolbar_add_item (p->available, item, index);
+ }
+ g_object_unref (item);
+ }
+}
+
+static void
+ephy_tb_editor_right_clicked_cb (GtkWidget *b, EphyTbEditor *tbe)
+{
+ EphyTbEditorPrivate *p = tbe->priv;
+ EphyTbItem *item = ephy_tb_editor_get_selected (tbe, GTK_TREE_VIEW (p->available_view));
+ gint index = ephy_tb_editor_get_selected_index (tbe, GTK_TREE_VIEW (p->current_view));
+ if (item)
+ {
+ if (ephy_tb_item_is_unique (item))
+ {
+ g_object_ref (item);
+ ephy_toolbar_remove_item (p->available, item);
+ }
+ else
+ {
+ item = ephy_tb_item_clone (item);
+ }
+ ephy_toolbar_add_item (p->tb, item, index);
+ ephy_tb_editor_select_index (tbe, GTK_TREE_VIEW (p->current_view), index);
+ g_object_unref (item);
+ }
+}
+
+static EphyTbItem *
+ephy_tb_editor_get_selected (EphyTbEditor *tbe, GtkTreeView *tv)
+{
+ GtkTreeSelection *sel = gtk_tree_view_get_selection (tv);
+ GtkTreeModel *tm;
+ GtkTreeIter iter;
+ if (gtk_tree_selection_get_selected (sel, &tm, &iter))
+ {
+ EphyTbItem *ret;
+ g_return_val_if_fail (EPHY_IS_TB_TREE_MODEL (tm), NULL);
+ ret = ephy_tb_tree_model_item_from_iter (EPHY_TB_TREE_MODEL (tm), &iter);
+ return ret;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+static gint
+ephy_tb_editor_get_selected_index (EphyTbEditor *tbe, GtkTreeView *tv)
+{
+ GtkTreeSelection *sel = gtk_tree_view_get_selection (tv);
+ GtkTreeModel *tm;
+ GtkTreeIter iter;
+ if (gtk_tree_selection_get_selected (sel, &tm, &iter))
+ {
+ GtkTreePath *p = gtk_tree_model_get_path (tm, &iter);
+ if (p)
+ {
+ gint ret = gtk_tree_path_get_depth (p) > 0 ? gtk_tree_path_get_indices (p)[0] : -1;
+ gtk_tree_path_free (p);
+ return ret;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+ else
+ {
+ return -1;
+ }
+}
+
+static void
+ephy_tb_editor_select_index (EphyTbEditor *tbe, GtkTreeView *tv, gint index)
+{
+ GtkTreeSelection *sel = gtk_tree_view_get_selection (tv);
+ GtkTreePath *p = gtk_tree_path_new ();
+ GtkTreeModel *tm = gtk_tree_view_get_model (tv);
+ gint max = gtk_tree_model_iter_n_children (tm, NULL);
+
+ if (index < 0 || index >= max)
+ {
+ index = max - 1;
+ }
+
+ gtk_tree_path_append_index (p, index);
+ gtk_tree_selection_select_path (sel, p);
+ gtk_tree_path_free (p);
+}
+
+static void
+ephy_tb_editor_finalize_impl (GObject *o)
+{
+ EphyTbEditor *tbe = EPHY_TB_EDITOR (o);
+ EphyTbEditorPrivate *p = tbe->priv;
+
+ if (p->tb)
+ {
+ g_signal_handlers_disconnect_matched (p->tb, G_SIGNAL_MATCH_DATA, 0, 0,
+ NULL, NULL, tbe);
+
+ g_object_unref (p->tb);
+ }
+ if (p->available)
+ {
+ g_signal_handlers_disconnect_matched (p->available, G_SIGNAL_MATCH_DATA, 0, 0,
+ NULL, NULL, tbe);
+ g_object_unref (p->available);
+ }
+
+ if (p->window)
+ {
+ gtk_widget_destroy (p->window);
+ }
+
+ g_free (p->tb_undo_string);
+ g_free (p->available_undo_string);
+
+ g_free (p);
+
+ DEBUG_MSG (("EphyTbEditor finalized\n"));
+
+ G_OBJECT_CLASS (g_object_class)->finalize (o);
+}
+
+EphyTbEditor *
+ephy_tb_editor_new (void)
+{
+ EphyTbEditor *ret = g_object_new (EPHY_TYPE_TB_EDITOR, NULL);
+ return ret;
+}
+
+void
+ephy_tb_editor_set_toolbar (EphyTbEditor *tbe, EphyToolbar *tb)
+{
+ EphyTbEditorPrivate *p = tbe->priv;
+
+ if (p->tb)
+ {
+ g_signal_handlers_disconnect_matched (p->tb, G_SIGNAL_MATCH_DATA, 0, 0,
+ NULL, NULL, tbe);
+ g_object_unref (p->tb);
+ }
+ p->tb = g_object_ref (tb);
+
+ g_free (p->tb_undo_string);
+ p->tb_undo_string = ephy_toolbar_to_string (p->tb);
+
+ if (p->available)
+ {
+ ephy_tb_editor_remove_used_items (tbe);
+ }
+
+ g_signal_connect (p->tb, "changed", G_CALLBACK (ephy_tb_editor_toolbar_changed_cb), tbe);
+
+ ephy_tb_editor_set_treeview_toolbar (tbe, GTK_TREE_VIEW (p->current_view), p->tb);
+
+ gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (p->current_view),
+ GDK_BUTTON1_MASK,
+ tree_view_row_targets,
+ G_N_ELEMENTS (tree_view_row_targets),
+ GDK_ACTION_MOVE);
+ gtk_tree_view_enable_model_drag_dest (GTK_TREE_VIEW (p->current_view),
+ tree_view_row_targets,
+ G_N_ELEMENTS (tree_view_row_targets),
+ GDK_ACTION_COPY);
+}
+
+void
+ephy_tb_editor_set_available (EphyTbEditor *tbe, EphyToolbar *tb)
+{
+ EphyTbEditorPrivate *p = tbe->priv;
+
+ if (p->available)
+ {
+ g_signal_handlers_disconnect_matched (p->available, G_SIGNAL_MATCH_DATA, 0, 0,
+ NULL, NULL, tbe);
+ g_object_unref (p->available);
+ }
+ p->available = g_object_ref (tb);
+
+ g_free (p->available_undo_string);
+ p->available_undo_string = ephy_toolbar_to_string (p->available);
+
+ ephy_toolbar_set_fixed_order (p->available, TRUE);
+
+ if (p->tb)
+ {
+ ephy_tb_editor_remove_used_items (tbe);
+ }
+
+ ephy_tb_editor_set_treeview_toolbar (tbe, GTK_TREE_VIEW (p->available_view), p->available);
+
+ gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (p->available_view),
+ GDK_BUTTON1_MASK,
+ tree_view_row_targets,
+ G_N_ELEMENTS (tree_view_row_targets),
+ GDK_ACTION_COPY);
+ gtk_tree_view_enable_model_drag_dest (GTK_TREE_VIEW (p->available_view),
+ tree_view_row_targets,
+ G_N_ELEMENTS (tree_view_row_targets),
+ GDK_ACTION_MOVE);
+}
+
+void
+ephy_tb_editor_set_parent (EphyTbEditor *tbe, GtkWidget *parent)
+{
+ gtk_window_set_transient_for (GTK_WINDOW (tbe->priv->window),
+ GTK_WINDOW (parent));
+}
+
+void
+ephy_tb_editor_show (EphyTbEditor *tbe)
+{
+ gtk_window_present (GTK_WINDOW (tbe->priv->window));
+}
+
+static void
+ephy_tb_editor_set_treeview_toolbar (EphyTbEditor *tbe, GtkTreeView *tv, EphyToolbar *tb)
+{
+ EphyTbTreeModel *tm = ephy_tb_tree_model_new ();
+ ephy_tb_tree_model_set_toolbar (tm, tb);
+ gtk_tree_view_set_model (tv, GTK_TREE_MODEL (tm));
+ g_object_unref (tm);
+}
+
+static void
+ephy_tb_editor_setup_treeview (EphyTbEditor *tbe, GtkTreeView *tv)
+{
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+ GtkTreeSelection *selection;
+
+ selection = gtk_tree_view_get_selection (tv);
+ column = gtk_tree_view_column_new ();
+ renderer = gtk_cell_renderer_pixbuf_new ();
+
+ gtk_tree_view_column_pack_start (column, renderer, FALSE);
+ gtk_tree_view_column_set_attributes (column, renderer,
+ "pixbuf", EPHY_TB_TREE_MODEL_COL_ICON,
+ NULL);
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (column, renderer, TRUE);
+ gtk_tree_view_column_set_attributes (column, renderer,
+ "text", EPHY_TB_TREE_MODEL_COL_NAME,
+ NULL);
+ gtk_tree_view_column_set_title (column, "Name");
+ gtk_tree_view_column_set_resizable (column, TRUE);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
+
+ g_signal_connect (tv, "button-press-event",
+ G_CALLBACK (ephy_tb_editor_treeview_button_press_event_cb), tbe);
+ g_signal_connect (selection, "changed",
+ G_CALLBACK (ephy_tb_editor_treeview_selection_changed_cb), tbe);
+}
+
+EphyToolbar *
+ephy_tb_editor_get_toolbar (EphyTbEditor *tbe)
+{
+ EphyTbEditorPrivate *p;
+
+ g_return_val_if_fail (EPHY_IS_TB_EDITOR (tbe), NULL);
+
+ p = tbe->priv;
+
+ return p->tb;
+}
+
+EphyToolbar *
+ephy_tb_editor_get_available (EphyTbEditor *tbe)
+{
+ EphyTbEditorPrivate *p;
+
+ g_return_val_if_fail (EPHY_IS_TB_EDITOR (tbe), NULL);
+
+ p = tbe->priv;
+
+ return p->available;
+}
+
+
+static void
+ephy_tb_editor_remove_used_items (EphyTbEditor *tbe)
+{
+ EphyTbEditorPrivate *p = tbe->priv;
+ const GSList *current_items;
+ const GSList *li;
+
+ g_return_if_fail (EPHY_IS_TOOLBAR (p->tb));
+ g_return_if_fail (EPHY_IS_TOOLBAR (p->available));
+
+ current_items = ephy_toolbar_get_item_list (p->tb);
+ for (li = current_items; li; li = li->next)
+ {
+ EphyTbItem *i = li->data;
+ if (ephy_tb_item_is_unique (i))
+ {
+ EphyTbItem *j = ephy_toolbar_get_item_by_id (p->available, i->id);
+ if (j)
+ {
+ ephy_toolbar_remove_item (p->available, j);
+ }
+ }
+ }
+}
+
+static void
+ephy_tb_editor_toolbar_changed_cb (EphyToolbar *tb, EphyTbEditor *tbe)
+{
+ EphyTbEditorPrivate *p = tbe->priv;
+
+ if (p->in_toolbar_changed)
+ {
+ return;
+ }
+
+ if (p->tb && p->available)
+ {
+ p->in_toolbar_changed = TRUE;
+ ephy_tb_editor_remove_used_items (tbe);
+ p->in_toolbar_changed = FALSE;
+ }
+}
+
+static gboolean
+ephy_tb_editor_treeview_button_press_event_cb (GtkWidget *widget,
+ GdkEventButton *event,
+ EphyTbEditor *tbe)
+{
+ EphyTbEditorPrivate *p = tbe->priv;
+
+ if (event->window != gtk_tree_view_get_bin_window (GTK_TREE_VIEW (widget)))
+ {
+ return FALSE;
+ }
+
+ if (event->type == GDK_2BUTTON_PRESS)
+ {
+ if (widget == p->current_view)
+ {
+ ephy_tb_editor_left_clicked_cb (NULL, tbe);
+ }
+ else if (widget == p->available_view)
+ {
+ ephy_tb_editor_right_clicked_cb (NULL, tbe);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+GtkButton *
+ephy_tb_editor_get_revert_button (EphyTbEditor *tbe)
+{
+ EphyTbEditorPrivate *p;
+ g_return_val_if_fail (EPHY_IS_TB_EDITOR (tbe), NULL);
+ p = tbe->priv;
+ g_return_val_if_fail (GTK_IS_BUTTON (p->revert_button), NULL);
+
+ return GTK_BUTTON (p->revert_button);
+
+}
+
diff --git a/lib/toolbar/ephy-toolbar-editor.h b/lib/toolbar/ephy-toolbar-editor.h
new file mode 100644
index 000000000..97ee10e7a
--- /dev/null
+++ b/lib/toolbar/ephy-toolbar-editor.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifndef EPHY_TOOLBAR_EDITOR_H
+#define EPHY_TOOLBAR_EDITOR_H
+
+#include <glib-object.h>
+#include <gtk/gtkbutton.h>
+
+#include "ephy-toolbar.h"
+
+G_BEGIN_DECLS
+
+/* object forward declarations */
+
+typedef struct _EphyTbEditor EphyTbEditor;
+typedef struct _EphyTbEditorClass EphyTbEditorClass;
+typedef struct _EphyTbEditorPrivate EphyTbEditorPrivate;
+
+/**
+ * TbEditor object
+ */
+
+#define EPHY_TYPE_TB_EDITOR (ephy_tb_editor_get_type())
+#define EPHY_TB_EDITOR(object) (G_TYPE_CHECK_INSTANCE_CAST((object), \
+ EPHY_TYPE_TB_EDITOR,\
+ EphyTbEditor))
+#define EPHY_TB_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EPHY_TYPE_TB_EDITOR,\
+ EphyTbEditorClass))
+#define EPHY_IS_TB_EDITOR(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), \
+ EPHY_TYPE_TB_EDITOR))
+#define EPHY_IS_TB_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EPHY_TYPE_TB_EDITOR))
+#define EPHY_TB_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPHY_TYPE_TB_EDITOR,\
+ EphyTbEditorClass))
+
+struct _EphyTbEditorClass
+{
+ GObjectClass parent_class;
+};
+
+/* Remember: fields are public read-only */
+struct _EphyTbEditor
+{
+ GObject parent_object;
+
+ EphyTbEditorPrivate *priv;
+};
+
+/* this class is abstract */
+
+GType ephy_tb_editor_get_type (void);
+EphyTbEditor * ephy_tb_editor_new (void);
+void ephy_tb_editor_set_toolbar (EphyTbEditor *tbe, EphyToolbar *tb);
+EphyToolbar * ephy_tb_editor_get_toolbar (EphyTbEditor *tbe);
+void ephy_tb_editor_set_available (EphyTbEditor *tbe, EphyToolbar *tb);
+EphyToolbar * ephy_tb_editor_get_available (EphyTbEditor *tbe);
+void ephy_tb_editor_set_parent (EphyTbEditor *tbe, GtkWidget *parent);
+void ephy_tb_editor_show (EphyTbEditor *tbe);
+/* the revert button is hidden initially */
+GtkButton * ephy_tb_editor_get_revert_button (EphyTbEditor *tbe);
+
+G_END_DECLS
+
+#endif
+
diff --git a/lib/toolbar/ephy-toolbar-item-factory.c b/lib/toolbar/ephy-toolbar-item-factory.c
new file mode 100644
index 000000000..1637100ae
--- /dev/null
+++ b/lib/toolbar/ephy-toolbar-item-factory.c
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "ephy-toolbar-item-factory.h"
+#include <string.h>
+
+#include "ephy-tbi-zoom.h"
+#include "ephy-tbi-separator.h"
+#include "ephy-tbi-favicon.h"
+#include "ephy-tbi-spinner.h"
+#include "ephy-tbi-location.h"
+#include "ephy-tbi-navigation-history.h"
+#include "ephy-tbi-std-toolitem.h"
+
+#define NOT_IMPLEMENTED g_warning ("not implemented: " G_STRLOC);
+//#define DEBUG_MSG(x) g_print x
+#define DEBUG_MSG(x)
+
+typedef EphyTbItem *(EphyTbItemConstructor) (void);
+
+typedef struct
+{
+ const char *type_name;
+ EphyTbItemConstructor *constructor;
+} EphyTbItemTypeInfo;
+
+static EphyTbItemTypeInfo ephy_tb_item_known_types[] =
+{
+ { "std_toolitem", (EphyTbItemConstructor *) ephy_tbi_std_toolitem_new },
+ { "navigation_history", (EphyTbItemConstructor *) ephy_tbi_navigation_history_new },
+ { "zoom", (EphyTbItemConstructor *) ephy_tbi_zoom_new },
+ { "location", (EphyTbItemConstructor *) ephy_tbi_location_new },
+ { "spinner", (EphyTbItemConstructor *) ephy_tbi_spinner_new },
+ { "favicon", (EphyTbItemConstructor *) ephy_tbi_favicon_new },
+ { "separator", (EphyTbItemConstructor *) ephy_tbi_separator_new },
+ { NULL, NULL }
+};
+
+EphyTbItem *
+ephy_toolbar_item_create_from_string (const gchar *str)
+{
+ EphyTbItem *ret = NULL;
+ gchar *type;
+ gchar *props;
+ gchar *id;
+ const gchar *rest;
+ const gchar *lpar;
+ const gchar *rpar;
+ const gchar *eq;
+ int i;
+
+ rest = str;
+
+ eq = strchr (rest, '=');
+ if (eq)
+ {
+ id = g_strndup (rest, eq - rest);
+ rest = eq + 1;
+ }
+ else
+ {
+ id = NULL;
+ }
+
+ lpar = strchr (rest, '(');
+ if (lpar)
+ {
+ type = g_strndup (rest, lpar - rest);
+ rest = lpar + 1;
+
+ rpar = strchr (rest, ')');
+ if (rpar)
+ {
+ props = g_strndup (rest, rpar - rest);
+ rest = rpar + 1;
+ }
+ else
+ {
+ props = g_strdup (rest);
+ }
+ }
+ else
+ {
+ type = g_strdup (rest);
+ props = NULL;
+ }
+
+ DEBUG_MSG (("ephytoolbar_item_create_from_string id=%s type=%s props=%s\n", id, type, props));
+
+ for (i = 0; ephy_tb_item_known_types[i].type_name; ++i)
+ {
+ if (!strcmp (type, ephy_tb_item_known_types[i].type_name))
+ {
+ ret = ephy_tb_item_known_types[i].constructor ();
+ if (id)
+ {
+ ephy_tb_item_set_id (ret, id);
+ }
+ if (props)
+ {
+ ephy_tb_item_parse_properties (ret, props);
+ }
+ }
+ }
+
+ if (!ret)
+ {
+ g_warning ("Error creating toolbar item of type %s", type);
+ }
+
+ if (id)
+ {
+ g_free (id);
+ }
+ if (type)
+ {
+ g_free (type);
+ }
+ if (props)
+ {
+ g_free (props);
+ }
+
+ return ret;
+}
+
+GSList *
+ephy_toolbar_list_item_types (void)
+{
+ int i;
+ GSList *ret = NULL;
+ for (i = 0; ephy_tb_item_known_types[i].type_name; ++i)
+ {
+ ret = g_slist_prepend (ret,
+ (gchar *) ephy_tb_item_known_types[i].type_name);
+ }
+ return ret;
+}
+
diff --git a/lib/toolbar/ephy-toolbar-item-factory.h b/lib/toolbar/ephy-toolbar-item-factory.h
new file mode 100644
index 000000000..e86179399
--- /dev/null
+++ b/lib/toolbar/ephy-toolbar-item-factory.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifndef EPHY_TOOLBAR_ITEM_FACTORY_H
+#define EPHY_TOOLBAR_ITEM_FACTORY_H
+
+#include "ephy-toolbar-item.h"
+
+G_BEGIN_DECLS
+
+EphyTbItem * ephy_toolbar_item_create_from_string (const gchar *str);
+GSList * ephy_toolbar_list_item_types (void);
+
+G_END_DECLS
+
+#endif
diff --git a/lib/toolbar/ephy-toolbar-item.c b/lib/toolbar/ephy-toolbar-item.c
new file mode 100644
index 000000000..f9c452f02
--- /dev/null
+++ b/lib/toolbar/ephy-toolbar-item.c
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <libgnome/gnome-i18n.h>
+
+#include "ephy-gobject-misc.h"
+#include "ephy-marshal.h"
+#include "ephy-toolbar-item.h"
+
+#define NOT_IMPLEMENTED g_warning ("not implemented: " G_STRLOC);
+//#define DEBUG_MSG(x) g_print x
+#define DEBUG_MSG(x)
+
+/**
+ * Private data
+ */
+struct _EphyTbItemPrivate
+{
+};
+
+/**
+ * Private functions, only availble from this file
+ */
+static void ephy_tb_item_class_init (EphyTbItemClass *klass);
+static void ephy_tb_item_init (EphyTbItem *tb);
+static void ephy_tb_item_finalize_impl (GObject *o);
+
+static gpointer g_object_class;
+
+/**
+ * TbItem object
+ */
+
+MAKE_GET_TYPE (ephy_tb_item, "EphyTbItem", EphyTbItem, ephy_tb_item_class_init,
+ ephy_tb_item_init, G_TYPE_OBJECT);
+
+static void
+ephy_tb_item_class_init (EphyTbItemClass *klass)
+{
+ G_OBJECT_CLASS (klass)->finalize = ephy_tb_item_finalize_impl;
+
+ g_object_class = g_type_class_peek_parent (klass);
+}
+
+static void
+ephy_tb_item_init (EphyTbItem *it)
+{
+ EphyTbItemPrivate *p = g_new0 (EphyTbItemPrivate, 1);
+ it->priv = p;
+ it->id = g_strdup ("");
+}
+
+static void
+ephy_tb_item_finalize_impl (GObject *o)
+{
+ EphyTbItem *it = EPHY_TB_ITEM (o);
+ EphyTbItemPrivate *p = it->priv;
+
+ g_free (it->id);
+ g_free (p);
+
+ DEBUG_MSG (("EphyTbItem finalized\n"));
+
+ G_OBJECT_CLASS (g_object_class)->finalize (o);
+}
+
+GtkWidget *
+ephy_tb_item_get_widget (EphyTbItem *i)
+{
+ return EPHY_TB_ITEM_GET_CLASS (i)->get_widget (i);
+}
+
+GdkPixbuf *
+ephy_tb_item_get_icon (EphyTbItem *i)
+{
+ return EPHY_TB_ITEM_GET_CLASS (i)->get_icon (i);
+}
+
+gchar *
+ephy_tb_item_get_name_human (EphyTbItem *i)
+{
+ return EPHY_TB_ITEM_GET_CLASS (i)->get_name_human (i);
+}
+
+gchar *
+ephy_tb_item_to_string (EphyTbItem *i)
+{
+ return EPHY_TB_ITEM_GET_CLASS (i)->to_string (i);
+}
+
+gboolean
+ephy_tb_item_is_unique (EphyTbItem *i)
+{
+ return EPHY_TB_ITEM_GET_CLASS (i)->is_unique (i);
+}
+
+EphyTbItem *
+ephy_tb_item_clone (EphyTbItem *i)
+{
+ return EPHY_TB_ITEM_GET_CLASS (i)->clone (i);
+}
+
+void
+ephy_tb_item_add_to_bonobo_tb (EphyTbItem *i, BonoboUIComponent *ui,
+ const char *container_path, guint index)
+{
+ EPHY_TB_ITEM_GET_CLASS (i)->add_to_bonobo_tb (i, ui, container_path, index);
+}
+
+void
+ephy_tb_item_set_id (EphyTbItem *i, const gchar *id)
+{
+ g_return_if_fail (EPHY_IS_TB_ITEM (i));
+
+ g_free (i->id);
+ i->id = g_strdup (id);
+}
+
+void
+ephy_tb_item_parse_properties (EphyTbItem *i, const gchar *props)
+{
+ EPHY_TB_ITEM_GET_CLASS (i)->parse_properties (i, props);
+}
diff --git a/lib/toolbar/ephy-toolbar-item.h b/lib/toolbar/ephy-toolbar-item.h
new file mode 100644
index 000000000..29e46697f
--- /dev/null
+++ b/lib/toolbar/ephy-toolbar-item.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifndef EPHY_TOOLBAR_ITEM_H
+#define EPHY_TOOLBAR_ITEM_H
+
+#include <glib-object.h>
+
+#include <bonobo/bonobo-ui-component.h>
+#include <gtk/gtkwidget.h>
+
+G_BEGIN_DECLS
+
+/* object forward declarations */
+
+typedef struct _EphyTbItem EphyTbItem;
+typedef struct _EphyTbItemClass EphyTbItemClass;
+typedef struct _EphyTbItemPrivate EphyTbItemPrivate;
+
+/**
+ * TbItem object
+ */
+
+#define EPHY_TYPE_TB_ITEM (ephy_tb_item_get_type())
+#define EPHY_TB_ITEM(object) (G_TYPE_CHECK_INSTANCE_CAST((object), EPHY_TYPE_TB_ITEM,\
+ EphyTbItem))
+#define EPHY_TB_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EPHY_TYPE_TB_ITEM,\
+ EphyTbItemClass))
+#define EPHY_IS_TB_ITEM(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), EPHY_TYPE_TB_ITEM))
+#define EPHY_IS_TB_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EPHY_TYPE_TB_ITEM))
+#define EPHY_TB_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPHY_TYPE_TB_ITEM,\
+ EphyTbItemClass))
+
+struct _EphyTbItemClass
+{
+ GObjectClass parent_class;
+
+ /* virtual */
+ GtkWidget * (*get_widget) (EphyTbItem *it);
+ GdkPixbuf * (*get_icon) (EphyTbItem *it);
+ gchar * (*get_name_human) (EphyTbItem *it);
+ gchar * (*to_string) (EphyTbItem *it);
+ gboolean (*is_unique) (EphyTbItem *it);
+ void (*add_to_bonobo_tb) (EphyTbItem *it, BonoboUIComponent *ui,
+ const char *container_path, guint index);
+ EphyTbItem * (*clone) (EphyTbItem *it);
+ void (*parse_properties) (EphyTbItem *it, const gchar *props);
+};
+
+/* Remember: fields are public read-only */
+struct _EphyTbItem
+{
+ GObject parent_object;
+
+ gchar *id;
+
+ EphyTbItemPrivate *priv;
+};
+
+/* this class is abstract */
+
+GType ephy_tb_item_get_type (void);
+GtkWidget * ephy_tb_item_get_widget (EphyTbItem *i);
+GdkPixbuf * ephy_tb_item_get_icon (EphyTbItem *i);
+gchar * ephy_tb_item_get_name_human (EphyTbItem *i);
+gchar * ephy_tb_item_to_string (EphyTbItem *i);
+gboolean ephy_tb_item_is_unique (EphyTbItem *i);
+void ephy_tb_item_add_to_bonobo_tb (EphyTbItem *i, BonoboUIComponent *ui,
+ const char *container_path, guint index);
+EphyTbItem * ephy_tb_item_clone (EphyTbItem *i);
+void ephy_tb_item_set_id (EphyTbItem *i, const gchar *id);
+void ephy_tb_item_parse_properties (EphyTbItem *i, const gchar *props);
+
+G_END_DECLS
+
+#endif
diff --git a/lib/toolbar/ephy-toolbar-tree-model.c b/lib/toolbar/ephy-toolbar-tree-model.c
new file mode 100644
index 000000000..91acba952
--- /dev/null
+++ b/lib/toolbar/ephy-toolbar-tree-model.c
@@ -0,0 +1,784 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernándezs Pascual <ric@users.sourceforge.net>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gtk/gtktreednd.h>
+#include <glib-object.h>
+#include <string.h>
+
+#include "ephy-toolbar-tree-model.h"
+
+#define NOT_IMPLEMENTED g_warning ("not implemented: " G_STRLOC);
+//#define DEBUG_MSG(x) g_print x
+#define DEBUG_MSG(x)
+
+#define VALID_ITER(iter, tb_tree_model) (iter!= NULL && iter->user_data != NULL \
+ && tb_tree_model->stamp == iter->stamp)
+
+/**
+ * Private data
+ */
+struct _EphyTbTreeModelPrivate
+{
+ EphyToolbar *tb;
+ GSList *curr_items;
+};
+
+/**
+ * Private functions
+ */
+static void ephy_tb_tree_model_init (EphyTbTreeModel *tb_tree_model);
+static void ephy_tb_tree_model_class_init (EphyTbTreeModelClass *tb_tree_model_class);
+static void ephy_tb_tree_model_tb_tree_model_init (GtkTreeModelIface *iface);
+static void ephy_tb_tree_model_drag_source_init (GtkTreeDragSourceIface *iface);
+static void ephy_tb_tree_model_drag_dest_init (GtkTreeDragDestIface *iface);
+static void ephy_tb_tree_model_finalize_impl (GObject *object);
+static guint ephy_tb_tree_model_get_flags_impl (GtkTreeModel *tb_tree_model);
+static gint ephy_tb_tree_model_get_n_columns_impl (GtkTreeModel *tb_tree_model);
+static GType ephy_tb_tree_model_get_column_type_impl (GtkTreeModel *tb_tree_model,
+ gint index);
+static gboolean ephy_tb_tree_model_get_iter_impl (GtkTreeModel *tb_tree_model,
+ GtkTreeIter *iter,
+ GtkTreePath *path);
+static GtkTreePath * ephy_tb_tree_model_get_path_impl (GtkTreeModel *tb_tree_model,
+ GtkTreeIter *iter);
+static void ephy_tb_tree_model_get_value_impl (GtkTreeModel *tb_tree_model,
+ GtkTreeIter *iter,
+ gint column,
+ GValue *value);
+static gboolean ephy_tb_tree_model_iter_next_impl (GtkTreeModel *tb_tree_model,
+ GtkTreeIter *iter);
+static gboolean ephy_tb_tree_model_iter_children_impl (GtkTreeModel *tb_tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent);
+static gboolean ephy_tb_tree_model_iter_has_child_impl (GtkTreeModel *tb_tree_model,
+ GtkTreeIter *iter);
+static gint ephy_tb_tree_model_iter_n_children_impl (GtkTreeModel *tb_tree_model,
+ GtkTreeIter *iter);
+static gboolean ephy_tb_tree_model_iter_nth_child_impl (GtkTreeModel *tb_tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent,
+ gint n);
+static gboolean ephy_tb_tree_model_iter_parent_impl (GtkTreeModel *tb_tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *child);
+
+/* DND interfaces */
+static gboolean ephy_tb_tree_model_drag_data_delete_impl(GtkTreeDragSource *drag_source,
+ GtkTreePath *path);
+static gboolean ephy_tb_tree_model_drag_data_get_impl (GtkTreeDragSource *drag_source,
+ GtkTreePath *path,
+ GtkSelectionData *selection_data);
+static gboolean ephy_tb_tree_model_drag_data_received_impl (GtkTreeDragDest *drag_dest,
+ GtkTreePath *dest,
+ GtkSelectionData *selection_data);
+static gboolean ephy_tb_tree_model_row_drop_possible_impl (GtkTreeDragDest *drag_dest,
+ GtkTreePath *dest_path,
+ GtkSelectionData *selection_data);
+
+/* helper functions */
+static void ephy_tb_tree_model_toolbar_changed_cb (EphyToolbar *tb, EphyTbTreeModel *tm);
+static void ephy_tb_tree_model_update (EphyTbTreeModel *tm);
+
+
+static GObjectClass *parent_class = NULL;
+
+GtkType
+ephy_tb_tree_model_get_type (void)
+{
+ static GType tb_tree_model_type = 0;
+
+ if (!tb_tree_model_type)
+ {
+ static const GTypeInfo tb_tree_model_info =
+ {
+ sizeof (EphyTbTreeModelClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) ephy_tb_tree_model_class_init,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (EphyTbTreeModel),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) ephy_tb_tree_model_init
+ };
+
+ static const GInterfaceInfo tb_gtk_tree_model_info =
+ {
+ (GInterfaceInitFunc) ephy_tb_tree_model_tb_tree_model_init,
+ NULL,
+ NULL
+ };
+
+ static const GInterfaceInfo drag_source_info =
+ {
+ (GInterfaceInitFunc) ephy_tb_tree_model_drag_source_init,
+ NULL,
+ NULL
+ };
+
+ static const GInterfaceInfo drag_dest_info =
+ {
+ (GInterfaceInitFunc) ephy_tb_tree_model_drag_dest_init,
+ NULL,
+ NULL
+ };
+
+ tb_tree_model_type = g_type_register_static (G_TYPE_OBJECT, "EphyTbTreeModel",
+ &tb_tree_model_info, 0);
+
+ g_type_add_interface_static (tb_tree_model_type,
+ GTK_TYPE_TREE_MODEL,
+ &tb_gtk_tree_model_info);
+ g_type_add_interface_static (tb_tree_model_type,
+ GTK_TYPE_TREE_DRAG_SOURCE,
+ &drag_source_info);
+ g_type_add_interface_static (tb_tree_model_type,
+ GTK_TYPE_TREE_DRAG_DEST,
+ &drag_dest_info);
+ }
+
+ return tb_tree_model_type;
+}
+
+static void
+ephy_tb_tree_model_class_init (EphyTbTreeModelClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ object_class = (GObjectClass *) class;
+
+ object_class->finalize = ephy_tb_tree_model_finalize_impl;
+}
+
+static void
+ephy_tb_tree_model_tb_tree_model_init (GtkTreeModelIface *iface)
+{
+ iface->get_flags = ephy_tb_tree_model_get_flags_impl;
+ iface->get_n_columns = ephy_tb_tree_model_get_n_columns_impl;
+ iface->get_column_type = ephy_tb_tree_model_get_column_type_impl;
+ iface->get_iter = ephy_tb_tree_model_get_iter_impl;
+ iface->get_path = ephy_tb_tree_model_get_path_impl;
+ iface->get_value = ephy_tb_tree_model_get_value_impl;
+ iface->iter_next = ephy_tb_tree_model_iter_next_impl;
+ iface->iter_children = ephy_tb_tree_model_iter_children_impl;
+ iface->iter_has_child = ephy_tb_tree_model_iter_has_child_impl;
+ iface->iter_n_children = ephy_tb_tree_model_iter_n_children_impl;
+ iface->iter_nth_child = ephy_tb_tree_model_iter_nth_child_impl;
+ iface->iter_parent = ephy_tb_tree_model_iter_parent_impl;
+}
+
+static void
+ephy_tb_tree_model_drag_source_init (GtkTreeDragSourceIface *iface)
+{
+ iface->drag_data_delete = ephy_tb_tree_model_drag_data_delete_impl;
+ iface->drag_data_get = ephy_tb_tree_model_drag_data_get_impl;
+}
+
+static void
+ephy_tb_tree_model_drag_dest_init (GtkTreeDragDestIface *iface)
+{
+ iface->drag_data_received = ephy_tb_tree_model_drag_data_received_impl;
+ iface->row_drop_possible = ephy_tb_tree_model_row_drop_possible_impl;
+}
+
+static void
+ephy_tb_tree_model_init (EphyTbTreeModel *tb_tree_model)
+{
+ EphyTbTreeModelPrivate *p = g_new0 (EphyTbTreeModelPrivate, 1);
+ tb_tree_model->priv = p;
+
+ do
+ {
+ tb_tree_model->stamp = g_random_int ();
+ }
+ while (tb_tree_model->stamp == 0);
+}
+
+EphyTbTreeModel *
+ephy_tb_tree_model_new (void)
+{
+ EphyTbTreeModel *ret = EPHY_TB_TREE_MODEL (g_object_new (EPHY_TYPE_TB_TREE_MODEL, NULL));
+ return ret;
+}
+
+
+void
+ephy_tb_tree_model_set_toolbar (EphyTbTreeModel *tm, EphyToolbar *tb)
+{
+ EphyTbTreeModelPrivate *p;
+
+ g_return_if_fail (EPHY_IS_TB_TREE_MODEL (tm));
+ g_return_if_fail (EPHY_IS_TOOLBAR (tb));
+
+ p = tm->priv;
+
+ if (p->tb)
+ {
+ g_signal_handlers_disconnect_matched (p->tb, G_SIGNAL_MATCH_DATA, 0, 0,
+ NULL, NULL, tm);
+ g_object_unref (p->tb);
+ }
+
+ p->tb = g_object_ref (tb);
+ g_signal_connect (p->tb, "changed", G_CALLBACK (ephy_tb_tree_model_toolbar_changed_cb), tm);
+
+ ephy_tb_tree_model_update (tm);
+}
+
+static void
+ephy_tb_tree_model_finalize_impl (GObject *object)
+{
+ EphyTbTreeModel *tm = EPHY_TB_TREE_MODEL (object);
+ EphyTbTreeModelPrivate *p = tm->priv;
+
+ DEBUG_MSG (("Finalizing a EphyTbTreeModel\n"));
+
+ if (p->tb)
+ {
+ g_signal_handlers_disconnect_matched (p->tb, G_SIGNAL_MATCH_DATA, 0, 0,
+ NULL, NULL, tm);
+ g_object_unref (p->tb);
+ }
+
+ g_slist_foreach (p->curr_items, (GFunc) g_object_unref, NULL);
+ g_slist_free (p->curr_items);
+ g_free (p);
+
+ (* parent_class->finalize) (object);
+}
+
+/* fulfill the GtkTreeModel requirements */
+
+static guint
+ephy_tb_tree_model_get_flags_impl (GtkTreeModel *tb_tree_model)
+{
+ return 0;
+}
+
+static gint
+ephy_tb_tree_model_get_n_columns_impl (GtkTreeModel *tb_tree_model)
+{
+ return EPHY_TB_TREE_MODEL_NUM_COLUMS;
+}
+
+static GType
+ephy_tb_tree_model_get_column_type_impl (GtkTreeModel *tb_tree_model,
+ gint index)
+{
+ g_return_val_if_fail (EPHY_IS_TB_TREE_MODEL (tb_tree_model), G_TYPE_INVALID);
+ g_return_val_if_fail ((index < EPHY_TB_TREE_MODEL_NUM_COLUMS) && (index >= 0), G_TYPE_INVALID);
+
+ switch (index)
+ {
+ case EPHY_TB_TREE_MODEL_COL_ICON:
+ return GDK_TYPE_PIXBUF;
+ break;
+ case EPHY_TB_TREE_MODEL_COL_NAME:
+ return G_TYPE_STRING;
+ break;
+ default:
+ g_assert_not_reached ();
+ return G_TYPE_INVALID;
+ break;
+ }
+}
+
+static gboolean
+ephy_tb_tree_model_get_iter_impl (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreePath *path)
+{
+ EphyTbTreeModel *tb_tree_model = (EphyTbTreeModel *) tree_model;
+ EphyTbTreeModelPrivate *p;
+ GSList *li;
+ gint i;
+
+ g_return_val_if_fail (EPHY_IS_TB_TREE_MODEL (tb_tree_model), FALSE);
+ g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE);
+
+ p = tb_tree_model->priv;
+ i = gtk_tree_path_get_indices (path)[0];
+ li = g_slist_nth (p->curr_items, i);
+
+ if (!li)
+ {
+ return FALSE;
+ }
+
+ iter->stamp = tb_tree_model->stamp;
+ iter->user_data = li;
+
+ return TRUE;
+}
+
+static GtkTreePath *
+ephy_tb_tree_model_get_path_impl (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ EphyTbTreeModel *tb_tree_model = (EphyTbTreeModel *) tree_model;
+ EphyTbTreeModelPrivate *p;
+ gint i;
+
+ g_return_val_if_fail (EPHY_IS_TB_TREE_MODEL (tb_tree_model), NULL);
+ g_return_val_if_fail (iter != NULL, NULL);
+ g_return_val_if_fail (iter->user_data != NULL, NULL);
+ g_return_val_if_fail (iter->stamp == tb_tree_model->stamp, NULL);
+
+ p = tb_tree_model->priv;
+
+ i = g_slist_position (p->curr_items, iter->user_data);
+ if (i < 0)
+ {
+ return NULL;
+ }
+ else
+ {
+ GtkTreePath *retval;
+ retval = gtk_tree_path_new ();
+ gtk_tree_path_append_index (retval, i);
+ return retval;
+ }
+}
+
+
+static void
+ephy_tb_tree_model_get_value_impl (GtkTreeModel *tb_tree_model,
+ GtkTreeIter *iter,
+ gint column,
+ GValue *value)
+{
+ EphyTbItem *it;
+ GdkPixbuf *pb;
+ gchar *s;
+
+ g_return_if_fail (EPHY_IS_TB_TREE_MODEL (tb_tree_model));
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (iter->stamp == EPHY_TB_TREE_MODEL (tb_tree_model)->stamp);
+ g_return_if_fail (EPHY_IS_TB_ITEM (((GSList *) iter->user_data)->data));
+ g_return_if_fail (column < EPHY_TB_TREE_MODEL_NUM_COLUMS);
+
+ it = ((GSList *) iter->user_data)->data;
+
+ switch (column) {
+ case EPHY_TB_TREE_MODEL_COL_ICON:
+ g_value_init (value, GDK_TYPE_PIXBUF);
+ pb = ephy_tb_item_get_icon (it);
+ g_value_set_object (value, pb);
+ break;
+ case EPHY_TB_TREE_MODEL_COL_NAME:
+ g_value_init (value, G_TYPE_STRING);
+ s = ephy_tb_item_get_name_human (it);
+ g_value_set_string_take_ownership (value, s);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static gboolean
+ephy_tb_tree_model_iter_next_impl (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ g_return_val_if_fail (EPHY_IS_TB_TREE_MODEL (tree_model), FALSE);
+ g_return_val_if_fail (EPHY_TB_TREE_MODEL (tree_model)->stamp == iter->stamp, FALSE);
+
+ iter->user_data = ((GSList *) (iter->user_data))->next;
+ return (iter->user_data != NULL);
+}
+
+static gboolean
+ephy_tb_tree_model_iter_children_impl (GtkTreeModel *tb_tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent)
+{
+ if (parent)
+ {
+ /* this is a list, nodes have no children */
+ return FALSE;
+ }
+ else
+ {
+ /* but if parent == NULL we return the list itself as children of the
+ * "root"
+ */
+ EphyTbTreeModel *tm = EPHY_TB_TREE_MODEL (tb_tree_model);
+ EphyTbTreeModelPrivate *p = tm->priv;
+
+ iter->stamp = tm->stamp;
+ iter->user_data = p->curr_items;
+ return (p->curr_items != NULL);
+ }
+}
+
+static gboolean
+ephy_tb_tree_model_iter_has_child_impl (GtkTreeModel *tb_tree_model,
+ GtkTreeIter *iter)
+{
+ return FALSE;
+}
+
+static gint
+ephy_tb_tree_model_iter_n_children_impl (GtkTreeModel *tb_tree_model,
+ GtkTreeIter *iter)
+{
+ EphyTbTreeModel *tm = (EphyTbTreeModel *) tb_tree_model;
+ EphyTbTreeModelPrivate *p;
+ g_return_val_if_fail (EPHY_IS_TB_TREE_MODEL (tm), -1);
+
+ p = tm->priv;
+
+ if (iter == NULL)
+ {
+ return g_slist_length (p->curr_items);
+ }
+
+ g_return_val_if_fail (tm->stamp == iter->stamp, -1);
+ return 0;
+}
+
+static gboolean
+ephy_tb_tree_model_iter_nth_child_impl (GtkTreeModel *tb_tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent,
+ gint n)
+{
+ EphyTbTreeModel *tm = (EphyTbTreeModel *) tb_tree_model;
+ EphyTbTreeModelPrivate *p;
+ g_return_val_if_fail (EPHY_IS_TB_TREE_MODEL (tm), FALSE);
+
+ p = tm->priv;
+
+ if (parent)
+ {
+ return FALSE;
+ }
+ else
+ {
+ GSList *li = g_slist_nth (p->curr_items, n);
+
+ if (li)
+ {
+ iter->stamp = tm->stamp;
+ iter->user_data = li;
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+}
+
+static gboolean
+ephy_tb_tree_model_iter_parent_impl (GtkTreeModel *tb_tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *child)
+{
+ return FALSE;
+}
+
+
+
+/* DND */
+
+
+static gboolean
+ephy_tb_tree_model_drag_data_delete_impl (GtkTreeDragSource *drag_source,
+ GtkTreePath *path)
+{
+ GtkTreeIter iter;
+ EphyTbTreeModel *tm;
+
+ g_return_val_if_fail (EPHY_IS_TB_TREE_MODEL (drag_source), FALSE);
+
+ tm = EPHY_TB_TREE_MODEL (drag_source);
+
+ DEBUG_MSG (("in ephy_tb_tree_model_drag_data_delete_impl\n"));
+
+ if (ephy_tb_tree_model_get_iter_impl (GTK_TREE_MODEL (tm), &iter, path))
+ {
+ EphyTbTreeModelPrivate *p = tm->priv;
+ EphyTbItem *it = ephy_tb_tree_model_item_from_iter (tm, &iter);
+ EphyTbItem *delete_hack;
+ if ((delete_hack = g_object_get_data (G_OBJECT (tm),
+ "gul-toolbar-tree-model-dnd-delete-hack")) != NULL)
+ {
+ g_return_val_if_fail (EPHY_IS_TB_ITEM (delete_hack), FALSE);
+ g_object_ref (delete_hack);
+
+ g_object_set_data (G_OBJECT (tm),
+ "gul-toolbar-tree-model-dnd-delete-hack", NULL);
+
+ if (!strcmp (delete_hack->id, it->id))
+ {
+ g_object_unref (delete_hack);
+ return FALSE;
+ }
+ g_object_unref (delete_hack);
+ }
+
+ ephy_toolbar_remove_item (p->tb, it);
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+static gboolean
+ephy_tb_tree_model_drag_data_get_impl (GtkTreeDragSource *drag_source,
+ GtkTreePath *path,
+ GtkSelectionData *selection_data)
+{
+ g_return_val_if_fail (EPHY_IS_TB_TREE_MODEL (drag_source), FALSE);
+
+ /* Note that we don't need to handle the GTK_TB_TREE_MODEL_ROW
+ * target, because the default handler does it for us, but
+ * we do anyway for the convenience of someone maybe overriding the
+ * default handler.
+ */
+
+ if (gtk_tree_set_row_drag_data (selection_data,
+ GTK_TREE_MODEL (drag_source),
+ path))
+ {
+ return TRUE;
+ }
+ else
+ {
+ /* to string ? */
+ }
+
+ return FALSE;
+}
+
+
+static gboolean
+ephy_tb_tree_model_drag_data_received_impl (GtkTreeDragDest *drag_dest,
+ GtkTreePath *dest,
+ GtkSelectionData *selection_data)
+{
+ EphyTbTreeModel *tbm;
+ GtkTreeModel *src_model = NULL;
+ GtkTreePath *src_path = NULL;
+
+ g_return_val_if_fail (EPHY_IS_TB_TREE_MODEL (drag_dest), FALSE);
+ g_return_val_if_fail (gtk_tree_path_get_depth (dest) == 1, FALSE);
+
+ tbm = EPHY_TB_TREE_MODEL (drag_dest);
+
+ DEBUG_MSG (("in ephy_tb_tree_model_drag_data_received_impl\n"));
+
+ if (gtk_tree_get_row_drag_data (selection_data,
+ &src_model,
+ &src_path)
+ && EPHY_IS_TB_TREE_MODEL (src_model))
+ {
+ /* copy the item */
+
+ GtkTreeIter src_iter;
+ EphyTbItem *it;
+ int idx = gtk_tree_path_get_indices (dest)[0];
+
+ if (!gtk_tree_model_get_iter (src_model,
+ &src_iter,
+ src_path))
+ {
+ gtk_tree_path_free (src_path);
+ return FALSE;
+ }
+ gtk_tree_path_free (src_path);
+
+ if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (drag_dest),
+ "gtk-tree-model-drop-append")))
+ {
+ ++idx;
+ }
+
+ it = ephy_tb_item_clone (EPHY_TB_ITEM (((GSList *)src_iter.user_data)->data));
+ ephy_toolbar_add_item (tbm->priv->tb, it, idx);
+
+ /* hack */
+ if (src_model == GTK_TREE_MODEL (drag_dest)
+ && ephy_toolbar_get_check_unique (EPHY_TB_TREE_MODEL (src_model)->priv->tb)
+ && ephy_tb_item_is_unique (it))
+ {
+ g_object_set_data_full (G_OBJECT (src_model),
+ "gul-toolbar-tree-model-dnd-delete-hack", it,
+ g_object_unref);
+ }
+ else
+ {
+ g_object_unref (it);
+ }
+
+ g_object_set_data (G_OBJECT (drag_dest), "gtk-tree-model-drop-append", NULL);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+ephy_tb_tree_model_row_drop_possible_impl (GtkTreeDragDest *drag_dest,
+ GtkTreePath *dest_path,
+ GtkSelectionData *selection_data)
+{
+ GtkTreeModel *src_model = NULL;
+ GtkTreePath *src_path = NULL;
+ gboolean retval = FALSE;
+ EphyTbTreeModel *tm;
+ EphyTbTreeModelPrivate *p;
+
+ g_return_val_if_fail (EPHY_IS_TB_TREE_MODEL (drag_dest), FALSE);
+ tm = EPHY_TB_TREE_MODEL (drag_dest);
+ p = tm->priv;
+
+ if (gtk_tree_path_get_depth (dest_path) != 1)
+ {
+ return FALSE;
+ }
+ if (!gtk_tree_get_row_drag_data (selection_data,
+ &src_model,
+ &src_path))
+ {
+ return FALSE;
+ }
+
+ /* can drop before any existing node, or before one past any existing. */
+
+ retval = (gtk_tree_path_get_indices (dest_path)[0] <= (gint) g_slist_length (p->curr_items));
+
+ gtk_tree_path_free (src_path);
+
+ return retval;
+}
+
+
+EphyTbItem *
+ephy_tb_tree_model_item_from_iter (EphyTbTreeModel *tm, GtkTreeIter *iter)
+{
+ return iter ? EPHY_TB_ITEM (((GSList *) iter->user_data)->data) : NULL;
+}
+
+static void
+ephy_tb_tree_model_toolbar_changed_cb (EphyToolbar *tb, EphyTbTreeModel *tm)
+{
+ ephy_tb_tree_model_update (tm);
+}
+
+static void
+ephy_tb_tree_model_update (EphyTbTreeModel *tm)
+{
+ EphyTbTreeModelPrivate *p;
+ GSList *new_items;
+ GSList *old_items;
+ GSList *li;
+ GSList *lj;
+ int i;
+
+ g_return_if_fail (EPHY_IS_TB_TREE_MODEL (tm));
+ p = tm->priv;
+ g_return_if_fail (EPHY_IS_TOOLBAR (p->tb));
+
+ old_items = p->curr_items;
+ new_items = g_slist_copy ((GSList *) ephy_toolbar_get_item_list (p->tb));
+ g_slist_foreach (new_items, (GFunc) g_object_ref, NULL);
+ p->curr_items = new_items;
+
+ li = new_items;
+ lj = old_items;
+ i = 0;
+
+ while (li && lj)
+ {
+ if (li->data == lj->data)
+ {
+ li = li->next;
+ lj = lj->next;
+ ++i;
+ }
+ else if (lj->next && lj->next->data == li->data)
+ {
+ GtkTreePath *p = gtk_tree_path_new ();
+ gtk_tree_path_append_index (p, i);
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (tm), p);
+ gtk_tree_path_free (p);
+ lj = lj->next;
+ }
+ else if (li->next && li->next->data == lj->data)
+ {
+ GtkTreePath *p = gtk_tree_path_new ();
+ GtkTreeIter iter;
+ iter.stamp = tm->stamp;
+ iter.user_data = li;
+ gtk_tree_path_append_index (p, i);
+ gtk_tree_model_row_inserted (GTK_TREE_MODEL (tm), p, &iter);
+ gtk_tree_path_free (p);
+ li = li->next;
+ ++i;
+ }
+ else
+ {
+ GtkTreePath *p = gtk_tree_path_new ();
+ GtkTreeIter iter;
+ iter.stamp = tm->stamp;
+ iter.user_data = li;
+ gtk_tree_path_append_index (p, i);
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (tm), p);
+ gtk_tree_model_row_inserted (GTK_TREE_MODEL (tm), p, &iter);
+ gtk_tree_path_free (p);
+ lj = lj->next;
+ li = li->next;
+ ++i;
+ }
+ }
+
+ while (li)
+ {
+ GtkTreePath *p = gtk_tree_path_new ();
+ GtkTreeIter iter;
+ iter.stamp = tm->stamp;
+ iter.user_data = li;
+ gtk_tree_path_append_index (p, i);
+ gtk_tree_model_row_inserted (GTK_TREE_MODEL (tm), p, &iter);
+ gtk_tree_path_free (p);
+ li = li->next;
+ ++i;
+ }
+
+ while (lj)
+ {
+ GtkTreePath *p = gtk_tree_path_new ();
+ gtk_tree_path_append_index (p, i);
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (tm), p);
+ gtk_tree_path_free (p);
+ lj = lj->next;
+ }
+
+ g_slist_foreach (old_items, (GFunc) g_object_unref, NULL);
+ g_slist_free (old_items);
+}
+
diff --git a/lib/toolbar/ephy-toolbar-tree-model.h b/lib/toolbar/ephy-toolbar-tree-model.h
new file mode 100644
index 000000000..893e6ba9a
--- /dev/null
+++ b/lib/toolbar/ephy-toolbar-tree-model.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernándezs Pascual <ric@users.sourceforge.net>
+ *
+ * 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.
+ */
+
+#ifndef EPHY_TOOLBAR_TREE_MODEL_H
+#define EPHY_TOOLBAR_TREE_MODEL_H
+
+#include <gtk/gtktreemodel.h>
+#include "ephy-toolbar.h"
+
+G_BEGIN_DECLS
+
+/* object forward declarations */
+
+typedef struct _EphyTbTreeModel EphyTbTreeModel;
+typedef struct _EphyTbTreeModelClass EphyTbTreeModelClass;
+typedef struct _EphyTbTreeModelPrivate EphyTbTreeModelPrivate;
+
+typedef enum {
+ EPHY_TB_TREE_MODEL_COL_ICON,
+ EPHY_TB_TREE_MODEL_COL_NAME,
+ EPHY_TB_TREE_MODEL_NUM_COLUMS
+} EphyTbTreeModelColumn;
+
+/**
+ * Tb tree model object
+ */
+
+#define EPHY_TYPE_TB_TREE_MODEL (ephy_tb_tree_model_get_type())
+#define EPHY_TB_TREE_MODEL(object) (G_TYPE_CHECK_INSTANCE_CAST((object), EPHY_TYPE_TB_TREE_MODEL,\
+ EphyTbTreeModel))
+#define EPHY_TB_TREE_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EPHY_TYPE_TB_TREE_MODEL,\
+ EphyTbTreeModelClass))
+#define EPHY_IS_TB_TREE_MODEL(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), EPHY_TYPE_TB_TREE_MODEL))
+#define EPHY_IS_TB_TREE_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EPHY_TYPE_TB_TREE_MODEL))
+#define EPHY_TB_TREE_MODEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPHY_TYPE_TB_TREE_MODEL,\
+ EphyTbTreeModelClass))
+
+struct _EphyTbTreeModel
+{
+ GObject parent;
+
+ EphyTbTreeModelPrivate *priv;
+ gint stamp;
+};
+
+struct _EphyTbTreeModelClass
+{
+ GObjectClass parent_class;
+};
+
+
+GtkType ephy_tb_tree_model_get_type (void);
+EphyTbTreeModel * ephy_tb_tree_model_new (void);
+void ephy_tb_tree_model_set_toolbar (EphyTbTreeModel *tm, EphyToolbar *tb);
+EphyTbItem * ephy_tb_tree_model_item_from_iter (EphyTbTreeModel *tm, GtkTreeIter *iter);
+
+G_END_DECLS
+
+#endif
diff --git a/lib/toolbar/ephy-toolbar.c b/lib/toolbar/ephy-toolbar.c
new file mode 100644
index 000000000..53598cc6c
--- /dev/null
+++ b/lib/toolbar/ephy-toolbar.c
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <libgnome/gnome-i18n.h>
+#include <string.h>
+#include "ephy-gobject-misc.h"
+#include "ephy-marshal.h"
+#include "ephy-toolbar.h"
+#include "ephy-toolbar-item-factory.h"
+#include "eel-gconf-extensions.h"
+
+#define NOT_IMPLEMENTED g_warning ("not implemented: " G_STRLOC);
+//#define DEBUG_MSG(x) g_print x
+#define DEBUG_MSG(x)
+
+/**
+ * Private data
+ */
+struct _EphyToolbarPrivate
+{
+ GSList *items;
+ guint gconf_notification_id;
+
+ gboolean check_unique;
+ gboolean fixed_order;
+ GSList *order; /* list of ids */
+};
+
+/**
+ * Private functions, only availble from this file
+ */
+static void ephy_toolbar_class_init (EphyToolbarClass *klass);
+static void ephy_toolbar_init (EphyToolbar *tb);
+static void ephy_toolbar_finalize_impl (GObject *o);
+static void ephy_toolbar_listen_to_gconf_cb (GConfClient* client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ gpointer user_data);
+static void ephy_toolbar_update_order (EphyToolbar *tb);
+
+
+static gpointer g_object_class;
+
+/* signals enums and ids */
+enum EphyToolbarSignalsEnum {
+ EPHY_TOOLBAR_CHANGED,
+ EPHY_TOOLBAR_LAST_SIGNAL
+};
+static gint EphyToolbarSignals[EPHY_TOOLBAR_LAST_SIGNAL];
+
+/**
+ * Toolbar object
+ */
+
+MAKE_GET_TYPE (ephy_toolbar, "EphyToolbar", EphyToolbar, ephy_toolbar_class_init,
+ ephy_toolbar_init, G_TYPE_OBJECT);
+
+static void
+ephy_toolbar_class_init (EphyToolbarClass *klass)
+{
+ G_OBJECT_CLASS (klass)->finalize = ephy_toolbar_finalize_impl;
+
+ EphyToolbarSignals[EPHY_TOOLBAR_CHANGED] = g_signal_new (
+ "changed", G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
+ G_STRUCT_OFFSET (EphyToolbarClass, changed),
+ NULL, NULL,
+ ephy_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_object_class = g_type_class_peek_parent (klass);
+}
+
+static void
+ephy_toolbar_init (EphyToolbar *tb)
+{
+ EphyToolbarPrivate *p = g_new0 (EphyToolbarPrivate, 1);
+ tb->priv = p;
+
+ p->check_unique = TRUE;
+}
+
+static void
+ephy_toolbar_finalize_impl (GObject *o)
+{
+ EphyToolbar *tb = EPHY_TOOLBAR (o);
+ EphyToolbarPrivate *p = tb->priv;
+
+ g_slist_foreach (p->items, (GFunc) g_object_unref, NULL);
+ g_slist_free (p->items);
+
+ if (p->gconf_notification_id)
+ {
+ eel_gconf_notification_remove (p->gconf_notification_id);
+ }
+
+ g_slist_foreach (p->order, (GFunc) g_free, NULL);
+ g_slist_free (p->order);
+
+ g_free (p);
+
+ DEBUG_MSG (("EphyToolbar finalized\n"));
+
+ G_OBJECT_CLASS (g_object_class)->finalize (o);
+}
+
+
+EphyToolbar *
+ephy_toolbar_new (void)
+{
+ EphyToolbar *ret = g_object_new (EPHY_TYPE_TOOLBAR, NULL);
+ return ret;
+}
+
+gboolean
+ephy_toolbar_parse (EphyToolbar *tb, const gchar *cfg)
+{
+ EphyToolbarPrivate *p = tb->priv;
+ GSList *list = NULL;
+ gchar **items;
+ int i;
+
+ g_return_val_if_fail (EPHY_IS_TOOLBAR (tb), FALSE);
+ g_return_val_if_fail (cfg != NULL, FALSE);
+
+ items = g_strsplit (cfg, ";", 9999);
+ if (!items) return FALSE;
+
+ for (i = 0; items[i]; ++i)
+ {
+ if (items[i][0])
+ {
+ EphyTbItem *it = ephy_toolbar_item_create_from_string (items[i]);
+
+ if (!it)
+ {
+ /* FIXME: this leaks everything... */
+ return FALSE;
+ }
+
+ list = g_slist_prepend (list, it);
+ }
+ }
+
+ g_strfreev (items);
+
+ g_slist_foreach (p->items, (GFunc) g_object_unref, NULL);
+ g_slist_free (p->items);
+ p->items = g_slist_reverse (list);
+
+ if (p->fixed_order)
+ {
+ ephy_toolbar_update_order (tb);
+ }
+
+ g_signal_emit (tb, EphyToolbarSignals[EPHY_TOOLBAR_CHANGED], 0);
+
+ return TRUE;
+}
+
+gchar *
+ephy_toolbar_to_string (EphyToolbar *tb)
+{
+ EphyToolbarPrivate *p = tb->priv;
+ gchar *ret;
+ GString *str = g_string_new ("");
+ GSList *li;
+
+ for (li = p->items; li; li = li->next)
+ {
+ EphyTbItem *it = li->data;
+ gchar *s = ephy_tb_item_to_string (it);
+ g_string_append (str, s);
+ if (li->next)
+ {
+ g_string_append (str, ";");
+ }
+ g_free (s);
+ }
+
+ ret = str->str;
+ g_string_free (str, FALSE);
+ return ret;
+}
+
+static void
+ephy_toolbar_listen_to_gconf_cb (GConfClient* client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ gpointer user_data)
+{
+ EphyToolbar *tb = user_data;
+ GConfValue *value;
+ const char *str;
+
+ g_return_if_fail (EPHY_IS_TOOLBAR (tb));
+
+ value = gconf_entry_get_value (entry);
+ str = gconf_value_get_string (value);
+
+ DEBUG_MSG (("in ephy_toolbar_listen_to_gconf_cb\n"));
+
+ ephy_toolbar_parse (tb, str);
+}
+
+/**
+ * Listen to changes in the toolbar configuration. Returns TRUE if the
+ * current configuration is valid.
+ */
+gboolean
+ephy_toolbar_listen_to_gconf (EphyToolbar *tb, const gchar *gconf_key)
+{
+ EphyToolbarPrivate *p = tb->priv;
+ gchar *s;
+ gboolean ret = FALSE;
+
+ if (p->gconf_notification_id)
+ {
+ eel_gconf_notification_remove (p->gconf_notification_id);
+ }
+
+ s = eel_gconf_get_string (gconf_key);
+ if (s)
+ {
+ ret = ephy_toolbar_parse (tb, s);
+ g_free (s);
+ }
+
+ p->gconf_notification_id = eel_gconf_notification_add (gconf_key,
+ ephy_toolbar_listen_to_gconf_cb,
+ tb);
+
+ DEBUG_MSG (("listening to %s, %d (FIXME: does not seem to work)\n",
+ gconf_key, p->gconf_notification_id));
+
+ return ret;
+}
+
+EphyTbItem *
+ephy_toolbar_get_item_by_id (EphyToolbar *tb, const gchar *id)
+{
+ EphyToolbarPrivate *p = tb->priv;
+ GSList *li;
+
+ for (li = p->items; li; li = li->next)
+ {
+ EphyTbItem *i = li->data;
+ if (i->id && !strcmp (i->id, id))
+ {
+ return i;
+ }
+ }
+ return NULL;
+}
+
+const GSList *
+ephy_toolbar_get_item_list (EphyToolbar *tb)
+{
+ EphyToolbarPrivate *p = tb->priv;
+ return p->items;
+}
+
+void
+ephy_toolbar_add_item (EphyToolbar *tb, EphyTbItem *it, gint index)
+{
+ EphyToolbarPrivate *p = tb->priv;
+ EphyTbItem *old_it;
+
+ g_return_if_fail (g_slist_find (p->items, it) == NULL);
+
+ if (p->check_unique && ephy_tb_item_is_unique (it)
+ && (old_it = ephy_toolbar_get_item_by_id (tb, it->id)) != NULL)
+ {
+ GSList *old_it_link;
+ if (p->fixed_order)
+ {
+ return;
+ }
+ old_it_link = g_slist_find (p->items, old_it);
+ p->items = g_slist_insert (p->items, old_it, index);
+ p->items = g_slist_delete_link (p->items, old_it_link);
+
+ }
+ else
+ {
+ if (p->fixed_order)
+ {
+ GSList *li;
+ if (ephy_toolbar_get_item_by_id (tb, it->id) != NULL)
+ {
+ return;
+ }
+ index = 0;
+ for (li = p->order; li && strcmp (li->data, it->id); li = li->next)
+ {
+ if (ephy_toolbar_get_item_by_id (tb, li->data) != NULL)
+ {
+ ++index;
+ }
+ }
+ }
+
+ p->items = g_slist_insert (p->items, it, index);
+ g_object_ref (it);
+ }
+ g_signal_emit (tb, EphyToolbarSignals[EPHY_TOOLBAR_CHANGED], 0);
+}
+
+void
+ephy_toolbar_remove_item (EphyToolbar *tb, EphyTbItem *it)
+{
+ EphyToolbarPrivate *p = tb->priv;
+
+ g_return_if_fail (g_slist_find (p->items, it) != NULL);
+
+ p->items = g_slist_remove (p->items, it);
+
+ g_signal_emit (tb, EphyToolbarSignals[EPHY_TOOLBAR_CHANGED], 0);
+
+ g_object_unref (it);
+}
+
+void
+ephy_toolbar_set_fixed_order (EphyToolbar *tb, gboolean value)
+{
+ EphyToolbarPrivate *p = tb->priv;
+ p->fixed_order = value;
+
+ if (value)
+ {
+ ephy_toolbar_update_order (tb);
+ }
+}
+
+void
+ephy_toolbar_set_check_unique (EphyToolbar *tb, gboolean value)
+{
+ EphyToolbarPrivate *p = tb->priv;
+ p->check_unique = value;
+
+ /* maybe it should remove duplicated items now, if any */
+}
+
+gboolean
+ephy_toolbar_get_check_unique (EphyToolbar *tb)
+{
+ EphyToolbarPrivate *p = tb->priv;
+ return p->check_unique;
+}
+
+static void
+ephy_toolbar_update_order (EphyToolbar *tb)
+{
+ EphyToolbarPrivate *p = tb->priv;
+ GSList *li;
+ GSList *lj;
+ GSList *new_order = NULL;
+
+ lj = p->order;
+ for (li = p->items; li; li = li->next)
+ {
+ EphyTbItem *i = li->data;
+ const gchar *id = i->id;
+
+ if (g_slist_find_custom (lj, id, (GCompareFunc) strcmp))
+ {
+ for ( ; lj && strcmp (lj->data, id); lj = lj->next)
+ {
+ if (ephy_toolbar_get_item_by_id (tb, lj->data) == NULL)
+ {
+ new_order = g_slist_prepend (new_order, g_strdup (lj->data));
+ }
+ }
+ }
+
+ new_order = g_slist_prepend (new_order, g_strdup (id));
+
+ }
+
+ for ( ; lj; lj = lj->next)
+ {
+ if (ephy_toolbar_get_item_by_id (tb, lj->data) == NULL)
+ {
+ new_order = g_slist_prepend (new_order, g_strdup (lj->data));
+ }
+ }
+
+ g_slist_foreach (p->order, (GFunc) g_free, NULL);
+ g_slist_free (p->order);
+
+ p->order = g_slist_reverse (new_order);
+
+#ifdef DEBUG_ORDER
+ DEBUG_MSG (("New order:\n"));
+ for (lj = p->order; lj; lj = lj->next)
+ {
+ DEBUG_MSG (("%s\n", (char *) lj->data));
+ }
+#endif
+}
+
diff --git a/lib/toolbar/ephy-toolbar.h b/lib/toolbar/ephy-toolbar.h
new file mode 100644
index 000000000..3c70a4783
--- /dev/null
+++ b/lib/toolbar/ephy-toolbar.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifndef EPHY_TOOLBAR_H
+#define EPHY_TOOLBAR_H
+
+#include <glib-object.h>
+
+#include "ephy-toolbar-item.h"
+
+G_BEGIN_DECLS
+
+/* object forward declarations */
+
+typedef struct _EphyToolbar EphyToolbar;
+typedef struct _EphyToolbarClass EphyToolbarClass;
+typedef struct _EphyToolbarPrivate EphyToolbarPrivate;
+
+/**
+ * Toolbar object
+ */
+
+#define EPHY_TYPE_TOOLBAR (ephy_toolbar_get_type())
+#define EPHY_TOOLBAR(object) (G_TYPE_CHECK_INSTANCE_CAST((object), EPHY_TYPE_TOOLBAR,\
+ EphyToolbar))
+#define EPHY_TOOLBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EPHY_TYPE_TOOLBAR,\
+ EphyToolbarClass))
+#define EPHY_IS_TOOLBAR(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), EPHY_TYPE_TOOLBAR))
+#define EPHY_IS_TOOLBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EPHY_TYPE_TOOLBAR))
+#define EPHY_TOOLBAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPHY_TYPE_TOOLBAR,\
+ EphyToolbarClass))
+
+struct _EphyToolbarClass
+{
+ GObjectClass parent_class;
+
+ /* signals */
+ void (*changed) (EphyToolbar *tb);
+
+};
+
+/* Remember: fields are public read-only */
+struct _EphyToolbar
+{
+ GObject parent_object;
+
+ EphyToolbarPrivate *priv;
+};
+
+GType ephy_toolbar_get_type (void);
+EphyToolbar * ephy_toolbar_new (void);
+gboolean ephy_toolbar_parse (EphyToolbar *tb, const gchar *cfg);
+gchar * ephy_toolbar_to_string (EphyToolbar *tb);
+gboolean ephy_toolbar_listen_to_gconf (EphyToolbar *tb, const gchar *gconf_key);
+EphyTbItem * ephy_toolbar_get_item_by_id (EphyToolbar *tb, const gchar *id);
+const GSList * ephy_toolbar_get_item_list (EphyToolbar *tb);
+void ephy_toolbar_add_item (EphyToolbar *tb, EphyTbItem *it, gint index);
+void ephy_toolbar_remove_item (EphyToolbar *tb, EphyTbItem *it);
+void ephy_toolbar_set_fixed_order (EphyToolbar *tb, gboolean value);
+void ephy_toolbar_set_check_unique (EphyToolbar *tb, gboolean value);
+gboolean ephy_toolbar_get_check_unique (EphyToolbar *tb);
+
+G_END_DECLS
+
+#endif
diff --git a/lib/widgets/.cvsignore b/lib/widgets/.cvsignore
new file mode 100644
index 000000000..20e4cb0c8
--- /dev/null
+++ b/lib/widgets/.cvsignore
@@ -0,0 +1,6 @@
+Makefile
+Makefile.in
+*.lo
+.deps
+.libs
+*.la
diff --git a/lib/widgets/Makefile.am b/lib/widgets/Makefile.am
new file mode 100644
index 000000000..a8b7a69b8
--- /dev/null
+++ b/lib/widgets/Makefile.am
@@ -0,0 +1,30 @@
+INCLUDES = \
+ -I$(top_srcdir)/lib \
+ $(WARN_CFLAGS) \
+ $(EPIPHANY_DEPENDENCY_CFLAGS) \
+ -DSHARE_DIR=\"$(pkgdatadir)\" \
+ -DG_DISABLE_DEPRECATED \
+ -DGDK_DISABLE_DEPRECATED \
+ -DGTK_DISABLE_DEPRECATED \
+ -DGDK_PIXBUF_DISABLE_DEPRECATED \
+ -DGNOME_DISABLE_DEPRECATED
+
+noinst_LTLIBRARIES = libephywidgets.la
+
+libephywidgets_la_SOURCES = \
+ ephy-ellipsizing-label.c \
+ ephy-ellipsizing-label.h \
+ ephy-notebook.c \
+ ephy-notebook.h \
+ ephy-location-entry.c \
+ ephy-location-entry.h \
+ ephy-autocompletion-window.c \
+ ephy-autocompletion-window.h \
+ ephy-spinner.c \
+ ephy-spinner.h \
+ eggtreemultidnd.c \
+ eggtreemultidnd.h \
+ ephy-tree-model-sort.c \
+ ephy-tree-model-sort.h \
+ eggtreemodelfilter.c \
+ eggtreemodelfilter.h
diff --git a/lib/widgets/eggtreemodelfilter.c b/lib/widgets/eggtreemodelfilter.c
new file mode 100644
index 000000000..b3b31a46d
--- /dev/null
+++ b/lib/widgets/eggtreemodelfilter.c
@@ -0,0 +1,2560 @@
+/* eggtreemodelfilter.c
+ * Copyright (C) 2000,2001 Red Hat, Inc., Jonathan Blandford <jrb@redhat.com>
+ * Copyright (C) 2001,2002 Kristian Rietveld <kris@gtk.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "eggtreemodelfilter.h"
+#include <gtk/gtksignal.h>
+#include <string.h>
+
+/****** NOTE NOTE NOTE WARNING WARNING ******
+ *
+ * This is *unstable* code. Don't use it in any project. This warning
+ * will be removed when this treemodel works.
+ */
+
+/*#define VERBOSE 1*/
+
+/* removed this when you add support for i18n */
+#define _
+
+/* ITER FORMAT:
+ *
+ * iter->stamp = filter->stamp
+ * iter->user_data = FilterLevel
+ * iter->user_data2 = FilterElt
+ */
+
+/* all paths, iters, etc prefixed with c_ are paths, iters, etc relative to the
+ * child model.
+ */
+
+typedef struct _FilterElt FilterElt;
+typedef struct _FilterLevel FilterLevel;
+
+struct _FilterElt
+{
+ GtkTreeIter iter;
+ FilterLevel *children;
+ gint offset;
+ gint ref_count;
+ gint zero_ref_count;
+ gboolean visible;
+};
+
+struct _FilterLevel
+{
+ GArray *array;
+ gint ref_count;
+
+ FilterElt *parent_elt;
+ FilterLevel *parent_level;
+};
+
+/* properties */
+enum
+{
+ PROP_0,
+ PROP_CHILD_MODEL,
+ PROP_VIRTUAL_ROOT
+};
+
+#define EGG_TREE_MODEL_FILTER_CACHE_CHILD_ITERS(filter) \
+ (((EggTreeModelFilter *)filter)->child_flags & GTK_TREE_MODEL_ITERS_PERSIST)
+
+#define FILTER_ELT(filter_elt) ((FilterElt *)filter_elt)
+#define FILTER_LEVEL(filter_level) ((FilterLevel *)filter_level)
+
+/* general code (object/interface init, properties, etc) */
+static void egg_tree_model_filter_init (EggTreeModelFilter *filter);
+static void egg_tree_model_filter_class_init (EggTreeModelFilterClass *filter_class);
+static void egg_tree_model_filter_tree_model_init (GtkTreeModelIface *iface);
+static void egg_tree_model_filter_finalize (GObject *object);
+static void egg_tree_model_filter_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void egg_tree_model_filter_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+/* signal handlers */
+static void egg_tree_model_filter_row_changed (GtkTreeModel *c_model,
+ GtkTreePath *c_path,
+ GtkTreeIter *c_iter,
+ gpointer data);
+static void egg_tree_model_filter_row_inserted (GtkTreeModel *c_model,
+ GtkTreePath *c_path,
+ GtkTreeIter *c_iter,
+ gpointer data);
+static void egg_tree_model_filter_row_has_child_toggled (GtkTreeModel *c_model,
+ GtkTreePath *c_path,
+ GtkTreeIter *c_iter,
+ gpointer data);
+static void egg_tree_model_filter_row_deleted (GtkTreeModel *c_model,
+ GtkTreePath *c_path,
+ gpointer data);
+static void egg_tree_model_filter_rows_reordered (GtkTreeModel *c_model,
+ GtkTreePath *c_path,
+ GtkTreeIter *c_iter,
+ gint *new_order,
+ gpointer data);
+
+/* GtkTreeModel interface */
+static guint egg_tree_model_filter_get_flags (GtkTreeModel *model);
+static gint egg_tree_model_filter_get_n_columns (GtkTreeModel *model);
+static GType egg_tree_model_filter_get_column_type (GtkTreeModel *model,
+ gint index);
+static gboolean egg_tree_model_filter_get_iter (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GtkTreePath *path);
+static GtkTreePath *egg_tree_model_filter_get_path (GtkTreeModel *model,
+ GtkTreeIter *iter);
+static void egg_tree_model_filter_get_value (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gint column,
+ GValue *value);
+static gboolean egg_tree_model_filter_iter_next (GtkTreeModel *model,
+ GtkTreeIter *iter);
+static gboolean egg_tree_model_filter_iter_children (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent);
+static gboolean egg_tree_model_filter_iter_has_child (GtkTreeModel *model,
+ GtkTreeIter *iter);
+static gint egg_tree_model_filter_iter_n_children (GtkTreeModel *model,
+ GtkTreeIter *iter);
+static gboolean egg_tree_model_filter_iter_nth_child (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent,
+ gint n);
+static gboolean egg_tree_model_filter_iter_parent (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GtkTreeIter *child);
+static void egg_tree_model_filter_ref_node (GtkTreeModel *model,
+ GtkTreeIter *iter);
+static void egg_tree_model_filter_unref_node (GtkTreeModel *model,
+ GtkTreeIter *iter);
+
+
+
+/* private functions */
+static void egg_tree_model_filter_build_level (EggTreeModelFilter *filter,
+ FilterLevel *parent_level,
+ FilterElt *parent_elt);
+static void egg_tree_model_filter_free_level (EggTreeModelFilter *filter,
+ FilterLevel *filter_level);
+
+static GtkTreePath *egg_tree_model_filter_elt_get_path (FilterLevel *level,
+ FilterElt *elt,
+ GtkTreePath *root);
+
+static GtkTreePath *egg_tree_model_filter_add_root (GtkTreePath *src,
+ GtkTreePath *root);
+static GtkTreePath *egg_tree_model_filter_remove_root (GtkTreePath *src,
+ GtkTreePath *root);
+
+static void egg_tree_model_filter_increment_stamp (EggTreeModelFilter *filter);
+
+static gboolean egg_tree_model_filter_visible (EggTreeModelFilter *filter,
+ GtkTreeIter *child_iter);
+static void egg_tree_model_filter_clear_cache_helper (EggTreeModelFilter *filter,
+ FilterLevel *level);
+
+static void egg_tree_model_filter_real_unref_node (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gboolean propagate_unref);
+
+static void egg_tree_model_filter_set_model (EggTreeModelFilter *filter,
+ GtkTreeModel *child_model);
+static void egg_tree_model_filter_set_root (EggTreeModelFilter *filter,
+ GtkTreePath *root);
+
+static GtkTreePath *egg_real_tree_model_filter_convert_child_path_to_path (EggTreeModelFilter *filter,
+ GtkTreePath *child_path,
+ gboolean build_levels,
+ gboolean fetch_childs);
+
+static gboolean egg_tree_model_filter_fetch_child (EggTreeModelFilter *filter,
+ FilterLevel *level,
+ gint offset);
+static void egg_tree_model_filter_remove_node (EggTreeModelFilter *filter,
+ GtkTreeIter *iter,
+ gboolean emit_signal);
+static void egg_tree_model_filter_update_childs (EggTreeModelFilter *filter,
+ FilterLevel *level,
+ FilterElt *elt);
+
+
+static GObjectClass *parent_class = NULL;
+
+GType
+egg_tree_model_filter_get_type (void)
+{
+ static GType tree_model_filter_type = 0;
+
+ if (!tree_model_filter_type)
+ {
+ static const GTypeInfo tree_model_filter_info =
+ {
+ sizeof (EggTreeModelFilterClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) egg_tree_model_filter_class_init,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (EggTreeModelFilter),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) egg_tree_model_filter_init
+ };
+
+ static const GInterfaceInfo tree_model_info =
+ {
+ (GInterfaceInitFunc) egg_tree_model_filter_tree_model_init,
+ NULL,
+ NULL
+ };
+
+ tree_model_filter_type = g_type_register_static (G_TYPE_OBJECT,
+ "EggTreeModelFilter",
+ &tree_model_filter_info, 0);
+
+ g_type_add_interface_static (tree_model_filter_type,
+ GTK_TYPE_TREE_MODEL,
+ &tree_model_info);
+ }
+
+ return tree_model_filter_type;
+}
+
+static void
+egg_tree_model_filter_init (EggTreeModelFilter *filter)
+{
+ filter->visible_column = -1;
+ filter->zero_ref_count = 0;
+ filter->visible_method_set = FALSE;
+ filter->modify_func_set = FALSE;
+}
+
+static void
+egg_tree_model_filter_class_init (EggTreeModelFilterClass *filter_class)
+{
+ GObjectClass *object_class;
+
+ object_class = (GObjectClass *) filter_class;
+ parent_class = g_type_class_peek_parent (filter_class);
+
+ object_class->set_property = egg_tree_model_filter_set_property;
+ object_class->get_property = egg_tree_model_filter_get_property;
+
+ object_class->finalize = egg_tree_model_filter_finalize;
+
+ /* Properties -- FIXME: write a better description ... */
+ g_object_class_install_property (object_class,
+ PROP_CHILD_MODEL,
+ g_param_spec_object ("child_model",
+ "The child model",
+ "The model for the TreeModelFilter to filter",
+ GTK_TYPE_TREE_MODEL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class,
+ PROP_VIRTUAL_ROOT,
+ g_param_spec_boxed ("virtual_root",
+ "The virtual root",
+ "The virtual root (relative to the child model) for this filtermodel",
+ GTK_TYPE_TREE_PATH,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+egg_tree_model_filter_tree_model_init (GtkTreeModelIface *iface)
+{
+ iface->get_flags = egg_tree_model_filter_get_flags;
+ iface->get_n_columns = egg_tree_model_filter_get_n_columns;
+ iface->get_column_type = egg_tree_model_filter_get_column_type;
+ iface->get_iter = egg_tree_model_filter_get_iter;
+ iface->get_path = egg_tree_model_filter_get_path;
+ iface->get_value = egg_tree_model_filter_get_value;
+ iface->iter_next = egg_tree_model_filter_iter_next;
+ iface->iter_children = egg_tree_model_filter_iter_children;
+ iface->iter_has_child = egg_tree_model_filter_iter_has_child;
+ iface->iter_n_children = egg_tree_model_filter_iter_n_children;
+ iface->iter_nth_child = egg_tree_model_filter_iter_nth_child;
+ iface->iter_parent = egg_tree_model_filter_iter_parent;
+ iface->ref_node = egg_tree_model_filter_ref_node;
+ iface->unref_node = egg_tree_model_filter_unref_node;
+}
+
+
+static void
+egg_tree_model_filter_finalize (GObject *object)
+{
+ EggTreeModelFilter *filter = (EggTreeModelFilter *) object;
+
+ egg_tree_model_filter_set_model (filter, NULL);
+
+ if (filter->virtual_root)
+ gtk_tree_path_free (filter->virtual_root);
+
+ if (filter->root)
+ egg_tree_model_filter_free_level (filter, filter->root);
+
+ if (filter->modify_types)
+ g_free (filter->modify_types);
+
+ /* must chain up */
+ parent_class->finalize (object);
+}
+
+static void
+egg_tree_model_filter_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EggTreeModelFilter *filter = EGG_TREE_MODEL_FILTER (object);
+
+ switch (prop_id)
+ {
+ case PROP_CHILD_MODEL:
+ egg_tree_model_filter_set_model (filter, g_value_get_object (value));
+ break;
+ case PROP_VIRTUAL_ROOT:
+ egg_tree_model_filter_set_root (filter, g_value_get_boxed (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+egg_tree_model_filter_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EggTreeModelFilter *filter = EGG_TREE_MODEL_FILTER (object);
+
+ switch (prop_id)
+ {
+ case PROP_CHILD_MODEL:
+ g_value_set_object (value, filter->child_model);
+ break;
+ case PROP_VIRTUAL_ROOT:
+ g_value_set_boxed (value, filter->virtual_root);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+/* helpers */
+
+static void
+egg_tree_model_filter_build_level (EggTreeModelFilter *filter,
+ FilterLevel *parent_level,
+ FilterElt *parent_elt)
+{
+ GtkTreeIter iter;
+ GtkTreeIter root;
+ FilterLevel *new_level;
+ gint length = 0;
+ gint i;
+
+ g_assert (filter->child_model != NULL);
+
+ if (!parent_level)
+ {
+ if (filter->virtual_root)
+ {
+ if (gtk_tree_model_get_iter (filter->child_model, &root, filter->virtual_root) == FALSE)
+ return;
+ length = gtk_tree_model_iter_n_children (filter->child_model, &root);
+
+#ifdef VERBOSE
+ g_print ("-- vroot %d children\n", length);
+#endif
+
+ if (gtk_tree_model_iter_children (filter->child_model, &iter, &root) == FALSE)
+ return;
+ }
+ else
+ {
+ if (!gtk_tree_model_get_iter_first (filter->child_model, &iter))
+ return;
+ length = gtk_tree_model_iter_n_children (filter->child_model, NULL);
+ }
+ }
+ else
+ {
+ GtkTreeIter parent_iter;
+ GtkTreeIter child_parent_iter;
+
+ parent_iter.stamp = filter->stamp;
+ parent_iter.user_data = parent_level;
+ parent_iter.user_data2 = parent_elt;
+
+ egg_tree_model_filter_convert_iter_to_child_iter (filter,
+ &child_parent_iter,
+ &parent_iter);
+ if (gtk_tree_model_iter_children (filter->child_model, &iter, &child_parent_iter) == FALSE)
+ return;
+
+ /* stamp may have changed */
+ egg_tree_model_filter_convert_iter_to_child_iter (filter,
+ &child_parent_iter,
+ &parent_iter);
+ length = gtk_tree_model_iter_n_children (filter->child_model, &child_parent_iter);
+ }
+
+ g_return_if_fail (length > 0);
+
+#ifdef VERBOSE
+ g_print ("-- building new level with %d childs\n", length);
+#endif
+
+ new_level = g_new (FilterLevel, 1);
+ new_level->array = g_array_sized_new (FALSE, FALSE,
+ sizeof (FilterElt),
+ length);
+ new_level->ref_count = 0;
+ new_level->parent_elt = parent_elt;
+ new_level->parent_level = parent_level;
+
+ if (parent_elt)
+ parent_elt->children = new_level;
+ else
+ filter->root = new_level;
+
+ /* increase the count of zero ref_counts */
+ while (parent_level)
+ {
+ parent_elt->zero_ref_count++;
+
+ parent_elt = parent_level->parent_elt;
+ parent_level = parent_level->parent_level;
+ }
+ filter->zero_ref_count++;
+
+#ifdef VERBOSE
+ g_print ("_build_level: zero ref count on filter is now %d\n",
+ filter->zero_ref_count);
+#endif
+
+ i = 0;
+ do
+ {
+ if (egg_tree_model_filter_visible (filter, &iter))
+ {
+ FilterElt filter_elt;
+
+ filter_elt.offset = i;
+ filter_elt.zero_ref_count = 0;
+ filter_elt.ref_count = 0;
+ filter_elt.children = NULL;
+ filter_elt.visible = TRUE;
+
+ if (EGG_TREE_MODEL_FILTER_CACHE_CHILD_ITERS (filter))
+ filter_elt.iter = iter;
+
+ g_array_append_val (new_level->array, filter_elt);
+ }
+ i++;
+ }
+ while (gtk_tree_model_iter_next (filter->child_model, &iter));
+}
+
+static void
+egg_tree_model_filter_free_level (EggTreeModelFilter *filter,
+ FilterLevel *filter_level)
+{
+ gint i;
+
+ g_assert (filter_level);
+
+ if (filter_level->ref_count == 0)
+ {
+ FilterLevel *parent_level = filter_level->parent_level;
+ FilterElt *parent_elt = filter_level->parent_elt;
+
+ do
+ {
+ if (parent_elt)
+ parent_elt->zero_ref_count--;
+
+ if (parent_level)
+ {
+ parent_elt = parent_level->parent_elt;
+ parent_level = parent_level->parent_level;
+ }
+ }
+ while (parent_level);
+ filter->zero_ref_count--;
+ }
+
+#ifdef VERBOSE
+ g_print ("freeing level\n");
+ g_print ("zero ref count is %d\n", filter->zero_ref_count);
+#endif
+
+ for (i = 0; i < filter_level->array->len; i++)
+ {
+ if (g_array_index (filter_level->array, FilterElt, i).children)
+ egg_tree_model_filter_free_level (filter,
+ FILTER_LEVEL (g_array_index (filter_level->array, FilterElt, i).children));
+ }
+
+ if (filter_level->parent_elt)
+ filter_level->parent_elt->children = NULL;
+ else
+ filter->root = NULL;
+
+ g_array_free (filter_level->array, TRUE);
+ filter_level->array = NULL;
+
+ g_free (filter_level);
+ filter_level = NULL;
+}
+
+static GtkTreePath *
+egg_tree_model_filter_elt_get_path (FilterLevel *level,
+ FilterElt *elt,
+ GtkTreePath *root)
+{
+ FilterLevel *walker = level;
+ FilterElt *walker2 = elt;
+ GtkTreePath *path;
+ GtkTreePath *real_path;
+
+ g_return_val_if_fail (level != NULL, NULL);
+ g_return_val_if_fail (elt != NULL, NULL);
+
+ path = gtk_tree_path_new ();
+
+ while (walker)
+ {
+ gtk_tree_path_prepend_index (path, walker2->offset);
+
+ walker2 = walker->parent_elt;
+ walker = walker->parent_level;
+ }
+
+ if (root)
+ {
+ real_path = gtk_tree_path_copy (root);
+
+ egg_tree_model_filter_add_root (real_path, path);
+ gtk_tree_path_free (path);
+ return real_path;
+ }
+
+ return path;
+}
+
+static GtkTreePath *
+egg_tree_model_filter_add_root (GtkTreePath *src,
+ GtkTreePath *root)
+{
+ GtkTreePath *retval;
+ gint i;
+
+ retval = gtk_tree_path_copy (root);
+
+ for (i = 0; i < gtk_tree_path_get_depth (src); i++)
+ gtk_tree_path_append_index (retval, gtk_tree_path_get_indices (src)[i]);
+
+ return retval;
+}
+
+static GtkTreePath *
+egg_tree_model_filter_remove_root (GtkTreePath *src,
+ GtkTreePath *root)
+{
+ GtkTreePath *retval;
+ gint i;
+ gint depth;
+ gint *indices;
+
+ if (gtk_tree_path_get_depth (src) <= gtk_tree_path_get_depth (root))
+ return NULL;
+
+ depth = gtk_tree_path_get_depth (src);
+ indices = gtk_tree_path_get_indices (src);
+
+ for (i = 0; i < gtk_tree_path_get_depth (root); i++)
+ if (indices[i] != gtk_tree_path_get_indices (root)[i])
+ return NULL;
+
+ retval = gtk_tree_path_new ();
+
+ for (; i < depth; i++)
+ gtk_tree_path_append_index (retval, indices[i]);
+
+ return retval;
+}
+
+static void
+egg_tree_model_filter_increment_stamp (EggTreeModelFilter *filter)
+{
+ do
+ {
+ filter->stamp++;
+ }
+ while (filter->stamp == 0);
+
+ egg_tree_model_filter_clear_cache (filter);
+}
+
+static gboolean
+egg_tree_model_filter_visible (EggTreeModelFilter *filter,
+ GtkTreeIter *child_iter)
+{
+ if (filter->visible_func)
+ {
+ return (filter->visible_func (filter->child_model,
+ child_iter,
+ filter->visible_data));
+ }
+ else if (filter->visible_column >= 0)
+ {
+ GValue val = {0, };
+
+ gtk_tree_model_get_value (filter->child_model, child_iter,
+ filter->visible_column, &val);
+
+ if (g_value_get_boolean (&val))
+ {
+ g_value_unset (&val);
+ return TRUE;
+ }
+
+ g_value_unset (&val);
+ return FALSE;
+ }
+
+ /* no filter thing set, so always visible */
+ return TRUE;
+}
+
+static void
+egg_tree_model_filter_clear_cache_helper (EggTreeModelFilter *filter,
+ FilterLevel *level)
+{
+ gint i;
+
+ g_assert (level);
+
+ for (i = 0; i < level->array->len; i++)
+ {
+ if (g_array_index (level->array, FilterElt, i).zero_ref_count > 0)
+ egg_tree_model_filter_clear_cache_helper (filter, g_array_index (level->array, FilterElt, i).children);
+ }
+
+ if (level->ref_count == 0 && level != filter->root)
+ {
+ egg_tree_model_filter_free_level (filter, level);
+ return;
+ }
+}
+
+static gboolean
+egg_tree_model_filter_fetch_child (EggTreeModelFilter *filter,
+ FilterLevel *level,
+ gint offset)
+{
+ gint i = 0;
+ gint len;
+ GtkTreePath *c_path = NULL;
+ GtkTreeIter c_iter;
+ GtkTreePath *c_parent_path = NULL;
+ GtkTreeIter c_parent_iter;
+ FilterElt elt;
+
+#ifdef VERBOSE
+ g_print ("_fetch_child: for offset %d\n", offset);
+#endif
+
+ /* check if child exists and is visible */
+ if (level->parent_elt)
+ {
+ c_parent_path =
+ egg_tree_model_filter_elt_get_path (level->parent_level,
+ level->parent_elt,
+ filter->virtual_root);
+ if (!c_parent_path)
+ return FALSE;
+ }
+ else
+ {
+ if (filter->virtual_root)
+ c_parent_path = gtk_tree_path_copy (filter->virtual_root);
+ else
+ c_parent_path = NULL;
+ }
+
+ if (c_parent_path)
+ {
+ gtk_tree_model_get_iter (filter->child_model,
+ &c_parent_iter,
+ c_parent_path);
+ len = gtk_tree_model_iter_n_children (filter->child_model,
+ &c_parent_iter);
+
+ c_path = gtk_tree_path_copy (c_parent_path);
+ gtk_tree_path_free (c_parent_path);
+ }
+ else
+ {
+ len = gtk_tree_model_iter_n_children (filter->child_model, NULL);
+ c_path = gtk_tree_path_new ();
+ }
+
+ gtk_tree_path_append_index (c_path, offset);
+ gtk_tree_model_get_iter (filter->child_model, &c_iter, c_path);
+ gtk_tree_path_free (c_path);
+
+ if (offset >= len || !egg_tree_model_filter_visible (filter, &c_iter))
+ return FALSE;
+
+ /* add child */
+ elt.offset = offset;
+ elt.zero_ref_count = 0;
+ elt.ref_count = 0;
+ elt.children = NULL;
+ /* visibility should be FALSE as we don't emit row_inserted */
+ elt.visible = FALSE;
+
+ if (EGG_TREE_MODEL_FILTER_CACHE_CHILD_ITERS (filter))
+ elt.iter = c_iter;
+
+ /* find index */
+ for (i = 0; i < level->array->len; i++)
+ if (g_array_index (level->array, FilterElt, i).offset > offset)
+ break;
+
+ g_array_insert_val (level->array, i, elt);
+
+ for (i = 0; i < level->array->len; i++)
+ {
+ FilterElt *e = &(g_array_index (level->array, FilterElt, i));
+ if (e->children)
+ e->children->parent_elt = e;
+ }
+
+ return TRUE;
+}
+
+static void
+egg_tree_model_filter_remove_node (EggTreeModelFilter *filter,
+ GtkTreeIter *iter,
+ gboolean emit_signal)
+{
+ FilterElt *elt, *parent;
+ FilterLevel *level, *parent_level;
+ gint offset, i, length, level_refcount;
+
+ /* FIXME: this function is very ugly. I need to rethink and
+ * rewrite it someday.
+ */
+
+ level = FILTER_LEVEL (iter->user_data);
+ elt = FILTER_ELT (iter->user_data2);
+
+ parent = level->parent_elt;
+ parent_level = level->parent_level;
+ length = level->array->len;
+ offset = elt->offset;
+
+#ifdef VERBOSE
+ g_print ("|___ removing node\n");
+#endif
+
+ /* ref counting */
+ while (elt->ref_count > 0)
+ egg_tree_model_filter_real_unref_node (GTK_TREE_MODEL (filter),
+ iter, FALSE);
+
+ level_refcount = level->ref_count;
+
+ /* do the ref counting first! this touches the stamp */
+ if (emit_signal)
+ {
+ GtkTreePath *path;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (filter), iter);
+ egg_tree_model_filter_increment_stamp (filter);
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (filter), path);
+ gtk_tree_path_free (path);
+ }
+
+ if ((length == 1 || level_refcount == 0) &&
+ emit_signal && iter->user_data != filter->root)
+ {
+ /* above code destroyed the level */
+ goto emit_has_child_toggled;
+ }
+
+ if (length == 1)
+ {
+ /* kill the level */
+#ifdef VERBOSE
+ g_print ("killing level ...\n");
+#endif
+ egg_tree_model_filter_free_level (filter, level);
+
+ if (!filter->root)
+ /* we killed the root */
+ return;
+ }
+ else
+ {
+#ifdef VERBOSE
+ g_print ("removing the node...\n");
+#endif
+
+ /* remove the node */
+ for (i = 0; i < level->array->len; i++)
+ if (elt->offset == g_array_index (level->array, FilterElt, i).offset)
+ break;
+
+ g_array_remove_index (level->array, i);
+
+ for (i = 0; i < level->array->len; i++)
+ {
+ /* NOTE: here we do *not* decrease offsets, because the node was
+ * not removed from the child model
+ */
+ elt = &g_array_index (level->array, FilterElt, i);
+ if (elt->children)
+ elt->children->parent_elt = elt;
+ }
+ }
+
+emit_has_child_toggled:
+ /* children are being handled first, so we can check it this way
+ *
+ * yes this if-statement is ugly
+ */
+ if ((parent && parent->children && parent->children->array->len <= 1) ||
+ (length == 1 && emit_signal && iter->user_data != filter->root))
+ {
+ /* latest child has been removed, level has been destroyed */
+ GtkTreeIter piter;
+ GtkTreePath *ppath;
+
+ piter.stamp = filter->stamp;
+ piter.user_data = parent_level;
+ piter.user_data2 = parent;
+
+ ppath = gtk_tree_model_get_path (GTK_TREE_MODEL (filter), &piter);
+
+#ifdef VERBOSE
+ g_print ("emitting has_child_toggled (by _filter_remove)\n");
+#endif
+
+ gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (filter),
+ ppath, &piter);
+ gtk_tree_path_free (ppath);
+ }
+}
+
+static void
+egg_tree_model_filter_update_childs (EggTreeModelFilter *filter,
+ FilterLevel *level,
+ FilterElt *elt)
+{
+ GtkTreeIter c_iter;
+ GtkTreeIter iter;
+
+#ifdef VERBOSE
+ g_print ("~~ a node came back, search childs\n");
+#endif
+
+ if (!elt->visible)
+ {
+#ifdef VERBOSE
+ g_print (" + given elt not visible -- bailing out\n");
+#endif
+ return;
+ }
+
+ iter.stamp = filter->stamp;
+ iter.user_data = level;
+ iter.user_data2 = elt;
+
+ egg_tree_model_filter_convert_iter_to_child_iter (filter, &c_iter, &iter);
+
+ if (gtk_tree_model_iter_has_child (filter->child_model, &c_iter))
+ {
+ GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (filter),
+ &iter);
+ gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (filter),
+ path,
+ &iter);
+ if (path)
+ gtk_tree_path_free (path);
+ }
+}
+
+/* TreeModel signals */
+static void
+egg_tree_model_filter_row_changed (GtkTreeModel *c_model,
+ GtkTreePath *c_path,
+ GtkTreeIter *c_iter,
+ gpointer data)
+{
+ EggTreeModelFilter *filter = EGG_TREE_MODEL_FILTER (data);
+ GtkTreeIter iter;
+ GtkTreeIter real_c_iter;
+ GtkTreePath *path;
+
+ FilterElt *elt;
+ FilterLevel *level;
+ gint offset;
+
+ gboolean new;
+ gboolean free_c_path = FALSE;
+
+ g_return_if_fail (c_path != NULL || c_iter != NULL);
+
+ if (!c_path)
+ {
+ c_path = gtk_tree_model_get_path (c_model, c_iter);
+ free_c_path = TRUE;
+ }
+
+ if (c_iter)
+ real_c_iter = *c_iter;
+ else
+ gtk_tree_model_get_iter (c_model, &real_c_iter, c_path);
+
+ if (!filter->root)
+ {
+ gint i;
+ FilterLevel *root;
+
+ /* build root level */
+ egg_tree_model_filter_build_level (filter, NULL, NULL);
+
+ root = FILTER_LEVEL (filter->root);
+
+ /* FIXME:
+ * we set the visibilities to FALSE here, so we ever emit
+ * a row_inserted. maybe it's even better to emit row_inserted
+ * here, not sure.
+ */
+ if (root)
+ for (i = 0; i < root->array->len; i++)
+ g_array_index (root->array, FilterElt, i).visible = FALSE;
+ }
+
+ path = egg_real_tree_model_filter_convert_child_path_to_path (filter,
+ c_path,
+ FALSE,
+ TRUE);
+ if (!path)
+ goto done;
+
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (filter), &iter, path);
+
+ level = FILTER_LEVEL (iter.user_data);
+ elt = FILTER_ELT (iter.user_data2);
+ offset = elt->offset;
+ new = egg_tree_model_filter_visible (filter, c_iter);
+
+ if (elt->visible == TRUE && new == FALSE)
+ {
+#ifdef VERBOSE
+ g_print ("visible to false -> delete row\n");
+#endif
+ egg_tree_model_filter_remove_node (filter, &iter, TRUE);
+ }
+ else if (elt->visible == FALSE && new == TRUE)
+ {
+ GtkTreeIter childs;
+
+#ifdef VERBOSE
+ g_print ("visible to true -> insert row\n");
+#endif
+
+ elt->visible = TRUE;
+
+ egg_tree_model_filter_increment_stamp (filter);
+ /* update stamp */
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (filter), &iter, path);
+ gtk_tree_model_row_inserted (GTK_TREE_MODEL (filter), path, &iter);
+
+ if (gtk_tree_model_iter_children (c_model, &childs, c_iter))
+ egg_tree_model_filter_update_childs (filter, level, elt);
+ }
+ else if (elt->visible == FALSE && new == FALSE)
+ {
+#ifdef VERBOSE
+ g_print ("remove node in silence\n");
+#endif
+ egg_tree_model_filter_remove_node (filter, &iter, FALSE);
+ }
+ else
+ {
+ GtkTreeIter childs;
+
+#ifdef VERBOSE
+ g_print ("no change in visibility -- pass row_changed\n");
+#endif
+
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (filter), path, &iter);
+
+ if (gtk_tree_model_iter_children (c_model, &childs, c_iter) &&
+ elt->visible)
+ egg_tree_model_filter_update_childs (filter, level, elt);
+ }
+
+ gtk_tree_path_free (path);
+
+done:
+ if (free_c_path)
+ gtk_tree_path_free (c_path);
+}
+
+static void
+egg_tree_model_filter_row_inserted (GtkTreeModel *c_model,
+ GtkTreePath *c_path,
+ GtkTreeIter *c_iter,
+ gpointer data)
+{
+ EggTreeModelFilter *filter = EGG_TREE_MODEL_FILTER (data);
+ GtkTreePath *path;
+ GtkTreePath *real_path;
+ GtkTreeIter iter;
+
+ GtkTreeIter real_c_iter;
+
+ FilterElt *elt;
+ FilterLevel *level;
+ FilterLevel *parent_level;
+
+ gint i = 0, offset, index = -1;
+
+ gboolean free_c_path = FALSE;
+
+ g_return_if_fail (c_path != NULL || c_iter != NULL);
+
+ if (!c_path)
+ {
+ c_path = gtk_tree_model_get_path (c_model, c_iter);
+ free_c_path = TRUE;
+ }
+
+ if (c_iter)
+ real_c_iter = *c_iter;
+ else
+ gtk_tree_model_get_iter (c_model, &real_c_iter, c_path);
+
+ /* the row has already been inserted. so we need to fixup the
+ * virtual root here first
+ */
+ if (filter->virtual_root)
+ {
+ if (gtk_tree_path_get_depth (filter->virtual_root) >=
+ gtk_tree_path_get_depth (c_path))
+ {
+ gint level;
+ gint *v_indices, *c_indices;
+
+ level = gtk_tree_path_get_depth (c_path) - 1;
+ v_indices = gtk_tree_path_get_indices (filter->virtual_root);
+ c_indices = gtk_tree_path_get_indices (c_path);
+
+ if (v_indices[level] >= c_indices[level])
+ (v_indices[level])++;
+ }
+ }
+
+ if (!filter->root)
+ {
+ egg_tree_model_filter_build_level (filter, NULL, NULL);
+ /* that already put the inserted iter in the level */
+
+ goto done_and_emit;
+ }
+
+ parent_level = level = FILTER_LEVEL (filter->root);
+
+ /* subtract virtual root if necessary */
+ if (filter->virtual_root)
+ {
+ real_path = egg_tree_model_filter_remove_root (c_path,
+ filter->virtual_root);
+ /* not our kiddo */
+ if (!real_path)
+ goto done;
+ }
+ else
+ real_path = gtk_tree_path_copy (c_path);
+
+ if (gtk_tree_path_get_depth (real_path) - 1 >= 1)
+ {
+ /* find the parent level */
+ while (i < gtk_tree_path_get_depth (real_path) - 1)
+ {
+ gint j;
+
+ if (!level)
+ /* we don't cover this signal */
+ goto done;
+
+ elt = NULL;
+ for (j = 0; j < level->array->len; j++)
+ if (g_array_index (level->array, FilterElt, j).offset ==
+ gtk_tree_path_get_indices (real_path)[i])
+ {
+ elt = &g_array_index (level->array, FilterElt, j);
+ break;
+ }
+
+ if (!elt)
+ /* parent is probably being filtered out */
+ goto done;
+
+ if (!elt->children)
+ {
+ GtkTreePath *tmppath;
+ GtkTreeIter tmpiter;
+
+ tmpiter.stamp = filter->stamp;
+ tmpiter.user_data = level;
+ tmpiter.user_data2 = elt;
+
+ tmppath = gtk_tree_model_get_path (GTK_TREE_MODEL (data),
+ &tmpiter);
+
+ if (tmppath)
+ {
+ gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (data),
+ tmppath, &tmpiter);
+ gtk_tree_path_free (tmppath);
+ }
+
+ /* not covering this signal */
+ goto done;
+ }
+
+ level = elt->children;
+ parent_level = level;
+ i++;
+ }
+ }
+
+ if (!parent_level)
+ goto done;
+
+ /* let's try to insert the value */
+ offset = gtk_tree_path_get_indices (real_path)[gtk_tree_path_get_depth (real_path) - 1];
+
+ /* only insert when visible */
+ if (egg_tree_model_filter_visible (filter, &real_c_iter))
+ {
+ FilterElt felt;
+
+ if (EGG_TREE_MODEL_FILTER_CACHE_CHILD_ITERS (filter))
+ felt.iter = real_c_iter;
+ felt.offset = offset;
+ felt.zero_ref_count = 0;
+ felt.ref_count = 0;
+ felt.visible = TRUE;
+ felt.children = NULL;
+
+ for (i = 0; i < level->array->len; i++)
+ if (g_array_index (level->array, FilterElt, i).offset > offset)
+ break;
+
+ g_array_insert_val (level->array, i, felt);
+ index = i;
+ }
+
+ /* update the offsets, yes if we didn't insert the node above, there will
+ * be a gap here. This will be filled with the node (via fetch_child) when
+ * it becomes visible
+ */
+ for (i = 0; i < level->array->len; i++)
+ {
+ FilterElt *e = &g_array_index (level->array, FilterElt, i);
+ if ((e->offset >= offset) && i != index)
+ e->offset++;
+ if (e->children)
+ e->children->parent_elt = e;
+ }
+
+ /* don't emit the signal if we aren't visible */
+ if (!egg_tree_model_filter_visible (filter, &real_c_iter))
+ goto done;
+
+done_and_emit:
+ /* NOTE: pass c_path here and NOT real_path. This function does
+ * root subtraction itself
+ */
+ path = egg_real_tree_model_filter_convert_child_path_to_path (filter,
+ c_path,
+ FALSE, TRUE);
+
+ if (!path)
+ return;
+
+ egg_tree_model_filter_increment_stamp (filter);
+
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (data), &iter, path);
+ gtk_tree_model_row_inserted (GTK_TREE_MODEL (data), path, &iter);
+
+#ifdef VERBOSE
+ g_print ("inserted row with offset %d\n", index);
+#endif
+
+done:
+ if (free_c_path)
+ gtk_tree_path_free (c_path);
+}
+
+static void
+egg_tree_model_filter_row_has_child_toggled (GtkTreeModel *c_model,
+ GtkTreePath *c_path,
+ GtkTreeIter *c_iter,
+ gpointer data)
+{
+ EggTreeModelFilter *filter = EGG_TREE_MODEL_FILTER (data);
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ g_return_if_fail (c_path != NULL && c_iter != NULL);
+
+ /* FIXME: does this code work? */
+
+ if (!egg_tree_model_filter_visible (filter, c_iter))
+ return;
+
+ path = egg_real_tree_model_filter_convert_child_path_to_path (filter,
+ c_path,
+ FALSE,
+ TRUE);
+ if (!path)
+ return;
+
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (data), &iter, path);
+ gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (data), path, &iter);
+
+ gtk_tree_path_free (path);
+}
+
+static void
+egg_tree_model_filter_row_deleted (GtkTreeModel *c_model,
+ GtkTreePath *c_path,
+ gpointer data)
+{
+ EggTreeModelFilter *filter = EGG_TREE_MODEL_FILTER (data);
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ FilterElt *elt;
+ FilterLevel *level;
+ gint offset;
+ gboolean emit_signal = TRUE;
+ gint i;
+
+ g_return_if_fail (c_path != NULL);
+
+ /* special case the deletion of an ancestor of the virtual root */
+ if (filter->virtual_root &&
+ (gtk_tree_path_is_ancestor (c_path, filter->virtual_root) ||
+ !gtk_tree_path_compare (c_path, filter->virtual_root)))
+ {
+ gint i;
+ GtkTreePath *path;
+ FilterLevel *level = FILTER_LEVEL (filter->root);
+
+ if (!level)
+ return;
+
+ /* remove everything in the filter model
+ *
+ * For now, we just iterate over the root level and emit a
+ * row_deleted for each FilterElt. Not sure if this is correct.
+ */
+
+ egg_tree_model_filter_increment_stamp (filter);
+ path = gtk_tree_path_new ();
+ gtk_tree_path_append_index (path, 0);
+
+ for (i = 0; i < level->array->len; i++)
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (data), path);
+
+ gtk_tree_path_free (path);
+ egg_tree_model_filter_free_level (filter, filter->root);
+
+ return;
+ }
+
+ /* fixup virtual root */
+ if (filter->virtual_root)
+ {
+ if (gtk_tree_path_get_depth (filter->virtual_root) >=
+ gtk_tree_path_get_depth (c_path))
+ {
+ gint level;
+ gint *v_indices, *c_indices;
+
+ level = gtk_tree_path_get_depth (c_path) - 1;
+ v_indices = gtk_tree_path_get_indices (filter->virtual_root);
+ c_indices = gtk_tree_path_get_indices (c_path);
+
+ if (v_indices[level] > c_indices[level])
+ (v_indices[level])--;
+ }
+ }
+
+ path = egg_real_tree_model_filter_convert_child_path_to_path (filter,
+ c_path,
+ FALSE,
+ FALSE);
+ if (!path)
+ {
+ path = egg_real_tree_model_filter_convert_child_path_to_path (filter,
+ c_path,
+ FALSE,
+ TRUE);
+
+ if (!path)
+ {
+ /* fixup the offsets */
+ GtkTreePath *real_path;
+
+ if (!filter->root)
+ return;
+
+ level = FILTER_LEVEL (filter->root);
+
+ /* subtract vroot if necessary */
+ if (filter->virtual_root)
+ {
+ real_path = egg_tree_model_filter_remove_root (c_path,
+ filter->virtual_root);
+ /* we don't handle this */
+ if (!real_path)
+ return;
+ }
+ else
+ real_path = gtk_tree_path_copy (c_path);
+
+ i = 0;
+ if (gtk_tree_path_get_depth (real_path) - 1 >= 1)
+ {
+ while (i < gtk_tree_path_get_depth (real_path) - 1)
+ {
+ gint j;
+
+ if (!level)
+ {
+ /* we don't cover this */
+ gtk_tree_path_free (real_path);
+ return;
+ }
+
+ elt = NULL;
+ for (j = 0; j < level->array->len; j++)
+ if (g_array_index (level->array, FilterElt, j).offset ==
+ gtk_tree_path_get_indices (real_path)[i])
+ {
+ elt = &g_array_index (level->array, FilterElt, j);
+ break;
+ }
+
+ if (!elt || !elt->children)
+ {
+ /* parent is filtered out, so no level */
+ gtk_tree_path_free (real_path);
+ return;
+ }
+
+ level = elt->children;
+ i++;
+ }
+ }
+
+ offset = gtk_tree_path_get_indices (real_path)[gtk_tree_path_get_depth (real_path) - 1];
+ gtk_tree_path_free (real_path);
+
+ if (!level)
+ return;
+
+ /* we need:
+ * - the offset of the removed item
+ * - the level
+ */
+ for (i = 0; i < level->array->len; i++)
+ {
+ elt = &g_array_index (level->array, FilterElt, i);
+ if (elt->offset > offset)
+ elt->offset--;
+ if (elt->children)
+ elt->children->parent_elt = elt;
+ }
+
+ return;
+ }
+
+ emit_signal = FALSE;
+ }
+
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (data), &iter, path);
+
+ level = FILTER_LEVEL (iter.user_data);
+ elt = FILTER_ELT (iter.user_data2);
+ offset = elt->offset;
+
+ if (emit_signal)
+ {
+ if (level->ref_count == 0 && level != filter->root)
+ {
+ egg_tree_model_filter_increment_stamp (filter);
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (data), path);
+
+ gtk_tree_path_free (path);
+ return;
+ }
+
+ egg_tree_model_filter_increment_stamp (filter);
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (data), path);
+ iter.stamp = filter->stamp;
+
+ while (elt->ref_count > 0)
+ egg_tree_model_filter_real_unref_node (GTK_TREE_MODEL (data), &iter,
+ FALSE);
+ }
+
+ if (level->array->len == 1)
+ {
+ /* kill level */
+ egg_tree_model_filter_free_level (filter, level);
+ }
+ else
+ {
+ /* remove the row */
+ for (i = 0; i < level->array->len; i++)
+ if (elt->offset == g_array_index (level->array, FilterElt, i).offset)
+ break;
+
+ offset = g_array_index (level->array, FilterElt, i).offset;
+ g_array_remove_index (level->array, i);
+
+ for (i = 0; i < level->array->len; i++)
+ {
+ elt = &g_array_index (level->array, FilterElt, i);
+ if (elt->offset > offset)
+ elt->offset--;
+ if (elt->children)
+ elt->children->parent_elt = elt;
+ }
+ }
+
+ gtk_tree_path_free (path);
+}
+
+static void
+egg_tree_model_filter_rows_reordered (GtkTreeModel *c_model,
+ GtkTreePath *c_path,
+ GtkTreeIter *c_iter,
+ gint *new_order,
+ gpointer data)
+{
+ FilterElt *elt;
+ FilterLevel *level;
+ EggTreeModelFilter *filter = EGG_TREE_MODEL_FILTER (data);
+
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ gint *tmp_array;
+ gint i, j, elt_count;
+ gint length;
+
+ GArray *new_array;
+
+ g_return_if_fail (new_order != NULL);
+
+#ifdef VERBOSE
+ g_print ("filter: reordering\n");
+#endif
+
+ if (c_path == NULL || gtk_tree_path_get_indices (c_path) == NULL)
+ {
+ if (!filter->root)
+ return;
+
+ length = gtk_tree_model_iter_n_children (c_model, NULL);
+
+ if (filter->virtual_root)
+ {
+ gint new_pos = -1;
+
+ /* reorder root level of path */
+ for (i = 0; i < length; i++)
+ if (new_order[i] == gtk_tree_path_get_indices (filter->virtual_root)[0])
+ new_pos = i;
+
+ if (new_pos < 0)
+ return;
+
+ gtk_tree_path_get_indices (filter->virtual_root)[0] = new_pos;
+ return;
+ }
+
+ path = gtk_tree_path_new ();
+ level = FILTER_LEVEL (filter->root);
+ }
+ else
+ {
+ GtkTreeIter child_iter;
+
+ /* virtual root anchor reordering */
+ if (filter->virtual_root &&
+ gtk_tree_path_get_depth (c_path) <
+ gtk_tree_path_get_depth (filter->virtual_root))
+ {
+ gint new_pos = -1;
+ gint length;
+ gint level;
+ GtkTreeIter real_c_iter;
+
+ level = gtk_tree_path_get_depth (c_path);
+
+ if (c_iter)
+ real_c_iter = *c_iter;
+ else
+ gtk_tree_model_get_iter (c_model, &real_c_iter, c_path);
+
+ length = gtk_tree_model_iter_n_children (c_model, &real_c_iter);
+
+ for (i = 0; i < length; i++)
+ if (new_order[i] == gtk_tree_path_get_indices (filter->virtual_root)[level])
+ new_pos = i;
+
+ if (new_pos < 0)
+ return;
+
+ gtk_tree_path_get_indices (filter->virtual_root)[level] = new_pos;
+ return;
+ }
+
+ path = egg_real_tree_model_filter_convert_child_path_to_path (filter,
+ c_path,
+ FALSE,
+ FALSE);
+ if (!path && filter->virtual_root &&
+ gtk_tree_path_compare (c_path, filter->virtual_root))
+ return;
+
+ if (!path && !filter->virtual_root)
+ return;
+
+ if (!path)
+ {
+ /* root level mode */
+ if (!c_iter)
+ gtk_tree_model_get_iter (c_model, c_iter, c_path);
+ length = gtk_tree_model_iter_n_children (c_model, c_iter);
+ path = gtk_tree_path_new ();
+ level = FILTER_LEVEL (filter->root);
+ }
+ else
+ {
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (data), &iter, path);
+
+ level = FILTER_LEVEL (iter.user_data);
+ elt = FILTER_ELT (iter.user_data2);
+
+ if (!elt->children)
+ {
+ gtk_tree_path_free (path);
+ return;
+ }
+
+ level = elt->children;
+
+ egg_tree_model_filter_convert_iter_to_child_iter (EGG_TREE_MODEL_FILTER (filter), &child_iter, &iter);
+ length = gtk_tree_model_iter_n_children (c_model, &child_iter);
+ }
+ }
+
+ if (level->array->len < 1)
+ return;
+
+ /* NOTE: we do not bail out here if level->array->len < 2 like
+ * GtkTreeModelSort does. This because we do some special tricky
+ * reordering.
+ */
+
+ /* construct a new array */
+ new_array = g_array_sized_new (FALSE, FALSE, sizeof (FilterElt),
+ level->array->len);
+ tmp_array = g_new (gint, level->array->len);
+
+ for (i = 0, elt_count = 0; i < length; i++)
+ {
+ FilterElt *e = NULL;
+ gint old_offset = -1;
+
+ for (j = 0; j < level->array->len; j++)
+ if (g_array_index (level->array, FilterElt, j).offset == new_order[i])
+ {
+ e = &g_array_index (level->array, FilterElt, j);
+ old_offset = j;
+ break;
+ }
+
+ if (!e)
+ continue;
+
+ tmp_array[elt_count] = old_offset;
+ g_array_append_val (new_array, *e);
+ g_array_index (new_array, FilterElt, elt_count).offset = i;
+ elt_count++;
+ }
+
+ g_array_free (level->array, TRUE);
+ level->array = new_array;
+
+ /* fix up stuff */
+ for (i = 0; i < level->array->len; i++)
+ {
+ FilterElt *e = &g_array_index (level->array, FilterElt, i);
+ if (e->children)
+ e->children->parent_elt = e;
+ }
+
+ /* emit rows_reordered */
+ if (!gtk_tree_path_get_indices (path))
+ gtk_tree_model_rows_reordered (GTK_TREE_MODEL (data), path, NULL,
+ tmp_array);
+ else
+ gtk_tree_model_rows_reordered (GTK_TREE_MODEL (data), path, &iter,
+ tmp_array);
+
+ /* done */
+ g_free (tmp_array);
+ gtk_tree_path_free (path);
+}
+
+/* TreeModelIface implementation */
+static guint
+egg_tree_model_filter_get_flags (GtkTreeModel *model)
+{
+ g_return_val_if_fail (EGG_IS_TREE_MODEL_FILTER (model), 0);
+
+ return 0;
+}
+
+static gint
+egg_tree_model_filter_get_n_columns (GtkTreeModel *model)
+{
+ EggTreeModelFilter *filter = (EggTreeModelFilter *)model;
+
+ g_return_val_if_fail (EGG_IS_TREE_MODEL_FILTER (model), 0);
+ g_return_val_if_fail (filter->child_model != NULL, 0);
+
+ if (filter->child_model == NULL)
+ return 0;
+
+ /* so we can't modify the modify func after this ... */
+ filter->modify_func_set = TRUE;
+
+ if (filter->modify_n_columns > 0)
+ return filter->modify_n_columns;
+
+ return gtk_tree_model_get_n_columns (filter->child_model);
+}
+
+static GType
+egg_tree_model_filter_get_column_type (GtkTreeModel *model,
+ gint index)
+{
+ EggTreeModelFilter *filter = (EggTreeModelFilter *)model;
+
+ g_return_val_if_fail (EGG_IS_TREE_MODEL_FILTER (model), G_TYPE_INVALID);
+ g_return_val_if_fail (filter->child_model != NULL, G_TYPE_INVALID);
+
+ /* so we can't modify the modify func after this ... */
+ filter->modify_func_set = TRUE;
+
+ if (filter->modify_types)
+ {
+ g_return_val_if_fail (index < filter->modify_n_columns, G_TYPE_INVALID);
+
+ return filter->modify_types[index];
+ }
+
+ return gtk_tree_model_get_column_type (filter->child_model, index);
+}
+
+static gboolean
+egg_tree_model_filter_get_iter (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GtkTreePath *path)
+{
+ EggTreeModelFilter *filter = (EggTreeModelFilter *)model;
+ gint *indices;
+ FilterLevel *level;
+ gint depth, i;
+
+ g_return_val_if_fail (EGG_IS_TREE_MODEL_FILTER (model), FALSE);
+ g_return_val_if_fail (filter->child_model != NULL, FALSE);
+
+ indices = gtk_tree_path_get_indices (path);
+
+ if (filter->root == NULL)
+ egg_tree_model_filter_build_level (filter, NULL, NULL);
+ level = FILTER_LEVEL (filter->root);
+
+ depth = gtk_tree_path_get_depth (path);
+ if (!depth)
+ {
+ iter->stamp = 0;
+ return FALSE;
+ }
+
+ for (i = 0; i < depth - 1; i++)
+ {
+ if (!level || indices[i] >= level->array->len)
+ {
+ return FALSE;
+ }
+
+ if (!g_array_index (level->array, FilterElt, indices[i]).children)
+ egg_tree_model_filter_build_level (filter, level,
+ &g_array_index (level->array,
+ FilterElt,
+ indices[i]));
+ level = g_array_index (level->array, FilterElt, indices[i]).children;
+ }
+
+ if (!level || indices[i] >= level->array->len)
+ {
+ iter->stamp = 0;
+ return FALSE;
+ }
+
+ iter->stamp = filter->stamp;
+ iter->user_data = level;
+ iter->user_data2 = &g_array_index (level->array, FilterElt,
+ indices[depth - 1]);
+
+ return TRUE;
+}
+
+static GtkTreePath *
+egg_tree_model_filter_get_path (GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ GtkTreePath *retval;
+ FilterLevel *level;
+ FilterElt *elt;
+
+ g_return_val_if_fail (EGG_IS_TREE_MODEL_FILTER (model), NULL);
+ g_return_val_if_fail (EGG_TREE_MODEL_FILTER (model)->child_model != NULL, NULL);
+ g_return_val_if_fail (EGG_TREE_MODEL_FILTER (model)->stamp == iter->stamp, NULL);
+
+ retval = gtk_tree_path_new ();
+ level = iter->user_data;
+ elt = iter->user_data2;
+
+ while (level)
+ {
+ gtk_tree_path_prepend_index (retval,
+ elt - FILTER_ELT (level->array->data));
+ elt = level->parent_elt;
+ level = level->parent_level;
+ }
+
+ return retval;
+}
+
+static void
+egg_tree_model_filter_get_value (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gint column,
+ GValue *value)
+{
+ GtkTreeIter child_iter;
+ EggTreeModelFilter *filter = EGG_TREE_MODEL_FILTER (model);
+
+ g_return_if_fail (EGG_IS_TREE_MODEL_FILTER (model));
+ g_return_if_fail (EGG_TREE_MODEL_FILTER (model)->child_model != NULL);
+ g_return_if_fail (EGG_TREE_MODEL_FILTER (model)->stamp == iter->stamp);
+
+ if (filter->modify_func)
+ {
+ g_return_if_fail (column < filter->modify_n_columns);
+
+ g_value_init (value, filter->modify_types[column]);
+ filter->modify_func (model,
+ iter,
+ value,
+ column,
+ filter->modify_data);
+
+ return;
+ }
+
+ egg_tree_model_filter_convert_iter_to_child_iter (EGG_TREE_MODEL_FILTER (model), &child_iter, iter);
+ gtk_tree_model_get_value (EGG_TREE_MODEL_FILTER (model)->child_model,
+ &child_iter, column, value);
+}
+
+static gboolean
+egg_tree_model_filter_iter_next (GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ FilterLevel *level;
+ FilterElt *elt;
+
+ g_return_val_if_fail (EGG_IS_TREE_MODEL_FILTER (model), FALSE);
+ g_return_val_if_fail (EGG_TREE_MODEL_FILTER (model)->child_model != NULL, FALSE);
+ g_return_val_if_fail (EGG_TREE_MODEL_FILTER (model)->stamp == iter->stamp, FALSE);
+
+ level = iter->user_data;
+ elt = iter->user_data2;
+
+ if (elt - FILTER_ELT (level->array->data) >= level->array->len - 1)
+ {
+ iter->stamp = 0;
+ return FALSE;
+ }
+
+ iter->user_data2 = elt + 1;
+
+ return TRUE;
+}
+
+static gboolean
+egg_tree_model_filter_iter_children (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent)
+{
+ EggTreeModelFilter *filter = (EggTreeModelFilter *)model;
+ FilterLevel *level;
+
+ iter->stamp = 0;
+ g_return_val_if_fail (EGG_IS_TREE_MODEL_FILTER (model), FALSE);
+ g_return_val_if_fail (filter->child_model != NULL, FALSE);
+ if (parent)
+ g_return_val_if_fail (filter->stamp == parent->stamp, FALSE);
+
+ if (!parent)
+ {
+ if (!filter->root)
+ egg_tree_model_filter_build_level (filter, NULL, NULL);
+ if (!filter->root)
+ return FALSE;
+
+ level = filter->root;
+ iter->stamp = filter->stamp;
+ iter->user_data = level;
+ iter->user_data2 = level->array->data;
+ }
+ else
+ {
+ if (FILTER_ELT (parent->user_data2)->children == NULL)
+ egg_tree_model_filter_build_level (filter,
+ FILTER_LEVEL (parent->user_data),
+ FILTER_ELT (parent->user_data2));
+ if (FILTER_ELT (parent->user_data2)->children == NULL)
+ return FALSE;
+
+ iter->stamp = filter->stamp;
+ iter->user_data = FILTER_ELT (parent->user_data2)->children;
+ iter->user_data2 = FILTER_LEVEL (iter->user_data)->array->data;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+egg_tree_model_filter_iter_has_child (GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ GtkTreeIter child_iter;
+ EggTreeModelFilter *filter = (EggTreeModelFilter *)model;
+ FilterElt *elt;
+
+ g_return_val_if_fail (EGG_IS_TREE_MODEL_FILTER (model), FALSE);
+ g_return_val_if_fail (filter->child_model != NULL, FALSE);
+ g_return_val_if_fail (filter->stamp == iter->stamp, FALSE);
+
+ filter = EGG_TREE_MODEL_FILTER (model);
+
+ egg_tree_model_filter_convert_iter_to_child_iter (EGG_TREE_MODEL_FILTER (model), &child_iter, iter);
+ elt = FILTER_ELT (iter->user_data2);
+
+ /* we need to build the level to check if not all children are filtered
+ * out
+ */
+ if (!elt->children
+ && gtk_tree_model_iter_has_child (filter->child_model, &child_iter))
+ egg_tree_model_filter_build_level (filter, FILTER_LEVEL (iter->user_data),
+ elt);
+
+ if (elt->children && elt->children->array->len > 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+static gint
+egg_tree_model_filter_iter_n_children (GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ GtkTreeIter child_iter;
+ EggTreeModelFilter *filter = (EggTreeModelFilter *)model;
+ FilterElt *elt;
+
+ g_return_val_if_fail (EGG_IS_TREE_MODEL_FILTER (model), 0);
+ g_return_val_if_fail (filter->child_model != NULL, 0);
+ if (iter)
+ g_return_val_if_fail (filter->stamp == iter->stamp, 0);
+
+ if (!iter)
+ {
+ int i = 0;
+ int count = 0;
+ GArray *a;
+
+ if (!filter->root)
+ egg_tree_model_filter_build_level (filter, NULL, NULL);
+
+ a = FILTER_LEVEL (filter->root)->array;
+
+ /* count visible nodes */
+
+ for (i = 0; i < a->len; i++)
+ if (g_array_index (a, FilterElt, i).visible)
+ count++;
+
+ return count;
+ }
+
+ elt = FILTER_ELT (iter->user_data2);
+ egg_tree_model_filter_convert_iter_to_child_iter (EGG_TREE_MODEL_FILTER (model), &child_iter, iter);
+
+ if (!elt->children &&
+ gtk_tree_model_iter_has_child (filter->child_model, &child_iter))
+ egg_tree_model_filter_build_level (filter,
+ FILTER_LEVEL (iter->user_data),
+ elt);
+
+ if (elt->children && elt->children->array->len)
+ {
+ int i = 0;
+ int count = 0;
+ GArray *a = elt->children->array;
+
+ /* count visible nodes */
+
+ for (i = 0; i < a->len; i++)
+ if (g_array_index (a, FilterElt, i).visible)
+ count++;
+
+ return count;
+ }
+
+ return 0;
+}
+
+static gboolean
+egg_tree_model_filter_iter_nth_child (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent,
+ gint n)
+{
+ FilterLevel *level;
+ GtkTreeIter children;
+
+ g_return_val_if_fail (EGG_IS_TREE_MODEL_FILTER (model), FALSE);
+ if (parent)
+ g_return_val_if_fail (EGG_TREE_MODEL_FILTER (model)->stamp == parent->stamp, FALSE);
+
+ /* use this instead of has_Child to force us to build the level, if needed */
+ if (egg_tree_model_filter_iter_children (model, &children, parent) == FALSE)
+ {
+ iter->stamp = 0;
+ return FALSE;
+ }
+
+ level = children.user_data;
+ if (n >= level->array->len)
+ {
+ iter->stamp = 0;
+ return FALSE;
+ }
+
+ iter->stamp = EGG_TREE_MODEL_FILTER (model)->stamp;
+ iter->user_data = level;
+ iter->user_data2 = &g_array_index (level->array, FilterElt, n);
+
+ return TRUE;
+}
+
+static gboolean
+egg_tree_model_filter_iter_parent (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GtkTreeIter *child)
+{
+ FilterLevel *level;
+
+ iter->stamp = 0;
+ g_return_val_if_fail (EGG_IS_TREE_MODEL_FILTER (model), FALSE);
+ g_return_val_if_fail (EGG_TREE_MODEL_FILTER (model)->child_model != NULL, FALSE);
+ g_return_val_if_fail (EGG_TREE_MODEL_FILTER (model)->stamp == child->stamp, FALSE);
+
+ level = child->user_data;
+
+ if (level->parent_level)
+ {
+ iter->stamp = EGG_TREE_MODEL_FILTER (model)->stamp;
+ iter->user_data = level->parent_level;
+ iter->user_data2 = level->parent_elt;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+egg_tree_model_filter_ref_node (GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ EggTreeModelFilter *filter = (EggTreeModelFilter *)model;
+ GtkTreeIter child_iter;
+ FilterLevel *level;
+ FilterElt *elt;
+
+ g_return_if_fail (EGG_IS_TREE_MODEL_FILTER (model));
+ g_return_if_fail (EGG_TREE_MODEL_FILTER (model)->child_model != NULL);
+ g_return_if_fail (EGG_TREE_MODEL_FILTER (model)->stamp == iter->stamp);
+
+ egg_tree_model_filter_convert_iter_to_child_iter (EGG_TREE_MODEL_FILTER (model), &child_iter, iter);
+
+ gtk_tree_model_ref_node (filter->child_model, &child_iter);
+
+ level = iter->user_data;
+ elt = iter->user_data2;
+
+ elt->ref_count++;
+ level->ref_count++;
+ if (level->ref_count == 1)
+ {
+ FilterLevel *parent_level = level->parent_level;
+ FilterElt *parent_elt = level->parent_elt;
+
+ /* we were at zero -- time to decrease the zero_ref_count val */
+ do
+ {
+ if (parent_elt)
+ parent_elt->zero_ref_count--;
+
+ if (parent_level)
+ {
+ parent_elt = parent_level->parent_elt;
+ parent_level = parent_level->parent_level;
+ }
+ }
+ while (parent_level);
+ filter->zero_ref_count--;
+ }
+
+#ifdef VERBOSE
+ g_print ("reffed %p\n", iter->user_data2);
+ g_print ("zero ref count is now %d\n", filter->zero_ref_count);
+ if (filter->zero_ref_count < 0)
+ g_warning ("zero_ref_count < 0.");
+#endif
+}
+
+static void
+egg_tree_model_filter_unref_node (GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ egg_tree_model_filter_real_unref_node (model, iter, TRUE);
+}
+
+static void
+egg_tree_model_filter_real_unref_node (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gboolean propagate_unref)
+{
+ EggTreeModelFilter *filter = (EggTreeModelFilter *)model;
+ FilterLevel *level;
+ FilterElt *elt;
+
+ g_return_if_fail (EGG_IS_TREE_MODEL_FILTER (model));
+ g_return_if_fail (filter->child_model != NULL);
+ g_return_if_fail (filter->stamp == iter->stamp);
+
+ if (propagate_unref)
+ {
+ GtkTreeIter child_iter;
+ egg_tree_model_filter_convert_iter_to_child_iter (EGG_TREE_MODEL_FILTER (model), &child_iter, iter);
+ gtk_tree_model_unref_node (filter->child_model, &child_iter);
+ }
+
+ level = iter->user_data;
+ elt = iter->user_data2;
+
+ g_return_if_fail (elt->ref_count > 0);
+
+ elt->ref_count--;
+ level->ref_count--;
+ if (level->ref_count == 0)
+ {
+ FilterLevel *parent_level = level->parent_level;
+ FilterElt *parent_elt = level->parent_elt;
+
+ /* we are at zero -- time to increase the zero_ref_count val */
+ while (parent_level)
+ {
+ parent_elt->zero_ref_count++;
+
+ parent_elt = parent_level->parent_elt;
+ parent_level = parent_level->parent_level;
+ }
+ filter->zero_ref_count++;
+ }
+
+#ifdef VERBOSE
+ g_print ("unreffed %p\n", iter->user_data2);
+ g_print ("zero ref count is now %d\n", filter->zero_ref_count);
+#endif
+}
+
+/* bits and pieces */
+static void
+egg_tree_model_filter_set_model (EggTreeModelFilter *filter,
+ GtkTreeModel *child_model)
+{
+ g_return_if_fail (EGG_IS_TREE_MODEL_FILTER (filter));
+
+ if (filter->child_model)
+ {
+ g_signal_handler_disconnect (G_OBJECT (filter->child_model),
+ filter->changed_id);
+ g_signal_handler_disconnect (G_OBJECT (filter->child_model),
+ filter->inserted_id);
+ g_signal_handler_disconnect (G_OBJECT (filter->child_model),
+ filter->has_child_toggled_id);
+ g_signal_handler_disconnect (G_OBJECT (filter->child_model),
+ filter->deleted_id);
+ g_signal_handler_disconnect (G_OBJECT (filter->child_model),
+ filter->reordered_id);
+
+ /* reset our state */
+ if (filter->root)
+ egg_tree_model_filter_free_level (filter, filter->root);
+
+ filter->root = NULL;
+ g_object_unref (G_OBJECT (filter->child_model));
+ filter->visible_column = -1;
+ /* FIXME: destroy more crack here? the funcs? */
+ }
+
+ filter->child_model = child_model;
+
+ if (child_model)
+ {
+ g_object_ref (G_OBJECT (filter->child_model));
+ filter->changed_id =
+ g_signal_connect (child_model, "row_changed",
+ G_CALLBACK (egg_tree_model_filter_row_changed),
+ filter);
+ filter->inserted_id =
+ g_signal_connect (child_model, "row_inserted",
+ G_CALLBACK (egg_tree_model_filter_row_inserted),
+ filter);
+ filter->has_child_toggled_id =
+ g_signal_connect (child_model, "row_has_child_toggled",
+ G_CALLBACK (egg_tree_model_filter_row_has_child_toggled),
+ filter);
+ filter->deleted_id =
+ g_signal_connect (child_model, "row_deleted",
+ G_CALLBACK (egg_tree_model_filter_row_deleted),
+ filter);
+ filter->reordered_id =
+ g_signal_connect (child_model, "rows_reordered",
+ G_CALLBACK (egg_tree_model_filter_rows_reordered),
+ filter);
+
+ filter->child_flags = gtk_tree_model_get_flags (child_model);
+ filter->stamp = g_random_int ();
+ }
+}
+
+static void
+egg_tree_model_filter_set_root (EggTreeModelFilter *filter,
+ GtkTreePath *root)
+{
+ g_return_if_fail (EGG_IS_TREE_MODEL_FILTER (filter));
+
+ if (!root)
+ filter->virtual_root = NULL;
+ else
+ filter->virtual_root = gtk_tree_path_copy (root);
+}
+
+/* public API */
+
+GtkTreeModel *
+egg_tree_model_filter_new (GtkTreeModel *child_model,
+ GtkTreePath *root)
+{
+ GtkTreeModel *retval;
+
+ g_return_val_if_fail (GTK_IS_TREE_MODEL (child_model), NULL);
+
+ retval = GTK_TREE_MODEL (g_object_new (egg_tree_model_filter_get_type (), NULL));
+
+ egg_tree_model_filter_set_model (EGG_TREE_MODEL_FILTER (retval),
+ child_model);
+ egg_tree_model_filter_set_root (EGG_TREE_MODEL_FILTER (retval), root);
+
+ return retval;
+}
+
+GtkTreeModel *
+egg_tree_model_filter_get_model (EggTreeModelFilter *filter)
+{
+ g_return_val_if_fail (EGG_IS_TREE_MODEL_FILTER (filter), NULL);
+
+ return filter->child_model;
+}
+
+void
+egg_tree_model_filter_set_visible_func (EggTreeModelFilter *filter,
+ EggTreeModelFilterVisibleFunc func,
+ gpointer data,
+ GtkDestroyNotify destroy)
+{
+ g_return_if_fail (EGG_IS_TREE_MODEL_FILTER (filter));
+ g_return_if_fail (func != NULL);
+ g_return_if_fail (filter->visible_method_set == FALSE);
+
+ if (filter->visible_func)
+ {
+ GtkDestroyNotify d = filter->visible_destroy;
+
+ filter->visible_destroy = NULL;
+ d (filter->visible_data);
+ }
+
+ filter->visible_func = func;
+ filter->visible_data = data;
+ filter->visible_destroy = destroy;
+
+ filter->visible_method_set = TRUE;
+}
+
+void
+egg_tree_model_filter_set_modify_func (EggTreeModelFilter *filter,
+ gint n_columns,
+ GType *types,
+ EggTreeModelFilterModifyFunc func,
+ gpointer data,
+ GtkDestroyNotify destroy)
+{
+ g_return_if_fail (EGG_IS_TREE_MODEL_FILTER (filter));
+ g_return_if_fail (func != NULL);
+ g_return_if_fail (filter->modify_func_set == FALSE);
+
+ if (filter->modify_destroy)
+ {
+ GtkDestroyNotify d = filter->modify_destroy;
+
+ filter->modify_destroy = NULL;
+ d (filter->modify_data);
+ }
+
+ filter->modify_n_columns = n_columns;
+ filter->modify_types = g_new0 (GType, n_columns);
+ memcpy (filter->modify_types, types, sizeof (GType) * n_columns);
+ filter->modify_func = func;
+ filter->modify_data = data;
+ filter->modify_destroy = destroy;
+
+ filter->modify_func_set = TRUE;
+}
+
+void
+egg_tree_model_filter_set_visible_column (EggTreeModelFilter *filter,
+ gint column)
+{
+ g_return_if_fail (EGG_IS_TREE_MODEL_FILTER (filter));
+ g_return_if_fail (column >= 0);
+ g_return_if_fail (filter->visible_method_set == FALSE);
+
+ filter->visible_column = column;
+
+ filter->visible_method_set = TRUE;
+}
+
+/* conversion */
+void
+egg_tree_model_filter_convert_child_iter_to_iter (EggTreeModelFilter *filter,
+ GtkTreeIter *filter_iter,
+ GtkTreeIter *child_iter)
+{
+ GtkTreePath *child_path, *path;
+
+ g_return_if_fail (EGG_IS_TREE_MODEL_FILTER (filter));
+ g_return_if_fail (filter->child_model != NULL);
+ g_return_if_fail (filter_iter != NULL);
+ g_return_if_fail (child_iter != NULL);
+
+ filter_iter->stamp = 0;
+
+ child_path = gtk_tree_model_get_path (filter->child_model, child_iter);
+ g_return_if_fail (child_path != NULL);
+
+ path = egg_tree_model_filter_convert_child_path_to_path (filter,
+ child_path);
+ gtk_tree_path_free (child_path);
+ g_return_if_fail (path != NULL);
+
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (filter), filter_iter, path);
+ gtk_tree_path_free (path);
+}
+
+void
+egg_tree_model_filter_convert_iter_to_child_iter (EggTreeModelFilter *filter,
+ GtkTreeIter *child_iter,
+ GtkTreeIter *filter_iter)
+{
+ g_return_if_fail (EGG_IS_TREE_MODEL_FILTER (filter));
+ g_return_if_fail (filter->child_model != NULL);
+ g_return_if_fail (child_iter != NULL);
+ g_return_if_fail (filter_iter != NULL);
+ g_return_if_fail (filter_iter->stamp == filter->stamp);
+
+ if (EGG_TREE_MODEL_FILTER_CACHE_CHILD_ITERS (filter))
+ {
+ *child_iter = FILTER_ELT (filter_iter->user_data2)->iter;
+ }
+ else
+ {
+ GtkTreePath *path;
+
+ path = egg_tree_model_filter_elt_get_path (filter_iter->user_data,
+ filter_iter->user_data2,
+ NULL);
+ gtk_tree_model_get_iter (filter->child_model, child_iter, path);
+ gtk_tree_path_free (path);
+ }
+}
+
+static GtkTreePath *
+egg_real_tree_model_filter_convert_child_path_to_path (EggTreeModelFilter *filter,
+ GtkTreePath *child_path,
+ gboolean build_levels,
+ gboolean fetch_childs)
+{
+ gint *child_indices;
+ GtkTreePath *retval;
+ GtkTreePath *real_path;
+ FilterLevel *level;
+ gint i;
+
+ g_return_val_if_fail (EGG_IS_TREE_MODEL_FILTER (filter), NULL);
+ g_return_val_if_fail (filter->child_model != NULL, NULL);
+ g_return_val_if_fail (child_path != NULL, NULL);
+
+ if (!filter->virtual_root)
+ real_path = gtk_tree_path_copy (child_path);
+ else
+ real_path = egg_tree_model_filter_remove_root (child_path,
+ filter->virtual_root);
+
+ if (!real_path)
+ return NULL;
+
+ retval = gtk_tree_path_new ();
+ child_indices = gtk_tree_path_get_indices (real_path);
+
+ if (filter->root == NULL && build_levels)
+ egg_tree_model_filter_build_level (filter, NULL, NULL);
+ level = FILTER_LEVEL (filter->root);
+
+ for (i = 0; i < gtk_tree_path_get_depth (real_path); i++)
+ {
+ gint j;
+ gboolean found_child = FALSE;
+
+ if (!level)
+ {
+ gtk_tree_path_free (real_path);
+ gtk_tree_path_free (retval);
+ return NULL;
+ }
+
+ for (j = 0; j < level->array->len; j++)
+ {
+ if ((g_array_index (level->array, FilterElt, j)).offset == child_indices[i])
+ {
+ gtk_tree_path_append_index (retval, j);
+ if (g_array_index (level->array, FilterElt, j).children == NULL && build_levels)
+ egg_tree_model_filter_build_level (filter,
+ level,
+ &g_array_index (level->array, FilterElt, j));
+ level = g_array_index (level->array, FilterElt, j).children;
+ found_child = TRUE;
+ break;
+ }
+ }
+
+ if (!found_child && fetch_childs)
+ {
+ /* didn't find the child, let's try to bring it back */
+ if (!egg_tree_model_filter_fetch_child (filter, level, child_indices[i]))
+ {
+ /* not there */
+ gtk_tree_path_free (real_path);
+ gtk_tree_path_free (retval);
+ return NULL;
+ }
+
+ /* yay, let's search for the child again */
+ for (j = 0; j < level->array->len; j++)
+ {
+ if ((g_array_index (level->array, FilterElt, j)).offset == child_indices[i])
+ {
+ gtk_tree_path_append_index (retval, j);
+ if (g_array_index (level->array, FilterElt, j).children == NULL && build_levels)
+ egg_tree_model_filter_build_level (filter,
+ level,
+ &g_array_index (level->array, FilterElt, j));
+ level = g_array_index (level->array, FilterElt, j).children;
+ found_child = TRUE;
+ break;
+ }
+ }
+
+ if (!found_child)
+ {
+ /* our happy fun fetch attempt failed ?!?!?! no child! */
+ gtk_tree_path_free (real_path);
+ gtk_tree_path_free (retval);
+ return NULL;
+ }
+ }
+ else if (!found_child && !fetch_childs)
+ {
+ /* no path */
+ gtk_tree_path_free (real_path);
+ gtk_tree_path_free (retval);
+ return NULL;
+ }
+ }
+
+ gtk_tree_path_free (real_path);
+ return retval;
+}
+
+GtkTreePath *
+egg_tree_model_filter_convert_child_path_to_path (EggTreeModelFilter *filter,
+ GtkTreePath *child_path)
+{
+ /* this function does the sanity checks */
+ return egg_real_tree_model_filter_convert_child_path_to_path (filter,
+ child_path,
+ TRUE,
+ TRUE);
+}
+
+GtkTreePath *
+egg_tree_model_filter_convert_path_to_child_path (EggTreeModelFilter *filter,
+ GtkTreePath *filter_path)
+{
+ gint *filter_indices;
+ GtkTreePath *retval;
+ FilterLevel *level;
+ gint i;
+
+ g_return_val_if_fail (EGG_IS_TREE_MODEL_FILTER (filter), NULL);
+ g_return_val_if_fail (filter->child_model != NULL, NULL);
+ g_return_val_if_fail (filter_path != NULL, NULL);
+
+ /* convert path */
+ retval = gtk_tree_path_new ();
+ filter_indices = gtk_tree_path_get_indices (filter_path);
+ if (!filter->root)
+ egg_tree_model_filter_build_level (filter, NULL, NULL);
+ level = FILTER_LEVEL (filter->root);
+
+ for (i = 0; i < gtk_tree_path_get_depth (filter_path); i++)
+ {
+ gint count = filter_indices[i];
+
+ if (!level || level->array->len <= filter_indices[i])
+ {
+ gtk_tree_path_free (retval);
+ return NULL;
+ }
+
+ if (g_array_index (level->array, FilterElt, count).children == NULL)
+ egg_tree_model_filter_build_level (filter, level, &g_array_index (level->array, FilterElt, count));
+
+ if (!level || level->array->len <= filter_indices[i])
+ {
+ gtk_tree_path_free (retval);
+ return NULL;
+ }
+
+ gtk_tree_path_append_index (retval, g_array_index (level->array, FilterElt, count).offset);
+ level = g_array_index (level->array, FilterElt, count).children;
+ }
+
+ /* apply vroot */
+
+ if (filter->virtual_root)
+ {
+ GtkTreePath *real_retval;
+
+ real_retval = egg_tree_model_filter_add_root (retval,
+ filter->virtual_root);
+ gtk_tree_path_free (retval);
+
+ return real_retval;
+ }
+
+ return retval;
+}
+
+void
+egg_tree_model_filter_clear_cache (EggTreeModelFilter *filter)
+{
+ g_return_if_fail (EGG_IS_TREE_MODEL_FILTER (filter));
+
+ if (filter->zero_ref_count)
+ egg_tree_model_filter_clear_cache_helper (filter,
+ FILTER_LEVEL (filter->root));
+}
diff --git a/lib/widgets/eggtreemodelfilter.h b/lib/widgets/eggtreemodelfilter.h
new file mode 100644
index 000000000..ada78c1be
--- /dev/null
+++ b/lib/widgets/eggtreemodelfilter.h
@@ -0,0 +1,123 @@
+/* eggtreemodelfilter.h
+ * Copyright (C) 2000,2001 Red Hat, Inc., Jonathan Blandford <jrb@redhat.com>
+ * Copyright (C) 2001,2002 Kristian Rietveld <kris@gtk.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EGG_TREE_MODEL_FILTER_H__
+#define __EGG_TREE_MODEL_FILTER_H__
+
+#include <gtk/gtktreemodel.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_TREE_MODEL_FILTER (egg_tree_model_filter_get_type ())
+#define EGG_TREE_MODEL_FILTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_TREE_MODEL_FILTER, EggTreeModelFilter))
+#define EGG_TREE_MODEL_FILTER_CLASS(vtable) (G_TYPE_CHECK_CLASS_CAST ((vtable), EGG_TYPE_TREE_MODEL_FILTER, EggTreeModelFilterClass))
+#define EGG_IS_TREE_MODEL_FILTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_TREE_MODEL_FILTER))
+#define EGG_IS_TREE_MODEL_FILTER_CLASS(vtable) (G_TYPE_CHECK_CLASS_TYPE ((vtable), EGG_TYPE_TREE_MODEL_FILTER))
+#define EGG_TREE_MODEL_FILTER_GET_CLASS(inst) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_TREE_MODEL_FILTER, EggTreeModelFilterClass))
+
+typedef gboolean (* EggTreeModelFilterVisibleFunc) (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data);
+typedef void (* EggTreeModelFilterModifyFunc) (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GValue *value,
+ gint column,
+ gpointer data);
+
+typedef struct _EggTreeModelFilter EggTreeModelFilter;
+typedef struct _EggTreeModelFilterClass EggTreeModelFilterClass;
+
+struct _EggTreeModelFilter
+{
+ GObject parent;
+
+ /*< private >*/
+ gpointer root;
+ gint stamp;
+ guint child_flags;
+ GtkTreeModel *child_model;
+ gint zero_ref_count;
+
+ GtkTreePath *virtual_root;
+
+ EggTreeModelFilterVisibleFunc visible_func;
+ gpointer visible_data;
+ GtkDestroyNotify visible_destroy;
+
+ gint modify_n_columns;
+ GType *modify_types;
+ EggTreeModelFilterModifyFunc modify_func;
+ gpointer modify_data;
+ gpointer modify_destroy;
+
+ gint visible_column;
+
+ gboolean visible_method_set;
+ gboolean modify_func_set;
+
+ /* signal ids */
+ guint changed_id;
+ guint inserted_id;
+ guint has_child_toggled_id;
+ guint deleted_id;
+ guint reordered_id;
+};
+
+struct _EggTreeModelFilterClass
+{
+ GObjectClass parent_class;
+};
+
+GType egg_tree_model_filter_get_type (void);
+GtkTreeModel * egg_tree_model_filter_new (GtkTreeModel *child_model,
+ GtkTreePath *root);
+void egg_tree_model_filter_set_visible_func (EggTreeModelFilter *filter,
+ EggTreeModelFilterVisibleFunc func,
+ gpointer data,
+ GtkDestroyNotify destroy);
+void egg_tree_model_filter_set_modify_func (EggTreeModelFilter *filter,
+ gint n_columns,
+ GType *types,
+ EggTreeModelFilterModifyFunc func,
+ gpointer data,
+ GtkDestroyNotify destroy);
+void egg_tree_model_filter_set_visible_column (EggTreeModelFilter *filter,
+ gint column);
+
+GtkTreeModel *egg_tree_model_filter_get_model (EggTreeModelFilter *filter);
+
+/* conversion */
+void egg_tree_model_filter_convert_child_iter_to_iter (EggTreeModelFilter *filter,
+ GtkTreeIter *filter_iter,
+ GtkTreeIter *child_iter);
+void egg_tree_model_filter_convert_iter_to_child_iter (EggTreeModelFilter *filter,
+ GtkTreeIter *child_iter,
+ GtkTreeIter *filter_iter);
+GtkTreePath *egg_tree_model_filter_convert_child_path_to_path (EggTreeModelFilter *filter,
+ GtkTreePath *child_path);
+GtkTreePath *egg_tree_model_filter_convert_path_to_child_path (EggTreeModelFilter *path,
+ GtkTreePath *filter_path);
+
+
+void egg_tree_model_filter_clear_cache (EggTreeModelFilter *filter);
+
+G_END_DECLS
+
+#endif /* __EGG_TREE_MODEL_FILTER_H__ */
diff --git a/lib/widgets/eggtreemultidnd.c b/lib/widgets/eggtreemultidnd.c
new file mode 100644
index 000000000..8be54f442
--- /dev/null
+++ b/lib/widgets/eggtreemultidnd.c
@@ -0,0 +1,415 @@
+/* eggtreemultidnd.c
+ * Copyright (C) 2001 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <string.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkmain.h>
+#include "eggtreemultidnd.h"
+
+#define EGG_TREE_MULTI_DND_STRING "EggTreeMultiDndString"
+
+typedef struct
+{
+ guint pressed_button;
+ gint x;
+ gint y;
+ guint motion_notify_handler;
+ guint button_release_handler;
+ guint drag_data_get_handler;
+ GSList *event_list;
+} EggTreeMultiDndData;
+
+/* CUT-N-PASTE from gtktreeview.c */
+typedef struct _TreeViewDragInfo TreeViewDragInfo;
+struct _TreeViewDragInfo
+{
+ GdkModifierType start_button_mask;
+ GtkTargetList *source_target_list;
+ GdkDragAction source_actions;
+
+ GtkTargetList *dest_target_list;
+
+ guint source_set : 1;
+ guint dest_set : 1;
+};
+
+
+GType
+egg_tree_multi_drag_source_get_type (void)
+{
+ static GType our_type = 0;
+
+ if (!our_type)
+ {
+ static const GTypeInfo our_info =
+ {
+ sizeof (EggTreeMultiDragSourceIface), /* class_size */
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ NULL,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ 0,
+ 0, /* n_preallocs */
+ NULL
+ };
+
+ our_type = g_type_register_static (G_TYPE_INTERFACE, "EggTreeMultiDragSource", &our_info, 0);
+ }
+
+ return our_type;
+}
+
+
+/**
+ * egg_tree_multi_drag_source_row_draggable:
+ * @drag_source: a #EggTreeMultiDragSource
+ * @path: row on which user is initiating a drag
+ *
+ * Asks the #EggTreeMultiDragSource whether a particular row can be used as
+ * the source of a DND operation. If the source doesn't implement
+ * this interface, the row is assumed draggable.
+ *
+ * Return value: %TRUE if the row can be dragged
+ **/
+gboolean
+egg_tree_multi_drag_source_row_draggable (EggTreeMultiDragSource *drag_source,
+ GList *path_list)
+{
+ EggTreeMultiDragSourceIface *iface = EGG_TREE_MULTI_DRAG_SOURCE_GET_IFACE (drag_source);
+
+ g_return_val_if_fail (EGG_IS_TREE_MULTI_DRAG_SOURCE (drag_source), FALSE);
+ g_return_val_if_fail (iface->row_draggable != NULL, FALSE);
+ g_return_val_if_fail (path_list != NULL, FALSE);
+
+ if (iface->row_draggable)
+ return (* iface->row_draggable) (drag_source, path_list);
+ else
+ return TRUE;
+}
+
+
+/**
+ * egg_tree_multi_drag_source_drag_data_delete:
+ * @drag_source: a #EggTreeMultiDragSource
+ * @path: row that was being dragged
+ *
+ * Asks the #EggTreeMultiDragSource to delete the row at @path, because
+ * it was moved somewhere else via drag-and-drop. Returns %FALSE
+ * if the deletion fails because @path no longer exists, or for
+ * some model-specific reason. Should robustly handle a @path no
+ * longer found in the model!
+ *
+ * Return value: %TRUE if the row was successfully deleted
+ **/
+gboolean
+egg_tree_multi_drag_source_drag_data_delete (EggTreeMultiDragSource *drag_source,
+ GList *path_list)
+{
+ EggTreeMultiDragSourceIface *iface = EGG_TREE_MULTI_DRAG_SOURCE_GET_IFACE (drag_source);
+
+ g_return_val_if_fail (EGG_IS_TREE_MULTI_DRAG_SOURCE (drag_source), FALSE);
+ g_return_val_if_fail (iface->drag_data_delete != NULL, FALSE);
+ g_return_val_if_fail (path_list != NULL, FALSE);
+
+ return (* iface->drag_data_delete) (drag_source, path_list);
+}
+
+/**
+ * egg_tree_multi_drag_source_drag_data_get:
+ * @drag_source: a #EggTreeMultiDragSource
+ * @path: row that was dragged
+ * @selection_data: a #EggSelectionData to fill with data from the dragged row
+ *
+ * Asks the #EggTreeMultiDragSource to fill in @selection_data with a
+ * representation of the row at @path. @selection_data->target gives
+ * the required type of the data. Should robustly handle a @path no
+ * longer found in the model!
+ *
+ * Return value: %TRUE if data of the required type was provided
+ **/
+gboolean
+egg_tree_multi_drag_source_drag_data_get (EggTreeMultiDragSource *drag_source,
+ GList *path_list,
+ guint info,
+ GtkSelectionData *selection_data)
+{
+ EggTreeMultiDragSourceIface *iface = EGG_TREE_MULTI_DRAG_SOURCE_GET_IFACE (drag_source);
+
+ g_return_val_if_fail (EGG_IS_TREE_MULTI_DRAG_SOURCE (drag_source), FALSE);
+ g_return_val_if_fail (iface->drag_data_get != NULL, FALSE);
+ g_return_val_if_fail (path_list != NULL, FALSE);
+ g_return_val_if_fail (selection_data != NULL, FALSE);
+
+ return (* iface->drag_data_get) (drag_source, path_list, info, selection_data);
+}
+
+static void
+stop_drag_check (GtkWidget *widget)
+{
+ EggTreeMultiDndData *priv_data;
+ GSList *l;
+
+ priv_data = g_object_get_data (G_OBJECT (widget), EGG_TREE_MULTI_DND_STRING);
+
+ for (l = priv_data->event_list; l != NULL; l = l->next)
+ gdk_event_free (l->data);
+
+ g_slist_free (priv_data->event_list);
+ priv_data->event_list = NULL;
+ g_signal_handler_disconnect (widget, priv_data->motion_notify_handler);
+ g_signal_handler_disconnect (widget, priv_data->button_release_handler);
+}
+
+static gboolean
+egg_tree_multi_drag_button_release_event (GtkWidget *widget,
+ GdkEventButton *event,
+ gpointer data)
+{
+ EggTreeMultiDndData *priv_data;
+ GSList *l;
+
+ priv_data = g_object_get_data (G_OBJECT (widget), EGG_TREE_MULTI_DND_STRING);
+
+ for (l = priv_data->event_list; l != NULL; l = l->next)
+ gtk_propagate_event (widget, l->data);
+
+ stop_drag_check (widget);
+
+ return FALSE;
+}
+
+static void
+selection_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ GList **list_ptr;
+
+ list_ptr = (GList **) data;
+
+ *list_ptr = g_list_prepend (*list_ptr, gtk_tree_row_reference_new (model, path));
+}
+
+static void
+path_list_free (GList *path_list)
+{
+ g_list_foreach (path_list, (GFunc) gtk_tree_row_reference_free, NULL);
+ g_list_free (path_list);
+}
+
+static void
+set_context_data (GdkDragContext *context,
+ GList *path_list)
+{
+ g_object_set_data_full (G_OBJECT (context),
+ "egg-tree-view-multi-source-row",
+ path_list,
+ (GDestroyNotify) path_list_free);
+}
+
+static GList *
+get_context_data (GdkDragContext *context)
+{
+ return g_object_get_data (G_OBJECT (context),
+ "egg-tree-view-multi-source-row");
+}
+
+/* CUT-N-PASTE from gtktreeview.c */
+static TreeViewDragInfo*
+get_info (GtkTreeView *tree_view)
+{
+ return g_object_get_data (G_OBJECT (tree_view), "gtk-tree-view-drag-info");
+}
+
+
+static void
+egg_tree_multi_drag_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time)
+{
+ GtkTreeView *tree_view;
+ GtkTreeModel *model;
+ TreeViewDragInfo *di;
+ GList *path_list;
+
+ tree_view = GTK_TREE_VIEW (widget);
+
+ model = gtk_tree_view_get_model (tree_view);
+
+ if (model == NULL)
+ return;
+
+ di = get_info (GTK_TREE_VIEW (widget));
+
+ if (di == NULL)
+ return;
+
+ path_list = get_context_data (context);
+
+ if (path_list == NULL)
+ return;
+
+ /* We can implement the GTK_TREE_MODEL_ROW target generically for
+ * any model; for DragSource models there are some other targets
+ * we also support.
+ */
+
+ if (EGG_IS_TREE_MULTI_DRAG_SOURCE (model))
+ {
+ egg_tree_multi_drag_source_drag_data_get (EGG_TREE_MULTI_DRAG_SOURCE (model),
+ path_list,
+ info,
+ selection_data);
+ }
+}
+
+static gboolean
+egg_tree_multi_drag_motion_event (GtkWidget *widget,
+ GdkEventMotion *event,
+ gpointer data)
+{
+ EggTreeMultiDndData *priv_data;
+
+ priv_data = g_object_get_data (G_OBJECT (widget), EGG_TREE_MULTI_DND_STRING);
+
+ if (gtk_drag_check_threshold (widget,
+ priv_data->x,
+ priv_data->y,
+ event->x,
+ event->y))
+ {
+ GList *path_list = NULL;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GdkDragContext *context;
+ TreeViewDragInfo *di;
+
+ di = get_info (GTK_TREE_VIEW (widget));
+
+ if (di == NULL)
+ return FALSE;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
+ stop_drag_check (widget);
+ gtk_tree_selection_selected_foreach (selection, selection_foreach, &path_list);
+ path_list = g_list_reverse (path_list);
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
+
+ if (egg_tree_multi_drag_source_row_draggable (EGG_TREE_MULTI_DRAG_SOURCE (model), path_list))
+ {
+ context = gtk_drag_begin (widget,
+ di->source_target_list,
+ di->source_actions,
+ priv_data->pressed_button,
+ (GdkEvent*)event);
+ set_context_data (context, path_list);
+ }
+ else
+ {
+ path_list_free (path_list);
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+egg_tree_multi_drag_button_press_event (GtkWidget *widget,
+ GdkEventButton *event,
+ gpointer data)
+{
+ GtkTreeView *tree_view;
+ GtkTreePath *path = NULL;
+ GtkTreeViewColumn *column = NULL;
+ gint cell_x, cell_y;
+ GtkTreeSelection *selection;
+ EggTreeMultiDndData *priv_data;
+
+ if (event->button == 3)
+ return FALSE;
+
+ tree_view = GTK_TREE_VIEW (widget);
+ priv_data = g_object_get_data (G_OBJECT (tree_view), EGG_TREE_MULTI_DND_STRING);
+ if (priv_data == NULL)
+ {
+ priv_data = g_new0 (EggTreeMultiDndData, 1);
+ g_object_set_data (G_OBJECT (tree_view), EGG_TREE_MULTI_DND_STRING, priv_data);
+ }
+
+ if (g_slist_find (priv_data->event_list, event))
+ return FALSE;
+
+ if (priv_data->event_list)
+ {
+ /* save the event to be propagated in order */
+ priv_data->event_list = g_slist_append (priv_data->event_list, gdk_event_copy ((GdkEvent*)event));
+ return TRUE;
+ }
+
+ if (event->type == GDK_2BUTTON_PRESS)
+ return FALSE;
+
+ gtk_tree_view_get_path_at_pos (tree_view,
+ event->x, event->y,
+ &path, &column,
+ &cell_x, &cell_y);
+
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ if (path && gtk_tree_selection_path_is_selected (selection, path))
+ {
+ priv_data->pressed_button = event->button;
+ priv_data->x = event->x;
+ priv_data->y = event->y;
+ priv_data->event_list = g_slist_append (priv_data->event_list, gdk_event_copy ((GdkEvent*)event));
+ priv_data->motion_notify_handler =
+ g_signal_connect (G_OBJECT (tree_view), "motion_notify_event", G_CALLBACK (egg_tree_multi_drag_motion_event), NULL);
+ priv_data->button_release_handler =
+ g_signal_connect (G_OBJECT (tree_view), "button_release_event", G_CALLBACK (egg_tree_multi_drag_button_release_event), NULL);
+
+ if (priv_data->drag_data_get_handler == 0)
+ {
+ priv_data->drag_data_get_handler =
+ g_signal_connect (G_OBJECT (tree_view), "drag_data_get", G_CALLBACK (egg_tree_multi_drag_drag_data_get), NULL);
+ }
+
+ return TRUE;
+ }
+
+ if (path)
+ {
+ gtk_tree_path_free (path);
+ }
+
+ return FALSE;
+}
+
+void
+egg_tree_multi_drag_add_drag_support (GtkTreeView *tree_view)
+{
+ g_return_if_fail (GTK_IS_TREE_VIEW (tree_view));
+ g_signal_connect (G_OBJECT (tree_view), "button_press_event", G_CALLBACK (egg_tree_multi_drag_button_press_event), NULL);
+}
+
diff --git a/lib/widgets/eggtreemultidnd.h b/lib/widgets/eggtreemultidnd.h
new file mode 100644
index 000000000..460e60239
--- /dev/null
+++ b/lib/widgets/eggtreemultidnd.h
@@ -0,0 +1,78 @@
+/* eggtreednd.h
+ * Copyright (C) 2001 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EGG_TREE_MULTI_DND_H__
+#define __EGG_TREE_MULTI_DND_H__
+
+#include <gtk/gtktreemodel.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtkdnd.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_TREE_MULTI_DRAG_SOURCE (egg_tree_multi_drag_source_get_type ())
+#define EGG_TREE_MULTI_DRAG_SOURCE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_TREE_MULTI_DRAG_SOURCE, EggTreeMultiDragSource))
+#define EGG_IS_TREE_MULTI_DRAG_SOURCE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_TREE_MULTI_DRAG_SOURCE))
+#define EGG_TREE_MULTI_DRAG_SOURCE_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), EGG_TYPE_TREE_MULTI_DRAG_SOURCE, EggTreeMultiDragSourceIface))
+
+typedef struct _EggTreeMultiDragSource EggTreeMultiDragSource; /* Dummy typedef */
+typedef struct _EggTreeMultiDragSourceIface EggTreeMultiDragSourceIface;
+
+struct _EggTreeMultiDragSourceIface
+{
+ GTypeInterface g_iface;
+
+ /* VTable - not signals */
+ gboolean (* row_draggable) (EggTreeMultiDragSource *drag_source,
+ GList *path_list);
+
+ gboolean (* drag_data_get) (EggTreeMultiDragSource *drag_source,
+ GList *path_list,
+ guint info,
+ GtkSelectionData *selection_data);
+
+ gboolean (* drag_data_delete) (EggTreeMultiDragSource *drag_source,
+ GList *path_list);
+};
+
+GType egg_tree_multi_drag_source_get_type (void) G_GNUC_CONST;
+
+/* Returns whether the given row can be dragged */
+gboolean egg_tree_multi_drag_source_row_draggable (EggTreeMultiDragSource *drag_source,
+ GList *path_list);
+
+/* Deletes the given row, or returns FALSE if it can't */
+gboolean egg_tree_multi_drag_source_drag_data_delete (EggTreeMultiDragSource *drag_source,
+ GList *path_list);
+
+
+/* Fills in selection_data with type selection_data->target based on the row
+ * denoted by path, returns TRUE if it does anything
+ */
+gboolean egg_tree_multi_drag_source_drag_data_get (EggTreeMultiDragSource *drag_source,
+ GList *path_list,
+ guint info,
+ GtkSelectionData *selection_data);
+void egg_tree_multi_drag_add_drag_support (GtkTreeView *tree_view);
+
+
+
+G_END_DECLS
+
+#endif /* __EGG_TREE_MULTI_DND_H__ */
diff --git a/lib/widgets/ephy-autocompletion-window.c b/lib/widgets/ephy-autocompletion-window.c
new file mode 100644
index 000000000..9c639a52a
--- /dev/null
+++ b/lib/widgets/ephy-autocompletion-window.c
@@ -0,0 +1,854 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#include <libgnome/gnome-i18n.h>
+#include <gtk/gtkcellrenderertext.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtkliststore.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtkvbox.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkframe.h>
+
+#include "ephy-autocompletion-window.h"
+#include "ephy-gobject-misc.h"
+#include "ephy-string.h"
+#include "ephy-marshal.h"
+#include "ephy-gui.h"
+
+/* This is copied from gtkscrollbarwindow.c */
+#define DEFAULT_SCROLLBAR_SPACING 3
+
+#define SCROLLBAR_SPACING(w) \
+ (GTK_SCROLLED_WINDOW_GET_CLASS (w)->scrollbar_spacing >= 0 ? \
+ GTK_SCROLLED_WINDOW_GET_CLASS (w)->scrollbar_spacing : DEFAULT_SCROLLBAR_SPACING)
+
+#define MAX_VISIBLE_ROWS 9
+#define MAX_COMPLETION_ALTERNATIVES 7
+
+//#define DEBUG_MSG(x) g_print x
+#define DEBUG_MSG(x)
+
+//#define DEBUG_TIME
+
+#ifdef DEBUG_TIME
+#include <glib/gtimer.h>
+#endif
+
+/**
+ * Private data
+ */
+struct _EphyAutocompletionWindowPrivate {
+ EphyAutocompletion *autocompletion;
+ GtkWidget *parent;
+
+ GtkWidget *window;
+ GtkScrolledWindow *scrolled_window;
+ GtkTreeView *tree_view;
+ GtkTreeViewColumn *col1;
+ GtkTreeView *action_tree_view;
+ GtkTreeViewColumn *action_col1;
+ GtkTreeView *active_tree_view;
+ gboolean only_actions;
+
+ char *selected;
+
+ GtkListStore *list_store;
+ GtkListStore *action_list_store;
+ guint last_added_match;
+ int view_nitems;
+
+ gboolean shown;
+};
+
+/**
+ * Private functions, only availble from this file
+ */
+static void ephy_autocompletion_window_class_init (EphyAutocompletionWindowClass *klass);
+static void ephy_autocompletion_window_init (EphyAutocompletionWindow *aw);
+static void ephy_autocompletion_window_finalize_impl (GObject *o);
+static void ephy_autocompletion_window_init_widgets (EphyAutocompletionWindow *aw);
+static void ephy_autocompletion_window_selection_changed_cb (GtkTreeSelection *treeselection,
+ EphyAutocompletionWindow *aw);
+static gboolean ephy_autocompletion_window_button_press_event_cb (GtkWidget *widget,
+ GdkEventButton *event,
+ EphyAutocompletionWindow *aw);
+static gboolean ephy_autocompletion_window_key_press_cb (GtkWidget *widget,
+ GdkEventKey *event,
+ EphyAutocompletionWindow *aw);
+static void ephy_autocompletion_window_event_after_cb (GtkWidget *wid, GdkEvent *event,
+ EphyAutocompletionWindow *aw);
+static void ephy_autocompletion_window_fill_store_chunk (EphyAutocompletionWindow *aw);
+
+
+static gpointer g_object_class;
+
+enum EphyAutocompletionWindowSignalsEnum {
+ ACTIVATED,
+ EPHY_AUTOCOMPLETION_WINDOW_HIDDEN,
+ EPHY_AUTOCOMPLETION_WINDOW_LAST_SIGNAL
+};
+static gint EphyAutocompletionWindowSignals[EPHY_AUTOCOMPLETION_WINDOW_LAST_SIGNAL];
+
+/**
+ * AutocompletionWindow object
+ */
+
+MAKE_GET_TYPE (ephy_autocompletion_window, "EphyAutocompletionWindow", EphyAutocompletionWindow,
+ ephy_autocompletion_window_class_init,
+ ephy_autocompletion_window_init, G_TYPE_OBJECT);
+
+static void
+ephy_autocompletion_window_class_init (EphyAutocompletionWindowClass *klass)
+{
+ G_OBJECT_CLASS (klass)->finalize = ephy_autocompletion_window_finalize_impl;
+ g_object_class = g_type_class_peek_parent (klass);
+
+ EphyAutocompletionWindowSignals[ACTIVATED] = g_signal_new (
+ "activated", G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
+ G_STRUCT_OFFSET (EphyAutocompletionWindowClass, activated),
+ NULL, NULL,
+ ephy_marshal_VOID__STRING_INT,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_STRING,
+ G_TYPE_INT);
+
+ EphyAutocompletionWindowSignals[EPHY_AUTOCOMPLETION_WINDOW_HIDDEN] = g_signal_new (
+ "hidden", G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
+ G_STRUCT_OFFSET (EphyAutocompletionWindowClass, hidden),
+ NULL, NULL,
+ ephy_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+ephy_autocompletion_window_init (EphyAutocompletionWindow *aw)
+{
+ EphyAutocompletionWindowPrivate *p = g_new0 (EphyAutocompletionWindowPrivate, 1);
+ GtkTreeSelection *s;
+
+ aw->priv = p;
+ p->selected = NULL;
+
+ ephy_autocompletion_window_init_widgets (aw);
+
+ s = gtk_tree_view_get_selection (p->tree_view);
+ /* I would like to use GTK_SELECTION_SINGLE, but it seems to require that one
+ item is selected always */
+ gtk_tree_selection_set_mode (s, GTK_SELECTION_MULTIPLE);
+
+ g_signal_connect (s, "changed", G_CALLBACK (ephy_autocompletion_window_selection_changed_cb), aw);
+
+ s = gtk_tree_view_get_selection (p->action_tree_view);
+ gtk_tree_selection_set_mode (s, GTK_SELECTION_MULTIPLE);
+
+ g_signal_connect (s, "changed", G_CALLBACK (ephy_autocompletion_window_selection_changed_cb), aw);
+}
+
+static void
+ephy_autocompletion_window_finalize_impl (GObject *o)
+{
+ EphyAutocompletionWindow *aw = EPHY_AUTOCOMPLETION_WINDOW (o);
+ EphyAutocompletionWindowPrivate *p = aw->priv;
+
+ if (p->list_store) g_object_unref (p->list_store);
+ if (p->action_list_store) g_object_unref (p->action_list_store);
+ if (p->parent) g_object_unref (p->parent);
+ if (p->window) gtk_widget_destroy (p->window);
+
+ if (p->autocompletion)
+ {
+ g_signal_handlers_disconnect_matched (p->autocompletion, G_SIGNAL_MATCH_DATA, 0, 0,
+ NULL, NULL, aw);
+ g_object_unref (p->autocompletion);
+ }
+
+ g_free (p->selected);
+ g_free (p);
+
+ gdk_pointer_ungrab (GDK_CURRENT_TIME);
+ gdk_keyboard_ungrab (GDK_CURRENT_TIME);
+
+ G_OBJECT_CLASS (g_object_class)->finalize (o);
+}
+
+static void
+ephy_autocompletion_window_init_widgets (EphyAutocompletionWindow *aw)
+{
+ EphyAutocompletionWindowPrivate *p = aw->priv;
+ GtkWidget *sw;
+ GtkCellRenderer *renderer;
+ GtkWidget *frame;
+ GtkWidget *vbox;
+ GdkColor *bg_color;
+ guint32 base, dark;
+ GValue v = { 0 };
+
+ p->window = gtk_window_new (GTK_WINDOW_POPUP);
+ gtk_window_set_resizable (GTK_WINDOW (p->window), FALSE);
+
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame),
+ GTK_SHADOW_OUT);
+ gtk_container_add (GTK_CONTAINER (p->window), frame);
+ gtk_widget_show (frame);
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_widget_show (vbox);
+ gtk_container_add (GTK_CONTAINER (frame), vbox);
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox),
+ sw, TRUE, TRUE, 0);
+ gtk_scrolled_window_set_shadow_type
+ (GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_IN);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+ p->scrolled_window = GTK_SCROLLED_WINDOW (sw);
+ gtk_widget_show (sw);
+
+ p->tree_view = GTK_TREE_VIEW (gtk_tree_view_new ());
+ gtk_container_add (GTK_CONTAINER (sw), GTK_WIDGET (p->tree_view));
+
+ renderer = gtk_cell_renderer_text_new ();
+ p->col1 = gtk_tree_view_column_new ();
+ gtk_tree_view_column_pack_start (p->col1, renderer, TRUE);
+ gtk_tree_view_column_set_attributes (p->col1, renderer,
+ "text", 0,
+ NULL);
+ gtk_tree_view_append_column (p->tree_view, p->col1);
+
+ gtk_tree_view_set_headers_visible (p->tree_view, FALSE);
+ gtk_widget_show (GTK_WIDGET(p->tree_view));
+
+ p->action_tree_view = GTK_TREE_VIEW (gtk_tree_view_new ());
+ gtk_box_pack_start (GTK_BOX (vbox),
+ GTK_WIDGET (p->action_tree_view),
+ FALSE, TRUE, 0);
+
+ renderer = gtk_cell_renderer_text_new ();
+
+ g_value_init (&v, GDK_TYPE_COLOR);
+ g_object_get_property (G_OBJECT (renderer), "cell_background_gdk", &v);
+ bg_color = g_value_peek_pointer (&v);
+ base = ephy_gui_gdk_color_to_rgb (bg_color);
+ dark = ephy_gui_rgb_shift_color (base, 0.15);
+ *bg_color = ephy_gui_gdk_rgb_to_color (dark);
+ g_object_set_property (G_OBJECT (renderer), "cell_background_gdk", &v);
+
+ p->action_col1 = gtk_tree_view_column_new ();
+ gtk_tree_view_column_pack_start (p->action_col1, renderer, TRUE);
+ gtk_tree_view_column_set_attributes (p->action_col1, renderer,
+ "text", 0,
+ NULL);
+ gtk_tree_view_append_column (p->action_tree_view, p->action_col1);
+
+ gtk_tree_view_set_headers_visible (p->action_tree_view, FALSE);
+ gtk_widget_show (GTK_WIDGET(p->action_tree_view));
+}
+
+EphyAutocompletionWindow *
+ephy_autocompletion_window_new (EphyAutocompletion *ac, GtkWidget *w)
+{
+ EphyAutocompletionWindow *ret = g_object_new (EPHY_TYPE_AUTOCOMPLETION_WINDOW, NULL);
+ ephy_autocompletion_window_set_parent_widget (ret, w);
+ ephy_autocompletion_window_set_autocompletion (ret, ac);
+ return ret;
+}
+
+void
+ephy_autocompletion_window_set_parent_widget (EphyAutocompletionWindow *aw, GtkWidget *w)
+{
+ if (aw->priv->parent) g_object_unref (aw->priv->parent);
+ aw->priv->parent = g_object_ref (w);
+}
+
+void
+ephy_autocompletion_window_set_autocompletion (EphyAutocompletionWindow *aw,
+ EphyAutocompletion *ac)
+{
+ EphyAutocompletionWindowPrivate *p = aw->priv;
+
+ if (p->autocompletion)
+ {
+ g_signal_handlers_disconnect_matched (p->autocompletion, G_SIGNAL_MATCH_DATA, 0, 0,
+ NULL, NULL, aw);
+
+ g_object_unref (p->autocompletion);
+
+ }
+ p->autocompletion = g_object_ref (ac);
+}
+
+static void
+ephy_autocompletion_window_selection_changed_cb (GtkTreeSelection *treeselection,
+ EphyAutocompletionWindow *aw)
+{
+ GList *l;
+ GtkTreeModel *model;
+
+ if (aw->priv->selected) g_free (aw->priv->selected);
+
+ l = gtk_tree_selection_get_selected_rows (treeselection, &model);
+ if (l)
+ {
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ path = (GtkTreePath *)l->data;
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_model_get (model, &iter, 1,
+ &aw->priv->selected, -1);
+
+ g_list_foreach (l, (GFunc)gtk_tree_path_free, NULL);
+ g_list_free (l);
+ }
+ else
+ {
+ aw->priv->selected = NULL;
+ }
+}
+
+static void
+ephy_autocompletion_window_get_dimensions (EphyAutocompletionWindow *aw,
+ int *x, int *y, int *width, int *height)
+{
+ GtkBin *popwin;
+ GtkWidget *widget;
+ GtkScrolledWindow *popup;
+ gint real_height;
+ GtkRequisition list_requisition;
+ gboolean show_vscroll = FALSE;
+ gint avail_height;
+ gint min_height;
+ gint alloc_width;
+ gint work_height;
+ gint old_height;
+ gint old_width;
+ int row_height;
+
+ widget = GTK_WIDGET (aw->priv->parent);
+ popup = GTK_SCROLLED_WINDOW (aw->priv->scrolled_window);
+ popwin = GTK_BIN (aw->priv->window);
+
+ gdk_window_get_origin (widget->window, x, y);
+ real_height = MIN (widget->requisition.height,
+ widget->allocation.height);
+ *y += real_height;
+ avail_height = gdk_screen_height () - *y;
+
+ gtk_widget_size_request (GTK_WIDGET(aw->priv->tree_view),
+ &list_requisition);
+
+ alloc_width = (widget->allocation.width -
+ 2 * popwin->child->style->xthickness -
+ 2 * GTK_CONTAINER (popwin->child)->border_width -
+ 2 * GTK_CONTAINER (popup)->border_width -
+ 2 * GTK_CONTAINER (GTK_BIN (popup)->child)->border_width -
+ 2 * GTK_BIN (popup)->child->style->xthickness);
+
+ work_height = (2 * popwin->child->style->ythickness +
+ 2 * GTK_CONTAINER (popwin->child)->border_width +
+ 2 * GTK_CONTAINER (popup)->border_width +
+ 2 * GTK_CONTAINER (GTK_BIN (popup)->child)->border_width +
+ 2 * GTK_BIN (popup)->child->style->ythickness);
+
+ min_height = MIN (list_requisition.height,
+ popup->vscrollbar->requisition.height);
+
+ row_height = list_requisition.height / MAX (aw->priv->view_nitems, 1);
+ DEBUG_MSG (("Real list requisition %d, Items %d\n", list_requisition.height, aw->priv->view_nitems));
+ list_requisition.height = MIN (row_height * MAX_VISIBLE_ROWS, list_requisition.height);
+ DEBUG_MSG (("Row Height %d, Fake list requisition %d\n",
+ row_height, list_requisition.height));
+
+ do
+ {
+ old_width = alloc_width;
+ old_height = work_height;
+
+ if (!show_vscroll &&
+ work_height + list_requisition.height > avail_height)
+ {
+ if (work_height + min_height > avail_height &&
+ *y - real_height > avail_height)
+ {
+ *y -= (work_height + list_requisition.height +
+ real_height);
+ break;
+ }
+ alloc_width -= (popup->vscrollbar->requisition.width +
+ SCROLLBAR_SPACING (popup));
+ show_vscroll = TRUE;
+ }
+ } while (old_width != alloc_width || old_height != work_height);
+
+ *width = widget->allocation.width;
+
+ if (*x < 0) *x = 0;
+
+ *height = MIN (work_height + list_requisition.height,
+ avail_height);
+
+ /* Action view */
+ work_height = (2 * GTK_CONTAINER (popup)->border_width +
+ 2 * GTK_WIDGET (popup)->style->ythickness);
+
+ if (!GTK_WIDGET_VISIBLE (aw->priv->scrolled_window))
+ {
+ *height = work_height;
+ }
+
+ gtk_widget_size_request (GTK_WIDGET(aw->priv->action_tree_view),
+ &list_requisition);
+
+ if (GTK_WIDGET_VISIBLE (aw->priv->action_tree_view))
+ {
+ *height += list_requisition.height;
+ }
+}
+
+static void
+ephy_autocompletion_window_fill_store_chunk (EphyAutocompletionWindow *aw)
+{
+ EphyAutocompletionWindowPrivate *p = aw->priv;
+ const EphyAutocompletionMatch *matches;
+ guint i;
+ gboolean changed;
+ guint nmatches;
+ guint last;
+ guint completion_nitems = 0, action_nitems = 0, substring_nitems = 0;
+#ifdef DEBUG_TIME
+ GTimer *timer;
+#endif
+ DEBUG_MSG (("ACW: Filling the list from %d\n", last));
+
+#ifdef DEBUG_TIME
+ timer = g_timer_new ();
+ g_timer_start (timer);
+#endif
+
+ nmatches = ephy_autocompletion_get_num_matches (p->autocompletion);
+ matches = ephy_autocompletion_get_matches_sorted_by_score (p->autocompletion,
+ &changed);
+ if (!changed) return;
+
+ if (p->list_store) g_object_unref (p->list_store);
+ p->list_store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+
+ if (p->action_list_store) g_object_unref (p->action_list_store);
+ p->action_list_store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+
+ last = p->last_added_match = 0;
+
+ for (i = 0; last < nmatches; i++, last++)
+ {
+ const EphyAutocompletionMatch *m = &matches[last];
+ GtkTreeIter iter;
+ GtkListStore *store;
+
+ if (m->is_action || m->substring ||
+ completion_nitems <= MAX_COMPLETION_ALTERNATIVES)
+ {
+ if (m->is_action)
+ {
+ store = p->action_list_store;
+ action_nitems ++;
+ }
+ else if (m->substring)
+ {
+ store = p->list_store;
+ substring_nitems ++;
+ }
+ else
+ {
+ store = p->list_store;
+ completion_nitems ++;
+ }
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ 0, m->title,
+ 1, m->target,
+ -1);
+ }
+ }
+
+ p->view_nitems = substring_nitems + completion_nitems;
+
+ gtk_widget_show (GTK_WIDGET (p->scrolled_window));
+ gtk_widget_show (GTK_WIDGET (p->action_tree_view));
+ if (p->view_nitems == 0)
+ {
+ gtk_widget_hide (GTK_WIDGET (p->scrolled_window));
+ }
+ if (action_nitems == 0)
+ {
+ gtk_widget_hide (GTK_WIDGET (p->action_tree_view));
+ }
+
+ p->last_added_match = last;
+
+#ifdef DEBUG_TIME
+ DEBUG_MSG (("ACW: %f elapsed filling the gtkliststore\n", g_timer_elapsed (timer, NULL)));
+ g_timer_destroy (timer);
+#endif
+}
+
+void
+ephy_autocompletion_window_show (EphyAutocompletionWindow *aw)
+{
+ EphyAutocompletionWindowPrivate *p = aw->priv;
+ gint x, y, height, width;
+ guint nmatches;
+#ifdef DEBUG_TIME
+ GTimer *timer1;
+ GTimer *timer2;
+#endif
+
+ g_return_if_fail (p->window);
+ g_return_if_fail (p->autocompletion);
+
+ nmatches = ephy_autocompletion_get_num_matches (p->autocompletion);
+ if (nmatches <= 0)
+ {
+ ephy_autocompletion_window_hide (aw);
+ return;
+ }
+
+#ifdef DEBUG_TIME
+ DEBUG_MSG (("ACW: showing window.\n"));
+ timer1 = g_timer_new ();
+ g_timer_start (timer1);
+#endif
+
+#ifdef DEBUG_TIME
+ timer2 = g_timer_new ();
+ g_timer_start (timer2);
+#endif
+
+ ephy_autocompletion_window_fill_store_chunk (aw);
+
+ p->only_actions = (!GTK_WIDGET_VISIBLE (p->scrolled_window) ||
+ GTK_WIDGET_HAS_FOCUS (p->action_tree_view));
+ if (p->only_actions)
+ {
+ p->active_tree_view = p->action_tree_view;
+ }
+ else
+ {
+ p->active_tree_view = p->tree_view;
+ }
+
+#ifdef DEBUG_TIME
+ DEBUG_MSG (("ACW: %f elapsed creating liststore\n", g_timer_elapsed (timer2, NULL)));
+#endif
+
+ gtk_tree_view_set_model (p->tree_view, GTK_TREE_MODEL (p->list_store));
+ gtk_tree_view_set_model (p->action_tree_view, GTK_TREE_MODEL (p->action_list_store));
+
+#ifdef DEBUG_TIME
+ g_timer_start (timer2);
+#endif
+
+ ephy_autocompletion_window_get_dimensions (aw, &x, &y, &width, &height);
+
+#ifdef DEBUG_TIME
+ DEBUG_MSG (("ACW: %f elapsed calculating dimensions\n", g_timer_elapsed (timer2, NULL)));
+ g_timer_destroy (timer2);
+#endif
+
+ gtk_widget_set_size_request (GTK_WIDGET (p->window), width,
+ height);
+ gtk_window_move (GTK_WINDOW (p->window), x, y);
+
+ if (!p->shown)
+ {
+ gtk_widget_show (p->window);
+
+ gdk_pointer_grab (p->parent->window, TRUE,
+ GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK,
+ NULL, NULL, GDK_CURRENT_TIME);
+ gdk_keyboard_grab (p->parent->window, TRUE, GDK_CURRENT_TIME);\
+ gtk_grab_add (p->window);
+
+ g_signal_connect (p->window, "button-press-event",
+ G_CALLBACK (ephy_autocompletion_window_button_press_event_cb),
+ aw);
+ g_signal_connect (p->window, "key-press-event",
+ G_CALLBACK (ephy_autocompletion_window_key_press_cb),
+ aw);
+ g_signal_connect (p->tree_view, "event-after",
+ G_CALLBACK (ephy_autocompletion_window_event_after_cb),
+ aw);
+ g_signal_connect (p->action_tree_view, "event-after",
+ G_CALLBACK (ephy_autocompletion_window_event_after_cb),
+ aw);
+
+ p->shown = TRUE;
+ }
+
+ gtk_tree_view_scroll_to_point (GTK_TREE_VIEW (p->tree_view), 0, 0);
+
+ gtk_widget_grab_focus (GTK_WIDGET (p->tree_view));
+#ifdef DEBUG_TIME
+ DEBUG_MSG (("ACW: %f elapsed showing window\n", g_timer_elapsed (timer1, NULL)));
+ g_timer_destroy (timer1);
+#endif
+}
+
+static gboolean
+ephy_autocompletion_window_button_press_event_cb (GtkWidget *widget,
+ GdkEventButton *event,
+ EphyAutocompletionWindow *aw)
+{
+ GtkWidget *event_widget;
+
+ event_widget = gtk_get_event_widget ((GdkEvent *) event);
+
+ /* Check to see if button press happened inside the alternatives
+ window. If not, destroy the window. */
+ if (event_widget != aw->priv->window)
+ {
+ while (event_widget)
+ {
+ if (event_widget == aw->priv->window)
+ return FALSE;
+ event_widget = event_widget->parent;
+ }
+ }
+ ephy_autocompletion_window_hide (aw);
+
+ return TRUE;
+}
+
+static GtkTreeView *
+hack_tree_view_move_selection (GtkTreeView *tv, GtkTreeView *alternate, int dir)
+{
+ GtkTreeSelection *ts = gtk_tree_view_get_selection (tv);
+ GtkTreeModel *model;
+ GList *selected = NULL;
+ selected = gtk_tree_selection_get_selected_rows (ts, &model);
+ gboolean prev_result = TRUE;
+
+ gtk_tree_selection_unselect_all (ts);
+
+ if (!selected)
+ {
+ GtkTreePath *p = gtk_tree_path_new_first ();
+ gtk_tree_selection_select_path (ts, p);
+ gtk_tree_view_scroll_to_cell (tv, p, NULL, FALSE, 0, 0);
+ gtk_tree_path_free (p);
+ }
+ else
+ {
+ GtkTreePath *p = selected->data;
+ int i;
+ if (dir > 0)
+ {
+ for (i = 0; i < dir; ++i)
+ {
+ gtk_tree_path_next (p);
+ }
+ }
+ else
+ {
+ for (i = 0; i > dir; --i)
+ {
+ prev_result = gtk_tree_path_prev (p);
+ }
+ }
+
+ if (prev_result)
+ {
+ gtk_tree_selection_select_path (ts, p);
+ gtk_tree_view_scroll_to_cell (tv, p, NULL, FALSE, 0, 0);
+ }
+ }
+
+ g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
+ g_list_free (selected);
+
+ if (!prev_result)
+ {
+ GtkTreeModel *model;
+ int c;
+ GtkTreeIter iter;
+ GtkTreePath *p;
+ GtkTreeSelection *selection;
+
+ model = gtk_tree_view_get_model (alternate);
+ c = gtk_tree_model_iter_n_children (model, NULL);
+ gtk_tree_model_iter_nth_child (model, &iter, NULL, c - 1);
+ p = gtk_tree_model_get_path (model, &iter);
+ selection = gtk_tree_view_get_selection (alternate);
+ gtk_tree_selection_select_path (selection, p);
+ gtk_tree_view_scroll_to_cell (alternate, p, NULL, FALSE, 0, 0);
+ gtk_tree_path_free (p);
+ return alternate;
+ }
+ else if (gtk_tree_selection_count_selected_rows (ts) == 0)
+ {
+ hack_tree_view_move_selection (alternate, tv, dir);
+ return alternate;
+ }
+
+ return tv;
+}
+
+static gboolean
+ephy_autocompletion_window_key_press_hack (EphyAutocompletionWindow *aw,
+ guint keyval)
+{
+ GtkTreeView *tree_view, *alt;
+ EphyAutocompletionWindowPrivate *p = aw->priv;
+ gboolean action;
+
+ action = (p->active_tree_view == p->action_tree_view);
+ tree_view = action ? p->action_tree_view : p->tree_view;
+ alt = action ? p->tree_view : p->action_tree_view;
+ alt = p->only_actions ? p->action_tree_view : alt;
+
+ switch (keyval)
+ {
+ case GDK_Up:
+ p->active_tree_view = hack_tree_view_move_selection
+ (tree_view, alt, -1);
+ break;
+ case GDK_Down:
+ p->active_tree_view = hack_tree_view_move_selection
+ (tree_view, alt, +1);
+ break;
+ case GDK_Page_Down:
+ p->active_tree_view = hack_tree_view_move_selection
+ (tree_view, alt, +5);
+ break;
+ case GDK_Page_Up:
+ p->active_tree_view = hack_tree_view_move_selection
+ (tree_view, alt, -5);
+ break;
+ case GDK_Return:
+ case GDK_space:
+ if (aw->priv->selected)
+ {
+ g_signal_emit (aw, EphyAutocompletionWindowSignals
+ [ACTIVATED], 0, aw->priv->selected, action);
+ }
+ break;
+ default:
+ g_warning ("Unexpected keyval");
+ break;
+ }
+ return TRUE;
+}
+
+static gboolean
+ephy_autocompletion_window_key_press_cb (GtkWidget *widget,
+ GdkEventKey *event,
+ EphyAutocompletionWindow *aw)
+{
+ GdkEventKey tmp_event;
+ EphyAutocompletionWindowPrivate *p = aw->priv;
+ GtkWidget *dest_widget;
+
+ /* allow keyboard navigation in the alternatives clist */
+ if (event->keyval == GDK_Up || event->keyval == GDK_Down
+ || event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down
+ || ((event->keyval == GDK_space || event->keyval == GDK_Return)
+ && p->selected))
+ {
+ return ephy_autocompletion_window_key_press_hack
+ (aw, event->keyval);
+ }
+ else
+ {
+ dest_widget = p->parent;
+ }
+
+ if (dest_widget != widget)
+ {
+ //DEBUG_MSG (("Resending event\n"));
+
+ tmp_event = *event;
+ gtk_widget_event (dest_widget, (GdkEvent *)&tmp_event);
+
+ return TRUE;
+ }
+ else
+ {
+ if (widget == GTK_WIDGET (p->tree_view))
+ {
+ //DEBUG_MSG (("on the tree view "));
+ }
+ //DEBUG_MSG (("Ignoring event\n"));
+ return FALSE;
+ }
+
+}
+
+void
+ephy_autocompletion_window_hide (EphyAutocompletionWindow *aw)
+{
+ if (aw->priv->window)
+ {
+ gtk_widget_hide (aw->priv->window);
+ gtk_grab_remove (aw->priv->window);
+ gdk_pointer_ungrab (GDK_CURRENT_TIME);
+ gdk_keyboard_ungrab (GDK_CURRENT_TIME);
+ ephy_autocompletion_window_unselect (aw);
+ g_signal_emit (aw, EphyAutocompletionWindowSignals[EPHY_AUTOCOMPLETION_WINDOW_HIDDEN], 0);
+ }
+ g_free (aw->priv->selected);
+ aw->priv->selected = NULL;
+ aw->priv->shown = FALSE;
+}
+
+void
+ephy_autocompletion_window_unselect (EphyAutocompletionWindow *aw)
+{
+ EphyAutocompletionWindowPrivate *p = aw->priv;
+ GtkTreeSelection *ts = gtk_tree_view_get_selection (p->tree_view);
+ gtk_tree_selection_unselect_all (ts);
+}
+
+static void
+ephy_autocompletion_window_event_after_cb (GtkWidget *wid, GdkEvent *event,
+ EphyAutocompletionWindow *aw)
+{
+ gboolean action;
+ EphyAutocompletionWindowPrivate *p = aw->priv;
+
+ action = (wid == GTK_WIDGET (p->action_tree_view));
+
+ if (event->type == GDK_BUTTON_PRESS
+ && ((GdkEventButton *) event)->button == 1)
+ {
+ if (p->selected)
+ {
+ g_signal_emit (aw, EphyAutocompletionWindowSignals
+ [ACTIVATED], 0, p->selected, action);
+ }
+ }
+}
diff --git a/lib/widgets/ephy-autocompletion-window.h b/lib/widgets/ephy-autocompletion-window.h
new file mode 100644
index 000000000..b390fc35c
--- /dev/null
+++ b/lib/widgets/ephy-autocompletion-window.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifndef EPHY_AUTOCOMPLETION_WINDOW_H
+#define EPHY_AUTOCOMPLETION_WINDOW_H
+
+#include <glib-object.h>
+#include <gtk/gtkwidget.h>
+
+#include "ephy-autocompletion.h"
+
+G_BEGIN_DECLS
+
+/* object forward declarations */
+
+typedef struct _EphyAutocompletionWindow EphyAutocompletionWindow;
+typedef struct _EphyAutocompletionWindowClass EphyAutocompletionWindowClass;
+typedef struct _EphyAutocompletionWindowPrivate EphyAutocompletionWindowPrivate;
+
+/**
+ * Editor object
+ */
+
+#define EPHY_TYPE_AUTOCOMPLETION_WINDOW (ephy_autocompletion_window_get_type())
+#define EPHY_AUTOCOMPLETION_WINDOW(object) (G_TYPE_CHECK_INSTANCE_CAST((object), \
+ EPHY_TYPE_AUTOCOMPLETION_WINDOW,\
+ EphyAutocompletionWindow))
+#define EPHY_AUTOCOMPLETION_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \
+ EPHY_TYPE_AUTOCOMPLETION_WINDOW,\
+ EphyAutocompletionWindowClass))
+#define EPHY_IS_AUTOCOMPLETION_WINDOW(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), \
+ EPHY_TYPE_AUTOCOMPLETION_WINDOW))
+#define EPHY_IS_AUTOCOMPLETION_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ EPHY_TYPE_AUTOCOMPLETION_WINDOW))
+#define EPHY_AUTOCOMPLETION_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \
+ EPHY_TYPE_AUTOCOMPLETION_WINDOW,\
+ EphyAutocompletionWindowClass))
+
+struct _EphyAutocompletionWindowClass
+{
+ GObjectClass parent_class;
+
+ /* signals */
+ void (*hidden) (EphyAutocompletionWindow *aw);
+ void (*activated) (EphyAutocompletionWindow *aw,
+ const char *target,
+ int action);
+
+};
+
+/* Remember: fields are public read-only */
+struct _EphyAutocompletionWindow
+{
+ GObject parent_object;
+
+ EphyAutocompletionWindowPrivate *priv;
+};
+
+GType ephy_autocompletion_window_get_type (void);
+EphyAutocompletionWindow *ephy_autocompletion_window_new (EphyAutocompletion *ac,
+ GtkWidget *parent);
+void ephy_autocompletion_window_set_parent_widget (EphyAutocompletionWindow *aw,
+ GtkWidget *w);
+void ephy_autocompletion_window_set_autocompletion (EphyAutocompletionWindow *aw,
+ EphyAutocompletion *ac);
+void ephy_autocompletion_window_show (EphyAutocompletionWindow *aw);
+void ephy_autocompletion_window_hide (EphyAutocompletionWindow *aw);
+void ephy_autocompletion_window_unselect (EphyAutocompletionWindow *aw);
+
+G_END_DECLS
+
+#endif
diff --git a/lib/widgets/ephy-ellipsizing-label.c b/lib/widgets/ephy-ellipsizing-label.c
new file mode 100644
index 000000000..13f911078
--- /dev/null
+++ b/lib/widgets/ephy-ellipsizing-label.c
@@ -0,0 +1,774 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* eel-ellipsizing-label.c: Subclass of GtkLabel that ellipsizes the text.
+
+ Copyright (C) 2001 Eazel, Inc.
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more priv.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: John Sullivan <sullivan@eazel.com>,
+ Marco Pesenti Gritti <marco@it.gnome.org> Markup support
+ */
+
+#include "ephy-ellipsizing-label.h"
+
+#include <string.h>
+
+struct EphyEllipsizingLabelPrivate
+{
+ char *full_text;
+
+ EphyEllipsizeMode mode;
+};
+
+static void ephy_ellipsizing_label_class_init (EphyEllipsizingLabelClass *class);
+static void ephy_ellipsizing_label_init (EphyEllipsizingLabel *label);
+
+static GObjectClass *parent_class = NULL;
+
+static int
+ephy_strcmp (const char *string_a, const char *string_b)
+{
+ return strcmp (string_a == NULL ? "" : string_a,
+ string_b == NULL ? "" : string_b);
+}
+
+static gboolean
+ephy_str_is_equal (const char *string_a, const char *string_b)
+{
+ return ephy_strcmp (string_a, string_b) == 0;
+}
+
+#define ELLIPSIS "..."
+
+/* Caution: this is an _expensive_ function */
+static int
+measure_string_width (const char *string,
+ PangoLayout *layout,
+ gboolean markup)
+{
+ int width;
+
+ if (markup)
+ {
+ pango_layout_set_markup (layout, string, -1);
+ }
+ else
+ {
+ pango_layout_set_text (layout, string, -1);
+ }
+
+ pango_layout_get_pixel_size (layout, &width, NULL);
+
+ return width;
+}
+
+/* this is also plenty slow */
+static void
+compute_character_widths (const char *string,
+ PangoLayout *layout,
+ int *char_len_return,
+ int **widths_return,
+ int **cuts_return,
+ gboolean markup)
+{
+ int *widths;
+ int *offsets;
+ int *cuts;
+ int char_len;
+ int byte_len;
+ const char *p;
+ const char *nm_string;
+ int i;
+ PangoLayoutIter *iter;
+ PangoLogAttr *attrs;
+
+#define BEGINS_UTF8_CHAR(x) (((x) & 0xc0) != 0x80)
+
+ if (markup)
+ {
+ pango_layout_set_markup (layout, string, -1);
+ }
+ else
+ {
+ pango_layout_set_text (layout, string, -1);
+ }
+
+ nm_string = pango_layout_get_text (layout);
+
+ char_len = g_utf8_strlen (nm_string, -1);
+ byte_len = strlen (nm_string);
+
+ widths = g_new (int, char_len);
+ offsets = g_new (int, byte_len);
+
+ /* Create a translation table from byte index to char offset */
+ p = nm_string;
+ i = 0;
+ while (*p) {
+ int byte_index = p - nm_string;
+
+ if (BEGINS_UTF8_CHAR (*p)) {
+ offsets[byte_index] = i;
+ ++i;
+ } else {
+ offsets[byte_index] = G_MAXINT; /* segv if we try to use this */
+ }
+
+ ++p;
+ }
+
+ /* Now fill in the widths array */
+ iter = pango_layout_get_iter (layout);
+
+ do {
+ PangoRectangle extents;
+ int byte_index;
+
+ byte_index = pango_layout_iter_get_index (iter);
+
+ if (byte_index < byte_len) {
+ pango_layout_iter_get_char_extents (iter, &extents);
+
+ g_assert (BEGINS_UTF8_CHAR (nm_string[byte_index]));
+ g_assert (offsets[byte_index] < char_len);
+
+ widths[offsets[byte_index]] = PANGO_PIXELS (extents.width);
+ }
+
+ } while (pango_layout_iter_next_char (iter));
+
+ pango_layout_iter_free (iter);
+
+ g_free (offsets);
+
+ *widths_return = widths;
+
+ /* Now compute character offsets that are legitimate places to
+ * chop the string
+ */
+ attrs = g_new (PangoLogAttr, char_len + 1);
+
+ pango_get_log_attrs (nm_string, byte_len, -1,
+ pango_context_get_language (
+ pango_layout_get_context (layout)),
+ attrs,
+ char_len + 1);
+
+ cuts = g_new (int, char_len);
+ i = 0;
+ while (i < char_len) {
+ cuts[i] = attrs[i].is_cursor_position;
+
+ ++i;
+ }
+
+ g_free (attrs);
+
+ *cuts_return = cuts;
+
+ *char_len_return = char_len;
+}
+
+typedef struct
+{
+ GString *string;
+ int start_offset;
+ int end_offset;
+ int position;
+} EllipsizeStringData;
+
+static void
+start_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ EllipsizeStringData *data = (EllipsizeStringData *)user_data;
+ int i;
+
+ g_string_append_c (data->string, '<');
+ g_string_append (data->string, element_name);
+
+ for (i = 0; attribute_names[i] != NULL; i++)
+ {
+ g_string_append_c (data->string, ' ');
+ g_string_append (data->string, attribute_names[i]);
+ g_string_append (data->string, "=\"");
+ g_string_append (data->string, attribute_values[i]);
+ g_string_append_c (data->string, '"');
+ }
+
+ g_string_append_c (data->string, '>');
+}
+
+static void
+end_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ EllipsizeStringData *data = (EllipsizeStringData *)user_data;
+
+ g_string_append (data->string, "</");
+ g_string_append (data->string, element_name);
+ g_string_append_c (data->string, '>');
+}
+
+static void
+append_ellipsized_text (const char *text,
+ EllipsizeStringData *data,
+ int text_len)
+{
+ int position;
+ int new_position;
+
+ position = data->position;
+ new_position = data->position + text_len;
+
+ if (position > data->start_offset &&
+ new_position < data->end_offset)
+ {
+ return;
+ }
+ else if ((position < data->start_offset &&
+ new_position < data->start_offset) ||
+ (position > data->end_offset &&
+ new_position > data->end_offset))
+ {
+ g_string_append (data->string,
+ text);
+ }
+ else if (position <= data->start_offset &&
+ new_position >= data->end_offset)
+ {
+ if (position < data->start_offset)
+ {
+ g_string_append_len (data->string,
+ text,
+ data->start_offset -
+ position);
+ }
+
+ g_string_append (data->string,
+ ELLIPSIS);
+
+ if (new_position > data->end_offset)
+ {
+ g_string_append_len (data->string,
+ text + data->end_offset -
+ position,
+ position + text_len -
+ data->end_offset);
+ }
+ }
+
+ data->position = new_position;
+}
+
+static void
+text_handler (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ EllipsizeStringData *data = (EllipsizeStringData *)user_data;
+
+ append_ellipsized_text (text, data, text_len);
+}
+
+static GMarkupParser pango_markup_parser = {
+ start_element_handler,
+ end_element_handler,
+ text_handler,
+ NULL,
+ NULL
+};
+
+static char *
+ellipsize_string (const char *string,
+ int start_offset,
+ int end_offset,
+ gboolean markup)
+{
+ GString *str;
+ EllipsizeStringData data;
+ char *result;
+ GMarkupParseContext *c;
+
+ str = g_string_new (NULL);
+ data.string = str;
+ data.start_offset = start_offset;
+ data.end_offset = end_offset;
+ data.position = 0;
+
+ if (markup)
+ {
+ c = g_markup_parse_context_new (&pango_markup_parser,
+ 0, &data, NULL);
+ g_markup_parse_context_parse (c, string, -1, NULL);
+ g_markup_parse_context_free (c);
+ }
+ else
+ {
+ append_ellipsized_text (string, &data,
+ g_utf8_strlen (string, -1));
+ }
+
+ result = str->str;
+ g_string_free (str, FALSE);
+ return result;
+}
+
+static char *
+ephy_string_ellipsize_start (const char *string, PangoLayout *layout, int width, gboolean markup)
+{
+ int resulting_width;
+ int *cuts;
+ int *widths;
+ int char_len;
+ int truncate_offset;
+ int bytes_end;
+
+ /* Zero-length string can't get shorter - catch this here to
+ * avoid expensive calculations
+ */
+ if (*string == '\0')
+ return g_strdup ("");
+
+ /* I'm not sure if this short-circuit is a net win; it might be better
+ * to just dump this, and always do the compute_character_widths() etc.
+ * down below.
+ */
+ resulting_width = measure_string_width (string, layout, markup);
+
+ if (resulting_width <= width) {
+ /* String is already short enough. */
+ return g_strdup (string);
+ }
+
+ /* Remove width of an ellipsis */
+ width -= measure_string_width (ELLIPSIS, layout, markup);
+
+ if (width < 0) {
+ /* No room even for an ellipsis. */
+ return g_strdup ("");
+ }
+
+ /* Our algorithm involves removing enough chars from the string to bring
+ * the width to the required small size. However, due to ligatures,
+ * combining characters, etc., it's not guaranteed that the algorithm
+ * always works 100%. It's sort of a heuristic thing. It should work
+ * nearly all the time... but I wouldn't put in
+ * g_assert (width of resulting string < width).
+ *
+ * Hmm, another thing that this breaks with is explicit line breaks
+ * in "string"
+ */
+
+ compute_character_widths (string, layout, &char_len, &widths, &cuts, markup);
+
+ for (truncate_offset = 1; truncate_offset < char_len; truncate_offset++) {
+
+ resulting_width -= widths[truncate_offset];
+
+ if (resulting_width <= width &&
+ cuts[truncate_offset]) {
+ break;
+ }
+ }
+
+ g_free (cuts);
+ g_free (widths);
+
+ bytes_end = g_utf8_offset_to_pointer (string, truncate_offset) - string;
+
+ return ellipsize_string (string, 0, bytes_end, markup);
+}
+
+static char *
+ephy_string_ellipsize_end (const char *string, PangoLayout *layout, int width, gboolean markup)
+{
+ int resulting_width;
+ int *cuts;
+ int *widths;
+ int char_len;
+ int truncate_offset;
+ int bytes_end;
+
+ /* See explanatory comments in ellipsize_start */
+
+ if (*string == '\0')
+ return g_strdup ("");
+
+ resulting_width = measure_string_width (string, layout, markup);
+
+ if (resulting_width <= width) {
+ return g_strdup (string);
+ }
+
+ width -= measure_string_width (ELLIPSIS, layout, markup);
+
+ if (width < 0) {
+ return g_strdup ("");
+ }
+
+ compute_character_widths (string, layout, &char_len, &widths, &cuts, markup);
+
+ for (truncate_offset = char_len - 1; truncate_offset > 0; truncate_offset--) {
+ resulting_width -= widths[truncate_offset];
+ if (resulting_width <= width &&
+ cuts[truncate_offset]) {
+ break;
+ }
+ }
+
+ g_free (cuts);
+ g_free (widths);
+
+ bytes_end = g_utf8_offset_to_pointer (string, truncate_offset) - string;
+
+ return ellipsize_string (string, bytes_end,
+ char_len, markup);
+}
+
+static char *
+ephy_string_ellipsize_middle (const char *string, PangoLayout *layout, int width, gboolean markup)
+{
+ int resulting_width;
+ int *cuts;
+ int *widths;
+ int char_len;
+ int starting_fragment_length;
+ int ending_fragment_offset;
+ int bytes_start;
+ int bytes_end;
+
+ /* See explanatory comments in ellipsize_start */
+
+ if (*string == '\0')
+ return g_strdup ("");
+
+ resulting_width = measure_string_width (string, layout, markup);
+
+ if (resulting_width <= width) {
+ return g_strdup (string);
+ }
+
+ width -= measure_string_width (ELLIPSIS, layout, markup);
+
+ if (width < 0) {
+ return g_strdup ("");
+ }
+
+ compute_character_widths (string, layout, &char_len, &widths, &cuts, markup);
+
+ starting_fragment_length = char_len / 2;
+ ending_fragment_offset = starting_fragment_length + 1;
+
+ /* depending on whether the original string length is odd or even, start by
+ * shaving off the characters from the starting or ending fragment
+ */
+ if (char_len % 2) {
+ goto shave_end;
+ }
+
+ while (starting_fragment_length > 0 || ending_fragment_offset < char_len) {
+ if (resulting_width <= width &&
+ cuts[ending_fragment_offset] &&
+ cuts[starting_fragment_length]) {
+ break;
+ }
+
+ if (starting_fragment_length > 0) {
+ resulting_width -= widths[starting_fragment_length];
+ starting_fragment_length--;
+ }
+
+ shave_end:
+ if (resulting_width <= width &&
+ cuts[ending_fragment_offset] &&
+ cuts[starting_fragment_length]) {
+ break;
+ }
+
+ if (ending_fragment_offset < char_len) {
+ resulting_width -= widths[ending_fragment_offset];
+ ending_fragment_offset++;
+ }
+ }
+
+ g_free (cuts);
+ g_free (widths);
+
+ bytes_start = g_utf8_offset_to_pointer (string, starting_fragment_length) - string;
+ bytes_end = g_utf8_offset_to_pointer (string, ending_fragment_offset) - string;
+
+ return ellipsize_string (string, bytes_start, bytes_end, markup);
+}
+
+
+/**
+ * ephy_pango_layout_set_text_ellipsized
+ *
+ * @layout: a pango layout
+ * @string: A a string to be ellipsized.
+ * @width: Desired maximum width in points.
+ * @mode: The desired ellipsizing mode.
+ *
+ * Truncates a string if required to fit in @width and sets it on the
+ * layout. Truncation involves removing characters from the start, middle or end
+ * respectively and replacing them with "...". Algorithm is a bit
+ * fuzzy, won't work 100%.
+ *
+ */
+static void
+gul_pango_layout_set_text_ellipsized (PangoLayout *layout,
+ const char *string,
+ int width,
+ EphyEllipsizeMode mode,
+ gboolean markup)
+{
+ char *s;
+
+ g_return_if_fail (PANGO_IS_LAYOUT (layout));
+ g_return_if_fail (string != NULL);
+ g_return_if_fail (width >= 0);
+
+ switch (mode) {
+ case EPHY_ELLIPSIZE_START:
+ s = ephy_string_ellipsize_start (string, layout, width, markup);
+ break;
+ case EPHY_ELLIPSIZE_MIDDLE:
+ s = ephy_string_ellipsize_middle (string, layout, width, markup);
+ break;
+ case EPHY_ELLIPSIZE_END:
+ s = ephy_string_ellipsize_end (string, layout, width, markup);
+ break;
+ default:
+ g_return_if_reached ();
+ s = NULL;
+ }
+
+ if (markup)
+ {
+ pango_layout_set_markup (layout, s, -1);
+ }
+ else
+ {
+ pango_layout_set_text (layout, s, -1);
+ }
+
+ g_free (s);
+}
+
+GType
+ephy_ellipsizing_label_get_type (void)
+{
+ static GType ephy_ellipsizing_label_type = 0;
+
+ if (ephy_ellipsizing_label_type == 0)
+ {
+ static const GTypeInfo our_info =
+ {
+ sizeof (EphyEllipsizingLabelClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) ephy_ellipsizing_label_class_init,
+ NULL,
+ NULL, /* class_data */
+ sizeof (EphyEllipsizingLabel),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) ephy_ellipsizing_label_init
+ };
+
+ ephy_ellipsizing_label_type = g_type_register_static (GTK_TYPE_LABEL,
+ "EphyEllipsizingLabel",
+ &our_info, 0);
+ }
+
+ return ephy_ellipsizing_label_type;
+}
+
+static void
+ephy_ellipsizing_label_init (EphyEllipsizingLabel *label)
+{
+ label->priv = g_new0 (EphyEllipsizingLabelPrivate, 1);
+
+ label->priv->mode = EPHY_ELLIPSIZE_NONE;
+}
+
+static void
+real_finalize (GObject *object)
+{
+ EphyEllipsizingLabel *label;
+
+ label = EPHY_ELLIPSIZING_LABEL (object);
+
+ g_free (label->priv->full_text);
+ g_free (label->priv);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+GtkWidget*
+ephy_ellipsizing_label_new (const char *string)
+{
+ EphyEllipsizingLabel *label;
+
+ label = g_object_new (EPHY_TYPE_ELLIPSIZING_LABEL, NULL);
+ ephy_ellipsizing_label_set_text (label, string);
+
+ return GTK_WIDGET (label);
+}
+
+void
+ephy_ellipsizing_label_set_text (EphyEllipsizingLabel *label,
+ const char *string)
+{
+ g_return_if_fail (EPHY_IS_ELLIPSIZING_LABEL (label));
+
+ if (ephy_str_is_equal (string, label->priv->full_text)) {
+ return;
+ }
+
+ g_free (label->priv->full_text);
+ label->priv->full_text = g_strdup (string);
+
+ /* Queues a resize as side effect */
+ gtk_label_set_text (GTK_LABEL (label), label->priv->full_text);
+}
+
+void
+ephy_ellipsizing_label_set_markup (EphyEllipsizingLabel *label,
+ const char *string)
+{
+ g_return_if_fail (EPHY_IS_ELLIPSIZING_LABEL (label));
+
+ if (ephy_str_is_equal (string, label->priv->full_text)) {
+ return;
+ }
+
+ g_free (label->priv->full_text);
+ label->priv->full_text = g_strdup (string);
+
+ /* Queues a resize as side effect */
+ gtk_label_set_markup (GTK_LABEL (label), label->priv->full_text);
+}
+
+void
+ephy_ellipsizing_label_set_mode (EphyEllipsizingLabel *label,
+ EphyEllipsizeMode mode)
+{
+ g_return_if_fail (EPHY_IS_ELLIPSIZING_LABEL (label));
+
+ label->priv->mode = mode;
+}
+
+static void
+real_size_request (GtkWidget *widget, GtkRequisition *requisition)
+{
+ GTK_WIDGET_CLASS (parent_class)->size_request (widget, requisition);
+
+ /* Don't demand any particular width; will draw ellipsized into whatever size we're given */
+ requisition->width = 0;
+}
+
+static void
+real_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
+{
+ EphyEllipsizingLabel *label;
+ gboolean markup;
+
+ markup = gtk_label_get_use_markup (GTK_LABEL (widget));
+
+ label = EPHY_ELLIPSIZING_LABEL (widget);
+
+ /* This is the bad hack of the century, using private
+ * GtkLabel layout object. If the layout is NULL
+ * then it got blown away since size request,
+ * we just punt in that case, I don't know what to do really.
+ */
+
+ if (GTK_LABEL (label)->layout != NULL) {
+ if (label->priv->full_text == NULL) {
+ pango_layout_set_text (GTK_LABEL (label)->layout, "", -1);
+ } else {
+ EphyEllipsizeMode mode;
+
+ if (label->priv->mode != EPHY_ELLIPSIZE_NONE)
+ mode = label->priv->mode;
+
+ if (ABS (GTK_MISC (label)->xalign - 0.5) < 1e-12)
+ mode = EPHY_ELLIPSIZE_MIDDLE;
+ else if (GTK_MISC (label)->xalign < 0.5)
+ mode = EPHY_ELLIPSIZE_END;
+ else
+ mode = EPHY_ELLIPSIZE_START;
+
+ gul_pango_layout_set_text_ellipsized (GTK_LABEL (label)->layout,
+ label->priv->full_text,
+ allocation->width,
+ mode,
+ markup);
+
+ gtk_widget_queue_draw (GTK_WIDGET (label));
+ }
+ }
+
+ GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
+}
+
+static gboolean
+real_expose_event (GtkWidget *widget, GdkEventExpose *event)
+{
+ EphyEllipsizingLabel *label;
+ GtkRequisition req;
+
+ label = EPHY_ELLIPSIZING_LABEL (widget);
+
+ /* push/pop the actual size so expose draws in the right
+ * place, yes this is bad hack central. Here we assume the
+ * ellipsized text has been set on the layout in size_allocate
+ */
+ GTK_WIDGET_CLASS (parent_class)->size_request (widget, &req);
+ widget->requisition.width = req.width;
+ GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
+ widget->requisition.width = 0;
+
+ return FALSE;
+}
+
+
+static void
+ephy_ellipsizing_label_class_init (EphyEllipsizingLabelClass *klass)
+{
+ GtkWidgetClass *widget_class;
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ widget_class = GTK_WIDGET_CLASS (klass);
+
+ G_OBJECT_CLASS (klass)->finalize = real_finalize;
+
+ widget_class->size_request = real_size_request;
+ widget_class->size_allocate = real_size_allocate;
+ widget_class->expose_event = real_expose_event;
+}
+
diff --git a/lib/widgets/ephy-ellipsizing-label.h b/lib/widgets/ephy-ellipsizing-label.h
new file mode 100644
index 000000000..6f596edfa
--- /dev/null
+++ b/lib/widgets/ephy-ellipsizing-label.h
@@ -0,0 +1,71 @@
+/* eel-ellipsizing-label.h: Subclass of GtkLabel that ellipsizes the text.
+
+ Copyright (C) 2001 Eazel, Inc.
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: John Sullivan <sullivan@eazel.com>,
+ */
+
+#ifndef EPHY_ELLIPSIZING_LABEL_H
+#define EPHY_ELLIPSIZING_LABEL_H
+
+#include <gtk/gtklabel.h>
+
+#define EPHY_TYPE_ELLIPSIZING_LABEL (ephy_ellipsizing_label_get_type ())
+#define EPHY_ELLIPSIZING_LABEL(obj) (GTK_CHECK_CAST ((obj), EPHY_TYPE_ELLIPSIZING_LABEL, EphyEllipsizingLabel))
+#define EPHY_ELLIPSIZING_LABEL_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), EPHY_TYPE_ELLIPSIZING_LABEL, EphyEllipsizingLabelClass))
+#define EPHY_IS_ELLIPSIZING_LABEL(obj) (GTK_CHECK_TYPE ((obj), EPHY_TYPE_ELLIPSIZING_LABEL))
+#define EPHY_IS_ELLIPSIZING_LABEL_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), EPHY_TYPE_ELLIPSIZING_LABEL))
+
+typedef struct EphyEllipsizingLabelPrivate EphyEllipsizingLabelPrivate;
+
+typedef enum
+{
+ EPHY_ELLIPSIZE_NONE,
+ EPHY_ELLIPSIZE_START,
+ EPHY_ELLIPSIZE_MIDDLE,
+ EPHY_ELLIPSIZE_END
+} EphyEllipsizeMode;
+
+typedef struct
+{
+ GtkLabel parent;
+
+ EphyEllipsizingLabelPrivate *priv;
+} EphyEllipsizingLabel;
+
+typedef struct
+{
+ GtkLabelClass parent_class;
+} EphyEllipsizingLabelClass;
+
+GtkType ephy_ellipsizing_label_get_type (void);
+
+GtkWidget *ephy_ellipsizing_label_new (const char *string);
+
+void ephy_ellipsizing_label_set_mode (EphyEllipsizingLabel *label,
+ EphyEllipsizeMode mode);
+
+void ephy_ellipsizing_label_set_text (EphyEllipsizingLabel *label,
+ const char *string);
+
+void ephy_ellipsizing_label_set_markup (EphyEllipsizingLabel *label,
+ const char *string);
+
+G_END_DECLS
+
+#endif /* EPHY_ELLIPSIZING_LABEL_H */
diff --git a/lib/widgets/ephy-location-entry.c b/lib/widgets/ephy-location-entry.c
new file mode 100644
index 000000000..b0669f89f
--- /dev/null
+++ b/lib/widgets/ephy-location-entry.c
@@ -0,0 +1,700 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#include "ephy-location-entry.h"
+#include "ephy-autocompletion-window.h"
+#include "ephy-marshal.h"
+#include "ephy-gobject-misc.h"
+#include "eel-gconf-extensions.h"
+#include "ephy-prefs.h"
+
+#include <gtk/gtkentry.h>
+#include <gtk/gtkwindow.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkmain.h>
+#include <libgnomeui/gnome-entry.h>
+#include <string.h>
+
+//#define DEBUG_MSG(x) g_print x
+#define DEBUG_MSG(x)
+
+#define NOT_IMPLEMENTED g_warning ("not implemented: " G_STRLOC);
+
+/**
+ * Private data
+ */
+struct _EphyLocationEntryPrivate {
+ GtkWidget *combo;
+ GtkWidget *entry;
+ gchar *before_completion;
+ EphyAutocompletion *autocompletion;
+ EphyAutocompletionWindow *autocompletion_window;
+ gboolean autocompletion_window_visible;
+ gint autocompletion_timeout;
+ gint show_alternatives_timeout;
+ gboolean block_set_autocompletion_key;
+
+ gchar *autocompletion_key;
+ gchar *last_completion;
+ char *last_action_target;
+};
+
+#define AUTOCOMPLETION_DELAY 10
+#define SHOW_ALTERNATIVES_DELAY 100
+
+/**
+ * Private functions, only availble from this file
+ */
+static void ephy_location_entry_class_init (EphyLocationEntryClass *klass);
+static void ephy_location_entry_init (EphyLocationEntry *w);
+static void ephy_location_entry_finalize_impl (GObject *o);
+static void ephy_location_entry_build (EphyLocationEntry *w);
+static gboolean ephy_location_entry_key_press_event_cb (GtkWidget *entry, GdkEventKey *event,
+ EphyLocationEntry *w);
+static void ephy_location_entry_activate_cb (GtkEntry *entry,
+ EphyLocationEntry *w);
+static void ephy_location_entry_autocompletion_sources_changed_cb (EphyAutocompletion *aw,
+ EphyLocationEntry *w);
+static gint ephy_location_entry_autocompletion_to (EphyLocationEntry *w);
+static gint ephy_location_entry_autocompletion_show_alternatives_to (EphyLocationEntry *w);
+static void ephy_location_entry_autocompletion_window_url_activated_cb
+/***/ (EphyAutocompletionWindow *aw,
+ const gchar *target,
+ int action,
+ EphyLocationEntry *w);
+static void ephy_location_entry_list_event_after_cb (GtkWidget *list,
+ GdkEvent *event,
+ EphyLocationEntry *e);
+static void ephy_location_entry_editable_changed_cb (GtkEditable *editable,
+ EphyLocationEntry *e);
+static void ephy_location_entry_set_autocompletion_key (EphyLocationEntry *e);
+static void ephy_location_entry_autocompletion_show_alternatives (EphyLocationEntry *w);
+static void ephy_location_entry_autocompletion_hide_alternatives (EphyLocationEntry *w);
+static void ephy_location_entry_autocompletion_window_hidden_cb (EphyAutocompletionWindow *aw,
+ EphyLocationEntry *w);
+
+
+
+
+static gpointer gtk_hbox_class;
+
+/**
+ * Signals enums and ids
+ */
+enum EphyLocationEntrySignalsEnum {
+ ACTIVATED,
+ LAST_SIGNAL
+};
+static gint EphyLocationEntrySignals[LAST_SIGNAL];
+
+/**
+ * EphyLocationEntry object
+ */
+
+MAKE_GET_TYPE (ephy_location_entry, "EphyLocationEntry", EphyLocationEntry,
+ ephy_location_entry_class_init,
+ ephy_location_entry_init, GTK_TYPE_HBOX);
+
+static void
+ephy_location_entry_class_init (EphyLocationEntryClass *klass)
+{
+ G_OBJECT_CLASS (klass)->finalize = ephy_location_entry_finalize_impl;
+ gtk_hbox_class = g_type_class_peek_parent (klass);
+
+ EphyLocationEntrySignals[ACTIVATED] = g_signal_new (
+ "activated", G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
+ G_STRUCT_OFFSET (EphyLocationEntryClass, activated),
+ NULL, NULL,
+ ephy_marshal_VOID__STRING_STRING,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+}
+
+static void
+ephy_location_entry_init (EphyLocationEntry *w)
+{
+ EphyLocationEntryPrivate *p = g_new0 (EphyLocationEntryPrivate, 1);
+ w->priv = p;
+ p->last_action_target = NULL;
+
+ ephy_location_entry_build (w);
+}
+
+static void
+ephy_location_entry_finalize_impl (GObject *o)
+{
+ EphyLocationEntry *w = EPHY_LOCATION_ENTRY (o);
+ EphyLocationEntryPrivate *p = w->priv;
+
+ if (p->autocompletion)
+ {
+ g_signal_handlers_disconnect_matched (p->autocompletion, G_SIGNAL_MATCH_DATA, 0, 0,
+ NULL, NULL, w);
+
+ g_signal_handlers_disconnect_matched (p->autocompletion_window, G_SIGNAL_MATCH_DATA, 0, 0,
+ NULL, NULL, w);
+
+ g_object_unref (G_OBJECT (p->autocompletion));
+ g_object_unref (G_OBJECT (p->autocompletion_window));
+ }
+
+ DEBUG_MSG (("EphyLocationEntry finalized\n"));
+
+ g_free (p);
+ G_OBJECT_CLASS (gtk_hbox_class)->finalize (o);
+}
+
+EphyLocationEntry *
+ephy_location_entry_new (void)
+{
+ return EPHY_LOCATION_ENTRY (g_object_new (EPHY_TYPE_LOCATION_ENTRY, NULL));
+}
+
+static void
+ephy_location_entry_build (EphyLocationEntry *w)
+{
+ EphyLocationEntryPrivate *p = w->priv;
+ GtkWidget *list;
+
+ p->combo = gnome_entry_new ("ephy-url-history");
+ p->entry = GTK_COMBO (p->combo)->entry;
+ gtk_widget_show (p->combo);
+ gtk_box_pack_start (GTK_BOX (w), p->combo, TRUE, TRUE, 0);
+
+ g_signal_connect (p->entry, "key-press-event",
+ G_CALLBACK (ephy_location_entry_key_press_event_cb), w);
+
+ g_signal_connect (p->entry, "activate",
+ G_CALLBACK (ephy_location_entry_activate_cb), w);
+
+ g_signal_connect (p->entry, "changed",
+ G_CALLBACK (ephy_location_entry_editable_changed_cb), w);
+
+ list = GTK_COMBO (p->combo)->list;
+
+ g_signal_connect_after (list, "event-after",
+ G_CALLBACK (ephy_location_entry_list_event_after_cb), w);
+
+}
+
+static gboolean
+ephy_location_ignore_prefix (EphyLocationEntry *w)
+{
+ char *text;
+ int text_len;
+ int i, k;
+ gboolean result = FALSE;
+ static const gchar *prefixes[] = {
+ EPHY_AUTOCOMPLETION_USUAL_WEB_PREFIXES,
+ NULL
+ };
+
+ text = ephy_location_entry_get_location (w);
+ text_len = g_utf8_strlen (text, -1);
+
+ for (i = 0; prefixes[i] != NULL; i++)
+ {
+ const char *prefix = prefixes[i];
+
+ for (k = 0; k < g_utf8_strlen (prefix, -1); k++)
+ {
+ if (text_len == (k + 1) &&
+ (strncmp (text, prefix, k + 1) == 0))
+ {
+ result = TRUE;
+ }
+ }
+ }
+
+ g_free (text);
+
+ return result;
+}
+
+static gint
+ephy_location_entry_autocompletion_show_alternatives_to (EphyLocationEntry *w)
+{
+ EphyLocationEntryPrivate *p = w->priv;
+
+ if (ephy_location_ignore_prefix (w)) return FALSE;
+
+ if (p->autocompletion)
+ {
+ DEBUG_MSG (("+ephy_location_entry_autocompletion_show_alternatives_to\n"));
+ ephy_location_entry_set_autocompletion_key (w);
+ ephy_location_entry_autocompletion_show_alternatives (w);
+ }
+ p->show_alternatives_timeout = 0;
+ return FALSE;
+}
+
+static void
+ephy_location_entry_autocompletion_hide_alternatives (EphyLocationEntry *w)
+{
+ EphyLocationEntryPrivate *p = w->priv;
+ if (p->autocompletion_window)
+ {
+ ephy_autocompletion_window_hide (p->autocompletion_window);
+ p->autocompletion_window_visible = FALSE;
+ }
+}
+
+static void
+ephy_location_entry_autocompletion_show_alternatives (EphyLocationEntry *w)
+{
+ EphyLocationEntryPrivate *p = w->priv;
+ if (p->autocompletion_window)
+ {
+ ephy_autocompletion_window_show (p->autocompletion_window);
+ p->autocompletion_window_visible = TRUE;
+ }
+}
+
+static void
+ephy_location_entry_autocompletion_unselect_alternatives (EphyLocationEntry *w)
+{
+ EphyLocationEntryPrivate *p = w->priv;
+ if (p->autocompletion_window)
+ {
+ ephy_autocompletion_window_unselect (p->autocompletion_window);
+ }
+}
+
+static gint
+ephy_location_entry_autocompletion_to (EphyLocationEntry *w)
+{
+ EphyLocationEntryPrivate *p = w->priv;
+ gchar *text;
+ gchar *common_prefix;
+
+ DEBUG_MSG (("in ephy_location_entry_autocompletion_to\n"));
+
+ ephy_location_entry_set_autocompletion_key (w);
+
+ {
+ GtkEditable *editable = GTK_EDITABLE (p->entry);
+ gint sstart, send;
+ gint pos = gtk_editable_get_position (editable);
+ const gchar *text = gtk_entry_get_text (GTK_ENTRY (p->entry));
+ gint text_len = strlen (text);
+ gtk_editable_get_selection_bounds (editable, &sstart, &send);
+
+ if (pos != text_len
+ || send != text_len)
+ {
+ /* the user is editing the entry, don't mess it */
+ DEBUG_MSG (("The user seems editing the text: pos = %d, strlen (text) = %d, sstart = %d, send = %d\n",
+ pos, strlen (text), sstart, send));
+ p->autocompletion_timeout = 0;
+ return FALSE;
+ }
+ }
+
+ common_prefix = ephy_autocompletion_get_common_prefix (p->autocompletion);
+
+ DEBUG_MSG (("common_prefix: %s\n", common_prefix));
+
+ if (common_prefix && (!p->before_completion || p->before_completion[0] == '\0'))
+ {
+ text = ephy_location_entry_get_location (w);
+ g_free (p->before_completion);
+ p->before_completion = text;
+ }
+
+ if (common_prefix)
+ {
+ /* check original length */
+ guint text_len = strlen (p->autocompletion_key);
+
+ p->block_set_autocompletion_key = TRUE;
+
+ /* set entry to completed text */
+ gtk_entry_set_text (GTK_ENTRY (p->entry), common_prefix);
+
+ /* move selection appropriately */
+ gtk_editable_select_region (GTK_EDITABLE (p->entry), text_len, -1);
+
+ p->block_set_autocompletion_key = FALSE;
+
+ g_free (p->last_completion);
+ p->last_completion = common_prefix;
+ }
+
+ p->autocompletion_timeout = 0;
+ return FALSE;
+}
+
+/* this is from the old location entry, need to do the autocompletion before implementing this */
+static gboolean
+ephy_location_entry_key_press_event_cb (GtkWidget *entry, GdkEventKey *event, EphyLocationEntry *w)
+{
+ EphyLocationEntryPrivate *p = w->priv;
+ static gboolean suggest = FALSE;
+ guint keyval = event->keyval;
+
+ if (p->autocompletion_timeout != 0)
+ {
+ gtk_timeout_remove (p->autocompletion_timeout);
+ p->autocompletion_timeout = 0;
+ }
+
+ if (p->show_alternatives_timeout != 0)
+ {
+ gtk_timeout_remove (p->show_alternatives_timeout);
+ p->show_alternatives_timeout = 0;
+ }
+
+ /* only suggest heuristic completions if TAB is hit twice */
+ if (event->keyval != GDK_Tab)
+ {
+ suggest = FALSE;
+ }
+
+ if (((event->state & GDK_Control_L || event->state & GDK_Control_R) &&
+ (keyval == GDK_a || keyval == GDK_b || keyval == GDK_c ||
+ keyval == GDK_d || keyval == GDK_e || keyval == GDK_f ||
+ keyval == GDK_h || keyval == GDK_k || keyval == GDK_u ||
+ keyval == GDK_v || keyval == GDK_w || keyval == GDK_x)) ||
+ (event->state == 0 && event->keyval == GDK_BackSpace))
+ {
+ ephy_location_entry_autocompletion_hide_alternatives (w);
+ return FALSE;
+ }
+
+ /* don't grab alt combos, thus you can still access the menus. */
+ if (event->state & GDK_MOD1_MASK)
+ {
+ ephy_location_entry_autocompletion_hide_alternatives (w);
+ return FALSE;
+ }
+
+ /* make sure the end key works at all times */
+ if ((!((event->state & GDK_SHIFT_MASK) ||
+ (event->state & GDK_CONTROL_MASK) ||
+ (event->state & GDK_MOD1_MASK))
+ && (event->keyval == GDK_End)))
+ {
+ ephy_location_entry_autocompletion_hide_alternatives (w);
+ gtk_editable_select_region (GTK_EDITABLE (p->entry), 0, 0);
+ gtk_editable_set_position (GTK_EDITABLE (p->entry), -1);
+ ephy_location_entry_autocompletion_unselect_alternatives (w);
+ return TRUE;
+ }
+
+ switch (event->keyval)
+ {
+ case GDK_Left:
+ case GDK_Right:
+ ephy_location_entry_autocompletion_hide_alternatives (w);
+ return FALSE;
+ case GDK_Up:
+ case GDK_Down:
+ case GDK_Page_Up:
+ case GDK_Page_Down:
+ ephy_location_entry_autocompletion_hide_alternatives (w);
+ //ephy_embed_grab_focus (window->active_embed);
+ return FALSE;
+ case GDK_Tab:
+ {
+ gchar *common_prefix = NULL;
+ gchar *text;
+
+ ephy_location_entry_set_autocompletion_key (w);
+
+ gtk_editable_delete_selection (GTK_EDITABLE (p->entry));
+ text = ephy_location_entry_get_location (w);
+ ephy_location_entry_autocompletion_unselect_alternatives (w);
+
+ if (p->autocompletion)
+ {
+ common_prefix = ephy_autocompletion_get_common_prefix (p->autocompletion);
+ }
+ suggest = FALSE;
+ if (common_prefix)
+ {
+ if (!p->before_completion)
+ {
+ p->before_completion = g_strdup (text);
+ }
+
+ p->block_set_autocompletion_key = TRUE;
+
+ gtk_entry_set_text (GTK_ENTRY (p->entry), common_prefix);
+ gtk_editable_set_position (GTK_EDITABLE (p->entry), -1);
+
+ p->block_set_autocompletion_key = FALSE;
+
+ ephy_location_entry_autocompletion_show_alternatives (w);
+ if (!strcmp (common_prefix, text))
+ {
+ /* really suggest something the next time */
+ suggest = TRUE;
+ }
+ g_free (common_prefix);
+ }
+ else
+ {
+ ephy_location_entry_autocompletion_hide_alternatives (w);
+ }
+ g_free (text);
+ return TRUE;
+ }
+ case GDK_Escape:
+ ephy_location_entry_autocompletion_hide_alternatives (w);
+ if (p->before_completion)
+ {
+ ephy_location_entry_set_location (w, p->before_completion);
+ g_free (p->before_completion);
+ p->before_completion = NULL;
+ gtk_editable_set_position (GTK_EDITABLE (p->entry), -1);
+ return TRUE;
+ }
+ break;
+ default:
+ ephy_location_entry_autocompletion_unselect_alternatives (w);
+ if ((event->string[0] > 32) && (event->string[0] < 126))
+ {
+ p->show_alternatives_timeout = g_timeout_add
+ (SHOW_ALTERNATIVES_DELAY,
+ (GSourceFunc) ephy_location_entry_autocompletion_show_alternatives_to, w);
+ }
+ break;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+ephy_location_entry_content_is_text (const char *content)
+{
+ return ((g_strrstr (content, ".") == NULL) &&
+ (g_strrstr (content, "/") == NULL));
+}
+
+static void
+ephy_location_entry_activate_cb (GtkEntry *entry, EphyLocationEntry *w)
+{
+ char *content;
+ char *target = NULL;
+
+ content = gtk_editable_get_chars (GTK_EDITABLE(entry), 0, -1);
+ if (ephy_location_entry_content_is_text (content))
+ {
+ target = w->priv->last_action_target;
+ }
+
+ ephy_location_entry_autocompletion_hide_alternatives (w);
+
+ DEBUG_MSG (("In ephy_location_entry_activate_cb, activating %s\n", content));
+
+ g_signal_emit (w, EphyLocationEntrySignals[ACTIVATED], 0, target, content);
+ g_free (content);
+}
+
+static void
+ephy_location_entry_autocompletion_sources_changed_cb (EphyAutocompletion *aw,
+ EphyLocationEntry *w)
+{
+ EphyLocationEntryPrivate *p = w->priv;
+
+ DEBUG_MSG (("in ephy_location_entry_autocompletion_sources_changed_cb\n"));
+
+ if (p->autocompletion_timeout == 0
+ && p->last_completion
+ && !strcmp (p->last_completion, gtk_entry_get_text (GTK_ENTRY (p->entry))))
+ {
+ p->autocompletion_timeout = gtk_timeout_add
+ (AUTOCOMPLETION_DELAY,
+ (GSourceFunc) ephy_location_entry_autocompletion_to, w);
+ }
+
+ if (p->show_alternatives_timeout == 0
+ && p->autocompletion_window_visible)
+ {
+ p->show_alternatives_timeout = gtk_timeout_add
+ (SHOW_ALTERNATIVES_DELAY,
+ (GSourceFunc) ephy_location_entry_autocompletion_show_alternatives_to, w);
+ }
+}
+
+void
+ephy_location_entry_set_location (EphyLocationEntry *w,
+ const gchar *new_location)
+{
+ EphyLocationEntryPrivate *p = w->priv;
+ int pos;
+ gtk_editable_delete_text (GTK_EDITABLE (p->entry), 0, -1);
+ gtk_editable_insert_text (GTK_EDITABLE (p->entry), new_location, g_utf8_strlen (new_location, -1),
+ &pos);
+}
+
+gchar *
+ephy_location_entry_get_location (EphyLocationEntry *w)
+{
+ char *location = gtk_editable_get_chars (GTK_EDITABLE (w->priv->entry), 0, -1);
+ return location;
+}
+
+void
+ephy_location_entry_set_autocompletion (EphyLocationEntry *w,
+ EphyAutocompletion *ac)
+{
+ EphyLocationEntryPrivate *p = w->priv;
+ if (p->autocompletion)
+ {
+ g_signal_handlers_disconnect_matched (p->autocompletion, G_SIGNAL_MATCH_DATA, 0, 0,
+ NULL, NULL, w);
+
+ g_signal_handlers_disconnect_matched (p->autocompletion_window, G_SIGNAL_MATCH_DATA, 0, 0,
+ NULL, NULL, w);
+
+ g_object_unref (G_OBJECT (p->autocompletion));
+ g_object_unref (p->autocompletion_window);
+ }
+ p->autocompletion = ac;
+ if (p->autocompletion)
+ {
+ g_object_ref (G_OBJECT (p->autocompletion));
+ p->autocompletion_window = ephy_autocompletion_window_new (p->autocompletion,
+ p->entry);
+ g_signal_connect (p->autocompletion_window, "activated",
+ G_CALLBACK (ephy_location_entry_autocompletion_window_url_activated_cb),
+ w);
+
+ g_signal_connect (p->autocompletion_window, "hidden",
+ G_CALLBACK (ephy_location_entry_autocompletion_window_hidden_cb),
+ w);
+
+ g_signal_connect (p->autocompletion, "sources-changed",
+ G_CALLBACK (ephy_location_entry_autocompletion_sources_changed_cb),
+ w);
+
+ ephy_location_entry_set_autocompletion_key (w);
+ }
+
+}
+
+static void
+ephy_location_entry_autocompletion_window_url_activated_cb (EphyAutocompletionWindow *aw,
+ const char *target,
+ int action,
+ EphyLocationEntry *w)
+{
+ char *content;
+
+ if (action)
+ {
+ if (w->priv->last_action_target)
+ g_free (w->priv->last_action_target);
+ w->priv->last_action_target = g_strdup (target);
+ }
+ else
+ {
+ ephy_location_entry_set_location (w, target);
+ }
+
+ content = gtk_editable_get_chars (GTK_EDITABLE(w->priv->entry), 0, -1);
+
+ DEBUG_MSG (("In location_entry_autocompletion_window_url_activated_cb, going to %s\n", content));
+
+ ephy_location_entry_autocompletion_hide_alternatives (w);
+
+ g_signal_emit (w, EphyLocationEntrySignals[ACTIVATED], 0,
+ action ? content : NULL, target);
+
+ g_free (content);
+}
+
+static void
+ephy_location_entry_autocompletion_window_hidden_cb (EphyAutocompletionWindow *aw,
+ EphyLocationEntry *w)
+{
+ EphyLocationEntryPrivate *p = w->priv;
+
+ DEBUG_MSG (("In location_entry_autocompletion_window_hidden_cb\n"));
+
+ p->autocompletion_window_visible = FALSE;
+
+ if (p->show_alternatives_timeout)
+ {
+ g_source_remove (p->show_alternatives_timeout);
+ p->show_alternatives_timeout = 0;
+ }
+
+ if (p->autocompletion_timeout)
+ {
+ g_source_remove (p->autocompletion_timeout);
+ p->autocompletion_timeout = 0;
+ }
+}
+
+void
+ephy_location_entry_activate (EphyLocationEntry *w)
+{
+ GtkWidget *toplevel;
+
+ toplevel = gtk_widget_get_toplevel (w->priv->entry);
+
+ gtk_editable_select_region (GTK_EDITABLE(w->priv->entry),
+ 0, -1);
+ gtk_window_set_focus (GTK_WINDOW(toplevel),
+ w->priv->entry);
+}
+
+
+static void
+ephy_location_entry_list_event_after_cb (GtkWidget *list,
+ GdkEvent *event,
+ EphyLocationEntry *e)
+{
+ if (event->type == GDK_BUTTON_PRESS
+ && ((GdkEventButton *) event)->button == 1)
+ {
+ gchar *url = ephy_location_entry_get_location (e);
+ g_signal_emit
+ (e, EphyLocationEntrySignals[ACTIVATED], 0, url);
+ g_free (url);
+ }
+}
+
+static void
+ephy_location_entry_editable_changed_cb (GtkEditable *editable, EphyLocationEntry *e)
+{
+ ephy_location_entry_set_autocompletion_key (e);
+}
+
+static void
+ephy_location_entry_set_autocompletion_key (EphyLocationEntry *e)
+{
+ EphyLocationEntryPrivate *p = e->priv;
+ if (p->autocompletion && !p->block_set_autocompletion_key)
+ {
+ GtkEditable *editable = GTK_EDITABLE (p->entry);
+ gint sstart, send;
+ gchar *text;
+ gtk_editable_get_selection_bounds (editable, &sstart, &send);
+ text = gtk_editable_get_chars (editable, 0, sstart);
+ ephy_autocompletion_set_key (p->autocompletion, text);
+ g_free (p->autocompletion_key);
+ p->autocompletion_key = text;
+ }
+}
+
diff --git a/lib/widgets/ephy-location-entry.h b/lib/widgets/ephy-location-entry.h
new file mode 100644
index 000000000..eebacc770
--- /dev/null
+++ b/lib/widgets/ephy-location-entry.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifndef EPHY_LOCATION_ENTRY_H
+#define EPHY_LOCATION_ENTRY_H
+
+#include <glib-object.h>
+#include <gtk/gtkhbox.h>
+
+#include "ephy-autocompletion.h"
+
+/* object forward declarations */
+
+typedef struct _EphyLocationEntry EphyLocationEntry;
+typedef struct _EphyLocationEntryClass EphyLocationEntryClass;
+typedef struct _EphyLocationEntryPrivate EphyLocationEntryPrivate;
+
+/**
+ * EphyFolderTbWidget object
+ */
+
+#define EPHY_TYPE_LOCATION_ENTRY (ephy_location_entry_get_type())
+#define EPHY_LOCATION_ENTRY(object) (G_TYPE_CHECK_INSTANCE_CAST((object), EPHY_TYPE_LOCATION_ENTRY,\
+ EphyLocationEntry))
+#define EPHY_LOCATION_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EPHY_TYPE_LOCATION_ENTRY,\
+ EphyLocationEntryClass))
+#define EPHY_IS_LOCATION_ENTRY(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), EPHY_TYPE_LOCATION_ENTRY))
+#define EPHY_IS_LOCATION_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EPHY_TYPE_LOCATION_ENTRY))
+#define EPHY_LOCATION_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPHY_TYPE_LOCATION_ENTRY,\
+ EphyLocationEntryClass))
+
+struct _EphyLocationEntryClass
+{
+ GtkHBoxClass parent_class;
+
+ /* signals */
+ void (*activated) (EphyLocationEntry *w,
+ const char *content,
+ const char *target);
+};
+
+/* Remember: fields are public read-only */
+struct _EphyLocationEntry
+{
+ GtkHBox parent_object;
+
+ EphyLocationEntryPrivate *priv;
+};
+
+GType ephy_location_entry_get_type (void);
+EphyLocationEntry * ephy_location_entry_new (void);
+void ephy_location_entry_set_location (EphyLocationEntry *w,
+ const gchar *new_location);
+gchar * ephy_location_entry_get_location (EphyLocationEntry *w);
+void ephy_location_entry_set_autocompletion (EphyLocationEntry *w,
+ EphyAutocompletion *ac);
+void ephy_location_entry_activate (EphyLocationEntry *w);
+
+#endif
diff --git a/lib/widgets/ephy-notebook.c b/lib/widgets/ephy-notebook.c
new file mode 100644
index 000000000..c03dc878a
--- /dev/null
+++ b/lib/widgets/ephy-notebook.c
@@ -0,0 +1,843 @@
+/*
+ * Copyright (C) 2002 Christophe Fergeau
+ *
+ * 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.
+ */
+
+#include "ephy-notebook.h"
+#include "eel-gconf-extensions.h"
+#include "ephy-prefs.h"
+#include "ephy-marshal.h"
+
+#include <gtk/gtk.h>
+#include <glib-object.h>
+#include <libgnome/gnome-i18n.h>
+
+#define AFTER_ALL_TABS -1
+#define NOT_IN_APP_WINDOWS -2
+#define TAB_MIN_SIZE 60
+#define TAB_NB_MAX 8
+
+struct EphyNotebookPrivate
+{
+ GList *focused_pages;
+ GList *opened_tabs;
+
+ /* Used during tab drag'n'drop */
+ gulong motion_notify_handler_id;
+ gint x_start, y_start;
+ gboolean drag_in_progress;
+ EphyNotebook *src_notebook;
+ gint src_page;
+};
+
+/* GObject boilerplate code */
+static void ephy_notebook_init (EphyNotebook *notebook);
+static void ephy_notebook_class_init (EphyNotebookClass *klass);
+static void ephy_notebook_finalize (GObject *object);
+
+/* Local variables */
+static GdkCursor *cursor = NULL;
+static GList *notebooks = NULL;
+
+
+/* Local functions */
+static void drag_start (EphyNotebook *notebook,
+ EphyNotebook *src_notebook,
+ gint src_page);
+static void drag_stop (EphyNotebook *notebook);
+
+static gboolean motion_notify_cb (EphyNotebook *notebook,
+ GdkEventMotion *event,
+ gpointer data);
+
+/* Signals */
+enum
+{
+ TAB_DROPPED,
+ TAB_DETACHED,
+ LAST_SIGNAL
+};
+
+static guint ephy_notebook_signals[LAST_SIGNAL] = { 0 };
+
+GType
+ephy_notebook_get_type (void)
+{
+ static GType ephy_notebook_type = 0;
+
+ if (ephy_notebook_type == 0)
+ {
+ static const GTypeInfo our_info =
+ {
+ sizeof (EphyNotebookClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) ephy_notebook_class_init,
+ NULL,
+ NULL, /* class_data */
+ sizeof (EphyNotebook),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) ephy_notebook_init
+ };
+
+ ephy_notebook_type = g_type_register_static (GTK_TYPE_NOTEBOOK,
+ "EphyNotebook",
+ &our_info, 0);
+ }
+
+ return ephy_notebook_type;
+}
+
+static void
+ephy_notebook_class_init (EphyNotebookClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = ephy_notebook_finalize;
+
+ /* init signals */
+ ephy_notebook_signals[TAB_DROPPED] =
+ g_signal_new ("tab_dropped",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EphyNotebookClass,
+ tab_dropped),
+ NULL, NULL,
+ ephy_marshal_VOID__OBJECT_OBJECT_INT,
+ G_TYPE_NONE,
+ 3,
+ GTK_TYPE_WIDGET,
+ EPHY_NOTEBOOK_TYPE,
+ G_TYPE_INT);
+ ephy_notebook_signals[TAB_DETACHED] =
+ g_signal_new ("tab_detached",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EphyNotebookClass,
+ tab_detached),
+ NULL, NULL,
+ ephy_marshal_VOID__INT_INT_INT,
+ G_TYPE_NONE,
+ 3,
+ G_TYPE_INT,
+ G_TYPE_INT,
+ G_TYPE_INT);
+
+}
+
+static gboolean
+is_in_notebook_window (EphyNotebook *notebook,
+ gint abs_x, gint abs_y)
+{
+ gint x, y;
+ gint rel_x, rel_y;
+ gint width, height;
+ GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET(notebook));
+ GdkWindow *window = GTK_WIDGET(toplevel)->window;
+
+ gdk_window_get_origin (window, &x, &y);
+ rel_x = abs_x - x;
+ rel_y = abs_y - y;
+
+ x = GTK_WIDGET(notebook)->allocation.x;
+ y = GTK_WIDGET(notebook)->allocation.y;
+ height = GTK_WIDGET(notebook)->allocation.height;
+ width = GTK_WIDGET(notebook)->allocation.width;
+ return ((rel_x>=x) && (rel_y>=y) && (rel_x<=x+width) && (rel_y<=y+height));
+}
+
+static EphyNotebook *
+find_notebook_at_pointer (gint abs_x, gint abs_y)
+{
+ GList *l;
+ gint x, y;
+ GdkWindow *win_at_pointer = gdk_window_at_pointer (&x, &y);
+ GdkWindow *parent_at_pointer = NULL;
+
+ if (win_at_pointer == NULL)
+ {
+ /* We are outside all windows containing a notebook */
+ return NULL;
+ }
+
+ gdk_window_get_toplevel (win_at_pointer);
+ /* When we are in the notebook event window, win_at_pointer will be
+ this event window, and the toplevel window we are interested in
+ will be its parent
+ */
+ parent_at_pointer = gdk_window_get_parent (win_at_pointer);
+
+ for (l = notebooks; l != NULL; l = l->next)
+ {
+ EphyNotebook *nb = EPHY_NOTEBOOK (l->data);
+ GdkWindow *win = GTK_WIDGET (nb)->window;
+
+ win = gdk_window_get_toplevel (win);
+ if (((win == win_at_pointer) || (win == parent_at_pointer))
+ && is_in_notebook_window (nb, abs_x, abs_y))
+ {
+ return nb;
+ }
+ }
+ return NULL;
+}
+
+
+static gint
+find_tab_num_at_pos (EphyNotebook *notebook, gint abs_x, gint abs_y)
+{
+ GtkPositionType tab_pos;
+ int page_num = 0;
+ GtkNotebook *nb = GTK_NOTEBOOK (notebook);
+ GtkWidget *page;
+
+ tab_pos = gtk_notebook_get_tab_pos (GTK_NOTEBOOK (notebook));
+
+ if (GTK_NOTEBOOK (notebook)->first_tab == NULL)
+ {
+ return AFTER_ALL_TABS;
+ }
+
+ g_assert (is_in_notebook_window(notebook, abs_x, abs_y));
+
+ while ((page = gtk_notebook_get_nth_page (nb, page_num)))
+ {
+ GtkWidget *tab;
+ gint max_x, max_y;
+ gint x_root, y_root;
+
+ tab = gtk_notebook_get_tab_label (nb, page);
+ g_return_val_if_fail (tab != NULL, -1);
+
+ if (!GTK_WIDGET_MAPPED (GTK_WIDGET (tab)))
+ {
+ page_num++;
+ continue;
+ }
+
+ gdk_window_get_origin (GDK_WINDOW (tab->window),
+ &x_root, &y_root);
+
+ max_x = x_root + tab->allocation.x + tab->allocation.width;
+ max_y = y_root + tab->allocation.y + tab->allocation.height;
+
+ if (((tab_pos == GTK_POS_TOP)
+ || (tab_pos == GTK_POS_BOTTOM))
+ &&(abs_x<=max_x))
+ {
+ return page_num;
+ }
+ else if (((tab_pos == GTK_POS_LEFT)
+ || (tab_pos == GTK_POS_RIGHT))
+ && (abs_y<=max_y))
+ {
+ return page_num;
+ }
+
+ page_num++;
+ }
+ return AFTER_ALL_TABS;
+}
+
+
+static gint find_notebook_and_tab_at_pos (gint abs_x, gint abs_y,
+ EphyNotebook **notebook,
+ gint *page_num)
+{
+ *notebook = find_notebook_at_pointer (abs_x, abs_y);
+ if (*notebook == NULL)
+ {
+ return NOT_IN_APP_WINDOWS;
+ }
+ *page_num = find_tab_num_at_pos (*notebook, abs_x, abs_y);
+
+ if (*page_num < 0)
+ {
+ return *page_num;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+static void
+tab_label_set_size (GtkWidget *window, GtkWidget *label)
+{
+ int label_width;
+
+ label_width = window->allocation.width/TAB_NB_MAX;
+
+ if (label_width < TAB_MIN_SIZE) label_width = TAB_MIN_SIZE;
+
+ gtk_widget_set_size_request (label, label_width, -1);
+}
+
+static GtkWidget *
+tab_get_label (EphyNotebook *nb, GtkWidget *child)
+{
+ GtkWidget *hbox, *label;
+
+ hbox = gtk_notebook_get_tab_label (GTK_NOTEBOOK (nb),
+ child);
+ label = g_object_get_data (G_OBJECT (hbox), "label");
+
+ return label;
+}
+
+static void
+tab_label_size_request_cb (GtkWidget *window,
+ GtkRequisition *requisition,
+ GtkWidget *child)
+{
+ GtkWidget *hbox;
+ GtkWidget *nb;
+
+ nb = child->parent;
+
+ hbox = gtk_notebook_get_tab_label (GTK_NOTEBOOK (nb),
+ child);
+ tab_label_set_size (window, hbox);
+}
+
+
+void
+ephy_notebook_move_page (EphyNotebook *src, EphyNotebook *dest,
+ GtkWidget *src_page, gint dest_page)
+{
+ GtkWidget *tab_label;
+
+ tab_label = gtk_notebook_get_tab_label (GTK_NOTEBOOK (src), src_page);
+
+ /* We don't want gtk to destroy tab and src_page behind our back */
+ g_object_ref (G_OBJECT (src_page));
+ g_object_ref (G_OBJECT (tab_label));
+ ephy_notebook_remove_page (EPHY_NOTEBOOK (src), src_page);
+ ephy_notebook_insert_page (EPHY_NOTEBOOK (dest), src_page,
+ dest_page, TRUE);
+ gtk_notebook_set_tab_label (GTK_NOTEBOOK (dest), src_page, tab_label);
+ g_object_unref (G_OBJECT (src_page));
+ g_object_unref (G_OBJECT (tab_label));
+}
+
+
+
+static void
+move_tab_to_another_notebook(EphyNotebook *src,
+ EphyNotebook *dest, gint dest_page)
+{
+ GtkWidget *child;
+ gint cur_page;
+
+ /* This is getting tricky, the tab was dragged in a notebook
+ * in another window of the same app, we move the tab
+ * to that new notebook, and let this notebook handle the
+ * drag
+ */
+ g_assert (dest != NULL);
+ g_assert (dest != src);
+
+ /* Move the widgets (tab label and tab content) to the new
+ * notebook
+ */
+ cur_page = gtk_notebook_get_current_page (GTK_NOTEBOOK (src));
+ child = gtk_notebook_get_nth_page (GTK_NOTEBOOK (src), cur_page);
+ ephy_notebook_move_page (src, dest, child, dest_page);
+
+ /* "Give" drag handling to the new notebook */
+ drag_start (dest, src->priv->src_notebook, src->priv->src_page);
+ drag_stop (src);
+ gtk_grab_remove (GTK_WIDGET (src));
+
+ dest->priv->motion_notify_handler_id =
+ g_signal_connect (G_OBJECT (dest),
+ "motion-notify-event",
+ G_CALLBACK (motion_notify_cb),
+ NULL);
+}
+
+
+static void
+move_tab (EphyNotebook *notebook, gint dest_page_num)
+{
+ gint cur_page_num;
+
+ cur_page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook));
+
+ if (dest_page_num != cur_page_num)
+ {
+ GtkWidget *cur_page;
+ cur_page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook),
+ cur_page_num);
+ gtk_notebook_reorder_child (GTK_NOTEBOOK (notebook), cur_page,
+ dest_page_num);
+
+ /* Reset the list of newly opened tabs when moving tabs. */
+ g_list_free (notebook->priv->opened_tabs);
+ notebook->priv->opened_tabs = NULL;
+ }
+}
+
+static void
+drag_start (EphyNotebook *notebook,
+ EphyNotebook *src_notebook,
+ gint src_page)
+{
+ notebook->priv->drag_in_progress = TRUE;
+ notebook->priv->src_notebook = src_notebook;
+ notebook->priv->src_page = src_page;
+
+ /* get a new cursor, if necessary */
+ if (!cursor) cursor = gdk_cursor_new (GDK_FLEUR);
+
+ /* grab the pointer */
+ gtk_grab_add (GTK_WIDGET (notebook));
+ if (!gdk_pointer_is_grabbed ()) {
+ gdk_pointer_grab (GDK_WINDOW(GTK_WIDGET (notebook)->window),
+ FALSE,
+ GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
+ NULL, cursor, GDK_CURRENT_TIME);
+ }
+}
+
+static void
+drag_stop (EphyNotebook *notebook)
+{
+ notebook->priv->drag_in_progress = FALSE;
+ notebook->priv->src_notebook = NULL;
+ notebook->priv->src_page = -1;
+ if (notebook->priv->motion_notify_handler_id != 0)
+ {
+ g_signal_handler_disconnect (G_OBJECT (notebook),
+ notebook->priv->motion_notify_handler_id);
+ notebook->priv->motion_notify_handler_id = 0;
+ }
+}
+
+/* Callbacks */
+static gboolean
+button_release_cb (EphyNotebook *notebook, GdkEventButton *event,
+ gpointer data)
+{
+ if (notebook->priv->drag_in_progress)
+ {
+ gint cur_page_num;
+ GtkWidget *cur_page;
+
+ cur_page_num =
+ gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook));
+ cur_page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook),
+ cur_page_num);
+
+ if (!is_in_notebook_window (notebook, event->x_root, event->y_root))
+ {
+ /* Tab was detached */
+ g_signal_emit (G_OBJECT(notebook),
+ ephy_notebook_signals[TAB_DETACHED], 0,
+ cur_page_num, (gint)event->x_root,
+ (gint)event->y_root);
+ }
+ else
+ {
+ /* Tab was dragged and dropped (but it may have stayed
+ in the same place) */
+ g_signal_emit (G_OBJECT(notebook),
+ ephy_notebook_signals[TAB_DROPPED], 0,
+ cur_page,
+ notebook->priv->src_notebook,
+ notebook->priv->src_page);
+ }
+
+ /* ungrab the pointer if it's grabbed */
+ if (gdk_pointer_is_grabbed ())
+ {
+ gdk_pointer_ungrab (GDK_CURRENT_TIME);
+ gtk_grab_remove (GTK_WIDGET (notebook));
+ }
+ }
+ /* This must be called even if a drag isn't happening */
+ drag_stop (notebook);
+ return FALSE;
+}
+
+
+static gboolean
+motion_notify_cb (EphyNotebook *notebook, GdkEventMotion *event,
+ gpointer data)
+{
+ EphyNotebook *dest;
+ gint page_num;
+ gint result;
+
+ /* If the notebook only has one tab, we don't want to do
+ * anything since ephy can't handle empty notebooks
+ */
+ if (g_list_length (GTK_NOTEBOOK (notebook)->children) <= 1) {
+ return FALSE;
+ }
+
+ if ((notebook->priv->drag_in_progress == FALSE)
+ && (gtk_drag_check_threshold (GTK_WIDGET (notebook),
+ notebook->priv->x_start,
+ notebook->priv->y_start,
+ event->x_root, event->y_root)))
+ {
+ gint cur_page;
+
+ cur_page = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook));
+ drag_start (notebook, notebook, cur_page);
+ }
+
+ result = find_notebook_and_tab_at_pos ((gint)event->x_root,
+ (gint)event->y_root,
+ &dest, &page_num);
+
+ if (result != NOT_IN_APP_WINDOWS)
+ {
+ if (dest != notebook)
+ {
+ move_tab_to_another_notebook (notebook, dest,
+ page_num);
+ }
+ else
+ {
+ g_assert (page_num >= -1);
+ move_tab (notebook, page_num);
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+button_press_cb (EphyNotebook *notebook,
+ GdkEventButton *event,
+ gpointer data)
+{
+ gint tab_clicked = find_tab_num_at_pos (notebook,
+ event->x_root,
+ event->y_root);
+
+ if (notebook->priv->drag_in_progress)
+ {
+ return TRUE;
+ }
+
+ if ((event->button == 1) && (event->type == GDK_BUTTON_PRESS)
+ && (tab_clicked != -1))
+ {
+ notebook->priv->x_start = event->x_root;
+ notebook->priv->y_start = event->y_root;
+ notebook->priv->motion_notify_handler_id =
+ g_signal_connect (G_OBJECT (notebook),
+ "motion-notify-event",
+ G_CALLBACK (motion_notify_cb), NULL);
+ }
+
+ return FALSE;
+}
+
+GtkWidget *
+ephy_notebook_new (void)
+{
+ return GTK_WIDGET (g_object_new (EPHY_NOTEBOOK_TYPE, NULL));
+}
+
+static void
+ephy_notebook_switch_page_cb (GtkNotebook *notebook,
+ GtkNotebookPage *page,
+ guint page_num,
+ gpointer data)
+{
+ EphyNotebook *nb = EPHY_NOTEBOOK (notebook);
+ GtkWidget *child;
+
+ child = gtk_notebook_get_nth_page (notebook, page_num);
+
+ /* Remove the old page, we dont want to grow unnecessarily
+ * the list */
+ if (nb->priv->focused_pages)
+ {
+ nb->priv->focused_pages =
+ g_list_remove (nb->priv->focused_pages, child);
+ }
+
+ nb->priv->focused_pages = g_list_append (nb->priv->focused_pages,
+ child);
+
+ /* Reset the list of newly opened tabs when switching tabs. */
+ g_list_free (nb->priv->opened_tabs);
+ nb->priv->opened_tabs = NULL;
+}
+
+static void
+ephy_notebook_init (EphyNotebook *notebook)
+{
+ notebook->priv = g_new (EphyNotebookPrivate, 1);
+
+ notebook->priv->drag_in_progress = FALSE;
+ notebook->priv->motion_notify_handler_id = 0;
+ notebook->priv->src_notebook = NULL;
+ notebook->priv->src_page = -1;
+ notebook->priv->focused_pages = NULL;
+ notebook->priv->opened_tabs = NULL;
+
+ notebooks = g_list_append (notebooks, notebook);
+
+ g_signal_connect (notebook, "button-press-event",
+ (GCallback)button_press_cb, NULL);
+ g_signal_connect (notebook, "button-release-event",
+ (GCallback)button_release_cb, NULL);
+ gtk_widget_add_events (GTK_WIDGET (notebook), GDK_BUTTON1_MOTION_MASK);
+
+ g_signal_connect_after (G_OBJECT (notebook), "switch_page",
+ G_CALLBACK (ephy_notebook_switch_page_cb),
+ NULL);
+}
+
+static void
+ephy_notebook_finalize (GObject *object)
+{
+ EphyNotebook *notebook = EPHY_NOTEBOOK (object);
+
+ notebooks = g_list_remove (notebooks, notebook);
+
+ if (notebook->priv->focused_pages)
+ {
+ g_list_free (notebook->priv->focused_pages);
+ }
+ g_list_free (notebook->priv->opened_tabs);
+
+ g_free (notebook->priv);
+}
+
+
+void
+ephy_notebook_set_page_status (EphyNotebook *nb,
+ GtkWidget *child,
+ EphyNotebookPageLoadStatus status)
+{
+}
+
+static void
+ephy_tab_close_button_clicked_cb (GtkWidget *widget,
+ GtkWidget *child)
+{
+ EphyNotebook *notebook;
+
+ notebook = EPHY_NOTEBOOK (gtk_widget_get_parent (child));
+ ephy_notebook_remove_page (notebook, child);
+}
+
+static GtkWidget *
+tab_build_label (EphyNotebook *nb, GtkWidget *child)
+{
+ GtkWidget *label, *hbox, *close_button, *image;
+ int h, w;
+ GClosure *closure;
+ GtkWidget *window;
+
+ window = gtk_widget_get_toplevel (GTK_WIDGET (nb));
+
+ gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &w, &h);
+
+ hbox = gtk_hbox_new (FALSE, 0);
+
+ /* setup close button */
+ close_button = gtk_button_new ();
+ gtk_button_set_relief (GTK_BUTTON (close_button),
+ GTK_RELIEF_NONE);
+ image = gtk_image_new_from_stock (GTK_STOCK_CLOSE,
+ GTK_ICON_SIZE_MENU);
+ gtk_widget_set_size_request (close_button, w, h);
+ gtk_container_add (GTK_CONTAINER (close_button),
+ image);
+
+ /* setup label */
+ label = gtk_label_new (_("Untitled"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.00, 0.5);
+ gtk_misc_set_padding (GTK_MISC (label), 4, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
+
+ tab_label_set_size (GTK_WIDGET (window), hbox);
+
+ closure = g_cclosure_new (G_CALLBACK (tab_label_size_request_cb),
+ child, NULL);
+ g_object_watch_closure (G_OBJECT (label), closure);
+ g_signal_connect_closure_by_id (G_OBJECT (window),
+ g_signal_lookup ("size_request",
+ G_OBJECT_TYPE (G_OBJECT (window))), 0,
+ closure,
+ FALSE);
+
+ /* setup button */
+ gtk_box_pack_start (GTK_BOX (hbox), close_button,
+ FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (close_button), "clicked",
+ G_CALLBACK (ephy_tab_close_button_clicked_cb),
+ child);
+
+ gtk_widget_show (hbox);
+ gtk_widget_show (label);
+ gtk_widget_show (image);
+ gtk_widget_show (close_button);
+
+ g_object_set_data (G_OBJECT (hbox), "label", label);
+
+ return hbox;
+}
+
+/*
+ * update_tabs_visibility: Hide tabs if there is only one tab
+ * and the pref is not set.
+ * HACK We need to show tabs before inserting the second. Otherwise
+ * gtknotebook go crazy.
+ */
+static void
+update_tabs_visibility (EphyNotebook *nb, gboolean before_inserting)
+{
+ gboolean show_tabs;
+ guint tabs_num = 1;
+
+ if (before_inserting) tabs_num--;
+
+ show_tabs = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), tabs_num) > 0;
+
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (nb), show_tabs);
+}
+
+void
+ephy_notebook_insert_page (EphyNotebook *nb,
+ GtkWidget *child,
+ int position,
+ gboolean jump_to)
+{
+ GtkWidget *tab_hbox;
+
+ tab_hbox = tab_build_label (nb, child);
+
+ update_tabs_visibility (nb, TRUE);
+
+ if (position == EPHY_NOTEBOOK_INSERT_GROUPED)
+ {
+ /* Keep a list of newly opened tabs, if the list is empty open the new
+ * tab after the current one. If it's not, add it after the newly
+ * opened tabs.
+ */
+ if (nb->priv->opened_tabs != NULL)
+ {
+ GList *last = g_list_last (nb->priv->opened_tabs);
+ GtkWidget *last_tab = last->data;
+ position = gtk_notebook_page_num
+ (GTK_NOTEBOOK (nb), last_tab) + 1;
+ }
+ else
+ {
+ position = gtk_notebook_get_current_page
+ (GTK_NOTEBOOK (nb)) + 1;
+ }
+ nb->priv->opened_tabs =
+ g_list_append (nb->priv->opened_tabs, child);
+ }
+
+ gtk_notebook_insert_page (GTK_NOTEBOOK (nb),
+ child,
+ tab_hbox, position);
+
+ if (jump_to)
+ {
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (nb),
+ position);
+ g_object_set_data (G_OBJECT (child), "jump_to",
+ GINT_TO_POINTER (jump_to));
+ }
+}
+
+static void
+smart_tab_switching_on_closure (EphyNotebook *nb,
+ GtkWidget *child)
+{
+ gboolean jump_to;
+
+ jump_to = GPOINTER_TO_INT (g_object_get_data
+ (G_OBJECT (child), "jump_to"));
+
+ if (!jump_to || !nb->priv->focused_pages)
+ {
+ gtk_notebook_next_page (GTK_NOTEBOOK (nb));
+ }
+ else
+ {
+ GList *l;
+ GtkWidget *child;
+ int page_num;
+
+ /* activate the last focused tab */
+ l = g_list_last (nb->priv->focused_pages);
+ child = GTK_WIDGET (l->data);
+ page_num = gtk_notebook_page_num (GTK_NOTEBOOK (nb),
+ child);
+ gtk_notebook_set_current_page
+ (GTK_NOTEBOOK (nb), page_num);
+ }
+}
+
+void
+ephy_notebook_remove_page (EphyNotebook *nb,
+ GtkWidget *child)
+{
+ int position, cur;
+ gboolean last_tab;
+
+ last_tab = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), 1) == NULL;
+ if (last_tab)
+ {
+ GtkWidget *window;
+ window = gtk_widget_get_toplevel (GTK_WIDGET (nb));
+ gtk_widget_destroy (window);
+ return;
+ }
+
+ /* Remove the page from the focused pages list */
+ nb->priv->focused_pages = g_list_remove (nb->priv->focused_pages,
+ child);
+ nb->priv->opened_tabs = g_list_remove (nb->priv->opened_tabs, child);
+
+
+ position = gtk_notebook_page_num (GTK_NOTEBOOK (nb),
+ child);
+
+ cur = gtk_notebook_get_current_page (GTK_NOTEBOOK (nb));
+ if (position == cur)
+ {
+ smart_tab_switching_on_closure (nb, child);
+ }
+
+ gtk_notebook_remove_page (GTK_NOTEBOOK (nb), position);
+
+ update_tabs_visibility (nb, FALSE);
+}
+
+void
+ephy_notebook_set_page_title (EphyNotebook *nb,
+ GtkWidget *child,
+ const char *title)
+{
+ GtkWidget *label;
+
+ label = tab_get_label (nb, child);
+ gtk_label_set_label (GTK_LABEL (label), title);
+}
diff --git a/lib/widgets/ephy-notebook.h b/lib/widgets/ephy-notebook.h
new file mode 100644
index 000000000..755eea84d
--- /dev/null
+++ b/lib/widgets/ephy-notebook.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2002 Christophe Fergeau
+ *
+ * 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.
+ */
+
+#ifndef EPHY_NOTEBOOK_H
+#define EPHY_NOTEBOOK_H
+
+#include <glib.h>
+#include <gtk/gtknotebook.h>
+
+G_BEGIN_DECLS
+
+typedef struct EphyNotebookClass EphyNotebookClass;
+
+#define EPHY_NOTEBOOK_TYPE (ephy_notebook_get_type ())
+#define EPHY_NOTEBOOK(obj) (GTK_CHECK_CAST ((obj), EPHY_NOTEBOOK_TYPE, EphyNotebook))
+#define EPHY_NOTEBOOK_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), EPHY_NOTEBOOK_TYPE, EphyNotebookClass))
+#define IS_EPHY_NOTEBOOK(obj) (GTK_CHECK_TYPE ((obj), EPHY_NOTEBOOK_TYPE))
+#define IS_EPHY_NOTEBOOK_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), EPHY_NOTEBOOK))
+
+typedef struct EphyNotebook EphyNotebook;
+typedef struct EphyNotebookPrivate EphyNotebookPrivate;
+
+typedef enum
+{
+ EPHY_NOTEBOOK_TAB_LOAD_NORMAL,
+ EPHY_NOTEBOOK_TAB_LOAD_LOADING,
+ EPHY_NOTEBOOK_TAB_LOAD_COMPLETED
+} EphyNotebookPageLoadStatus;
+
+enum
+{
+ EPHY_NOTEBOOK_INSERT_LAST = -1,
+ EPHY_NOTEBOOK_INSERT_GROUPED = -2
+};
+
+struct EphyNotebook
+{
+ GtkNotebook parent;
+ EphyNotebookPrivate *priv;
+};
+
+struct EphyNotebookClass
+{
+ GtkNotebookClass parent_class;
+
+ /* Signals */
+ void (* tab_dropped) (EphyNotebook *dest,
+ GtkWidget *widget,
+ EphyNotebook *src,
+ gint src_page);
+ void (* tab_detached) (EphyNotebook *dest,
+ gint cur_page,
+ gint root_x, gint root_y);
+
+};
+
+GType ephy_notebook_get_type (void);
+
+GtkWidget *ephy_notebook_new (void);
+
+void ephy_notebook_insert_page (EphyNotebook *nb,
+ GtkWidget *child,
+ int position,
+ gboolean jump_to);
+
+void ephy_notebook_remove_page (EphyNotebook *nb,
+ GtkWidget *child);
+
+void ephy_notebook_move_page (EphyNotebook *src,
+ EphyNotebook *dest,
+ GtkWidget *src_page,
+ gint dest_page);
+
+void ephy_notebook_set_page_status (EphyNotebook *nb,
+ GtkWidget *child,
+ EphyNotebookPageLoadStatus status);
+
+void ephy_notebook_set_page_title (EphyNotebook *nb,
+ GtkWidget *child,
+ const char *title);
+
+G_END_DECLS;
+
+#endif /* EPHY_NOTEBOOK_H */
diff --git a/lib/widgets/ephy-spinner.c b/lib/widgets/ephy-spinner.c
new file mode 100644
index 000000000..e4462f889
--- /dev/null
+++ b/lib/widgets/ephy-spinner.c
@@ -0,0 +1,897 @@
+/*
+ * Nautilus
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ * Copyright (C) 2002 Marco Pesenti Gritti
+ *
+ * Nautilus is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Nautilus 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
+ *
+ * Author: Andy Hertzfeld <andy@eazel.com>
+ *
+ * Ephy port by Marco Pesenti Gritti <marco@it.gnome.org>
+ *
+ * This is the spinner (for busy feedback) for the location bar
+ *
+ */
+
+#include "config.h"
+#include "ephy-spinner.h"
+#include "eel-gconf-extensions.h"
+#include "ephy-prefs.h"
+#include "ephy-string.h"
+#include "ephy-file-helpers.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gtk/gtkmenu.h>
+#include <gtk/gtkmenuitem.h>
+#include <gtk/gtksignal.h>
+#include <libgnome/gnome-macros.h>
+#include <libgnome/gnome-util.h>
+#include <math.h>
+#include <libgnomevfs/gnome-vfs.h>
+
+#define spinner_DEFAULT_TIMEOUT 100 /* Milliseconds Per Frame */
+
+struct EphySpinnerDetails {
+ GList *image_list;
+
+ GdkPixbuf *quiescent_pixbuf;
+
+ int max_frame;
+ int delay;
+ int current_frame;
+ guint timer_task;
+
+ gboolean ready;
+ gboolean small_mode;
+
+ gboolean button_in;
+ gboolean button_down;
+
+ gint theme_notif;
+};
+
+static void ephy_spinner_class_init (EphySpinnerClass *class);
+static void ephy_spinner_init (EphySpinner *spinner);
+static void ephy_spinner_load_images (EphySpinner *spinner);
+static void ephy_spinner_unload_images (EphySpinner *spinner);
+static void ephy_spinner_remove_update_callback (EphySpinner *spinner);
+
+
+static GList *spinner_directories = NULL;
+
+static void
+ephy_spinner_init_directory_list (void);
+static void
+ephy_spinner_search_directory (const gchar *base, GList **spinner_list);
+static EphySpinnerInfo *
+ephy_spinner_get_theme_info (const gchar *base, const gchar *theme_name);
+static gchar *
+ephy_spinner_get_theme_path (const gchar *theme_name);
+
+
+static GObjectClass *parent_class = NULL;
+
+GType
+ephy_spinner_get_type (void)
+{
+ static GType ephy_spinner_type = 0;
+
+ if (ephy_spinner_type == 0)
+ {
+ static const GTypeInfo our_info =
+ {
+ sizeof (EphySpinnerClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) ephy_spinner_class_init,
+ NULL,
+ NULL, /* class_data */
+ sizeof (EphySpinner),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) ephy_spinner_init
+ };
+
+ ephy_spinner_type = g_type_register_static (GTK_TYPE_EVENT_BOX,
+ "EphySpinner",
+ &our_info, 0);
+
+ ephy_spinner_init_directory_list ();
+ }
+
+ return ephy_spinner_type;
+
+}
+
+/*
+ * ephy_spinner_new:
+ *
+ * Create a new #EphySpinner. The spinner is a widget
+ * that gives the user feedback about network status with
+ * an animated image.
+ *
+ * Return Value: the spinner #GtkWidget
+ **/
+GtkWidget *
+ephy_spinner_new (void)
+{
+ GtkWidget *s;
+
+ s = GTK_WIDGET (g_object_new (EPHY_SPINNER_TYPE, NULL));
+
+ return s;
+}
+
+static gboolean
+is_throbbing (EphySpinner *spinner)
+{
+ return spinner->details->timer_task != 0;
+}
+
+/* loop through all the images taking their union to compute the width and height of the spinner */
+static void
+get_spinner_dimensions (EphySpinner *spinner, int *spinner_width, int* spinner_height)
+{
+ int current_width, current_height;
+ int pixbuf_width, pixbuf_height;
+ GList *current_entry;
+ GdkPixbuf *pixbuf;
+
+ /* start with the quiescent image */
+ current_width = gdk_pixbuf_get_width (spinner->details->quiescent_pixbuf);
+ current_height = gdk_pixbuf_get_height (spinner->details->quiescent_pixbuf);
+
+ /* loop through all the installed images, taking the union */
+ current_entry = spinner->details->image_list;
+ while (current_entry != NULL) {
+ pixbuf = GDK_PIXBUF (current_entry->data);
+ pixbuf_width = gdk_pixbuf_get_width (pixbuf);
+ pixbuf_height = gdk_pixbuf_get_height (pixbuf);
+
+ if (pixbuf_width > current_width) {
+ current_width = pixbuf_width;
+ }
+
+ if (pixbuf_height > current_height) {
+ current_height = pixbuf_height;
+ }
+
+ current_entry = current_entry->next;
+ }
+
+ /* return the result */
+ *spinner_width = current_width;
+ *spinner_height = current_height;
+}
+
+/* handler for handling theme changes */
+static void
+ephy_spinner_theme_changed (GConfClient *client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ gpointer user_data)
+{
+ EphySpinner *spinner;
+
+ spinner = EPHY_SPINNER (user_data);
+ gtk_widget_hide (GTK_WIDGET (spinner));
+ ephy_spinner_load_images (spinner);
+ gtk_widget_show (GTK_WIDGET (spinner));
+ gtk_widget_queue_resize ( GTK_WIDGET (spinner));
+}
+
+static void
+ephy_spinner_init (EphySpinner *spinner)
+{
+ GtkWidget *widget = GTK_WIDGET (spinner);
+
+ GTK_WIDGET_UNSET_FLAGS (spinner, GTK_NO_WINDOW);
+
+ gtk_widget_set_events (widget,
+ gtk_widget_get_events (widget)
+ | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
+ | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
+
+ spinner->details = g_new0 (EphySpinnerDetails, 1);
+
+ spinner->details->delay = spinner_DEFAULT_TIMEOUT;
+
+ ephy_spinner_load_images (spinner);
+ gtk_widget_show (widget);
+
+ spinner->details->theme_notif =
+ eel_gconf_notification_add (CONF_TOOLBAR_SPINNER_THEME,
+ (GConfClientNotifyFunc)
+ ephy_spinner_theme_changed,
+ spinner);
+}
+
+/* here's the routine that selects the image to draw, based on the spinner's state */
+
+static GdkPixbuf *
+select_spinner_image (EphySpinner *spinner)
+{
+ GList *element;
+
+ if (spinner->details->timer_task == 0) {
+ return g_object_ref (spinner->details->quiescent_pixbuf);
+ }
+
+ if (spinner->details->image_list == NULL) {
+ return NULL;
+ }
+
+ element = g_list_nth (spinner->details->image_list, spinner->details->current_frame);
+
+ return g_object_ref (element->data);
+}
+
+static guchar
+lighten_component (guchar cur_value)
+{
+ int new_value = cur_value;
+ new_value += 24 + (new_value >> 3);
+ if (new_value > 255) {
+ new_value = 255;
+ }
+ return (guchar) new_value;
+}
+
+static GdkPixbuf *
+create_new_pixbuf (GdkPixbuf *src)
+{
+ g_return_val_if_fail (gdk_pixbuf_get_colorspace (src) == GDK_COLORSPACE_RGB, NULL);
+ g_return_val_if_fail ((!gdk_pixbuf_get_has_alpha (src)
+ && gdk_pixbuf_get_n_channels (src) == 3)
+ || (gdk_pixbuf_get_has_alpha (src)
+ && gdk_pixbuf_get_n_channels (src) == 4), NULL);
+
+ return gdk_pixbuf_new (gdk_pixbuf_get_colorspace (src),
+ gdk_pixbuf_get_has_alpha (src),
+ gdk_pixbuf_get_bits_per_sample (src),
+ gdk_pixbuf_get_width (src),
+ gdk_pixbuf_get_height (src));
+}
+
+static GdkPixbuf *
+eel_create_darkened_pixbuf (GdkPixbuf *src, int saturation, int darken)
+{
+ gint i, j;
+ gint width, height, src_row_stride, dest_row_stride;
+ gboolean has_alpha;
+ guchar *target_pixels, *original_pixels;
+ guchar *pixsrc, *pixdest;
+ guchar intensity;
+ guchar alpha;
+ guchar negalpha;
+ guchar r, g, b;
+ GdkPixbuf *dest;
+
+ g_return_val_if_fail (gdk_pixbuf_get_colorspace (src) == GDK_COLORSPACE_RGB, NULL);
+ g_return_val_if_fail ((!gdk_pixbuf_get_has_alpha (src)
+ && gdk_pixbuf_get_n_channels (src) == 3)
+ || (gdk_pixbuf_get_has_alpha (src)
+ && gdk_pixbuf_get_n_channels (src) == 4), NULL);
+ g_return_val_if_fail (gdk_pixbuf_get_bits_per_sample (src) == 8, NULL);
+
+ dest = create_new_pixbuf (src);
+
+ has_alpha = gdk_pixbuf_get_has_alpha (src);
+ width = gdk_pixbuf_get_width (src);
+ height = gdk_pixbuf_get_height (src);
+ dest_row_stride = gdk_pixbuf_get_rowstride (dest);
+ src_row_stride = gdk_pixbuf_get_rowstride (src);
+ target_pixels = gdk_pixbuf_get_pixels (dest);
+ original_pixels = gdk_pixbuf_get_pixels (src);
+
+ for (i = 0; i < height; i++) {
+ pixdest = target_pixels + i * dest_row_stride;
+ pixsrc = original_pixels + i * src_row_stride;
+ for (j = 0; j < width; j++) {
+ r = *pixsrc++;
+ g = *pixsrc++;
+ b = *pixsrc++;
+ intensity = (r * 77 + g * 150 + b * 28) >> 8;
+ negalpha = ((255 - saturation) * darken) >> 8;
+ alpha = (saturation * darken) >> 8;
+ *pixdest++ = (negalpha * intensity + alpha * r) >> 8;
+ *pixdest++ = (negalpha * intensity + alpha * g) >> 8;
+ *pixdest++ = (negalpha * intensity + alpha * b) >> 8;
+ if (has_alpha) {
+ *pixdest++ = *pixsrc++;
+ }
+ }
+ }
+ return dest;
+}
+
+static GdkPixbuf *
+eel_create_spotlight_pixbuf (GdkPixbuf* src)
+{
+ GdkPixbuf *dest;
+ int i, j;
+ int width, height, has_alpha, src_row_stride, dst_row_stride;
+ guchar *target_pixels, *original_pixels;
+ guchar *pixsrc, *pixdest;
+
+ g_return_val_if_fail (gdk_pixbuf_get_colorspace (src) == GDK_COLORSPACE_RGB, NULL);
+ g_return_val_if_fail ((!gdk_pixbuf_get_has_alpha (src)
+ && gdk_pixbuf_get_n_channels (src) == 3)
+ || (gdk_pixbuf_get_has_alpha (src)
+ && gdk_pixbuf_get_n_channels (src) == 4), NULL);
+ g_return_val_if_fail (gdk_pixbuf_get_bits_per_sample (src) == 8, NULL);
+
+ dest = create_new_pixbuf (src);
+
+ has_alpha = gdk_pixbuf_get_has_alpha (src);
+ width = gdk_pixbuf_get_width (src);
+ height = gdk_pixbuf_get_height (src);
+ dst_row_stride = gdk_pixbuf_get_rowstride (dest);
+ src_row_stride = gdk_pixbuf_get_rowstride (src);
+ target_pixels = gdk_pixbuf_get_pixels (dest);
+ original_pixels = gdk_pixbuf_get_pixels (src);
+
+ for (i = 0; i < height; i++) {
+ pixdest = target_pixels + i * dst_row_stride;
+ pixsrc = original_pixels + i * src_row_stride;
+ for (j = 0; j < width; j++) {
+ *pixdest++ = lighten_component (*pixsrc++);
+ *pixdest++ = lighten_component (*pixsrc++);
+ *pixdest++ = lighten_component (*pixsrc++);
+ if (has_alpha) {
+ *pixdest++ = *pixsrc++;
+ }
+ }
+ }
+ return dest;
+}
+
+/* handle expose events */
+
+static int
+ephy_spinner_expose (GtkWidget *widget, GdkEventExpose *event)
+{
+ EphySpinner *spinner;
+ GdkPixbuf *pixbuf, *massaged_pixbuf;
+ int x_offset, y_offset, width, height;
+ GdkRectangle pix_area, dest;
+
+ g_return_val_if_fail (IS_EPHY_SPINNER (widget), FALSE);
+
+ spinner = EPHY_SPINNER (widget);
+ if (!spinner->details->ready) {
+ return FALSE;
+ }
+
+ pixbuf = select_spinner_image (spinner);
+ if (pixbuf == NULL) {
+ return FALSE;
+ }
+
+ /* Get the right tint on the image */
+
+ if (spinner->details->button_in) {
+ if (spinner->details->button_down) {
+ massaged_pixbuf = eel_create_darkened_pixbuf (pixbuf, 0.8 * 255, 0.8 * 255);
+ } else {
+ massaged_pixbuf = eel_create_spotlight_pixbuf (pixbuf);
+ }
+ g_object_unref (pixbuf);
+ pixbuf = massaged_pixbuf;
+ }
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+ /* Compute the offsets for the image centered on our allocation */
+ x_offset = widget->allocation.x + (widget->allocation.width - width) / 2;
+ y_offset = widget->allocation.y + (widget->allocation.height - height) / 2;
+
+ pix_area.x = x_offset;
+ pix_area.y = y_offset;
+ pix_area.width = width;
+ pix_area.height = height;
+
+ if (!gdk_rectangle_intersect (&event->area, &pix_area, &dest)) {
+ g_object_unref (pixbuf);
+ return FALSE;
+ }
+
+ gdk_pixbuf_render_to_drawable_alpha (
+ pixbuf, widget->window,
+ dest.x - x_offset, dest.y - y_offset,
+ dest.x, dest.y,
+ dest.width, dest.height,
+ GDK_PIXBUF_ALPHA_BILEVEL, 128,
+ GDK_RGB_DITHER_MAX,
+ 0, 0);
+
+ g_object_unref (pixbuf);
+
+ return FALSE;
+}
+
+static void
+ephy_spinner_map (GtkWidget *widget)
+{
+ EphySpinner *spinner;
+
+ spinner = EPHY_SPINNER (widget);
+
+ GTK_WIDGET_CLASS (parent_class)->map (widget);
+
+ spinner->details->ready = TRUE;
+}
+
+/* here's the actual timeout task to bump the frame and schedule a redraw */
+
+static gboolean
+bump_spinner_frame (gpointer callback_data)
+{
+ EphySpinner *spinner;
+
+ spinner = EPHY_SPINNER (callback_data);
+ if (!spinner->details->ready) {
+ return TRUE;
+ }
+
+ spinner->details->current_frame += 1;
+ if (spinner->details->current_frame > spinner->details->max_frame - 1) {
+ spinner->details->current_frame = 0;
+ }
+
+ gtk_widget_queue_draw (GTK_WIDGET (spinner));
+ return TRUE;
+}
+
+/**
+ * ephy_spinner_start:
+ * @spinner: a #EphySpinner
+ *
+ * Start the spinner animation.
+ **/
+void
+ephy_spinner_start (EphySpinner *spinner)
+{
+ if (is_throbbing (spinner)) {
+ return;
+ }
+
+ if (spinner->details->timer_task != 0) {
+ gtk_timeout_remove (spinner->details->timer_task);
+ }
+
+ /* reset the frame count */
+ spinner->details->current_frame = 0;
+ spinner->details->timer_task = gtk_timeout_add (spinner->details->delay,
+ bump_spinner_frame,
+ spinner);
+}
+
+static void
+ephy_spinner_remove_update_callback (EphySpinner *spinner)
+{
+ if (spinner->details->timer_task != 0) {
+ gtk_timeout_remove (spinner->details->timer_task);
+ }
+
+ spinner->details->timer_task = 0;
+}
+
+/**
+ * ephy_spinner_stop:
+ * @spinner: a #EphySpinner
+ *
+ * Stop the spinner animation.
+ **/
+void
+ephy_spinner_stop (EphySpinner *spinner)
+{
+ if (!is_throbbing (spinner)) {
+ return;
+ }
+
+ ephy_spinner_remove_update_callback (spinner);
+ gtk_widget_queue_draw (GTK_WIDGET (spinner));
+
+}
+
+/* routines to load the images used to draw the spinner */
+
+/* unload all the images, and the list itself */
+
+static void
+ephy_spinner_unload_images (EphySpinner *spinner)
+{
+ GList *current_entry;
+
+ if (spinner->details->quiescent_pixbuf != NULL) {
+ g_object_unref (spinner->details->quiescent_pixbuf);
+ spinner->details->quiescent_pixbuf = NULL;
+ }
+
+ /* unref all the images in the list, and then let go of the list itself */
+ current_entry = spinner->details->image_list;
+ while (current_entry != NULL) {
+ g_object_unref (current_entry->data);
+ current_entry = current_entry->next;
+ }
+
+ g_list_free (spinner->details->image_list);
+ spinner->details->image_list = NULL;
+}
+
+static GdkPixbuf*
+load_themed_image (const char *path, const char *file_name,
+ gboolean small_mode)
+{
+ GdkPixbuf *pixbuf, *temp_pixbuf;
+ char *image_path;
+
+ image_path = g_build_filename (path, file_name, NULL);
+
+ if (!g_file_test(image_path, G_FILE_TEST_EXISTS))
+ {
+ g_free (image_path);
+ return NULL;
+ }
+
+ if (image_path) {
+ pixbuf = gdk_pixbuf_new_from_file (image_path, NULL);
+
+ if (small_mode && pixbuf) {
+ temp_pixbuf = gdk_pixbuf_scale_simple (pixbuf,
+ gdk_pixbuf_get_width (pixbuf) * 2 / 3,
+ gdk_pixbuf_get_height (pixbuf) * 2 / 3,
+ GDK_INTERP_BILINEAR);
+ g_object_unref (pixbuf);
+ pixbuf = temp_pixbuf;
+ }
+
+ g_free (image_path);
+
+ return pixbuf;
+ }
+ return NULL;
+}
+
+/* utility to make the spinner frame name from the index */
+
+static char *
+make_spinner_frame_name (int index)
+{
+ return g_strdup_printf ("%03d.png", index);
+}
+
+/* load all of the images of the spinner sequentially */
+static void
+ephy_spinner_load_images (EphySpinner *spinner)
+{
+ int index;
+ char *spinner_frame_name;
+ GdkPixbuf *pixbuf, *qpixbuf;
+ GList *image_list;
+ char *image_theme;
+ char *path;
+
+ ephy_spinner_unload_images (spinner);
+
+ image_theme = eel_gconf_get_string (CONF_TOOLBAR_SPINNER_THEME);
+
+ path = ephy_spinner_get_theme_path (image_theme);
+ g_return_if_fail (path != NULL);
+
+ qpixbuf = load_themed_image (path, "rest.png",
+ spinner->details->small_mode);
+
+ g_return_if_fail (qpixbuf != NULL);
+ spinner->details->quiescent_pixbuf = qpixbuf;
+
+ spinner->details->max_frame = 50;
+
+ image_list = NULL;
+ for (index = 1; index <= spinner->details->max_frame; index++) {
+ spinner_frame_name = make_spinner_frame_name (index);
+ pixbuf = load_themed_image (path, spinner_frame_name,
+ spinner->details->small_mode);
+ g_free (spinner_frame_name);
+ if (pixbuf == NULL) {
+ spinner->details->max_frame = index - 1;
+ break;
+ }
+ image_list = g_list_prepend (image_list, pixbuf);
+ }
+ spinner->details->image_list = g_list_reverse (image_list);
+
+ g_free (image_theme);
+}
+
+static gboolean
+ephy_spinner_enter_notify_event (GtkWidget *widget, GdkEventCrossing *event)
+{
+ EphySpinner *spinner;
+
+ spinner = EPHY_SPINNER (widget);
+
+ if (!spinner->details->button_in) {
+ spinner->details->button_in = TRUE;
+ gtk_widget_queue_draw (widget);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+ephy_spinner_leave_notify_event (GtkWidget *widget, GdkEventCrossing *event)
+{
+ EphySpinner *spinner;
+
+ spinner = EPHY_SPINNER (widget);
+
+ if (spinner->details->button_in) {
+ spinner->details->button_in = FALSE;
+ gtk_widget_queue_draw (widget);
+ }
+
+ return FALSE;
+}
+
+/* handle button presses by posting a change on the "location" property */
+
+static gboolean
+ephy_spinner_button_press_event (GtkWidget *widget, GdkEventButton *event)
+{
+ EphySpinner *spinner;
+
+ spinner = EPHY_SPINNER (widget);
+
+ if (event->button == 1) {
+ spinner->details->button_down = TRUE;
+ spinner->details->button_in = TRUE;
+ gtk_widget_queue_draw (widget);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+ephy_spinner_set_location (EphySpinner *spinner)
+{
+}
+
+static gboolean
+ephy_spinner_button_release_event (GtkWidget *widget, GdkEventButton *event)
+{
+ EphySpinner *spinner;
+
+ spinner = EPHY_SPINNER (widget);
+
+ if (event->button == 1) {
+ if (spinner->details->button_in) {
+ ephy_spinner_set_location (spinner);
+ }
+ spinner->details->button_down = FALSE;
+ gtk_widget_queue_draw (widget);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/*
+ * ephy_spinner_set_small_mode:
+ * @spinner: a #EphySpinner
+ * @new_mode: pass true to enable the small mode, false to disable
+ *
+ * Set the size mode of the spinner. We need a small mode to deal
+ * with only icons toolbars.
+ **/
+void
+ephy_spinner_set_small_mode (EphySpinner *spinner, gboolean new_mode)
+{
+ if (new_mode != spinner->details->small_mode) {
+ spinner->details->small_mode = new_mode;
+ ephy_spinner_load_images (spinner);
+
+ gtk_widget_queue_resize (GTK_WIDGET (spinner));
+ }
+}
+
+/* handle setting the size */
+
+static void
+ephy_spinner_size_request (GtkWidget *widget, GtkRequisition *requisition)
+{
+ int spinner_width, spinner_height;
+ EphySpinner *spinner = EPHY_SPINNER (widget);
+
+ get_spinner_dimensions (spinner, &spinner_width, &spinner_height);
+
+ /* allocate some extra margin so we don't butt up against toolbar edges */
+ requisition->width = spinner_width + 8;
+ requisition->height = spinner_height;
+}
+
+static void
+ephy_spinner_finalize (GObject *object)
+{
+ EphySpinner *spinner;
+
+ spinner = EPHY_SPINNER (object);
+
+ ephy_spinner_remove_update_callback (spinner);
+ ephy_spinner_unload_images (spinner);
+
+ eel_gconf_notification_remove (spinner->details->theme_notif);
+
+ g_free (spinner->details);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+ephy_spinner_class_init (EphySpinnerClass *class)
+{
+ GtkWidgetClass *widget_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ widget_class = GTK_WIDGET_CLASS (class);
+
+ G_OBJECT_CLASS (class)->finalize = ephy_spinner_finalize;
+
+ widget_class->expose_event = ephy_spinner_expose;
+ widget_class->button_press_event = ephy_spinner_button_press_event;
+ widget_class->button_release_event = ephy_spinner_button_release_event;
+ widget_class->enter_notify_event = ephy_spinner_enter_notify_event;
+ widget_class->leave_notify_event = ephy_spinner_leave_notify_event;
+ widget_class->size_request = ephy_spinner_size_request;
+ widget_class->map = ephy_spinner_map;
+}
+
+static void
+ephy_spinner_search_directory (const gchar *base, GList **spinner_list)
+{
+ GnomeVFSResult rc;
+ GList *list, *node;
+
+ rc = gnome_vfs_directory_list_load
+ (&list, base, (GNOME_VFS_FILE_INFO_GET_MIME_TYPE |
+ GNOME_VFS_FILE_INFO_FORCE_FAST_MIME_TYPE |
+ GNOME_VFS_FILE_INFO_FOLLOW_LINKS));
+ if (rc != GNOME_VFS_OK) return;
+
+ for (node = list; node != NULL; node = g_list_next (node))
+ {
+ GnomeVFSFileInfo *file_info = node->data;
+ EphySpinnerInfo *info;
+
+ if (file_info->name[0] == '.')
+ continue;
+ if (file_info->type != GNOME_VFS_FILE_TYPE_DIRECTORY)
+ continue;
+
+ info = ephy_spinner_get_theme_info (base, file_info->name);
+ if (info != NULL)
+ {
+ *spinner_list = g_list_append (*spinner_list, info);
+ }
+ }
+
+ gnome_vfs_file_info_list_free (list);
+}
+
+static EphySpinnerInfo *
+ephy_spinner_get_theme_info (const gchar *base, const gchar *theme_name)
+{
+ EphySpinnerInfo *info;
+ gchar *path;
+ gchar *icon;
+
+ path = g_build_filename (base, theme_name, NULL);
+ icon = g_build_filename (path, "rest.png", NULL);
+
+ if (!g_file_test (icon, G_FILE_TEST_EXISTS))
+ {
+ g_free (path);
+ g_free (icon);
+
+ /* handle nautilus throbbers as well */
+
+ path = g_build_filename (base, theme_name, "throbber", NULL);
+ icon = g_build_filename (path, "rest.png", NULL);
+ }
+
+ if (!g_file_test (icon, G_FILE_TEST_EXISTS))
+ {
+ g_free (path);
+ g_free (icon);
+
+ return NULL;
+ }
+
+ info = g_new(EphySpinnerInfo, 1);
+ info->name = g_strdup (theme_name);
+ info->directory = path;
+ info->filename = icon;
+
+ return info;
+}
+
+static void
+ephy_spinner_init_directory_list (void)
+{
+ gchar *path;
+
+ path = g_build_filename (g_get_home_dir (), ephy_dot_dir (), "spinners", NULL);
+ spinner_directories = g_list_append (spinner_directories, path);
+
+ path = g_build_filename (SHARE_DIR, "spinners", NULL);
+ spinner_directories = g_list_append (spinner_directories, path);
+
+ path = g_build_filename (SHARE_DIR, "..", "pixmaps", "nautilus", NULL);
+ spinner_directories = g_list_append (spinner_directories, path);
+
+#ifdef NAUTILUS_PREFIX
+ path = g_build_filename (NAUTILUS_PREFIX, "share", "pixmaps", "nautilus", NULL);
+ spinner_directories = g_list_append (spinner_directories, path);
+#endif
+}
+
+GList *
+ephy_spinner_list_spinners (void)
+{
+ GList *spinner_list = NULL;
+ GList *tmp;
+
+ for (tmp = spinner_directories; tmp != NULL; tmp = g_list_next (tmp))
+ {
+ gchar *path = tmp->data;
+ ephy_spinner_search_directory (path, &spinner_list);
+ }
+
+ return spinner_list;
+}
+
+static gchar *
+ephy_spinner_get_theme_path (const gchar *theme_name)
+{
+ EphySpinnerInfo *info;
+ GList *tmp;
+
+ for (tmp = spinner_directories; tmp != NULL; tmp = g_list_next (tmp))
+ {
+ gchar *path = tmp->data;
+
+ info = ephy_spinner_get_theme_info (path, theme_name);
+ if (info != NULL)
+ {
+ path = g_strdup (info->directory);
+ ephy_spinner_info_free (info);
+ return path;
+ }
+ }
+
+ return NULL;
+}
+
+void
+ephy_spinner_info_free (EphySpinnerInfo *info)
+{
+ g_free (info->name);
+ g_free (info->directory);
+ g_free (info->filename);
+ g_free (info);
+}
diff --git a/lib/widgets/ephy-spinner.h b/lib/widgets/ephy-spinner.h
new file mode 100644
index 000000000..c72bee8c1
--- /dev/null
+++ b/lib/widgets/ephy-spinner.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Nautilus
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Nautilus 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
+ *
+ * Author: Andy Hertzfeld <andy@eazel.com>
+ *
+ * This is the header file for the throbber on the location bar
+ *
+ */
+
+#ifndef EPHY_SPINNER_H
+#define EPHY_SPINNER_H
+
+#include <gtk/gtkeventbox.h>
+#include <bonobo.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_SPINNER_TYPE (ephy_spinner_get_type ())
+#define EPHY_SPINNER(obj) (GTK_CHECK_CAST ((obj), EPHY_SPINNER_TYPE, EphySpinner))
+#define EPHY_SPINNER_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), EPHY_SPINNER_TYPE, EphySpinnerClass))
+#define IS_EPHY_SPINNER(obj) (GTK_CHECK_TYPE ((obj), EPHY_SPINNER_TYPE))
+#define IS_EPHY_SPINNER_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), EPHY_SPINNER_TYPE))
+
+typedef struct EphySpinnerInfo EphySpinnerInfo;
+
+struct EphySpinnerInfo
+{
+ gchar *name;
+ gchar *filename;
+ gchar *directory;
+};
+
+typedef struct EphySpinner EphySpinner;
+typedef struct EphySpinnerClass EphySpinnerClass;
+typedef struct EphySpinnerDetails EphySpinnerDetails;
+
+struct EphySpinner {
+ GtkEventBox parent;
+ EphySpinnerDetails *details;
+};
+
+struct EphySpinnerClass {
+ GtkEventBoxClass parent_class;
+};
+
+GtkType ephy_spinner_get_type (void);
+GtkWidget *ephy_spinner_new (void);
+void ephy_spinner_start (EphySpinner *throbber);
+void ephy_spinner_stop (EphySpinner *throbber);
+void ephy_spinner_set_small_mode (EphySpinner *throbber,
+ gboolean new_mode);
+
+GList *ephy_spinner_list_spinners (void);
+void ephy_spinner_info_free (EphySpinnerInfo *info);
+
+G_END_DECLS
+
+#endif /* EPHY_SPINNER_H */
+
+
diff --git a/lib/widgets/ephy-tree-model-sort.c b/lib/widgets/ephy-tree-model-sort.c
new file mode 100644
index 000000000..3c2377cf0
--- /dev/null
+++ b/lib/widgets/ephy-tree-model-sort.c
@@ -0,0 +1,240 @@
+/* Rhythmbox.
+ * Copyright (C) 2002 Olivier Martin <omartin@ifrance.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Id$
+ */
+
+#include <gtk/gtkmarshal.h>
+#include <string.h>
+
+#include "ephy-node.h"
+#include "ephy-tree-model-sort.h"
+#include "eggtreemultidnd.h"
+#include "ephy-dnd.h"
+#include "ephy-marshal.h"
+
+static void ephy_tree_model_sort_class_init (EphyTreeModelSortClass *klass);
+static void ephy_tree_model_sort_init (EphyTreeModelSort *ma);
+static void ephy_tree_model_sort_finalize (GObject *object);
+static void ephy_tree_model_sort_multi_drag_source_init (EggTreeMultiDragSourceIface *iface);
+static gboolean ephy_tree_model_sort_multi_row_draggable (EggTreeMultiDragSource *drag_source,
+ GList *path_list);
+static gboolean ephy_tree_model_sort_multi_drag_data_get (EggTreeMultiDragSource *drag_source,
+ GList *path_list,
+ guint info,
+ GtkSelectionData *selection_data);
+static gboolean ephy_tree_model_sort_multi_drag_data_delete (EggTreeMultiDragSource *drag_source,
+ GList *path_list);
+
+struct EphyTreeModelSortPrivate
+{
+ char *str_list;
+};
+
+enum
+{
+ NODE_FROM_ITER,
+ LAST_SIGNAL
+};
+
+static GObjectClass *parent_class = NULL;
+
+static guint ephy_tree_model_sort_signals[LAST_SIGNAL] = { 0 };
+
+GType
+ephy_tree_model_sort_get_type (void)
+{
+ static GType ephy_tree_model_sort_type = 0;
+
+ if (ephy_tree_model_sort_type == 0)
+ {
+ static const GTypeInfo our_info =
+ {
+ sizeof (EphyTreeModelSortClass),
+ NULL, /* base init */
+ NULL, /* base finalize */
+ (GClassInitFunc) ephy_tree_model_sort_class_init,
+ NULL, /* class finalize */
+ NULL, /* class data */
+ sizeof (EphyTreeModelSort),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) ephy_tree_model_sort_init
+ };
+ static const GInterfaceInfo multi_drag_source_info =
+ {
+ (GInterfaceInitFunc) ephy_tree_model_sort_multi_drag_source_init,
+ NULL,
+ NULL
+ };
+
+ ephy_tree_model_sort_type = g_type_register_static (GTK_TYPE_TREE_MODEL_SORT,
+ "EphyTreeModelSort",
+ &our_info, 0);
+
+ g_type_add_interface_static (ephy_tree_model_sort_type,
+ EGG_TYPE_TREE_MULTI_DRAG_SOURCE,
+ &multi_drag_source_info);
+ }
+
+ return ephy_tree_model_sort_type;
+}
+
+static void
+ephy_tree_model_sort_class_init (EphyTreeModelSortClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->finalize = ephy_tree_model_sort_finalize;
+
+ ephy_tree_model_sort_signals[NODE_FROM_ITER] =
+ g_signal_new ("node_from_iter",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EphyTreeModelSortClass, node_from_iter),
+ NULL, NULL,
+ ephy_marshal_VOID__POINTER_POINTER,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER);
+}
+
+static void
+ephy_tree_model_sort_init (EphyTreeModelSort *ma)
+{
+ ma->priv = g_new0 (EphyTreeModelSortPrivate, 1);
+}
+
+static void
+ephy_tree_model_sort_finalize (GObject *object)
+{
+ EphyTreeModelSort *model;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (EPHY_IS_TREE_MODEL_SORT (object));
+
+ model = EPHY_TREE_MODEL_SORT (object);
+
+ g_free (model->priv->str_list);
+ g_free (model->priv);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+GtkTreeModel*
+ephy_tree_model_sort_new (GtkTreeModel *child_model)
+{
+ GtkTreeModel *model;
+
+ g_return_val_if_fail (child_model != NULL, NULL);
+
+ model = GTK_TREE_MODEL (g_object_new (EPHY_TYPE_TREE_MODEL_SORT,
+ "model", child_model,
+ NULL));
+
+ return model;
+}
+
+static void
+ephy_tree_model_sort_multi_drag_source_init (EggTreeMultiDragSourceIface *iface)
+{
+ iface->row_draggable = ephy_tree_model_sort_multi_row_draggable;
+ iface->drag_data_get = ephy_tree_model_sort_multi_drag_data_get;
+ iface->drag_data_delete = ephy_tree_model_sort_multi_drag_data_delete;
+}
+
+static gboolean
+ephy_tree_model_sort_multi_row_draggable (EggTreeMultiDragSource *drag_source, GList *path_list)
+{
+ GList *l;
+
+ for (l = path_list; l != NULL; l = g_list_next (l))
+ {
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ EphyNode *node = NULL;
+
+ path = gtk_tree_row_reference_get_path (l->data);
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source), &iter, path);
+ g_signal_emit (G_OBJECT (drag_source),
+ ephy_tree_model_sort_signals[NODE_FROM_ITER],
+ 0, &iter, &node);
+
+ if (node == NULL)
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+ephy_tree_model_sort_multi_drag_data_delete (EggTreeMultiDragSource *drag_source,
+ GList *path_list)
+{
+ return TRUE;
+}
+
+static void
+each_url_get_data_binder (EphyDragEachSelectedItemDataGet iteratee,
+ gpointer iterator_context, gpointer data)
+{
+ gpointer *context = (gpointer *) iterator_context;
+ GList *path_list = (GList *) (context[0]);
+ GList *i;
+ GtkTreeModel *model = GTK_TREE_MODEL (context[1]);
+
+ for (i = path_list; i != NULL; i = i->next)
+ {
+ GtkTreeIter iter;
+ GtkTreePath *path = gtk_tree_row_reference_get_path (i->data);
+ EphyNode *node = NULL;
+ const char *value;
+
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path);
+ g_signal_emit (G_OBJECT (model),
+ ephy_tree_model_sort_signals[NODE_FROM_ITER],
+ 0, &iter, &node);
+
+ if (node == NULL)
+ return;
+
+ value = ephy_node_get_property_string (node,
+ EPHY_DND_NODE_PROPERTY);
+
+ iteratee (value, -1, -1, -1, -1, data);
+ }
+}
+
+static gboolean
+ephy_tree_model_sort_multi_drag_data_get (EggTreeMultiDragSource *drag_source,
+ GList *path_list,
+ guint info,
+ GtkSelectionData *selection_data)
+{ gpointer icontext[2];
+
+ icontext[0] = path_list;
+ icontext[1] = drag_source;
+
+ ephy_dnd_drag_data_get (NULL, NULL, selection_data,
+ info, 0, &icontext, each_url_get_data_binder);
+
+ return TRUE;
+}
diff --git a/lib/widgets/ephy-tree-model-sort.h b/lib/widgets/ephy-tree-model-sort.h
new file mode 100644
index 000000000..f8cb9fb68
--- /dev/null
+++ b/lib/widgets/ephy-tree-model-sort.h
@@ -0,0 +1,59 @@
+/* Copyright (C) 2002 Olivier Martin <omartin@ifrance.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Id$
+ */
+
+#ifndef EPHY_TREE_MODEL_SORT_H
+#define EPHY_TREE_MODEL_SORT_H
+
+#include <glib-object.h>
+
+#include <gtk/gtktreemodelsort.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_TREE_MODEL_SORT (ephy_tree_model_sort_get_type ())
+#define EPHY_TREE_MODEL_SORT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EPHY_TYPE_TREE_MODEL_SORT, EphyTreeModelSort))
+#define EPHY_TREE_MODEL_SORT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EPHY_TYPE_TREE_MODEL_SORT, EphyTreeModelSortClass))
+#define EPHY_IS_TREE_MODEL_SORT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EPHY_TYPE_TREE_MODEL_SORT))
+#define EPHY_IS_TREE_MODEL_SORT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EPHY_TYPE_TREE_MODEL_SORT))
+#define EPHY_TREE_MODEL_SORT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EPHY_TYPE_TREE_MODEL_SORT, EphyTreeModelSortClass))
+
+typedef struct EphyTreeModelSortPrivate EphyTreeModelSortPrivate;
+
+typedef struct
+{
+ GtkTreeModelSort parent;
+
+ EphyTreeModelSortPrivate *priv;
+} EphyTreeModelSort;
+
+typedef struct
+{
+ GtkTreeModelSortClass parent_class;
+
+ void (*node_from_iter) (EphyTreeModelSort *model, GtkTreeIter *iter, void **node);
+} EphyTreeModelSortClass;
+
+GType ephy_tree_model_sort_get_type (void);
+
+GtkTreeModel *ephy_tree_model_sort_new (GtkTreeModel *child_model);
+
+
+G_END_DECLS
+
+#endif /* EPHY_TREE_MODEL_SORT_H */