diff options
author | Marco Pesenti Gritti <mpeseng@src.gnome.org> | 2002-12-31 03:29:24 +0800 |
---|---|---|
committer | Marco Pesenti Gritti <mpeseng@src.gnome.org> | 2002-12-31 03:29:24 +0800 |
commit | 6876ede98282c7db318089bfefb292aa59e55d48 (patch) | |
tree | 76b23252d04da232d0ebf22e53bfe3e022686af9 /lib | |
download | gsoc2013-epiphany-6876ede98282c7db318089bfefb292aa59e55d48.tar.gz gsoc2013-epiphany-6876ede98282c7db318089bfefb292aa59e55d48.tar.zst gsoc2013-epiphany-6876ede98282c7db318089bfefb292aa59e55d48.zip |
Initial revision
Diffstat (limited to 'lib')
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 */ |