diff options
-rw-r--r-- | gtk/Makefile.am | 7 | ||||
-rw-r--r-- | gtk/gtk.h | 3 | ||||
-rw-r--r-- | gtk/gtkcelllayout.c | 168 | ||||
-rw-r--r-- | gtk/gtkcelllayout.h | 96 | ||||
-rw-r--r-- | gtk/gtkentry.c | 256 | ||||
-rw-r--r-- | gtk/gtkentry.h | 5 | ||||
-rw-r--r-- | gtk/gtkentrycompletion.c | 956 | ||||
-rw-r--r-- | gtk/gtkentrycompletion.h | 103 | ||||
-rw-r--r-- | gtk/gtkentryprivate.h | 65 | ||||
-rw-r--r-- | gtk/gtkmarshalers.list | 1 | ||||
-rw-r--r-- | gtk/gtktreemodelfilter.c | 2746 | ||||
-rw-r--r-- | gtk/gtktreemodelfilter.h | 99 | ||||
-rw-r--r-- | gtk/gtktreeviewcolumn.c | 332 | ||||
-rw-r--r-- | po/POTFILES.in | 3 |
14 files changed, 4727 insertions, 113 deletions
diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 3d0fc73cc..abca8097b 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -102,6 +102,7 @@ gtk_public_h_sources = \ gtkbutton.h \ gtkcalendar.h \ gtkcelleditable.h \ + gtkcelllayout.h \ gtkcellrenderer.h \ gtkcellrendererpixbuf.h \ gtkcellrenderertext.h \ @@ -124,6 +125,7 @@ gtk_public_h_sources = \ gtkdrawingarea.h \ gtkeditable.h \ gtkentry.h \ + gtkentrycompletion.h \ gtkenums.h \ gtkeventbox.h \ gtkexpander.h \ @@ -221,6 +223,7 @@ gtk_public_h_sources = \ gtktreednd.h \ gtktreeitem.h \ gtktreemodel.h \ + gtktreemodelfilter.h \ gtktreemodelsort.h \ gtktreeselection.h \ gtktreesortable.h \ @@ -241,6 +244,7 @@ gtk_public_h_sources = \ # GTK+ header files that don't get installed gtk_private_h_sources = \ + gtkentryprivate.h \ gtkrbtree.h \ gtktextbtree.h \ gtktextchildprivate.h \ @@ -277,6 +281,7 @@ gtk_c_sources = \ gtkbox.c \ gtkbutton.c \ gtkcalendar.c \ + gtkcelllayout.c \ gtkcellrenderer.c \ gtkcelleditable.c \ gtkcellrenderertext.c \ @@ -299,6 +304,7 @@ gtk_c_sources = \ gtkdrawingarea.c \ gtkeditable.c \ gtkentry.c \ + gtkentrycompletion.c \ gtkeventbox.c \ gtkexpander.c \ gtkfilesel.c \ @@ -403,6 +409,7 @@ gtk_c_sources = \ gtktreedatalist.c \ gtktreednd.c \ gtktreemodel.c \ + gtktreemodelfilter.c \ gtktreemodelsort.c \ gtktreeselection.c \ gtktreesortable.c \ @@ -43,6 +43,7 @@ #include <gtk/gtkbox.h> #include <gtk/gtkbutton.h> #include <gtk/gtkcalendar.h> +#include <gtk/gtkcelllayout.h> #include <gtk/gtkcellrenderer.h> #include <gtk/gtkcellrendererpixbuf.h> #include <gtk/gtkcellrenderertext.h> @@ -63,6 +64,7 @@ #include <gtk/gtkdrawingarea.h> #include <gtk/gtkeditable.h> #include <gtk/gtkentry.h> +#include <gtk/gtkentrycompletion.h> #include <gtk/gtkenums.h> #include <gtk/gtkeventbox.h> #include <gtk/gtkexpander.h> @@ -152,6 +154,7 @@ #include <gtk/gtktreednd.h> #include <gtk/gtktreeitem.h> #include <gtk/gtktreemodel.h> +#include <gtk/gtktreemodelfilter.h> #include <gtk/gtktreemodelsort.h> #include <gtk/gtktreeselection.h> #include <gtk/gtktreestore.h> diff --git a/gtk/gtkcelllayout.c b/gtk/gtkcelllayout.c new file mode 100644 index 000000000..043b7073a --- /dev/null +++ b/gtk/gtkcelllayout.c @@ -0,0 +1,168 @@ +/* gtkcelllayout.c + * Copyright (C) 2003 Kristian Rietveld <kris@gtk.org> + * + * 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 "gtkcelllayout.h" + +GType +gtk_cell_layout_get_type (void) +{ + static GType cell_layout_type = 0; + + if (! cell_layout_type) + { + static const GTypeInfo cell_layout_info = + { + sizeof (GtkCellLayoutIface), + NULL, + NULL, + NULL, + NULL, + NULL, + 0, + 0, + NULL + }; + + cell_layout_type = + g_type_register_static (G_TYPE_INTERFACE, "GtkCellLayout", + &cell_layout_info, 0); + + g_type_interface_add_prerequisite (cell_layout_type, G_TYPE_OBJECT); + } + + return cell_layout_type; +} + + +void +gtk_cell_layout_pack_start (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + gboolean expand) +{ + g_return_if_fail (GTK_IS_CELL_LAYOUT (cell_layout)); + g_return_if_fail (GTK_IS_CELL_RENDERER (cell)); + + (* GTK_CELL_LAYOUT_GET_IFACE (cell_layout)->pack_start) (cell_layout, + cell, + expand); +} + +void +gtk_cell_layout_pack_end (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + gboolean expand) +{ + g_return_if_fail (GTK_IS_CELL_LAYOUT (cell_layout)); + g_return_if_fail (GTK_IS_CELL_RENDERER (cell)); + + (* GTK_CELL_LAYOUT_GET_IFACE (cell_layout)->pack_end) (cell_layout, + cell, + expand); +} + +void +gtk_cell_layout_clear (GtkCellLayout *cell_layout) +{ + g_return_if_fail (GTK_IS_CELL_LAYOUT (cell_layout)); + + (* GTK_CELL_LAYOUT_GET_IFACE (cell_layout)->clear) (cell_layout); +} + +static void +gtk_cell_layout_set_attributesv (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + va_list args) +{ + gchar *attribute; + gint column; + GtkCellLayoutIface *iface; + + attribute = va_arg (args, gchar *); + + iface = GTK_CELL_LAYOUT_GET_IFACE (cell_layout); + + (* iface->clear_attributes) (cell_layout, cell); + + while (attribute != NULL) + { + column = va_arg (args, gint); + (* iface->add_attribute) (cell_layout, cell, attribute, column); + attribute = va_arg (args, gchar *); + } +} + +void +gtk_cell_layout_set_attributes (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + ...) +{ + va_list args; + + g_return_if_fail (GTK_IS_CELL_LAYOUT (cell_layout)); + g_return_if_fail (GTK_IS_CELL_RENDERER (cell)); + + va_start (args, cell); + gtk_cell_layout_set_attributesv (cell_layout, cell, args); + va_end (args); +} + +void +gtk_cell_layout_add_attribute (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + const gchar *attribute, + gint column) +{ + g_return_if_fail (GTK_IS_CELL_LAYOUT (cell_layout)); + g_return_if_fail (GTK_IS_CELL_RENDERER (cell)); + g_return_if_fail (attribute != NULL); + g_return_if_fail (column >= 0); + + (* GTK_CELL_LAYOUT_GET_IFACE (cell_layout)->add_attribute) (cell_layout, + cell, + attribute, + column); +} + +void +gtk_cell_layout_set_cell_data_func (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + GtkCellLayoutDataFunc func, + gpointer func_data, + GDestroyNotify destroy) +{ + g_return_if_fail (GTK_IS_CELL_LAYOUT (cell_layout)); + g_return_if_fail (GTK_IS_CELL_RENDERER (cell)); + + (* GTK_CELL_LAYOUT_GET_IFACE (cell_layout)->set_cell_data_func) (cell_layout, + cell, + func, + func_data, + destroy); +} + +void +gtk_cell_layout_clear_attributes (GtkCellLayout *cell_layout, + GtkCellRenderer *cell) +{ + g_return_if_fail (GTK_IS_CELL_LAYOUT (cell_layout)); + g_return_if_fail (GTK_IS_CELL_RENDERER (cell)); + + (* GTK_CELL_LAYOUT_GET_IFACE (cell_layout)->clear_attributes) (cell_layout, + cell); +} diff --git a/gtk/gtkcelllayout.h b/gtk/gtkcelllayout.h new file mode 100644 index 000000000..5e60e2e36 --- /dev/null +++ b/gtk/gtkcelllayout.h @@ -0,0 +1,96 @@ +/* gtkcelllayout.h + * Copyright (C) 2003 Kristian Rietveld <kris@gtk.org> + * + * 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 __GTK_CELL_LAYOUT_H__ +#define __GTK_CELL_LAYOUT_H__ + +#include <glib-object.h> + +#include <gtk/gtkcellrenderer.h> +#include <gtk/gtktreeviewcolumn.h> + +G_BEGIN_DECLS + +#define GTK_TYPE_CELL_LAYOUT (gtk_cell_layout_get_type ()) +#define GTK_CELL_LAYOUT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CELL_LAYOUT, GtkCellLayout)) +#define GTK_IS_CELL_LAYOUT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_CELL_LAYOUT)) +#define GTK_CELL_LAYOUT_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GTK_TYPE_CELL_LAYOUT, GtkCellLayoutIface)) + +typedef struct _GtkCellLayout GtkCellLayout; /* dummy typedef */ +typedef struct _GtkCellLayoutIface GtkCellLayoutIface; + +/* keep in sync with GtkTreeCellDataFunc */ +typedef void (* GtkCellLayoutDataFunc) (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gpointer data); + +struct _GtkCellLayoutIface +{ + GTypeInterface g_iface; + + /* Virtual Table */ + void (* pack_start) (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + gboolean expand); + void (* pack_end) (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + gboolean expand); + void (* clear) (GtkCellLayout *cell_layout); + void (* add_attribute) (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + const gchar *attribute, + gint column); + void (* set_cell_data_func) (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + GtkCellLayoutDataFunc func, + gpointer func_data, + GDestroyNotify destroy); + void (* clear_attributes) (GtkCellLayout *cell_layout, + GtkCellRenderer *cell); +}; + +GType gtk_cell_layout_get_type (void); +void gtk_cell_layout_pack_start (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + gboolean expand); +void gtk_cell_layout_pack_end (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + gboolean expand); +void gtk_cell_layout_clear (GtkCellLayout *cell_layout); +void gtk_cell_layout_set_attributes (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + ...); +void gtk_cell_layout_add_attribute (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + const gchar *attribute, + gint column); +void gtk_cell_layout_set_cell_data_func (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + GtkCellLayoutDataFunc func, + gpointer func_data, + GDestroyNotify destroy); +void gtk_cell_layout_clear_attributes (GtkCellLayout *cell_layout, + GtkCellRenderer *cell); + + +G_END_DECLS + +#endif /* __GTK_CELL_LAYOUT_H__ */ diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c index 24fa80877..2ec396e4e 100644 --- a/gtk/gtkentry.c +++ b/gtk/gtkentry.c @@ -47,10 +47,17 @@ #include "gtkstock.h" #include "gtktextutil.h" #include "gtkwindow.h" +#include "gtktreeview.h" +#include "gtktreeselection.h" +#include "gtkentryprivate.h" +#include "gtkcelllayout.h" + +#define GTK_ENTRY_COMPLETION_KEY "gtk-entry-completion-key" #define MIN_ENTRY_WIDTH 150 #define DRAW_TIMEOUT 20 #define INNER_BORDER 2 +#define COMPLETION_TIMEOUT 300 /* Initial size of buffer, in bytes */ #define MIN_SIZE 16 @@ -936,6 +943,8 @@ gtk_entry_finalize (GObject *object) { GtkEntry *entry = GTK_ENTRY (object); + gtk_entry_set_completion (entry, NULL); + if (entry->cached_layout) g_object_unref (entry->cached_layout); @@ -1664,6 +1673,7 @@ gtk_entry_focus_out (GtkWidget *widget, GdkEventFocus *event) { GtkEntry *entry = GTK_ENTRY (widget); + GtkEntryCompletion *completion; gtk_widget_queue_draw (widget); @@ -1675,6 +1685,10 @@ gtk_entry_focus_out (GtkWidget *widget, g_signal_handlers_disconnect_by_func (gdk_keymap_get_for_display (gtk_widget_get_display (widget)), gtk_entry_keymap_direction_changed, entry); + + completion = gtk_entry_get_completion (entry); + if (completion) + _gtk_entry_completion_popdown (completion); return FALSE; } @@ -4451,3 +4465,245 @@ gtk_entry_pend_cursor_blink (GtkEntry *entry) show_cursor (entry); } } + +/* completion */ +static gint +gtk_entry_completion_timeout (gpointer data) +{ + GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (data); + + GDK_THREADS_ENTER (); + + completion->priv->completion_timeout = 0; + + if (strlen (gtk_entry_get_text (GTK_ENTRY (completion->priv->entry))) >= completion->priv->minimum_key_length) + { + gint matches; + gint actions; + GtkTreeSelection *s; + + gtk_entry_completion_complete (completion); + matches = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->filter_model), NULL); + + gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->tree_view))); + + s = gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->action_view)); + + gtk_tree_selection_unselect_all (s); + + actions = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->actions), NULL); + + if (matches > 0 || actions > 0) + _gtk_entry_completion_popup (completion); + } + + GDK_THREADS_LEAVE (); + + return FALSE; +} + +static gboolean +gtk_entry_completion_key_press (GtkWidget *widget, + GdkEventKey *event, + gpointer user_data) +{ + gint matches, actions = 0; + GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data); + + if (!GTK_WIDGET_MAPPED (completion->priv->popup_window)) + return FALSE; + + matches = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->filter_model), NULL); + + if (completion->priv->actions) + actions = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->actions), NULL); + + if (event->keyval == GDK_Up || event->keyval == GDK_Down) + { + GtkTreePath *path = NULL; + + if (event->keyval == GDK_Up) + { + completion->priv->current_selected--; + if (completion->priv->current_selected < 0) + completion->priv->current_selected = 0; + } + else + { + completion->priv->current_selected++; + if (completion->priv->current_selected >= matches + actions) + completion->priv->current_selected = 0; + } + + if (completion->priv->current_selected < matches) + { + gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->action_view))); + + path = gtk_tree_path_new_from_indices (completion->priv->current_selected, -1); + gtk_tree_view_set_cursor (GTK_TREE_VIEW (completion->priv->tree_view), + path, NULL, FALSE); + } + else if (completion->priv->current_selected - matches >= 0) + { + gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->tree_view))); + + path = gtk_tree_path_new_from_indices (completion->priv->current_selected - matches, -1); + gtk_tree_view_set_cursor (GTK_TREE_VIEW (completion->priv->action_view), + path, NULL, FALSE); + } + + gtk_tree_path_free (path); + + return TRUE; + } + else if (event->keyval == GDK_ISO_Enter || + event->keyval == GDK_Return || + event->keyval == GDK_Escape) + { + _gtk_entry_completion_popdown (completion); + + if (event->keyval == GDK_Escape) + return TRUE; + + if (completion->priv->current_selected < matches) + { + GtkTreeIter iter; + GtkTreeModel *model = NULL; + GtkTreeSelection *sel; + gboolean entry_set; + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->tree_view)); + gtk_tree_selection_get_selected (sel, &model, &iter); + + g_signal_emit_by_name (completion, "match_selected", + model, &iter, &entry_set); + + if (!entry_set) + { + gchar *str = NULL; + + gtk_tree_model_get (model, &iter, + completion->priv->text_column, &str, + -1); + + g_signal_handler_block (widget, completion->priv->changed_id); + gtk_entry_set_text (GTK_ENTRY (widget), str); + g_signal_handler_unblock (widget, completion->priv->changed_id); + + /* move the cursor to the end */ + gtk_editable_set_position (GTK_EDITABLE (widget), -1); + + g_free (str); + } + + return TRUE; + } + else if (completion->priv->current_selected - matches >= 0) + { + GtkTreePath *path; + + path = gtk_tree_path_new_from_indices (completion->priv->current_selected - matches, -1); + + g_signal_emit_by_name (completion, "action_activated", + gtk_tree_path_get_indices (path)[0]); + gtk_tree_path_free (path); + } + } + + return FALSE; +} + +static void +gtk_entry_completion_changed (GtkWidget *entry, + gpointer user_data) +{ + GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data); + + if (GTK_WIDGET_MAPPED (completion->priv->popup_window)) + _gtk_entry_completion_popdown (completion); + + /* (re)install completion timeout */ + if (completion->priv->completion_timeout) + g_source_remove (completion->priv->completion_timeout); + + if (!gtk_entry_get_text (GTK_ENTRY (entry))) + return; + + /* no need to normalize for this test */ + if (! strcmp ("", gtk_entry_get_text (GTK_ENTRY (entry)))) + return; + + completion->priv->completion_timeout = + g_timeout_add (COMPLETION_TIMEOUT, + gtk_entry_completion_timeout, + completion); +} + +void +gtk_entry_set_completion (GtkEntry *entry, + GtkEntryCompletion *completion) +{ + GtkEntryCompletion *old; + + g_return_if_fail (GTK_IS_ENTRY (entry)); + g_return_if_fail (!completion || GTK_IS_ENTRY_COMPLETION (completion)); + + old = gtk_entry_get_completion (entry); + + if (old) + { + if (old->priv->completion_timeout) + { + g_source_remove (old->priv->completion_timeout); + old->priv->completion_timeout = 0; + } + + if (GTK_WIDGET_MAPPED (old->priv->popup_window)) + _gtk_entry_completion_popdown (old); + + gtk_cell_layout_clear (GTK_CELL_LAYOUT (old)); + old->priv->text_column = -1; + + if (g_signal_handler_is_connected (entry, old->priv->changed_id)) + g_signal_handler_disconnect (entry, old->priv->changed_id); + if (g_signal_handler_is_connected (entry, old->priv->key_press_id)) + g_signal_handler_disconnect (entry, old->priv->key_press_id); + + old->priv->entry = NULL; + + g_object_unref (old); + } + + if (!completion) + { + g_object_set_data (G_OBJECT (entry), GTK_ENTRY_COMPLETION_KEY, NULL); + return; + } + + /* hook into the entry */ + g_object_ref (completion); + + completion->priv->changed_id = + g_signal_connect (entry, "changed", + G_CALLBACK (gtk_entry_completion_changed), completion); + + completion->priv->key_press_id = + g_signal_connect (entry, "key_press_event", + G_CALLBACK (gtk_entry_completion_key_press), completion); + + completion->priv->entry = GTK_WIDGET (entry); + g_object_set_data (G_OBJECT (entry), GTK_ENTRY_COMPLETION_KEY, completion); +} + +GtkEntryCompletion * +gtk_entry_get_completion (GtkEntry *entry) +{ + GtkEntryCompletion *completion; + + g_return_val_if_fail (GTK_IS_ENTRY (entry), NULL); + + completion = GTK_ENTRY_COMPLETION (g_object_get_data (G_OBJECT (entry), + GTK_ENTRY_COMPLETION_KEY)); + + return completion; +} diff --git a/gtk/gtkentry.h b/gtk/gtkentry.h index c8f4ef1a3..d7b2f2547 100644 --- a/gtk/gtkentry.h +++ b/gtk/gtkentry.h @@ -32,6 +32,7 @@ #include <gtk/gtkeditable.h> #include <gtk/gtkimcontext.h> #include <gtk/gtkmenu.h> +#include <gtk/gtkentrycompletion.h> #include <pango/pango.h> #ifdef __cplusplus @@ -182,6 +183,10 @@ void gtk_entry_get_layout_offsets (GtkEntry *entry, gint *x, gint *y); +void gtk_entry_set_completion (GtkEntry *entry, + GtkEntryCompletion *completion); +GtkEntryCompletion *gtk_entry_get_completion (GtkEntry *entry); + /* Deprecated compatibility functions */ diff --git a/gtk/gtkentrycompletion.c b/gtk/gtkentrycompletion.c new file mode 100644 index 000000000..d4c4ad3d2 --- /dev/null +++ b/gtk/gtkentrycompletion.c @@ -0,0 +1,956 @@ +/* gtkentrycompletion.c + * Copyright (C) 2003 Kristian Rietveld <kris@gtk.org> + * + * 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 "gtkentrycompletion.h" +#include "gtkentryprivate.h" +#include "gtkcelllayout.h" + +#include <gtk/gtkintl.h> +#include <gtk/gtkcellrenderertext.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtkscrolledwindow.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkwindow.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkmain.h> +#include <gtk/gtksignal.h> +#include <gtk/gtkmarshalers.h> + +#include <string.h> + + +/* signals */ +enum +{ + MATCH_SELECTED, + ACTION_ACTIVATED, + LAST_SIGNAL +}; + +/* properties */ +enum +{ + PROP_0, + PROP_MODEL, + PROP_MINIMUM_KEY_LENGTH +}; + + +static void gtk_entry_completion_class_init (GtkEntryCompletionClass *klass); +static void gtk_entry_completion_cell_layout_init (GtkCellLayoutIface *iface); +static void gtk_entry_completion_init (GtkEntryCompletion *completion); +static void gtk_entry_completion_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_entry_completion_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void gtk_entry_completion_finalize (GObject *object); + +static void gtk_entry_completion_pack_start (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + gboolean expand); +static void gtk_entry_completion_pack_end (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + gboolean expand); +static void gtk_entry_completion_clear (GtkCellLayout *cell_layout); +static void gtk_entry_completion_add_attribute (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + const char *attribute, + gint column); +static void gtk_entry_completion_set_cell_data_func (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + GtkCellLayoutDataFunc func, + gpointer func_data, + GDestroyNotify destroy); +static void gtk_entry_completion_clear_attributes (GtkCellLayout *cell_layout, + GtkCellRenderer *cell); + +static gboolean gtk_entry_completion_visible_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data); +static gboolean gtk_entry_completion_popup_key_press (GtkWidget *widget, + GdkEventKey *event, + gpointer user_data); +static gboolean gtk_entry_completion_popup_button_press (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data); +static gboolean gtk_entry_completion_list_button_press (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data); +static gboolean gtk_entry_completion_action_button_press (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data); + +static void gtk_entry_completion_insert_action (GtkEntryCompletion *completion, + gint index, + gchar *string, + gboolean markup); +static void gtk_entry_completion_action_data_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data); + + +static guint entry_completion_signals[LAST_SIGNAL] = { 0 }; + + +GType +gtk_entry_completion_get_type (void) +{ + static GType entry_completion_type = 0; + + if (!entry_completion_type) + { + static const GTypeInfo entry_completion_info = + { + sizeof (GtkEntryCompletionClass), + NULL, + NULL, + (GClassInitFunc) gtk_entry_completion_class_init, + NULL, + NULL, + sizeof (GtkEntryCompletion), + 0, + (GInstanceInitFunc) gtk_entry_completion_init + }; + + static const GInterfaceInfo cell_layout_info = + { + (GInterfaceInitFunc) gtk_entry_completion_cell_layout_init, + NULL, + NULL + }; + + entry_completion_type = + g_type_register_static (G_TYPE_OBJECT, "GtkEntryCompletion", + &entry_completion_info, 0); + + g_type_add_interface_static (entry_completion_type, + GTK_TYPE_CELL_LAYOUT, + &cell_layout_info); + } + + return entry_completion_type; +} + +static void +gtk_entry_completion_class_init (GtkEntryCompletionClass *klass) +{ + GObjectClass *object_class; + + object_class = (GObjectClass *)klass; + + object_class->set_property = gtk_entry_completion_set_property; + object_class->get_property = gtk_entry_completion_get_property; + object_class->finalize = gtk_entry_completion_finalize; + + entry_completion_signals[MATCH_SELECTED] = + g_signal_new ("match_selected", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkEntryCompletionClass, match_selected), + _gtk_boolean_handled_accumulator, NULL, + _gtk_marshal_BOOLEAN__OBJECT_BOXED, + G_TYPE_BOOLEAN, 2, + GTK_TYPE_TREE_MODEL, + GTK_TYPE_TREE_ITER); + entry_completion_signals[ACTION_ACTIVATED] = + g_signal_new ("action_activated", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkEntryCompletionClass, action_activated), + NULL, NULL, + _gtk_marshal_NONE__INT, + G_TYPE_NONE, 1, + G_TYPE_INT); + + g_object_class_install_property (object_class, + PROP_MODEL, + g_param_spec_object ("model", + _("Completion Model"), + _("The model to find matches in"), + GTK_TYPE_TREE_MODEL, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_MINIMUM_KEY_LENGTH, + g_param_spec_int ("minimum_key_length", + _("Minimum Key Length"), + _("Minimum length of the search key in order to look up matches"), + -1, + G_MAXINT, + 1, + G_PARAM_READWRITE)); + + g_type_class_add_private (object_class, sizeof (GtkEntryCompletionPrivate)); +} + +static void +gtk_entry_completion_cell_layout_init (GtkCellLayoutIface *iface) +{ + iface->pack_start = gtk_entry_completion_pack_start; + iface->pack_end = gtk_entry_completion_pack_end; + iface->clear = gtk_entry_completion_clear; + iface->add_attribute = gtk_entry_completion_add_attribute; + iface->set_cell_data_func = gtk_entry_completion_set_cell_data_func; + iface->clear_attributes = gtk_entry_completion_clear_attributes; +} + +static void +gtk_entry_completion_init (GtkEntryCompletion *completion) +{ + GtkCellRenderer *cell; + GtkTreeSelection *sel; + GtkEntryCompletionPrivate *priv; + + /* yes, also priv, need to keep the code readable */ + priv = completion->priv = GTK_ENTRY_COMPLETION_GET_PRIVATE (completion); + + priv->minimum_key_length = 1; + priv->text_column = -1; + + /* completions */ + priv->filter_model = NULL; + + priv->tree_view = gtk_tree_view_new (); + g_signal_connect (priv->tree_view, "button_press_event", + G_CALLBACK (gtk_entry_completion_list_button_press), + completion); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->tree_view), FALSE); + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_view)); + gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE); + gtk_tree_selection_unselect_all (sel); + + priv->column = gtk_tree_view_column_new (); + gtk_tree_view_append_column (GTK_TREE_VIEW (priv->tree_view), priv->column); + + priv->scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (priv->scrolled_window), + GTK_SHADOW_ETCHED_IN); + + + /* actions */ + priv->actions = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN); + + priv->action_view = + gtk_tree_view_new_with_model (GTK_TREE_MODEL (priv->actions)); + g_signal_connect (priv->action_view, "button_press_event", + G_CALLBACK (gtk_entry_completion_action_button_press), + completion); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->action_view), FALSE); + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->action_view)); + gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE); + gtk_tree_selection_unselect_all (sel); + + cell = gtk_cell_renderer_text_new (); + g_object_set (cell, "cell_background_gdk", + &priv->tree_view->style->bg[GTK_STATE_NORMAL], + NULL); + gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (priv->action_view), + 0, "", + cell, + gtk_entry_completion_action_data_func, + NULL, + NULL); + + /* pack it all */ + priv->popup_window = gtk_window_new (GTK_WINDOW_POPUP); + gtk_window_set_resizable (GTK_WINDOW (priv->popup_window), FALSE); + g_signal_connect (priv->popup_window, "key_press_event", + G_CALLBACK (gtk_entry_completion_popup_key_press), + completion); + g_signal_connect (priv->popup_window, "button_press_event", + G_CALLBACK (gtk_entry_completion_popup_button_press), + completion); + + priv->vbox = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (priv->popup_window), priv->vbox); + + gtk_container_add (GTK_CONTAINER (priv->scrolled_window), priv->tree_view); + gtk_box_pack_start (GTK_BOX (priv->vbox), priv->scrolled_window, + TRUE, TRUE, 0); + + /* we don't want to see the action treeview when no actions have + * been inserted, so we pack the action treeview after the first + * action has been added + */ +} + +static void +gtk_entry_completion_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (object); + + switch (prop_id) + { + case PROP_MODEL: + gtk_entry_completion_set_model (completion, + g_value_get_object (value)); + break; + + case PROP_MINIMUM_KEY_LENGTH: + gtk_entry_completion_set_minimum_key_length (completion, + g_value_get_int (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_entry_completion_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (object); + + switch (prop_id) + { + case PROP_MODEL: + g_value_set_object (value, + gtk_entry_completion_get_model (completion)); + break; + + case PROP_MINIMUM_KEY_LENGTH: + g_value_set_int (value, gtk_entry_completion_get_minimum_key_length (completion)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_entry_completion_finalize (GObject *object) +{ + GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (object); + + if (completion->priv->tree_view) + gtk_widget_destroy (completion->priv->tree_view); + + if (completion->priv->entry) + gtk_entry_set_completion (GTK_ENTRY (completion->priv->entry), NULL); + + if (completion->priv->actions) + g_object_unref (completion->priv->actions); + + if (completion->priv->case_normalized_key) + g_free (completion->priv->case_normalized_key); + + if (completion->priv->popup_window) + gtk_widget_destroy (completion->priv->popup_window); +} + +/* implement cell layout interface */ +static void +gtk_entry_completion_pack_start (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + gboolean expand) +{ + GtkEntryCompletionPrivate *priv; + + g_return_if_fail (GTK_IS_ENTRY_COMPLETION (cell_layout)); + + priv = GTK_ENTRY_COMPLETION_GET_PRIVATE (cell_layout); + + gtk_tree_view_column_pack_start (priv->column, cell, expand); +} + +static void +gtk_entry_completion_pack_end (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + gboolean expand) +{ + GtkEntryCompletionPrivate *priv; + + g_return_if_fail (GTK_IS_ENTRY_COMPLETION (cell_layout)); + + priv = GTK_ENTRY_COMPLETION_GET_PRIVATE (cell_layout); + + gtk_tree_view_column_pack_end (priv->column, cell, expand); +} + +static void +gtk_entry_completion_clear (GtkCellLayout *cell_layout) +{ + GtkEntryCompletionPrivate *priv; + + g_return_if_fail (GTK_IS_ENTRY_COMPLETION (cell_layout)); + + priv = GTK_ENTRY_COMPLETION_GET_PRIVATE (cell_layout); + + gtk_tree_view_column_clear (priv->column); +} + +static void +gtk_entry_completion_add_attribute (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + const gchar *attribute, + gint column) +{ + GtkEntryCompletionPrivate *priv; + + g_return_if_fail (GTK_IS_ENTRY_COMPLETION (cell_layout)); + + priv = GTK_ENTRY_COMPLETION_GET_PRIVATE (cell_layout); + + gtk_tree_view_column_add_attribute (priv->column, cell, attribute, column); +} + +static void +gtk_entry_completion_set_cell_data_func (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + GtkCellLayoutDataFunc func, + gpointer func_data, + GDestroyNotify destroy) +{ + GtkEntryCompletionPrivate *priv; + + g_return_if_fail (GTK_IS_ENTRY_COMPLETION (cell_layout)); + + priv = GTK_ENTRY_COMPLETION_GET_PRIVATE (cell_layout); + + gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->column), + cell, func, func_data, destroy); +} + +static void +gtk_entry_completion_clear_attributes (GtkCellLayout *cell_layout, + GtkCellRenderer *cell) +{ + GtkEntryCompletionPrivate *priv; + + g_return_if_fail (GTK_IS_ENTRY_COMPLETION (cell_layout)); + + priv = GTK_ENTRY_COMPLETION_GET_PRIVATE (cell_layout); + + gtk_tree_view_column_clear_attributes (priv->column, cell); +} + +/* all those callbacks */ +static gboolean +gtk_entry_completion_default_completion_func (GtkEntryCompletion *completion, + const gchar *key, + GtkTreeIter *iter, + gpointer user_data) +{ + gchar *item = NULL; + gchar *normalized_string; + gchar *case_normalized_string; + + gboolean ret = FALSE; + + GtkTreeModel *model; + + model = gtk_tree_model_filter_get_model (completion->priv->filter_model); + + gtk_tree_model_get (model, iter, + completion->priv->text_column, &item, + -1); + + normalized_string = g_utf8_normalize (item, -1, G_NORMALIZE_ALL); + case_normalized_string = g_utf8_casefold (normalized_string, -1); + + if (!strncmp (key, case_normalized_string, strlen (key))) + ret = TRUE; + + g_free (item); + g_free (normalized_string); + g_free (case_normalized_string); + + return ret; +} + +static gboolean +gtk_entry_completion_visible_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gboolean ret = FALSE; + + GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (data); + + if (!completion->priv->case_normalized_key) + return ret; + + if (completion->priv->text_column >= 0) + ret = gtk_entry_completion_default_completion_func (completion, + completion->priv->case_normalized_key, + iter, + NULL); + + else if (completion->priv->match_func) + ret = (* completion->priv->match_func) (completion, + completion->priv->case_normalized_key, + iter, + completion->priv->match_data); + + return ret; +} + +static gboolean +gtk_entry_completion_popup_key_press (GtkWidget *widget, + GdkEventKey *event, + gpointer user_data) +{ + GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data); + + if (!GTK_WIDGET_MAPPED (completion->priv->popup_window)) + return FALSE; + + /* propagate event to the entry */ + gtk_widget_event (completion->priv->entry, (GdkEvent *)event); + + return TRUE; +} + +static gboolean +gtk_entry_completion_popup_button_press (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data) +{ + GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data); + + if (!GTK_WIDGET_MAPPED (completion->priv->popup_window)) + return FALSE; + + /* if we come here, it's usually time to popdown */ + _gtk_entry_completion_popdown (completion); + + return TRUE; +} + +static gboolean +gtk_entry_completion_list_button_press (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data) +{ + GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data); + GtkTreePath *path = NULL; + + if (!GTK_WIDGET_MAPPED (completion->priv->popup_window)) + return FALSE; + + if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), + event->x, event->y, + &path, NULL, NULL, NULL)) + { + gboolean entry_set; + GtkTreeIter iter; + + gtk_tree_model_get_iter (GTK_TREE_MODEL (completion->priv->filter_model), + &iter, path); + gtk_tree_path_free (path); + + g_signal_emit (completion, entry_completion_signals[MATCH_SELECTED], + 0, GTK_TREE_MODEL (completion->priv->filter_model), + &iter, &entry_set); + + if (!entry_set) + { + gchar *str = NULL; + + gtk_tree_model_get (GTK_TREE_MODEL (completion->priv->filter_model), + &iter, + completion->priv->text_column, &str, + -1); + + g_signal_handler_block (completion->priv->entry, + completion->priv->changed_id); + gtk_entry_set_text (GTK_ENTRY (completion->priv->entry), str); + g_signal_handler_unblock (completion->priv->entry, + completion->priv->changed_id); + + /* move cursor to the end */ + gtk_editable_set_position (GTK_EDITABLE (completion->priv->entry), + -1); + + g_free (str); + } + + _gtk_entry_completion_popdown (completion); + return TRUE; + } + + return FALSE; +} + +static gboolean +gtk_entry_completion_action_button_press (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data) +{ + GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data); + GtkTreePath *path = NULL; + + if (!GTK_WIDGET_MAPPED (completion->priv->popup_window)) + return FALSE; + + if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), + event->x, event->y, + &path, NULL, NULL, NULL)) + { + g_signal_emit (completion, entry_completion_signals[ACTION_ACTIVATED], + 0, gtk_tree_path_get_indices (path)[0]); + gtk_tree_path_free (path); + + _gtk_entry_completion_popdown (completion); + return TRUE; + } + + return FALSE; +} + +static void +gtk_entry_completion_action_data_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gchar *string = NULL; + gboolean markup; + + gtk_tree_model_get (model, iter, + 0, &string, + 1, &markup, + -1); + + if (!string) + return; + + if (markup) + g_object_set (G_OBJECT (cell), + "text", NULL, + "markup", string, + NULL); + else + g_object_set (G_OBJECT (cell), + "markup", NULL, + "text", string, + NULL); +} + +/* public API */ +GtkEntryCompletion * +gtk_entry_completion_new (void) +{ + GtkEntryCompletion *completion; + + completion = g_object_new (GTK_TYPE_ENTRY_COMPLETION, NULL); + + return completion; +} + +GtkWidget * +gtk_entry_completion_get_entry (GtkEntryCompletion *completion) +{ + g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), NULL); + + return completion->priv->entry; +} + +void +gtk_entry_completion_set_model (GtkEntryCompletion *completion, + GtkTreeModel *model) +{ + g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); + g_return_if_fail (GTK_IS_TREE_MODEL (model)); + + /* code will unref the old filter model (if any) */ + completion->priv->filter_model = + GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (model, NULL)); + gtk_tree_model_filter_set_visible_func (completion->priv->filter_model, + gtk_entry_completion_visible_func, + completion, + NULL); + gtk_tree_view_set_model (GTK_TREE_VIEW (completion->priv->tree_view), + GTK_TREE_MODEL (completion->priv->filter_model)); + g_object_unref (G_OBJECT (completion->priv->filter_model)); +} + +GtkTreeModel * +gtk_entry_completion_get_model (GtkEntryCompletion *completion) +{ + g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), NULL); + + return gtk_tree_model_filter_get_model (completion->priv->filter_model); +} + +void +gtk_entry_completion_set_match_func (GtkEntryCompletion *completion, + GtkEntryCompletionMatchFunc func, + gpointer func_data, + GDestroyNotify func_notify) +{ + g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); + + if (completion->priv->match_notify) + (* completion->priv->match_notify) (completion->priv->match_data); + + completion->priv->match_func = func; + completion->priv->match_data = func_data; + completion->priv->match_notify = func_notify; +} + +void +gtk_entry_completion_set_minimum_key_length (GtkEntryCompletion *completion, + gint length) +{ + g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); + g_return_if_fail (length >= 1); + + completion->priv->minimum_key_length = length; +} + +gint +gtk_entry_completion_get_minimum_key_length (GtkEntryCompletion *completion) +{ + g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), 0); + + return completion->priv->minimum_key_length; +} + +void +gtk_entry_completion_complete (GtkEntryCompletion *completion) +{ + gchar *tmp; + + g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); + g_return_if_fail (completion->priv->filter_model != NULL); + + if (completion->priv->case_normalized_key) + g_free (completion->priv->case_normalized_key); + + tmp = g_utf8_normalize (gtk_entry_get_text (GTK_ENTRY (completion->priv->entry)), + -1, G_NORMALIZE_ALL); + completion->priv->case_normalized_key = g_utf8_casefold (tmp, -1); + g_free (tmp); + + gtk_tree_model_filter_refilter (completion->priv->filter_model); +} + +static void +gtk_entry_completion_insert_action (GtkEntryCompletion *completion, + gint index, + gchar *string, + gboolean markup) +{ + GtkTreeIter iter; + + gtk_list_store_insert (completion->priv->actions, &iter, index); + gtk_list_store_set (completion->priv->actions, &iter, + 0, string, + 1, markup, + -1); + + if (!completion->priv->action_view->parent) + { + GtkTreePath *path = gtk_tree_path_new_from_indices (0, -1); + + gtk_tree_view_set_cursor (GTK_TREE_VIEW (completion->priv->action_view), + path, NULL, FALSE); + gtk_tree_path_free (path); + + gtk_box_pack_start (GTK_BOX (completion->priv->vbox), + completion->priv->action_view, FALSE, FALSE, 0); + gtk_widget_show (completion->priv->action_view); + } +} + +void +gtk_entry_completion_insert_action_text (GtkEntryCompletion *completion, + gint index, + gchar *text) +{ + g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); + g_return_if_fail (text != NULL); + + gtk_entry_completion_insert_action (completion, index, text, FALSE); +} + +void +gtk_entry_completion_insert_action_markup (GtkEntryCompletion *completion, + gint index, + gchar *markup) +{ + g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); + g_return_if_fail (markup != NULL); + + gtk_entry_completion_insert_action (completion, index, markup, TRUE); +} + +void +gtk_entry_completion_delete_action (GtkEntryCompletion *completion, + gint index) +{ + GtkTreeIter iter; + + g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); + g_return_if_fail (index >= 0); + + gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (completion->priv->actions), + &iter, NULL, index); + gtk_list_store_remove (completion->priv->actions, &iter); +} + +void +gtk_entry_completion_set_text_column (GtkEntryCompletion *completion, + gint column) +{ + GtkCellRenderer *cell; + + g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); + g_return_if_fail (column >= 0); + + completion->priv->text_column = column; + + cell = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion), + cell, TRUE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (completion), + cell, + "text", column); +} + +/* private */ + +/* lame copy from gtkentry.c */ +static void +get_borders (GtkEntry *entry, + gint *xborder, + gint *yborder) +{ + GtkWidget *widget = GTK_WIDGET (entry); + gint focus_width; + gboolean interior_focus; + + gtk_widget_style_get (widget, + "interior-focus", &interior_focus, + "focus-line-width", &focus_width, + NULL); + + if (entry->has_frame) + { + *xborder = widget->style->xthickness; + *yborder = widget->style->ythickness; + } + else + { + *xborder = 0; + *yborder = 0; + } + + if (!interior_focus) + { + *xborder += focus_width; + *yborder += focus_width; + } +} + +/* this function is a bit nasty */ +void +_gtk_entry_completion_popup (GtkEntryCompletion *completion) +{ + gint x, y, x_border, y_border; + gint items; + gint height; + GtkTreePath *path; + + if (GTK_WIDGET_MAPPED (completion->priv->popup_window)) + return; + + gtk_widget_show_all (completion->priv->vbox); + + gdk_window_get_origin (completion->priv->entry->window, &x, &y); + get_borders (GTK_ENTRY (completion->priv->entry), &x_border, &y_border); + + items = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->filter_model), NULL); + + items = MIN (items, 15); + + gtk_tree_view_column_cell_get_size (completion->priv->column, NULL, + NULL, NULL, NULL, &height); + + gtk_widget_set_size_request (completion->priv->tree_view, + completion->priv->entry->allocation.width - 2 * x_border, + items * height); + + if (items <= 0) + gtk_widget_hide (completion->priv->scrolled_window); + + /* default on the first match */ + path = gtk_tree_path_new_from_indices (0, -1); + gtk_tree_view_set_cursor (GTK_TREE_VIEW (completion->priv->tree_view), path, + NULL, FALSE); + completion->priv->current_selected = 0; + gtk_tree_path_free (path); + + items = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->actions), NULL); + + if (items) + { + gtk_tree_view_column_cell_get_size (gtk_tree_view_get_column (GTK_TREE_VIEW (completion->priv->action_view), 0), + NULL, NULL, NULL, NULL, + &height); + + gtk_widget_set_size_request (completion->priv->action_view, + completion->priv->entry->allocation.width - 2 * x_border, + items * height); + } + + x += x_border; + y += 2 * y_border; + + gtk_window_move (GTK_WINDOW (completion->priv->popup_window), x, y + height); + + gtk_widget_show (completion->priv->popup_window); + + gtk_grab_add (completion->priv->popup_window); + gdk_pointer_grab (completion->priv->popup_window->window, TRUE, + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK, + NULL, NULL, GDK_CURRENT_TIME); +} + +void +_gtk_entry_completion_popdown (GtkEntryCompletion *completion) +{ + gdk_pointer_ungrab (GDK_CURRENT_TIME); + gtk_grab_remove (completion->priv->popup_window); + + gtk_widget_hide (completion->priv->popup_window); +} diff --git a/gtk/gtkentrycompletion.h b/gtk/gtkentrycompletion.h new file mode 100644 index 000000000..ecf255778 --- /dev/null +++ b/gtk/gtkentrycompletion.h @@ -0,0 +1,103 @@ +/* gtkentrycompletion.h + * Copyright (C) 2003 Kristian Rietveld <kris@gtk.org> + * + * 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 __GTK_ENTRY_COMPLETION_H__ +#define __GTK_ENTRY_COMPLETION_H__ + +#include <glib-object.h> + +#include <gtk/gtktreemodel.h> +#include <gtk/gtkliststore.h> +#include <gtk/gtktreeviewcolumn.h> +#include <gtk/gtktreemodelfilter.h> + +G_BEGIN_DECLS + +#define GTK_TYPE_ENTRY_COMPLETION (gtk_entry_completion_get_type ()) +#define GTK_ENTRY_COMPLETION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_ENTRY_COMPLETION, GtkEntryCompletion)) +#define GTK_ENTRY_COMPLETION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_ENTRY_COMPLETION, GtkEntryCompletionClass)) +#define GTK_IS_ENTRY_COMPLETION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_ENTRY_COMPLETION)) +#define GTK_IS_ENTRY_COMPLETION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_ENTRY_COMPLETION)) +#define GTK_ENTRY_COMPLETION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_ENTRY_COMPLETION, GtkEntryCompletionClass)) +#define GTK_ENTRY_COMPLETION_GET_PRIVATE(obj)(G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_ENTRY_COMPLETION, GtkEntryCompletionPrivate)) + +typedef struct _GtkEntryCompletion GtkEntryCompletion; +typedef struct _GtkEntryCompletionClass GtkEntryCompletionClass; +typedef struct _GtkEntryCompletionPrivate GtkEntryCompletionPrivate; + +typedef gboolean (* GtkEntryCompletionMatchFunc) (GtkEntryCompletion *completion, + const gchar *key, + GtkTreeIter *iter, + gpointer user_data); + + +struct _GtkEntryCompletion +{ + GObject parent_instance; + + /*< private >*/ + GtkEntryCompletionPrivate *priv; +}; + +struct _GtkEntryCompletionClass +{ + GObjectClass parent_class; + + gboolean (* match_selected) (GtkEntryCompletion *completion, + GtkTreeModel *model, + GtkTreeIter *iter); + void (* action_activated) (GtkEntryCompletion *completion, + gint index); +}; + +/* core */ +GType gtk_entry_completion_get_type (void); +GtkEntryCompletion *gtk_entry_completion_new (void); + +GtkWidget *gtk_entry_completion_get_entry (GtkEntryCompletion *entry); + +void gtk_entry_completion_set_model (GtkEntryCompletion *completion, + GtkTreeModel *model); +GtkTreeModel *gtk_entry_completion_get_model (GtkEntryCompletion *completion); + +void gtk_entry_completion_set_match_func (GtkEntryCompletion *completion, + GtkEntryCompletionMatchFunc func, + gpointer func_data, + GDestroyNotify func_notify); +void gtk_entry_completion_set_minimum_key_length (GtkEntryCompletion *completion, + gint length); +gint gtk_entry_completion_get_minimum_key_length (GtkEntryCompletion *completion); +void gtk_entry_completion_complete (GtkEntryCompletion *completion); + +void gtk_entry_completion_insert_action_text (GtkEntryCompletion *completion, + gint index, + gchar *text); +void gtk_entry_completion_insert_action_markup (GtkEntryCompletion *completion, + gint index, + gchar *markup); +void gtk_entry_completion_delete_action (GtkEntryCompletion *completion, + gint index); + +/* convenience */ +void gtk_entry_completion_set_text_column (GtkEntryCompletion *completion, + gint column); + +G_END_DECLS + +#endif /* __GTK_ENTRY_COMPLETION_H__ */ diff --git a/gtk/gtkentryprivate.h b/gtk/gtkentryprivate.h new file mode 100644 index 000000000..741ec3803 --- /dev/null +++ b/gtk/gtkentryprivate.h @@ -0,0 +1,65 @@ +/* gtkentryprivate.h + * Copyright (C) 2003 Kristian Rietveld <kris@gtk.org> + * + * 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 __GTK_ENTRY_PRIVATE_H__ +#define __GTK_ENTRY_PRIVATE_H__ + +#include <gtk/gtktreeviewcolumn.h> +#include <gtk/gtktreemodelfilter.h> +#include <gtk/gtkliststore.h> +#include <gtk/gtkentrycompletion.h> + +G_BEGIN_DECLS + +struct _GtkEntryCompletionPrivate +{ + GtkWidget *entry; + + GtkWidget *tree_view; + GtkTreeViewColumn *column; + GtkTreeModelFilter *filter_model; + GtkListStore *actions; + + GtkEntryCompletionMatchFunc match_func; + gpointer match_data; + GDestroyNotify match_notify; + + gint minimum_key_length; + gint text_column; + gint current_selected; + + gchar *case_normalized_key; + + /* only used by GtkEntry when attached: */ + GtkWidget *popup_window; + GtkWidget *vbox; + GtkWidget *scrolled_window; + GtkWidget *action_view; + + gulong completion_timeout; + gulong changed_id; + gulong key_press_id; +}; + +void _gtk_entry_completion_popup (GtkEntryCompletion *completion); +void _gtk_entry_completion_popdown (GtkEntryCompletion *completion); + +G_END_DECLS + +#endif /* __GTK_ENTRY_PRIVATE_H__ */ diff --git a/gtk/gtkmarshalers.list b/gtk/gtkmarshalers.list index c474a4a68..601139e44 100644 --- a/gtk/gtkmarshalers.list +++ b/gtk/gtkmarshalers.list @@ -28,6 +28,7 @@ BOOLEAN:ENUM,INT BOOLEAN:OBJECT,UINT,FLAGS BOOLEAN:OBJECT,INT,INT,UINT BOOLEAN:OBJECT,STRING,STRING,BOXED +BOOLEAN:OBJECT,BOXED BOOLEAN:OBJECT,BOXED,BOXED BOOLEAN:OBJECT,STRING,STRING BOOLEAN:INT,INT diff --git a/gtk/gtktreemodelfilter.c b/gtk/gtktreemodelfilter.c new file mode 100644 index 000000000..3e5d52ce8 --- /dev/null +++ b/gtk/gtktreemodelfilter.c @@ -0,0 +1,2746 @@ +/* gtktreemodelfilter.c + * Copyright (C) 2000,2001 Red Hat, Inc., Jonathan Blandford <jrb@redhat.com> + * Copyright (C) 2001-2003 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 "gtktreemodelfilter.h" +#include <gtk/gtkintl.h> +#include <string.h> + +/* ITER FORMAT: + * + * iter->stamp = filter->priv->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; +}; + +struct _GtkTreeModelFilterPrivate +{ + gpointer root; + gint stamp; + guint child_flags; + GtkTreeModel *child_model; + gint zero_ref_count; + + guint root_level_visible; + + GtkTreePath *virtual_root; + + GtkTreeModelFilterVisibleFunc visible_func; + gpointer visible_data; + GtkDestroyNotify visible_destroy; + + gint modify_n_columns; + GType *modify_types; + GtkTreeModelFilterModifyFunc 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; +}; + +/* properties */ +enum +{ + PROP_0, + PROP_CHILD_MODEL, + PROP_VIRTUAL_ROOT +}; + +#define GTK_TREE_MODEL_FILTER_CACHE_CHILD_ITERS(filter) \ + (((GtkTreeModelFilter *)filter)->priv->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 gtk_tree_model_filter_init (GtkTreeModelFilter *filter); +static void gtk_tree_model_filter_class_init (GtkTreeModelFilterClass *filter_class); +static void gtk_tree_model_filter_tree_model_init (GtkTreeModelIface *iface); +static void gtk_tree_model_filter_finalize (GObject *object); +static void gtk_tree_model_filter_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_tree_model_filter_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +/* signal handlers */ +static void gtk_tree_model_filter_row_changed (GtkTreeModel *c_model, + GtkTreePath *c_path, + GtkTreeIter *c_iter, + gpointer data); +static void gtk_tree_model_filter_row_inserted (GtkTreeModel *c_model, + GtkTreePath *c_path, + GtkTreeIter *c_iter, + gpointer data); +static void gtk_tree_model_filter_row_has_child_toggled (GtkTreeModel *c_model, + GtkTreePath *c_path, + GtkTreeIter *c_iter, + gpointer data); +static void gtk_tree_model_filter_row_deleted (GtkTreeModel *c_model, + GtkTreePath *c_path, + gpointer data); +static void gtk_tree_model_filter_rows_reordered (GtkTreeModel *c_model, + GtkTreePath *c_path, + GtkTreeIter *c_iter, + gint *new_order, + gpointer data); + +/* GtkTreeModel interface */ +static guint gtk_tree_model_filter_get_flags (GtkTreeModel *model); +static gint gtk_tree_model_filter_get_n_columns (GtkTreeModel *model); +static GType gtk_tree_model_filter_get_column_type (GtkTreeModel *model, + gint index); +static gboolean gtk_tree_model_filter_get_iter (GtkTreeModel *model, + GtkTreeIter *iter, + GtkTreePath *path); +static GtkTreePath *gtk_tree_model_filter_get_path (GtkTreeModel *model, + GtkTreeIter *iter); +static void gtk_tree_model_filter_get_value (GtkTreeModel *model, + GtkTreeIter *iter, + gint column, + GValue *value); +static gboolean gtk_tree_model_filter_iter_next (GtkTreeModel *model, + GtkTreeIter *iter); +static gboolean gtk_tree_model_filter_iter_children (GtkTreeModel *model, + GtkTreeIter *iter, + GtkTreeIter *parent); +static gboolean gtk_tree_model_filter_iter_has_child (GtkTreeModel *model, + GtkTreeIter *iter); +static gint gtk_tree_model_filter_iter_n_children (GtkTreeModel *model, + GtkTreeIter *iter); +static gboolean gtk_tree_model_filter_iter_nth_child (GtkTreeModel *model, + GtkTreeIter *iter, + GtkTreeIter *parent, + gint n); +static gboolean gtk_tree_model_filter_iter_parent (GtkTreeModel *model, + GtkTreeIter *iter, + GtkTreeIter *child); +static void gtk_tree_model_filter_ref_node (GtkTreeModel *model, + GtkTreeIter *iter); +static void gtk_tree_model_filter_unref_node (GtkTreeModel *model, + GtkTreeIter *iter); + + +/* private functions */ +static void gtk_tree_model_filter_build_level (GtkTreeModelFilter *filter, + FilterLevel *parent_level, + FilterElt *parent_elt); +static void gtk_tree_model_filter_free_level (GtkTreeModelFilter *filter, + FilterLevel *filter_level); + +static GtkTreePath *gtk_tree_model_filter_elt_get_path (FilterLevel *level, + FilterElt *elt, + GtkTreePath *root); + +static GtkTreePath *gtk_tree_model_filter_add_root (GtkTreePath *src, + GtkTreePath *root); +static GtkTreePath *gtk_tree_model_filter_remove_root (GtkTreePath *src, + GtkTreePath *root); + +static void gtk_tree_model_filter_increment_stamp (GtkTreeModelFilter *filter); + +static gboolean gtk_tree_model_filter_visible (GtkTreeModelFilter *filter, + GtkTreeIter *child_iter); +static void gtk_tree_model_filter_clear_cache_helper (GtkTreeModelFilter *filter, + FilterLevel *level); + +static void gtk_tree_model_filter_real_unref_node (GtkTreeModel *model, + GtkTreeIter *iter, + gboolean propagate_unref); + +static void gtk_tree_model_filter_set_model (GtkTreeModelFilter *filter, + GtkTreeModel *child_model); +static void gtk_tree_model_filter_set_root (GtkTreeModelFilter *filter, + GtkTreePath *root); + +static GtkTreePath *gtk_real_tree_model_filter_convert_child_path_to_path (GtkTreeModelFilter *filter, + GtkTreePath *child_path, + gboolean build_levels, + gboolean fetch_childs); + +static FilterElt *gtk_tree_model_filter_fetch_child (GtkTreeModelFilter *filter, + FilterLevel *level, + gint offset, + gint *index); +static void gtk_tree_model_filter_remove_node (GtkTreeModelFilter *filter, + GtkTreeIter *iter, + gboolean emit_signal); +static void gtk_tree_model_filter_update_childs (GtkTreeModelFilter *filter, + FilterLevel *level, + FilterElt *elt); +static FilterElt *bsearch_elt_with_offset (GArray *array, + gint offset, + gint *index); + + +static GObjectClass *parent_class = NULL; + +GType +gtk_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 (GtkTreeModelFilterClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gtk_tree_model_filter_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GtkTreeModelFilter), + 0, /* n_preallocs */ + (GInstanceInitFunc) gtk_tree_model_filter_init + }; + + static const GInterfaceInfo tree_model_info = + { + (GInterfaceInitFunc) gtk_tree_model_filter_tree_model_init, + NULL, + NULL + }; + + tree_model_filter_type = g_type_register_static (G_TYPE_OBJECT, + "GtkTreeModelFilter", + &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 +gtk_tree_model_filter_init (GtkTreeModelFilter *filter) +{ + filter->priv = GTK_TREE_MODEL_FILTER_GET_PRIVATE (filter); + + filter->priv->visible_column = -1; + filter->priv->zero_ref_count = 0; + filter->priv->visible_method_set = FALSE; + filter->priv->modify_func_set = FALSE; +} + +static void +gtk_tree_model_filter_class_init (GtkTreeModelFilterClass *filter_class) +{ + GObjectClass *object_class; + + object_class = (GObjectClass *) filter_class; + parent_class = g_type_class_peek_parent (filter_class); + + object_class->set_property = gtk_tree_model_filter_set_property; + object_class->get_property = gtk_tree_model_filter_get_property; + + object_class->finalize = gtk_tree_model_filter_finalize; + + /* Properties -- FIXME: disabled translations for now, until I can come up with 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 filtermodel 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)); + + g_type_class_add_private (object_class, sizeof (GtkTreeModelFilterPrivate)); +} + +static void +gtk_tree_model_filter_tree_model_init (GtkTreeModelIface *iface) +{ + iface->get_flags = gtk_tree_model_filter_get_flags; + iface->get_n_columns = gtk_tree_model_filter_get_n_columns; + iface->get_column_type = gtk_tree_model_filter_get_column_type; + iface->get_iter = gtk_tree_model_filter_get_iter; + iface->get_path = gtk_tree_model_filter_get_path; + iface->get_value = gtk_tree_model_filter_get_value; + iface->iter_next = gtk_tree_model_filter_iter_next; + iface->iter_children = gtk_tree_model_filter_iter_children; + iface->iter_has_child = gtk_tree_model_filter_iter_has_child; + iface->iter_n_children = gtk_tree_model_filter_iter_n_children; + iface->iter_nth_child = gtk_tree_model_filter_iter_nth_child; + iface->iter_parent = gtk_tree_model_filter_iter_parent; + iface->ref_node = gtk_tree_model_filter_ref_node; + iface->unref_node = gtk_tree_model_filter_unref_node; +} + + +static void +gtk_tree_model_filter_finalize (GObject *object) +{ + GtkTreeModelFilter *filter = (GtkTreeModelFilter *) object; + + gtk_tree_model_filter_set_model (filter, NULL); + + if (filter->priv->virtual_root) + gtk_tree_path_free (filter->priv->virtual_root); + + if (filter->priv->root) + gtk_tree_model_filter_free_level (filter, filter->priv->root); + + if (filter->priv->modify_types) + g_free (filter->priv->modify_types); + + /* must chain up */ + parent_class->finalize (object); +} + +static void +gtk_tree_model_filter_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkTreeModelFilter *filter = GTK_TREE_MODEL_FILTER (object); + + switch (prop_id) + { + case PROP_CHILD_MODEL: + gtk_tree_model_filter_set_model (filter, g_value_get_object (value)); + break; + case PROP_VIRTUAL_ROOT: + gtk_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 +gtk_tree_model_filter_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkTreeModelFilter *filter = GTK_TREE_MODEL_FILTER (object); + + switch (prop_id) + { + case PROP_CHILD_MODEL: + g_value_set_object (value, filter->priv->child_model); + break; + case PROP_VIRTUAL_ROOT: + g_value_set_boxed (value, filter->priv->virtual_root); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* helpers */ + +static void +gtk_tree_model_filter_build_level (GtkTreeModelFilter *filter, + FilterLevel *parent_level, + FilterElt *parent_elt) +{ + GtkTreeIter iter; + GtkTreeIter root; + FilterLevel *new_level; + gint length = 0; + gint i; + + g_assert (filter->priv->child_model != NULL); + + if (!parent_level) + { + if (filter->priv->virtual_root) + { + if (gtk_tree_model_get_iter (filter->priv->child_model, &root, filter->priv->virtual_root) == FALSE) + return; + length = gtk_tree_model_iter_n_children (filter->priv->child_model, &root); + + if (gtk_tree_model_iter_children (filter->priv->child_model, &iter, &root) == FALSE) + return; + } + else + { + if (!gtk_tree_model_get_iter_first (filter->priv->child_model, &iter)) + return; + length = gtk_tree_model_iter_n_children (filter->priv->child_model, NULL); + } + } + else + { + GtkTreeIter parent_iter; + GtkTreeIter child_parent_iter; + + parent_iter.stamp = filter->priv->stamp; + parent_iter.user_data = parent_level; + parent_iter.user_data2 = parent_elt; + + gtk_tree_model_filter_convert_iter_to_child_iter (filter, + &child_parent_iter, + &parent_iter); + if (gtk_tree_model_iter_children (filter->priv->child_model, &iter, &child_parent_iter) == FALSE) + return; + + /* stamp may have changed */ + gtk_tree_model_filter_convert_iter_to_child_iter (filter, + &child_parent_iter, + &parent_iter); + length = gtk_tree_model_iter_n_children (filter->priv->child_model, &child_parent_iter); + } + + g_return_if_fail (length > 0); + + 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->priv->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->priv->zero_ref_count++; + + i = 0; + + if (!new_level->parent_level) + filter->priv->root_level_visible = 0; + + do + { + if (gtk_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 (GTK_TREE_MODEL_FILTER_CACHE_CHILD_ITERS (filter)) + filter_elt.iter = iter; + + g_array_append_val (new_level->array, filter_elt); + + if (!new_level->parent_level) + filter->priv->root_level_visible++; + } + i++; + } + while (gtk_tree_model_iter_next (filter->priv->child_model, &iter)); +} + +static void +gtk_tree_model_filter_free_level (GtkTreeModelFilter *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->priv->zero_ref_count--; + } + + for (i = 0; i < filter_level->array->len; i++) + { + if (g_array_index (filter_level->array, FilterElt, i).children) + gtk_tree_model_filter_free_level (filter, + FILTER_LEVEL (g_array_index (filter_level->array, FilterElt, i).children)); + } + + if (!filter_level->parent_level) + filter->priv->root_level_visible = 0; + + if (filter_level->parent_elt) + filter_level->parent_elt->children = NULL; + else + filter->priv->root = NULL; + + g_array_free (filter_level->array, TRUE); + filter_level->array = NULL; + + g_free (filter_level); + filter_level = NULL; +} + +static GtkTreePath * +gtk_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); + + gtk_tree_model_filter_add_root (real_path, path); + gtk_tree_path_free (path); + return real_path; + } + + return path; +} + +static GtkTreePath * +gtk_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 * +gtk_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 +gtk_tree_model_filter_increment_stamp (GtkTreeModelFilter *filter) +{ + do + { + filter->priv->stamp++; + } + while (filter->priv->stamp == 0); + + gtk_tree_model_filter_clear_cache (filter); +} + +static gboolean +gtk_tree_model_filter_visible (GtkTreeModelFilter *filter, + GtkTreeIter *child_iter) +{ + if (filter->priv->visible_func) + { + return (filter->priv->visible_func (filter->priv->child_model, + child_iter, + filter->priv->visible_data)); + } + else if (filter->priv->visible_column >= 0) + { + GValue val = {0, }; + + gtk_tree_model_get_value (filter->priv->child_model, child_iter, + filter->priv->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 +gtk_tree_model_filter_clear_cache_helper (GtkTreeModelFilter *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) + gtk_tree_model_filter_clear_cache_helper (filter, g_array_index (level->array, FilterElt, i).children); + } + + if (level->ref_count == 0 && level != filter->priv->root) + { + gtk_tree_model_filter_free_level (filter, level); + return; + } +} + +static FilterElt * +gtk_tree_model_filter_fetch_child (GtkTreeModelFilter *filter, + FilterLevel *level, + gint offset, + gint *index) +{ + gint i = 0; + gint start, middle, end; + gint len; + GtkTreePath *c_path = NULL; + GtkTreeIter c_iter; + GtkTreePath *c_parent_path = NULL; + GtkTreeIter c_parent_iter; + FilterElt elt; + + /* check if child exists and is visible */ + if (level->parent_elt) + { + c_parent_path = + gtk_tree_model_filter_elt_get_path (level->parent_level, + level->parent_elt, + filter->priv->virtual_root); + if (!c_parent_path) + return NULL; + } + else + { + if (filter->priv->virtual_root) + c_parent_path = gtk_tree_path_copy (filter->priv->virtual_root); + else + c_parent_path = NULL; + } + + if (c_parent_path) + { + gtk_tree_model_get_iter (filter->priv->child_model, + &c_parent_iter, + c_parent_path); + len = gtk_tree_model_iter_n_children (filter->priv->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->priv->child_model, NULL); + c_path = gtk_tree_path_new (); + } + + gtk_tree_path_append_index (c_path, offset); + gtk_tree_model_get_iter (filter->priv->child_model, &c_iter, c_path); + gtk_tree_path_free (c_path); + + if (offset >= len || !gtk_tree_model_filter_visible (filter, &c_iter)) + return NULL; + + /* 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 (GTK_TREE_MODEL_FILTER_CACHE_CHILD_ITERS (filter)) + elt.iter = c_iter; + + /* find index (binary search on offset) */ + start = 0; + end = level->array->len; + + if (start != end) + { + while (start != end) + { + middle = (start + end) / 2; + + if (g_array_index (level->array, FilterElt, middle).offset <= offset) + start = middle + 1; + else + end = middle; + } + + if (g_array_index (level->array, FilterElt, middle).offset <= offset) + i = middle + 1; + else + i = middle; + } + else + i = 0; + + g_array_insert_val (level->array, i, elt); + *index = i; + + for (i = MAX (--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 &g_array_index (level->array, FilterElt, *index); +} + +static void +gtk_tree_model_filter_remove_node (GtkTreeModelFilter *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; + + /* ref counting */ + while (elt->ref_count > 0) + gtk_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); + gtk_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->priv->root) + { + /* above code destroyed the level */ + goto emit_has_child_toggled; + } + + if (length == 1) + { + /* kill the level */ + gtk_tree_model_filter_free_level (filter, level); + + if (!filter->priv->root) + /* we killed the root */ + return; + } + else + { + FilterElt *tmp; + + /* remove the node */ + tmp = bsearch_elt_with_offset (level->array, elt->offset, &i); + + if (tmp) + { + g_array_remove_index (level->array, i); + + for (i = MAX (--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->priv->root)) + { + /* latest child has been removed, level has been destroyed */ + GtkTreeIter piter; + GtkTreePath *ppath; + + piter.stamp = filter->priv->stamp; + piter.user_data = parent_level; + piter.user_data2 = parent; + + ppath = gtk_tree_model_get_path (GTK_TREE_MODEL (filter), &piter); + + gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (filter), + ppath, &piter); + gtk_tree_path_free (ppath); + } +} + +static void +gtk_tree_model_filter_update_childs (GtkTreeModelFilter *filter, + FilterLevel *level, + FilterElt *elt) +{ + GtkTreeIter c_iter; + GtkTreeIter iter; + + if (!elt->visible) + return; + + iter.stamp = filter->priv->stamp; + iter.user_data = level; + iter.user_data2 = elt; + + gtk_tree_model_filter_convert_iter_to_child_iter (filter, &c_iter, &iter); + + if (gtk_tree_model_iter_has_child (filter->priv->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); + } +} + +static FilterElt * +bsearch_elt_with_offset (GArray *array, + gint offset, + gint *index) +{ + gint start, middle, end; + FilterElt *elt; + + start = 0; + end = array->len; + + if (array->len < 1) + return NULL; + + if (start == end) + { + elt = &g_array_index (array, FilterElt, 0); + + if (elt->offset == offset) + { + *index = 0; + return elt; + } + else + return NULL; + } + + while (start != end) + { + middle = (start + end) / 2; + + elt = &g_array_index (array, FilterElt, middle); + + if (elt->offset < offset) + start = middle + 1; + else if (elt->offset > offset) + end = middle; + else + break; + } + + if (elt->offset == offset) + { + *index = middle; + return elt; + } + + return NULL; +} + +/* TreeModel signals */ +static void +gtk_tree_model_filter_row_changed (GtkTreeModel *c_model, + GtkTreePath *c_path, + GtkTreeIter *c_iter, + gpointer data) +{ + GtkTreeModelFilter *filter = GTK_TREE_MODEL_FILTER (data); + GtkTreeIter iter; + GtkTreeIter childs; + GtkTreeIter real_c_iter; + GtkTreePath *path = NULL; + + FilterElt *elt; + FilterLevel *level; + + gboolean requested_state; + gboolean current_state; + 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); + + /* what's the requested state? */ + requested_state = gtk_tree_model_filter_visible (filter, &real_c_iter); + + /* now, let's see whether the item is there */ + path = gtk_real_tree_model_filter_convert_child_path_to_path (filter, + c_path, + FALSE, + FALSE); + + if (path) + { + gtk_tree_model_get_iter (GTK_TREE_MODEL (filter), &iter, path); + current_state = FILTER_ELT (iter.user_data2)->visible; + } + else + current_state = FALSE; + + if (current_state == FALSE && requested_state == FALSE) + /* no changes required */ + goto done; + + if (current_state == TRUE && requested_state == FALSE) + { + /* get rid of this node */ + gtk_tree_model_get_iter (GTK_TREE_MODEL (filter), &iter, path); + gtk_tree_model_filter_remove_node (filter, &iter, TRUE); + + level = FILTER_LEVEL (iter.user_data); + + if (!level->parent_level) + filter->priv->root_level_visible--; + + goto done; + } + + if (current_state == TRUE && requested_state == TRUE) + { + /* progate the signal */ + gtk_tree_model_get_iter (GTK_TREE_MODEL (filter), &iter, path); + gtk_tree_model_row_changed (GTK_TREE_MODEL (filter), path, &iter); + + level = FILTER_LEVEL (iter.user_data); + elt = FILTER_ELT (iter.user_data2); + + /* and update the childs */ + if (gtk_tree_model_iter_children (c_model, &childs, &real_c_iter)) + gtk_tree_model_filter_update_childs (filter, level, elt); + + goto done; + } + + /* only current == FALSE and requested == TRUE is left, + * pull in the child + */ + g_return_if_fail (current_state == FALSE && requested_state == TRUE); + + /* make sure the new item has been pulled in */ + if (!filter->priv->root) + { + gint i; + FilterLevel *root; + + gtk_tree_model_filter_build_level (filter, NULL, NULL); + + root = FILTER_LEVEL (filter->priv->root); + + if (root) + { + for (i = 0; i < root->array->len; i++) + g_array_index (root->array, FilterElt, i).visible = FALSE; + filter->priv->root_level_visible = 0; + } + } + + if (!path) + path = gtk_real_tree_model_filter_convert_child_path_to_path (filter, + c_path, + FALSE, + TRUE); + + g_return_if_fail (path != NULL); + + gtk_tree_model_filter_increment_stamp (filter); + gtk_tree_model_get_iter (GTK_TREE_MODEL (filter), &iter, path); + + level = FILTER_LEVEL (iter.user_data); + elt = FILTER_ELT (iter.user_data2); + + elt->visible = TRUE; + + if (!level->parent_level) + filter->priv->root_level_visible++; + + /* update stamp */ + gtk_tree_model_row_inserted (GTK_TREE_MODEL (filter), path, &iter); + + if (gtk_tree_model_iter_children (c_model, &childs, c_iter)) + gtk_tree_model_filter_update_childs (filter, level, elt); + +done: + if (path) + gtk_tree_path_free (path); + + if (free_c_path) + gtk_tree_path_free (c_path); +} + +static void +gtk_tree_model_filter_row_inserted (GtkTreeModel *c_model, + GtkTreePath *c_path, + GtkTreeIter *c_iter, + gpointer data) +{ + GtkTreeModelFilter *filter = GTK_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->priv->virtual_root) + { + if (gtk_tree_path_get_depth (filter->priv->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->priv->virtual_root); + c_indices = gtk_tree_path_get_indices (c_path); + + if (v_indices[level] >= c_indices[level]) + (v_indices[level])++; + } + } + + if (!filter->priv->root) + { + gtk_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->priv->root); + + /* subtract virtual root if necessary */ + if (filter->priv->virtual_root) + { + real_path = gtk_tree_model_filter_remove_root (c_path, + filter->priv->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 = bsearch_elt_with_offset (level->array, + gtk_tree_path_get_indices (real_path)[i], + &j); + + if (!elt) + /* parent is probably being filtered out */ + goto done; + + if (!elt->children) + { + GtkTreePath *tmppath; + GtkTreeIter tmpiter; + + tmpiter.stamp = filter->priv->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]; + + /* 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)) + e->offset++; + } + + /* only insert when visible */ + if (gtk_tree_model_filter_visible (filter, &real_c_iter)) + { + FilterElt felt; + + if (GTK_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; + + if (!level->parent_level) + filter->priv->root_level_visible++; + } + + /* another iteration to update the references of childs to parents. */ + 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; + } + + /* don't emit the signal if we aren't visible */ + if (!gtk_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 = gtk_real_tree_model_filter_convert_child_path_to_path (filter, + c_path, + FALSE, TRUE); + + if (!path) + return; + + gtk_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); + +done: + if (free_c_path) + gtk_tree_path_free (c_path); +} + +static void +gtk_tree_model_filter_row_has_child_toggled (GtkTreeModel *c_model, + GtkTreePath *c_path, + GtkTreeIter *c_iter, + gpointer data) +{ + GtkTreeModelFilter *filter = GTK_TREE_MODEL_FILTER (data); + GtkTreePath *path; + GtkTreeIter iter; + + g_return_if_fail (c_path != NULL && c_iter != NULL); + + /* FIXME: does this code work? */ + + if (!gtk_tree_model_filter_visible (filter, c_iter)) + return; + + path = gtk_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 +gtk_tree_model_filter_row_deleted (GtkTreeModel *c_model, + GtkTreePath *c_path, + gpointer data) +{ + GtkTreeModelFilter *filter = GTK_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->priv->virtual_root && + (gtk_tree_path_is_ancestor (c_path, filter->priv->virtual_root) || + !gtk_tree_path_compare (c_path, filter->priv->virtual_root))) + { + gint i; + GtkTreePath *path; + FilterLevel *level = FILTER_LEVEL (filter->priv->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. + */ + + gtk_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); + gtk_tree_model_filter_free_level (filter, filter->priv->root); + + return; + } + + /* fixup virtual root */ + if (filter->priv->virtual_root) + { + if (gtk_tree_path_get_depth (filter->priv->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->priv->virtual_root); + c_indices = gtk_tree_path_get_indices (c_path); + + if (v_indices[level] > c_indices[level]) + (v_indices[level])--; + } + } + + path = gtk_real_tree_model_filter_convert_child_path_to_path (filter, + c_path, + FALSE, + FALSE); + if (!path) + { + path = gtk_real_tree_model_filter_convert_child_path_to_path (filter, + c_path, + FALSE, + TRUE); + + if (!path) + { + /* fixup the offsets */ + GtkTreePath *real_path; + + if (!filter->priv->root) + return; + + level = FILTER_LEVEL (filter->priv->root); + + /* subtract vroot if necessary */ + if (filter->priv->virtual_root) + { + real_path = gtk_tree_model_filter_remove_root (c_path, + filter->priv->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 = bsearch_elt_with_offset (level->array, + gtk_tree_path_get_indices (real_path)[i], + &j); + + 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 (!level->parent_level && elt->visible) + filter->priv->root_level_visible--; + + if (emit_signal) + { + if (level->ref_count == 0 && level != filter->priv->root) + { + gtk_tree_model_filter_increment_stamp (filter); + gtk_tree_model_row_deleted (GTK_TREE_MODEL (data), path); + + gtk_tree_path_free (path); + return; + } + + gtk_tree_model_filter_increment_stamp (filter); + gtk_tree_model_row_deleted (GTK_TREE_MODEL (data), path); + iter.stamp = filter->priv->stamp; + + while (elt->ref_count > 0) + gtk_tree_model_filter_real_unref_node (GTK_TREE_MODEL (data), &iter, + FALSE); + } + + if (level->array->len == 1) + { + /* kill level */ + gtk_tree_model_filter_free_level (filter, level); + } + else + { + FilterElt *tmp; + + /* remove the row */ + tmp = bsearch_elt_with_offset (level->array, elt->offset, &i); + + offset = tmp->offset; + g_array_remove_index (level->array, i); + + for (i = MAX (--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 +gtk_tree_model_filter_rows_reordered (GtkTreeModel *c_model, + GtkTreePath *c_path, + GtkTreeIter *c_iter, + gint *new_order, + gpointer data) +{ + FilterElt *elt; + FilterLevel *level; + GtkTreeModelFilter *filter = GTK_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); + + if (c_path == NULL || gtk_tree_path_get_indices (c_path) == NULL) + { + if (!filter->priv->root) + return; + + length = gtk_tree_model_iter_n_children (c_model, NULL); + + if (filter->priv->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->priv->virtual_root)[0]) + new_pos = i; + + if (new_pos < 0) + return; + + gtk_tree_path_get_indices (filter->priv->virtual_root)[0] = new_pos; + return; + } + + path = gtk_tree_path_new (); + level = FILTER_LEVEL (filter->priv->root); + } + else + { + GtkTreeIter child_iter; + + /* virtual root anchor reordering */ + if (filter->priv->virtual_root && + gtk_tree_path_get_depth (c_path) < + gtk_tree_path_get_depth (filter->priv->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->priv->virtual_root)[level]) + new_pos = i; + + if (new_pos < 0) + return; + + gtk_tree_path_get_indices (filter->priv->virtual_root)[level] = new_pos; + return; + } + + path = gtk_real_tree_model_filter_convert_child_path_to_path (filter, + c_path, + FALSE, + FALSE); + if (!path && filter->priv->virtual_root && + gtk_tree_path_compare (c_path, filter->priv->virtual_root)) + return; + + if (!path && !filter->priv->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->priv->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; + + gtk_tree_model_filter_convert_iter_to_child_iter (GTK_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 +gtk_tree_model_filter_get_flags (GtkTreeModel *model) +{ + g_return_val_if_fail (GTK_IS_TREE_MODEL_FILTER (model), 0); + + return 0; +} + +static gint +gtk_tree_model_filter_get_n_columns (GtkTreeModel *model) +{ + GtkTreeModelFilter *filter = (GtkTreeModelFilter *)model; + + g_return_val_if_fail (GTK_IS_TREE_MODEL_FILTER (model), 0); + g_return_val_if_fail (filter->priv->child_model != NULL, 0); + + if (filter->priv->child_model == NULL) + return 0; + + /* so we can't modify the modify func after this ... */ + filter->priv->modify_func_set = TRUE; + + if (filter->priv->modify_n_columns > 0) + return filter->priv->modify_n_columns; + + return gtk_tree_model_get_n_columns (filter->priv->child_model); +} + +static GType +gtk_tree_model_filter_get_column_type (GtkTreeModel *model, + gint index) +{ + GtkTreeModelFilter *filter = (GtkTreeModelFilter *)model; + + g_return_val_if_fail (GTK_IS_TREE_MODEL_FILTER (model), G_TYPE_INVALID); + g_return_val_if_fail (filter->priv->child_model != NULL, G_TYPE_INVALID); + + /* so we can't modify the modify func after this ... */ + filter->priv->modify_func_set = TRUE; + + if (filter->priv->modify_types) + { + g_return_val_if_fail (index < filter->priv->modify_n_columns, G_TYPE_INVALID); + + return filter->priv->modify_types[index]; + } + + return gtk_tree_model_get_column_type (filter->priv->child_model, index); +} + +static gboolean +gtk_tree_model_filter_get_iter (GtkTreeModel *model, + GtkTreeIter *iter, + GtkTreePath *path) +{ + GtkTreeModelFilter *filter = (GtkTreeModelFilter *)model; + gint *indices; + FilterLevel *level; + gint depth, i; + + g_return_val_if_fail (GTK_IS_TREE_MODEL_FILTER (model), FALSE); + g_return_val_if_fail (filter->priv->child_model != NULL, FALSE); + + indices = gtk_tree_path_get_indices (path); + + if (filter->priv->root == NULL) + gtk_tree_model_filter_build_level (filter, NULL, NULL); + level = FILTER_LEVEL (filter->priv->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) + gtk_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->priv->stamp; + iter->user_data = level; + iter->user_data2 = &g_array_index (level->array, FilterElt, + indices[depth - 1]); + + return TRUE; +} + +static GtkTreePath * +gtk_tree_model_filter_get_path (GtkTreeModel *model, + GtkTreeIter *iter) +{ + GtkTreePath *retval; + FilterLevel *level; + FilterElt *elt; + + g_return_val_if_fail (GTK_IS_TREE_MODEL_FILTER (model), NULL); + g_return_val_if_fail (GTK_TREE_MODEL_FILTER (model)->priv->child_model != NULL, NULL); + g_return_val_if_fail (GTK_TREE_MODEL_FILTER (model)->priv->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 +gtk_tree_model_filter_get_value (GtkTreeModel *model, + GtkTreeIter *iter, + gint column, + GValue *value) +{ + GtkTreeIter child_iter; + GtkTreeModelFilter *filter = GTK_TREE_MODEL_FILTER (model); + + g_return_if_fail (GTK_IS_TREE_MODEL_FILTER (model)); + g_return_if_fail (GTK_TREE_MODEL_FILTER (model)->priv->child_model != NULL); + g_return_if_fail (GTK_TREE_MODEL_FILTER (model)->priv->stamp == iter->stamp); + + if (filter->priv->modify_func) + { + g_return_if_fail (column < filter->priv->modify_n_columns); + + g_value_init (value, filter->priv->modify_types[column]); + filter->priv->modify_func (model, + iter, + value, + column, + filter->priv->modify_data); + + return; + } + + gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), &child_iter, iter); + gtk_tree_model_get_value (GTK_TREE_MODEL_FILTER (model)->priv->child_model, + &child_iter, column, value); +} + +static gboolean +gtk_tree_model_filter_iter_next (GtkTreeModel *model, + GtkTreeIter *iter) +{ + FilterLevel *level; + FilterElt *elt; + + g_return_val_if_fail (GTK_IS_TREE_MODEL_FILTER (model), FALSE); + g_return_val_if_fail (GTK_TREE_MODEL_FILTER (model)->priv->child_model != NULL, FALSE); + g_return_val_if_fail (GTK_TREE_MODEL_FILTER (model)->priv->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 +gtk_tree_model_filter_iter_children (GtkTreeModel *model, + GtkTreeIter *iter, + GtkTreeIter *parent) +{ + GtkTreeModelFilter *filter = (GtkTreeModelFilter *)model; + FilterLevel *level; + + iter->stamp = 0; + g_return_val_if_fail (GTK_IS_TREE_MODEL_FILTER (model), FALSE); + g_return_val_if_fail (filter->priv->child_model != NULL, FALSE); + if (parent) + g_return_val_if_fail (filter->priv->stamp == parent->stamp, FALSE); + + if (!parent) + { + if (!filter->priv->root) + gtk_tree_model_filter_build_level (filter, NULL, NULL); + if (!filter->priv->root) + return FALSE; + + level = filter->priv->root; + iter->stamp = filter->priv->stamp; + iter->user_data = level; + iter->user_data2 = level->array->data; + } + else + { + if (FILTER_ELT (parent->user_data2)->children == NULL) + gtk_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; + + /* empty array? */ + if (FILTER_ELT (parent->user_data2)->children->array->len <= 0) + return FALSE; + + iter->stamp = filter->priv->stamp; + iter->user_data = FILTER_ELT (parent->user_data2)->children; + iter->user_data2 = FILTER_LEVEL (iter->user_data)->array->data; + } + + return TRUE; +} + +static gboolean +gtk_tree_model_filter_iter_has_child (GtkTreeModel *model, + GtkTreeIter *iter) +{ + GtkTreeIter child_iter; + GtkTreeModelFilter *filter = (GtkTreeModelFilter *)model; + FilterElt *elt; + + g_return_val_if_fail (GTK_IS_TREE_MODEL_FILTER (model), FALSE); + g_return_val_if_fail (filter->priv->child_model != NULL, FALSE); + g_return_val_if_fail (filter->priv->stamp == iter->stamp, FALSE); + + filter = GTK_TREE_MODEL_FILTER (model); + + gtk_tree_model_filter_convert_iter_to_child_iter (GTK_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->priv->child_model, &child_iter)) + gtk_tree_model_filter_build_level (filter, FILTER_LEVEL (iter->user_data), + elt); + + /* FIXME: we should prolly count the visible nodes here, just like in + * _iter_n_children. + */ + if (elt->children && elt->children->array->len > 0) + return TRUE; + + return FALSE; +} + +static gint +gtk_tree_model_filter_iter_n_children (GtkTreeModel *model, + GtkTreeIter *iter) +{ + GtkTreeIter child_iter; + GtkTreeModelFilter *filter = (GtkTreeModelFilter *)model; + FilterElt *elt; + + g_return_val_if_fail (GTK_IS_TREE_MODEL_FILTER (model), 0); + g_return_val_if_fail (filter->priv->child_model != NULL, 0); + if (iter) + g_return_val_if_fail (filter->priv->stamp == iter->stamp, 0); + + if (!iter) + { + if (!filter->priv->root) + gtk_tree_model_filter_build_level (filter, NULL, NULL); + + /* count visible nodes */ + return filter->priv->root_level_visible; + } + + elt = FILTER_ELT (iter->user_data2); + gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), &child_iter, iter); + + if (!elt->children && + gtk_tree_model_iter_has_child (filter->priv->child_model, &child_iter)) + gtk_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 +gtk_tree_model_filter_iter_nth_child (GtkTreeModel *model, + GtkTreeIter *iter, + GtkTreeIter *parent, + gint n) +{ + FilterLevel *level; + GtkTreeIter children; + + g_return_val_if_fail (GTK_IS_TREE_MODEL_FILTER (model), FALSE); + if (parent) + g_return_val_if_fail (GTK_TREE_MODEL_FILTER (model)->priv->stamp == parent->stamp, FALSE); + + /* use this instead of has_Child to force us to build the level, if needed */ + if (gtk_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 = GTK_TREE_MODEL_FILTER (model)->priv->stamp; + iter->user_data = level; + iter->user_data2 = &g_array_index (level->array, FilterElt, n); + + return TRUE; +} + +static gboolean +gtk_tree_model_filter_iter_parent (GtkTreeModel *model, + GtkTreeIter *iter, + GtkTreeIter *child) +{ + FilterLevel *level; + + iter->stamp = 0; + g_return_val_if_fail (GTK_IS_TREE_MODEL_FILTER (model), FALSE); + g_return_val_if_fail (GTK_TREE_MODEL_FILTER (model)->priv->child_model != NULL, FALSE); + g_return_val_if_fail (GTK_TREE_MODEL_FILTER (model)->priv->stamp == child->stamp, FALSE); + + level = child->user_data; + + if (level->parent_level) + { + iter->stamp = GTK_TREE_MODEL_FILTER (model)->priv->stamp; + iter->user_data = level->parent_level; + iter->user_data2 = level->parent_elt; + + return TRUE; + } + + return FALSE; +} + +static void +gtk_tree_model_filter_ref_node (GtkTreeModel *model, + GtkTreeIter *iter) +{ + GtkTreeModelFilter *filter = (GtkTreeModelFilter *)model; + GtkTreeIter child_iter; + FilterLevel *level; + FilterElt *elt; + + g_return_if_fail (GTK_IS_TREE_MODEL_FILTER (model)); + g_return_if_fail (GTK_TREE_MODEL_FILTER (model)->priv->child_model != NULL); + g_return_if_fail (GTK_TREE_MODEL_FILTER (model)->priv->stamp == iter->stamp); + + gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), &child_iter, iter); + + gtk_tree_model_ref_node (filter->priv->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->priv->zero_ref_count--; + } +} + +static void +gtk_tree_model_filter_unref_node (GtkTreeModel *model, + GtkTreeIter *iter) +{ + gtk_tree_model_filter_real_unref_node (model, iter, TRUE); +} + +static void +gtk_tree_model_filter_real_unref_node (GtkTreeModel *model, + GtkTreeIter *iter, + gboolean propagate_unref) +{ + GtkTreeModelFilter *filter = (GtkTreeModelFilter *)model; + FilterLevel *level; + FilterElt *elt; + + g_return_if_fail (GTK_IS_TREE_MODEL_FILTER (model)); + g_return_if_fail (filter->priv->child_model != NULL); + g_return_if_fail (filter->priv->stamp == iter->stamp); + + if (propagate_unref) + { + GtkTreeIter child_iter; + gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), &child_iter, iter); + gtk_tree_model_unref_node (filter->priv->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->priv->zero_ref_count++; + } +} + +/* bits and pieces */ +static void +gtk_tree_model_filter_set_model (GtkTreeModelFilter *filter, + GtkTreeModel *child_model) +{ + g_return_if_fail (GTK_IS_TREE_MODEL_FILTER (filter)); + + if (filter->priv->child_model) + { + g_signal_handler_disconnect (G_OBJECT (filter->priv->child_model), + filter->priv->changed_id); + g_signal_handler_disconnect (G_OBJECT (filter->priv->child_model), + filter->priv->inserted_id); + g_signal_handler_disconnect (G_OBJECT (filter->priv->child_model), + filter->priv->has_child_toggled_id); + g_signal_handler_disconnect (G_OBJECT (filter->priv->child_model), + filter->priv->deleted_id); + g_signal_handler_disconnect (G_OBJECT (filter->priv->child_model), + filter->priv->reordered_id); + + /* reset our state */ + if (filter->priv->root) + gtk_tree_model_filter_free_level (filter, filter->priv->root); + + filter->priv->root = NULL; + g_object_unref (G_OBJECT (filter->priv->child_model)); + filter->priv->visible_column = -1; + /* FIXME: destroy more crack here? the funcs? */ + } + + filter->priv->child_model = child_model; + + if (child_model) + { + g_object_ref (G_OBJECT (filter->priv->child_model)); + filter->priv->changed_id = + g_signal_connect (child_model, "row_changed", + G_CALLBACK (gtk_tree_model_filter_row_changed), + filter); + filter->priv->inserted_id = + g_signal_connect (child_model, "row_inserted", + G_CALLBACK (gtk_tree_model_filter_row_inserted), + filter); + filter->priv->has_child_toggled_id = + g_signal_connect (child_model, "row_has_child_toggled", + G_CALLBACK (gtk_tree_model_filter_row_has_child_toggled), + filter); + filter->priv->deleted_id = + g_signal_connect (child_model, "row_deleted", + G_CALLBACK (gtk_tree_model_filter_row_deleted), + filter); + filter->priv->reordered_id = + g_signal_connect (child_model, "rows_reordered", + G_CALLBACK (gtk_tree_model_filter_rows_reordered), + filter); + + filter->priv->child_flags = gtk_tree_model_get_flags (child_model); + filter->priv->stamp = g_random_int (); + } +} + +static void +gtk_tree_model_filter_set_root (GtkTreeModelFilter *filter, + GtkTreePath *root) +{ + g_return_if_fail (GTK_IS_TREE_MODEL_FILTER (filter)); + + if (!root) + filter->priv->virtual_root = NULL; + else + filter->priv->virtual_root = gtk_tree_path_copy (root); +} + +/* public API */ + +/** + * gtk_tree_model_filter_new: + * @child_model: A #GtkTreeModel. + * @root: A #GtkTreePath or %NULL. + * + * Creates a new #GtkTreeModel, with @child_model as the child_model + * and @root as the virtual root. + * + * Return value: A new #GtkTreeModel. + */ +GtkTreeModel * +gtk_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 (gtk_tree_model_filter_get_type (), NULL)); + + gtk_tree_model_filter_set_model (GTK_TREE_MODEL_FILTER (retval), + child_model); + gtk_tree_model_filter_set_root (GTK_TREE_MODEL_FILTER (retval), root); + + return retval; +} + +/** + * gtk_tree_model_filter_get_model: + * @filter: A #GtkTreeModelFilter. + * + * Returns a pointer to the child model of @filter. + * + * Return value: A pointer to a #GtkTreeModel. + */ +GtkTreeModel * +gtk_tree_model_filter_get_model (GtkTreeModelFilter *filter) +{ + g_return_val_if_fail (GTK_IS_TREE_MODEL_FILTER (filter), NULL); + + return filter->priv->child_model; +} + +/** + * gtk_tree_model_filter_set_visible_func: + * @filter: A #GtkTreeModelFilter. + * @func: A #GtkTreeModelFilterVisibleFunc, the visible function. + * @data: User data to pass to the visible function, or %NULL. + * @destroy: Destroy notifier of @data, or %NULL. + * + * Sets the visible function used when filtering the @filter to be @func. The + * function should return %TRUE if the given row should be visible and + * %FALSE otherwise. + */ +void +gtk_tree_model_filter_set_visible_func (GtkTreeModelFilter *filter, + GtkTreeModelFilterVisibleFunc func, + gpointer data, + GtkDestroyNotify destroy) +{ + g_return_if_fail (GTK_IS_TREE_MODEL_FILTER (filter)); + g_return_if_fail (func != NULL); + g_return_if_fail (filter->priv->visible_method_set == FALSE); + + if (filter->priv->visible_func) + { + GtkDestroyNotify d = filter->priv->visible_destroy; + + filter->priv->visible_destroy = NULL; + d (filter->priv->visible_data); + } + + filter->priv->visible_func = func; + filter->priv->visible_data = data; + filter->priv->visible_destroy = destroy; + + filter->priv->visible_method_set = TRUE; +} + +/** + * gtk_tree_model_filter_set_modify_func: + * @filter: A #GtkTreeModelFilter. + * @n_columns: The number of columns in the filter model. + * @types: The #GType<!-- -->s of the columns. + * @func: A #GtkTreeModelFilterModifyFunc, or %NULL. + * @data: User data to pass to the modify function, or %NULL. + * @destroy: Destroy notifier of @data, or %NULL. + * + * Sets the @filter to have @n_columns columns with @types. If @func + * is not %NULL, it will set @func to be the modify function of @filter. + */ +void +gtk_tree_model_filter_set_modify_func (GtkTreeModelFilter *filter, + gint n_columns, + GType *types, + GtkTreeModelFilterModifyFunc func, + gpointer data, + GtkDestroyNotify destroy) +{ + g_return_if_fail (GTK_IS_TREE_MODEL_FILTER (filter)); + g_return_if_fail (func != NULL); + g_return_if_fail (filter->priv->modify_func_set == FALSE); + + if (filter->priv->modify_destroy) + { + GtkDestroyNotify d = filter->priv->modify_destroy; + + filter->priv->modify_destroy = NULL; + d (filter->priv->modify_data); + } + + filter->priv->modify_n_columns = n_columns; + filter->priv->modify_types = g_new0 (GType, n_columns); + memcpy (filter->priv->modify_types, types, sizeof (GType) * n_columns); + filter->priv->modify_func = func; + filter->priv->modify_data = data; + filter->priv->modify_destroy = destroy; + + filter->priv->modify_func_set = TRUE; +} + +/** + * gtk_tree_model_filter_set_visible_column: + * @filter: A #GtkTreeModelFilter. + * @column: A #gint which is the column containing the visible information. + * + * Sets @column of the child_model to be the column where @filter should + * look for visibility information. @columns should be a column of type + * %G_TYPE_BOOLEAN, where %TRUE means that a row is visible, and %FALSE + * if not. + */ +void +gtk_tree_model_filter_set_visible_column (GtkTreeModelFilter *filter, + gint column) +{ + g_return_if_fail (GTK_IS_TREE_MODEL_FILTER (filter)); + g_return_if_fail (column >= 0); + g_return_if_fail (filter->priv->visible_method_set == FALSE); + + filter->priv->visible_column = column; + + filter->priv->visible_method_set = TRUE; +} + +/* conversion */ + +/** + * gtk_tree_model_filter_convert_child_iter_to_iter: + * @filter: A #GtkTreeModelFilter. + * @filter_iter: An uninitialized #GtkTreeIter. + * @child_iter: A valid #GtkTreeIter pointing to a row on the child model. + * + * Sets @filter_iter to point to the row in @filter that corresponds to the + * row pointed at by @child_iter. + */ +void +gtk_tree_model_filter_convert_child_iter_to_iter (GtkTreeModelFilter *filter, + GtkTreeIter *filter_iter, + GtkTreeIter *child_iter) +{ + GtkTreePath *child_path, *path; + + g_return_if_fail (GTK_IS_TREE_MODEL_FILTER (filter)); + g_return_if_fail (filter->priv->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->priv->child_model, child_iter); + g_return_if_fail (child_path != NULL); + + path = gtk_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); +} + +/** + * gtk_tree_model_filter_convert_iter_to_child_iter: + * @filter: A #GtkTreeModelFilter. + * @child_iter: An uninitialized #GtkTreeIter. + * @filter_iter: A valid #GtkTreeIter pointing to a row on @filter. + * + * Sets @child_iter to point to the row pointed to by @filter_iter. + */ +void +gtk_tree_model_filter_convert_iter_to_child_iter (GtkTreeModelFilter *filter, + GtkTreeIter *child_iter, + GtkTreeIter *filter_iter) +{ + g_return_if_fail (GTK_IS_TREE_MODEL_FILTER (filter)); + g_return_if_fail (filter->priv->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->priv->stamp); + + if (GTK_TREE_MODEL_FILTER_CACHE_CHILD_ITERS (filter)) + { + *child_iter = FILTER_ELT (filter_iter->user_data2)->iter; + } + else + { + GtkTreePath *path; + + path = gtk_tree_model_filter_elt_get_path (filter_iter->user_data, + filter_iter->user_data2, + NULL); + gtk_tree_model_get_iter (filter->priv->child_model, child_iter, path); + gtk_tree_path_free (path); + } +} + +static GtkTreePath * +gtk_real_tree_model_filter_convert_child_path_to_path (GtkTreeModelFilter *filter, + GtkTreePath *child_path, + gboolean build_levels, + gboolean fetch_childs) +{ + gint *child_indices; + GtkTreePath *retval; + GtkTreePath *real_path; + FilterLevel *level; + FilterElt *tmp; + gint i; + + g_return_val_if_fail (GTK_IS_TREE_MODEL_FILTER (filter), NULL); + g_return_val_if_fail (filter->priv->child_model != NULL, NULL); + g_return_val_if_fail (child_path != NULL, NULL); + + if (!filter->priv->virtual_root) + real_path = gtk_tree_path_copy (child_path); + else + real_path = gtk_tree_model_filter_remove_root (child_path, + filter->priv->virtual_root); + + if (!real_path) + return NULL; + + retval = gtk_tree_path_new (); + child_indices = gtk_tree_path_get_indices (real_path); + + if (filter->priv->root == NULL && build_levels) + gtk_tree_model_filter_build_level (filter, NULL, NULL); + level = FILTER_LEVEL (filter->priv->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; + } + + tmp = bsearch_elt_with_offset (level->array, child_indices[i], &j); + if (tmp) + { + gtk_tree_path_append_index (retval, j); + if (!tmp->children && build_levels) + gtk_tree_model_filter_build_level (filter, level, tmp); + level = tmp->children; + found_child = TRUE; + } + + if (!found_child && fetch_childs) + { + tmp = gtk_tree_model_filter_fetch_child (filter, level, + child_indices[i], + &j); + + /* didn't find the child, let's try to bring it back */ + if (!tmp || tmp->offset != child_indices[i]) + { + /* not there */ + gtk_tree_path_free (real_path); + gtk_tree_path_free (retval); + return NULL; + } + + gtk_tree_path_append_index (retval, j); + if (!tmp->children && build_levels) + gtk_tree_model_filter_build_level (filter, level, tmp); + level = tmp->children; + found_child = TRUE; + } + 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; +} + +/** + * gtk_tree_model_filter_convert_child_path_to_path: + * @filter: A #GtkTreeModelFilter. + * @child_path: A #GtkTreePath to convert. + * + * Converts @child_path to a path relative to @filter. That is, @child_path + * points to a path in the child model. The rerturned path will point to the + * same row in the filtered model. If @child_path isn't a valid path on the + * child model, then %NULL is returned. + * + * Return value: A newly allocated #GtkTreePath, or %NULL. + */ +GtkTreePath * +gtk_tree_model_filter_convert_child_path_to_path (GtkTreeModelFilter *filter, + GtkTreePath *child_path) +{ + /* this function does the sanity checks */ + return gtk_real_tree_model_filter_convert_child_path_to_path (filter, + child_path, + TRUE, + TRUE); +} + +/** + * gtk_tree_model_filter_convert_path_to_child_path: + * @filter: A #GtkTreeModelFilter. + * @filter_path: A #GtkTreePath to convert. + * + * Converts @filter_path to a path on the child model of @filter. That is, + * @filter_path points to a location in @filter. The returned path will + * point to the same location in the model not being filtered. If @filter_path + * does not point to a location in the child model, %NULL is returned. + * + * Return value: A newly allocated #GtkTreePath, or %NULL. + */ +GtkTreePath * +gtk_tree_model_filter_convert_path_to_child_path (GtkTreeModelFilter *filter, + GtkTreePath *filter_path) +{ + gint *filter_indices; + GtkTreePath *retval; + FilterLevel *level; + gint i; + + g_return_val_if_fail (GTK_IS_TREE_MODEL_FILTER (filter), NULL); + g_return_val_if_fail (filter->priv->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->priv->root) + gtk_tree_model_filter_build_level (filter, NULL, NULL); + level = FILTER_LEVEL (filter->priv->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) + gtk_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->priv->virtual_root) + { + GtkTreePath *real_retval; + + real_retval = gtk_tree_model_filter_add_root (retval, + filter->priv->virtual_root); + gtk_tree_path_free (retval); + + return real_retval; + } + + return retval; +} + +static gboolean +gtk_tree_model_filter_refilter_helper (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + /* evil, don't try this at home, but certainly speeds things up */ + gtk_tree_model_filter_row_changed (model, path, iter, data); + + return FALSE; +} + +/** + * gtk_tree_model_filter_refilter: + * @filter: A #GtkTreeModelFilter. + * + * Emits ::row_changed for each row in the child model, which causes + * the filter to re-evaluate whether a row is visible or not. + */ +void +gtk_tree_model_filter_refilter (GtkTreeModelFilter *filter) +{ + g_return_if_fail (GTK_IS_TREE_MODEL_FILTER (filter)); + + /* S L O W */ + gtk_tree_model_foreach (filter->priv->child_model, + gtk_tree_model_filter_refilter_helper, + filter); +} + +/** + * gtk_tree_model_filter_clear_cache: + * @filter: A #GtkTreeModelFilter. + * + * This function should almost never be called. It clears the @filter + * of any cached iterators that haven't been reffed with + * gtk_tree_model_ref_node(). This might be useful if the child model + * being filtered is static (and doesn't change often) and there has been + * a lot of unreffed access to nodes. As a side effect of this function, + * all unreffed itters will be invalid. + */ +void +gtk_tree_model_filter_clear_cache (GtkTreeModelFilter *filter) +{ + g_return_if_fail (GTK_IS_TREE_MODEL_FILTER (filter)); + + if (filter->priv->zero_ref_count) + gtk_tree_model_filter_clear_cache_helper (filter, + FILTER_LEVEL (filter->priv->root)); +} diff --git a/gtk/gtktreemodelfilter.h b/gtk/gtktreemodelfilter.h new file mode 100644 index 000000000..6445f0b72 --- /dev/null +++ b/gtk/gtktreemodelfilter.h @@ -0,0 +1,99 @@ +/* gtktreemodelfilter.h + * Copyright (C) 2000,2001 Red Hat, Inc., Jonathan Blandford <jrb@redhat.com> + * Copyright (C) 2001-2003 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 __GTK_TREE_MODEL_FILTER_H__ +#define __GTK_TREE_MODEL_FILTER_H__ + +#include <gtk/gtktreemodel.h> + +G_BEGIN_DECLS + +#define GTK_TYPE_TREE_MODEL_FILTER (gtk_tree_model_filter_get_type ()) +#define GTK_TREE_MODEL_FILTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_TREE_MODEL_FILTER, GtkTreeModelFilter)) +#define GTK_TREE_MODEL_FILTER_CLASS(vtable) (G_TYPE_CHECK_CLASS_CAST ((vtable), GTK_TYPE_TREE_MODEL_FILTER, GtkTreeModelFilterClass)) +#define GTK_IS_TREE_MODEL_FILTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_TREE_MODEL_FILTER)) +#define GTK_IS_TREE_MODEL_FILTER_CLASS(vtable) (G_TYPE_CHECK_CLASS_TYPE ((vtable), GTK_TYPE_TREE_MODEL_FILTER)) +#define GTK_TREE_MODEL_FILTER_GET_CLASS(inst) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_TREE_MODEL_FILTER, GtkTreeModelFilterClass)) +#define GTK_TREE_MODEL_FILTER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_TREE_MODEL_FILTER, GtkTreeModelFilterPrivate)) + +typedef gboolean (* GtkTreeModelFilterVisibleFunc) (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data); +typedef void (* GtkTreeModelFilterModifyFunc) (GtkTreeModel *model, + GtkTreeIter *iter, + GValue *value, + gint column, + gpointer data); + +typedef struct _GtkTreeModelFilter GtkTreeModelFilter; +typedef struct _GtkTreeModelFilterClass GtkTreeModelFilterClass; +typedef struct _GtkTreeModelFilterPrivate GtkTreeModelFilterPrivate; + +struct _GtkTreeModelFilter +{ + GObject parent; + + /*< private >*/ + GtkTreeModelFilterPrivate *priv; +}; + +struct _GtkTreeModelFilterClass +{ + GObjectClass parent_class; +}; + +/* base */ +GType gtk_tree_model_filter_get_type (void); +GtkTreeModel *gtk_tree_model_filter_new (GtkTreeModel *child_model, + GtkTreePath *root); +void gtk_tree_model_filter_set_visible_func (GtkTreeModelFilter *filter, + GtkTreeModelFilterVisibleFunc func, + gpointer data, + GtkDestroyNotify destroy); +void gtk_tree_model_filter_set_modify_func (GtkTreeModelFilter *filter, + gint n_columns, + GType *types, + GtkTreeModelFilterModifyFunc func, + gpointer data, + GtkDestroyNotify destroy); +void gtk_tree_model_filter_set_visible_column (GtkTreeModelFilter *filter, + gint column); + +GtkTreeModel *gtk_tree_model_filter_get_model (GtkTreeModelFilter *filter); + +/* conversion */ +void gtk_tree_model_filter_convert_child_iter_to_iter (GtkTreeModelFilter *filter, + GtkTreeIter *filter_iter, + GtkTreeIter *child_iter); +void gtk_tree_model_filter_convert_iter_to_child_iter (GtkTreeModelFilter *filter, + GtkTreeIter *child_iter, + GtkTreeIter *filter_iter); +GtkTreePath *gtk_tree_model_filter_convert_child_path_to_path (GtkTreeModelFilter *filter, + GtkTreePath *child_path); +GtkTreePath *gtk_tree_model_filter_convert_path_to_child_path (GtkTreeModelFilter *path, + GtkTreePath *filter_path); + +/* extras */ +void gtk_tree_model_filter_refilter (GtkTreeModelFilter *filter); +void gtk_tree_model_filter_clear_cache (GtkTreeModelFilter *filter); + +G_END_DECLS + +#endif /* __GTK_TREE_MODEL_FILTER_H__ */ diff --git a/gtk/gtktreeviewcolumn.c b/gtk/gtktreeviewcolumn.c index ba372b88a..cba68c0e6 100644 --- a/gtk/gtktreeviewcolumn.c +++ b/gtk/gtktreeviewcolumn.c @@ -21,6 +21,7 @@ #include "gtktreeviewcolumn.h" #include "gtktreeview.h" #include "gtktreeprivate.h" +#include "gtkcelllayout.h" #include "gtkbutton.h" #include "gtkalignment.h" #include "gtklabel.h" @@ -74,6 +75,7 @@ struct _GtkTreeViewColumnCellInfo /* Type methods */ static void gtk_tree_view_column_init (GtkTreeViewColumn *tree_column); static void gtk_tree_view_column_class_init (GtkTreeViewColumnClass *klass); +static void gtk_tree_view_column_cell_layout_init (GtkCellLayoutIface *iface); /* GObject methods */ static void gtk_tree_view_column_set_property (GObject *object, @@ -86,6 +88,26 @@ static void gtk_tree_view_column_get_property (GObject GParamSpec *pspec); static void gtk_tree_view_column_finalize (GObject *object); +/* GtkCellLayout implementation */ +static void gtk_tree_view_column_cell_layout_pack_start (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + gboolean expand); +static void gtk_tree_view_column_cell_layout_pack_end (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + gboolean expand); +static void gtk_tree_view_column_cell_layout_clear (GtkCellLayout *cell_layout); +static void gtk_tree_view_column_cell_layout_add_attribute (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + const gchar *attribute, + gint column); +static void gtk_tree_view_column_cell_layout_set_cell_data_func (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + GtkCellLayoutDataFunc func, + gpointer func_data, + GDestroyNotify destroy); +static void gtk_tree_view_column_cell_layout_clear_attributes (GtkCellLayout *cell_layout, + GtkCellRenderer *cell); + /* Button handling code */ static void gtk_tree_view_column_create_button (GtkTreeViewColumn *tree_column); static void gtk_tree_view_column_update_button (GtkTreeViewColumn *tree_column); @@ -145,12 +167,23 @@ gtk_tree_view_column_get_type (void) NULL, /* class_data */ sizeof (GtkTreeViewColumn), 0, - (GInstanceInitFunc) gtk_tree_view_column_init, + (GInstanceInitFunc) gtk_tree_view_column_init + }; + + static const GInterfaceInfo cell_layout_info = + { + (GInterfaceInitFunc) gtk_tree_view_column_cell_layout_init, + NULL, + NULL }; tree_column_type = g_type_register_static (GTK_TYPE_OBJECT, "GtkTreeViewColumn", &tree_column_info, 0); + + g_type_add_interface_static (tree_column_type, + GTK_TYPE_CELL_LAYOUT, + &cell_layout_info); } return tree_column_type; @@ -315,6 +348,17 @@ gtk_tree_view_column_class_init (GtkTreeViewColumnClass *class) } static void +gtk_tree_view_column_cell_layout_init (GtkCellLayoutIface *iface) +{ + iface->pack_start = gtk_tree_view_column_cell_layout_pack_start; + iface->pack_end = gtk_tree_view_column_cell_layout_pack_end; + iface->clear = gtk_tree_view_column_cell_layout_clear; + iface->add_attribute = gtk_tree_view_column_cell_layout_add_attribute; + iface->set_cell_data_func = gtk_tree_view_column_cell_layout_set_cell_data_func; + iface->clear_attributes = gtk_tree_view_column_cell_layout_clear_attributes; +} + +static void gtk_tree_view_column_init (GtkTreeViewColumn *tree_column) { tree_column->button = NULL; @@ -552,6 +596,169 @@ gtk_tree_view_column_get_property (GObject *object, } } +/* Implementation of GtkCellLayout interface + */ + +static void +gtk_tree_view_column_cell_layout_pack_start (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + gboolean expand) +{ + GtkTreeViewColumn *column; + GtkTreeViewColumnCellInfo *cell_info; + + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (cell_layout)); + column = GTK_TREE_VIEW_COLUMN (cell_layout); + g_return_if_fail (! gtk_tree_view_column_get_cell_info (column, cell)); + + g_object_ref (cell); + gtk_object_sink (GTK_OBJECT (cell)); + + cell_info = g_new0 (GtkTreeViewColumnCellInfo, 1); + cell_info->cell = cell; + cell_info->expand = expand ? TRUE : FALSE; + cell_info->pack = GTK_PACK_START; + cell_info->has_focus = 0; + cell_info->attributes = NULL; + + column->cell_list = g_list_append (column->cell_list, cell_info); +} + +static void +gtk_tree_view_column_cell_layout_pack_end (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + gboolean expand) +{ + GtkTreeViewColumn *column; + GtkTreeViewColumnCellInfo *cell_info; + + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (cell_layout)); + column = GTK_TREE_VIEW_COLUMN (cell_layout); + g_return_if_fail (! gtk_tree_view_column_get_cell_info (column, cell)); + + g_object_ref (cell); + gtk_object_sink (GTK_OBJECT (cell)); + + cell_info = g_new0 (GtkTreeViewColumnCellInfo, 1); + cell_info->cell = cell; + cell_info->expand = expand ? TRUE : FALSE; + cell_info->pack = GTK_PACK_END; + cell_info->has_focus = 0; + cell_info->attributes = NULL; + + column->cell_list = g_list_append (column->cell_list, cell_info); +} + +static void +gtk_tree_view_column_cell_layout_clear (GtkCellLayout *cell_layout) +{ + GList *list; + GtkTreeViewColumn *column; + + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (cell_layout)); + column = GTK_TREE_VIEW_COLUMN (cell_layout); + + for (list = column->cell_list; list; list = list->next) + { + GtkTreeViewColumnCellInfo *info = (GtkTreeViewColumnCellInfo *)list->data; + + gtk_tree_view_column_clear_attributes (column, info->cell); + g_object_unref (info->cell); + g_free (info); + } + + g_list_free (column->cell_list); + column->cell_list = NULL; +} + +static void +gtk_tree_view_column_cell_layout_add_attribute (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + const gchar *attribute, + gint column) +{ + GtkTreeViewColumn *tree_column; + GtkTreeViewColumnCellInfo *info; + + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (cell_layout)); + tree_column = GTK_TREE_VIEW_COLUMN (cell_layout); + + info = gtk_tree_view_column_get_cell_info (tree_column, cell); + g_return_if_fail (info != NULL); + + info->attributes = g_slist_prepend (info->attributes, GINT_TO_POINTER (column)); + info->attributes = g_slist_prepend (info->attributes, g_strdup (attribute)); + + if (tree_column->tree_view) + _gtk_tree_view_column_cell_set_dirty (tree_column, TRUE); +} + +static void +gtk_tree_view_column_cell_layout_set_cell_data_func (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + GtkCellLayoutDataFunc func, + gpointer func_data, + GDestroyNotify destroy) +{ + GtkTreeViewColumn *column; + GtkTreeViewColumnCellInfo *info; + + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (cell_layout)); + column = GTK_TREE_VIEW_COLUMN (cell_layout); + + info = gtk_tree_view_column_get_cell_info (column, cell); + g_return_if_fail (info != NULL); + + if (info->destroy) + { + GDestroyNotify d = info->destroy; + + info->destroy = NULL; + d (info->func_data); + } + + info->func = (GtkTreeCellDataFunc)func; + info->func_data = func_data; + info->destroy = destroy; + + if (column->tree_view) + _gtk_tree_view_column_cell_set_dirty (column, TRUE); +} + +static void +gtk_tree_view_column_cell_layout_clear_attributes (GtkCellLayout *cell_layout, + GtkCellRenderer *cell_renderer) +{ + GtkTreeViewColumn *column; + GtkTreeViewColumnCellInfo *info; + + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (cell_layout)); + column = GTK_TREE_VIEW_COLUMN (cell_layout); + + info = gtk_tree_view_column_get_cell_info (column, cell_renderer); + gtk_tree_view_column_clear_attributes_by_info (column, info); +} + +static void +gtk_tree_view_column_clear_attributes_by_info (GtkTreeViewColumn *tree_column, + GtkTreeViewColumnCellInfo *info) +{ + GSList *list; + + list = info->attributes; + + while (list && list->next) + { + g_free (list->data); + list = list->next->next; + } + g_slist_free (info->attributes); + info->attributes = NULL; + + if (tree_column->tree_view) + _gtk_tree_view_column_cell_set_dirty (tree_column, TRUE); +} + /* Helper functions */ @@ -1246,23 +1453,7 @@ gtk_tree_view_column_pack_start (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, gboolean expand) { - GtkTreeViewColumnCellInfo *cell_info; - - g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); - g_return_if_fail (GTK_IS_CELL_RENDERER (cell)); - g_return_if_fail (! gtk_tree_view_column_get_cell_info (tree_column, cell)); - - g_object_ref (cell); - gtk_object_sink (GTK_OBJECT (cell)); - - cell_info = g_new0 (GtkTreeViewColumnCellInfo, 1); - cell_info->cell = cell; - cell_info->expand = expand ? TRUE : FALSE; - cell_info->pack = GTK_PACK_START; - cell_info->has_focus = 0; - cell_info->attributes = NULL; - - tree_column->cell_list = g_list_append (tree_column->cell_list, cell_info); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (tree_column), cell, expand); } /** @@ -1280,26 +1471,9 @@ gtk_tree_view_column_pack_end (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, gboolean expand) { - GtkTreeViewColumnCellInfo *cell_info; - - g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); - g_return_if_fail (GTK_IS_CELL_RENDERER (cell)); - g_return_if_fail (! gtk_tree_view_column_get_cell_info (tree_column, cell)); - - g_object_ref (cell); - gtk_object_sink (GTK_OBJECT (cell)); - - cell_info = g_new0 (GtkTreeViewColumnCellInfo, 1); - cell_info->cell = cell; - cell_info->expand = expand ? TRUE : FALSE; - cell_info->pack = GTK_PACK_END; - cell_info->has_focus = 0; - cell_info->attributes = NULL; - - tree_column->cell_list = g_list_append (tree_column->cell_list, cell_info); + gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (tree_column), cell, expand); } - /** * gtk_tree_view_column_clear: * @tree_column: A #GtkTreeViewColumn @@ -1309,20 +1483,7 @@ gtk_tree_view_column_pack_end (GtkTreeViewColumn *tree_column, void gtk_tree_view_column_clear (GtkTreeViewColumn *tree_column) { - GList *list; - g_return_if_fail (tree_column != NULL); - - for (list = tree_column->cell_list; list; list = list->next) - { - GtkTreeViewColumnCellInfo *info = (GtkTreeViewColumnCellInfo *)list->data; - - gtk_tree_view_column_clear_attributes (tree_column, info->cell); - g_object_unref (info->cell); - g_free (info); - } - - g_list_free (tree_column->cell_list); - tree_column->cell_list = NULL; + gtk_cell_layout_clear (GTK_CELL_LAYOUT (tree_column)); } /** @@ -1371,18 +1532,8 @@ gtk_tree_view_column_add_attribute (GtkTreeViewColumn *tree_column, const gchar *attribute, gint column) { - GtkTreeViewColumnCellInfo *info; - - g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); - info = gtk_tree_view_column_get_cell_info (tree_column, cell_renderer); - g_return_if_fail (info != NULL); - - info->attributes = g_slist_prepend (info->attributes, GINT_TO_POINTER (column)); - info->attributes = g_slist_prepend (info->attributes, g_strdup (attribute)); - - if (tree_column->tree_view) - _gtk_tree_view_column_cell_set_dirty (tree_column, TRUE); - + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (tree_column), + cell_renderer, attribute, column); } static void @@ -1454,28 +1605,10 @@ gtk_tree_view_column_set_cell_data_func (GtkTreeViewColumn *tree_column, gpointer func_data, GtkDestroyNotify destroy) { - GtkTreeViewColumnCellInfo *info; - - g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); - g_return_if_fail (GTK_IS_CELL_RENDERER (cell_renderer)); - info = gtk_tree_view_column_get_cell_info (tree_column, cell_renderer); - - g_return_if_fail (info != NULL); - - if (info->destroy) - { - GtkDestroyNotify d = info->destroy; - - info->destroy = NULL; - d (info->func_data); - } - - info->func = func; - info->func_data = func_data; - info->destroy = destroy; - - if (tree_column->tree_view) - _gtk_tree_view_column_cell_set_dirty (tree_column, TRUE); + gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (tree_column), + cell_renderer, + (GtkCellLayoutDataFunc)func, + func_data, destroy); } @@ -1491,37 +1624,10 @@ void gtk_tree_view_column_clear_attributes (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell_renderer) { - GtkTreeViewColumnCellInfo *info; - - g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); - g_return_if_fail (GTK_IS_CELL_RENDERER (cell_renderer)); - - info = gtk_tree_view_column_get_cell_info (tree_column, cell_renderer); - - gtk_tree_view_column_clear_attributes_by_info (tree_column, info); + gtk_cell_layout_clear_attributes (GTK_CELL_LAYOUT (tree_column), + cell_renderer); } -static void -gtk_tree_view_column_clear_attributes_by_info (GtkTreeViewColumn *tree_column, - GtkTreeViewColumnCellInfo *info) -{ - GSList *list; - - list = info->attributes; - - while (list && list->next) - { - g_free (list->data); - list = list->next->next; - } - g_slist_free (info->attributes); - info->attributes = NULL; - - if (tree_column->tree_view) - _gtk_tree_view_column_cell_set_dirty (tree_column, TRUE); -} - - /** * gtk_tree_view_column_set_spacing: * @tree_column: A #GtkTreeViewColumn. diff --git a/po/POTFILES.in b/po/POTFILES.in index cebfa125a..ffa3d0b5d 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -28,6 +28,7 @@ gtk/gtkbbox.c gtk/gtkbox.c gtk/gtkbutton.c gtk/gtkcalendar.c +gtk/gtkcelllayout.c gtk/gtkcellrenderer.c gtk/gtkcellrendererpixbuf.c gtk/gtkcellrenderertext.c @@ -41,6 +42,7 @@ gtk/gtkcontainer.c gtk/gtkcurve.c gtk/gtkdialog.c gtk/gtkentry.c +gtk/gtkentrycompletion.c gtk/gtkfilesel.c gtk/gtkfixed.c gtk/gtkfontsel.c @@ -89,6 +91,7 @@ gtk/gtkthemes.c gtk/gtktipsquery.c gtk/gtktogglebutton.c gtk/gtktoolbar.c +gtk/gtktreemodelfilter.c gtk/gtktreemodelsort.c gtk/gtktreeview.c gtk/gtktreeviewcolumn.c |