aboutsummaryrefslogtreecommitdiffstats
path: root/lib/widgets
diff options
context:
space:
mode:
authorMarco Pesenti Gritti <mpeseng@src.gnome.org>2002-12-31 03:29:24 +0800
committerMarco Pesenti Gritti <mpeseng@src.gnome.org>2002-12-31 03:29:24 +0800
commit6876ede98282c7db318089bfefb292aa59e55d48 (patch)
tree76b23252d04da232d0ebf22e53bfe3e022686af9 /lib/widgets
downloadgsoc2013-epiphany-6876ede98282c7db318089bfefb292aa59e55d48.tar
gsoc2013-epiphany-6876ede98282c7db318089bfefb292aa59e55d48.tar.gz
gsoc2013-epiphany-6876ede98282c7db318089bfefb292aa59e55d48.tar.bz2
gsoc2013-epiphany-6876ede98282c7db318089bfefb292aa59e55d48.tar.lz
gsoc2013-epiphany-6876ede98282c7db318089bfefb292aa59e55d48.tar.xz
gsoc2013-epiphany-6876ede98282c7db318089bfefb292aa59e55d48.tar.zst
gsoc2013-epiphany-6876ede98282c7db318089bfefb292aa59e55d48.zip
Initial revision
Diffstat (limited to 'lib/widgets')
-rw-r--r--lib/widgets/.cvsignore6
-rw-r--r--lib/widgets/Makefile.am30
-rw-r--r--lib/widgets/eggtreemodelfilter.c2560
-rw-r--r--lib/widgets/eggtreemodelfilter.h123
-rw-r--r--lib/widgets/eggtreemultidnd.c415
-rw-r--r--lib/widgets/eggtreemultidnd.h78
-rw-r--r--lib/widgets/ephy-autocompletion-window.c854
-rw-r--r--lib/widgets/ephy-autocompletion-window.h87
-rw-r--r--lib/widgets/ephy-ellipsizing-label.c774
-rw-r--r--lib/widgets/ephy-ellipsizing-label.h71
-rw-r--r--lib/widgets/ephy-location-entry.c700
-rw-r--r--lib/widgets/ephy-location-entry.h74
-rw-r--r--lib/widgets/ephy-notebook.c843
-rw-r--r--lib/widgets/ephy-notebook.h99
-rw-r--r--lib/widgets/ephy-spinner.c897
-rw-r--r--lib/widgets/ephy-spinner.h78
-rw-r--r--lib/widgets/ephy-tree-model-sort.c240
-rw-r--r--lib/widgets/ephy-tree-model-sort.h59
18 files changed, 7988 insertions, 0 deletions
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 */