diff options
author | Marco Pesenti Gritti <mpeseng@src.gnome.org> | 2002-12-31 03:29:24 +0800 |
---|---|---|
committer | Marco Pesenti Gritti <mpeseng@src.gnome.org> | 2002-12-31 03:29:24 +0800 |
commit | 6876ede98282c7db318089bfefb292aa59e55d48 (patch) | |
tree | 76b23252d04da232d0ebf22e53bfe3e022686af9 /lib/widgets | |
download | gsoc2013-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/.cvsignore | 6 | ||||
-rw-r--r-- | lib/widgets/Makefile.am | 30 | ||||
-rw-r--r-- | lib/widgets/eggtreemodelfilter.c | 2560 | ||||
-rw-r--r-- | lib/widgets/eggtreemodelfilter.h | 123 | ||||
-rw-r--r-- | lib/widgets/eggtreemultidnd.c | 415 | ||||
-rw-r--r-- | lib/widgets/eggtreemultidnd.h | 78 | ||||
-rw-r--r-- | lib/widgets/ephy-autocompletion-window.c | 854 | ||||
-rw-r--r-- | lib/widgets/ephy-autocompletion-window.h | 87 | ||||
-rw-r--r-- | lib/widgets/ephy-ellipsizing-label.c | 774 | ||||
-rw-r--r-- | lib/widgets/ephy-ellipsizing-label.h | 71 | ||||
-rw-r--r-- | lib/widgets/ephy-location-entry.c | 700 | ||||
-rw-r--r-- | lib/widgets/ephy-location-entry.h | 74 | ||||
-rw-r--r-- | lib/widgets/ephy-notebook.c | 843 | ||||
-rw-r--r-- | lib/widgets/ephy-notebook.h | 99 | ||||
-rw-r--r-- | lib/widgets/ephy-spinner.c | 897 | ||||
-rw-r--r-- | lib/widgets/ephy-spinner.h | 78 | ||||
-rw-r--r-- | lib/widgets/ephy-tree-model-sort.c | 240 | ||||
-rw-r--r-- | lib/widgets/ephy-tree-model-sort.h | 59 |
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 */ |