From 805fea20cbe22a73bee50a807d63510d69774c9c Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sun, 24 May 2020 17:47:53 +0200 Subject: [PATCH 001/170] eventcontrollermotion: Remove mode argument It's not used and nobody has documented how it's meant to work anyway. --- gtk/gtkeventcontrollermotion.c | 14 +++++--------- gtk/gtkiconview.c | 6 ++---- gtk/gtklabel.c | 2 -- gtk/gtkpopovermenu.c | 3 +-- gtk/gtkpopovermenubar.c | 2 -- gtk/gtkscrolledwindow.c | 1 - gtk/gtktreepopover.c | 1 - gtk/gtktreeview.c | 4 ---- 8 files changed, 8 insertions(+), 25 deletions(-) diff --git a/gtk/gtkeventcontrollermotion.c b/gtk/gtkeventcontrollermotion.c index 180c5cebeb..ba725bd02b 100644 --- a/gtk/gtkeventcontrollermotion.c +++ b/gtk/gtkeventcontrollermotion.c @@ -127,7 +127,7 @@ update_pointer_focus (GtkEventController *controller, } if (leave) - g_signal_emit (controller, signals[LEAVE], 0, crossing->mode); + g_signal_emit (controller, signals[LEAVE], 0); g_object_freeze_notify (G_OBJECT (motion)); if (motion->is_pointer != is_pointer) @@ -143,7 +143,7 @@ update_pointer_focus (GtkEventController *controller, g_object_thaw_notify (G_OBJECT (motion)); if (enter) - g_signal_emit (controller, signals[ENTER], 0, x, y, crossing->mode); + g_signal_emit (controller, signals[ENTER], 0, x, y); } static void @@ -232,7 +232,6 @@ gtk_event_controller_motion_class_init (GtkEventControllerMotionClass *klass) * @controller: the object which received the signal * @x: coordinates of pointer location * @y: coordinates of pointer location - * @mode: crossing mode * * Signals that the pointer has entered the widget. */ @@ -242,15 +241,13 @@ gtk_event_controller_motion_class_init (GtkEventControllerMotionClass *klass) G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, - G_TYPE_NONE, 3, + G_TYPE_NONE, 2, G_TYPE_DOUBLE, - G_TYPE_DOUBLE, - GDK_TYPE_CROSSING_MODE); + G_TYPE_DOUBLE); /** * GtkEventControllerMotion::leave: * @controller: the object which received the signal - * @mode: crossing mode * * Signals that the pointer has left the widget. */ @@ -260,8 +257,7 @@ gtk_event_controller_motion_class_init (GtkEventControllerMotionClass *klass) G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, - G_TYPE_NONE, 1, - GDK_TYPE_CROSSING_MODE); + G_TYPE_NONE, 0); /** * GtkEventControllerMotion::motion: diff --git a/gtk/gtkiconview.c b/gtk/gtkiconview.c index 17a36840af..e907594d2b 100644 --- a/gtk/gtkiconview.c +++ b/gtk/gtkiconview.c @@ -163,7 +163,6 @@ static void gtk_icon_view_motion (GtkEventControl double y, gpointer user_data); static void gtk_icon_view_leave (GtkEventController *controller, - GdkCrossingMode mode, gpointer user_data); static void gtk_icon_view_button_press (GtkGestureClick *gesture, int n_press, @@ -1878,9 +1877,8 @@ gtk_icon_view_motion (GtkEventController *controller, } static void -gtk_icon_view_leave(GtkEventController *controller, - GdkCrossingMode mode, - gpointer user_data) +gtk_icon_view_leave (GtkEventController *controller, + gpointer user_data) { GtkIconView *icon_view; GtkIconViewPrivate *priv; diff --git a/gtk/gtklabel.c b/gtk/gtklabel.c index a66581cb48..688c4b3460 100644 --- a/gtk/gtklabel.c +++ b/gtk/gtklabel.c @@ -439,7 +439,6 @@ static void gtk_label_motion (GtkEventControllerMotion *controller, double y, gpointer data); static void gtk_label_leave (GtkEventControllerMotion *controller, - GdkCrossingMode mode, gpointer data); static gboolean gtk_label_grab_focus (GtkWidget *widget); @@ -4287,7 +4286,6 @@ gtk_label_motion (GtkEventControllerMotion *controller, static void gtk_label_leave (GtkEventControllerMotion *controller, - GdkCrossingMode mode, gpointer data) { GtkLabel *self = GTK_LABEL (data); diff --git a/gtk/gtkpopovermenu.c b/gtk/gtkpopovermenu.c index 718cfe1d58..127683ea3e 100644 --- a/gtk/gtkpopovermenu.c +++ b/gtk/gtkpopovermenu.c @@ -242,14 +242,13 @@ focus_out (GtkEventController *controller, static void leave_cb (GtkEventController *controller, - GdkCrossingMode mode, gpointer data) { GtkWidget *target; target = gtk_event_controller_get_widget (controller); - if (mode == GDK_CROSSING_NORMAL) + if (!gtk_event_controller_motion_contains_pointer (GTK_EVENT_CONTROLLER_MOTION (controller))) gtk_popover_menu_set_active_item (GTK_POPOVER_MENU (target), NULL); } diff --git a/gtk/gtkpopovermenubar.c b/gtk/gtkpopovermenubar.c index e563bfa1d4..005fc5e50f 100644 --- a/gtk/gtkpopovermenubar.c +++ b/gtk/gtkpopovermenubar.c @@ -169,7 +169,6 @@ static void item_enter_cb (GtkEventController *controller, double x, double y, - GdkCrossingMode mode, gpointer data) { GtkWidget *target; @@ -183,7 +182,6 @@ item_enter_cb (GtkEventController *controller, static void bar_leave_cb (GtkEventController *controller, - GdkCrossingMode mode, gpointer data) { GtkWidget *target; diff --git a/gtk/gtkscrolledwindow.c b/gtk/gtkscrolledwindow.c index ba7eb7338b..00e088cd7e 100644 --- a/gtk/gtkscrolledwindow.c +++ b/gtk/gtkscrolledwindow.c @@ -477,7 +477,6 @@ add_tab_bindings (GtkWidgetClass *widget_class, static void motion_controller_leave (GtkEventController *controller, - GdkCrossingMode mode, GtkScrolledWindow *scrolled_window) { GtkScrolledWindowPrivate *priv = gtk_scrolled_window_get_instance_private (scrolled_window); diff --git a/gtk/gtktreepopover.c b/gtk/gtktreepopover.c index dba7de69e0..232a85d9f8 100644 --- a/gtk/gtktreepopover.c +++ b/gtk/gtktreepopover.c @@ -617,7 +617,6 @@ static void enter_cb (GtkEventController *controller, double x, double y, - GdkCrossingMode mode, GtkTreePopover *popover) { GtkWidget *item; diff --git a/gtk/gtktreeview.c b/gtk/gtktreeview.c index 597dc3a3d0..26f05fed9d 100644 --- a/gtk/gtktreeview.c +++ b/gtk/gtktreeview.c @@ -974,10 +974,8 @@ static void gtk_tree_view_drag_gesture_end (GtkGestureDrag *ges static void gtk_tree_view_motion_controller_enter (GtkEventControllerMotion *controller, double x, double y, - GdkCrossingMode mode, GtkTreeView *tree_view); static void gtk_tree_view_motion_controller_leave (GtkEventControllerMotion *controller, - GdkCrossingMode mode, GtkTreeView *tree_view); static void gtk_tree_view_motion_controller_motion (GtkEventControllerMotion *controller, double x, @@ -5463,7 +5461,6 @@ static void gtk_tree_view_motion_controller_enter (GtkEventControllerMotion *controller, double x, double y, - GdkCrossingMode mode, GtkTreeView *tree_view) { GtkTreeRBTree *tree; @@ -5489,7 +5486,6 @@ gtk_tree_view_motion_controller_enter (GtkEventControllerMotion *controller, static void gtk_tree_view_motion_controller_leave (GtkEventControllerMotion *controller, - GdkCrossingMode mode, GtkTreeView *tree_view) { if (tree_view->prelight_node) From 682e97826c99e56c79aca8b5d54e171a18f63546 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 2 Oct 2019 05:38:11 +0200 Subject: [PATCH 002/170] Add GtkDirectoryList Adds a new listmodel called GtkDirectoryList that lists the children of a GFile as GFileInfos. This is supposed to be used by the filechooser. --- docs/reference/gtk/gtk4-docs.xml | 1 + docs/reference/gtk/gtk4-sections.txt | 24 + docs/reference/gtk/gtk4.types.in | 1 + gtk/gtk.h | 1 + gtk/gtkdirectorylist.c | 681 +++++++++++++++++++++++++++ gtk/gtkdirectorylist.h | 67 +++ gtk/meson.build | 2 + 7 files changed, 777 insertions(+) create mode 100644 gtk/gtkdirectorylist.c create mode 100644 gtk/gtkdirectorylist.h diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index 7d9321bb1b..22e5fa0c82 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -56,6 +56,7 @@ + diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 97d20ece0b..d519009a94 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -1160,6 +1160,30 @@ GTK_TYPE_FILE_FILTER gtk_file_filter_get_type +
+gtkdirectorylist +GtkDirectoryList +GtkDirectoryList +gtk_directory_list_new +gtk_directory_list_get_attributes +gtk_directory_list_set_attributes +gtk_directory_list_get_file +gtk_directory_list_set_file +gtk_directory_list_get_io_priority +gtk_directory_list_set_io_priority +gtk_directory_list_is_loading +gtk_directory_list_get_error + +GTK_DIRECTORY_LIST +GTK_IS_DIRECTORY_LIST +GTK_TYPE_DIRECTORY_LIST +GTK_DIRECTORY_LIST_CLASS +GTK_IS_DIRECTORY_LIST_CLASS +GTK_DIRECTORY_LIST_GET_CLASS + +gtk_directory_list_get_type +
+
gtkfilterlistmodel GtkFilterListModel diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in index 416ab5bc97..d93f495c80 100644 --- a/docs/reference/gtk/gtk4.types.in +++ b/docs/reference/gtk/gtk4.types.in @@ -54,6 +54,7 @@ gtk_constraint_layout_get_type gtk_constraint_target_get_type gtk_css_provider_get_type gtk_dialog_get_type +gtk_directory_list_get_type gtk_drag_icon_get_type gtk_drag_source_get_type gtk_drawing_area_get_type diff --git a/gtk/gtk.h b/gtk/gtk.h index eba47fb967..3a6312d35d 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -86,6 +86,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkdirectorylist.c b/gtk/gtkdirectorylist.c new file mode 100644 index 0000000000..053305b674 --- /dev/null +++ b/gtk/gtkdirectorylist.c @@ -0,0 +1,681 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gtkdirectorylist.h" + +#include "gtkintl.h" +#include "gtkprivate.h" + +/** + * SECTION:gtkdirectorylist + * @title: GtkDirectoryList + * @short_description: A list model for directory listings + * @see_also: #GListModel, g_file_enumerate_children() + * + * #GtkDirectoryList is a list model that wraps g_file_enumerate_children_async(). + * It presents a #GListModel and fills it asynchronously with the #GFileInfos + * returned from that function. + * + * Enumeration will start automatically when a the GtkDirectoryList:file property + * is set. + * + * While the #GtkDirectoryList is being filled, the GtkDirectoryList:loading + * property will be set to %TRUE. You can listen to that property if you want + * to show information like a #GtkSpinner or a "Loading..." text. + * If loading fails at any point, the GtkDirectoryList:error property will be set + * to give more indication about the failure. + * + * The #GFileInfos returned from a #GtkDirectoryList have the "standard::file" + * attribute set to the #GFile they refer to. This way you can get at the file + * that is referred to in the same way you would via g_file_enumerator_get_child(). + * This means you do not need access to the #GtkDirectoryList but can access + * the #GFile directly from the #GFileInfo when operating with a #GtkListView or + * similar. + */ + +/* random number that everyone else seems to use, too */ +#define FILES_PER_QUERY 100 + +enum { + PROP_0, + PROP_ATTRIBUTES, + PROP_ERROR, + PROP_FILE, + PROP_IO_PRIORITY, + PROP_ITEM_TYPE, + PROP_LOADING, + NUM_PROPERTIES +}; + +struct _GtkDirectoryList +{ + GObject parent_instance; + + char *attributes; + int io_priority; + GFile *file; + + GCancellable *cancellable; + GError *error; /* Error while loading */ + GSequence *items; /* Use GPtrArray or GListStore here? */ +}; + +struct _GtkDirectoryListClass +{ + GObjectClass parent_class; +}; + +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +static GType +gtk_directory_list_get_item_type (GListModel *list) +{ + return G_TYPE_FILE_INFO; +} + +static guint +gtk_directory_list_get_n_items (GListModel *list) +{ + GtkDirectoryList *self = GTK_DIRECTORY_LIST (list); + + return g_sequence_get_length (self->items); +} + +static gpointer +gtk_directory_list_get_item (GListModel *list, + guint position) +{ + GtkDirectoryList *self = GTK_DIRECTORY_LIST (list); + GSequenceIter *iter; + + iter = g_sequence_get_iter_at_pos (self->items, position); + + if (g_sequence_iter_is_end (iter)) + return NULL; + else + return g_object_ref (g_sequence_get (iter)); +} + +static void +gtk_directory_list_model_init (GListModelInterface *iface) +{ + iface->get_item_type = gtk_directory_list_get_item_type; + iface->get_n_items = gtk_directory_list_get_n_items; + iface->get_item = gtk_directory_list_get_item; +} + +G_DEFINE_TYPE_WITH_CODE (GtkDirectoryList, gtk_directory_list, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_directory_list_model_init)) + +static void +gtk_directory_list_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkDirectoryList *self = GTK_DIRECTORY_LIST (object); + + switch (prop_id) + { + case PROP_ATTRIBUTES: + gtk_directory_list_set_attributes (self, g_value_get_string (value)); + break; + + case PROP_FILE: + gtk_directory_list_set_file (self, g_value_get_object (value)); + break; + + case PROP_IO_PRIORITY: + gtk_directory_list_set_io_priority (self, g_value_get_int (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_directory_list_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkDirectoryList *self = GTK_DIRECTORY_LIST (object); + + switch (prop_id) + { + case PROP_ATTRIBUTES: + g_value_set_string (value, self->attributes); + break; + + case PROP_ERROR: + g_value_set_boxed (value, self->error); + break; + + case PROP_FILE: + g_value_set_object (value, self->file); + break; + + case PROP_IO_PRIORITY: + g_value_set_int (value, self->io_priority); + break; + + case PROP_ITEM_TYPE: + g_value_set_gtype (value, G_TYPE_FILE_INFO); + break; + + case PROP_LOADING: + g_value_set_boolean (value, gtk_directory_list_is_loading (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +gtk_directory_list_stop_loading (GtkDirectoryList *self) +{ + if (self->cancellable == NULL) + return FALSE; + + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); + return TRUE; +} + +static void +gtk_directory_list_dispose (GObject *object) +{ + GtkDirectoryList *self = GTK_DIRECTORY_LIST (object); + + gtk_directory_list_stop_loading (self); + + g_clear_object (&self->file); + g_clear_pointer (&self->attributes, g_free); + + g_clear_error (&self->error); + g_clear_pointer (&self->items, g_sequence_free); + + G_OBJECT_CLASS (gtk_directory_list_parent_class)->dispose (object); +} + +static void +gtk_directory_list_class_init (GtkDirectoryListClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + gobject_class->set_property = gtk_directory_list_set_property; + gobject_class->get_property = gtk_directory_list_get_property; + gobject_class->dispose = gtk_directory_list_dispose; + + /** + * GtkDirectoryList:attributes: + * + * The attributes to query + */ + properties[PROP_ATTRIBUTES] = + g_param_spec_string ("attributes", + P_("attributes"), + P_("Attributes to query"), + NULL, + GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkDirectoryList:error: + * + * Error encountered while loading files + */ + properties[PROP_ERROR] = + g_param_spec_boxed ("error", + P_("error"), + P_("Error encountered while loading files"), + G_TYPE_ERROR, + GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkDirectoryList:file: + * + * File to query + */ + properties[PROP_FILE] = + g_param_spec_object ("file", + P_("File"), + P_("The file to query"), + G_TYPE_FILE, + GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkDirectoryList:io-priority: + * + * Priority used when loading + */ + properties[PROP_IO_PRIORITY] = + g_param_spec_int ("io-priority", + P_("IO priority"), + P_("Priority used when loading"), + -G_MAXINT, G_MAXINT, G_PRIORITY_DEFAULT, + GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkDirectoryList:item-type: + * + * The #GType for elements of this object + */ + properties[PROP_ITEM_TYPE] = + g_param_spec_gtype ("item-type", + P_("Item type"), + P_("The type of elements of this object"), + G_TYPE_FILE_INFO, + GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkDirectoryList:loading: + * + * %TRUE if files are being loaded + */ + properties[PROP_LOADING] = + g_param_spec_boolean ("loading", + P_("loading"), + P_("TRUE if files are being loaded"), + FALSE, + GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties); +} + +static void +gtk_directory_list_init (GtkDirectoryList *self) +{ + self->items = g_sequence_new (g_object_unref); + self->io_priority = G_PRIORITY_DEFAULT; +} + +/** + * gtk_directory_list_new: + * @file: (allow-none): The file to query + * @attributes: (allow-none): The attributes to query with + * + * Creates a new #GtkDirectoryList querying the given @file with the given + * @attributes. + * + * Returns: a new #GtkDirectoryList + **/ +GtkDirectoryList * +gtk_directory_list_new (const char *attributes, + GFile *file) + +{ + g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL); + + return g_object_new (GTK_TYPE_DIRECTORY_LIST, + "attributes", attributes, + "file", file, + NULL); +} + +static void +gtk_directory_list_clear_items (GtkDirectoryList *self) +{ + guint n_items; + + n_items = g_sequence_get_length (self->items); + if (n_items > 0) + { + g_sequence_remove_range (g_sequence_get_begin_iter (self->items), + g_sequence_get_end_iter (self->items)); + + g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, 0); + } + + if (self->error) + { + g_clear_error (&self->error); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]); + } +} + +static void +gtk_directory_list_enumerator_closed_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + g_file_enumerator_close_finish (G_FILE_ENUMERATOR (source), res, NULL); +} + +static void +gtk_directory_list_got_files_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + GtkDirectoryList *self = user_data; /* invalid if cancelled */ + GFileEnumerator *enumerator = G_FILE_ENUMERATOR (source); + GError *error = NULL; + GList *l, *files; + guint n; + + files = g_file_enumerator_next_files_finish (enumerator, res, &error); + + if (files == NULL) + { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_clear_error (&error); + return; + } + + g_file_enumerator_close_async (enumerator, + self->io_priority, + NULL, + gtk_directory_list_enumerator_closed_cb, + NULL); + + g_object_freeze_notify (G_OBJECT (self)); + + g_clear_object (&self->cancellable); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]); + + if (error) + { + self->error = error; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]); + } + + g_object_thaw_notify (G_OBJECT (self)); + return; + } + + n = 0; + for (l = files; l; l = l->next) + { + GFileInfo *info; + GFile *file; + + info = l->data; + file = g_file_enumerator_get_child (enumerator, info); + g_file_info_set_attribute_object (info, "standard::file", G_OBJECT (file)); + g_object_unref (file); + g_sequence_append (self->items, info); + n++; + } + g_list_free (files); + + g_file_enumerator_next_files_async (enumerator, + g_file_is_native (self->file) ? 50 * FILES_PER_QUERY : FILES_PER_QUERY, + self->io_priority, + self->cancellable, + gtk_directory_list_got_files_cb, + self); + + if (n > 0) + g_list_model_items_changed (G_LIST_MODEL (self), g_sequence_get_length (self->items) - n, 0, n); +} + +static void +gtk_directory_list_got_enumerator_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + GtkDirectoryList *self = user_data; /* invalid if cancelled */ + GFile *file = G_FILE (source); + GFileEnumerator *enumerator; + GError *error = NULL; + + enumerator = g_file_enumerate_children_finish (file, res, &error); + if (enumerator == NULL) + { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_clear_error (&error); + return; + } + + g_object_freeze_notify (G_OBJECT (self)); + self->error = error; + g_clear_object (&self->cancellable); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]); + g_object_thaw_notify (G_OBJECT (self)); + return; + } + + g_file_enumerator_next_files_async (enumerator, + g_file_is_native (file) ? 50 * FILES_PER_QUERY : FILES_PER_QUERY, + self->io_priority, + self->cancellable, + gtk_directory_list_got_files_cb, + self); + g_object_unref (enumerator); +} + +static void +gtk_directory_list_start_loading (GtkDirectoryList *self) +{ + gboolean was_loading; + + was_loading = gtk_directory_list_stop_loading (self); + gtk_directory_list_clear_items (self); + + if (self->file == NULL) + { + if (was_loading) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]); + return; + } + + self->cancellable = g_cancellable_new (); + g_file_enumerate_children_async (self->file, + self->attributes, + G_FILE_QUERY_INFO_NONE, + self->io_priority, + self->cancellable, + gtk_directory_list_got_enumerator_cb, + self); + + if (!was_loading) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]); +} + +/** + * gtk_directory_list_set_file: + * @self: a #GtkDirectoryList + * @file: (allow-none): the #GFile to be enumerated + * + * Sets the @file to be enumerated and starts the enumeration. + * + * If @file is %NULL, the result will be an empty list. + */ +void +gtk_directory_list_set_file (GtkDirectoryList *self, + GFile *file) +{ + g_return_if_fail (GTK_IS_DIRECTORY_LIST (self)); + g_return_if_fail (file == NULL || G_IS_FILE (file)); + + if (self->file == file || + (self->file && file && g_file_equal (self->file, file))) + return; + + g_object_freeze_notify (G_OBJECT (self)); + + g_set_object (&self->file, file); + + gtk_directory_list_start_loading (self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILE]); + + g_object_thaw_notify (G_OBJECT (self)); +} + +/** + * gtk_directory_list_get_file: + * @self: a #GtkDirectoryList + * + * Gets the file whose children are currently enumerated. + * + * Returns: (nullable) (transfer none): The file whose children are enumerated + **/ +GFile * +gtk_directory_list_get_file (GtkDirectoryList *self) +{ + g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), NULL); + + return self->file; +} + +/** + * gtk_directory_list_set_attributes: + * @self: a #GtkDirectoryList + * @attributes: (allow-none): the attributes to enumerate + * + * Sets the @attributes to be enumerated and starts the enumeration. + * + * If @attributes is %NULL, no attributes will be queried, but a list + * of #GFileInfos will still be created. + */ +void +gtk_directory_list_set_attributes (GtkDirectoryList *self, + const char *attributes) +{ + g_return_if_fail (GTK_IS_DIRECTORY_LIST (self)); + + if (self->attributes == attributes) + return; + + g_object_freeze_notify (G_OBJECT (self)); + + g_free (self->attributes); + self->attributes = g_strdup (attributes); + + gtk_directory_list_start_loading (self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ATTRIBUTES]); + + g_object_thaw_notify (G_OBJECT (self)); +} + +/** + * gtk_directory_list_get_attributes: + * @self: a #GtkDirectoryList + * + * Gets the attributes queried on the children. + * + * Returns: (nullable) (transfer none): The queried attributes + */ +const char * +gtk_directory_list_get_attributes (GtkDirectoryList *self) +{ + g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), NULL); + + return self->attributes; +} + +/** + * gtk_directory_list_set_io_priority: + * @self: a #GtkDirectoryList + * @io_priority: IO priority to use + * + * Sets the IO priority to use while loading directories. + * + * Setting the priority while @self is loading will reprioritize the + * ongoing load as soon as possible. + * + * The default IO priority is %G_PRIORITY_DEFAULT, which is higher than + * the GTK redraw priority. If you are loading a lot of directories in + * parrallel, lowering it to something like %G_PRIORITY_DEFAULT_IDLE + * may increase responsiveness. + */ +void +gtk_directory_list_set_io_priority (GtkDirectoryList *self, + int io_priority) +{ + g_return_if_fail (GTK_IS_DIRECTORY_LIST (self)); + + if (self->io_priority == io_priority) + return; + + self->io_priority = io_priority; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_IO_PRIORITY]); +} + +/** + * gtk_directory_list_get_io_priority: + * @self: a #GtkDirectoryList + * + * Gets the IO priority set via gtk_directory_list_set_io_priority(). + * + * Returns: The IO priority. + */ +int +gtk_directory_list_get_io_priority (GtkDirectoryList *self) +{ + g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), G_PRIORITY_DEFAULT); + + return self->io_priority; +} + +/** + * gtk_directory_list_is_loading: + * @self: a #GtkDirectoryList + * + * Returns %TRUE if the children enumeration is currently in + * progress. + * Files will be added to @self from time to time while loading is + * going on. The order in which are added is undefined and may change + * inbetween runs. + * + * Returns: %TRUE if @self is loading + */ +gboolean +gtk_directory_list_is_loading (GtkDirectoryList *self) +{ + g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), FALSE); + + return self->cancellable != NULL; +} + +/** + * gtk_directory_list_get_error: + * @self: a #GtkDirectoryList + * + * Gets the loading error, if any. + * + * If an error occurs during the loading process, the loading process + * will finish and this property allows querying the error that happened. + * This error will persist until a file is loaded again. + * + * An error being set does not mean that no files were loaded, and all + * successfully queried files will remain in the list. + * + * Returns: (nullable) (transfer none): The loading error or %NULL if + * loading finished successfully. + */ +const GError * +gtk_directory_list_get_error (GtkDirectoryList *self) +{ + g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), FALSE); + + return self->error; +} + diff --git a/gtk/gtkdirectorylist.h b/gtk/gtkdirectorylist.h new file mode 100644 index 0000000000..0952224c40 --- /dev/null +++ b/gtk/gtkdirectorylist.h @@ -0,0 +1,67 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __GTK_DIRECTORY_LIST_H__ +#define __GTK_DIRECTORY_LIST_H__ + + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +/* for GDK_AVAILABLE_IN_ALL */ +#include + + +G_BEGIN_DECLS + +#define GTK_TYPE_DIRECTORY_LIST (gtk_directory_list_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkDirectoryList, gtk_directory_list, GTK, DIRECTORY_LIST, GObject) + +GDK_AVAILABLE_IN_ALL +GtkDirectoryList * gtk_directory_list_new (const char *attributes, + GFile *file); + +GDK_AVAILABLE_IN_ALL +void gtk_directory_list_set_file (GtkDirectoryList *self, + GFile *file); +GDK_AVAILABLE_IN_ALL +GFile * gtk_directory_list_get_file (GtkDirectoryList *self); +GDK_AVAILABLE_IN_ALL +void gtk_directory_list_set_attributes (GtkDirectoryList *self, + const char *attributes); +GDK_AVAILABLE_IN_ALL +const char * gtk_directory_list_get_attributes (GtkDirectoryList *self); +GDK_AVAILABLE_IN_ALL +void gtk_directory_list_set_io_priority (GtkDirectoryList *self, + int io_priority); +GDK_AVAILABLE_IN_ALL +int gtk_directory_list_get_io_priority (GtkDirectoryList *self); + +GDK_AVAILABLE_IN_ALL +gboolean gtk_directory_list_is_loading (GtkDirectoryList *self); +GDK_AVAILABLE_IN_ALL +const GError * gtk_directory_list_get_error (GtkDirectoryList *self); + +G_END_DECLS + +#endif /* __GTK_DIRECTORY_LIST_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index 9012ae1a5c..260e3ebd1b 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -201,6 +201,7 @@ gtk_public_sources = files([ 'gtkcssprovider.c', 'gtkcustomlayout.c', 'gtkdialog.c', + 'gtkdirectorylist.c', 'gtkdragicon.c', 'gtkdragsource.c', 'gtkdrawingarea.c', @@ -455,6 +456,7 @@ gtk_public_headers = files([ 'gtkcustomlayout.h', 'gtkdebug.h', 'gtkdialog.h', + 'gtkdirectorylist.h', 'gtkdragicon.h', 'gtkdragsource.h', 'gtkdrawingarea.h', From 1df17f2feaf37bc4f0aa1f18d26f2deba91f7dd7 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 9 Nov 2019 02:55:48 +0100 Subject: [PATCH 003/170] Add GtkFilter --- docs/reference/gtk/gtk4-docs.xml | 3 + docs/reference/gtk/gtk4-sections.txt | 20 +++ docs/reference/gtk/gtk4.types.in | 1 + gtk/gtk.h | 1 + gtk/gtkfilter.c | 181 +++++++++++++++++++++++++++ gtk/gtkfilter.h | 121 ++++++++++++++++++ gtk/meson.build | 2 + 7 files changed, 329 insertions(+) create mode 100644 gtk/gtkfilter.c create mode 100644 gtk/gtkfilter.h diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index 22e5fa0c82..48d28d412c 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -49,6 +49,9 @@ GListModel support +
+ +
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index d519009a94..eda7e15c96 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -1184,6 +1184,26 @@ GTK_DIRECTORY_LIST_GET_CLASS gtk_directory_list_get_type
+
+gtkfilter +GtkFilter +GtkFilter +gtk_filter_match +gtk_filter_get_strictness + +GtkFilterChange +gtk_filter_changed + +GTK_FILTER +GTK_IS_FILTER +GTK_TYPE_FILTER +GTK_FILTER_CLASS +GTK_IS_FILTER_CLASS +GTK_FILTER_GET_CLASS + +gtk_filter_get_type +
+
gtkfilterlistmodel GtkFilterListModel diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in index d93f495c80..df153a01e4 100644 --- a/docs/reference/gtk/gtk4.types.in +++ b/docs/reference/gtk/gtk4.types.in @@ -77,6 +77,7 @@ gtk_file_chooser_dialog_get_type gtk_file_chooser_get_type gtk_file_chooser_widget_get_type gtk_file_filter_get_type +gtk_filter_get_type gtk_filter_list_model_get_type gtk_fixed_get_type gtk_fixed_layout_get_type diff --git a/gtk/gtk.h b/gtk/gtk.h index 3a6312d35d..6953d0d3e5 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -114,6 +114,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkfilter.c b/gtk/gtkfilter.c new file mode 100644 index 0000000000..697359d74b --- /dev/null +++ b/gtk/gtkfilter.c @@ -0,0 +1,181 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gtkfilter.h" + +#include "gtkintl.h" +#include "gtktypebuiltins.h" + +/** + * SECTION:gtkfilter + * @Title: GtkFilter + * @Short_description: Filtering items + * @See_also: #GtkFilerListModel + * + * #GtkFilter is the way to describe filters to be used in #GtkFilterListModel. + * + * The model will use a filter to determine if it should filter items or not + * by calling gtk_filter_match() for each item and only keeping the ones + * visible that the function returns %TRUE for. + * + * Filters may change what items they match through their lifetime. In that + * case, they can call gtk_filter_changed() which will emit the #GtkFilter:changed + * signal to notify that previous filter results are no longer valid and that + * items should be checked via gtk_filter_match() again. + * + * GTK provides various premade filter implementations for common filtering + * operations. These filters often include properties that can be linked to + * various widgets to easily allow searches. + * + * However, in particular for large lists or complex search methods, it is + * also possible to subclass #GtkFilter and provide one's own filter. + */ + +enum { + CHANGED, + LAST_SIGNAL +}; + +G_DEFINE_TYPE (GtkFilter, gtk_filter, G_TYPE_OBJECT) + +static guint signals[LAST_SIGNAL] = { 0 }; + +static gboolean +gtk_filter_default_match (GtkFilter *self, + gpointer item) +{ + g_critical ("Filter of type '%s' does not implement GtkFilter::match", G_OBJECT_TYPE_NAME (self)); + + return FALSE; +} + +static GtkFilterMatch +gtk_filter_default_get_strictness (GtkFilter *self) +{ + return GTK_FILTER_MATCH_SOME; +} + +static void +gtk_filter_class_init (GtkFilterClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + class->match = gtk_filter_default_match; + class->get_strictness = gtk_filter_default_get_strictness; + + /** + * GtkFilter:changed: + * @self: The #GtkFilter + * @change: how the filter changed + * + * This signal is emitted whenever the filter changed. Users of the filter + * should then check items again via gtk_filter_match(). + * + * #GtkFilterListModel handles this signal automatically. + * + * Depending on the @change parameter, not all items need to be changed, but + * only some. Refer to the #GtkFilterChange documentation for details. + */ + signals[CHANGED] = + g_signal_new (I_("changed"), + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__ENUM, + G_TYPE_NONE, 1, + GTK_TYPE_FILTER_CHANGE); + g_signal_set_va_marshaller (signals[CHANGED], + G_TYPE_FROM_CLASS (gobject_class), + g_cclosure_marshal_VOID__ENUMv); +} + +static void +gtk_filter_init (GtkFilter *self) +{ +} + +/** + * gtk_filter_match: + * @self: a #GtkFilter + * @item: (type GObject) (transfer none): The item to check + * + * Checks if the given @item is matched by the filter or not. + * + * Returns: %TRUE if the filter matches the item and a filter model should + * keep it, %FALSE if not. + */ +gboolean +gtk_filter_match (GtkFilter *self, + gpointer item) +{ + g_return_val_if_fail (GTK_IS_FILTER (self), FALSE); + g_return_val_if_fail (item != NULL, FALSE); + + return GTK_FILTER_GET_CLASS (self)->match (self, item); +} + +/** + * gtk_filter_get_strictness: + * @self: a #GtkFilter + * + * Gets the known strictness of @filters. If the strictness is not known, + * %GTK_FILTER_MATCH_SOME is returned. + * + * This value may change after emission of the GtkFilter:changed signal. + * + * This function is meant purely for optimization purposes, filters can + * choose to omit implementing it, but #GtkFilterListModel uses it. + * + * Returns: the strictness of @self + **/ +GtkFilterMatch +gtk_filter_get_strictness (GtkFilter *self) +{ + g_return_val_if_fail (GTK_IS_FILTER (self), GTK_FILTER_MATCH_SOME); + + return GTK_FILTER_GET_CLASS (self)->get_strictness (self); +} + +/** + * gtk_filter_changed: + * @self: a #GtkFilter + * @change: How the filter changed + * + * Emits the #GtkFilter:changed signal to notify all users of the filter that + * the filter changed. Users of the filter should then check items again via + * gtk_filter_match(). + * + * Depending on the @change parameter, not all items need to be changed, but + * only some. Refer to the #GtkFilterChange documentation for details. + * + * This function is intended for implementors of #GtkFilter subclasses and + * should not be called from other functions. + */ +void +gtk_filter_changed (GtkFilter *self, + GtkFilterChange change) +{ + g_return_if_fail (GTK_IS_FILTER (self)); + + g_signal_emit (self, signals[CHANGED], 0, change); +} + diff --git a/gtk/gtkfilter.h b/gtk/gtkfilter.h new file mode 100644 index 0000000000..fcd1c7b02c --- /dev/null +++ b/gtk/gtkfilter.h @@ -0,0 +1,121 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __GTK_FILTER_H__ +#define __GTK_FILTER_H__ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +/** + * GtkFilterMatch: + * @GTK_FILTER_MATCH_SOME: The filter matches some items, + * gtk_filter_match() may return %TRUE or %FALSE + * @GTK_FILTER_MATCH_NONE: The filter does not match any item, + * gtk_filter_match() will always return %FALSE. + * @GTK_FILTER_MATCH_ALL: The filter matches all items, + * gtk_filter_match() will alays return %TRUE. + * + * Describes the known strictness of a filter. + * + * Note that for filters where the strictness is not known, + * %@GTK_FILTER_MATCH_SOME is always an acceptable value, + * even if a filter does match all or no items. + */ +typedef enum { + GTK_FILTER_MATCH_SOME = 0, + GTK_FILTER_MATCH_NONE, + GTK_FILTER_MATCH_ALL +} GtkFilterMatch; + +/** + * GtkFilterChange: + * @GTK_FILTER_CHANGE_DIFFERENT: The filter change cannot be + * described with any of the other enumeration values. + * @GTK_FILTER_CHANGE_LESS_STRICT: The filter is less strict than + * it was before: All items that it used to return %TRUE for + * still return %TRUE, others now may, too. + * @GTK_FILTER_CHANGE_MORE_STRICT: The filter is more strict than + * it was before: All items that it used to return %FALSE for + * still return %FALSE, others now may, too. + * + * Describes changes in a filter in more detail and allows objects + * using the filter to optimize refiltering items. + * + * If you are writing an implementation and are not sure which + * value to pass, @GTK_FILTER_CHANGE_DIFFERENT is always a correct + * choice. + */ +typedef enum { + GTK_FILTER_CHANGE_DIFFERENT = 0, + GTK_FILTER_CHANGE_LESS_STRICT, + GTK_FILTER_CHANGE_MORE_STRICT, +} GtkFilterChange; + +#define GTK_TYPE_FILTER (gtk_filter_get_type ()) + +/** + * GtkFilter: + * + * The object describing a filter. + */ +GDK_AVAILABLE_IN_ALL +G_DECLARE_DERIVABLE_TYPE (GtkFilter, gtk_filter, GTK, FILTER, GObject) + +struct _GtkFilterClass +{ + GObjectClass parent_class; + + gboolean (* match) (GtkFilter *self, + gpointer item); + + /* optional */ + GtkFilterMatch (* get_strictness) (GtkFilter *self); + + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); + void (*_gtk_reserved3) (void); + void (*_gtk_reserved4) (void); + void (*_gtk_reserved5) (void); + void (*_gtk_reserved6) (void); + void (*_gtk_reserved7) (void); + void (*_gtk_reserved8) (void); +}; + +GDK_AVAILABLE_IN_ALL +gboolean gtk_filter_match (GtkFilter *self, + gpointer item); +GDK_AVAILABLE_IN_ALL +GtkFilterMatch gtk_filter_get_strictness (GtkFilter *self); + +/* for filter implementations */ +GDK_AVAILABLE_IN_ALL +void gtk_filter_changed (GtkFilter *self, + GtkFilterChange change); + + +G_END_DECLS + +#endif /* __GTK_FILTER_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index 260e3ebd1b..91fb469abc 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -227,6 +227,7 @@ gtk_public_sources = files([ 'gtkfilechoosernative.c', 'gtkfilechooserwidget.c', 'gtkfilefilter.c', + 'gtkfilter.c', 'gtkfilterlistmodel.c', 'gtkfixed.c', 'gtkfixedlayout.c', @@ -482,6 +483,7 @@ gtk_public_headers = files([ 'gtkfilechoosernative.h', 'gtkfilechooserwidget.h', 'gtkfilefilter.h', + 'gtkfilter.h', 'gtkfilterlistmodel.h', 'gtkfixed.h', 'gtkfixedlayout.h', From 1ab081b584003fc84bcff2ef68f994aa0fa2f858 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sun, 10 Nov 2019 01:30:02 +0100 Subject: [PATCH 004/170] Add GtkCustomFilter --- docs/reference/gtk/gtk4-docs.xml | 1 + docs/reference/gtk/gtk4-sections.txt | 19 ++++ docs/reference/gtk/gtk4.types.in | 1 + gtk/gtk.h | 1 + gtk/gtkcustomfilter.c | 157 +++++++++++++++++++++++++++ gtk/gtkcustomfilter.h | 60 ++++++++++ gtk/meson.build | 2 + 7 files changed, 241 insertions(+) create mode 100644 gtk/gtkcustomfilter.c create mode 100644 gtk/gtkcustomfilter.h diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index 48d28d412c..3495ec1607 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -51,6 +51,7 @@
+
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index eda7e15c96..fdbcf599a3 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -1193,6 +1193,8 @@ gtk_filter_get_strictness GtkFilterChange gtk_filter_changed + +gtk_custom_filter_new GTK_FILTER GTK_IS_FILTER @@ -1204,6 +1206,23 @@ GTK_FILTER_GET_CLASS gtk_filter_get_type
+
+gtkcustomfilter +GtkCustomFilter +GtkCustomFilter +GtkCustomFilterFunc +gtk_custom_filter_new + +GTK_CUSTOM_FILTER +GTK_IS_CUSTOM_FILTER +GTK_TYPE_CUSTOM_FILTER +GTK_CUSTOM_FILTER_CLASS +GTK_IS_CUSTOM_FILTER_CLASS +GTK_CUSTOM_FILTER_GET_CLASS + +gtk_custom_filter_get_type +
+
gtkfilterlistmodel GtkFilterListModel diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in index df153a01e4..a8975f30bb 100644 --- a/docs/reference/gtk/gtk4.types.in +++ b/docs/reference/gtk/gtk4.types.in @@ -53,6 +53,7 @@ gtk_constraint_guide_get_type gtk_constraint_layout_get_type gtk_constraint_target_get_type gtk_css_provider_get_type +gtk_custom_filter_get_type gtk_dialog_get_type gtk_directory_list_get_type gtk_drag_icon_get_type diff --git a/gtk/gtk.h b/gtk/gtk.h index 6953d0d3e5..60aaf1754c 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -116,6 +116,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkcustomfilter.c b/gtk/gtkcustomfilter.c new file mode 100644 index 0000000000..d2ec8dc23a --- /dev/null +++ b/gtk/gtkcustomfilter.c @@ -0,0 +1,157 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gtkcustomfilter.h" + +#include "gtkintl.h" +#include "gtktypebuiltins.h" + +/** + * SECTION:gtkcustomfilter + * @Title: GtkCustomFilter + * @Short_description: Filtering with callbacks + * + * #GtkCustomFilter is a #GtkFilter that uses a callback to determine whether + * to include an item or not. + */ +struct _GtkCustomFilter +{ + GtkFilter parent_instance; + + GtkCustomFilterFunc match_func; + gpointer user_data; + GDestroyNotify user_destroy; +}; + +G_DEFINE_TYPE (GtkCustomFilter, gtk_custom_filter, GTK_TYPE_FILTER) + +static gboolean +gtk_custom_filter_match (GtkFilter *filter, + gpointer item) +{ + GtkCustomFilter *self = GTK_CUSTOM_FILTER (filter); + + if (!self->match_func) + return TRUE; + + return self->match_func (item, self->user_data); +} + +static GtkFilterMatch +gtk_custom_filter_get_strictness (GtkFilter *filter) +{ + GtkCustomFilter *self = GTK_CUSTOM_FILTER (filter); + + if (!self->match_func) + return GTK_FILTER_MATCH_ALL; + + return GTK_FILTER_MATCH_SOME; +} + +static void +gtk_custom_filter_dispose (GObject *object) +{ + GtkCustomFilter *self = GTK_CUSTOM_FILTER (object); + + if (self->user_destroy) + self->user_destroy (self->user_data); + + G_OBJECT_CLASS (gtk_custom_filter_parent_class)->dispose (object); +} + +static void +gtk_custom_filter_class_init (GtkCustomFilterClass *class) +{ + GtkFilterClass *filter_class = GTK_FILTER_CLASS (class); + GObjectClass *object_class = G_OBJECT_CLASS (class); + + filter_class->match = gtk_custom_filter_match; + filter_class->get_strictness = gtk_custom_filter_get_strictness; + + object_class->dispose = gtk_custom_filter_dispose; +} + +static void +gtk_custom_filter_init (GtkCustomFilter *self) +{ +} + +/** + * gtk_custom_filter_new: + * @match_func: (nullable): function to filter items + * @user_data: (nullable): user data to pass to @match_func + * @user_destroy: destory notify + * + * Creates a new filter using the given @match_func to filter + * items. + * + * If the filter func changes its filtering behavior, + * gtk_filter_changed() needs to be called. + * + * Returns: a new #GtkFilter + **/ +GtkFilter * +gtk_custom_filter_new (GtkCustomFilterFunc match_func, + gpointer user_data, + GDestroyNotify user_destroy) +{ + GtkCustomFilter *result; + + result = g_object_new (GTK_TYPE_CUSTOM_FILTER, NULL); + + gtk_custom_filter_set_filter_func (result, match_func, user_data, user_destroy); + + return GTK_FILTER (result); +} + +/** + * gtk_custom_filter_set_filter_func: + * @self: a #GtkCustomFilter + * @match_func: (nullable): function to filter items + * @user_data: (nullable): user data to pass to @match_func + * @user_destroy: destory notify + * + * Sets (or unsets) the function used for filtering items. + * + * If the filter func changes its filtering behavior, + * gtk_filter_changed() needs to be called. + * + * If a previous function was set, its @user_destroy will be + * called now. + **/ +void +gtk_custom_filter_set_filter_func (GtkCustomFilter *self, + GtkCustomFilterFunc match_func, + gpointer user_data, + GDestroyNotify user_destroy) +{ + g_return_if_fail (GTK_IS_CUSTOM_FILTER (self)); + g_return_if_fail (match_func || (user_data == NULL && !user_destroy)); + + if (self->user_destroy) + self->user_destroy (self->user_data); + + self->match_func = match_func; + self->user_data = user_data; + self->user_destroy = user_destroy; + + gtk_filter_changed (GTK_FILTER (self), GTK_FILTER_CHANGE_DIFFERENT); +} diff --git a/gtk/gtkcustomfilter.h b/gtk/gtkcustomfilter.h new file mode 100644 index 0000000000..c6ee063886 --- /dev/null +++ b/gtk/gtkcustomfilter.h @@ -0,0 +1,60 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __GTK_CUSTOM_FILTER_H__ +#define __GTK_CUSTOM_FILTER_H__ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +/** + * GtkCustomFilterFunc: + * @item: (type GObject): The item to be matched + * @user_data: user data + * + * User function that is called to determine if the @item should be matched. + * If the filter matches the item, this function must return %TRUE. If the + * item should be filtered out, %FALSE must be returned. + * + * Returns: %TRUE to keep the item around + */ +typedef gboolean (* GtkCustomFilterFunc) (gpointer item, gpointer user_data); + +#define GTK_TYPE_CUSTOM_FILTER (gtk_custom_filter_get_type ()) +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkCustomFilter, gtk_custom_filter, GTK, CUSTOM_FILTER, GtkFilter) +GDK_AVAILABLE_IN_ALL +GtkFilter * gtk_custom_filter_new (GtkCustomFilterFunc match_func, + gpointer user_data, + GDestroyNotify user_destroy); + +GDK_AVAILABLE_IN_ALL +void gtk_custom_filter_set_filter_func (GtkCustomFilter *self, + GtkCustomFilterFunc match_func, + gpointer user_data, + GDestroyNotify user_destroy); + +G_END_DECLS + +#endif /* __GTK_CUSTOM_FILTER_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index 91fb469abc..6103a77e3f 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -199,6 +199,7 @@ gtk_public_sources = files([ 'gtkconstraintlayout.c', 'gtkconstraint.c', 'gtkcssprovider.c', + 'gtkcustomfilter.c', 'gtkcustomlayout.c', 'gtkdialog.c', 'gtkdirectorylist.c', @@ -454,6 +455,7 @@ gtk_public_headers = files([ 'gtkconstraintlayout.h', 'gtkconstraint.h', 'gtkcssprovider.h', + 'gtkcustomfilter.h', 'gtkcustomlayout.h', 'gtkdebug.h', 'gtkdialog.h', From b1090ac8e2fb39e42dfd6b1ea7aaaa0cf817c257 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sun, 10 Nov 2019 02:13:41 +0100 Subject: [PATCH 005/170] tests: Remove testtreemodel test testlistview does everything this test does. --- tests/meson.build | 1 - tests/testtreelistmodel.c | 394 -------------------------------------- 2 files changed, 395 deletions(-) delete mode 100644 tests/testtreelistmodel.c diff --git a/tests/meson.build b/tests/meson.build index 5cb5a04d92..34cb9f07cd 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -92,7 +92,6 @@ gtk_tests = [ ['teststack'], ['testrevealer'], ['testrevealer2'], - ['testtreelistmodel'], ['testwindowsize'], ['testpopover'], ['listmodel'], diff --git a/tests/testtreelistmodel.c b/tests/testtreelistmodel.c deleted file mode 100644 index 74a312be26..0000000000 --- a/tests/testtreelistmodel.c +++ /dev/null @@ -1,394 +0,0 @@ -#include - -#define ROWS 30 - -GSList *pending = NULL; -guint active = 0; - -static void -got_files (GObject *enumerate, - GAsyncResult *res, - gpointer store); - -static gboolean -start_enumerate (GListStore *store) -{ - GFileEnumerator *enumerate; - GFile *file = g_object_get_data (G_OBJECT (store), "file"); - GError *error = NULL; - - enumerate = g_file_enumerate_children (file, - G_FILE_ATTRIBUTE_STANDARD_TYPE - "," G_FILE_ATTRIBUTE_STANDARD_ICON - "," G_FILE_ATTRIBUTE_STANDARD_NAME, - 0, - NULL, - &error); - - if (enumerate == NULL) - { - if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TOO_MANY_OPEN_FILES) && active) - { - g_clear_error (&error); - pending = g_slist_prepend (pending, g_object_ref (store)); - return TRUE; - } - - g_clear_error (&error); - g_object_unref (store); - return FALSE; - } - - if (active > 20) - { - g_object_unref (enumerate); - pending = g_slist_prepend (pending, g_object_ref (store)); - return TRUE; - } - - active++; - g_file_enumerator_next_files_async (enumerate, - g_file_is_native (file) ? 5000 : 100, - G_PRIORITY_DEFAULT_IDLE, - NULL, - got_files, - g_object_ref (store)); - - g_object_unref (enumerate); - return TRUE; -} - -static void -got_files (GObject *enumerate, - GAsyncResult *res, - gpointer user_data) -{ - GList *l, *files; - GFile *file = g_object_get_data (user_data, "file"); - GPtrArray *array; - - files = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (enumerate), res, NULL); - if (files == NULL) - { - g_object_unref (user_data); - if (pending) - { - GListStore *store = pending->data; - pending = g_slist_remove (pending, store); - start_enumerate (store); - } - active--; - return; - } - - array = g_ptr_array_new (); - g_ptr_array_new_with_free_func (g_object_unref); - for (l = files; l; l = l->next) - { - GFileInfo *info = l->data; - GFile *child; - - child = g_file_get_child (file, g_file_info_get_name (info)); - g_object_set_data_full (G_OBJECT (info), "file", child, g_object_unref); - g_ptr_array_add (array, info); - } - g_list_free (files); - - g_list_store_splice (user_data, g_list_model_get_n_items (user_data), 0, array->pdata, array->len); - g_ptr_array_unref (array); - - g_file_enumerator_next_files_async (G_FILE_ENUMERATOR (enumerate), - g_file_is_native (file) ? 5000 : 100, - G_PRIORITY_DEFAULT_IDLE, - NULL, - got_files, - user_data); -} - -static int -compare_files (gconstpointer first, - gconstpointer second, - gpointer unused) -{ - GFile *first_file, *second_file; - char *first_path, *second_path; - int result; -#if 0 - GFileType first_type, second_type; - - /* This is a bit slow, because each g_file_query_file_type() does a stat() */ - first_type = g_file_query_file_type (G_FILE (first), G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL); - second_type = g_file_query_file_type (G_FILE (second), G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL); - - if (first_type == G_FILE_TYPE_DIRECTORY && second_type != G_FILE_TYPE_DIRECTORY) - return -1; - if (first_type != G_FILE_TYPE_DIRECTORY && second_type == G_FILE_TYPE_DIRECTORY) - return 1; -#endif - - first_file = g_object_get_data (G_OBJECT (first), "file"); - second_file = g_object_get_data (G_OBJECT (second), "file"); - first_path = g_file_get_path (first_file); - second_path = g_file_get_path (second_file); - - result = g_ascii_strcasecmp (first_path, second_path); - - g_free (first_path); - g_free (second_path); - - return result; -} - -static GListModel * -create_list_model_for_directory (gpointer file) -{ - GtkSortListModel *sort; - GListStore *store; - - if (g_file_query_file_type (file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) != G_FILE_TYPE_DIRECTORY) - return NULL; - - store = g_list_store_new (G_TYPE_FILE_INFO); - g_object_set_data_full (G_OBJECT (store), "file", g_object_ref (file), g_object_unref); - - if (!start_enumerate (store)) - return NULL; - - sort = gtk_sort_list_model_new (G_LIST_MODEL (store), - compare_files, - NULL, NULL); - g_object_unref (store); - return G_LIST_MODEL (sort); -} - -static GtkWidget * -create_widget_for_model (gpointer item, - gpointer root) -{ - GtkWidget *row, *box, *child; - GFileInfo *info; - GFile *file; - guint depth; - GIcon *icon; - - row = gtk_list_box_row_new (); - gtk_widget_set_vexpand (row, TRUE); - - box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); - gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), box); - - depth = gtk_tree_list_row_get_depth (item); - if (depth > 0) - { - child = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); - gtk_widget_set_size_request (child, 16 * depth, 0); - gtk_box_append (GTK_BOX (box), child); - } - - if (gtk_tree_list_row_is_expandable (item)) - { - GtkWidget *title, *arrow; - - child = g_object_new (GTK_TYPE_BOX, "css-name", "expander", NULL); - - title = g_object_new (GTK_TYPE_TOGGLE_BUTTON, "css-name", "title", NULL); - gtk_button_set_has_frame (GTK_BUTTON (title), FALSE); - g_object_bind_property (item, "expanded", title, "active", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); - g_object_set_data_full (G_OBJECT (title), "make-sure-its-not-unreffed", g_object_ref (item), g_object_unref); - gtk_box_append (GTK_BOX (child), title); - - arrow = g_object_new (GTK_TYPE_SPINNER, "css-name", "arrow", NULL); - gtk_button_set_child (GTK_BUTTON (title), arrow); - } - else - { - child = gtk_image_new (); /* empty whatever */ - } - gtk_box_append (GTK_BOX (box), child); - - info = gtk_tree_list_row_get_item (item); - - icon = g_file_info_get_icon (info); - if (icon) - { - child = gtk_image_new_from_gicon (icon); - gtk_box_append (GTK_BOX (box), child); - } - - file = g_object_get_data (G_OBJECT (info), "file"); - child = gtk_label_new (g_file_get_basename (file)); - g_object_unref (info); - - gtk_box_append (GTK_BOX (box), child); - - return row; -} - -static GListModel * -create_list_model_for_file_info (gpointer file_info, - gpointer unused) -{ - GFile *file = g_object_get_data (file_info, "file"); - - if (file == NULL) - return NULL; - - return create_list_model_for_directory (file); -} - -static void -update_adjustment (GListModel *model, - guint position, - guint removed, - guint added, - GtkAdjustment *adjustment) -{ - gtk_adjustment_set_upper (adjustment, MAX (ROWS, g_list_model_get_n_items (model))); -} - -static gboolean -update_statusbar (GtkStatusbar *statusbar) -{ - GListModel *model = g_object_get_data (G_OBJECT (statusbar), "model"); - GString *string = g_string_new (NULL); - guint n; - gboolean result = G_SOURCE_REMOVE; - - gtk_statusbar_remove_all (statusbar, 0); - - n = g_list_model_get_n_items (model); - g_string_append_printf (string, "%u", n); - if (GTK_IS_FILTER_LIST_MODEL (model)) - { - guint n_unfiltered = g_list_model_get_n_items (gtk_filter_list_model_get_model (GTK_FILTER_LIST_MODEL (model))); - if (n != n_unfiltered) - g_string_append_printf (string, "/%u", n_unfiltered); - } - g_string_append (string, " items"); - - if (pending || active) - { - g_string_append_printf (string, " (%u directories remaining)", active + g_slist_length (pending)); - result = G_SOURCE_CONTINUE; - } - - gtk_statusbar_push (statusbar, 0, string->str); - g_free (string->str); - - return result; -} - -static gboolean -match_file (gpointer item, gpointer data) -{ - GtkWidget *search_entry = data; - GFileInfo *info = gtk_tree_list_row_get_item (item); - GFile *file = g_object_get_data (G_OBJECT (info), "file"); - char *path; - gboolean result; - - path = g_file_get_path (file); - - result = strstr (path, gtk_editable_get_text (GTK_EDITABLE (search_entry))) != NULL; - - g_object_unref (info); - g_free (path); - - return result; -} - -static void -quit_cb (GtkWidget *widget, - gpointer data) -{ - gboolean *done = data; - - *done = TRUE; - - g_main_context_wakeup (NULL); -} - -int -main (int argc, char *argv[]) -{ - GtkAdjustment *adjustment; - GtkWidget *win, *hbox, *vbox, *scrollbar, *listbox, *search_entry, *statusbar; - GListModel *dirmodel; - GtkTreeListModel *tree; - GtkFilterListModel *filter; - GtkSliceListModel *slice; - GFile *root; - gboolean done = FALSE; - - gtk_init (); - - win = gtk_window_new (); - gtk_window_set_default_size (GTK_WINDOW (win), 400, 600); - g_signal_connect (win, "destroy", G_CALLBACK (quit_cb), &done); - - vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - gtk_window_set_child (GTK_WINDOW (win), vbox); - - search_entry = gtk_search_entry_new (); - gtk_box_append (GTK_BOX (vbox), search_entry); - - hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); - gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (search_entry), hbox); - gtk_box_append (GTK_BOX (vbox), hbox); - - listbox = gtk_list_box_new (); - gtk_widget_set_hexpand (listbox, TRUE); - gtk_box_append (GTK_BOX (hbox), listbox); - - if (argc > 1) - root = g_file_new_for_commandline_arg (argv[1]); - else - root = g_file_new_for_path (g_get_current_dir ()); - dirmodel = create_list_model_for_directory (root); - tree = gtk_tree_list_model_new (FALSE, - dirmodel, - TRUE, - create_list_model_for_file_info, - NULL, NULL); - g_object_unref (dirmodel); - - filter = gtk_filter_list_model_new (G_LIST_MODEL (tree), - match_file, - search_entry, - NULL); - g_signal_connect_swapped (search_entry, "search-changed", G_CALLBACK (gtk_filter_list_model_refilter), filter); - slice = gtk_slice_list_model_new (G_LIST_MODEL (filter), 0, ROWS); - - gtk_list_box_bind_model (GTK_LIST_BOX (listbox), - G_LIST_MODEL (slice), - create_widget_for_model, - root, g_object_unref); - - adjustment = gtk_adjustment_new (0, - 0, MAX (g_list_model_get_n_items (G_LIST_MODEL (tree)), ROWS), - 1, ROWS - 1, - ROWS); - g_object_bind_property (adjustment, "value", slice, "offset", 0); - g_signal_connect (filter, "items-changed", G_CALLBACK (update_adjustment), adjustment); - - scrollbar = gtk_scrollbar_new (GTK_ORIENTATION_VERTICAL, adjustment); - gtk_box_append (GTK_BOX (hbox), scrollbar); - - statusbar = gtk_statusbar_new (); - gtk_widget_add_tick_callback (statusbar, (GtkTickCallback) update_statusbar, NULL, NULL); - g_object_set_data (G_OBJECT (statusbar), "model", filter); - g_signal_connect_swapped (filter, "items-changed", G_CALLBACK (update_statusbar), statusbar); - update_statusbar (GTK_STATUSBAR (statusbar)); - gtk_box_append (GTK_BOX (vbox), statusbar); - - g_object_unref (tree); - g_object_unref (filter); - g_object_unref (slice); - - gtk_widget_show (win); - - while (!done) - g_main_context_iteration (NULL, TRUE); - - return 0; -} From 4abdf695e3e2996d7151d129552dfd2b11135ad9 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sun, 10 Nov 2019 02:39:31 +0100 Subject: [PATCH 006/170] filterlistmodel: Rewrite to use GtkFilter --- demos/constraint-editor/constraint-view.c | 9 +- docs/reference/gtk/gtk4-sections.txt | 5 +- gtk/gtkfilterlistmodel.c | 503 +++++++++++++++------- gtk/gtkfilterlistmodel.h | 32 +- gtk/inspector/object-tree.c | 6 +- testsuite/gtk/filterlistmodel.c | 80 +++- 6 files changed, 431 insertions(+), 204 deletions(-) diff --git a/demos/constraint-editor/constraint-view.c b/demos/constraint-editor/constraint-view.c index d1ea9c9401..76b4acd848 100644 --- a/demos/constraint-editor/constraint-view.c +++ b/demos/constraint-editor/constraint-view.c @@ -169,6 +169,7 @@ constraint_view_init (ConstraintView *self) GListModel *guides; GListModel *children; GListModel *constraints; + GtkFilter *filter; manager = gtk_constraint_layout_new (); gtk_widget_set_layout_manager (GTK_WIDGET (self), manager); @@ -176,8 +177,12 @@ constraint_view_init (ConstraintView *self) all_children = gtk_widget_observe_children (GTK_WIDGET (self)); all_constraints = gtk_constraint_layout_observe_constraints (GTK_CONSTRAINT_LAYOUT (manager)); guides = gtk_constraint_layout_observe_guides (GTK_CONSTRAINT_LAYOUT (manager)); - constraints = (GListModel *)gtk_filter_list_model_new (all_constraints, omit_internal, NULL, NULL); - children = (GListModel *)gtk_filter_list_model_new (all_children, omit_internal, NULL, NULL); + filter = gtk_custom_filter_new (omit_internal, NULL, NULL); + constraints = (GListModel *)gtk_filter_list_model_new (all_constraints, filter); + g_object_unref (filter); + filter = gtk_custom_filter_new (omit_internal, NULL, NULL); + children = (GListModel *)gtk_filter_list_model_new (all_children, filter); + g_object_unref (filter); list = g_list_store_new (G_TYPE_LIST_MODEL); g_list_store_append (list, children); diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index fdbcf599a3..25c977ec82 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -1232,9 +1232,8 @@ gtk_filter_list_model_new gtk_filter_list_model_new_for_type gtk_filter_list_model_set_model gtk_filter_list_model_get_model -gtk_filter_list_model_set_filter_func -gtk_filter_list_model_has_filter -gtk_filter_list_model_refilter +gtk_filter_list_model_set_filter +gtk_filter_list_model_get_filter GTK_FILTER_LIST_MODEL GTK_IS_FILTER_LIST_MODEL diff --git a/gtk/gtkfilterlistmodel.c b/gtk/gtkfilterlistmodel.c index 2f5c4dae6d..615360fe66 100644 --- a/gtk/gtkfilterlistmodel.c +++ b/gtk/gtkfilterlistmodel.c @@ -29,17 +29,17 @@ * SECTION:gtkfilterlistmodel * @title: GtkFilterListModel * @short_description: A list model that filters its items - * @see_also: #GListModel + * @see_also: #GListModel, #GtkFilter * * #GtkFilterListModel is a list model that filters a given other * listmodel. * It hides some elements from the other model according to - * criteria given by a #GtkFilterListModelFilterFunc. + * criteria given by a #GtkFilter. */ enum { PROP_0, - PROP_HAS_FILTER, + PROP_FILTER, PROP_ITEM_TYPE, PROP_MODEL, NUM_PROPERTIES @@ -65,11 +65,10 @@ struct _GtkFilterListModel GType item_type; GListModel *model; - GtkFilterListModelFilterFunc filter_func; - gpointer user_data; - GDestroyNotify user_destroy; + GtkFilter *filter; + GtkFilterMatch strictness; - GtkRbTree *items; /* NULL if filter_func == NULL */ + GtkRbTree *items; /* NULL if strictness != GTK_FILTER_MATCH_SOME */ }; struct _GtkFilterListModelClass @@ -79,6 +78,33 @@ struct _GtkFilterListModelClass static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; +static void +gtk_filter_list_model_augment (GtkRbTree *filter, + gpointer _aug, + gpointer _node, + gpointer left, + gpointer right) +{ + FilterNode *node = _node; + FilterAugment *aug = _aug; + + aug->n_items = 1; + aug->n_visible = node->visible ? 1 : 0; + + if (left) + { + FilterAugment *left_aug = gtk_rb_tree_get_augment (filter, left); + aug->n_items += left_aug->n_items; + aug->n_visible += left_aug->n_visible; + } + if (right) + { + FilterAugment *right_aug = gtk_rb_tree_get_augment (filter, right); + aug->n_items += right_aug->n_items; + aug->n_visible += right_aug->n_visible; + } +} + static FilterNode * gtk_filter_list_model_get_nth_filtered (GtkRbTree *tree, guint position, @@ -180,11 +206,20 @@ gtk_filter_list_model_get_n_items (GListModel *list) FilterAugment *aug; FilterNode *node; - if (self->model == NULL) - return 0; + switch (self->strictness) + { + case GTK_FILTER_MATCH_NONE: + return 0; - if (!self->items) - return g_list_model_get_n_items (self->model); + case GTK_FILTER_MATCH_ALL: + return g_list_model_get_n_items (self->model); + + default: + g_assert_not_reached (); + G_GNUC_FALLTHROUGH; + case GTK_FILTER_MATCH_SOME: + break; + } node = gtk_rb_tree_get_root (self->items); if (node == NULL) @@ -201,13 +236,22 @@ gtk_filter_list_model_get_item (GListModel *list, GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (list); guint unfiltered; - if (self->model == NULL) - return NULL; + switch (self->strictness) + { + case GTK_FILTER_MATCH_NONE: + return NULL; - if (self->items) - gtk_filter_list_model_get_nth_filtered (self->items, position, &unfiltered); - else - unfiltered = position; + case GTK_FILTER_MATCH_ALL: + unfiltered = position; + break; + + default: + g_assert_not_reached (); + G_GNUC_FALLTHROUGH; + case GTK_FILTER_MATCH_SOME: + gtk_filter_list_model_get_nth_filtered (self->items, position, &unfiltered); + break; + } return g_list_model_get_item (self->model, unfiltered); } @@ -230,8 +274,11 @@ gtk_filter_list_model_run_filter (GtkFilterListModel *self, gpointer item; gboolean visible; + /* all other cases should have beeen optimized away */ + g_assert (self->strictness == GTK_FILTER_MATCH_SOME); + item = g_list_model_get_item (self->model, position); - visible = self->filter_func (item, self->user_data); + visible = gtk_filter_match (self->filter, item); g_object_unref (item); return visible; @@ -269,10 +316,20 @@ gtk_filter_list_model_items_changed_cb (GListModel *model, FilterNode *node; guint i, filter_position, filter_removed, filter_added; - if (self->items == NULL) + switch (self->strictness) { + case GTK_FILTER_MATCH_NONE: + return; + + case GTK_FILTER_MATCH_ALL: g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added); return; + + default: + g_assert_not_reached (); + G_GNUC_FALLTHROUGH; + case GTK_FILTER_MATCH_SOME: + break; } node = gtk_filter_list_model_get_nth (self->items, position, &filter_position); @@ -303,6 +360,10 @@ gtk_filter_list_model_set_property (GObject *object, switch (prop_id) { + case PROP_FILTER: + gtk_filter_list_model_set_filter (self, g_value_get_object (value)); + break; + case PROP_ITEM_TYPE: self->item_type = g_value_get_gtype (value); break; @@ -327,8 +388,8 @@ gtk_filter_list_model_get_property (GObject *object, switch (prop_id) { - case PROP_HAS_FILTER: - g_value_set_boolean (value, self->items != NULL); + case PROP_FILTER: + g_value_set_object (value, self->filter); break; case PROP_ITEM_TYPE: @@ -357,17 +418,223 @@ gtk_filter_list_model_clear_model (GtkFilterListModel *self) gtk_rb_tree_remove_all (self->items); } +/* + * gtk_filter_list_model_find_filtered: + * @self: a #GtkFilterListModel + * @start: (out) (caller-allocates): number of unfiltered items + * at start of list + * @end: (out) (caller-allocates): number of unfiltered items + * at end of list + * @n_items: (out) (caller-allocates): number of unfiltered items in + * list + * + * Checks if elements in self->items are filtered out and returns + * the range that they occupy. + * This function is intended to be used for GListModel::items-changed + * emissions, so it is called in an intermediate state for @self. + * + * Returns: %TRUE if elements are filtered out, %FALSE if none are + **/ +static gboolean +gtk_filter_list_model_find_filtered (GtkFilterListModel *self, + guint *start, + guint *end, + guint *n_items) +{ + FilterNode *root, *node, *tmp; + FilterAugment *aug; + + if (self->items == NULL || self->model == NULL) + return FALSE; + + root = gtk_rb_tree_get_root (self->items); + if (root == NULL) + return FALSE; /* empty parent model */ + + aug = gtk_rb_tree_get_augment (self->items, root); + if (aug->n_items == aug->n_visible) + return FALSE; /* all items visible */ + + /* find first filtered */ + *start = 0; + *end = 0; + *n_items = aug->n_visible; + + node = root; + while (node) + { + tmp = gtk_rb_tree_node_get_left (node); + if (tmp) + { + aug = gtk_rb_tree_get_augment (self->items, tmp); + if (aug->n_visible < aug->n_items) + { + node = tmp; + continue; + } + *start += aug->n_items; + } + + if (!node->visible) + break; + + (*start)++; + + node = gtk_rb_tree_node_get_right (node); + } + + /* find last filtered by doing everything the opposite way */ + node = root; + while (node) + { + tmp = gtk_rb_tree_node_get_right (node); + if (tmp) + { + aug = gtk_rb_tree_get_augment (self->items, tmp); + if (aug->n_visible < aug->n_items) + { + node = tmp; + continue; + } + *end += aug->n_items; + } + + if (!node->visible) + break; + + (*end)++; + + node = gtk_rb_tree_node_get_left (node); + } + + return TRUE; +} + +static void +gtk_filter_list_model_refilter (GtkFilterListModel *self); + +static void +gtk_filter_list_model_update_strictness_and_refilter (GtkFilterListModel *self) +{ + GtkFilterMatch new_strictness; + + if (self->model == NULL) + new_strictness = GTK_FILTER_MATCH_NONE; + else if (self->filter == NULL) + new_strictness = GTK_FILTER_MATCH_ALL; + else + new_strictness = gtk_filter_get_strictness (self->filter); + + /* don't set self->strictness yet so get_n_items() and friends return old values */ + + switch (new_strictness) + { + case GTK_FILTER_MATCH_NONE: + { + guint n_before = g_list_model_get_n_items (G_LIST_MODEL (self)); + g_clear_pointer (&self->items, gtk_rb_tree_unref); + self->strictness = new_strictness; + if (n_before > 0) + g_list_model_items_changed (G_LIST_MODEL (self), 0, n_before, 0); + } + break; + + case GTK_FILTER_MATCH_ALL: + switch (self->strictness) + { + case GTK_FILTER_MATCH_NONE: + self->strictness = new_strictness; + g_list_model_items_changed (G_LIST_MODEL (self), 0, 0, g_list_model_get_n_items (self->model)); + break; + case GTK_FILTER_MATCH_ALL: + self->strictness = new_strictness; + break; + default: + case GTK_FILTER_MATCH_SOME: + { + guint start, end, n_before, n_after; + self->strictness = new_strictness; + if (gtk_filter_list_model_find_filtered (self, &start, &end, &n_before)) + { + n_after = g_list_model_get_n_items (G_LIST_MODEL (self)); + g_clear_pointer (&self->items, gtk_rb_tree_unref); + g_list_model_items_changed (G_LIST_MODEL (self), start, n_before - end - start, n_after - end - start); + } + else + { + g_clear_pointer (&self->items, gtk_rb_tree_unref); + } + } + break; + } + break; + + default: + g_assert_not_reached (); + G_GNUC_FALLTHROUGH; + case GTK_FILTER_MATCH_SOME: + switch (self->strictness) + { + case GTK_FILTER_MATCH_NONE: + { + guint n_after; + self->strictness = new_strictness; + self->items = gtk_rb_tree_new (FilterNode, + FilterAugment, + gtk_filter_list_model_augment, + NULL, NULL); + n_after = gtk_filter_list_model_add_items (self, NULL, 0, g_list_model_get_n_items (self->model)); + if (n_after > 0) + g_list_model_items_changed (G_LIST_MODEL (self), 0, 0, n_after); + } + break; + case GTK_FILTER_MATCH_ALL: + { + guint start, end, n_before, n_after; + self->strictness = new_strictness; + self->items = gtk_rb_tree_new (FilterNode, + FilterAugment, + gtk_filter_list_model_augment, + NULL, NULL); + n_before = g_list_model_get_n_items (self->model); + gtk_filter_list_model_add_items (self, NULL, 0, n_before); + if (gtk_filter_list_model_find_filtered (self, &start, &end, &n_after)) + g_list_model_items_changed (G_LIST_MODEL (self), start, n_before - end - start, n_after - end - start); + } + break; + default: + case GTK_FILTER_MATCH_SOME: + gtk_filter_list_model_refilter (self); + break; + } + } +} + +static void +gtk_filter_list_model_filter_changed_cb (GtkFilter *filter, + GtkFilterChange change, + GtkFilterListModel *self) +{ + gtk_filter_list_model_update_strictness_and_refilter (self); +} + +static void +gtk_filter_list_model_clear_filter (GtkFilterListModel *self) +{ + if (self->filter == NULL) + return; + + g_signal_handlers_disconnect_by_func (self->filter, gtk_filter_list_model_filter_changed_cb, self); + g_clear_object (&self->filter); +} + static void gtk_filter_list_model_dispose (GObject *object) { GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (object); gtk_filter_list_model_clear_model (self); - if (self->user_destroy) - self->user_destroy (self->user_data); - self->filter_func = NULL; - self->user_data = NULL; - self->user_destroy = NULL; + gtk_filter_list_model_clear_filter (self); g_clear_pointer (&self->items, gtk_rb_tree_unref); G_OBJECT_CLASS (gtk_filter_list_model_parent_class)->dispose (object); @@ -383,16 +650,16 @@ gtk_filter_list_model_class_init (GtkFilterListModelClass *class) gobject_class->dispose = gtk_filter_list_model_dispose; /** - * GtkFilterListModel:has-filter: + * GtkFilterListModel:filter: * - * If a filter is set for this model + * The filter for this model */ - properties[PROP_HAS_FILTER] = - g_param_spec_boolean ("has-filter", - P_("has filter"), - P_("If a filter is set for this model"), - FALSE, - GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); + properties[PROP_FILTER] = + g_param_spec_object ("filter", + P_("Filter"), + P_("The filter set for this model"), + GTK_TYPE_FILTER, + GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * GtkFilterListModel:item-type: @@ -424,53 +691,22 @@ gtk_filter_list_model_class_init (GtkFilterListModelClass *class) static void gtk_filter_list_model_init (GtkFilterListModel *self) { -} - - -static void -gtk_filter_list_model_augment (GtkRbTree *filter, - gpointer _aug, - gpointer _node, - gpointer left, - gpointer right) -{ - FilterNode *node = _node; - FilterAugment *aug = _aug; - - aug->n_items = 1; - aug->n_visible = node->visible ? 1 : 0; - - if (left) - { - FilterAugment *left_aug = gtk_rb_tree_get_augment (filter, left); - aug->n_items += left_aug->n_items; - aug->n_visible += left_aug->n_visible; - } - if (right) - { - FilterAugment *right_aug = gtk_rb_tree_get_augment (filter, right); - aug->n_items += right_aug->n_items; - aug->n_visible += right_aug->n_visible; - } + self->strictness = GTK_FILTER_MATCH_NONE; } /** * gtk_filter_list_model_new: * @model: the model to sort - * @filter_func: (allow-none): filter function or %NULL to not filter items - * @user_data: (closure): user data passed to @filter_func - * @user_destroy: destroy notifier for @user_data + * @filter: (allow-none): filter or %NULL to not filter items * * Creates a new #GtkFilterListModel that will filter @model using the given - * @filter_func. + * @filter. * * Returns: a new #GtkFilterListModel **/ GtkFilterListModel * -gtk_filter_list_model_new (GListModel *model, - GtkFilterListModelFilterFunc filter_func, - gpointer user_data, - GDestroyNotify user_destroy) +gtk_filter_list_model_new (GListModel *model, + GtkFilter *filter) { GtkFilterListModel *result; @@ -479,11 +715,9 @@ gtk_filter_list_model_new (GListModel *model, result = g_object_new (GTK_TYPE_FILTER_LIST_MODEL, "item-type", g_list_model_get_item_type (model), "model", model, + "filter", filter, NULL); - if (filter_func) - gtk_filter_list_model_set_filter_func (result, filter_func, user_data, user_destroy); - return result; } @@ -492,7 +726,7 @@ gtk_filter_list_model_new (GListModel *model, * @item_type: the type of the items that will be returned * * Creates a new empty filter list model set up to return items of type @item_type. - * It is up to the application to set a proper filter function and model to ensure + * It is up to the application to set a proper filter and model to ensure * the item type is matched. * * Returns: a new #GtkFilterListModel @@ -508,66 +742,53 @@ gtk_filter_list_model_new_for_type (GType item_type) } /** - * gtk_filter_list_model_set_filter_func: + * gtk_filter_list_model_set_filter: * @self: a #GtkFilterListModel - * @filter_func: (allow-none): filter function or %NULL to not filter items - * @user_data: (closure): user data passed to @filter_func - * @user_destroy: destroy notifier for @user_data + * @filter: (allow-none) (transfer none): filter to use or %NULL to not filter items * - * Sets the function used to filter items. The function will be called for every - * item and if it returns %TRUE the item is considered visible. + * Sets the filter used to filter items. **/ void -gtk_filter_list_model_set_filter_func (GtkFilterListModel *self, - GtkFilterListModelFilterFunc filter_func, - gpointer user_data, - GDestroyNotify user_destroy) +gtk_filter_list_model_set_filter (GtkFilterListModel *self, + GtkFilter *filter) { - gboolean was_filtered, will_be_filtered; - g_return_if_fail (GTK_IS_FILTER_LIST_MODEL (self)); - g_return_if_fail (filter_func != NULL || (user_data == NULL && !user_destroy)); + g_return_if_fail (filter == NULL || GTK_IS_FILTER (filter)); - was_filtered = self->filter_func != NULL; - will_be_filtered = filter_func != NULL; - - if (!was_filtered && !will_be_filtered) + if (self->filter == filter) return; - if (self->user_destroy) - self->user_destroy (self->user_data); + gtk_filter_list_model_clear_filter (self); - self->filter_func = filter_func; - self->user_data = user_data; - self->user_destroy = user_destroy; - - if (!will_be_filtered) + if (filter) { - g_clear_pointer (&self->items, gtk_rb_tree_unref); + self->filter = g_object_ref (filter); + g_signal_connect (filter, "changed", G_CALLBACK (gtk_filter_list_model_filter_changed_cb), self); + gtk_filter_list_model_filter_changed_cb (filter, GTK_FILTER_CHANGE_DIFFERENT, self); } - else if (!was_filtered) + else { - guint i, n_items; - - self->items = gtk_rb_tree_new (FilterNode, - FilterAugment, - gtk_filter_list_model_augment, - NULL, NULL); - if (self->model) - { - n_items = g_list_model_get_n_items (self->model); - for (i = 0; i < n_items; i++) - { - FilterNode *node = gtk_rb_tree_insert_before (self->items, NULL); - node->visible = TRUE; - } - } + gtk_filter_list_model_update_strictness_and_refilter (self); } - gtk_filter_list_model_refilter (self); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILTER]); +} - if (was_filtered != will_be_filtered) - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HAS_FILTER]); +/** + * gtk_filter_list_model_get_filter: + * @self: a #GtkFilterListModel + * + * Gets the #GtkFilter currently set on @self. + * + * Returns: (nullable) (transfer none): The filter currently in use + * or %NULL if the list isn't filtered + **/ +GtkFilter * +gtk_filter_list_model_get_filter (GtkFilterListModel *self) +{ + g_return_val_if_fail (GTK_IS_FILTER_LIST_MODEL (self), FALSE); + + return self->filter; } /** @@ -579,8 +800,8 @@ gtk_filter_list_model_set_filter_func (GtkFilterListModel *self, * * Note that GTK makes no effort to ensure that @model conforms to * the item type of @self. It assumes that the caller knows what they - * are doing and have set up an appropriate filter function to ensure - * that item types match. + * are doing and have set up an appropriate filter to ensure that item + * types match. **/ void gtk_filter_list_model_set_model (GtkFilterListModel *self, @@ -603,14 +824,23 @@ gtk_filter_list_model_set_model (GtkFilterListModel *self, { self->model = g_object_ref (model); g_signal_connect (model, "items-changed", G_CALLBACK (gtk_filter_list_model_items_changed_cb), self); - if (self->items) + if (removed == 0) + { + self->strictness = GTK_FILTER_MATCH_NONE; + gtk_filter_list_model_update_strictness_and_refilter (self); + added = 0; + } + else if (self->items) added = gtk_filter_list_model_add_items (self, NULL, 0, g_list_model_get_n_items (model)); else added = g_list_model_get_n_items (model); } else - added = 0; - + { + self->strictness = GTK_FILTER_MATCH_NONE; + added = 0; + } + if (removed > 0 || added > 0) g_list_model_items_changed (G_LIST_MODEL (self), 0, removed, added); @@ -633,32 +863,7 @@ gtk_filter_list_model_get_model (GtkFilterListModel *self) return self->model; } -/** - * gtk_filter_list_model_has_filter: - * @self: a #GtkFilterListModel - * - * Checks if a filter function is currently set on @self - * - * Returns: %TRUE if a filter function is set - **/ -gboolean -gtk_filter_list_model_has_filter (GtkFilterListModel *self) -{ - g_return_val_if_fail (GTK_IS_FILTER_LIST_MODEL (self), FALSE); - - return self->filter_func != NULL; -} - -/** - * gtk_filter_list_model_refilter: - * @self: a #GtkFilterListModel - * - * Causes @self to refilter all items in the model. - * - * Calling this function is necessary when data used by the filter - * function has changed. - **/ -void +static void gtk_filter_list_model_refilter (GtkFilterListModel *self) { FilterNode *node; diff --git a/gtk/gtkfilterlistmodel.h b/gtk/gtkfilterlistmodel.h index e1f256ac2d..fcb8dfab5c 100644 --- a/gtk/gtkfilterlistmodel.h +++ b/gtk/gtkfilterlistmodel.h @@ -26,7 +26,7 @@ #endif #include -#include +#include G_BEGIN_DECLS @@ -36,42 +36,22 @@ G_BEGIN_DECLS GDK_AVAILABLE_IN_ALL G_DECLARE_FINAL_TYPE (GtkFilterListModel, gtk_filter_list_model, GTK, FILTER_LIST_MODEL, GObject) -/** - * GtkFilterListModelFilterFunc: - * @item: (type GObject): The item that may be filtered - * @user_data: user data - * - * User function that is called to determine if the @item of the original model should be visible. - * If it should be visible, this function must return %TRUE. If the model should filter out the - * @item, %FALSE must be returned. - * - * Returns: %TRUE to keep the item around - */ -typedef gboolean (* GtkFilterListModelFilterFunc) (gpointer item, gpointer user_data); - GDK_AVAILABLE_IN_ALL GtkFilterListModel * gtk_filter_list_model_new (GListModel *model, - GtkFilterListModelFilterFunc filter_func, - gpointer user_data, - GDestroyNotify user_destroy); + GtkFilter *filter); GDK_AVAILABLE_IN_ALL GtkFilterListModel * gtk_filter_list_model_new_for_type (GType item_type); GDK_AVAILABLE_IN_ALL -void gtk_filter_list_model_set_filter_func (GtkFilterListModel *self, - GtkFilterListModelFilterFunc filter_func, - gpointer user_data, - GDestroyNotify user_destroy); +void gtk_filter_list_model_set_filter (GtkFilterListModel *self, + GtkFilter *filter); +GDK_AVAILABLE_IN_ALL +GtkFilter * gtk_filter_list_model_get_filter (GtkFilterListModel *self); GDK_AVAILABLE_IN_ALL void gtk_filter_list_model_set_model (GtkFilterListModel *self, GListModel *model); GDK_AVAILABLE_IN_ALL GListModel * gtk_filter_list_model_get_model (GtkFilterListModel *self); -GDK_AVAILABLE_IN_ALL -gboolean gtk_filter_list_model_has_filter (GtkFilterListModel *self); - -GDK_AVAILABLE_IN_ALL -void gtk_filter_list_model_refilter (GtkFilterListModel *self); G_END_DECLS diff --git a/gtk/inspector/object-tree.c b/gtk/inspector/object-tree.c index 361f246a5d..e17f1594a5 100644 --- a/gtk/inspector/object-tree.c +++ b/gtk/inspector/object-tree.c @@ -36,6 +36,7 @@ #include "gtkcelllayout.h" #include "gtkcomboboxprivate.h" #include "gtkfilterlistmodel.h" +#include "gtkcustomfilter.h" #include "gtkflattenlistmodel.h" #include "gtkbuiltiniconprivate.h" #include "gtkiconview.h" @@ -1091,6 +1092,7 @@ toplevel_filter_func (gpointer item, static GListModel * create_root_model (GdkDisplay *display) { + GtkFilter *custom_filter; GtkFilterListModel *filter; GtkFlattenListModel *flatten; GListStore *list, *special; @@ -1107,9 +1109,9 @@ create_root_model (GdkDisplay *display) g_object_unref (special); filter = gtk_filter_list_model_new_for_type (G_TYPE_OBJECT); - gtk_filter_list_model_set_filter_func (filter, - toplevel_filter_func, + custom_filter = gtk_custom_filter_new (toplevel_filter_func, display, NULL); + gtk_filter_list_model_set_filter (filter, custom_filter); gtk_filter_list_model_set_model (filter, gtk_window_get_toplevels ()); g_list_store_append (list, filter); g_object_unref (filter); diff --git a/testsuite/gtk/filterlistmodel.c b/testsuite/gtk/filterlistmodel.c index 35ddfde052..0e29cf501c 100644 --- a/testsuite/gtk/filterlistmodel.c +++ b/testsuite/gtk/filterlistmodel.c @@ -150,14 +150,20 @@ free_changes (gpointer data) } static GtkFilterListModel * -new_model (guint size, - GtkFilterListModelFilterFunc filter_func, - gpointer data) +new_model (guint size, + GtkCustomFilterFunc filter_func, + gpointer data) { GtkFilterListModel *result; + GtkFilter *filter; GString *changes; - result = gtk_filter_list_model_new (G_LIST_MODEL (new_store (1, size, 1)), filter_func, data, NULL); + if (filter_func) + filter = gtk_custom_filter_new (filter_func, data, NULL); + else + filter = NULL; + result = gtk_filter_list_model_new (G_LIST_MODEL (new_store (1, size, 1)), filter); + g_clear_object (&filter); changes = g_string_new (""); g_object_set_qdata_full (G_OBJECT(result), changes_quark, changes, free_changes); g_signal_connect (result, "items-changed", G_CALLBACK (items_changed), changes); @@ -220,89 +226,119 @@ test_create (void) } static void -test_empty_set_filter_func (void) +test_empty_set_filter (void) { GtkFilterListModel *filter; + GtkFilter *custom; filter = new_model (10, NULL, NULL); - gtk_filter_list_model_set_filter_func (filter, is_smaller_than, GUINT_TO_POINTER (20), NULL); + custom = gtk_custom_filter_new (is_smaller_than, GUINT_TO_POINTER (20), NULL); + gtk_filter_list_model_set_filter (filter, custom); + g_object_unref (custom); assert_model (filter, "1 2 3 4 5 6 7 8 9 10"); assert_changes (filter, ""); g_object_unref (filter); filter = new_model (10, NULL, NULL); - gtk_filter_list_model_set_filter_func (filter, is_smaller_than, GUINT_TO_POINTER (7), NULL); + custom = gtk_custom_filter_new (is_smaller_than, GUINT_TO_POINTER (7), NULL); + gtk_filter_list_model_set_filter (filter, custom); + g_object_unref (custom); assert_model (filter, "1 2 3 4 5 6"); assert_changes (filter, "6-4"); g_object_unref (filter); filter = new_model (10, NULL, NULL); - gtk_filter_list_model_set_filter_func (filter, is_smaller_than, GUINT_TO_POINTER (0), NULL); + custom = gtk_custom_filter_new (is_smaller_than, GUINT_TO_POINTER (0), NULL); + gtk_filter_list_model_set_filter (filter, custom); + g_object_unref (custom); assert_model (filter, ""); assert_changes (filter, "0-10"); g_object_unref (filter); filter = new_model (10, NULL, NULL); - gtk_filter_list_model_set_filter_func (filter, is_larger_than, GUINT_TO_POINTER (0), NULL); + custom = gtk_custom_filter_new (is_larger_than, GUINT_TO_POINTER (0), NULL); + gtk_filter_list_model_set_filter (filter, custom); + g_object_unref (custom); assert_model (filter, "1 2 3 4 5 6 7 8 9 10"); assert_changes (filter, ""); g_object_unref (filter); filter = new_model (10, NULL, NULL); - gtk_filter_list_model_set_filter_func (filter, is_larger_than, GUINT_TO_POINTER (3), NULL); + custom = gtk_custom_filter_new (is_larger_than, GUINT_TO_POINTER (3), NULL); + gtk_filter_list_model_set_filter (filter, custom); + g_object_unref (custom); assert_model (filter, "4 5 6 7 8 9 10"); assert_changes (filter, "0-3"); g_object_unref (filter); filter = new_model (10, NULL, NULL); - gtk_filter_list_model_set_filter_func (filter, is_larger_than, GUINT_TO_POINTER (20), NULL); + custom = gtk_custom_filter_new (is_larger_than, GUINT_TO_POINTER (20), NULL); + gtk_filter_list_model_set_filter (filter, custom); + g_object_unref (custom); assert_model (filter, ""); assert_changes (filter, "0-10"); g_object_unref (filter); filter = new_model (10, NULL, NULL); - gtk_filter_list_model_set_filter_func (filter, is_near, GUINT_TO_POINTER (5), NULL); + custom = gtk_custom_filter_new (is_near, GUINT_TO_POINTER (5), NULL); + gtk_filter_list_model_set_filter (filter, custom); + g_object_unref (custom); assert_model (filter, "3 4 5 6 7"); assert_changes (filter, "0-10+5"); g_object_unref (filter); filter = new_model (10, NULL, NULL); - gtk_filter_list_model_set_filter_func (filter, is_not_near, GUINT_TO_POINTER (5), NULL); + custom = gtk_custom_filter_new (is_not_near, GUINT_TO_POINTER (5), NULL); + gtk_filter_list_model_set_filter (filter, custom); + g_object_unref (custom); assert_model (filter, "1 2 8 9 10"); assert_changes (filter, "2-5"); g_object_unref (filter); } static void -test_change_filter_func (void) +test_change_filter (void) { GtkFilterListModel *filter; + GtkFilter *custom; filter = new_model (10, is_not_near, GUINT_TO_POINTER (5)); assert_model (filter, "1 2 8 9 10"); assert_changes (filter, ""); - gtk_filter_list_model_set_filter_func (filter, is_not_near, GUINT_TO_POINTER (6), NULL); + custom = gtk_custom_filter_new (is_not_near, GUINT_TO_POINTER (6), NULL); + gtk_filter_list_model_set_filter (filter, custom); + g_object_unref (custom); assert_model (filter, "1 2 3 9 10"); assert_changes (filter, "2-1+1"); - gtk_filter_list_model_set_filter_func (filter, is_not_near, GUINT_TO_POINTER (9), NULL); + custom = gtk_custom_filter_new (is_not_near, GUINT_TO_POINTER (9), NULL); + gtk_filter_list_model_set_filter (filter, custom); + g_object_unref (custom); assert_model (filter, "1 2 3 4 5 6"); assert_changes (filter, "3-2+3"); - gtk_filter_list_model_set_filter_func (filter, is_smaller_than, GUINT_TO_POINTER (6), NULL); + custom = gtk_custom_filter_new (is_smaller_than, GUINT_TO_POINTER (6), NULL); + gtk_filter_list_model_set_filter (filter, custom); + g_object_unref (custom); assert_model (filter, "1 2 3 4 5"); assert_changes (filter, "-5"); - gtk_filter_list_model_set_filter_func (filter, is_larger_than, GUINT_TO_POINTER (4), NULL); + custom = gtk_custom_filter_new (is_larger_than, GUINT_TO_POINTER (4), NULL); + gtk_filter_list_model_set_filter (filter, custom); + g_object_unref (custom); assert_model (filter, "5 6 7 8 9 10"); assert_changes (filter, "0-5+6"); - gtk_filter_list_model_set_filter_func (filter, is_not_near, GUINT_TO_POINTER (2), NULL); + custom = gtk_custom_filter_new (is_not_near, GUINT_TO_POINTER (2), NULL); + gtk_filter_list_model_set_filter (filter, custom); + g_object_unref (custom); assert_model (filter, "5 6 7 8 9 10"); assert_changes (filter, ""); - gtk_filter_list_model_set_filter_func (filter, is_not_near, GUINT_TO_POINTER (4), NULL); + custom = gtk_custom_filter_new (is_not_near, GUINT_TO_POINTER (4), NULL); + gtk_filter_list_model_set_filter (filter, custom); + g_object_unref (custom); assert_model (filter, "1 7 8 9 10"); assert_changes (filter, "0-2+1"); @@ -319,8 +355,8 @@ main (int argc, char *argv[]) changes_quark = g_quark_from_static_string ("What did I see? Can I believe what I saw?"); g_test_add_func ("/filterlistmodel/create", test_create); - g_test_add_func ("/filterlistmodel/empty_set_filter_func", test_empty_set_filter_func); - g_test_add_func ("/filterlistmodel/change_filter_func", test_change_filter_func); + g_test_add_func ("/filterlistmodel/empty_set_filter", test_empty_set_filter); + g_test_add_func ("/filterlistmodel/change_filter", test_change_filter); return g_test_run (); } From e9f1ee5aabe1eaecee786bed049c36e0d9644052 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 16 Nov 2019 21:52:18 +0100 Subject: [PATCH 007/170] Add GtkExpression GtkExpressions allow looking up values from objects. There are a few simple expressions, but the main one is the closure expression that just calls a user-provided closure. --- docs/reference/gtk/gtk4-docs.xml | 1 + docs/reference/gtk/gtk4-sections.txt | 36 +- gtk/gtk.h | 1 + gtk/gtkexpression.c | 606 +++++++++++++++++++++++++++ gtk/gtkexpression.h | 77 ++++ gtk/meson.build | 2 + 6 files changed, 721 insertions(+), 2 deletions(-) create mode 100644 gtk/gtkexpression.c create mode 100644 gtk/gtkexpression.h diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index 3495ec1607..32812e1b1a 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -48,6 +48,7 @@ GListModel support +
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 25c977ec82..89978fbcac 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -7066,6 +7066,38 @@ gtk_overlay_layout_child_get_clip_overlay GTK_TYPE_OVERLAY_LAYOUT gtk_overlay_layout_get_type GTK_TYPE_OVERLAY_LAYOUT_CHLD -gtk_overlay_layout_child_ -get_type +gtk_overlay_layout_child_get_type +
+ +
+gtkexpression +GtkExpression +GtkExpressionWatch +GtkExpressionNotify +gtk_expression_ref +gtk_expression_unref +gtk_expression_get_value_type +gtk_expression_is_static +gtk_expression_evaluate +gtk_expression_watch +gtk_expression_bind +gtk_expression_watch_ref +gtk_expression_watch_unref +gtk_expression_watch_evaluate +gtk_expression_watch_unwatch + + +gtk_property_expression_new +gtk_property_expression_new_for_pspec +gtk_constant_expression_new +gtk_constant_expression_new_for_value +gtk_object_expression +gtk_closure_expression_new +gtk_cclosure_expression_new + + +GTK_IS_EXPRESSION +GTK_TYPE_EXPRESSION + +gtk_expression_get_type
diff --git a/gtk/gtk.h b/gtk/gtk.h index 60aaf1754c..e4f07f6ac3 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -106,6 +106,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkexpression.c b/gtk/gtkexpression.c new file mode 100644 index 0000000000..dbd100c767 --- /dev/null +++ b/gtk/gtkexpression.c @@ -0,0 +1,606 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gtkexpression.h" + +#include + +/** + * SECTION:gtkexpression + * @Short_description: Expressions to values + * @Title: GtkExpression + * + * GtkExpression provides a way to describe references to #GValues. + * + * An expression needs to be `evaluated` to obtain the value that it currently refers + * to. An evaluation always happens in the context of a current object called `this` + * (it mirrors the behavior of object-oriented languages), which may or may not + * influence the result of the evaluation. Use gtk_expression_evaluate() for + * evaluating an expression. + * + * Various methods for defining expressions exist, from simple constants via + * gtk_constant_expression_new() to looking up properties in a #GObject (even + * recursively) via gtk_property_expression_new() or providing custom functions to + * transform and combine expressions via gtk_closure_expression_new(). + * + * By default, expressions are not paying attention to changes and evaluation is + * just a snapshot of the current state at a given time. To get informed about + * changes, an expression needs to be `watched` via a #GtkExpressionWatch, which + * will cause a callback to be called whenever the value of the expression may + * have changed. gtk_expression_watch() starts watching an expression, and + * gtk_expression_watch_unwatch() stops. + * + * Watches can be created for automatically updating the propery of an object, + * similar to GObject's #GBinding mechanism, by using gtk_expression_bind(). + */ +typedef struct _GtkExpressionClass GtkExpressionClass; + +struct _GtkExpression +{ + const GtkExpressionClass *expression_class; + GType value_type; + + GtkExpression *owner; +}; + +struct _GtkExpressionClass +{ + gsize struct_size; + const char *type_name; + + void (* finalize) (GtkExpression *expr); + gboolean (* evaluate) (GtkExpression *expr, + gpointer this, + GValue *value); +}; + +/** + * GtkExpression: (ref-func gtk_expression_ref) (unref-func gtk_expression_unref) + * + * The `GtkExpression` structure contains only private data. + */ + +G_DEFINE_BOXED_TYPE (GtkExpression, gtk_expression, + gtk_expression_ref, + gtk_expression_unref) + +/*< private > + * gtk_expression_alloc: + * @expression_class: class structure for this expression + * @value_type: the type of the value returned by this expression + * + * Returns: (transfer full): the newly created #GtkExpression + */ +static gpointer +gtk_expression_alloc (const GtkExpressionClass *expression_class, + GType value_type) +{ + GtkExpression *self; + + g_return_val_if_fail (expression_class != NULL, NULL); + + self = g_atomic_rc_box_alloc0 (expression_class->struct_size); + + self->expression_class = expression_class; + self->value_type = value_type; + + return self; +} + +/*** CONSTANT ***/ + +typedef struct _GtkConstantExpression GtkConstantExpression; + +struct _GtkConstantExpression +{ + GtkExpression parent; + + GValue value; +}; + +static void +gtk_constant_expression_finalize (GtkExpression *expr) +{ + GtkConstantExpression *self = (GtkConstantExpression *) expr; + + g_value_unset (&self->value); +} + +static gboolean +gtk_constant_expression_evaluate (GtkExpression *expr, + gpointer this, + GValue *value) +{ + GtkConstantExpression *self = (GtkConstantExpression *) expr; + + g_value_init (value, G_VALUE_TYPE (&self->value)); + g_value_copy (&self->value, value); + return TRUE; +} + +static const GtkExpressionClass GTK_CONSTANT_EXPRESSION_CLASS = +{ + sizeof (GtkConstantExpression), + "GtkConstantExpression", + gtk_constant_expression_finalize, + gtk_constant_expression_evaluate, +}; + +/** + * gtk_constant_expression_new: + * @value_type: The type of the object + * @...: arguments to create the object from + * + * Creates a GtkExpression that evaluates to the + * object given by the arguments. + * + * Returns: a new #GtkExpression + */ +GtkExpression * +gtk_constant_expression_new (GType value_type, + ...) +{ + GValue value = G_VALUE_INIT; + GtkExpression *result; + va_list args; + char *error; + + va_start (args, value_type); + G_VALUE_COLLECT_INIT (&value, value_type, + args, G_VALUE_NOCOPY_CONTENTS, + &error); + if (error) + { + g_critical ("%s: %s", G_STRLOC, error); + g_free (error); + /* we purposely leak the value here, it might not be + * in a sane state if an error condition occurred + */ + return NULL; + } + + result = gtk_constant_expression_new_for_value (&value); + + g_value_unset (&value); + va_end (args); + + return result; +} + +/** + * gtk_constant_expression_new_for_value: + * @value: a #GValue + * + * Creates an expression that always evaluates to the given @value. + * + * Returns: a new #GtkExpression + **/ +GtkExpression * +gtk_constant_expression_new_for_value (const GValue *value) +{ + GtkConstantExpression *result; + + g_return_val_if_fail (G_IS_VALUE (value), NULL); + + result = gtk_expression_alloc (>K_CONSTANT_EXPRESSION_CLASS, G_VALUE_TYPE (value)); + + g_value_init (&result->value, G_VALUE_TYPE (value)); + g_value_copy (value, &result->value); + + return (GtkExpression *) result; +} + +/*** PROPERTY ***/ + +typedef struct _GtkPropertyExpression GtkPropertyExpression; + +struct _GtkPropertyExpression +{ + GtkExpression parent; + + GtkExpression *expr; + + GParamSpec *pspec; +}; + +static void +gtk_property_expression_finalize (GtkExpression *expr) +{ + GtkPropertyExpression *self = (GtkPropertyExpression *) expr; + + g_clear_pointer (&self->expr, gtk_expression_unref); +} + +static GObject * +gtk_property_expression_get_object (GtkPropertyExpression *self, + gpointer this) +{ + GValue expr_value = G_VALUE_INIT; + GObject *object; + + if (self->expr == NULL) + { + if (this) + return g_object_ref (this); + else + return NULL; + } + + if (!gtk_expression_evaluate (self->expr, this, &expr_value)) + return NULL; + + if (!G_VALUE_HOLDS_OBJECT (&expr_value)) + { + g_value_unset (&expr_value); + return NULL; + } + + object = g_value_dup_object (&expr_value); + g_value_unset (&expr_value); + + if (!G_TYPE_CHECK_INSTANCE_TYPE (object, self->pspec->owner_type)) + { + g_object_unref (object); + return NULL; + } + + return object; +} + +static gboolean +gtk_property_expression_evaluate (GtkExpression *expr, + gpointer this, + GValue *value) +{ + GtkPropertyExpression *self = (GtkPropertyExpression *) expr; + GObject *object; + + object = gtk_property_expression_get_object (self, this); + if (object == NULL) + return FALSE; + + g_object_get_property (object, self->pspec->name, value); + g_object_unref (object); + return TRUE; +} + +static const GtkExpressionClass GTK_PROPERTY_EXPRESSION_CLASS = +{ + sizeof (GtkPropertyExpression), + "GtkPropertyExpression", + gtk_property_expression_finalize, + gtk_property_expression_evaluate, +}; + +/** + * gtk_property_expression_new: + * @this_type: The type to expect for the this type + * @expression: (nullable) (transfer full): Expression to + * evaluate to get the object to query or %NULL to + * query the `this` object + * @property_name: name of the property + * + * Creates an expression that looks up a property via the + * given @expression or the `this` argument when @expression + * is %NULL. + * + * If the resulting object conforms to @this_type, its property + * named @property_name will be queried. + * Otherwise, this expression's evaluation will fail. + * + * The given @this_type must have a property with @property_name. + * + * Returns: a new #GtkExpression + **/ +GtkExpression * +gtk_property_expression_new (GType this_type, + GtkExpression *expression, + const char *property_name) +{ + GParamSpec *pspec; + + if (g_type_is_a (this_type, G_TYPE_OBJECT)) + { + pspec = g_object_class_find_property (g_type_class_peek (this_type), property_name); + } + else if (g_type_is_a (this_type, G_TYPE_INTERFACE)) + { + pspec = g_object_interface_find_property (g_type_default_interface_peek (this_type), property_name); + } + else + { + g_critical ("Type `%s` does not support properties", g_type_name (this_type)); + return NULL; + } + + if (pspec == NULL) + { + g_critical ("Type `%s` does not have a property named `%s`", g_type_name (this_type), property_name); + return NULL; + } + + return gtk_property_expression_new_for_pspec (expression, pspec); +} + +/** + * gtk_property_expression_new_for_pspec: + * @expression: (nullable) (transfer full): Expression to + * evaluate to get the object to query or %NULL to + * query the `this` object + * @pspec: the #GParamSpec for the property to query + * + * Creates an expression that looks up a property via the + * given @expression or the `this` argument when @expression + * is %NULL. + * + * If the resulting object conforms to @this_type, its + * property specified by @pspec will be queried. + * Otherwise, this expression's evaluation will fail. + * + * Returns: a new #GtkExpression + **/ +GtkExpression * +gtk_property_expression_new_for_pspec (GtkExpression *expression, + GParamSpec *pspec) +{ + GtkPropertyExpression *result; + + result = gtk_expression_alloc (>K_PROPERTY_EXPRESSION_CLASS, pspec->value_type); + + result->pspec = pspec; + result->expr = expression; + + return (GtkExpression *) result; +} + +/*** CLOSURE ***/ + +typedef struct _GtkClosureExpression GtkClosureExpression; + +struct _GtkClosureExpression +{ + GtkExpression parent; + + GClosure *closure; + guint n_params; + GtkExpression **params; +}; + +static void +gtk_closure_expression_finalize (GtkExpression *expr) +{ + GtkClosureExpression *self = (GtkClosureExpression *) expr; + guint i; + + for (i = 0; i < self->n_params; i++) + { + gtk_expression_unref (self->params[i]); + } + g_free (self->params); + + g_closure_unref (self->closure); +} + +static gboolean +gtk_closure_expression_evaluate (GtkExpression *expr, + gpointer this, + GValue *value) +{ + GtkClosureExpression *self = (GtkClosureExpression *) expr; + GValue *instance_and_params; + gboolean result = TRUE; + guint i; + + instance_and_params = g_alloca (sizeof (GValue) * (self->n_params + 1)); + memset (instance_and_params, 0, sizeof (GValue) * (self->n_params + 1)); + + for (i = 0; i < self->n_params; i++) + { + if (!gtk_expression_evaluate (self->params[i], this, &instance_and_params[i + 1])) + { + result = FALSE; + goto out; + } + } + if (this) + g_value_init_from_instance (instance_and_params, this); + else + g_value_init (instance_and_params, G_TYPE_OBJECT); + + g_value_init (value, expr->value_type); + g_closure_invoke (self->closure, + value, + self->n_params + 1, + instance_and_params, + NULL); + +out: + for (i = 0; i < self->n_params + 1; i++) + g_value_unset (&instance_and_params[i]); + + return result; +} + +static const GtkExpressionClass GTK_CLOSURE_EXPRESSION_CLASS = +{ + sizeof (GtkClosureExpression), + "GtkClosureExpression", + gtk_closure_expression_finalize, + gtk_closure_expression_evaluate, +}; + +/** + * gtk_closure_expression_new: + * @type: the type of the value that this expression evaluates to + * @closure: closure to call when evaluating this expression. If closure is floating, it is adopted + * @n_params: the number of params needed for evaluating @closure + * @params: (array length=n_params) (transfer full): expressions for each parameter + * + * Creates a GtkExpression that calls @closure when it is evaluated. + * @closure is called with the @this object and the results of evaluating + * the @params expressions. + * + * Returns: a new #GtkExpression + */ +GtkExpression * +gtk_closure_expression_new (GType value_type, + GClosure *closure, + guint n_params, + GtkExpression **params) +{ + GtkClosureExpression *result; + guint i; + + g_return_val_if_fail (closure != NULL, NULL); + g_return_val_if_fail (n_params == 0 || params != NULL, NULL); + + result = gtk_expression_alloc (>K_CLOSURE_EXPRESSION_CLASS, value_type); + + result->closure = g_closure_ref (closure); + g_closure_sink (closure); + if (G_CLOSURE_NEEDS_MARSHAL (closure)) + g_closure_set_marshal (closure, g_cclosure_marshal_generic); + + result->n_params = n_params; + result->params = g_new (GtkExpression *, n_params); + for (i = 0; i < n_params; i++) + result->params[i] = params[i]; + + return (GtkExpression *) result; +} + +/** + * gtk_cclosure_expression_new: + * @type: the type of the value that this expression evaluates to + * @marshal: marshaller used for creating a closure + * @n_params: the number of params needed for evaluating @closure + * @params: (array length=n_params) (transfer full): expressions for each parameter + * @callback_func: callback used for creating a closure + * @user_data: user data used for creating a closure + * @user_destroy: destroy notify for @user_data + * + * This function is a variant of gtk_closure_expression_new() that + * creates a #GClosure by calling gtk_cclosure_new() with the given + * @callback_func, @user_data and @user_destroy. + * + * Returns: a new #GtkExpression + */ +GtkExpression * +gtk_cclosure_expression_new (GType value_type, + GClosureMarshal marshal, + guint n_params, + GtkExpression **params, + GCallback callback_func, + gpointer user_data, + GClosureNotify user_destroy) +{ + GClosure *closure; + + closure = g_cclosure_new (callback_func, user_data, user_destroy); + if (marshal) + g_closure_set_marshal (closure, marshal); + + return gtk_closure_expression_new (value_type, closure, n_params, params); +} + +/*** PUBLIC API ***/ + +static void +gtk_expression_finalize (GtkExpression *self) +{ + self->expression_class->finalize (self); +} + +/** + * gtk_expression_ref: + * @self: (allow-none): a #GtkExpression + * + * Acquires a reference on the given #GtkExpression. + * + * Returns: (transfer none): the #GtkExpression with an additional reference + */ +GtkExpression * +gtk_expression_ref (GtkExpression *self) +{ + return g_atomic_rc_box_acquire (self); +} + +/** + * gtk_expression_unref: + * @self: (allow-none): a #GtkExpression + * + * Releases a reference on the given #GtkExpression. + * + * If the reference was the last, the resources associated to the @self are + * freed. + */ +void +gtk_expression_unref (GtkExpression *self) +{ + g_atomic_rc_box_release_full (self, (GDestroyNotify) gtk_expression_finalize); +} + +/** + * gtk_expression_get_value_type: + * @self: a #GtkExpression + * + * Gets the #GType that this expression evaluates to. This type + * is constant and will not change over the lifetime of this expression. + * + * Returns: The type returned from gtk_expression_evaluate() + **/ +GType +gtk_expression_get_value_type (GtkExpression *self) +{ + g_return_val_if_fail (GTK_IS_EXPRESSION (self), G_TYPE_INVALID); + + return self->value_type; +} + +/** + * gtk_expression_evaluate: + * @self: a #GtkExpression + * @this_: (transfer none) (type GObject) (nullable): the this argument for the evaluation + * @value: an empty #GValue + * + * Evaluates the given expression and on success stores the result + * in @value. The #GType of @value will be the type given by + * gtk_expression_get_value_type(). + * + * It is possible that expressions cannot be evaluated - for example + * when the expression references objects that have been destroyed or + * set to %NULL. In that case @value will remain empty and %FALSE + * will be returned. + * + * Returns: %TRUE if the expression could be evaluated + **/ +gboolean +gtk_expression_evaluate (GtkExpression *self, + gpointer this_, + GValue *value) +{ + g_return_val_if_fail (GTK_IS_EXPRESSION (self), FALSE); + g_return_val_if_fail (this_ == NULL || G_IS_OBJECT (this_), FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + return self->expression_class->evaluate (self, this_, value); +} + diff --git a/gtk/gtkexpression.h b/gtk/gtkexpression.h new file mode 100644 index 0000000000..270761e8ab --- /dev/null +++ b/gtk/gtkexpression.h @@ -0,0 +1,77 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + + +#ifndef __GTK_EXPRESSION_H__ +#define __GTK_EXPRESSION_H__ + +#include + +G_BEGIN_DECLS + +typedef struct _GtkExpression GtkExpression; + +#define GTK_IS_EXPRESSION(expr) ((expr) != NULL) + +#define GTK_TYPE_EXPRESSION (gtk_expression_get_type ()) + +GDK_AVAILABLE_IN_ALL +GType gtk_expression_get_type (void) G_GNUC_CONST; + +GDK_AVAILABLE_IN_ALL +GtkExpression * gtk_expression_ref (GtkExpression *self); +GDK_AVAILABLE_IN_ALL +void gtk_expression_unref (GtkExpression *self); + +GDK_AVAILABLE_IN_ALL +GType gtk_expression_get_value_type (GtkExpression *self); +GDK_AVAILABLE_IN_ALL +gboolean gtk_expression_evaluate (GtkExpression *self, + gpointer this_, + GValue *value); + +GDK_AVAILABLE_IN_ALL +GtkExpression * gtk_property_expression_new (GType this_type, + GtkExpression *expression, + const char *property_name); +GDK_AVAILABLE_IN_ALL +GtkExpression * gtk_property_expression_new_for_pspec (GtkExpression *expression, + GParamSpec *pspec); +GDK_AVAILABLE_IN_ALL +GtkExpression * gtk_constant_expression_new (GType value_type, + ...); +GDK_AVAILABLE_IN_ALL +GtkExpression * gtk_constant_expression_new_for_value (const GValue *value); +GDK_AVAILABLE_IN_ALL +GtkExpression * gtk_closure_expression_new (GType value_type, + GClosure *closure, + guint n_params, + GtkExpression **params); +GDK_AVAILABLE_IN_ALL +GtkExpression * gtk_cclosure_expression_new (GType value_type, + GClosureMarshal marshal, + guint n_params, + GtkExpression **params, + GCallback callback_func, + gpointer user_data, + GClosureNotify user_destroy); + +G_END_DECLS + +#endif /* __GTK_EXPRESSION_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index 6103a77e3f..af908e76ff 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -222,6 +222,7 @@ gtk_public_sources = files([ 'gtkeventcontrollermotion.c', 'gtkeventcontrollerscroll.c', 'gtkexpander.c', + 'gtkexpression.c', 'gtkfilechooser.c', 'gtkfilechooserbutton.c', 'gtkfilechooserdialog.c', @@ -479,6 +480,7 @@ gtk_public_headers = files([ 'gtkeventcontrollermotion.h', 'gtkeventcontrollerlegacy.h', 'gtkexpander.h', + 'gtkexpression.h', 'gtkfilechooser.h', 'gtkfilechooserbutton.h', 'gtkfilechooserdialog.h', From bd8655fdeeaf864ee12e8a6d99ac060df54ef95a Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 26 Nov 2019 18:59:34 +0100 Subject: [PATCH 008/170] expression: Add GtkObjectExpression Weak refs break cycles... --- docs/reference/gtk/gtk4-sections.txt | 2 +- gtk/gtkexpression.c | 79 ++++++++++++++++++++++++++++ gtk/gtkexpression.h | 2 + 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 89978fbcac..35b6d34946 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -7091,7 +7091,7 @@ gtk_property_expression_new gtk_property_expression_new_for_pspec gtk_constant_expression_new gtk_constant_expression_new_for_value -gtk_object_expression +gtk_object_expression_new gtk_closure_expression_new gtk_cclosure_expression_new diff --git a/gtk/gtkexpression.c b/gtk/gtkexpression.c index dbd100c767..d6b0c78206 100644 --- a/gtk/gtkexpression.c +++ b/gtk/gtkexpression.c @@ -208,6 +208,85 @@ gtk_constant_expression_new_for_value (const GValue *value) return (GtkExpression *) result; } +/*** OBJECT ***/ + +typedef struct _GtkObjectExpression GtkObjectExpression; + +struct _GtkObjectExpression +{ + GtkExpression parent; + + GObject *object; +}; + +static void +gtk_object_expression_weak_ref_cb (gpointer data, + GObject *object) +{ + GtkObjectExpression *self = (GtkObjectExpression *) data; + + self->object = NULL; +} + +static void +gtk_object_expression_finalize (GtkExpression *expr) +{ + GtkObjectExpression *self = (GtkObjectExpression *) expr; + + if (self->object) + g_object_weak_unref (self->object, gtk_object_expression_weak_ref_cb, self); +} + +static gboolean +gtk_object_expression_evaluate (GtkExpression *expr, + gpointer this, + GValue *value) +{ + GtkObjectExpression *self = (GtkObjectExpression *) expr; + + if (self->object == NULL) + return FALSE; + + g_value_init (value, gtk_expression_get_value_type (expr)); + g_value_set_object (value, self->object); + return TRUE; +} + +static const GtkExpressionClass GTK_OBJECT_EXPRESSION_CLASS = +{ + sizeof (GtkObjectExpression), + "GtkObjectExpression", + gtk_object_expression_finalize, + gtk_object_expression_evaluate +}; + +/** + * gtk_object_expression_new: + * @object: (transfer none): object to watch + * + * Creates an expression evaluating to the given @object with a weak reference. + * Once the @object is disposed, it will fail to evaluate. + * This expression is meant to break reference cycles. + * + * If you want to keep a reference to @object, use gtk_constant_expression_new(). + * + * Returns: a new #GtkExpression + **/ +GtkExpression * +gtk_object_expression_new (GObject *object) +{ + GtkObjectExpression *result; + + g_return_val_if_fail (G_IS_OBJECT (object), NULL); + + result = gtk_expression_alloc (>K_OBJECT_EXPRESSION_CLASS, G_OBJECT_TYPE (object)); + + result->object = object; + g_object_weak_ref (object, gtk_object_expression_weak_ref_cb, result); + + return (GtkExpression *) result; +} + /*** PROPERTY ***/ typedef struct _GtkPropertyExpression GtkPropertyExpression; diff --git a/gtk/gtkexpression.h b/gtk/gtkexpression.h index 270761e8ab..3df3788d1b 100644 --- a/gtk/gtkexpression.h +++ b/gtk/gtkexpression.h @@ -59,6 +59,8 @@ GtkExpression * gtk_constant_expression_new (GType GDK_AVAILABLE_IN_ALL GtkExpression * gtk_constant_expression_new_for_value (const GValue *value); GDK_AVAILABLE_IN_ALL +GtkExpression * gtk_object_expression_new (GObject *object); +GDK_AVAILABLE_IN_ALL GtkExpression * gtk_closure_expression_new (GType value_type, GClosure *closure, guint n_params, From 410e7dcf5d0fdc60285554e49a17fdee9e8a886e Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 16 Nov 2019 22:15:51 +0100 Subject: [PATCH 009/170] filter: Add GtkStringFilter Users provide a search filter and an expression that evaluates the items to a string and then the filter goes and matches those strings to the search term. --- docs/reference/gtk/gtk4-docs.xml | 1 + docs/reference/gtk/gtk4-sections.txt | 18 + docs/reference/gtk/gtk4.types.in | 1 + gtk/gtk.h | 1 + gtk/gtkstringfilter.c | 532 +++++++++++++++++++++++++++ gtk/gtkstringfilter.h | 81 ++++ gtk/meson.build | 2 + 7 files changed, 636 insertions(+) create mode 100644 gtk/gtkstringfilter.c create mode 100644 gtk/gtkstringfilter.h diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index 32812e1b1a..af3b78fcfa 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -53,6 +53,7 @@
+
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 35b6d34946..9f108af1d2 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -7101,3 +7101,21 @@ GTK_TYPE_EXPRESSION gtk_expression_get_type
+ +
+gtkstringfilter +GtkStringFilter +GtkStringFilterMatchMode +gtk_string_filter_new +gtk_string_filter_get_search +gtk_string_filter_set_search +gtk_string_filter_get_expression +gtk_string_filter_set_expression +gtk_string_filter_get_ignore_case +gtk_string_filter_set_ignore_case +gtk_string_filter_get_match_mode +gtk_string_filter_set_match_mode + + +gtk_string_filter_get_type +
diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in index a8975f30bb..9a3af66df4 100644 --- a/docs/reference/gtk/gtk4.types.in +++ b/docs/reference/gtk/gtk4.types.in @@ -189,6 +189,7 @@ gtk_stack_page_get_type gtk_stack_sidebar_get_type gtk_stack_switcher_get_type gtk_statusbar_get_type +gtk_string_filter_get_type gtk_switch_get_type gtk_level_bar_get_type gtk_style_context_get_type diff --git a/gtk/gtk.h b/gtk/gtk.h index e4f07f6ac3..c85c039b45 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -222,6 +222,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkstringfilter.c b/gtk/gtkstringfilter.c new file mode 100644 index 0000000000..ff409be8d0 --- /dev/null +++ b/gtk/gtkstringfilter.c @@ -0,0 +1,532 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gtkstringfilter.h" + +#include "gtkintl.h" +#include "gtktypebuiltins.h" + +/** + * SECTION:gtkstringfilter + * @Title: GtkStringFilter + * @Short_description: Filtering by string + * + * GtkStringFilter determines whether to include items by looking + * at strings and comparing them to a fixed search term. The strings + * are obtained from the items by evaluating a #GtkExpression. + * + * GtkStringFilter has several different modes of comparison - it + * can match the whole string, just a prefix, or any substring. + */ + +struct _GtkStringFilter +{ + GtkFilter parent_instance; + + char *search; + char *search_prepared; + + gboolean ignore_case; + GtkStringFilterMatchMode match_mode; + + GtkExpression *expression; +}; + +enum { + PROP_0, + PROP_EXPRESSION, + PROP_IGNORE_CASE, + PROP_MATCH_MODE, + PROP_SEARCH, + NUM_PROPERTIES +}; + +G_DEFINE_TYPE (GtkStringFilter, gtk_string_filter, GTK_TYPE_FILTER) + +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +static char * +gtk_string_filter_prepare (GtkStringFilter *self, + const char *s) +{ + char *tmp; + char *result; + + if (s == NULL || s[0] == '\0') + return NULL; + + tmp = g_utf8_normalize (s, -1, G_NORMALIZE_ALL); + + if (!self->ignore_case) + return tmp; + + result = g_utf8_casefold (tmp, -1); + g_free (tmp); + + return result; +} + +/* This is necessary because code just looks at self->search otherwise + * and that can be the empty string... + */ +static gboolean +gtk_string_filter_has_search (GtkStringFilter *self) +{ + return self->search_prepared != NULL; +} + +static gboolean +gtk_string_filter_match (GtkFilter *filter, + gpointer item) +{ + GtkStringFilter *self = GTK_STRING_FILTER (filter); + GValue value = G_VALUE_INIT; + char *prepared; + const char *s; + gboolean result; + + if (!gtk_string_filter_has_search (self)) + return TRUE; + + if (self->expression == NULL || + !gtk_expression_evaluate (self->expression, item, &value)) + return FALSE; + s = g_value_get_string (&value); + if (s == NULL) + return FALSE; + prepared = gtk_string_filter_prepare (self, s); + + switch (self->match_mode) + { + case GTK_STRING_FILTER_MATCH_MODE_EXACT: + result = strcmp (prepared, self->search_prepared) == 0; + break; + case GTK_STRING_FILTER_MATCH_MODE_SUBSTRING: + result = strstr (prepared, self->search_prepared) != NULL; + break; + case GTK_STRING_FILTER_MATCH_MODE_PREFIX: + result = g_str_has_prefix (prepared, self->search_prepared); + break; + default: + g_assert_not_reached (); + } + +#if 0 + g_print ("%s (%s) %s %s (%s)\n", s, prepared, result ? "==" : "!=", self->search, self->search_prepared); +#endif + + g_free (prepared); + g_value_unset (&value); + + return result; +} + +static GtkFilterMatch +gtk_string_filter_get_strictness (GtkFilter *filter) +{ + GtkStringFilter *self = GTK_STRING_FILTER (filter); + + if (!gtk_string_filter_has_search (self)) + return GTK_FILTER_MATCH_ALL; + + if (self->expression == NULL) + return GTK_FILTER_MATCH_NONE; + + return GTK_FILTER_MATCH_SOME; +} + +static void +gtk_string_filter_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkStringFilter *self = GTK_STRING_FILTER (object); + + switch (prop_id) + { + case PROP_EXPRESSION: + gtk_string_filter_set_expression (self, g_value_get_boxed (value)); + break; + + case PROP_IGNORE_CASE: + gtk_string_filter_set_ignore_case (self, g_value_get_boolean (value)); + break; + + case PROP_MATCH_MODE: + gtk_string_filter_set_match_mode (self, g_value_get_enum (value)); + break; + + case PROP_SEARCH: + gtk_string_filter_set_search (self, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_string_filter_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkStringFilter *self = GTK_STRING_FILTER (object); + + switch (prop_id) + { + case PROP_EXPRESSION: + g_value_set_boxed (value, self->expression); + break; + + case PROP_IGNORE_CASE: + g_value_set_boolean (value, self->ignore_case); + break; + + case PROP_MATCH_MODE: + g_value_set_enum (value, self->match_mode); + break; + + case PROP_SEARCH: + g_value_set_string (value, self->search); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_string_filter_dispose (GObject *object) +{ + GtkStringFilter *self = GTK_STRING_FILTER (object); + + g_clear_pointer (&self->search, g_free); + g_clear_pointer (&self->search_prepared, g_free); + g_clear_pointer (&self->expression, gtk_expression_unref); + + G_OBJECT_CLASS (gtk_string_filter_parent_class)->dispose (object); +} + +static void +gtk_string_filter_class_init (GtkStringFilterClass *class) +{ + GtkFilterClass *filter_class = GTK_FILTER_CLASS (class); + GObjectClass *object_class = G_OBJECT_CLASS (class); + + filter_class->match = gtk_string_filter_match; + filter_class->get_strictness = gtk_string_filter_get_strictness; + + object_class->get_property = gtk_string_filter_get_property; + object_class->set_property = gtk_string_filter_set_property; + object_class->dispose = gtk_string_filter_dispose; + + /** + * GtkStringFilter:expression: + * + * The expression to evalute on item to get a string to compare with + */ + properties[PROP_EXPRESSION] = + g_param_spec_boxed ("expression", + P_("Expression"), + P_("Expression to compare with"), + GTK_TYPE_EXPRESSION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkStringFilter:ignore-case: + * + * If matching is case sensitive + */ + properties[PROP_IGNORE_CASE] = + g_param_spec_boolean ("ignore-case", + P_("Ignore case"), + P_("If matching is case sensitive"), + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkStringFilter:match-mode: + * + * If exact matches are necessary or if substrings are allowed + */ + properties[PROP_MATCH_MODE] = + g_param_spec_enum ("match-mode", + P_("Match mode"), + P_("If exact matches are necessary or if substrings are allowed"), + GTK_TYPE_STRING_FILTER_MATCH_MODE, + GTK_STRING_FILTER_MATCH_MODE_SUBSTRING, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkStringFilter:search: + * + * The search term + */ + properties[PROP_SEARCH] = + g_param_spec_string ("search", + P_("Search"), + P_("The search term"), + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); + +} + +static void +gtk_string_filter_init (GtkStringFilter *self) +{ + self->ignore_case = TRUE; + self->match_mode = GTK_STRING_FILTER_MATCH_MODE_SUBSTRING; +} + +/** + * gtk_string_filter_new: + * + * Creates a new string filter. + * + * You will want to set up the filter by providing a string to search for + * and by providing a property to look up on the item. + * + * Returns: a new #GtkStringFilter + **/ +GtkFilter * +gtk_string_filter_new (void) +{ + return g_object_new (GTK_TYPE_STRING_FILTER, NULL); +} + +/** + * gtk_string_filter_get_search: + * @self: a #GtkStringFilter + * + * Gets the search string set via gtk_string_filter_set_search(). + * + * Returns: (allow-none) (transfer none): The search string + **/ +const char * +gtk_string_filter_get_search (GtkStringFilter *self) +{ + g_return_val_if_fail (GTK_IS_STRING_FILTER (self), NULL); + + return self->search; +} + +/** + * gtk_string_filter_set_search: + * @self: a #GtkStringFilter + * @search: (transfer none) (nullable): The string to search for + * or %NULL to clear the search + * + * Sets the string to search for. + **/ +void +gtk_string_filter_set_search (GtkStringFilter *self, + const char *search) +{ + GtkFilterChange change; + + g_return_if_fail (GTK_IS_STRING_FILTER (self)); + + if (g_strcmp0 (self->search, search) == 0) + return; + + if (search == NULL || search[0] == 0) + change = GTK_FILTER_CHANGE_LESS_STRICT; + else if (!gtk_string_filter_has_search (self)) + change = GTK_FILTER_CHANGE_MORE_STRICT; + else if (g_str_has_prefix (search, self->search)) + change = GTK_FILTER_CHANGE_MORE_STRICT; + else if (g_str_has_prefix (self->search, search)) + change = GTK_FILTER_CHANGE_LESS_STRICT; + else + change = GTK_FILTER_CHANGE_DIFFERENT; + + g_free (self->search); + g_free (self->search_prepared); + + self->search = g_strdup (search); + self->search_prepared = gtk_string_filter_prepare (self, search); + + gtk_filter_changed (GTK_FILTER (self), change); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCH]); +} + +/** + * gtk_string_filter_get_expression: + * @self: a #GtkStringFilter + * + * Gets the expression that the string filter uses to + * obtain strings from items. + * + * Returns: a #GtkExpression + */ +GtkExpression * +gtk_string_filter_get_expression (GtkStringFilter *self) +{ + g_return_val_if_fail (GTK_IS_STRING_FILTER (self), NULL); + + return self->expression; +} + +/** + * gtk_string_filter_set_expression: + * @self: a #GtkStringFilter + * @expression: a #GtkExpression + * + * Sets the expression that the string filter uses to + * obtain strings from items. The expression must have + * a value type of #G_TYPE_STRING. + */ +void +gtk_string_filter_set_expression (GtkStringFilter *self, + GtkExpression *expression) +{ + g_return_if_fail (GTK_IS_STRING_FILTER (self)); + g_return_if_fail (expression == NULL || gtk_expression_get_value_type (expression) == G_TYPE_STRING); + + if (self->expression == expression) + return; + + g_clear_pointer (&self->expression, gtk_expression_unref); + self->expression = gtk_expression_ref (expression); + + if (gtk_string_filter_has_search (self)) + gtk_filter_changed (GTK_FILTER (self), GTK_FILTER_CHANGE_DIFFERENT); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EXPRESSION]); +} + +/** + * gtk_string_filter_get_ignore_case: + * @self: a #GtkStringFilter + * + * Returns whether the filter ignores case differences. + * + * Returns: %TRUE if the filter ignores case + */ +gboolean +gtk_string_filter_get_ignore_case (GtkStringFilter *self) +{ + g_return_val_if_fail (GTK_IS_STRING_FILTER (self), TRUE); + + return self->ignore_case; +} + +/** + * gtk_string_filter_set_ignore_case: + * @self: a #GtkStringFilter + * @ignore_case: %TRUE to ignore case + * + * Sets whether the filter ignores case differences. + */ +void +gtk_string_filter_set_ignore_case (GtkStringFilter *self, + gboolean ignore_case) +{ + g_return_if_fail (GTK_IS_STRING_FILTER (self)); + + if (self->ignore_case == ignore_case) + return; + + self->ignore_case = ignore_case; + + if (self->search) + { + g_free (self->search_prepared); + self->search_prepared = gtk_string_filter_prepare (self, self->search); + gtk_filter_changed (GTK_FILTER (self), ignore_case ? GTK_FILTER_CHANGE_LESS_STRICT : GTK_FILTER_CHANGE_MORE_STRICT); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_IGNORE_CASE]); +} + +/** + * gtk_string_filter_get_match_mode: + * @self: a #GtkStringFilter + * + * Returns the match mode that the filter is using. + * + * Returns: the match mode of the filter + */ +GtkStringFilterMatchMode +gtk_string_filter_get_match_mode (GtkStringFilter *self) +{ + g_return_val_if_fail (GTK_IS_STRING_FILTER (self), GTK_STRING_FILTER_MATCH_MODE_EXACT); + + return self->match_mode; +} + +/** + * gtk_string_filter_set_match_mode: + * @self: a #GtkStringFilter + * @mode: the new match mode + * + * Sets the match mode fot the filter. + */ +void +gtk_string_filter_set_match_mode (GtkStringFilter *self, + GtkStringFilterMatchMode mode) +{ + GtkStringFilterMatchMode old_mode; + + g_return_if_fail (GTK_IS_STRING_FILTER (self)); + + if (self->match_mode == mode) + return; + + old_mode = self->match_mode; + self->match_mode = mode; + + if (self->search_prepared && self->expression) + { + switch (old_mode) + { + case GTK_STRING_FILTER_MATCH_MODE_EXACT: + gtk_filter_changed (GTK_FILTER (self), GTK_FILTER_CHANGE_LESS_STRICT); + break; + + case GTK_STRING_FILTER_MATCH_MODE_SUBSTRING: + gtk_filter_changed (GTK_FILTER (self), GTK_FILTER_CHANGE_MORE_STRICT); + break; + + case GTK_STRING_FILTER_MATCH_MODE_PREFIX: + if (mode == GTK_STRING_FILTER_MATCH_MODE_SUBSTRING) + gtk_filter_changed (GTK_FILTER (self), GTK_FILTER_CHANGE_LESS_STRICT); + else + gtk_filter_changed (GTK_FILTER (self), GTK_FILTER_CHANGE_MORE_STRICT); + break; + + default: + g_assert_not_reached (); + break; + } + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MATCH_MODE]); +} + + diff --git a/gtk/gtkstringfilter.h b/gtk/gtkstringfilter.h new file mode 100644 index 0000000000..2c5e866bac --- /dev/null +++ b/gtk/gtkstringfilter.h @@ -0,0 +1,81 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __GTK_STRING_FILTER_H__ +#define __GTK_STRING_FILTER_H__ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +/** + * GtkStringFilterMatchMode: + * @GTK_STRING_FILTER_MATCH_MODE_EXACT: The search string and + * text must match exactly. + * @GTK_STRING_FILTER_MATCH_MODE_SUBSTRING: The search string + * must be contained as a substring inside the text. + * @GTK_STRING_FILTER_MATCH_MODE_PREFIX: The text must begin + * with the search string. + * + * Specifies how search strings are matched inside text. + */ +typedef enum { + GTK_STRING_FILTER_MATCH_MODE_EXACT, + GTK_STRING_FILTER_MATCH_MODE_SUBSTRING, + GTK_STRING_FILTER_MATCH_MODE_PREFIX +} GtkStringFilterMatchMode; + +#define GTK_TYPE_STRING_FILTER (gtk_string_filter_get_type ()) +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkStringFilter, gtk_string_filter, GTK, STRING_FILTER, GtkFilter) + +GDK_AVAILABLE_IN_ALL +GtkFilter * gtk_string_filter_new (void); + +GDK_AVAILABLE_IN_ALL +const char * gtk_string_filter_get_search (GtkStringFilter *self); +GDK_AVAILABLE_IN_ALL +void gtk_string_filter_set_search (GtkStringFilter *self, + const char *search); +GDK_AVAILABLE_IN_ALL +GtkExpression * gtk_string_filter_get_expression (GtkStringFilter *self); +GDK_AVAILABLE_IN_ALL +void gtk_string_filter_set_expression (GtkStringFilter *self, + GtkExpression *expression); +GDK_AVAILABLE_IN_ALL +gboolean gtk_string_filter_get_ignore_case (GtkStringFilter *self); +GDK_AVAILABLE_IN_ALL +void gtk_string_filter_set_ignore_case (GtkStringFilter *self, + gboolean ignore_case); +GDK_AVAILABLE_IN_ALL +GtkStringFilterMatchMode gtk_string_filter_get_match_mode (GtkStringFilter *self); +GDK_AVAILABLE_IN_ALL +void gtk_string_filter_set_match_mode (GtkStringFilter *self, + GtkStringFilterMatchMode mode); + + + +G_END_DECLS + +#endif /* __GTK_STRING_FILTER_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index af908e76ff..2ff16a97b6 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -352,6 +352,7 @@ gtk_public_sources = files([ 'gtkstacksidebar.c', 'gtkstackswitcher.c', 'gtkstatusbar.c', + 'gtkstringfilter.c', 'gtkstylecontext.c', 'gtkstyleprovider.c', 'gtkswitch.c', @@ -596,6 +597,7 @@ gtk_public_headers = files([ 'gtkstacksidebar.h', 'gtkstackswitcher.h', 'gtkstatusbar.h', + 'gtkstringfilter.h', 'gtkstylecontext.h', 'gtkstyleprovider.h', 'gtkswitch.h', From 22659afd00d03ba507946894321a4c749ffdd59a Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 11 Dec 2019 21:28:38 +0100 Subject: [PATCH 010/170] Add GtkMultiFilter, GtkAnyFilter, GtkEveryFilter GtkMultiFilter is the abstract base class for managing multiple child filter. GtkAnyFilter and GtkEveryFilter are the actual implementations. --- docs/reference/gtk/gtk4-docs.xml | 1 + docs/reference/gtk/gtk4-sections.txt | 23 ++ docs/reference/gtk/gtk4.types.in | 3 + gtk/gtk.h | 1 + gtk/gtkmultifilter.c | 431 +++++++++++++++++++++++++++ gtk/gtkmultifilter.h | 58 ++++ gtk/meson.build | 2 + 7 files changed, 519 insertions(+) create mode 100644 gtk/gtkmultifilter.c create mode 100644 gtk/gtkmultifilter.h diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index af3b78fcfa..e9b4ab8c06 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -54,6 +54,7 @@ + diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 9f108af1d2..5ead3a136c 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -1223,6 +1223,29 @@ GTK_CUSTOM_FILTER_GET_CLASS gtk_custom_filter_get_type +
+gtkmultifilter +GtkMultiFilter +GtkMultiFilter +gtk_multi_filter_append +gtk_multi_filter_remove + +GtkAnyFilter +gtk_any_filter_new + +GtkEveryFilter +gtk_every_filter_new + +GTK_CUSTOM_FILTER +GTK_IS_CUSTOM_FILTER +GTK_TYPE_CUSTOM_FILTER +GTK_CUSTOM_FILTER_CLASS +GTK_IS_CUSTOM_FILTER_CLASS +GTK_CUSTOM_FILTER_GET_CLASS + +gtk_custom_filter_get_type +
+
gtkfilterlistmodel GtkFilterListModel diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in index 9a3af66df4..0ef0a863f5 100644 --- a/docs/reference/gtk/gtk4.types.in +++ b/docs/reference/gtk/gtk4.types.in @@ -7,6 +7,7 @@ gtk_accessible_get_type gtk_actionable_get_type gtk_action_bar_get_type gtk_adjustment_get_type +gtk_any_filter_get_type gtk_app_chooser_get_type gtk_app_chooser_button_get_type gtk_app_chooser_dialog_get_type @@ -72,6 +73,7 @@ gtk_event_controller_focus_get_type gtk_event_controller_legacy_get_type gtk_event_controller_motion_get_type gtk_event_controller_scroll_get_type +gtk_every_filter_get_type gtk_expander_get_type gtk_file_chooser_button_get_type gtk_file_chooser_dialog_get_type @@ -127,6 +129,7 @@ gtk_media_stream_get_type gtk_menu_button_get_type gtk_message_dialog_get_type gtk_mount_operation_get_type +gtk_multi_filter_get_type gtk_native_get_type gtk_native_dialog_get_type gtk_no_selection_get_type diff --git a/gtk/gtk.h b/gtk/gtk.h index c85c039b45..3b03bbbc44 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -162,6 +162,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkmultifilter.c b/gtk/gtkmultifilter.c new file mode 100644 index 0000000000..3c57a36724 --- /dev/null +++ b/gtk/gtkmultifilter.c @@ -0,0 +1,431 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gtkmultifilter.h" + +#include "gtkbuildable.h" +#include "gtkintl.h" +#include "gtktypebuiltins.h" + +/*** MULTI FILTER ***/ + +/** + * SECTION:gtkmultifilter + * @Title: GtkMultiFilter + * @Short_description: Combining multiple filters + * + * GtkMultiFilter is the base type that implements support for handling + * multiple filters. + * + * GtkAnyFilter is an implementation of GtkMultiFilter that matches an item + * when at least one of its filters matches. + * + * GtkEveryFilter is an implementation of GtkMultiFilter that matches an item + * when each of its filters matches. + */ +struct _GtkMultiFilter +{ + GtkFilter parent_instance; + + GSequence *filters; +}; + +struct _GtkMultiFilterClass +{ + GtkFilterClass parent_class; + + GtkFilterChange addition_change; + GtkFilterChange removal_change; +}; + +static GType +gtk_multi_filter_get_item_type (GListModel *list) +{ + return GTK_TYPE_FILTER; +} + +static guint +gtk_multi_filter_get_n_items (GListModel *list) +{ + GtkMultiFilter *self = GTK_MULTI_FILTER (list); + + return g_sequence_get_length (self->filters); +} + +static gpointer +gtk_multi_filter_get_item (GListModel *list, + guint position) +{ + GtkMultiFilter *self = GTK_MULTI_FILTER (list); + GSequenceIter *iter; + + iter = g_sequence_get_iter_at_pos (self->filters, position); + + if (g_sequence_iter_is_end (iter)) + return NULL; + else + return g_object_ref (g_sequence_get (iter)); +} + +static void +gtk_multi_filter_list_model_init (GListModelInterface *iface) +{ + iface->get_item_type = gtk_multi_filter_get_item_type; + iface->get_n_items = gtk_multi_filter_get_n_items; + iface->get_item = gtk_multi_filter_get_item; +} + +static GtkBuildableIface *parent_buildable_iface; + +static void +gtk_multi_filter_buildable_add_child (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *type) +{ + if (GTK_IS_FILTER (child)) + gtk_multi_filter_append (GTK_MULTI_FILTER (buildable), g_object_ref (GTK_FILTER (child))); + else + parent_buildable_iface->add_child (buildable, builder, child, type); +} + +static void +gtk_multi_filter_buildable_init (GtkBuildableIface *iface) +{ + parent_buildable_iface = g_type_interface_peek_parent (iface); + + iface->add_child = gtk_multi_filter_buildable_add_child; +} + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GtkMultiFilter, gtk_multi_filter, GTK_TYPE_FILTER, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_multi_filter_list_model_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_multi_filter_buildable_init)) + +static void +gtk_multi_filter_changed_cb (GtkFilter *filter, + GtkFilterChange change, + GtkMultiFilter *self) +{ + gtk_filter_changed (GTK_FILTER (self), change); +} + +static void +gtk_multi_filter_remove_iter (GtkMultiFilter *self, + GSequenceIter *iter) +{ + GtkFilter *filter; + + filter = g_sequence_get (iter); + g_signal_handlers_disconnect_by_func (filter, gtk_multi_filter_changed_cb, self); + g_object_unref (filter); + g_sequence_remove (iter); +} + +static void +gtk_multi_filter_dispose (GObject *object) +{ + GtkMultiFilter *self = GTK_MULTI_FILTER (object); + + while (!g_sequence_is_empty (self->filters)) + gtk_multi_filter_remove_iter (self, g_sequence_get_begin_iter (self->filters)); + + G_OBJECT_CLASS (gtk_multi_filter_parent_class)->dispose (object); +} + +static void +gtk_multi_filter_finalize (GObject *object) +{ + GtkMultiFilter *self = GTK_MULTI_FILTER (object); + + g_assert (g_sequence_is_empty (self->filters)); + g_sequence_free (self->filters); + + G_OBJECT_CLASS (gtk_multi_filter_parent_class)->finalize (object); +} + +static void +gtk_multi_filter_class_init (GtkMultiFilterClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->dispose = gtk_multi_filter_dispose; + object_class->finalize = gtk_multi_filter_finalize; +} + +static void +gtk_multi_filter_init (GtkMultiFilter *self) +{ + self->filters = g_sequence_new (NULL); +} + +/** + * gtk_multi_filter_append: + * @self: a #GtkMultiFilter + * @filter: (transfer full): A new filter to use + * + * Adds a @filter to @self to use for matching. + **/ +void +gtk_multi_filter_append (GtkMultiFilter *self, + GtkFilter *filter) +{ + g_return_if_fail (GTK_IS_MULTI_FILTER (self)); + g_return_if_fail (GTK_IS_FILTER (filter)); + + g_signal_connect (filter, "changed", G_CALLBACK (gtk_multi_filter_changed_cb), self); + g_sequence_append (self->filters, filter); + + gtk_filter_changed (GTK_FILTER (self), + GTK_MULTI_FILTER_GET_CLASS (self)->addition_change); +} + +/** + * gtk_multi_filter_remove: + * @self: a #GtkMultiFilter + * @position: position of filter to remove + * + * Removes the filter at the given @position from the list of filters used + * by @self. + * If @position is larger than the number of filters, nothing happens and + * the function returns. + **/ +void +gtk_multi_filter_remove (GtkMultiFilter *self, + guint position) +{ + GSequenceIter *iter; + guint length; + + length = g_sequence_get_length (self->filters); + if (position >= length) + return; + + iter = g_sequence_get_iter_at_pos (self->filters, position); + gtk_multi_filter_remove_iter (self, iter); + + gtk_filter_changed (GTK_FILTER (self), + GTK_MULTI_FILTER_GET_CLASS (self)->removal_change); +} + +/*** ANY FILTER ***/ + +struct _GtkAnyFilter +{ + GtkMultiFilter parent_instance; +}; + +struct _GtkAnyFilterClass +{ + GtkMultiFilterClass parent_class; +}; + +G_DEFINE_TYPE (GtkAnyFilter, gtk_any_filter, GTK_TYPE_MULTI_FILTER) + +static gboolean +gtk_any_filter_match (GtkFilter *filter, + gpointer item) +{ + GtkMultiFilter *self = GTK_MULTI_FILTER (filter); + GSequenceIter *iter; + + for (iter = g_sequence_get_begin_iter (self->filters); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + GtkFilter *child = g_sequence_get (iter); + + if (gtk_filter_match (child, item)) + return TRUE; + } + + return FALSE; +} + +static GtkFilterMatch +gtk_any_filter_get_strictness (GtkFilter *filter) +{ + GtkMultiFilter *multi = GTK_MULTI_FILTER (filter); + GSequenceIter *iter; + GtkFilterMatch result = GTK_FILTER_MATCH_NONE; + + for (iter = g_sequence_get_begin_iter (multi->filters); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + GtkFilter *child = g_sequence_get (iter); + + switch (gtk_filter_get_strictness (child)) + { + case GTK_FILTER_MATCH_SOME: + result = GTK_FILTER_MATCH_SOME; + break; + case GTK_FILTER_MATCH_NONE: + break; + case GTK_FILTER_MATCH_ALL: + return GTK_FILTER_MATCH_ALL; + default: + g_return_val_if_reached (GTK_FILTER_MATCH_NONE); + break; + } + } + + return result; +} + +static void +gtk_any_filter_class_init (GtkAnyFilterClass *class) +{ + GtkMultiFilterClass *multi_filter_class = GTK_MULTI_FILTER_CLASS (class); + GtkFilterClass *filter_class = GTK_FILTER_CLASS (class); + + multi_filter_class->addition_change = GTK_FILTER_CHANGE_LESS_STRICT; + multi_filter_class->removal_change = GTK_FILTER_CHANGE_MORE_STRICT; + + filter_class->match = gtk_any_filter_match; + filter_class->get_strictness = gtk_any_filter_get_strictness; +} + +static void +gtk_any_filter_init (GtkAnyFilter *self) +{ +} + +/** + * gtk_any_filter_new: + * + * Creates a new empty "any" filter. + * Use gtk_multi_filter_append() to add filters to it. + * + * This filter matches an item if any of the filters added to it + * matches the item. + * In particular, this means that if no filter has been added to + * it, the filter matches no item. + * + * Returns: a new #GtkFilter + **/ +GtkFilter * +gtk_any_filter_new (void) +{ + return g_object_new (GTK_TYPE_ANY_FILTER, NULL); +} + +/*** EVERY FILTER ***/ + +struct _GtkEveryFilter +{ + GtkMultiFilter parent_instance; +}; + +struct _GtkEveryFilterClass +{ + GtkMultiFilterClass parent_class; +}; + +G_DEFINE_TYPE (GtkEveryFilter, gtk_every_filter, GTK_TYPE_MULTI_FILTER) + +static gboolean +gtk_every_filter_match (GtkFilter *filter, + gpointer item) +{ + GtkMultiFilter *self = GTK_MULTI_FILTER (filter); + GSequenceIter *iter; + + for (iter = g_sequence_get_begin_iter (self->filters); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + GtkFilter *child = g_sequence_get (iter); + + if (!gtk_filter_match (child, item)) + return FALSE; + } + + return TRUE; +} + +static GtkFilterMatch +gtk_every_filter_get_strictness (GtkFilter *filter) +{ + GtkMultiFilter *multi = GTK_MULTI_FILTER (filter); + GSequenceIter *iter; + GtkFilterMatch result = GTK_FILTER_MATCH_ALL; + + for (iter = g_sequence_get_begin_iter (multi->filters); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + GtkFilter *child = g_sequence_get (iter); + + switch (gtk_filter_get_strictness (child)) + { + case GTK_FILTER_MATCH_SOME: + result = GTK_FILTER_MATCH_SOME; + break; + case GTK_FILTER_MATCH_NONE: + return GTK_FILTER_MATCH_NONE; + case GTK_FILTER_MATCH_ALL: + break; + default: + g_return_val_if_reached (GTK_FILTER_MATCH_NONE); + break; + } + } + + return result; +} + +static void +gtk_every_filter_class_init (GtkEveryFilterClass *class) +{ + GtkMultiFilterClass *multi_filter_class = GTK_MULTI_FILTER_CLASS (class); + GtkFilterClass *filter_class = GTK_FILTER_CLASS (class); + + multi_filter_class->addition_change = GTK_FILTER_CHANGE_MORE_STRICT; + multi_filter_class->removal_change = GTK_FILTER_CHANGE_LESS_STRICT; + + filter_class->match = gtk_every_filter_match; + filter_class->get_strictness = gtk_every_filter_get_strictness; +} + +static void +gtk_every_filter_init (GtkEveryFilter *self) +{ +} + +/** + * gtk_every_filter_new: + * + * Creates a new empty "every" filter. + * Use gtk_multi_filter_append() to add filters to it. + * + * This filter matches an item if each of the filters added to it + * matches the item. + * In particular, this means that if no filter has been added to + * it, the filter matches every item. + * + * Returns: a new #GtkFilter + **/ +GtkFilter * +gtk_every_filter_new (void) +{ + return g_object_new (GTK_TYPE_EVERY_FILTER, NULL); +} + diff --git a/gtk/gtkmultifilter.h b/gtk/gtkmultifilter.h new file mode 100644 index 0000000000..492539e0dc --- /dev/null +++ b/gtk/gtkmultifilter.h @@ -0,0 +1,58 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __GTK_MULTI_FILTER_H__ +#define __GTK_MULTI_FILTER_H__ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_MULTI_FILTER (gtk_multi_filter_get_type ()) +GDK_AVAILABLE_IN_ALL +GDK_DECLARE_INTERNAL_TYPE (GtkMultiFilter, gtk_multi_filter, GTK, MULTI_FILTER, GtkFilter) + +GDK_AVAILABLE_IN_ALL +void gtk_multi_filter_append (GtkMultiFilter *self, + GtkFilter *filter); +GDK_AVAILABLE_IN_ALL +void gtk_multi_filter_remove (GtkMultiFilter *self, + guint position); + +#define GTK_TYPE_ANY_FILTER (gtk_any_filter_get_type ()) +GDK_AVAILABLE_IN_ALL +GDK_DECLARE_INTERNAL_TYPE (GtkAnyFilter, gtk_any_filter, GTK, ANY_FILTER, GtkMultiFilter) +GDK_AVAILABLE_IN_ALL +GtkFilter * gtk_any_filter_new (void); + +#define GTK_TYPE_EVERY_FILTER (gtk_every_filter_get_type ()) +GDK_AVAILABLE_IN_ALL +GDK_DECLARE_INTERNAL_TYPE (GtkEveryFilter, gtk_every_filter, GTK, EVERY_FILTER, GtkMultiFilter) +GDK_AVAILABLE_IN_ALL +GtkFilter * gtk_every_filter_new (void); + + +G_END_DECLS + +#endif /* __GTK_MULTI_FILTER_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index 2ff16a97b6..8b073c58e2 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -283,6 +283,7 @@ gtk_public_sources = files([ 'gtkmodelbutton.c', 'gtkmodules.c', 'gtkmountoperation.c', + 'gtkmultifilter.c', 'gtknativedialog.c', 'gtknomediafile.c', 'gtknoselection.c', @@ -537,6 +538,7 @@ gtk_public_headers = files([ 'gtkmenubutton.h', 'gtkmessagedialog.h', 'gtkmountoperation.h', + 'gtkmultifilter.h', 'gtknative.h', 'gtknativedialog.h', 'gtknoselection.h', From 2df3c39e50a478232a057a222fa14f3c1013e66f Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 11 Dec 2019 21:26:43 +0100 Subject: [PATCH 011/170] filter: Add tests Some basic tests for GtkFilter --- testsuite/gtk/filter.c | 354 ++++++++++++++++++++++++++++++++++++++ testsuite/gtk/meson.build | 1 + 2 files changed, 355 insertions(+) create mode 100644 testsuite/gtk/filter.c diff --git a/testsuite/gtk/filter.c b/testsuite/gtk/filter.c new file mode 100644 index 0000000000..5c5fd707c7 --- /dev/null +++ b/testsuite/gtk/filter.c @@ -0,0 +1,354 @@ +/* GtkRBTree tests. + * + * Copyright (C) 2011, Red Hat, Inc. + * Authors: Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include + +#include + +static GQuark number_quark; + +static guint +get (GListModel *model, + guint position) +{ + GObject *object = g_list_model_get_item (model, position); + g_assert (object != NULL); + return GPOINTER_TO_UINT (g_object_get_qdata (object, number_quark)); +} + +static char * +get_string (gpointer object) +{ + return g_strdup_printf ("%u", GPOINTER_TO_UINT (g_object_get_qdata (object, number_quark))); +} + +static void +append_digit (GString *s, + guint digit) +{ + static const char *names[10] = { NULL, "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" }; + + if (digit == 0) + return; + + g_assert (digit < 10); + + if (s->len) + g_string_append_c (s, ' '); + g_string_append (s, names[digit]); +} + +static void +append_below_thousand (GString *s, + guint n) +{ + if (n >= 100) + { + append_digit (s, n / 100); + g_string_append (s, " hundred"); + n %= 100; + } + + if (n >= 20) + { + const char *names[10] = { NULL, NULL, "twenty", "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety" }; + if (s->len) + g_string_append_c (s, ' '); + g_string_append (s, names [n / 10]); + n %= 10; + } + + if (n >= 10) + { + const char *names[10] = { "ten", "eleven", "twelve", "thirteen", "fourteen", + "fifteen", "sixteen", "seventeen", "eighteen", "nineteen" }; + if (s->len) + g_string_append_c (s, ' '); + g_string_append (s, names [n - 10]); + } + else + { + append_digit (s, n); + } +} + +static char * +get_spelled_out (gpointer object) +{ + guint n = GPOINTER_TO_UINT (g_object_get_qdata (object, number_quark)); + GString *s; + + g_assert (n < 1000000); + + if (n == 0) + return g_strdup ("Zero"); + + s = g_string_new (NULL); + + if (n >= 1000) + { + append_below_thousand (s, n / 1000); + g_string_append (s, " thousand"); + n %= 1000; + } + + append_below_thousand (s, n); + + /* Capitalize first letter so we can do case-sensitive matching */ + s->str[0] = g_ascii_toupper (s->str[0]); + + return g_string_free (s, FALSE); +} + +static char * +model_to_string (GListModel *model) +{ + GString *string = g_string_new (NULL); + guint i; + + for (i = 0; i < g_list_model_get_n_items (model); i++) + { + if (i > 0) + g_string_append (string, " "); + g_string_append_printf (string, "%u", get (model, i)); + } + + return g_string_free (string, FALSE); +} + +static GListStore * +new_store (guint start, + guint end, + guint step); + +static void +add (GListStore *store, + guint number) +{ + GObject *object; + + /* 0 cannot be differentiated from NULL, so don't use it */ + g_assert (number != 0); + + object = g_object_new (G_TYPE_OBJECT, NULL); + g_object_set_qdata (object, number_quark, GUINT_TO_POINTER (number)); + g_list_store_append (store, object); + g_object_unref (object); +} + +#define assert_model(model, expected) G_STMT_START{ \ + char *s = model_to_string (G_LIST_MODEL (model)); \ + if (!g_str_equal (s, expected)) \ + g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #model " == " #expected, s, "==", expected); \ + g_free (s); \ +}G_STMT_END + +static GListStore * +new_empty_store (void) +{ + return g_list_store_new (G_TYPE_OBJECT); +} + +static GListStore * +new_store (guint start, + guint end, + guint step) +{ + GListStore *store = new_empty_store (); + guint i; + + for (i = start; i <= end; i += step) + add (store, i); + + return store; +} + +static GtkFilterListModel * +new_model (guint size, + GtkFilter *filter) +{ + GtkFilterListModel *result; + + result = gtk_filter_list_model_new (G_LIST_MODEL (new_store (1, size, 1)), filter); + + return result; +} + +static gboolean +divisible_by (gpointer item, + gpointer data) +{ + return GPOINTER_TO_UINT (g_object_get_qdata (item, number_quark)) % GPOINTER_TO_UINT (data) == 0; +} + +static void +test_simple (void) +{ + GtkFilterListModel *model; + GtkFilter *filter; + + filter = gtk_custom_filter_new (divisible_by, GUINT_TO_POINTER (3), NULL); + model = new_model (20, filter); + g_object_unref (filter); + assert_model (model, "3 6 9 12 15 18"); + g_object_unref (model); +} + +static void +test_any_simple (void) +{ + GtkFilterListModel *model; + GtkFilter *any, *filter1, *filter2; + + any = gtk_any_filter_new (); + filter1 = gtk_custom_filter_new (divisible_by, GUINT_TO_POINTER (3), NULL); + filter2 = gtk_custom_filter_new (divisible_by, GUINT_TO_POINTER (5), NULL); + + model = new_model (20, any); + assert_model (model, ""); + + gtk_multi_filter_append (GTK_MULTI_FILTER (any), filter1); + assert_model (model, "3 6 9 12 15 18"); + + gtk_multi_filter_append (GTK_MULTI_FILTER (any), filter2); + assert_model (model, "3 5 6 9 10 12 15 18 20"); + + gtk_multi_filter_remove (GTK_MULTI_FILTER (any), 0); + assert_model (model, "5 10 15 20"); + + /* doesn't exist */ + gtk_multi_filter_remove (GTK_MULTI_FILTER (any), 10); + assert_model (model, "5 10 15 20"); + + gtk_multi_filter_remove (GTK_MULTI_FILTER (any), 0); + assert_model (model, ""); + + g_object_unref (model); + g_object_unref (any); +} + +static void +test_string_simple (void) +{ + GtkFilterListModel *model; + GtkFilter *filter; + GtkExpression *expr; + + expr = gtk_cclosure_expression_new (G_TYPE_STRING, + NULL, + 0, NULL, + G_CALLBACK (get_string), + NULL, NULL); + + filter = gtk_string_filter_new (); + gtk_string_filter_set_expression (GTK_STRING_FILTER (filter), expr); + + model = new_model (20, filter); + assert_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20"); + + gtk_string_filter_set_search (GTK_STRING_FILTER (filter), "1"); + assert_model (model, "1 10 11 12 13 14 15 16 17 18 19"); + + g_object_unref (model); + g_object_unref (filter); + gtk_expression_unref (expr); +} + +static void +test_string_properties (void) +{ + GtkFilterListModel *model; + GtkFilter *filter; + GtkExpression *expr; + + expr = gtk_cclosure_expression_new (G_TYPE_STRING, + NULL, + 0, NULL, + G_CALLBACK (get_spelled_out), + NULL, NULL); + + filter = gtk_string_filter_new (); + gtk_string_filter_set_expression (GTK_STRING_FILTER (filter), expr); + + model = new_model (1000, filter); + gtk_string_filter_set_search (GTK_STRING_FILTER (filter), "thirte"); + assert_model (model, "13 113 213 313 413 513 613 713 813 913"); + + gtk_string_filter_set_search (GTK_STRING_FILTER (filter), "thirteen"); + assert_model (model, "13 113 213 313 413 513 613 713 813 913"); + + gtk_string_filter_set_ignore_case (GTK_STRING_FILTER (filter), FALSE); + assert_model (model, "113 213 313 413 513 613 713 813 913"); + + gtk_string_filter_set_search (GTK_STRING_FILTER (filter), "Thirteen"); + assert_model (model, "13"); + + gtk_string_filter_set_match_mode (GTK_STRING_FILTER (filter), GTK_STRING_FILTER_MATCH_MODE_EXACT); + assert_model (model, "13"); + + gtk_string_filter_set_ignore_case (GTK_STRING_FILTER (filter), TRUE); + assert_model (model, "13"); + + gtk_string_filter_set_match_mode (GTK_STRING_FILTER (filter), GTK_STRING_FILTER_MATCH_MODE_SUBSTRING); + assert_model (model, "13 113 213 313 413 513 613 713 813 913"); + + g_object_unref (model); + g_object_unref (filter); + gtk_expression_unref (expr); +} + +static void +test_every_dispose (void) +{ + GtkFilter *filter, *filter1, *filter2; + + filter = gtk_every_filter_new (); + + filter1 = gtk_custom_filter_new (divisible_by, GUINT_TO_POINTER (3), NULL); + filter2 = gtk_custom_filter_new (divisible_by, GUINT_TO_POINTER (5), NULL); + + g_object_ref (filter1); + g_object_ref (filter2); + + gtk_multi_filter_append (GTK_MULTI_FILTER (filter), filter1); + gtk_multi_filter_append (GTK_MULTI_FILTER (filter), filter2); + + g_object_unref (filter); + + g_object_unref (filter1); + g_object_unref (filter2); +} + +int +main (int argc, char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + setlocale (LC_ALL, "C"); + + number_quark = g_quark_from_static_string ("Hell and fire was spawned to be released."); + + g_test_add_func ("/filter/simple", test_simple); + g_test_add_func ("/filter/any/simple", test_any_simple); + g_test_add_func ("/filter/string/simple", test_string_simple); + g_test_add_func ("/filter/string/properties", test_string_properties); + g_test_add_func ("/filter/every/dispose", test_every_dispose); + + return g_test_run (); +} diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build index dbdf2fc483..a1e5fcd0eb 100644 --- a/testsuite/gtk/meson.build +++ b/testsuite/gtk/meson.build @@ -26,6 +26,7 @@ tests = [ ['rbtree-crash', ['../../gtk/gtkrbtree.c'], ['-DGTK_COMPILATION', '-UG_ENABLE_DEBUG']], ['defaultvalue'], ['entry'], + ['filter'], ['filterlistmodel'], ['flattenlistmodel'], ['floating'], From fde75aa9f68530b693f47a4ef327dc29a3070ee5 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 18 Nov 2019 04:35:36 +0100 Subject: [PATCH 012/170] builder: Add support for parsing expressions --- gtk/gtkbuilder.c | 13 +- gtk/gtkbuilderparser.c | 439 +++++++++++++++++++++++++++++++++++++++- gtk/gtkbuilderprivate.h | 33 +++ gtk/gtkexpression.c | 38 ++++ testsuite/gtk/builder.c | 59 ++++++ 5 files changed, 580 insertions(+), 2 deletions(-) diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c index 3f8ba5329e..244f8e8a9a 100644 --- a/gtk/gtkbuilder.c +++ b/gtk/gtkbuilder.c @@ -527,7 +527,18 @@ gtk_builder_get_parameters (GtkBuilder *builder, const char *property_name = g_intern_string (prop->pspec->name); GValue property_value = G_VALUE_INIT; - if (prop->bound && (!prop->text || prop->text->len == 0)) + if (prop->value) + { + g_value_init (&property_value, G_PARAM_SPEC_VALUE_TYPE (prop->pspec)); + + if (G_PARAM_SPEC_VALUE_TYPE (prop->pspec) == GTK_TYPE_EXPRESSION) + g_value_set_boxed (&property_value, prop->value); + else + { + g_assert_not_reached(); + } + } + else if (prop->bound && (!prop->text || prop->text->len == 0)) { /* Ignore properties with a binding and no value since they are * only there for to express the binding. diff --git a/gtk/gtkbuilderparser.c b/gtk/gtkbuilderparser.c index fd2d6d91f7..00e856f05a 100644 --- a/gtk/gtkbuilderparser.c +++ b/gtk/gtkbuilderparser.c @@ -932,7 +932,7 @@ parse_property (ParserData *data, return; } - info = g_slice_new (PropertyInfo); + info = g_slice_new0 (PropertyInfo); info->tag_type = TAG_PROPERTY; info->pspec = pspec; info->text = g_string_new (""); @@ -948,11 +948,395 @@ parse_property (ParserData *data, static void free_property_info (PropertyInfo *info) { + if (info->value) + { + if (G_PARAM_SPEC_VALUE_TYPE (info->pspec) == GTK_TYPE_EXPRESSION) + gtk_expression_unref (info->value); + else + g_assert_not_reached(); + } g_string_free (info->text, TRUE); g_free (info->context); g_slice_free (PropertyInfo, info); } +static void +free_expression_info (ExpressionInfo *info) +{ + switch (info->expression_type) + { + case EXPRESSION_EXPRESSION: + g_clear_pointer (&info->expression, gtk_expression_unref); + break; + + case EXPRESSION_CONSTANT: + g_string_free (info->constant.text, TRUE); + break; + + case EXPRESSION_CLOSURE: + g_free (info->closure.function_name); + g_free (info->closure.object_name); + g_slist_free_full (info->closure.params, (GDestroyNotify) free_expression_info); + break; + + case EXPRESSION_PROPERTY: + g_clear_pointer (&info->property.expression, free_expression_info); + g_free (info->property.property_name); + break; + + default: + g_assert_not_reached (); + break; + } + g_slice_free (ExpressionInfo, info); +} + +static gboolean +check_expression_parent (ParserData *data) +{ + CommonInfo *common_info = state_peek_info (data, CommonInfo); + + if (common_info == NULL) + return FALSE; + + if (common_info->tag_type == TAG_PROPERTY) + { + PropertyInfo *prop_info = (PropertyInfo *) common_info; + + return G_PARAM_SPEC_VALUE_TYPE (prop_info->pspec) == GTK_TYPE_EXPRESSION; + } + + if (common_info->tag_type == TAG_EXPRESSION) + { + ExpressionInfo *expr_info = (ExpressionInfo *) common_info; + + switch (expr_info->expression_type) + { + case EXPRESSION_CLOSURE: + return TRUE; + case EXPRESSION_CONSTANT: + return FALSE; + case EXPRESSION_PROPERTY: + return expr_info->property.expression == NULL; + case EXPRESSION_EXPRESSION: + default: + g_assert_not_reached (); + return FALSE; + } + } + + return FALSE; +} + +static void +parse_constant_expression (ParserData *data, + const gchar *element_name, + const gchar **names, + const gchar **values, + GError **error) +{ + ExpressionInfo *info; + const char *type_name; + GType type; + + if (!check_expression_parent (data)) + { + error_invalid_tag (data, element_name, NULL, error); + return; + } + + if (!g_markup_collect_attributes (element_name, names, values, error, + G_MARKUP_COLLECT_STRING, "type", &type_name, + G_MARKUP_COLLECT_INVALID)) + { + _gtk_builder_prefix_error (data->builder, &data->ctx, error); + return; + } + + type = gtk_builder_get_type_from_name (data->builder, type_name); + if (type == G_TYPE_INVALID) + { + g_set_error (error, + GTK_BUILDER_ERROR, + GTK_BUILDER_ERROR_INVALID_VALUE, + "Invalid type '%s'", type_name); + _gtk_builder_prefix_error (data->builder, &data->ctx, error); + return; + } + + info = g_slice_new0 (ExpressionInfo); + info->tag_type = TAG_EXPRESSION; + info->expression_type = EXPRESSION_CONSTANT; + info->constant.type = type; + info->constant.text = g_string_new (NULL); + + state_push (data, info); +} + +static void +parse_closure_expression (ParserData *data, + const gchar *element_name, + const gchar **names, + const gchar **values, + GError **error) +{ + ExpressionInfo *info; + const char *type_name; + const char *function_name; + const char *object_name = NULL; + gboolean swapped = -1; + GType type; + + if (!check_expression_parent (data)) + { + error_invalid_tag (data, element_name, NULL, error); + return; + } + + if (!g_markup_collect_attributes (element_name, names, values, error, + G_MARKUP_COLLECT_STRING, "type", &type_name, + G_MARKUP_COLLECT_STRING, "function", &function_name, + G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "object", &object_name, + G_MARKUP_COLLECT_TRISTATE|G_MARKUP_COLLECT_OPTIONAL, "swapped", &swapped, + G_MARKUP_COLLECT_INVALID)) + { + _gtk_builder_prefix_error (data->builder, &data->ctx, error); + return; + } + + type = gtk_builder_get_type_from_name (data->builder, type_name); + if (type == G_TYPE_INVALID) + { + g_set_error (error, + GTK_BUILDER_ERROR, + GTK_BUILDER_ERROR_INVALID_VALUE, + "Invalid type '%s'", type_name); + _gtk_builder_prefix_error (data->builder, &data->ctx, error); + return; + } + + /* Swapped defaults to FALSE except when object is set */ + if (swapped == -1) + { + if (object_name) + swapped = TRUE; + else + swapped = FALSE; + } + + info = g_slice_new0 (ExpressionInfo); + info->tag_type = TAG_EXPRESSION; + info->expression_type = EXPRESSION_CLOSURE; + info->closure.type = type; + info->closure.swapped = swapped; + info->closure.function_name = g_strdup (function_name); + info->closure.object_name = g_strdup (object_name); + + state_push (data, info); +} + +static void +parse_lookup_expression (ParserData *data, + const gchar *element_name, + const gchar **names, + const gchar **values, + GError **error) +{ + ExpressionInfo *info; + const char *property_name; + const char *type_name; + GType type; + + if (!check_expression_parent (data)) + { + error_invalid_tag (data, element_name, NULL, error); + return; + } + + if (!g_markup_collect_attributes (element_name, names, values, error, + G_MARKUP_COLLECT_STRING, "type", &type_name, + G_MARKUP_COLLECT_STRING, "name", &property_name, + G_MARKUP_COLLECT_INVALID)) + { + _gtk_builder_prefix_error (data->builder, &data->ctx, error); + return; + } + + type = gtk_builder_get_type_from_name (data->builder, type_name); + if (type == G_TYPE_INVALID) + { + g_set_error (error, + GTK_BUILDER_ERROR, + GTK_BUILDER_ERROR_INVALID_VALUE, + "Invalid type '%s'", type_name); + _gtk_builder_prefix_error (data->builder, &data->ctx, error); + return; + } + + info = g_slice_new0 (ExpressionInfo); + info->tag_type = TAG_EXPRESSION; + info->expression_type = EXPRESSION_PROPERTY; + info->property.this_type = type; + info->property.property_name = g_strdup (property_name); + + state_push (data, info); +} + +static GtkExpression * +expression_info_construct (GtkBuilder *builder, + ExpressionInfo *info, + GError **error) +{ + switch (info->expression_type) + { + case EXPRESSION_EXPRESSION: + break; + + case EXPRESSION_CONSTANT: + { + GtkExpression *expr; + GValue value = G_VALUE_INIT; + + if (!gtk_builder_value_from_string_type (builder, + info->constant.type, + info->constant.text->str, + &value, + error)) + return NULL; + + if (G_VALUE_HOLDS_OBJECT (&value)) + expr = gtk_object_expression_new (g_value_get_object (&value)); + else + expr = gtk_constant_expression_new_for_value (&value); + g_value_unset (&value); + + g_string_free (info->constant.text, TRUE); + info->expression_type = EXPRESSION_EXPRESSION; + info->expression = expr; + } + break; + + case EXPRESSION_CLOSURE: + { + GObject *object; + GClosure *closure; + guint i, n_params; + GtkExpression **params; + GtkExpression *expression; + GSList *l; + + if (info->closure.object_name) + { + object = gtk_builder_lookup_object (builder, info->closure.object_name, 0, 0, error); + if (object == NULL) + return NULL; + } + else + { + object = NULL; + } + + closure = gtk_builder_create_closure (builder, + info->closure.function_name, + info->closure.swapped, + object, + error); + if (closure == NULL) + return NULL; + n_params = g_slist_length (info->closure.params); + params = g_newa (GtkExpression *, n_params); + i = n_params; + for (l = info->closure.params; l; l = l->next) + { + params[--i] = expression_info_construct (builder, l->data, error); + if (params[i] == NULL) + return NULL; + } + expression = gtk_closure_expression_new (info->closure.type, closure, n_params, params); + g_free (info->closure.function_name); + g_free (info->closure.object_name); + g_slist_free_full (info->closure.params, (GDestroyNotify) free_expression_info); + info->expression_type = EXPRESSION_EXPRESSION; + info->expression = expression; + } + break; + + case EXPRESSION_PROPERTY: + { + GtkExpression *expression; + GType type; + GParamSpec *pspec; + + if (info->property.expression) + { + expression = expression_info_construct (builder, info->property.expression, error); + if (expression == NULL) + return NULL; + g_clear_pointer (&info->property.expression, free_expression_info); + } + else + expression = NULL; + + if (info->property.this_type != G_TYPE_INVALID) + type = info->property.this_type; + else if (expression != NULL) + type = gtk_expression_get_value_type (expression); + else + { + g_set_error (error, + GTK_BUILDER_ERROR, + GTK_BUILDER_ERROR_MISSING_ATTRIBUTE, + "Lookups require a type attribute if they don't have an expression."); + return NULL; + } + + if (g_type_is_a (type, G_TYPE_OBJECT)) + { + GObjectClass *class = g_type_class_ref (type); + pspec = g_object_class_find_property (class, info->property.property_name); + g_type_class_unref (class); + } + else if (g_type_is_a (type, G_TYPE_INTERFACE)) + { + GTypeInterface *iface = g_type_default_interface_ref (type); + pspec = g_object_interface_find_property (iface, info->property.property_name); + g_type_default_interface_unref (iface); + } + else + { + g_set_error (error, + GTK_BUILDER_ERROR, + GTK_BUILDER_ERROR_MISSING_ATTRIBUTE, + "Type `%s` does not support properties", + g_type_name (type)); + return NULL; + } + + if (pspec == NULL) + { + g_set_error (error, + GTK_BUILDER_ERROR, + GTK_BUILDER_ERROR_MISSING_ATTRIBUTE, + "Type `%s` does not have a property name `%s`", + g_type_name (type), info->property.property_name); + return NULL; + } + + expression = gtk_property_expression_new_for_pspec (expression, pspec); + + g_free (info->property.property_name); + info->expression_type = EXPRESSION_EXPRESSION; + info->expression = expression; + } + break; + + default: + g_return_val_if_reached (NULL); + } + + return gtk_expression_ref (info->expression); +} + static void parse_signal (ParserData *data, const gchar *element_name, @@ -1287,6 +1671,12 @@ start_element (GtkBuildableParseContext *context, parse_requires (data, element_name, names, values, error); else if (strcmp (element_name, "interface") == 0) parse_interface (data, element_name, names, values, error); + else if (strcmp (element_name, "constant") == 0) + parse_constant_expression (data, element_name, names, values, error); + else if (strcmp (element_name, "closure") == 0) + parse_closure_expression (data, element_name, names, values, error); + else if (strcmp (element_name, "lookup") == 0) + parse_lookup_expression (data, element_name, names, values, error); else if (strcmp (element_name, "menu") == 0) _gtk_builder_menu_start (data, element_name, names, values, error); else if (strcmp (element_name, "placeholder") == 0) @@ -1422,6 +1812,44 @@ end_element (GtkBuildableParseContext *context, signal_info->object_name = g_strdup (object_info->id); object_info->signals = g_slist_prepend (object_info->signals, signal_info); } + else if (strcmp (element_name, "constant") == 0 || + strcmp (element_name, "closure") == 0 || + strcmp (element_name, "lookup") == 0) + { + ExpressionInfo *expression_info = state_pop_info (data, ExpressionInfo); + CommonInfo *parent_info = state_peek_info (data, CommonInfo); + g_assert (parent_info != NULL); + + if (parent_info->tag_type == TAG_PROPERTY) + { + PropertyInfo *prop_info = (PropertyInfo *) parent_info; + + prop_info->value = expression_info_construct (data->builder, expression_info, error); + } + else if (parent_info->tag_type == TAG_EXPRESSION) + { + ExpressionInfo *expr_info = (ExpressionInfo *) parent_info; + + switch (expr_info->expression_type) + { + case EXPRESSION_CLOSURE: + expr_info->closure.params = g_slist_prepend (expr_info->closure.params, expression_info); + break; + case EXPRESSION_PROPERTY: + expr_info->property.expression = expression_info; + break; + case EXPRESSION_EXPRESSION: + case EXPRESSION_CONSTANT: + default: + g_assert_not_reached (); + break; + } + } + else + { + g_assert_not_reached (); + } + } else if (strcmp (element_name, "requires") == 0) { RequiresInfo *req_info = state_pop_info (data, RequiresInfo); @@ -1502,6 +1930,12 @@ text (GtkBuildableParseContext *context, g_string_append_len (prop_info->text, text, text_len); } + else if (strcmp (gtk_buildable_parse_context_get_element (context), "constant") == 0) + { + ExpressionInfo *expr_info = (ExpressionInfo *) info; + + g_string_append_len (expr_info->constant.text, text, text_len); + } } static void @@ -1525,6 +1959,9 @@ free_info (CommonInfo *info) case TAG_REQUIRES: free_requires_info ((RequiresInfo *)info, NULL); break; + case TAG_EXPRESSION: + free_expression_info ((ExpressionInfo *)info); + break; default: g_assert_not_reached (); } diff --git a/gtk/gtkbuilderprivate.h b/gtk/gtkbuilderprivate.h index 9ddf24b402..32227c10f1 100644 --- a/gtk/gtkbuilderprivate.h +++ b/gtk/gtkbuilderprivate.h @@ -21,6 +21,7 @@ #include "gtkbuilder.h" #include "gtkbuildable.h" +#include "gtkexpression.h" enum { TAG_PROPERTY, @@ -31,6 +32,7 @@ enum { TAG_SIGNAL, TAG_INTERFACE, TAG_TEMPLATE, + TAG_EXPRESSION, }; typedef struct { @@ -64,6 +66,7 @@ typedef struct { typedef struct { guint tag_type; GParamSpec *pspec; + gpointer value; GString *text; gboolean translatable:1; gboolean bound:1; @@ -72,6 +75,36 @@ typedef struct { gint col; } PropertyInfo; +typedef struct _ExpressionInfo ExpressionInfo; +struct _ExpressionInfo { + guint tag_type; + enum { + EXPRESSION_EXPRESSION, + EXPRESSION_CONSTANT, + EXPRESSION_CLOSURE, + EXPRESSION_PROPERTY + } expression_type; + union { + GtkExpression *expression; + struct { + GType type; + GString *text; + } constant; + struct { + GType type; + char *function_name; + char *object_name; + gboolean swapped; + GSList *params; + } closure; + struct { + GType this_type; + char *property_name; + ExpressionInfo *expression; + } property; + }; +}; + typedef struct { guint tag_type; gchar *object_name; diff --git a/gtk/gtkexpression.c b/gtk/gtkexpression.c index d6b0c78206..0014a06b69 100644 --- a/gtk/gtkexpression.c +++ b/gtk/gtkexpression.c @@ -50,6 +50,44 @@ * * Watches can be created for automatically updating the propery of an object, * similar to GObject's #GBinding mechanism, by using gtk_expression_bind(). + * + * # GtkExpression in .ui files + * + * GtkBuilder has support for creating expressions. The syntax here can be used where + * a #GtkExpression object is needed like in a tag for an expression + * property, or in a tag to bind a property to an expression. + * + * To create an property expression, use the element. It can have a `type` + * attribute to specify the object type, and a `name` attribute to specify the property + * to look up. The content of can either be an element specfiying the expression + * to use the object, or a string that specifies the name of the object to use. + * + * Example: + * |[ + * string_filter + * |] + * + * To create a constant expression, use the element. If the type attribute + * is specified, the element content is interpreted as a value of that type. Otherwise, + * it is assumed to be an object. + * + * Example: + * |[ + * string_filter + * Hello, world + * ]| + * + * To create a closure expression, use the element. The `type` and `function` + * attributes specify what function to use for the closure, the content of the element + * contains the expressions for the parameters. + * + * Example: + * |[ + * + * File size: + * myfile + * + * ]| */ typedef struct _GtkExpressionClass GtkExpressionClass; diff --git a/testsuite/gtk/builder.c b/testsuite/gtk/builder.c index d545b3a667..db1eb2dd04 100644 --- a/testsuite/gtk/builder.c +++ b/testsuite/gtk/builder.c @@ -2492,6 +2492,64 @@ test_transforms (void) g_object_unref (builder); } +char * +builder_get_search (gpointer item) +{ + return g_strdup (gtk_string_filter_get_search (item)); +} + +char * +builder_copy_arg (gpointer item, const char *arg) +{ + return g_strdup (arg); +} + +static void +test_expressions (void) +{ + const char *tests[] = { + "" + " " + " Hello World" + " Hello World" + " " + "", + "" + " " + " Hello World" + " " + " " + "", + "" + " " + " Hello World" + " " + " " + "", + "" + " " + " Hello World" + " " + " Hello World" + " " + " " + "", + }; + GtkBuilder *builder; + GObject *obj; + guint i; + + for (i = 0; i < G_N_ELEMENTS (tests); i++) + { + builder = builder_new_from_string (tests[i], -1, NULL); + obj = gtk_builder_get_object (builder, "filter"); + g_assert (GTK_IS_FILTER (obj)); + g_assert (gtk_filter_match (GTK_FILTER (obj), obj)); + + g_object_unref (builder); + } +} + int main (int argc, char **argv) { @@ -2538,6 +2596,7 @@ main (int argc, char **argv) g_test_add_func ("/Builder/FileFilter", test_file_filter); g_test_add_func ("/Builder/Shortcuts", test_shortcuts); g_test_add_func ("/Builder/Transforms", test_transforms); + g_test_add_func ("/Builder/Expressions", test_expressions); return g_test_run(); } From ec742f9373356ec05fabf7b083fbc33be0c276a0 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 21 Nov 2019 05:53:56 +0100 Subject: [PATCH 013/170] expression: Add the ability to watch an expression --- gtk/gtkexpression.c | 574 +++++++++++++++++++++++++++++++++++++++++++- gtk/gtkexpression.h | 28 +++ 2 files changed, 597 insertions(+), 5 deletions(-) diff --git a/gtk/gtkexpression.c b/gtk/gtkexpression.c index 0014a06b69..0647c1f8e7 100644 --- a/gtk/gtkexpression.c +++ b/gtk/gtkexpression.c @@ -99,15 +99,37 @@ struct _GtkExpression GtkExpression *owner; }; +typedef struct _GtkExpressionSubWatch GtkExpressionSubWatch; + +struct _GtkExpressionWatch +{ + GtkExpression *expression; + GObject *this; + GDestroyNotify user_destroy; + GtkExpressionNotify notify; + gpointer user_data; + guchar sub[0]; +}; + struct _GtkExpressionClass { gsize struct_size; const char *type_name; void (* finalize) (GtkExpression *expr); + gboolean (* is_static) (GtkExpression *expr); gboolean (* evaluate) (GtkExpression *expr, gpointer this, GValue *value); + + gsize (* watch_size) (GtkExpression *expr); + void (* watch) (GtkExpression *self, + GtkExpressionSubWatch *watch, + gpointer this_, + GtkExpressionNotify notify, + gpointer user_data); + void (* unwatch) (GtkExpression *self, + GtkExpressionSubWatch *watch); }; /** @@ -143,6 +165,50 @@ gtk_expression_alloc (const GtkExpressionClass *expression_class, return self; } +static gsize +gtk_expression_watch_size_static (GtkExpression *self) +{ + return 0; +} + +static void +gtk_expression_watch_static (GtkExpression *self, + GtkExpressionSubWatch *watch, + gpointer this_, + GtkExpressionNotify notify, + gpointer user_data) +{ +} + +static void +gtk_expression_unwatch_static (GtkExpression *self, + GtkExpressionSubWatch *watch) +{ +} + +static gsize +gtk_expression_watch_size (GtkExpression *self) +{ + return self->expression_class->watch_size (self); +} + +static void +gtk_expression_subwatch_init (GtkExpression *self, + GtkExpressionSubWatch *watch, + gpointer this, + GtkExpressionNotify notify, + gpointer user_data) +{ + self->expression_class->watch (self, watch, this, notify, user_data); +} + +static void +gtk_expression_subwatch_finish (GtkExpression *self, + GtkExpressionSubWatch *watch) +{ + self->expression_class->unwatch (self, watch); +} + /*** CONSTANT ***/ typedef struct _GtkConstantExpression GtkConstantExpression; @@ -162,6 +228,12 @@ gtk_constant_expression_finalize (GtkExpression *expr) g_value_unset (&self->value); } +static gboolean +gtk_constant_expression_is_static (GtkExpression *expr) +{ + return TRUE; +} + static gboolean gtk_constant_expression_evaluate (GtkExpression *expr, gpointer this, @@ -179,7 +251,11 @@ static const GtkExpressionClass GTK_CONSTANT_EXPRESSION_CLASS = sizeof (GtkConstantExpression), "GtkConstantExpression", gtk_constant_expression_finalize, + gtk_constant_expression_is_static, gtk_constant_expression_evaluate, + gtk_expression_watch_size_static, + gtk_expression_watch_static, + gtk_expression_unwatch_static }; /** @@ -249,12 +325,20 @@ gtk_constant_expression_new_for_value (const GValue *value) /*** OBJECT ***/ typedef struct _GtkObjectExpression GtkObjectExpression; +typedef struct _GtkObjectExpressionWatch GtkObjectExpressionWatch; struct _GtkObjectExpression { GtkExpression parent; GObject *object; + GSList *watches; +}; + +struct _GtkObjectExpressionWatch +{ + GtkExpressionNotify notify; + gpointer user_data; }; static void @@ -262,8 +346,16 @@ gtk_object_expression_weak_ref_cb (gpointer data, GObject *object) { GtkObjectExpression *self = (GtkObjectExpression *) data; + GSList *l; self->object = NULL; + + for (l = self->watches; l; l = l->next) + { + GtkObjectExpressionWatch *owatch = l->data; + + owatch->notify (owatch->user_data); + } } static void @@ -273,6 +365,14 @@ gtk_object_expression_finalize (GtkExpression *expr) if (self->object) g_object_weak_unref (self->object, gtk_object_expression_weak_ref_cb, self); + + g_assert (self->watches == NULL); +} + +static gboolean +gtk_object_expression_is_static (GtkExpression *expr) +{ + return FALSE; } static gboolean @@ -290,12 +390,46 @@ gtk_object_expression_evaluate (GtkExpression *expr, return TRUE; } +static gsize +gtk_object_expression_watch_size (GtkExpression *expr) +{ + return sizeof (GtkObjectExpressionWatch); +} + +static void +gtk_object_expression_watch (GtkExpression *expr, + GtkExpressionSubWatch *watch, + gpointer this_, + GtkExpressionNotify notify, + gpointer user_data) +{ + GtkObjectExpression *self = (GtkObjectExpression *) expr; + GtkObjectExpressionWatch *owatch = (GtkObjectExpressionWatch *) watch; + + owatch->notify = notify; + owatch->user_data = user_data; + self->watches = g_slist_prepend (self->watches, owatch); +} + +static void +gtk_object_expression_unwatch (GtkExpression *expr, + GtkExpressionSubWatch *watch) +{ + GtkObjectExpression *self = (GtkObjectExpression *) expr; + + self->watches = g_slist_remove (self->watches, watch); +} + static const GtkExpressionClass GTK_OBJECT_EXPRESSION_CLASS = { sizeof (GtkObjectExpression), "GtkObjectExpression", gtk_object_expression_finalize, - gtk_object_expression_evaluate + gtk_object_expression_is_static, + gtk_object_expression_evaluate, + gtk_object_expression_watch_size, + gtk_object_expression_watch, + gtk_object_expression_unwatch }; /** @@ -346,6 +480,12 @@ gtk_property_expression_finalize (GtkExpression *expr) g_clear_pointer (&self->expr, gtk_expression_unref); } +static gboolean +gtk_property_expression_is_static (GtkExpression *expr) +{ + return FALSE; +} + static GObject * gtk_property_expression_get_object (GtkPropertyExpression *self, gpointer this) @@ -372,6 +512,8 @@ gtk_property_expression_get_object (GtkPropertyExpression *self, object = g_value_dup_object (&expr_value); g_value_unset (&expr_value); + if (object == NULL) + return NULL; if (!G_TYPE_CHECK_INSTANCE_TYPE (object, self->pspec->owner_type)) { @@ -399,12 +541,132 @@ gtk_property_expression_evaluate (GtkExpression *expr, return TRUE; } +typedef struct _GtkPropertyExpressionWatch GtkPropertyExpressionWatch; + +struct _GtkPropertyExpressionWatch +{ + GtkExpressionNotify notify; + gpointer user_data; + + GtkPropertyExpression *expr; + gpointer this; + GClosure *closure; + guchar sub[0]; +}; + +static void +gtk_property_expression_watch_destroy_closure (GtkPropertyExpressionWatch *pwatch) +{ + if (pwatch->closure == NULL) + return; + + g_closure_invalidate (pwatch->closure); + g_closure_unref (pwatch->closure); + pwatch->closure = NULL; +} + +static void +gtk_property_expression_watch_notify_cb (GObject *object, + GParamSpec *pspec, + GtkPropertyExpressionWatch *pwatch) +{ + pwatch->notify (pwatch->user_data); +} + +static void +gtk_property_expression_watch_create_closure (GtkPropertyExpressionWatch *pwatch) +{ + GObject *object; + + object = gtk_property_expression_get_object (pwatch->expr, pwatch->this); + if (object == NULL) + return; + + pwatch->closure = g_cclosure_new (G_CALLBACK (gtk_property_expression_watch_notify_cb), pwatch, NULL); + if (!g_signal_connect_closure_by_id (object, + g_signal_lookup ("notify", G_OBJECT_TYPE (object)), + g_quark_from_string (pwatch->expr->pspec->name), + g_closure_ref (pwatch->closure), + FALSE)) + { + g_assert_not_reached (); + } + + g_object_unref (object); +} + +static void +gtk_property_expression_watch_expr_notify_cb (gpointer data) +{ + GtkPropertyExpressionWatch *pwatch = data; + + gtk_property_expression_watch_destroy_closure (pwatch); + gtk_property_expression_watch_create_closure (pwatch); + pwatch->notify (pwatch->user_data); +} + +static gsize +gtk_property_expression_watch_size (GtkExpression *expr) +{ + GtkPropertyExpression *self = (GtkPropertyExpression *) expr; + gsize result; + + result = sizeof (GtkPropertyExpressionWatch); + if (self->expr) + result += gtk_expression_watch_size (self->expr); + + return result; +} + +static void +gtk_property_expression_watch (GtkExpression *expr, + GtkExpressionSubWatch *watch, + gpointer this_, + GtkExpressionNotify notify, + gpointer user_data) +{ + GtkPropertyExpressionWatch *pwatch = (GtkPropertyExpressionWatch *) watch; + GtkPropertyExpression *self = (GtkPropertyExpression *) expr; + + pwatch->notify = notify; + pwatch->user_data = user_data; + pwatch->expr = self; + pwatch->this = this_; + if (self->expr && !gtk_expression_is_static (self->expr)) + { + gtk_expression_subwatch_init (self->expr, + (GtkExpressionSubWatch *) pwatch->sub, + this_, + gtk_property_expression_watch_expr_notify_cb, + pwatch); + } + + gtk_property_expression_watch_create_closure (pwatch); +} + +static void +gtk_property_expression_unwatch (GtkExpression *expr, + GtkExpressionSubWatch *watch) +{ + GtkPropertyExpressionWatch *pwatch = (GtkPropertyExpressionWatch *) watch; + GtkPropertyExpression *self = (GtkPropertyExpression *) expr; + + gtk_property_expression_watch_destroy_closure (pwatch); + + if (self->expr && !gtk_expression_is_static (self->expr)) + gtk_expression_subwatch_finish (self->expr, (GtkExpressionSubWatch *) pwatch->sub); +} + static const GtkExpressionClass GTK_PROPERTY_EXPRESSION_CLASS = { sizeof (GtkPropertyExpression), "GtkPropertyExpression", gtk_property_expression_finalize, + gtk_property_expression_is_static, gtk_property_expression_evaluate, + gtk_property_expression_watch_size, + gtk_property_expression_watch, + gtk_property_expression_unwatch }; /** @@ -436,11 +698,15 @@ gtk_property_expression_new (GType this_type, if (g_type_is_a (this_type, G_TYPE_OBJECT)) { - pspec = g_object_class_find_property (g_type_class_peek (this_type), property_name); + GObjectClass *class = g_type_class_ref (this_type); + pspec = g_object_class_find_property (class, property_name); + g_type_class_unref (class); } else if (g_type_is_a (this_type, G_TYPE_INTERFACE)) { - pspec = g_object_interface_find_property (g_type_default_interface_peek (this_type), property_name); + GTypeInterface *iface = g_type_default_interface_ref (this_type); + pspec = g_object_interface_find_property (iface, property_name); + g_type_default_interface_unref (iface); } else { @@ -516,6 +782,21 @@ gtk_closure_expression_finalize (GtkExpression *expr) g_closure_unref (self->closure); } +static gboolean +gtk_closure_expression_is_static (GtkExpression *expr) +{ + GtkClosureExpression *self = (GtkClosureExpression *) expr; + guint i; + + for (i = 0; i < self->n_params; i++) + { + if (!gtk_expression_is_static (self->params[i])) + return FALSE; + } + + return TRUE; +} + static gboolean gtk_closure_expression_evaluate (GtkExpression *expr, gpointer this, @@ -556,17 +837,109 @@ out: return result; } +typedef struct _GtkClosureExpressionWatch GtkClosureExpressionWatch; +struct _GtkClosureExpressionWatch +{ + GtkExpressionNotify notify; + gpointer user_data; + + guchar sub[0]; +}; + +static void +gtk_closure_expression_watch_notify_cb (gpointer data) +{ + GtkClosureExpressionWatch *cwatch = data; + + cwatch->notify (cwatch->user_data); +} + +static gsize +gtk_closure_expression_watch_size (GtkExpression *expr) +{ + GtkClosureExpression *self = (GtkClosureExpression *) expr; + gsize size; + guint i; + + size = sizeof (GtkClosureExpressionWatch); + + for (i = 0; i < self->n_params; i++) + { + if (gtk_expression_is_static (self->params[i])) + continue; + + size += gtk_expression_watch_size (self->params[i]); + } + + return size; +} + +static void +gtk_closure_expression_watch (GtkExpression *expr, + GtkExpressionSubWatch *watch, + gpointer this_, + GtkExpressionNotify notify, + gpointer user_data) +{ + GtkClosureExpressionWatch *cwatch = (GtkClosureExpressionWatch *) watch; + GtkClosureExpression *self = (GtkClosureExpression *) expr; + guchar *sub; + guint i; + + cwatch->notify = notify; + cwatch->user_data = user_data; + + sub = cwatch->sub; + for (i = 0; i < self->n_params; i++) + { + if (gtk_expression_is_static (self->params[i])) + continue; + + gtk_expression_subwatch_init (self->params[i], + (GtkExpressionSubWatch *) sub, + this_, + gtk_closure_expression_watch_notify_cb, + watch); + sub += gtk_expression_watch_size (self->params[i]); + } +} + +static void +gtk_closure_expression_unwatch (GtkExpression *expr, + GtkExpressionSubWatch *watch) +{ + GtkClosureExpressionWatch *cwatch = (GtkClosureExpressionWatch *) watch; + GtkClosureExpression *self = (GtkClosureExpression *) expr; + guchar *sub; + guint i; + + sub = cwatch->sub; + for (i = 0; i < self->n_params; i++) + { + if (gtk_expression_is_static (self->params[i])) + continue; + + gtk_expression_subwatch_finish (self->params[i], + (GtkExpressionSubWatch *) sub); + sub += gtk_expression_watch_size (self->params[i]); + } +} + static const GtkExpressionClass GTK_CLOSURE_EXPRESSION_CLASS = { sizeof (GtkClosureExpression), "GtkClosureExpression", gtk_closure_expression_finalize, + gtk_closure_expression_is_static, gtk_closure_expression_evaluate, + gtk_closure_expression_watch_size, + gtk_closure_expression_watch, + gtk_closure_expression_unwatch }; /** * gtk_closure_expression_new: - * @type: the type of the value that this expression evaluates to + * @value_type: the type of the value that this expression evaluates to * @closure: closure to call when evaluating this expression. If closure is floating, it is adopted * @n_params: the number of params needed for evaluating @closure * @params: (array length=n_params) (transfer full): expressions for each parameter @@ -606,7 +979,7 @@ gtk_closure_expression_new (GType value_type, /** * gtk_cclosure_expression_new: - * @type: the type of the value that this expression evaluates to + * @value_type: the type of the value that this expression evaluates to * @marshal: marshaller used for creating a closure * @n_params: the number of params needed for evaluating @closure * @params: (array length=n_params) (transfer full): expressions for each parameter @@ -721,3 +1094,194 @@ gtk_expression_evaluate (GtkExpression *self, return self->expression_class->evaluate (self, this_, value); } +/** + * gtk_expression_is_static: + * @self: a #GtkExpression + * + * Checks if the expression is static. + * + * A static expression will never change its result when + * gtk_expression_evaluate() is called on it with the same arguments. + * + * That means a call to gtk_expression_watch() is not necessary because + * it will never trigger a notify. + * + * Returns: %TRUE if the expression is static + **/ +gboolean +gtk_expression_is_static (GtkExpression *self) +{ + return self->expression_class->is_static (self); +} + +static gboolean +gtk_expression_watch_is_watching (GtkExpressionWatch *watch) +{ + return watch->expression != NULL; +} + +static void +gtk_expression_watch_this_cb (gpointer data, + GObject *this) +{ + GtkExpressionWatch *watch = data; + + watch->this = NULL; + + watch->notify (watch->user_data); + gtk_expression_watch_unwatch (watch); +} + +static void +gtk_expression_watch_cb (gpointer data) +{ + GtkExpressionWatch *watch = data; + + if (!gtk_expression_watch_is_watching (watch)) + return; + + watch->notify (watch->user_data); +} + +/** + * gtk_expression_watch: + * @self: a #GtkExpression + * @this_: (transfer none) (type GObject) (nullable): the this argument to + * watch + * @notify: (closure user_data): callback to invoke when the + * expression changes + * @user_data: user data to pass to @notify callback + * @user_destroy: destroy notify for @user_data + * + * Installs a watch for the given @expression that calls the @notify function + * whenever the evaluation of @self may have changed. + * + * GTK cannot guarantee that the evaluation did indeed change when the @notify + * gets invoked, but it guarantees the opposite: When it did in fact change, + * the @notify will be invoked. + * + * Returns: (transfer none): The newly installed watch. Note that the only + * reference held to the watch will be released when the watch is unwatched + * which can happen automatically, and not just via + * gtk_expression_watch_unwatch(). You should call gtk_expression_watch_ref() + * if you want to keep the watch around. + **/ +GtkExpressionWatch * +gtk_expression_watch (GtkExpression *self, + gpointer this_, + GtkExpressionNotify notify, + gpointer user_data, + GDestroyNotify user_destroy) +{ + GtkExpressionWatch *watch; + + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (this_ == NULL || G_IS_OBJECT (this_), NULL); + g_return_val_if_fail (notify != NULL, NULL); + + watch = g_atomic_rc_box_alloc0 (sizeof (GtkExpressionWatch) + gtk_expression_watch_size (self)); + + watch->expression = gtk_expression_ref (self); + watch->this = this_; + if (this_) + g_object_weak_ref (this_, gtk_expression_watch_this_cb, watch); + watch->notify = notify; + watch->user_data = user_data; + watch->user_destroy = user_destroy; + + gtk_expression_subwatch_init (self, + (GtkExpressionSubWatch *) watch->sub, + this_, + gtk_expression_watch_cb, + watch); + + return watch; +} + +/** + * gtk_expression_watch_ref: + * @watch: (allow-none): a #GtkExpressionWatch + * + * Acquires a reference on the given #GtkExpressionWatch. + * + * Returns: (transfer none): the #GtkExpression with an additional reference + */ +GtkExpressionWatch * +gtk_expression_watch_ref (GtkExpressionWatch *watch) +{ + return g_atomic_rc_box_acquire (watch); +} + +static void +gtk_expression_watch_finalize (gpointer data) +{ + GtkExpressionWatch *watch = data; + + g_assert (!gtk_expression_watch_is_watching (watch)); +} + +/** + * gtk_expression_watch_unref: + * @watch: (allow-none): a #GtkExpressionWatch + * + * Releases a reference on the given #GtkExpressionWatch. + * + * If the reference was the last, the resources associated to @self are + * freed. + */ +void +gtk_expression_watch_unref (GtkExpressionWatch *watch) +{ + g_atomic_rc_box_release_full (watch, gtk_expression_watch_finalize); +} + +/** + * gtk_expression_watch_unwatch: + * @watch: (transfer none): watch to release + * + * Stops watching an expression that was established via gtk_expression_watch(). + **/ +void +gtk_expression_watch_unwatch (GtkExpressionWatch *watch) +{ + if (!gtk_expression_watch_is_watching (watch)) + return; + + gtk_expression_subwatch_finish (watch->expression, (GtkExpressionSubWatch *) watch->sub); + + if (watch->this) + g_object_weak_unref (watch->this, gtk_expression_watch_this_cb, watch); + + if (watch->user_destroy) + watch->user_destroy (watch->user_data); + + g_clear_pointer (&watch->expression, gtk_expression_unref); + + gtk_expression_watch_unref (watch); +} + +/** + * gtk_expression_watch_evaluate: + * @watch: a #GtkExpressionWatch + * @value: an empty #GValue to be set + * + * Evaluates the watched expression and on success stores the result + * in @value. + * + * This is equivalent to calling gtk_expression_evaluate() with the + * expression and this pointer originally used to create @watch. + * + * Returns: %TRUE if the expression could be evaluated and @value was set + **/ +gboolean +gtk_expression_watch_evaluate (GtkExpressionWatch *watch, + GValue *value) +{ + g_return_val_if_fail (watch != NULL, FALSE); + + if (!gtk_expression_watch_is_watching (watch)) + return FALSE; + + return gtk_expression_evaluate (watch->expression, watch->this, value); +} + diff --git a/gtk/gtkexpression.h b/gtk/gtkexpression.h index 3df3788d1b..2b40f4d932 100644 --- a/gtk/gtkexpression.h +++ b/gtk/gtkexpression.h @@ -26,6 +26,16 @@ G_BEGIN_DECLS typedef struct _GtkExpression GtkExpression; +typedef struct _GtkExpressionWatch GtkExpressionWatch; + +/** + * GtkExpressionNotify: + * @user_data: data passed to gtk_expression_watch() + * + * Callback called by gtk_expression_watch() when the + * expression value changes. + */ +typedef void (* GtkExpressionNotify) (gpointer user_data); #define GTK_IS_EXPRESSION(expr) ((expr) != NULL) @@ -42,9 +52,27 @@ void gtk_expression_unref (GtkExpression GDK_AVAILABLE_IN_ALL GType gtk_expression_get_value_type (GtkExpression *self); GDK_AVAILABLE_IN_ALL +gboolean gtk_expression_is_static (GtkExpression *self); +GDK_AVAILABLE_IN_ALL gboolean gtk_expression_evaluate (GtkExpression *self, gpointer this_, GValue *value); +GDK_AVAILABLE_IN_ALL +GtkExpressionWatch * gtk_expression_watch (GtkExpression *self, + gpointer this_, + GtkExpressionNotify notify, + gpointer user_data, + GDestroyNotify user_destroy); + +GDK_AVAILABLE_IN_ALL +GtkExpressionWatch * gtk_expression_watch_ref (GtkExpressionWatch *watch); +GDK_AVAILABLE_IN_ALL +void gtk_expression_watch_unref (GtkExpressionWatch *watch); +GDK_AVAILABLE_IN_ALL +gboolean gtk_expression_watch_evaluate (GtkExpressionWatch *watch, + GValue *value); +GDK_AVAILABLE_IN_ALL +void gtk_expression_watch_unwatch (GtkExpressionWatch *watch); GDK_AVAILABLE_IN_ALL GtkExpression * gtk_property_expression_new (GType this_type, From 92c359ca0970bd901eee25a6368f6ce79099855b Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 21 Nov 2019 06:02:56 +0100 Subject: [PATCH 014/170] testsuite: Add expression tests --- testsuite/gtk/expression.c | 134 +++++++++++++++++++++++++++++++++++++ testsuite/gtk/meson.build | 1 + 2 files changed, 135 insertions(+) create mode 100644 testsuite/gtk/expression.c diff --git a/testsuite/gtk/expression.c b/testsuite/gtk/expression.c new file mode 100644 index 0000000000..8c6754fe63 --- /dev/null +++ b/testsuite/gtk/expression.c @@ -0,0 +1,134 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include + +#include + +static void +inc_counter (gpointer data) +{ + guint *counter = data; + + *counter += 1; +} + +static void +test_property (void) +{ + GValue value = G_VALUE_INIT; + GtkExpression *expr; + GtkExpressionWatch *watch; + GtkStringFilter *filter; + guint counter = 0; + + filter = GTK_STRING_FILTER (gtk_string_filter_new ()); + expr = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, NULL, "search"); + watch = gtk_expression_watch (expr, filter, inc_counter, &counter, NULL); + + g_assert (gtk_expression_evaluate (expr, filter, &value)); + g_assert_cmpstr (g_value_get_string (&value), ==, NULL); + g_value_unset (&value); + + gtk_string_filter_set_search (filter, "Hello World"); + g_assert_cmpint (counter, ==, 1); + g_assert (gtk_expression_evaluate (expr, filter , &value)); + g_assert_cmpstr (g_value_get_string (&value), ==, "Hello World"); + g_value_unset (&value); + + gtk_expression_unref (expr); + g_object_unref (filter); + gtk_expression_watch_unwatch (watch); +} + +static char * +print_filter_info (GtkStringFilter *filter, + const char *search, + gboolean ignore_case, + GtkStringFilterMatchMode match_mode) +{ + g_assert_cmpstr (search, ==, gtk_string_filter_get_search (filter)); + g_assert_cmpint (ignore_case, ==, gtk_string_filter_get_ignore_case (filter)); + g_assert_cmpint (match_mode, ==, gtk_string_filter_get_match_mode (filter)); + + return g_strdup ("OK"); +} + +static void +test_closure (void) +{ + GValue value = G_VALUE_INIT; + GtkExpression *expr, *pexpr[3]; + GtkExpressionWatch *watch; + GtkStringFilter *filter; + guint counter = 0; + + filter = GTK_STRING_FILTER (gtk_string_filter_new ()); + pexpr[0] = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, NULL, "search"); + pexpr[1] = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, NULL, "ignore-case"); + pexpr[2] = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, NULL, "match-mode"); + expr = gtk_cclosure_expression_new (G_TYPE_STRING, + NULL, + 3, + pexpr, + G_CALLBACK (print_filter_info), + NULL, + NULL); + watch = gtk_expression_watch (expr, filter, inc_counter, &counter, NULL); + + g_assert (gtk_expression_evaluate (expr, filter, &value)); + g_assert_cmpstr (g_value_get_string (&value), ==, "OK"); + g_value_unset (&value); + + gtk_string_filter_set_search (filter, "Hello World"); + g_assert_cmpint (counter, ==, 1); + g_assert (gtk_expression_evaluate (expr, filter , &value)); + g_assert_cmpstr (g_value_get_string (&value), ==, "OK"); + g_value_unset (&value); + + gtk_string_filter_set_ignore_case (filter, FALSE); + g_assert_cmpint (counter, ==, 2); + g_assert (gtk_expression_evaluate (expr, filter , &value)); + g_assert_cmpstr (g_value_get_string (&value), ==, "OK"); + g_value_unset (&value); + + gtk_string_filter_set_search (filter, "Hello"); + gtk_string_filter_set_ignore_case (filter, TRUE); + gtk_string_filter_set_match_mode (filter, GTK_STRING_FILTER_MATCH_MODE_EXACT); + g_assert_cmpint (counter, ==, 5); + g_assert (gtk_expression_evaluate (expr, filter , &value)); + g_assert_cmpstr (g_value_get_string (&value), ==, "OK"); + g_value_unset (&value); + + gtk_expression_unref (expr); + g_object_unref (filter); + gtk_expression_watch_unwatch (watch); +} + +int +main (int argc, char *argv[]) +{ + gtk_test_init (&argc, &argv, NULL); + setlocale (LC_ALL, "C"); + + g_test_add_func ("/expression/property", test_property); + g_test_add_func ("/expression/closure", test_closure); + + return g_test_run (); +} diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build index a1e5fcd0eb..3688b45dfa 100644 --- a/testsuite/gtk/meson.build +++ b/testsuite/gtk/meson.build @@ -26,6 +26,7 @@ tests = [ ['rbtree-crash', ['../../gtk/gtkrbtree.c'], ['-DGTK_COMPILATION', '-UG_ENABLE_DEBUG']], ['defaultvalue'], ['entry'], + ['expression'], ['filter'], ['filterlistmodel'], ['flattenlistmodel'], From b7efe4eb4fc99fd1ca5901f90d962478aba16e00 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sun, 24 Nov 2019 08:05:41 +0100 Subject: [PATCH 015/170] expression: Add gtk_expression_bind() Add a simple way to bind expressions to object properties. This is essentially the thing to replace g_object_bind_property(). --- gtk/gtkexpression.c | 119 ++++++++++++++++++++++++++++++++++++++++++++ gtk/gtkexpression.h | 4 ++ 2 files changed, 123 insertions(+) diff --git a/gtk/gtkexpression.c b/gtk/gtkexpression.c index 0647c1f8e7..dfa65e251e 100644 --- a/gtk/gtkexpression.c +++ b/gtk/gtkexpression.c @@ -1285,3 +1285,122 @@ gtk_expression_watch_evaluate (GtkExpressionWatch *watch, return gtk_expression_evaluate (watch->expression, watch->this, value); } +typedef struct { + GtkExpressionWatch *watch; + GtkExpression *expression; + GObject *object; + GParamSpec *pspec; +} GtkExpressionBind; + +static void +free_binds (gpointer data) +{ + GSList *l; + + for (l = data; l; l = l->next) + { + GtkExpressionBind *bind = l->data; + + bind->object = NULL; + gtk_expression_watch_unwatch (bind->watch); + } + g_slist_free (data); +} + +static void +gtk_expression_bind_free (gpointer data) +{ + GtkExpressionBind *bind = data; + + if (bind->object) + { + GSList *binds; + binds = g_object_steal_data (bind->object, "gtk-expression-binds"); + binds = g_slist_remove (binds, bind); + if (binds) + g_object_set_data_full (bind->object, "gtk-expression-binds", binds, free_binds); + } + gtk_expression_unref (bind->expression); + + g_slice_free (GtkExpressionBind, bind); +} + +static void +gtk_expression_bind_notify (gpointer data) +{ + GValue value = G_VALUE_INIT; + GtkExpressionBind *bind = data; + + if (!gtk_expression_evaluate (bind->expression, bind->object, &value)) + return; + + g_object_set_property (bind->object, bind->pspec->name, &value); + g_value_unset (&value); +} + +/** + * gtk_expression_bind: + * @self: (transfer full): a #GtkExpression + * @object: (transfer none) (type GObject): the object to bind + * @property: name of the property to bind to + * + * Bind @object's property named @property to @self. + * + * The value that @self evaluates to is set via g_object_set() on + * @object. This is repeated whenever @self changes to ensure that + * the object's property stays synchronized with @self. + * + * If @self's evaluation fails, @object's @property is not updated. + * You can ensure that this doesn't happen by using a fallback + * expression. + * + * Note that this function takes ownership of @self. If you want + * to keep it around, you should gtk_expression_ref() it beforehand. + * + * Returns: (transfer none): a #GtkExpressionWatch + **/ +GtkExpressionWatch * +gtk_expression_bind (GtkExpression *self, + gpointer object, + const char *property) +{ + GtkExpressionBind *bind; + GParamSpec *pspec; + GSList *binds; + + g_return_val_if_fail (GTK_IS_EXPRESSION (self), NULL); + g_return_val_if_fail (G_IS_OBJECT (object), NULL); + g_return_val_if_fail (property != NULL, NULL); + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), property); + if (G_UNLIKELY (pspec == NULL)) + { + g_critical ("%s: Class '%s' has no property named '%s'", + G_STRFUNC, G_OBJECT_TYPE_NAME (object), property); + return NULL; + } + if (G_UNLIKELY ((pspec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) != G_PARAM_WRITABLE)) + { + g_critical ("%s: property '%s' of class '%s' is not writable", + G_STRFUNC, pspec->name, G_OBJECT_TYPE_NAME (object)); + return NULL; + } + + bind = g_slice_new0 (GtkExpressionBind); + bind->expression = self; + bind->object = object; + bind->pspec = pspec; + bind->watch = gtk_expression_watch (self, + object, + gtk_expression_bind_notify, + bind, + gtk_expression_bind_free); + binds = g_object_steal_data (object, "gtk-expression-binds"); + binds = g_slist_prepend (binds, bind); + g_object_set_data_full (object, "gtk-expression-binds", binds, free_binds); + + gtk_expression_unref (self); + + gtk_expression_bind_notify (bind); + + return bind->watch; +} diff --git a/gtk/gtkexpression.h b/gtk/gtkexpression.h index 2b40f4d932..0727580c45 100644 --- a/gtk/gtkexpression.h +++ b/gtk/gtkexpression.h @@ -63,6 +63,10 @@ GtkExpressionWatch * gtk_expression_watch (GtkExpression GtkExpressionNotify notify, gpointer user_data, GDestroyNotify user_destroy); +GDK_AVAILABLE_IN_ALL +GtkExpressionWatch * gtk_expression_bind (GtkExpression *self, + gpointer object, + const char * property); GDK_AVAILABLE_IN_ALL GtkExpressionWatch * gtk_expression_watch_ref (GtkExpressionWatch *watch); From ad60efb5d79002bda0202b66d60bd6aaec64df95 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 26 Nov 2019 03:57:40 +0100 Subject: [PATCH 016/170] expression: Invalidate bindings before destroying them Use a weak ref to invalidate bindings. Make sure that this happens before creating any watches, so that notifies from the watched expression about changes will not trigger set_property() calls during dispose()/finalize(). Invalidating also ensures that the watches aren't removed, which can trigger warnings if the watches are watching the object itself, and the weak refs cannot be removed anymore. --- gtk/gtkexpression.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/gtk/gtkexpression.c b/gtk/gtkexpression.c index dfa65e251e..c698f8038e 100644 --- a/gtk/gtkexpression.c +++ b/gtk/gtkexpression.c @@ -1292,6 +1292,21 @@ typedef struct { GParamSpec *pspec; } GtkExpressionBind; +static void +invalidate_binds (gpointer unused, + GObject *object) +{ + GSList *l, *binds; + + binds = g_object_get_data (object, "gtk-expression-binds"); + for (l = binds; l; l = l->next) + { + GtkExpressionBind *bind = l->data; + + bind->object = NULL; + } +} + static void free_binds (gpointer data) { @@ -1319,6 +1334,8 @@ gtk_expression_bind_free (gpointer data) binds = g_slist_remove (binds, bind); if (binds) g_object_set_data_full (bind->object, "gtk-expression-binds", binds, free_binds); + else + g_object_weak_unref (bind->object, invalidate_binds, NULL); } gtk_expression_unref (bind->expression); @@ -1331,6 +1348,9 @@ gtk_expression_bind_notify (gpointer data) GValue value = G_VALUE_INIT; GtkExpressionBind *bind = data; + if (bind->object == NULL) + return; + if (!gtk_expression_evaluate (bind->expression, bind->object, &value)) return; @@ -1386,6 +1406,9 @@ gtk_expression_bind (GtkExpression *self, } bind = g_slice_new0 (GtkExpressionBind); + binds = g_object_steal_data (object, "gtk-expression-binds"); + if (binds == NULL) + g_object_weak_ref (object, invalidate_binds, NULL); bind->expression = self; bind->object = object; bind->pspec = pspec; @@ -1394,7 +1417,6 @@ gtk_expression_bind (GtkExpression *self, gtk_expression_bind_notify, bind, gtk_expression_bind_free); - binds = g_object_steal_data (object, "gtk-expression-binds"); binds = g_slist_prepend (binds, bind); g_object_set_data_full (object, "gtk-expression-binds", binds, free_binds); From 115923b2d9f823c7783709d92dc91f48902eea4c Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 28 Nov 2019 14:40:34 -0500 Subject: [PATCH 017/170] More expression tests Test type mismatches, and the this pointer during evaluation. --- testsuite/gtk/expression.c | 302 ++++++++++++++++++++++++++++++++++++- 1 file changed, 301 insertions(+), 1 deletion(-) diff --git a/testsuite/gtk/expression.c b/testsuite/gtk/expression.c index 8c6754fe63..663e0fcbaf 100644 --- a/testsuite/gtk/expression.c +++ b/testsuite/gtk/expression.c @@ -48,13 +48,17 @@ test_property (void) gtk_string_filter_set_search (filter, "Hello World"); g_assert_cmpint (counter, ==, 1); + counter = 0; + g_assert (gtk_expression_evaluate (expr, filter , &value)); g_assert_cmpstr (g_value_get_string (&value), ==, "Hello World"); g_value_unset (&value); + gtk_expression_watch_unwatch (watch); + g_assert_cmpint (counter, ==, 0); + gtk_expression_unref (expr); g_object_unref (filter); - gtk_expression_watch_unwatch (watch); } static char * @@ -116,9 +120,298 @@ test_closure (void) g_assert_cmpstr (g_value_get_string (&value), ==, "OK"); g_value_unset (&value); + gtk_expression_watch_unwatch (watch); + g_assert_cmpint (counter, ==, 5); + gtk_expression_unref (expr); g_object_unref (filter); +} + +static void +test_constant (void) +{ + GtkExpression *expr; + GValue value = G_VALUE_INIT; + gboolean res; + + expr = gtk_constant_expression_new (G_TYPE_INT, 22); + g_assert_cmpint (gtk_expression_get_value_type (expr), ==, G_TYPE_INT); + g_assert_true (gtk_expression_is_static (expr)); + + res = gtk_expression_evaluate (expr, NULL, &value); + g_assert_true (res); + g_assert_cmpint (g_value_get_int (&value), ==, 22); + + gtk_expression_unref (expr); +} + +/* Test that object expressions fail to evaluate when + * the object is gone. + */ +static void +test_object (void) +{ + GtkExpression *expr; + GObject *obj; + GValue value = G_VALUE_INIT; + gboolean res; + + obj = G_OBJECT (gtk_string_filter_new ()); + + expr = gtk_object_expression_new (obj); + g_assert_true (!gtk_expression_is_static (expr)); + g_assert_cmpint (gtk_expression_get_value_type (expr), ==, GTK_TYPE_STRING_FILTER); + + res = gtk_expression_evaluate (expr, NULL, &value); + g_assert_true (res); + g_assert_true (g_value_get_object (&value) == obj); + g_value_unset (&value); + + g_clear_object (&obj); + res = gtk_expression_evaluate (expr, NULL, &value); + g_assert_false (res); + + gtk_expression_unref (expr); +} + +/* Some basic tests that nested expressions work; in particular test + * that watching works when things change deeper in the expression tree + * + * The setup we use is GtkFilterListModel -> GtkFilter -> "search" property, + * which gives us an expression tree like + * + * GtkPropertyExpression "search" + * -> GtkPropertyExpression "filter" + * -> GtkObjectExpression listmodel + * + * We test setting both the search property and the filter property. + */ +static void +test_nested (void) +{ + GtkExpression *list_expr; + GtkExpression *filter_expr; + GtkExpression *expr; + GtkFilter *filter; + GListModel *list; + GtkFilterListModel *filtered; + GValue value = G_VALUE_INIT; + gboolean res; + GtkExpressionWatch *watch; + guint counter = 0; + + filter = gtk_string_filter_new (); + gtk_string_filter_set_search (GTK_STRING_FILTER (filter), "word"); + list = G_LIST_MODEL (g_list_store_new (G_TYPE_OBJECT)); + filtered = gtk_filter_list_model_new (list, filter); + + list_expr = gtk_object_expression_new (G_OBJECT (filtered)); + filter_expr = gtk_property_expression_new (GTK_TYPE_FILTER_LIST_MODEL, list_expr, "filter"); + expr = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, filter_expr, "search"); + + g_assert_true (!gtk_expression_is_static (expr)); + g_assert_cmpint (gtk_expression_get_value_type (expr), ==, G_TYPE_STRING); + + res = gtk_expression_evaluate (expr, NULL, &value); + g_assert_true (res); + g_assert_cmpstr (g_value_get_string (&value), ==, "word"); + g_value_unset (&value); + + watch = gtk_expression_watch (expr, NULL, inc_counter, &counter, NULL); + gtk_string_filter_set_search (GTK_STRING_FILTER (filter), "salad"); + g_assert_cmpint (counter, ==, 1); + counter = 0; + + res = gtk_expression_evaluate (expr, NULL, &value); + g_assert_true (res); + g_assert_cmpstr (g_value_get_string (&value), ==, "salad"); + g_value_unset (&value); + + gtk_filter_list_model_set_filter (filtered, filter); + g_assert_cmpint (counter, ==, 0); + + g_clear_object (&filter); + filter = gtk_string_filter_new (); + gtk_string_filter_set_search (GTK_STRING_FILTER (filter), "salad"); + gtk_filter_list_model_set_filter (filtered, filter); + g_assert_cmpint (counter, ==, 1); + counter = 0; + + res = gtk_expression_evaluate (expr, NULL, &value); + g_assert_true (res); + g_assert_cmpstr (g_value_get_string (&value), ==, "salad"); + g_value_unset (&value); + + gtk_string_filter_set_search (GTK_STRING_FILTER (filter), "bar"); + g_assert_cmpint (counter, ==, 1); + counter = 0; + + res = gtk_expression_evaluate (expr, NULL, &value); + g_assert_true (res); + g_assert_cmpstr (g_value_get_string (&value), ==, "bar"); + g_value_unset (&value); + + gtk_filter_list_model_set_filter (filtered, NULL); + g_assert_cmpint (counter, ==, 1); + counter = 0; + + res = gtk_expression_evaluate (expr, NULL, &value); + g_assert_false (res); + gtk_expression_watch_unwatch (watch); + g_assert_cmpint (counter, ==, 0); + + g_object_unref (filtered); + g_object_unref (list); + g_object_unref (filter); + gtk_expression_unref (expr); +} + +/* This test uses the same setup as the last test, but + * passes the filter as the "this" object when creating + * the watch. + * + * So when we set a new filter and the old one gets desroyed, + * the watch should invalidate itself because its this object + * is gone. + */ +static void +test_nested_this_destroyed (void) +{ + GtkExpression *list_expr; + GtkExpression *filter_expr; + GtkExpression *expr; + GtkFilter *filter; + GListModel *list; + GtkFilterListModel *filtered; + GValue value = G_VALUE_INIT; + gboolean res; + GtkExpressionWatch *watch; + guint counter = 0; + + filter = gtk_string_filter_new (); + gtk_string_filter_set_search (GTK_STRING_FILTER (filter), "word"); + list = G_LIST_MODEL (g_list_store_new (G_TYPE_OBJECT)); + filtered = gtk_filter_list_model_new (list, filter); + + list_expr = gtk_object_expression_new (G_OBJECT (filtered)); + filter_expr = gtk_property_expression_new (GTK_TYPE_FILTER_LIST_MODEL, list_expr, "filter"); + expr = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, filter_expr, "search"); + + watch = gtk_expression_watch (expr, filter, inc_counter, &counter, NULL); + gtk_expression_watch_ref (watch); + res = gtk_expression_watch_evaluate (watch, &value); + g_assert_true (res); + g_assert_cmpstr (g_value_get_string (&value), ==, "word"); + g_value_unset (&value); + + g_clear_object (&filter); + g_assert_cmpint (counter, ==, 0); + + filter = gtk_string_filter_new (); + gtk_string_filter_set_search (GTK_STRING_FILTER (filter), "salad"); + gtk_filter_list_model_set_filter (filtered, filter); + g_assert_cmpint (counter, ==, 1); + counter = 0; + + res = gtk_expression_watch_evaluate (watch, &value); + g_assert_false (res); + + gtk_string_filter_set_search (GTK_STRING_FILTER (filter), "bar"); + g_assert_cmpint (counter, ==, 0); + + gtk_filter_list_model_set_filter (filtered, NULL); + g_assert_cmpint (counter, ==, 0); + + res = gtk_expression_watch_evaluate (watch, &value); + g_assert_false (res); + g_assert_false (G_IS_VALUE (&value)); + + /* We unwatch on purpose here to make sure it doesn't do bad things. */ + gtk_expression_watch_unwatch (watch); + gtk_expression_watch_unref (watch); + g_assert_cmpint (counter, ==, 0); + + g_object_unref (filtered); + g_object_unref (list); + g_object_unref (filter); + gtk_expression_unref (expr); +} + +/* Test that property expressions fail to evaluate if the + * expression evaluates to an object of the wrong type + */ +static void +test_type_mismatch (void) +{ + GtkFilter *filter; + GtkExpression *expr; + GValue value = G_VALUE_INIT; + gboolean res; + + filter = gtk_any_filter_new (); + + expr = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, gtk_constant_expression_new (GTK_TYPE_ANY_FILTER, filter), "search"); + + res = gtk_expression_evaluate (expr, NULL, &value); + g_assert_false (res); + g_assert_false (G_IS_VALUE (&value)); + + gtk_expression_unref (expr); + g_object_unref (filter); +} + +/* Some basic tests around 'this' */ +static void +test_this (void) +{ + GtkFilter *filter; + GtkFilter *filter2; + GtkExpression *expr; + GValue value = G_VALUE_INIT; + gboolean res; + + expr = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, NULL, "search"); + + filter = gtk_string_filter_new (); + gtk_string_filter_set_search (GTK_STRING_FILTER (filter), "word"); + + filter2 = gtk_string_filter_new (); + gtk_string_filter_set_search (GTK_STRING_FILTER (filter2), "sausage"); + + res = gtk_expression_evaluate (expr, filter, &value); + g_assert_true (res); + g_assert_cmpstr (g_value_get_string (&value), ==, "word"); + g_value_unset (&value); + + res = gtk_expression_evaluate (expr, filter2, &value); + g_assert_true (res); + g_assert_cmpstr (g_value_get_string (&value), ==, "sausage"); + g_value_unset (&value); + + gtk_expression_unref (expr); + g_object_unref (filter2); + g_object_unref (filter); +} + +/* Check that even for static expressions, watches can be created + * and destroying the "this" argument does invalidate the + * expression. + */ +static void +test_constant_watch_this_destroyed (void) +{ + GtkExpression *expr; + GObject *this; + guint counter = 0; + + this = g_object_new (G_TYPE_OBJECT, NULL); + expr = gtk_constant_expression_new (G_TYPE_INT, 42); + gtk_expression_watch (expr, this, inc_counter, &counter, NULL); + g_assert_cmpint (counter, ==, 0); + + g_clear_object (&this); + g_assert_cmpint (counter, ==, 1); } int @@ -129,6 +422,13 @@ main (int argc, char *argv[]) g_test_add_func ("/expression/property", test_property); g_test_add_func ("/expression/closure", test_closure); + g_test_add_func ("/expression/constant", test_constant); + g_test_add_func ("/expression/constant-watch-this-destroyed", test_constant_watch_this_destroyed); + g_test_add_func ("/expression/object", test_object); + g_test_add_func ("/expression/nested", test_nested); + g_test_add_func ("/expression/nested-this-destroyed", test_nested_this_destroyed); + g_test_add_func ("/expression/type-mismatch", test_type_mismatch); + g_test_add_func ("/expression/this", test_this); return g_test_run (); } From cb15ec0257830b267c1027b0a10e21fdfc20d979 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 12 Dec 2019 05:16:31 +0100 Subject: [PATCH 018/170] Add GtkOrdering This is an enum that we're gonna use soon and it's worth introducing as a separate commit. The intention is to have meaningful names for return values in comparison functions. --- docs/reference/gtk/gtk4-sections.txt | 2 ++ gtk/gtkenums.h | 33 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 5ead3a136c..df839a3ec4 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -4638,6 +4638,8 @@ GtkDeleteType GtkDirectionType GtkJustification GtkMovementStep +GtkOrdering +gtk_ordering_from_cmpfunc GtkOrientation GtkPackType GtkPositionType diff --git a/gtk/gtkenums.h b/gtk/gtkenums.h index b761944d35..af36f1031a 100644 --- a/gtk/gtkenums.h +++ b/gtk/gtkenums.h @@ -550,6 +550,39 @@ typedef enum GTK_NUMBER_UP_LAYOUT_BOTTOM_TO_TOP_RIGHT_TO_LEFT /*< nick=btrl >*/ } GtkNumberUpLayout; +/** + * GtkOrdering: + * @GTK_ORDERING_SMALLER: the first value is smaller than the second + * @GTK_ORDERING_EQUAL: the two values are equal + * @GTK_ORDERING_LARGER: the first value is larger than the second + * + * Describes the way two values can be compared. + * + * These values can be used with a #GCompareFunc. However, a + * #GCompareFunc is allowed to return any integer values. + * For converting such a value to a #GtkOrdering, use + * gtk_ordering_from_cmpfunc(). + */ +typedef enum { + GTK_ORDERING_SMALLER = -1, + GTK_ORDERING_EQUAL = 0, + GTK_ORDERING_LARGER = 1 +} GtkOrdering; + +/** + * gtk_ordering_from_cmpfunc: + * @cmpfunc_result: Result of a comparison function + * + * Converts the result of a #GCompareFunc like strcmp() to a #GtkOrdering. + * + * Returns: the corresponding #GtkOrdering + **/ +static inline GtkOrdering +gtk_ordering_from_cmpfunc (int cmpfunc_result) +{ + return (GtkOrdering) ((cmpfunc_result > 0) - (cmpfunc_result < 0)); +} + /** * GtkPageOrientation: * @GTK_PAGE_ORIENTATION_PORTRAIT: Portrait mode. From b2b847f3657eeac02f18797a6757cb9325d9bc19 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 2 Dec 2019 23:43:14 -0500 Subject: [PATCH 019/170] Add GtkSorter This is a helper object for sorting, similar to GtkFilter. --- docs/reference/gtk/gtk4-docs.xml | 3 + docs/reference/gtk/gtk4-sections.txt | 20 +++ docs/reference/gtk/gtk4.types.in | 1 + gtk/gtk.h | 1 + gtk/gtksorter.c | 207 +++++++++++++++++++++++++++ gtk/gtksorter.h | 124 ++++++++++++++++ gtk/meson.build | 2 + 7 files changed, 358 insertions(+) create mode 100644 gtk/gtksorter.c create mode 100644 gtk/gtksorter.h diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index e9b4ab8c06..18451cfae2 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -60,6 +60,9 @@ +
+ +
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index df839a3ec4..614527aa0f 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -2421,6 +2421,26 @@ GTK_SLICE_LIST_MODEL_GET_CLASS gtk_slice_list_model_get_type
+
+gtksorter +GtkSorter +GtkSorter +GtkSorterOrder +GtkSorterChange +gtk_sorter_compare +gtk_sorter_get_order +gtk_sorter_changed + +GTK_SORTER +GTK_IS_SORTER +GTK_TYPE_SORTER +GTK_SORTER_CLASS +GTK_IS_SORTER_CLASS +GTK_SORTER_GET_CLASS + +gtk_sorter_get_type +
+
gtksortlistmodel GtkSortListModel diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in index 0ef0a863f5..3b52d11d51 100644 --- a/docs/reference/gtk/gtk4.types.in +++ b/docs/reference/gtk/gtk4.types.in @@ -185,6 +185,7 @@ gtk_size_group_get_type gtk_slice_list_model_get_type gtk_snapshot_get_type gtk_sort_list_model_get_type +gtk_sorter_get_type gtk_spin_button_get_type gtk_spinner_get_type gtk_stack_get_type diff --git a/gtk/gtk.h b/gtk/gtk.h index 3b03bbbc44..4e0a62bf5b 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -214,6 +214,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtksorter.c b/gtk/gtksorter.c new file mode 100644 index 0000000000..22ef6c4644 --- /dev/null +++ b/gtk/gtksorter.c @@ -0,0 +1,207 @@ +/* + * Copyright © 2019 Matthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Matthias Clasen + */ + +#include "config.h" + +#include "gtksorter.h" + +#include "gtkintl.h" +#include "gtktypebuiltins.h" + +/** + * SECTION:gtksorter + * @title: GtkSorter + * @Short_description: Sorting items + * @See_also: #GtkSortListModel + * + * #GtkSorter is the way to describe sorting criteria. + * Its primary user is #GtkSortListModel. + * + * The model will use a sorter to determine the order in which its items should appear + * by calling gtk_sorter_compare() for pairs of items. + * + * Sorters may change their sorting behavior through their lifetime. In that case, + * they call gtk_sorter_changed(), which will emit the #GtkSorter::changed signal to + * notify that the sort order is no longer valid and should be updated by calling + * gtk_sorter_compare() again. + * + * GTK provides various pre-made sorter implementations for common sorting operations. + * #GtkColumnView has built-in support for sorting lists via the #GtkColumnViewColumn:sorter + * property, where the user can change the sorting by clicking on list headers. + * + * Of course, in particular for large lists, it is also possible to subclass #GtkSorter + * and provide one's own sorter. + */ + +enum { + CHANGED, + LAST_SIGNAL +}; + +G_DEFINE_TYPE (GtkSorter, gtk_sorter, G_TYPE_OBJECT) + +static guint signals[LAST_SIGNAL] = { 0 }; + +static GtkOrdering +gtk_sorter_default_compare (GtkSorter *self, + gpointer item1, + gpointer item2) +{ + g_critical ("Sorter of type '%s' does not implement GtkSorter::compare", G_OBJECT_TYPE_NAME (self)); + + return GTK_ORDERING_EQUAL; +} + +static GtkSorterOrder +gtk_sorter_default_get_order (GtkSorter *self) +{ + return GTK_SORTER_ORDER_PARTIAL; +} + +static void +gtk_sorter_class_init (GtkSorterClass *class) +{ + class->compare = gtk_sorter_default_compare; + class->get_order = gtk_sorter_default_get_order; + + /** + * GtkSorter::changed: + * @self: The #GtkSorter + * @change: how the sorter changed + * + * This signal is emitted whenever the sorter changed. Users of the sorter + * should then update the sort order again via gtk_sorter_compare(). + * + * #GtkSortListModel handles this signal automatically. + * + * Depending on the @change parameter, it may be possible to update + * the sort order without a full resorting. Refer to the #GtkSorterChange + * documentation for details. + */ + signals[CHANGED] = + g_signal_new (I_("changed"), + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__ENUM, + G_TYPE_NONE, 1, + GTK_TYPE_SORTER_CHANGE); + g_signal_set_va_marshaller (signals[CHANGED], + G_TYPE_FROM_CLASS (class), + g_cclosure_marshal_VOID__ENUMv); +} + +static void +gtk_sorter_init (GtkSorter *self) +{ +} + +/** + * gtk_sorter_compare: + * @self: a #GtkSorter + * @item1: (type GObject) (transfer none): first item to compare + * @item2: (type GObject) (transfer none): second item to compare + * + * Compares two given items according to the sort order implemented + * by the sorter. + * + * Sorters implement a partial order: + * * It is reflexive, ie a = a + * * It is antisymmetric, ie if a < b and b < a, then a = b + * * It is transitive, ie given any 3 items with a ≤ b and b ≤ c, + * then a ≤ c + * + * The sorter may signal it conforms to additional constraints + * via the return value of gtk_sorter_get_order(). + * + * Returns: %GTK_ORDERING_EQUAL if @item1 == @item2, + * %GTK_ORDERING_SMALLER if @item1 < @item2, + * %GTK_ORDERING_LARGER if @item1 > @item2 + */ +GtkOrdering +gtk_sorter_compare (GtkSorter *self, + gpointer item1, + gpointer item2) +{ + GtkOrdering result; + + g_return_val_if_fail (GTK_IS_SORTER (self), GTK_ORDERING_EQUAL); + g_return_val_if_fail (item1 && item2, GTK_ORDERING_EQUAL); + + if (item1 == item2) + return GTK_ORDERING_EQUAL; + + result = GTK_SORTER_GET_CLASS (self)->compare (self, item1, item2); + +#ifdef G_ENABLE_DEBUG + if (result < -1 || result > 1) + { + g_critical ("A sorter of type \"%s\" returned %d, which is not a valid GtkOrdering result.\n" + "Did you forget to call gtk_ordering_from_cmpfunc()?", + G_OBJECT_TYPE_NAME (self), (int) result); + } +#endif + + return result; +} + +/** + * gtk_sorter_get_order: + * @self: a #GtkSorter + * + * Gets the order that @self conforms to. See #GtkSorterOrder for details + * of the possible return values. + * + * This function is intended to allow optimizations. + * + * Returns: The order + **/ +GtkSorterOrder +gtk_sorter_get_order (GtkSorter *self) +{ + g_return_val_if_fail (GTK_IS_SORTER (self), GTK_SORTER_ORDER_PARTIAL); + + return GTK_SORTER_GET_CLASS (self)->get_order (self); +} + +/** + * gtk_sorter_changed: + * @self: a #GtkSorter + * @change: How the sorter changed + * + * Emits the #GtkSorter::changed signal to notify all users of the sorter + * that it has changed. Users of the sorter should then update the sort + * order via gtk_sorter_compare(). + * + * Depending on the @change parameter, it may be possible to update + * the sort order without a full resorting. Refer to the #GtkSorterChange + * documentation for details. + * + * This function is intended for implementors of #GtkSorter subclasses and + * should not be called from other functions. + */ +void +gtk_sorter_changed (GtkSorter *self, + GtkSorterChange change) +{ + g_return_if_fail (GTK_IS_SORTER (self)); + + g_signal_emit (self, signals[CHANGED], 0, change); +} diff --git a/gtk/gtksorter.h b/gtk/gtksorter.h new file mode 100644 index 0000000000..5a997a7c84 --- /dev/null +++ b/gtk/gtksorter.h @@ -0,0 +1,124 @@ +/* + * Copyright © 2019 Matthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Matthias Clasen + */ + +#ifndef __GTK_SORTER_H__ +#define __GTK_SORTER_H__ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +/** + * GtkSorterOrder: + * @GTK_SORTER_ORDER_PARTIAL: A partial order. And #GtkOrdering is possible. + * @GTK_SORTER_ORDER_INVALID: An invalid order. gtk_sorter_compare() will + * always return %GTK_ORDERING_INVALID if both items are unequal. + * @GTK_SORTER_ORDER_NONE: No order, all elements are considered equal. + * gtk_sorter_compare() will only return %GTK_ORDERING_EQUAL or + * %GTK_ORDERING_INVALID. + * @GTK_SORTER_ORDER_TOTAL: A total order. gtk_sorter_compare() will only + * return %GTK_ORDERING_EQUAL if an item is compared with itself. Two + * different items will never cause this value to be returned. + * + * Describes the type of order that a #GtkSorter may describe. + */ +typedef enum { + GTK_SORTER_ORDER_PARTIAL, + GTK_SORTER_ORDER_NONE, + GTK_SORTER_ORDER_TOTAL +} GtkSorterOrder; + +/** + * GtkSorterChange: + * @GTK_SORTER_CHANGE_DIFFERENT: The sorter change cannot be described + * by any of the other enumeration values + * @GTK_SORTER_CHANGE_INVERTED: The sort order was inverted. Comparisons + * that returned %GTK_ORDERING_SMALLER now return %GTK_ORDERING_LARGER + * and vice versa. Other comparisons return the same values as before. + * @GTK_SORTER_CHANGE_LESS_STRICT: The sorter is less strict: Comparisons + * may now return %GTK_ORDERING_EQUAL that did not do so before. + * @GTK_SORTER_CHANGE_MORE_STRICT: The sorter is more strict: Comparisons + * that did return %GTK_ORDERING_EQUAL may not do so anymore. + * + * Describes changes in a sorter in more detail and allows users + * to optimize resorting. + */ +typedef enum { + GTK_SORTER_CHANGE_DIFFERENT, + GTK_SORTER_CHANGE_INVERTED, + GTK_SORTER_CHANGE_LESS_STRICT, + GTK_SORTER_CHANGE_MORE_STRICT +} GtkSorterChange; + +#define GTK_TYPE_SORTER (gtk_sorter_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_DERIVABLE_TYPE (GtkSorter, gtk_sorter, GTK, SORTER, GObject) + +/** + * GtkSorterClass + * @compare: Compare two items. See gtk_sorter_compare() for details. + * @get_order: Get the #GtkSorderOrder that applies to the current sorter. + * If unimplemented, it returns %GTK_SORTER_ORDER_PARTIAL. + * + * The virtual table for #GtkSorter. + */ +struct _GtkSorterClass +{ + GObjectClass parent_class; + + GtkOrdering (* compare) (GtkSorter *self, + gpointer item1, + gpointer item2); + + /* optional */ + GtkSorterOrder (* get_order) (GtkSorter *self); + + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); + void (*_gtk_reserved3) (void); + void (*_gtk_reserved4) (void); + void (*_gtk_reserved5) (void); + void (*_gtk_reserved6) (void); + void (*_gtk_reserved7) (void); + void (*_gtk_reserved8) (void); +}; + +GDK_AVAILABLE_IN_ALL +GtkOrdering gtk_sorter_compare (GtkSorter *self, + gpointer item1, + gpointer item2); +GDK_AVAILABLE_IN_ALL +GtkSorterOrder gtk_sorter_get_order (GtkSorter *self); + +/* for sorter implementations */ +GDK_AVAILABLE_IN_ALL +void gtk_sorter_changed (GtkSorter *self, + GtkSorterChange change); + +G_END_DECLS + +#endif /* __GTK_SORTER_H__ */ + diff --git a/gtk/meson.build b/gtk/meson.build index 8b073c58e2..c8157ef2b5 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -346,6 +346,7 @@ gtk_public_sources = files([ 'gtksizerequest.c', 'gtkslicelistmodel.c', 'gtksnapshot.c', + 'gtksorter.c', 'gtksortlistmodel.c', 'gtkspinbutton.c', 'gtkspinner.c', @@ -592,6 +593,7 @@ gtk_public_headers = files([ 'gtksizerequest.h', 'gtkslicelistmodel.h', 'gtksnapshot.h', + 'gtksorter.h', 'gtksortlistmodel.h', 'gtkspinbutton.h', 'gtkspinner.h', From e74a9d09e65f15fdc569db0cf34eefd89aa2d3a5 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 2 Dec 2019 23:43:14 -0500 Subject: [PATCH 020/170] Add GtkCustomSorter This is a GtkSorter implementation which uses a GCompareDataFunc. --- docs/reference/gtk/gtk4-docs.xml | 1 + docs/reference/gtk/gtk4-sections.txt | 15 +++ docs/reference/gtk/gtk4.types.in | 1 + gtk/gtk.h | 1 + gtk/gtkcustomsorter.c | 159 +++++++++++++++++++++++++++ gtk/gtkcustomsorter.h | 49 +++++++++ gtk/meson.build | 2 + 7 files changed, 228 insertions(+) create mode 100644 gtk/gtkcustomsorter.c create mode 100644 gtk/gtkcustomsorter.h diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index 18451cfae2..dc6024ed4c 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -62,6 +62,7 @@
+
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 614527aa0f..d13c697ed6 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -2441,6 +2441,21 @@ GTK_SORTER_GET_CLASS gtk_sorter_get_type
+
+gtkcustomsorter +GtkCustomSorter +GtkCustomSorter +gtk_custom_sorter_new + +GTK_CUSTOM_SORTER +GTK_IS_CUSTOM_SORTER +GTK_TYPE_CUSTOM_SORTER +GTK_IS_CUSTOM_SORTER_CLASS +GTK_CUSTOM_SORTER_GET_CLASS + +gtk_custom_sorter_get_type +
+
gtksortlistmodel GtkSortListModel diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in index 3b52d11d51..8ddec9caf5 100644 --- a/docs/reference/gtk/gtk4.types.in +++ b/docs/reference/gtk/gtk4.types.in @@ -55,6 +55,7 @@ gtk_constraint_layout_get_type gtk_constraint_target_get_type gtk_css_provider_get_type gtk_custom_filter_get_type +gtk_custom_sorter_get_type gtk_dialog_get_type gtk_directory_list_get_type gtk_drag_icon_get_type diff --git a/gtk/gtk.h b/gtk/gtk.h index 4e0a62bf5b..bcd673a2e0 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -84,6 +84,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkcustomsorter.c b/gtk/gtkcustomsorter.c new file mode 100644 index 0000000000..6359fd68b0 --- /dev/null +++ b/gtk/gtkcustomsorter.c @@ -0,0 +1,159 @@ +/* + * Copyright © 2019 Matthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Matthias Clasen + */ + +#include "config.h" + +#include "gtkcustomsorter.h" + +#include "gtkintl.h" +#include "gtktypebuiltins.h" + +/** + * SECTION:gtkcustomsorter + * @Title: GtkCustomSorter + * @Short_description: Sorting with a callback + * + * GtkCustomSorter is a #GtkSorter implementation that sorts + * via a traditional #GCompareDataFunc callback. + */ +struct _GtkCustomSorter +{ + GtkSorter parent_instance; + + GCompareDataFunc sort_func; + gpointer user_data; + GDestroyNotify user_destroy; +}; + +G_DEFINE_TYPE (GtkCustomSorter, gtk_custom_sorter, GTK_TYPE_SORTER) + +static GtkOrdering +gtk_custom_sorter_compare (GtkSorter *sorter, + gpointer item1, + gpointer item2) +{ + GtkCustomSorter *self = GTK_CUSTOM_SORTER (sorter); + + if (!self->sort_func) + return GTK_ORDERING_EQUAL; + + return gtk_ordering_from_cmpfunc (self->sort_func (item1, item2, self->user_data)); +} + +static GtkSorterOrder +gtk_custom_sorter_get_order (GtkSorter *sorter) +{ + GtkCustomSorter *self = GTK_CUSTOM_SORTER (sorter); + + if (!self->sort_func) + return GTK_SORTER_ORDER_NONE; + + return GTK_SORTER_ORDER_PARTIAL; +} + +static void +gtk_custom_sorter_dispose (GObject *object) +{ + GtkCustomSorter *self = GTK_CUSTOM_SORTER (object); + + if (self->user_destroy) + self->user_destroy (self->user_data); + + self->sort_func = NULL; + self->user_destroy = NULL; + self->user_data = NULL; + + G_OBJECT_CLASS (gtk_custom_sorter_parent_class)->dispose (object); +} + +static void +gtk_custom_sorter_class_init (GtkCustomSorterClass *class) +{ + GtkSorterClass *sorter_class = GTK_SORTER_CLASS (class); + GObjectClass *object_class = G_OBJECT_CLASS (class); + + sorter_class->compare = gtk_custom_sorter_compare; + sorter_class->get_order = gtk_custom_sorter_get_order; + + object_class->dispose = gtk_custom_sorter_dispose; +} + +static void +gtk_custom_sorter_init (GtkCustomSorter *self) +{ +} + +/** + * gtk_custom_sorter_new: + * @sort_func: the #GCompareDataFunc to use for sorting + * @user_data: (nullable): user data to pass to @sort_func + * @user_destroy: (nullable): destroy notify for @user_data + * + * Creates a new #GtkSorter that works by calling + * @sort_func to compare items. + * + * Returns: a new #GTkSorter + */ +GtkSorter * +gtk_custom_sorter_new (GCompareDataFunc sort_func, + gpointer user_data, + GDestroyNotify user_destroy) +{ + GtkCustomSorter *sorter; + + sorter = g_object_new (GTK_TYPE_CUSTOM_SORTER, NULL); + + gtk_custom_sorter_set_sort_func (sorter, sort_func, user_data, user_destroy); + + return GTK_SORTER (sorter); +} + +/** + * gtk_custom_sorter_set_sort_func: + * @self: a #GtkCustomSorter + * @sort_func: (nullable): function to sort items + * @user_data: (nullable): user data to pass to @match_func + * @user_destroy: destory notify + * + * Sets (or unsets) the function used for sorting items. + * + * If the sort func changes its sorting behavior, + * gtk_sorter_changed() needs to be called. + * + * If a previous function was set, its @user_destroy will be + * called now. + **/ +void +gtk_custom_sorter_set_sort_func (GtkCustomSorter *self, + GCompareDataFunc sort_func, + gpointer user_data, + GDestroyNotify user_destroy) +{ + g_return_if_fail (GTK_IS_CUSTOM_SORTER (self)); + g_return_if_fail (sort_func || (user_data == NULL && !user_destroy)); + + if (self->user_destroy) + self->user_destroy (self->user_data); + + self->sort_func = sort_func; + self->user_data = user_data; + self->user_destroy = user_destroy; + + gtk_sorter_changed (GTK_SORTER (self), GTK_SORTER_CHANGE_DIFFERENT); +} diff --git a/gtk/gtkcustomsorter.h b/gtk/gtkcustomsorter.h new file mode 100644 index 0000000000..50cf090637 --- /dev/null +++ b/gtk/gtkcustomsorter.h @@ -0,0 +1,49 @@ +/* + * Copyright © 2019 Matthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Matthias Clasen + */ + +#ifndef __GTK_CUSTOM_SORTER_H__ +#define __GTK_CUSTOM_SORTER_H__ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_CUSTOM_SORTER (gtk_custom_sorter_get_type ()) +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkCustomSorter, gtk_custom_sorter, GTK, CUSTOM_SORTER, GtkSorter) + +GDK_AVAILABLE_IN_ALL +GtkSorter * gtk_custom_sorter_new (GCompareDataFunc sort_func, + gpointer user_data, + GDestroyNotify user_destroy); + +GDK_AVAILABLE_IN_ALL +void gtk_custom_sorter_set_sort_func (GtkCustomSorter *self, + GCompareDataFunc sort_func, + gpointer user_data, + GDestroyNotify user_destroy); + +G_END_DECLS + +#endif /* __GTK_CUSTOM_SORTER_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index c8157ef2b5..6010248986 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -201,6 +201,7 @@ gtk_public_sources = files([ 'gtkcssprovider.c', 'gtkcustomfilter.c', 'gtkcustomlayout.c', + 'gtkcustomsorter.c', 'gtkdialog.c', 'gtkdirectorylist.c', 'gtkdragicon.c', @@ -461,6 +462,7 @@ gtk_public_headers = files([ 'gtkcssprovider.h', 'gtkcustomfilter.h', 'gtkcustomlayout.h', + 'gtkcustomsorter.h', 'gtkdebug.h', 'gtkdialog.h', 'gtkdirectorylist.h', From 6d68c536f3f35bf887975e7a9a5bd491c5437e66 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 2 Dec 2019 23:43:14 -0500 Subject: [PATCH 021/170] Add GtkStringSorter This is a GtkSorter implementation collating strings --- docs/reference/gtk/gtk4-docs.xml | 1 + docs/reference/gtk/gtk4-sections.txt | 19 ++ docs/reference/gtk/gtk4.types.in | 1 + gtk/gtk.h | 1 + gtk/gtkstringsorter.c | 351 +++++++++++++++++++++++++++ gtk/gtkstringsorter.h | 52 ++++ gtk/meson.build | 2 + 7 files changed, 427 insertions(+) create mode 100644 gtk/gtkstringsorter.c create mode 100644 gtk/gtkstringsorter.h diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index dc6024ed4c..67040526c6 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -63,6 +63,7 @@
+
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index d13c697ed6..eed0ef504a 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -2441,6 +2441,25 @@ GTK_SORTER_GET_CLASS gtk_sorter_get_type
+
+gtkstringsorter +GtkStringSorter +GtkStringSorter +gtk_string_sorter_new +gtk_string_sorter_get_expression +gtk_string_sorter_set_expression +gtk_string_sorter_get_ignore_case +gtk_string_sorter_set_ignore_case + +GTK_STRING_SORTER +GTK_IS_STRING_SORTER +GTK_TYPE_STRING_SORTER +GTK_IS_STRING_SORTER_CLASS +GTK_STRING_SORTER_GET_CLASS + +gtk_string_sorter_get_type +
+
gtkcustomsorter GtkCustomSorter diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in index 8ddec9caf5..982b398517 100644 --- a/docs/reference/gtk/gtk4.types.in +++ b/docs/reference/gtk/gtk4.types.in @@ -195,6 +195,7 @@ gtk_stack_sidebar_get_type gtk_stack_switcher_get_type gtk_statusbar_get_type gtk_string_filter_get_type +gtk_string_sorter_get_type gtk_switch_get_type gtk_level_bar_get_type gtk_style_context_get_type diff --git a/gtk/gtk.h b/gtk/gtk.h index bcd673a2e0..93f6d77a80 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -226,6 +226,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkstringsorter.c b/gtk/gtkstringsorter.c new file mode 100644 index 0000000000..5fcc63c566 --- /dev/null +++ b/gtk/gtkstringsorter.c @@ -0,0 +1,351 @@ +/* + * Copyright © 2019 Matthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Matthias Clasen + */ + +#include "config.h" + +#include "gtkstringsorter.h" + +#include "gtkintl.h" +#include "gtktypebuiltins.h" + +/** + * SECTION:gtkstringsorter + * @Title: GtkStringSorter + * @Short_description: Sort by comparing strings + * @See_also: #GtkExpression + * + * GtkStringSorter is a #GtkSorter that compares strings. It does the + * comparison in a linguistically correct way using the current locale by + * normalizing Unicode strings and possibly case-folding them before + * performing the comparison. + * + * To obtain the strings to compare, this sorter evaluates a #GtkExpression. + */ + +struct _GtkStringSorter +{ + GtkSorter parent_instance; + + gboolean ignore_case; + + GtkExpression *expression; +}; + +enum { + PROP_0, + PROP_EXPRESSION, + PROP_IGNORE_CASE, + NUM_PROPERTIES +}; + +G_DEFINE_TYPE (GtkStringSorter, gtk_string_sorter, GTK_TYPE_SORTER) + +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +static GtkOrdering +gtk_string_sorter_compare (GtkSorter *sorter, + gpointer item1, + gpointer item2) +{ + GtkStringSorter *self = GTK_STRING_SORTER (sorter); + GValue value1 = G_VALUE_INIT; + GValue value2 = G_VALUE_INIT; + const char *s1, *s2; + gboolean res1, res2; + GtkOrdering result; + + if (self->expression == NULL) + return GTK_ORDERING_EQUAL; + + res1 = gtk_expression_evaluate (self->expression, item1, &value1); + res2 = gtk_expression_evaluate (self->expression, item2, &value2); + + /* If items don't evaluate, order them at the end, so they aren't + * in the way. */ + if (!res1) + { + result = res2 ? GTK_ORDERING_LARGER : GTK_ORDERING_EQUAL; + goto out; + } + else if (!res2) + { + result = GTK_ORDERING_SMALLER; + goto out; + } + + s1 = g_value_get_string (&value1); + s2 = g_value_get_string (&value2); + + /* If strings are NULL, order them before "". */ + if (s1 == NULL) + { + result = s2 == NULL ? GTK_ORDERING_EQUAL : GTK_ORDERING_SMALLER; + goto out; + } + else if (s2 == NULL) + { + result = GTK_ORDERING_LARGER; + goto out; + } + + if (self->ignore_case) + { + char *t1, *t2; + + t1 = g_utf8_casefold (s1, -1); + t2 = g_utf8_casefold (s2, -1); + + result = gtk_ordering_from_cmpfunc (g_utf8_collate (t1, t2)); + + g_free (t1); + g_free (t2); + } + else + result = gtk_ordering_from_cmpfunc (g_utf8_collate (s1, s2)); + +out: + g_value_unset (&value1); + g_value_unset (&value2); + + return result; +} + +static GtkSorterOrder +gtk_string_sorter_get_order (GtkSorter *sorter) +{ + GtkStringSorter *self = GTK_STRING_SORTER (sorter); + + if (self->expression == NULL) + return GTK_SORTER_ORDER_NONE; + + return GTK_SORTER_ORDER_PARTIAL; +} + +static void +gtk_string_sorter_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkStringSorter *self = GTK_STRING_SORTER (object); + + switch (prop_id) + { + case PROP_EXPRESSION: + gtk_string_sorter_set_expression (self, g_value_get_boxed (value)); + break; + + case PROP_IGNORE_CASE: + gtk_string_sorter_set_ignore_case (self, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_string_sorter_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkStringSorter *self = GTK_STRING_SORTER (object); + + switch (prop_id) + { + case PROP_EXPRESSION: + g_value_set_boxed (value, self->expression); + break; + + case PROP_IGNORE_CASE: + g_value_set_boolean (value, self->ignore_case); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_string_sorter_dispose (GObject *object) +{ + GtkStringSorter *self = GTK_STRING_SORTER (object); + + g_clear_pointer (&self->expression, gtk_expression_unref); + + G_OBJECT_CLASS (gtk_string_sorter_parent_class)->dispose (object); +} + +static void +gtk_string_sorter_class_init (GtkStringSorterClass *class) +{ + GtkSorterClass *sorter_class = GTK_SORTER_CLASS (class); + GObjectClass *object_class = G_OBJECT_CLASS (class); + + sorter_class->compare = gtk_string_sorter_compare; + sorter_class->get_order = gtk_string_sorter_get_order; + + object_class->get_property = gtk_string_sorter_get_property; + object_class->set_property = gtk_string_sorter_set_property; + object_class->dispose = gtk_string_sorter_dispose; + + /** + * GtkStringSorter:expression: + * + * The expression to evalute on item to get a string to compare with + */ + properties[PROP_EXPRESSION] = + g_param_spec_boxed ("expression", + P_("Expression"), + P_("Expression to compare with"), + GTK_TYPE_EXPRESSION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkStringSorter:ignore-case: + * + * If matching is case sensitive + */ + properties[PROP_IGNORE_CASE] = + g_param_spec_boolean ("ignore-case", + P_("Ignore case"), + P_("If matching is case sensitive"), + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); + +} + +static void +gtk_string_sorter_init (GtkStringSorter *self) +{ + self->ignore_case = TRUE; +} + +/** + * gtk_string_sorter_new: + * @expression: (transfer full) (nullable): The expression to evaluate + * + * Creates a new string sorter that compares items using the given + * @expression. + * + * Unless an expression is set on it, this sorter will always + * compare items as invalid. + * + * Returns: a new #GtkSorter + */ +GtkSorter * +gtk_string_sorter_new (GtkExpression *expression) +{ + GtkSorter *result; + + result = g_object_new (GTK_TYPE_STRING_SORTER, + "expression", expression, + NULL); + + g_clear_pointer (&expression, gtk_expression_unref); + + return result; +} + +/** + * gtk_string_sorter_get_expression: + * @self: a #GtkStringSorter + * + * Gets the expression that is evaluated to obtain strings from items. + * + * Returns: (nullable): a #GtkExpression, or %NULL + */ +GtkExpression * +gtk_string_sorter_get_expression (GtkStringSorter *self) +{ + g_return_val_if_fail (GTK_IS_STRING_SORTER (self), NULL); + + return self->expression; +} + +/** + * gtk_string_sorter_set_expression: + * @self: a #GtkStringSorter + * @expression: (nullable) (transfer none): a #GtkExpression, or %NULL + * + * Sets the expression that is evaluated to obtain strings from items. + * + * The expression must have the type G_TYPE_STRING. + */ +void +gtk_string_sorter_set_expression (GtkStringSorter *self, + GtkExpression *expression) +{ + g_return_if_fail (GTK_IS_STRING_SORTER (self)); + g_return_if_fail (expression == NULL || gtk_expression_get_value_type (expression) == G_TYPE_STRING); + + if (self->expression == expression) + return; + + g_clear_pointer (&self->expression, gtk_expression_unref); + if (expression) + self->expression = gtk_expression_ref (expression); + + gtk_sorter_changed (GTK_SORTER (self), GTK_SORTER_CHANGE_DIFFERENT); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EXPRESSION]); +} + +/** + * gtk_string_sorter_get_ignore_case: + * @self: a #GtkStringSorter + * + * Gets whether the sorter ignores case differences. + * + * Returns: %TRUE if @self is ignoring case differences + */ +gboolean +gtk_string_sorter_get_ignore_case (GtkStringSorter *self) +{ + g_return_val_if_fail (GTK_IS_STRING_SORTER (self), TRUE); + + return self->ignore_case; +} + +/** + * gtk_string_sorter_set_ignore_case: + * @self: a #GtkStringSorter + * + * Sets whether the sorter will ignore case differences. + */ +void +gtk_string_sorter_set_ignore_case (GtkStringSorter *self, + gboolean ignore_case) +{ + g_return_if_fail (GTK_IS_STRING_SORTER (self)); + + if (self->ignore_case == ignore_case) + return; + + self->ignore_case = ignore_case; + + gtk_sorter_changed (GTK_SORTER (self), ignore_case ? GTK_SORTER_CHANGE_LESS_STRICT : GTK_SORTER_CHANGE_MORE_STRICT); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_IGNORE_CASE]); +} diff --git a/gtk/gtkstringsorter.h b/gtk/gtkstringsorter.h new file mode 100644 index 0000000000..e7323aacdc --- /dev/null +++ b/gtk/gtkstringsorter.h @@ -0,0 +1,52 @@ +/* + * Copyright © 2019 Matthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Matthias Clasen + */ + +#ifndef __GTK_STRING_SORTER_H__ +#define __GTK_STRING_SORTER_H__ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_STRING_SORTER (gtk_string_sorter_get_type ()) +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkStringSorter, gtk_string_sorter, GTK, STRING_SORTER, GtkSorter) + +GDK_AVAILABLE_IN_ALL +GtkSorter * gtk_string_sorter_new (GtkExpression *expression); + +GDK_AVAILABLE_IN_ALL +GtkExpression * gtk_string_sorter_get_expression (GtkStringSorter *self); +GDK_AVAILABLE_IN_ALL +void gtk_string_sorter_set_expression (GtkStringSorter *self, + GtkExpression *expression); +GDK_AVAILABLE_IN_ALL +gboolean gtk_string_sorter_get_ignore_case (GtkStringSorter *self); +GDK_AVAILABLE_IN_ALL +void gtk_string_sorter_set_ignore_case (GtkStringSorter *self, + gboolean ignore_case); + +G_END_DECLS + +#endif /* __GTK_STRING_SORTER_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index 6010248986..b0fe6c89fd 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -356,6 +356,7 @@ gtk_public_sources = files([ 'gtkstackswitcher.c', 'gtkstatusbar.c', 'gtkstringfilter.c', + 'gtkstringsorter.c', 'gtkstylecontext.c', 'gtkstyleprovider.c', 'gtkswitch.c', @@ -604,6 +605,7 @@ gtk_public_headers = files([ 'gtkstackswitcher.h', 'gtkstatusbar.h', 'gtkstringfilter.h', + 'gtkstringsorter.h', 'gtkstylecontext.h', 'gtkstyleprovider.h', 'gtkswitch.h', From ae4bb2d9143a4092d8ded7952f01b7cb07a3541a Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 8 Dec 2019 13:54:57 -0500 Subject: [PATCH 022/170] Add GtkMultiSorter This is a sorter that tries multiple sorters in turn. --- docs/reference/gtk/gtk4-docs.xml | 1 + docs/reference/gtk/gtk4-sections.txt | 17 ++ docs/reference/gtk/gtk4.types.in | 1 + gtk/gtk.h | 1 + gtk/gtkmultisorter.c | 310 +++++++++++++++++++++++++++ gtk/gtkmultisorter.h | 49 +++++ gtk/meson.build | 2 + 7 files changed, 381 insertions(+) create mode 100644 gtk/gtkmultisorter.c create mode 100644 gtk/gtkmultisorter.h diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index 67040526c6..b83897cdc8 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -64,6 +64,7 @@ +
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index eed0ef504a..e04a0b94f6 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -2475,6 +2475,23 @@ GTK_CUSTOM_SORTER_GET_CLASS gtk_custom_sorter_get_type +
+gtkmultisorter +GtkMultiSorter +GtkMultiSorter +gtk_multi_sorter_new +gtk_multi_sorter_append +gtk_multi_sorter_remove + +GTK_MULTI_SORTER +GTK_IS_MULTI_SORTER +GTK_TYPE_MULTI_SORTER +GTK_IS_MULTI_SORTER_CLASS +GTK_MULTI_SORTER_GET_CLASS + +gtk_multi_sorter_get_type +
+
gtksortlistmodel GtkSortListModel diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in index 982b398517..473c4d13cc 100644 --- a/docs/reference/gtk/gtk4.types.in +++ b/docs/reference/gtk/gtk4.types.in @@ -131,6 +131,7 @@ gtk_menu_button_get_type gtk_message_dialog_get_type gtk_mount_operation_get_type gtk_multi_filter_get_type +gtk_multi_sorter_get_type gtk_native_get_type gtk_native_dialog_get_type gtk_no_selection_get_type diff --git a/gtk/gtk.h b/gtk/gtk.h index 93f6d77a80..43dbad1753 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -164,6 +164,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkmultisorter.c b/gtk/gtkmultisorter.c new file mode 100644 index 0000000000..66400c50a2 --- /dev/null +++ b/gtk/gtkmultisorter.c @@ -0,0 +1,310 @@ +/* + * Copyright © 2019 Matthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Matthias Clasen + */ + +#include "config.h" + +#include "gtkmultisorter.h" + +#include "gtkbuildable.h" +#include "gtkintl.h" +#include "gtktypebuiltins.h" + +/** + * SECTION:gtkmultisorter + * @Title: GtkMultiSorter + * @Short_description: Combining multiple sorters + * + * GtkMultiSorter combines multiple sorters by trying them + * in turn. If the first sorter compares two items as equal, + * the second is tried next, and so on. + */ +struct _GtkMultiSorter +{ + GtkSorter parent_instance; + + GSequence *sorters; +}; + +static GType +gtk_multi_sorter_get_item_type (GListModel *list) +{ + return GTK_TYPE_SORTER; +} + +static guint +gtk_multi_sorter_get_n_items (GListModel *list) +{ + GtkMultiSorter *self = GTK_MULTI_SORTER (list); + + return g_sequence_get_length (self->sorters); +} + +static gpointer +gtk_multi_sorter_get_item (GListModel *list, + guint position) +{ + GtkMultiSorter *self = GTK_MULTI_SORTER (list); + GSequenceIter *iter; + + iter = g_sequence_get_iter_at_pos (self->sorters, position); + + if (g_sequence_iter_is_end (iter)) + return NULL; + else + return g_object_ref (g_sequence_get (iter)); +} + +static void +gtk_multi_sorter_list_model_init (GListModelInterface *iface) +{ + iface->get_item_type = gtk_multi_sorter_get_item_type; + iface->get_n_items = gtk_multi_sorter_get_n_items; + iface->get_item = gtk_multi_sorter_get_item; +} + +static GtkBuildableIface *parent_buildable_iface; + +static void +gtk_multi_sorter_buildable_add_child (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *type) +{ + if (GTK_IS_SORTER (child)) + gtk_multi_sorter_append (GTK_MULTI_SORTER (buildable), g_object_ref (GTK_SORTER (child))); + else + parent_buildable_iface->add_child (buildable, builder, child, type); +} + +static void +gtk_multi_sorter_buildable_init (GtkBuildableIface *iface) +{ + parent_buildable_iface = g_type_interface_peek_parent (iface); + + iface->add_child = gtk_multi_sorter_buildable_add_child; +} + +G_DEFINE_TYPE_WITH_CODE (GtkMultiSorter, gtk_multi_sorter, GTK_TYPE_SORTER, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_multi_sorter_list_model_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_multi_sorter_buildable_init)) + +static GtkOrdering +gtk_multi_sorter_compare (GtkSorter *sorter, + gpointer item1, + gpointer item2) +{ + GtkMultiSorter *self = GTK_MULTI_SORTER (sorter); + GtkOrdering result = GTK_ORDERING_EQUAL; + GSequenceIter *iter; + + for (iter = g_sequence_get_begin_iter (self->sorters); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + GtkSorter *child = g_sequence_get (iter); + + result = gtk_sorter_compare (child, item1, item2); + if (result != GTK_ORDERING_EQUAL) + break; + } + + return result; +} + +static GtkSorterOrder +gtk_multi_sorter_get_order (GtkSorter *sorter) +{ + GtkMultiSorter *self = GTK_MULTI_SORTER (sorter); + GtkSorterOrder result = GTK_SORTER_ORDER_NONE; + GSequenceIter *iter; + + for (iter = g_sequence_get_begin_iter (self->sorters); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + GtkSorter *child = g_sequence_get (iter); + GtkSorterOrder child_order; + + child_order = gtk_sorter_get_order (child); + switch (child_order) + { + case GTK_SORTER_ORDER_PARTIAL: + result = GTK_SORTER_ORDER_PARTIAL; + break; + case GTK_SORTER_ORDER_NONE: + break; + case GTK_SORTER_ORDER_TOTAL: + return GTK_SORTER_ORDER_TOTAL; + default: + g_assert_not_reached (); + break; + } + } + + return result; +} + +static void +gtk_multi_sorter_changed_cb (GtkSorter *sorter, + GtkSorterChange change, + GtkMultiSorter *self) +{ + /* Using an enum on purpose, so gcc complains about this case if + * new values are added to the enum */ + switch (change) + { + case GTK_SORTER_CHANGE_INVERTED: + /* This could do a lot better with change handling, in particular in cases where self->n_sorters == 1 + * or if sorter == self->sorters[0] */ + change = GTK_SORTER_CHANGE_DIFFERENT; + break; + + case GTK_SORTER_CHANGE_DIFFERENT: + case GTK_SORTER_CHANGE_LESS_STRICT: + case GTK_SORTER_CHANGE_MORE_STRICT: + break; + + default: + g_assert_not_reached (); + change = GTK_SORTER_CHANGE_DIFFERENT; + } + gtk_sorter_changed (GTK_SORTER (self), change); +} + +static void +gtk_multi_sorter_remove_iter (GtkMultiSorter *self, + GSequenceIter *iter) +{ + GtkSorter *sorter; + + sorter = g_sequence_get (iter); + g_signal_handlers_disconnect_by_func (sorter, gtk_multi_sorter_changed_cb, self); + g_object_unref (sorter); + g_sequence_remove (iter); +} + +static void +gtk_multi_sorter_dispose (GObject *object) +{ + GtkMultiSorter *self = GTK_MULTI_SORTER (object); + + while (!g_sequence_is_empty (self->sorters)) + gtk_multi_sorter_remove_iter (self, g_sequence_get_begin_iter (self->sorters)); + + G_OBJECT_CLASS (gtk_multi_sorter_parent_class)->dispose (object); +} + +static void +gtk_multi_sorter_finalize (GObject *object) +{ + GtkMultiSorter *self = GTK_MULTI_SORTER (object); + + g_sequence_free (self->sorters); + + G_OBJECT_CLASS (gtk_multi_sorter_parent_class)->finalize (object); +} + +static void +gtk_multi_sorter_class_init (GtkMultiSorterClass *class) +{ + GtkSorterClass *sorter_class = GTK_SORTER_CLASS (class); + GObjectClass *object_class = G_OBJECT_CLASS (class); + + sorter_class->compare = gtk_multi_sorter_compare; + sorter_class->get_order = gtk_multi_sorter_get_order; + + object_class->dispose = gtk_multi_sorter_dispose; + object_class->finalize = gtk_multi_sorter_finalize; +} + +static void +gtk_multi_sorter_init (GtkMultiSorter *self) +{ + self->sorters = g_sequence_new (NULL); +} + +/** + * gtk_multi_sorter_new: + * + * Creates a new multi sorter. + * + * This sorter compares items by trying each of the sorters + * in turn, until one returns non-zero. In particular, if + * no sorter has been added to it, it will always compare + * items as equal. + * + * Returns: a new #GtkSorter + */ +GtkSorter * +gtk_multi_sorter_new (void) +{ + return g_object_new (GTK_TYPE_MULTI_SORTER, NULL); +} + +/** + * gtk_multi_sorter_append: + * @self: a #GtkMultiSorter + * @sorter: (transfer full): a sorter to add + * + * Add @sorter to @self to use for sorting at the end. @self + * will consult all existing sorters before it will sort with + * the given @sorter. + */ +void +gtk_multi_sorter_append (GtkMultiSorter *self, + GtkSorter *sorter) +{ + g_return_if_fail (GTK_IS_MULTI_SORTER (self)); + g_return_if_fail (GTK_IS_SORTER (sorter)); + + g_signal_connect (sorter, "changed", G_CALLBACK (gtk_multi_sorter_changed_cb), self); + g_sequence_append (self->sorters, sorter); + + gtk_sorter_changed (GTK_SORTER (self), GTK_SORTER_CHANGE_MORE_STRICT); +} + +/** + * gtk_multi_sorter_remove: + * @self: a #GtkMultiSorter + * @position: position of sorter to remove + * + * Removes the sorter at the given @position from the list of sorter + * used by @self. + * + * If @position is larger than the number of sorters, nothing happens. + */ +void +gtk_multi_sorter_remove (GtkMultiSorter *self, + guint position) +{ + GSequenceIter *iter; + guint length; + + g_return_if_fail (GTK_IS_MULTI_SORTER (self)); + + length = g_sequence_get_length (self->sorters); + if (position >= length) + return; + + iter = g_sequence_get_iter_at_pos (self->sorters, position); + gtk_multi_sorter_remove_iter (self, iter); + + gtk_sorter_changed (GTK_SORTER (self), GTK_SORTER_CHANGE_LESS_STRICT); +} + diff --git a/gtk/gtkmultisorter.h b/gtk/gtkmultisorter.h new file mode 100644 index 0000000000..4f265bf6a2 --- /dev/null +++ b/gtk/gtkmultisorter.h @@ -0,0 +1,49 @@ +/* + * Copyright © 2019 Matthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Matthias Clasen + */ + +#ifndef __GTK_MULTI_SORTER_H__ +#define __GTK_MULTI_SORTER_H__ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_MULTI_SORTER (gtk_multi_sorter_get_type ()) +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkMultiSorter, gtk_multi_sorter, GTK, MULTI_SORTER, GtkSorter) + +GDK_AVAILABLE_IN_ALL +GtkSorter * gtk_multi_sorter_new (void); + +GDK_AVAILABLE_IN_ALL +void gtk_multi_sorter_append (GtkMultiSorter *self, + GtkSorter *sorter); + +GDK_AVAILABLE_IN_ALL +void gtk_multi_sorter_remove (GtkMultiSorter *self, + guint position); + +G_END_DECLS + +#endif /* __GTK_MULTI_SORTER_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index b0fe6c89fd..4663211d73 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -285,6 +285,7 @@ gtk_public_sources = files([ 'gtkmodules.c', 'gtkmountoperation.c', 'gtkmultifilter.c', + 'gtkmultisorter.c', 'gtknativedialog.c', 'gtknomediafile.c', 'gtknoselection.c', @@ -543,6 +544,7 @@ gtk_public_headers = files([ 'gtkmessagedialog.h', 'gtkmountoperation.h', 'gtkmultifilter.h', + 'gtkmultisorter.h', 'gtknative.h', 'gtknativedialog.h', 'gtknoselection.h', From 1eda9884a0d405ee6bb5e6436849623d254d41b3 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 8 Dec 2019 14:37:20 -0500 Subject: [PATCH 023/170] Add GtkNumericSorter This sorter compares numbers obtained from items by evaluating an expression. --- config.h.meson | 6 +- docs/reference/gtk/gtk4-docs.xml | 1 + docs/reference/gtk/gtk4-sections.txt | 19 ++ docs/reference/gtk/gtk4.types.in | 1 + gtk/fallback-c89.c | 24 ++ gtk/gtk.h | 1 + gtk/gtknumericsorter.c | 420 +++++++++++++++++++++++++++ gtk/gtknumericsorter.h | 53 ++++ gtk/meson.build | 2 + meson.build | 1 + 10 files changed, 527 insertions(+), 1 deletion(-) create mode 100644 gtk/gtknumericsorter.c create mode 100644 gtk/gtknumericsorter.h diff --git a/config.h.meson b/config.h.meson index 06705aefc7..fac759a2a7 100644 --- a/config.h.meson +++ b/config.h.meson @@ -35,6 +35,10 @@ */ #mesondefine HAVE_DECL_ISNAN +/* Define to 1 if you have the declaration of `isnanf', and to 0 if you don't. + */ +#mesondefine HAVE_DECL_ISNANF + /* Define to 1 if you have the header file. */ #mesondefine HAVE_DLFCN_H @@ -294,4 +298,4 @@ #mesondefine ISO_CODES_PREFIX /* Define if tracker3 is available */ -#mesondefine HAVE_TRACKER3 \ No newline at end of file +#mesondefine HAVE_TRACKER3 diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index b83897cdc8..979ec7192a 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -64,6 +64,7 @@ +
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index e04a0b94f6..c54826ffb2 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -2460,6 +2460,25 @@ GTK_STRING_SORTER_GET_CLASS gtk_string_sorter_get_type +
+gtknumericsorter +GtkNumericSorter +GtkNumericSorter +gtk_numeric_sorter_new +gtk_numeric_sorter_get_expression +gtk_numeric_sorter_set_expression +gtk_numeric_sorter_get_sort_order +gtk_numeric_sorter_set_sort_order + +GTK_NUMERIC_SORTER +GTK_IS_NUMERIC_SORTER +GTK_TYPE_NUMERIC_SORTER +GTK_IS_NUMERIC_SORTER_CLASS +GTK_NUMERIC_SORTER_GET_CLASS + +gtk_numeric_sorter_get_type +
+
gtkcustomsorter GtkCustomSorter diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in index 473c4d13cc..0b74d93159 100644 --- a/docs/reference/gtk/gtk4.types.in +++ b/docs/reference/gtk/gtk4.types.in @@ -137,6 +137,7 @@ gtk_native_dialog_get_type gtk_no_selection_get_type gtk_notebook_get_type gtk_notebook_page_get_type +gtk_numeric_sorter_get_type gtk_orientable_get_type gtk_overlay_get_type gtk_overlay_layout_get_type diff --git a/gtk/fallback-c89.c b/gtk/fallback-c89.c index 61536771ee..a566e3423a 100644 --- a/gtk/fallback-c89.c +++ b/gtk/fallback-c89.c @@ -80,6 +80,30 @@ isinf (double x) } #endif +#ifndef HAVE_DECL_ISNAN +/* it seems of the supported compilers only + * MSVC does not have isnan(), but it does + * have _isnan() which does the same as isnan() + */ +static inline gboolean +isnan (double x) +{ + return _isnan (x); +} +#endif + +#ifndef HAVE_DECL_ISNANF +/* it seems of the supported compilers only + * MSVC does not have isnanf(), but it does + * have _isnanf() which does the same as isnanf() + */ +static inline gboolean +isnanf (float x) +{ + return _isnanf (x); +} +#endif + #ifndef INFINITY /* define INFINITY for compilers that lack support for it */ # ifdef HUGE_VALF diff --git a/gtk/gtk.h b/gtk/gtk.h index 43dbad1753..e2959a2609 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -169,6 +169,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtknumericsorter.c b/gtk/gtknumericsorter.c new file mode 100644 index 0000000000..253fd201d7 --- /dev/null +++ b/gtk/gtknumericsorter.c @@ -0,0 +1,420 @@ +/* + * Copyright © 2019 Matthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Matthias Clasen + */ + +#include "config.h" + +#include "gtknumericsorter.h" + +#include "gtkintl.h" +#include "gtktypebuiltins.h" + +#include +#include "fallback-c89.c" + +/** + * SECTION:gtknumericsorter + * @Title: GtkNumericSorter + * @Short_description: Sort by comparing numbers + * @See_also: #GtkExpression + * + * GtkNumericSorter is a #GtkSorter that compares numbers. + * + * To obtain the numbers to compare, this sorter evaluates a #GtkExpression. + */ + +struct _GtkNumericSorter +{ + GtkSorter parent_instance; + + GtkSortType sort_order; + + GtkExpression *expression; +}; + +enum { + PROP_0, + PROP_EXPRESSION, + PROP_SORT_ORDER, + NUM_PROPERTIES +}; + +G_DEFINE_TYPE (GtkNumericSorter, gtk_numeric_sorter, GTK_TYPE_SORTER) + +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +#define DO_COMPARISON(_result, _type, _getter, _order) G_STMT_START{ \ + _type num1 = _getter (&value1); \ + _type num2 = _getter (&value2); \ +\ + if (num1 < num2) \ + _result = _order == GTK_SORT_ASCENDING ? GTK_ORDERING_SMALLER : GTK_ORDERING_LARGER; \ + else if (num1 > num2) \ + _result = _order == GTK_SORT_ASCENDING ? GTK_ORDERING_LARGER : GTK_ORDERING_SMALLER; \ + else \ + _result = GTK_ORDERING_EQUAL; \ +}G_STMT_END + +static GtkOrdering +gtk_numeric_sorter_compare (GtkSorter *sorter, + gpointer item1, + gpointer item2) +{ + GtkNumericSorter *self = GTK_NUMERIC_SORTER (sorter); + GValue value1 = G_VALUE_INIT; + GValue value2 = G_VALUE_INIT; + gboolean res1, res2; + GtkOrdering result; + + if (self->expression == NULL) + return GTK_ORDERING_EQUAL; + + res1 = gtk_expression_evaluate (self->expression, item1, &value1); + res2 = gtk_expression_evaluate (self->expression, item2, &value2); + + /* If items don't evaluate, order them at the end, so they aren't + * in the way. */ + if (!res1) + { + result = res2 ? GTK_ORDERING_LARGER : GTK_ORDERING_EQUAL; + goto out; + } + else if (!res2) + { + result = GTK_ORDERING_SMALLER; + goto out; + } + + switch (g_type_fundamental (G_VALUE_TYPE (&value1))) + { + case G_TYPE_BOOLEAN: + DO_COMPARISON (result, gboolean, g_value_get_boolean, self->sort_order); + break; + + case G_TYPE_CHAR: + DO_COMPARISON (result, char, g_value_get_char, self->sort_order); + break; + + case G_TYPE_UCHAR: + DO_COMPARISON (result, guchar, g_value_get_uchar, self->sort_order); + break; + + case G_TYPE_INT: + DO_COMPARISON (result, int, g_value_get_int, self->sort_order); + break; + + case G_TYPE_UINT: + DO_COMPARISON (result, guint, g_value_get_uint, self->sort_order); + break; + + case G_TYPE_FLOAT: + { + float num1 = g_value_get_float (&value1); + float num2 = g_value_get_float (&value2); + + if (isnanf (num1) && isnanf (num2)) + result = GTK_ORDERING_EQUAL; + else if (isnanf (num1)) + result = self->sort_order == GTK_SORT_ASCENDING ? GTK_ORDERING_LARGER : GTK_ORDERING_SMALLER; + else if (isnanf (num2)) + result = self->sort_order == GTK_SORT_ASCENDING ? GTK_ORDERING_SMALLER : GTK_ORDERING_LARGER; + else if (num1 < num2) + result = self->sort_order == GTK_SORT_ASCENDING ? GTK_ORDERING_SMALLER : GTK_ORDERING_LARGER; + else if (num1 > num2) + result = self->sort_order == GTK_SORT_ASCENDING ? GTK_ORDERING_LARGER : GTK_ORDERING_SMALLER; + else + result = GTK_ORDERING_EQUAL; + break; + } + + case G_TYPE_DOUBLE: + { + double num1 = g_value_get_double (&value1); + double num2 = g_value_get_double (&value2); + + if (isnan (num1) && isnan (num2)) + result = GTK_ORDERING_EQUAL; + else if (isnan (num1)) + result = self->sort_order == GTK_SORT_ASCENDING ? GTK_ORDERING_LARGER : GTK_ORDERING_SMALLER; + else if (isnan (num2)) + result = self->sort_order == GTK_SORT_ASCENDING ? GTK_ORDERING_SMALLER : GTK_ORDERING_LARGER; + else if (num1 < num2) + result = self->sort_order == GTK_SORT_ASCENDING ? GTK_ORDERING_SMALLER : GTK_ORDERING_LARGER; + else if (num1 > num2) + result = self->sort_order == GTK_SORT_ASCENDING ? GTK_ORDERING_LARGER : GTK_ORDERING_SMALLER; + else + result = GTK_ORDERING_EQUAL; + break; + } + + case G_TYPE_LONG: + DO_COMPARISON (result, long, g_value_get_long, self->sort_order); + break; + + case G_TYPE_ULONG: + DO_COMPARISON (result, gulong, g_value_get_ulong, self->sort_order); + break; + + case G_TYPE_INT64: + DO_COMPARISON (result, gint64, g_value_get_int64, self->sort_order); + break; + + case G_TYPE_UINT64: + DO_COMPARISON (result, guint64, g_value_get_uint64, self->sort_order); + break; + + default: + g_critical ("Invalid value type %s for expression\n", g_type_name (gtk_expression_get_value_type (self->expression))); + result = GTK_ORDERING_EQUAL; + break; + } + +out: + g_value_unset (&value1); + g_value_unset (&value2); + + return result; +} + +static GtkSorterOrder +gtk_numeric_sorter_get_order (GtkSorter *sorter) +{ + GtkNumericSorter *self = GTK_NUMERIC_SORTER (sorter); + + if (self->expression == NULL) + return GTK_SORTER_ORDER_NONE; + + return GTK_SORTER_ORDER_PARTIAL; +} + +static void +gtk_numeric_sorter_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkNumericSorter *self = GTK_NUMERIC_SORTER (object); + + switch (prop_id) + { + case PROP_EXPRESSION: + gtk_numeric_sorter_set_expression (self, g_value_get_boxed (value)); + break; + + case PROP_SORT_ORDER: + gtk_numeric_sorter_set_sort_order (self, g_value_get_enum (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_numeric_sorter_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkNumericSorter *self = GTK_NUMERIC_SORTER (object); + + switch (prop_id) + { + case PROP_EXPRESSION: + g_value_set_boxed (value, self->expression); + break; + + case PROP_SORT_ORDER: + g_value_set_enum (value, self->sort_order); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_numeric_sorter_dispose (GObject *object) +{ + GtkNumericSorter *self = GTK_NUMERIC_SORTER (object); + + g_clear_pointer (&self->expression, gtk_expression_unref); + + G_OBJECT_CLASS (gtk_numeric_sorter_parent_class)->dispose (object); +} + +static void +gtk_numeric_sorter_class_init (GtkNumericSorterClass *class) +{ + GtkSorterClass *sorter_class = GTK_SORTER_CLASS (class); + GObjectClass *object_class = G_OBJECT_CLASS (class); + + sorter_class->compare = gtk_numeric_sorter_compare; + sorter_class->get_order = gtk_numeric_sorter_get_order; + + object_class->get_property = gtk_numeric_sorter_get_property; + object_class->set_property = gtk_numeric_sorter_set_property; + object_class->dispose = gtk_numeric_sorter_dispose; + + /** + * GtkNumericSorter:expression: + * + * The expression to evalute on items to get a number to compare with + */ + properties[PROP_EXPRESSION] = + g_param_spec_boxed ("expression", + P_("Expression"), + P_("Expression to compare with"), + GTK_TYPE_EXPRESSION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkNumericSorter:sort-order: + * + * Whether the sorter will sort smaller numbers first + */ + properties[PROP_SORT_ORDER] = + g_param_spec_enum ("sort-order", + P_("Sort order"), + P_("Whether to sort smaller numbers first"), + GTK_TYPE_SORT_TYPE, + GTK_SORT_ASCENDING, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); + +} + +static void +gtk_numeric_sorter_init (GtkNumericSorter *self) +{ + self->sort_order = GTK_SORT_ASCENDING; +} + +/** + * gtk_numeric_sorter_new: + * @expression: (transfer full) (nullable): The expression to evaluate + * + * Creates a new numeric sorter using the given @expression. + * + * Smaller numbers will be sorted first. You can call + * gtk_numeric_sorter_set_sort_order() to change this. + * + * Returns: a new #GtkSorter + */ +GtkSorter * +gtk_numeric_sorter_new (GtkExpression *expression) +{ + GtkSorter *result; + + result = g_object_new (GTK_TYPE_NUMERIC_SORTER, + "expression", expression, + NULL); + + g_clear_pointer (&expression, gtk_expression_unref); + + return result; +} + +/** + * gtk_numeric_sorter_get_expression: + * @self: a #GtkNumericSorter + * + * Gets the expression that is evaluated to obtain numbers from items. + * + * Returns: (nullable): a #GtkExpression, or %NULL + */ +GtkExpression * +gtk_numeric_sorter_get_expression (GtkNumericSorter *self) +{ + g_return_val_if_fail (GTK_IS_NUMERIC_SORTER (self), NULL); + + return self->expression; +} + +/** + * gtk_numeric_sorter_set_expression: + * @self: a #GtkNumericSorter + * @expression: (nullable) (transfer none): a #GtkExpression, or %NULL + * + * Sets the expression that is evaluated to obtain numbers from items. + * + * Unless an expression is set on @self, the sorter will always + * compare items as invalid. + * + * The expression must have a return type that can be compared + * numerically, such as #G_TYPE_INT or #G_TYPE_DOUBLE. + */ +void +gtk_numeric_sorter_set_expression (GtkNumericSorter *self, + GtkExpression *expression) +{ + g_return_if_fail (GTK_IS_NUMERIC_SORTER (self)); + + if (self->expression == expression) + return; + + g_clear_pointer (&self->expression, gtk_expression_unref); + if (expression) + self->expression = gtk_expression_ref (expression); + + gtk_sorter_changed (GTK_SORTER (self), GTK_SORTER_CHANGE_DIFFERENT); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EXPRESSION]); +} + +/** + * gtk_numeric_sorter_set_sort_order: + * @self: a #GtkNumericSorter + * @sort_order: whether to sort smaller numbers first + * + * Sets whether to sort smaller numbers before larger ones. + */ +void +gtk_numeric_sorter_set_sort_order (GtkNumericSorter *self, + GtkSortType sort_order) +{ + g_return_if_fail (GTK_IS_NUMERIC_SORTER (self)); + + if (self->sort_order == sort_order) + return; + + self->sort_order = sort_order; + + gtk_sorter_changed (GTK_SORTER (self), GTK_SORTER_CHANGE_INVERTED); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORT_ORDER]); +} + +/** + * gtk_numeric_sorter_get_sort_order: + * @self: a #GtkNumericSorter + * + * Gets whether this sorter will sort smaller numbers first. + * + * Returns: the order of the numbers + */ +GtkSortType +gtk_numeric_sorter_get_sort_order (GtkNumericSorter *self) +{ + g_return_val_if_fail (GTK_IS_NUMERIC_SORTER (self), GTK_SORT_ASCENDING); + + return self->sort_order; +} diff --git a/gtk/gtknumericsorter.h b/gtk/gtknumericsorter.h new file mode 100644 index 0000000000..39bb48e212 --- /dev/null +++ b/gtk/gtknumericsorter.h @@ -0,0 +1,53 @@ +/* + * Copyright © 2019 Matthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Matthias Clasen + */ + +#ifndef __GTK_NUMERIC_SORTER_H__ +#define __GTK_NUMERIC_SORTER_H__ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_NUMERIC_SORTER (gtk_numeric_sorter_get_type ()) +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkNumericSorter, gtk_numeric_sorter, GTK, NUMERIC_SORTER, GtkSorter) + +GDK_AVAILABLE_IN_ALL +GtkSorter * gtk_numeric_sorter_new (GtkExpression *expression); + +GDK_AVAILABLE_IN_ALL +GtkExpression * gtk_numeric_sorter_get_expression (GtkNumericSorter *self); +GDK_AVAILABLE_IN_ALL +void gtk_numeric_sorter_set_expression (GtkNumericSorter *self, + GtkExpression *expression); + +GDK_AVAILABLE_IN_ALL +GtkSortType gtk_numeric_sorter_get_sort_order (GtkNumericSorter *self); +GDK_AVAILABLE_IN_ALL +void gtk_numeric_sorter_set_sort_order (GtkNumericSorter *self, + GtkSortType sort_order); + +G_END_DECLS + +#endif /* __GTK_NUMERIC_SORTER_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index 4663211d73..f85d089a2f 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -290,6 +290,7 @@ gtk_public_sources = files([ 'gtknomediafile.c', 'gtknoselection.c', 'gtknotebook.c', + 'gtknumericsorter.c', 'gtkorientable.c', 'gtkoverlay.c', 'gtkoverlaylayout.c', @@ -549,6 +550,7 @@ gtk_public_headers = files([ 'gtknativedialog.h', 'gtknoselection.h', 'gtknotebook.h', + 'gtknumericsorter.h', 'gtkorientable.h', 'gtkoverlay.h', 'gtkoverlaylayout.h', diff --git a/meson.build b/meson.build index 912c0f8a95..874fee25a8 100644 --- a/meson.build +++ b/meson.build @@ -213,6 +213,7 @@ endforeach cdata.set('HAVE_DECL_ISINF', cc.has_header_symbol('math.h', 'isinf')) cdata.set('HAVE_DECL_ISNAN', cc.has_header_symbol('math.h', 'isnan')) +cdata.set('HAVE_DECL_ISNANF', cc.has_header_symbol('math.h', 'isnanf')) # Disable deprecation checks for all libraries we depend on on stable branches. # This is so newer versions of those libraries don't cause more warnings with From 11a1f8f36a9136add6c4e765493009572c1203b3 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 2 Dec 2019 23:44:22 -0500 Subject: [PATCH 024/170] Redo sort list model with GtkSorter Reshuffle the api to take full advantage of GtkSorter. Update all callers. --- docs/reference/gtk/gtk4-sections.txt | 5 +- gtk/gtksortlistmodel.c | 218 ++++++++++++++------------- gtk/gtksortlistmodel.h | 21 +-- gtk/inspector/controllers.c | 8 +- testsuite/gtk/sortlistmodel.c | 23 ++- 5 files changed, 142 insertions(+), 133 deletions(-) diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index c54826ffb2..1765f97f71 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -2517,11 +2517,10 @@ gtk_multi_sorter_get_type GtkSortListModel gtk_sort_list_model_new gtk_sort_list_model_new_for_type -gtk_sort_list_model_set_sort_func -gtk_sort_list_model_has_sort +gtk_sort_list_model_set_sorter +gtk_sort_list_model_get_sorter gtk_sort_list_model_set_model gtk_sort_list_model_get_model -gtk_sort_list_model_resort GTK_SORT_LIST_MODEL GTK_IS_SORT_LIST_MODEL diff --git a/gtk/gtksortlistmodel.c b/gtk/gtksortlistmodel.c index ac82bb146a..fc88d6de70 100644 --- a/gtk/gtksortlistmodel.c +++ b/gtk/gtksortlistmodel.c @@ -28,10 +28,10 @@ * SECTION:gtksortlistmodel * @title: GtkSortListModel * @short_description: A list model that sorts its items - * @see_also: #GListModel + * @see_also: #GListModel, #GtkSorter * * #GtkSortListModel is a list model that takes a list model and - * sorts its elements according to a compare function. + * sorts its elements according to a #GtkSorter. * * #GtkSortListModel is a generic model and because of that it * cannot take advantage of any external knowledge when sorting. @@ -42,9 +42,9 @@ enum { PROP_0, - PROP_HAS_SORT, PROP_ITEM_TYPE, PROP_MODEL, + PROP_SORTER, NUM_PROPERTIES }; @@ -54,9 +54,7 @@ struct _GtkSortListModel GType item_type; GListModel *model; - GCompareDataFunc sort_func; - gpointer user_data; - GDestroyNotify user_destroy; + GtkSorter *sorter; GSequence *sorted; /* NULL if sort_func == NULL */ GSequence *unsorted; /* NULL if sort_func == NULL */ @@ -157,6 +155,14 @@ gtk_sort_list_model_remove_items (GtkSortListModel *self, *unmodified_end = end; } +static int +_sort_func (gconstpointer item1, + gconstpointer item2, + gpointer data) +{ + return gtk_sorter_compare (GTK_SORTER (data), (gpointer)item1, (gpointer)item2); +} + static void gtk_sort_list_model_add_items (GtkSortListModel *self, guint position, @@ -173,7 +179,7 @@ gtk_sort_list_model_add_items (GtkSortListModel *self, for (i = 0; i < n_items; i++) { gpointer item = g_list_model_get_item (self->model, position + i); - sorted_iter = g_sequence_insert_sorted (self->sorted, item, self->sort_func, self->user_data); + sorted_iter = g_sequence_insert_sorted (self->sorted, item, _sort_func, self->sorter); g_sequence_insert_before (unsorted_iter, sorted_iter); if (unmodified_start != NULL || unmodified_end != NULL) { @@ -234,6 +240,10 @@ gtk_sort_list_model_set_property (GObject *object, gtk_sort_list_model_set_model (self, g_value_get_object (value)); break; + case PROP_SORTER: + gtk_sort_list_model_set_sorter (self, g_value_get_object (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -250,10 +260,6 @@ gtk_sort_list_model_get_property (GObject *object, switch (prop_id) { - case PROP_HAS_SORT: - g_value_set_boolean (value, self->sort_func != NULL); - break; - case PROP_ITEM_TYPE: g_value_set_gtype (value, self->item_type); break; @@ -262,12 +268,26 @@ gtk_sort_list_model_get_property (GObject *object, g_value_set_object (value, self->model); break; + case PROP_SORTER: + g_value_set_object (value, self->sorter); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } +static void gtk_sort_list_model_resort (GtkSortListModel *self); + +static void +gtk_sort_list_model_sorter_changed_cb (GtkSorter *sorter, + int change, + GtkSortListModel *self) +{ + gtk_sort_list_model_resort (self); +} + static void gtk_sort_list_model_clear_model (GtkSortListModel *self) { @@ -280,17 +300,23 @@ gtk_sort_list_model_clear_model (GtkSortListModel *self) g_clear_pointer (&self->unsorted, g_sequence_free); } +static void +gtk_sort_list_model_clear_sorter (GtkSortListModel *self) +{ + if (self->sorter == NULL) + return; + + g_signal_handlers_disconnect_by_func (self->sorter, gtk_sort_list_model_sorter_changed_cb, self); + g_clear_object (&self->sorter); +} + static void gtk_sort_list_model_dispose (GObject *object) { GtkSortListModel *self = GTK_SORT_LIST_MODEL (object); gtk_sort_list_model_clear_model (self); - if (self->user_destroy) - self->user_destroy (self->user_data); - self->sort_func = NULL; - self->user_data = NULL; - self->user_destroy = NULL; + gtk_sort_list_model_clear_sorter (self); G_OBJECT_CLASS (gtk_sort_list_model_parent_class)->dispose (object); }; @@ -305,16 +331,16 @@ gtk_sort_list_model_class_init (GtkSortListModelClass *class) gobject_class->dispose = gtk_sort_list_model_dispose; /** - * GtkSortListModel:has-sort: + * GtkSortListModel:sorter: * - * If a sort function is set for this model + * The sorter for this model */ - properties[PROP_HAS_SORT] = - g_param_spec_boolean ("has-sort", - P_("has sort"), - P_("If a sort function is set for this model"), - FALSE, - GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); + properties[PROP_SORTER] = + g_param_spec_object ("sorter", + P_("Sorter"), + P_("The sorter for this model"), + GTK_TYPE_SORTER, + GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * GtkSortListModel:item-type: @@ -348,36 +374,30 @@ gtk_sort_list_model_init (GtkSortListModel *self) { } - /** * gtk_sort_list_model_new: * @model: the model to sort - * @sort_func: (allow-none): sort function or %NULL to not sort items - * @user_data: (closure): user data passed to @sort_func - * @user_destroy: destroy notifier for @user_data + * @sorter: (allow-none): the #GtkSorter to sort @model with * - * Creates a new sort list model that uses the @sort_func to sort @model. + * Creates a new sort list model that uses the @sorter to sort @model. * * Returns: a new #GtkSortListModel **/ GtkSortListModel * -gtk_sort_list_model_new (GListModel *model, - GCompareDataFunc sort_func, - gpointer user_data, - GDestroyNotify user_destroy) +gtk_sort_list_model_new (GListModel *model, + GtkSorter *sorter) { GtkSortListModel *result; g_return_val_if_fail (G_IS_LIST_MODEL (model), NULL); + g_return_val_if_fail (sorter == NULL || GTK_IS_SORTER (sorter), NULL); result = g_object_new (GTK_TYPE_SORT_LIST_MODEL, "item-type", g_list_model_get_item_type (model), "model", model, + "sorter", sorter, NULL); - if (sort_func) - gtk_sort_list_model_set_sort_func (result, sort_func, user_data, user_destroy); - return result; } @@ -404,7 +424,7 @@ gtk_sort_list_model_new_for_type (GType item_type) static void gtk_sort_list_model_create_sequences (GtkSortListModel *self) { - if (!self->sort_func || self->model == NULL) + if (self->sorter == NULL || self->model == NULL) return; self->sorted = g_sequence_new (g_object_unref); @@ -413,50 +433,6 @@ gtk_sort_list_model_create_sequences (GtkSortListModel *self) gtk_sort_list_model_add_items (self, 0, g_list_model_get_n_items (self->model), NULL, NULL); } -/** - * gtk_sort_list_model_set_sort_func: - * @self: a #GtkSortListModel - * @sort_func: (allow-none): sort function or %NULL to not sort items - * @user_data: (closure): user data passed to @sort_func - * @user_destroy: destroy notifier for @user_data - * - * Sets the function used to sort items. The function will be called for every - * item and must return an integer less than, equal to, or greater than zero if - * for two items from the model if the first item is considered to be respectively - * less than, equal to, or greater than the second. - **/ -void -gtk_sort_list_model_set_sort_func (GtkSortListModel *self, - GCompareDataFunc sort_func, - gpointer user_data, - GDestroyNotify user_destroy) -{ - guint n_items; - - g_return_if_fail (GTK_IS_SORT_LIST_MODEL (self)); - g_return_if_fail (sort_func != NULL || (user_data == NULL && !user_destroy)); - - if (!sort_func && !self->sort_func) - return; - - if (self->user_destroy) - self->user_destroy (self->user_data); - - g_clear_pointer (&self->unsorted, g_sequence_free); - g_clear_pointer (&self->sorted, g_sequence_free); - self->sort_func = sort_func; - self->user_data = user_data; - self->user_destroy = user_destroy; - - gtk_sort_list_model_create_sequences (self); - - n_items = g_list_model_get_n_items (G_LIST_MODEL (self)); - if (n_items > 1) - g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items); - - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HAS_SORT]); -} - /** * gtk_sort_list_model_set_model: * @self: a #GtkSortListModel @@ -517,32 +493,7 @@ gtk_sort_list_model_get_model (GtkSortListModel *self) return self->model; } -/** - * gtk_sort_list_model_has_sort: - * @self: a #GtkSortListModel - * - * Checks if a sort function is currently set on @self - * - * Returns: %TRUE if a sort function is set - **/ -gboolean -gtk_sort_list_model_has_sort (GtkSortListModel *self) -{ - g_return_val_if_fail (GTK_IS_SORT_LIST_MODEL (self), FALSE); - - return self->sort_func != NULL; -} - -/** - * gtk_sort_list_model_resort: - * @self: a #GtkSortListModel - * - * Causes @self to resort all items in the model. - * - * Calling this function is necessary when data used by the sort - * function has changed. - **/ -void +static void gtk_sort_list_model_resort (GtkSortListModel *self) { guint n_items; @@ -556,8 +507,59 @@ gtk_sort_list_model_resort (GtkSortListModel *self) if (n_items <= 1) return; - g_sequence_sort (self->sorted, self->sort_func, self->user_data); + g_sequence_sort (self->sorted, _sort_func, self->sorter); g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items); } +/** + * gtk_sort_list_model_set_sorter: + * @self: a #GtkSortListModel + * @sorter: (allow-none): the #GtkSorter to sort @model with + * + * Sets a new sorter on @self. + */ +void +gtk_sort_list_model_set_sorter (GtkSortListModel *self, + GtkSorter *sorter) +{ + guint n_items; + + g_return_if_fail (GTK_IS_SORT_LIST_MODEL (self)); + g_return_if_fail (sorter == NULL || GTK_IS_SORTER (sorter)); + + gtk_sort_list_model_clear_sorter (self); + + if (sorter) + { + self->sorter = g_object_ref (sorter); + g_signal_connect (sorter, "changed", G_CALLBACK (gtk_sort_list_model_sorter_changed_cb), self); + } + + g_clear_pointer (&self->unsorted, g_sequence_free); + g_clear_pointer (&self->sorted, g_sequence_free); + + gtk_sort_list_model_create_sequences (self); + + n_items = g_list_model_get_n_items (G_LIST_MODEL (self)); + if (n_items > 1) + g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]); +} + +/** + * gtk_sort_list_model_get_sorter: + * @self: a #GtkSortLisTModel + * + * Gets the sorter that is used to sort @self. + * + * Returns: (nullable) (transfer none): the sorter of #self + */ +GtkSorter * +gtk_sort_list_model_get_sorter (GtkSortListModel *self) +{ + g_return_val_if_fail (GTK_IS_SORT_LIST_MODEL (self), NULL); + + return self->sorter; +} diff --git a/gtk/gtksortlistmodel.h b/gtk/gtksortlistmodel.h index 2fbc465dcb..3e009502e2 100644 --- a/gtk/gtksortlistmodel.h +++ b/gtk/gtksortlistmodel.h @@ -27,6 +27,7 @@ #include #include +#include G_BEGIN_DECLS @@ -37,29 +38,23 @@ GDK_AVAILABLE_IN_ALL G_DECLARE_FINAL_TYPE (GtkSortListModel, gtk_sort_list_model, GTK, SORT_LIST_MODEL, GObject) GDK_AVAILABLE_IN_ALL -GtkSortListModel * gtk_sort_list_model_new (GListModel *model, - GCompareDataFunc sort_func, - gpointer user_data, - GDestroyNotify user_destroy); +GtkSortListModel * gtk_sort_list_model_new (GListModel *model, + GtkSorter *sorter); GDK_AVAILABLE_IN_ALL -GtkSortListModel * gtk_sort_list_model_new_for_type (GType item_type); +GtkSortListModel * gtk_sort_list_model_new_for_type (GType item_type); GDK_AVAILABLE_IN_ALL -void gtk_sort_list_model_set_sort_func (GtkSortListModel *self, - GCompareDataFunc sort_func, - gpointer user_data, - GDestroyNotify user_destroy); +void gtk_sort_list_model_set_sorter (GtkSortListModel *self, + GtkSorter *sorter); GDK_AVAILABLE_IN_ALL -gboolean gtk_sort_list_model_has_sort (GtkSortListModel *self); +GtkSorter * gtk_sort_list_model_get_sorter (GtkSortListModel *self); + GDK_AVAILABLE_IN_ALL void gtk_sort_list_model_set_model (GtkSortListModel *self, GListModel *model); GDK_AVAILABLE_IN_ALL GListModel * gtk_sort_list_model_get_model (GtkSortListModel *self); -GDK_AVAILABLE_IN_ALL -void gtk_sort_list_model_resort (GtkSortListModel *self); - G_END_DECLS #endif /* __GTK_SORT_LIST_MODEL_H__ */ diff --git a/gtk/inspector/controllers.c b/gtk/inspector/controllers.c index 64063508a6..2e926675d7 100644 --- a/gtk/inspector/controllers.c +++ b/gtk/inspector/controllers.c @@ -35,6 +35,7 @@ #include "gtkwidgetprivate.h" #include "gtkstack.h" #include "gtkstylecontext.h" +#include "gtkcustomsorter.h" enum { @@ -217,6 +218,7 @@ gtk_inspector_controllers_set_object (GtkInspectorControllers *sl, GtkMapListModel *map_model; GtkFlattenListModel *flatten_model; GtkSortListModel *sort_model; + GtkSorter *sorter; stack = gtk_widget_get_parent (GTK_WIDGET (sl)); page = gtk_stack_get_page (GTK_STACK (stack), GTK_WIDGET (sl)); @@ -237,9 +239,9 @@ gtk_inspector_controllers_set_object (GtkInspectorControllers *sl, flatten_model = gtk_flatten_list_model_new (GTK_TYPE_EVENT_CONTROLLER, G_LIST_MODEL (map_model)); - sort_model = gtk_sort_list_model_new (G_LIST_MODEL (flatten_model), - compare_controllers, - NULL, NULL); + sorter = gtk_custom_sorter_new (compare_controllers, NULL, NULL); + sort_model = gtk_sort_list_model_new (G_LIST_MODEL (flatten_model), sorter); + g_object_unref (sorter); gtk_list_box_bind_model (GTK_LIST_BOX (priv->listbox), G_LIST_MODEL (sort_model), diff --git a/testsuite/gtk/sortlistmodel.c b/testsuite/gtk/sortlistmodel.c index 1fb430aafb..e0ed7e37df 100644 --- a/testsuite/gtk/sortlistmodel.c +++ b/testsuite/gtk/sortlistmodel.c @@ -195,7 +195,13 @@ new_model (gpointer model) g_assert (model == NULL || G_IS_LIST_MODEL (model)); if (model) - result = gtk_sort_list_model_new (model, compare, NULL, NULL); + { + GtkSorter *sorter; + + sorter = gtk_custom_sorter_new (compare, NULL, NULL); + result = gtk_sort_list_model_new (model, sorter); + g_object_unref (sorter); + } else result = gtk_sort_list_model_new_for_type (G_TYPE_OBJECT); @@ -275,9 +281,10 @@ test_set_model (void) } static void -test_set_sort_func (void) +test_set_sorter (void) { GtkSortListModel *sort; + GtkSorter *sorter; GListStore *store; store = new_store ((guint[]) { 4, 8, 2, 6, 10, 0 }); @@ -285,15 +292,19 @@ test_set_sort_func (void) assert_model (sort, "2 4 6 8 10"); assert_changes (sort, ""); - gtk_sort_list_model_set_sort_func (sort, compare_modulo, GUINT_TO_POINTER (5), NULL); + sorter = gtk_custom_sorter_new (compare_modulo, GUINT_TO_POINTER (5), NULL); + gtk_sort_list_model_set_sorter (sort, sorter); + g_object_unref (sorter); assert_model (sort, "10 6 2 8 4"); assert_changes (sort, "0-5+5"); - gtk_sort_list_model_set_sort_func (sort, NULL, NULL, NULL); + gtk_sort_list_model_set_sorter (sort, NULL); assert_model (sort, "4 8 2 6 10"); assert_changes (sort, "0-5+5"); - gtk_sort_list_model_set_sort_func (sort, compare, NULL, NULL); + sorter = gtk_custom_sorter_new (compare, NULL, NULL); + gtk_sort_list_model_set_sorter (sort, sorter); + g_object_unref (sorter); assert_model (sort, "2 4 6 8 10"); /* Technically, this is correct, but we shortcut setting the sort func: * assert_changes (sort, "0-4+4"); */ @@ -395,7 +406,7 @@ main (int argc, char *argv[]) g_test_add_func ("/sortlistmodel/create_empty", test_create_empty); g_test_add_func ("/sortlistmodel/create", test_create); g_test_add_func ("/sortlistmodel/set-model", test_set_model); - g_test_add_func ("/sortlistmodel/set-sort-func", test_set_sort_func); + g_test_add_func ("/sortlistmodel/set-sorter", test_set_sorter); #if GLIB_CHECK_VERSION (2, 58, 0) /* g_list_store_splice() is broken before 2.58 */ g_test_add_func ("/sortlistmodel/add_items", test_add_items); g_test_add_func ("/sortlistmodel/remove_items", test_remove_items); From 16ab64809387c89f74606930d12ca39b6a19fa67 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 11 Dec 2019 01:11:59 +0100 Subject: [PATCH 025/170] sortlistmodel: Redo the way we store the items We need to keep this data around for changes in future commits where we make the sorting stable. An important part of the new data handling is that the unsorted list needs to always be dealt with before the sorted list - upon creation we rely on the unsorted iter and upon destruction, the sorted sequence frees the entry leaving the unsorted sequence pointer invalid. This change does not do any behavioral changes. --- gtk/gtksortlistmodel.c | 64 +++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/gtk/gtksortlistmodel.c b/gtk/gtksortlistmodel.c index fc88d6de70..2fa9de9944 100644 --- a/gtk/gtksortlistmodel.c +++ b/gtk/gtksortlistmodel.c @@ -48,6 +48,8 @@ enum { NUM_PROPERTIES }; +typedef struct _GtkSortListEntry GtkSortListEntry; + struct _GtkSortListModel { GObject parent_instance; @@ -56,8 +58,8 @@ struct _GtkSortListModel GListModel *model; GtkSorter *sorter; - GSequence *sorted; /* NULL if sort_func == NULL */ - GSequence *unsorted; /* NULL if sort_func == NULL */ + GSequence *sorted; /* NULL if known unsorted */ + GSequence *unsorted; /* NULL if known unsorted */ }; struct _GtkSortListModelClass @@ -65,8 +67,25 @@ struct _GtkSortListModelClass GObjectClass parent_class; }; +struct _GtkSortListEntry +{ + GSequenceIter *sorted_iter; + GSequenceIter *unsorted_iter; + gpointer item; /* holds ref */ +}; + static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; +static void +gtk_sort_list_entry_free (gpointer data) +{ + GtkSortListEntry *entry = data; + + g_object_unref (entry->item); + + g_slice_free (GtkSortListEntry, entry); +} + static GType gtk_sort_list_model_get_item_type (GListModel *list) { @@ -83,9 +102,6 @@ gtk_sort_list_model_get_n_items (GListModel *list) if (self->model == NULL) return 0; - if (self->sorted) - return g_sequence_get_length (self->sorted); - return g_list_model_get_n_items (self->model); } @@ -95,6 +111,7 @@ gtk_sort_list_model_get_item (GListModel *list, { GtkSortListModel *self = GTK_SORT_LIST_MODEL (list); GSequenceIter *iter; + GtkSortListEntry *entry; if (self->model == NULL) return NULL; @@ -104,9 +121,11 @@ gtk_sort_list_model_get_item (GListModel *list, iter = g_sequence_get_iter_at_pos (self->sorted, position); if (g_sequence_iter_is_end (iter)) - return NULL; + return NULL; - return g_object_ref (g_sequence_get (iter)); + entry = g_sequence_get (iter); + + return g_object_ref (entry->item); } static void @@ -135,18 +154,18 @@ gtk_sort_list_model_remove_items (GtkSortListModel *self, for (i = 0; i < n_items ; i++) { - GSequenceIter *sorted_iter; + GtkSortListEntry *entry; GSequenceIter *next; next = g_sequence_iter_next (unsorted_iter); - sorted_iter = g_sequence_get (unsorted_iter); - pos = g_sequence_iter_get_position (sorted_iter); + entry = g_sequence_get (unsorted_iter); + pos = g_sequence_iter_get_position (entry->sorted_iter); start = MIN (start, pos); end = MIN (end, length_before - i - 1 - pos); - g_sequence_remove (sorted_iter); - g_sequence_remove (unsorted_iter); + g_sequence_remove (entry->unsorted_iter); + g_sequence_remove (entry->sorted_iter); unsorted_iter = next; } @@ -160,7 +179,10 @@ _sort_func (gconstpointer item1, gconstpointer item2, gpointer data) { - return gtk_sorter_compare (GTK_SORTER (data), (gpointer)item1, (gpointer)item2); + GtkSortListEntry *entry1 = (GtkSortListEntry *) item1; + GtkSortListEntry *entry2 = (GtkSortListEntry *) item2; + + return gtk_sorter_compare (GTK_SORTER (data), entry1->item, entry2->item); } static void @@ -170,20 +192,22 @@ gtk_sort_list_model_add_items (GtkSortListModel *self, guint *unmodified_start, guint *unmodified_end) { - GSequenceIter *unsorted_iter, *sorted_iter; + GSequenceIter *unsorted_end; guint i, pos, start, end, length_before; - unsorted_iter = g_sequence_get_iter_at_pos (self->unsorted, position); + unsorted_end = g_sequence_get_iter_at_pos (self->unsorted, position); start = end = length_before = g_sequence_get_length (self->sorted); for (i = 0; i < n_items; i++) { - gpointer item = g_list_model_get_item (self->model, position + i); - sorted_iter = g_sequence_insert_sorted (self->sorted, item, _sort_func, self->sorter); - g_sequence_insert_before (unsorted_iter, sorted_iter); + GtkSortListEntry *entry = g_slice_new0 (GtkSortListEntry); + + entry->item = g_list_model_get_item (self->model, position + i); + entry->unsorted_iter = g_sequence_insert_before (unsorted_end, entry); + entry->sorted_iter = g_sequence_insert_sorted (self->sorted, entry, _sort_func, self->sorter); if (unmodified_start != NULL || unmodified_end != NULL) { - pos = g_sequence_iter_get_position (sorted_iter); + pos = g_sequence_iter_get_position (entry->sorted_iter); start = MIN (start, pos); end = MIN (end, length_before + i - pos); } @@ -427,7 +451,7 @@ gtk_sort_list_model_create_sequences (GtkSortListModel *self) if (self->sorter == NULL || self->model == NULL) return; - self->sorted = g_sequence_new (g_object_unref); + self->sorted = g_sequence_new (gtk_sort_list_entry_free); self->unsorted = g_sequence_new (NULL); gtk_sort_list_model_add_items (self, 0, g_list_model_get_n_items (self->model), NULL, NULL); From ee3faf24b9a5a206d7f5365e1957f54d048d3962 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 11 Dec 2019 01:22:20 +0100 Subject: [PATCH 026/170] sortlistmodel: Make sort stable The sort of the sortlistmodel is now stable with respect to the original list model. That means that if the sorter compares items as equal, the model will make sure those items keep the order they were in in the original model. Or in other words: The model guarantees a total order based on the item's position in the original model. --- gtk/gtksortlistmodel.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gtk/gtksortlistmodel.c b/gtk/gtksortlistmodel.c index 2fa9de9944..5444ee74ae 100644 --- a/gtk/gtksortlistmodel.c +++ b/gtk/gtksortlistmodel.c @@ -181,8 +181,14 @@ _sort_func (gconstpointer item1, { GtkSortListEntry *entry1 = (GtkSortListEntry *) item1; GtkSortListEntry *entry2 = (GtkSortListEntry *) item2; + GtkOrdering result; - return gtk_sorter_compare (GTK_SORTER (data), entry1->item, entry2->item); + result = gtk_sorter_compare (GTK_SORTER (data), entry1->item, entry2->item); + + if (result == GTK_ORDERING_EQUAL) + result = g_sequence_iter_compare (entry1->unsorted_iter, entry2->unsorted_iter); + + return result; } static void From d1b2ded7e27102bd23665e212ef12df38a64c820 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Wed, 4 Dec 2019 19:18:50 -0500 Subject: [PATCH 027/170] sorter: Add tests Some basic tests for GtkSorter. --- testsuite/gtk/meson.build | 1 + testsuite/gtk/sorter.c | 671 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 672 insertions(+) create mode 100644 testsuite/gtk/sorter.c diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build index 3688b45dfa..e0ea2edf0b 100644 --- a/testsuite/gtk/meson.build +++ b/testsuite/gtk/meson.build @@ -54,6 +54,7 @@ tests = [ ['shortcuts'], ['singleselection'], ['slicelistmodel'], + ['sorter'], ['sortlistmodel'], ['spinbutton'], ['templates'], diff --git a/testsuite/gtk/sorter.c b/testsuite/gtk/sorter.c new file mode 100644 index 0000000000..e8e3e56d6e --- /dev/null +++ b/testsuite/gtk/sorter.c @@ -0,0 +1,671 @@ +/* GtkSorter tests. + * + * Copyright (C) 2019, Red Hat, Inc. + * Authors: Benjamin Otte + * Matthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include + +#include + +static GQuark number_quark; + +static guint +get_number (GObject *object) +{ + return GPOINTER_TO_UINT (g_object_get_qdata (object, number_quark)); +} + +static guint +get (GListModel *model, + guint position) +{ + GObject *object = g_list_model_get_item (model, position); + guint number; + g_assert (object != NULL); + number = get_number (object); + g_object_unref (object); + return number; +} + +static char * +get_string (gpointer object) +{ + return g_strdup_printf ("%u", GPOINTER_TO_UINT (g_object_get_qdata (object, number_quark))); +} + +static guint +get_number_mod_5 (GObject *object) +{ + return get_number (object) % 5; +} + +static void +append_digit (GString *s, + guint digit) +{ + static const char *names[10] = { NULL, "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" }; + + if (digit == 0) + return; + + g_assert (digit < 10); + + if (s->len) + g_string_append_c (s, ' '); + g_string_append (s, names[digit]); +} + +static void +append_below_thousand (GString *s, + guint n) +{ + if (n >= 100) + { + append_digit (s, n / 100); + g_string_append (s, " hundred"); + n %= 100; + } + + if (n >= 20) + { + const char *names[10] = { NULL, NULL, "twenty", "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety" }; + if (s->len) + g_string_append_c (s, ' '); + g_string_append (s, names [n / 10]); + n %= 10; + } + + if (n >= 10) + { + const char *names[10] = { "ten", "eleven", "twelve", "thirteen", "fourteen", + "fifteen", "sixteen", "seventeen", "eighteen", "nineteen" }; + if (s->len) + g_string_append_c (s, ' '); + g_string_append (s, names [n - 10]); + } + else + { + append_digit (s, n); + } +} + +static char * +get_spelled_out (gpointer object) +{ + guint n = GPOINTER_TO_UINT (g_object_get_qdata (object, number_quark)); + GString *s; + + g_assert (n < 1000000); + + if (n == 0) + return g_strdup ("Zero"); + + s = g_string_new (NULL); + + if (n >= 1000) + { + append_below_thousand (s, n / 1000); + g_string_append (s, " thousand"); + n %= 1000; + } + + append_below_thousand (s, n); + + /* Capitalize first letter so we can do case-sensitive sorting */ + s->str[0] = g_ascii_toupper (s->str[0]); + + return g_string_free (s, FALSE); +} + +static char * +model_to_string (GListModel *model) +{ + GString *string = g_string_new (NULL); + guint i; + + for (i = 0; i < g_list_model_get_n_items (model); i++) + { + if (i > 0) + g_string_append (string, " "); + g_string_append_printf (string, "%u", get (model, i)); + } + + return g_string_free (string, FALSE); +} + +static GListStore * +new_store (guint start, + guint end, + guint step); + +static void +add (GListStore *store, + guint number) +{ + GObject *object; + + /* 0 cannot be differentiated from NULL, so don't use it */ + g_assert (number != 0); + + object = g_object_new (G_TYPE_OBJECT, NULL); + g_object_set_qdata (object, number_quark, GUINT_TO_POINTER (number)); + g_list_store_append (store, object); + g_object_unref (object); +} + +#define assert_model(model, expected) G_STMT_START{ \ + char *s = model_to_string (G_LIST_MODEL (model)); \ + if (!g_str_equal (s, expected)) \ + g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #model " == " #expected, s, "==", expected); \ + g_free (s); \ +}G_STMT_END + +#define assert_not_model(model, expected) G_STMT_START{ \ + char *s = model_to_string (G_LIST_MODEL (model)); \ + if (g_str_equal (s, expected)) \ + g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #model " != " #expected, s, "!=", expected); \ + g_free (s); \ +}G_STMT_END + +/* This could be faster by foreach()ing through the models and comparing + * the item pointers */ +#define assert_model_equal(model1, model2) G_STMT_START{\ + char *s1 = model_to_string (G_LIST_MODEL (model1)); \ + char *s2 = model_to_string (G_LIST_MODEL (model2)); \ + if (!g_str_equal (s1, s2)) \ + g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #model1 " != " #model2, s1, "==", s2); \ + g_free (s2); \ + g_free (s1); \ +}G_STMT_END + +static GListStore * +new_empty_store (void) +{ + return g_list_store_new (G_TYPE_OBJECT); +} + +static GListStore * +new_store (guint start, + guint end, + guint step) +{ + GListStore *store = new_empty_store (); + guint i; + + for (i = start; i <= end; i += step) + add (store, i); + + return store; +} + +static GListStore * +fisher_yates_shuffle (GListStore *store) +{ + int i, n; + gboolean shuffled = FALSE; + + while (!shuffled) + { + n = g_list_model_get_n_items (G_LIST_MODEL (store)); + for (i = 0; i < n; i++) + { + int pos = g_random_int_range (0, n - i); + GObject *item; + + item = g_list_model_get_item (G_LIST_MODEL (store), pos); + g_list_store_remove (store, pos); + g_list_store_append (store, item); + g_object_unref (item); + shuffled |= pos != 0; + } + } + + return store; +} + +static GtkSortListModel * +new_model (guint size, + GtkSorter *sorter) +{ + GtkSortListModel *result; + + result = gtk_sort_list_model_new (G_LIST_MODEL (fisher_yates_shuffle (new_store (1, size, 1))), sorter); + + return result; +} + +static int +compare_numbers (gconstpointer item1, + gconstpointer item2, + gpointer data) +{ + guint n1 = get_number (G_OBJECT (item1)); + guint n2 = get_number (G_OBJECT (item2)); + + return n1 - n2; +} + +static void +test_simple (void) +{ + GtkSortListModel *model; + GtkSorter *sorter; + + model = new_model (20, NULL); + assert_not_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20"); + + sorter = gtk_custom_sorter_new (compare_numbers, NULL, NULL); + gtk_sort_list_model_set_sorter (model, sorter); + g_object_unref (sorter); + + assert_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20"); + + gtk_sort_list_model_set_sorter (model, NULL); + assert_not_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20"); + + g_object_unref (model); +} + +static void +test_string (void) +{ + GtkSortListModel *model; + GtkSorter *sorter; + GtkExpression *expression; + + model = new_model (20, NULL); + assert_not_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20"); + + sorter = gtk_string_sorter_new (gtk_cclosure_expression_new (G_TYPE_STRING, NULL, 0, NULL, (GCallback)get_string, NULL, NULL)); + + gtk_sort_list_model_set_sorter (model, sorter); + g_object_unref (sorter); + + assert_model (model, "1 10 11 12 13 14 15 16 17 18 19 2 20 3 4 5 6 7 8 9"); + + expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL, 0, NULL, (GCallback)get_spelled_out, NULL, NULL); + gtk_string_sorter_set_expression (GTK_STRING_SORTER (sorter), expression); + gtk_expression_unref (expression); + + assert_model (model, "8 18 11 15 5 4 14 9 19 1 7 17 6 16 10 13 3 12 20 2"); + + gtk_string_sorter_set_expression (GTK_STRING_SORTER (sorter), NULL); + assert_not_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20"); + + g_object_unref (model); +} + +static void +inc_counter (GtkSorter *sorter, int change, gpointer data) +{ + int *counter = data; + + (*counter)++; +} + +static void +test_change (void) +{ + GtkSorter *sorter; + GtkExpression *expression; + int counter = 0; + + sorter = gtk_string_sorter_new (NULL); + g_signal_connect (sorter, "changed", G_CALLBACK (inc_counter), &counter); + + expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL, 0, NULL, (GCallback)get_string, NULL, NULL); + gtk_string_sorter_set_expression (GTK_STRING_SORTER (sorter), expression); + g_assert_cmpint (counter, ==, 1); + + gtk_string_sorter_set_expression (GTK_STRING_SORTER (sorter), expression); + g_assert_cmpint (counter, ==, 1); + + gtk_expression_unref (expression); + + gtk_string_sorter_set_ignore_case (GTK_STRING_SORTER (sorter), FALSE); + g_assert_cmpint (counter, ==, 2); + + gtk_string_sorter_set_ignore_case (GTK_STRING_SORTER (sorter), FALSE); + g_assert_cmpint (counter, ==, 2); + + g_object_unref (sorter); +} + +static void +test_numeric (void) +{ + GtkSortListModel *model; + GtkSorter *sorter; + + model = new_model (20, NULL); + assert_not_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20"); + + sorter = gtk_numeric_sorter_new (gtk_cclosure_expression_new (G_TYPE_UINT, NULL, 0, NULL, (GCallback)get_number, NULL, NULL)); + gtk_sort_list_model_set_sorter (model, sorter); + assert_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20"); + + gtk_numeric_sorter_set_sort_order (GTK_NUMERIC_SORTER (sorter), GTK_SORT_DESCENDING); + assert_model (model, "20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1"); + + gtk_numeric_sorter_set_sort_order (GTK_NUMERIC_SORTER (sorter), GTK_SORT_ASCENDING); + assert_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20"); + + gtk_numeric_sorter_set_expression (GTK_NUMERIC_SORTER (sorter), NULL); + assert_not_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20"); + + g_object_unref (sorter); + g_object_unref (model); +} + +/* sort even numbers before odd, don't care about anything else */ +static int +compare_even (gconstpointer item1, + gconstpointer item2, + gpointer data) +{ + guint n1 = get_number (G_OBJECT (item1)); + guint n2 = get_number (G_OBJECT (item2)); + int r1 = n1 % 2; + int r2 = n2 % 2; + + if (r1 == r2) + return 0; + + if (r1 == 1) + return 1; + + return -1; +} + +static void +test_multi (void) +{ + GtkSortListModel *model; + GtkSorter *sorter; + GtkSorter *sorter1; + GtkSorter *sorter2; + GtkExpression *expression; + + model = new_model (20, NULL); + assert_not_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20"); + + sorter2 = gtk_numeric_sorter_new (NULL); + gtk_sort_list_model_set_sorter (model, sorter2); + expression = gtk_cclosure_expression_new (G_TYPE_UINT, NULL, 0, NULL, (GCallback)get_number, NULL, NULL); + gtk_numeric_sorter_set_expression (GTK_NUMERIC_SORTER (sorter2), expression); + gtk_expression_unref (expression); + + assert_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20"); + + sorter = gtk_multi_sorter_new (); + gtk_sort_list_model_set_sorter (model, sorter); + + sorter1 = gtk_custom_sorter_new (compare_even, NULL, NULL); + gtk_multi_sorter_append (GTK_MULTI_SORTER (sorter), sorter1); + gtk_multi_sorter_append (GTK_MULTI_SORTER (sorter), sorter2); + + assert_model (model, "2 4 6 8 10 12 14 16 18 20 1 3 5 7 9 11 13 15 17 19"); + + /* This doesn't do anything */ + gtk_multi_sorter_remove (GTK_MULTI_SORTER (sorter), 12345); + assert_model (model, "2 4 6 8 10 12 14 16 18 20 1 3 5 7 9 11 13 15 17 19"); + + gtk_multi_sorter_remove (GTK_MULTI_SORTER (sorter), 0); + assert_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20"); + + gtk_multi_sorter_remove (GTK_MULTI_SORTER (sorter), 0); + assert_not_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20"); + + g_object_unref (model); + g_object_unref (sorter); +} + +/* Check that the multi sorter properly disconnects its changed signal */ +static void +test_multi_destruct (void) +{ + GtkSorter *multi, *sorter; + + multi = gtk_multi_sorter_new (); + sorter = gtk_numeric_sorter_new (gtk_cclosure_expression_new (G_TYPE_UINT, NULL, 0, NULL, (GCallback)get_number, NULL, NULL)); + gtk_multi_sorter_append (GTK_MULTI_SORTER (multi), g_object_ref (sorter)); + g_object_unref (multi); + + gtk_numeric_sorter_set_sort_order (GTK_NUMERIC_SORTER (sorter), GTK_SORT_DESCENDING); + g_object_unref (sorter); +} + +static void +test_multi_changes (void) +{ + GtkSortListModel *model; + GtkSorter *multi; + GtkSorter *sorter1; + GtkSorter *sorter2; + GtkSorter *sorter3; + GtkExpression *expression; + int counter = 0; + + /* We want a sorted model, so that we can be sure partial sorts do the right thing */ + model = gtk_sort_list_model_new (G_LIST_MODEL (new_store (1, 20, 1)), NULL); + assert_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20"); + + multi = gtk_multi_sorter_new (); + g_signal_connect (multi, "changed", G_CALLBACK (inc_counter), &counter); + gtk_sort_list_model_set_sorter (model, multi); + assert_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20"); + g_assert_cmpint (counter, ==, 0); + + sorter1 = gtk_numeric_sorter_new (NULL); + gtk_multi_sorter_append (GTK_MULTI_SORTER (multi), sorter1); + assert_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20"); + g_assert_cmpint (counter, ==, 1); + + expression = gtk_cclosure_expression_new (G_TYPE_UINT, NULL, 0, NULL, (GCallback)get_number_mod_5, NULL, NULL); + gtk_numeric_sorter_set_expression (GTK_NUMERIC_SORTER (sorter1), expression); + gtk_expression_unref (expression); + assert_model (model, "5 10 15 20 1 6 11 16 2 7 12 17 3 8 13 18 4 9 14 19"); + g_assert_cmpint (counter, ==, 2); + + gtk_numeric_sorter_set_sort_order (GTK_NUMERIC_SORTER (sorter1), GTK_SORT_DESCENDING); + assert_model (model, "4 9 14 19 3 8 13 18 2 7 12 17 1 6 11 16 5 10 15 20"); + g_assert_cmpint (counter, ==, 3); + + sorter2 = gtk_custom_sorter_new (compare_even, NULL, NULL); + gtk_multi_sorter_append (GTK_MULTI_SORTER (multi), sorter2); + assert_model (model, "4 14 9 19 8 18 3 13 2 12 7 17 6 16 1 11 10 20 5 15"); + g_assert_cmpint (counter, ==, 4); + + gtk_numeric_sorter_set_sort_order (GTK_NUMERIC_SORTER (sorter1), GTK_SORT_ASCENDING); + assert_model (model, "10 20 5 15 6 16 1 11 2 12 7 17 8 18 3 13 4 14 9 19"); + g_assert_cmpint (counter, ==, 5); + + sorter3 = gtk_string_sorter_new (gtk_cclosure_expression_new (G_TYPE_STRING, NULL, 0, NULL, (GCallback)get_spelled_out, NULL, NULL)); + gtk_multi_sorter_append (GTK_MULTI_SORTER (multi), sorter3); + assert_model (model, "10 20 15 5 6 16 11 1 12 2 7 17 8 18 13 3 4 14 9 19"); + g_assert_cmpint (counter, ==, 6); + + gtk_multi_sorter_remove (GTK_MULTI_SORTER (multi), 1); + assert_model (model, "15 5 10 20 11 1 6 16 7 17 12 2 8 18 13 3 4 14 9 19"); + g_assert_cmpint (counter, ==, 7); + + gtk_multi_sorter_remove (GTK_MULTI_SORTER (multi), 1); + assert_model (model, "5 10 15 20 1 6 11 16 2 7 12 17 3 8 13 18 4 9 14 19"); + g_assert_cmpint (counter, ==, 8); + + gtk_multi_sorter_remove (GTK_MULTI_SORTER (multi), 0); + assert_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20"); + g_assert_cmpint (counter, ==, 9); + + g_object_unref (multi); + g_object_unref (model); +} + +static GtkSorter * +even_odd_sorter_new (void) +{ + return gtk_custom_sorter_new (compare_even, NULL, NULL); +} + +static GtkSorter * +numeric_sorter_new (void) +{ + return gtk_numeric_sorter_new (gtk_cclosure_expression_new (G_TYPE_UINT, NULL, 0, NULL, (GCallback)get_number, NULL, NULL)); +} + +static void +switch_order (GtkSorter *sorter) +{ + if (gtk_numeric_sorter_get_sort_order (GTK_NUMERIC_SORTER (sorter)) == GTK_SORT_ASCENDING) + gtk_numeric_sorter_set_sort_order (GTK_NUMERIC_SORTER (sorter), GTK_SORT_DESCENDING); + else + gtk_numeric_sorter_set_sort_order (GTK_NUMERIC_SORTER (sorter), GTK_SORT_ASCENDING); +} + +static void +set_order_ascending (GtkSorter *sorter) +{ + gtk_numeric_sorter_set_sort_order (GTK_NUMERIC_SORTER (sorter), GTK_SORT_ASCENDING); +} + +static void +set_order_descending (GtkSorter *sorter) +{ + gtk_numeric_sorter_set_sort_order (GTK_NUMERIC_SORTER (sorter), GTK_SORT_DESCENDING); +} + +static void +set_expression_get_number (GtkSorter *sorter) +{ + GtkExpression *expression = gtk_cclosure_expression_new (G_TYPE_UINT, NULL, 0, NULL, (GCallback)get_number, NULL, NULL); + gtk_numeric_sorter_set_expression (GTK_NUMERIC_SORTER (sorter), expression); + gtk_expression_unref (expression); +} + +static void +set_expression_get_number_mod_5 (GtkSorter *sorter) +{ + GtkExpression *expression = gtk_cclosure_expression_new (G_TYPE_UINT, NULL, 0, NULL, (GCallback)get_number, NULL, NULL); + gtk_numeric_sorter_set_expression (GTK_NUMERIC_SORTER (sorter), expression); + gtk_expression_unref (expression); +} + +static void +modify_sorter (GtkSorter *multi) +{ + struct { + GType type; + GtkSorter * (* create_func) (void); + void (* modify_func) (GtkSorter *); + } options[] = { + { GTK_TYPE_CUSTOM_SORTER, even_odd_sorter_new, NULL }, + { GTK_TYPE_NUMERIC_SORTER, numeric_sorter_new, switch_order }, + { GTK_TYPE_NUMERIC_SORTER, numeric_sorter_new, set_order_ascending }, + { GTK_TYPE_NUMERIC_SORTER, numeric_sorter_new, set_order_descending }, + { GTK_TYPE_NUMERIC_SORTER, numeric_sorter_new, set_expression_get_number, }, + { GTK_TYPE_NUMERIC_SORTER, numeric_sorter_new, set_expression_get_number_mod_5, } + }; + GtkSorter *current; + guint option; + + current = g_list_model_get_item (G_LIST_MODEL (multi), 0); + option = g_random_int_range (0, G_N_ELEMENTS (options)); + + if (current == NULL || options[option].type != G_OBJECT_TYPE (current) || options[option].modify_func == NULL) + { + g_clear_object (¤t); + gtk_multi_sorter_remove (GTK_MULTI_SORTER (multi), 0); + + current = options[option].create_func (); + if (options[option].modify_func) + options[option].modify_func (current); + + gtk_multi_sorter_append (GTK_MULTI_SORTER (multi), current); + } + else + { + options[option].modify_func (current); + } +} + +static void +test_stable (void) +{ + GtkSortListModel *model1, *model2, *model2b; + GtkSorter *multi, *a, *b; + guint i; + + a = gtk_multi_sorter_new (); + b = gtk_multi_sorter_new (); + /* We create 2 setups: + * 1. sortmodel (multisorter [a, b]) + * 2. sortmodel (b) => sortmodel (a) + * Given stability of the sort, these 2 setups should always produce the + * same results, namely the list should be sorter by a before it's sorted + * by b. + * + * All we do is make a and b random sorters and assert that the 2 setups + * produce the same order every time. + */ + multi = gtk_multi_sorter_new (); + gtk_multi_sorter_append (GTK_MULTI_SORTER (multi), a); + gtk_multi_sorter_append (GTK_MULTI_SORTER (multi), b); + model1 = new_model (20, multi); + g_object_unref (multi); + model2b = gtk_sort_list_model_new (gtk_sort_list_model_get_model (model1), b); + model2 = gtk_sort_list_model_new (G_LIST_MODEL (model2b), a); + assert_model_equal (model1, model2); + + modify_sorter (a); + assert_model_equal (model1, model2); + modify_sorter (b); + assert_model_equal (model1, model2); + + for (i = 0; i < 100; i++) + { + modify_sorter (g_random_boolean () ? a : b); + assert_model_equal (model1, model2); + } + + g_object_unref (model1); + g_object_unref (model2); + g_object_unref (model2b); +} + +int +main (int argc, char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + setlocale (LC_ALL, "C"); + + number_quark = g_quark_from_static_string ("Like a trashcan fire in a prison cell"); + + g_test_add_func ("/sorter/simple", test_simple); + g_test_add_func ("/sorter/string", test_string); + g_test_add_func ("/sorter/change", test_change); + g_test_add_func ("/sorter/numeric", test_numeric); + g_test_add_func ("/sorter/multi", test_multi); + g_test_add_func ("/sorter/multi-destruct", test_multi_destruct); + g_test_add_func ("/sorter/multi-changes", test_multi_changes); + g_test_add_func ("/sorter/stable", test_stable); + + return g_test_run (); +} From d3dc9c41b409551596d079fa47ea43c066e5e09b Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 25 Nov 2019 07:28:19 +0100 Subject: [PATCH 028/170] gtk-demo: Make fishbowl info text use bindings It's a good demo for how bindings can format multiple properties into an informative string with 1 line of code (and 5 lines of XML). --- demos/gtk-demo/fishbowl.c | 8 ++++++++ demos/gtk-demo/fishbowl.ui | 22 ++++++---------------- demos/gtk-demo/gtkfishbowl.c | 17 ----------------- 3 files changed, 14 insertions(+), 33 deletions(-) diff --git a/demos/gtk-demo/fishbowl.c b/demos/gtk-demo/fishbowl.c index 6cc2dfc1a2..bc3848fb18 100644 --- a/demos/gtk-demo/fishbowl.c +++ b/demos/gtk-demo/fishbowl.c @@ -249,6 +249,14 @@ fishbowl_changes_toggled_cb (GtkToggleButton *button, gtk_button_set_icon_name (GTK_BUTTON (button), "changes-allow"); } +char * +format_header_cb (GObject *object, + guint count, + double fps) +{ + return g_strdup_printf ("%u Icons, %.2f fps", count, fps); +} + GtkWidget * do_fishbowl (GtkWidget *do_widget) { diff --git a/demos/gtk-demo/fishbowl.ui b/demos/gtk-demo/fishbowl.ui index fcf5d38fa4..b174e416fb 100644 --- a/demos/gtk-demo/fishbowl.ui +++ b/demos/gtk-demo/fishbowl.ui @@ -28,22 +28,12 @@ - fps - - - - - - - - - - Icons, - - - - - + + + bowl + bowl + + diff --git a/demos/gtk-demo/gtkfishbowl.c b/demos/gtk-demo/gtkfishbowl.c index 0d9f85f124..58c2c5a182 100644 --- a/demos/gtk-demo/gtkfishbowl.c +++ b/demos/gtk-demo/gtkfishbowl.c @@ -53,7 +53,6 @@ enum { PROP_BENCHMARK, PROP_COUNT, PROP_FRAMERATE, - PROP_FRAMERATE_STRING, PROP_UPDATE_DELAY, NUM_PROPERTIES }; @@ -289,14 +288,6 @@ gtk_fishbowl_get_property (GObject *object, g_value_set_double (value, gtk_fishbowl_get_framerate (fishbowl)); break; - case PROP_FRAMERATE_STRING: - { - char *s = g_strdup_printf ("%.2f", gtk_fishbowl_get_framerate (fishbowl)); - g_value_set_string (value, s); - g_free (s); - } - break; - case PROP_UPDATE_DELAY: g_value_set_int64 (value, gtk_fishbowl_get_update_delay (fishbowl)); break; @@ -350,13 +341,6 @@ gtk_fishbowl_class_init (GtkFishbowlClass *klass) 0, G_PARAM_READABLE); - props[PROP_FRAMERATE_STRING] = - g_param_spec_string ("framerate-string", - "Framerate as string", - "Framerate as string, with 2 decimals", - NULL, - G_PARAM_READABLE); - props[PROP_UPDATE_DELAY] = g_param_spec_int64 ("update-delay", "Update delay", @@ -508,7 +492,6 @@ gtk_fishbowl_do_update (GtkFishbowl *fishbowl) priv->framerate = ((int)(priv->framerate * 100))/100.0; g_object_notify_by_pspec (G_OBJECT (fishbowl), props[PROP_FRAMERATE]); - g_object_notify_by_pspec (G_OBJECT (fishbowl), props[PROP_FRAMERATE_STRING]); if (!priv->benchmark) return; From ed22af50bca0c82e0b107382dbef6e51f2bf0477 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 25 Nov 2019 08:01:31 +0100 Subject: [PATCH 029/170] builder: Make type optional If no type is set, use the type of the expression. --- gtk/gtkbuilderparser.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/gtk/gtkbuilderparser.c b/gtk/gtkbuilderparser.c index 00e856f05a..98c7ee6003 100644 --- a/gtk/gtkbuilderparser.c +++ b/gtk/gtkbuilderparser.c @@ -1144,7 +1144,7 @@ parse_lookup_expression (ParserData *data, { ExpressionInfo *info; const char *property_name; - const char *type_name; + const char *type_name = NULL; GType type; if (!check_expression_parent (data)) @@ -1154,7 +1154,7 @@ parse_lookup_expression (ParserData *data, } if (!g_markup_collect_attributes (element_name, names, values, error, - G_MARKUP_COLLECT_STRING, "type", &type_name, + G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "type", &type_name, G_MARKUP_COLLECT_STRING, "name", &property_name, G_MARKUP_COLLECT_INVALID)) { @@ -1162,15 +1162,22 @@ parse_lookup_expression (ParserData *data, return; } - type = gtk_builder_get_type_from_name (data->builder, type_name); - if (type == G_TYPE_INVALID) + if (type_name == NULL) { - g_set_error (error, - GTK_BUILDER_ERROR, - GTK_BUILDER_ERROR_INVALID_VALUE, - "Invalid type '%s'", type_name); - _gtk_builder_prefix_error (data->builder, &data->ctx, error); - return; + type = G_TYPE_INVALID; + } + else + { + type = gtk_builder_get_type_from_name (data->builder, type_name); + if (type == G_TYPE_INVALID) + { + g_set_error (error, + GTK_BUILDER_ERROR, + GTK_BUILDER_ERROR_INVALID_VALUE, + "Invalid type '%s'", type_name); + _gtk_builder_prefix_error (data->builder, &data->ctx, error); + return; + } } info = g_slice_new0 (ExpressionInfo); From 448a88e4f5dc2117a1e2aa4de5a10abbe57b1587 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 25 Nov 2019 08:04:24 +0100 Subject: [PATCH 030/170] builder: Allow without a type A constant without a type is assumed to be an object. This is the most common case and allows foo without requiring updates to the type whenever the foo object changes. --- gtk/gtkbuilderparser.c | 60 +++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/gtk/gtkbuilderparser.c b/gtk/gtkbuilderparser.c index 98c7ee6003..28247b0da7 100644 --- a/gtk/gtkbuilderparser.c +++ b/gtk/gtkbuilderparser.c @@ -1036,7 +1036,7 @@ parse_constant_expression (ParserData *data, GError **error) { ExpressionInfo *info; - const char *type_name; + const char *type_name = NULL; GType type; if (!check_expression_parent (data)) @@ -1046,22 +1046,27 @@ parse_constant_expression (ParserData *data, } if (!g_markup_collect_attributes (element_name, names, values, error, - G_MARKUP_COLLECT_STRING, "type", &type_name, + G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "type", &type_name, G_MARKUP_COLLECT_INVALID)) { _gtk_builder_prefix_error (data->builder, &data->ctx, error); return; } - type = gtk_builder_get_type_from_name (data->builder, type_name); - if (type == G_TYPE_INVALID) + if (type_name == NULL) + type = G_TYPE_INVALID; + else { - g_set_error (error, - GTK_BUILDER_ERROR, - GTK_BUILDER_ERROR_INVALID_VALUE, - "Invalid type '%s'", type_name); - _gtk_builder_prefix_error (data->builder, &data->ctx, error); - return; + type = gtk_builder_get_type_from_name (data->builder, type_name); + if (type == G_TYPE_INVALID) + { + g_set_error (error, + GTK_BUILDER_ERROR, + GTK_BUILDER_ERROR_INVALID_VALUE, + "Invalid type '%s'", type_name); + _gtk_builder_prefix_error (data->builder, &data->ctx, error); + return; + } } info = g_slice_new0 (ExpressionInfo); @@ -1202,20 +1207,33 @@ expression_info_construct (GtkBuilder *builder, case EXPRESSION_CONSTANT: { GtkExpression *expr; - GValue value = G_VALUE_INIT; - if (!gtk_builder_value_from_string_type (builder, - info->constant.type, - info->constant.text->str, - &value, - error)) - return NULL; + if (info->constant.type == G_TYPE_INVALID) + { + GObject *o = gtk_builder_lookup_object (builder, info->constant.text->str, 0, 0, error); + if (o == NULL) + return NULL; - if (G_VALUE_HOLDS_OBJECT (&value)) - expr = gtk_object_expression_new (g_value_get_object (&value)); + expr = gtk_object_expression_new (o); + } else - expr = gtk_constant_expression_new_for_value (&value); - g_value_unset (&value); + { + GValue value = G_VALUE_INIT; + + if (!gtk_builder_value_from_string_type (builder, + info->constant.type, + info->constant.text->str, + &value, + error)) + return NULL; + + if (G_VALUE_HOLDS_OBJECT (&value)) + expr = gtk_object_expression_new (g_value_get_object (&value)); + else + expr = gtk_constant_expression_new_for_value (&value); + + g_value_unset (&value); + } g_string_free (info->constant.text, TRUE); info->expression_type = EXPRESSION_EXPRESSION; From 713a6676ff3789235b7e5ab0d15f9e3376af0972 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 25 Nov 2019 08:06:45 +0100 Subject: [PATCH 031/170] builder: Allow text content in foo is now short for foo ie it looks up the object with the given name so it can then do a property lookup with it. This is the most common operation, so it's a nice shortcut. --- gtk/gtkbuilderparser.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/gtk/gtkbuilderparser.c b/gtk/gtkbuilderparser.c index 28247b0da7..0b98d68ae6 100644 --- a/gtk/gtkbuilderparser.c +++ b/gtk/gtkbuilderparser.c @@ -1961,6 +1961,27 @@ text (GtkBuildableParseContext *context, g_string_append_len (expr_info->constant.text, text, text_len); } + else if (strcmp (gtk_buildable_parse_context_get_element (context), "lookup") == 0) + { + ExpressionInfo *expr_info = (ExpressionInfo *) info; + + while (g_ascii_isspace (*text) && text_len > 0) + { + text++; + text_len--; + } + while (text_len > 0 && g_ascii_isspace (text[text_len - 1])) + text_len--; + if (expr_info->property.expression == NULL && text_len > 0) + { + ExpressionInfo *constant = g_slice_new0 (ExpressionInfo); + constant->tag_type = TAG_EXPRESSION; + constant->expression_type = EXPRESSION_CONSTANT; + constant->constant.type = G_TYPE_INVALID; + constant->constant.text = g_string_new_len (text, text_len); + expr_info->property.expression = constant; + } + } } static void From 934bfc8887bc62e3f8131b691d889236ca32e6f6 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 25 Nov 2019 08:15:31 +0100 Subject: [PATCH 032/170] builder: Add tag The tag contains an expression that it then gtk_expression_bind()s to the object it is contained in. --- gtk/gtkbuilder.c | 66 ++++++++++++------- gtk/gtkbuilderparser.c | 141 ++++++++++++++++++++++++++++++++++++++-- gtk/gtkbuilderprivate.h | 20 +++++- 3 files changed, 200 insertions(+), 27 deletions(-) diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c index 244f8e8a9a..5b5a66eb90 100644 --- a/gtk/gtkbuilder.c +++ b/gtk/gtkbuilder.c @@ -219,6 +219,7 @@ #include "gtkbuildable.h" #include "gtkbuilderscopeprivate.h" #include "gtkdebug.h" +#include "gtkexpression.h" #include "gtkmain.h" #include "gtkicontheme.h" #include "gtkintl.h" @@ -685,8 +686,22 @@ gtk_builder_take_bindings (GtkBuilder *builder, for (l = bindings; l; l = l->next) { - BindingInfo *info = l->data; - info->target = target; + CommonInfo *common_info = l->data; + + if (common_info->tag_type == TAG_BINDING) + { + BindingInfo *info = l->data; + info->target = target; + } + else if (common_info->tag_type == TAG_BINDING_EXPRESSION) + { + BindingExpressionInfo *info = l->data; + info->target = target; + } + else + { + g_assert_not_reached (); + } } priv->bindings = g_slist_concat (priv->bindings, bindings); @@ -1013,17 +1028,6 @@ gtk_builder_apply_delayed_properties (GtkBuilder *builder, return result; } -static inline void -free_binding_info (gpointer data, - gpointer user) -{ - BindingInfo *info = data; - - g_free (info->source); - g_free (info->source_property); - g_slice_free (BindingInfo, data); -} - static inline gboolean gtk_builder_create_bindings (GtkBuilder *builder, GError **error) @@ -1034,26 +1038,44 @@ gtk_builder_create_bindings (GtkBuilder *builder, for (l = priv->bindings; l; l = l->next) { - BindingInfo *info = l->data; - GObject *source; + CommonInfo *common_info = l->data; - if (result) + if (common_info->tag_type == TAG_BINDING) { - source = gtk_builder_lookup_object (builder, info->source, info->line, info->col, error); + BindingInfo *info = l->data; + GObject *source; + + source = _gtk_builder_lookup_object (builder, info->source, info->line, info->col); if (source) g_object_bind_property (source, info->source_property, info->target, info->target_pspec->name, info->flags); - else - result = FALSE; - } - free_binding_info (info, NULL); + _free_binding_info (info, NULL); + } + else if (common_info->tag_type == TAG_BINDING_EXPRESSION) + { + BindingExpressionInfo *info = l->data; + GtkExpression *expression; + + expression = expression_info_construct (builder, info->expr, error); + if (expression == NULL) + { + g_prefix_error (error, "%s:%d:%d: ", priv->filename, info->line, info->col); + error = NULL; + result = FALSE; + } + else + { + gtk_expression_bind (expression, info->target, info->target_pspec->name); + } + + free_binding_expression_info (info); + } } g_slist_free (priv->bindings); priv->bindings = NULL; - return result; } diff --git a/gtk/gtkbuilderparser.c b/gtk/gtkbuilderparser.c index 0b98d68ae6..d6e4b87e95 100644 --- a/gtk/gtkbuilderparser.c +++ b/gtk/gtkbuilderparser.c @@ -913,7 +913,8 @@ parse_property (ParserData *data, { BindingInfo *binfo; - binfo = g_slice_new (BindingInfo); + binfo = g_slice_new0 (BindingInfo); + binfo->tag_type = TAG_BINDING; binfo->target = NULL; binfo->target_pspec = pspec; binfo->source = g_strdup (bind_source); @@ -945,6 +946,78 @@ parse_property (ParserData *data, state_push (data, info); } +static void +parse_binding (ParserData *data, + const gchar *element_name, + const gchar **names, + const gchar **values, + GError **error) +{ + BindingExpressionInfo *info; + const gchar *name = NULL; + ObjectInfo *object_info; + GParamSpec *pspec = NULL; + + object_info = state_peek_info (data, ObjectInfo); + if (!object_info || + !(object_info->tag_type == TAG_OBJECT || + object_info->tag_type == TAG_TEMPLATE)) + { + error_invalid_tag (data, element_name, NULL, error); + return; + } + + if (!g_markup_collect_attributes (element_name, names, values, error, + G_MARKUP_COLLECT_STRING, "name", &name, + G_MARKUP_COLLECT_INVALID)) + { + _gtk_builder_prefix_error (data->builder, &data->ctx, error); + return; + } + + pspec = g_object_class_find_property (object_info->oclass, name); + + if (!pspec) + { + g_set_error (error, + GTK_BUILDER_ERROR, + GTK_BUILDER_ERROR_INVALID_PROPERTY, + "Invalid property: %s.%s", + g_type_name (object_info->type), name); + _gtk_builder_prefix_error (data->builder, &data->ctx, error); + return; + } + else if (pspec->flags & G_PARAM_CONSTRUCT_ONLY) + { + g_set_error (error, + GTK_BUILDER_ERROR, + GTK_BUILDER_ERROR_INVALID_PROPERTY, + "%s.%s is a construct-only property", + g_type_name (object_info->type), name); + _gtk_builder_prefix_error (data->builder, &data->ctx, error); + return; + } + else if (!(pspec->flags & G_PARAM_WRITABLE)) + { + g_set_error (error, + GTK_BUILDER_ERROR, + GTK_BUILDER_ERROR_INVALID_PROPERTY, + "%s.%s is a non-writable property", + g_type_name (object_info->type), name); + _gtk_builder_prefix_error (data->builder, &data->ctx, error); + return; + } + + + info = g_slice_new0 (BindingExpressionInfo); + info->tag_type = TAG_BINDING_EXPRESSION; + info->target = NULL; + info->target_pspec = pspec; + gtk_buildable_parse_context_get_position (&data->ctx, &info->line, &info->col); + + state_push (data, info); +} + static void free_property_info (PropertyInfo *info) { @@ -1005,8 +1078,13 @@ check_expression_parent (ParserData *data) return G_PARAM_SPEC_VALUE_TYPE (prop_info->pspec) == GTK_TYPE_EXPRESSION; } + else if (common_info->tag_type == TAG_BINDING_EXPRESSION) + { + BindingExpressionInfo *expr_info = (BindingExpressionInfo *) common_info; - if (common_info->tag_type == TAG_EXPRESSION) + return expr_info->expr == NULL; + } + else if (common_info->tag_type == TAG_EXPRESSION) { ExpressionInfo *expr_info = (ExpressionInfo *) common_info; @@ -1194,7 +1272,7 @@ parse_lookup_expression (ParserData *data, state_push (data, info); } -static GtkExpression * +GtkExpression * expression_info_construct (GtkBuilder *builder, ExpressionInfo *info, GError **error) @@ -1446,6 +1524,23 @@ _free_signal_info (SignalInfo *info, g_slice_free (SignalInfo, info); } +void +_free_binding_info (BindingInfo *info, + gpointer user) +{ + g_free (info->source); + g_free (info->source_property); + g_slice_free (BindingInfo, info); +} + +void +free_binding_expression_info (BindingExpressionInfo *info) +{ + if (info->expr) + free_expression_info (info->expr); + g_slice_free (BindingExpressionInfo, info); +} + static void free_requires_info (RequiresInfo *info, gpointer user_data) @@ -1686,6 +1781,8 @@ start_element (GtkBuildableParseContext *context, } else if (strcmp (element_name, "property") == 0) parse_property (data, element_name, names, values, error); + else if (strcmp (element_name, "binding") == 0) + parse_binding (data, element_name, names, values, error); else if (strcmp (element_name, "child") == 0) parse_child (data, element_name, names, values, error); else if (strcmp (element_name, "signal") == 0) @@ -1777,6 +1874,30 @@ end_element (GtkBuildableParseContext *context, else g_assert_not_reached (); } + else if (strcmp (element_name, "binding") == 0) + { + BindingExpressionInfo *binfo = state_pop_info (data, BindingExpressionInfo); + CommonInfo *info = state_peek_info (data, CommonInfo); + + g_assert (info != NULL); + + if (binfo->expr == NULL) + { + g_set_error (error, + GTK_BUILDER_ERROR, + GTK_BUILDER_ERROR_INVALID_TAG, + "Binding tag requires an expression"); + free_binding_expression_info (binfo); + } + else if (info->tag_type == TAG_OBJECT || + info->tag_type == TAG_TEMPLATE) + { + ObjectInfo *object_info = (ObjectInfo*)info; + object_info->bindings = g_slist_prepend (object_info->bindings, binfo); + } + else + g_assert_not_reached (); + } else if (strcmp (element_name, "object") == 0 || strcmp (element_name, "template") == 0) { @@ -1845,7 +1966,13 @@ end_element (GtkBuildableParseContext *context, CommonInfo *parent_info = state_peek_info (data, CommonInfo); g_assert (parent_info != NULL); - if (parent_info->tag_type == TAG_PROPERTY) + if (parent_info->tag_type == TAG_BINDING_EXPRESSION) + { + BindingExpressionInfo *expr_info = (BindingExpressionInfo *) parent_info; + + expr_info->expr = expression_info; + } + else if (parent_info->tag_type == TAG_PROPERTY) { PropertyInfo *prop_info = (PropertyInfo *) parent_info; @@ -1996,6 +2123,12 @@ free_info (CommonInfo *info) case TAG_CHILD: free_child_info ((ChildInfo *)info); break; + case TAG_BINDING: + _free_binding_info ((BindingInfo *)info, NULL); + break; + case TAG_BINDING_EXPRESSION: + free_binding_expression_info ((BindingExpressionInfo *) info); + break; case TAG_PROPERTY: free_property_info ((PropertyInfo *)info); break; diff --git a/gtk/gtkbuilderprivate.h b/gtk/gtkbuilderprivate.h index 32227c10f1..506a082887 100644 --- a/gtk/gtkbuilderprivate.h +++ b/gtk/gtkbuilderprivate.h @@ -25,7 +25,8 @@ enum { TAG_PROPERTY, - TAG_MENU, + TAG_BINDING, + TAG_BINDING_EXPRESSION, TAG_REQUIRES, TAG_OBJECT, TAG_CHILD, @@ -117,6 +118,7 @@ typedef struct { typedef struct { + guint tag_type; GObject *target; GParamSpec *target_pspec; gchar *source; @@ -126,6 +128,16 @@ typedef struct gint col; } BindingInfo; +typedef struct +{ + guint tag_type; + GObject *target; + GParamSpec *target_pspec; + ExpressionInfo *expr; + gint line; + gint col; +} BindingExpressionInfo; + typedef struct { guint tag_type; gchar *library; @@ -212,6 +224,12 @@ gboolean _gtk_builder_finish (GtkBuilder *builder, GError **error); void _free_signal_info (SignalInfo *info, gpointer user_data); +void _free_binding_info (BindingInfo *info, + gpointer user_data); +void free_binding_expression_info (BindingExpressionInfo *info); +GtkExpression * expression_info_construct (GtkBuilder *builder, + ExpressionInfo *info, + GError **error); /* Internal API which might be made public at some point */ gboolean _gtk_builder_boolean_from_string (const gchar *string, From e19c4a33728954f9c00ddd43635fa339249b3ad6 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sun, 16 Sep 2018 20:52:06 +0200 Subject: [PATCH 033/170] gtk: Add a GtkListView skeleton --- docs/reference/gtk/gtk4-docs.xml | 1 + docs/reference/gtk/gtk4-sections.txt | 18 ++ docs/reference/gtk/gtk4.types.in | 1 + gtk/gtk.h | 1 + gtk/gtklistview.c | 260 +++++++++++++++++++++++++++ gtk/gtklistview.h | 48 +++++ gtk/meson.build | 2 + 7 files changed, 331 insertions(+) create mode 100644 gtk/gtklistview.c create mode 100644 gtk/gtklistview.h diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index 979ec7192a..b5b87e3902 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -112,6 +112,7 @@ + diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 1765f97f71..beb6b2df87 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -392,6 +392,24 @@ gtk_single_selection_set_can_unselect gtk_single_selection_get_type
+
+gtklistview +GtkListView +GtkListView +gtk_list_view_new +gtk_list_view_set_model +gtk_list_view_get_model + +GTK_LIST_VIEW +GTK_LIST_VIEW_CLASS +GTK_LIST_VIEW_GET_CLASS +GTK_IS_LIST_VIEW +GTK_IS_LIST_VIEW_CLASS +GTK_TYPE_LIST_VIEW + +gtk_list_view_get_type +
+
gtkbuildable GtkBuildable diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in index 0b74d93159..6ada01a6b7 100644 --- a/docs/reference/gtk/gtk4.types.in +++ b/docs/reference/gtk/gtk4.types.in @@ -122,6 +122,7 @@ gtk_link_button_get_type gtk_list_store_get_type gtk_list_box_get_type gtk_list_box_row_get_type +gtk_list_view_get_type gtk_lock_button_get_type gtk_map_list_model_get_type gtk_media_controls_get_type diff --git a/gtk/gtk.h b/gtk/gtk.h index e2959a2609..680efe4581 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -154,6 +154,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c new file mode 100644 index 0000000000..d5f2ca3841 --- /dev/null +++ b/gtk/gtklistview.c @@ -0,0 +1,260 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gtklistview.h" + +#include "gtkintl.h" +#include "gtkprivate.h" + +/** + * SECTION:gtklistview + * @title: GtkListView + * @short_description: A widget for displaying lists + * @see_also: #GListModel + * + * GtkListView is a widget to present a view into a large dynamic list of items. + */ + +struct _GtkListView +{ + GtkWidget parent_instance; + + GListModel *model; +}; + +enum +{ + PROP_0, + PROP_MODEL, + + N_PROPS +}; + +G_DEFINE_TYPE (GtkListView, gtk_list_view, GTK_TYPE_WIDGET) + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +static gboolean +gtk_list_view_is_empty (GtkListView *self) +{ + return self->model == NULL; +} + +static void +gtk_list_view_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + GtkListView *self = GTK_LIST_VIEW (widget); + + if (gtk_list_view_is_empty (self)) + { + *minimum = 0; + *natural = 0; + return; + } + + *minimum = 0; + *natural = 0; + return; +} + +static void +gtk_list_view_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + //GtkListView *self = GTK_LIST_VIEW (widget); +} + +static void +gtk_list_view_model_items_changed_cb (GListModel *model, + guint position, + guint removed, + guint added, + GtkListView *self) +{ +} + +static void +gtk_list_view_clear_model (GtkListView *self) +{ + if (self->model == NULL) + return; + + g_signal_handlers_disconnect_by_func (self->model, gtk_list_view_model_items_changed_cb, self); + g_clear_object (&self->model); +} + +static void +gtk_list_view_dispose (GObject *object) +{ + GtkListView *self = GTK_LIST_VIEW (object); + + gtk_list_view_clear_model (self); + + G_OBJECT_CLASS (gtk_list_view_parent_class)->dispose (object); +} + +static void +gtk_list_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GtkListView *self = GTK_LIST_VIEW (object); + + switch (property_id) + { + case PROP_MODEL: + g_value_set_object (value, self->model); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gtk_list_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkListView *self = GTK_LIST_VIEW (object); + + switch (property_id) + { + case PROP_MODEL: + gtk_list_view_set_model (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gtk_list_view_class_init (GtkListViewClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + widget_class->measure = gtk_list_view_measure; + widget_class->size_allocate = gtk_list_view_size_allocate; + + gobject_class->dispose = gtk_list_view_dispose; + gobject_class->get_property = gtk_list_view_get_property; + gobject_class->set_property = gtk_list_view_set_property; + + /** + * GtkListView:model: + * + * Model for the items displayed + */ + properties[PROP_MODEL] = + g_param_spec_object ("model", + P_("Model"), + P_("Model for the items displayed"), + G_TYPE_LIST_MODEL, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPS, properties); + + gtk_widget_class_set_css_name (widget_class, I_("list")); +} + +static void +gtk_list_view_init (GtkListView *self) +{ +} + +/** + * gtk_list_view_new: + * + * Creates a new empty #GtkListView. + * + * You most likely want to call gtk_list_view_set_model() to set + * a model and then set up a way to map its items to widgets next. + * + * Returns: a new #GtkListView + **/ +GtkWidget * +gtk_list_view_new (void) +{ + return g_object_new (GTK_TYPE_LIST_VIEW, NULL); +} + +/** + * gtk_list_view_get_model: + * @self: a #GtkListView + * + * Gets the model that's currently used to read the items displayed. + * + * Returns: (nullable) (transfer none): The model in use + **/ +GListModel * +gtk_list_view_get_model (GtkListView *self) +{ + g_return_val_if_fail (GTK_IS_LIST_VIEW (self), NULL); + + return self->model; +} + +/** + * gtk_list_view_set_model: + * @self: a #GtkListView + * @file: (allow-none) (transfer none): the model to use or %NULL for none + * + * Sets the #GListModel to use for + **/ +void +gtk_list_view_set_model (GtkListView *self, + GListModel *model) +{ + g_return_if_fail (GTK_IS_LIST_VIEW (self)); + g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model)); + + if (self->model == model) + return; + + gtk_list_view_clear_model (self); + + if (model) + { + self->model = g_object_ref (model); + + g_signal_connect (model, + "items-changed", + G_CALLBACK (gtk_list_view_model_items_changed_cb), + self); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]); +} + diff --git a/gtk/gtklistview.h b/gtk/gtklistview.h new file mode 100644 index 0000000000..9329e21d75 --- /dev/null +++ b/gtk/gtklistview.h @@ -0,0 +1,48 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __GTK_LIST_VIEW_H__ +#define __GTK_LIST_VIEW_H__ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_LIST_VIEW (gtk_list_view_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkListView, gtk_list_view, GTK, LIST_VIEW, GtkWidget) + +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_list_view_new (void); + +GDK_AVAILABLE_IN_ALL +GListModel * gtk_list_view_get_model (GtkListView *self); +GDK_AVAILABLE_IN_ALL +void gtk_list_view_set_model (GtkListView *self, + GListModel *model); + + +G_END_DECLS + +#endif /* __GTK_LIST_VIEW_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index f85d089a2f..8b221d7c3d 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -273,6 +273,7 @@ gtk_public_sources = files([ 'gtklistbox.c', 'gtklistlistmodel.c', 'gtkliststore.c', + 'gtklistview.c', 'gtklockbutton.c', 'gtkmain.c', 'gtkmaplistmodel.c', @@ -535,6 +536,7 @@ gtk_public_headers = files([ 'gtklinkbutton.h', 'gtklistbox.h', 'gtkliststore.h', + 'gtklistview.h', 'gtklockbutton.h', 'gtkmain.h', 'gtkmaplistmodel.h', From e20c207a22eff26cca7f75bc0ef1bb308cef4afa Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 17 Sep 2018 07:29:50 +0200 Subject: [PATCH 034/170] listview: Introduce GtkListItemFactory Thisis the abstraction I intend to use for creating widgets and binding them to the item out of the listview. For now this is a very dumb wrapper around the functions that exist in the API. But it leaves the freedom to turn this into public API, make an interface out of it and most of all write different implementations, in particular one that uses GtkBuilder. --- docs/reference/gtk/gtk4-sections.txt | 14 +++ docs/reference/gtk/gtk4.types.in | 1 + docs/reference/gtk/meson.build | 1 + gtk/gtklistitemfactory.c | 153 +++++++++++++++++++++++++++ gtk/gtklistitemfactoryprivate.h | 54 ++++++++++ gtk/gtklistview.c | 22 +++- gtk/gtklistview.h | 37 ++++++- gtk/meson.build | 1 + 8 files changed, 281 insertions(+), 2 deletions(-) create mode 100644 gtk/gtklistitemfactory.c create mode 100644 gtk/gtklistitemfactoryprivate.h diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index beb6b2df87..3479a557b2 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -393,6 +393,20 @@ gtk_single_selection_get_type
+gtklistitemfactory +GtkListItemFactory +GtkListItemFactory + +GTK_LIST_ITEM_FACTORY +GTK_LIST_ITEM_FACTORY_CLASS +GTK_LIST_ITEM_FACTORY_GET_CLASS +GTK_IS_LIST_ITEM_FACTORY +GTK_IS_LIST_ITEM_FACTORY_CLASS +GTK_TYPE_LIST_ITEM_FACTORY + +gtk_list_item_factory_get_type +
+ gtklistview GtkListView GtkListView diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in index 6ada01a6b7..30292ea74a 100644 --- a/docs/reference/gtk/gtk4.types.in +++ b/docs/reference/gtk/gtk4.types.in @@ -119,6 +119,7 @@ gtk_label_get_type gtk_layout_child_get_type gtk_layout_manager_get_type gtk_link_button_get_type +gtk_list_item_factory_get_type gtk_list_store_get_type gtk_list_box_get_type gtk_list_box_row_get_type diff --git a/docs/reference/gtk/meson.build b/docs/reference/gtk/meson.build index 07536264eb..bc28cbc7ed 100644 --- a/docs/reference/gtk/meson.build +++ b/docs/reference/gtk/meson.build @@ -132,6 +132,7 @@ private_headers = [ 'gtkimmoduleprivate.h', 'gtkkineticscrollingprivate.h', 'gtklabelprivate.h', + 'gtklistitemfactoryprivate.h', 'gtklockbuttonprivate.h', 'gtkmagnifierprivate.h', 'gtkmediafileprivate.h', diff --git a/gtk/gtklistitemfactory.c b/gtk/gtklistitemfactory.c new file mode 100644 index 0000000000..7519bc01bb --- /dev/null +++ b/gtk/gtklistitemfactory.c @@ -0,0 +1,153 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gtklistitemfactoryprivate.h" + +/** + * SECTION:gtklistitemfactory + * @Title: GtkListItemFactory + * @Short_description: Mapping list items to widgets + * + * #GtkListItemFactory is one of the core concepts of handling list widgets. + * It is the object tasked with creating widgets for items taken from a + * #GListModel when the views need them and updating them as the items + * displayed by the view change. + * + * A view is usually only able to display anything after both a factory + * and a model have been set on the view. So it is important that you do + * not skip this step when setting up your first view. + * + * Because views do not display the whole list at once but only a few + * items, they only need to maintain a few widgets at a time. They will + * instruct the #GtkListItemFactory to create these widgets and bind them + * to the items that are currently displayed. + * As the list model changes or the user scrolls to the list, the items will + * change and the view will instruct the factory to bind the widgets to those + * new items. + * + * The actual widgets used for displaying those widgets is provided by you. + * + * When the factory needs widgets created, it will create a #GtkListItem and + * hand it to your code to set up a widget for. This list item will provide + * various properties with information about what item to display and provide + * you with some opportunities to configure its behavior. See the #GtkListItem + * documentation for further details. + * + * Various implementations of #GtkListItemFactory exist to allow you different + * ways to provide those widgets. The most common implementations are + * #GtkBuilderListItemFactory which takes a #GtkBuilder .ui file and then creates + * and manages widgets everything automatically from the information in that file + * and #GtkSignalListItemFactory which allows you to connect to signals with your + * own code and retain full control over how the widgets are setup and managed. + * + * A #GtkListItemFactory is supposed to be final - that means its behavior should + * not change and the first widget created from it should behave the same way as + * the last widget created from it. + * If you intend to do changes to the behavior, it is recommended that you create + * a new #GtkListItemFactory which will allow the views to recreate its widgets. + * + * Once you have chosen your factory and created it, you need to set it on the + * view widget you want to use it with, such as via gtk_list_view_set_factory(). + * Reusing factories across different views is allowed, but very uncommon. + */ + +struct _GtkListItemFactory +{ + GObject parent_instance; + + GtkListCreateWidgetFunc create_func; + GtkListBindWidgetFunc bind_func; + gpointer user_data; + GDestroyNotify user_destroy; +}; + +struct _GtkListItemFactoryClass +{ + GObjectClass parent_class; +}; + +G_DEFINE_TYPE (GtkListItemFactory, gtk_list_item_factory, G_TYPE_OBJECT) + +static void +gtk_list_item_factory_finalize (GObject *object) +{ + GtkListItemFactory *self = GTK_LIST_ITEM_FACTORY (object); + + if (self->user_destroy) + self->user_destroy (self->user_data); + + G_OBJECT_CLASS (gtk_list_item_factory_parent_class)->finalize (object); +} + +static void +gtk_list_item_factory_class_init (GtkListItemFactoryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gtk_list_item_factory_finalize; +} + +static void +gtk_list_item_factory_init (GtkListItemFactory *self) +{ +} + +GtkListItemFactory * +gtk_list_item_factory_new (GtkListCreateWidgetFunc create_func, + GtkListBindWidgetFunc bind_func, + gpointer user_data, + GDestroyNotify user_destroy) +{ + GtkListItemFactory *self; + + g_return_val_if_fail (create_func, NULL); + g_return_val_if_fail (bind_func, NULL); + g_return_val_if_fail (user_data != NULL || user_destroy == NULL, NULL); + + self = g_object_new (GTK_TYPE_LIST_ITEM_FACTORY, NULL); + + self->create_func = create_func; + self->bind_func = bind_func; + self->user_data = user_data; + self->user_destroy = user_destroy; + + return self; +} + +GtkWidget * +gtk_list_item_factory_create (GtkListItemFactory *self) +{ + g_return_val_if_fail (GTK_IS_LIST_ITEM_FACTORY (self), NULL); + + return self->create_func (self->user_data); +} + +void +gtk_list_item_factory_bind (GtkListItemFactory *self, + GtkWidget *widget, + gpointer item) +{ + g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + self->bind_func (widget, item, self->user_data); +} + diff --git a/gtk/gtklistitemfactoryprivate.h b/gtk/gtklistitemfactoryprivate.h new file mode 100644 index 0000000000..28ae55b4aa --- /dev/null +++ b/gtk/gtklistitemfactoryprivate.h @@ -0,0 +1,54 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + + +#ifndef __GTK_LIST_ITEM_FACTORY_H__ +#define __GTK_LIST_ITEM_FACTORY_H__ + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_LIST_ITEM_FACTORY (gtk_list_item_factory_get_type ()) +#define GTK_LIST_ITEM_FACTORY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_LIST_ITEM_FACTORY, GtkListItemFactory)) +#define GTK_LIST_ITEM_FACTORY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_LIST_ITEM_FACTORY, GtkListItemFactoryClass)) +#define GTK_IS_LIST_ITEM_FACTORY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_LIST_ITEM_FACTORY)) +#define GTK_IS_LIST_ITEM_FACTORY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_LIST_ITEM_FACTORY)) +#define GTK_LIST_ITEM_FACTORY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_LIST_ITEM_FACTORY, GtkListItemFactoryClass)) + +typedef struct _GtkListItemFactory GtkListItemFactory; +typedef struct _GtkListItemFactoryClass GtkListItemFactoryClass; + +GType gtk_list_item_factory_get_type (void) G_GNUC_CONST; + +GtkListItemFactory * gtk_list_item_factory_new (GtkListCreateWidgetFunc create_func, + GtkListBindWidgetFunc bind_func, + gpointer user_data, + GDestroyNotify user_destroy); + +GtkWidget * gtk_list_item_factory_create (GtkListItemFactory *self); + +void gtk_list_item_factory_bind (GtkListItemFactory *self, + GtkWidget *widget, + gpointer item); + + +G_END_DECLS + +#endif /* __GTK_LIST_ITEM_FACTORY_H__ */ diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index d5f2ca3841..b8a5337d8a 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -22,7 +22,7 @@ #include "gtklistview.h" #include "gtkintl.h" -#include "gtkprivate.h" +#include "gtklistitemfactoryprivate.h" /** * SECTION:gtklistview @@ -38,6 +38,7 @@ struct _GtkListView GtkWidget parent_instance; GListModel *model; + GtkListItemFactory *item_factory; }; enum @@ -116,6 +117,8 @@ gtk_list_view_dispose (GObject *object) gtk_list_view_clear_model (self); + g_clear_object (&self->item_factory); + G_OBJECT_CLASS (gtk_list_view_parent_class)->dispose (object); } @@ -258,3 +261,20 @@ gtk_list_view_set_model (GtkListView *self, g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]); } +void +gtk_list_view_set_functions (GtkListView *self, + GtkListCreateWidgetFunc create_func, + GtkListBindWidgetFunc bind_func, + gpointer user_data, + GDestroyNotify user_destroy) +{ + g_return_if_fail (GTK_IS_LIST_VIEW (self)); + g_return_if_fail (create_func); + g_return_if_fail (bind_func); + g_return_if_fail (user_data != NULL || user_destroy == NULL); + + g_clear_object (&self->item_factory); + + self->item_factory = gtk_list_item_factory_new (create_func, bind_func, user_data, user_destroy); +} + diff --git a/gtk/gtklistview.h b/gtk/gtklistview.h index 9329e21d75..541bf14874 100644 --- a/gtk/gtklistview.h +++ b/gtk/gtklistview.h @@ -28,6 +28,36 @@ G_BEGIN_DECLS +/** + * GtkListCreateWidgetFunc: + * @user_data: (closure): user data + * + * Called whenever a new widget needs to be created for managing a row in + * the list. + * + * The widget will later be bound to an item via the #GtkListBindWidgetFunc. + * + * Returns: (transfer full): a #GtkWidget + */ +typedef GtkWidget * (* GtkListCreateWidgetFunc) (gpointer user_data); + +/** + * GtkListBindWidgetFunc: + * @widget: The #GtkWidget to bind + * @item: (type GObject) (allow-none): item to bind or %NULL to unbind + * the widget. + * @user_data: (closure): user data + * + * Binds a widget previously created via a #GtkListCreateWidgetFunc to + * an @item. + * + * Rebinding a @widget to different @items is supported as well as + * unbinding it by setting @item to %NULL. + */ +typedef void (* GtkListBindWidgetFunc) (GtkWidget *widget, + gpointer item, + gpointer user_data); + #define GTK_TYPE_LIST_VIEW (gtk_list_view_get_type ()) GDK_AVAILABLE_IN_ALL @@ -41,7 +71,12 @@ GListModel * gtk_list_view_get_model (GtkListView GDK_AVAILABLE_IN_ALL void gtk_list_view_set_model (GtkListView *self, GListModel *model); - +GDK_AVAILABLE_IN_ALL +void gtk_list_view_set_functions (GtkListView *self, + GtkListCreateWidgetFunc create_func, + GtkListBindWidgetFunc bind_func, + gpointer user_data, + GDestroyNotify user_destroy); G_END_DECLS diff --git a/gtk/meson.build b/gtk/meson.build index 8b221d7c3d..c1cb68daf4 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -271,6 +271,7 @@ gtk_public_sources = files([ 'gtklevelbar.c', 'gtklinkbutton.c', 'gtklistbox.c', + 'gtklistitemfactory.c', 'gtklistlistmodel.c', 'gtkliststore.c', 'gtklistview.c', From c835ae2a02d0452f8a7011c5fd92449840721742 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 18 Sep 2018 04:56:19 +0200 Subject: [PATCH 035/170] listview: Make widget actually do something The thing we're actually doing is create and maintain a widget for every row. That's it. Also add a testcase using this. The testcase quickly allocates too many rows though and then becomes unresponsive though. You have been warned. --- gtk/gtklistview.c | 236 +++++++++++++++++++++++++++-- tests/meson.build | 1 + tests/testlistview.c | 345 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 569 insertions(+), 13 deletions(-) create mode 100644 tests/testlistview.c diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index b8a5337d8a..17e4925d0d 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -22,6 +22,7 @@ #include "gtklistview.h" #include "gtkintl.h" +#include "gtkrbtreeprivate.h" #include "gtklistitemfactoryprivate.h" /** @@ -33,12 +34,30 @@ * GtkListView is a widget to present a view into a large dynamic list of items. */ +typedef struct _ListRow ListRow; +typedef struct _ListRowAugment ListRowAugment; + struct _GtkListView { GtkWidget parent_instance; GListModel *model; GtkListItemFactory *item_factory; + + GtkRbTree *rows; +}; + +struct _ListRow +{ + guint n_rows; + guint height; + GtkWidget *widget; +}; + +struct _ListRowAugment +{ + guint n_rows; + guint height; }; enum @@ -53,10 +72,78 @@ G_DEFINE_TYPE (GtkListView, gtk_list_view, GTK_TYPE_WIDGET) static GParamSpec *properties[N_PROPS] = { NULL, }; -static gboolean -gtk_list_view_is_empty (GtkListView *self) +static void +list_row_augment (GtkRbTree *tree, + gpointer node_augment, + gpointer node, + gpointer left, + gpointer right) { - return self->model == NULL; + ListRow *row = node; + ListRowAugment *aug = node_augment; + + aug->height = row->height; + aug->n_rows = row->n_rows; + + if (left) + { + ListRowAugment *left_aug = gtk_rb_tree_get_augment (tree, left); + + aug->height += left_aug->height; + aug->n_rows += left_aug->n_rows; + } + + if (right) + { + ListRowAugment *right_aug = gtk_rb_tree_get_augment (tree, right); + + aug->height += right_aug->height; + aug->n_rows += right_aug->n_rows; + } +} + +static void +list_row_clear (gpointer _row) +{ + ListRow *row = _row; + + g_clear_pointer (&row->widget, gtk_widget_unparent); +} + +static ListRow * +gtk_list_view_get_row (GtkListView *self, + guint position, + guint *offset) +{ + ListRow *row, *tmp; + + row = gtk_rb_tree_get_root (self->rows); + + while (row) + { + tmp = gtk_rb_tree_node_get_left (row); + if (tmp) + { + ListRowAugment *aug = gtk_rb_tree_get_augment (self->rows, tmp); + if (position < aug->n_rows) + { + row = tmp; + continue; + } + position -= aug->n_rows; + } + + if (position < row->n_rows) + break; + position -= row->n_rows; + + row = gtk_rb_tree_node_get_right (row); + } + + if (offset) + *offset = row ? position : 0; + + return row; } static void @@ -69,17 +156,38 @@ gtk_list_view_measure (GtkWidget *widget, int *natural_baseline) { GtkListView *self = GTK_LIST_VIEW (widget); + ListRow *row; + int min, nat, child_min, child_nat; - if (gtk_list_view_is_empty (self)) + /* XXX: Figure out how to split a given height into per-row heights. + * Good luck! */ + if (orientation == GTK_ORIENTATION_HORIZONTAL) + for_size = -1; + + min = 0; + nat = 0; + + for (row = gtk_rb_tree_get_first (self->rows); + row != NULL; + row = gtk_rb_tree_node_get_next (row)) { - *minimum = 0; - *natural = 0; - return; + gtk_widget_measure (row->widget, + orientation, for_size, + &child_min, &child_nat, NULL, NULL); + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + min = MAX (min, child_min); + nat = MAX (nat, child_nat); + } + else + { + min += child_nat; + nat = min; + } } - *minimum = 0; - *natural = 0; - return; + *minimum = min; + *natural = nat; } static void @@ -88,7 +196,81 @@ gtk_list_view_size_allocate (GtkWidget *widget, int height, int baseline) { - //GtkListView *self = GTK_LIST_VIEW (widget); + GtkListView *self = GTK_LIST_VIEW (widget); + GtkAllocation child_allocation = { 0, 0, 0, 0 }; + ListRow *row; + int nat; + + child_allocation.width = width; + + for (row = gtk_rb_tree_get_first (self->rows); + row != NULL; + row = gtk_rb_tree_node_get_next (row)) + { + gtk_widget_measure (row->widget, GTK_ORIENTATION_VERTICAL, + width, + NULL, &nat, NULL, NULL); + if (row->height != nat) + { + row->height = nat; + gtk_rb_tree_node_mark_dirty (row); + } + child_allocation.height = row->height; + gtk_widget_size_allocate (row->widget, &child_allocation, -1); + child_allocation.y += child_allocation.height; + } +} + +static void +gtk_list_view_remove_rows (GtkListView *self, + guint position, + guint n_rows) +{ + ListRow *row; + guint i; + + if (n_rows == 0) + return; + + row = gtk_list_view_get_row (self, position, NULL); + + for (i = 0; i < n_rows; i++) + { + ListRow *next = gtk_rb_tree_node_get_next (row); + gtk_rb_tree_remove (self->rows, row); + row = next; + } + + gtk_widget_queue_resize (GTK_WIDGET (self)); +} + +static void +gtk_list_view_add_rows (GtkListView *self, + guint position, + guint n_rows) +{ + ListRow *row; + guint i; + + if (n_rows == 0) + return; + + row = gtk_list_view_get_row (self, position, NULL); + + for (i = 0; i < n_rows; i++) + { + ListRow *new_row; + gpointer item; + + new_row = gtk_rb_tree_insert_before (self->rows, row); + new_row->n_rows = 1; + new_row->widget = gtk_list_item_factory_create (self->item_factory); + gtk_widget_insert_before (new_row->widget, GTK_WIDGET (self), row ? row->widget : NULL); + item = g_list_model_get_item (self->model, position + i); + gtk_list_item_factory_bind (self->item_factory, new_row->widget, item); + } + + gtk_widget_queue_resize (GTK_WIDGET (self)); } static void @@ -98,6 +280,8 @@ gtk_list_view_model_items_changed_cb (GListModel *model, guint added, GtkListView *self) { + gtk_list_view_remove_rows (self, position, removed); + gtk_list_view_add_rows (self, position, added); } static void @@ -106,6 +290,8 @@ gtk_list_view_clear_model (GtkListView *self) if (self->model == NULL) return; + gtk_list_view_remove_rows (self, 0, g_list_model_get_n_items (self->model)); + g_signal_handlers_disconnect_by_func (self->model, gtk_list_view_model_items_changed_cb, self); g_clear_object (&self->model); } @@ -122,6 +308,16 @@ gtk_list_view_dispose (GObject *object) G_OBJECT_CLASS (gtk_list_view_parent_class)->dispose (object); } +static void +gtk_list_view_finalize (GObject *object) +{ + GtkListView *self = GTK_LIST_VIEW (object); + + gtk_rb_tree_unref (self->rows); + + G_OBJECT_CLASS (gtk_list_view_parent_class)->finalize (object); +} + static void gtk_list_view_get_property (GObject *object, guint property_id, @@ -172,6 +368,7 @@ gtk_list_view_class_init (GtkListViewClass *klass) widget_class->size_allocate = gtk_list_view_size_allocate; gobject_class->dispose = gtk_list_view_dispose; + gobject_class->finalize = gtk_list_view_finalize; gobject_class->get_property = gtk_list_view_get_property; gobject_class->set_property = gtk_list_view_set_property; @@ -195,6 +392,11 @@ gtk_list_view_class_init (GtkListViewClass *klass) static void gtk_list_view_init (GtkListView *self) { + self->rows = gtk_rb_tree_new (ListRow, + ListRowAugment, + list_row_augment, + list_row_clear, + NULL); } /** @@ -253,9 +455,11 @@ gtk_list_view_set_model (GtkListView *self, self->model = g_object_ref (model); g_signal_connect (model, - "items-changed", + "items-changed", G_CALLBACK (gtk_list_view_model_items_changed_cb), self); + + gtk_list_view_add_rows (self, 0, g_list_model_get_n_items (model)); } g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]); @@ -268,13 +472,19 @@ gtk_list_view_set_functions (GtkListView *self, gpointer user_data, GDestroyNotify user_destroy) { + guint n_items; + g_return_if_fail (GTK_IS_LIST_VIEW (self)); g_return_if_fail (create_func); g_return_if_fail (bind_func); g_return_if_fail (user_data != NULL || user_destroy == NULL); - g_clear_object (&self->item_factory); + n_items = self->model ? g_list_model_get_n_items (self->model) : 0; + gtk_list_view_remove_rows (self, 0, n_items); + g_clear_object (&self->item_factory); self->item_factory = gtk_list_item_factory_new (create_func, bind_func, user_data, user_destroy); + + gtk_list_view_add_rows (self, 0, n_items); } diff --git a/tests/meson.build b/tests/meson.build index 34cb9f07cd..65c4189631 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -49,6 +49,7 @@ gtk_tests = [ ['testlist2'], ['testlist3'], ['testlist4'], + ['testlistview'], ['testlevelbar'], ['testlockbutton'], ['testmenubutton'], diff --git a/tests/testlistview.c b/tests/testlistview.c new file mode 100644 index 0000000000..1668fe2a18 --- /dev/null +++ b/tests/testlistview.c @@ -0,0 +1,345 @@ +#include + +#define ROWS 30 + +GSList *pending = NULL; +guint active = 0; + +static void +got_files (GObject *enumerate, + GAsyncResult *res, + gpointer store); + +static gboolean +start_enumerate (GListStore *store) +{ + GFileEnumerator *enumerate; + GFile *file = g_object_get_data (G_OBJECT (store), "file"); + GError *error = NULL; + + enumerate = g_file_enumerate_children (file, + G_FILE_ATTRIBUTE_STANDARD_TYPE + "," G_FILE_ATTRIBUTE_STANDARD_ICON + "," G_FILE_ATTRIBUTE_STANDARD_NAME, + 0, + NULL, + &error); + + if (enumerate == NULL) + { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TOO_MANY_OPEN_FILES) && active) + { + g_clear_error (&error); + pending = g_slist_prepend (pending, g_object_ref (store)); + return TRUE; + } + + g_clear_error (&error); + g_object_unref (store); + return FALSE; + } + + if (active > 20) + { + g_object_unref (enumerate); + pending = g_slist_prepend (pending, g_object_ref (store)); + return TRUE; + } + + active++; + g_file_enumerator_next_files_async (enumerate, + g_file_is_native (file) ? 5000 : 100, + G_PRIORITY_DEFAULT_IDLE, + NULL, + got_files, + g_object_ref (store)); + + g_object_unref (enumerate); + return TRUE; +} + +static void +got_files (GObject *enumerate, + GAsyncResult *res, + gpointer store) +{ + GList *l, *files; + GFile *file = g_object_get_data (store, "file"); + GPtrArray *array; + + files = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (enumerate), res, NULL); + if (files == NULL) + { + g_object_unref (store); + if (pending) + { + GListStore *next = pending->data; + pending = g_slist_remove (pending, next); + start_enumerate (next); + } + active--; + return; + } + + array = g_ptr_array_new (); + g_ptr_array_new_with_free_func (g_object_unref); + for (l = files; l; l = l->next) + { + GFileInfo *info = l->data; + GFile *child; + + child = g_file_get_child (file, g_file_info_get_name (info)); + g_object_set_data_full (G_OBJECT (info), "file", child, g_object_unref); + g_ptr_array_add (array, info); + } + g_list_free (files); + + g_list_store_splice (store, g_list_model_get_n_items (store), 0, array->pdata, array->len); + g_ptr_array_unref (array); + + g_file_enumerator_next_files_async (G_FILE_ENUMERATOR (enumerate), + g_file_is_native (file) ? 5000 : 100, + G_PRIORITY_DEFAULT_IDLE, + NULL, + got_files, + store); +} + +static char * +get_file_path (GFileInfo *info) +{ + GFile *file; + + file = g_object_get_data (G_OBJECT (info), "file"); + return g_file_get_path (file); +} + +static GListModel * +create_list_model_for_directory (gpointer file) +{ + GtkSortListModel *sort; + GListStore *store; + GtkSorter *sorter; + + if (g_file_query_file_type (file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) != G_FILE_TYPE_DIRECTORY) + return NULL; + + store = g_list_store_new (G_TYPE_FILE_INFO); + g_object_set_data_full (G_OBJECT (store), "file", g_object_ref (file), g_object_unref); + + if (!start_enumerate (store)) + return NULL; + + sorter = gtk_string_sorter_new (gtk_cclosure_expression_new (G_TYPE_STRING, NULL, 0, NULL, (GCallback) get_file_path, NULL, NULL)); + sort = gtk_sort_list_model_new (G_LIST_MODEL (store), sorter); + g_object_unref (sorter); + + g_object_unref (store); + return G_LIST_MODEL (sort); +} + +static GtkWidget * +create_widget (gpointer unused) +{ + return gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); +} + +static void +bind_widget (GtkWidget *box, + gpointer item, + gpointer unused) +{ + GtkWidget *child; + GFileInfo *info; + GFile *file; + guint depth; + GIcon *icon; + + while (gtk_widget_get_first_child (box)) + gtk_box_remove (GTK_BOX (box), gtk_widget_get_first_child (box)); + + depth = gtk_tree_list_row_get_depth (item); + if (depth > 0) + { + child = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_set_size_request (child, 16 * depth, 0); + gtk_box_append (GTK_BOX (box), child); + } + + if (gtk_tree_list_row_is_expandable (item)) + { + GtkWidget *arrow; + + child = g_object_new (GTK_TYPE_TOGGLE_BUTTON, "css-name", "expander-widget", NULL); + gtk_button_set_has_frame (GTK_BUTTON (child), FALSE); + g_object_bind_property (item, "expanded", child, "active", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); + g_object_set_data_full (G_OBJECT (child), "make-sure-its-not-unreffed", g_object_ref (item), g_object_unref); + + arrow = g_object_new (GTK_TYPE_SPINNER, "css-name", "expander", NULL); + g_object_bind_property (item, "expanded", arrow, "spinning", G_BINDING_SYNC_CREATE); + gtk_button_set_child (GTK_BUTTON (child), arrow); + } + else + { + child = gtk_image_new (); /* empty whatever */ + } + gtk_box_append (GTK_BOX (box), child); + + info = gtk_tree_list_row_get_item (item); + + icon = g_file_info_get_icon (info); + if (icon) + { + child = gtk_image_new_from_gicon (icon); + gtk_box_append (GTK_BOX (box), child); + } + + file = g_object_get_data (G_OBJECT (info), "file"); + child = gtk_label_new (g_file_get_basename (file)); + g_object_unref (info); + + gtk_box_append (GTK_BOX (box), child); +} + +static GListModel * +create_list_model_for_file_info (gpointer file_info, + gpointer unused) +{ + GFile *file = g_object_get_data (file_info, "file"); + + if (file == NULL) + return NULL; + + return create_list_model_for_directory (file); +} + +static gboolean +update_statusbar (GtkStatusbar *statusbar) +{ + GListModel *model = g_object_get_data (G_OBJECT (statusbar), "model"); + GString *string = g_string_new (NULL); + guint n; + gboolean result = G_SOURCE_REMOVE; + + gtk_statusbar_remove_all (statusbar, 0); + + n = g_list_model_get_n_items (model); + g_string_append_printf (string, "%u", n); + if (GTK_IS_FILTER_LIST_MODEL (model)) + { + guint n_unfiltered = g_list_model_get_n_items (gtk_filter_list_model_get_model (GTK_FILTER_LIST_MODEL (model))); + if (n != n_unfiltered) + g_string_append_printf (string, "/%u", n_unfiltered); + } + g_string_append (string, " items"); + + if (pending || active) + { + g_string_append_printf (string, " (%u directories remaining)", active + g_slist_length (pending)); + result = G_SOURCE_CONTINUE; + } + + gtk_statusbar_push (statusbar, 0, string->str); + g_free (string->str); + + return result; +} + +static gboolean +match_file (gpointer item, gpointer data) +{ + GtkWidget *search_entry = data; + GFileInfo *info = gtk_tree_list_row_get_item (item); + GFile *file = g_object_get_data (G_OBJECT (info), "file"); + char *path; + gboolean result; + + path = g_file_get_path (file); + + result = strstr (path, gtk_editable_get_text (GTK_EDITABLE (search_entry))) != NULL; + + g_object_unref (info); + g_free (path); + + return result; +} + +static void +search_changed_cb (GtkSearchEntry *entry, + GtkFilter *custom_filter) +{ + gtk_filter_changed (custom_filter, GTK_FILTER_CHANGE_DIFFERENT); +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *win, *vbox, *sw, *listview, *search_entry, *statusbar; + GListModel *dirmodel; + GtkTreeListModel *tree; + GtkFilterListModel *filter; + GtkFilter *custom_filter; + GFile *root; + GListModel *toplevels; + + gtk_init (); + + win = gtk_window_new (); + gtk_window_set_default_size (GTK_WINDOW (win), 400, 600); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_window_set_child (GTK_WINDOW (win), vbox); + + search_entry = gtk_search_entry_new (); + gtk_box_append (GTK_BOX (vbox), search_entry); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_set_vexpand (sw, TRUE); + gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (search_entry), sw); + gtk_box_append (GTK_BOX (vbox), sw); + + listview = gtk_list_view_new (); + gtk_list_view_set_functions (GTK_LIST_VIEW (listview), + create_widget, + bind_widget, + NULL, NULL); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), listview); + + if (argc > 1) + root = g_file_new_for_commandline_arg (argv[1]); + else + root = g_file_new_for_path (g_get_current_dir ()); + dirmodel = create_list_model_for_directory (root); + tree = gtk_tree_list_model_new (FALSE, + dirmodel, + TRUE, + create_list_model_for_file_info, + NULL, NULL); + g_object_unref (dirmodel); + g_object_unref (root); + + custom_filter = gtk_custom_filter_new (match_file, search_entry, NULL); + filter = gtk_filter_list_model_new (G_LIST_MODEL (tree), custom_filter); + g_signal_connect (search_entry, "search-changed", G_CALLBACK (search_changed_cb), custom_filter); + g_object_unref (custom_filter); + + gtk_list_view_set_model (GTK_LIST_VIEW (listview), G_LIST_MODEL (filter)); + + statusbar = gtk_statusbar_new (); + gtk_widget_add_tick_callback (statusbar, (GtkTickCallback) update_statusbar, NULL, NULL); + g_object_set_data (G_OBJECT (statusbar), "model", filter); + g_signal_connect_swapped (filter, "items-changed", G_CALLBACK (update_statusbar), statusbar); + update_statusbar (GTK_STATUSBAR (statusbar)); + gtk_box_append (GTK_BOX (vbox), statusbar); + + g_object_unref (tree); + g_object_unref (filter); + + gtk_widget_show (win); + + toplevels = gtk_window_get_toplevels (); + while (g_list_model_get_n_items (toplevels)) + g_main_context_iteration (NULL, TRUE); + + return 0; +} From ed8fe6c219c9abdc5b8085589c23afc9f7dadaa0 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 18 Sep 2018 19:58:19 +0200 Subject: [PATCH 036/170] listview: Implement GtkScrollable Scrolling in a very basic form is also supported --- gtk/gtklistview.c | 218 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 210 insertions(+), 8 deletions(-) diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index 17e4925d0d..f1a18224ce 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -21,9 +21,12 @@ #include "gtklistview.h" +#include "gtkadjustment.h" #include "gtkintl.h" #include "gtkrbtreeprivate.h" #include "gtklistitemfactoryprivate.h" +#include "gtkscrollable.h" +#include "gtkwidgetprivate.h" /** * SECTION:gtklistview @@ -43,8 +46,11 @@ struct _GtkListView GListModel *model; GtkListItemFactory *item_factory; + GtkAdjustment *adjustment[2]; + GtkScrollablePolicy scroll_policy[2]; GtkRbTree *rows; + int list_width; }; struct _ListRow @@ -63,12 +69,17 @@ struct _ListRowAugment enum { PROP_0, + PROP_HADJUSTMENT, + PROP_HSCROLL_POLICY, PROP_MODEL, + PROP_VADJUSTMENT, + PROP_VSCROLL_POLICY, N_PROPS }; -G_DEFINE_TYPE (GtkListView, gtk_list_view, GTK_TYPE_WIDGET) +G_DEFINE_TYPE_WITH_CODE (GtkListView, gtk_list_view, GTK_TYPE_WIDGET, + G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL)) static GParamSpec *properties[N_PROPS] = { NULL, }; @@ -146,6 +157,52 @@ gtk_list_view_get_row (GtkListView *self, return row; } +static int +gtk_list_view_get_list_height (GtkListView *self) +{ + ListRow *row; + ListRowAugment *aug; + + row = gtk_rb_tree_get_root (self->rows); + if (row == NULL) + return 0; + + aug = gtk_rb_tree_get_augment (self->rows, row); + return aug->height; +} + +static void +gtk_list_view_update_adjustments (GtkListView *self, + GtkOrientation orientation) +{ + double upper, page_size, value; + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + page_size = gtk_widget_get_width (GTK_WIDGET (self)); + upper = self->list_width; + value = 0; + if (_gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL) + value = upper - page_size - value; + } + else + { + page_size = gtk_widget_get_height (GTK_WIDGET (self)); + upper = gtk_list_view_get_list_height (self); + value = 0; + } + upper = MAX (upper, page_size); + value = gtk_adjustment_get_value (self->adjustment[orientation]); + + gtk_adjustment_configure (self->adjustment[orientation], + value, + 0, + upper, + page_size * 0.1, + page_size * 0.9, + page_size); +} + static void gtk_list_view_measure (GtkWidget *widget, GtkOrientation orientation, @@ -199,22 +256,48 @@ gtk_list_view_size_allocate (GtkWidget *widget, GtkListView *self = GTK_LIST_VIEW (widget); GtkAllocation child_allocation = { 0, 0, 0, 0 }; ListRow *row; - int nat; + int min, nat, row_height; - child_allocation.width = width; + /* step 1: determine width of the list */ + gtk_widget_measure (widget, GTK_ORIENTATION_HORIZONTAL, + -1, + &min, &nat, NULL, NULL); + if (self->scroll_policy[GTK_ORIENTATION_HORIZONTAL] == GTK_SCROLL_MINIMUM) + self->list_width = MAX (min, width); + else + self->list_width = MAX (nat, width); + /* step 2: determine height of list */ for (row = gtk_rb_tree_get_first (self->rows); row != NULL; row = gtk_rb_tree_node_get_next (row)) { gtk_widget_measure (row->widget, GTK_ORIENTATION_VERTICAL, - width, - NULL, &nat, NULL, NULL); - if (row->height != nat) + self->list_width, + &min, &nat, NULL, NULL); + if (self->scroll_policy[GTK_ORIENTATION_VERTICAL] == GTK_SCROLL_MINIMUM) + row_height = min; + else + row_height = nat; + if (row->height != row_height) { - row->height = nat; + row->height = row_height; gtk_rb_tree_node_mark_dirty (row); } + } + + /* step 3: update the adjustments */ + gtk_list_view_update_adjustments (self, GTK_ORIENTATION_HORIZONTAL); + gtk_list_view_update_adjustments (self, GTK_ORIENTATION_VERTICAL); + + /* step 4: actually allocate the widgets */ + child_allocation.x = - gtk_adjustment_get_value (self->adjustment[GTK_ORIENTATION_HORIZONTAL]); + child_allocation.y = - gtk_adjustment_get_value (self->adjustment[GTK_ORIENTATION_VERTICAL]); + child_allocation.width = self->list_width; + for (row = gtk_rb_tree_get_first (self->rows); + row != NULL; + row = gtk_rb_tree_node_get_next (row)) + { child_allocation.height = row->height; gtk_widget_size_allocate (row->widget, &child_allocation, -1); child_allocation.y += child_allocation.height; @@ -292,10 +375,32 @@ gtk_list_view_clear_model (GtkListView *self) gtk_list_view_remove_rows (self, 0, g_list_model_get_n_items (self->model)); - g_signal_handlers_disconnect_by_func (self->model, gtk_list_view_model_items_changed_cb, self); + g_signal_handlers_disconnect_by_func (self->model, + gtk_list_view_model_items_changed_cb, + self); g_clear_object (&self->model); } +static void +gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment, + GtkListView *self) +{ + gtk_widget_queue_allocate (GTK_WIDGET (self)); +} + +static void +gtk_list_view_clear_adjustment (GtkListView *self, + GtkOrientation orientation) +{ + if (self->adjustment[orientation] == NULL) + return; + + g_signal_handlers_disconnect_by_func (self->adjustment[orientation], + gtk_list_view_adjustment_value_changed_cb, + self); + g_clear_object (&self->adjustment[orientation]); +} + static void gtk_list_view_dispose (GObject *object) { @@ -303,6 +408,9 @@ gtk_list_view_dispose (GObject *object) gtk_list_view_clear_model (self); + gtk_list_view_clear_adjustment (self, GTK_ORIENTATION_HORIZONTAL); + gtk_list_view_clear_adjustment (self, GTK_ORIENTATION_VERTICAL); + g_clear_object (&self->item_factory); G_OBJECT_CLASS (gtk_list_view_parent_class)->dispose (object); @@ -328,16 +436,73 @@ gtk_list_view_get_property (GObject *object, switch (property_id) { + case PROP_HADJUSTMENT: + g_value_set_object (value, self->adjustment[GTK_ORIENTATION_HORIZONTAL]); + break; + + case PROP_HSCROLL_POLICY: + g_value_set_enum (value, self->scroll_policy[GTK_ORIENTATION_HORIZONTAL]); + break; + case PROP_MODEL: g_value_set_object (value, self->model); break; + case PROP_VADJUSTMENT: + g_value_set_object (value, self->adjustment[GTK_ORIENTATION_VERTICAL]); + break; + + case PROP_VSCROLL_POLICY: + g_value_set_enum (value, self->scroll_policy[GTK_ORIENTATION_VERTICAL]); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } +static void +gtk_list_view_set_adjustment (GtkListView *self, + GtkOrientation orientation, + GtkAdjustment *adjustment) +{ + if (self->adjustment[orientation] == adjustment) + return; + + if (adjustment == NULL) + adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + g_object_ref_sink (adjustment); + + gtk_list_view_clear_adjustment (self, orientation); + + self->adjustment[orientation] = adjustment; + + g_signal_connect (adjustment, "value-changed", + G_CALLBACK (gtk_list_view_adjustment_value_changed_cb), + self); + + gtk_widget_queue_allocate (GTK_WIDGET (self)); +} + +static void +gtk_list_view_set_scroll_policy (GtkListView *self, + GtkOrientation orientation, + GtkScrollablePolicy scroll_policy) +{ + if (self->scroll_policy[orientation] == scroll_policy) + return; + + self->scroll_policy[orientation] = scroll_policy; + + gtk_widget_queue_resize (GTK_WIDGET (self)); + + g_object_notify_by_pspec (G_OBJECT (self), + orientation == GTK_ORIENTATION_HORIZONTAL + ? properties[PROP_HSCROLL_POLICY] + : properties[PROP_VSCROLL_POLICY]); +} + static void gtk_list_view_set_property (GObject *object, guint property_id, @@ -348,10 +513,26 @@ gtk_list_view_set_property (GObject *object, switch (property_id) { + case PROP_HADJUSTMENT: + gtk_list_view_set_adjustment (self, GTK_ORIENTATION_HORIZONTAL, g_value_get_object (value)); + break; + + case PROP_HSCROLL_POLICY: + gtk_list_view_set_scroll_policy (self, GTK_ORIENTATION_HORIZONTAL, g_value_get_enum (value)); + break; + case PROP_MODEL: gtk_list_view_set_model (self, g_value_get_object (value)); break; + case PROP_VADJUSTMENT: + gtk_list_view_set_adjustment (self, GTK_ORIENTATION_VERTICAL, g_value_get_object (value)); + break; + + case PROP_VSCROLL_POLICY: + gtk_list_view_set_scroll_policy (self, GTK_ORIENTATION_VERTICAL, g_value_get_enum (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -363,6 +544,7 @@ gtk_list_view_class_init (GtkListViewClass *klass) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + gpointer iface; widget_class->measure = gtk_list_view_measure; widget_class->size_allocate = gtk_list_view_size_allocate; @@ -372,6 +554,21 @@ gtk_list_view_class_init (GtkListViewClass *klass) gobject_class->get_property = gtk_list_view_get_property; gobject_class->set_property = gtk_list_view_set_property; + /* GtkScrollable implementation */ + iface = g_type_default_interface_peek (GTK_TYPE_SCROLLABLE); + properties[PROP_HADJUSTMENT] = + g_param_spec_override ("hadjustment", + g_object_interface_find_property (iface, "hadjustment")); + properties[PROP_HSCROLL_POLICY] = + g_param_spec_override ("hscroll-policy", + g_object_interface_find_property (iface, "hscroll-policy")); + properties[PROP_VADJUSTMENT] = + g_param_spec_override ("vadjustment", + g_object_interface_find_property (iface, "vadjustment")); + properties[PROP_VSCROLL_POLICY] = + g_param_spec_override ("vscroll-policy", + g_object_interface_find_property (iface, "vscroll-policy")); + /** * GtkListView:model: * @@ -397,6 +594,11 @@ gtk_list_view_init (GtkListView *self) list_row_augment, list_row_clear, NULL); + + self->adjustment[GTK_ORIENTATION_HORIZONTAL] = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + self->adjustment[GTK_ORIENTATION_VERTICAL] = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + + gtk_widget_set_overflow (GTK_WIDGET (self), GTK_OVERFLOW_HIDDEN); } /** From d03a55599bf6ca590c90375c4f741278a3b36f60 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 19 Sep 2018 04:29:33 +0200 Subject: [PATCH 037/170] tests: Add a test for a permanently changing listview This is mostly for dealing with proper anchoring and can be used to check that things don't scroll or that selection and focus handling properly works. For comparison purposes, a ListBox is provided next to it. --- tests/meson.build | 1 + tests/testlistview-animating.c | 140 +++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 tests/testlistview-animating.c diff --git a/tests/meson.build b/tests/meson.build index 65c4189631..524a814816 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -50,6 +50,7 @@ gtk_tests = [ ['testlist3'], ['testlist4'], ['testlistview'], + ['testlistview-animating'], ['testlevelbar'], ['testlockbutton'], ['testmenubutton'], diff --git a/tests/testlistview-animating.c b/tests/testlistview-animating.c new file mode 100644 index 0000000000..e2c6196ab2 --- /dev/null +++ b/tests/testlistview-animating.c @@ -0,0 +1,140 @@ +#include + +#define AVERAGE 300 +#define VARIANCE 200 + +static GtkWidget * +create_widget (gpointer unused) +{ + return gtk_label_new (""); +} + +static void +bind_widget (GtkWidget *widget, + gpointer item, + gpointer unused) +{ + const char *message = g_object_get_data (item, "message"); + + gtk_label_set_text (GTK_LABEL (widget), message); +} + +static GtkWidget * +create_widget_for_listbox (gpointer item, + gpointer unused) +{ + GtkWidget *widget; + + widget = create_widget (unused); + bind_widget (widget, item, unused); + + return widget; +} + +static void +add (GListStore *store) +{ + static guint counter; + GObject *o; + char *message; + guint pos; + + o = g_object_new (G_TYPE_OBJECT, NULL); + message = g_strdup_printf ("Item %u", ++counter); + g_object_set_data_full (o, "message", message, g_free); + + pos = g_random_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store)) + 1); + g_list_store_insert (store, pos, o); + g_object_unref (o); +} + +static void +delete (GListStore *store) +{ + guint pos; + + pos = g_random_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store))); + g_list_store_remove (store, pos); +} + +static gboolean +do_stuff (gpointer store) +{ + if (g_random_int_range (AVERAGE - VARIANCE, AVERAGE + VARIANCE) < g_list_model_get_n_items (store)) + delete (store); + else + add (store); + + return G_SOURCE_CONTINUE; +} + +int +main (int argc, + char *argv[]) +{ + GtkWidget *win, *hbox, *vbox, *sw, *listview, *listbox, *label; + GListStore *store; + GListModel *toplevels; + guint i; + + gtk_init (); + + win = gtk_window_new (); + gtk_window_set_default_size (GTK_WINDOW (win), 400, 600); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + gtk_window_set_child (GTK_WINDOW (win), hbox); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4); + gtk_box_append (GTK_BOX (hbox), vbox); + + label = gtk_label_new ("GtkListView"); + gtk_box_append (GTK_BOX (vbox), label); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_set_hexpand (sw, TRUE); + gtk_widget_set_vexpand (sw, TRUE); + gtk_box_append (GTK_BOX (vbox), sw); + + listview = gtk_list_view_new (); + gtk_list_view_set_functions (GTK_LIST_VIEW (listview), + create_widget, + bind_widget, + NULL, NULL); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), listview); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4); + gtk_box_append (GTK_BOX (hbox), vbox); + + label = gtk_label_new ("GtkListBox"); + gtk_box_append (GTK_BOX (vbox), label); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_set_hexpand (sw, TRUE); + gtk_widget_set_vexpand (sw, TRUE); + gtk_box_append (GTK_BOX (vbox), sw); + + listbox = gtk_list_box_new (); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), listbox); + + store = g_list_store_new (G_TYPE_OBJECT); + for (i = 0; i < AVERAGE; i++) + add (store); + gtk_list_view_set_model (GTK_LIST_VIEW (listview), G_LIST_MODEL (store)); + gtk_list_box_bind_model (GTK_LIST_BOX (listbox), + G_LIST_MODEL (store), + create_widget_for_listbox, + NULL, NULL); + + g_timeout_add (100, do_stuff, store); + + gtk_widget_show (win); + + toplevels = gtk_window_get_toplevels (); + while (g_list_model_get_n_items (toplevels)) + g_main_context_iteration (NULL, TRUE); + + g_object_unref (store); + + return 0; +} From b3c150e929f863c6838882c7a5686ecfdc98cb9d Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 19 Sep 2018 07:08:30 +0200 Subject: [PATCH 038/170] listview: Implement an anchor The anchor selection is very basic: just anchor the top row. That's vastly better than any other widget already though. --- gtk/gtklistview.c | 172 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 167 insertions(+), 5 deletions(-) diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index f1a18224ce..abaf526419 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -51,6 +51,12 @@ struct _GtkListView GtkRbTree *rows; int list_width; + + /* managing the visible region */ + guint anchor_pos; + int anchor_dy; + guint first_visible_pos; + guint lasst_visible_pos; }; struct _ListRow @@ -157,6 +163,112 @@ gtk_list_view_get_row (GtkListView *self, return row; } +static guint +list_row_get_position (GtkListView *self, + ListRow *row) +{ + ListRow *parent, *left; + int pos; + + left = gtk_rb_tree_node_get_left (row); + if (left) + { + ListRowAugment *aug = gtk_rb_tree_get_augment (self->rows, left); + pos = aug->n_rows; + } + else + { + pos = 0; + } + + for (parent = gtk_rb_tree_node_get_parent (row); + parent != NULL; + parent = gtk_rb_tree_node_get_parent (row)) + { + left = gtk_rb_tree_node_get_left (parent); + + if (left != row) + { + if (left) + { + ListRowAugment *aug = gtk_rb_tree_get_augment (self->rows, left); + pos += aug->n_rows; + } + pos += parent->n_rows; + } + + row = parent; + } + + return pos; +} + +static ListRow * +gtk_list_view_get_row_at_y (GtkListView *self, + int y, + int *offset) +{ + ListRow *row, *tmp; + + row = gtk_rb_tree_get_root (self->rows); + + while (row) + { + tmp = gtk_rb_tree_node_get_left (row); + if (tmp) + { + ListRowAugment *aug = gtk_rb_tree_get_augment (self->rows, tmp); + if (y < aug->height) + { + row = tmp; + continue; + } + y -= aug->height; + } + + if (y < row->height) + break; + y -= row->height; + + row = gtk_rb_tree_node_get_right (row); + } + + if (offset) + *offset = row ? y : 0; + + return row; +} + +static int +list_row_get_y (GtkListView *self, + ListRow *row) +{ + ListRow *parent; + int y = 0; + + y = 0; + for (parent = gtk_rb_tree_node_get_parent (row); + parent != NULL; + parent = gtk_rb_tree_node_get_parent (row)) + { + ListRow *left = gtk_rb_tree_node_get_left (parent); + + if (left != row) + { + if (left) + { + ListRowAugment *aug = gtk_rb_tree_get_augment (self->rows, left); + y += aug->height; + } + y += parent->height; + } + + row = parent; + } + + return y ; +} + static int gtk_list_view_get_list_height (GtkListView *self) { @@ -184,15 +296,23 @@ gtk_list_view_update_adjustments (GtkListView *self, value = 0; if (_gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL) value = upper - page_size - value; + value = gtk_adjustment_get_value (self->adjustment[orientation]); } else { + ListRow *row; + page_size = gtk_widget_get_height (GTK_WIDGET (self)); upper = gtk_list_view_get_list_height (self); - value = 0; + + row = gtk_list_view_get_row (self, self->anchor_pos, NULL); + if (row) + value = list_row_get_y (self, row); + else + value = 0; + value += self->anchor_dy; } upper = MAX (upper, page_size); - value = gtk_adjustment_get_value (self->adjustment[orientation]); gtk_adjustment_configure (self->adjustment[orientation], value, @@ -310,11 +430,24 @@ gtk_list_view_remove_rows (GtkListView *self, guint n_rows) { ListRow *row; - guint i; + guint i, n_remaining; if (n_rows == 0) return; + n_remaining = self->model ? g_list_model_get_n_items (self->model) : 0; + if (self->anchor_pos >= position + n_rows) + { + self->anchor_pos -= n_rows; + } + else if (self->anchor_pos >= position) + { + self->anchor_pos = position; + if (self->anchor_pos > 0 && self->anchor_pos >= n_remaining) + self->anchor_pos = n_remaining - 1; + self->anchor_dy = 0; + } + row = gtk_list_view_get_row (self, position, NULL); for (i = 0; i < n_rows; i++) @@ -333,11 +466,18 @@ gtk_list_view_add_rows (GtkListView *self, guint n_rows) { ListRow *row; - guint i; + guint i, n_total; if (n_rows == 0) return; + n_total = self->model ? g_list_model_get_n_items (self->model) : 0; + if (self->anchor_pos >= position) + { + if (n_total != n_rows) /* the model was not empty before */ + self->anchor_pos += n_rows; + } + row = gtk_list_view_get_row (self, position, NULL); for (i = 0; i < n_rows; i++) @@ -385,7 +525,29 @@ static void gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment, GtkListView *self) { - gtk_widget_queue_allocate (GTK_WIDGET (self)); + if (adjustment == self->adjustment[GTK_ORIENTATION_VERTICAL]) + { + ListRow *row; + guint pos; + int dy; + + row = gtk_list_view_get_row_at_y (self, gtk_adjustment_get_value (adjustment), &dy); + if (row) + pos = list_row_get_position (self, row); + else + pos = 0; + + if (pos != self->anchor_pos || dy != self->anchor_dy) + { + self->anchor_pos = pos; + self->anchor_dy = dy; + gtk_widget_queue_allocate (GTK_WIDGET (self)); + } + } + else + { + gtk_widget_queue_allocate (GTK_WIDGET (self)); + } } static void From 9d5bb875b124636d94d5fdd09cc4e665e1702528 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 20 Sep 2018 05:18:34 +0200 Subject: [PATCH 039/170] listview: Add GtkListItemManager It's all stubs for now, but here's the basic ideas about what this object is supposed to do: (1) It's supposed to be handling all the child GtkWidgets that are used by the listview, so that the listview can concern itself with how many items it needs and where to put them. (2) It's meant to do the caching of widgets that are not (currently) used. (3) It's meant to track items that remain in the model across items-changed emissions and just change position. (2) It's code that can be shared between listview and potential other widgets like a GridView. It's also free to assume that the number of items it's supposed to manage doesn't grow too much, so it's free to use O(N) algorithms. --- docs/reference/gtk/meson.build | 1 + gtk/gtklistitemmanager.c | 181 ++++++++++++++++++++++++++++++++ gtk/gtklistitemmanagerprivate.h | 62 +++++++++++ gtk/gtklistview.c | 27 +++-- gtk/meson.build | 1 + 5 files changed, 262 insertions(+), 10 deletions(-) create mode 100644 gtk/gtklistitemmanager.c create mode 100644 gtk/gtklistitemmanagerprivate.h diff --git a/docs/reference/gtk/meson.build b/docs/reference/gtk/meson.build index bc28cbc7ed..0f9abbe09a 100644 --- a/docs/reference/gtk/meson.build +++ b/docs/reference/gtk/meson.build @@ -133,6 +133,7 @@ private_headers = [ 'gtkkineticscrollingprivate.h', 'gtklabelprivate.h', 'gtklistitemfactoryprivate.h', + 'gtklistitemmanagerprivate.h', 'gtklockbuttonprivate.h', 'gtkmagnifierprivate.h', 'gtkmediafileprivate.h', diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c new file mode 100644 index 0000000000..28630bda99 --- /dev/null +++ b/gtk/gtklistitemmanager.c @@ -0,0 +1,181 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gtklistitemmanagerprivate.h" + +struct _GtkListItemManager +{ + GObject parent_instance; + + GtkWidget *widget; + GListModel *model; + GtkListItemFactory *factory; +}; + +struct _GtkListItemManagerClass +{ + GObjectClass parent_class; +}; + +G_DEFINE_TYPE (GtkListItemManager, gtk_list_item_manager, G_TYPE_OBJECT) + +static void +gtk_list_item_manager_dispose (GObject *object) +{ + GtkListItemManager *self = GTK_LIST_ITEM_MANAGER (object); + + g_clear_object (&self->model); + g_clear_object (&self->factory); + + G_OBJECT_CLASS (gtk_list_item_manager_parent_class)->dispose (object); +} + +static void +gtk_list_item_manager_class_init (GtkListItemManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gtk_list_item_manager_dispose; +} + +static void +gtk_list_item_manager_init (GtkListItemManager *self) +{ +} + +GtkListItemManager * +gtk_list_item_manager_new (GtkWidget *widget) +{ + GtkListItemManager *self; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); + + self = g_object_new (GTK_TYPE_LIST_ITEM_MANAGER, NULL); + + self->widget = widget; + + return self; +} + +void +gtk_list_item_manager_set_factory (GtkListItemManager *self, + GtkListItemFactory *factory) +{ + g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); + g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (factory)); + + if (self->factory == factory) + return; + + g_clear_object (&self->factory); + + self->factory = g_object_ref (factory); +} + +GtkListItemFactory * +gtk_list_item_manager_get_factory (GtkListItemManager *self) +{ + g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL); + + return self->factory; +} + +void +gtk_list_item_manager_set_model (GtkListItemManager *self, + GListModel *model) +{ + g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); + g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model)); + + if (self->model == model) + return; + + g_clear_object (&self->model); + + if (model) + self->model = g_object_ref (model); +} + +GListModel * +gtk_list_item_manager_get_model (GtkListItemManager *self) +{ + g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL); + + return self->model; +} + +/* + * gtk_list_item_manager_model_changed: + * @self: a #GtkListItemManager + * @position: the position at which the model changed + * @removed: the number of items removed + * @added: the number of items added + * + * This function must be called by the owning @widget at the + * appropriate time. + * The manager does not connect to GListModel::items-changed itself + * but relies on its widget calling this function. + * + * This function should be called after @widget has released all + * #GListItems it intends to delete in response to the @removed rows + * but before it starts creating new ones for the @added rows. + **/ +void +gtk_list_item_manager_model_changed (GtkListItemManager *self, + guint position, + guint removed, + guint added) +{ + g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); + g_return_if_fail (self->model != NULL); +} + +/* + * gtk_list_item_manager_create_list_item: + * @self: a #GtkListItemManager + * @position: the row in the model to create a list item for + * @next_sibling: the widget this widget should be inserted before or %NULL + * if none + * + * Creates a new list item widget to use for @position. No widget may + * yet exist that is used for @position. + * + * Returns: a properly setup widget to use in @position + **/ +GtkWidget * +gtk_list_item_manager_create_list_item (GtkListItemManager *self, + guint position, + GtkWidget *next_sibling) +{ + GtkWidget *result; + gpointer item; + + g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL); + g_return_val_if_fail (next_sibling == NULL || GTK_IS_WIDGET (next_sibling), NULL); + + result = gtk_list_item_factory_create (self->factory); + item = g_list_model_get_item (self->model, position); + gtk_list_item_factory_bind (self->factory, result, item); + g_object_unref (item); + gtk_widget_insert_before (result, self->widget, next_sibling); + + return result; +} diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h new file mode 100644 index 0000000000..34b57da97e --- /dev/null +++ b/gtk/gtklistitemmanagerprivate.h @@ -0,0 +1,62 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + + +#ifndef __GTK_LIST_ITEM_MANAGER_H__ +#define __GTK_LIST_ITEM_MANAGER_H__ + +#include "gtk/gtktypes.h" + +#include "gtk/gtklistitemfactoryprivate.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_LIST_ITEM_MANAGER (gtk_list_item_manager_get_type ()) +#define GTK_LIST_ITEM_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_LIST_ITEM_MANAGER, GtkListItemManager)) +#define GTK_LIST_ITEM_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_LIST_ITEM_MANAGER, GtkListItemManagerClass)) +#define GTK_IS_LIST_ITEM_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_LIST_ITEM_MANAGER)) +#define GTK_IS_LIST_ITEM_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_LIST_ITEM_MANAGER)) +#define GTK_LIST_ITEM_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_LIST_ITEM_MANAGER, GtkListItemManagerClass)) + +typedef struct _GtkListItemManager GtkListItemManager; +typedef struct _GtkListItemManagerClass GtkListItemManagerClass; + +GType gtk_list_item_manager_get_type (void) G_GNUC_CONST; + +GtkListItemManager * gtk_list_item_manager_new (GtkWidget *widget); + +void gtk_list_item_manager_set_factory (GtkListItemManager *self, + GtkListItemFactory *factory); +GtkListItemFactory * gtk_list_item_manager_get_factory (GtkListItemManager *self); +void gtk_list_item_manager_set_model (GtkListItemManager *self, + GListModel *model); +GListModel * gtk_list_item_manager_get_model (GtkListItemManager *self); + + +void gtk_list_item_manager_model_changed (GtkListItemManager *self, + guint position, + guint removed, + guint added); +GtkWidget * gtk_list_item_manager_create_list_item (GtkListItemManager *self, + guint position, + GtkWidget *next_sibling); + +G_END_DECLS + +#endif /* __GTK_LIST_ITEM_MANAGER_H__ */ diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index abaf526419..dec238c00c 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -25,6 +25,7 @@ #include "gtkintl.h" #include "gtkrbtreeprivate.h" #include "gtklistitemfactoryprivate.h" +#include "gtklistitemmanagerprivate.h" #include "gtkscrollable.h" #include "gtkwidgetprivate.h" @@ -45,7 +46,7 @@ struct _GtkListView GtkWidget parent_instance; GListModel *model; - GtkListItemFactory *item_factory; + GtkListItemManager *item_manager; GtkAdjustment *adjustment[2]; GtkScrollablePolicy scroll_policy[2]; @@ -483,14 +484,12 @@ gtk_list_view_add_rows (GtkListView *self, for (i = 0; i < n_rows; i++) { ListRow *new_row; - gpointer item; - + new_row = gtk_rb_tree_insert_before (self->rows, row); new_row->n_rows = 1; - new_row->widget = gtk_list_item_factory_create (self->item_factory); - gtk_widget_insert_before (new_row->widget, GTK_WIDGET (self), row ? row->widget : NULL); - item = g_list_model_get_item (self->model, position + i); - gtk_list_item_factory_bind (self->item_factory, new_row->widget, item); + new_row->widget = gtk_list_item_manager_create_list_item (self->item_manager, + position + i, + row ? row->widget : NULL); } gtk_widget_queue_resize (GTK_WIDGET (self)); @@ -504,6 +503,7 @@ gtk_list_view_model_items_changed_cb (GListModel *model, GtkListView *self) { gtk_list_view_remove_rows (self, position, removed); + gtk_list_item_manager_model_changed (self->item_manager, position, removed, added); gtk_list_view_add_rows (self, position, added); } @@ -573,7 +573,7 @@ gtk_list_view_dispose (GObject *object) gtk_list_view_clear_adjustment (self, GTK_ORIENTATION_HORIZONTAL); gtk_list_view_clear_adjustment (self, GTK_ORIENTATION_VERTICAL); - g_clear_object (&self->item_factory); + g_clear_object (&self->item_manager); G_OBJECT_CLASS (gtk_list_view_parent_class)->dispose (object); } @@ -584,6 +584,7 @@ gtk_list_view_finalize (GObject *object) GtkListView *self = GTK_LIST_VIEW (object); gtk_rb_tree_unref (self->rows); + g_clear_object (&self->item_manager); G_OBJECT_CLASS (gtk_list_view_parent_class)->finalize (object); } @@ -751,6 +752,8 @@ gtk_list_view_class_init (GtkListViewClass *klass) static void gtk_list_view_init (GtkListView *self) { + self->item_manager = gtk_list_item_manager_new (GTK_WIDGET (self)); + self->rows = gtk_rb_tree_new (ListRow, ListRowAugment, list_row_augment, @@ -814,6 +817,8 @@ gtk_list_view_set_model (GtkListView *self, gtk_list_view_clear_model (self); + gtk_list_item_manager_set_model (self->item_manager, model); + if (model) { self->model = g_object_ref (model); @@ -836,6 +841,7 @@ gtk_list_view_set_functions (GtkListView *self, gpointer user_data, GDestroyNotify user_destroy) { + GtkListItemFactory *factory; guint n_items; g_return_if_fail (GTK_IS_LIST_VIEW (self)); @@ -846,8 +852,9 @@ gtk_list_view_set_functions (GtkListView *self, n_items = self->model ? g_list_model_get_n_items (self->model) : 0; gtk_list_view_remove_rows (self, 0, n_items); - g_clear_object (&self->item_factory); - self->item_factory = gtk_list_item_factory_new (create_func, bind_func, user_data, user_destroy); + factory = gtk_list_item_factory_new (create_func, bind_func, user_data, user_destroy); + gtk_list_item_manager_set_factory (self->item_manager, factory); + g_object_unref (factory); gtk_list_view_add_rows (self, 0, n_items); } diff --git a/gtk/meson.build b/gtk/meson.build index c1cb68daf4..338ed4ad44 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -272,6 +272,7 @@ gtk_public_sources = files([ 'gtklinkbutton.c', 'gtklistbox.c', 'gtklistitemfactory.c', + 'gtklistitemmanager.c', 'gtklistlistmodel.c', 'gtkliststore.c', 'gtklistview.c', From e1fa6271581b151e2900152499ecfae3feb2eae1 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 21 Sep 2018 05:05:34 +0200 Subject: [PATCH 040/170] listview: Add GtkListItem GtkListItem is a generic row widget that is supposed to replace GtkListBoxRow and GtkFlowBoxChild. --- docs/reference/gtk/gtk4-sections.txt | 19 ++ docs/reference/gtk/gtk4.types.in | 1 + docs/reference/gtk/meson.build | 1 + gtk/gtk.h | 1 + gtk/gtklistitem.c | 279 +++++++++++++++++++++++++++ gtk/gtklistitem.h | 57 ++++++ gtk/gtklistitemfactory.c | 31 ++- gtk/gtklistitemfactoryprivate.h | 7 +- gtk/gtklistitemmanager.c | 6 +- gtk/gtklistitemprivate.h | 35 ++++ gtk/meson.build | 2 + 11 files changed, 429 insertions(+), 10 deletions(-) create mode 100644 gtk/gtklistitem.c create mode 100644 gtk/gtklistitem.h create mode 100644 gtk/gtklistitemprivate.h diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 3479a557b2..270a86f88e 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -392,6 +392,24 @@ gtk_single_selection_set_can_unselect gtk_single_selection_get_type +
+gtklistitem +GtkListItem +GtkListItem +gtk_list_item_get_item +gtk_list_item_get_child +gtk_list_item_set_child + +GTK_LIST_ITEM +GTK_LIST_ITEM_CLASS +GTK_LIST_ITEM_GET_CLASS +GTK_IS_LIST_ITEM +GTK_IS_LIST_ITEM_CLASS +GTK_TYPE_LIST_ITEM + +gtk_list_item_get_type +
+
gtklistitemfactory GtkListItemFactory @@ -407,6 +425,7 @@ GTK_TYPE_LIST_ITEM_FACTORY gtk_list_item_factory_get_type
+
gtklistview GtkListView GtkListView diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in index 30292ea74a..9ff1c8c204 100644 --- a/docs/reference/gtk/gtk4.types.in +++ b/docs/reference/gtk/gtk4.types.in @@ -119,6 +119,7 @@ gtk_label_get_type gtk_layout_child_get_type gtk_layout_manager_get_type gtk_link_button_get_type +gtk_list_item_get_type gtk_list_item_factory_get_type gtk_list_store_get_type gtk_list_box_get_type diff --git a/docs/reference/gtk/meson.build b/docs/reference/gtk/meson.build index 0f9abbe09a..e2aa759385 100644 --- a/docs/reference/gtk/meson.build +++ b/docs/reference/gtk/meson.build @@ -132,6 +132,7 @@ private_headers = [ 'gtkimmoduleprivate.h', 'gtkkineticscrollingprivate.h', 'gtklabelprivate.h', + 'gtklistitemprivate.h', 'gtklistitemfactoryprivate.h', 'gtklistitemmanagerprivate.h', 'gtklockbuttonprivate.h', diff --git a/gtk/gtk.h b/gtk/gtk.h index 680efe4581..e06f384ad6 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -153,6 +153,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtklistitem.c b/gtk/gtklistitem.c new file mode 100644 index 0000000000..65f52acf22 --- /dev/null +++ b/gtk/gtklistitem.c @@ -0,0 +1,279 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gtklistitemprivate.h" + +#include "gtkbinlayout.h" +#include "gtkintl.h" +#include "gtkwidget.h" + +/** + * SECTION:gtklistitem + * @title: GtkListItem + * @short_description: Object used to represent items of a ListModel + * @see_also: #GtkListView, #GListModel + * + * #GtkListItem is the object that list-handling containers such + * as #GtkListView use to represent items in a #GListModel. They are + * managed by the container and cannot be created by application code. + * + * #GtkListItems need to be populated by application code. This is done by + * calling gtk_list_item_set_child(). + * + * #GtkListItems exist in 2 stages: + * + * 1. The unbound stage where the listitem is not currently connected to + * an item in the list. In that case, the #GtkListItem:item property is + * set to %NULL. + * + * 2. The bound stage where the listitem references an item from the list. + * The #GtkListItem:item property is not %NULL. + */ + +struct _GtkListItem +{ + GtkWidget parent_instance; + + GObject *item; + GtkWidget *child; +}; + +struct _GtkListItemClass +{ + GtkWidgetClass parent_class; +}; + +enum +{ + PROP_0, + PROP_CHILD, + PROP_ITEM, + + N_PROPS +}; + +G_DEFINE_TYPE (GtkListItem, gtk_list_item, GTK_TYPE_WIDGET) + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +static void +gtk_list_item_dispose (GObject *object) +{ + GtkListItem *self = GTK_LIST_ITEM (object); + + g_assert (self->item == NULL); + g_clear_pointer (&self->child, gtk_widget_unparent); + + G_OBJECT_CLASS (gtk_list_item_parent_class)->dispose (object); +} + +static void +gtk_list_item_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GtkListItem *self = GTK_LIST_ITEM (object); + + switch (property_id) + { + case PROP_CHILD: + g_value_set_object (value, self->child); + break; + + case PROP_ITEM: + g_value_set_object (value, self->item); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gtk_list_item_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkListItem *self = GTK_LIST_ITEM (object); + + switch (property_id) + { + case PROP_CHILD: + gtk_list_item_set_child (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gtk_list_item_class_init (GtkListItemClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->dispose = gtk_list_item_dispose; + gobject_class->get_property = gtk_list_item_get_property; + gobject_class->set_property = gtk_list_item_set_property; + + /** + * GtkListItem:child: + * + * Widget used for display + */ + properties[PROP_CHILD] = + g_param_spec_object ("child", + P_("Child"), + P_("Widget used for display"), + GTK_TYPE_WIDGET, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * GtkListItem:item: + * + * Displayed item + */ + properties[PROP_ITEM] = + g_param_spec_object ("item", + P_("Item"), + P_("Displayed item"), + G_TYPE_OBJECT, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPS, properties); + + /* This gets overwritten by gtk_list_item_new() but better safe than sorry */ + gtk_widget_class_set_css_name (widget_class, I_("row")); + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); +} + +static void +gtk_list_item_init (GtkListItem *self) +{ +} + +GtkWidget * +gtk_list_item_new (const char *css_name) +{ + g_return_val_if_fail (css_name != NULL, NULL); + + return g_object_new (GTK_TYPE_LIST_ITEM, + "css-name", css_name, + NULL); +} + +/** + * gtk_list_item_get_item: + * @self: a #GtkListItem + * + * Gets the item that is currently displayed or model that @self is + * currently bound to or %NULL if @self is unbound. + * + * Returns: (nullable) (transfer none) (type GObject): The model in use + **/ +gpointer +gtk_list_item_get_item (GtkListItem *self) +{ + g_return_val_if_fail (GTK_IS_LIST_ITEM (self), NULL); + + return self->item; +} + +/** + * gtk_list_item_get_child: + * @self: a #GtkListItem + * + * Gets the child previously set via gtk_list_item_set_child() or + * %NULL if none was set. + * + * Returns: (transfer none) (nullable): The child + **/ +GtkWidget * +gtk_list_item_get_child (GtkListItem *self) +{ + g_return_val_if_fail (GTK_IS_LIST_ITEM (self), NULL); + + return self->child; +} + +/** + * gtk_list_item_set_child: + * @self: a #GtkListItem + * @child: (nullable): The list item's child or %NULL to unset + * + * Sets the child to be used for this listitem. + * + * This function is typically called by applications when + * setting up a listitem so that the widget can be reused when + * binding it multiple times. + **/ +void +gtk_list_item_set_child (GtkListItem *self, + GtkWidget *child) +{ + g_return_if_fail (GTK_IS_LIST_ITEM (self)); + g_return_if_fail (child == NULL || GTK_IS_WIDGET (child)); + + if (self->child == child) + return; + + g_clear_pointer (&self->child, gtk_widget_unparent); + + if (child) + { + gtk_widget_insert_after (child, GTK_WIDGET (self), NULL); + self->child = child; + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]); +} + +void +gtk_list_item_bind (GtkListItem *self, + gpointer item) +{ + g_return_if_fail (GTK_IS_LIST_ITEM (self)); + g_return_if_fail (G_IS_OBJECT (item)); + /* Must unbind before rebinding */ + g_return_if_fail (self->item == NULL); + + self->item = g_object_ref (item); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]); +} + +void +gtk_list_item_unbind (GtkListItem *self) +{ + g_return_if_fail (GTK_IS_LIST_ITEM (self)); + /* Must be bound */ + g_return_if_fail (self->item != NULL); + + g_clear_object (&self->item); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]); +} + diff --git a/gtk/gtklistitem.h b/gtk/gtklistitem.h new file mode 100644 index 0000000000..3af77cb14e --- /dev/null +++ b/gtk/gtklistitem.h @@ -0,0 +1,57 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __GTK_LIST_ITEM_H__ +#define __GTK_LIST_ITEM_H__ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_LIST_ITEM (gtk_list_item_get_type ()) +#define GTK_TYPE_LIST_ITEM (gtk_list_item_get_type ()) +#define GTK_LIST_ITEM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_LIST_ITEM, GtkListItem)) +#define GTK_LIST_ITEM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_LIST_ITEM, GtkListItemClass)) +#define GTK_IS_LIST_ITEM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_LIST_ITEM)) +#define GTK_IS_LIST_ITEM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_LIST_ITEM)) +#define GTK_LIST_ITEM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_LIST_ITEM, GtkListItemClass)) + +typedef struct _GtkListItem GtkListItem; +typedef struct _GtkListItemClass GtkListItemClass; + +GDK_AVAILABLE_IN_ALL +GType gtk_list_item_get_type (void) G_GNUC_CONST; + +GDK_AVAILABLE_IN_ALL +gpointer gtk_list_item_get_item (GtkListItem *self); + +GDK_AVAILABLE_IN_ALL +void gtk_list_item_set_child (GtkListItem *self, + GtkWidget *child); +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_list_item_get_child (GtkListItem *self); + + +G_END_DECLS + +#endif /* __GTK_LIST_ITEM_H__ */ diff --git a/gtk/gtklistitemfactory.c b/gtk/gtklistitemfactory.c index 7519bc01bb..a258dee1f4 100644 --- a/gtk/gtklistitemfactory.c +++ b/gtk/gtklistitemfactory.c @@ -21,6 +21,8 @@ #include "gtklistitemfactoryprivate.h" +#include "gtklistitemprivate.h" + /** * SECTION:gtklistitemfactory * @Title: GtkListItemFactory @@ -132,22 +134,41 @@ gtk_list_item_factory_new (GtkListCreateWidgetFunc create_func, return self; } -GtkWidget * +GtkListItem * gtk_list_item_factory_create (GtkListItemFactory *self) { + GtkWidget *widget, *result; + g_return_val_if_fail (GTK_IS_LIST_ITEM_FACTORY (self), NULL); - return self->create_func (self->user_data); + widget = self->create_func (self->user_data); + + result = gtk_list_item_new ("row"); + + gtk_list_item_set_child (GTK_LIST_ITEM (result), widget); + + return GTK_LIST_ITEM (result); } void gtk_list_item_factory_bind (GtkListItemFactory *self, - GtkWidget *widget, + GtkListItem *list_item, gpointer item) { g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self)); - g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (GTK_IS_LIST_ITEM (list_item)); - self->bind_func (widget, item, self->user_data); + gtk_list_item_bind (list_item, item); + + self->bind_func (gtk_list_item_get_child (list_item), item, self->user_data); } +void +gtk_list_item_factory_unbind (GtkListItemFactory *self, + GtkListItem *list_item) +{ + g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self)); + g_return_if_fail (GTK_IS_LIST_ITEM (list_item)); + + gtk_list_item_unbind (list_item); +} diff --git a/gtk/gtklistitemfactoryprivate.h b/gtk/gtklistitemfactoryprivate.h index 28ae55b4aa..a6bec12faf 100644 --- a/gtk/gtklistitemfactoryprivate.h +++ b/gtk/gtklistitemfactoryprivate.h @@ -21,6 +21,7 @@ #ifndef __GTK_LIST_ITEM_FACTORY_H__ #define __GTK_LIST_ITEM_FACTORY_H__ +#include #include G_BEGIN_DECLS @@ -42,11 +43,13 @@ GtkListItemFactory * gtk_list_item_factory_new (GtkListCreateWi gpointer user_data, GDestroyNotify user_destroy); -GtkWidget * gtk_list_item_factory_create (GtkListItemFactory *self); +GtkListItem * gtk_list_item_factory_create (GtkListItemFactory *self); void gtk_list_item_factory_bind (GtkListItemFactory *self, - GtkWidget *widget, + GtkListItem *list_item, gpointer item); +void gtk_list_item_factory_unbind (GtkListItemFactory *self, + GtkListItem *list_item); G_END_DECLS diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index 28630bda99..500cc38e5e 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -165,7 +165,7 @@ gtk_list_item_manager_create_list_item (GtkListItemManager *self, guint position, GtkWidget *next_sibling) { - GtkWidget *result; + GtkListItem *result; gpointer item; g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL); @@ -175,7 +175,7 @@ gtk_list_item_manager_create_list_item (GtkListItemManager *self, item = g_list_model_get_item (self->model, position); gtk_list_item_factory_bind (self->factory, result, item); g_object_unref (item); - gtk_widget_insert_before (result, self->widget, next_sibling); + gtk_widget_insert_before (GTK_WIDGET (result), self->widget, next_sibling); - return result; + return GTK_WIDGET (result); } diff --git a/gtk/gtklistitemprivate.h b/gtk/gtklistitemprivate.h new file mode 100644 index 0000000000..a0a90f7d79 --- /dev/null +++ b/gtk/gtklistitemprivate.h @@ -0,0 +1,35 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __GTK_LIST_ITEM_PRIVATE_H__ +#define __GTK_LIST_ITEM_PRIVATE_H__ + +#include "gtklistitem.h" + +G_BEGIN_DECLS + +GtkWidget * gtk_list_item_new (const char *css_name); + +void gtk_list_item_bind (GtkListItem *self, + gpointer item); +void gtk_list_item_unbind (GtkListItem *self); + +G_END_DECLS + +#endif /* __GTK_LIST_ITEM_PRIVATE_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index 338ed4ad44..6c516bc1c2 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -271,6 +271,7 @@ gtk_public_sources = files([ 'gtklevelbar.c', 'gtklinkbutton.c', 'gtklistbox.c', + 'gtklistitem.c', 'gtklistitemfactory.c', 'gtklistitemmanager.c', 'gtklistlistmodel.c', @@ -537,6 +538,7 @@ gtk_public_headers = files([ 'gtklevelbar.h', 'gtklinkbutton.h', 'gtklistbox.h', + 'gtklistitem.h', 'gtkliststore.h', 'gtklistview.h', 'gtklockbutton.h', From 54042029d3e0cd2fbd880cdfbe923c1866db769e Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 21 Sep 2018 05:32:00 +0200 Subject: [PATCH 041/170] listview: Make the listitemmanager stricter Require that items created with the manager get destroyed via the manager. To that purpose, renamed create_list_item() to acquire_list_item() and add a matching release_list_item() function. This way, the manager can in the future keep track of all items and cache information about them. --- gtk/gtklistitemmanager.c | 35 ++++++++++++++++++++++++++++----- gtk/gtklistitemmanagerprivate.h | 4 +++- gtk/gtklistview.c | 10 ++++++---- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index 500cc38e5e..2f2a1d2db9 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -149,21 +149,27 @@ gtk_list_item_manager_model_changed (GtkListItemManager *self, } /* - * gtk_list_item_manager_create_list_item: + * gtk_list_item_manager_acquire_list_item: * @self: a #GtkListItemManager * @position: the row in the model to create a list item for * @next_sibling: the widget this widget should be inserted before or %NULL * if none * - * Creates a new list item widget to use for @position. No widget may + * Creates a list item widget to use for @position. No widget may * yet exist that is used for @position. * + * When the returned item is no longer needed, the caller is responsible + * for calling gtk_list_item_manager_release_list_item(). + * A particular case is when the row at @position is removed. In that case, + * all list items in the removed range must be released before + * gtk_list_item_manager_model_changed() is called. + * * Returns: a properly setup widget to use in @position **/ GtkWidget * -gtk_list_item_manager_create_list_item (GtkListItemManager *self, - guint position, - GtkWidget *next_sibling) +gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, + guint position, + GtkWidget *next_sibling) { GtkListItem *result; gpointer item; @@ -179,3 +185,22 @@ gtk_list_item_manager_create_list_item (GtkListItemManager *self, return GTK_WIDGET (result); } + +/* + * gtk_list_item_manager_release_list_item: + * @self: a #GtkListItemManager + * @item: an item previously acquired with + * gtk_list_item_manager_acquire_list_item() + * + * Releases an item that was previously acquired via + * gtk_list_item_manager_acquire_list_item() and is no longer in use. + **/ +void +gtk_list_item_manager_release_list_item (GtkListItemManager *self, + GtkWidget *item) +{ + g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); + g_return_if_fail (GTK_IS_LIST_ITEM (item)); + + gtk_widget_unparent (item); +} diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h index 34b57da97e..84f574576e 100644 --- a/gtk/gtklistitemmanagerprivate.h +++ b/gtk/gtklistitemmanagerprivate.h @@ -53,9 +53,11 @@ void gtk_list_item_manager_model_changed (GtkListItemMana guint position, guint removed, guint added); -GtkWidget * gtk_list_item_manager_create_list_item (GtkListItemManager *self, +GtkWidget * gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, guint position, GtkWidget *next_sibling); +void gtk_list_item_manager_release_list_item (GtkListItemManager *self, + GtkWidget *widget); G_END_DECLS diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index dec238c00c..c9d0e0ac29 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -125,7 +125,7 @@ list_row_clear (gpointer _row) { ListRow *row = _row; - g_clear_pointer (&row->widget, gtk_widget_unparent); + g_assert (row->widget == NULL); } static ListRow * @@ -454,6 +454,8 @@ gtk_list_view_remove_rows (GtkListView *self, for (i = 0; i < n_rows; i++) { ListRow *next = gtk_rb_tree_node_get_next (row); + gtk_list_item_manager_release_list_item (self->item_manager, row->widget); + row->widget = NULL; gtk_rb_tree_remove (self->rows, row); row = next; } @@ -487,9 +489,9 @@ gtk_list_view_add_rows (GtkListView *self, new_row = gtk_rb_tree_insert_before (self->rows, row); new_row->n_rows = 1; - new_row->widget = gtk_list_item_manager_create_list_item (self->item_manager, - position + i, - row ? row->widget : NULL); + new_row->widget = gtk_list_item_manager_acquire_list_item (self->item_manager, + position + i, + row ? row->widget : NULL); } gtk_widget_queue_resize (GTK_WIDGET (self)); From ec8684e87ddf2dbedbf935678e402a99661bbe61 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 22 Sep 2018 22:11:27 +0200 Subject: [PATCH 042/170] listview: Change change management Add a GtkListItemManagerChange object that tracks all removed list rows during an item-changed signal so they can be added back later. --- gtk/gtklistitemmanager.c | 149 ++++++++++++++++++++++++++++---- gtk/gtklistitemmanagerprivate.h | 16 +++- gtk/gtklistview.c | 53 ++++++++---- 3 files changed, 178 insertions(+), 40 deletions(-) diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index 2f2a1d2db9..a88502efbb 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -35,6 +35,11 @@ struct _GtkListItemManagerClass GObjectClass parent_class; }; +struct _GtkListItemManagerChange +{ + GHashTable *items; +}; + G_DEFINE_TYPE (GtkListItemManager, gtk_list_item_manager, G_TYPE_OBJECT) static void @@ -122,30 +127,81 @@ gtk_list_item_manager_get_model (GtkListItemManager *self) return self->model; } +#if 0 /* - * gtk_list_item_manager_model_changed: + * gtk_list_item_manager_get_size: * @self: a #GtkListItemManager - * @position: the position at which the model changed - * @removed: the number of items removed - * @added: the number of items added * - * This function must be called by the owning @widget at the - * appropriate time. - * The manager does not connect to GListModel::items-changed itself - * but relies on its widget calling this function. + * Queries the number of widgets currently handled by @self. * - * This function should be called after @widget has released all - * #GListItems it intends to delete in response to the @removed rows - * but before it starts creating new ones for the @added rows. + * This includes both widgets that have been acquired and + * those currently waiting to be used again. + * + * Returns: Number of widgets handled by @self + **/ +guint +gtk_list_item_manager_get_size (GtkListItemManager *self) +{ + return g_hash_table_size (self->pool); +} +#endif + +/* + * gtk_list_item_manager_begin_change: + * @self: a #GtkListItemManager + * + * Begins a change operation in response to a model's items-changed + * signal. + * During an ongoing change operation, list items will not be discarded + * when released but will be kept around in anticipation of them being + * added back in a different posiion later. + * + * Once it is known that no more list items will be reused, + * gtk_list_item_manager_end_change() should be called. This should happen + * as early as possible, so the list items held for the change can be + * reqcquired. + * + * Returns: The object to use for this change + **/ +GtkListItemManagerChange * +gtk_list_item_manager_begin_change (GtkListItemManager *self) +{ + GtkListItemManagerChange *change; + + g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL); + + change = g_slice_new (GtkListItemManagerChange); + change->items = g_hash_table_new (g_direct_hash, g_direct_equal); + + return change; +} + +/* + * gtk_list_item_manager_end_change: + * @self: a #GtkListItemManager + * @change: a change + * + * Ends a change operation begun with gtk_list_item_manager_begin_change() + * and releases all list items still cached. **/ void -gtk_list_item_manager_model_changed (GtkListItemManager *self, - guint position, - guint removed, - guint added) +gtk_list_item_manager_end_change (GtkListItemManager *self, + GtkListItemManagerChange *change) { + GHashTableIter iter; + gpointer list_item; + g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); - g_return_if_fail (self->model != NULL); + g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); + + g_hash_table_iter_init (&iter, change->items); + while (g_hash_table_iter_next (&iter, NULL, &list_item)) + { + gtk_list_item_manager_release_list_item (self, NULL, list_item); + } + + g_hash_table_unref (change->items); + g_slice_free (GtkListItemManagerChange, change); } /* @@ -178,6 +234,7 @@ gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, g_return_val_if_fail (next_sibling == NULL || GTK_IS_WIDGET (next_sibling), NULL); result = gtk_list_item_factory_create (self->factory); + item = g_list_model_get_item (self->model, position); gtk_list_item_factory_bind (self->factory, result, item); g_object_unref (item); @@ -186,9 +243,55 @@ gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, return GTK_WIDGET (result); } +/** + * gtk_list_item_manager_try_acquire_list_item_from_change: + * @self: a #GtkListItemManager + * @position: the row in the model to create a list item for + * @next_sibling: the widget this widget should be inserted before or %NULL + * if none + * + * Like gtk_list_item_manager_acquire_list_item(), but only tries to acquire list + * items from those previously released as part of @change. + * If no matching list item is found, %NULL is returned and the caller should use + * gtk_list_item_manager_acquire_list_item(). + * + * Returns: (nullable): a properly setup widget to use in @position or %NULL if + * no item for reuse existed + **/ +GtkWidget * +gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self, + GtkListItemManagerChange *change, + guint position, + GtkWidget *next_sibling) +{ + GtkListItem *result; + gpointer item; + + g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL); + g_return_val_if_fail (next_sibling == NULL || GTK_IS_WIDGET (next_sibling), NULL); + + /* XXX: can we avoid temporarily allocating items on failure? */ + item = g_list_model_get_item (self->model, position); + if (g_hash_table_steal_extended (change->items, item, NULL, (gpointer *) &result)) + { + gtk_widget_insert_before (GTK_WIDGET (result), self->widget, next_sibling); + /* XXX: Should we let the listview do this? */ + gtk_widget_queue_resize (GTK_WIDGET (result)); + } + else + { + result = NULL; + } + g_object_unref (item); + + return GTK_WIDGET (result); +} + /* * gtk_list_item_manager_release_list_item: * @self: a #GtkListItemManager + * @change: (allow-none): The change associated with this release or + * %NULL if this is a final removal * @item: an item previously acquired with * gtk_list_item_manager_acquire_list_item() * @@ -196,11 +299,21 @@ gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, * gtk_list_item_manager_acquire_list_item() and is no longer in use. **/ void -gtk_list_item_manager_release_list_item (GtkListItemManager *self, - GtkWidget *item) +gtk_list_item_manager_release_list_item (GtkListItemManager *self, + GtkListItemManagerChange *change, + GtkWidget *item) { g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); g_return_if_fail (GTK_IS_LIST_ITEM (item)); + if (change != NULL) + { + if (g_hash_table_insert (change->items, gtk_list_item_get_item (GTK_LIST_ITEM (item)), item)) + return; + + g_warning ("FIXME: Handle the same item multiple times in the list.\nLars says this totally should not happen, but here we are."); + } + + gtk_list_item_factory_unbind (self->factory, GTK_LIST_ITEM (item)); gtk_widget_unparent (item); } diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h index 84f574576e..2af8e8fae7 100644 --- a/gtk/gtklistitemmanagerprivate.h +++ b/gtk/gtklistitemmanagerprivate.h @@ -36,6 +36,7 @@ G_BEGIN_DECLS typedef struct _GtkListItemManager GtkListItemManager; typedef struct _GtkListItemManagerClass GtkListItemManagerClass; +typedef struct _GtkListItemManagerChange GtkListItemManagerChange; GType gtk_list_item_manager_get_type (void) G_GNUC_CONST; @@ -48,15 +49,22 @@ void gtk_list_item_manager_set_model (GtkListItemMana GListModel *model); GListModel * gtk_list_item_manager_get_model (GtkListItemManager *self); +guint gtk_list_item_manager_get_size (GtkListItemManager *self); -void gtk_list_item_manager_model_changed (GtkListItemManager *self, - guint position, - guint removed, - guint added); +GtkListItemManagerChange * + gtk_list_item_manager_begin_change (GtkListItemManager *self); +void gtk_list_item_manager_end_change (GtkListItemManager *self, + GtkListItemManagerChange *change); GtkWidget * gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, guint position, GtkWidget *next_sibling); +GtkWidget * gtk_list_item_manager_try_reacquire_list_item + (GtkListItemManager *self, + GtkListItemManagerChange *change, + guint position, + GtkWidget *next_sibling); void gtk_list_item_manager_release_list_item (GtkListItemManager *self, + GtkListItemManagerChange *change, GtkWidget *widget); G_END_DECLS diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index c9d0e0ac29..f5c36e55fc 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -426,9 +426,10 @@ gtk_list_view_size_allocate (GtkWidget *widget, } static void -gtk_list_view_remove_rows (GtkListView *self, - guint position, - guint n_rows) +gtk_list_view_remove_rows (GtkListView *self, + GtkListItemManagerChange *change, + guint position, + guint n_rows) { ListRow *row; guint i, n_remaining; @@ -454,7 +455,7 @@ gtk_list_view_remove_rows (GtkListView *self, for (i = 0; i < n_rows; i++) { ListRow *next = gtk_rb_tree_node_get_next (row); - gtk_list_item_manager_release_list_item (self->item_manager, row->widget); + gtk_list_item_manager_release_list_item (self->item_manager, change, row->widget); row->widget = NULL; gtk_rb_tree_remove (self->rows, row); row = next; @@ -464,10 +465,11 @@ gtk_list_view_remove_rows (GtkListView *self, } static void -gtk_list_view_add_rows (GtkListView *self, - guint position, - guint n_rows) -{ +gtk_list_view_add_rows (GtkListView *self, + GtkListItemManagerChange *change, + guint position, + guint n_rows) +{ ListRow *row; guint i, n_total; @@ -489,9 +491,19 @@ gtk_list_view_add_rows (GtkListView *self, new_row = gtk_rb_tree_insert_before (self->rows, row); new_row->n_rows = 1; - new_row->widget = gtk_list_item_manager_acquire_list_item (self->item_manager, - position + i, - row ? row->widget : NULL); + if (change) + { + new_row->widget = gtk_list_item_manager_try_reacquire_list_item (self->item_manager, + change, + position + i, + row ? row->widget : NULL); + } + if (new_row->widget == NULL) + { + new_row->widget = gtk_list_item_manager_acquire_list_item (self->item_manager, + position + i, + row ? row->widget : NULL); + } } gtk_widget_queue_resize (GTK_WIDGET (self)); @@ -504,9 +516,14 @@ gtk_list_view_model_items_changed_cb (GListModel *model, guint added, GtkListView *self) { - gtk_list_view_remove_rows (self, position, removed); - gtk_list_item_manager_model_changed (self->item_manager, position, removed, added); - gtk_list_view_add_rows (self, position, added); + GtkListItemManagerChange *change; + + change = gtk_list_item_manager_begin_change (self->item_manager); + + gtk_list_view_remove_rows (self, change, position, removed); + gtk_list_view_add_rows (self, change, position, added); + + gtk_list_item_manager_end_change (self->item_manager, change); } static void @@ -515,7 +532,7 @@ gtk_list_view_clear_model (GtkListView *self) if (self->model == NULL) return; - gtk_list_view_remove_rows (self, 0, g_list_model_get_n_items (self->model)); + gtk_list_view_remove_rows (self, NULL, 0, g_list_model_get_n_items (self->model)); g_signal_handlers_disconnect_by_func (self->model, gtk_list_view_model_items_changed_cb, @@ -830,7 +847,7 @@ gtk_list_view_set_model (GtkListView *self, G_CALLBACK (gtk_list_view_model_items_changed_cb), self); - gtk_list_view_add_rows (self, 0, g_list_model_get_n_items (model)); + gtk_list_view_add_rows (self, NULL, 0, g_list_model_get_n_items (model)); } g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]); @@ -852,12 +869,12 @@ gtk_list_view_set_functions (GtkListView *self, g_return_if_fail (user_data != NULL || user_destroy == NULL); n_items = self->model ? g_list_model_get_n_items (self->model) : 0; - gtk_list_view_remove_rows (self, 0, n_items); + gtk_list_view_remove_rows (self, NULL, 0, n_items); factory = gtk_list_item_factory_new (create_func, bind_func, user_data, user_destroy); gtk_list_item_manager_set_factory (self->item_manager, factory); g_object_unref (factory); - gtk_list_view_add_rows (self, 0, n_items); + gtk_list_view_add_rows (self, NULL, 0, n_items); } From 378a573cf47aafdfb67bb0b291cac1af8cbc6e97 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 22 Sep 2018 22:14:25 +0200 Subject: [PATCH 043/170] tests: Make animating listview do random resorts --- tests/testlistview-animating.c | 43 +++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/tests/testlistview-animating.c b/tests/testlistview-animating.c index e2c6196ab2..a750945d44 100644 --- a/tests/testlistview-animating.c +++ b/tests/testlistview-animating.c @@ -1,7 +1,12 @@ #include +#ifdef SMALL +#define AVERAGE 15 +#define VARIANCE 10 +#else #define AVERAGE 300 #define VARIANCE 200 +#endif static GtkWidget * create_widget (gpointer unused) @@ -31,6 +36,12 @@ create_widget_for_listbox (gpointer item, return widget; } +static guint +get_number (GObject *item) +{ + return GPOINTER_TO_UINT (g_object_get_data (item, "counter")) % 1000; +} + static void add (GListStore *store) { @@ -39,8 +50,10 @@ add (GListStore *store) char *message; guint pos; + counter++; o = g_object_new (G_TYPE_OBJECT, NULL); - message = g_strdup_printf ("Item %u", ++counter); + g_object_set_data (o, "counter", GUINT_TO_POINTER (counter)); + message = g_strdup_printf ("Item %u", counter); g_object_set_data_full (o, "message", message, g_free); pos = g_random_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store)) + 1); @@ -68,6 +81,17 @@ do_stuff (gpointer store) return G_SOURCE_CONTINUE; } +static gboolean +revert_sort (gpointer sorter) +{ + if (gtk_numeric_sorter_get_sort_order (sorter) == GTK_SORT_ASCENDING) + gtk_numeric_sorter_set_sort_order (sorter, GTK_SORT_DESCENDING); + else + gtk_numeric_sorter_set_sort_order (sorter, GTK_SORT_ASCENDING); + + return G_SOURCE_CONTINUE; +} + int main (int argc, char *argv[]) @@ -75,10 +99,19 @@ main (int argc, GtkWidget *win, *hbox, *vbox, *sw, *listview, *listbox, *label; GListStore *store; GListModel *toplevels; + GtkSortListModel *sort; + GtkSorter *sorter; guint i; gtk_init (); + store = g_list_store_new (G_TYPE_OBJECT); + for (i = 0; i < AVERAGE; i++) + add (store); + sorter = gtk_numeric_sorter_new (gtk_cclosure_expression_new (G_TYPE_UINT, NULL, 0, NULL, (GCallback)get_number, NULL, NULL)); + sort = gtk_sort_list_model_new (G_LIST_MODEL (store), sorter); + g_object_unref (sorter); + win = gtk_window_new (); gtk_window_set_default_size (GTK_WINDOW (win), 400, 600); @@ -117,16 +150,14 @@ main (int argc, listbox = gtk_list_box_new (); gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), listbox); - store = g_list_store_new (G_TYPE_OBJECT); - for (i = 0; i < AVERAGE; i++) - add (store); - gtk_list_view_set_model (GTK_LIST_VIEW (listview), G_LIST_MODEL (store)); + gtk_list_view_set_model (GTK_LIST_VIEW (listview), G_LIST_MODEL (sort)); gtk_list_box_bind_model (GTK_LIST_BOX (listbox), - G_LIST_MODEL (store), + G_LIST_MODEL (sort), create_widget_for_listbox, NULL, NULL); g_timeout_add (100, do_stuff, store); + g_timeout_add_seconds (3, revert_sort, sorter); gtk_widget_show (win); From fe14181d4e69ac261dd7878ac3d952609866d4b7 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 24 Sep 2018 04:01:37 +0200 Subject: [PATCH 044/170] listitem: Add gtk_list_item_get_position() Also refactor the whole list item management yet again. Now, list item APIs doesn't have bind/unbind functions anymore, but only property setters. The item factory is the only one doing the binding. As before, the item manager manages when items need to be bound. --- docs/reference/gtk/gtk4-sections.txt | 1 + gtk/gtklistitem.c | 66 ++++++++++++++++++++++------ gtk/gtklistitem.h | 2 + gtk/gtklistitemfactory.c | 26 ++++++++++- gtk/gtklistitemfactoryprivate.h | 4 ++ gtk/gtklistitemmanager.c | 23 +++++++++- gtk/gtklistitemmanagerprivate.h | 3 ++ gtk/gtklistitemprivate.h | 5 ++- gtk/gtklistview.c | 18 ++++++++ 9 files changed, 130 insertions(+), 18 deletions(-) diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 270a86f88e..c11db12cae 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -397,6 +397,7 @@ gtk_single_selection_get_type GtkListItem GtkListItem gtk_list_item_get_item +gtk_list_item_get_position gtk_list_item_get_child gtk_list_item_set_child diff --git a/gtk/gtklistitem.c b/gtk/gtklistitem.c index 65f52acf22..ac4fda6737 100644 --- a/gtk/gtklistitem.c +++ b/gtk/gtklistitem.c @@ -54,6 +54,7 @@ struct _GtkListItem GObject *item; GtkWidget *child; + guint position; }; struct _GtkListItemClass @@ -66,6 +67,7 @@ enum PROP_0, PROP_CHILD, PROP_ITEM, + PROP_POSITION, N_PROPS }; @@ -103,6 +105,10 @@ gtk_list_item_get_property (GObject *object, g_value_set_object (value, self->item); break; + case PROP_POSITION: + g_value_set_uint (value, self->position); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -163,6 +169,18 @@ gtk_list_item_class_init (GtkListItemClass *klass) G_TYPE_OBJECT, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** + * GtkListItem:position: + * + * Position of the item + */ + properties[PROP_POSITION] = + g_param_spec_uint ("position", + P_("Position"), + P_("Position of the item"), + 0, G_MAXUINT, GTK_INVALID_LIST_POSITION, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (gobject_class, N_PROPS, properties); /* This gets overwritten by gtk_list_item_new() but better safe than sorry */ @@ -189,10 +207,10 @@ gtk_list_item_new (const char *css_name) * gtk_list_item_get_item: * @self: a #GtkListItem * - * Gets the item that is currently displayed or model that @self is + * Gets the item that is currently displayed in model that @self is * currently bound to or %NULL if @self is unbound. * - * Returns: (nullable) (transfer none) (type GObject): The model in use + * Returns: (nullable) (transfer none) (type GObject): The item displayed **/ gpointer gtk_list_item_get_item (GtkListItem *self) @@ -252,28 +270,50 @@ gtk_list_item_set_child (GtkListItem *self, } void -gtk_list_item_bind (GtkListItem *self, - gpointer item) +gtk_list_item_set_item (GtkListItem *self, + gpointer item) { g_return_if_fail (GTK_IS_LIST_ITEM (self)); - g_return_if_fail (G_IS_OBJECT (item)); - /* Must unbind before rebinding */ - g_return_if_fail (self->item == NULL); + g_return_if_fail (item == NULL || G_IS_OBJECT (item)); - self->item = g_object_ref (item); + if (self->item == item) + return; + + g_clear_object (&self->item); + if (item) + self->item = g_object_ref (item); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]); } +/** + * gtk_list_item_get_position: + * @self: a #GtkListItem + * + * Gets the position in the model that @self currently displays. + * If @self is unbound, 0 is returned. + * + * Returns: The position of this item + **/ +guint +gtk_list_item_get_position (GtkListItem *self) +{ + g_return_val_if_fail (GTK_IS_LIST_ITEM (self), 0); + + return self->position; +} + void -gtk_list_item_unbind (GtkListItem *self) +gtk_list_item_set_position (GtkListItem *self, + guint position) { g_return_if_fail (GTK_IS_LIST_ITEM (self)); - /* Must be bound */ - g_return_if_fail (self->item != NULL); - g_clear_object (&self->item); + if (self->position == position) + return; - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]); + self->position = position; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_POSITION]); } diff --git a/gtk/gtklistitem.h b/gtk/gtklistitem.h index 3af77cb14e..ed7f21c45f 100644 --- a/gtk/gtklistitem.h +++ b/gtk/gtklistitem.h @@ -44,6 +44,8 @@ GType gtk_list_item_get_type (void) G_GNUC_CO GDK_AVAILABLE_IN_ALL gpointer gtk_list_item_get_item (GtkListItem *self); +GDK_AVAILABLE_IN_ALL +guint gtk_list_item_get_position (GtkListItem *self); GDK_AVAILABLE_IN_ALL void gtk_list_item_set_child (GtkListItem *self, diff --git a/gtk/gtklistitemfactory.c b/gtk/gtklistitemfactory.c index a258dee1f4..d95752b21c 100644 --- a/gtk/gtklistitemfactory.c +++ b/gtk/gtklistitemfactory.c @@ -153,14 +153,31 @@ gtk_list_item_factory_create (GtkListItemFactory *self) void gtk_list_item_factory_bind (GtkListItemFactory *self, GtkListItem *list_item, + guint position, gpointer item) { g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self)); g_return_if_fail (GTK_IS_LIST_ITEM (list_item)); - gtk_list_item_bind (list_item, item); + g_object_freeze_notify (G_OBJECT (list_item)); + + gtk_list_item_set_item (list_item, item); + gtk_list_item_set_position (list_item, position); self->bind_func (gtk_list_item_get_child (list_item), item, self->user_data); + + g_object_thaw_notify (G_OBJECT (list_item)); +} + +void +gtk_list_item_factory_update (GtkListItemFactory *self, + GtkListItem *list_item, + guint position) +{ + g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self)); + g_return_if_fail (GTK_IS_LIST_ITEM (list_item)); + + gtk_list_item_set_position (list_item, position); } void @@ -170,5 +187,10 @@ gtk_list_item_factory_unbind (GtkListItemFactory *self, g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self)); g_return_if_fail (GTK_IS_LIST_ITEM (list_item)); - gtk_list_item_unbind (list_item); + g_object_freeze_notify (G_OBJECT (list_item)); + + gtk_list_item_set_item (list_item, NULL); + gtk_list_item_set_position (list_item, 0); + + g_object_thaw_notify (G_OBJECT (list_item)); } diff --git a/gtk/gtklistitemfactoryprivate.h b/gtk/gtklistitemfactoryprivate.h index a6bec12faf..21bc5b5586 100644 --- a/gtk/gtklistitemfactoryprivate.h +++ b/gtk/gtklistitemfactoryprivate.h @@ -47,7 +47,11 @@ GtkListItem * gtk_list_item_factory_create (GtkListItemFact void gtk_list_item_factory_bind (GtkListItemFactory *self, GtkListItem *list_item, + guint position, gpointer item); +void gtk_list_item_factory_update (GtkListItemFactory *self, + GtkListItem *list_item, + guint position); void gtk_list_item_factory_unbind (GtkListItemFactory *self, GtkListItem *list_item); diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index a88502efbb..d365137d18 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -236,7 +236,7 @@ gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, result = gtk_list_item_factory_create (self->factory); item = g_list_model_get_item (self->model, position); - gtk_list_item_factory_bind (self->factory, result, item); + gtk_list_item_factory_bind (self->factory, result, position, item); g_object_unref (item); gtk_widget_insert_before (GTK_WIDGET (result), self->widget, next_sibling); @@ -274,6 +274,7 @@ gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self, item = g_list_model_get_item (self->model, position); if (g_hash_table_steal_extended (change->items, item, NULL, (gpointer *) &result)) { + gtk_list_item_factory_update (self->factory, result, position); gtk_widget_insert_before (GTK_WIDGET (result), self->widget, next_sibling); /* XXX: Should we let the listview do this? */ gtk_widget_queue_resize (GTK_WIDGET (result)); @@ -287,6 +288,26 @@ gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self, return GTK_WIDGET (result); } +/** + * gtk_list_item_manager_update_list_item: + * @self: a #GtkListItemManager + * @item: a #GtkListItem that has been acquired + * @position: the new position of that list item + * + * Updates the position of the given @item. This function must be called whenever + * the position of an item changes, like when new items are added before it. + **/ +void +gtk_list_item_manager_update_list_item (GtkListItemManager *self, + GtkWidget *item, + guint position) +{ + g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); + g_return_if_fail (GTK_IS_LIST_ITEM (item)); + + gtk_list_item_factory_update (self->factory, GTK_LIST_ITEM (item), position); +} + /* * gtk_list_item_manager_release_list_item: * @self: a #GtkListItemManager diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h index 2af8e8fae7..d1cdfaf819 100644 --- a/gtk/gtklistitemmanagerprivate.h +++ b/gtk/gtklistitemmanagerprivate.h @@ -63,6 +63,9 @@ GtkWidget * gtk_list_item_manager_try_reacquire_list_item GtkListItemManagerChange *change, guint position, GtkWidget *next_sibling); +void gtk_list_item_manager_update_list_item (GtkListItemManager *self, + GtkWidget *item, + guint position); void gtk_list_item_manager_release_list_item (GtkListItemManager *self, GtkListItemManagerChange *change, GtkWidget *widget); diff --git a/gtk/gtklistitemprivate.h b/gtk/gtklistitemprivate.h index a0a90f7d79..f238685c76 100644 --- a/gtk/gtklistitemprivate.h +++ b/gtk/gtklistitemprivate.h @@ -26,9 +26,10 @@ G_BEGIN_DECLS GtkWidget * gtk_list_item_new (const char *css_name); -void gtk_list_item_bind (GtkListItem *self, +void gtk_list_item_set_item (GtkListItem *self, gpointer item); -void gtk_list_item_unbind (GtkListItem *self); +void gtk_list_item_set_position (GtkListItem *self, + guint position); G_END_DECLS diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index f5c36e55fc..1539a2a530 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -509,6 +509,22 @@ gtk_list_view_add_rows (GtkListView *self, gtk_widget_queue_resize (GTK_WIDGET (self)); } +static void +gtk_list_view_update_rows (GtkListView *self, + guint position) +{ + ListRow *row; + + for (row = gtk_list_view_get_row (self, position, NULL); + row; + row = gtk_rb_tree_node_get_next (row)) + { + gtk_list_item_manager_update_list_item (self->item_manager, row->widget, position); + + position++; + } +} + static void gtk_list_view_model_items_changed_cb (GListModel *model, guint position, @@ -522,6 +538,8 @@ gtk_list_view_model_items_changed_cb (GListModel *model, gtk_list_view_remove_rows (self, change, position, removed); gtk_list_view_add_rows (self, change, position, added); + if (removed != added) + gtk_list_view_update_rows (self, position + added); gtk_list_item_manager_end_change (self->item_manager, change); } From e5add36a17cb64e6b300830f037d373bb8c0e031 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 24 Sep 2018 04:42:15 +0200 Subject: [PATCH 045/170] listview: Change how binding is done We now don't let the functions create widgets for the item from the listmodel, instead we hand out a GtkListItem for them to add a widget to. GtkListItems are created in advance and can only be filled in by the binding code by gtk_container_add()ing a widget. However, they are GObjects, so they can provide properties that the binding code can make use of - either via notify signals or GBinding. --- gtk/gtklistitem.h | 29 +++++++++++++++++++++++- gtk/gtklistitemfactory.c | 27 +++++++++++----------- gtk/gtklistitemfactoryprivate.h | 4 ++-- gtk/gtklistview.c | 15 ++++++------- gtk/gtklistview.h | 35 +++-------------------------- tests/testlistview-animating.c | 40 +++++++++++++++++++++++---------- tests/testlistview.c | 22 ++++++++---------- 7 files changed, 90 insertions(+), 82 deletions(-) diff --git a/gtk/gtklistitem.h b/gtk/gtklistitem.h index ed7f21c45f..e26c57ba4d 100644 --- a/gtk/gtklistitem.h +++ b/gtk/gtklistitem.h @@ -28,7 +28,6 @@ G_BEGIN_DECLS -#define GTK_TYPE_LIST_ITEM (gtk_list_item_get_type ()) #define GTK_TYPE_LIST_ITEM (gtk_list_item_get_type ()) #define GTK_LIST_ITEM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_LIST_ITEM, GtkListItem)) #define GTK_LIST_ITEM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_LIST_ITEM, GtkListItemClass)) @@ -42,6 +41,34 @@ typedef struct _GtkListItemClass GtkListItemClass; GDK_AVAILABLE_IN_ALL GType gtk_list_item_get_type (void) G_GNUC_CONST; +/** + * GtkListItemSetupFunc: + * @item: the #GtkListItem to set up + * @user_data: (closure): user data + * + * Called whenever a new list item needs to be setup for managing a row in + * the list. + * + * At this point, the list item is not bound yet, so gtk_list_item_get_item() + * will return %NULL. + * The list item will later be bound to an item via the #GtkListItemBindFunc. + */ +typedef void (* GtkListItemSetupFunc) (GtkListItem *item, gpointer user_data); + +/** + * GtkListItemBindFunc: + * @item: the #GtkListItem to bind + * @user_data: (closure): user data + * + * Binds a#GtkListItem previously set up via a #GtkListItemSetupFunc to + * an @item. + * + * Rebinding a @item to different @items is supported as well as + * unbinding it by setting @item to %NULL. + */ +typedef void (* GtkListItemBindFunc) (GtkListItem *item, + gpointer user_data); + GDK_AVAILABLE_IN_ALL gpointer gtk_list_item_get_item (GtkListItem *self); GDK_AVAILABLE_IN_ALL diff --git a/gtk/gtklistitemfactory.c b/gtk/gtklistitemfactory.c index d95752b21c..e102e25df3 100644 --- a/gtk/gtklistitemfactory.c +++ b/gtk/gtklistitemfactory.c @@ -75,8 +75,8 @@ struct _GtkListItemFactory { GObject parent_instance; - GtkListCreateWidgetFunc create_func; - GtkListBindWidgetFunc bind_func; + GtkListItemSetupFunc setup_func; + GtkListItemBindFunc bind_func; gpointer user_data; GDestroyNotify user_destroy; }; @@ -113,20 +113,19 @@ gtk_list_item_factory_init (GtkListItemFactory *self) } GtkListItemFactory * -gtk_list_item_factory_new (GtkListCreateWidgetFunc create_func, - GtkListBindWidgetFunc bind_func, - gpointer user_data, - GDestroyNotify user_destroy) +gtk_list_item_factory_new (GtkListItemSetupFunc setup_func, + GtkListItemBindFunc bind_func, + gpointer user_data, + GDestroyNotify user_destroy) { GtkListItemFactory *self; - g_return_val_if_fail (create_func, NULL); - g_return_val_if_fail (bind_func, NULL); + g_return_val_if_fail (setup_func || bind_func, NULL); g_return_val_if_fail (user_data != NULL || user_destroy == NULL, NULL); self = g_object_new (GTK_TYPE_LIST_ITEM_FACTORY, NULL); - self->create_func = create_func; + self->setup_func = setup_func; self->bind_func = bind_func; self->user_data = user_data; self->user_destroy = user_destroy; @@ -137,15 +136,14 @@ gtk_list_item_factory_new (GtkListCreateWidgetFunc create_func, GtkListItem * gtk_list_item_factory_create (GtkListItemFactory *self) { - GtkWidget *widget, *result; + GtkWidget *result; g_return_val_if_fail (GTK_IS_LIST_ITEM_FACTORY (self), NULL); - widget = self->create_func (self->user_data); - result = gtk_list_item_new ("row"); - gtk_list_item_set_child (GTK_LIST_ITEM (result), widget); + if (self->setup_func) + self->setup_func (GTK_LIST_ITEM (result), self->user_data); return GTK_LIST_ITEM (result); } @@ -164,7 +162,8 @@ gtk_list_item_factory_bind (GtkListItemFactory *self, gtk_list_item_set_item (list_item, item); gtk_list_item_set_position (list_item, position); - self->bind_func (gtk_list_item_get_child (list_item), item, self->user_data); + if (self->bind_func) + self->bind_func (list_item, self->user_data); g_object_thaw_notify (G_OBJECT (list_item)); } diff --git a/gtk/gtklistitemfactoryprivate.h b/gtk/gtklistitemfactoryprivate.h index 21bc5b5586..779251344b 100644 --- a/gtk/gtklistitemfactoryprivate.h +++ b/gtk/gtklistitemfactoryprivate.h @@ -38,8 +38,8 @@ typedef struct _GtkListItemFactoryClass GtkListItemFactoryClass; GType gtk_list_item_factory_get_type (void) G_GNUC_CONST; -GtkListItemFactory * gtk_list_item_factory_new (GtkListCreateWidgetFunc create_func, - GtkListBindWidgetFunc bind_func, +GtkListItemFactory * gtk_list_item_factory_new (GtkListItemSetupFunc setup_func, + GtkListItemBindFunc bind_func, gpointer user_data, GDestroyNotify user_destroy); diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index 1539a2a530..36e710ab18 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -872,24 +872,23 @@ gtk_list_view_set_model (GtkListView *self, } void -gtk_list_view_set_functions (GtkListView *self, - GtkListCreateWidgetFunc create_func, - GtkListBindWidgetFunc bind_func, - gpointer user_data, - GDestroyNotify user_destroy) +gtk_list_view_set_functions (GtkListView *self, + GtkListItemSetupFunc setup_func, + GtkListItemBindFunc bind_func, + gpointer user_data, + GDestroyNotify user_destroy) { GtkListItemFactory *factory; guint n_items; g_return_if_fail (GTK_IS_LIST_VIEW (self)); - g_return_if_fail (create_func); - g_return_if_fail (bind_func); + g_return_if_fail (setup_func || bind_func); g_return_if_fail (user_data != NULL || user_destroy == NULL); n_items = self->model ? g_list_model_get_n_items (self->model) : 0; gtk_list_view_remove_rows (self, NULL, 0, n_items); - factory = gtk_list_item_factory_new (create_func, bind_func, user_data, user_destroy); + factory = gtk_list_item_factory_new (setup_func, bind_func, user_data, user_destroy); gtk_list_item_manager_set_factory (self->item_manager, factory); g_object_unref (factory); diff --git a/gtk/gtklistview.h b/gtk/gtklistview.h index 541bf14874..4d1a0a5d77 100644 --- a/gtk/gtklistview.h +++ b/gtk/gtklistview.h @@ -25,39 +25,10 @@ #endif #include +#include G_BEGIN_DECLS -/** - * GtkListCreateWidgetFunc: - * @user_data: (closure): user data - * - * Called whenever a new widget needs to be created for managing a row in - * the list. - * - * The widget will later be bound to an item via the #GtkListBindWidgetFunc. - * - * Returns: (transfer full): a #GtkWidget - */ -typedef GtkWidget * (* GtkListCreateWidgetFunc) (gpointer user_data); - -/** - * GtkListBindWidgetFunc: - * @widget: The #GtkWidget to bind - * @item: (type GObject) (allow-none): item to bind or %NULL to unbind - * the widget. - * @user_data: (closure): user data - * - * Binds a widget previously created via a #GtkListCreateWidgetFunc to - * an @item. - * - * Rebinding a @widget to different @items is supported as well as - * unbinding it by setting @item to %NULL. - */ -typedef void (* GtkListBindWidgetFunc) (GtkWidget *widget, - gpointer item, - gpointer user_data); - #define GTK_TYPE_LIST_VIEW (gtk_list_view_get_type ()) GDK_AVAILABLE_IN_ALL @@ -73,8 +44,8 @@ void gtk_list_view_set_model (GtkListView GListModel *model); GDK_AVAILABLE_IN_ALL void gtk_list_view_set_functions (GtkListView *self, - GtkListCreateWidgetFunc create_func, - GtkListBindWidgetFunc bind_func, + GtkListItemSetupFunc setup_func, + GtkListItemBindFunc bind_func, gpointer user_data, GDestroyNotify user_destroy); diff --git a/tests/testlistview-animating.c b/tests/testlistview-animating.c index a750945d44..3a0b08dfab 100644 --- a/tests/testlistview-animating.c +++ b/tests/testlistview-animating.c @@ -8,30 +8,46 @@ #define VARIANCE 200 #endif -static GtkWidget * -create_widget (gpointer unused) +static void +setup_list_item (GtkListItem *list_item, + gpointer unused) { - return gtk_label_new (""); + GtkWidget *label = gtk_label_new (""); + + gtk_list_item_set_child (list_item, label); } static void -bind_widget (GtkWidget *widget, - gpointer item, - gpointer unused) +bind_list_item (GtkListItem *list_item, + gpointer unused) { - const char *message = g_object_get_data (item, "message"); + GtkWidget *label; + gpointer item; + char *s; - gtk_label_set_text (GTK_LABEL (widget), message); + item = gtk_list_item_get_item (list_item); + + if (item) + s = g_strdup_printf ("%u: %s", + gtk_list_item_get_position (list_item), + (const char *) g_object_get_data (item, "message")); + else + s = NULL; + + label = gtk_list_item_get_child (list_item); + gtk_label_set_text (GTK_LABEL (label), s); + + g_free (s); } static GtkWidget * create_widget_for_listbox (gpointer item, gpointer unused) { + const char *message = g_object_get_data (item, "message"); GtkWidget *widget; - widget = create_widget (unused); - bind_widget (widget, item, unused); + widget = gtk_label_new (message); return widget; } @@ -131,8 +147,8 @@ main (int argc, listview = gtk_list_view_new (); gtk_list_view_set_functions (GTK_LIST_VIEW (listview), - create_widget, - bind_widget, + setup_list_item, + bind_list_item, NULL, NULL); gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), listview); diff --git a/tests/testlistview.c b/tests/testlistview.c index 1668fe2a18..6ccea18728 100644 --- a/tests/testlistview.c +++ b/tests/testlistview.c @@ -138,25 +138,21 @@ create_list_model_for_directory (gpointer file) return G_LIST_MODEL (sort); } -static GtkWidget * -create_widget (gpointer unused) -{ - return gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); -} - static void -bind_widget (GtkWidget *box, - gpointer item, - gpointer unused) +bind_widget (GtkListItem *list_item, + gpointer unused) { - GtkWidget *child; + GtkWidget *box, *child; GFileInfo *info; GFile *file; guint depth; GIcon *icon; + gpointer item; - while (gtk_widget_get_first_child (box)) - gtk_box_remove (GTK_BOX (box), gtk_widget_get_first_child (box)); + item = gtk_list_item_get_item (list_item); + + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + gtk_list_item_set_child (list_item, box); depth = gtk_tree_list_row_get_depth (item); if (depth > 0) @@ -300,7 +296,7 @@ main (int argc, char *argv[]) listview = gtk_list_view_new (); gtk_list_view_set_functions (GTK_LIST_VIEW (listview), - create_widget, + NULL, bind_widget, NULL, NULL); gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), listview); From 2ba2a216cabae74d26ad445a580ac1b151d34b41 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 25 Sep 2018 00:16:27 +0200 Subject: [PATCH 046/170] listview: Change anchor handling again The anchor is now a tuple of { listitem, align }. Using the actual list item allows keeping the anchor across changes in position (ie when lists get resorted) while still being able to fall back to positions (list items store their position) when an item gets removed. The align value is in the range [0..1] and defines where in the visible area to do the alignment. 0.0 means to align the top of the row with the top of the visible area, 1.0 aligns the bottom of the widget with the visible area and 0.5 keeps the center of the widget at the center of the visible area. It works conceptually the same as percentages in CSS background-position (where the background area and the background image's size are matched the same way) or CSS transform-origin. --- gtk/gtklistitemmanager.c | 24 +++++ gtk/gtklistitemmanagerprivate.h | 3 + gtk/gtklistview.c | 154 ++++++++++++++++++++------------ 3 files changed, 122 insertions(+), 59 deletions(-) diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index d365137d18..cce8eda34d 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -204,6 +204,30 @@ gtk_list_item_manager_end_change (GtkListItemManager *self, g_slice_free (GtkListItemManagerChange, change); } +/* + * gtk_list_item_manager_change_contains: + * @change: a #GtkListItemManagerChange + * @list_item: The item that may have been released into this change set + * + * Checks if @list_item has been released as part of @change but not been + * reacquired yet. + * + * This is useful to test before calling gtk_list_item_manager_end_change() + * if special actions need to be performed when important list items - like + * the focused item - are about to be deleted. + * + * Returns: %TRUE if the item is part of this change + **/ +gboolean +gtk_list_item_manager_change_contains (GtkListItemManagerChange *change, + GtkWidget *list_item) +{ + g_return_val_if_fail (change != NULL, FALSE); + g_return_val_if_fail (GTK_IS_LIST_ITEM (list_item), FALSE); + + return g_hash_table_lookup (change->items, gtk_list_item_get_item (GTK_LIST_ITEM (list_item))) == list_item; +} + /* * gtk_list_item_manager_acquire_list_item: * @self: a #GtkListItemManager diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h index d1cdfaf819..cf7721bc6c 100644 --- a/gtk/gtklistitemmanagerprivate.h +++ b/gtk/gtklistitemmanagerprivate.h @@ -55,6 +55,9 @@ GtkListItemManagerChange * gtk_list_item_manager_begin_change (GtkListItemManager *self); void gtk_list_item_manager_end_change (GtkListItemManager *self, GtkListItemManagerChange *change); +gboolean gtk_list_item_manager_change_contains (GtkListItemManagerChange *change, + GtkWidget *list_item); + GtkWidget * gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, guint position, GtkWidget *next_sibling); diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index 36e710ab18..35866d47b7 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -54,10 +54,8 @@ struct _GtkListView int list_width; /* managing the visible region */ - guint anchor_pos; - int anchor_dy; - guint first_visible_pos; - guint lasst_visible_pos; + GtkWidget *anchor; + int anchor_align; }; struct _ListRow @@ -284,6 +282,54 @@ gtk_list_view_get_list_height (GtkListView *self) return aug->height; } +static void +gtk_list_view_set_anchor (GtkListView *self, + guint position, + double align) +{ + ListRow *row; + + g_assert (align >= 0.0 && align <= 1.0); + + row = gtk_list_view_get_row (self, position, NULL); + if (row == NULL) + { + /* like, if the list is empty */ + self->anchor = NULL; + self->anchor_align = 0.0; + return; + } + + self->anchor = row->widget; + self->anchor_align = align; + + gtk_widget_queue_allocate (GTK_WIDGET (self)); +} + +static void +gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment, + GtkListView *self) +{ + if (adjustment == self->adjustment[GTK_ORIENTATION_VERTICAL]) + { + ListRow *row; + guint pos; + int dy; + + row = gtk_list_view_get_row_at_y (self, gtk_adjustment_get_value (adjustment), &dy); + if (row) + pos = list_row_get_position (self, row); + else + pos = 0; + + gtk_list_view_set_anchor (self, pos, 0); + } + else + { + gtk_widget_queue_allocate (GTK_WIDGET (self)); + } +} + static void gtk_list_view_update_adjustments (GtkListView *self, GtkOrientation orientation) @@ -306,15 +352,21 @@ gtk_list_view_update_adjustments (GtkListView *self, page_size = gtk_widget_get_height (GTK_WIDGET (self)); upper = gtk_list_view_get_list_height (self); - row = gtk_list_view_get_row (self, self->anchor_pos, NULL); + if (self->anchor) + row = gtk_list_view_get_row (self, gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)), NULL); + else + row = NULL; if (row) value = list_row_get_y (self, row); else value = 0; - value += self->anchor_dy; + value -= self->anchor_align * (page_size - (row ? row->height : 0)); } upper = MAX (upper, page_size); + g_signal_handlers_block_by_func (self->adjustment[orientation], + gtk_list_view_adjustment_value_changed_cb, + self); gtk_adjustment_configure (self->adjustment[orientation], value, 0, @@ -322,6 +374,9 @@ gtk_list_view_update_adjustments (GtkListView *self, page_size * 0.1, page_size * 0.9, page_size); + g_signal_handlers_unblock_by_func (self->adjustment[orientation], + gtk_list_view_adjustment_value_changed_cb, + self); } static void @@ -413,7 +468,7 @@ gtk_list_view_size_allocate (GtkWidget *widget, /* step 4: actually allocate the widgets */ child_allocation.x = - gtk_adjustment_get_value (self->adjustment[GTK_ORIENTATION_HORIZONTAL]); - child_allocation.y = - gtk_adjustment_get_value (self->adjustment[GTK_ORIENTATION_VERTICAL]); + child_allocation.y = - round (gtk_adjustment_get_value (self->adjustment[GTK_ORIENTATION_VERTICAL])); child_allocation.width = self->list_width; for (row = gtk_rb_tree_get_first (self->rows); row != NULL; @@ -425,6 +480,23 @@ gtk_list_view_size_allocate (GtkWidget *widget, } } +static guint +gtk_list_view_get_anchor (GtkListView *self, + double *align) +{ + guint anchor_pos; + + if (self->anchor) + anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); + else + anchor_pos = 0; + + if (align) + *align = self->anchor_align; + + return anchor_pos; +} + static void gtk_list_view_remove_rows (GtkListView *self, GtkListItemManagerChange *change, @@ -432,24 +504,11 @@ gtk_list_view_remove_rows (GtkListView *self, guint n_rows) { ListRow *row; - guint i, n_remaining; + guint i; if (n_rows == 0) return; - n_remaining = self->model ? g_list_model_get_n_items (self->model) : 0; - if (self->anchor_pos >= position + n_rows) - { - self->anchor_pos -= n_rows; - } - else if (self->anchor_pos >= position) - { - self->anchor_pos = position; - if (self->anchor_pos > 0 && self->anchor_pos >= n_remaining) - self->anchor_pos = n_remaining - 1; - self->anchor_dy = 0; - } - row = gtk_list_view_get_row (self, position, NULL); for (i = 0; i < n_rows; i++) @@ -471,18 +530,11 @@ gtk_list_view_add_rows (GtkListView *self, guint n_rows) { ListRow *row; - guint i, n_total; + guint i; if (n_rows == 0) return; - n_total = self->model ? g_list_model_get_n_items (self->model) : 0; - if (self->anchor_pos >= position) - { - if (n_total != n_rows) /* the model was not empty before */ - self->anchor_pos += n_rows; - } - row = gtk_list_view_get_row (self, position, NULL); for (i = 0; i < n_rows; i++) @@ -541,6 +593,15 @@ gtk_list_view_model_items_changed_cb (GListModel *model, if (removed != added) gtk_list_view_update_rows (self, position + added); + if (gtk_list_item_manager_change_contains (change, self->anchor)) + { + guint anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); + + /* removed cannot be NULL or the anchor wouldn't have been removed */ + anchor_pos = position + (anchor_pos - position) * added / removed; + gtk_list_view_set_anchor (self, anchor_pos, self->anchor_align); + } + gtk_list_item_manager_end_change (self->item_manager, change); } @@ -558,35 +619,6 @@ gtk_list_view_clear_model (GtkListView *self) g_clear_object (&self->model); } -static void -gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment, - GtkListView *self) -{ - if (adjustment == self->adjustment[GTK_ORIENTATION_VERTICAL]) - { - ListRow *row; - guint pos; - int dy; - - row = gtk_list_view_get_row_at_y (self, gtk_adjustment_get_value (adjustment), &dy); - if (row) - pos = list_row_get_position (self, row); - else - pos = 0; - - if (pos != self->anchor_pos || dy != self->anchor_dy) - { - self->anchor_pos = pos; - self->anchor_dy = dy; - gtk_widget_queue_allocate (GTK_WIDGET (self)); - } - } - else - { - gtk_widget_queue_allocate (GTK_WIDGET (self)); - } -} - static void gtk_list_view_clear_adjustment (GtkListView *self, GtkOrientation orientation) @@ -866,6 +898,7 @@ gtk_list_view_set_model (GtkListView *self, self); gtk_list_view_add_rows (self, NULL, 0, g_list_model_get_n_items (model)); + gtk_list_view_set_anchor (self, 0, 0); } g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]); @@ -879,13 +912,15 @@ gtk_list_view_set_functions (GtkListView *self, GDestroyNotify user_destroy) { GtkListItemFactory *factory; - guint n_items; + guint n_items, anchor; + double anchor_align; g_return_if_fail (GTK_IS_LIST_VIEW (self)); g_return_if_fail (setup_func || bind_func); g_return_if_fail (user_data != NULL || user_destroy == NULL); n_items = self->model ? g_list_model_get_n_items (self->model) : 0; + anchor = gtk_list_view_get_anchor (self, &anchor_align); gtk_list_view_remove_rows (self, NULL, 0, n_items); factory = gtk_list_item_factory_new (setup_func, bind_func, user_data, user_destroy); @@ -893,5 +928,6 @@ gtk_list_view_set_functions (GtkListView *self, g_object_unref (factory); gtk_list_view_add_rows (self, NULL, 0, n_items); + gtk_list_view_set_anchor (self, anchor, anchor_align); } From b64da6d26820840354ff8880c7fc13b70ffffe34 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 26 Sep 2018 01:37:38 +0200 Subject: [PATCH 047/170] listview: Only allocate necesary rows This is the big one. The listview only allocates 200 rows around the visible row now. Everything else is kept in ListRow instances with row->widget == NULL. For rows without a widget, we assign the median height of the child widgets as the row's height and then do all calculations as if there were widgets that had requested that height (like setting adjustment values or reacting to adjustment value changes). When the view is scrolled, we bind the 200 rows to the new visible area, so that the part of the listview that can be seen is always allocated. --- gtk/gtklistview.c | 614 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 495 insertions(+), 119 deletions(-) diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index 35866d47b7..7ef2529e3e 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -29,6 +29,12 @@ #include "gtkscrollable.h" #include "gtkwidgetprivate.h" +/* Maximum number of list items created by the listview. + * For debugging, you can set this to G_MAXUINT to ensure + * there's always a list item for every row. + */ +#define GTK_LIST_VIEW_MAX_LIST_ITEMS 200 + /** * SECTION:gtklistview * @title: GtkListView @@ -54,21 +60,23 @@ struct _GtkListView int list_width; /* managing the visible region */ - GtkWidget *anchor; - int anchor_align; + GtkWidget *anchor; /* may be NULL if list is empty */ + int anchor_align; /* what to align the anchor to */ + guint anchor_start; /* start of region we allocate row widgets for */ + guint anchor_end; /* end of same region - first position to not have a widget */ }; struct _ListRow { guint n_rows; - guint height; + guint height; /* per row */ GtkWidget *widget; }; struct _ListRowAugment { guint n_rows; - guint height; + guint height; /* total */ }; enum @@ -88,6 +96,28 @@ G_DEFINE_TYPE_WITH_CODE (GtkListView, gtk_list_view, GTK_TYPE_WIDGET, static GParamSpec *properties[N_PROPS] = { NULL, }; +static void G_GNUC_UNUSED +dump (GtkListView *self) +{ + ListRow *row; + guint n_widgets, n_list_rows; + + n_widgets = 0; + n_list_rows = 0; + g_print ("ANCHOR: %u - %u\n", self->anchor_start, self->anchor_end); + for (row = gtk_rb_tree_get_first (self->rows); + row; + row = gtk_rb_tree_node_get_next (row)) + { + if (row->widget) + n_widgets++; + n_list_rows++; + g_print (" %4u%s (%upx)\n", row->n_rows, row->widget ? " (widget)" : "", row->height); + } + + g_print (" => %u widgets in %u list rows\n", n_widgets, n_list_rows); +} + static void list_row_augment (GtkRbTree *tree, gpointer node_augment, @@ -98,7 +128,7 @@ list_row_augment (GtkRbTree *tree, ListRow *row = node; ListRowAugment *aug = node_augment; - aug->height = row->height; + aug->height = row->height * row->n_rows; aug->n_rows = row->n_rows; if (left) @@ -225,9 +255,9 @@ gtk_list_view_get_row_at_y (GtkListView *self, y -= aug->height; } - if (y < row->height) + if (y < row->height * row->n_rows) break; - y -= row->height; + y -= row->height * row->n_rows; row = gtk_rb_tree_node_get_right (row); } @@ -242,15 +272,23 @@ static int list_row_get_y (GtkListView *self, ListRow *row) { - ListRow *parent; - int y = 0; + ListRow *parent, *left; + int y; + + left = gtk_rb_tree_node_get_left (row); + if (left) + { + ListRowAugment *aug = gtk_rb_tree_get_augment (self->rows, left); + y = aug->height; + } + else + y = 0; - y = 0; for (parent = gtk_rb_tree_node_get_parent (row); parent != NULL; parent = gtk_rb_tree_node_get_parent (row)) { - ListRow *left = gtk_rb_tree_node_get_left (parent); + left = gtk_rb_tree_node_get_left (parent); if (left != row) { @@ -259,7 +297,7 @@ list_row_get_y (GtkListView *self, ListRowAugment *aug = gtk_rb_tree_get_augment (self->rows, left); y += aug->height; } - y += parent->height; + y += parent->height * parent->n_rows; } row = parent; @@ -282,25 +320,189 @@ gtk_list_view_get_list_height (GtkListView *self) return aug->height; } +static gboolean +gtk_list_view_merge_list_rows (GtkListView *self, + ListRow *first, + ListRow *second) +{ + if (first->widget || second->widget) + return FALSE; + + first->n_rows += second->n_rows; + gtk_rb_tree_node_mark_dirty (first); + gtk_rb_tree_remove (self->rows, second); + + return TRUE; +} + static void -gtk_list_view_set_anchor (GtkListView *self, - guint position, - double align) +gtk_list_view_release_rows (GtkListView *self) +{ + ListRow *row, *prev, *next; + guint i; + + row = gtk_rb_tree_get_first (self->rows); + i = 0; + while (i < self->anchor_start) + { + if (row->widget) + { + gtk_list_item_manager_release_list_item (self->item_manager, NULL, row->widget); + row->widget = NULL; + i++; + prev = gtk_rb_tree_node_get_previous (row); + if (prev && gtk_list_view_merge_list_rows (self, prev, row)) + row = prev; + next = gtk_rb_tree_node_get_next (row); + if (next && next->widget == NULL) + { + i += next->n_rows; + if (!gtk_list_view_merge_list_rows (self, next, row)) + g_assert_not_reached (); + row = gtk_rb_tree_node_get_next (next); + } + else + { + row = next; + } + } + else + { + i += row->n_rows; + row = gtk_rb_tree_node_get_next (row); + } + } + + row = gtk_list_view_get_row (self, self->anchor_end, NULL); + if (row == NULL) + return; + + if (row->widget) + { + gtk_list_item_manager_release_list_item (self->item_manager, NULL, row->widget); + row->widget = NULL; + prev = gtk_rb_tree_node_get_previous (row); + if (prev && gtk_list_view_merge_list_rows (self, prev, row)) + row = prev; + } + + while ((next = gtk_rb_tree_node_get_next (row))) + { + if (next->widget) + { + gtk_list_item_manager_release_list_item (self->item_manager, NULL, next->widget); + next->widget = NULL; + } + gtk_list_view_merge_list_rows (self, row, next); + } +} + +static void +gtk_list_view_ensure_rows (GtkListView *self, + GtkListItemManagerChange *change, + guint update_start) +{ + ListRow *row, *new_row; + guint i, offset; + GtkWidget *insert_before; + + gtk_list_view_release_rows (self); + + row = gtk_list_view_get_row (self, self->anchor_start, &offset); + if (offset > 0) + { + new_row = gtk_rb_tree_insert_before (self->rows, row); + new_row->n_rows = offset; + row->n_rows -= offset; + gtk_rb_tree_node_mark_dirty (row); + } + + insert_before = gtk_widget_get_first_child (GTK_WIDGET (self)); + + for (i = self->anchor_start; i < self->anchor_end; i++) + { + if (row->n_rows > 1) + { + new_row = gtk_rb_tree_insert_before (self->rows, row); + new_row->n_rows = 1; + row->n_rows--; + gtk_rb_tree_node_mark_dirty (row); + } + else + { + new_row = row; + row = gtk_rb_tree_node_get_next (row); + } + if (new_row->widget == NULL) + { + if (change) + { + new_row->widget = gtk_list_item_manager_try_reacquire_list_item (self->item_manager, + change, + i, + insert_before); + } + if (new_row->widget == NULL) + { + new_row->widget = gtk_list_item_manager_acquire_list_item (self->item_manager, + i, + insert_before); + } + } + else + { + if (update_start <= i) + gtk_list_item_manager_update_list_item (self->item_manager, new_row->widget, i); + insert_before = gtk_widget_get_next_sibling (new_row->widget); + } + } +} + +static void +gtk_list_view_unset_anchor (GtkListView *self) +{ + self->anchor = NULL; + self->anchor_align = 0; + self->anchor_start = 0; + self->anchor_end = 0; +} + +static void +gtk_list_view_set_anchor (GtkListView *self, + guint position, + double align, + GtkListItemManagerChange *change, + guint update_start) { ListRow *row; + guint items_before, items_after, total_items, n_rows; g_assert (align >= 0.0 && align <= 1.0); - row = gtk_list_view_get_row (self, position, NULL); - if (row == NULL) + if (self->model) + n_rows = g_list_model_get_n_items (self->model); + else + n_rows = 0; + if (n_rows == 0) { - /* like, if the list is empty */ - self->anchor = NULL; - self->anchor_align = 0.0; + gtk_list_view_unset_anchor (self); return; } + total_items = MIN (GTK_LIST_VIEW_MAX_LIST_ITEMS, n_rows); + if (align < 0.5) + items_before = ceil (total_items * align); + else + items_before = floor (total_items * align); + items_after = total_items - items_before; + self->anchor_start = CLAMP (position, items_before, n_rows - items_after) - items_before; + self->anchor_end = self->anchor_start + total_items; + g_assert (self->anchor_end <= n_rows); + gtk_list_view_ensure_rows (self, change, update_start); + + row = gtk_list_view_get_row (self, position, NULL); self->anchor = row->widget; + g_assert (self->anchor); self->anchor_align = align; gtk_widget_queue_allocate (GTK_WIDGET (self)); @@ -318,11 +520,13 @@ gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment, row = gtk_list_view_get_row_at_y (self, gtk_adjustment_get_value (adjustment), &dy); if (row) - pos = list_row_get_position (self, row); + { + pos = list_row_get_position (self, row) + dy / row->height; + } else pos = 0; - gtk_list_view_set_anchor (self, pos, 0); + gtk_list_view_set_anchor (self, pos, 0, NULL, (guint) -1); } else { @@ -379,6 +583,112 @@ gtk_list_view_update_adjustments (GtkListView *self, self); } +static int +compare_ints (gconstpointer first, + gconstpointer second) +{ + return *(int *) first - *(int *) second; +} + +static guint +gtk_list_view_get_unknown_row_height (GtkListView *self, + GArray *heights) +{ + g_return_val_if_fail (heights->len > 0, 0); + + /* return the median and hope rows are generally uniform with few outliers */ + g_array_sort (heights, compare_ints); + + return g_array_index (heights, int, heights->len / 2); +} + +static void +gtk_list_view_measure_across (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural) +{ + GtkListView *self = GTK_LIST_VIEW (widget); + ListRow *row; + int min, nat, child_min, child_nat; + /* XXX: Figure out how to split a given height into per-row heights. + * Good luck! */ + for_size = -1; + + min = 0; + nat = 0; + + for (row = gtk_rb_tree_get_first (self->rows); + row != NULL; + row = gtk_rb_tree_node_get_next (row)) + { + /* ignore unavailable rows */ + if (row->widget == NULL) + continue; + + gtk_widget_measure (row->widget, + orientation, for_size, + &child_min, &child_nat, NULL, NULL); + min = MAX (min, child_min); + nat = MAX (nat, child_nat); + } + + *minimum = min; + *natural = nat; +} + +static void +gtk_list_view_measure_list (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural) +{ + GtkListView *self = GTK_LIST_VIEW (widget); + ListRow *row; + int min, nat, child_min, child_nat; + GArray *min_heights, *nat_heights; + guint n_unknown; + + min_heights = g_array_new (FALSE, FALSE, sizeof (int)); + nat_heights = g_array_new (FALSE, FALSE, sizeof (int)); + n_unknown = 0; + min = 0; + nat = 0; + + for (row = gtk_rb_tree_get_first (self->rows); + row != NULL; + row = gtk_rb_tree_node_get_next (row)) + { + if (row->widget) + { + gtk_widget_measure (row->widget, + orientation, for_size, + &child_min, &child_nat, NULL, NULL); + g_array_append_val (min_heights, child_min); + g_array_append_val (nat_heights, child_nat); + min += child_min; + nat += child_nat; + } + else + { + n_unknown += row->n_rows; + } + } + + if (n_unknown) + { + min += n_unknown * gtk_list_view_get_unknown_row_height (self, min_heights); + nat += n_unknown * gtk_list_view_get_unknown_row_height (self, nat_heights); + } + g_array_free (min_heights, TRUE); + g_array_free (nat_heights, TRUE); + + *minimum = min; + *natural = nat; +} + static void gtk_list_view_measure (GtkWidget *widget, GtkOrientation orientation, @@ -388,39 +698,10 @@ gtk_list_view_measure (GtkWidget *widget, int *minimum_baseline, int *natural_baseline) { - GtkListView *self = GTK_LIST_VIEW (widget); - ListRow *row; - int min, nat, child_min, child_nat; - - /* XXX: Figure out how to split a given height into per-row heights. - * Good luck! */ if (orientation == GTK_ORIENTATION_HORIZONTAL) - for_size = -1; - - min = 0; - nat = 0; - - for (row = gtk_rb_tree_get_first (self->rows); - row != NULL; - row = gtk_rb_tree_node_get_next (row)) - { - gtk_widget_measure (row->widget, - orientation, for_size, - &child_min, &child_nat, NULL, NULL); - if (orientation == GTK_ORIENTATION_HORIZONTAL) - { - min = MAX (min, child_min); - nat = MAX (nat, child_nat); - } - else - { - min += child_nat; - nat = min; - } - } - - *minimum = min; - *natural = nat; + gtk_list_view_measure_across (widget, orientation, for_size, minimum, natural); + else + gtk_list_view_measure_list (widget, orientation, for_size, minimum, natural); } static void @@ -432,8 +713,13 @@ gtk_list_view_size_allocate (GtkWidget *widget, GtkListView *self = GTK_LIST_VIEW (widget); GtkAllocation child_allocation = { 0, 0, 0, 0 }; ListRow *row; + GArray *heights; int min, nat, row_height; + /* step 0: exit early if list is empty */ + if (gtk_rb_tree_get_root (self->rows) == NULL) + return; + /* step 1: determine width of the list */ gtk_widget_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1, @@ -443,11 +729,16 @@ gtk_list_view_size_allocate (GtkWidget *widget, else self->list_width = MAX (nat, width); - /* step 2: determine height of list */ + /* step 2: determine height of known list items */ + heights = g_array_new (FALSE, FALSE, sizeof (int)); + for (row = gtk_rb_tree_get_first (self->rows); row != NULL; row = gtk_rb_tree_node_get_next (row)) { + if (row->widget == NULL) + continue; + gtk_widget_measure (row->widget, GTK_ORIENTATION_VERTICAL, self->list_width, &min, &nat, NULL, NULL); @@ -460,6 +751,25 @@ gtk_list_view_size_allocate (GtkWidget *widget, row->height = row_height; gtk_rb_tree_node_mark_dirty (row); } + g_array_append_val (heights, row_height); + } + + /* step 3: determine height of unknown items */ + row_height = gtk_list_view_get_unknown_row_height (self, heights); + g_array_free (heights, TRUE); + + for (row = gtk_rb_tree_get_first (self->rows); + row != NULL; + row = gtk_rb_tree_node_get_next (row)) + { + if (row->widget) + continue; + + if (row->height != row_height) + { + row->height = row_height; + gtk_rb_tree_node_mark_dirty (row); + } } /* step 3: update the adjustments */ @@ -474,9 +784,13 @@ gtk_list_view_size_allocate (GtkWidget *widget, row != NULL; row = gtk_rb_tree_node_get_next (row)) { - child_allocation.height = row->height; - gtk_widget_size_allocate (row->widget, &child_allocation, -1); - child_allocation.y += child_allocation.height; + if (row->widget) + { + child_allocation.height = row->height; + gtk_widget_size_allocate (row->widget, &child_allocation, -1); + } + + child_allocation.y += row->height * row->n_rows; } } @@ -504,79 +818,56 @@ gtk_list_view_remove_rows (GtkListView *self, guint n_rows) { ListRow *row; - guint i; if (n_rows == 0) return; row = gtk_list_view_get_row (self, position, NULL); - for (i = 0; i < n_rows; i++) + while (n_rows > 0) { - ListRow *next = gtk_rb_tree_node_get_next (row); - gtk_list_item_manager_release_list_item (self->item_manager, change, row->widget); - row->widget = NULL; - gtk_rb_tree_remove (self->rows, row); - row = next; + if (row->n_rows > n_rows) + { + row->n_rows -= n_rows; + gtk_rb_tree_node_mark_dirty (row); + n_rows = 0; + } + else + { + ListRow *next = gtk_rb_tree_node_get_next (row); + if (row->widget) + gtk_list_item_manager_release_list_item (self->item_manager, change, row->widget); + row->widget = NULL; + n_rows -= row->n_rows; + gtk_rb_tree_remove (self->rows, row); + row = next; + } } gtk_widget_queue_resize (GTK_WIDGET (self)); } static void -gtk_list_view_add_rows (GtkListView *self, - GtkListItemManagerChange *change, - guint position, - guint n_rows) +gtk_list_view_add_rows (GtkListView *self, + guint position, + guint n_rows) { ListRow *row; - guint i; + guint offset; if (n_rows == 0) return; - row = gtk_list_view_get_row (self, position, NULL); + row = gtk_list_view_get_row (self, position, &offset); - for (i = 0; i < n_rows; i++) - { - ListRow *new_row; - - new_row = gtk_rb_tree_insert_before (self->rows, row); - new_row->n_rows = 1; - if (change) - { - new_row->widget = gtk_list_item_manager_try_reacquire_list_item (self->item_manager, - change, - position + i, - row ? row->widget : NULL); - } - if (new_row->widget == NULL) - { - new_row->widget = gtk_list_item_manager_acquire_list_item (self->item_manager, - position + i, - row ? row->widget : NULL); - } - } + if (row == NULL || row->widget) + row = gtk_rb_tree_insert_before (self->rows, row); + row->n_rows += n_rows; + gtk_rb_tree_node_mark_dirty (row); gtk_widget_queue_resize (GTK_WIDGET (self)); } -static void -gtk_list_view_update_rows (GtkListView *self, - guint position) -{ - ListRow *row; - - for (row = gtk_list_view_get_row (self, position, NULL); - row; - row = gtk_rb_tree_node_get_next (row)) - { - gtk_list_item_manager_update_list_item (self->item_manager, row->widget, position); - - position++; - } -} - static void gtk_list_view_model_items_changed_cb (GListModel *model, guint position, @@ -589,17 +880,100 @@ gtk_list_view_model_items_changed_cb (GListModel *model, change = gtk_list_item_manager_begin_change (self->item_manager); gtk_list_view_remove_rows (self, change, position, removed); - gtk_list_view_add_rows (self, change, position, added); - if (removed != added) - gtk_list_view_update_rows (self, position + added); + gtk_list_view_add_rows (self, position, added); - if (gtk_list_item_manager_change_contains (change, self->anchor)) + /* The anchor was removed, but it may just have moved to a different position */ + if (self->anchor && gtk_list_item_manager_change_contains (change, self->anchor)) { - guint anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); + /* The anchor was removed, do a more expensive rebuild trying to find if + * the anchor maybe got readded somewhere else */ + ListRow *row, *new_row; + GtkWidget *insert_before; + guint i, offset, anchor_pos; - /* removed cannot be NULL or the anchor wouldn't have been removed */ - anchor_pos = position + (anchor_pos - position) * added / removed; - gtk_list_view_set_anchor (self, anchor_pos, self->anchor_align); + row = gtk_list_view_get_row (self, position, &offset); + for (new_row = row; + new_row && new_row->widget == NULL; + new_row = gtk_rb_tree_node_get_next (new_row)) + ; + if (new_row) + insert_before = new_row->widget; + else + insert_before = NULL; /* we're at the end */ + + for (i = 0; i < added; i++) + { + GtkWidget *widget; + + widget = gtk_list_item_manager_try_reacquire_list_item (self->item_manager, + change, + position + i, + insert_before); + if (widget == NULL) + { + offset++; + continue; + } + + if (offset > 0) + { + new_row = gtk_rb_tree_insert_before (self->rows, row); + new_row->n_rows = offset; + row->n_rows -= offset; + offset = 0; + gtk_rb_tree_node_mark_dirty (row); + } + + if (row->n_rows == 1) + { + new_row = row; + row = gtk_rb_tree_node_get_next (row); + } + else + { + new_row = gtk_rb_tree_insert_before (self->rows, row); + new_row->n_rows = 1; + row->n_rows--; + gtk_rb_tree_node_mark_dirty (row); + } + + new_row->widget = widget; + if (widget == self->anchor) + { + anchor_pos = position + i; + break; + } + } + + if (i == added) + { + /* The anchor wasn't readded. Guess a good anchor position */ + anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); + + anchor_pos = position + (anchor_pos - position) * added / removed; + } + gtk_list_view_set_anchor (self, anchor_pos, self->anchor_align, change, position); + } + else + { + /* The anchor is still where it was. + * We just may need to update its position and check that its surrounding widgets + * exist (they might be new ones). */ + guint anchor_pos; + + if (self->anchor) + { + anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); + + if (anchor_pos >= position) + anchor_pos += added - removed; + } + else + { + anchor_pos = 0; + } + + gtk_list_view_set_anchor (self, anchor_pos, self->anchor_align, change, position); } gtk_list_item_manager_end_change (self->item_manager, change); @@ -617,6 +991,8 @@ gtk_list_view_clear_model (GtkListView *self) gtk_list_view_model_items_changed_cb, self); g_clear_object (&self->model); + + gtk_list_view_unset_anchor (self); } static void @@ -897,8 +1273,8 @@ gtk_list_view_set_model (GtkListView *self, G_CALLBACK (gtk_list_view_model_items_changed_cb), self); - gtk_list_view_add_rows (self, NULL, 0, g_list_model_get_n_items (model)); - gtk_list_view_set_anchor (self, 0, 0); + gtk_list_view_add_rows (self, 0, g_list_model_get_n_items (model)); + gtk_list_view_set_anchor (self, 0, 0, NULL, (guint) -1); } g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]); @@ -927,7 +1303,7 @@ gtk_list_view_set_functions (GtkListView *self, gtk_list_item_manager_set_factory (self->item_manager, factory); g_object_unref (factory); - gtk_list_view_add_rows (self, NULL, 0, n_items); - gtk_list_view_set_anchor (self, anchor, anchor_align); + gtk_list_view_add_rows (self, 0, n_items); + gtk_list_view_set_anchor (self, anchor, anchor_align, NULL, (guint) -1); } From 7389e704dceb5282f6d080610486f9240b8c838b Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 26 Sep 2018 01:41:39 +0200 Subject: [PATCH 048/170] testlistview: Show the row number Always show the current row. This is mostly useful for debugging, not for beauty. --- tests/testlistview.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/testlistview.c b/tests/testlistview.c index 6ccea18728..14d4cddbf0 100644 --- a/tests/testlistview.c +++ b/tests/testlistview.c @@ -148,12 +148,19 @@ bind_widget (GtkListItem *list_item, guint depth; GIcon *icon; gpointer item; + char *s; item = gtk_list_item_get_item (list_item); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); gtk_list_item_set_child (list_item, box); + child = gtk_label_new (NULL); + gtk_label_set_width_chars (GTK_LABEL (child), 5); + gtk_label_set_xalign (GTK_LABEL (child), 1.0); + g_object_bind_property (list_item, "position", child, "label", G_BINDING_SYNC_CREATE); + gtk_box_append (GTK_BOX (box), child); + depth = gtk_tree_list_row_get_depth (item); if (depth > 0) { @@ -191,7 +198,9 @@ bind_widget (GtkListItem *list_item, } file = g_object_get_data (G_OBJECT (info), "file"); - child = gtk_label_new (g_file_get_basename (file)); + s = g_file_get_basename (file); + child = gtk_label_new (s); + g_free (s); g_object_unref (info); gtk_box_append (GTK_BOX (box), child); From d8eec549f099d61d18971708c6eee6d69ad5b7a4 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 26 Sep 2018 02:18:13 +0200 Subject: [PATCH 049/170] testlistview: Create widgets only once Previously, we were recreating all widgets every time the list item was rebound, which caused a lot of extra work every time we scrolled. Now we keep the widgets around and only set their properties again when the item changes. --- tests/testlistview.c | 153 ++++++++++++++++++++++++++++--------------- 1 file changed, 99 insertions(+), 54 deletions(-) diff --git a/tests/testlistview.c b/tests/testlistview.c index 14d4cddbf0..676124237c 100644 --- a/tests/testlistview.c +++ b/tests/testlistview.c @@ -20,7 +20,8 @@ start_enumerate (GListStore *store) enumerate = g_file_enumerate_children (file, G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_ICON - "," G_FILE_ATTRIBUTE_STANDARD_NAME, + "," G_FILE_ATTRIBUTE_STANDARD_NAME + "," G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, 0, NULL, &error); @@ -138,72 +139,116 @@ create_list_model_for_directory (gpointer file) return G_LIST_MODEL (sort); } +typedef struct _RowData RowData; +struct _RowData +{ + GtkWidget *depth_box; + GtkWidget *expander; + GtkWidget *icon; + GtkWidget *name; + + GtkTreeListRow *current_item; + GBinding *expander_binding; +}; + +static void row_data_notify_item (GtkListItem *item, + GParamSpec *pspec, + RowData *data); static void -bind_widget (GtkListItem *list_item, - gpointer unused) +row_data_unbind (RowData *data) +{ + if (data->current_item == NULL) + return; + + g_binding_unbind (data->expander_binding); + + g_clear_object (&data->current_item); +} + +static void +row_data_bind (RowData *data, + GtkTreeListRow *item) +{ + GFileInfo *info; + GIcon *icon; + guint depth; + + row_data_unbind (data); + + if (item == NULL) + return; + + data->current_item = g_object_ref (item); + + depth = gtk_tree_list_row_get_depth (item); + gtk_widget_set_size_request (data->depth_box, 16 * depth, 0); + + gtk_widget_set_sensitive (data->expander, gtk_tree_list_row_is_expandable (item)); + data->expander_binding = g_object_bind_property (item, "expanded", data->expander, "active", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); + + info = gtk_tree_list_row_get_item (item); + + icon = g_file_info_get_icon (info); + gtk_widget_set_visible (data->icon, icon != NULL); + if (icon) + gtk_image_set_from_gicon (GTK_IMAGE (data->icon), icon); + + gtk_label_set_label (GTK_LABEL (data->name), g_file_info_get_display_name (info)); + + g_object_unref (info); +} + +static void +row_data_notify_item (GtkListItem *item, + GParamSpec *pspec, + RowData *data) +{ + row_data_bind (data, gtk_list_item_get_item (item)); +} + +static void +row_data_free (gpointer _data) +{ + RowData *data = _data; + + row_data_unbind (data); + + g_slice_free (RowData, data); +} + +static void +setup_widget (GtkListItem *list_item, + gpointer unused) { GtkWidget *box, *child; - GFileInfo *info; - GFile *file; - guint depth; - GIcon *icon; - gpointer item; - char *s; + RowData *data; - item = gtk_list_item_get_item (list_item); + data = g_slice_new0 (RowData); + g_signal_connect (list_item, "notify::item", G_CALLBACK (row_data_notify_item), data); + g_object_set_data_full (G_OBJECT (list_item), "row-data", data, row_data_free); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); gtk_list_item_set_child (list_item, box); child = gtk_label_new (NULL); gtk_label_set_width_chars (GTK_LABEL (child), 5); - gtk_label_set_xalign (GTK_LABEL (child), 1.0); - g_object_bind_property (list_item, "position", child, "label", G_BINDING_SYNC_CREATE); gtk_box_append (GTK_BOX (box), child); - depth = gtk_tree_list_row_get_depth (item); - if (depth > 0) - { - child = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); - gtk_widget_set_size_request (child, 16 * depth, 0); - gtk_box_append (GTK_BOX (box), child); - } + data->depth_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_append (GTK_BOX (box), data->depth_box); + + data->expander = g_object_new (GTK_TYPE_TOGGLE_BUTTON, "css-name", "expander-widget", NULL); + gtk_button_set_has_frame (GTK_BUTTON (data->expander), FALSE); + gtk_box_append (GTK_BOX (box), data->expander); + child = g_object_new (GTK_TYPE_SPINNER, "css-name", "expander", NULL); + g_object_bind_property (data->expander, "active", child, "spinning", G_BINDING_SYNC_CREATE); + gtk_button_set_child (GTK_BUTTON (data->expander), child); - if (gtk_tree_list_row_is_expandable (item)) - { - GtkWidget *arrow; + data->icon = gtk_image_new (); + gtk_box_append (GTK_BOX (box), data->icon); - child = g_object_new (GTK_TYPE_TOGGLE_BUTTON, "css-name", "expander-widget", NULL); - gtk_button_set_has_frame (GTK_BUTTON (child), FALSE); - g_object_bind_property (item, "expanded", child, "active", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); - g_object_set_data_full (G_OBJECT (child), "make-sure-its-not-unreffed", g_object_ref (item), g_object_unref); - - arrow = g_object_new (GTK_TYPE_SPINNER, "css-name", "expander", NULL); - g_object_bind_property (item, "expanded", arrow, "spinning", G_BINDING_SYNC_CREATE); - gtk_button_set_child (GTK_BUTTON (child), arrow); - } - else - { - child = gtk_image_new (); /* empty whatever */ - } - gtk_box_append (GTK_BOX (box), child); - - info = gtk_tree_list_row_get_item (item); - - icon = g_file_info_get_icon (info); - if (icon) - { - child = gtk_image_new_from_gicon (icon); - gtk_box_append (GTK_BOX (box), child); - } - - file = g_object_get_data (G_OBJECT (info), "file"); - s = g_file_get_basename (file); - child = gtk_label_new (s); - g_free (s); - g_object_unref (info); - - gtk_box_append (GTK_BOX (box), child); + data->name = gtk_label_new (NULL); + gtk_box_append (GTK_BOX (box), data->name); } static GListModel * @@ -305,8 +350,8 @@ main (int argc, char *argv[]) listview = gtk_list_view_new (); gtk_list_view_set_functions (GTK_LIST_VIEW (listview), + setup_widget, NULL, - bind_widget, NULL, NULL); gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), listview); From fbfc7dc6901dc724309e6b223a0a60667bab67ef Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 28 Sep 2018 02:05:46 +0200 Subject: [PATCH 050/170] listitemmanager: Switch from "insert_before" to "insert_after" argumnet We reorder widgets start to end, so when reusing a list item, we correctly know the previous sibling for that list item, but not the next sibling yet. We just know the widget it should ultimately be in front of. So we can do a more correct guess of the list item's place in the widget tree if we think about where to place an item like this. Actually using this change will come in the next commit. --- gtk/gtklistitemmanager.c | 20 ++++++++++---------- gtk/gtklistitemmanagerprivate.h | 4 ++-- gtk/gtklistview.c | 26 ++++++++++++++------------ 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index cce8eda34d..32687440b2 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -232,8 +232,8 @@ gtk_list_item_manager_change_contains (GtkListItemManagerChange *change, * gtk_list_item_manager_acquire_list_item: * @self: a #GtkListItemManager * @position: the row in the model to create a list item for - * @next_sibling: the widget this widget should be inserted before or %NULL - * if none + * @prev_sibling: the widget this widget should be inserted before or %NULL + * if it should be the first widget * * Creates a list item widget to use for @position. No widget may * yet exist that is used for @position. @@ -249,20 +249,20 @@ gtk_list_item_manager_change_contains (GtkListItemManagerChange *change, GtkWidget * gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, guint position, - GtkWidget *next_sibling) + GtkWidget *prev_sibling) { GtkListItem *result; gpointer item; g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL); - g_return_val_if_fail (next_sibling == NULL || GTK_IS_WIDGET (next_sibling), NULL); + g_return_val_if_fail (prev_sibling == NULL || GTK_IS_WIDGET (prev_sibling), NULL); result = gtk_list_item_factory_create (self->factory); item = g_list_model_get_item (self->model, position); gtk_list_item_factory_bind (self->factory, result, position, item); g_object_unref (item); - gtk_widget_insert_before (GTK_WIDGET (result), self->widget, next_sibling); + gtk_widget_insert_after (GTK_WIDGET (result), self->widget, prev_sibling); return GTK_WIDGET (result); } @@ -271,8 +271,8 @@ gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, * gtk_list_item_manager_try_acquire_list_item_from_change: * @self: a #GtkListItemManager * @position: the row in the model to create a list item for - * @next_sibling: the widget this widget should be inserted before or %NULL - * if none + * @prev_sibling: the widget this widget should be inserted after or %NULL + * if it should be the first widget * * Like gtk_list_item_manager_acquire_list_item(), but only tries to acquire list * items from those previously released as part of @change. @@ -286,20 +286,20 @@ GtkWidget * gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self, GtkListItemManagerChange *change, guint position, - GtkWidget *next_sibling) + GtkWidget *prev_sibling) { GtkListItem *result; gpointer item; g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL); - g_return_val_if_fail (next_sibling == NULL || GTK_IS_WIDGET (next_sibling), NULL); + g_return_val_if_fail (prev_sibling == NULL || GTK_IS_WIDGET (prev_sibling), NULL); /* XXX: can we avoid temporarily allocating items on failure? */ item = g_list_model_get_item (self->model, position); if (g_hash_table_steal_extended (change->items, item, NULL, (gpointer *) &result)) { gtk_list_item_factory_update (self->factory, result, position); - gtk_widget_insert_before (GTK_WIDGET (result), self->widget, next_sibling); + gtk_widget_insert_after (GTK_WIDGET (result), self->widget, prev_sibling); /* XXX: Should we let the listview do this? */ gtk_widget_queue_resize (GTK_WIDGET (result)); } diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h index cf7721bc6c..0f1d1bddd1 100644 --- a/gtk/gtklistitemmanagerprivate.h +++ b/gtk/gtklistitemmanagerprivate.h @@ -60,12 +60,12 @@ gboolean gtk_list_item_manager_change_contains (GtkListItemMana GtkWidget * gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, guint position, - GtkWidget *next_sibling); + GtkWidget *prev_sibling); GtkWidget * gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self, GtkListItemManagerChange *change, guint position, - GtkWidget *next_sibling); + GtkWidget *prev_sibling); void gtk_list_item_manager_update_list_item (GtkListItemManager *self, GtkWidget *item, guint position); diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index 7ef2529e3e..c9cfc7c840 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -404,7 +404,7 @@ gtk_list_view_ensure_rows (GtkListView *self, { ListRow *row, *new_row; guint i, offset; - GtkWidget *insert_before; + GtkWidget *insert_after; gtk_list_view_release_rows (self); @@ -417,7 +417,7 @@ gtk_list_view_ensure_rows (GtkListView *self, gtk_rb_tree_node_mark_dirty (row); } - insert_before = gtk_widget_get_first_child (GTK_WIDGET (self)); + insert_after = NULL; for (i = self->anchor_start; i < self->anchor_end; i++) { @@ -440,21 +440,21 @@ gtk_list_view_ensure_rows (GtkListView *self, new_row->widget = gtk_list_item_manager_try_reacquire_list_item (self->item_manager, change, i, - insert_before); + insert_after); } if (new_row->widget == NULL) { new_row->widget = gtk_list_item_manager_acquire_list_item (self->item_manager, i, - insert_before); + insert_after); } } else { if (update_start <= i) gtk_list_item_manager_update_list_item (self->item_manager, new_row->widget, i); - insert_before = gtk_widget_get_next_sibling (new_row->widget); } + insert_after = new_row->widget; } } @@ -888,18 +888,18 @@ gtk_list_view_model_items_changed_cb (GListModel *model, /* The anchor was removed, do a more expensive rebuild trying to find if * the anchor maybe got readded somewhere else */ ListRow *row, *new_row; - GtkWidget *insert_before; + GtkWidget *insert_after; guint i, offset, anchor_pos; row = gtk_list_view_get_row (self, position, &offset); - for (new_row = row; + for (new_row = gtk_rb_tree_node_get_previous (row); new_row && new_row->widget == NULL; - new_row = gtk_rb_tree_node_get_next (new_row)) - ; + new_row = gtk_rb_tree_node_get_previous (new_row)) + { } if (new_row) - insert_before = new_row->widget; + insert_after = new_row->widget; else - insert_before = NULL; /* we're at the end */ + insert_after = NULL; /* we're at the start */ for (i = 0; i < added; i++) { @@ -908,7 +908,7 @@ gtk_list_view_model_items_changed_cb (GListModel *model, widget = gtk_list_item_manager_try_reacquire_list_item (self->item_manager, change, position + i, - insert_before); + insert_after); if (widget == NULL) { offset++; @@ -938,6 +938,8 @@ gtk_list_view_model_items_changed_cb (GListModel *model, } new_row->widget = widget; + insert_after = widget; + if (widget == self->anchor) { anchor_pos = position + i; From 7831980c1dd7f17136d0f4a4f6df8a01e603dde0 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 28 Sep 2018 02:45:54 +0200 Subject: [PATCH 051/170] listlistmodel: Add gtk_list_list_model_item_moved() Use it to fix a case that just said g_warning ("oops"). Apparently I had forgotten the case where a container moved a child in the widget tree. --- gtk/gtklistlistmodel.c | 64 +++++++++++++++++++++++++---------- gtk/gtklistlistmodelprivate.h | 3 ++ gtk/gtkwidget.c | 2 +- 3 files changed, 51 insertions(+), 18 deletions(-) diff --git a/gtk/gtklistlistmodel.c b/gtk/gtklistlistmodel.c index 3d10f49280..f01133978d 100644 --- a/gtk/gtklistlistmodel.c +++ b/gtk/gtklistlistmodel.c @@ -206,15 +206,12 @@ gtk_list_list_model_new_with_size (GType item_type, return result; } -void -gtk_list_list_model_item_added (GtkListListModel *self, - gpointer item) +static guint +gtk_list_list_model_find (GtkListListModel *self, + gpointer item) { - gpointer x; guint position; - - g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self)); - g_return_if_fail (item != NULL); + gpointer x; position = 0; for (x = self->get_first (self->data); @@ -222,7 +219,17 @@ gtk_list_list_model_item_added (GtkListListModel *self, x = self->get_next (x, self->data)) position++; - gtk_list_list_model_item_added_at (self, position); + return position; +} + +void +gtk_list_list_model_item_added (GtkListListModel *self, + gpointer item) +{ + g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self)); + g_return_if_fail (item != NULL); + + gtk_list_list_model_item_added_at (self, gtk_list_list_model_find (self, item)); } void @@ -241,26 +248,49 @@ void gtk_list_list_model_item_removed (GtkListListModel *self, gpointer previous) { - gpointer x; guint position; g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self)); if (previous == NULL) + position = 0; + else + position = 1 + gtk_list_list_model_find (self, previous); + + gtk_list_list_model_item_removed_at (self, position); +} + +void +gtk_list_list_model_item_moved (GtkListListModel *self, + gpointer item, + gpointer previous_previous) +{ + guint position, previous_position; + guint min, max; + + g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self)); + g_return_if_fail (item != previous_previous); + + position = gtk_list_list_model_find (self, item); + + if (previous_previous == NULL) { - position = 0; + previous_position = 0; } else { - position = 1; - - for (x = self->get_first (self->data); - x != previous; - x = self->get_next (x, self->data)) - position++; + previous_position = gtk_list_list_model_find (self, previous_previous); + if (position > previous_position) + previous_position++; } - gtk_list_list_model_item_removed_at (self, position); + /* item didn't move */ + if (position == previous_position) + return; + + min = MIN (position, previous_position); + max = MAX (position, previous_position) + 1; + g_list_model_items_changed (G_LIST_MODEL (self), min, max - min, max - min); } void diff --git a/gtk/gtklistlistmodelprivate.h b/gtk/gtklistlistmodelprivate.h index 3103b3c7c3..f0a5717238 100644 --- a/gtk/gtklistlistmodelprivate.h +++ b/gtk/gtklistlistmodelprivate.h @@ -64,6 +64,9 @@ void gtk_list_list_model_item_removed (GtkListListMode gpointer previous); void gtk_list_list_model_item_removed_at (GtkListListModel *self, guint position); +void gtk_list_list_model_item_moved (GtkListListModel *self, + gpointer item, + gpointer previous_previous); void gtk_list_list_model_clear (GtkListListModel *self); diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index f667a5d8d4..32286e9d4d 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -5881,7 +5881,7 @@ gtk_widget_reposition_after (GtkWidget *widget, if (parent->priv->children_observer) { if (prev_previous) - g_warning ("oops"); + gtk_list_list_model_item_moved (parent->priv->children_observer, widget, prev_previous); else gtk_list_list_model_item_added (parent->priv->children_observer, widget); } From 5b69fd535d1bac96912566dc8c2d2ca3c3f969ed Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 28 Sep 2018 03:33:16 +0200 Subject: [PATCH 052/170] listview: Try to keep the list items in order when scrolling Instead of just destroying all items and then recreating them (or even hide()ing and then show()ing them again (or even even repositioning them in the widget tree)), just try to reust them in the order they are. This works surprisingly well when scrolling and most/all widgets just moved. --- gtk/gtklistitemmanager.c | 29 ++++++++++++++++++++++++++++ gtk/gtklistitemmanagerprivate.h | 4 ++++ gtk/gtklistview.c | 34 ++++++++++++++++++++++++--------- 3 files changed, 58 insertions(+), 9 deletions(-) diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index 32687440b2..62888f366a 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -21,6 +21,8 @@ #include "gtklistitemmanagerprivate.h" +#include "gtkwidgetprivate.h" + struct _GtkListItemManager { GObject parent_instance; @@ -312,6 +314,33 @@ gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self, return GTK_WIDGET (result); } +/** + * gtk_list_item_manager_move_list_item: + * @self: a #GtkListItemManager + * @list_item: an acquired #GtkListItem that should be moved to represent + * a different row + * @position: the new position of that list item + * @prev_sibling: the new previous sibling + * + * Moves the widget to represent a new position in the listmodel without + * releasing the item. + * + * This is most useful when scrolling. + **/ +void +gtk_list_item_manager_move_list_item (GtkListItemManager *self, + GtkWidget *list_item, + guint position, + GtkWidget *prev_sibling) +{ + gpointer item; + + item = g_list_model_get_item (self->model, position); + gtk_list_item_factory_bind (self->factory, GTK_LIST_ITEM (list_item), position, item); + gtk_widget_insert_after (list_item, _gtk_widget_get_parent (list_item), prev_sibling); + g_object_unref (item); +} + /** * gtk_list_item_manager_update_list_item: * @self: a #GtkListItemManager diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h index 0f1d1bddd1..d34e346a21 100644 --- a/gtk/gtklistitemmanagerprivate.h +++ b/gtk/gtklistitemmanagerprivate.h @@ -69,6 +69,10 @@ GtkWidget * gtk_list_item_manager_try_reacquire_list_item void gtk_list_item_manager_update_list_item (GtkListItemManager *self, GtkWidget *item, guint position); +void gtk_list_item_manager_move_list_item (GtkListItemManager *self, + GtkWidget *list_item, + guint position, + GtkWidget *prev_sibling); void gtk_list_item_manager_release_list_item (GtkListItemManager *self, GtkListItemManagerChange *change, GtkWidget *widget); diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index c9cfc7c840..35cf3b6da3 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -336,7 +336,8 @@ gtk_list_view_merge_list_rows (GtkListView *self, } static void -gtk_list_view_release_rows (GtkListView *self) +gtk_list_view_release_rows (GtkListView *self, + GQueue *released) { ListRow *row, *prev, *next; guint i; @@ -347,7 +348,7 @@ gtk_list_view_release_rows (GtkListView *self) { if (row->widget) { - gtk_list_item_manager_release_list_item (self->item_manager, NULL, row->widget); + g_queue_push_tail (released, row->widget); row->widget = NULL; i++; prev = gtk_rb_tree_node_get_previous (row); @@ -379,7 +380,7 @@ gtk_list_view_release_rows (GtkListView *self) if (row->widget) { - gtk_list_item_manager_release_list_item (self->item_manager, NULL, row->widget); + g_queue_push_tail (released, row->widget); row->widget = NULL; prev = gtk_rb_tree_node_get_previous (row); if (prev && gtk_list_view_merge_list_rows (self, prev, row)) @@ -390,7 +391,7 @@ gtk_list_view_release_rows (GtkListView *self) { if (next->widget) { - gtk_list_item_manager_release_list_item (self->item_manager, NULL, next->widget); + g_queue_push_tail (released, next->widget); next->widget = NULL; } gtk_list_view_merge_list_rows (self, row, next); @@ -404,9 +405,10 @@ gtk_list_view_ensure_rows (GtkListView *self, { ListRow *row, *new_row; guint i, offset; - GtkWidget *insert_after; + GtkWidget *widget, *insert_after; + GQueue released = G_QUEUE_INIT; - gtk_list_view_release_rows (self); + gtk_list_view_release_rows (self, &released); row = gtk_list_view_get_row (self, self->anchor_start, &offset); if (offset > 0) @@ -444,9 +446,20 @@ gtk_list_view_ensure_rows (GtkListView *self, } if (new_row->widget == NULL) { - new_row->widget = gtk_list_item_manager_acquire_list_item (self->item_manager, - i, - insert_after); + new_row->widget = g_queue_pop_head (&released); + if (new_row->widget) + { + gtk_list_item_manager_move_list_item (self->item_manager, + new_row->widget, + i, + insert_after); + } + else + { + new_row->widget = gtk_list_item_manager_acquire_list_item (self->item_manager, + i, + insert_after); + } } } else @@ -456,6 +469,9 @@ gtk_list_view_ensure_rows (GtkListView *self, } insert_after = new_row->widget; } + + while ((widget = g_queue_pop_head (&released))) + gtk_list_item_manager_release_list_item (self->item_manager, NULL, widget); } static void From b3fb80c60825813f9cdea24831404a885eb6fba2 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 29 Sep 2018 22:34:43 +0200 Subject: [PATCH 053/170] listview: Add selection properties to ListItem This just brings the infrastructure into place, we're not using the properties yet. --- docs/reference/gtk/gtk4-sections.txt | 3 + gtk/gtklistitem.c | 128 +++++++++++++++++++++++++++ gtk/gtklistitem.h | 8 ++ gtk/gtklistitemfactory.c | 13 ++- gtk/gtklistitemfactoryprivate.h | 6 +- gtk/gtklistitemmanager.c | 8 +- gtk/gtklistitemprivate.h | 2 + 7 files changed, 160 insertions(+), 8 deletions(-) diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index c11db12cae..6c7e109204 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -400,6 +400,9 @@ gtk_list_item_get_item gtk_list_item_get_position gtk_list_item_get_child gtk_list_item_set_child +gtk_list_item_get_selected +gtk_list_item_get_selectable +gtk_list_item_set_selectable GTK_LIST_ITEM GTK_LIST_ITEM_CLASS diff --git a/gtk/gtklistitem.c b/gtk/gtklistitem.c index ac4fda6737..a11b8b3cc8 100644 --- a/gtk/gtklistitem.c +++ b/gtk/gtklistitem.c @@ -55,6 +55,9 @@ struct _GtkListItem GObject *item; GtkWidget *child; guint position; + + guint selectable : 1; + guint selected : 1; }; struct _GtkListItemClass @@ -68,6 +71,8 @@ enum PROP_CHILD, PROP_ITEM, PROP_POSITION, + PROP_SELECTABLE, + PROP_SELECTED, N_PROPS }; @@ -109,6 +114,14 @@ gtk_list_item_get_property (GObject *object, g_value_set_uint (value, self->position); break; + case PROP_SELECTABLE: + g_value_set_boolean (value, self->selectable); + break; + + case PROP_SELECTED: + g_value_set_boolean (value, self->selected); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -129,6 +142,10 @@ gtk_list_item_set_property (GObject *object, gtk_list_item_set_child (self, g_value_get_object (value)); break; + case PROP_SELECTABLE: + gtk_list_item_set_selectable (self, g_value_get_boolean (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -181,6 +198,30 @@ gtk_list_item_class_init (GtkListItemClass *klass) 0, G_MAXUINT, GTK_INVALID_LIST_POSITION, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** + * GtkListItem:selectable: + * + * If the item can be selected by the user + */ + properties[PROP_SELECTABLE] = + g_param_spec_boolean ("selectable", + P_("Selectable"), + P_("If the item can be selected by the user"), + TRUE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * GtkListItem:selected: + * + * If the item is currently selected + */ + properties[PROP_SELECTED] = + g_param_spec_boolean ("selected", + P_("Selected"), + P_("If the item is currently selected"), + FALSE, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (gobject_class, N_PROPS, properties); /* This gets overwritten by gtk_list_item_new() but better safe than sorry */ @@ -191,6 +232,7 @@ gtk_list_item_class_init (GtkListItemClass *klass) static void gtk_list_item_init (GtkListItem *self) { + self->selectable = TRUE; } GtkWidget * @@ -317,3 +359,89 @@ gtk_list_item_set_position (GtkListItem *self, g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_POSITION]); } +/** + * gtk_list_item_get_selected: + * @self: a #GtkListItem + * + * Checks if the item is displayed as selected. The selected state is + * maintained by the container and its list model and cannot be set + * otherwise. + * + * Returns: %TRUE if the item is selected. + **/ +gboolean +gtk_list_item_get_selected (GtkListItem *self) +{ + g_return_val_if_fail (GTK_IS_LIST_ITEM (self), FALSE); + + return self->selected; +} + +void +gtk_list_item_set_selected (GtkListItem *self, + gboolean selected) +{ + g_return_if_fail (GTK_IS_LIST_ITEM (self)); + + if (self->selected == selected) + return; + + self->selected = selected; + + if (selected) + gtk_widget_set_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED, FALSE); + else + gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED]); +} + +/** + * gtk_list_item_get_selectable: + * @self: a #GtkListItem + * + * Checks if a list item has been set to be selectable via + * gtk_list_item_set_selectable(). + * + * Do not confuse this function with gtk_list_item_get_selected(). + * + * Returns: %TRUE if the item is selectable + **/ +gboolean +gtk_list_item_get_selectable (GtkListItem *self) +{ + g_return_val_if_fail (GTK_IS_LIST_ITEM (self), FALSE); + + return self->selectable; +} + +/** + * gtk_list_item_set_selectable: + * @self: a #GtkListItem + * @selectable: if the item should be selectable + * + * Sets @self to be selectable. If an item is selectable, clicking + * on the item or using the keyboard will try to select or unselect + * the item. If this succeeds is up to the model to determine, as + * it is managing the selected state. + * + * Note that this means that making an item non-selectable has no + * influence on the selected state at all. A non-selectable item + * may still be selected. + * + * By default, list items are selectable. When rebinding them to + * a new item, they will also be reset to be selectable by GTK. + **/ +void +gtk_list_item_set_selectable (GtkListItem *self, + gboolean selectable) +{ + g_return_if_fail (GTK_IS_LIST_ITEM (self)); + + if (self->selectable == selectable) + return; + + self->selectable = selectable; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTABLE]); +} diff --git a/gtk/gtklistitem.h b/gtk/gtklistitem.h index e26c57ba4d..65d0dcdb68 100644 --- a/gtk/gtklistitem.h +++ b/gtk/gtklistitem.h @@ -73,6 +73,14 @@ GDK_AVAILABLE_IN_ALL gpointer gtk_list_item_get_item (GtkListItem *self); GDK_AVAILABLE_IN_ALL guint gtk_list_item_get_position (GtkListItem *self); +GDK_AVAILABLE_IN_ALL +gboolean gtk_list_item_get_selected (GtkListItem *self); +GDK_AVAILABLE_IN_ALL +gboolean gtk_list_item_get_selectable (GtkListItem *self); +GDK_AVAILABLE_IN_ALL +void gtk_list_item_set_selectable (GtkListItem *self, + gboolean selectable); + GDK_AVAILABLE_IN_ALL void gtk_list_item_set_child (GtkListItem *self, diff --git a/gtk/gtklistitemfactory.c b/gtk/gtklistitemfactory.c index e102e25df3..7a508aed45 100644 --- a/gtk/gtklistitemfactory.c +++ b/gtk/gtklistitemfactory.c @@ -152,7 +152,8 @@ void gtk_list_item_factory_bind (GtkListItemFactory *self, GtkListItem *list_item, guint position, - gpointer item) + gpointer item, + gboolean selected) { g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self)); g_return_if_fail (GTK_IS_LIST_ITEM (list_item)); @@ -161,6 +162,7 @@ gtk_list_item_factory_bind (GtkListItemFactory *self, gtk_list_item_set_item (list_item, item); gtk_list_item_set_position (list_item, position); + gtk_list_item_set_selected (list_item, selected); if (self->bind_func) self->bind_func (list_item, self->user_data); @@ -171,12 +173,18 @@ gtk_list_item_factory_bind (GtkListItemFactory *self, void gtk_list_item_factory_update (GtkListItemFactory *self, GtkListItem *list_item, - guint position) + guint position, + gboolean selected) { g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self)); g_return_if_fail (GTK_IS_LIST_ITEM (list_item)); + g_object_freeze_notify (G_OBJECT (list_item)); + gtk_list_item_set_position (list_item, position); + gtk_list_item_set_selected (list_item, selected); + + g_object_thaw_notify (G_OBJECT (list_item)); } void @@ -190,6 +198,7 @@ gtk_list_item_factory_unbind (GtkListItemFactory *self, gtk_list_item_set_item (list_item, NULL); gtk_list_item_set_position (list_item, 0); + gtk_list_item_set_selected (list_item, FALSE); g_object_thaw_notify (G_OBJECT (list_item)); } diff --git a/gtk/gtklistitemfactoryprivate.h b/gtk/gtklistitemfactoryprivate.h index 779251344b..3e815fa131 100644 --- a/gtk/gtklistitemfactoryprivate.h +++ b/gtk/gtklistitemfactoryprivate.h @@ -48,10 +48,12 @@ GtkListItem * gtk_list_item_factory_create (GtkListItemFact void gtk_list_item_factory_bind (GtkListItemFactory *self, GtkListItem *list_item, guint position, - gpointer item); + gpointer item, + gboolean selected); void gtk_list_item_factory_update (GtkListItemFactory *self, GtkListItem *list_item, - guint position); + guint position, + gboolean selected); void gtk_list_item_factory_unbind (GtkListItemFactory *self, GtkListItem *list_item); diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index 62888f366a..1e74c8d8e1 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -262,7 +262,7 @@ gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, result = gtk_list_item_factory_create (self->factory); item = g_list_model_get_item (self->model, position); - gtk_list_item_factory_bind (self->factory, result, position, item); + gtk_list_item_factory_bind (self->factory, result, position, item, FALSE); g_object_unref (item); gtk_widget_insert_after (GTK_WIDGET (result), self->widget, prev_sibling); @@ -300,7 +300,7 @@ gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self, item = g_list_model_get_item (self->model, position); if (g_hash_table_steal_extended (change->items, item, NULL, (gpointer *) &result)) { - gtk_list_item_factory_update (self->factory, result, position); + gtk_list_item_factory_update (self->factory, result, position, FALSE); gtk_widget_insert_after (GTK_WIDGET (result), self->widget, prev_sibling); /* XXX: Should we let the listview do this? */ gtk_widget_queue_resize (GTK_WIDGET (result)); @@ -336,7 +336,7 @@ gtk_list_item_manager_move_list_item (GtkListItemManager *self, gpointer item; item = g_list_model_get_item (self->model, position); - gtk_list_item_factory_bind (self->factory, GTK_LIST_ITEM (list_item), position, item); + gtk_list_item_factory_bind (self->factory, GTK_LIST_ITEM (list_item), position, item, FALSE); gtk_widget_insert_after (list_item, _gtk_widget_get_parent (list_item), prev_sibling); g_object_unref (item); } @@ -358,7 +358,7 @@ gtk_list_item_manager_update_list_item (GtkListItemManager *self, g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); g_return_if_fail (GTK_IS_LIST_ITEM (item)); - gtk_list_item_factory_update (self->factory, GTK_LIST_ITEM (item), position); + gtk_list_item_factory_update (self->factory, GTK_LIST_ITEM (item), position, FALSE); } /* diff --git a/gtk/gtklistitemprivate.h b/gtk/gtklistitemprivate.h index f238685c76..08ed82f162 100644 --- a/gtk/gtklistitemprivate.h +++ b/gtk/gtklistitemprivate.h @@ -30,6 +30,8 @@ void gtk_list_item_set_item (GtkListItem gpointer item); void gtk_list_item_set_position (GtkListItem *self, guint position); +void gtk_list_item_set_selected (GtkListItem *self, + gboolean selected); G_END_DECLS From 01386aef295e71588153759929713c71d0d9f92d Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 29 Sep 2018 22:38:09 +0200 Subject: [PATCH 054/170] listview: Reset listitems' CSS animations when rebinding This way, newly displayed rows don't play an unselect animation (text fading in) when they are unselected, but the row was previously used for a selected item. --- gtk/gtklistitem.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gtk/gtklistitem.c b/gtk/gtklistitem.c index a11b8b3cc8..411d187b89 100644 --- a/gtk/gtklistitem.c +++ b/gtk/gtklistitem.c @@ -22,8 +22,10 @@ #include "gtklistitemprivate.h" #include "gtkbinlayout.h" +#include "gtkcssnodeprivate.h" #include "gtkintl.h" #include "gtkwidget.h" +#include "gtkwidgetprivate.h" /** * SECTION:gtklistitem @@ -325,6 +327,8 @@ gtk_list_item_set_item (GtkListItem *self, if (item) self->item = g_object_ref (item); + gtk_css_node_invalidate (gtk_widget_get_css_node (GTK_WIDGET (self)), GTK_CSS_CHANGE_ANIMATIONS); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]); } From d8c116f20ab99dee036f2913a48ad4eed56d8a88 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 3 Oct 2018 18:53:06 +0200 Subject: [PATCH 055/170] listview: Add initial support for displaying selections --- gtk/gtklistitemmanager.c | 25 +++++++----- gtk/gtklistitemmanagerprivate.h | 5 ++- gtk/gtklistview.c | 67 ++++++++++++++++++++++++++++++--- 3 files changed, 81 insertions(+), 16 deletions(-) diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index 1e74c8d8e1..e7c11f483f 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -28,7 +28,7 @@ struct _GtkListItemManager GObject parent_instance; GtkWidget *widget; - GListModel *model; + GtkSelectionModel *model; GtkListItemFactory *factory; }; @@ -107,7 +107,7 @@ gtk_list_item_manager_get_factory (GtkListItemManager *self) void gtk_list_item_manager_set_model (GtkListItemManager *self, - GListModel *model) + GtkSelectionModel *model) { g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model)); @@ -121,7 +121,7 @@ gtk_list_item_manager_set_model (GtkListItemManager *self, self->model = g_object_ref (model); } -GListModel * +GtkSelectionModel * gtk_list_item_manager_get_model (GtkListItemManager *self) { g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL); @@ -255,14 +255,16 @@ gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, { GtkListItem *result; gpointer item; + gboolean selected; g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL); g_return_val_if_fail (prev_sibling == NULL || GTK_IS_WIDGET (prev_sibling), NULL); result = gtk_list_item_factory_create (self->factory); - item = g_list_model_get_item (self->model, position); - gtk_list_item_factory_bind (self->factory, result, position, item, FALSE); + item = g_list_model_get_item (G_LIST_MODEL (self->model), position); + selected = gtk_selection_model_is_selected (self->model, position); + gtk_list_item_factory_bind (self->factory, result, position, item, selected); g_object_unref (item); gtk_widget_insert_after (GTK_WIDGET (result), self->widget, prev_sibling); @@ -297,7 +299,7 @@ gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self, g_return_val_if_fail (prev_sibling == NULL || GTK_IS_WIDGET (prev_sibling), NULL); /* XXX: can we avoid temporarily allocating items on failure? */ - item = g_list_model_get_item (self->model, position); + item = g_list_model_get_item (G_LIST_MODEL (self->model), position); if (g_hash_table_steal_extended (change->items, item, NULL, (gpointer *) &result)) { gtk_list_item_factory_update (self->factory, result, position, FALSE); @@ -334,9 +336,11 @@ gtk_list_item_manager_move_list_item (GtkListItemManager *self, GtkWidget *prev_sibling) { gpointer item; + gboolean selected; - item = g_list_model_get_item (self->model, position); - gtk_list_item_factory_bind (self->factory, GTK_LIST_ITEM (list_item), position, item, FALSE); + item = g_list_model_get_item (G_LIST_MODEL (self->model), position); + selected = gtk_selection_model_is_selected (self->model, position); + gtk_list_item_factory_bind (self->factory, GTK_LIST_ITEM (list_item), position, item, selected); gtk_widget_insert_after (list_item, _gtk_widget_get_parent (list_item), prev_sibling); g_object_unref (item); } @@ -355,10 +359,13 @@ gtk_list_item_manager_update_list_item (GtkListItemManager *self, GtkWidget *item, guint position) { + gboolean selected; + g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); g_return_if_fail (GTK_IS_LIST_ITEM (item)); - gtk_list_item_factory_update (self->factory, GTK_LIST_ITEM (item), position, FALSE); + selected = gtk_selection_model_is_selected (self->model, position); + gtk_list_item_factory_update (self->factory, GTK_LIST_ITEM (item), position, selected); } /* diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h index d34e346a21..47a4434d7d 100644 --- a/gtk/gtklistitemmanagerprivate.h +++ b/gtk/gtklistitemmanagerprivate.h @@ -24,6 +24,7 @@ #include "gtk/gtktypes.h" #include "gtk/gtklistitemfactoryprivate.h" +#include "gtk/gtkselectionmodel.h" G_BEGIN_DECLS @@ -46,8 +47,8 @@ void gtk_list_item_manager_set_factory (GtkListItemMana GtkListItemFactory *factory); GtkListItemFactory * gtk_list_item_manager_get_factory (GtkListItemManager *self); void gtk_list_item_manager_set_model (GtkListItemManager *self, - GListModel *model); -GListModel * gtk_list_item_manager_get_model (GtkListItemManager *self); + GtkSelectionModel *model); +GtkSelectionModel * gtk_list_item_manager_get_model (GtkListItemManager *self); guint gtk_list_item_manager_get_size (GtkListItemManager *self); diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index 35cf3b6da3..494a2a591b 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -27,6 +27,8 @@ #include "gtklistitemfactoryprivate.h" #include "gtklistitemmanagerprivate.h" #include "gtkscrollable.h" +#include "gtkselectionmodel.h" +#include "gtksingleselection.h" #include "gtkwidgetprivate.h" /* Maximum number of list items created by the listview. @@ -908,7 +910,7 @@ gtk_list_view_model_items_changed_cb (GListModel *model, guint i, offset, anchor_pos; row = gtk_list_view_get_row (self, position, &offset); - for (new_row = gtk_rb_tree_node_get_previous (row); + for (new_row = row ? gtk_rb_tree_node_get_previous (row) : gtk_rb_tree_get_last (self->rows); new_row && new_row->widget == NULL; new_row = gtk_rb_tree_node_get_previous (new_row)) { } @@ -969,6 +971,9 @@ gtk_list_view_model_items_changed_cb (GListModel *model, anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); anchor_pos = position + (anchor_pos - position) * added / removed; + if (anchor_pos >= g_list_model_get_n_items (self->model) && + anchor_pos > 0) + anchor_pos--; } gtk_list_view_set_anchor (self, anchor_pos, self->anchor_align, change, position); } @@ -997,6 +1002,34 @@ gtk_list_view_model_items_changed_cb (GListModel *model, gtk_list_item_manager_end_change (self->item_manager, change); } +static void +gtk_list_view_model_selection_changed_cb (GListModel *model, + guint position, + guint n_items, + GtkListView *self) +{ + ListRow *row; + guint offset; + + row = gtk_list_view_get_row (self, position, &offset); + + if (offset) + { + position += row->n_rows - offset; + n_items -= row->n_rows - offset; + row = gtk_rb_tree_node_get_next (row); + } + + while (n_items > 0) + { + if (row->widget) + gtk_list_item_manager_update_list_item (self->item_manager, row->widget, position); + position += row->n_rows; + n_items -= MIN (n_items, row->n_rows); + row = gtk_rb_tree_node_get_next (row); + } +} + static void gtk_list_view_clear_model (GtkListView *self) { @@ -1005,6 +1038,9 @@ gtk_list_view_clear_model (GtkListView *self) gtk_list_view_remove_rows (self, NULL, 0, g_list_model_get_n_items (self->model)); + g_signal_handlers_disconnect_by_func (self->model, + gtk_list_view_model_selection_changed_cb, + self); g_signal_handlers_disconnect_by_func (self->model, gtk_list_view_model_items_changed_cb, self); @@ -1264,9 +1300,12 @@ gtk_list_view_get_model (GtkListView *self) /** * gtk_list_view_set_model: * @self: a #GtkListView - * @file: (allow-none) (transfer none): the model to use or %NULL for none + * @model: (allow-none) (transfer none): the model to use or %NULL for none * - * Sets the #GListModel to use for + * Sets the #GListModel to use. + * + * If the @model is a #GtkSelectionModel, it is used for managing the selection. + * Otherwise, @self creates a #GtkSingleSelection for the selection. **/ void gtk_list_view_set_model (GtkListView *self, @@ -1280,20 +1319,38 @@ gtk_list_view_set_model (GtkListView *self, gtk_list_view_clear_model (self); - gtk_list_item_manager_set_model (self->item_manager, model); - if (model) { + GtkSelectionModel *selection_model; + self->model = g_object_ref (model); + if (GTK_IS_SELECTION_MODEL (model)) + selection_model = GTK_SELECTION_MODEL (g_object_ref (model)); + else + selection_model = GTK_SELECTION_MODEL (gtk_single_selection_new (model)); + + gtk_list_item_manager_set_model (self->item_manager, selection_model); + g_signal_connect (model, "items-changed", G_CALLBACK (gtk_list_view_model_items_changed_cb), self); + g_signal_connect (selection_model, + "selection-changed", + G_CALLBACK (gtk_list_view_model_selection_changed_cb), + self); + + g_object_unref (selection_model); gtk_list_view_add_rows (self, 0, g_list_model_get_n_items (model)); gtk_list_view_set_anchor (self, 0, 0, NULL, (guint) -1); } + else + { + gtk_list_item_manager_set_model (self->item_manager, NULL); + } + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]); } From 057effc5d6c349edcf31c5596262790367557af1 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 5 Oct 2018 23:24:18 +0200 Subject: [PATCH 056/170] listitem: Add a press gesture to select the item This is implemented by using actions, which are a neat trick to get to allow the ListItem to call functions on the ListView without actually needing to be aware of it. --- gtk/gtklistitem.c | 91 ++++++++++++++++++++++++++++++--- gtk/gtklistitemfactory.c | 15 ++---- gtk/gtklistitemfactoryprivate.h | 3 +- gtk/gtklistitemmanager.c | 4 +- gtk/gtklistitemprivate.h | 4 +- gtk/gtklistview.c | 51 ++++++++++++++++++ 6 files changed, 149 insertions(+), 19 deletions(-) diff --git a/gtk/gtklistitem.c b/gtk/gtklistitem.c index 411d187b89..f5a00ac036 100644 --- a/gtk/gtklistitem.c +++ b/gtk/gtklistitem.c @@ -23,7 +23,9 @@ #include "gtkbinlayout.h" #include "gtkcssnodeprivate.h" +#include "gtkgestureclick.h" #include "gtkintl.h" +#include "gtkmain.h" #include "gtkwidget.h" #include "gtkwidgetprivate.h" @@ -232,19 +234,94 @@ gtk_list_item_class_init (GtkListItemClass *klass) } static void -gtk_list_item_init (GtkListItem *self) +gtk_list_item_click_gesture_pressed (GtkGestureClick *gesture, + int n_press, + double x, + double y, + GtkListItem *self) { - self->selectable = TRUE; + GtkWidget *widget = GTK_WIDGET (self); + GdkModifierType state; + GdkEvent *event; + gboolean extend, modify; + + if (!self->selectable) + { + gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED); + return; + } + + event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), + gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture))); + state = gdk_event_get_modifier_state (event); + extend = (state & GDK_SHIFT_MASK) != 0; + modify = (state & GDK_CONTROL_MASK) != 0; + + gtk_widget_activate_action (GTK_WIDGET (self), + "list.select-item", + "(ubb)", + self->position, modify, extend); + + gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_ACTIVE, FALSE); + + if (gtk_widget_get_focus_on_click (widget)) + gtk_widget_grab_focus (widget); } -GtkWidget * +static void +gtk_list_item_click_gesture_released (GtkGestureClick *gesture, + int n_press, + double x, + double y, + GtkListItem *self) +{ + gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE); +} + +static void +gtk_list_item_click_gesture_canceled (GtkGestureClick *gesture, + GdkEventSequence *sequence, + GtkListItem *self) +{ + gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE); +} + +static void +gtk_list_item_init (GtkListItem *self) +{ + GtkGesture *gesture; + + self->selectable = TRUE; + gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE); + + gesture = gtk_gesture_click_new (); + gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), + GTK_PHASE_BUBBLE); + gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), + FALSE); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), + GDK_BUTTON_PRIMARY); + g_signal_connect (gesture, "pressed", + G_CALLBACK (gtk_list_item_click_gesture_pressed), self); + g_signal_connect (gesture, "released", + G_CALLBACK (gtk_list_item_click_gesture_released), self); + g_signal_connect (gesture, "cancel", + G_CALLBACK (gtk_list_item_click_gesture_canceled), self); + gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture)); +} + +GtkListItem * gtk_list_item_new (const char *css_name) { + GtkListItem *result; + g_return_val_if_fail (css_name != NULL, NULL); - return g_object_new (GTK_TYPE_LIST_ITEM, - "css-name", css_name, - NULL); + result = g_object_new (GTK_TYPE_LIST_ITEM, + "css-name", css_name, + NULL); + + return result; } /** @@ -447,5 +524,7 @@ gtk_list_item_set_selectable (GtkListItem *self, self->selectable = selectable; + gtk_widget_set_can_focus (GTK_WIDGET (self), self->selectable); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTABLE]); } diff --git a/gtk/gtklistitemfactory.c b/gtk/gtklistitemfactory.c index 7a508aed45..012ed3b123 100644 --- a/gtk/gtklistitemfactory.c +++ b/gtk/gtklistitemfactory.c @@ -133,19 +133,14 @@ gtk_list_item_factory_new (GtkListItemSetupFunc setup_func, return self; } -GtkListItem * -gtk_list_item_factory_create (GtkListItemFactory *self) +void +gtk_list_item_factory_setup (GtkListItemFactory *self, + GtkListItem *list_item) { - GtkWidget *result; - - g_return_val_if_fail (GTK_IS_LIST_ITEM_FACTORY (self), NULL); - - result = gtk_list_item_new ("row"); + g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self)); if (self->setup_func) - self->setup_func (GTK_LIST_ITEM (result), self->user_data); - - return GTK_LIST_ITEM (result); + self->setup_func (list_item, self->user_data); } void diff --git a/gtk/gtklistitemfactoryprivate.h b/gtk/gtklistitemfactoryprivate.h index 3e815fa131..dc538f09fa 100644 --- a/gtk/gtklistitemfactoryprivate.h +++ b/gtk/gtklistitemfactoryprivate.h @@ -43,7 +43,8 @@ GtkListItemFactory * gtk_list_item_factory_new (GtkListItemSetu gpointer user_data, GDestroyNotify user_destroy); -GtkListItem * gtk_list_item_factory_create (GtkListItemFactory *self); +void gtk_list_item_factory_setup (GtkListItemFactory *self, + GtkListItem *list_item); void gtk_list_item_factory_bind (GtkListItemFactory *self, GtkListItem *list_item, diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index e7c11f483f..00bce1a10b 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -21,6 +21,7 @@ #include "gtklistitemmanagerprivate.h" +#include "gtklistitemprivate.h" #include "gtkwidgetprivate.h" struct _GtkListItemManager @@ -260,7 +261,8 @@ gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL); g_return_val_if_fail (prev_sibling == NULL || GTK_IS_WIDGET (prev_sibling), NULL); - result = gtk_list_item_factory_create (self->factory); + result = gtk_list_item_new ("row"); + gtk_list_item_factory_setup (self->factory, result); item = g_list_model_get_item (G_LIST_MODEL (self->model), position); selected = gtk_selection_model_is_selected (self->model, position); diff --git a/gtk/gtklistitemprivate.h b/gtk/gtklistitemprivate.h index 08ed82f162..3a2ac463d1 100644 --- a/gtk/gtklistitemprivate.h +++ b/gtk/gtklistitemprivate.h @@ -22,9 +22,11 @@ #include "gtklistitem.h" +#include "gtklistitemmanagerprivate.h" + G_BEGIN_DECLS -GtkWidget * gtk_list_item_new (const char *css_name); +GtkListItem * gtk_list_item_new (const char *css_name); void gtk_list_item_set_item (GtkListItem *self, gpointer item); diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index 494a2a591b..36c18d698b 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -1201,6 +1201,34 @@ gtk_list_view_set_property (GObject *object, } } +static void +gtk_list_view_select_item (GtkWidget *widget, + const char *action_name, + GVariant *parameter) +{ + GtkListView *self = GTK_LIST_VIEW (widget); + GtkSelectionModel *selection_model; + guint pos; + gboolean modify, extend; + + selection_model = gtk_list_item_manager_get_model (self->item_manager); + g_variant_get (parameter, "(ubb)", &pos, &modify, &extend); + + /* XXX: handle extend by tracking the item to extend from */ + + if (modify) + { + if (gtk_selection_model_is_selected (selection_model, pos)) + gtk_selection_model_unselect_item (selection_model, pos); + else + gtk_selection_model_select_item (selection_model, pos, FALSE); + } + else + { + gtk_selection_model_select_item (selection_model, pos, TRUE); + } +} + static void gtk_list_view_class_init (GtkListViewClass *klass) { @@ -1245,6 +1273,29 @@ gtk_list_view_class_init (GtkListViewClass *klass) g_object_class_install_properties (gobject_class, N_PROPS, properties); + /** + * GtkListView|list.select-item: + * @position: position of item to select + * @modify: %TRUE to toggle the existing selection, %FALSE to select + * @extend: %TRUE to extend the selection + * + * Changes selection. + * + * If @extend is %TRUE and the model supports selecting ranges, the + * affected items are all items from the last selected item to the item + * in @position. + * If @extend is %FALSE or selecting ranges is not supported, only the + * item in @position is affected. + * + * If @modify is %TRUE, the affected items will be set to the same state. + * If @modify is %FALSE, the affected items will be selected and + * all other items will be deselected. + */ + gtk_widget_class_install_action (widget_class, + "list.select-item", + "(ubb)", + gtk_list_view_select_item); + gtk_widget_class_set_css_name (widget_class, I_("list")); } From 70aaecc9377235ac49efb6f9cdd84cfe96278bc9 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sun, 27 Jan 2019 20:15:23 +0100 Subject: [PATCH 057/170] gtk: Add a GtkGridView skeleton --- docs/reference/gtk/gtk4-docs.xml | 1 + docs/reference/gtk/gtk4-sections.txt | 18 ++ docs/reference/gtk/gtk4.types.in | 1 + gtk/gtk.h | 1 + gtk/gtkgridview.c | 260 +++++++++++++++++++++++++++ gtk/gtkgridview.h | 48 +++++ gtk/meson.build | 2 + 7 files changed, 331 insertions(+) create mode 100644 gtk/gtkgridview.c create mode 100644 gtk/gtkgridview.h diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index b5b87e3902..8c49516cb1 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -113,6 +113,7 @@ + diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 6c7e109204..ac78a750f7 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -447,6 +447,24 @@ GTK_TYPE_LIST_VIEW gtk_list_view_get_type
+
+gtkgridview +GtkGridView +GtkGridView +gtk_grid_view_new +gtk_grid_view_set_model +gtk_grid_view_get_model + +GTK_GRID_VIEW +GTK_GRID_VIEW_CLASS +GTK_GRID_VIEW_GET_CLASS +GTK_IS_GRID_VIEW +GTK_IS_GRID_VIEW_CLASS +GTK_TYPE_GRID_VIEW + +gtk_grid_view_get_type +
+
gtkbuildable GtkBuildable diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in index 9ff1c8c204..4054e2912a 100644 --- a/docs/reference/gtk/gtk4.types.in +++ b/docs/reference/gtk/gtk4.types.in @@ -107,6 +107,7 @@ gtk_gl_area_get_type gtk_grid_get_type gtk_grid_layout_child_get_type gtk_grid_layout_get_type +gtk_grid_view_get_type gtk_header_bar_get_type gtk_icon_theme_get_type gtk_icon_view_get_type diff --git a/gtk/gtk.h b/gtk/gtk.h index e06f384ad6..759ea0eac9 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -139,6 +139,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkgridview.c b/gtk/gtkgridview.c new file mode 100644 index 0000000000..9c37c6eefb --- /dev/null +++ b/gtk/gtkgridview.c @@ -0,0 +1,260 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gtkgridview.h" + +#include "gtkintl.h" +#include "gtkprivate.h" + +/** + * SECTION:gtkgridview + * @title: GtkGridView + * @short_description: A widget for displaying lists in a grid + * @see_also: #GListModel + * + * GtkGridView is a widget to present a view into a large dynamic list of items. + */ + +struct _GtkGridView +{ + GtkWidget parent_instance; + + GListModel *model; +}; + +enum +{ + PROP_0, + PROP_MODEL, + + N_PROPS +}; + +G_DEFINE_TYPE (GtkGridView, gtk_grid_view, GTK_TYPE_WIDGET) + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +static gboolean +gtk_grid_view_is_empty (GtkGridView *self) +{ + return self->model == NULL; +} + +static void +gtk_grid_view_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + GtkGridView *self = GTK_GRID_VIEW (widget); + + if (gtk_grid_view_is_empty (self)) + { + *minimum = 0; + *natural = 0; + return; + } + + *minimum = 0; + *natural = 0; + return; +} + +static void +gtk_grid_view_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + //GtkGridView *self = GTK_GRID_VIEW (widget); +} + +static void +gtk_grid_view_model_items_changed_cb (GListModel *model, + guint position, + guint removed, + guint added, + GtkGridView *self) +{ +} + +static void +gtk_grid_view_clear_model (GtkGridView *self) +{ + if (self->model == NULL) + return; + + g_signal_handlers_disconnect_by_func (self->model, gtk_grid_view_model_items_changed_cb, self); + g_clear_object (&self->model); +} + +static void +gtk_grid_view_dispose (GObject *object) +{ + GtkGridView *self = GTK_GRID_VIEW (object); + + gtk_grid_view_clear_model (self); + + G_OBJECT_CLASS (gtk_grid_view_parent_class)->dispose (object); +} + +static void +gtk_grid_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GtkGridView *self = GTK_GRID_VIEW (object); + + switch (property_id) + { + case PROP_MODEL: + g_value_set_object (value, self->model); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gtk_grid_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkGridView *self = GTK_GRID_VIEW (object); + + switch (property_id) + { + case PROP_MODEL: + gtk_grid_view_set_model (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gtk_grid_view_class_init (GtkGridViewClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + widget_class->measure = gtk_grid_view_measure; + widget_class->size_allocate = gtk_grid_view_size_allocate; + + gobject_class->dispose = gtk_grid_view_dispose; + gobject_class->get_property = gtk_grid_view_get_property; + gobject_class->set_property = gtk_grid_view_set_property; + + /** + * GtkGridView:model: + * + * Model for the items displayed + */ + properties[PROP_MODEL] = + g_param_spec_object ("model", + P_("Model"), + P_("Model for the items displayed"), + G_TYPE_LIST_MODEL, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPS, properties); + + gtk_widget_class_set_css_name (widget_class, I_("list")); +} + +static void +gtk_grid_view_init (GtkGridView *self) +{ +} + +/** + * gtk_grid_view_new: + * + * Creates a new empty #GtkGridView. + * + * You most likely want to call gtk_grid_view_set_model() to set + * a model and then set up a way to map its items to widgets next. + * + * Returns: a new #GtkGridView + **/ +GtkWidget * +gtk_grid_view_new (void) +{ + return g_object_new (GTK_TYPE_GRID_VIEW, NULL); +} + +/** + * gtk_grid_view_get_model: + * @self: a #GtkGridView + * + * Gets the model that's currently used to read the items displayed. + * + * Returns: (nullable) (transfer none): The model in use + **/ +GListModel * +gtk_grid_view_get_model (GtkGridView *self) +{ + g_return_val_if_fail (GTK_IS_GRID_VIEW (self), NULL); + + return self->model; +} + +/** + * gtk_grid_view_set_model: + * @self: a #GtkGridView + * @model: (allow-none) (transfer none): the model to use or %NULL for none + * + * Sets the #GListModel to use for + **/ +void +gtk_grid_view_set_model (GtkGridView *self, + GListModel *model) +{ + g_return_if_fail (GTK_IS_GRID_VIEW (self)); + g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model)); + + if (self->model == model) + return; + + gtk_grid_view_clear_model (self); + + if (model) + { + self->model = g_object_ref (model); + + g_signal_connect (model, + "items-changed", + G_CALLBACK (gtk_grid_view_model_items_changed_cb), + self); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]); +} + diff --git a/gtk/gtkgridview.h b/gtk/gtkgridview.h new file mode 100644 index 0000000000..0462c960ce --- /dev/null +++ b/gtk/gtkgridview.h @@ -0,0 +1,48 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __GTK_GRID_VIEW_H__ +#define __GTK_GRID_VIEW_H__ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_GRID_VIEW (gtk_grid_view_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkGridView, gtk_grid_view, GTK, GRID_VIEW, GtkWidget) + +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_grid_view_new (void); + +GDK_AVAILABLE_IN_ALL +GListModel * gtk_grid_view_get_model (GtkGridView *self); +GDK_AVAILABLE_IN_ALL +void gtk_grid_view_set_model (GtkGridView *self, + GListModel *model); + + +G_END_DECLS + +#endif /* __GTK_GRID_VIEW_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index 6c516bc1c2..f45cca1cdd 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -255,6 +255,7 @@ gtk_public_sources = files([ 'gtkglarea.c', 'gtkgrid.c', 'gtkgridlayout.c', + 'gtkgridview.c', 'gtkheaderbar.c', 'gtkicontheme.c', 'gtkiconview.c', @@ -523,6 +524,7 @@ gtk_public_headers = files([ 'gtkglarea.h', 'gtkgrid.h', 'gtkgridlayout.h', + 'gtkgridview.h', 'gtkheaderbar.h', 'gtkicontheme.h', 'gtkiconview.h', From 86a75abe511dfa536c0b31d40d492bce5407cf77 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 28 Jan 2019 02:24:34 +0100 Subject: [PATCH 058/170] gridview: Add API for setting number of columns The API isn't used yet. --- docs/reference/gtk/gtk4-sections.txt | 4 + gtk/gtkgridview.c | 137 +++++++++++++++++++++++++++ gtk/gtkgridview.h | 10 ++ 3 files changed, 151 insertions(+) diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index ac78a750f7..f97a7f1ba9 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -454,6 +454,10 @@ GtkGridView gtk_grid_view_new gtk_grid_view_set_model gtk_grid_view_get_model +gtk_grid_view_set_max_columns +gtk_grid_view_get_max_columns +gtk_grid_view_set_min_columns +gtk_grid_view_get_min_columns GTK_GRID_VIEW GTK_GRID_VIEW_CLASS diff --git a/gtk/gtkgridview.c b/gtk/gtkgridview.c index 9c37c6eefb..7d17a9fcb3 100644 --- a/gtk/gtkgridview.c +++ b/gtk/gtkgridview.c @@ -24,6 +24,8 @@ #include "gtkintl.h" #include "gtkprivate.h" +#define DEFAULT_MAX_COLUMNS (7) + /** * SECTION:gtkgridview * @title: GtkGridView @@ -38,11 +40,15 @@ struct _GtkGridView GtkWidget parent_instance; GListModel *model; + guint min_columns; + guint max_columns; }; enum { PROP_0, + PROP_MAX_COLUMNS, + PROP_MIN_COLUMNS, PROP_MODEL, N_PROPS @@ -129,6 +135,14 @@ gtk_grid_view_get_property (GObject *object, switch (property_id) { + case PROP_MAX_COLUMNS: + g_value_set_uint (value, self->max_columns); + break; + + case PROP_MIN_COLUMNS: + g_value_set_uint (value, self->min_columns); + break; + case PROP_MODEL: g_value_set_object (value, self->model); break; @@ -149,6 +163,14 @@ gtk_grid_view_set_property (GObject *object, switch (property_id) { + case PROP_MAX_COLUMNS: + gtk_grid_view_set_max_columns (self, g_value_get_uint (value)); + break; + + case PROP_MIN_COLUMNS: + gtk_grid_view_set_min_columns (self, g_value_get_uint (value)); + break; + case PROP_MODEL: gtk_grid_view_set_model (self, g_value_get_object (value)); break; @@ -172,6 +194,33 @@ gtk_grid_view_class_init (GtkGridViewClass *klass) gobject_class->get_property = gtk_grid_view_get_property; gobject_class->set_property = gtk_grid_view_set_property; + /** + * GtkGridView:max-columns: + * + * Maximum number of columns per row + * + * If this number is smaller than GtkGridView:min-columns, that value + * is used instead. + */ + properties[PROP_MAX_COLUMNS] = + g_param_spec_uint ("max-columns", + P_("Max columns"), + P_("Maximum number of columns per row"), + 1, G_MAXUINT, DEFAULT_MAX_COLUMNS, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * GtkGridView:min-columns: + * + * Minimum number of columns per row + */ + properties[PROP_MIN_COLUMNS] = + g_param_spec_uint ("min-columns", + P_("Min columns"), + P_("Minimum number of columns per row"), + 1, G_MAXUINT, 1, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** * GtkGridView:model: * @@ -192,6 +241,8 @@ gtk_grid_view_class_init (GtkGridViewClass *klass) static void gtk_grid_view_init (GtkGridView *self) { + self->min_columns = 1; + self->max_columns = DEFAULT_MAX_COLUMNS; } /** @@ -258,3 +309,89 @@ gtk_grid_view_set_model (GtkGridView *self, g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]); } +/** + * gtk_grid_view_get_max_columns: + * @self: a #GtkGridView + * + * Gets the maximum number of columns that the grid will use. + * + * Returns: The maximum number of columns + **/ +guint +gtk_grid_view_get_max_columns (GtkGridView *self) +{ + g_return_val_if_fail (GTK_IS_GRID_VIEW (self), DEFAULT_MAX_COLUMNS); + + return self->max_columns; +} + +/** + * gtk_grid_view_set_max_columns: + * @self: a #GtkGridView + * @max_columns: The maximum number of columns + * + * Sets the maximum number of columns to use. This number must be at least 1. + * + * If @max_columns is smaller than the minimum set via + * gtk_grid_view_set_min_columns(), that value is used instead. + **/ +void +gtk_grid_view_set_max_columns (GtkGridView *self, + guint max_columns) +{ + g_return_if_fail (GTK_IS_GRID_VIEW (self)); + g_return_if_fail (max_columns > 0); + + if (self->max_columns == max_columns) + return; + + self->max_columns = max_columns; + + gtk_widget_queue_resize (GTK_WIDGET (self)); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MAX_COLUMNS]); +} + +/** + * gtk_grid_view_get_min_columns: + * @self: a #GtkGridView + * + * Gets the minimum number of columns that the grid will use. + * + * Returns: The minimum number of columns + **/ +guint +gtk_grid_view_get_min_columns (GtkGridView *self) +{ + g_return_val_if_fail (GTK_IS_GRID_VIEW (self), 1); + + return self->min_columns; +} + +/** + * gtk_grid_view_set_min_columns: + * @self: a #GtkGridView + * @min_columns: The minimum number of columns + * + * Sets the minimum number of columns to use. This number must be at least 1. + * + * If @min_columns is smaller than the minimum set via + * gtk_grid_view_set_max_columns(), that value is ignored. + **/ +void +gtk_grid_view_set_min_columns (GtkGridView *self, + guint min_columns) +{ + g_return_if_fail (GTK_IS_GRID_VIEW (self)); + g_return_if_fail (min_columns > 0); + + if (self->min_columns == min_columns) + return; + + self->min_columns = min_columns; + + gtk_widget_queue_resize (GTK_WIDGET (self)); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MIN_COLUMNS]); +} + diff --git a/gtk/gtkgridview.h b/gtk/gtkgridview.h index 0462c960ce..3bdd6505ba 100644 --- a/gtk/gtkgridview.h +++ b/gtk/gtkgridview.h @@ -41,6 +41,16 @@ GListModel * gtk_grid_view_get_model (GtkGridView GDK_AVAILABLE_IN_ALL void gtk_grid_view_set_model (GtkGridView *self, GListModel *model); +GDK_AVAILABLE_IN_ALL +guint gtk_grid_view_get_min_columns (GtkGridView *self); +GDK_AVAILABLE_IN_ALL +void gtk_grid_view_set_min_columns (GtkGridView *self, + guint min_columns); +GDK_AVAILABLE_IN_ALL +guint gtk_grid_view_get_max_columns (GtkGridView *self); +GDK_AVAILABLE_IN_ALL +void gtk_grid_view_set_max_columns (GtkGridView *self, + guint max_columns); G_END_DECLS From dc91782165df63f5c3eed463e3e74c87e466ae2a Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 6 Feb 2019 20:48:08 +0100 Subject: [PATCH 059/170] listitemmanager: Move list of listitems here All the listview infrastructure moved with it, so the next step is moving that back... --- gtk/gtklistitemmanager.c | 678 +++++++++++++++++++++++++++++++- gtk/gtklistitemmanagerprivate.h | 45 ++- gtk/gtklistview.c | 658 +++---------------------------- 3 files changed, 749 insertions(+), 632 deletions(-) diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index 00bce1a10b..6cadec777b 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -24,6 +24,8 @@ #include "gtklistitemprivate.h" #include "gtkwidgetprivate.h" +#define GTK_LIST_VIEW_MAX_LIST_ITEMS 200 + struct _GtkListItemManager { GObject parent_instance; @@ -31,6 +33,14 @@ struct _GtkListItemManager GtkWidget *widget; GtkSelectionModel *model; GtkListItemFactory *factory; + + GtkRbTree *items; + + /* managing the visible region */ + GtkWidget *anchor; /* may be NULL if list is empty */ + int anchor_align; /* what to align the anchor to */ + guint anchor_start; /* start of region we allocate row widgets for */ + guint anchor_end; /* end of same region - first position to not have a widget */ }; struct _GtkListItemManagerClass @@ -45,14 +55,632 @@ struct _GtkListItemManagerChange G_DEFINE_TYPE (GtkListItemManager, gtk_list_item_manager, G_TYPE_OBJECT) +void +gtk_list_item_manager_augment_node (GtkRbTree *tree, + gpointer node_augment, + gpointer node, + gpointer left, + gpointer right) +{ + GtkListItemManagerItem *item = node; + GtkListItemManagerItemAugment *aug = node_augment; + + aug->n_items = item->n_items; + + if (left) + { + GtkListItemManagerItemAugment *left_aug = gtk_rb_tree_get_augment (tree, left); + + aug->n_items += left_aug->n_items; + } + + if (right) + { + GtkListItemManagerItemAugment *right_aug = gtk_rb_tree_get_augment (tree, right); + + aug->n_items += right_aug->n_items; + } +} + +static void +gtk_list_item_manager_clear_node (gpointer _item) +{ + GtkListItemManagerItem *item = _item; + + g_assert (item->widget == NULL); +} + +GtkListItemManager * +gtk_list_item_manager_new_for_size (GtkWidget *widget, + gsize element_size, + gsize augment_size, + GtkRbTreeAugmentFunc augment_func) +{ + GtkListItemManager *self; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); + g_return_val_if_fail (element_size >= sizeof (GtkListItemManagerItem), NULL); + g_return_val_if_fail (augment_size >= sizeof (GtkListItemManagerItemAugment), NULL); + + self = g_object_new (GTK_TYPE_LIST_ITEM_MANAGER, NULL); + + /* not taking a ref because the widget refs us */ + self->widget = widget; + + self->items = gtk_rb_tree_new_for_size (element_size, + augment_size, + augment_func, + gtk_list_item_manager_clear_node, + NULL); + + return self; +} + +gpointer +gtk_list_item_manager_get_first (GtkListItemManager *self) +{ + return gtk_rb_tree_get_first (self->items); +} + +gpointer +gtk_list_item_manager_get_root (GtkListItemManager *self) +{ + return gtk_rb_tree_get_root (self->items); +} + +/* + * gtk_list_item_manager_get_nth: + * @self: a #GtkListItemManager + * @position: position of the item + * @offset: (out): offset into the returned item + * + * Looks up the GtkListItemManagerItem that represents @position. + * + * If a the returned item represents multiple rows, the @offset into + * the returned item for @position will be set. If the returned item + * represents a row with an existing widget, @offset will always be 0. + * + * Returns: (type GtkListItemManagerItem): the item for @position or + * %NULL if position is out of range + **/ +gpointer +gtk_list_item_manager_get_nth (GtkListItemManager *self, + guint position, + guint *offset) +{ + GtkListItemManagerItem *item, *tmp; + + item = gtk_rb_tree_get_root (self->items); + + while (item) + { + tmp = gtk_rb_tree_node_get_left (item); + if (tmp) + { + GtkListItemManagerItemAugment *aug = gtk_rb_tree_get_augment (self->items, tmp); + if (position < aug->n_items) + { + item = tmp; + continue; + } + position -= aug->n_items; + } + + if (position < item->n_items) + break; + position -= item->n_items; + + item = gtk_rb_tree_node_get_right (item); + } + + if (offset) + *offset = item ? position : 0; + + return item; +} + +guint +gtk_list_item_manager_get_item_position (GtkListItemManager *self, + gpointer item) +{ + GtkListItemManagerItem *parent, *left; + int pos; + + left = gtk_rb_tree_node_get_left (item); + if (left) + { + GtkListItemManagerItemAugment *aug = gtk_rb_tree_get_augment (self->items, left); + pos = aug->n_items; + } + else + { + pos = 0; + } + + for (parent = gtk_rb_tree_node_get_parent (item); + parent != NULL; + parent = gtk_rb_tree_node_get_parent (item)) + { + left = gtk_rb_tree_node_get_left (parent); + + if (left != item) + { + if (left) + { + GtkListItemManagerItemAugment *aug = gtk_rb_tree_get_augment (self->items, left); + pos += aug->n_items; + } + pos += parent->n_items; + } + + item = parent; + } + + return pos; +} + +gpointer +gtk_list_item_manager_get_item_augment (GtkListItemManager *self, + gpointer item) +{ + return gtk_rb_tree_get_augment (self->items, item); +} + +static void +gtk_list_item_manager_remove_items (GtkListItemManager *self, + GtkListItemManagerChange *change, + guint position, + guint n_items) +{ + GtkListItemManagerItem *item; + + if (n_items == 0) + return; + + item = gtk_list_item_manager_get_nth (self, position, NULL); + + while (n_items > 0) + { + if (item->n_items > n_items) + { + item->n_items -= n_items; + gtk_rb_tree_node_mark_dirty (item); + n_items = 0; + } + else + { + GtkListItemManagerItem *next = gtk_rb_tree_node_get_next (item); + if (item->widget) + gtk_list_item_manager_release_list_item (self, change, item->widget); + item->widget = NULL; + n_items -= item->n_items; + gtk_rb_tree_remove (self->items, item); + item = next; + } + } + + gtk_widget_queue_resize (GTK_WIDGET (self->widget)); +} + +static void +gtk_list_item_manager_add_items (GtkListItemManager *self, + guint position, + guint n_items) +{ + GtkListItemManagerItem *item; + guint offset; + + if (n_items == 0) + return; + + item = gtk_list_item_manager_get_nth (self, position, &offset); + + if (item == NULL || item->widget) + item = gtk_rb_tree_insert_before (self->items, item); + item->n_items += n_items; + gtk_rb_tree_node_mark_dirty (item); + + gtk_widget_queue_resize (GTK_WIDGET (self->widget)); +} + +static void +gtk_list_item_manager_unset_anchor (GtkListItemManager *self) +{ + self->anchor = NULL; + self->anchor_align = 0; + self->anchor_start = 0; + self->anchor_end = 0; +} + +static gboolean +gtk_list_item_manager_merge_list_items (GtkListItemManager *self, + GtkListItemManagerItem *first, + GtkListItemManagerItem *second) +{ + if (first->widget || second->widget) + return FALSE; + + first->n_items += second->n_items; + gtk_rb_tree_node_mark_dirty (first); + gtk_rb_tree_remove (self->items, second); + + return TRUE; +} + +static void +gtk_list_item_manager_release_items (GtkListItemManager *self, + GQueue *released) +{ + GtkListItemManagerItem *item, *prev, *next; + guint i; + + item = gtk_rb_tree_get_first (self->items); + i = 0; + while (i < self->anchor_start) + { + if (item->widget) + { + g_queue_push_tail (released, item->widget); + item->widget = NULL; + i++; + prev = gtk_rb_tree_node_get_previous (item); + if (prev && gtk_list_item_manager_merge_list_items (self, prev, item)) + item = prev; + next = gtk_rb_tree_node_get_next (item); + if (next && next->widget == NULL) + { + i += next->n_items; + if (!gtk_list_item_manager_merge_list_items (self, next, item)) + g_assert_not_reached (); + item = gtk_rb_tree_node_get_next (next); + } + else + { + item = next; + } + } + else + { + i += item->n_items; + item = gtk_rb_tree_node_get_next (item); + } + } + + item = gtk_list_item_manager_get_nth (self, self->anchor_end, NULL); + if (item == NULL) + return; + + if (item->widget) + { + g_queue_push_tail (released, item->widget); + item->widget = NULL; + prev = gtk_rb_tree_node_get_previous (item); + if (prev && gtk_list_item_manager_merge_list_items (self, prev, item)) + item = prev; + } + + while ((next = gtk_rb_tree_node_get_next (item))) + { + if (next->widget) + { + g_queue_push_tail (released, next->widget); + next->widget = NULL; + } + gtk_list_item_manager_merge_list_items (self, item, next); + } +} + +static void +gtk_list_item_manager_ensure_items (GtkListItemManager *self, + GtkListItemManagerChange *change, + guint update_start) +{ + GtkListItemManagerItem *item, *new_item; + guint i, offset; + GtkWidget *widget, *insert_after; + GQueue released = G_QUEUE_INIT; + + gtk_list_item_manager_release_items (self, &released); + + item = gtk_list_item_manager_get_nth (self, self->anchor_start, &offset); + if (offset > 0) + { + new_item = gtk_rb_tree_insert_before (self->items, item); + new_item->n_items = offset; + item->n_items -= offset; + gtk_rb_tree_node_mark_dirty (item); + } + + insert_after = NULL; + + for (i = self->anchor_start; i < self->anchor_end; i++) + { + if (item->n_items > 1) + { + new_item = gtk_rb_tree_insert_before (self->items, item); + new_item->n_items = 1; + item->n_items--; + gtk_rb_tree_node_mark_dirty (item); + } + else + { + new_item = item; + item = gtk_rb_tree_node_get_next (item); + } + if (new_item->widget == NULL) + { + if (change) + { + new_item->widget = gtk_list_item_manager_try_reacquire_list_item (self, + change, + i, + insert_after); + } + if (new_item->widget == NULL) + { + new_item->widget = g_queue_pop_head (&released); + if (new_item->widget) + { + gtk_list_item_manager_move_list_item (self, + new_item->widget, + i, + insert_after); + } + else + { + new_item->widget = gtk_list_item_manager_acquire_list_item (self, + i, + insert_after); + } + } + } + else + { + if (update_start <= i) + gtk_list_item_manager_update_list_item (self, new_item->widget, i); + } + insert_after = new_item->widget; + } + + while ((widget = g_queue_pop_head (&released))) + gtk_list_item_manager_release_list_item (self, NULL, widget); +} + +void +gtk_list_item_manager_set_anchor (GtkListItemManager *self, + guint position, + double align, + GtkListItemManagerChange *change, + guint update_start) +{ + GtkListItemManagerItem *item; + guint items_before, items_after, total_items, n_items; + + g_assert (align >= 0.0 && align <= 1.0); + + if (self->model) + n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model)); + else + n_items = 0; + if (n_items == 0) + { + gtk_list_item_manager_unset_anchor (self); + return; + } + total_items = MIN (GTK_LIST_VIEW_MAX_LIST_ITEMS, n_items); + if (align < 0.5) + items_before = ceil (total_items * align); + else + items_before = floor (total_items * align); + items_after = total_items - items_before; + self->anchor_start = CLAMP (position, items_before, n_items - items_after) - items_before; + self->anchor_end = self->anchor_start + total_items; + g_assert (self->anchor_end <= n_items); + + gtk_list_item_manager_ensure_items (self, change, update_start); + + item = gtk_list_item_manager_get_nth (self, position, NULL); + self->anchor = item->widget; + g_assert (self->anchor); + self->anchor_align = align; + + gtk_widget_queue_allocate (GTK_WIDGET (self->widget)); +} + +static void +gtk_list_item_manager_model_items_changed_cb (GListModel *model, + guint position, + guint removed, + guint added, + GtkListItemManager *self) +{ + GtkListItemManagerChange *change; + + change = gtk_list_item_manager_begin_change (self); + + gtk_list_item_manager_remove_items (self, change, position, removed); + gtk_list_item_manager_add_items (self, position, added); + + /* The anchor was removed, but it may just have moved to a different position */ + if (self->anchor && gtk_list_item_manager_change_contains (change, self->anchor)) + { + /* The anchor was removed, do a more expensive rebuild trying to find if + * the anchor maybe got readded somewhere else */ + GtkListItemManagerItem *item, *new_item; + GtkWidget *insert_after; + guint i, offset, anchor_pos; + + item = gtk_list_item_manager_get_nth (self, position, &offset); + for (new_item = item ? gtk_rb_tree_node_get_previous (item) : gtk_rb_tree_get_last (self->items); + new_item && new_item->widget == NULL; + new_item = gtk_rb_tree_node_get_previous (new_item)) + { } + if (new_item) + insert_after = new_item->widget; + else + insert_after = NULL; /* we're at the start */ + + for (i = 0; i < added; i++) + { + GtkWidget *widget; + + widget = gtk_list_item_manager_try_reacquire_list_item (self, + change, + position + i, + insert_after); + if (widget == NULL) + { + offset++; + continue; + } + + if (offset > 0) + { + new_item = gtk_rb_tree_insert_before (self->items, item); + new_item->n_items = offset; + item->n_items -= offset; + offset = 0; + gtk_rb_tree_node_mark_dirty (item); + } + + if (item->n_items == 1) + { + new_item = item; + item = gtk_rb_tree_node_get_next (item); + } + else + { + new_item = gtk_rb_tree_insert_before (self->items, item); + new_item->n_items = 1; + item->n_items--; + gtk_rb_tree_node_mark_dirty (item); + } + + new_item->widget = widget; + insert_after = widget; + + if (widget == self->anchor) + { + anchor_pos = position + i; + break; + } + } + + if (i == added) + { + /* The anchor wasn't readded. Guess a good anchor position */ + anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); + + anchor_pos = position + (anchor_pos - position) * added / removed; + if (anchor_pos >= g_list_model_get_n_items (G_LIST_MODEL (self->model)) && + anchor_pos > 0) + anchor_pos--; + } + gtk_list_item_manager_set_anchor (self, anchor_pos, self->anchor_align, change, position); + } + else + { + /* The anchor is still where it was. + * We just may need to update its position and check that its surrounding widgets + * exist (they might be new ones). */ + guint anchor_pos; + + if (self->anchor) + { + anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); + + if (anchor_pos >= position) + anchor_pos += added - removed; + } + else + { + anchor_pos = 0; + } + + gtk_list_item_manager_set_anchor (self, anchor_pos, self->anchor_align, change, position); + } + + gtk_list_item_manager_end_change (self, change); +} + +static void +gtk_list_item_manager_model_selection_changed_cb (GListModel *model, + guint position, + guint n_items, + GtkListItemManager *self) +{ + GtkListItemManagerItem *item; + guint offset; + + item = gtk_list_item_manager_get_nth (self, position, &offset); + + if (offset) + { + position += item->n_items - offset; + if (item->n_items - offset > n_items) + n_items = 0; + else + n_items -= item->n_items - offset; + item = gtk_rb_tree_node_get_next (item); + } + + while (n_items > 0) + { + if (item->widget) + gtk_list_item_manager_update_list_item (self, item->widget, position); + position += item->n_items; + n_items -= MIN (n_items, item->n_items); + item = gtk_rb_tree_node_get_next (item); + } +} + +guint +gtk_list_item_manager_get_anchor (GtkListItemManager *self, + double *align) +{ + guint anchor_pos; + + if (self->anchor) + anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); + else + anchor_pos = 0; + + if (align) + *align = self->anchor_align; + + return anchor_pos; +} + +static void +gtk_list_item_manager_clear_model (GtkListItemManager *self) +{ + if (self->model == NULL) + return; + + gtk_list_item_manager_remove_items (self, NULL, 0, g_list_model_get_n_items (G_LIST_MODEL (self->model))); + + g_signal_handlers_disconnect_by_func (self->model, + gtk_list_item_manager_model_selection_changed_cb, + self); + g_signal_handlers_disconnect_by_func (self->model, + gtk_list_item_manager_model_items_changed_cb, + self); + g_clear_object (&self->model); + + gtk_list_item_manager_unset_anchor (self); +} + static void gtk_list_item_manager_dispose (GObject *object) { GtkListItemManager *self = GTK_LIST_ITEM_MANAGER (object); - g_clear_object (&self->model); + gtk_list_item_manager_clear_model (self); + g_clear_object (&self->factory); + g_clear_pointer (&self->items, gtk_rb_tree_unref); + G_OBJECT_CLASS (gtk_list_item_manager_parent_class)->dispose (object); } @@ -69,33 +697,27 @@ gtk_list_item_manager_init (GtkListItemManager *self) { } -GtkListItemManager * -gtk_list_item_manager_new (GtkWidget *widget) -{ - GtkListItemManager *self; - - g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); - - self = g_object_new (GTK_TYPE_LIST_ITEM_MANAGER, NULL); - - self->widget = widget; - - return self; -} - void gtk_list_item_manager_set_factory (GtkListItemManager *self, GtkListItemFactory *factory) { + guint n_items, anchor; + double anchor_align; + g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (factory)); if (self->factory == factory) return; - g_clear_object (&self->factory); + n_items = self->model ? g_list_model_get_n_items (G_LIST_MODEL (self->model)) : 0; + anchor = gtk_list_item_manager_get_anchor (self, &anchor_align); + gtk_list_item_manager_remove_items (self, NULL, 0, n_items); - self->factory = g_object_ref (factory); + g_set_object (&self->factory, factory); + + gtk_list_item_manager_add_items (self, 0, n_items); + gtk_list_item_manager_set_anchor (self, anchor, anchor_align, NULL, (guint) -1); } GtkListItemFactory * @@ -111,15 +733,30 @@ gtk_list_item_manager_set_model (GtkListItemManager *self, GtkSelectionModel *model) { g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); - g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model)); + g_return_if_fail (model == NULL || GTK_IS_SELECTION_MODEL (model)); if (self->model == model) return; - g_clear_object (&self->model); + gtk_list_item_manager_clear_model (self); if (model) - self->model = g_object_ref (model); + { + self->model = g_object_ref (model); + + g_signal_connect (model, + "items-changed", + G_CALLBACK (gtk_list_item_manager_model_items_changed_cb), + self); + g_signal_connect (model, + "selection-changed", + G_CALLBACK (gtk_list_item_manager_model_selection_changed_cb), + self); + + gtk_list_item_manager_add_items (self, 0, g_list_model_get_n_items (G_LIST_MODEL (model))); + + gtk_list_item_manager_set_anchor (self, 0, 0, NULL, (guint) -1); + } } GtkSelectionModel * @@ -400,3 +1037,4 @@ gtk_list_item_manager_release_list_item (GtkListItemManager *self, gtk_list_item_factory_unbind (self->factory, GTK_LIST_ITEM (item)); gtk_widget_unparent (item); } + diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h index 47a4434d7d..9e1ebab073 100644 --- a/gtk/gtklistitemmanagerprivate.h +++ b/gtk/gtklistitemmanagerprivate.h @@ -24,6 +24,7 @@ #include "gtk/gtktypes.h" #include "gtk/gtklistitemfactoryprivate.h" +#include "gtk/gtkrbtreeprivate.h" #include "gtk/gtkselectionmodel.h" G_BEGIN_DECLS @@ -38,10 +39,44 @@ G_BEGIN_DECLS typedef struct _GtkListItemManager GtkListItemManager; typedef struct _GtkListItemManagerClass GtkListItemManagerClass; typedef struct _GtkListItemManagerChange GtkListItemManagerChange; +typedef struct _GtkListItemManagerItem GtkListItemManagerItem; /* sorry */ +typedef struct _GtkListItemManagerItemAugment GtkListItemManagerItemAugment; + +struct _GtkListItemManagerItem +{ + GtkWidget *widget; + guint n_items; +}; + +struct _GtkListItemManagerItemAugment +{ + guint n_items; +}; + GType gtk_list_item_manager_get_type (void) G_GNUC_CONST; -GtkListItemManager * gtk_list_item_manager_new (GtkWidget *widget); +GtkListItemManager * gtk_list_item_manager_new_for_size (GtkWidget *widget, + gsize element_size, + gsize augment_size, + GtkRbTreeAugmentFunc augment_func); +#define gtk_list_item_manager_new(widget, type, augment_type, augment_func) \ + gtk_list_item_manager_new_for_size (widget, sizeof (type), sizeof (augment_type), (augment_func)) + +void gtk_list_item_manager_augment_node (GtkRbTree *tree, + gpointer node_augment, + gpointer node, + gpointer left, + gpointer right); +gpointer gtk_list_item_manager_get_root (GtkListItemManager *self); +gpointer gtk_list_item_manager_get_first (GtkListItemManager *self); +gpointer gtk_list_item_manager_get_nth (GtkListItemManager *self, + guint position, + guint *offset); +guint gtk_list_item_manager_get_item_position (GtkListItemManager *self, + gpointer item); +gpointer gtk_list_item_manager_get_item_augment (GtkListItemManager *self, + gpointer item); void gtk_list_item_manager_set_factory (GtkListItemManager *self, GtkListItemFactory *factory); @@ -52,6 +87,14 @@ GtkSelectionModel * gtk_list_item_manager_get_model (GtkListItemMana guint gtk_list_item_manager_get_size (GtkListItemManager *self); +void gtk_list_item_manager_set_anchor (GtkListItemManager *self, + guint position, + double align, + GtkListItemManagerChange *change, + guint update_start); +guint gtk_list_item_manager_get_anchor (GtkListItemManager *self, + double *align); + GtkListItemManagerChange * gtk_list_item_manager_begin_change (GtkListItemManager *self); void gtk_list_item_manager_end_change (GtkListItemManager *self, diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index 36c18d698b..e91b1b7b62 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -58,26 +58,18 @@ struct _GtkListView GtkAdjustment *adjustment[2]; GtkScrollablePolicy scroll_policy[2]; - GtkRbTree *rows; int list_width; - - /* managing the visible region */ - GtkWidget *anchor; /* may be NULL if list is empty */ - int anchor_align; /* what to align the anchor to */ - guint anchor_start; /* start of region we allocate row widgets for */ - guint anchor_end; /* end of same region - first position to not have a widget */ }; struct _ListRow { - guint n_rows; + GtkListItemManagerItem parent; guint height; /* per row */ - GtkWidget *widget; }; struct _ListRowAugment { - guint n_rows; + GtkListItemManagerItemAugment parent; guint height; /* total */ }; @@ -106,15 +98,15 @@ dump (GtkListView *self) n_widgets = 0; n_list_rows = 0; - g_print ("ANCHOR: %u - %u\n", self->anchor_start, self->anchor_end); - for (row = gtk_rb_tree_get_first (self->rows); + //g_print ("ANCHOR: %u - %u\n", self->anchor_start, self->anchor_end); + for (row = gtk_list_item_manager_get_first (self->item_manager); row; row = gtk_rb_tree_node_get_next (row)) { - if (row->widget) + if (row->parent.widget) n_widgets++; n_list_rows++; - g_print (" %4u%s (%upx)\n", row->n_rows, row->widget ? " (widget)" : "", row->height); + g_print (" %4u%s (%upx)\n", row->parent.n_items, row->parent.widget ? " (widget)" : "", row->height); } g_print (" => %u widgets in %u list rows\n", n_widgets, n_list_rows); @@ -130,15 +122,15 @@ list_row_augment (GtkRbTree *tree, ListRow *row = node; ListRowAugment *aug = node_augment; - aug->height = row->height * row->n_rows; - aug->n_rows = row->n_rows; + gtk_list_item_manager_augment_node (tree, node_augment, node, left, right); + + aug->height = row->height * row->parent.n_items; if (left) { ListRowAugment *left_aug = gtk_rb_tree_get_augment (tree, left); aug->height += left_aug->height; - aug->n_rows += left_aug->n_rows; } if (right) @@ -146,94 +138,9 @@ list_row_augment (GtkRbTree *tree, ListRowAugment *right_aug = gtk_rb_tree_get_augment (tree, right); aug->height += right_aug->height; - aug->n_rows += right_aug->n_rows; } } -static void -list_row_clear (gpointer _row) -{ - ListRow *row = _row; - - g_assert (row->widget == NULL); -} - -static ListRow * -gtk_list_view_get_row (GtkListView *self, - guint position, - guint *offset) -{ - ListRow *row, *tmp; - - row = gtk_rb_tree_get_root (self->rows); - - while (row) - { - tmp = gtk_rb_tree_node_get_left (row); - if (tmp) - { - ListRowAugment *aug = gtk_rb_tree_get_augment (self->rows, tmp); - if (position < aug->n_rows) - { - row = tmp; - continue; - } - position -= aug->n_rows; - } - - if (position < row->n_rows) - break; - position -= row->n_rows; - - row = gtk_rb_tree_node_get_right (row); - } - - if (offset) - *offset = row ? position : 0; - - return row; -} - -static guint -list_row_get_position (GtkListView *self, - ListRow *row) -{ - ListRow *parent, *left; - int pos; - - left = gtk_rb_tree_node_get_left (row); - if (left) - { - ListRowAugment *aug = gtk_rb_tree_get_augment (self->rows, left); - pos = aug->n_rows; - } - else - { - pos = 0; - } - - for (parent = gtk_rb_tree_node_get_parent (row); - parent != NULL; - parent = gtk_rb_tree_node_get_parent (row)) - { - left = gtk_rb_tree_node_get_left (parent); - - if (left != row) - { - if (left) - { - ListRowAugment *aug = gtk_rb_tree_get_augment (self->rows, left); - pos += aug->n_rows; - } - pos += parent->n_rows; - } - - row = parent; - } - - return pos; -} - static ListRow * gtk_list_view_get_row_at_y (GtkListView *self, int y, @@ -241,14 +148,14 @@ gtk_list_view_get_row_at_y (GtkListView *self, { ListRow *row, *tmp; - row = gtk_rb_tree_get_root (self->rows); + row = gtk_list_item_manager_get_root (self->item_manager); while (row) { tmp = gtk_rb_tree_node_get_left (row); if (tmp) { - ListRowAugment *aug = gtk_rb_tree_get_augment (self->rows, tmp); + ListRowAugment *aug = gtk_list_item_manager_get_item_augment (self->item_manager, tmp); if (y < aug->height) { row = tmp; @@ -257,9 +164,9 @@ gtk_list_view_get_row_at_y (GtkListView *self, y -= aug->height; } - if (y < row->height * row->n_rows) + if (y < row->height * row->parent.n_items) break; - y -= row->height * row->n_rows; + y -= row->height * row->parent.n_items; row = gtk_rb_tree_node_get_right (row); } @@ -280,7 +187,7 @@ list_row_get_y (GtkListView *self, left = gtk_rb_tree_node_get_left (row); if (left) { - ListRowAugment *aug = gtk_rb_tree_get_augment (self->rows, left); + ListRowAugment *aug = gtk_list_item_manager_get_item_augment (self->item_manager, left); y = aug->height; } else @@ -296,10 +203,10 @@ list_row_get_y (GtkListView *self, { if (left) { - ListRowAugment *aug = gtk_rb_tree_get_augment (self->rows, left); + ListRowAugment *aug = gtk_list_item_manager_get_item_augment (self->item_manager, left); y += aug->height; } - y += parent->height * parent->n_rows; + y += parent->height * parent->parent.n_items; } row = parent; @@ -314,218 +221,14 @@ gtk_list_view_get_list_height (GtkListView *self) ListRow *row; ListRowAugment *aug; - row = gtk_rb_tree_get_root (self->rows); + row = gtk_list_item_manager_get_root (self->item_manager); if (row == NULL) return 0; - aug = gtk_rb_tree_get_augment (self->rows, row); + aug = gtk_list_item_manager_get_item_augment (self->item_manager, row); return aug->height; } -static gboolean -gtk_list_view_merge_list_rows (GtkListView *self, - ListRow *first, - ListRow *second) -{ - if (first->widget || second->widget) - return FALSE; - - first->n_rows += second->n_rows; - gtk_rb_tree_node_mark_dirty (first); - gtk_rb_tree_remove (self->rows, second); - - return TRUE; -} - -static void -gtk_list_view_release_rows (GtkListView *self, - GQueue *released) -{ - ListRow *row, *prev, *next; - guint i; - - row = gtk_rb_tree_get_first (self->rows); - i = 0; - while (i < self->anchor_start) - { - if (row->widget) - { - g_queue_push_tail (released, row->widget); - row->widget = NULL; - i++; - prev = gtk_rb_tree_node_get_previous (row); - if (prev && gtk_list_view_merge_list_rows (self, prev, row)) - row = prev; - next = gtk_rb_tree_node_get_next (row); - if (next && next->widget == NULL) - { - i += next->n_rows; - if (!gtk_list_view_merge_list_rows (self, next, row)) - g_assert_not_reached (); - row = gtk_rb_tree_node_get_next (next); - } - else - { - row = next; - } - } - else - { - i += row->n_rows; - row = gtk_rb_tree_node_get_next (row); - } - } - - row = gtk_list_view_get_row (self, self->anchor_end, NULL); - if (row == NULL) - return; - - if (row->widget) - { - g_queue_push_tail (released, row->widget); - row->widget = NULL; - prev = gtk_rb_tree_node_get_previous (row); - if (prev && gtk_list_view_merge_list_rows (self, prev, row)) - row = prev; - } - - while ((next = gtk_rb_tree_node_get_next (row))) - { - if (next->widget) - { - g_queue_push_tail (released, next->widget); - next->widget = NULL; - } - gtk_list_view_merge_list_rows (self, row, next); - } -} - -static void -gtk_list_view_ensure_rows (GtkListView *self, - GtkListItemManagerChange *change, - guint update_start) -{ - ListRow *row, *new_row; - guint i, offset; - GtkWidget *widget, *insert_after; - GQueue released = G_QUEUE_INIT; - - gtk_list_view_release_rows (self, &released); - - row = gtk_list_view_get_row (self, self->anchor_start, &offset); - if (offset > 0) - { - new_row = gtk_rb_tree_insert_before (self->rows, row); - new_row->n_rows = offset; - row->n_rows -= offset; - gtk_rb_tree_node_mark_dirty (row); - } - - insert_after = NULL; - - for (i = self->anchor_start; i < self->anchor_end; i++) - { - if (row->n_rows > 1) - { - new_row = gtk_rb_tree_insert_before (self->rows, row); - new_row->n_rows = 1; - row->n_rows--; - gtk_rb_tree_node_mark_dirty (row); - } - else - { - new_row = row; - row = gtk_rb_tree_node_get_next (row); - } - if (new_row->widget == NULL) - { - if (change) - { - new_row->widget = gtk_list_item_manager_try_reacquire_list_item (self->item_manager, - change, - i, - insert_after); - } - if (new_row->widget == NULL) - { - new_row->widget = g_queue_pop_head (&released); - if (new_row->widget) - { - gtk_list_item_manager_move_list_item (self->item_manager, - new_row->widget, - i, - insert_after); - } - else - { - new_row->widget = gtk_list_item_manager_acquire_list_item (self->item_manager, - i, - insert_after); - } - } - } - else - { - if (update_start <= i) - gtk_list_item_manager_update_list_item (self->item_manager, new_row->widget, i); - } - insert_after = new_row->widget; - } - - while ((widget = g_queue_pop_head (&released))) - gtk_list_item_manager_release_list_item (self->item_manager, NULL, widget); -} - -static void -gtk_list_view_unset_anchor (GtkListView *self) -{ - self->anchor = NULL; - self->anchor_align = 0; - self->anchor_start = 0; - self->anchor_end = 0; -} - -static void -gtk_list_view_set_anchor (GtkListView *self, - guint position, - double align, - GtkListItemManagerChange *change, - guint update_start) -{ - ListRow *row; - guint items_before, items_after, total_items, n_rows; - - g_assert (align >= 0.0 && align <= 1.0); - - if (self->model) - n_rows = g_list_model_get_n_items (self->model); - else - n_rows = 0; - if (n_rows == 0) - { - gtk_list_view_unset_anchor (self); - return; - } - total_items = MIN (GTK_LIST_VIEW_MAX_LIST_ITEMS, n_rows); - if (align < 0.5) - items_before = ceil (total_items * align); - else - items_before = floor (total_items * align); - items_after = total_items - items_before; - self->anchor_start = CLAMP (position, items_before, n_rows - items_after) - items_before; - self->anchor_end = self->anchor_start + total_items; - g_assert (self->anchor_end <= n_rows); - - gtk_list_view_ensure_rows (self, change, update_start); - - row = gtk_list_view_get_row (self, position, NULL); - self->anchor = row->widget; - g_assert (self->anchor); - self->anchor_align = align; - - gtk_widget_queue_allocate (GTK_WIDGET (self)); -} - static void gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment, GtkListView *self) @@ -539,17 +242,15 @@ gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment, row = gtk_list_view_get_row_at_y (self, gtk_adjustment_get_value (adjustment), &dy); if (row) { - pos = list_row_get_position (self, row) + dy / row->height; + pos = gtk_list_item_manager_get_item_position (self->item_manager, row) + dy / row->height; } else pos = 0; - gtk_list_view_set_anchor (self, pos, 0, NULL, (guint) -1); - } - else - { - gtk_widget_queue_allocate (GTK_WIDGET (self)); + gtk_list_item_manager_set_anchor (self->item_manager, pos, 0, NULL, (guint) -1); } + + gtk_widget_queue_allocate (GTK_WIDGET (self)); } static void @@ -570,19 +271,19 @@ gtk_list_view_update_adjustments (GtkListView *self, else { ListRow *row; + guint anchor; + double anchor_align; page_size = gtk_widget_get_height (GTK_WIDGET (self)); upper = gtk_list_view_get_list_height (self); - if (self->anchor) - row = gtk_list_view_get_row (self, gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)), NULL); - else - row = NULL; + anchor = gtk_list_item_manager_get_anchor (self->item_manager, &anchor_align); + row = gtk_list_item_manager_get_nth (self->item_manager, anchor, NULL); if (row) value = list_row_get_y (self, row); else value = 0; - value -= self->anchor_align * (page_size - (row ? row->height : 0)); + value -= anchor_align * (page_size - (row ? row->height : 0)); } upper = MAX (upper, page_size); @@ -637,15 +338,15 @@ gtk_list_view_measure_across (GtkWidget *widget, min = 0; nat = 0; - for (row = gtk_rb_tree_get_first (self->rows); + for (row = gtk_list_item_manager_get_first (self->item_manager); row != NULL; row = gtk_rb_tree_node_get_next (row)) { /* ignore unavailable rows */ - if (row->widget == NULL) + if (row->parent.widget == NULL) continue; - gtk_widget_measure (row->widget, + gtk_widget_measure (row->parent.widget, orientation, for_size, &child_min, &child_nat, NULL, NULL); min = MAX (min, child_min); @@ -675,13 +376,13 @@ gtk_list_view_measure_list (GtkWidget *widget, min = 0; nat = 0; - for (row = gtk_rb_tree_get_first (self->rows); + for (row = gtk_list_item_manager_get_first (self->item_manager); row != NULL; row = gtk_rb_tree_node_get_next (row)) { - if (row->widget) + if (row->parent.widget) { - gtk_widget_measure (row->widget, + gtk_widget_measure (row->parent.widget, orientation, for_size, &child_min, &child_nat, NULL, NULL); g_array_append_val (min_heights, child_min); @@ -691,7 +392,7 @@ gtk_list_view_measure_list (GtkWidget *widget, } else { - n_unknown += row->n_rows; + n_unknown += row->parent.n_items; } } @@ -735,7 +436,7 @@ gtk_list_view_size_allocate (GtkWidget *widget, int min, nat, row_height; /* step 0: exit early if list is empty */ - if (gtk_rb_tree_get_root (self->rows) == NULL) + if (gtk_list_item_manager_get_root (self->item_manager) == NULL) return; /* step 1: determine width of the list */ @@ -750,14 +451,14 @@ gtk_list_view_size_allocate (GtkWidget *widget, /* step 2: determine height of known list items */ heights = g_array_new (FALSE, FALSE, sizeof (int)); - for (row = gtk_rb_tree_get_first (self->rows); + for (row = gtk_list_item_manager_get_first (self->item_manager); row != NULL; row = gtk_rb_tree_node_get_next (row)) { - if (row->widget == NULL) + if (row->parent.widget == NULL) continue; - gtk_widget_measure (row->widget, GTK_ORIENTATION_VERTICAL, + gtk_widget_measure (row->parent.widget, GTK_ORIENTATION_VERTICAL, self->list_width, &min, &nat, NULL, NULL); if (self->scroll_policy[GTK_ORIENTATION_VERTICAL] == GTK_SCROLL_MINIMUM) @@ -776,11 +477,11 @@ gtk_list_view_size_allocate (GtkWidget *widget, row_height = gtk_list_view_get_unknown_row_height (self, heights); g_array_free (heights, TRUE); - for (row = gtk_rb_tree_get_first (self->rows); + for (row = gtk_list_item_manager_get_first (self->item_manager); row != NULL; row = gtk_rb_tree_node_get_next (row)) { - if (row->widget) + if (row->parent.widget) continue; if (row->height != row_height) @@ -798,257 +499,20 @@ gtk_list_view_size_allocate (GtkWidget *widget, child_allocation.x = - gtk_adjustment_get_value (self->adjustment[GTK_ORIENTATION_HORIZONTAL]); child_allocation.y = - round (gtk_adjustment_get_value (self->adjustment[GTK_ORIENTATION_VERTICAL])); child_allocation.width = self->list_width; - for (row = gtk_rb_tree_get_first (self->rows); + for (row = gtk_list_item_manager_get_first (self->item_manager); row != NULL; row = gtk_rb_tree_node_get_next (row)) { - if (row->widget) + if (row->parent.widget) { child_allocation.height = row->height; - gtk_widget_size_allocate (row->widget, &child_allocation, -1); + gtk_widget_size_allocate (row->parent.widget, &child_allocation, -1); } - child_allocation.y += row->height * row->n_rows; + child_allocation.y += row->height * row->parent.n_items; } } -static guint -gtk_list_view_get_anchor (GtkListView *self, - double *align) -{ - guint anchor_pos; - - if (self->anchor) - anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); - else - anchor_pos = 0; - - if (align) - *align = self->anchor_align; - - return anchor_pos; -} - -static void -gtk_list_view_remove_rows (GtkListView *self, - GtkListItemManagerChange *change, - guint position, - guint n_rows) -{ - ListRow *row; - - if (n_rows == 0) - return; - - row = gtk_list_view_get_row (self, position, NULL); - - while (n_rows > 0) - { - if (row->n_rows > n_rows) - { - row->n_rows -= n_rows; - gtk_rb_tree_node_mark_dirty (row); - n_rows = 0; - } - else - { - ListRow *next = gtk_rb_tree_node_get_next (row); - if (row->widget) - gtk_list_item_manager_release_list_item (self->item_manager, change, row->widget); - row->widget = NULL; - n_rows -= row->n_rows; - gtk_rb_tree_remove (self->rows, row); - row = next; - } - } - - gtk_widget_queue_resize (GTK_WIDGET (self)); -} - -static void -gtk_list_view_add_rows (GtkListView *self, - guint position, - guint n_rows) -{ - ListRow *row; - guint offset; - - if (n_rows == 0) - return; - - row = gtk_list_view_get_row (self, position, &offset); - - if (row == NULL || row->widget) - row = gtk_rb_tree_insert_before (self->rows, row); - row->n_rows += n_rows; - gtk_rb_tree_node_mark_dirty (row); - - gtk_widget_queue_resize (GTK_WIDGET (self)); -} - -static void -gtk_list_view_model_items_changed_cb (GListModel *model, - guint position, - guint removed, - guint added, - GtkListView *self) -{ - GtkListItemManagerChange *change; - - change = gtk_list_item_manager_begin_change (self->item_manager); - - gtk_list_view_remove_rows (self, change, position, removed); - gtk_list_view_add_rows (self, position, added); - - /* The anchor was removed, but it may just have moved to a different position */ - if (self->anchor && gtk_list_item_manager_change_contains (change, self->anchor)) - { - /* The anchor was removed, do a more expensive rebuild trying to find if - * the anchor maybe got readded somewhere else */ - ListRow *row, *new_row; - GtkWidget *insert_after; - guint i, offset, anchor_pos; - - row = gtk_list_view_get_row (self, position, &offset); - for (new_row = row ? gtk_rb_tree_node_get_previous (row) : gtk_rb_tree_get_last (self->rows); - new_row && new_row->widget == NULL; - new_row = gtk_rb_tree_node_get_previous (new_row)) - { } - if (new_row) - insert_after = new_row->widget; - else - insert_after = NULL; /* we're at the start */ - - for (i = 0; i < added; i++) - { - GtkWidget *widget; - - widget = gtk_list_item_manager_try_reacquire_list_item (self->item_manager, - change, - position + i, - insert_after); - if (widget == NULL) - { - offset++; - continue; - } - - if (offset > 0) - { - new_row = gtk_rb_tree_insert_before (self->rows, row); - new_row->n_rows = offset; - row->n_rows -= offset; - offset = 0; - gtk_rb_tree_node_mark_dirty (row); - } - - if (row->n_rows == 1) - { - new_row = row; - row = gtk_rb_tree_node_get_next (row); - } - else - { - new_row = gtk_rb_tree_insert_before (self->rows, row); - new_row->n_rows = 1; - row->n_rows--; - gtk_rb_tree_node_mark_dirty (row); - } - - new_row->widget = widget; - insert_after = widget; - - if (widget == self->anchor) - { - anchor_pos = position + i; - break; - } - } - - if (i == added) - { - /* The anchor wasn't readded. Guess a good anchor position */ - anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); - - anchor_pos = position + (anchor_pos - position) * added / removed; - if (anchor_pos >= g_list_model_get_n_items (self->model) && - anchor_pos > 0) - anchor_pos--; - } - gtk_list_view_set_anchor (self, anchor_pos, self->anchor_align, change, position); - } - else - { - /* The anchor is still where it was. - * We just may need to update its position and check that its surrounding widgets - * exist (they might be new ones). */ - guint anchor_pos; - - if (self->anchor) - { - anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); - - if (anchor_pos >= position) - anchor_pos += added - removed; - } - else - { - anchor_pos = 0; - } - - gtk_list_view_set_anchor (self, anchor_pos, self->anchor_align, change, position); - } - - gtk_list_item_manager_end_change (self->item_manager, change); -} - -static void -gtk_list_view_model_selection_changed_cb (GListModel *model, - guint position, - guint n_items, - GtkListView *self) -{ - ListRow *row; - guint offset; - - row = gtk_list_view_get_row (self, position, &offset); - - if (offset) - { - position += row->n_rows - offset; - n_items -= row->n_rows - offset; - row = gtk_rb_tree_node_get_next (row); - } - - while (n_items > 0) - { - if (row->widget) - gtk_list_item_manager_update_list_item (self->item_manager, row->widget, position); - position += row->n_rows; - n_items -= MIN (n_items, row->n_rows); - row = gtk_rb_tree_node_get_next (row); - } -} - -static void -gtk_list_view_clear_model (GtkListView *self) -{ - if (self->model == NULL) - return; - - gtk_list_view_remove_rows (self, NULL, 0, g_list_model_get_n_items (self->model)); - - g_signal_handlers_disconnect_by_func (self->model, - gtk_list_view_model_selection_changed_cb, - self); - g_signal_handlers_disconnect_by_func (self->model, - gtk_list_view_model_items_changed_cb, - self); - g_clear_object (&self->model); - - gtk_list_view_unset_anchor (self); -} - static void gtk_list_view_clear_adjustment (GtkListView *self, GtkOrientation orientation) @@ -1067,7 +531,7 @@ gtk_list_view_dispose (GObject *object) { GtkListView *self = GTK_LIST_VIEW (object); - gtk_list_view_clear_model (self); + g_clear_object (&self->model); gtk_list_view_clear_adjustment (self, GTK_ORIENTATION_HORIZONTAL); gtk_list_view_clear_adjustment (self, GTK_ORIENTATION_VERTICAL); @@ -1082,7 +546,6 @@ gtk_list_view_finalize (GObject *object) { GtkListView *self = GTK_LIST_VIEW (object); - gtk_rb_tree_unref (self->rows); g_clear_object (&self->item_manager); G_OBJECT_CLASS (gtk_list_view_parent_class)->finalize (object); @@ -1302,13 +765,7 @@ gtk_list_view_class_init (GtkListViewClass *klass) static void gtk_list_view_init (GtkListView *self) { - self->item_manager = gtk_list_item_manager_new (GTK_WIDGET (self)); - - self->rows = gtk_rb_tree_new (ListRow, - ListRowAugment, - list_row_augment, - list_row_clear, - NULL); + self->item_manager = gtk_list_item_manager_new (GTK_WIDGET (self), ListRow, ListRowAugment, list_row_augment); self->adjustment[GTK_ORIENTATION_HORIZONTAL] = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); self->adjustment[GTK_ORIENTATION_VERTICAL] = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); @@ -1368,7 +825,7 @@ gtk_list_view_set_model (GtkListView *self, if (self->model == model) return; - gtk_list_view_clear_model (self); + g_clear_object (&self->model); if (model) { @@ -1383,19 +840,7 @@ gtk_list_view_set_model (GtkListView *self, gtk_list_item_manager_set_model (self->item_manager, selection_model); - g_signal_connect (model, - "items-changed", - G_CALLBACK (gtk_list_view_model_items_changed_cb), - self); - g_signal_connect (selection_model, - "selection-changed", - G_CALLBACK (gtk_list_view_model_selection_changed_cb), - self); - g_object_unref (selection_model); - - gtk_list_view_add_rows (self, 0, g_list_model_get_n_items (model)); - gtk_list_view_set_anchor (self, 0, 0, NULL, (guint) -1); } else { @@ -1414,22 +859,13 @@ gtk_list_view_set_functions (GtkListView *self, GDestroyNotify user_destroy) { GtkListItemFactory *factory; - guint n_items, anchor; - double anchor_align; g_return_if_fail (GTK_IS_LIST_VIEW (self)); g_return_if_fail (setup_func || bind_func); g_return_if_fail (user_data != NULL || user_destroy == NULL); - n_items = self->model ? g_list_model_get_n_items (self->model) : 0; - anchor = gtk_list_view_get_anchor (self, &anchor_align); - gtk_list_view_remove_rows (self, NULL, 0, n_items); - factory = gtk_list_item_factory_new (setup_func, bind_func, user_data, user_destroy); gtk_list_item_manager_set_factory (self->item_manager, factory); g_object_unref (factory); - - gtk_list_view_add_rows (self, 0, n_items); - gtk_list_view_set_anchor (self, anchor, anchor_align, NULL, (guint) -1); } From 368f04e06b03bf04ad33714014249c84da3ece61 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 6 Feb 2019 21:18:33 +0100 Subject: [PATCH 060/170] gridview: Implement GtkScrollable We can now scroll all the nothing we display. We also clip it properly. --- gtk/gtkgridview.c | 130 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 128 insertions(+), 2 deletions(-) diff --git a/gtk/gtkgridview.c b/gtk/gtkgridview.c index 7d17a9fcb3..265d34c72d 100644 --- a/gtk/gtkgridview.c +++ b/gtk/gtkgridview.c @@ -21,8 +21,10 @@ #include "gtkgridview.h" +#include "gtkadjustment.h" #include "gtkintl.h" #include "gtkprivate.h" +#include "gtkscrollable.h" #define DEFAULT_MAX_COLUMNS (7) @@ -40,6 +42,8 @@ struct _GtkGridView GtkWidget parent_instance; GListModel *model; + GtkAdjustment *adjustment[2]; + GtkScrollablePolicy scroll_policy[2]; guint min_columns; guint max_columns; }; @@ -47,17 +51,29 @@ struct _GtkGridView enum { PROP_0, + PROP_HADJUSTMENT, + PROP_HSCROLL_POLICY, PROP_MAX_COLUMNS, PROP_MIN_COLUMNS, PROP_MODEL, + PROP_VADJUSTMENT, + PROP_VSCROLL_POLICY, N_PROPS }; -G_DEFINE_TYPE (GtkGridView, gtk_grid_view, GTK_TYPE_WIDGET) +G_DEFINE_TYPE_WITH_CODE (GtkGridView, gtk_grid_view, GTK_TYPE_WIDGET, + G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL)) static GParamSpec *properties[N_PROPS] = { NULL, }; +static void +gtk_grid_view_adjustment_value_changed_cb (GtkAdjustment *adjustment, + GtkGridView *self) +{ + gtk_widget_queue_allocate (GTK_WIDGET (self)); +} + static gboolean gtk_grid_view_is_empty (GtkGridView *self) { @@ -115,6 +131,19 @@ gtk_grid_view_clear_model (GtkGridView *self) g_clear_object (&self->model); } +static void +gtk_grid_view_clear_adjustment (GtkGridView *self, + GtkOrientation orientation) +{ + if (self->adjustment[orientation] == NULL) + return; + + g_signal_handlers_disconnect_by_func (self->adjustment[orientation], + gtk_grid_view_adjustment_value_changed_cb, + self); + g_clear_object (&self->adjustment[orientation]); +} + static void gtk_grid_view_dispose (GObject *object) { @@ -122,6 +151,9 @@ gtk_grid_view_dispose (GObject *object) gtk_grid_view_clear_model (self); + gtk_grid_view_clear_adjustment (self, GTK_ORIENTATION_HORIZONTAL); + gtk_grid_view_clear_adjustment (self, GTK_ORIENTATION_VERTICAL); + G_OBJECT_CLASS (gtk_grid_view_parent_class)->dispose (object); } @@ -135,6 +167,14 @@ gtk_grid_view_get_property (GObject *object, switch (property_id) { + case PROP_HADJUSTMENT: + g_value_set_object (value, self->adjustment[GTK_ORIENTATION_HORIZONTAL]); + break; + + case PROP_HSCROLL_POLICY: + g_value_set_enum (value, self->scroll_policy[GTK_ORIENTATION_HORIZONTAL]); + break; + case PROP_MAX_COLUMNS: g_value_set_uint (value, self->max_columns); break; @@ -147,12 +187,61 @@ gtk_grid_view_get_property (GObject *object, g_value_set_object (value, self->model); break; + case PROP_VADJUSTMENT: + g_value_set_object (value, self->adjustment[GTK_ORIENTATION_VERTICAL]); + break; + + case PROP_VSCROLL_POLICY: + g_value_set_enum (value, self->scroll_policy[GTK_ORIENTATION_VERTICAL]); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } +static void +gtk_grid_view_set_adjustment (GtkGridView *self, + GtkOrientation orientation, + GtkAdjustment *adjustment) +{ + if (self->adjustment[orientation] == adjustment) + return; + + if (adjustment == NULL) + adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + g_object_ref_sink (adjustment); + + gtk_grid_view_clear_adjustment (self, orientation); + + self->adjustment[orientation] = adjustment; + + g_signal_connect (adjustment, "value-changed", + G_CALLBACK (gtk_grid_view_adjustment_value_changed_cb), + self); + + gtk_widget_queue_allocate (GTK_WIDGET (self)); +} + +static void +gtk_grid_view_set_scroll_policy (GtkGridView *self, + GtkOrientation orientation, + GtkScrollablePolicy scroll_policy) +{ + if (self->scroll_policy[orientation] == scroll_policy) + return; + + self->scroll_policy[orientation] = scroll_policy; + + gtk_widget_queue_resize (GTK_WIDGET (self)); + + g_object_notify_by_pspec (G_OBJECT (self), + orientation == GTK_ORIENTATION_HORIZONTAL + ? properties[PROP_HSCROLL_POLICY] + : properties[PROP_VSCROLL_POLICY]); +} + static void gtk_grid_view_set_property (GObject *object, guint property_id, @@ -163,6 +252,14 @@ gtk_grid_view_set_property (GObject *object, switch (property_id) { + case PROP_HADJUSTMENT: + gtk_grid_view_set_adjustment (self, GTK_ORIENTATION_HORIZONTAL, g_value_get_object (value)); + break; + + case PROP_HSCROLL_POLICY: + gtk_grid_view_set_scroll_policy (self, GTK_ORIENTATION_HORIZONTAL, g_value_get_enum (value)); + break; + case PROP_MAX_COLUMNS: gtk_grid_view_set_max_columns (self, g_value_get_uint (value)); break; @@ -175,6 +272,14 @@ gtk_grid_view_set_property (GObject *object, gtk_grid_view_set_model (self, g_value_get_object (value)); break; + case PROP_VADJUSTMENT: + gtk_grid_view_set_adjustment (self, GTK_ORIENTATION_VERTICAL, g_value_get_object (value)); + break; + + case PROP_VSCROLL_POLICY: + gtk_grid_view_set_scroll_policy (self, GTK_ORIENTATION_VERTICAL, g_value_get_enum (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -186,6 +291,7 @@ gtk_grid_view_class_init (GtkGridViewClass *klass) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + gpointer iface; widget_class->measure = gtk_grid_view_measure; widget_class->size_allocate = gtk_grid_view_size_allocate; @@ -194,6 +300,21 @@ gtk_grid_view_class_init (GtkGridViewClass *klass) gobject_class->get_property = gtk_grid_view_get_property; gobject_class->set_property = gtk_grid_view_set_property; + /* GtkScrollable implementation */ + iface = g_type_default_interface_peek (GTK_TYPE_SCROLLABLE); + properties[PROP_HADJUSTMENT] = + g_param_spec_override ("hadjustment", + g_object_interface_find_property (iface, "hadjustment")); + properties[PROP_HSCROLL_POLICY] = + g_param_spec_override ("hscroll-policy", + g_object_interface_find_property (iface, "hscroll-policy")); + properties[PROP_VADJUSTMENT] = + g_param_spec_override ("vadjustment", + g_object_interface_find_property (iface, "vadjustment")); + properties[PROP_VSCROLL_POLICY] = + g_param_spec_override ("vscroll-policy", + g_object_interface_find_property (iface, "vscroll-policy")); + /** * GtkGridView:max-columns: * @@ -235,7 +356,7 @@ gtk_grid_view_class_init (GtkGridViewClass *klass) g_object_class_install_properties (gobject_class, N_PROPS, properties); - gtk_widget_class_set_css_name (widget_class, I_("list")); + gtk_widget_class_set_css_name (widget_class, I_("grid")); } static void @@ -243,6 +364,11 @@ gtk_grid_view_init (GtkGridView *self) { self->min_columns = 1; self->max_columns = DEFAULT_MAX_COLUMNS; + + self->adjustment[GTK_ORIENTATION_HORIZONTAL] = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + self->adjustment[GTK_ORIENTATION_VERTICAL] = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + + gtk_widget_set_overflow (GTK_WIDGET (self), GTK_OVERFLOW_HIDDEN); } /** From ce489f21fb959ba65a1019c32d42de4594d12425 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 22 Feb 2019 03:05:54 +0100 Subject: [PATCH 061/170] listitemmanager: Simplify Remove a bunch of API from the headers that isn't used anymore and then refactor code to not call it anymore. In particular, get rid of GtkListItemManagerChange and replace it with a GHashTable. --- gtk/gtklistitemmanager.c | 195 ++++++++++---------------------- gtk/gtklistitemmanagerprivate.h | 28 +---- 2 files changed, 60 insertions(+), 163 deletions(-) diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index 6cadec777b..ac922cc709 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -48,11 +48,24 @@ struct _GtkListItemManagerClass GObjectClass parent_class; }; -struct _GtkListItemManagerChange -{ - GHashTable *items; -}; - +static GtkWidget * gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, + guint position, + GtkWidget *prev_sibling); +static GtkWidget * gtk_list_item_manager_try_reacquire_list_item + (GtkListItemManager *self, + GHashTable *change, + guint position, + GtkWidget *prev_sibling); +static void gtk_list_item_manager_update_list_item (GtkListItemManager *self, + GtkWidget *item, + guint position); +static void gtk_list_item_manager_move_list_item (GtkListItemManager *self, + GtkWidget *list_item, + guint position, + GtkWidget *prev_sibling); +static void gtk_list_item_manager_release_list_item (GtkListItemManager *self, + GHashTable *change, + GtkWidget *widget); G_DEFINE_TYPE (GtkListItemManager, gtk_list_item_manager, G_TYPE_OBJECT) void @@ -227,10 +240,10 @@ gtk_list_item_manager_get_item_augment (GtkListItemManager *self, } static void -gtk_list_item_manager_remove_items (GtkListItemManager *self, - GtkListItemManagerChange *change, - guint position, - guint n_items) +gtk_list_item_manager_remove_items (GtkListItemManager *self, + GHashTable *change, + guint position, + guint n_items) { GtkListItemManagerItem *item; @@ -371,9 +384,9 @@ gtk_list_item_manager_release_items (GtkListItemManager *self, } static void -gtk_list_item_manager_ensure_items (GtkListItemManager *self, - GtkListItemManagerChange *change, - guint update_start) +gtk_list_item_manager_ensure_items (GtkListItemManager *self, + GHashTable *change, + guint update_start) { GtkListItemManagerItem *item, *new_item; guint i, offset; @@ -447,11 +460,11 @@ gtk_list_item_manager_ensure_items (GtkListItemManager *self, } void -gtk_list_item_manager_set_anchor (GtkListItemManager *self, - guint position, - double align, - GtkListItemManagerChange *change, - guint update_start) +gtk_list_item_manager_set_anchor (GtkListItemManager *self, + guint position, + double align, + GHashTable *change, + guint update_start) { GtkListItemManagerItem *item; guint items_before, items_after, total_items, n_items; @@ -494,15 +507,17 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model, guint added, GtkListItemManager *self) { - GtkListItemManagerChange *change; + GHashTable *change; + GHashTableIter iter; + gpointer list_item; - change = gtk_list_item_manager_begin_change (self); + change = g_hash_table_new (g_direct_hash, g_direct_equal); gtk_list_item_manager_remove_items (self, change, position, removed); gtk_list_item_manager_add_items (self, position, added); /* The anchor was removed, but it may just have moved to a different position */ - if (self->anchor && gtk_list_item_manager_change_contains (change, self->anchor)) + if (self->anchor && g_hash_table_lookup (change, gtk_list_item_get_item (GTK_LIST_ITEM (self->anchor))) == self->anchor) { /* The anchor was removed, do a more expensive rebuild trying to find if * the anchor maybe got readded somewhere else */ @@ -600,7 +615,16 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model, gtk_list_item_manager_set_anchor (self, anchor_pos, self->anchor_align, change, position); } - gtk_list_item_manager_end_change (self, change); + g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); + g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); + + g_hash_table_iter_init (&iter, change); + while (g_hash_table_iter_next (&iter, NULL, &list_item)) + { + gtk_list_item_manager_release_list_item (self, NULL, list_item); + } + + g_hash_table_unref (change); } static void @@ -767,107 +791,6 @@ gtk_list_item_manager_get_model (GtkListItemManager *self) return self->model; } -#if 0 -/* - * gtk_list_item_manager_get_size: - * @self: a #GtkListItemManager - * - * Queries the number of widgets currently handled by @self. - * - * This includes both widgets that have been acquired and - * those currently waiting to be used again. - * - * Returns: Number of widgets handled by @self - **/ -guint -gtk_list_item_manager_get_size (GtkListItemManager *self) -{ - return g_hash_table_size (self->pool); -} -#endif - -/* - * gtk_list_item_manager_begin_change: - * @self: a #GtkListItemManager - * - * Begins a change operation in response to a model's items-changed - * signal. - * During an ongoing change operation, list items will not be discarded - * when released but will be kept around in anticipation of them being - * added back in a different posiion later. - * - * Once it is known that no more list items will be reused, - * gtk_list_item_manager_end_change() should be called. This should happen - * as early as possible, so the list items held for the change can be - * reqcquired. - * - * Returns: The object to use for this change - **/ -GtkListItemManagerChange * -gtk_list_item_manager_begin_change (GtkListItemManager *self) -{ - GtkListItemManagerChange *change; - - g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL); - - change = g_slice_new (GtkListItemManagerChange); - change->items = g_hash_table_new (g_direct_hash, g_direct_equal); - - return change; -} - -/* - * gtk_list_item_manager_end_change: - * @self: a #GtkListItemManager - * @change: a change - * - * Ends a change operation begun with gtk_list_item_manager_begin_change() - * and releases all list items still cached. - **/ -void -gtk_list_item_manager_end_change (GtkListItemManager *self, - GtkListItemManagerChange *change) -{ - GHashTableIter iter; - gpointer list_item; - - g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); - g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); - - g_hash_table_iter_init (&iter, change->items); - while (g_hash_table_iter_next (&iter, NULL, &list_item)) - { - gtk_list_item_manager_release_list_item (self, NULL, list_item); - } - - g_hash_table_unref (change->items); - g_slice_free (GtkListItemManagerChange, change); -} - -/* - * gtk_list_item_manager_change_contains: - * @change: a #GtkListItemManagerChange - * @list_item: The item that may have been released into this change set - * - * Checks if @list_item has been released as part of @change but not been - * reacquired yet. - * - * This is useful to test before calling gtk_list_item_manager_end_change() - * if special actions need to be performed when important list items - like - * the focused item - are about to be deleted. - * - * Returns: %TRUE if the item is part of this change - **/ -gboolean -gtk_list_item_manager_change_contains (GtkListItemManagerChange *change, - GtkWidget *list_item) -{ - g_return_val_if_fail (change != NULL, FALSE); - g_return_val_if_fail (GTK_IS_LIST_ITEM (list_item), FALSE); - - return g_hash_table_lookup (change->items, gtk_list_item_get_item (GTK_LIST_ITEM (list_item))) == list_item; -} - /* * gtk_list_item_manager_acquire_list_item: * @self: a #GtkListItemManager @@ -886,7 +809,7 @@ gtk_list_item_manager_change_contains (GtkListItemManagerChange *change, * * Returns: a properly setup widget to use in @position **/ -GtkWidget * +static GtkWidget * gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, guint position, GtkWidget *prev_sibling) @@ -925,11 +848,11 @@ gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, * Returns: (nullable): a properly setup widget to use in @position or %NULL if * no item for reuse existed **/ -GtkWidget * -gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self, - GtkListItemManagerChange *change, - guint position, - GtkWidget *prev_sibling) +static GtkWidget * +gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self, + GHashTable *change, + guint position, + GtkWidget *prev_sibling) { GtkListItem *result; gpointer item; @@ -939,7 +862,7 @@ gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self, /* XXX: can we avoid temporarily allocating items on failure? */ item = g_list_model_get_item (G_LIST_MODEL (self->model), position); - if (g_hash_table_steal_extended (change->items, item, NULL, (gpointer *) &result)) + if (g_hash_table_steal_extended (change, item, NULL, (gpointer *) &result)) { gtk_list_item_factory_update (self->factory, result, position, FALSE); gtk_widget_insert_after (GTK_WIDGET (result), self->widget, prev_sibling); @@ -968,7 +891,7 @@ gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self, * * This is most useful when scrolling. **/ -void +static void gtk_list_item_manager_move_list_item (GtkListItemManager *self, GtkWidget *list_item, guint position, @@ -993,7 +916,7 @@ gtk_list_item_manager_move_list_item (GtkListItemManager *self, * Updates the position of the given @item. This function must be called whenever * the position of an item changes, like when new items are added before it. **/ -void +static void gtk_list_item_manager_update_list_item (GtkListItemManager *self, GtkWidget *item, guint position) @@ -1018,17 +941,17 @@ gtk_list_item_manager_update_list_item (GtkListItemManager *self, * Releases an item that was previously acquired via * gtk_list_item_manager_acquire_list_item() and is no longer in use. **/ -void -gtk_list_item_manager_release_list_item (GtkListItemManager *self, - GtkListItemManagerChange *change, - GtkWidget *item) +static void +gtk_list_item_manager_release_list_item (GtkListItemManager *self, + GHashTable *change, + GtkWidget *item) { g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); g_return_if_fail (GTK_IS_LIST_ITEM (item)); if (change != NULL) { - if (g_hash_table_insert (change->items, gtk_list_item_get_item (GTK_LIST_ITEM (item)), item)) + if (g_hash_table_insert (change, gtk_list_item_get_item (GTK_LIST_ITEM (item)), item)) return; g_warning ("FIXME: Handle the same item multiple times in the list.\nLars says this totally should not happen, but here we are."); diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h index 9e1ebab073..9a506c7573 100644 --- a/gtk/gtklistitemmanagerprivate.h +++ b/gtk/gtklistitemmanagerprivate.h @@ -38,7 +38,6 @@ G_BEGIN_DECLS typedef struct _GtkListItemManager GtkListItemManager; typedef struct _GtkListItemManagerClass GtkListItemManagerClass; -typedef struct _GtkListItemManagerChange GtkListItemManagerChange; typedef struct _GtkListItemManagerItem GtkListItemManagerItem; /* sorry */ typedef struct _GtkListItemManagerItemAugment GtkListItemManagerItemAugment; @@ -90,36 +89,11 @@ guint gtk_list_item_manager_get_size (GtkListItemMana void gtk_list_item_manager_set_anchor (GtkListItemManager *self, guint position, double align, - GtkListItemManagerChange *change, + GHashTable *change, guint update_start); guint gtk_list_item_manager_get_anchor (GtkListItemManager *self, double *align); -GtkListItemManagerChange * - gtk_list_item_manager_begin_change (GtkListItemManager *self); -void gtk_list_item_manager_end_change (GtkListItemManager *self, - GtkListItemManagerChange *change); -gboolean gtk_list_item_manager_change_contains (GtkListItemManagerChange *change, - GtkWidget *list_item); - -GtkWidget * gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, - guint position, - GtkWidget *prev_sibling); -GtkWidget * gtk_list_item_manager_try_reacquire_list_item - (GtkListItemManager *self, - GtkListItemManagerChange *change, - guint position, - GtkWidget *prev_sibling); -void gtk_list_item_manager_update_list_item (GtkListItemManager *self, - GtkWidget *item, - guint position); -void gtk_list_item_manager_move_list_item (GtkListItemManager *self, - GtkWidget *list_item, - guint position, - GtkWidget *prev_sibling); -void gtk_list_item_manager_release_list_item (GtkListItemManager *self, - GtkListItemManagerChange *change, - GtkWidget *widget); G_END_DECLS From 1acfae8df285e516eb8245fb999b2481714bc448 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 27 Feb 2019 08:45:28 +0100 Subject: [PATCH 062/170] listitemmanager: Add trackers ... and replace the anchor tracking with a tracker. Trackers track an item through the list across changes and ensure that this item (and potentially siblings before/after it) are always backed by a GtkListItem and that if the item gets removed a replacement gets chosen. This is now used for tracking the anchor but can also be used to add trackers for the cursor later. --- gtk/gtklistitemmanager.c | 592 +++++++++++++++++++++----------- gtk/gtklistitemmanagerprivate.h | 16 +- gtk/gtklistview.c | 38 +- 3 files changed, 426 insertions(+), 220 deletions(-) diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index ac922cc709..35a894ed51 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -35,12 +35,7 @@ struct _GtkListItemManager GtkListItemFactory *factory; GtkRbTree *items; - - /* managing the visible region */ - GtkWidget *anchor; /* may be NULL if list is empty */ - int anchor_align; /* what to align the anchor to */ - guint anchor_start; /* start of region we allocate row widgets for */ - guint anchor_end; /* end of same region - first position to not have a widget */ + GSList *trackers; }; struct _GtkListItemManagerClass @@ -48,6 +43,14 @@ struct _GtkListItemManagerClass GObjectClass parent_class; }; +struct _GtkListItemTracker +{ + guint position; + GtkListItem *widget; + guint n_before; + guint n_after; +}; + static GtkWidget * gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, guint position, GtkWidget *prev_sibling); @@ -239,6 +242,104 @@ gtk_list_item_manager_get_item_augment (GtkListItemManager *self, return gtk_rb_tree_get_augment (self->items, item); } +static void +gtk_list_item_tracker_unset_position (GtkListItemManager *self, + GtkListItemTracker *tracker) +{ + tracker->widget = NULL; + tracker->position = GTK_INVALID_LIST_POSITION; +} + +static gboolean +gtk_list_item_tracker_query_range (GtkListItemManager *self, + GtkListItemTracker *tracker, + guint n_items, + guint *out_start, + guint *out_n_items) +{ + /* We can't look at tracker->widget here because we might not + * have set it yet. + */ + if (tracker->position == GTK_INVALID_LIST_POSITION) + return FALSE; + + /* This is magic I made up that is meant to be both + * correct and doesn't overflow when start and/or end are close to 0 or + * close to max. + * But beware, I didn't test it. + */ + *out_n_items = tracker->n_before + tracker->n_after + 1; + *out_n_items = MIN (*out_n_items, n_items); + + *out_start = MAX (tracker->position, tracker->n_before) - tracker->n_before; + *out_start = MIN (*out_start, n_items - *out_n_items); + + return TRUE; +} + +static void +gtk_list_item_query_tracked_range (GtkListItemManager *self, + guint n_items, + guint position, + guint *out_n_items, + gboolean *out_tracked) +{ + GSList *l; + guint tracker_start, tracker_n_items; + + g_assert (position < n_items); + + *out_tracked = FALSE; + *out_n_items = n_items - position; + + /* step 1: Check if position is tracked */ + + for (l = self->trackers; l; l = l->next) + { + if (!gtk_list_item_tracker_query_range (self, l->data, n_items, &tracker_start, &tracker_n_items)) + continue; + + if (tracker_start > position) + { + *out_n_items = MIN (*out_n_items, tracker_start - position); + } + else if (tracker_start + tracker_n_items <= position) + { + /* do nothing */ + } + else + { + *out_tracked = TRUE; + *out_n_items = tracker_start + tracker_n_items - position; + break; + } + } + + /* If nothing's tracked, we're done */ + if (!*out_tracked) + return; + + /* step 2: make the tracked range as large as possible + * NB: This is O(N_TRACKERS^2), but the number of trackers should be <5 */ +restart: + for (l = self->trackers; l; l = l->next) + { + if (!gtk_list_item_tracker_query_range (self, l->data, n_items, &tracker_start, &tracker_n_items)) + continue; + + if (tracker_start + tracker_n_items <= position + *out_n_items) + continue; + if (tracker_start > position + *out_n_items) + continue; + + if (*out_n_items + position < tracker_start + tracker_n_items) + { + *out_n_items = tracker_start + tracker_n_items - position; + goto restart; + } + } +} + static void gtk_list_item_manager_remove_items (GtkListItemManager *self, GHashTable *change, @@ -296,15 +397,6 @@ gtk_list_item_manager_add_items (GtkListItemManager *self, gtk_widget_queue_resize (GTK_WIDGET (self->widget)); } -static void -gtk_list_item_manager_unset_anchor (GtkListItemManager *self) -{ - self->anchor = NULL; - self->anchor_align = 0; - self->anchor_start = 0; - self->anchor_end = 0; -} - static gboolean gtk_list_item_manager_merge_list_items (GtkListItemManager *self, GtkListItemManagerItem *first, @@ -325,61 +417,53 @@ gtk_list_item_manager_release_items (GtkListItemManager *self, GQueue *released) { GtkListItemManagerItem *item, *prev, *next; - guint i; + guint position, i, n_items, query_n_items; + gboolean tracked; - item = gtk_rb_tree_get_first (self->items); - i = 0; - while (i < self->anchor_start) + n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model)); + position = 0; + + while (position < n_items) { - if (item->widget) + gtk_list_item_query_tracked_range (self, n_items, position, &query_n_items, &tracked); + if (tracked) { - g_queue_push_tail (released, item->widget); - item->widget = NULL; - i++; - prev = gtk_rb_tree_node_get_previous (item); - if (prev && gtk_list_item_manager_merge_list_items (self, prev, item)) - item = prev; - next = gtk_rb_tree_node_get_next (item); - if (next && next->widget == NULL) + position += query_n_items; + continue; + } + + item = gtk_list_item_manager_get_nth (self, position, &i); + i = position - i; + while (i < position + query_n_items) + { + if (item->widget) { - i += next->n_items; - if (!gtk_list_item_manager_merge_list_items (self, next, item)) - g_assert_not_reached (); - item = gtk_rb_tree_node_get_next (next); + g_queue_push_tail (released, item->widget); + item->widget = NULL; + i++; + prev = gtk_rb_tree_node_get_previous (item); + if (prev && gtk_list_item_manager_merge_list_items (self, prev, item)) + item = prev; + next = gtk_rb_tree_node_get_next (item); + if (next && next->widget == NULL) + { + i += next->n_items; + if (!gtk_list_item_manager_merge_list_items (self, next, item)) + g_assert_not_reached (); + item = gtk_rb_tree_node_get_next (next); + } + else + { + item = next; + } } - else + else { - item = next; + i += item->n_items; + item = gtk_rb_tree_node_get_next (item); } } - else - { - i += item->n_items; - item = gtk_rb_tree_node_get_next (item); - } - } - - item = gtk_list_item_manager_get_nth (self, self->anchor_end, NULL); - if (item == NULL) - return; - - if (item->widget) - { - g_queue_push_tail (released, item->widget); - item->widget = NULL; - prev = gtk_rb_tree_node_get_previous (item); - if (prev && gtk_list_item_manager_merge_list_items (self, prev, item)) - item = prev; - } - - while ((next = gtk_rb_tree_node_get_next (item))) - { - if (next->widget) - { - g_queue_push_tail (released, next->widget); - next->widget = NULL; - } - gtk_list_item_manager_merge_list_items (self, item, next); + position += query_n_items; } } @@ -389,117 +473,98 @@ gtk_list_item_manager_ensure_items (GtkListItemManager *self, guint update_start) { GtkListItemManagerItem *item, *new_item; - guint i, offset; GtkWidget *widget, *insert_after; + guint position, i, n_items, query_n_items, offset; GQueue released = G_QUEUE_INIT; + gboolean tracked; + + if (self->model == NULL) + return; + + n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model)); + position = 0; gtk_list_item_manager_release_items (self, &released); - item = gtk_list_item_manager_get_nth (self, self->anchor_start, &offset); - if (offset > 0) + while (position < n_items) { - new_item = gtk_rb_tree_insert_before (self->items, item); - new_item->n_items = offset; - item->n_items -= offset; - gtk_rb_tree_node_mark_dirty (item); - } + gtk_list_item_query_tracked_range (self, n_items, position, &query_n_items, &tracked); + if (!tracked) + { + position += query_n_items; + continue; + } - insert_after = NULL; + item = gtk_list_item_manager_get_nth (self, position, &offset); + for (new_item = item; + new_item && new_item->widget == NULL; + new_item = gtk_rb_tree_node_get_previous (new_item)) + { /* do nothing */ } + insert_after = new_item ? new_item->widget : NULL; - for (i = self->anchor_start; i < self->anchor_end; i++) - { - if (item->n_items > 1) + if (offset > 0) { new_item = gtk_rb_tree_insert_before (self->items, item); - new_item->n_items = 1; - item->n_items--; + new_item->n_items = offset; + item->n_items -= offset; gtk_rb_tree_node_mark_dirty (item); } - else + + for (i = 0; i < query_n_items; i++) { - new_item = item; - item = gtk_rb_tree_node_get_next (item); - } - if (new_item->widget == NULL) - { - if (change) + if (item->n_items > 1) { - new_item->widget = gtk_list_item_manager_try_reacquire_list_item (self, - change, - i, - insert_after); + new_item = gtk_rb_tree_insert_before (self->items, item); + new_item->n_items = 1; + item->n_items--; + gtk_rb_tree_node_mark_dirty (item); + } + else + { + new_item = item; + item = gtk_rb_tree_node_get_next (item); } if (new_item->widget == NULL) { - new_item->widget = g_queue_pop_head (&released); - if (new_item->widget) + if (change) { - gtk_list_item_manager_move_list_item (self, - new_item->widget, - i, - insert_after); + new_item->widget = gtk_list_item_manager_try_reacquire_list_item (self, + change, + position + i, + insert_after); } - else + if (new_item->widget == NULL) { - new_item->widget = gtk_list_item_manager_acquire_list_item (self, - i, - insert_after); + new_item->widget = g_queue_pop_head (&released); + if (new_item->widget) + { + gtk_list_item_manager_move_list_item (self, + new_item->widget, + position + i, + insert_after); + } + else + { + new_item->widget = gtk_list_item_manager_acquire_list_item (self, + position + i, + insert_after); + } } } + else + { + if (update_start <= position + i) + gtk_list_item_manager_update_list_item (self, new_item->widget, position + i); + } + insert_after = new_item->widget; } - else - { - if (update_start <= i) - gtk_list_item_manager_update_list_item (self, new_item->widget, i); - } - insert_after = new_item->widget; + position += query_n_items; } while ((widget = g_queue_pop_head (&released))) gtk_list_item_manager_release_list_item (self, NULL, widget); } -void -gtk_list_item_manager_set_anchor (GtkListItemManager *self, - guint position, - double align, - GHashTable *change, - guint update_start) -{ - GtkListItemManagerItem *item; - guint items_before, items_after, total_items, n_items; - - g_assert (align >= 0.0 && align <= 1.0); - - if (self->model) - n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model)); - else - n_items = 0; - if (n_items == 0) - { - gtk_list_item_manager_unset_anchor (self); - return; - } - total_items = MIN (GTK_LIST_VIEW_MAX_LIST_ITEMS, n_items); - if (align < 0.5) - items_before = ceil (total_items * align); - else - items_before = floor (total_items * align); - items_after = total_items - items_before; - self->anchor_start = CLAMP (position, items_before, n_items - items_after) - items_before; - self->anchor_end = self->anchor_start + total_items; - g_assert (self->anchor_end <= n_items); - - gtk_list_item_manager_ensure_items (self, change, update_start); - - item = gtk_list_item_manager_get_nth (self, position, NULL); - self->anchor = item->widget; - g_assert (self->anchor); - self->anchor_align = align; - - gtk_widget_queue_allocate (GTK_WIDGET (self->widget)); -} - static void gtk_list_item_manager_model_items_changed_cb (GListModel *model, guint position, @@ -510,20 +575,34 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model, GHashTable *change; GHashTableIter iter; gpointer list_item; + GSList *l; + guint n_items; + n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model)); change = g_hash_table_new (g_direct_hash, g_direct_equal); gtk_list_item_manager_remove_items (self, change, position, removed); gtk_list_item_manager_add_items (self, position, added); - /* The anchor was removed, but it may just have moved to a different position */ - if (self->anchor && g_hash_table_lookup (change, gtk_list_item_get_item (GTK_LIST_ITEM (self->anchor))) == self->anchor) + /* Check if any tracked item was removed */ + for (l = self->trackers; l; l = l->next) + { + GtkListItemTracker *tracker = l->data; + + if (tracker->widget == NULL) + continue; + + if (g_hash_table_lookup (change, gtk_list_item_get_item (tracker->widget))) + break; + } + + /* At least one tracked item was removed, do a more expensive rebuild + * trying to find where it moved */ + if (l) { - /* The anchor was removed, do a more expensive rebuild trying to find if - * the anchor maybe got readded somewhere else */ GtkListItemManagerItem *item, *new_item; GtkWidget *insert_after; - guint i, offset, anchor_pos; + guint i, offset; item = gtk_list_item_manager_get_nth (self, position, &offset); for (new_item = item ? gtk_rb_tree_node_get_previous (item) : gtk_rb_tree_get_last (self->items); @@ -573,50 +652,76 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model, new_item->widget = widget; insert_after = widget; - - if (widget == self->anchor) - { - anchor_pos = position + i; - break; - } } - - if (i == added) - { - /* The anchor wasn't readded. Guess a good anchor position */ - anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); - - anchor_pos = position + (anchor_pos - position) * added / removed; - if (anchor_pos >= g_list_model_get_n_items (G_LIST_MODEL (self->model)) && - anchor_pos > 0) - anchor_pos--; - } - gtk_list_item_manager_set_anchor (self, anchor_pos, self->anchor_align, change, position); } - else - { - /* The anchor is still where it was. - * We just may need to update its position and check that its surrounding widgets - * exist (they might be new ones). */ - guint anchor_pos; - - if (self->anchor) - { - anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); - if (anchor_pos >= position) - anchor_pos += added - removed; + /* Update tracker positions if necessary, they need to have correct + * positions for gtk_list_item_manager_ensure_items(). + * We don't update the items, they will be updated by ensure_items() + * and then we can update them. */ + for (l = self->trackers; l; l = l->next) + { + GtkListItemTracker *tracker = l->data; + + if (tracker->position == GTK_INVALID_LIST_POSITION) + { + /* if the list is no longer empty, set the tracker to a valid position. */ + if (n_items > 0 && n_items == added && removed == 0) + tracker->position = 0; + } + else if (tracker->position >= position + removed) + { + tracker->position += added - removed; + } + else if (tracker->position >= position) + { + if (g_hash_table_lookup (change, gtk_list_item_get_item (tracker->widget))) + { + /* The item is gone. Guess a good new position */ + tracker->position = position + (tracker->position - position) * added / removed; + if (tracker->position >= n_items) + { + if (n_items == 0) + tracker->position = GTK_INVALID_LIST_POSITION; + else + tracker->position--; + } + tracker->widget = NULL; + } + else + { + /* item was put in its right place in the expensive loop above, + * and we updated its position while at it. So grab it from there. + */ + tracker->position = gtk_list_item_get_position (tracker->widget); + } } else { - anchor_pos = 0; + /* nothing changed for items before position */ } - - gtk_list_item_manager_set_anchor (self, anchor_pos, self->anchor_align, change, position); } - g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); - g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); + gtk_list_item_manager_ensure_items (self, change, position + added); + + /* final loop through the trackers: Grab the missing widgets. + * For items that had been removed and a new position was set, grab + * their item now that we ensured it exists. + */ + for (l = self->trackers; l; l = l->next) + { + GtkListItemTracker *tracker = l->data; + GtkListItemManagerItem *item; + + if (tracker->widget != NULL || + tracker->position == GTK_INVALID_LIST_POSITION) + continue; + + item = gtk_list_item_manager_get_nth (self, tracker->position, NULL); + g_assert (item != NULL); + g_assert (item->widget); + tracker->widget = GTK_LIST_ITEM (item->widget); + } g_hash_table_iter_init (&iter, change); while (g_hash_table_iter_next (&iter, NULL, &list_item)) @@ -625,6 +730,8 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model, } g_hash_table_unref (change); + + gtk_widget_queue_resize (self->widget); } static void @@ -658,30 +765,19 @@ gtk_list_item_manager_model_selection_changed_cb (GListModel *model, } } -guint -gtk_list_item_manager_get_anchor (GtkListItemManager *self, - double *align) -{ - guint anchor_pos; - - if (self->anchor) - anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); - else - anchor_pos = 0; - - if (align) - *align = self->anchor_align; - - return anchor_pos; -} - static void gtk_list_item_manager_clear_model (GtkListItemManager *self) { + GSList *l; + if (self->model == NULL) return; gtk_list_item_manager_remove_items (self, NULL, 0, g_list_model_get_n_items (G_LIST_MODEL (self->model))); + for (l = self->trackers; l; l = l->next) + { + gtk_list_item_tracker_unset_position (self, l->data); + } g_signal_handlers_disconnect_by_func (self->model, gtk_list_item_manager_model_selection_changed_cb, @@ -690,8 +786,6 @@ gtk_list_item_manager_clear_model (GtkListItemManager *self) gtk_list_item_manager_model_items_changed_cb, self); g_clear_object (&self->model); - - gtk_list_item_manager_unset_anchor (self); } static void @@ -725,8 +819,8 @@ void gtk_list_item_manager_set_factory (GtkListItemManager *self, GtkListItemFactory *factory) { - guint n_items, anchor; - double anchor_align; + guint n_items; + GSList *l; g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (factory)); @@ -735,13 +829,26 @@ gtk_list_item_manager_set_factory (GtkListItemManager *self, return; n_items = self->model ? g_list_model_get_n_items (G_LIST_MODEL (self->model)) : 0; - anchor = gtk_list_item_manager_get_anchor (self, &anchor_align); gtk_list_item_manager_remove_items (self, NULL, 0, n_items); g_set_object (&self->factory, factory); gtk_list_item_manager_add_items (self, 0, n_items); - gtk_list_item_manager_set_anchor (self, anchor, anchor_align, NULL, (guint) -1); + + gtk_list_item_manager_ensure_items (self, NULL, G_MAXUINT); + + for (l = self->trackers; l; l = l->next) + { + GtkListItemTracker *tracker = l->data; + GtkListItemManagerItem *item; + + if (tracker->widget == NULL) + continue; + + item = gtk_list_item_manager_get_nth (self, tracker->position, NULL); + g_assert (item); + tracker->widget = GTK_LIST_ITEM (item->widget); + } } GtkListItemFactory * @@ -778,8 +885,6 @@ gtk_list_item_manager_set_model (GtkListItemManager *self, self); gtk_list_item_manager_add_items (self, 0, g_list_model_get_n_items (G_LIST_MODEL (model))); - - gtk_list_item_manager_set_anchor (self, 0, 0, NULL, (guint) -1); } } @@ -961,3 +1066,72 @@ gtk_list_item_manager_release_list_item (GtkListItemManager *self, gtk_widget_unparent (item); } +GtkListItemTracker * +gtk_list_item_tracker_new (GtkListItemManager *self) +{ + GtkListItemTracker *tracker; + + g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL); + + tracker = g_slice_new0 (GtkListItemTracker); + + tracker->position = GTK_INVALID_LIST_POSITION; + + self->trackers = g_slist_prepend (self->trackers, tracker); + + return tracker; +} + +void +gtk_list_item_tracker_free (GtkListItemManager *self, + GtkListItemTracker *tracker) +{ + gtk_list_item_tracker_unset_position (self, tracker); + + self->trackers = g_slist_remove (self->trackers, tracker); + + g_slice_free (GtkListItemTracker, tracker); + + gtk_list_item_manager_ensure_items (self, NULL, G_MAXUINT); + + gtk_widget_queue_resize (self->widget); +} + +void +gtk_list_item_tracker_set_position (GtkListItemManager *self, + GtkListItemTracker *tracker, + guint position, + guint n_before, + guint n_after) +{ + GtkListItemManagerItem *item; + guint n_items; + + gtk_list_item_tracker_unset_position (self, tracker); + + if (self->model == NULL) + return; + + n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model)); + if (position >= n_items) + position = n_items - 1; /* for n_items == 0 this underflows to GTK_INVALID_LIST_POSITION */ + + tracker->position = position; + tracker->n_before = n_before; + tracker->n_after = n_after; + + gtk_list_item_manager_ensure_items (self, NULL, G_MAXUINT); + + item = gtk_list_item_manager_get_nth (self, position, NULL); + if (item) + tracker->widget = GTK_LIST_ITEM (item->widget); + + gtk_widget_queue_resize (self->widget); +} + +guint +gtk_list_item_tracker_get_position (GtkListItemManager *self, + GtkListItemTracker *tracker) +{ + return tracker->position; +} diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h index 9a506c7573..c83fc7d92f 100644 --- a/gtk/gtklistitemmanagerprivate.h +++ b/gtk/gtklistitemmanagerprivate.h @@ -40,6 +40,7 @@ typedef struct _GtkListItemManager GtkListItemManager; typedef struct _GtkListItemManagerClass GtkListItemManagerClass; typedef struct _GtkListItemManagerItem GtkListItemManagerItem; /* sorry */ typedef struct _GtkListItemManagerItemAugment GtkListItemManagerItemAugment; +typedef struct _GtkListItemTracker GtkListItemTracker; struct _GtkListItemManagerItem { @@ -86,13 +87,16 @@ GtkSelectionModel * gtk_list_item_manager_get_model (GtkListItemMana guint gtk_list_item_manager_get_size (GtkListItemManager *self); -void gtk_list_item_manager_set_anchor (GtkListItemManager *self, +GtkListItemTracker * gtk_list_item_tracker_new (GtkListItemManager *self); +void gtk_list_item_tracker_free (GtkListItemManager *self, + GtkListItemTracker *tracker); +void gtk_list_item_tracker_set_position (GtkListItemManager *self, + GtkListItemTracker *tracker, guint position, - double align, - GHashTable *change, - guint update_start); -guint gtk_list_item_manager_get_anchor (GtkListItemManager *self, - double *align); + guint n_before, + guint n_after); +guint gtk_list_item_tracker_get_position (GtkListItemManager *self, + GtkListItemTracker *tracker); G_END_DECLS diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index e91b1b7b62..f2363d9661 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -37,6 +37,9 @@ */ #define GTK_LIST_VIEW_MAX_LIST_ITEMS 200 +/* Extra items to keep above + below every tracker */ +#define GTK_LIST_VIEW_EXTRA_ITEMS 2 + /** * SECTION:gtklistview * @title: GtkListView @@ -59,6 +62,9 @@ struct _GtkListView GtkScrollablePolicy scroll_policy[2]; int list_width; + + GtkListItemTracker *anchor; + double anchor_align; }; struct _ListRow @@ -229,6 +235,23 @@ gtk_list_view_get_list_height (GtkListView *self) return aug->height; } +static void +gtk_list_view_set_anchor (GtkListView *self, + guint position, + double align) +{ + gtk_list_item_tracker_set_position (self->item_manager, + self->anchor, + position, + GTK_LIST_VIEW_EXTRA_ITEMS + GTK_LIST_VIEW_MAX_LIST_ITEMS * align, + GTK_LIST_VIEW_EXTRA_ITEMS + GTK_LIST_VIEW_MAX_LIST_ITEMS - 1 - GTK_LIST_VIEW_MAX_LIST_ITEMS * align); + if (self->anchor_align != align) + { + self->anchor_align = align; + gtk_widget_queue_allocate (GTK_WIDGET (self)); + } +} + static void gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment, GtkListView *self) @@ -247,7 +270,7 @@ gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment, else pos = 0; - gtk_list_item_manager_set_anchor (self->item_manager, pos, 0, NULL, (guint) -1); + gtk_list_view_set_anchor (self, pos, 0); } gtk_widget_queue_allocate (GTK_WIDGET (self)); @@ -272,18 +295,17 @@ gtk_list_view_update_adjustments (GtkListView *self, { ListRow *row; guint anchor; - double anchor_align; page_size = gtk_widget_get_height (GTK_WIDGET (self)); upper = gtk_list_view_get_list_height (self); - anchor = gtk_list_item_manager_get_anchor (self->item_manager, &anchor_align); + anchor = gtk_list_item_tracker_get_position (self->item_manager, self->anchor); row = gtk_list_item_manager_get_nth (self->item_manager, anchor, NULL); if (row) value = list_row_get_y (self, row); else value = 0; - value -= anchor_align * (page_size - (row ? row->height : 0)); + value -= self->anchor_align * (page_size - (row ? row->height : 0)); } upper = MAX (upper, page_size); @@ -536,6 +558,11 @@ gtk_list_view_dispose (GObject *object) gtk_list_view_clear_adjustment (self, GTK_ORIENTATION_HORIZONTAL); gtk_list_view_clear_adjustment (self, GTK_ORIENTATION_VERTICAL); + if (self->anchor) + { + gtk_list_item_tracker_free (self->item_manager, self->anchor); + self->anchor = NULL; + } g_clear_object (&self->item_manager); G_OBJECT_CLASS (gtk_list_view_parent_class)->dispose (object); @@ -766,6 +793,7 @@ static void gtk_list_view_init (GtkListView *self) { self->item_manager = gtk_list_item_manager_new (GTK_WIDGET (self), ListRow, ListRowAugment, list_row_augment); + self->anchor = gtk_list_item_tracker_new (self->item_manager); self->adjustment[GTK_ORIENTATION_HORIZONTAL] = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); self->adjustment[GTK_ORIENTATION_VERTICAL] = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); @@ -839,6 +867,7 @@ gtk_list_view_set_model (GtkListView *self, selection_model = GTK_SELECTION_MODEL (gtk_single_selection_new (model)); gtk_list_item_manager_set_model (self->item_manager, selection_model); + gtk_list_view_set_anchor (self, 0, 0.0); g_object_unref (selection_model); } @@ -847,7 +876,6 @@ gtk_list_view_set_model (GtkListView *self, gtk_list_item_manager_set_model (self->item_manager, NULL); } - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]); } From 0174bf434573ee7b7e49505f72f26cdb4bd15f3d Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 4 Jun 2019 03:01:15 +0200 Subject: [PATCH 063/170] listview: Add gtk_list_view_set_show_separators() Do the same thing that GtkListBox does in commit 0249bd4f8a03ec473f3ca9d4324a3a8b75bd3f44 --- docs/reference/gtk/gtk4-sections.txt | 2 + gtk/gtklistview.c | 66 ++++++++++++++++++++++++++++ gtk/gtklistview.h | 6 +++ 3 files changed, 74 insertions(+) diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index f97a7f1ba9..da60b34aaf 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -436,6 +436,8 @@ GtkListView gtk_list_view_new gtk_list_view_set_model gtk_list_view_get_model +gtk_list_view_set_show_separators +gtk_list_view_get_show_separators GTK_LIST_VIEW GTK_LIST_VIEW_CLASS diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index f2363d9661..42cbc07ebd 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -29,6 +29,7 @@ #include "gtkscrollable.h" #include "gtkselectionmodel.h" #include "gtksingleselection.h" +#include "gtkstylecontext.h" #include "gtkwidgetprivate.h" /* Maximum number of list items created by the listview. @@ -60,6 +61,7 @@ struct _GtkListView GtkListItemManager *item_manager; GtkAdjustment *adjustment[2]; GtkScrollablePolicy scroll_policy[2]; + gboolean show_separators; int list_width; @@ -85,6 +87,7 @@ enum PROP_HADJUSTMENT, PROP_HSCROLL_POLICY, PROP_MODEL, + PROP_SHOW_SEPARATORS, PROP_VADJUSTMENT, PROP_VSCROLL_POLICY, @@ -600,6 +603,10 @@ gtk_list_view_get_property (GObject *object, g_value_set_object (value, self->model); break; + case PROP_SHOW_SEPARATORS: + g_value_set_boolean (value, self->show_separators); + break; + case PROP_VADJUSTMENT: g_value_set_object (value, self->adjustment[GTK_ORIENTATION_VERTICAL]); break; @@ -677,6 +684,10 @@ gtk_list_view_set_property (GObject *object, gtk_list_view_set_model (self, g_value_get_object (value)); break; + case PROP_SHOW_SEPARATORS: + gtk_list_view_set_show_separators (self, g_value_get_boolean (value)); + break; + case PROP_VADJUSTMENT: gtk_list_view_set_adjustment (self, GTK_ORIENTATION_VERTICAL, g_value_get_object (value)); break; @@ -761,6 +772,18 @@ gtk_list_view_class_init (GtkListViewClass *klass) G_TYPE_LIST_MODEL, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** + * GtkListView:show-separators: + * + * Show separators between rows + */ + properties[PROP_SHOW_SEPARATORS] = + g_param_spec_boolean ("show-separators", + P_("Show separators"), + P_("Show separators between rows"), + FALSE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_properties (gobject_class, N_PROPS, properties); /** @@ -897,3 +920,46 @@ gtk_list_view_set_functions (GtkListView *self, g_object_unref (factory); } +/** + * gtk_list_view_set_show_separators: + * @self: a #GtkListView + * @show_separators: %TRUE to show separators + * + * Sets whether the list box should show separators + * between rows. + */ +void +gtk_list_view_set_show_separators (GtkListView *self, + gboolean show_separators) +{ + g_return_if_fail (GTK_IS_LIST_VIEW (self)); + + if (self->show_separators == show_separators) + return; + + self->show_separators = show_separators; + + if (show_separators) + gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (self)), "separators"); + else + gtk_style_context_remove_class (gtk_widget_get_style_context (GTK_WIDGET (self)), "separators"); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_SEPARATORS]); +} + +/** + * gtk_list_view_get_show_separators: + * @self: a #GtkListView + * + * Returns whether the list box should show separators + * between rows. + * + * Returns: %TRUE if the list box shows separators + */ +gboolean +gtk_list_view_get_show_separators (GtkListView *self) +{ + g_return_val_if_fail (GTK_IS_LIST_VIEW (self), FALSE); + + return self->show_separators; +} diff --git a/gtk/gtklistview.h b/gtk/gtklistview.h index 4d1a0a5d77..77d8d502ab 100644 --- a/gtk/gtklistview.h +++ b/gtk/gtklistview.h @@ -49,6 +49,12 @@ void gtk_list_view_set_functions (GtkListView gpointer user_data, GDestroyNotify user_destroy); +GDK_AVAILABLE_IN_ALL +void gtk_list_view_set_show_separators (GtkListView *self, + gboolean show_separators); +GDK_AVAILABLE_IN_ALL +gboolean gtk_list_view_get_show_separators (GtkListView *self); + G_END_DECLS #endif /* __GTK_LIST_VIEW_H__ */ From 10b967ae1f65ec269cde4b5c83b0dd777bf46301 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sun, 9 Jun 2019 22:50:46 +0200 Subject: [PATCH 064/170] listitemfactory: Sanitize APIs Make sure the APIs follow a predictable path: setup bind rebind/update (0-N times) unbind teardown This is the first step towards providing multiple different factories. --- gtk/gtklistitemfactory.c | 51 ++++++++++++++++++++++----------- gtk/gtklistitemfactoryprivate.h | 22 ++++++++++++++ gtk/gtklistitemmanager.c | 3 +- 3 files changed, 59 insertions(+), 17 deletions(-) diff --git a/gtk/gtklistitemfactory.c b/gtk/gtklistitemfactory.c index 012ed3b123..510c04889b 100644 --- a/gtk/gtklistitemfactory.c +++ b/gtk/gtklistitemfactory.c @@ -71,21 +71,6 @@ * Reusing factories across different views is allowed, but very uncommon. */ -struct _GtkListItemFactory -{ - GObject parent_instance; - - GtkListItemSetupFunc setup_func; - GtkListItemBindFunc bind_func; - gpointer user_data; - GDestroyNotify user_destroy; -}; - -struct _GtkListItemFactoryClass -{ - GObjectClass parent_class; -}; - G_DEFINE_TYPE (GtkListItemFactory, gtk_list_item_factory, G_TYPE_OBJECT) static void @@ -135,7 +120,7 @@ gtk_list_item_factory_new (GtkListItemSetupFunc setup_func, void gtk_list_item_factory_setup (GtkListItemFactory *self, - GtkListItem *list_item) + GtkListItem *list_item) { g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self)); @@ -165,6 +150,28 @@ gtk_list_item_factory_bind (GtkListItemFactory *self, g_object_thaw_notify (G_OBJECT (list_item)); } +void +gtk_list_item_factory_rebind (GtkListItemFactory *self, + GtkListItem *list_item, + guint position, + gpointer item, + gboolean selected) +{ + g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self)); + g_return_if_fail (GTK_IS_LIST_ITEM (list_item)); + + g_object_freeze_notify (G_OBJECT (list_item)); + + gtk_list_item_set_item (list_item, item); + gtk_list_item_set_position (list_item, position); + gtk_list_item_set_selected (list_item, selected); + + if (self->bind_func) + self->bind_func (list_item, self->user_data); + + g_object_thaw_notify (G_OBJECT (list_item)); +} + void gtk_list_item_factory_update (GtkListItemFactory *self, GtkListItem *list_item, @@ -197,3 +204,15 @@ gtk_list_item_factory_unbind (GtkListItemFactory *self, g_object_thaw_notify (G_OBJECT (list_item)); } + +void +gtk_list_item_factory_teardown (GtkListItemFactory *self, + GtkListItem *list_item) +{ + g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self)); + + gtk_list_item_set_child (list_item, NULL); + + gtk_list_item_set_selectable (list_item, TRUE); +} + diff --git a/gtk/gtklistitemfactoryprivate.h b/gtk/gtklistitemfactoryprivate.h index dc538f09fa..34582257bc 100644 --- a/gtk/gtklistitemfactoryprivate.h +++ b/gtk/gtklistitemfactoryprivate.h @@ -36,6 +36,21 @@ G_BEGIN_DECLS typedef struct _GtkListItemFactory GtkListItemFactory; typedef struct _GtkListItemFactoryClass GtkListItemFactoryClass; +struct _GtkListItemFactory +{ + GObject parent_instance; + + GtkListItemSetupFunc setup_func; + GtkListItemBindFunc bind_func; + gpointer user_data; + GDestroyNotify user_destroy; +}; + +struct _GtkListItemFactoryClass +{ + GObjectClass parent_class; +}; + GType gtk_list_item_factory_get_type (void) G_GNUC_CONST; GtkListItemFactory * gtk_list_item_factory_new (GtkListItemSetupFunc setup_func, @@ -45,12 +60,19 @@ GtkListItemFactory * gtk_list_item_factory_new (GtkListItemSetu void gtk_list_item_factory_setup (GtkListItemFactory *self, GtkListItem *list_item); +void gtk_list_item_factory_teardown (GtkListItemFactory *self, + GtkListItem *list_item); void gtk_list_item_factory_bind (GtkListItemFactory *self, GtkListItem *list_item, guint position, gpointer item, gboolean selected); +void gtk_list_item_factory_rebind (GtkListItemFactory *self, + GtkListItem *list_item, + guint position, + gpointer item, + gboolean selected); void gtk_list_item_factory_update (GtkListItemFactory *self, GtkListItem *list_item, guint position, diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index 35a894ed51..fde93c9708 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -1007,7 +1007,7 @@ gtk_list_item_manager_move_list_item (GtkListItemManager *self, item = g_list_model_get_item (G_LIST_MODEL (self->model), position); selected = gtk_selection_model_is_selected (self->model, position); - gtk_list_item_factory_bind (self->factory, GTK_LIST_ITEM (list_item), position, item, selected); + gtk_list_item_factory_rebind (self->factory, GTK_LIST_ITEM (list_item), position, item, selected); gtk_widget_insert_after (list_item, _gtk_widget_get_parent (list_item), prev_sibling); g_object_unref (item); } @@ -1063,6 +1063,7 @@ gtk_list_item_manager_release_list_item (GtkListItemManager *self, } gtk_list_item_factory_unbind (self->factory, GTK_LIST_ITEM (item)); + gtk_list_item_factory_teardown (self->factory, GTK_LIST_ITEM (item)); gtk_widget_unparent (item); } From 824326a02928321db0e751b98dbbee25a4c031bf Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 10 Jun 2019 00:17:44 +0200 Subject: [PATCH 065/170] listitemfactory: vfuncify No functional changes other than a new indirection. --- gtk/gtklistitemfactory.c | 118 +++++++++++++++++++++++--------- gtk/gtklistitemfactoryprivate.h | 28 ++++++++ 2 files changed, 115 insertions(+), 31 deletions(-) diff --git a/gtk/gtklistitemfactory.c b/gtk/gtklistitemfactory.c index 510c04889b..1025173bd8 100644 --- a/gtk/gtklistitemfactory.c +++ b/gtk/gtklistitemfactory.c @@ -73,6 +73,72 @@ G_DEFINE_TYPE (GtkListItemFactory, gtk_list_item_factory, G_TYPE_OBJECT) +static void +gtk_list_item_factory_default_setup (GtkListItemFactory *self, + GtkListItem *list_item) +{ + if (self->setup_func) + self->setup_func (list_item, self->user_data); +} + +static void +gtk_list_item_factory_default_teardown (GtkListItemFactory *self, + GtkListItem *list_item) +{ + gtk_list_item_set_child (list_item, NULL); + gtk_list_item_set_selectable (list_item, TRUE); + +} + +static void +gtk_list_item_factory_default_bind (GtkListItemFactory *self, + GtkListItem *list_item, + guint position, + gpointer item, + gboolean selected) +{ + gtk_list_item_set_item (list_item, item); + gtk_list_item_set_position (list_item, position); + gtk_list_item_set_selected (list_item, selected); + + if (self->bind_func) + self->bind_func (list_item, self->user_data); +} + +static void +gtk_list_item_factory_default_rebind (GtkListItemFactory *self, + GtkListItem *list_item, + guint position, + gpointer item, + gboolean selected) +{ + gtk_list_item_set_item (list_item, item); + gtk_list_item_set_position (list_item, position); + gtk_list_item_set_selected (list_item, selected); + + if (self->bind_func) + self->bind_func (list_item, self->user_data); +} + +static void +gtk_list_item_factory_default_update (GtkListItemFactory *self, + GtkListItem *list_item, + guint position, + gboolean selected) +{ + gtk_list_item_set_position (list_item, position); + gtk_list_item_set_selected (list_item, selected); +} + +static void +gtk_list_item_factory_default_unbind (GtkListItemFactory *self, + GtkListItem *list_item) +{ + gtk_list_item_set_item (list_item, NULL); + gtk_list_item_set_position (list_item, 0); + gtk_list_item_set_selected (list_item, FALSE); +} + static void gtk_list_item_factory_finalize (GObject *object) { @@ -90,6 +156,13 @@ gtk_list_item_factory_class_init (GtkListItemFactoryClass *klass) GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = gtk_list_item_factory_finalize; + + klass->setup = gtk_list_item_factory_default_setup; + klass->teardown = gtk_list_item_factory_default_teardown; + klass->bind = gtk_list_item_factory_default_bind; + klass->rebind = gtk_list_item_factory_default_rebind; + klass->update = gtk_list_item_factory_default_update; + klass->unbind = gtk_list_item_factory_default_unbind; } static void @@ -124,8 +197,16 @@ gtk_list_item_factory_setup (GtkListItemFactory *self, { g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self)); - if (self->setup_func) - self->setup_func (list_item, self->user_data); + GTK_LIST_ITEM_FACTORY_GET_CLASS (self)->setup (self, list_item); +} + +void +gtk_list_item_factory_teardown (GtkListItemFactory *self, + GtkListItem *list_item) +{ + g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self)); + + GTK_LIST_ITEM_FACTORY_GET_CLASS (self)->teardown (self, list_item); } void @@ -140,12 +221,7 @@ gtk_list_item_factory_bind (GtkListItemFactory *self, g_object_freeze_notify (G_OBJECT (list_item)); - gtk_list_item_set_item (list_item, item); - gtk_list_item_set_position (list_item, position); - gtk_list_item_set_selected (list_item, selected); - - if (self->bind_func) - self->bind_func (list_item, self->user_data); + GTK_LIST_ITEM_FACTORY_GET_CLASS (self)->bind (self, list_item, position, item, selected); g_object_thaw_notify (G_OBJECT (list_item)); } @@ -162,12 +238,7 @@ gtk_list_item_factory_rebind (GtkListItemFactory *self, g_object_freeze_notify (G_OBJECT (list_item)); - gtk_list_item_set_item (list_item, item); - gtk_list_item_set_position (list_item, position); - gtk_list_item_set_selected (list_item, selected); - - if (self->bind_func) - self->bind_func (list_item, self->user_data); + GTK_LIST_ITEM_FACTORY_GET_CLASS (self)->rebind (self, list_item, position, item, selected); g_object_thaw_notify (G_OBJECT (list_item)); } @@ -183,8 +254,7 @@ gtk_list_item_factory_update (GtkListItemFactory *self, g_object_freeze_notify (G_OBJECT (list_item)); - gtk_list_item_set_position (list_item, position); - gtk_list_item_set_selected (list_item, selected); + GTK_LIST_ITEM_FACTORY_GET_CLASS (self)->update (self, list_item, position, selected); g_object_thaw_notify (G_OBJECT (list_item)); } @@ -198,21 +268,7 @@ gtk_list_item_factory_unbind (GtkListItemFactory *self, g_object_freeze_notify (G_OBJECT (list_item)); - gtk_list_item_set_item (list_item, NULL); - gtk_list_item_set_position (list_item, 0); - gtk_list_item_set_selected (list_item, FALSE); + GTK_LIST_ITEM_FACTORY_GET_CLASS (self)->unbind (self, list_item); g_object_thaw_notify (G_OBJECT (list_item)); } - -void -gtk_list_item_factory_teardown (GtkListItemFactory *self, - GtkListItem *list_item) -{ - g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self)); - - gtk_list_item_set_child (list_item, NULL); - - gtk_list_item_set_selectable (list_item, TRUE); -} - diff --git a/gtk/gtklistitemfactoryprivate.h b/gtk/gtklistitemfactoryprivate.h index 34582257bc..98ca4b350e 100644 --- a/gtk/gtklistitemfactoryprivate.h +++ b/gtk/gtklistitemfactoryprivate.h @@ -49,6 +49,34 @@ struct _GtkListItemFactory struct _GtkListItemFactoryClass { GObjectClass parent_class; + + /* setup @list_item so it can be bound */ + void (* setup) (GtkListItemFactory *self, + GtkListItem *list_item); + /* undo the effects of GtkListItemFactoryClass::setup() */ + void (* teardown) (GtkListItemFactory *self, + GtkListItem *list_item); + + /* bind @list_item to the given @item, which is in @position and @selected state */ + void (* bind) (GtkListItemFactory *self, + GtkListItem *list_item, + guint position, + gpointer item, + gboolean selected); + /* unbind the current item and bind a new one */ + void (* rebind) (GtkListItemFactory *self, + GtkListItem *list_item, + guint position, + gpointer item, + gboolean selected); + /* like GtkListItemFactoryClass::rebind(), but the item didn't change */ + void (* update) (GtkListItemFactory *self, + GtkListItem *list_item, + guint position, + gboolean selected); + /* undo the effects of GtkListItemFactoryClass::bind() */ + void (* unbind) (GtkListItemFactory *self, + GtkListItem *list_item); }; GType gtk_list_item_factory_get_type (void) G_GNUC_CONST; From 2227fb957fe511cd954fe39f26b536f2d00f11f6 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 10 Jun 2019 03:43:52 +0200 Subject: [PATCH 066/170] listitemfactory: Split implementation out .. into gtkfunctionslistitemfactory.c Now we can add a different implmenetation. --- gtk/gtkfunctionslistitemfactory.c | 134 +++++++++++++++++++++++ gtk/gtkfunctionslistitemfactoryprivate.h | 48 ++++++++ gtk/gtklistitemfactory.c | 44 -------- gtk/gtklistitemfactoryprivate.h | 10 -- gtk/gtklistview.c | 6 +- gtk/meson.build | 1 + 6 files changed, 186 insertions(+), 57 deletions(-) create mode 100644 gtk/gtkfunctionslistitemfactory.c create mode 100644 gtk/gtkfunctionslistitemfactoryprivate.h diff --git a/gtk/gtkfunctionslistitemfactory.c b/gtk/gtkfunctionslistitemfactory.c new file mode 100644 index 0000000000..6841a2a36b --- /dev/null +++ b/gtk/gtkfunctionslistitemfactory.c @@ -0,0 +1,134 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gtkfunctionslistitemfactoryprivate.h" + +#include "gtklistitemprivate.h" + +struct _GtkFunctionsListItemFactory +{ + GtkListItemFactory parent_instance; + + GtkListItemSetupFunc setup_func; + GtkListItemBindFunc bind_func; + gpointer user_data; + GDestroyNotify user_destroy; +}; + +struct _GtkFunctionsListItemFactoryClass +{ + GtkListItemFactoryClass parent_class; +}; + +G_DEFINE_TYPE (GtkFunctionsListItemFactory, gtk_functions_list_item_factory, GTK_TYPE_LIST_ITEM_FACTORY) + +static void +gtk_functions_list_item_factory_setup (GtkListItemFactory *factory, + GtkListItem *list_item) +{ + GtkFunctionsListItemFactory *self = GTK_FUNCTIONS_LIST_ITEM_FACTORY (factory); + + GTK_LIST_ITEM_FACTORY_CLASS (gtk_functions_list_item_factory_parent_class)->setup (factory, list_item); + + if (self->setup_func) + self->setup_func (list_item, self->user_data); +} + +static void +gtk_functions_list_item_factory_bind (GtkListItemFactory *factory, + GtkListItem *list_item, + guint position, + gpointer item, + gboolean selected) +{ + GtkFunctionsListItemFactory *self = GTK_FUNCTIONS_LIST_ITEM_FACTORY (factory); + + GTK_LIST_ITEM_FACTORY_CLASS (gtk_functions_list_item_factory_parent_class)->bind (factory, list_item, position, item, selected); + + if (self->bind_func) + self->bind_func (list_item, self->user_data); +} + +static void +gtk_functions_list_item_factory_rebind (GtkListItemFactory *factory, + GtkListItem *list_item, + guint position, + gpointer item, + gboolean selected) +{ + GtkFunctionsListItemFactory *self = GTK_FUNCTIONS_LIST_ITEM_FACTORY (factory); + + GTK_LIST_ITEM_FACTORY_CLASS (gtk_functions_list_item_factory_parent_class)->bind (factory, list_item, position, item, selected); + + if (self->bind_func) + self->bind_func (list_item, self->user_data); +} + +static void +gtk_functions_list_item_factory_finalize (GObject *object) +{ + GtkFunctionsListItemFactory *self = GTK_FUNCTIONS_LIST_ITEM_FACTORY (object); + + if (self->user_destroy) + self->user_destroy (self->user_data); + + G_OBJECT_CLASS (gtk_functions_list_item_factory_parent_class)->finalize (object); +} + +static void +gtk_functions_list_item_factory_class_init (GtkFunctionsListItemFactoryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkListItemFactoryClass *factory_class = GTK_LIST_ITEM_FACTORY_CLASS (klass); + + object_class->finalize = gtk_functions_list_item_factory_finalize; + + factory_class->setup = gtk_functions_list_item_factory_setup; + factory_class->bind = gtk_functions_list_item_factory_bind; + factory_class->rebind = gtk_functions_list_item_factory_rebind; +} + +static void +gtk_functions_list_item_factory_init (GtkFunctionsListItemFactory *self) +{ +} + +GtkListItemFactory * +gtk_functions_list_item_factory_new (GtkListItemSetupFunc setup_func, + GtkListItemBindFunc bind_func, + gpointer user_data, + GDestroyNotify user_destroy) +{ + GtkFunctionsListItemFactory *self; + + g_return_val_if_fail (setup_func || bind_func, NULL); + g_return_val_if_fail (user_data != NULL || user_destroy == NULL, NULL); + + self = g_object_new (GTK_TYPE_FUNCTIONS_LIST_ITEM_FACTORY, NULL); + + self->setup_func = setup_func; + self->bind_func = bind_func; + self->user_data = user_data; + self->user_destroy = user_destroy; + + return GTK_LIST_ITEM_FACTORY (self); +} + diff --git a/gtk/gtkfunctionslistitemfactoryprivate.h b/gtk/gtkfunctionslistitemfactoryprivate.h new file mode 100644 index 0000000000..aeac050a28 --- /dev/null +++ b/gtk/gtkfunctionslistitemfactoryprivate.h @@ -0,0 +1,48 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + + +#ifndef __GTK_FUNCTIONS_LIST_ITEM_FACTORY_H__ +#define __GTK_FUNCTIONS_LIST_ITEM_FACTORY_H__ + +#include "gtklistitemfactoryprivate.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_FUNCTIONS_LIST_ITEM_FACTORY (gtk_functions_list_item_factory_get_type ()) +#define GTK_FUNCTIONS_LIST_ITEM_FACTORY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_FUNCTIONS_LIST_ITEM_FACTORY, GtkFunctionsListItemFactory)) +#define GTK_FUNCTIONS_LIST_ITEM_FACTORY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_FUNCTIONS_LIST_ITEM_FACTORY, GtkFunctionsListItemFactoryClass)) +#define GTK_IS_FUNCTIONS_LIST_ITEM_FACTORY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_FUNCTIONS_LIST_ITEM_FACTORY)) +#define GTK_IS_FUNCTIONS_LIST_ITEM_FACTORY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_FUNCTIONS_LIST_ITEM_FACTORY)) +#define GTK_FUNCTIONS_LIST_ITEM_FACTORY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_FUNCTIONS_LIST_ITEM_FACTORY, GtkFunctionsListItemFactoryClass)) + +typedef struct _GtkFunctionsListItemFactory GtkFunctionsListItemFactory; +typedef struct _GtkFunctionsListItemFactoryClass GtkFunctionsListItemFactoryClass; + +GType gtk_functions_list_item_factory_get_type (void) G_GNUC_CONST; + +GtkListItemFactory * gtk_functions_list_item_factory_new (GtkListItemSetupFunc setup_func, + GtkListItemBindFunc bind_func, + gpointer user_data, + GDestroyNotify user_destroy); + + +G_END_DECLS + +#endif /* __GTK_FUNCTIONS_LIST_ITEM_FACTORY_H__ */ diff --git a/gtk/gtklistitemfactory.c b/gtk/gtklistitemfactory.c index 1025173bd8..a6012a9285 100644 --- a/gtk/gtklistitemfactory.c +++ b/gtk/gtklistitemfactory.c @@ -77,8 +77,6 @@ static void gtk_list_item_factory_default_setup (GtkListItemFactory *self, GtkListItem *list_item) { - if (self->setup_func) - self->setup_func (list_item, self->user_data); } static void @@ -100,9 +98,6 @@ gtk_list_item_factory_default_bind (GtkListItemFactory *self, gtk_list_item_set_item (list_item, item); gtk_list_item_set_position (list_item, position); gtk_list_item_set_selected (list_item, selected); - - if (self->bind_func) - self->bind_func (list_item, self->user_data); } static void @@ -115,9 +110,6 @@ gtk_list_item_factory_default_rebind (GtkListItemFactory *self, gtk_list_item_set_item (list_item, item); gtk_list_item_set_position (list_item, position); gtk_list_item_set_selected (list_item, selected); - - if (self->bind_func) - self->bind_func (list_item, self->user_data); } static void @@ -139,24 +131,9 @@ gtk_list_item_factory_default_unbind (GtkListItemFactory *self, gtk_list_item_set_selected (list_item, FALSE); } -static void -gtk_list_item_factory_finalize (GObject *object) -{ - GtkListItemFactory *self = GTK_LIST_ITEM_FACTORY (object); - - if (self->user_destroy) - self->user_destroy (self->user_data); - - G_OBJECT_CLASS (gtk_list_item_factory_parent_class)->finalize (object); -} - static void gtk_list_item_factory_class_init (GtkListItemFactoryClass *klass) { - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = gtk_list_item_factory_finalize; - klass->setup = gtk_list_item_factory_default_setup; klass->teardown = gtk_list_item_factory_default_teardown; klass->bind = gtk_list_item_factory_default_bind; @@ -170,27 +147,6 @@ gtk_list_item_factory_init (GtkListItemFactory *self) { } -GtkListItemFactory * -gtk_list_item_factory_new (GtkListItemSetupFunc setup_func, - GtkListItemBindFunc bind_func, - gpointer user_data, - GDestroyNotify user_destroy) -{ - GtkListItemFactory *self; - - g_return_val_if_fail (setup_func || bind_func, NULL); - g_return_val_if_fail (user_data != NULL || user_destroy == NULL, NULL); - - self = g_object_new (GTK_TYPE_LIST_ITEM_FACTORY, NULL); - - self->setup_func = setup_func; - self->bind_func = bind_func; - self->user_data = user_data; - self->user_destroy = user_destroy; - - return self; -} - void gtk_list_item_factory_setup (GtkListItemFactory *self, GtkListItem *list_item) diff --git a/gtk/gtklistitemfactoryprivate.h b/gtk/gtklistitemfactoryprivate.h index 98ca4b350e..dc32429dd2 100644 --- a/gtk/gtklistitemfactoryprivate.h +++ b/gtk/gtklistitemfactoryprivate.h @@ -39,11 +39,6 @@ typedef struct _GtkListItemFactoryClass GtkListItemFactoryClass; struct _GtkListItemFactory { GObject parent_instance; - - GtkListItemSetupFunc setup_func; - GtkListItemBindFunc bind_func; - gpointer user_data; - GDestroyNotify user_destroy; }; struct _GtkListItemFactoryClass @@ -81,11 +76,6 @@ struct _GtkListItemFactoryClass GType gtk_list_item_factory_get_type (void) G_GNUC_CONST; -GtkListItemFactory * gtk_list_item_factory_new (GtkListItemSetupFunc setup_func, - GtkListItemBindFunc bind_func, - gpointer user_data, - GDestroyNotify user_destroy); - void gtk_list_item_factory_setup (GtkListItemFactory *self, GtkListItem *list_item); void gtk_list_item_factory_teardown (GtkListItemFactory *self, diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index 42cbc07ebd..589acc8239 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -23,9 +23,9 @@ #include "gtkadjustment.h" #include "gtkintl.h" -#include "gtkrbtreeprivate.h" -#include "gtklistitemfactoryprivate.h" +#include "gtkfunctionslistitemfactoryprivate.h" #include "gtklistitemmanagerprivate.h" +#include "gtkrbtreeprivate.h" #include "gtkscrollable.h" #include "gtkselectionmodel.h" #include "gtksingleselection.h" @@ -915,7 +915,7 @@ gtk_list_view_set_functions (GtkListView *self, g_return_if_fail (setup_func || bind_func); g_return_if_fail (user_data != NULL || user_destroy == NULL); - factory = gtk_list_item_factory_new (setup_func, bind_func, user_data, user_destroy); + factory = gtk_functions_list_item_factory_new (setup_func, bind_func, user_data, user_destroy); gtk_list_item_manager_set_factory (self->item_manager, factory); g_object_unref (factory); } diff --git a/gtk/meson.build b/gtk/meson.build index f45cca1cdd..befe9e92a1 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -242,6 +242,7 @@ gtk_public_sources = files([ 'gtkfontchooserutils.c', 'gtkfontchooserwidget.c', 'gtkframe.c', + 'gtkfunctionslistitemfactory.c', 'gtkgesture.c', 'gtkgesturedrag.c', 'gtkgesturelongpress.c', From cfb293d3966de730dc633002203ad9188e994a55 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 10 Jun 2019 04:58:45 +0200 Subject: [PATCH 067/170] listitemfactory: Add a factory for ui files Reuse - - both - - - both - - - both - From 084725e280f532e3a4ec203adb066d0abef8e131 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 7 Nov 2019 12:17:53 +0100 Subject: [PATCH 139/170] columnview: Fix styling with Adwaita - Use "treeview" as the node name - Add .view style class --- gtk/gtkcolumnview.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gtk/gtkcolumnview.c b/gtk/gtkcolumnview.c index 932b8d74d1..00b5e3a21b 100644 --- a/gtk/gtkcolumnview.c +++ b/gtk/gtkcolumnview.c @@ -25,6 +25,7 @@ #include "gtkbuildable.h" #include "gtkcolumnlistitemfactoryprivate.h" #include "gtkcolumnviewcolumnprivate.h" +#include "gtkcssnodeprivate.h" #include "gtkintl.h" #include "gtklistview.h" #include "gtkmain.h" @@ -342,8 +343,7 @@ gtk_column_view_class_init (GtkColumnViewClass *klass) G_TYPE_FROM_CLASS (gobject_class), g_cclosure_marshal_VOID__UINTv); - gtk_widget_class_set_css_name (widget_class, I_("columnview")); - gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT); + gtk_widget_class_set_css_name (widget_class, I_("treeview")); } static void @@ -359,6 +359,10 @@ gtk_column_view_init (GtkColumnView *self) g_signal_connect (self->listview, "activate", G_CALLBACK (gtk_column_view_activate_cb), self); gtk_widget_set_parent (GTK_WIDGET (self->listview), GTK_WIDGET (self)); + gtk_css_node_add_class (gtk_widget_get_css_node (GTK_WIDGET (self)), + g_quark_from_static_string (I_("view"))); + + gtk_widget_set_layout_manager (GTK_WIDGET (self), gtk_box_layout_new (GTK_ORIENTATION_VERTICAL)); gtk_widget_set_overflow (GTK_WIDGET (self), GTK_OVERFLOW_HIDDEN); } From 767e7cb06f35d1df6f380c0091227870e6ef497d Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 7 Nov 2019 18:56:52 +0100 Subject: [PATCH 140/170] constraint-editor: Don't poke around in widget internals --- demos/constraint-editor/guide-editor.c | 56 -------------------------- 1 file changed, 56 deletions(-) diff --git a/demos/constraint-editor/guide-editor.c b/demos/constraint-editor/guide-editor.c index 228b9db739..74dfe22f09 100644 --- a/demos/constraint-editor/guide-editor.c +++ b/demos/constraint-editor/guide-editor.c @@ -184,58 +184,6 @@ max_input (GtkSpinButton *spin_button, return FALSE; } -static gboolean -min_output (GtkSpinButton *spin_button) -{ - GtkAdjustment *adjustment; - double value; - GtkWidget *box, *text; - - adjustment = gtk_spin_button_get_adjustment (spin_button); - value = gtk_adjustment_get_value (adjustment); - - box = gtk_widget_get_first_child (GTK_WIDGET (spin_button)); - text = gtk_widget_get_first_child (box); - - if (value == 0.0) - { - gtk_editable_set_text (GTK_EDITABLE (spin_button), ""); - gtk_text_set_placeholder_text (GTK_TEXT (text), "unset"); - return TRUE; - } - else - { - gtk_text_set_placeholder_text (GTK_TEXT (text), ""); - return FALSE; - } -} - -static gboolean -max_output (GtkSpinButton *spin_button) -{ - GtkAdjustment *adjustment; - double value; - GtkWidget *box, *text; - - adjustment = gtk_spin_button_get_adjustment (spin_button); - value = gtk_adjustment_get_value (adjustment); - - box = gtk_widget_get_first_child (GTK_WIDGET (spin_button)); - text = gtk_widget_get_first_child (box); - - if (value == (double)G_MAXINT) - { - gtk_editable_set_text (GTK_EDITABLE (spin_button), ""); - gtk_text_set_placeholder_text (GTK_TEXT (text), "unset"); - return TRUE; - } - else - { - gtk_text_set_placeholder_text (GTK_TEXT (text), ""); - return FALSE; - } -} - static void guide_editor_constructed (GObject *object) { @@ -244,16 +192,12 @@ guide_editor_constructed (GObject *object) guide_strength_combo (editor->strength); g_signal_connect (editor->min_width, "input", G_CALLBACK (min_input), NULL); - g_signal_connect (editor->min_width, "output", G_CALLBACK (min_output), NULL); g_signal_connect (editor->min_height, "input", G_CALLBACK (min_input), NULL); - g_signal_connect (editor->min_height, "output", G_CALLBACK (min_output), NULL); g_signal_connect (editor->max_width, "input", G_CALLBACK (max_input), NULL); - g_signal_connect (editor->max_width, "output", G_CALLBACK (max_output), NULL); g_signal_connect (editor->max_height, "input", G_CALLBACK (max_input), NULL); - g_signal_connect (editor->max_height, "output", G_CALLBACK (max_output), NULL); if (editor->guide) { From 326cb1148b8a46416239bd00782ad1a07109fbcd Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 7 Nov 2019 23:40:47 +0100 Subject: [PATCH 141/170] columnview: Add a custom LayoutManager The ColumnView now allocates column widths first and then the individual rows use the new layout manager which looks at the column allocations to allocate their children. --- docs/reference/gtk/meson.build | 1 + gtk/gtkcolumnlistitemfactory.c | 3 +- gtk/gtkcolumnview.c | 119 +++++++++++++++++++++++- gtk/gtkcolumnviewcell.c | 32 ++----- gtk/gtkcolumnviewcellprivate.h | 5 +- gtk/gtkcolumnviewcolumn.c | 28 +++++- gtk/gtkcolumnviewcolumnprivate.h | 6 ++ gtk/gtkcolumnviewlayout.c | 149 +++++++++++++++++++++++++++++++ gtk/gtkcolumnviewlayoutprivate.h | 35 ++++++++ gtk/gtkcolumnviewprivate.h | 3 + gtk/meson.build | 5 +- 11 files changed, 354 insertions(+), 32 deletions(-) create mode 100644 gtk/gtkcolumnviewlayout.c create mode 100644 gtk/gtkcolumnviewlayoutprivate.h diff --git a/docs/reference/gtk/meson.build b/docs/reference/gtk/meson.build index e002c3feda..797784e81c 100644 --- a/docs/reference/gtk/meson.build +++ b/docs/reference/gtk/meson.build @@ -27,6 +27,7 @@ private_headers = [ 'gtkcolumnlistitemfactoryprivate.h', 'gtkcolumnviewcellprivate.h', 'gtkcolumnviewcolumnprivate.h', + 'gtkcolumnviewlayoutprivate.h', 'gtkcolumnviewprivate.h', 'gtkcomboboxprivate.h', 'gtkconstraintexpressionprivate.h', diff --git a/gtk/gtkcolumnlistitemfactory.c b/gtk/gtkcolumnlistitemfactory.c index ac5c3e1766..0b2151f12f 100644 --- a/gtk/gtkcolumnlistitemfactory.c +++ b/gtk/gtkcolumnlistitemfactory.c @@ -23,6 +23,7 @@ #include "gtkboxlayout.h" #include "gtkcolumnviewcolumnprivate.h" +#include "gtkcolumnviewlayoutprivate.h" #include "gtklistitemfactoryprivate.h" #include "gtklistitemprivate.h" @@ -51,7 +52,7 @@ gtk_column_list_item_factory_setup (GtkListItemFactory *factory, /* FIXME: evil */ gtk_widget_set_layout_manager (GTK_WIDGET (widget), - gtk_box_layout_new (GTK_ORIENTATION_HORIZONTAL)); + gtk_column_view_layout_new (self->view)); GTK_LIST_ITEM_FACTORY_CLASS (gtk_column_list_item_factory_parent_class)->setup (factory, widget, list_item); diff --git a/gtk/gtkcolumnview.c b/gtk/gtkcolumnview.c index 00b5e3a21b..8582e7293d 100644 --- a/gtk/gtkcolumnview.c +++ b/gtk/gtkcolumnview.c @@ -118,6 +118,93 @@ G_DEFINE_TYPE_WITH_CODE (GtkColumnView, gtk_column_view, GTK_TYPE_WIDGET, static GParamSpec *properties[N_PROPS] = { NULL, }; static guint signals[LAST_SIGNAL] = { 0 }; +static void +gtk_column_view_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + GtkColumnView *self = GTK_COLUMN_VIEW (widget); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + gtk_column_view_measure_across (self, minimum, natural); + } + else + { + gtk_widget_measure (GTK_WIDGET (self->listview), + orientation, for_size, + minimum, natural, + minimum_baseline, natural_baseline); + } +} + +static int +gtk_column_view_allocate_columns (GtkColumnView *self, + int width) +{ + GtkScrollablePolicy scroll_policy; + int col_min, col_nat, widget_min, widget_nat, extra, col_size, x; + guint i; + + gtk_column_view_measure_across (self, &col_min, &col_nat); + gtk_widget_measure (GTK_WIDGET (self->listview), + GTK_ORIENTATION_HORIZONTAL, -1, + &widget_min, &widget_nat, + NULL, NULL); + + scroll_policy = gtk_scrollable_get_hscroll_policy (GTK_SCROLLABLE (self->listview)); + if (scroll_policy == GTK_SCROLL_MINIMUM) + { + extra = widget_min - col_min; + col_size = col_min; + } + else + { + extra = widget_nat - col_nat; + col_size = col_nat; + } + width -= extra; + width = MAX (width, col_size); + + x = 0; + for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (self->columns)); i++) + { + GtkColumnViewColumn *column; + + column = g_list_model_get_item (G_LIST_MODEL (self->columns), i); + gtk_column_view_column_measure (column, &col_min, &col_nat); + if (scroll_policy == GTK_SCROLL_MINIMUM) + col_size = col_min; + else + col_size = col_nat; + + gtk_column_view_column_allocate (column, x, col_size); + x += col_size; + + g_object_unref (column); + } + + return width + extra; +} + +static void +gtk_column_view_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + GtkColumnView *self = GTK_COLUMN_VIEW (widget); + int full_width; + + full_width = gtk_column_view_allocate_columns (self, width); + + gtk_widget_allocate (GTK_WIDGET (self->listview), full_width, height, baseline, NULL); +} + static void gtk_column_view_activate_cb (GtkListView *listview, guint pos, @@ -261,6 +348,9 @@ gtk_column_view_class_init (GtkColumnViewClass *klass) GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gpointer iface; + widget_class->measure = gtk_column_view_measure; + widget_class->size_allocate = gtk_column_view_allocate; + gobject_class->dispose = gtk_column_view_dispose; gobject_class->finalize = gtk_column_view_finalize; gobject_class->get_property = gtk_column_view_get_property; @@ -362,7 +452,6 @@ gtk_column_view_init (GtkColumnView *self) gtk_css_node_add_class (gtk_widget_get_css_node (GTK_WIDGET (self)), g_quark_from_static_string (I_("view"))); - gtk_widget_set_layout_manager (GTK_WIDGET (self), gtk_box_layout_new (GTK_ORIENTATION_VERTICAL)); gtk_widget_set_overflow (GTK_WIDGET (self), GTK_OVERFLOW_HIDDEN); } @@ -531,3 +620,31 @@ gtk_column_view_remove_column (GtkColumnView *self, g_list_store_remove (self->columns, i); } +void +gtk_column_view_measure_across (GtkColumnView *self, + int *minimum, + int *natural) +{ + guint i; + int min, nat; + + min = 0; + nat = 0; + + for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (self->columns)); i++) + { + GtkColumnViewColumn *column; + int col_min, col_nat; + + column = g_list_model_get_item (G_LIST_MODEL (self->columns), i); + gtk_column_view_column_measure (column, &col_min, &col_nat); + min += col_min; + nat += col_nat; + + g_object_unref (column); + } + + *minimum = min; + *natural = nat; +} + diff --git a/gtk/gtkcolumnviewcell.c b/gtk/gtkcolumnviewcell.c index db3085dbe2..ecaa39dd46 100644 --- a/gtk/gtkcolumnviewcell.c +++ b/gtk/gtkcolumnviewcell.c @@ -44,17 +44,6 @@ struct _GtkColumnViewCellClass G_DEFINE_TYPE (GtkColumnViewCell, gtk_column_view_cell, GTK_TYPE_LIST_ITEM_WIDGET) -void -gtk_column_view_cell_measure_contents (GtkColumnViewCell *self, - int *minimum, - int *natural) -{ - GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self)); - - if (child) - gtk_widget_measure (child, GTK_ORIENTATION_HORIZONTAL, -1, minimum, natural, NULL, NULL); -} - static void gtk_column_view_cell_measure (GtkWidget *widget, GtkOrientation orientation, @@ -64,19 +53,10 @@ gtk_column_view_cell_measure (GtkWidget *widget, int *minimum_baseline, int *natural_baseline) { - GtkColumnViewCell *self = GTK_COLUMN_VIEW_CELL (widget); + GtkWidget *child = gtk_widget_get_first_child (widget); - if (orientation == GTK_ORIENTATION_HORIZONTAL) - { - gtk_column_view_column_measure (self->column, minimum, natural); - } - else - { - GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self)); - - if (child) - gtk_widget_measure (child, orientation, for_size, minimum, natural, minimum_baseline, natural_baseline); - } + if (child) + gtk_widget_measure (child, orientation, for_size, minimum, natural, minimum_baseline, natural_baseline); } static void @@ -202,3 +182,9 @@ gtk_column_view_cell_get_prev (GtkColumnViewCell *self) { return self->prev_cell; } + +GtkColumnViewColumn * +gtk_column_view_cell_get_column (GtkColumnViewCell *self) +{ + return self->column; +} diff --git a/gtk/gtkcolumnviewcellprivate.h b/gtk/gtkcolumnviewcellprivate.h index 1ebdc710f4..4378d082e7 100644 --- a/gtk/gtkcolumnviewcellprivate.h +++ b/gtk/gtkcolumnviewcellprivate.h @@ -42,10 +42,7 @@ void gtk_column_view_cell_remove (GtkColumnViewCe GtkColumnViewCell * gtk_column_view_cell_get_next (GtkColumnViewCell *self); GtkColumnViewCell * gtk_column_view_cell_get_prev (GtkColumnViewCell *self); - -void gtk_column_view_cell_measure_contents (GtkColumnViewCell *self, - int *minimum, - int *natural); +GtkColumnViewColumn * gtk_column_view_cell_get_column (GtkColumnViewCell *self); G_END_DECLS diff --git a/gtk/gtkcolumnviewcolumn.c b/gtk/gtkcolumnviewcolumn.c index a34e81fa84..aaa8c32fdc 100644 --- a/gtk/gtkcolumnviewcolumn.c +++ b/gtk/gtkcolumnviewcolumn.c @@ -52,6 +52,8 @@ struct _GtkColumnViewColumn int minimum_size_request; int natural_size_request; + int allocation_offset; + int allocation_size; /* This list isn't sorted - this is just caching for performance */ GtkColumnViewCell *first_cell; /* no reference, just caching */ @@ -310,7 +312,11 @@ gtk_column_view_column_measure (GtkColumnViewColumn *self, for (cell = self->first_cell; cell; cell = gtk_column_view_cell_get_next (cell)) { - gtk_column_view_cell_measure_contents (cell, &cell_min, &cell_nat); + gtk_widget_measure (GTK_WIDGET (cell), + GTK_ORIENTATION_HORIZONTAL, + -1, + &cell_min, &cell_nat, + NULL, NULL); min = MAX (min, cell_min); nat = MAX (nat, cell_nat); @@ -324,6 +330,26 @@ gtk_column_view_column_measure (GtkColumnViewColumn *self, *natural = self->natural_size_request; } +void +gtk_column_view_column_allocate (GtkColumnViewColumn *self, + int offset, + int size) +{ + self->allocation_offset = offset; + self->allocation_size = size; +} + +void +gtk_column_view_column_get_allocation (GtkColumnViewColumn *self, + int *offset, + int *size) +{ + if (offset) + *offset = self->allocation_offset; + if (size) + *size = self->allocation_size; +} + static void gtk_column_view_column_create_cells (GtkColumnViewColumn *self) { diff --git a/gtk/gtkcolumnviewcolumnprivate.h b/gtk/gtkcolumnviewcolumnprivate.h index fc0fe07c78..d7e06a5b0f 100644 --- a/gtk/gtkcolumnviewcolumnprivate.h +++ b/gtk/gtkcolumnviewcolumnprivate.h @@ -37,5 +37,11 @@ void gtk_column_view_column_queue_resize (GtkColu void gtk_column_view_column_measure (GtkColumnViewColumn *self, int *minimum, int *natural); +void gtk_column_view_column_allocate (GtkColumnViewColumn *self, + int offset, + int size); +void gtk_column_view_column_get_allocation (GtkColumnViewColumn *self, + int *offset, + int *size); #endif /* __GTK_COLUMN_VIEW_COLUMN_PRIVATE_H__ */ diff --git a/gtk/gtkcolumnviewlayout.c b/gtk/gtkcolumnviewlayout.c new file mode 100644 index 0000000000..3afd6a2af3 --- /dev/null +++ b/gtk/gtkcolumnviewlayout.c @@ -0,0 +1,149 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gtkcolumnviewlayoutprivate.h" + +#include "gtkcolumnviewcellprivate.h" +#include "gtkcolumnviewcolumnprivate.h" +#include "gtkcolumnviewprivate.h" +#include "gtkwidgetprivate.h" + +struct _GtkColumnViewLayout +{ + GtkLayoutManager parent_instance; + + GtkColumnView *view; /* no reference */ +}; + +G_DEFINE_TYPE (GtkColumnViewLayout, gtk_column_view_layout, GTK_TYPE_LAYOUT_MANAGER) + +static void +gtk_column_view_layout_measure_along (GtkColumnViewLayout *self, + GtkWidget *widget, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + GtkOrientation orientation = GTK_ORIENTATION_VERTICAL; + GtkWidget *child; + + for (child = _gtk_widget_get_first_child (widget); + child != NULL; + child = _gtk_widget_get_next_sibling (child)) + { + int child_min = 0; + int child_nat = 0; + int child_min_baseline = -1; + int child_nat_baseline = -1; + + gtk_widget_measure (child, orientation, for_size, + &child_min, &child_nat, + &child_min_baseline, &child_nat_baseline); + + *minimum = MAX (*minimum, child_min); + *natural = MAX (*natural, child_nat); + + if (child_min_baseline > -1) + *minimum_baseline = MAX (*minimum_baseline, child_min_baseline); + if (child_nat_baseline > -1) + *natural_baseline = MAX (*natural_baseline, child_nat_baseline); + } +} + +static void +gtk_column_view_layout_measure (GtkLayoutManager *layout, + GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + GtkColumnViewLayout *self = GTK_COLUMN_VIEW_LAYOUT (layout); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + gtk_column_view_measure_across (GTK_COLUMN_VIEW (self->view), + minimum, + natural); + } + else + { + gtk_column_view_layout_measure_along (self, + widget, + for_size, + minimum, + natural, + minimum_baseline, + natural_baseline); + } +} + +static void +gtk_column_view_layout_allocate (GtkLayoutManager *layout_manager, + GtkWidget *widget, + int width, + int height, + int baseline) +{ + GtkWidget *child; + + for (child = _gtk_widget_get_first_child (widget); + child != NULL; + child = _gtk_widget_get_next_sibling (child)) + { + GtkColumnViewCell *cell = GTK_COLUMN_VIEW_CELL (child); + GtkColumnViewColumn *column = gtk_column_view_cell_get_column (cell); + int col_x, col_width; + + gtk_column_view_column_get_allocation (column, &col_x, &col_width); + gtk_widget_size_allocate (child, &(GtkAllocation) { col_x, 0, col_width, height }, baseline); + } +} + +static void +gtk_column_view_layout_class_init (GtkColumnViewLayoutClass *klass) +{ + GtkLayoutManagerClass *layout_manager_class = GTK_LAYOUT_MANAGER_CLASS (klass); + + layout_manager_class->measure = gtk_column_view_layout_measure; + layout_manager_class->allocate = gtk_column_view_layout_allocate; +} + +static void +gtk_column_view_layout_init (GtkColumnViewLayout *self) +{ +} + +GtkLayoutManager * +gtk_column_view_layout_new (GtkColumnView *view) +{ + GtkColumnViewLayout *result; + + result = g_object_new (GTK_TYPE_COLUMN_VIEW_LAYOUT, NULL); + + result->view = view; + + return GTK_LAYOUT_MANAGER (result); +} diff --git a/gtk/gtkcolumnviewlayoutprivate.h b/gtk/gtkcolumnviewlayoutprivate.h new file mode 100644 index 0000000000..6e786cdbd5 --- /dev/null +++ b/gtk/gtkcolumnviewlayoutprivate.h @@ -0,0 +1,35 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __GTK_COLUMN_VIEW_LAYOUT_PRIVATE_H__ +#define __GTK_COLUMN_VIEW_LAYOUT_PRIVATE_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_COLUMN_VIEW_LAYOUT (gtk_column_view_layout_get_type ()) + +G_DECLARE_FINAL_TYPE (GtkColumnViewLayout, gtk_column_view_layout, GTK, COLUMN_VIEW_LAYOUT, GtkLayoutManager) + +GtkLayoutManager * gtk_column_view_layout_new (GtkColumnView *view); + + +#endif /* __GTK_COLUMN_VIEW_LAYOUT_PRIVATE_H__ */ diff --git a/gtk/gtkcolumnviewprivate.h b/gtk/gtkcolumnviewprivate.h index 417a942ab4..d95577fe98 100644 --- a/gtk/gtkcolumnviewprivate.h +++ b/gtk/gtkcolumnviewprivate.h @@ -22,5 +22,8 @@ #include "gtk/gtkcolumnview.h" +void gtk_column_view_measure_across (GtkColumnView *self, + int *minimum, + int *natural); #endif /* __GTK_COLUMN_VIEW_PRIVATE_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index 80fffed33e..565c23ad5b 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -37,6 +37,9 @@ gtk_private_sources = files([ 'gtkcolorpickershell.c', 'gtkcolorscale.c', 'gtkcolorswatch.c', + 'gtkcolumnlistitemfactory.c', + 'gtkcolumnviewcell.c', + 'gtkcolumnviewlayout.c', 'gtkconstraintexpression.c', 'gtkconstraintsolver.c', 'gtkconstraintvflparser.c', @@ -193,9 +196,7 @@ gtk_public_sources = files([ 'gtkcolorchooserdialog.c', 'gtkcolorchooserwidget.c', 'gtkcolorutils.c', - 'gtkcolumnlistitemfactory.c', 'gtkcolumnview.c', - 'gtkcolumnviewcell.c', 'gtkcolumnviewcolumn.c', 'gtkcombobox.c', 'gtkcomboboxtext.c', From 9f19699806879568b310b831e04db8dc35f21881 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 28 Oct 2019 20:50:25 +0100 Subject: [PATCH 142/170] tests: Add testcolumnview --- tests/meson.build | 1 + tests/testcolumnview.c | 700 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 701 insertions(+) create mode 100644 tests/testcolumnview.c diff --git a/tests/meson.build b/tests/meson.build index 524a814816..36857d2cd8 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -20,6 +20,7 @@ gtk_tests = [ ['testcalendar'], ['testclipboard2'], ['testcombo'], + ['testcolumnview'], ['testcombochange'], ['testcellrenderertext'], ['testdialog'], diff --git a/tests/testcolumnview.c b/tests/testcolumnview.c new file mode 100644 index 0000000000..0913aea5e9 --- /dev/null +++ b/tests/testcolumnview.c @@ -0,0 +1,700 @@ +#include + +GSList *pending = NULL; +guint active = 0; + +static void +loading_cb (GtkDirectoryList *dir, + GParamSpec *pspec, + gpointer unused) +{ + if (gtk_directory_list_is_loading (dir)) + { + active++; + /* HACK: ensure loading finishes and the dir doesn't get destroyed */ + g_object_ref (dir); + } + else + { + active--; + g_object_unref (dir); + + while (active < 20 && pending) + { + GtkDirectoryList *dir2 = pending->data; + pending = g_slist_remove (pending, dir2); + gtk_directory_list_set_file (dir2, g_object_get_data (G_OBJECT (dir2), "file")); + g_object_unref (dir2); + } + } +} + +static GtkDirectoryList * +create_directory_list (GFile *file) +{ + GtkDirectoryList *dir; + + dir = gtk_directory_list_new ("*", + NULL); + gtk_directory_list_set_io_priority (dir, G_PRIORITY_DEFAULT_IDLE); + g_signal_connect (dir, "notify::loading", G_CALLBACK (loading_cb), NULL); + g_assert (!gtk_directory_list_is_loading (dir)); + + if (active > 20) + { + g_object_set_data_full (G_OBJECT (dir), "file", g_object_ref (file), g_object_unref); + pending = g_slist_prepend (pending, g_object_ref (dir)); + } + else + { + gtk_directory_list_set_file (dir, file); + } + + return dir; +} + +static GListModel * +create_list_model_for_directory (gpointer file) +{ + if (g_file_query_file_type (file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) != G_FILE_TYPE_DIRECTORY) + return NULL; + + return G_LIST_MODEL (create_directory_list (file)); +} + +#if 0 +typedef struct _RowData RowData; +struct _RowData +{ + GtkWidget *expander; + GtkWidget *icon; + GtkWidget *name; + GCancellable *cancellable; + + GtkTreeListRow *current_item; +}; + +static void row_data_notify_item (GtkListItem *item, + GParamSpec *pspec, + RowData *data); +static void +row_data_unbind (RowData *data) +{ + if (data->current_item == NULL) + return; + + if (data->cancellable) + { + g_cancellable_cancel (data->cancellable); + g_clear_object (&data->cancellable); + } + + g_clear_object (&data->current_item); +} + +static void +row_data_update_info (RowData *data, + GFileInfo *info) +{ + GIcon *icon; + const char *thumbnail_path; + + thumbnail_path = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH); + if (thumbnail_path) + { + /* XXX: not async */ + GFile *thumbnail_file = g_file_new_for_path (thumbnail_path); + icon = g_file_icon_new (thumbnail_file); + g_object_unref (thumbnail_file); + } + else + { + icon = g_file_info_get_icon (info); + } + + gtk_widget_set_visible (data->icon, icon != NULL); + gtk_image_set_from_gicon (GTK_IMAGE (data->icon), icon); +} + +static void +copy_attribute (GFileInfo *to, + GFileInfo *from, + const gchar *attribute) +{ + GFileAttributeType type; + gpointer value; + + if (g_file_info_get_attribute_data (from, attribute, &type, &value, NULL)) + g_file_info_set_attribute (to, attribute, type, value); +} + +static void +row_data_got_thumbnail_info_cb (GObject *source, + GAsyncResult *res, + gpointer _data) +{ + RowData *data = _data; /* invalid if operation was cancelled */ + GFile *file = G_FILE (source); + GFileInfo *queried, *info; + + queried = g_file_query_info_finish (file, res, NULL); + if (queried == NULL) + return; + + /* now we know row is valid */ + + info = gtk_tree_list_row_get_item (data->current_item); + + copy_attribute (info, queried, G_FILE_ATTRIBUTE_THUMBNAIL_PATH); + copy_attribute (info, queried, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED); + copy_attribute (info, queried, G_FILE_ATTRIBUTE_STANDARD_ICON); + + g_object_unref (queried); + + row_data_update_info (data, info); + + g_clear_object (&data->cancellable); +} + +static void +row_data_bind (RowData *data, + GtkTreeListRow *item) +{ + GFileInfo *info; + + row_data_unbind (data); + + if (item == NULL) + return; + + data->current_item = g_object_ref (item); + + gtk_tree_expander_set_list_row (GTK_TREE_EXPANDER (data->expander), item); + + info = gtk_tree_list_row_get_item (item); + + if (!g_file_info_has_attribute (info, "filechooser::queried")) + { + data->cancellable = g_cancellable_new (); + g_file_info_set_attribute_boolean (info, "filechooser::queried", TRUE); + g_file_query_info_async (G_FILE (g_file_info_get_attribute_object (info, "standard::file")), + G_FILE_ATTRIBUTE_THUMBNAIL_PATH "," + G_FILE_ATTRIBUTE_THUMBNAILING_FAILED "," + G_FILE_ATTRIBUTE_STANDARD_ICON, + G_FILE_QUERY_INFO_NONE, + G_PRIORITY_DEFAULT, + data->cancellable, + row_data_got_thumbnail_info_cb, + data); + } + + row_data_update_info (data, info); + + gtk_label_set_label (GTK_LABEL (data->name), g_file_info_get_display_name (info)); + + g_object_unref (info); +} + +static void +row_data_notify_item (GtkListItem *item, + GParamSpec *pspec, + RowData *data) +{ + row_data_bind (data, gtk_list_item_get_item (item)); +} + +static void +row_data_free (gpointer _data) +{ + RowData *data = _data; + + row_data_unbind (data); + + g_slice_free (RowData, data); +} + +static void +setup_widget (GtkListItem *list_item, + gpointer unused) +{ + GtkWidget *box, *child; + RowData *data; + + data = g_slice_new0 (RowData); + g_signal_connect (list_item, "notify::item", G_CALLBACK (row_data_notify_item), data); + g_object_set_data_full (G_OBJECT (list_item), "row-data", data, row_data_free); + + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + gtk_container_add (GTK_CONTAINER (list_item), box); + + child = gtk_label_new (NULL); + gtk_label_set_width_chars (GTK_LABEL (child), 5); + gtk_label_set_xalign (GTK_LABEL (child), 1.0); + g_object_bind_property (list_item, "position", child, "label", G_BINDING_SYNC_CREATE); + gtk_container_add (GTK_CONTAINER (box), child); + + data->expander = gtk_tree_expander_new (); + gtk_container_add (GTK_CONTAINER (box), data->expander); + + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + gtk_tree_expander_set_child (GTK_TREE_EXPANDER (data->expander), box); + + data->icon = gtk_image_new (); + gtk_container_add (GTK_CONTAINER (box), data->icon); + + data->name = gtk_label_new (NULL); + gtk_label_set_max_width_chars (GTK_LABEL (data->name), 25); + gtk_label_set_ellipsize (GTK_LABEL (data->name), PANGO_ELLIPSIZE_END); + gtk_container_add (GTK_CONTAINER (box), data->name); +} +#endif + +static GListModel * +create_list_model_for_file_info (gpointer file_info, + gpointer unused) +{ + GFile *file = G_FILE (g_file_info_get_attribute_object (file_info, "standard::file")); + + if (file == NULL) + return NULL; + + return create_list_model_for_directory (file); +} + +static gboolean +update_statusbar (GtkStatusbar *statusbar) +{ + GListModel *model = g_object_get_data (G_OBJECT (statusbar), "model"); + GString *string = g_string_new (NULL); + guint n; + gboolean result = G_SOURCE_REMOVE; + + gtk_statusbar_remove_all (statusbar, 0); + + n = g_list_model_get_n_items (model); + g_string_append_printf (string, "%u", n); + if (GTK_IS_FILTER_LIST_MODEL (model)) + { + guint n_unfiltered = g_list_model_get_n_items (gtk_filter_list_model_get_model (GTK_FILTER_LIST_MODEL (model))); + if (n != n_unfiltered) + g_string_append_printf (string, "/%u", n_unfiltered); + } + g_string_append (string, " items"); + + if (pending || active) + { + g_string_append_printf (string, " (%u directories remaining)", active + g_slist_length (pending)); + result = G_SOURCE_CONTINUE; + } + result = G_SOURCE_CONTINUE; + + gtk_statusbar_push (statusbar, 0, string->str); + g_free (string->str); + + return result; +} + +static gboolean +match_file (gpointer item, gpointer data) +{ + GtkWidget *search_entry = data; + GFileInfo *info = gtk_tree_list_row_get_item (item); + GFile *file = G_FILE (g_file_info_get_attribute_object (info, "standard::file")); + char *path; + gboolean result; + + path = g_file_get_path (file); + + result = strstr (path, gtk_editable_get_text (GTK_EDITABLE (search_entry))) != NULL; + + g_object_unref (info); + g_free (path); + + return result; +} + +static GObject * +get_object (GObject *unused, + GFileInfo *info, + const char *attribute) +{ + GObject *o; + + if (info == NULL) + return NULL; + + o = g_file_info_get_attribute_object (info, attribute); + if (o) + g_object_ref (o); + + return o; +} + +static char * +get_string (GObject *unused, + GFileInfo *info, + const char *attribute) +{ + if (info == NULL) + return NULL; + + return g_file_info_get_attribute_as_string (info, attribute); +} + +static gboolean +get_boolean (GObject *unused, + GFileInfo *info, + const char *attribute) +{ + if (info == NULL) + return FALSE; + + return g_file_info_get_attribute_boolean (info, attribute); +} + +const char *ui_file = +"\n" +"\n" +" \n" +" \n" +" \n" +" Name\n" +" \n" +" \n" +" \n" +"\n" +" \n" +"\n" +" ]]>\n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n"; + +#define SIMPLE_STRING_FACTORY(attr, type) \ +"\n" \ +"\n" \ +" \n" \ +"\n" \ + +#define BOOLEAN_FACTORY(attr) \ +"\n" \ +"\n" \ +" \n" \ +"\n" \ + +#define ICON_FACTORY(attr) \ +"\n" \ +"\n" \ +" \n" \ +"\n" \ + +struct { + const char *title; + const char *factory_xml; +} extra_columns[] = { + { "Type", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_TYPE, "uint32") }, + { "Hidden", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN) }, + { "Backup", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP) }, + { "Symlink", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK) }, + { "Virtual", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL) }, + { "Volatile", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_VOLATILE) }, + { "Edit name", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME, "string") }, + { "Copy name", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_COPY_NAME, "string") }, + { "Description", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION, "string") }, + { "Icon", ICON_FACTORY (G_FILE_ATTRIBUTE_STANDARD_ICON) }, + { "Symbolic icon", ICON_FACTORY (G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON) }, + { "Content type", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, "string") }, + { "Fast content type", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, "string") }, + { "Size", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_SIZE, "uint64") }, + { "Allocated size", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE, "uint64") }, + { "Target URI", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, "string") }, + { "Sort order", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_SORT_ORDER, "int32") }, + { "ETAG value", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_ETAG_VALUE, "string") }, + { "File ID", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_ID_FILE, "string") }, + { "Filesystem ID", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_ID_FILESYSTEM, "string") }, + { "Read", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_READ) }, + { "Write", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE) }, + { "Execute", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE) }, + { "Delete", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE) }, + { "Trash", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH) }, + { "Rename", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME) }, + { "Can mount", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT) }, + { "Can unmount", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT) }, + { "Can eject", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_CAN_EJECT) }, + { "UNIX device", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_UNIX_DEVICE, "uint32") }, + { "UNIX device file", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_UNIX_DEVICE_FILE, "string") }, + { "owner", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_OWNER_USER, "string") }, + { "owner (real)", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_OWNER_USER_REAL, "string") }, + { "group", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_OWNER_GROUP, "string") }, + { "Preview icon", ICON_FACTORY (G_FILE_ATTRIBUTE_PREVIEW_ICON) }, +}; + +#if 0 +#define G_FILE_ATTRIBUTE_MOUNTABLE_CAN_START "mountable::can-start" /* boolean */ +#define G_FILE_ATTRIBUTE_MOUNTABLE_CAN_START_DEGRADED "mountable::can-start-degraded" /* boolean */ +#define G_FILE_ATTRIBUTE_MOUNTABLE_CAN_STOP "mountable::can-stop" /* boolean */ +#define G_FILE_ATTRIBUTE_MOUNTABLE_START_STOP_TYPE "mountable::start-stop-type" /* uint32 (GDriveStartStopType) */ +#define G_FILE_ATTRIBUTE_MOUNTABLE_CAN_POLL "mountable::can-poll" /* boolean */ +#define G_FILE_ATTRIBUTE_MOUNTABLE_IS_MEDIA_CHECK_AUTOMATIC "mountable::is-media-check-automatic" /* boolean */ +#define G_FILE_ATTRIBUTE_TIME_MODIFIED "time::modified" /* uint64 */ +#define G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC "time::modified-usec" /* uint32 */ +#define G_FILE_ATTRIBUTE_TIME_ACCESS "time::access" /* uint64 */ +#define G_FILE_ATTRIBUTE_TIME_ACCESS_USEC "time::access-usec" /* uint32 */ +#define G_FILE_ATTRIBUTE_TIME_CHANGED "time::changed" /* uint64 */ +#define G_FILE_ATTRIBUTE_TIME_CHANGED_USEC "time::changed-usec" /* uint32 */ +#define G_FILE_ATTRIBUTE_TIME_CREATED "time::created" /* uint64 */ +#define G_FILE_ATTRIBUTE_TIME_CREATED_USEC "time::created-usec" /* uint32 */ +#define G_FILE_ATTRIBUTE_UNIX_DEVICE "unix::device" /* uint32 */ +#define G_FILE_ATTRIBUTE_UNIX_INODE "unix::inode" /* uint64 */ +#define G_FILE_ATTRIBUTE_UNIX_MODE "unix::mode" /* uint32 */ +#define G_FILE_ATTRIBUTE_UNIX_NLINK "unix::nlink" /* uint32 */ +#define G_FILE_ATTRIBUTE_UNIX_UID "unix::uid" /* uint32 */ +#define G_FILE_ATTRIBUTE_UNIX_GID "unix::gid" /* uint32 */ +#define G_FILE_ATTRIBUTE_UNIX_RDEV "unix::rdev" /* uint32 */ +#define G_FILE_ATTRIBUTE_UNIX_BLOCK_SIZE "unix::block-size" /* uint32 */ +#define G_FILE_ATTRIBUTE_UNIX_BLOCKS "unix::blocks" /* uint64 */ +#define G_FILE_ATTRIBUTE_UNIX_IS_MOUNTPOINT "unix::is-mountpoint" /* boolean */ +#define G_FILE_ATTRIBUTE_DOS_IS_ARCHIVE "dos::is-archive" /* boolean */ +#define G_FILE_ATTRIBUTE_DOS_IS_SYSTEM "dos::is-system" /* boolean */ +#define G_FILE_ATTRIBUTE_DOS_IS_MOUNTPOINT "dos::is-mountpoint" /* boolean */ +#define G_FILE_ATTRIBUTE_DOS_REPARSE_POINT_TAG "dos::reparse-point-tag" /* uint32 */ +#define G_FILE_ATTRIBUTE_THUMBNAIL_PATH "thumbnail::path" /* bytestring */ +#define G_FILE_ATTRIBUTE_THUMBNAILING_FAILED "thumbnail::failed" /* boolean */ +#define G_FILE_ATTRIBUTE_THUMBNAIL_IS_VALID "thumbnail::is-valid" /* boolean */ +#define G_FILE_ATTRIBUTE_FILESYSTEM_SIZE "filesystem::size" /* uint64 */ +#define G_FILE_ATTRIBUTE_FILESYSTEM_FREE "filesystem::free" /* uint64 */ +#define G_FILE_ATTRIBUTE_FILESYSTEM_USED "filesystem::used" /* uint64 */ +#define G_FILE_ATTRIBUTE_FILESYSTEM_TYPE "filesystem::type" /* string */ +#define G_FILE_ATTRIBUTE_FILESYSTEM_READONLY "filesystem::readonly" /* boolean */ +#define G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW "filesystem::use-preview" /* uint32 (GFilesystemPreviewType) */ +#define G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE "filesystem::remote" /* boolean */ +#define G_FILE_ATTRIBUTE_GVFS_BACKEND "gvfs::backend" /* string */ +#define G_FILE_ATTRIBUTE_SELINUX_CONTEXT "selinux::context" /* string */ +#define G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT "trash::item-count" /* uint32 */ +#define G_FILE_ATTRIBUTE_TRASH_ORIG_PATH "trash::orig-path" /* byte string */ +#define G_FILE_ATTRIBUTE_TRASH_DELETION_DATE "trash::deletion-date" /* string */ +#define G_FILE_ATTRIBUTE_RECENT_MODIFIED "recent::modified" /* int64 (time_t) */ +#endif + +const char *factory_ui = +"\n" +"\n" +" \n" +"\n"; + +static GtkBuilderScope * +create_scope (void) +{ +#define ADD_SYMBOL(name) \ + gtk_builder_cscope_add_callback_symbol (GTK_BUILDER_CSCOPE (scope), G_STRINGIFY (name), G_CALLBACK (name)) + GtkBuilderScope *scope; + + scope = gtk_builder_cscope_new (); + + ADD_SYMBOL (get_object); + ADD_SYMBOL (get_string); + ADD_SYMBOL (get_boolean); + + return scope; +#undef ADD_SYMBOL +} + +static void +add_extra_columns (GtkColumnView *view, + GtkBuilderScope *scope) +{ + GtkColumnViewColumn *column; + GBytes *bytes; + guint i; + + for (i = 0; i < G_N_ELEMENTS(extra_columns); i++) + { + bytes = g_bytes_new_static (extra_columns[i].factory_xml, strlen (extra_columns[i].factory_xml)); + column = gtk_column_view_column_new_with_factory (extra_columns[i].title, + gtk_builder_list_item_factory_new_from_bytes (scope, bytes)); + g_bytes_unref (bytes); + gtk_column_view_append_column (view, column); + } +} + +static void +search_changed_cb (GtkSearchEntry *entry, + GtkFilter *custom_filter) +{ + gtk_filter_changed (custom_filter, GTK_FILTER_CHANGE_DIFFERENT); +} + +int +main (int argc, char *argv[]) +{ + GListModel *toplevels; + GtkWidget *win, *hbox, *vbox, *sw, *view, *list, *search_entry, *statusbar; + GListModel *dirmodel; + GtkTreeListModel *tree; + GtkFilterListModel *filter; + GtkFilter *custom_filter; + GFile *root; + GtkBuilderScope *scope; + GtkBuilder *builder; + GError *error = NULL; + + gtk_init (); + + win = gtk_window_new (); + gtk_window_set_default_size (GTK_WINDOW (win), 800, 600); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_window_set_child (GTK_WINDOW (win), hbox); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_box_append (GTK_BOX (hbox), vbox); + + search_entry = gtk_search_entry_new (); + gtk_box_append (GTK_BOX (vbox), search_entry); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_set_hexpand (sw, TRUE); + gtk_widget_set_vexpand (sw, TRUE); + gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (search_entry), sw); + gtk_box_append (GTK_BOX (vbox), sw); + + scope = create_scope (); + builder = gtk_builder_new (); + gtk_builder_set_scope (builder, scope); + if (!gtk_builder_add_from_string (builder, ui_file, -1, &error)) + { + g_assert_no_error (error); + } + view = GTK_WIDGET (gtk_builder_get_object (builder, "view")); + add_extra_columns (GTK_COLUMN_VIEW (view), scope); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), view); + g_object_unref (builder); + + if (argc > 1) + root = g_file_new_for_commandline_arg (argv[1]); + else + root = g_file_new_for_path (g_get_current_dir ()); + dirmodel = create_list_model_for_directory (root); + tree = gtk_tree_list_model_new (FALSE, + dirmodel, + TRUE, + create_list_model_for_file_info, + NULL, NULL); + g_object_unref (dirmodel); + g_object_unref (root); + + custom_filter = gtk_custom_filter_new (match_file, g_object_ref (search_entry), g_object_unref); + filter = gtk_filter_list_model_new (G_LIST_MODEL (tree), custom_filter); + g_signal_connect (search_entry, "search-changed", G_CALLBACK (search_changed_cb), custom_filter); + g_object_unref (custom_filter); + + gtk_column_view_set_model (GTK_COLUMN_VIEW (view), G_LIST_MODEL (filter)); + + statusbar = gtk_statusbar_new (); + gtk_widget_add_tick_callback (statusbar, (GtkTickCallback) update_statusbar, NULL, NULL); + g_object_set_data (G_OBJECT (statusbar), "model", filter); + g_signal_connect_swapped (filter, "items-changed", G_CALLBACK (update_statusbar), statusbar); + update_statusbar (GTK_STATUSBAR (statusbar)); + gtk_box_append (GTK_BOX (vbox), statusbar); + + g_object_unref (filter); + g_object_unref (tree); + + list = gtk_list_view_new_with_factory ( + gtk_builder_list_item_factory_new_from_bytes (scope, g_bytes_new_static (factory_ui, strlen (factory_ui)))); + gtk_list_view_set_model (GTK_LIST_VIEW (list), gtk_column_view_get_columns (GTK_COLUMN_VIEW (view))); + gtk_box_append (GTK_BOX (hbox), list); + + g_object_unref (scope); + + gtk_widget_show (win); + + toplevels = gtk_window_get_toplevels (); + while (g_list_model_get_n_items (toplevels)) + g_main_context_iteration (NULL, TRUE); + + + return 0; +} From efcb3a9d675378776052c8c24e625dbcabb625cd Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 8 Nov 2019 21:23:03 +0100 Subject: [PATCH 143/170] columnview: Add header This uses a custom GtkColumnViewTitle widget. So far that widget is pretty boring, but that will change once we added resizing, reordering, dnd, sorting, hiding/showing of columns or whatever UIs we want. --- docs/reference/gtk/meson.build | 1 + gtk/gtkcolumnview.c | 43 ++++++++-- gtk/gtkcolumnviewcolumn.c | 55 ++++++++++++- gtk/gtkcolumnviewlayout.c | 9 +- gtk/gtkcolumnviewprivate.h | 4 + gtk/gtkcolumnviewtitle.c | 142 ++++++++++++++++++++++++++++++++ gtk/gtkcolumnviewtitleprivate.h | 47 +++++++++++ gtk/meson.build | 1 + 8 files changed, 291 insertions(+), 11 deletions(-) create mode 100644 gtk/gtkcolumnviewtitle.c create mode 100644 gtk/gtkcolumnviewtitleprivate.h diff --git a/docs/reference/gtk/meson.build b/docs/reference/gtk/meson.build index 797784e81c..40584ab31f 100644 --- a/docs/reference/gtk/meson.build +++ b/docs/reference/gtk/meson.build @@ -29,6 +29,7 @@ private_headers = [ 'gtkcolumnviewcolumnprivate.h', 'gtkcolumnviewlayoutprivate.h', 'gtkcolumnviewprivate.h', + 'gtkcolumnviewtitleprivate.h', 'gtkcomboboxprivate.h', 'gtkconstraintexpressionprivate.h', 'gtkconstraintguideprivate.h', diff --git a/gtk/gtkcolumnview.c b/gtk/gtkcolumnview.c index 8582e7293d..d6170dfc46 100644 --- a/gtk/gtkcolumnview.c +++ b/gtk/gtkcolumnview.c @@ -25,6 +25,7 @@ #include "gtkbuildable.h" #include "gtkcolumnlistitemfactoryprivate.h" #include "gtkcolumnviewcolumnprivate.h" +#include "gtkcolumnviewlayoutprivate.h" #include "gtkcssnodeprivate.h" #include "gtkintl.h" #include "gtklistview.h" @@ -49,6 +50,8 @@ struct _GtkColumnView GListStore *columns; + GtkWidget *header; + GtkListView *listview; GtkColumnListItemFactory *factory; }; @@ -135,10 +138,18 @@ gtk_column_view_measure (GtkWidget *widget, } else { + int header_min, header_nat, list_min, list_nat; + gtk_widget_measure (GTK_WIDGET (self->listview), orientation, for_size, - minimum, natural, - minimum_baseline, natural_baseline); + &header_min, &header_nat, + NULL, NULL); + gtk_widget_measure (GTK_WIDGET (self->listview), + orientation, for_size, + &list_min, &list_nat, + NULL, NULL); + *minimum = header_min + list_min; + *natural = header_nat + list_nat; } } @@ -151,7 +162,7 @@ gtk_column_view_allocate_columns (GtkColumnView *self, guint i; gtk_column_view_measure_across (self, &col_min, &col_nat); - gtk_widget_measure (GTK_WIDGET (self->listview), + gtk_widget_measure (GTK_WIDGET (self), GTK_ORIENTATION_HORIZONTAL, -1, &widget_min, &widget_nat, NULL, NULL); @@ -198,11 +209,20 @@ gtk_column_view_allocate (GtkWidget *widget, int baseline) { GtkColumnView *self = GTK_COLUMN_VIEW (widget); - int full_width; + int full_width, header_height, min, nat; full_width = gtk_column_view_allocate_columns (self, width); - gtk_widget_allocate (GTK_WIDGET (self->listview), full_width, height, baseline, NULL); + gtk_widget_measure (self->header, GTK_ORIENTATION_VERTICAL, full_width, &min, &nat, NULL, NULL); + if (gtk_scrollable_get_vscroll_policy (GTK_SCROLLABLE (self->listview)) == GTK_SCROLL_MINIMUM) + header_height = min; + else + header_height = nat; + gtk_widget_allocate (self->header, full_width, header_height, -1, NULL); + + gtk_widget_allocate (GTK_WIDGET (self->listview), + full_width, height - header_height, -1, + gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (0, header_height))); } static void @@ -225,6 +245,8 @@ gtk_column_view_dispose (GObject *object) g_object_unref (column); } + g_clear_pointer (&self->header, gtk_widget_unparent); + g_clear_pointer ((GtkWidget **) &self->listview, gtk_widget_unparent); g_clear_object (&self->factory); @@ -441,6 +463,11 @@ gtk_column_view_init (GtkColumnView *self) { self->columns = g_list_store_new (GTK_TYPE_COLUMN_VIEW_COLUMN); + self->header = gtk_list_item_widget_new (NULL, "header"); + gtk_widget_set_can_focus (self->header, FALSE); + gtk_widget_set_layout_manager (self->header, gtk_column_view_layout_new (self)); + gtk_widget_set_parent (self->header, GTK_WIDGET (self)); + self->factory = gtk_column_list_item_factory_new (self); self->listview = GTK_LIST_VIEW (gtk_list_view_new_with_factory ( GTK_LIST_ITEM_FACTORY (g_object_ref (self->factory)))); @@ -648,3 +675,9 @@ gtk_column_view_measure_across (GtkColumnView *self, *natural = nat; } +GtkListItemWidget * +gtk_column_view_get_header_widget (GtkColumnView *self) +{ + return GTK_LIST_ITEM_WIDGET (self->header); +} + diff --git a/gtk/gtkcolumnviewcolumn.c b/gtk/gtkcolumnviewcolumn.c index aaa8c32fdc..c33c50cd53 100644 --- a/gtk/gtkcolumnviewcolumn.c +++ b/gtk/gtkcolumnviewcolumn.c @@ -21,6 +21,8 @@ #include "gtkcolumnviewcolumnprivate.h" +#include "gtkcolumnviewprivate.h" +#include "gtkcolumnviewtitleprivate.h" #include "gtkintl.h" #include "gtklistbaseprivate.h" #include "gtklistitemwidgetprivate.h" @@ -49,6 +51,7 @@ struct _GtkColumnViewColumn /* data for the view */ GtkColumnView *view; + GtkWidget *header; int minimum_size_request; int natural_size_request; @@ -291,6 +294,9 @@ gtk_column_view_column_queue_resize (GtkColumnViewColumn *self) self->minimum_size_request = -1; self->natural_size_request = -1; + if (self->header) + gtk_widget_queue_resize (self->header); + for (cell = self->first_cell; cell; cell = gtk_column_view_cell_get_next (cell)) { gtk_widget_queue_resize (GTK_WIDGET (cell)); @@ -307,8 +313,15 @@ gtk_column_view_column_measure (GtkColumnViewColumn *self, GtkColumnViewCell *cell; int min, nat, cell_min, cell_nat; - min = 0; - nat = 0; + if (self->header) + { + gtk_widget_measure (self->header, GTK_ORIENTATION_HORIZONTAL, -1, &min, &nat, NULL, NULL); + } + else + { + min = 0; + nat = 0; + } for (cell = self->first_cell; cell; cell = gtk_column_view_cell_get_next (cell)) { @@ -385,6 +398,30 @@ gtk_column_view_column_remove_cells (GtkColumnViewColumn *self) gtk_column_view_cell_remove (self->first_cell); } +static void +gtk_column_view_column_create_header (GtkColumnViewColumn *self) +{ + if (self->header != NULL) + return; + + self->header = gtk_column_view_title_new (self); + gtk_list_item_widget_add_child (gtk_column_view_get_header_widget (self->view), + self->header); + gtk_column_view_column_queue_resize (self); +} + +static void +gtk_column_view_column_remove_header (GtkColumnViewColumn *self) +{ + if (self->header == NULL) + return; + + gtk_list_item_widget_remove_child (gtk_column_view_get_header_widget (self->view), + self->header); + self->header = NULL; + gtk_column_view_column_queue_resize (self); +} + static void gtk_column_view_column_ensure_cells (GtkColumnViewColumn *self) { @@ -392,6 +429,11 @@ gtk_column_view_column_ensure_cells (GtkColumnViewColumn *self) gtk_column_view_column_create_cells (self); else gtk_column_view_column_remove_cells (self); + + if (self->view) + gtk_column_view_column_create_header (self); + else + gtk_column_view_column_remove_header (self); } /** @@ -419,10 +461,12 @@ gtk_column_view_column_set_column_view (GtkColumnViewColumn *self, if (self->view == view) return; + gtk_column_view_column_remove_cells (self); + gtk_column_view_column_remove_header (self); + self->view = view; - if (view) - gtk_column_view_column_ensure_cells (self); + gtk_column_view_column_ensure_cells (self); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_COLUMN_VIEW]); } @@ -486,6 +530,9 @@ gtk_column_view_column_set_title (GtkColumnViewColumn *self, g_free (self->title); self->title = g_strdup (title); + if (self->header) + gtk_column_view_title_update (GTK_COLUMN_VIEW_TITLE (self->header)); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TITLE]); } diff --git a/gtk/gtkcolumnviewlayout.c b/gtk/gtkcolumnviewlayout.c index 3afd6a2af3..c4ba28a112 100644 --- a/gtk/gtkcolumnviewlayout.c +++ b/gtk/gtkcolumnviewlayout.c @@ -24,6 +24,7 @@ #include "gtkcolumnviewcellprivate.h" #include "gtkcolumnviewcolumnprivate.h" #include "gtkcolumnviewprivate.h" +#include "gtkcolumnviewtitleprivate.h" #include "gtkwidgetprivate.h" struct _GtkColumnViewLayout @@ -113,10 +114,14 @@ gtk_column_view_layout_allocate (GtkLayoutManager *layout_manager, child != NULL; child = _gtk_widget_get_next_sibling (child)) { - GtkColumnViewCell *cell = GTK_COLUMN_VIEW_CELL (child); - GtkColumnViewColumn *column = gtk_column_view_cell_get_column (cell); + GtkColumnViewColumn *column; int col_x, col_width; + if (GTK_IS_COLUMN_VIEW_CELL (child)) + column = gtk_column_view_cell_get_column (GTK_COLUMN_VIEW_CELL (child)); + else + column = gtk_column_view_title_get_column (GTK_COLUMN_VIEW_TITLE (child)); + gtk_column_view_column_get_allocation (column, &col_x, &col_width); gtk_widget_size_allocate (child, &(GtkAllocation) { col_x, 0, col_width, height }, baseline); } diff --git a/gtk/gtkcolumnviewprivate.h b/gtk/gtkcolumnviewprivate.h index d95577fe98..c356fae508 100644 --- a/gtk/gtkcolumnviewprivate.h +++ b/gtk/gtkcolumnviewprivate.h @@ -22,6 +22,10 @@ #include "gtk/gtkcolumnview.h" +#include "gtk/gtklistitemwidgetprivate.h" + +GtkListItemWidget * gtk_column_view_get_header_widget (GtkColumnView *self); + void gtk_column_view_measure_across (GtkColumnView *self, int *minimum, int *natural); diff --git a/gtk/gtkcolumnviewtitle.c b/gtk/gtkcolumnviewtitle.c new file mode 100644 index 0000000000..7170018eb4 --- /dev/null +++ b/gtk/gtkcolumnviewtitle.c @@ -0,0 +1,142 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gtkcolumnviewtitleprivate.h" + +#include "gtkcolumnviewcolumnprivate.h" +#include "gtkintl.h" +#include "gtklabel.h" +#include "gtkwidgetprivate.h" + +struct _GtkColumnViewTitle +{ + GtkWidget parent_instance; + + GtkColumnViewColumn *column; + + GtkWidget *title; +}; + +struct _GtkColumnViewTitleClass +{ + GtkWidgetClass parent_class; +}; + +G_DEFINE_TYPE (GtkColumnViewTitle, gtk_column_view_title, GTK_TYPE_WIDGET) + +static void +gtk_column_view_title_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + GtkWidget *child = gtk_widget_get_first_child (widget); + + if (child) + gtk_widget_measure (child, orientation, for_size, minimum, natural, minimum_baseline, natural_baseline); +} + +static void +gtk_column_view_title_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + GtkWidget *child = gtk_widget_get_first_child (widget); + + if (child) + gtk_widget_allocate (child, width, height, baseline, NULL); +} + +static void +gtk_column_view_title_dispose (GObject *object) +{ + GtkColumnViewTitle *self = GTK_COLUMN_VIEW_TITLE (object); + + g_clear_pointer(&self->title, gtk_widget_unparent); + + g_clear_object (&self->column); + + G_OBJECT_CLASS (gtk_column_view_title_parent_class)->dispose (object); +} + +static void +gtk_column_view_title_class_init (GtkColumnViewTitleClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + widget_class->measure = gtk_column_view_title_measure; + widget_class->size_allocate = gtk_column_view_title_size_allocate; + + gobject_class->dispose = gtk_column_view_title_dispose; + + gtk_widget_class_set_css_name (widget_class, I_("button")); +} + +static void +gtk_column_view_title_resize_func (GtkWidget *widget) +{ + GtkColumnViewTitle *self = GTK_COLUMN_VIEW_TITLE (widget); + + if (self->column) + gtk_column_view_column_queue_resize (self->column); +} + +static void +gtk_column_view_title_init (GtkColumnViewTitle *self) +{ + GtkWidget *widget = GTK_WIDGET (self); + + widget->priv->resize_func = gtk_column_view_title_resize_func; + + self->title = gtk_label_new (NULL); + gtk_widget_set_parent (self->title, widget); +} + +GtkWidget * +gtk_column_view_title_new (GtkColumnViewColumn *column) +{ + GtkColumnViewTitle *title; + + title = g_object_new (GTK_TYPE_COLUMN_VIEW_TITLE, + NULL); + + title->column = g_object_ref (column); + gtk_column_view_title_update (title); + + return GTK_WIDGET (title); +} + +void +gtk_column_view_title_update (GtkColumnViewTitle *self) +{ + gtk_label_set_label (GTK_LABEL (self->title), gtk_column_view_column_get_title (self->column)); +} + +GtkColumnViewColumn * +gtk_column_view_title_get_column (GtkColumnViewTitle *self) +{ + return self->column; +} diff --git a/gtk/gtkcolumnviewtitleprivate.h b/gtk/gtkcolumnviewtitleprivate.h new file mode 100644 index 0000000000..84bc6a4050 --- /dev/null +++ b/gtk/gtkcolumnviewtitleprivate.h @@ -0,0 +1,47 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __GTK_COLUMN_VIEW_TITLE_PRIVATE_H__ +#define __GTK_COLUMN_VIEW_TITLE_PRIVATE_H__ + +#include "gtkcolumnviewcolumn.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_COLUMN_VIEW_TITLE (gtk_column_view_title_get_type ()) +#define GTK_COLUMN_VIEW_TITLE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_COLUMN_VIEW_TITLE, GtkColumnViewTitle)) +#define GTK_COLUMN_VIEW_TITLE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_COLUMN_VIEW_TITLE, GtkColumnViewTitleClass)) +#define GTK_IS_COLUMN_VIEW_TITLE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_COLUMN_VIEW_TITLE)) +#define GTK_IS_COLUMN_VIEW_TITLE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_COLUMN_VIEW_TITLE)) +#define GTK_COLUMN_VIEW_TITLE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_COLUMN_VIEW_TITLE, GtkColumnViewTitleClass)) + +typedef struct _GtkColumnViewTitle GtkColumnViewTitle; +typedef struct _GtkColumnViewTitleClass GtkColumnViewTitleClass; + +GType gtk_column_view_title_get_type (void) G_GNUC_CONST; + +GtkWidget * gtk_column_view_title_new (GtkColumnViewColumn *column); + +void gtk_column_view_title_update (GtkColumnViewTitle *self); + +GtkColumnViewColumn * gtk_column_view_title_get_column (GtkColumnViewTitle *self); + +G_END_DECLS + +#endif /* __GTK_COLUMN_VIEW_TITLE_PRIVATE_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index 565c23ad5b..642904b231 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -40,6 +40,7 @@ gtk_private_sources = files([ 'gtkcolumnlistitemfactory.c', 'gtkcolumnviewcell.c', 'gtkcolumnviewlayout.c', + 'gtkcolumnviewtitle.c', 'gtkconstraintexpression.c', 'gtkconstraintsolver.c', 'gtkconstraintvflparser.c', From e72119e9bb463b172b33980cf3c5c62ee57cd613 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 9 Nov 2019 00:36:59 +0100 Subject: [PATCH 144/170] inspector: Remove private struct from controllers --- gtk/inspector/controllers.c | 79 ++++++++++++++++++++----------------- gtk/inspector/controllers.h | 27 ++----------- 2 files changed, 46 insertions(+), 60 deletions(-) diff --git a/gtk/inspector/controllers.c b/gtk/inspector/controllers.c index 2e926675d7..ad5181b89e 100644 --- a/gtk/inspector/controllers.c +++ b/gtk/inspector/controllers.c @@ -37,40 +37,46 @@ #include "gtkstylecontext.h" #include "gtkcustomsorter.h" -enum +struct _GtkInspectorControllers { - PROP_0, - PROP_OBJECT_TREE -}; + GtkBox parent_instance; -struct _GtkInspectorControllersPrivate -{ GtkWidget *listbox; GtkPropertyLookupListModel *model; GtkSizeGroup *sizegroup; GtkInspectorObjectTree *object_tree; }; -G_DEFINE_TYPE_WITH_PRIVATE (GtkInspectorControllers, gtk_inspector_controllers, GTK_TYPE_BOX) +struct _GtkInspectorControllersClass +{ + GtkBoxClass parent_class; +}; + +enum +{ + PROP_0, + PROP_OBJECT_TREE +}; + +G_DEFINE_TYPE (GtkInspectorControllers, gtk_inspector_controllers, GTK_TYPE_BOX) static void row_activated (GtkListBox *box, GtkListBoxRow *row, - GtkInspectorControllers *sl) + GtkInspectorControllers *self) { GObject *controller; controller = G_OBJECT (g_object_get_data (G_OBJECT (row), "controller")); - gtk_inspector_object_tree_select_object (sl->priv->object_tree, controller); + gtk_inspector_object_tree_select_object (self->object_tree, controller); } static void -gtk_inspector_controllers_init (GtkInspectorControllers *sl) +gtk_inspector_controllers_init (GtkInspectorControllers *self) { GtkWidget *sw, *box; - sl->priv = gtk_inspector_controllers_get_instance_private (sl); - sl->priv->sizegroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + self->sizegroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); sw = gtk_scrolled_window_new (NULL, NULL); @@ -85,19 +91,19 @@ gtk_inspector_controllers_init (GtkInspectorControllers *sl) gtk_widget_set_hexpand (box, TRUE); gtk_widget_set_vexpand (box, TRUE); - sl->priv->listbox = gtk_list_box_new (); - gtk_widget_add_css_class (sl->priv->listbox, "frame"); - gtk_widget_set_halign (sl->priv->listbox, GTK_ALIGN_CENTER); - g_signal_connect (sl->priv->listbox, "row-activated", G_CALLBACK (row_activated), sl); - gtk_list_box_set_selection_mode (GTK_LIST_BOX (sl->priv->listbox), GTK_SELECTION_NONE); - gtk_box_append (GTK_BOX (box), sl->priv->listbox); + self->listbox = gtk_list_box_new (); + gtk_widget_add_css_class (self->listbox, "frame"); + gtk_widget_set_halign (self->listbox, GTK_ALIGN_CENTER); + g_signal_connect (self->listbox, "row-activated", G_CALLBACK (row_activated), self); + gtk_list_box_set_selection_mode (GTK_LIST_BOX (self->listbox), GTK_SELECTION_NONE); + gtk_box_append (GTK_BOX (box), self->listbox); - gtk_box_append (GTK_BOX (sl), sw); + gtk_box_append (GTK_BOX (self), sw); } static void phase_changed_cb (GtkComboBox *combo, - GtkInspectorControllers *sl) + GtkInspectorControllers *self) { GtkWidget *row; GtkPropagationPhase phase; @@ -113,8 +119,8 @@ static GtkWidget * create_controller_widget (gpointer item, gpointer user_data) { + GtkInspectorControllers *self = user_data; GtkEventController *controller = item; - GtkInspectorControllers *sl = user_data; GtkWidget *row; GtkWidget *box; GtkWidget *label; @@ -131,7 +137,7 @@ create_controller_widget (gpointer item, label = gtk_label_new (G_OBJECT_TYPE_NAME (controller)); g_object_set (label, "xalign", 0.0, NULL); gtk_box_append (GTK_BOX (box), label); - gtk_size_group_add_widget (sl->priv->sizegroup, label); + gtk_size_group_add_widget (self->sizegroup, label); gtk_widget_set_halign (label, GTK_ALIGN_START); gtk_widget_set_valign (label, GTK_ALIGN_BASELINE); @@ -146,7 +152,7 @@ create_controller_widget (gpointer item, gtk_widget_set_valign (label, GTK_ALIGN_BASELINE); g_object_set_data (G_OBJECT (row), "controller", controller); - g_signal_connect (combo, "changed", G_CALLBACK (phase_changed_cb), sl); + g_signal_connect (combo, "changed", G_CALLBACK (phase_changed_cb), self); return row; } @@ -209,19 +215,18 @@ compare_controllers (gconstpointer _first, } void -gtk_inspector_controllers_set_object (GtkInspectorControllers *sl, +gtk_inspector_controllers_set_object (GtkInspectorControllers *self, GObject *object) { GtkWidget *stack; GtkStackPage *page; - GtkInspectorControllersPrivate *priv = sl->priv; GtkMapListModel *map_model; GtkFlattenListModel *flatten_model; GtkSortListModel *sort_model; GtkSorter *sorter; - stack = gtk_widget_get_parent (GTK_WIDGET (sl)); - page = gtk_stack_get_page (GTK_STACK (stack), GTK_WIDGET (sl)); + stack = gtk_widget_get_parent (GTK_WIDGET (self)); + page = gtk_stack_get_page (GTK_STACK (stack), GTK_WIDGET (self)); if (!GTK_IS_WIDGET (object)) { @@ -231,11 +236,11 @@ gtk_inspector_controllers_set_object (GtkInspectorControllers *sl, g_object_set (page, "visible", TRUE, NULL); - priv->model = gtk_property_lookup_list_model_new (GTK_TYPE_WIDGET, "parent"); - gtk_property_lookup_list_model_set_object (priv->model, object); + self->model = gtk_property_lookup_list_model_new (GTK_TYPE_WIDGET, "parent"); + gtk_property_lookup_list_model_set_object (self->model, object); - map_model = gtk_map_list_model_new (G_TYPE_LIST_MODEL, G_LIST_MODEL (priv->model), map_to_controllers, NULL, NULL); - g_object_unref (priv->model); + map_model = gtk_map_list_model_new (G_TYPE_LIST_MODEL, G_LIST_MODEL (self->model), map_to_controllers, NULL, NULL); + g_object_unref (self->model); flatten_model = gtk_flatten_list_model_new (GTK_TYPE_EVENT_CONTROLLER, G_LIST_MODEL (map_model)); @@ -243,10 +248,10 @@ gtk_inspector_controllers_set_object (GtkInspectorControllers *sl, sort_model = gtk_sort_list_model_new (G_LIST_MODEL (flatten_model), sorter); g_object_unref (sorter); - gtk_list_box_bind_model (GTK_LIST_BOX (priv->listbox), + gtk_list_box_bind_model (GTK_LIST_BOX (self->listbox), G_LIST_MODEL (sort_model), create_controller_widget, - sl, + self, NULL); g_object_unref (sort_model); @@ -260,12 +265,12 @@ get_property (GObject *object, GValue *value, GParamSpec *pspec) { - GtkInspectorControllers *sl = GTK_INSPECTOR_CONTROLLERS (object); + GtkInspectorControllers *self = GTK_INSPECTOR_CONTROLLERS (object); switch (param_id) { case PROP_OBJECT_TREE: - g_value_take_object (value, sl->priv->object_tree); + g_value_take_object (value, self->object_tree); break; default: @@ -280,12 +285,12 @@ set_property (GObject *object, const GValue *value, GParamSpec *pspec) { - GtkInspectorControllers *sl = GTK_INSPECTOR_CONTROLLERS (object); + GtkInspectorControllers *self = GTK_INSPECTOR_CONTROLLERS (object); switch (param_id) { case PROP_OBJECT_TREE: - sl->priv->object_tree = g_value_get_object (value); + self->object_tree = g_value_get_object (value); break; default: diff --git a/gtk/inspector/controllers.h b/gtk/inspector/controllers.h index a0f9f8c265..d4c884cb0d 100644 --- a/gtk/inspector/controllers.h +++ b/gtk/inspector/controllers.h @@ -18,32 +18,13 @@ #ifndef _GTK_INSPECTOR_CONTROLLERS_H_ #define _GTK_INSPECTOR_CONTROLLERS_H_ -#include - -#define GTK_TYPE_INSPECTOR_CONTROLLERS (gtk_inspector_controllers_get_type()) -#define GTK_INSPECTOR_CONTROLLERS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_INSPECTOR_CONTROLLERS, GtkInspectorControllers)) -#define GTK_INSPECTOR_CONTROLLERS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_INSPECTOR_CONTROLLERS, GtkInspectorControllersClass)) -#define GTK_INSPECTOR_IS_GESTURES(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_TYPE_INSPECTOR_CONTROLLERS)) -#define GTK_INSPECTOR_IS_GESTURES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GTK_TYPE_INSPECTOR_CONTROLLERS)) -#define GTK_INSPECTOR_CONTROLLERS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_INSPECTOR_CONTROLLERS, GtkInspectorControllersClass)) - - -typedef struct _GtkInspectorControllersPrivate GtkInspectorControllersPrivate; - -typedef struct _GtkInspectorControllers -{ - GtkBox parent; - GtkInspectorControllersPrivate *priv; -} GtkInspectorControllers; - -typedef struct _GtkInspectorControllersClass -{ - GtkBoxClass parent; -} GtkInspectorControllersClass; +#include G_BEGIN_DECLS -GType gtk_inspector_controllers_get_type (void); +#define GTK_TYPE_INSPECTOR_CONTROLLERS gtk_inspector_controllers_get_type() + +G_DECLARE_FINAL_TYPE (GtkInspectorControllers, gtk_inspector_controllers, GTK, INSPECTOR_CONTROLLERS, GtkBox) void gtk_inspector_controllers_set_object (GtkInspectorControllers *sl, GObject *object); From 2787e916b25676b6afb60d426774130f3d8db761 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 9 Nov 2019 00:43:41 +0100 Subject: [PATCH 145/170] inspector: Make Controller page a GtkWidget --- gtk/inspector/controllers.c | 49 ++++++++++++++++++++++++------------- gtk/inspector/controllers.h | 4 +-- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/gtk/inspector/controllers.c b/gtk/inspector/controllers.c index ad5181b89e..fa41af9b65 100644 --- a/gtk/inspector/controllers.c +++ b/gtk/inspector/controllers.c @@ -21,8 +21,9 @@ #include "controllers.h" #include "object-tree.h" -#include "gtksizegroup.h" +#include "gtkbinlayout.h" #include "gtkcomboboxtext.h" +#include "gtkcustomsorter.h" #include "gtkflattenlistmodel.h" #include "gtkframe.h" #include "gtkgesture.h" @@ -31,15 +32,15 @@ #include "gtkmaplistmodel.h" #include "gtkpropertylookuplistmodelprivate.h" #include "gtkscrolledwindow.h" +#include "gtksizegroup.h" #include "gtksortlistmodel.h" -#include "gtkwidgetprivate.h" #include "gtkstack.h" #include "gtkstylecontext.h" -#include "gtkcustomsorter.h" +#include "gtkwidgetprivate.h" struct _GtkInspectorControllers { - GtkBox parent_instance; + GtkWidget parent_instance; GtkWidget *listbox; GtkPropertyLookupListModel *model; @@ -49,7 +50,7 @@ struct _GtkInspectorControllers struct _GtkInspectorControllersClass { - GtkBoxClass parent_class; + GtkWidgetClass parent_class; }; enum @@ -58,7 +59,7 @@ enum PROP_OBJECT_TREE }; -G_DEFINE_TYPE (GtkInspectorControllers, gtk_inspector_controllers, GTK_TYPE_BOX) +G_DEFINE_TYPE (GtkInspectorControllers, gtk_inspector_controllers, GTK_TYPE_WIDGET) static void row_activated (GtkListBox *box, @@ -98,7 +99,7 @@ gtk_inspector_controllers_init (GtkInspectorControllers *self) gtk_list_box_set_selection_mode (GTK_LIST_BOX (self->listbox), GTK_SELECTION_NONE); gtk_box_append (GTK_BOX (box), self->listbox); - gtk_box_append (GTK_BOX (self), sw); + gtk_widget_set_parent (sw, GTK_WIDGET (self)); } static void @@ -260,10 +261,10 @@ gtk_inspector_controllers_set_object (GtkInspectorControllers *self, } static void -get_property (GObject *object, - guint param_id, - GValue *value, - GParamSpec *pspec) +gtk_inspector_controllers_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) { GtkInspectorControllers *self = GTK_INSPECTOR_CONTROLLERS (object); @@ -280,10 +281,10 @@ get_property (GObject *object, } static void -set_property (GObject *object, - guint param_id, - const GValue *value, - GParamSpec *pspec) +gtk_inspector_controllers_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) { GtkInspectorControllers *self = GTK_INSPECTOR_CONTROLLERS (object); @@ -299,17 +300,31 @@ set_property (GObject *object, } } +static void +gtk_inspector_controllers_dispose (GObject *object) +{ + GtkInspectorControllers *self = GTK_INSPECTOR_CONTROLLERS (object); + + gtk_widget_unparent (gtk_widget_get_first_child (GTK_WIDGET (self))); + + G_OBJECT_CLASS (gtk_inspector_controllers_parent_class)->dispose (object); +} + static void gtk_inspector_controllers_class_init (GtkInspectorControllersClass *klass) { + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass); - object_class->get_property = get_property; - object_class->set_property = set_property; + object_class->get_property = gtk_inspector_controllers_get_property; + object_class->set_property = gtk_inspector_controllers_set_property; + object_class->dispose= gtk_inspector_controllers_dispose; g_object_class_install_property (object_class, PROP_OBJECT_TREE, g_param_spec_object ("object-tree", "Widget Tree", "Widget tree", GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); } // vim: set et sw=2 ts=2: diff --git a/gtk/inspector/controllers.h b/gtk/inspector/controllers.h index d4c884cb0d..d4df218b88 100644 --- a/gtk/inspector/controllers.h +++ b/gtk/inspector/controllers.h @@ -18,13 +18,13 @@ #ifndef _GTK_INSPECTOR_CONTROLLERS_H_ #define _GTK_INSPECTOR_CONTROLLERS_H_ -#include +#include G_BEGIN_DECLS #define GTK_TYPE_INSPECTOR_CONTROLLERS gtk_inspector_controllers_get_type() -G_DECLARE_FINAL_TYPE (GtkInspectorControllers, gtk_inspector_controllers, GTK, INSPECTOR_CONTROLLERS, GtkBox) +G_DECLARE_FINAL_TYPE (GtkInspectorControllers, gtk_inspector_controllers, GTK, INSPECTOR_CONTROLLERS, GtkWidget) void gtk_inspector_controllers_set_object (GtkInspectorControllers *sl, GObject *object); From 650688c6357d1513a039ab6bc033a8c1e6907ad8 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 9 Nov 2019 01:13:28 +0100 Subject: [PATCH 146/170] inspector: Remove private struct for prop editor --- gtk/inspector/prop-editor.c | 198 ++++++++++++++++++------------------ gtk/inspector/prop-editor.h | 34 +------ 2 files changed, 105 insertions(+), 127 deletions(-) diff --git a/gtk/inspector/prop-editor.c b/gtk/inspector/prop-editor.c index 3504ec2383..258a5fafe8 100644 --- a/gtk/inspector/prop-editor.c +++ b/gtk/inspector/prop-editor.c @@ -19,6 +19,7 @@ #include #include "prop-editor.h" + #include "strv-editor.h" #include "object-tree.h" #include "prop-list.h" @@ -48,11 +49,13 @@ #include "gtkcomboboxtext.h" #include "gtkmenubutton.h" -struct _GtkInspectorPropEditorPrivate +struct _GtkInspectorPropEditor { + GtkBox parent_instance; + GObject *object; gchar *name; - GtkWidget *editor; + GtkWidget *self; GtkSizeGroup *size_group; }; @@ -72,12 +75,12 @@ enum static guint signals[N_SIGNALS] = { 0 }; -G_DEFINE_TYPE_WITH_PRIVATE (GtkInspectorPropEditor, gtk_inspector_prop_editor, GTK_TYPE_BOX); +G_DEFINE_TYPE (GtkInspectorPropEditor, gtk_inspector_prop_editor, GTK_TYPE_BOX); static GParamSpec * -find_property (GtkInspectorPropEditor *editor) +find_property (GtkInspectorPropEditor *self) { - return g_object_class_find_property (G_OBJECT_GET_CLASS (editor->priv->object), editor->priv->name); + return g_object_class_find_property (G_OBJECT_GET_CLASS (self->object), self->name); } typedef struct @@ -133,18 +136,18 @@ g_object_connect_property (GObject *object, } static void -block_notify (GObject *editor) +block_notify (GObject *self) { - DisconnectData *dd = (DisconnectData *)g_object_get_data (editor, "alive-object-data"); + DisconnectData *dd = (DisconnectData *)g_object_get_data (self, "alive-object-data"); if (dd) g_signal_handler_block (dd->instance, dd->id); } static void -unblock_notify (GObject *editor) +unblock_notify (GObject *self) { - DisconnectData *dd = (DisconnectData *)g_object_get_data (editor, "alive-object-data"); + DisconnectData *dd = (DisconnectData *)g_object_get_data (self, "alive-object-data"); if (dd) g_signal_handler_unblock (dd->instance, dd->id); @@ -385,24 +388,24 @@ string_changed (GObject *object, GParamSpec *pspec, gpointer data) } static void -strv_modified (GtkInspectorStrvEditor *editor, ObjectProperty *p) +strv_modified (GtkInspectorStrvEditor *self, ObjectProperty *p) { GValue val = G_VALUE_INIT; gchar **strv; g_value_init (&val, G_TYPE_STRV); - strv = gtk_inspector_strv_editor_get_strv (editor); + strv = gtk_inspector_strv_editor_get_strv (self); g_value_take_boxed (&val, strv); - block_notify (G_OBJECT (editor)); + block_notify (G_OBJECT (self)); set_property_value (p->obj, p->spec, &val); - unblock_notify (G_OBJECT (editor)); + unblock_notify (G_OBJECT (self)); g_value_unset (&val); } static void strv_changed (GObject *object, GParamSpec *pspec, gpointer data) { - GtkInspectorStrvEditor *editor = data; + GtkInspectorStrvEditor *self = data; GValue val = G_VALUE_INIT; gchar **strv; @@ -410,9 +413,9 @@ strv_changed (GObject *object, GParamSpec *pspec, gpointer data) get_property_value (object, pspec, &val); strv = g_value_get_boxed (&val); - block_controller (G_OBJECT (editor)); - gtk_inspector_strv_editor_set_strv (editor, strv); - unblock_controller (G_OBJECT (editor)); + block_controller (G_OBJECT (self)); + gtk_inspector_strv_editor_set_strv (self, strv); + unblock_controller (G_OBJECT (self)); g_value_unset (&val); } @@ -689,13 +692,13 @@ object_changed (GObject *object, GParamSpec *pspec, gpointer data) } static void -object_properties (GtkInspectorPropEditor *editor) +object_properties (GtkInspectorPropEditor *self) { GObject *obj; - g_object_get (editor->priv->object, editor->priv->name, &obj, NULL); + g_object_get (self->object, self->name, &obj, NULL); if (G_IS_OBJECT (obj)) - g_signal_emit (editor, signals[SHOW_OBJECT], 0, obj, editor->priv->name, "properties"); + g_signal_emit (self, signals[SHOW_OBJECT], 0, obj, self->name, "properties"); } static void @@ -771,11 +774,11 @@ font_changed (GObject *object, GParamSpec *pspec, gpointer data) } static void -item_properties (GtkButton *button, GtkInspectorPropEditor *editor) +item_properties (GtkButton *button, GtkInspectorPropEditor *self) { GObject *item; item = g_object_get_data (G_OBJECT (button), "item"); - g_signal_emit (editor, signals[SHOW_OBJECT], 0, item, "Item", "properties"); + g_signal_emit (self, signals[SHOW_OBJECT], 0, item, "Item", "properties"); } static GtkWidget * @@ -803,7 +806,7 @@ create_row (gpointer item, static GtkWidget * property_editor (GObject *object, GParamSpec *spec, - GtkInspectorPropEditor *editor) + GtkInspectorPropEditor *self) { GtkWidget *prop_edit; GtkAdjustment *adj; @@ -1032,7 +1035,7 @@ property_editor (GObject *object, box = gtk_list_box_new (); gtk_list_box_set_selection_mode (GTK_LIST_BOX (box), GTK_SELECTION_NONE); - gtk_list_box_bind_model (GTK_LIST_BOX (box), model, create_row, editor, NULL); + gtk_list_box_bind_model (GTK_LIST_BOX (box), model, create_row, self, NULL); g_object_unref (model); gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), box); @@ -1047,7 +1050,7 @@ property_editor (GObject *object, button = gtk_button_new_with_label (_("Properties")); g_signal_connect_swapped (button, "clicked", G_CALLBACK (object_properties), - editor); + self); gtk_box_append (GTK_BOX (prop_edit), label); gtk_box_append (GTK_BOX (prop_edit), button); gtk_widget_show (label); @@ -1118,10 +1121,9 @@ property_editor (GObject *object, } static void -gtk_inspector_prop_editor_init (GtkInspectorPropEditor *editor) +gtk_inspector_prop_editor_init (GtkInspectorPropEditor *self) { - editor->priv = gtk_inspector_prop_editor_get_instance_private (editor); - g_object_set (editor, + g_object_set (self, "orientation", GTK_ORIENTATION_HORIZONTAL, "spacing", 10, NULL); @@ -1153,17 +1155,17 @@ gtk_cell_layout_get_widget (GtkCellLayout *layout) static void model_properties (GtkButton *button, - GtkInspectorPropEditor *editor) + GtkInspectorPropEditor *self) { GObject *model; model = g_object_get_data (G_OBJECT (button), "model"); - g_signal_emit (editor, signals[SHOW_OBJECT], 0, model, "model", "data"); + g_signal_emit (self, signals[SHOW_OBJECT], 0, model, "model", "data"); } static void attribute_mapping_changed (GtkComboBox *combo, - GtkInspectorPropEditor *editor) + GtkInspectorPropEditor *self) { gint col; gpointer layout; @@ -1171,16 +1173,16 @@ attribute_mapping_changed (GtkComboBox *combo, GtkCellArea *area; col = gtk_combo_box_get_active (combo) - 1; - layout = g_object_get_data (editor->priv->object, "gtk-inspector-cell-layout"); + layout = g_object_get_data (self->object, "gtk-inspector-cell-layout"); if (GTK_IS_CELL_LAYOUT (layout)) { - cell = GTK_CELL_RENDERER (editor->priv->object); + cell = GTK_CELL_RENDERER (self->object); area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (layout)); - gtk_cell_area_attribute_disconnect (area, cell, editor->priv->name); + gtk_cell_area_attribute_disconnect (area, cell, self->name); if (col != -1) - gtk_cell_area_attribute_connect (area, cell, editor->priv->name, col); - gtk_widget_set_sensitive (editor->priv->editor, col == -1); - notify_property (editor->priv->object, find_property (editor)); + gtk_cell_area_attribute_connect (area, cell, self->name, col); + gtk_widget_set_sensitive (self->self, col == -1); + notify_property (self->object, find_property (self)); gtk_widget_queue_draw (gtk_cell_layout_get_widget (GTK_CELL_LAYOUT (layout))); } } @@ -1188,7 +1190,7 @@ attribute_mapping_changed (GtkComboBox *combo, static GtkWidget * attribute_editor (GObject *object, GParamSpec *spec, - GtkInspectorPropEditor *editor) + GtkInspectorPropEditor *self) { gpointer layout; GtkCellArea *area; @@ -1211,7 +1213,7 @@ attribute_editor (GObject *object, area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (layout)); col = gtk_cell_area_attribute_get_column (area, GTK_CELL_RENDERER (object), - editor->priv->name); + self->name); model = gtk_cell_layout_get_model (GTK_CELL_LAYOUT (layout)); } @@ -1222,7 +1224,7 @@ attribute_editor (GObject *object, button = gtk_button_new_with_label (_("Model")); g_object_set_data (G_OBJECT (button), "model", model); - g_signal_connect (button, "clicked", G_CALLBACK (model_properties), editor); + g_signal_connect (button, "clicked", G_CALLBACK (model_properties), self); gtk_box_append (GTK_BOX (box), button); gtk_box_append (GTK_BOX (box), gtk_label_new (_("Column:"))); @@ -1246,9 +1248,9 @@ attribute_editor (GObject *object, g_free (text); } gtk_combo_box_set_active (GTK_COMBO_BOX (combo), col + 1); - attribute_mapping_changed (GTK_COMBO_BOX (combo), editor); + attribute_mapping_changed (GTK_COMBO_BOX (combo), self); g_signal_connect (combo, "changed", - G_CALLBACK (attribute_mapping_changed), editor); + G_CALLBACK (attribute_mapping_changed), self); gtk_box_append (GTK_BOX (box), combo); return box; @@ -1293,17 +1295,17 @@ find_action_owner (GtkActionable *actionable) static void show_action_owner (GtkButton *button, - GtkInspectorPropEditor *editor) + GtkInspectorPropEditor *self) { GObject *owner; owner = g_object_get_data (G_OBJECT (button), "owner"); - g_signal_emit (editor, signals[SHOW_OBJECT], 0, owner, NULL, "actions"); + g_signal_emit (self, signals[SHOW_OBJECT], 0, owner, NULL, "actions"); } static GtkWidget * action_editor (GObject *object, - GtkInspectorPropEditor *editor) + GtkInspectorPropEditor *self) { GtkWidget *box; GtkWidget *button; @@ -1322,7 +1324,7 @@ action_editor (GObject *object, button = gtk_button_new_with_label (_("Properties")); g_object_set_data (G_OBJECT (button), "owner", owner); g_signal_connect (button, "clicked", - G_CALLBACK (show_action_owner), editor); + G_CALLBACK (show_action_owner), self); gtk_box_append (GTK_BOX (box), button); } @@ -1375,25 +1377,25 @@ typedef struct } GSettingsBinding; static void -add_attribute_info (GtkInspectorPropEditor *editor, +add_attribute_info (GtkInspectorPropEditor *self, GParamSpec *spec) { - if (GTK_IS_CELL_RENDERER (editor->priv->object)) - gtk_box_append (GTK_BOX (editor), - attribute_editor (editor->priv->object, spec, editor)); + if (GTK_IS_CELL_RENDERER (self->object)) + gtk_box_append (GTK_BOX (self), + attribute_editor (self->object, spec, self)); } static void -add_actionable_info (GtkInspectorPropEditor *editor) +add_actionable_info (GtkInspectorPropEditor *self) { - if (GTK_IS_ACTIONABLE (editor->priv->object) && - g_strcmp0 (editor->priv->name, "action-name") == 0) - gtk_box_append (GTK_BOX (editor), - action_editor (editor->priv->object, editor)); + if (GTK_IS_ACTIONABLE (self->object) && + g_strcmp0 (self->name, "action-name") == 0) + gtk_box_append (GTK_BOX (self), + action_editor (self->object, self)); } static void -add_settings_info (GtkInspectorPropEditor *editor) +add_settings_info (GtkInspectorPropEditor *self) { gchar *key; GSettingsBinding *binding; @@ -1405,8 +1407,8 @@ add_settings_info (GtkInspectorPropEditor *editor) GtkWidget *label; gchar *str; - object = editor->priv->object; - name = editor->priv->name; + object = self->object; + name = self->name; key = g_strconcat ("gsettingsbinding-", name, NULL); binding = (GSettingsBinding *)g_object_get_data (object, key); @@ -1450,18 +1452,18 @@ add_settings_info (GtkInspectorPropEditor *editor) gtk_box_append (GTK_BOX (row), label); g_free (str); - gtk_box_append (GTK_BOX (editor), row); + gtk_box_append (GTK_BOX (self), row); } static void -reset_setting (GtkInspectorPropEditor *editor) +reset_setting (GtkInspectorPropEditor *self) { - gtk_settings_reset_property (GTK_SETTINGS (editor->priv->object), - editor->priv->name); + gtk_settings_reset_property (GTK_SETTINGS (self->object), + self->name); } static void -add_gtk_settings_info (GtkInspectorPropEditor *editor) +add_gtk_settings_info (GtkInspectorPropEditor *self) { GObject *object; const gchar *name; @@ -1469,8 +1471,8 @@ add_gtk_settings_info (GtkInspectorPropEditor *editor) const gchar *source; GtkWidget *button; - object = editor->priv->object; - name = editor->priv->name; + object = self->object; + name = self->name; if (!GTK_IS_SETTINGS (object)) return; @@ -1480,7 +1482,7 @@ add_gtk_settings_info (GtkInspectorPropEditor *editor) button = gtk_button_new_with_label (_("Reset")); gtk_box_append (GTK_BOX (row), button); gtk_widget_set_sensitive (button, FALSE); - g_signal_connect_swapped (button, "clicked", G_CALLBACK (reset_setting), editor); + g_signal_connect_swapped (button, "clicked", G_CALLBACK (reset_setting), self); switch (_gtk_settings_get_setting_source (GTK_SETTINGS (object), name)) { @@ -1504,7 +1506,7 @@ add_gtk_settings_info (GtkInspectorPropEditor *editor) gtk_box_append (GTK_BOX (row), gtk_label_new (_("Source:"))); gtk_box_append (GTK_BOX (row), gtk_label_new (source)); - gtk_box_append (GTK_BOX (editor), row); + gtk_box_append (GTK_BOX (self), row); } static void @@ -1530,13 +1532,13 @@ readonly_changed (GObject *object, static void constructed (GObject *object) { - GtkInspectorPropEditor *editor = GTK_INSPECTOR_PROP_EDITOR (object); + GtkInspectorPropEditor *self = GTK_INSPECTOR_PROP_EDITOR (object); GParamSpec *spec; GtkWidget *label; gboolean can_modify; GtkWidget *box; - spec = find_property (editor); + spec = find_property (self); can_modify = ((spec->flags & G_PARAM_WRITABLE) != 0 && (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0); @@ -1569,35 +1571,35 @@ constructed (GObject *object) gtk_widget_add_css_class (label, GTK_STYLE_CLASS_DIM_LABEL); gtk_box_append (GTK_BOX (box), label); - readonly_changed (editor->priv->object, spec, label); - g_object_connect_property (editor->priv->object, spec, + readonly_changed (self->object, spec, label); + g_object_connect_property (self->object, spec, G_CALLBACK (readonly_changed), label, G_OBJECT (label)); - if (editor->priv->size_group) - gtk_size_group_add_widget (editor->priv->size_group, box); - gtk_box_append (GTK_BOX (editor), box); + if (self->size_group) + gtk_size_group_add_widget (self->size_group, box); + gtk_box_append (GTK_BOX (self), box); return; } - editor->priv->editor = property_editor (editor->priv->object, spec, editor); - gtk_box_append (GTK_BOX (box), editor->priv->editor); - if (editor->priv->size_group) - gtk_size_group_add_widget (editor->priv->size_group, box); - gtk_box_append (GTK_BOX (editor), box); + self->self = property_editor (self->object, spec, self); + gtk_box_append (GTK_BOX (box), self->self); + if (self->size_group) + gtk_size_group_add_widget (self->size_group, box); + gtk_box_append (GTK_BOX (self), box); - add_attribute_info (editor, spec); - add_actionable_info (editor); - add_settings_info (editor); - add_gtk_settings_info (editor); + add_attribute_info (self, spec); + add_actionable_info (self); + add_settings_info (self); + add_gtk_settings_info (self); } static void finalize (GObject *object) { - GtkInspectorPropEditor *editor = GTK_INSPECTOR_PROP_EDITOR (object); + GtkInspectorPropEditor *self = GTK_INSPECTOR_PROP_EDITOR (object); - g_free (editor->priv->name); + g_free (self->name); G_OBJECT_CLASS (gtk_inspector_prop_editor_parent_class)->finalize (object); } @@ -1608,20 +1610,20 @@ get_property (GObject *object, GValue *value, GParamSpec *pspec) { - GtkInspectorPropEditor *r = GTK_INSPECTOR_PROP_EDITOR (object); + GtkInspectorPropEditor *self = GTK_INSPECTOR_PROP_EDITOR (object); switch (param_id) { case PROP_OBJECT: - g_value_set_object (value, r->priv->object); + g_value_set_object (value, self->object); break; case PROP_NAME: - g_value_set_string (value, r->priv->name); + g_value_set_string (value, self->name); break; case PROP_SIZE_GROUP: - g_value_set_object (value, r->priv->size_group); + g_value_set_object (value, self->size_group); break; default: @@ -1636,21 +1638,21 @@ set_property (GObject *object, const GValue *value, GParamSpec *pspec) { - GtkInspectorPropEditor *r = GTK_INSPECTOR_PROP_EDITOR (object); + GtkInspectorPropEditor *self = GTK_INSPECTOR_PROP_EDITOR (object); switch (param_id) { case PROP_OBJECT: - r->priv->object = g_value_get_object (value); + self->object = g_value_get_object (value); break; case PROP_NAME: - g_free (r->priv->name); - r->priv->name = g_value_dup_string (value); + g_free (self->name); + self->name = g_value_dup_string (value); break; case PROP_SIZE_GROUP: - r->priv->size_group = g_value_get_object (value); + self->size_group = g_value_get_object (value); break; default: @@ -1673,7 +1675,7 @@ gtk_inspector_prop_editor_class_init (GtkInspectorPropEditorClass *klass) g_signal_new ("show-object", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GtkInspectorPropEditorClass, show_object), + 0, NULL, NULL, NULL, G_TYPE_NONE, 3, G_TYPE_OBJECT, G_TYPE_STRING, G_TYPE_STRING); @@ -1703,13 +1705,13 @@ gtk_inspector_prop_editor_new (GObject *object, } gboolean -gtk_inspector_prop_editor_should_expand (GtkInspectorPropEditor *editor) +gtk_inspector_prop_editor_should_expand (GtkInspectorPropEditor *self) { - if (GTK_IS_SCROLLED_WINDOW (editor->priv->editor)) + if (GTK_IS_SCROLLED_WINDOW (self->self)) { GtkPolicyType policy; - g_object_get (editor->priv->editor, "vscrollbar-policy", &policy, NULL); + g_object_get (self->self, "vscrollbar-policy", &policy, NULL); if (policy != GTK_POLICY_NEVER) return TRUE; } diff --git a/gtk/inspector/prop-editor.h b/gtk/inspector/prop-editor.h index 6c7cb2555d..6b0f720b74 100644 --- a/gtk/inspector/prop-editor.h +++ b/gtk/inspector/prop-editor.h @@ -19,40 +19,16 @@ #define _GTK_INSPECTOR_PROP_EDITOR_H_ -#include +#include #include - -#define GTK_TYPE_INSPECTOR_PROP_EDITOR (gtk_inspector_prop_editor_get_type()) -#define GTK_INSPECTOR_PROP_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_INSPECTOR_PROP_EDITOR, GtkInspectorPropEditor)) -#define GTK_INSPECTOR_PROP_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_INSPECTOR_PROP_EDITOR, GtkInspectorPropEditorClass)) -#define GTK_INSPECTOR_IS_PROP_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_TYPE_INSPECTOR_PROP_EDITOR)) -#define GTK_INSPECTOR_IS_PROP_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GTK_TYPE_INSPECTOR_PROP_EDITOR)) -#define GTK_INSPECTOR_PROP_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_INSPECTOR_PROP_EDITOR, GtkInspectorPropEditorClass)) - -typedef struct _GtkInspectorPropEditorPrivate GtkInspectorPropEditorPrivate; - -typedef struct -{ - GtkBox parent; - GtkInspectorPropEditorPrivate *priv; -} GtkInspectorPropEditor; - -typedef struct -{ - GtkBoxClass parent; - - void (*show_object) (GtkInspectorPropEditor *editor, - GObject *object, - const gchar *name, - const gchar *tab); -} GtkInspectorPropEditorClass; - - G_BEGIN_DECLS -GType gtk_inspector_prop_editor_get_type (void); +#define GTK_TYPE_INSPECTOR_PROP_EDITOR (gtk_inspector_prop_editor_get_type()) + +G_DECLARE_FINAL_TYPE (GtkInspectorPropEditor, gtk_inspector_prop_editor, GTK, INSPECTOR_PROP_EDITOR, GtkBox) + GtkWidget *gtk_inspector_prop_editor_new (GObject *object, const gchar *name, GtkSizeGroup *values); From c337887e29bc1bd9da374ddd3a00d92a774bf32c Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 26 Nov 2019 06:09:20 +0100 Subject: [PATCH 147/170] xxx: Add a hack to make paintables transform to/from objects See also: https://gitlab.gnome.org/GNOME/glib/merge_requests/1251 --- gdk/gdkpaintable.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/gdk/gdkpaintable.c b/gdk/gdkpaintable.c index fc91762784..56b13bcd95 100644 --- a/gdk/gdkpaintable.c +++ b/gdk/gdkpaintable.c @@ -136,6 +136,16 @@ static double gdk_paintable_default_get_intrinsic_aspect_ratio (GdkPaintable *pa return (double) width / height; }; +static void +g_value_object_transform_value (const GValue *src_value, + GValue *dest_value) +{ + if (src_value->data[0].v_pointer && g_type_is_a (G_OBJECT_TYPE (src_value->data[0].v_pointer), G_VALUE_TYPE (dest_value))) + dest_value->data[0].v_pointer = g_object_ref (src_value->data[0].v_pointer); + else + dest_value->data[0].v_pointer = NULL; +} + static void gdk_paintable_default_init (GdkPaintableInterface *iface) { @@ -146,6 +156,9 @@ gdk_paintable_default_init (GdkPaintableInterface *iface) iface->get_intrinsic_height = gdk_paintable_default_get_intrinsic_height; iface->get_intrinsic_aspect_ratio = gdk_paintable_default_get_intrinsic_aspect_ratio; + g_value_register_transform_func (G_TYPE_OBJECT, GDK_TYPE_PAINTABLE, g_value_object_transform_value); + g_value_register_transform_func (GDK_TYPE_PAINTABLE, G_TYPE_OBJECT, g_value_object_transform_value); + /** * GdkPaintable::invalidate-contents * @paintable: a #GdkPaintable From 22e6fa3a64024aef798138db7b6e9a3e0ccd1565 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sun, 24 Nov 2019 08:07:33 +0100 Subject: [PATCH 148/170] gtk-demo: Add a Clocks demo This demo is meant to showcase expressions. It also needs the fixes in glib 2.64 to work properly. --- demos/gtk-demo/demo.gresource.xml | 1 + demos/gtk-demo/listview_clocks.c | 487 ++++++++++++++++++++++++++++++ demos/gtk-demo/meson.build | 3 +- 3 files changed, 490 insertions(+), 1 deletion(-) create mode 100644 demos/gtk-demo/listview_clocks.c diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml index bfaca7e81a..5e18b3b195 100644 --- a/demos/gtk-demo/demo.gresource.xml +++ b/demos/gtk-demo/demo.gresource.xml @@ -216,6 +216,7 @@ links.c listbox.c listview_applauncher.c + listview_clocks.c listview_filebrowser.c listview_minesweeper.c listview_settings.c diff --git a/demos/gtk-demo/listview_clocks.c b/demos/gtk-demo/listview_clocks.c new file mode 100644 index 0000000000..3254428d7b --- /dev/null +++ b/demos/gtk-demo/listview_clocks.c @@ -0,0 +1,487 @@ +/* Lists/Clocks + * + * This demo displays the time in different timezones. + * + * The goal is to show how to set up expressions that track changes + * in objects and make them update widgets. + * + * For that, we create a GtkClock object that updates its time every + * second and then use various ways to display that time. + * + * Typically, this will be done using GtkBuilder .ui files with the + * help of the tag, but this demo shows the code that runs + * behind that. + */ + +#include + +#define GTK_TYPE_CLOCK (gtk_clock_get_type ()) +G_DECLARE_FINAL_TYPE (GtkClock, gtk_clock, GTK, CLOCK, GObject) + +/* This is our object. It's just a timezone */ +typedef struct _GtkClock GtkClock; +struct _GtkClock +{ + GObject parent_instance; + + /* We allow this to be NULL for the local timezone */ + GTimeZone *timezone; + /* Name of the location we're displaying time for */ + char *location; +}; + +enum { + PROP_0, + PROP_LOCATION, + PROP_TIME, + PROP_TIMEZONE, + + N_PROPS +}; + +/* This function returns the current time in the clock's timezone. + * Note that this returns a new object every time, so we need to + * remember to unref it after use. */ +static GDateTime * +gtk_clock_get_time (GtkClock *clock) +{ + if (clock->timezone) + return g_date_time_new_now (clock->timezone); + else + return g_date_time_new_now_local (); +} + +/* Here, we implement the functionality required by the GdkPaintable interface. + * This way we have a trivial way to display an analog clock. + * It also allows demonstrating how to directly use objects in the listview + * later by making this object do something interesting. */ +static void +gtk_clock_snapshot (GdkPaintable *paintable, + GdkSnapshot *snapshot, + double width, + double height) +{ + GtkClock *self = GTK_CLOCK (paintable); + GDateTime *time; + GskRoundedRect outline; + +#define BLACK ((GdkRGBA) { 0, 0, 0, 1 }) + + /* save/restore() is necessary so we can undo the transforms we start + * out with. */ + gtk_snapshot_save (snapshot); + + /* First, we move the (0, 0) point to the center of the area so + * we can draw everything relative to it. */ + gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (width / 2, height / 2)); + /* Next we scale it, so that we can pretend that the clock is + * 100px in size. That way, we don't need to do any complicated + * math later. + * We use MIN() here so that we use the smaller dimension for sizing. + * That way we don't overdraw but keep the aspect ratio. */ + gtk_snapshot_scale (snapshot, MIN (width, height) / 100.0, MIN (width, height) / 100.0); + /* Now we have a circle with diameter 100px (and radius 50px) that + * has its (0, 0) point at the center. + * Let's draw a simple clock into it. */ + + time = gtk_clock_get_time (self); + + /* First, draw a circle. This is a neat little trick to draw a circle + * without requiring Cairo. */ + gsk_rounded_rect_init_from_rect (&outline, &GRAPHENE_RECT_INIT(-50, -50, 100, 100), 50); + gtk_snapshot_append_border (snapshot, + &outline, + (float[4]) { 4, 4, 4, 4 }, + (GdkRGBA [4]) { BLACK, BLACK, BLACK, BLACK }); + + /* Next, draw the hour hand. + * We do this using tranforms again: Instead of computing where the angle points + * to, we just rotate everything and then draw the hand as if if was :00. + * We don't even need to care about am/pm here because rotations just work. */ + gtk_snapshot_save (snapshot); + gtk_snapshot_rotate (snapshot, 30 * g_date_time_get_hour (time) + 0.5 * g_date_time_get_minute (time)); + gsk_rounded_rect_init_from_rect (&outline, &GRAPHENE_RECT_INIT(-2, -23, 4, 25), 2); + gtk_snapshot_push_rounded_clip (snapshot, &outline); + gtk_snapshot_append_color (snapshot, &BLACK, &outline.bounds); + gtk_snapshot_pop (snapshot); + gtk_snapshot_restore (snapshot); + + /* And the same as above for the minute hand. Just make this one longer + * so people can tell the hands apart. */ + gtk_snapshot_save (snapshot); + gtk_snapshot_rotate (snapshot, 6 * g_date_time_get_minute (time)); + gsk_rounded_rect_init_from_rect (&outline, &GRAPHENE_RECT_INIT(-2, -43, 4, 45), 2); + gtk_snapshot_push_rounded_clip (snapshot, &outline); + gtk_snapshot_append_color (snapshot, &BLACK, &outline.bounds); + gtk_snapshot_pop (snapshot); + gtk_snapshot_restore (snapshot); + + /* and finally, the second indicator. */ + gtk_snapshot_save (snapshot); + gtk_snapshot_rotate (snapshot, 6 * g_date_time_get_second (time)); + gsk_rounded_rect_init_from_rect (&outline, &GRAPHENE_RECT_INIT(-2, -43, 4, 10), 2); + gtk_snapshot_push_rounded_clip (snapshot, &outline); + gtk_snapshot_append_color (snapshot, &BLACK, &outline.bounds); + gtk_snapshot_pop (snapshot); + gtk_snapshot_restore (snapshot); + + /* And finally, don't forget to restore the initial save() that we did for + * the initial transformations. */ + gtk_snapshot_restore (snapshot); + + g_date_time_unref (time); +} + +/* Our desired size is 100px. That sounds okay for an analog clock */ +static int +gtk_clock_get_intrinsic_width (GdkPaintable *paintable) +{ + return 100; +} + +static int +gtk_clock_get_intrinsic_height (GdkPaintable *paintable) +{ + return 100; +} + +/* Initialize the paintable interface. This way we turn our clock objects + * into objects that can be drawn. + * There are more functions to this interface to define desired size, + * but this is enough. + */ +static void +gtk_clock_paintable_init (GdkPaintableInterface *iface) +{ + iface->snapshot = gtk_clock_snapshot; + iface->get_intrinsic_width = gtk_clock_get_intrinsic_width; + iface->get_intrinsic_height = gtk_clock_get_intrinsic_height; +} + +/* Finally, we define the type. The important part is adding the paintable + * interface, so GTK knows that this object can indeed be drawm. + */ +G_DEFINE_TYPE_WITH_CODE (GtkClock, gtk_clock, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, + gtk_clock_paintable_init)) + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +static void +gtk_clock_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GtkClock *self = GTK_CLOCK (object); + + switch (property_id) + { + case PROP_LOCATION: + g_value_set_string (value, self->location); + break; + + case PROP_TIME: + g_value_take_boxed (value, gtk_clock_get_time (self)); + break; + + case PROP_TIMEZONE: + g_value_set_boxed (value, self->timezone); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gtk_clock_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkClock *self = GTK_CLOCK (object); + + switch (property_id) + { + case PROP_LOCATION: + self->location = g_value_dup_string (value); + break; + + case PROP_TIMEZONE: + self->timezone = g_value_dup_boxed (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +/* This is the list of all the ticking clocks */ +static GSList *ticking_clocks = NULL; +/* This is the id of the timeout source that is updating all ticking clocks */ +static guint ticking_clock_id = 0; + +/* Every second, this function is called to tell everybody that the + * clocks are ticking. + */ +static gboolean +gtk_clock_tick (gpointer unused) +{ + GSList *l; + + for (l = ticking_clocks; l; l = l->next) + { + GtkClock *clock = l->data; + + /* We will now return a different value for the time porperty, + * so notify about that. + */ + g_object_notify_by_pspec (G_OBJECT (clock), properties[PROP_TIME]); + /* We will also draw the hands of the clock differently. + * So notify about that, too. + */ + gdk_paintable_invalidate_contents (GDK_PAINTABLE (clock)); + } + + return G_SOURCE_CONTINUE; +} + +static void +gtk_clock_stop_ticking (GtkClock *self) +{ + ticking_clocks = g_slist_remove (ticking_clocks, self); + + /* If no clock is remaining, stop running the tick updates */ + if (ticking_clocks == NULL && ticking_clock_id != 0) + g_clear_handle_id (&ticking_clock_id, g_source_remove); +} + +static void +gtk_clock_start_ticking (GtkClock *self) +{ + /* if no clock is ticking yet, start */ + if (ticking_clock_id == 0) + ticking_clock_id = g_timeout_add_seconds (1, gtk_clock_tick, NULL); + + ticking_clocks = g_slist_prepend (ticking_clocks, self); +} + +static void +gtk_clock_finalize (GObject *object) +{ + GtkClock *self = GTK_CLOCK (object); + + gtk_clock_stop_ticking (self); + + g_free (self->location); + g_clear_pointer (&self->timezone, g_time_zone_unref); + + G_OBJECT_CLASS (gtk_clock_parent_class)->finalize (object); +} + +static void +gtk_clock_class_init (GtkClockClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->get_property = gtk_clock_get_property; + gobject_class->set_property = gtk_clock_set_property; + gobject_class->finalize = gtk_clock_finalize; + + properties[PROP_LOCATION] = + g_param_spec_string ("location", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + properties[PROP_TIME] = + g_param_spec_boxed ("time", NULL, NULL, G_TYPE_DATE_TIME, G_PARAM_READABLE); + properties[PROP_TIMEZONE] = + g_param_spec_boxed ("timezone", NULL, NULL, G_TYPE_TIME_ZONE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (gobject_class, N_PROPS, properties); +} + +static void +gtk_clock_init (GtkClock *self) +{ + gtk_clock_start_ticking (self); +} + +static GtkClock * +gtk_clock_new (const char *location, + GTimeZone *timezone) +{ + GtkClock *result; + + result = g_object_new (GTK_TYPE_CLOCK, + "location", location, + "timezone", timezone, + NULL); + + g_clear_pointer (&timezone, g_time_zone_unref); + + return result; +} + +static GListModel * +create_clocks_model (void) +{ + GListStore *result; + GtkClock *clock; + + result = g_list_store_new (GTK_TYPE_CLOCK); + + /* local time */ + clock = gtk_clock_new ("local", NULL); + g_list_store_append (result, clock); + g_object_unref (clock); + /* UTC time */ + clock = gtk_clock_new ("UTC", g_time_zone_new_utc ()); + g_list_store_append (result, clock); + g_object_unref (clock); + /* A bunch of timezones with GTK hackers */ + clock = gtk_clock_new ("San Francisco", g_time_zone_new ("America/Los_Angeles")); + g_list_store_append (result, clock); + g_object_unref (clock); + clock = gtk_clock_new ("Boston", g_time_zone_new ("America/New_York")); + g_list_store_append (result, clock); + g_object_unref (clock); + clock = gtk_clock_new ("London", g_time_zone_new ("Europe/London")); + g_list_store_append (result, clock); + g_object_unref (clock); + clock = gtk_clock_new ("Berlin", g_time_zone_new ("Europe/Berlin")); + g_list_store_append (result, clock); + g_object_unref (clock); + clock = gtk_clock_new ("Moscow", g_time_zone_new ("Europe/Moscow")); + g_list_store_append (result, clock); + g_object_unref (clock); + clock = gtk_clock_new ("New Delhi", g_time_zone_new ("Asia/Kolkata")); + g_list_store_append (result, clock); + g_object_unref (clock); + clock = gtk_clock_new ("Shanghai", g_time_zone_new ("Asia/Shanghai")); + g_list_store_append (result, clock); + g_object_unref (clock); + + return G_LIST_MODEL (result); +} + +static char * +convert_time_to_string (GObject *image, + GDateTime *time, + gpointer unused) +{ + return g_date_time_format (time, "%x\n%X"); +} + +/* And this function is the crux for this whole demo. + * It shows how to use expressions to set up bindings. + */ +static void +setup_listitem_cb (GtkListItemFactory *factory, + GtkListItem *list_item) +{ + GtkWidget *box, *picture, *location_label, *time_label; + GtkExpression *clock_expression, *expression; + + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_list_item_set_child (list_item, box); + + /* First, we create an expression that gets us the clock from the listitem: + * 1. Create an expression that gets the list item. + * 2. Use that expression's "item" property to get the clock + */ + expression = gtk_constant_expression_new (GTK_TYPE_LIST_ITEM, list_item); + clock_expression = gtk_property_expression_new (GTK_TYPE_LIST_ITEM, expression, "item"); + + /* Bind the clock's location to a label. + * This is easy: We just get the "location" property of the clock. + */ + expression = gtk_property_expression_new (GTK_TYPE_CLOCK, + gtk_expression_ref (clock_expression), + "location"); + /* Now create the label and bind the expression to it. */ + location_label = gtk_label_new (NULL); + gtk_expression_bind (expression, location_label, "label"); + gtk_box_append (GTK_BOX (box), location_label); + + + /* Here we bind the item itself to a GdkPicture. + * This is simply done by using the clock expression itself. + */ + expression = gtk_expression_ref (clock_expression); + /* Now create the widget and bind the expression to it. */ + picture = gtk_picture_new (); + gtk_expression_bind (expression, picture, "paintable"); + gtk_box_append (GTK_BOX (box), picture); + + + /* And finally, everything comes together. + * We create a label for displaying the time as text. + * For that, we need to transform the "GDateTime" of the + * time property into a string so that the label can display it. + */ + expression = gtk_property_expression_new (GTK_TYPE_CLOCK, + gtk_expression_ref (clock_expression), + "time"); + expression = gtk_cclosure_expression_new (G_TYPE_STRING, + NULL, + 1, (GtkExpression *[1]) { expression }, + G_CALLBACK (convert_time_to_string), + NULL, NULL); + /* Now create the label and bind the expression to it. */ + time_label = gtk_label_new (NULL); + gtk_expression_bind (expression, time_label, "label"); + gtk_box_append (GTK_BOX (box), time_label); + + gtk_expression_unref (clock_expression); +} + +static GtkWidget *window = NULL; + +GtkWidget * +do_listview_clocks (GtkWidget *do_widget) +{ + if (window == NULL) + { + GtkWidget *gridview, *sw; + GtkListItemFactory *factory; + GListModel *model; + GtkNoSelection *selection; + + /* This is the normal window setup code every demo does */ + window = gtk_window_new (); + gtk_window_set_display (GTK_WINDOW (window), + gtk_widget_get_display (do_widget)); + g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window); + + /* List widgets go into a scrolled window. Always. */ + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_window_set_child (GTK_WINDOW (window), sw); + + /* Create the factory that creates the listitems. Because we + * used bindings above during setup, we only need to connect + * to the setup signal. + * The bindings take care of the bind step. + */ + factory = gtk_signal_list_item_factory_new (); + g_signal_connect (factory, "setup", G_CALLBACK (setup_listitem_cb), NULL); + + gridview = gtk_grid_view_new_with_factory (factory); + gtk_scrollable_set_hscroll_policy (GTK_SCROLLABLE (gridview), GTK_SCROLL_NATURAL); + gtk_scrollable_set_vscroll_policy (GTK_SCROLLABLE (gridview), GTK_SCROLL_NATURAL); + + model = create_clocks_model (); + selection = gtk_no_selection_new (model); + gtk_grid_view_set_model (GTK_GRID_VIEW (gridview), G_LIST_MODEL (selection)); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), gridview); + g_object_unref (selection); + g_object_unref (model); + } + + if (!gtk_widget_get_visible (window)) + gtk_widget_show (window); + else + gtk_window_destroy (GTK_WINDOW (window)); + + return window; +} diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build index 4b61514e7c..fe116c503d 100644 --- a/demos/gtk-demo/meson.build +++ b/demos/gtk-demo/meson.build @@ -43,6 +43,7 @@ demos = files([ 'flowbox.c', 'list_store.c', 'listview_applauncher.c', + 'listview_clocks.c', 'listview_filebrowser.c', 'listview_minesweeper.c', 'listview_settings.c', @@ -102,7 +103,7 @@ extra_demo_sources = files(['main.c', if harfbuzz_dep.found() and pangoft_dep.found() demos += files('font_features.c') extra_demo_sources += files(['script-names.c', 'language-names.c']) - gtkdemo_deps += [ harfbuzz_dep, ] + gtkdemo_deps += [ harfbuzz_dep, epoxy_dep ] endif if os_unix From b43c8ae6469d0a52bc281d7beff43bca4b03bd80 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 28 Nov 2019 02:32:12 +0100 Subject: [PATCH 149/170] expression: Allow passing a this object to bind() This gives a bit more control over the arguments passed to expressions. --- demos/gtk-demo/listview_clocks.c | 6 +-- gtk/gtkbuilder.c | 38 ++++++++++++--- gtk/gtkbuilderparser.c | 6 ++- gtk/gtkbuilderprivate.h | 1 + gtk/gtkexpression.c | 83 ++++++++++++++++++++------------ gtk/gtkexpression.h | 5 +- 6 files changed, 94 insertions(+), 45 deletions(-) diff --git a/demos/gtk-demo/listview_clocks.c b/demos/gtk-demo/listview_clocks.c index 3254428d7b..95d45dadd7 100644 --- a/demos/gtk-demo/listview_clocks.c +++ b/demos/gtk-demo/listview_clocks.c @@ -401,7 +401,7 @@ setup_listitem_cb (GtkListItemFactory *factory, "location"); /* Now create the label and bind the expression to it. */ location_label = gtk_label_new (NULL); - gtk_expression_bind (expression, location_label, "label"); + gtk_expression_bind (expression, location_label, "label", location_label); gtk_box_append (GTK_BOX (box), location_label); @@ -411,7 +411,7 @@ setup_listitem_cb (GtkListItemFactory *factory, expression = gtk_expression_ref (clock_expression); /* Now create the widget and bind the expression to it. */ picture = gtk_picture_new (); - gtk_expression_bind (expression, picture, "paintable"); + gtk_expression_bind (expression, picture, "paintable", picture); gtk_box_append (GTK_BOX (box), picture); @@ -430,7 +430,7 @@ setup_listitem_cb (GtkListItemFactory *factory, NULL, NULL); /* Now create the label and bind the expression to it. */ time_label = gtk_label_new (NULL); - gtk_expression_bind (expression, time_label, "label"); + gtk_expression_bind (expression, time_label, "label", time_label); gtk_box_append (GTK_BOX (box), time_label); gtk_expression_unref (clock_expression); diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c index 6121d2ed8e..385b78db65 100644 --- a/gtk/gtkbuilder.c +++ b/gtk/gtkbuilder.c @@ -1064,11 +1064,13 @@ gtk_builder_create_bindings (GtkBuilder *builder, BindingInfo *info = l->data; GObject *source; - source = _gtk_builder_lookup_object (builder, info->source, info->line, info->col); + source = gtk_builder_lookup_object (builder, info->source, info->line, info->col, error); if (source) g_object_bind_property (source, info->source_property, info->target, info->target_pspec->name, info->flags); + else + error = NULL; _free_binding_info (info, NULL); } @@ -1076,17 +1078,39 @@ gtk_builder_create_bindings (GtkBuilder *builder, { BindingExpressionInfo *info = l->data; GtkExpression *expression; + GObject *object; - expression = expression_info_construct (builder, info->expr, error); - if (expression == NULL) + if (info->object_name) { - g_prefix_error (error, "%s:%d:%d: ", priv->filename, info->line, info->col); - error = NULL; - result = FALSE; + object = gtk_builder_lookup_object (builder, info->object_name, info->line, info->col, error); + if (object == NULL) + { + error = NULL; + result = FALSE; + } + } + else if (priv->current_object) + { + object = priv->current_object; } else { - gtk_expression_bind (expression, info->target, info->target_pspec->name); + object = info->target; + } + + if (object) + { + expression = expression_info_construct (builder, info->expr, error); + if (expression == NULL) + { + g_prefix_error (error, "%s:%d:%d: ", priv->filename, info->line, info->col); + error = NULL; + result = FALSE; + } + else + { + gtk_expression_bind (expression, info->target, info->target_pspec->name, object); + } } free_binding_expression_info (info); diff --git a/gtk/gtkbuilderparser.c b/gtk/gtkbuilderparser.c index d6e4b87e95..5014b161f4 100644 --- a/gtk/gtkbuilderparser.c +++ b/gtk/gtkbuilderparser.c @@ -954,7 +954,8 @@ parse_binding (ParserData *data, GError **error) { BindingExpressionInfo *info; - const gchar *name = NULL; + const char *name = NULL; + const char *object_name = NULL; ObjectInfo *object_info; GParamSpec *pspec = NULL; @@ -969,6 +970,7 @@ parse_binding (ParserData *data, if (!g_markup_collect_attributes (element_name, names, values, error, G_MARKUP_COLLECT_STRING, "name", &name, + G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "object", &object_name, G_MARKUP_COLLECT_INVALID)) { _gtk_builder_prefix_error (data->builder, &data->ctx, error); @@ -1013,6 +1015,7 @@ parse_binding (ParserData *data, info->tag_type = TAG_BINDING_EXPRESSION; info->target = NULL; info->target_pspec = pspec; + info->object_name = g_strdup (object_name); gtk_buildable_parse_context_get_position (&data->ctx, &info->line, &info->col); state_push (data, info); @@ -1538,6 +1541,7 @@ free_binding_expression_info (BindingExpressionInfo *info) { if (info->expr) free_expression_info (info->expr); + g_free (info->object_name); g_slice_free (BindingExpressionInfo, info); } diff --git a/gtk/gtkbuilderprivate.h b/gtk/gtkbuilderprivate.h index 506a082887..1bee1bda81 100644 --- a/gtk/gtkbuilderprivate.h +++ b/gtk/gtkbuilderprivate.h @@ -133,6 +133,7 @@ typedef struct guint tag_type; GObject *target; GParamSpec *target_pspec; + char *object_name; ExpressionInfo *expr; gint line; gint col; diff --git a/gtk/gtkexpression.c b/gtk/gtkexpression.c index c698f8038e..0689b0a504 100644 --- a/gtk/gtkexpression.c +++ b/gtk/gtkexpression.c @@ -1287,8 +1287,7 @@ gtk_expression_watch_evaluate (GtkExpressionWatch *watch, typedef struct { GtkExpressionWatch *watch; - GtkExpression *expression; - GObject *object; + GObject *target; GParamSpec *pspec; } GtkExpressionBind; @@ -1303,7 +1302,12 @@ invalidate_binds (gpointer unused, { GtkExpressionBind *bind = l->data; - bind->object = NULL; + /* This guarantees we neither try to update bindings + * (which would wreck havoc because the object is + * dispose()ing itself) nor try to destroy bindings + * anymore, so destruction can be done in free_binds(). + */ + bind->target = NULL; } } @@ -1316,8 +1320,10 @@ free_binds (gpointer data) { GtkExpressionBind *bind = l->data; - bind->object = NULL; - gtk_expression_watch_unwatch (bind->watch); + g_assert (bind->target == NULL); + if (bind->watch) + gtk_expression_watch_unwatch (bind->watch); + g_slice_free (GtkExpressionBind, bind); } g_slist_free (data); } @@ -1327,19 +1333,30 @@ gtk_expression_bind_free (gpointer data) { GtkExpressionBind *bind = data; - if (bind->object) + if (bind->target) { GSList *binds; - binds = g_object_steal_data (bind->object, "gtk-expression-binds"); + binds = g_object_steal_data (bind->target, "gtk-expression-binds"); binds = g_slist_remove (binds, bind); if (binds) - g_object_set_data_full (bind->object, "gtk-expression-binds", binds, free_binds); + g_object_set_data_full (bind->target, "gtk-expression-binds", binds, free_binds); else - g_object_weak_unref (bind->object, invalidate_binds, NULL); - } - gtk_expression_unref (bind->expression); + g_object_weak_unref (bind->target, invalidate_binds, NULL); - g_slice_free (GtkExpressionBind, bind); + g_slice_free (GtkExpressionBind, bind); + } + else + { + /* If a bind gets unwatched after invalidate_binds() but + * before free_binds(), we end up here. This can happen if + * the bind was watching itself or if the target's dispose() + * function freed the object that was watched. + * We make sure we don't destroy the binding or free_binds() will do + * bad stuff, but we clear the watch, so free_binds() won't try to + * unwatch() it. + */ + bind->watch = NULL; + } } static void @@ -1348,29 +1365,31 @@ gtk_expression_bind_notify (gpointer data) GValue value = G_VALUE_INIT; GtkExpressionBind *bind = data; - if (bind->object == NULL) + if (bind->target == NULL) return; - if (!gtk_expression_evaluate (bind->expression, bind->object, &value)) + if (!gtk_expression_watch_evaluate (bind->watch, &value)) return; - g_object_set_property (bind->object, bind->pspec->name, &value); + g_object_set_property (bind->target, bind->pspec->name, &value); g_value_unset (&value); } /** * gtk_expression_bind: * @self: (transfer full): a #GtkExpression - * @object: (transfer none) (type GObject): the object to bind - * @property: name of the property to bind to + * @target: (transfer none) (type GObject): the target object to bind to + * @property: name of the property on @target to bind to + * @this_: (transfer none) (type GObject): the this argument for + * the evaluation of @self * - * Bind @object's property named @property to @self. + * Bind @target's property named @property to @self. * * The value that @self evaluates to is set via g_object_set() on - * @object. This is repeated whenever @self changes to ensure that + * @target. This is repeated whenever @self changes to ensure that * the object's property stays synchronized with @self. * - * If @self's evaluation fails, @object's @property is not updated. + * If @self's evaluation fails, @target's @property is not updated. * You can ensure that this doesn't happen by using a fallback * expression. * @@ -1381,44 +1400,44 @@ gtk_expression_bind_notify (gpointer data) **/ GtkExpressionWatch * gtk_expression_bind (GtkExpression *self, - gpointer object, - const char *property) + gpointer target, + const char *property, + gpointer this_) { GtkExpressionBind *bind; GParamSpec *pspec; GSList *binds; g_return_val_if_fail (GTK_IS_EXPRESSION (self), NULL); - g_return_val_if_fail (G_IS_OBJECT (object), NULL); + g_return_val_if_fail (G_IS_OBJECT (target), NULL); g_return_val_if_fail (property != NULL, NULL); - pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), property); + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (target), property); if (G_UNLIKELY (pspec == NULL)) { g_critical ("%s: Class '%s' has no property named '%s'", - G_STRFUNC, G_OBJECT_TYPE_NAME (object), property); + G_STRFUNC, G_OBJECT_TYPE_NAME (target), property); return NULL; } if (G_UNLIKELY ((pspec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) != G_PARAM_WRITABLE)) { g_critical ("%s: property '%s' of class '%s' is not writable", - G_STRFUNC, pspec->name, G_OBJECT_TYPE_NAME (object)); + G_STRFUNC, pspec->name, G_OBJECT_TYPE_NAME (target)); return NULL; } bind = g_slice_new0 (GtkExpressionBind); - binds = g_object_steal_data (object, "gtk-expression-binds"); + binds = g_object_steal_data (target, "gtk-expression-binds"); if (binds == NULL) - g_object_weak_ref (object, invalidate_binds, NULL); - bind->expression = self; - bind->object = object; + g_object_weak_ref (target, invalidate_binds, NULL); + bind->target = target; bind->pspec = pspec; bind->watch = gtk_expression_watch (self, - object, + this_, gtk_expression_bind_notify, bind, gtk_expression_bind_free); binds = g_slist_prepend (binds, bind); - g_object_set_data_full (object, "gtk-expression-binds", binds, free_binds); + g_object_set_data_full (target, "gtk-expression-binds", binds, free_binds); gtk_expression_unref (self); diff --git a/gtk/gtkexpression.h b/gtk/gtkexpression.h index 0727580c45..9705dae18f 100644 --- a/gtk/gtkexpression.h +++ b/gtk/gtkexpression.h @@ -65,8 +65,9 @@ GtkExpressionWatch * gtk_expression_watch (GtkExpression GDestroyNotify user_destroy); GDK_AVAILABLE_IN_ALL GtkExpressionWatch * gtk_expression_bind (GtkExpression *self, - gpointer object, - const char * property); + gpointer target, + const char * property, + gpointer this_); GDK_AVAILABLE_IN_ALL GtkExpressionWatch * gtk_expression_watch_ref (GtkExpressionWatch *watch); From 542829ee81f89baf9e4fedc3f9dae063ded81eb9 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 14 Dec 2019 15:36:34 -0500 Subject: [PATCH 150/170] Add some tests for expression binding In particular, test that expressios can deal with object == this. --- testsuite/gtk/expression.c | 305 +++++++++++++++++++++++++++++++++++++ 1 file changed, 305 insertions(+) diff --git a/testsuite/gtk/expression.c b/testsuite/gtk/expression.c index 663e0fcbaf..d8c75e527b 100644 --- a/testsuite/gtk/expression.c +++ b/testsuite/gtk/expression.c @@ -412,6 +412,304 @@ test_constant_watch_this_destroyed (void) g_clear_object (&this); g_assert_cmpint (counter, ==, 1); + + gtk_expression_unref (expr); +} + +/* Basic test of gtk_expression_bind */ +static void +test_bind (void) +{ + GtkFilter *target; + GtkFilter *source; + GtkExpression *expr; + GtkExpressionWatch *watch; + GValue value = G_VALUE_INIT; + gboolean res; + + expr = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, NULL, "search"); + + target = gtk_string_filter_new (); + gtk_string_filter_set_search (GTK_STRING_FILTER (target), "word"); + g_assert_cmpstr (gtk_string_filter_get_search (GTK_STRING_FILTER (target)), ==, "word"); + + source = gtk_string_filter_new (); + gtk_string_filter_set_search (GTK_STRING_FILTER (source), "sausage"); + + watch = gtk_expression_bind (expr, target, "search", source); + gtk_expression_watch_ref (watch); + g_assert_cmpstr (gtk_string_filter_get_search (GTK_STRING_FILTER (target)), ==, "sausage"); + + gtk_string_filter_set_search (GTK_STRING_FILTER (source), "salad"); + g_assert_cmpstr (gtk_string_filter_get_search (GTK_STRING_FILTER (target)), ==, "salad"); + res = gtk_expression_watch_evaluate (watch, &value); + g_assert_true (res); + g_assert_cmpstr (g_value_get_string (&value), ==, "salad"); + g_value_unset (&value); + + g_object_unref (source); + g_assert_cmpstr (gtk_string_filter_get_search (GTK_STRING_FILTER (target)), ==, "salad"); + res = gtk_expression_watch_evaluate (watch, &value); + g_assert_false (res); + g_assert_false (G_IS_VALUE (&value)); + + g_object_unref (target); + gtk_expression_watch_unref (watch); +} + +/* Another test of bind, this time we watch ourselves */ +static void +test_bind_self (void) +{ + GtkFilter *filter; + GtkExpression *expr; + + expr = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, + NULL, + "ignore-case"); + + filter = gtk_string_filter_new (); + gtk_string_filter_set_search (GTK_STRING_FILTER (filter), "word"); + g_assert_cmpstr (gtk_string_filter_get_search (GTK_STRING_FILTER (filter)), ==, "word"); + + gtk_expression_bind (expr, filter, "search", filter); + g_assert_cmpstr (gtk_string_filter_get_search (GTK_STRING_FILTER (filter)), ==, "TRUE"); + + g_object_unref (filter); +} + +/* Test bind does the right memory management if the target's + * dispose() kills the source */ +static void +test_bind_child (void) +{ + GtkFilter *filter; + GtkFilterListModel *child, *target; + GtkExpression *expr; + + expr = gtk_property_expression_new (GTK_TYPE_FILTER_LIST_MODEL, + NULL, + "filter"); + + filter = gtk_string_filter_new (); + child = gtk_filter_list_model_new_for_type (G_TYPE_OBJECT); + gtk_filter_list_model_set_filter (child, filter); + target = gtk_filter_list_model_new (G_LIST_MODEL (child), NULL); + g_object_unref (child); + g_object_unref (filter); + + gtk_expression_bind (expr, target, "filter", child); + g_assert_true (gtk_filter_list_model_get_filter (child) == gtk_filter_list_model_get_filter (target)); + + filter = gtk_string_filter_new (); + gtk_filter_list_model_set_filter (child, filter); + g_assert_true (filter == gtk_filter_list_model_get_filter (target)); + g_assert_true (gtk_filter_list_model_get_filter (child) == gtk_filter_list_model_get_filter (target)); + g_object_unref (filter); + + g_object_unref (target); +} + +/* Another test of gtk_expression_bind that exercises the subwatch code paths */ +static void +test_nested_bind (void) +{ + GtkFilter *filter; + GtkFilter *filter2; + GtkFilter *filter3; + GListModel *list; + GtkFilterListModel *filtered; + GtkExpression *expr; + GtkExpression *filter_expr; + gboolean res; + GValue value = G_VALUE_INIT; + + filter2 = gtk_string_filter_new (); + gtk_string_filter_set_search (GTK_STRING_FILTER (filter2), "sausage"); + + list = G_LIST_MODEL (g_list_store_new (G_TYPE_OBJECT)); + filtered = gtk_filter_list_model_new (list, filter2); + + filter_expr = gtk_property_expression_new (GTK_TYPE_FILTER_LIST_MODEL, + gtk_object_expression_new (G_OBJECT (filtered)), + "filter"); + expr = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, gtk_expression_ref (filter_expr), "search"); + + filter = gtk_string_filter_new (); + gtk_string_filter_set_search (GTK_STRING_FILTER (filter), "word"); + g_assert_cmpstr (gtk_string_filter_get_search (GTK_STRING_FILTER (filter)), ==, "word"); + + gtk_expression_bind (gtk_expression_ref (expr), filter, "search", NULL); + + gtk_string_filter_set_search (GTK_STRING_FILTER (filter2), "sausage"); + g_assert_cmpstr (gtk_string_filter_get_search (GTK_STRING_FILTER (filter)), ==, "sausage"); + + filter3 = gtk_string_filter_new (); + gtk_string_filter_set_search (GTK_STRING_FILTER (filter3), "banana"); + gtk_filter_list_model_set_filter (filtered, filter3); + + /* check that the expressions evaluate correctly */ + res = gtk_expression_evaluate (filter_expr, NULL, &value); + g_assert_true (res); + g_assert (g_value_get_object (&value) == filter3); + g_value_unset (&value); + + res = gtk_expression_evaluate (expr, NULL, &value); + g_assert_true (res); + g_assert_cmpstr (g_value_get_string (&value), ==, "banana"); + g_value_unset (&value); + + /* and the bind too */ + g_assert_cmpstr (gtk_string_filter_get_search (GTK_STRING_FILTER (filter)), ==, "banana"); + + g_object_unref (filter); + g_object_unref (filter2); + g_object_unref (filter3); + g_object_unref (filtered); + g_object_unref (list); + + gtk_expression_unref (expr); + gtk_expression_unref (filter_expr); +} + +static char * +some_cb (gpointer this, + const char *search, + gboolean ignore_case, + gpointer data) +{ + if (!search) + return NULL; + + if (ignore_case) + return g_utf8_strdown (search, -1); + else + return g_strdup (search); +} + +/* Test that things work as expected when the same object is used multiple times in an + * expression or its subexpressions. + */ +static void +test_double_bind (void) +{ + GtkStringFilter *filter1; + GtkStringFilter *filter2; + GtkExpression *expr; + GtkExpression *filter_expr; + GtkExpression *params[2]; + + filter1 = GTK_STRING_FILTER (gtk_string_filter_new ()); + filter2 = GTK_STRING_FILTER (gtk_string_filter_new ()); + + filter_expr = gtk_object_expression_new (G_OBJECT (filter1)); + + params[0] = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, gtk_expression_ref (filter_expr), "search"); + params[1] = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, gtk_expression_ref (filter_expr), "ignore-case"); + expr = gtk_cclosure_expression_new (G_TYPE_STRING, + NULL, + 2, params, + (GCallback)some_cb, + NULL, NULL); + + gtk_expression_bind (gtk_expression_ref (expr), filter2, "search", NULL); + + gtk_string_filter_set_search (GTK_STRING_FILTER (filter1), "Banana"); + g_assert_cmpstr (gtk_string_filter_get_search (GTK_STRING_FILTER (filter2)), ==, "banana"); + + gtk_string_filter_set_ignore_case (GTK_STRING_FILTER (filter1), FALSE); + g_assert_cmpstr (gtk_string_filter_get_search (GTK_STRING_FILTER (filter2)), ==, "Banana"); + + gtk_expression_unref (expr); + gtk_expression_unref (filter_expr); + + g_object_unref (filter1); + g_object_unref (filter2); +} + +/* Test that having multiple binds on the same object works. */ +static void +test_binds (void) +{ + GtkStringFilter *filter1; + GtkStringFilter *filter2; + GtkStringFilter *filter3; + GtkExpression *expr; + GtkExpression *expr2; + GtkExpression *filter1_expr; + GtkExpression *filter2_expr; + GtkExpression *params[2]; + + filter1 = GTK_STRING_FILTER (gtk_string_filter_new ()); + filter2 = GTK_STRING_FILTER (gtk_string_filter_new ()); + filter3 = GTK_STRING_FILTER (gtk_string_filter_new ()); + + filter1_expr = gtk_object_expression_new (G_OBJECT (filter1)); + filter2_expr = gtk_object_expression_new (G_OBJECT (filter2)); + + params[0] = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, gtk_expression_ref (filter1_expr), "search"); + params[1] = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, gtk_expression_ref (filter2_expr), "ignore-case"); + expr = gtk_cclosure_expression_new (G_TYPE_STRING, + NULL, + 2, params, + (GCallback)some_cb, + NULL, NULL); + + expr2 = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, gtk_expression_ref (filter2_expr), "ignore-case"); + + gtk_expression_bind (gtk_expression_ref (expr), filter3, "search", NULL); + gtk_expression_bind (gtk_expression_ref (expr2), filter3, "ignore-case", NULL); + + gtk_string_filter_set_search (GTK_STRING_FILTER (filter1), "Banana"); + g_assert_cmpstr (gtk_string_filter_get_search (GTK_STRING_FILTER (filter3)), ==, "banana"); + g_assert_true (gtk_string_filter_get_ignore_case (GTK_STRING_FILTER (filter3))); + + gtk_string_filter_set_ignore_case (GTK_STRING_FILTER (filter2), FALSE); + g_assert_cmpstr (gtk_string_filter_get_search (GTK_STRING_FILTER (filter3)), ==, "Banana"); + g_assert_false (gtk_string_filter_get_ignore_case (GTK_STRING_FILTER (filter3))); + + /* invalidate the first bind */ + g_object_unref (filter1); + + gtk_string_filter_set_ignore_case (GTK_STRING_FILTER (filter2), TRUE); + g_assert_cmpstr (gtk_string_filter_get_search (GTK_STRING_FILTER (filter3)), ==, "Banana"); + g_assert_true (gtk_string_filter_get_ignore_case (GTK_STRING_FILTER (filter3))); + + gtk_expression_unref (expr); + gtk_expression_unref (expr2); + gtk_expression_unref (filter1_expr); + gtk_expression_unref (filter2_expr); + + g_object_unref (filter2); + g_object_unref (filter3); +} + +/* test that binds work ok with object expressions */ +static void +test_bind_object (void) +{ + GtkFilter *filter; + GListStore *store; + GtkFilterListModel *model; + GtkExpression *expr; + + filter = gtk_string_filter_new (); + store = g_list_store_new (G_TYPE_OBJECT); + model = gtk_filter_list_model_new (G_LIST_MODEL (store), NULL); + + expr = gtk_object_expression_new (G_OBJECT (filter)); + + gtk_expression_bind (gtk_expression_ref (expr), model, "filter", NULL); + + g_assert_true (gtk_filter_list_model_get_filter (model) == filter); + + g_object_unref (filter); + + g_assert_true (gtk_filter_list_model_get_filter (model) == filter); + + gtk_expression_unref (expr); + g_object_unref (model); + g_object_unref (store); } int @@ -429,6 +727,13 @@ main (int argc, char *argv[]) g_test_add_func ("/expression/nested-this-destroyed", test_nested_this_destroyed); g_test_add_func ("/expression/type-mismatch", test_type_mismatch); g_test_add_func ("/expression/this", test_this); + g_test_add_func ("/expression/bind", test_bind); + g_test_add_func ("/expression/bind-self", test_bind_self); + g_test_add_func ("/expression/bind-child", test_bind_child); + g_test_add_func ("/expression/nested-bind", test_nested_bind); + g_test_add_func ("/expression/double-bind", test_double_bind); + g_test_add_func ("/expression/binds", test_binds); + g_test_add_func ("/expression/bind-object", test_bind_object); return g_test_run (); } From 58b65d1bf62ca2ce50cd82f3cace4840db25af88 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 28 Nov 2019 04:00:39 +0100 Subject: [PATCH 151/170] fontchooserwidget: Port to listmodels The port is kind of evil, in that it stores either a PangoFontFamily or a PangoFontFace in the list, depending on if the fontchooser is configured to select fonts or faces. It also does not cache the font description anymore, so more calls to pango_font_describe() may happen. If both of these issues turn out problematic, the fontchooser would need to resurrect GtkDelayedFontDescription again and put objects of that type through the model. These changes depend on Pango 1.46's introduction of listmodels and various new getters, so the dependency has been upgraded. --- gtk/gtkfontchooserwidget.c | 948 +++++++++------------------------ gtk/ui/gtkfontchooserwidget.ui | 108 ++-- 2 files changed, 334 insertions(+), 722 deletions(-) diff --git a/gtk/gtkfontchooserwidget.c b/gtk/gtkfontchooserwidget.c index 93aa5b4921..b29506c85f 100644 --- a/gtk/gtkfontchooserwidget.c +++ b/gtk/gtkfontchooserwidget.c @@ -30,20 +30,20 @@ #include "gtkbuildable.h" #include "gtkbox.h" #include "gtkbinlayout.h" -#include "gtkcellrenderertext.h" -#include "gtkcssnumbervalueprivate.h" +#include "gtkcustomfilter.h" #include "gtkentry.h" -#include "gtksearchentry.h" +#include "gtkfilter.h" #include "gtkgrid.h" #include "gtkfontchooser.h" #include "gtkfontchooserutils.h" #include "gtkintl.h" #include "gtklabel.h" -#include "gtkliststore.h" +#include "gtksingleselection.h" #include "gtkstack.h" #include "gtkprivate.h" #include "gtkscale.h" #include "gtkscrolledwindow.h" +#include "gtksearchentry.h" #include "gtkspinbutton.h" #include "gtkstylecontextprivate.h" #include "gtktextview.h" @@ -57,6 +57,9 @@ #include "gtkgestureclick.h" #include "gtkeventcontrollerscroll.h" #include "gtkroot.h" +#include "gtkfilterlistmodel.h" +#include "gtkflattenlistmodel.h" +#include "gtkmaplistmodel.h" #include @@ -99,12 +102,10 @@ struct _GtkFontChooserWidget GtkWidget *grid; GtkWidget *search_entry; GtkWidget *family_face_list; - GtkTreeViewColumn *family_face_column; - GtkCellRenderer *family_face_cell; - GtkWidget *list_scrolled_window; GtkWidget *list_stack; - GtkTreeModel *model; - GtkTreeModel *filter_model; + GtkSingleSelection *selection; + GtkCustomFilter *custom_filter; + GtkFilterListModel *filter_model; GtkWidget *preview; GtkWidget *preview2; @@ -125,8 +126,7 @@ struct _GtkFontChooserWidget PangoFontDescription *font_desc; char *font_features; PangoLanguage *language; - GtkTreeIter font_iter; /* invalid if font not available or pointer into model - (not filter_model) to the row containing font */ + GtkFontFilterFunc filter_func; gpointer filter_data; GDestroyNotify filter_data_destroy; @@ -153,14 +153,6 @@ enum { PROP_TWEAK_ACTION }; -/* Keep in line with GtkTreeStore defined in gtkfontchooserwidget.ui */ -enum { - FAMILY_COLUMN, - FACE_COLUMN, - FONT_DESC_COLUMN, - PREVIEW_TITLE_COLUMN -}; - static void gtk_font_chooser_widget_set_property (GObject *object, guint prop_id, const GValue *value, @@ -171,19 +163,13 @@ static void gtk_font_chooser_widget_get_property (GObject *objec GParamSpec *pspec); static void gtk_font_chooser_widget_finalize (GObject *object); -static gboolean gtk_font_chooser_widget_find_font (GtkFontChooserWidget *fontchooser, - const PangoFontDescription *font_desc, - GtkTreeIter *iter); -static void gtk_font_chooser_widget_ensure_selection (GtkFontChooserWidget *fontchooser); - static gchar *gtk_font_chooser_widget_get_font (GtkFontChooserWidget *fontchooser); static void gtk_font_chooser_widget_set_font (GtkFontChooserWidget *fontchooser, const gchar *fontname); static PangoFontDescription *gtk_font_chooser_widget_get_font_desc (GtkFontChooserWidget *fontchooser); static void gtk_font_chooser_widget_merge_font_desc(GtkFontChooserWidget *fontchooser, - const PangoFontDescription *font_desc, - GtkTreeIter *iter); + const PangoFontDescription *font_desc); static void gtk_font_chooser_widget_take_font_desc (GtkFontChooserWidget *fontchooser, PangoFontDescription *font_desc); @@ -196,25 +182,12 @@ static gboolean gtk_font_chooser_widget_get_show_preview_entry (GtkFontChooserWi static void gtk_font_chooser_widget_set_show_preview_entry (GtkFontChooserWidget *fontchooser, gboolean show_preview_entry); -static void gtk_font_chooser_widget_set_cell_size (GtkFontChooserWidget *fontchooser); -static void gtk_font_chooser_widget_load_fonts (GtkFontChooserWidget *fontchooser, - gboolean force); static void gtk_font_chooser_widget_populate_features (GtkFontChooserWidget *fontchooser); -static gboolean visible_func (GtkTreeModel *model, - GtkTreeIter *iter, - gpointer user_data); -static void gtk_font_chooser_widget_cell_data_func (GtkTreeViewColumn *column, - GtkCellRenderer *cell, - GtkTreeModel *tree_model, - GtkTreeIter *iter, - gpointer user_data); static void gtk_font_chooser_widget_set_level (GtkFontChooserWidget *fontchooser, GtkFontChooserLevel level); static GtkFontChooserLevel gtk_font_chooser_widget_get_level (GtkFontChooserWidget *fontchooser); static void gtk_font_chooser_widget_set_language (GtkFontChooserWidget *fontchooser, const char *language); -static void selection_changed (GtkTreeSelection *selection, - GtkFontChooserWidget *fontchooser); static void update_font_features (GtkFontChooserWidget *fontchooser); static void gtk_font_chooser_widget_iface_init (GtkFontChooserIface *iface); @@ -223,65 +196,6 @@ G_DEFINE_TYPE_WITH_CODE (GtkFontChooserWidget, gtk_font_chooser_widget, GTK_TYPE G_IMPLEMENT_INTERFACE (GTK_TYPE_FONT_CHOOSER, gtk_font_chooser_widget_iface_init)) -typedef struct _GtkDelayedFontDescription GtkDelayedFontDescription; -struct _GtkDelayedFontDescription { - PangoFontFace *face; - PangoFontDescription *desc; - guint ref_count; -}; - -static GtkDelayedFontDescription * -gtk_delayed_font_description_new (PangoFontFace *face) -{ - GtkDelayedFontDescription *result; - - result = g_slice_new0 (GtkDelayedFontDescription); - - result->face = g_object_ref (face); - result->desc = NULL; - result->ref_count = 1; - - return result; -} - -static GtkDelayedFontDescription * -gtk_delayed_font_description_ref (GtkDelayedFontDescription *desc) -{ - desc->ref_count++; - - return desc; -} - -static void -gtk_delayed_font_description_unref (GtkDelayedFontDescription *desc) -{ - desc->ref_count--; - - if (desc->ref_count > 0) - return; - - g_object_unref (desc->face); - if (desc->desc) - pango_font_description_free (desc->desc); - - g_slice_free (GtkDelayedFontDescription, desc); -} - -static const PangoFontDescription * -gtk_delayed_font_description_get (GtkDelayedFontDescription *desc) -{ - if (desc->desc == NULL) - desc->desc = pango_font_face_describe (desc->face); - - return desc->desc; -} - -#define GTK_TYPE_DELAYED_FONT_DESCRIPTION (gtk_delayed_font_description_get_type ()) -GType gtk_delayed_font_description_get_type (void); - -G_DEFINE_BOXED_TYPE (GtkDelayedFontDescription, gtk_delayed_font_description, - gtk_delayed_font_description_ref, - gtk_delayed_font_description_unref) static void gtk_font_chooser_widget_set_property (GObject *object, guint prop_id, @@ -356,20 +270,6 @@ gtk_font_chooser_widget_get_property (GObject *object, } } -static void -gtk_font_chooser_widget_refilter_font_list (GtkFontChooserWidget *fontchooser) -{ - gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (fontchooser->filter_model)); - gtk_font_chooser_widget_ensure_selection (fontchooser); -} - -static void -text_changed_cb (GtkEntry *entry, - GtkFontChooserWidget *fc) -{ - gtk_font_chooser_widget_refilter_font_list (fc); -} - static void stop_search_cb (GtkSearchEntry *entry, GtkFontChooserWidget *fc) @@ -425,29 +325,31 @@ output_cb (GtkSpinButton *spin, } static void -gtk_font_chooser_widget_update_marks (GtkFontChooserWidget *fontchooser) +gtk_font_chooser_widget_update_marks (GtkFontChooserWidget *self) { GtkAdjustment *adj, *spin_adj; const int *sizes; gint *font_sizes; gint i, n_sizes; gdouble value, spin_value; + gpointer item; - if (gtk_list_store_iter_is_valid (GTK_LIST_STORE (fontchooser->model), &fontchooser->font_iter)) + item = gtk_single_selection_get_selected_item (self->selection); + + if (item) { PangoFontFace *face; - gtk_tree_model_get (fontchooser->model, &fontchooser->font_iter, - FACE_COLUMN, &face, - -1); + if (PANGO_IS_FONT_FAMILY (item)) + face = pango_font_family_get_face (item, NULL); + else + face = item; pango_font_face_list_sizes (face, &font_sizes, &n_sizes); /* It seems not many fonts actually have a sane set of sizes */ for (i = 0; i < n_sizes; i++) font_sizes[i] = font_sizes[i] / PANGO_SCALE; - - g_object_unref (face); } else { @@ -469,11 +371,11 @@ gtk_font_chooser_widget_update_marks (GtkFontChooserWidget *fontchooser) sizes = font_sizes; } - gtk_scale_clear_marks (GTK_SCALE (fontchooser->size_slider)); - gtk_scale_clear_marks (GTK_SCALE (fontchooser->size_slider2)); + gtk_scale_clear_marks (GTK_SCALE (self->size_slider)); + gtk_scale_clear_marks (GTK_SCALE (self->size_slider2)); - adj = gtk_range_get_adjustment (GTK_RANGE (fontchooser->size_slider)); - spin_adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (fontchooser->size_spin)); + adj = gtk_range_get_adjustment (GTK_RANGE (self->size_slider)); + spin_adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (self->size_spin)); spin_value = gtk_adjustment_get_value (spin_adj); if (spin_value < sizes[0]) @@ -484,7 +386,7 @@ gtk_font_chooser_widget_update_marks (GtkFontChooserWidget *fontchooser) value = (gdouble)spin_value; /* ensure clamping doesn't callback into font resizing code */ - g_signal_handlers_block_by_func (adj, size_change_cb, fontchooser); + g_signal_handlers_block_by_func (adj, size_change_cb, self); gtk_adjustment_configure (adj, value, sizes[0], @@ -492,14 +394,14 @@ gtk_font_chooser_widget_update_marks (GtkFontChooserWidget *fontchooser) gtk_adjustment_get_step_increment (adj), gtk_adjustment_get_page_increment (adj), gtk_adjustment_get_page_size (adj)); - g_signal_handlers_unblock_by_func (adj, size_change_cb, fontchooser); + g_signal_handlers_unblock_by_func (adj, size_change_cb, self); for (i = 0; i < n_sizes; i++) { - gtk_scale_add_mark (GTK_SCALE (fontchooser->size_slider), + gtk_scale_add_mark (GTK_SCALE (self->size_slider), sizes[i], GTK_POS_BOTTOM, NULL); - gtk_scale_add_mark (GTK_SCALE (fontchooser->size_slider2), + gtk_scale_add_mark (GTK_SCALE (self->size_slider2), sizes[i], GTK_POS_BOTTOM, NULL); } @@ -508,10 +410,9 @@ gtk_font_chooser_widget_update_marks (GtkFontChooserWidget *fontchooser) } static void -row_activated_cb (GtkTreeView *view, - GtkTreePath *path, - GtkTreeViewColumn *column, - gpointer user_data) +row_activated_cb (GtkWidget *view, + guint pos, + gpointer user_data) { GtkFontChooserWidget *fontchooser = user_data; gchar *fontname; @@ -521,57 +422,99 @@ row_activated_cb (GtkTreeView *view, g_free (fontname); } -static void -cursor_changed_cb (GtkTreeView *treeview, - gpointer user_data) -{ - GtkFontChooserWidget *fontchooser = user_data; - GtkDelayedFontDescription *desc; - GtkTreeIter filter_iter, iter; - GtkTreePath *path = NULL; - - gtk_tree_view_get_cursor (treeview, &path, NULL); - - if (!path) - return; - - if (!gtk_tree_model_get_iter (fontchooser->filter_model, &filter_iter, path)) - { - gtk_tree_path_free (path); - return; - } - - gtk_tree_path_free (path); - - gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (fontchooser->filter_model), - &iter, - &filter_iter); - gtk_tree_model_get (fontchooser->model, &iter, - FONT_DESC_COLUMN, &desc, - -1); - - pango_font_description_set_variations (fontchooser->font_desc, NULL); - gtk_font_chooser_widget_merge_font_desc (fontchooser, - gtk_delayed_font_description_get (desc), - &iter); - - gtk_delayed_font_description_unref (desc); -} - static void resize_by_scroll_cb (GtkEventControllerScroll *controller, double dx, double dy, gpointer user_data) { - GtkFontChooserWidget *fc = user_data; - GtkAdjustment *adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (fc->size_spin)); + GtkFontChooserWidget *self = user_data; + GtkAdjustment *adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (self->size_spin)); gtk_adjustment_set_value (adj, gtk_adjustment_get_value (adj) + gtk_adjustment_get_step_increment (adj) * dx); } +static void +selection_changed_cb (GtkSingleSelection *selection, + GParamSpec *pspec, + GtkFontChooserWidget *self) +{ + gpointer item; + + item = gtk_single_selection_get_selected_item (selection); + if (item) + { + PangoFontFace *face; + PangoFontDescription *desc; + + if (PANGO_IS_FONT_FAMILY (item)) + face = pango_font_family_get_face (item, NULL); + else + face = item; + desc = pango_font_face_describe (face); + pango_font_description_set_variations (self->font_desc, NULL); + gtk_font_chooser_widget_merge_font_desc (self, desc); + pango_font_description_free (desc); + g_simple_action_set_enabled (G_SIMPLE_ACTION (self->tweak_action), TRUE); + } + else + { + g_simple_action_set_state (G_SIMPLE_ACTION (self->tweak_action), g_variant_new_boolean (FALSE)); + g_simple_action_set_enabled (G_SIMPLE_ACTION (self->tweak_action), FALSE); + } + + g_object_notify (G_OBJECT (self), "font"); + g_object_notify (G_OBJECT (self), "font-desc"); +} + +static char * +get_font_name (GObject *ignore, + gpointer item) +{ + if (item == NULL) + return NULL; + + if (PANGO_IS_FONT_FACE (item)) + { + return g_strconcat (pango_font_family_get_name (pango_font_face_get_family (item)), + " ", + pango_font_face_get_face_name (item), + NULL); + } + else + { + return g_strdup (pango_font_family_get_name (item)); + } +} + +static PangoAttrList * +get_font_attributes (GObject *ignore, + gpointer item) +{ + PangoAttribute *attribute; + PangoAttrList *attrs; + + attrs = pango_attr_list_new (); + + if (item) + { + PangoFontFace *face; + PangoFontDescription *font_desc; + + if (PANGO_IS_FONT_FAMILY (item)) + face = pango_font_family_get_face (item, NULL); + else + face = item; + font_desc = pango_font_face_describe (face); + attribute = pango_attr_font_desc_new (font_desc); + pango_attr_list_insert (attrs, attribute); + } + + return attrs; +} + static void gtk_font_chooser_widget_update_preview_attributes (GtkFontChooserWidget *fontchooser) { @@ -595,17 +538,17 @@ gtk_font_chooser_widget_update_preview_attributes (GtkFontChooserWidget *fontcho } static void -rows_changed_cb (GtkFontChooserWidget *fontchooser) +rows_changed_cb (GtkFontChooserWidget *self) { const char *page; - if (gtk_tree_model_iter_n_children (fontchooser->filter_model, NULL) == 0) + if (g_list_model_get_n_items (G_LIST_MODEL (self->selection)) == 0) page = "empty"; else page = "list"; - if (strcmp (gtk_stack_get_visible_child_name (GTK_STACK (fontchooser->list_stack)), page) != 0) - gtk_stack_set_visible_child_name (GTK_STACK (fontchooser->list_stack), page); + if (strcmp (gtk_stack_get_visible_child_name (GTK_STACK (self->list_stack)), page) != 0) + gtk_stack_set_visible_child_name (GTK_STACK (self->list_stack), page); } static void @@ -657,40 +600,21 @@ gtk_font_chooser_widget_unmap (GtkWidget *widget) GTK_WIDGET_CLASS (gtk_font_chooser_widget_parent_class)->unmap (widget); } -static void -fontconfig_changed (GtkFontChooserWidget *fontchooser) -{ - gtk_font_chooser_widget_load_fonts (fontchooser, TRUE); -} - static void gtk_font_chooser_widget_root (GtkWidget *widget) { - GtkSettings *settings; - GTK_WIDGET_CLASS (gtk_font_chooser_widget_parent_class)->root (widget); g_signal_connect_swapped (gtk_widget_get_root (widget), "notify::focus-widget", G_CALLBACK (update_key_capture), widget); - - settings = gtk_widget_get_settings (widget); - g_signal_connect_object (settings, "notify::gtk-fontconfig-timestamp", - G_CALLBACK (fontconfig_changed), widget, G_CONNECT_SWAPPED); - - gtk_font_chooser_widget_load_fonts (GTK_FONT_CHOOSER_WIDGET (widget), FALSE); - } +} static void gtk_font_chooser_widget_unroot (GtkWidget *widget) { - GtkSettings *settings; - g_signal_handlers_disconnect_by_func (gtk_widget_get_root (widget), update_key_capture, widget); - settings = gtk_widget_get_settings (widget); - g_signal_handlers_disconnect_by_func (settings, fontconfig_changed, widget); - GTK_WIDGET_CLASS (gtk_font_chooser_widget_parent_class)->unroot (widget); } @@ -699,8 +623,9 @@ gtk_font_chooser_widget_dispose (GObject *object) { GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (object); - if (self->family_face_list) - gtk_tree_view_set_model (GTK_TREE_VIEW (self->family_face_list), NULL); + self->filter_func = NULL; + g_clear_pointer (&self->filter_data, self->filter_data_destroy); + g_clear_pointer (&self->stack, gtk_widget_unparent); G_OBJECT_CLASS (gtk_font_chooser_widget_parent_class)->dispose (object); @@ -713,7 +638,6 @@ gtk_font_chooser_widget_class_init (GtkFontChooserWidgetClass *klass) GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GParamSpec *pspec; - g_type_ensure (GTK_TYPE_DELAYED_FONT_DESCRIPTION); g_type_ensure (G_TYPE_THEMED_ICON); widget_class->root = gtk_font_chooser_widget_root; @@ -751,12 +675,10 @@ gtk_font_chooser_widget_class_init (GtkFontChooserWidgetClass *klass) gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, search_entry); gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, family_face_list); - gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, family_face_column); - gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, family_face_cell); - gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, list_scrolled_window); gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, list_stack); - gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, model); gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, filter_model); + gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, selection); + gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, custom_filter); gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, preview); gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, preview2); gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, size_label); @@ -769,14 +691,14 @@ gtk_font_chooser_widget_class_init (GtkFontChooserWidgetClass *klass) gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, feature_box); gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, axis_grid); - gtk_widget_class_bind_template_callback (widget_class, text_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, get_font_name); + gtk_widget_class_bind_template_callback (widget_class, get_font_attributes); gtk_widget_class_bind_template_callback (widget_class, stop_search_cb); - gtk_widget_class_bind_template_callback (widget_class, cursor_changed_cb); gtk_widget_class_bind_template_callback (widget_class, row_activated_cb); gtk_widget_class_bind_template_callback (widget_class, rows_changed_cb); gtk_widget_class_bind_template_callback (widget_class, size_change_cb); gtk_widget_class_bind_template_callback (widget_class, output_cb); - gtk_widget_class_bind_template_callback (widget_class, selection_changed); + gtk_widget_class_bind_template_callback (widget_class, selection_changed_cb); gtk_widget_class_bind_template_callback (widget_class, resize_by_scroll_cb); gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); @@ -853,51 +775,59 @@ axis_free (gpointer v) } static void -gtk_font_chooser_widget_init (GtkFontChooserWidget *fontchooser) +update_fontlist (GtkFontChooserWidget *self) { - gtk_widget_init_template (GTK_WIDGET (fontchooser)); + PangoFontMap *fontmap; + GListModel *model; - fontchooser->axes = g_hash_table_new_full (axis_hash, axis_equal, NULL, axis_free); + fontmap = self->font_map; + if (!fontmap) + fontmap = pango_cairo_font_map_get_default (); + + if ((self->level & GTK_FONT_CHOOSER_LEVEL_STYLE) == 0) + model = g_object_ref (G_LIST_MODEL (fontmap)); + else + model = G_LIST_MODEL (gtk_flatten_list_model_new (PANGO_TYPE_FONT_FACE, G_LIST_MODEL (fontmap))); + gtk_filter_list_model_set_model (self->filter_model, model); + g_object_unref (model); +} + +static void +gtk_font_chooser_widget_init (GtkFontChooserWidget *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + self->axes = g_hash_table_new_full (axis_hash, axis_equal, NULL, axis_free); /* Default preview string */ - fontchooser->preview_text = g_strdup (pango_language_get_sample_string (NULL)); - fontchooser->show_preview_entry = TRUE; - fontchooser->font_desc = pango_font_description_new (); - fontchooser->level = GTK_FONT_CHOOSER_LEVEL_FAMILY | + self->preview_text = g_strdup (pango_language_get_sample_string (NULL)); + self->show_preview_entry = TRUE; + self->font_desc = pango_font_description_new (); + self->level = GTK_FONT_CHOOSER_LEVEL_FAMILY | GTK_FONT_CHOOSER_LEVEL_STYLE | GTK_FONT_CHOOSER_LEVEL_SIZE; - fontchooser->language = pango_language_get_default (); + self->language = pango_language_get_default (); /* Set default preview text */ - gtk_editable_set_text (GTK_EDITABLE (fontchooser->preview), fontchooser->preview_text); + gtk_editable_set_text (GTK_EDITABLE (self->preview), self->preview_text); - gtk_font_chooser_widget_update_preview_attributes (fontchooser); + gtk_font_chooser_widget_update_preview_attributes (self); /* Set the upper values of the spin/scale with G_MAXINT / PANGO_SCALE */ - gtk_spin_button_set_range (GTK_SPIN_BUTTON (fontchooser->size_spin), + gtk_spin_button_set_range (GTK_SPIN_BUTTON (self->size_spin), 1.0, (gdouble)(G_MAXINT / PANGO_SCALE)); - gtk_adjustment_set_upper (gtk_range_get_adjustment (GTK_RANGE (fontchooser->size_slider)), + gtk_adjustment_set_upper (gtk_range_get_adjustment (GTK_RANGE (self->size_slider)), (gdouble)(G_MAXINT / PANGO_SCALE)); - /* Setup treeview/model auxiliary functions */ - gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (fontchooser->filter_model), - visible_func, (gpointer)fontchooser, NULL); + self->tweak_action = G_ACTION (g_simple_action_new_stateful ("tweak", NULL, g_variant_new_boolean (FALSE))); + g_signal_connect (self->tweak_action, "change-state", G_CALLBACK (change_tweak), self); - gtk_tree_view_column_set_cell_data_func (fontchooser->family_face_column, - fontchooser->family_face_cell, - gtk_font_chooser_widget_cell_data_func, - fontchooser, - NULL); - - fontchooser->tweak_action = G_ACTION (g_simple_action_new_stateful ("tweak", NULL, g_variant_new_boolean (FALSE))); - g_signal_connect (fontchooser->tweak_action, "change-state", G_CALLBACK (change_tweak), fontchooser); + update_fontlist (self); /* Load data and set initial style-dependent parameters */ - gtk_font_chooser_widget_load_fonts (fontchooser, TRUE); - gtk_font_chooser_widget_populate_features (fontchooser); + gtk_font_chooser_widget_populate_features (self); - gtk_font_chooser_widget_set_cell_size (fontchooser); - gtk_font_chooser_widget_take_font_desc (fontchooser, NULL); + gtk_font_chooser_widget_take_font_desc (self, NULL); } /** @@ -913,271 +843,6 @@ gtk_font_chooser_widget_new (void) return g_object_new (GTK_TYPE_FONT_CHOOSER_WIDGET, NULL); } -static int -cmp_families (const void *a, - const void *b) -{ - const char *a_name = pango_font_family_get_name (*(PangoFontFamily **)a); - const char *b_name = pango_font_family_get_name (*(PangoFontFamily **)b); - - return g_utf8_collate (a_name, b_name); -} - -static void -gtk_font_chooser_widget_load_fonts (GtkFontChooserWidget *fontchooser, - gboolean force) -{ - GtkListStore *list_store; - gint n_families, i; - PangoFontFamily **families; - guint fontconfig_timestamp; - gboolean need_reload; - PangoFontMap *font_map; - - g_object_get (gtk_widget_get_settings (GTK_WIDGET (fontchooser)), - "gtk-fontconfig-timestamp", &fontconfig_timestamp, - NULL); - - /* The fontconfig timestamp is only set on systems with fontconfig; every - * other platform will set it to 0. For those systems, we fall back to - * reloading the fonts every time. - */ - need_reload = fontconfig_timestamp == 0 || - fontconfig_timestamp != fontchooser->last_fontconfig_timestamp; - - fontchooser->last_fontconfig_timestamp = fontconfig_timestamp; - - if (!need_reload && !force) - return; - - list_store = GTK_LIST_STORE (fontchooser->model); - - if (fontchooser->font_map) - font_map = fontchooser->font_map; - else - font_map = pango_cairo_font_map_get_default (); - pango_font_map_list_families (font_map, &families, &n_families); - - qsort (families, n_families, sizeof (PangoFontFamily *), cmp_families); - - g_signal_handlers_block_by_func (fontchooser->family_face_list, cursor_changed_cb, fontchooser); - g_signal_handlers_block_by_func (fontchooser->filter_model, rows_changed_cb, fontchooser); - gtk_list_store_clear (list_store); - - /* Iterate over families and faces */ - for (i = 0; i < n_families; i++) - { - GtkTreeIter iter; - PangoFontFace **faces; - int j, n_faces; - const gchar *fam_name = pango_font_family_get_name (families[i]); - - pango_font_family_list_faces (families[i], &faces, &n_faces); - - for (j = 0; j < n_faces; j++) - { - GtkDelayedFontDescription *desc; - const gchar *face_name; - char *title; - - face_name = pango_font_face_get_face_name (faces[j]); - - if ((fontchooser->level & GTK_FONT_CHOOSER_LEVEL_STYLE) != 0) - title = g_strconcat (fam_name, " ", face_name, NULL); - else - title = g_strdup (fam_name); - - desc = gtk_delayed_font_description_new (faces[j]); - - gtk_list_store_insert_with_values (list_store, &iter, -1, - FAMILY_COLUMN, families[i], - FACE_COLUMN, faces[j], - FONT_DESC_COLUMN, desc, - PREVIEW_TITLE_COLUMN, title, - -1); - - g_free (title); - gtk_delayed_font_description_unref (desc); - - if ((fontchooser->level & GTK_FONT_CHOOSER_LEVEL_STYLE) == 0) - break; - } - - g_free (faces); - } - - g_free (families); - - rows_changed_cb (fontchooser); - - g_signal_handlers_unblock_by_func (fontchooser->filter_model, rows_changed_cb, fontchooser); - g_signal_handlers_unblock_by_func (fontchooser->family_face_list, cursor_changed_cb, fontchooser); - - /* now make sure the font list looks right */ - if (!gtk_font_chooser_widget_find_font (fontchooser, fontchooser->font_desc, &fontchooser->font_iter)) - memset (&fontchooser->font_iter, 0, sizeof (GtkTreeIter)); - - gtk_font_chooser_widget_ensure_selection (fontchooser); -} - -static gboolean -visible_func (GtkTreeModel *model, - GtkTreeIter *iter, - gpointer user_data) -{ - GtkFontChooserWidget *fontchooser = user_data; - gboolean result = TRUE; - const gchar *search_text; - gchar **split_terms; - gchar *font_name, *font_name_casefold; - guint i; - - if (fontchooser->filter_func != NULL) - { - PangoFontFamily *family; - PangoFontFace *face; - - gtk_tree_model_get (model, iter, - FAMILY_COLUMN, &family, - FACE_COLUMN, &face, - -1); - - result = fontchooser->filter_func (family, face, fontchooser->filter_data); - - g_object_unref (family); - g_object_unref (face); - - if (!result) - return FALSE; - } - - /* If there's no filter string we show the item */ - search_text = gtk_editable_get_text (GTK_EDITABLE (fontchooser->search_entry)); - if (strlen (search_text) == 0) - return TRUE; - - gtk_tree_model_get (model, iter, - PREVIEW_TITLE_COLUMN, &font_name, - -1); - - if (font_name == NULL) - return FALSE; - - split_terms = g_strsplit (search_text, " ", 0); - font_name_casefold = g_utf8_casefold (font_name, -1); - - for (i = 0; split_terms[i] && result; i++) - { - gchar* term_casefold = g_utf8_casefold (split_terms[i], -1); - - if (!strstr (font_name_casefold, term_casefold)) - result = FALSE; - - g_free (term_casefold); - } - - g_free (font_name_casefold); - g_free (font_name); - g_strfreev (split_terms); - - return result; -} - -/* in pango units */ -static int -gtk_font_chooser_widget_get_preview_text_height (GtkFontChooserWidget *fontchooser) -{ - GtkWidget *treeview = fontchooser->family_face_list; - GtkStyleContext *context; - double font_size; - - context = gtk_widget_get_style_context (treeview); - font_size = _gtk_css_number_value_get (_gtk_style_context_peek_property (context, - GTK_CSS_PROPERTY_FONT_SIZE), - 100); - - return PANGO_SCALE_X_LARGE * font_size * PANGO_SCALE; -} - -static PangoAttrList * -gtk_font_chooser_widget_get_preview_attributes (GtkFontChooserWidget *fontchooser, - const PangoFontDescription *font_desc) -{ - PangoAttribute *attribute; - PangoAttrList *attrs; - - attrs = pango_attr_list_new (); - - if (font_desc) - { - attribute = pango_attr_font_desc_new (font_desc); - pango_attr_list_insert (attrs, attribute); - } - - attribute = pango_attr_size_new_absolute (gtk_font_chooser_widget_get_preview_text_height (fontchooser)); - pango_attr_list_insert (attrs, attribute); - - return attrs; -} - -static void -gtk_font_chooser_widget_cell_data_func (GtkTreeViewColumn *column, - GtkCellRenderer *cell, - GtkTreeModel *tree_model, - GtkTreeIter *iter, - gpointer user_data) -{ - GtkFontChooserWidget *fontchooser = user_data; - GtkDelayedFontDescription *desc; - PangoAttrList *attrs; - char *preview_title; - - gtk_tree_model_get (tree_model, iter, - PREVIEW_TITLE_COLUMN, &preview_title, - FONT_DESC_COLUMN, &desc, - -1); - - attrs = gtk_font_chooser_widget_get_preview_attributes (fontchooser, - gtk_delayed_font_description_get (desc)); - - g_object_set (cell, - "xpad", 20, - "ypad", 10, - "attributes", attrs, - "text", preview_title, - NULL); - - gtk_delayed_font_description_unref (desc); - pango_attr_list_unref (attrs); - g_free (preview_title); -} - -static void -gtk_font_chooser_widget_set_cell_size (GtkFontChooserWidget *fontchooser) -{ - PangoAttrList *attrs; - GtkRequisition size; - - gtk_cell_renderer_set_fixed_size (fontchooser->family_face_cell, -1, -1); - - attrs = gtk_font_chooser_widget_get_preview_attributes (fontchooser, NULL); - - g_object_set (fontchooser->family_face_cell, - "xpad", 20, - "ypad", 10, - "attributes", attrs, - "text", "x", - NULL); - - pango_attr_list_unref (attrs); - - gtk_cell_renderer_get_preferred_size (fontchooser->family_face_cell, - fontchooser->family_face_list, - &size, - NULL); - gtk_cell_renderer_set_fixed_size (fontchooser->family_face_cell, size.width, size.height); -} - static void gtk_font_chooser_widget_finalize (GObject *object) { @@ -1211,89 +876,86 @@ my_pango_font_family_equal (const char *familya, return g_ascii_strcasecmp (familya, familyb) == 0; } -static gboolean -gtk_font_chooser_widget_find_font (GtkFontChooserWidget *fontchooser, - const PangoFontDescription *font_desc, - /* out arguments */ - GtkTreeIter *iter) +static void +gtk_font_chooser_widget_ensure_matching_selection (GtkFontChooserWidget *self) { - gboolean valid; + const char *desc_family; + guint i, n; - if (pango_font_description_get_family (font_desc) == NULL) - return FALSE; - - for (valid = gtk_tree_model_get_iter_first (fontchooser->model, iter); - valid; - valid = gtk_tree_model_iter_next (fontchooser->model, iter)) + desc_family = pango_font_description_get_family (self->font_desc); + if (desc_family == NULL) { - GtkDelayedFontDescription *desc; - PangoFontDescription *merged; + gtk_single_selection_set_selected (self->selection, GTK_INVALID_LIST_POSITION); + return; + } + + n = g_list_model_get_n_items (G_LIST_MODEL (self->selection)); + for (i = 0; i < n; i++) + { + gpointer item = g_list_model_get_item (G_LIST_MODEL (self->selection), i); + PangoFontFace *face; PangoFontFamily *family; + PangoFontDescription *merged; - gtk_tree_model_get (fontchooser->model, iter, - FAMILY_COLUMN, &family, - FONT_DESC_COLUMN, &desc, - -1); - - if (!my_pango_font_family_equal (pango_font_description_get_family (font_desc), - pango_font_family_get_name (family))) + if (PANGO_IS_FONT_FAMILY (item)) { - gtk_delayed_font_description_unref (desc); - g_object_unref (family); + family = item; + face = pango_font_family_get_face (family, NULL); + } + else + { + face = item; + family = pango_font_face_get_family (face); + } + if (!my_pango_font_family_equal (desc_family, pango_font_family_get_name (family))) + { + g_object_unref (face); continue; } - merged = pango_font_description_copy_static (gtk_delayed_font_description_get (desc)); + merged = pango_font_face_describe (face); + pango_font_description_merge_static (merged, self->font_desc, FALSE); + g_object_unref (face); - pango_font_description_merge_static (merged, font_desc, FALSE); - if (pango_font_description_equal (merged, font_desc)) + if (pango_font_description_equal (merged, self->font_desc)) { - gtk_delayed_font_description_unref (desc); pango_font_description_free (merged); - g_object_unref (family); break; } - gtk_delayed_font_description_unref (desc); pango_font_description_free (merged); - g_object_unref (family); } - return valid; -} - -static PangoFontFamily * -gtk_font_chooser_widget_get_family (GtkFontChooser *chooser) -{ - GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (chooser); - PangoFontFamily *family; - - if (!gtk_list_store_iter_is_valid (GTK_LIST_STORE (fontchooser->model), &fontchooser->font_iter)) - return NULL; - - gtk_tree_model_get (fontchooser->model, &fontchooser->font_iter, - FAMILY_COLUMN, &family, - -1); - g_object_unref (family); - - return family; + gtk_single_selection_set_selected (self->selection, i); } static PangoFontFace * gtk_font_chooser_widget_get_face (GtkFontChooser *chooser) { - GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (chooser); - PangoFontFace *face; + GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (chooser); + gpointer item; - if (!gtk_list_store_iter_is_valid (GTK_LIST_STORE (fontchooser->model), &fontchooser->font_iter)) + item = gtk_single_selection_get_selected_item (self->selection); + if (PANGO_IS_FONT_FAMILY (item)) + return pango_font_family_get_face (item, NULL); + else + return item; +} + +static PangoFontFamily * +gtk_font_chooser_widget_get_family (GtkFontChooser *chooser) +{ + GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (chooser); + gpointer item; + + item = gtk_single_selection_get_selected_item (self->selection); + if (item == NULL) return NULL; - gtk_tree_model_get (fontchooser->model, &fontchooser->font_iter, - FACE_COLUMN, &face, - -1); - g_object_unref (face); - - return face; + if (PANGO_IS_FONT_FAMILY (item)) + return item; + else + return pango_font_face_get_family (item); } static gint @@ -1320,13 +982,10 @@ gtk_font_chooser_widget_get_font (GtkFontChooserWidget *fontchooser) } static PangoFontDescription * -gtk_font_chooser_widget_get_font_desc (GtkFontChooserWidget *fontchooser) +gtk_font_chooser_widget_get_font_desc (GtkFontChooserWidget *self) { - GtkTreeSelection *selection; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (fontchooser->family_face_list)); - if (gtk_tree_selection_count_selected_rows (selection) > 0) - return fontchooser->font_desc; + if (gtk_single_selection_get_selected_item (self->selection)) + return self->font_desc; return NULL; } @@ -1341,95 +1000,6 @@ gtk_font_chooser_widget_set_font (GtkFontChooserWidget *fontchooser, gtk_font_chooser_widget_take_font_desc (fontchooser, font_desc); } -static void -gtk_font_chooser_widget_update_font_name (GtkFontChooserWidget *fontchooser, - GtkTreeSelection *selection) -{ - GtkTreeModel *model; - GtkTreeIter iter; - PangoFontFamily *family; - PangoFontFace *face; - GtkDelayedFontDescription *desc; - const PangoFontDescription *font_desc; - PangoAttrList *attrs; - const char *fam_name; - const char *face_name; - char *title; - - gtk_tree_selection_get_selected (selection, &model, &iter); - gtk_tree_model_get (model, &iter, - FAMILY_COLUMN, &family, - FACE_COLUMN, &face, - FONT_DESC_COLUMN, &desc, - -1); - - fam_name = pango_font_family_get_name (family); - face_name = pango_font_face_get_face_name (face); - font_desc = gtk_delayed_font_description_get (desc); - - g_object_unref (family); - g_object_unref (face); - gtk_delayed_font_description_unref (desc); - - if ((fontchooser->level & GTK_FONT_CHOOSER_LEVEL_STYLE) != 0) - title = g_strconcat (fam_name, " ", face_name, NULL); - else - title = g_strdup (fam_name); - - attrs = gtk_font_chooser_widget_get_preview_attributes (fontchooser, font_desc); - gtk_label_set_attributes (GTK_LABEL (fontchooser->font_name_label), attrs); - pango_attr_list_unref (attrs); - - gtk_label_set_label (GTK_LABEL (fontchooser->font_name_label), title); - g_free (title); -} - -static void -selection_changed (GtkTreeSelection *selection, - GtkFontChooserWidget *fontchooser) -{ - g_object_notify (G_OBJECT (fontchooser), "font"); - g_object_notify (G_OBJECT (fontchooser), "font-desc"); - - if (gtk_tree_selection_count_selected_rows (selection) > 0) - { - gtk_font_chooser_widget_update_font_name (fontchooser, selection); - g_simple_action_set_enabled (G_SIMPLE_ACTION (fontchooser->tweak_action), TRUE); - } - else - { - g_simple_action_set_state (G_SIMPLE_ACTION (fontchooser->tweak_action), g_variant_new_boolean (FALSE)); - g_simple_action_set_enabled (G_SIMPLE_ACTION (fontchooser->tweak_action), FALSE); - } -} - -static void -gtk_font_chooser_widget_ensure_selection (GtkFontChooserWidget *fontchooser) -{ - GtkTreeSelection *selection; - GtkTreeIter filter_iter; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (fontchooser->family_face_list)); - - if (gtk_list_store_iter_is_valid (GTK_LIST_STORE (fontchooser->model), &fontchooser->font_iter) && - gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER (fontchooser->filter_model), - &filter_iter, - &fontchooser->font_iter)) - { - GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (fontchooser->filter_model), - &filter_iter); - - gtk_tree_selection_select_iter (selection, &filter_iter); - gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (fontchooser->family_face_list), - path, NULL, FALSE, 0.0, 0.0); - gtk_tree_path_free (path); - } - else - { - gtk_tree_selection_unselect_all (selection); - } -} - /* OpenType variations */ static void @@ -2238,8 +1808,7 @@ update_font_features (GtkFontChooserWidget *fontchooser) static void gtk_font_chooser_widget_merge_font_desc (GtkFontChooserWidget *fontchooser, - const PangoFontDescription *font_desc, - GtkTreeIter *iter) + const PangoFontDescription *font_desc) { PangoFontMask mask; @@ -2268,16 +1837,6 @@ gtk_font_chooser_widget_merge_font_desc (GtkFontChooserWidget *fontchooser { gboolean has_tweak = FALSE; - if (&fontchooser->font_iter != iter) - { - if (iter == NULL) - memset (&fontchooser->font_iter, 0, sizeof (GtkTreeIter)); - else - memcpy (&fontchooser->font_iter, iter, sizeof (GtkTreeIter)); - - gtk_font_chooser_widget_ensure_selection (fontchooser); - } - gtk_font_chooser_widget_update_marks (fontchooser); if (gtk_font_chooser_widget_update_font_features (fontchooser)) @@ -2304,19 +1863,12 @@ gtk_font_chooser_widget_take_font_desc (GtkFontChooserWidget *fontchooser, font_desc = pango_font_description_from_string (GTK_FONT_CHOOSER_DEFAULT_FONT_NAME); mask = pango_font_description_get_set_fields (font_desc); + gtk_font_chooser_widget_merge_font_desc (fontchooser, font_desc); + if (mask & (PANGO_FONT_MASK_FAMILY | PANGO_FONT_MASK_STYLE | PANGO_FONT_MASK_VARIANT | PANGO_FONT_MASK_WEIGHT | PANGO_FONT_MASK_STRETCH)) { - GtkTreeIter iter; - - if (gtk_font_chooser_widget_find_font (fontchooser, font_desc, &iter)) - gtk_font_chooser_widget_merge_font_desc (fontchooser, font_desc, &iter); - else - gtk_font_chooser_widget_merge_font_desc (fontchooser, font_desc, NULL); - } - else - { - gtk_font_chooser_widget_merge_font_desc (fontchooser, font_desc, &fontchooser->font_iter); + gtk_font_chooser_widget_ensure_matching_selection (fontchooser); } pango_font_description_free (font_desc); @@ -2339,10 +1891,6 @@ gtk_font_chooser_widget_set_preview_text (GtkFontChooserWidget *fontchooser, gtk_editable_set_text (GTK_EDITABLE (fontchooser->preview), text); g_object_notify (G_OBJECT (fontchooser), "preview-text"); - - /* XXX: There's no API to tell the treeview that a column has changed, - * so we just */ - gtk_widget_queue_draw (fontchooser->family_face_list); } static gboolean @@ -2387,7 +1935,7 @@ gtk_font_chooser_widget_set_font_map (GtkFontChooser *chooser, context = gtk_widget_get_pango_context (fontchooser->preview); pango_context_set_font_map (context, fontmap); - gtk_font_chooser_widget_load_fonts (fontchooser, TRUE); + update_fontlist (fontchooser); } } @@ -2399,22 +1947,54 @@ gtk_font_chooser_widget_get_font_map (GtkFontChooser *chooser) return fontchooser->font_map; } +static gboolean +gtk_font_chooser_widget_filter_cb (gpointer item, + gpointer data) +{ + GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (data); + PangoFontFamily *family; + PangoFontFace *face; + + if (PANGO_IS_FONT_FAMILY (item)) + { + family = item; + face = pango_font_family_get_face (family, NULL); + } + else + { + face = item; + family = pango_font_face_get_family (face); + } + + return self->filter_func (family, face, self->filter_data); +} + static void -gtk_font_chooser_widget_set_filter_func (GtkFontChooser *chooser, +gtk_font_chooser_widget_set_filter_func (GtkFontChooser *chooser, GtkFontFilterFunc filter, gpointer data, GDestroyNotify destroy) { - GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (chooser); + GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (chooser); - if (fontchooser->filter_data_destroy) - fontchooser->filter_data_destroy (fontchooser->filter_data); + if (self->filter_data_destroy) + self->filter_data_destroy (self->filter_data); - fontchooser->filter_func = filter; - fontchooser->filter_data = data; - fontchooser->filter_data_destroy = destroy; + self->filter_func = filter; + self->filter_data = data; + self->filter_data_destroy = destroy; - gtk_font_chooser_widget_refilter_font_list (fontchooser); + if (filter) + { + gtk_custom_filter_set_filter_func (self->custom_filter, + gtk_font_chooser_widget_filter_cb, + self, + NULL); + } + else + { + gtk_custom_filter_set_filter_func (self->custom_filter, NULL, NULL, NULL); + } } static void @@ -2439,7 +2019,7 @@ gtk_font_chooser_widget_set_level (GtkFontChooserWidget *fontchooser, gtk_widget_hide (fontchooser->size_spin); } - gtk_font_chooser_widget_load_fonts (fontchooser, TRUE); + update_fontlist (fontchooser); g_object_notify (G_OBJECT (fontchooser), "level"); } diff --git a/gtk/ui/gtkfontchooserwidget.ui b/gtk/ui/gtkfontchooserwidget.ui index ac50802f91..01a3816677 100644 --- a/gtk/ui/gtkfontchooserwidget.ui +++ b/gtk/ui/gtkfontchooserwidget.ui @@ -1,17 +1,30 @@ - - - - - - - - - - model - - + + + + + + + + + + + search_entry + + + + + + + + + + + + + + 100 @@ -40,7 +53,6 @@ 1 1 Search font name - 0 @@ -54,11 +66,11 @@ list - + 6 6 - + 400 300 1 @@ -66,30 +78,40 @@ never 1 - - filter_model - 0 - 0 - 1 - - - - - browse - + + 1 + selection + + + + + + + + ]]> - - - - fixed - Font Family - - - end - - - - + @@ -224,6 +246,16 @@ 12 end 0 + + + selection + + + + + selection + + From 2f21003064cfee165d03f4e2ab0e97cc0e9f1108 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 12 Dec 2019 22:45:43 -0500 Subject: [PATCH 152/170] docs: Reorganize list widgets in their own chapter --- docs/reference/gtk/gtk4-docs.xml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index ca0f623b1d..b48b7ca008 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -73,6 +73,20 @@ + + List-based Widgets + + +
+ + +
+ + + + +
+ @@ -113,9 +127,6 @@ - - - From 21eac434c37c057f416aad4b1ec45152a281cd89 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Fri, 13 Dec 2019 14:03:20 -0500 Subject: [PATCH 153/170] builder-tool: Pass through CDATA where it makes sense This avoids a ton of escaping for GtkBuilderListItemFactory::bytes. --- gtk/tools/gtk-builder-tool-simplify.c | 62 ++++++++++++++++++--------- 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/gtk/tools/gtk-builder-tool-simplify.c b/gtk/tools/gtk-builder-tool-simplify.c index 228912ab5f..375702e72b 100644 --- a/gtk/tools/gtk-builder-tool-simplify.c +++ b/gtk/tools/gtk-builder-tool-simplify.c @@ -264,6 +264,36 @@ keep_for_rewrite (const char *class_name, return found; } +static gboolean +has_attribute (Element *elt, + const char *name, + const char *value) +{ + int i; + + for (i = 0; elt->attribute_names[i]; i++) + { + if (strcmp (elt->attribute_names[i], name) == 0 && + (value == NULL || strcmp (elt->attribute_values[i], value) == 0)) + return TRUE; + } + + return FALSE; +} + +static gboolean +is_cdata_property (Element *element) +{ + if (g_str_equal (element->element_name, "property") && + has_attribute (element, "name", "bytes") && + element->parent != NULL && + g_str_equal (element->parent->element_name, "object") && + has_attribute (element->parent, "class", "GtkBuilderListItemFactory")) + return TRUE; + + return FALSE; +} + static gboolean is_pcdata_element (Element *element) { @@ -486,23 +516,6 @@ value_is_default (Element *element, return ret; } -static gboolean -has_attribute (Element *elt, - const char *name, - const char *value) -{ - int i; - - for (i = 0; elt->attribute_names[i]; i++) - { - if (strcmp (elt->attribute_names[i], name) == 0 && - (value == NULL || strcmp (elt->attribute_values[i], value) == 0)) - return TRUE; - } - - return FALSE; -} - static const char * get_attribute_value (Element *element, const char *name) @@ -1794,9 +1807,18 @@ dump_element (Element *element, } else { - char *escaped = g_markup_escape_text (element->data, -1); - g_fprintf (output, "%s", escaped); - g_free (escaped); + if (is_cdata_property (element)) + { + g_fprintf (output, "data); + g_fprintf (output, "]]>"); + } + else + { + char *escaped = g_markup_escape_text (element->data, -1); + g_fprintf (output, "%s", escaped); + g_free (escaped); + } } g_fprintf (output, "\n", element->element_name); } From 69c86ae3855485bd9f68343233b7cdb6ef7f3712 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Fri, 13 Dec 2019 20:56:32 -0500 Subject: [PATCH 154/170] listitemwidget: Add single-click-activate Add a mode to GtkListItemWidget that activates on single click and selects on hover. Make GtkListItemManager set this on its items when its own 'property' of the same name is set. --- gtk/gtklistitemmanager.c | 30 ++++++++++++++++++ gtk/gtklistitemmanagerprivate.h | 5 +++ gtk/gtklistitemwidget.c | 54 ++++++++++++++++++++++++++++++++- gtk/gtklistitemwidgetprivate.h | 3 ++ 4 files changed, 91 insertions(+), 1 deletion(-) diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index 92221235b6..ef0570d45a 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -33,6 +33,7 @@ struct _GtkListItemManager GtkWidget *widget; GtkSelectionModel *model; GtkListItemFactory *factory; + gboolean single_click_activate; const char *item_css_name; GtkRbTree *items; @@ -932,6 +933,8 @@ gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, result = gtk_list_item_widget_new (self->factory, self->item_css_name); + gtk_list_item_widget_set_single_click_activate (GTK_LIST_ITEM_WIDGET (result), self->single_click_activate); + item = g_list_model_get_item (G_LIST_MODEL (self->model), position); selected = gtk_selection_model_is_selected (self->model, position); gtk_list_item_widget_update (GTK_LIST_ITEM_WIDGET (result), position, item, selected); @@ -1079,6 +1082,33 @@ gtk_list_item_manager_release_list_item (GtkListItemManager *self, gtk_widget_unparent (item); } +void +gtk_list_item_manager_set_single_click_activate (GtkListItemManager *self, + gboolean single_click_activate) +{ + GtkListItemManagerItem *item; + + g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); + + self->single_click_activate = single_click_activate; + + for (item = gtk_rb_tree_get_first (self->items); + item != NULL; + item = gtk_rb_tree_node_get_next (item)) + { + if (item->widget) + gtk_list_item_widget_set_single_click_activate (GTK_LIST_ITEM_WIDGET (item->widget), single_click_activate); + } +} + +gboolean +gtk_list_item_manager_get_single_click_activate (GtkListItemManager *self) +{ + g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), FALSE); + + return self->single_click_activate; +} + GtkListItemTracker * gtk_list_item_tracker_new (GtkListItemManager *self) { diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h index 18cdc6d9ff..ccb0570c20 100644 --- a/gtk/gtklistitemmanagerprivate.h +++ b/gtk/gtklistitemmanagerprivate.h @@ -87,6 +87,11 @@ void gtk_list_item_manager_set_model (GtkListItemMana GtkSelectionModel * gtk_list_item_manager_get_model (GtkListItemManager *self); guint gtk_list_item_manager_get_size (GtkListItemManager *self); +void gtk_list_item_manager_set_single_click_activate + (GtkListItemManager *self, + gboolean single_click_activate); +gboolean gtk_list_item_manager_get_single_click_activate + (GtkListItemManager *self); GtkListItemTracker * gtk_list_item_tracker_new (GtkListItemManager *self); void gtk_list_item_tracker_free (GtkListItemManager *self, diff --git a/gtk/gtklistitemwidget.c b/gtk/gtklistitemwidget.c index 7fc1061edd..2e6078d704 100644 --- a/gtk/gtklistitemwidget.c +++ b/gtk/gtklistitemwidget.c @@ -24,6 +24,7 @@ #include "gtkbinlayout.h" #include "gtkcssnodeprivate.h" #include "gtkeventcontrollerfocus.h" +#include "gtkeventcontrollermotion.h" #include "gtkgestureclick.h" #include "gtkintl.h" #include "gtklistitemfactoryprivate.h" @@ -42,11 +43,13 @@ struct _GtkListItemWidgetPrivate GObject *item; guint position; gboolean selected; + gboolean single_click_activate; }; enum { PROP_0, PROP_FACTORY, + PROP_SINGLE_CLICK_ACTIVATE, N_PROPS }; @@ -161,6 +164,10 @@ gtk_list_item_widget_set_property (GObject *object, gtk_list_item_widget_set_factory (self, g_value_get_object (value)); break; + case PROP_SINGLE_CLICK_ACTIVATE: + gtk_list_item_widget_set_single_click_activate (self, g_value_get_boolean (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -224,6 +231,13 @@ gtk_list_item_widget_class_init (GtkListItemWidgetClass *klass) GTK_TYPE_LIST_ITEM_FACTORY, G_PARAM_WRITABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + properties[PROP_SINGLE_CLICK_ACTIVATE] = + g_param_spec_boolean ("single-click-activate", + "Single click activate", + "Activate on single click", + FALSE, + G_PARAM_WRITABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (gobject_class, N_PROPS, properties); signals[ACTIVATE_SIGNAL] = @@ -322,7 +336,7 @@ gtk_list_item_widget_click_gesture_pressed (GtkGestureClick *gesture, if (!priv->list_item || priv->list_item->activatable) { - if (n_press == 2) + if (n_press == 2 || priv->single_click_activate) { gtk_widget_activate_action (GTK_WIDGET (self), "list.activate-item", @@ -350,6 +364,26 @@ gtk_list_item_widget_enter_cb (GtkEventControllerFocus *controller, priv->position); } +static void +gtk_list_item_widget_hover_cb (GtkEventControllerMotion *controller, + double x, + double y, + GtkListItemWidget *self) +{ + GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); + + if (!priv->single_click_activate) + return; + + if (!priv->list_item || priv->list_item->selectable) + { + gtk_widget_activate_action (GTK_WIDGET (self), + "list.select-item", + "(ubb)", + priv->position, FALSE, FALSE); + } +} + static void gtk_list_item_widget_click_gesture_released (GtkGestureClick *gesture, int n_press, @@ -394,6 +428,10 @@ gtk_list_item_widget_init (GtkListItemWidget *self) controller = gtk_event_controller_focus_new (); g_signal_connect (controller, "enter", G_CALLBACK (gtk_list_item_widget_enter_cb), self); gtk_widget_add_controller (GTK_WIDGET (self), controller); + + controller = gtk_event_controller_motion_new (); + g_signal_connect (controller, "enter", G_CALLBACK (gtk_list_item_widget_hover_cb), self); + gtk_widget_add_controller (GTK_WIDGET (self), controller); } GtkWidget * @@ -528,6 +566,20 @@ gtk_list_item_widget_set_factory (GtkListItemWidget *self, g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]); } +void +gtk_list_item_widget_set_single_click_activate (GtkListItemWidget *self, + gboolean single_click_activate) +{ + GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); + + if (priv->single_click_activate == single_click_activate) + return; + + priv->single_click_activate = single_click_activate; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SINGLE_CLICK_ACTIVATE]); +} + void gtk_list_item_widget_add_child (GtkListItemWidget *self, GtkWidget *child) diff --git a/gtk/gtklistitemwidgetprivate.h b/gtk/gtklistitemwidgetprivate.h index b5f2ec7393..f3c7fdbdc9 100644 --- a/gtk/gtklistitemwidgetprivate.h +++ b/gtk/gtklistitemwidgetprivate.h @@ -70,6 +70,9 @@ void gtk_list_item_widget_default_update (GtkListItemWidg void gtk_list_item_widget_set_factory (GtkListItemWidget *self, GtkListItemFactory *factory); +void gtk_list_item_widget_set_single_click_activate + (GtkListItemWidget *self, + gboolean single_click_activate); void gtk_list_item_widget_add_child (GtkListItemWidget *self, GtkWidget *child); void gtk_list_item_widget_remove_child (GtkListItemWidget *self, From 2c4c07c96dc28a523b0af368900ecef9334d38d7 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Fri, 13 Dec 2019 20:57:57 -0500 Subject: [PATCH 155/170] listview: Add single-click-activate Add a single-click-activate property to GtkListView. --- docs/reference/gtk/gtk4-sections.txt | 2 + gtk/gtklistview.c | 60 ++++++++++++++++++++++++++++ gtk/gtklistview.h | 6 +++ 3 files changed, 68 insertions(+) diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index df02bbc71c..29fbba6b52 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -477,6 +477,8 @@ gtk_list_view_set_model gtk_list_view_get_model gtk_list_view_set_show_separators gtk_list_view_get_show_separators +gtk_list_view_set_single_click_activate +gtk_list_view_get_single_click_activate GTK_LIST_VIEW GTK_LIST_VIEW_CLASS diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index a590abd882..30cd6bc847 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -84,6 +84,7 @@ enum PROP_FACTORY, PROP_MODEL, PROP_SHOW_SEPARATORS, + PROP_SINGLE_CLICK_ACTIVATE, N_PROPS }; @@ -638,6 +639,10 @@ gtk_list_view_get_property (GObject *object, g_value_set_boolean (value, self->show_separators); break; + case PROP_SINGLE_CLICK_ACTIVATE: + g_value_set_boolean (value, gtk_list_item_manager_get_single_click_activate (self->item_manager)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -666,6 +671,10 @@ gtk_list_view_set_property (GObject *object, gtk_list_view_set_show_separators (self, g_value_get_boolean (value)); break; + case PROP_SINGLE_CLICK_ACTIVATE: + gtk_list_view_set_single_click_activate (self, g_value_get_boolean (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -750,6 +759,18 @@ gtk_list_view_class_init (GtkListViewClass *klass) FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + /** + * GtkListView:single-click-activate: + * + * Activate rows on single click and select them on hover + */ + properties[PROP_SINGLE_CLICK_ACTIVATE] = + g_param_spec_boolean ("single-click-activate", + P_("Single click activate"), + P_("Activate rows on single click"), + FALSE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_properties (gobject_class, N_PROPS, properties); /** @@ -973,3 +994,42 @@ gtk_list_view_get_show_separators (GtkListView *self) return self->show_separators; } + +/** + * gtk_list_view_set_single_click_activate: + * @self: a #GtkListView + * @single_click_activate: %TRUE to activate items on single click + * + * Sets whether rows should be activated on single click and + * selected on hover. + */ +void +gtk_list_view_set_single_click_activate (GtkListView *self, + gboolean single_click_activate) +{ + g_return_if_fail (GTK_IS_LIST_VIEW (self)); + + if (single_click_activate == gtk_list_item_manager_get_single_click_activate (self->item_manager)) + return; + + gtk_list_item_manager_set_single_click_activate (self->item_manager, single_click_activate); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SINGLE_CLICK_ACTIVATE]); +} + +/** + * gtk_list_view_get_single_click_activate: + * @self: a #GtkListView + * + * Returns whether rows will be activated on single click and + * selected on hover. + * + * Returns: %TRUE if rows are activated on single click + */ +gboolean +gtk_list_view_get_single_click_activate (GtkListView *self) +{ + g_return_val_if_fail (GTK_IS_LIST_VIEW (self), FALSE); + + return gtk_list_item_manager_get_single_click_activate (self->item_manager); +} diff --git a/gtk/gtklistview.h b/gtk/gtklistview.h index 3d5f6a8a56..f00bcf7e3f 100644 --- a/gtk/gtklistview.h +++ b/gtk/gtklistview.h @@ -69,6 +69,12 @@ void gtk_list_view_set_show_separators (GtkListView GDK_AVAILABLE_IN_ALL gboolean gtk_list_view_get_show_separators (GtkListView *self); +GDK_AVAILABLE_IN_ALL +void gtk_list_view_set_single_click_activate (GtkListView *self, + gboolean single_click_activate); +GDK_AVAILABLE_IN_ALL +gboolean gtk_list_view_get_single_click_activate (GtkListView *self); + G_END_DECLS #endif /* __GTK_LIST_VIEW_H__ */ From 66500a6882cb6498b89b0e72513afdc942d2c39d Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Wed, 4 Dec 2019 08:13:13 -0500 Subject: [PATCH 156/170] columnview: Add sorting This is a somewhat large commit that: - Adds GtkColumnViewSorter This is a special-purpose, private sorter implementation which sorts according to multiple sorters, allowing each individual sorter to be inverted. This will be used with clickable column view headers. - Adds a read-only GtkColumnView::sorter property The GtkColumnView creates a GtkColumnViewSorter at startup that it uses for this property. - Adds a writable GtkColumnViewColumn::sorter property This allows defining per-column sorters. Whenever an application sets a sorter for a column, the header becomes clickable and whenever a header is clicked, that column's sorter is prepended to the list of sorters, unless it is already the first sorter, in which case we invert its order. No column can be in the list more than once. --- docs/reference/gtk/gtk4-sections.txt | 3 + docs/reference/gtk/meson.build | 1 + gtk/gtkcolumnview.c | 55 ++++++ gtk/gtkcolumnview.h | 6 + gtk/gtkcolumnviewcolumn.c | 95 +++++++++ gtk/gtkcolumnviewcolumn.h | 7 + gtk/gtkcolumnviewcolumnprivate.h | 3 + gtk/gtkcolumnviewprivate.h | 1 + gtk/gtkcolumnviewsorter.c | 282 +++++++++++++++++++++++++++ gtk/gtkcolumnviewsorterprivate.h | 53 +++++ gtk/meson.build | 1 + testsuite/gtk/defaultvalue.c | 3 +- 12 files changed, 509 insertions(+), 1 deletion(-) create mode 100644 gtk/gtkcolumnviewsorter.c create mode 100644 gtk/gtkcolumnviewsorterprivate.h diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 29fbba6b52..071d026d07 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -500,6 +500,7 @@ gtk_column_view_remove_column gtk_column_view_get_columns gtk_column_view_get_model gtk_column_view_set_model +gtk_column_view_get_sorter gtk_column_view_get_show_separators gtk_column_view_set_show_separators @@ -524,6 +525,8 @@ gtk_column_view_column_set_factory gtk_column_view_column_get_factory gtk_column_view_column_set_title gtk_column_view_column_get_title +gtk_column_view_column_set_sorter +gtk_column_view_column_get_sorter GTK_COLUMN_VIEW_COLUMN GTK_COLUMN_VIEW_COLUMN_CLASS diff --git a/docs/reference/gtk/meson.build b/docs/reference/gtk/meson.build index 40584ab31f..ddc9e9c622 100644 --- a/docs/reference/gtk/meson.build +++ b/docs/reference/gtk/meson.build @@ -29,6 +29,7 @@ private_headers = [ 'gtkcolumnviewcolumnprivate.h', 'gtkcolumnviewlayoutprivate.h', 'gtkcolumnviewprivate.h', + 'gtkcolumnviewsorterprivate.h', 'gtkcolumnviewtitleprivate.h', 'gtkcomboboxprivate.h', 'gtkconstraintexpressionprivate.h', diff --git a/gtk/gtkcolumnview.c b/gtk/gtkcolumnview.c index d6170dfc46..9d3bf91299 100644 --- a/gtk/gtkcolumnview.c +++ b/gtk/gtkcolumnview.c @@ -26,6 +26,7 @@ #include "gtkcolumnlistitemfactoryprivate.h" #include "gtkcolumnviewcolumnprivate.h" #include "gtkcolumnviewlayoutprivate.h" +#include "gtkcolumnviewsorterprivate.h" #include "gtkcssnodeprivate.h" #include "gtkintl.h" #include "gtklistview.h" @@ -42,6 +43,12 @@ * * GtkColumnView is a widget to present a view into a large dynamic list of items * using multiple columns. + * + * It supports sorting that can be customized by the user by clicking on column + * view headers. To set this up, the #GtkSorter returned by gtk_column_view_get_sorter() + * must be attached to a sort model for the data that the view is showing, and the + * columns must have sorters attached to them by calling gtk_column_view_column_set_sorter(). + * The initial sort order can be set with gtk_column_view_sort_by_column(). */ struct _GtkColumnView @@ -54,6 +61,8 @@ struct _GtkColumnView GtkListView *listview; GtkColumnListItemFactory *factory; + + GtkSorter *sorter; }; struct _GtkColumnViewClass @@ -69,6 +78,7 @@ enum PROP_HSCROLL_POLICY, PROP_MODEL, PROP_SHOW_SEPARATORS, + PROP_SORTER, PROP_VADJUSTMENT, PROP_VSCROLL_POLICY, @@ -250,6 +260,8 @@ gtk_column_view_dispose (GObject *object) g_clear_pointer ((GtkWidget **) &self->listview, gtk_widget_unparent); g_clear_object (&self->factory); + g_clear_object (&self->sorter); + G_OBJECT_CLASS (gtk_column_view_parent_class)->dispose (object); } @@ -301,6 +313,10 @@ gtk_column_view_get_property (GObject *object, g_value_set_enum (value, gtk_scrollable_get_vscroll_policy (GTK_SCROLLABLE (self->listview))); break; + case PROP_SORTER: + g_value_set_object (value, self->sorter); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -429,6 +445,18 @@ gtk_column_view_class_init (GtkColumnViewClass *klass) FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + /** + * GtkColumnView:sorter: + * + * Sorter with the sorting choices of the user + */ + properties[PROP_SORTER] = + g_param_spec_object ("sorter", + P_("Sorter"), + P_("Sorter with sorting choices of the user"), + GTK_TYPE_SORTER, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (gobject_class, N_PROPS, properties); /** @@ -468,6 +496,7 @@ gtk_column_view_init (GtkColumnView *self) gtk_widget_set_layout_manager (self->header, gtk_column_view_layout_new (self)); gtk_widget_set_parent (self->header, GTK_WIDGET (self)); + self->sorter = gtk_column_view_sorter_new (); self->factory = gtk_column_list_item_factory_new (self); self->listview = GTK_LIST_VIEW (gtk_list_view_new_with_factory ( GTK_LIST_ITEM_FACTORY (g_object_ref (self->factory)))); @@ -643,6 +672,7 @@ gtk_column_view_remove_column (GtkColumnView *self, break; } + gtk_column_view_sorter_remove_column (GTK_COLUMN_VIEW_SORTER (self->sorter), column); gtk_column_view_column_set_column_view (column, NULL); g_list_store_remove (self->columns, i); } @@ -681,3 +711,28 @@ gtk_column_view_get_header_widget (GtkColumnView *self) return GTK_LIST_ITEM_WIDGET (self->header); } +/** + * gtk_column_view_get_sorter: + * @self: a #GtkColumnView + * + * Returns the sorter associated with users sorting choices in + * the column view. + * + * To allow users to customizable sorting by clicking on column + * headers, this sorter needs to be set on the sort + * model(s) underneath the model that is displayed + * by the view. + * + * See gtk_column_view_column_get_sorter() for setting up + * per-column sorting. + * + * Returns: (transfer none): the #GtkSorter of @self + */ +GtkSorter * +gtk_column_view_get_sorter (GtkColumnView *self) +{ + g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), NULL); + + return self->sorter; +} + diff --git a/gtk/gtkcolumnview.h b/gtk/gtkcolumnview.h index a4ac08a8b1..6ec91319e8 100644 --- a/gtk/gtkcolumnview.h +++ b/gtk/gtkcolumnview.h @@ -25,6 +25,8 @@ #endif #include +#include +#include G_BEGIN_DECLS @@ -65,12 +67,16 @@ GListModel * gtk_column_view_get_model (GtkColumnView GDK_AVAILABLE_IN_ALL void gtk_column_view_set_model (GtkColumnView *self, GListModel *model); + GDK_AVAILABLE_IN_ALL gboolean gtk_column_view_get_show_separators (GtkColumnView *self); GDK_AVAILABLE_IN_ALL void gtk_column_view_set_show_separators (GtkColumnView *self, gboolean show_separators); +GDK_AVAILABLE_IN_ALL +GtkSorter * gtk_column_view_get_sorter (GtkColumnView *self); + G_END_DECLS #endif /* __GTK_COLUMN_VIEW_H__ */ diff --git a/gtk/gtkcolumnviewcolumn.c b/gtk/gtkcolumnviewcolumn.c index c33c50cd53..de240b0d4e 100644 --- a/gtk/gtkcolumnviewcolumn.c +++ b/gtk/gtkcolumnviewcolumn.c @@ -20,6 +20,7 @@ #include "config.h" #include "gtkcolumnviewcolumnprivate.h" +#include "gtkcolumnviewsorterprivate.h" #include "gtkcolumnviewprivate.h" #include "gtkcolumnviewtitleprivate.h" @@ -32,6 +33,7 @@ #include "gtksizegroup.h" #include "gtkstylecontext.h" #include "gtkwidgetprivate.h" +#include "gtksorter.h" /** * SECTION:gtkcolumnviewcolumn @@ -48,6 +50,7 @@ struct _GtkColumnViewColumn GtkListItemFactory *factory; char *title; + GtkSorter *sorter; /* data for the view */ GtkColumnView *view; @@ -73,6 +76,7 @@ enum PROP_COLUMN_VIEW, PROP_FACTORY, PROP_TITLE, + PROP_SORTER, N_PROPS }; @@ -90,6 +94,7 @@ gtk_column_view_column_dispose (GObject *object) g_assert (self->first_cell == NULL); /* no view = no children */ g_clear_object (&self->factory); + g_clear_object (&self->sorter); g_clear_pointer (&self->title, g_free); G_OBJECT_CLASS (gtk_column_view_column_parent_class)->dispose (object); @@ -117,6 +122,10 @@ gtk_column_view_column_get_property (GObject *object, g_value_set_string (value, self->title); break; + case PROP_SORTER: + g_value_set_object (value, self->sorter); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -141,6 +150,10 @@ gtk_column_view_column_set_property (GObject *object, gtk_column_view_column_set_title (self, g_value_get_string (value)); break; + case PROP_SORTER: + gtk_column_view_column_set_sorter (self, g_value_get_object (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -192,6 +205,18 @@ gtk_column_view_column_class_init (GtkColumnViewColumnClass *klass) NULL, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + /** + * GtkColumnViewColumn:sorter: + * + * Sorter for sorting items according to this column + */ + properties[PROP_SORTER] = + g_param_spec_object ("sorter", + P_("Sorter"), + P_("Sorter for sorting items according to this column"), + GTK_TYPE_SORTER, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (gobject_class, N_PROPS, properties); } @@ -552,3 +577,73 @@ gtk_column_view_column_get_title (GtkColumnViewColumn *self) return self->title; } +#if 0 +static void +gtk_column_view_column_add_to_sorter (GtkColumnViewColumn *self) +{ + if (self->view == NULL) + return; + + gtk_column_view_sorter_add_column (GTK_COLUMN_VIEW_SORTER (gtk_column_view_get_sorter (self->view)), self); +} +#endif + +static void +gtk_column_view_column_remove_from_sorter (GtkColumnViewColumn *self) +{ + if (self->view == NULL) + return; + + gtk_column_view_sorter_remove_column (GTK_COLUMN_VIEW_SORTER (gtk_column_view_get_sorter (self->view)), self); +} + +/** + * gtk_column_view_column_set_sorter: + * @self: a #GtkColumnViewColumn + * @sorter: (nullable): the #GtkSorter to associate with @column + * + * Associates a sorter with the column. + * + * This sorter can be made active by clicking on the column + * header, or by calling gtk_column_view_sort_by_column(). + */ +void +gtk_column_view_column_set_sorter (GtkColumnViewColumn *self, + GtkSorter *sorter) +{ + g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self)); + g_return_if_fail (sorter == NULL || GTK_IS_SORTER (sorter)); + + if (!g_set_object (&self->sorter, sorter)) + return; + + gtk_column_view_column_remove_from_sorter (self); + + if (self->header) + gtk_column_view_title_update (GTK_COLUMN_VIEW_TITLE (self->header)); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]); +} + +/** + * gtk_column_view_column_get_sorter: + * @self: a #GtkColumnViewColumn + * + * Returns the sorter that is associated with the column. + * + * Returns: (transfer none): the #GtkSorter of @self + */ +GtkSorter * +gtk_column_view_column_get_sorter (GtkColumnViewColumn *self) +{ + g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self), NULL); + + return self->sorter; +} + +void +gtk_column_view_column_notify_sort (GtkColumnViewColumn *self) +{ + if (self->header) + gtk_column_view_title_update (GTK_COLUMN_VIEW_TITLE (self->header)); +} diff --git a/gtk/gtkcolumnviewcolumn.h b/gtk/gtkcolumnviewcolumn.h index 612cdb1854..f74cedaa08 100644 --- a/gtk/gtkcolumnviewcolumn.h +++ b/gtk/gtkcolumnviewcolumn.h @@ -25,6 +25,7 @@ #endif #include +#include G_BEGIN_DECLS @@ -65,6 +66,12 @@ void gtk_column_view_column_set_title (GtkColu GDK_AVAILABLE_IN_ALL const char * gtk_column_view_column_get_title (GtkColumnViewColumn *self); +GDK_AVAILABLE_IN_ALL +void gtk_column_view_column_set_sorter (GtkColumnViewColumn *self, + GtkSorter *sorter); +GDK_AVAILABLE_IN_ALL +GtkSorter * gtk_column_view_column_get_sorter (GtkColumnViewColumn *self); + G_END_DECLS #endif /* __GTK_COLUMN_VIEW_COLUMN_H__ */ diff --git a/gtk/gtkcolumnviewcolumnprivate.h b/gtk/gtkcolumnviewcolumnprivate.h index d7e06a5b0f..fe46663e63 100644 --- a/gtk/gtkcolumnviewcolumnprivate.h +++ b/gtk/gtkcolumnviewcolumnprivate.h @@ -24,6 +24,7 @@ #include "gtk/gtkcolumnviewcellprivate.h" + void gtk_column_view_column_set_column_view (GtkColumnViewColumn *self, GtkColumnView *view); @@ -44,4 +45,6 @@ void gtk_column_view_column_get_allocation (GtkColu int *offset, int *size); +void gtk_column_view_column_notify_sort (GtkColumnViewColumn *self); + #endif /* __GTK_COLUMN_VIEW_COLUMN_PRIVATE_H__ */ diff --git a/gtk/gtkcolumnviewprivate.h b/gtk/gtkcolumnviewprivate.h index c356fae508..0d5cbbeed6 100644 --- a/gtk/gtkcolumnviewprivate.h +++ b/gtk/gtkcolumnviewprivate.h @@ -22,6 +22,7 @@ #include "gtk/gtkcolumnview.h" +#include "gtk/gtkcolumnviewsorterprivate.h" #include "gtk/gtklistitemwidgetprivate.h" GtkListItemWidget * gtk_column_view_get_header_widget (GtkColumnView *self); diff --git a/gtk/gtkcolumnviewsorter.c b/gtk/gtkcolumnviewsorter.c new file mode 100644 index 0000000000..d431de0cb7 --- /dev/null +++ b/gtk/gtkcolumnviewsorter.c @@ -0,0 +1,282 @@ +/* + * Copyright © 2019 Matthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Matthias Clasen + */ + +#include "config.h" + +#include "gtkcolumnviewsorterprivate.h" + +#include "gtkcolumnviewcolumnprivate.h" +#include "gtkintl.h" +#include "gtktypebuiltins.h" + +typedef struct +{ + GtkColumnViewColumn *column; + GtkSorter *sorter; + gboolean inverted; + gulong changed_id; +} Sorter; + +static void +free_sorter (gpointer data) +{ + Sorter *s = data; + + g_signal_handler_disconnect (s->sorter, s->changed_id); + g_object_unref (s->sorter); + g_object_unref (s->column); + g_free (s); +} + +struct _GtkColumnViewSorter +{ + GtkSorter parent_instance; + + GSequence *sorters; +}; + +G_DEFINE_TYPE (GtkColumnViewSorter, gtk_column_view_sorter, GTK_TYPE_SORTER) + +static GtkOrdering +gtk_column_view_sorter_compare (GtkSorter *sorter, + gpointer item1, + gpointer item2) +{ + GtkColumnViewSorter *self = GTK_COLUMN_VIEW_SORTER (sorter); + GtkOrdering result = GTK_ORDERING_EQUAL; + GSequenceIter *iter; + + for (iter = g_sequence_get_begin_iter (self->sorters); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + Sorter *s = g_sequence_get (iter); + + result = gtk_sorter_compare (s->sorter, item1, item2); + if (s->inverted) + result = - result; + + if (result != GTK_ORDERING_EQUAL) + break; + } + + return result; +} + +static GtkSorterOrder +gtk_column_view_sorter_get_order (GtkSorter *sorter) +{ + GtkColumnViewSorter *self = GTK_COLUMN_VIEW_SORTER (sorter); + GtkSorterOrder result = GTK_SORTER_ORDER_NONE; + GSequenceIter *iter; + + for (iter = g_sequence_get_begin_iter (self->sorters); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + Sorter *s = g_sequence_get (iter); + + switch (gtk_sorter_get_order (s->sorter)) + { + case GTK_SORTER_ORDER_PARTIAL: + result = GTK_SORTER_ORDER_PARTIAL; + break; + case GTK_SORTER_ORDER_NONE: + break; + case GTK_SORTER_ORDER_TOTAL: + return GTK_SORTER_ORDER_TOTAL; + default: + g_assert_not_reached (); + break; + } + } + + return result; +} + +static void +gtk_column_view_sorter_dispose (GObject *object) +{ + GtkColumnViewSorter *self = GTK_COLUMN_VIEW_SORTER (object); + + g_clear_pointer (&self->sorters, g_sequence_free); + + G_OBJECT_CLASS (gtk_column_view_sorter_parent_class)->dispose (object); +} + +static void +gtk_column_view_sorter_class_init (GtkColumnViewSorterClass *class) +{ + GtkSorterClass *sorter_class = GTK_SORTER_CLASS (class); + GObjectClass *object_class = G_OBJECT_CLASS (class); + + sorter_class->compare = gtk_column_view_sorter_compare; + sorter_class->get_order = gtk_column_view_sorter_get_order; + + object_class->dispose = gtk_column_view_sorter_dispose; +} + +static void +gtk_column_view_sorter_init (GtkColumnViewSorter *self) +{ + self->sorters = g_sequence_new (free_sorter); +} + +GtkSorter * +gtk_column_view_sorter_new (void) +{ + return g_object_new (GTK_TYPE_COLUMN_VIEW_SORTER, NULL); +} + +static void +gtk_column_view_sorter_changed_cb (GtkSorter *sorter, int change, gpointer data) +{ + gtk_sorter_changed (GTK_SORTER (data), GTK_SORTER_CHANGE_DIFFERENT); +} + +static gboolean +remove_column (GtkColumnViewSorter *self, + GtkColumnViewColumn *column) +{ + GSequenceIter *iter; + + for (iter = g_sequence_get_begin_iter (self->sorters); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + Sorter *s = g_sequence_get (iter); + + if (s->column == column) + { + g_sequence_remove (iter); + return TRUE; + } + } + + return FALSE; +} + +gboolean +gtk_column_view_sorter_add_column (GtkColumnViewSorter *self, + GtkColumnViewColumn *column) +{ + GSequenceIter *iter; + GtkSorter *sorter; + Sorter *s, *first; + + g_return_val_if_fail (GTK_IS_COLUMN_VIEW_SORTER (self), FALSE); + g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (column), FALSE); + + sorter = gtk_column_view_column_get_sorter (column); + if (sorter == NULL) + return FALSE; + + iter = g_sequence_get_begin_iter (self->sorters); + if (!g_sequence_iter_is_end (iter)) + { + first = g_sequence_get (iter); + if (first->column == column) + { + first->inverted = !first->inverted; + goto out; + } + } + else + first = NULL; + + remove_column (self, column); + + s = g_new (Sorter, 1); + s->column = g_object_ref (column); + s->sorter = g_object_ref (sorter); + s->changed_id = g_signal_connect (sorter, "changed", G_CALLBACK (gtk_column_view_sorter_changed_cb), self); + s->inverted = FALSE; + + g_sequence_insert_before (iter, s); + + /* notify the previous first column to stop drawing an arrow */ + if (first) + gtk_column_view_column_notify_sort (first->column); + +out: + gtk_sorter_changed (GTK_SORTER (self), GTK_SORTER_CHANGE_DIFFERENT); + + gtk_column_view_column_notify_sort (column); + + return TRUE; +} + +gboolean +gtk_column_view_sorter_remove_column (GtkColumnViewSorter *self, + GtkColumnViewColumn *column) +{ + g_return_val_if_fail (GTK_IS_COLUMN_VIEW_SORTER (self), FALSE); + g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (column), FALSE); + + if (remove_column (self, column)) + { + gtk_sorter_changed (GTK_SORTER (self), GTK_SORTER_CHANGE_DIFFERENT); + gtk_column_view_column_notify_sort (column); + return TRUE; + } + + return FALSE; +} + +void +gtk_column_view_sorter_clear (GtkColumnViewSorter *self) +{ + GSequenceIter *iter; + Sorter *s; + GtkColumnViewColumn *column; + + g_return_if_fail (GTK_IS_COLUMN_VIEW_SORTER (self)); + + if (g_sequence_is_empty (self->sorters)) + return; + + iter = g_sequence_get_begin_iter (self->sorters); + s = g_sequence_get (iter); + column = s->column; + g_sequence_remove_range (iter, g_sequence_get_end_iter (self->sorters)); + + gtk_sorter_changed (GTK_SORTER (self), GTK_SORTER_CHANGE_DIFFERENT); + + gtk_column_view_column_notify_sort (column); +} + +GtkColumnViewColumn * +gtk_column_view_sorter_get_sort_column (GtkColumnViewSorter *self, + gboolean *inverted) +{ + GSequenceIter *iter; + Sorter *s; + + g_return_val_if_fail (GTK_IS_COLUMN_VIEW_SORTER (self), NULL); + + if (g_sequence_is_empty (self->sorters)) + return NULL; + + iter = g_sequence_get_begin_iter (self->sorters); + s = g_sequence_get (iter); + + *inverted = s->inverted; + + return s->column; +} diff --git a/gtk/gtkcolumnviewsorterprivate.h b/gtk/gtkcolumnviewsorterprivate.h new file mode 100644 index 0000000000..ce89952170 --- /dev/null +++ b/gtk/gtkcolumnviewsorterprivate.h @@ -0,0 +1,53 @@ +/* + * Copyright © 2019 Matthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Matthias Clasen + */ + +#ifndef __GTK_COLUMN_VIEW_SORTER_H__ +#define __GTK_COLUMN_VIEW_SORTER_H__ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_COLUMN_VIEW_SORTER (gtk_column_view_sorter_get_type ()) + +G_DECLARE_FINAL_TYPE (GtkColumnViewSorter, gtk_column_view_sorter, GTK, COLUMN_VIEW_SORTER, GtkSorter) + +GtkSorter * gtk_column_view_sorter_new (void); + +gboolean gtk_column_view_sorter_add_column (GtkColumnViewSorter *self, + GtkColumnViewColumn *column); +gboolean gtk_column_view_sorter_remove_column (GtkColumnViewSorter *self, + GtkColumnViewColumn *column); + +void gtk_column_view_sorter_clear (GtkColumnViewSorter *self); + +GtkColumnViewColumn * gtk_column_view_sorter_get_sort_column (GtkColumnViewSorter *self, + gboolean *inverted); + + +G_END_DECLS + +#endif /* __GTK_SORTER_H__ */ + diff --git a/gtk/meson.build b/gtk/meson.build index 642904b231..f53667a891 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -199,6 +199,7 @@ gtk_public_sources = files([ 'gtkcolorutils.c', 'gtkcolumnview.c', 'gtkcolumnviewcolumn.c', + 'gtkcolumnviewsorter.c', 'gtkcombobox.c', 'gtkcomboboxtext.c', 'gtkcomposetable.c', diff --git a/testsuite/gtk/defaultvalue.c b/testsuite/gtk/defaultvalue.c index 88b60463d5..79ed6179f3 100644 --- a/testsuite/gtk/defaultvalue.c +++ b/testsuite/gtk/defaultvalue.c @@ -240,7 +240,8 @@ test_type (gconstpointer data) continue; if (g_type_is_a (type, GTK_TYPE_COLUMN_VIEW) && - strcmp (pspec->name, "columns") == 0) + (strcmp (pspec->name, "columns") == 0 || + strcmp (pspec->name, "sorter") == 0)) continue; G_GNUC_BEGIN_IGNORE_DEPRECATIONS From 2945430ad7fdfd7be303ea6d32d35cf632e8937e Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 3 Dec 2019 21:31:57 -0500 Subject: [PATCH 157/170] columnview: Add a sort-by api --- docs/reference/gtk/gtk4-sections.txt | 1 + gtk/gtkcolumnview.c | 33 +++++++++++++++++++++ gtk/gtkcolumnview.h | 4 +++ gtk/gtkcolumnviewsorter.c | 43 +++++++++++++++++++++++++++- gtk/gtkcolumnviewsorterprivate.h | 4 +++ 5 files changed, 84 insertions(+), 1 deletion(-) diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 071d026d07..3f2597cedf 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -503,6 +503,7 @@ gtk_column_view_set_model gtk_column_view_get_sorter gtk_column_view_get_show_separators gtk_column_view_set_show_separators +gtk_column_view_sort_by_column GTK_COLUMN_VIEW GTK_COLUMN_VIEW_CLASS diff --git a/gtk/gtkcolumnview.c b/gtk/gtkcolumnview.c index 9d3bf91299..32a2bea66c 100644 --- a/gtk/gtkcolumnview.c +++ b/gtk/gtkcolumnview.c @@ -736,3 +736,36 @@ gtk_column_view_get_sorter (GtkColumnView *self) return self->sorter; } +/** + * gtk_column_view_sort_by_column: + * @self: a #GtkColumnView + * @column: (allow-none): the #GtkColumnViewColumn to sort by, or %NULL + * @direction: the direction to sort in + * + * Sets the sorting of the view. + * + * This function should be used to set up the initial sorting. At runtime, + * users can change the sorting of a column view by clicking on the list headers. + * + * This call only has an effect if the sorter returned by gtk_column_view_get_sorter() + * is set on a sort model, and gtk_column_view_column_set_sorter() has been called + * on @column to associate a sorter with the column. + * + * If @column is %NULL, the view will be unsorted. + */ +void +gtk_column_view_sort_by_column (GtkColumnView *self, + GtkColumnViewColumn *column, + GtkSortType direction) +{ + g_return_if_fail (GTK_IS_COLUMN_VIEW (self)); + g_return_if_fail (column == NULL || GTK_IS_COLUMN_VIEW_COLUMN (column)); + g_return_if_fail (column == NULL || gtk_column_view_column_get_column_view (column) == self); + + if (column == NULL) + gtk_column_view_sorter_clear (GTK_COLUMN_VIEW_SORTER (self->sorter)); + else + gtk_column_view_sorter_set_column (GTK_COLUMN_VIEW_SORTER (self->sorter), + column, + direction == GTK_SORT_DESCENDING); +} diff --git a/gtk/gtkcolumnview.h b/gtk/gtkcolumnview.h index 6ec91319e8..b24dc7d308 100644 --- a/gtk/gtkcolumnview.h +++ b/gtk/gtkcolumnview.h @@ -77,6 +77,10 @@ void gtk_column_view_set_show_separators (GtkColumnView GDK_AVAILABLE_IN_ALL GtkSorter * gtk_column_view_get_sorter (GtkColumnView *self); +void gtk_column_view_sort_by_column (GtkColumnView *self, + GtkColumnViewColumn *column, + GtkSortType direction); + G_END_DECLS #endif /* __GTK_COLUMN_VIEW_H__ */ diff --git a/gtk/gtkcolumnviewsorter.c b/gtk/gtkcolumnviewsorter.c index d431de0cb7..587731e1cd 100644 --- a/gtk/gtkcolumnviewsorter.c +++ b/gtk/gtkcolumnviewsorter.c @@ -239,6 +239,43 @@ gtk_column_view_sorter_remove_column (GtkColumnViewSorter *self, return FALSE; } +gboolean +gtk_column_view_sorter_set_column (GtkColumnViewSorter *self, + GtkColumnViewColumn *column, + gboolean inverted) +{ + GtkSorter *sorter; + Sorter *s; + + g_return_val_if_fail (GTK_IS_COLUMN_VIEW_SORTER (self), FALSE); + g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (column), FALSE); + + sorter = gtk_column_view_column_get_sorter (column); + if (sorter == NULL) + return FALSE; + + g_object_ref (column); + + g_sequence_remove_range (g_sequence_get_begin_iter (self->sorters), + g_sequence_get_end_iter (self->sorters)); + + s = g_new (Sorter, 1); + s->column = g_object_ref (column); + s->sorter = g_object_ref (sorter); + s->changed_id = g_signal_connect (sorter, "changed", G_CALLBACK (gtk_column_view_sorter_changed_cb), self); + s->inverted = inverted; + + g_sequence_prepend (self->sorters, s); + + gtk_sorter_changed (GTK_SORTER (self), GTK_SORTER_CHANGE_DIFFERENT); + + gtk_column_view_column_notify_sort (column); + + g_object_unref (column); + + return TRUE; +} + void gtk_column_view_sorter_clear (GtkColumnViewSorter *self) { @@ -253,12 +290,16 @@ gtk_column_view_sorter_clear (GtkColumnViewSorter *self) iter = g_sequence_get_begin_iter (self->sorters); s = g_sequence_get (iter); - column = s->column; + + column = g_object_ref (s->column); + g_sequence_remove_range (iter, g_sequence_get_end_iter (self->sorters)); gtk_sorter_changed (GTK_SORTER (self), GTK_SORTER_CHANGE_DIFFERENT); gtk_column_view_column_notify_sort (column); + + g_object_unref (column); } GtkColumnViewColumn * diff --git a/gtk/gtkcolumnviewsorterprivate.h b/gtk/gtkcolumnviewsorterprivate.h index ce89952170..c81c565252 100644 --- a/gtk/gtkcolumnviewsorterprivate.h +++ b/gtk/gtkcolumnviewsorterprivate.h @@ -46,6 +46,10 @@ void gtk_column_view_sorter_clear (GtkColumnViewSo GtkColumnViewColumn * gtk_column_view_sorter_get_sort_column (GtkColumnViewSorter *self, gboolean *inverted); +gboolean gtk_column_view_sorter_set_column (GtkColumnViewSorter *self, + GtkColumnViewColumn *column, + gboolean inverted); + G_END_DECLS From 1ba1eda8ab36491dc7568732a0013f8ed8e9da6e Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Wed, 4 Dec 2019 08:52:12 -0500 Subject: [PATCH 158/170] column view title: Show sort indicators --- gtk/gtkcolumnviewtitle.c | 74 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/gtk/gtkcolumnviewtitle.c b/gtk/gtkcolumnviewtitle.c index 7170018eb4..306064bcd8 100644 --- a/gtk/gtkcolumnviewtitle.c +++ b/gtk/gtkcolumnviewtitle.c @@ -21,10 +21,15 @@ #include "gtkcolumnviewtitleprivate.h" +#include "gtkcolumnviewprivate.h" #include "gtkcolumnviewcolumnprivate.h" +#include "gtkcolumnviewsorterprivate.h" #include "gtkintl.h" #include "gtklabel.h" #include "gtkwidgetprivate.h" +#include "gtkbox.h" +#include "gtkimage.h" +#include "gtkgestureclick.h" struct _GtkColumnViewTitle { @@ -32,7 +37,9 @@ struct _GtkColumnViewTitle GtkColumnViewColumn *column; + GtkWidget *box; GtkWidget *title; + GtkWidget *sort; }; struct _GtkColumnViewTitleClass @@ -74,7 +81,7 @@ gtk_column_view_title_dispose (GObject *object) { GtkColumnViewTitle *self = GTK_COLUMN_VIEW_TITLE (object); - g_clear_pointer(&self->title, gtk_widget_unparent); + g_clear_pointer (&self->box, gtk_widget_unparent); g_clear_object (&self->column); @@ -104,15 +111,47 @@ gtk_column_view_title_resize_func (GtkWidget *widget) gtk_column_view_column_queue_resize (self->column); } +static void +click_pressed_cb (GtkGestureClick *gesture, + guint n_press, + gdouble x, + gdouble y, + GtkWidget *widget) +{ + GtkColumnViewTitle *self = GTK_COLUMN_VIEW_TITLE (widget); + GtkSorter *sorter; + GtkColumnView *view; + GtkColumnViewSorter *view_sorter; + + sorter = gtk_column_view_column_get_sorter (self->column); + if (sorter == NULL) + return; + + view = gtk_column_view_column_get_column_view (self->column); + view_sorter = GTK_COLUMN_VIEW_SORTER (gtk_column_view_get_sorter (view)); + gtk_column_view_sorter_add_column (view_sorter, self->column); +} + static void gtk_column_view_title_init (GtkColumnViewTitle *self) { GtkWidget *widget = GTK_WIDGET (self); + GtkGesture *gesture; widget->priv->resize_func = gtk_column_view_title_resize_func; + self->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_set_parent (self->box, widget); + self->title = gtk_label_new (NULL); - gtk_widget_set_parent (self->title, widget); + gtk_box_append (GTK_BOX (self->box), self->title); + + self->sort = gtk_image_new (); + gtk_box_append (GTK_BOX (self->box), self->sort); + + gesture = gtk_gesture_click_new (); + g_signal_connect (gesture, "pressed", G_CALLBACK (click_pressed_cb), self); + gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture)); } GtkWidget * @@ -120,8 +159,7 @@ gtk_column_view_title_new (GtkColumnViewColumn *column) { GtkColumnViewTitle *title; - title = g_object_new (GTK_TYPE_COLUMN_VIEW_TITLE, - NULL); + title = g_object_new (GTK_TYPE_COLUMN_VIEW_TITLE, NULL); title->column = g_object_ref (column); gtk_column_view_title_update (title); @@ -132,7 +170,35 @@ gtk_column_view_title_new (GtkColumnViewColumn *column) void gtk_column_view_title_update (GtkColumnViewTitle *self) { + GtkSorter *sorter; + GtkColumnView *view; + GtkColumnViewSorter *view_sorter; + gboolean inverted; + GtkColumnViewColumn *active; + gtk_label_set_label (GTK_LABEL (self->title), gtk_column_view_column_get_title (self->column)); + + sorter = gtk_column_view_column_get_sorter (self->column); + + if (sorter) + { + view = gtk_column_view_column_get_column_view (self->column); + view_sorter = GTK_COLUMN_VIEW_SORTER (gtk_column_view_get_sorter (view)); + active = gtk_column_view_sorter_get_sort_column (view_sorter, &inverted); + + gtk_widget_show (self->sort); + if (self->column == active) + { + if (inverted) + gtk_image_set_from_icon_name (GTK_IMAGE (self->sort), "pan-down-symbolic"); + else + gtk_image_set_from_icon_name (GTK_IMAGE (self->sort), "pan-up-symbolic"); + } + else + gtk_image_clear (GTK_IMAGE (self->sort)); + } + else + gtk_widget_hide (self->sort); } GtkColumnViewColumn * From fb78f1ef8e13b4f64e395ce3dc7b9c7a2aeb22a0 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 17 Dec 2019 15:35:24 +0100 Subject: [PATCH 159/170] testcolumnview: Add sorters --- tests/testcolumnview.c | 132 ++++++++++++++++++++++++++++++----------- 1 file changed, 97 insertions(+), 35 deletions(-) diff --git a/tests/testcolumnview.c b/tests/testcolumnview.c index 0913aea5e9..bb49622313 100644 --- a/tests/testcolumnview.c +++ b/tests/testcolumnview.c @@ -313,6 +313,54 @@ match_file (gpointer item, gpointer data) return result; } +static int +compare_file_attribute (gconstpointer info1_, + gconstpointer info2_, + gpointer data) +{ + GFileInfo *info1 = (gpointer) info1_; + GFileInfo *info2 = (gpointer) info2_; + const char *attribute = data; + GFileAttributeType type1, type2; + + type1 = g_file_info_get_attribute_type (info1, attribute); + type2 = g_file_info_get_attribute_type (info2, attribute); + if (type1 != type2) + return (int) type2 - (int) type1; + + switch (type1) + { + case G_FILE_ATTRIBUTE_TYPE_INVALID: + case G_FILE_ATTRIBUTE_TYPE_OBJECT: + case G_FILE_ATTRIBUTE_TYPE_STRINGV: + return 0; + case G_FILE_ATTRIBUTE_TYPE_STRING: + return g_utf8_collate (g_file_info_get_attribute_string (info1, attribute), + g_file_info_get_attribute_string (info2, attribute)); + case G_FILE_ATTRIBUTE_TYPE_BYTE_STRING: + return strcmp (g_file_info_get_attribute_byte_string (info1, attribute), + g_file_info_get_attribute_byte_string (info2, attribute)); + case G_FILE_ATTRIBUTE_TYPE_BOOLEAN: + return g_file_info_get_attribute_boolean (info1, attribute) + - g_file_info_get_attribute_boolean (info2, attribute); + case G_FILE_ATTRIBUTE_TYPE_UINT32: + return g_file_info_get_attribute_uint32 (info1, attribute) + - g_file_info_get_attribute_uint32 (info2, attribute); + case G_FILE_ATTRIBUTE_TYPE_INT32: + return g_file_info_get_attribute_int32 (info1, attribute) + - g_file_info_get_attribute_int32 (info2, attribute); + case G_FILE_ATTRIBUTE_TYPE_UINT64: + return g_file_info_get_attribute_uint64 (info1, attribute) + - g_file_info_get_attribute_uint64 (info2, attribute); + case G_FILE_ATTRIBUTE_TYPE_INT64: + return g_file_info_get_attribute_int64 (info1, attribute) + - g_file_info_get_attribute_int64 (info2, attribute); + default: + g_assert_not_reached (); + return 0; + } +} + static GObject * get_object (GObject *unused, GFileInfo *info, @@ -403,6 +451,15 @@ const char *ui_file = " ]]>\n" "
\n" " \n" +" \n" +" \n" +" \n" +" \n" +" standard::display-name" +" \n" +" \n" +" \n" +" \n" "
\n" " \n" "
\n" @@ -462,43 +519,44 @@ const char *ui_file = struct { const char *title; + const char *attribute; const char *factory_xml; } extra_columns[] = { - { "Type", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_TYPE, "uint32") }, - { "Hidden", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN) }, - { "Backup", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP) }, - { "Symlink", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK) }, - { "Virtual", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL) }, - { "Volatile", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_VOLATILE) }, - { "Edit name", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME, "string") }, - { "Copy name", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_COPY_NAME, "string") }, - { "Description", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION, "string") }, - { "Icon", ICON_FACTORY (G_FILE_ATTRIBUTE_STANDARD_ICON) }, - { "Symbolic icon", ICON_FACTORY (G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON) }, - { "Content type", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, "string") }, - { "Fast content type", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, "string") }, - { "Size", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_SIZE, "uint64") }, - { "Allocated size", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE, "uint64") }, - { "Target URI", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, "string") }, - { "Sort order", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_SORT_ORDER, "int32") }, - { "ETAG value", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_ETAG_VALUE, "string") }, - { "File ID", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_ID_FILE, "string") }, - { "Filesystem ID", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_ID_FILESYSTEM, "string") }, - { "Read", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_READ) }, - { "Write", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE) }, - { "Execute", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE) }, - { "Delete", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE) }, - { "Trash", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH) }, - { "Rename", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME) }, - { "Can mount", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT) }, - { "Can unmount", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT) }, - { "Can eject", BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_CAN_EJECT) }, - { "UNIX device", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_UNIX_DEVICE, "uint32") }, - { "UNIX device file", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_UNIX_DEVICE_FILE, "string") }, - { "owner", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_OWNER_USER, "string") }, - { "owner (real)", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_OWNER_USER_REAL, "string") }, - { "group", SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_OWNER_GROUP, "string") }, - { "Preview icon", ICON_FACTORY (G_FILE_ATTRIBUTE_PREVIEW_ICON) }, + { "Type", G_FILE_ATTRIBUTE_STANDARD_TYPE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_TYPE, "uint32") }, + { "Hidden", G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN) }, + { "Backup", G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP) }, + { "Symlink", G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK) }, + { "Virtual", G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL) }, + { "Volatile", G_FILE_ATTRIBUTE_STANDARD_IS_VOLATILE, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_STANDARD_IS_VOLATILE) }, + { "Edit name", G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME, "string") }, + { "Copy name", G_FILE_ATTRIBUTE_STANDARD_COPY_NAME, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_COPY_NAME, "string") }, + { "Description", G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION, "string") }, + { "Icon", G_FILE_ATTRIBUTE_STANDARD_ICON, ICON_FACTORY (G_FILE_ATTRIBUTE_STANDARD_ICON) }, + { "Symbolic icon", G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON, ICON_FACTORY (G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON) }, + { "Content type", G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, "string") }, + { "Fast content type", G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, "string") }, + { "Size", G_FILE_ATTRIBUTE_STANDARD_SIZE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_SIZE, "uint64") }, + { "Allocated size", G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE, "uint64") }, + { "Target URI", G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, "string") }, + { "Sort order", G_FILE_ATTRIBUTE_STANDARD_SORT_ORDER, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_STANDARD_SORT_ORDER, "int32") }, + { "ETAG value", G_FILE_ATTRIBUTE_ETAG_VALUE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_ETAG_VALUE, "string") }, + { "File ID", G_FILE_ATTRIBUTE_ID_FILE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_ID_FILE, "string") }, + { "Filesystem ID", G_FILE_ATTRIBUTE_ID_FILESYSTEM, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_ID_FILESYSTEM, "string") }, + { "Read", G_FILE_ATTRIBUTE_ACCESS_CAN_READ, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_READ) }, + { "Write", G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE) }, + { "Execute", G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE) }, + { "Delete", G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE) }, + { "Trash", G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH) }, + { "Rename", G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME) }, + { "Can mount", G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT) }, + { "Can unmount", G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT) }, + { "Can eject", G_FILE_ATTRIBUTE_MOUNTABLE_CAN_EJECT, BOOLEAN_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_CAN_EJECT) }, + { "UNIX device", G_FILE_ATTRIBUTE_MOUNTABLE_UNIX_DEVICE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_UNIX_DEVICE, "uint32") }, + { "UNIX device file", G_FILE_ATTRIBUTE_MOUNTABLE_UNIX_DEVICE_FILE, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_MOUNTABLE_UNIX_DEVICE_FILE, "string") }, + { "owner", G_FILE_ATTRIBUTE_OWNER_USER, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_OWNER_USER, "string") }, + { "owner (real)", G_FILE_ATTRIBUTE_OWNER_USER_REAL, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_OWNER_USER_REAL, "string") }, + { "group", G_FILE_ATTRIBUTE_OWNER_GROUP, SIMPLE_STRING_FACTORY (G_FILE_ATTRIBUTE_OWNER_GROUP, "string") }, + { "Preview icon", G_FILE_ATTRIBUTE_PREVIEW_ICON, ICON_FACTORY (G_FILE_ATTRIBUTE_PREVIEW_ICON) }, }; #if 0 @@ -586,6 +644,7 @@ add_extra_columns (GtkColumnView *view, GtkBuilderScope *scope) { GtkColumnViewColumn *column; + GtkSorter *sorter; GBytes *bytes; guint i; @@ -595,6 +654,9 @@ add_extra_columns (GtkColumnView *view, column = gtk_column_view_column_new_with_factory (extra_columns[i].title, gtk_builder_list_item_factory_new_from_bytes (scope, bytes)); g_bytes_unref (bytes); + sorter = gtk_custom_sorter_new (compare_file_attribute, (gpointer) extra_columns[i].attribute, NULL); + gtk_column_view_column_set_sorter (column, sorter); + g_object_unref (sorter); gtk_column_view_append_column (view, column); } } From e3ce99988f89a084deb370f477660216d163dca7 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 8 Dec 2019 17:34:10 -0500 Subject: [PATCH 160/170] Add GtkTreeListRowSorter This is a special-purpose sorter that can apply the sorting of another sorter to the levels of a GtkTreeListModel. --- docs/reference/gtk/gtk4-docs.xml | 1 + docs/reference/gtk/gtk4-sections.txt | 17 ++ docs/reference/gtk/gtk4.types.in | 1 + gtk/gtk.h | 1 + gtk/gtktreelistrowsorter.c | 329 +++++++++++++++++++++++++++ gtk/gtktreelistrowsorter.h | 47 ++++ gtk/meson.build | 2 + tests/testcolumnview.c | 8 +- 8 files changed, 405 insertions(+), 1 deletion(-) create mode 100644 gtk/gtktreelistrowsorter.c create mode 100644 gtk/gtktreelistrowsorter.h diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index b48b7ca008..670d80d49e 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -90,6 +90,7 @@ + diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 3f2597cedf..8123359e5e 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -2680,6 +2680,23 @@ GTK_MULTI_SORTER_GET_CLASS gtk_multi_sorter_get_type +
+gtktreelistrowsorter +GtkTreeListRowSorter +GtkTreeListRowSorter +gtk_tree_list_row_sorter_new +gtk_tree_list_row_sorter_get_sorter +gtk_tree_list_row_sorter_set_sorter + +GTK_TREE_LIST_ROW_SORTER +GTK_IS_TREE_LIST_ROW_SORTER +GTK_TYPE_TREE_LIST_ROW_SORTER +GTK_IS_TREE_LIST_ROW_SORTER_CLASS +GTK_TREE_LIST_ROW_SORTER_GET_CLASS + +gtk_tree_list_row_sorter_get_type +
+
gtksortlistmodel GtkSortListModel diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in index aef3e772c6..be5ab6d20c 100644 --- a/docs/reference/gtk/gtk4.types.in +++ b/docs/reference/gtk/gtk4.types.in @@ -223,6 +223,7 @@ gtk_tree_drag_dest_get_type gtk_tree_drag_source_get_type gtk_tree_list_model_get_type gtk_tree_list_row_get_type +gtk_tree_list_row_sorter_get_type gtk_tree_model_filter_get_type gtk_tree_model_get_type gtk_tree_model_sort_get_type diff --git a/gtk/gtk.h b/gtk/gtk.h index 7651a2b390..bea638962a 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -257,6 +257,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtktreelistrowsorter.c b/gtk/gtktreelistrowsorter.c new file mode 100644 index 0000000000..bd7d7ee6b4 --- /dev/null +++ b/gtk/gtktreelistrowsorter.c @@ -0,0 +1,329 @@ +/* + * Copyright © 2019 Matthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Matthias Clasen + */ + +#include "config.h" + +#include "gtktreelistrowsorter.h" + +#include "gtktreelistmodel.h" + +#include "gtkintl.h" +#include "gtktypebuiltins.h" + +/** + * SECTION:gtktreelistrowsorter + * @title: GtkTreeListRowSorter + * @Short_description: Sort trees by levels + * @See_also: #GtkTreeListModel + * + * #GtkTreeListRowSorter is a special-purpose sorter that will apply a given + * sorter to the levels in a tree, while respecting the tree structure. + * + * Here is an example for setting up a column view with a tree model and + * a GtkTreeListSorter: + * + * |[ + * sorter = gtk_tree_list_row_sorter_new (gtk_column_view_get_sorter (view)); + * sort_model = gtk_sort_list_model_new (tree_model, sorter); + * selection = gtk_single_selection_new (sort_model); + * gtk_column_view_set_model (view, G_LIST_MODEL (selection)); + * ]| + */ + +struct _GtkTreeListRowSorter +{ + GtkSorter parent_instance; + + GtkSorter *sorter; +}; + +enum { + PROP_0, + PROP_SORTER, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +G_DEFINE_TYPE (GtkTreeListRowSorter, gtk_tree_list_row_sorter, GTK_TYPE_SORTER) + +static GtkOrdering +gtk_tree_list_row_sorter_compare (GtkSorter *sorter, + gpointer item1, + gpointer item2) +{ + GtkTreeListRowSorter *self = GTK_TREE_LIST_ROW_SORTER (sorter); + GtkTreeListRow *r1, *r2; + GtkTreeListRow *p1, *p2; + guint d1, d2; + GtkOrdering result = GTK_ORDERING_EQUAL; + + /* break ties here so we really are a total order */ + if (!GTK_IS_TREE_LIST_ROW (item1)) + return GTK_IS_TREE_LIST_ROW (item2) ? GTK_ORDERING_LARGER : (item1 < item2 ? GTK_ORDERING_SMALLER : GTK_ORDERING_LARGER); + else if (!GTK_IS_TREE_LIST_ROW (item2)) + return GTK_ORDERING_SMALLER; + + r1 = GTK_TREE_LIST_ROW (item1); + r2 = GTK_TREE_LIST_ROW (item2); + + g_object_ref (r1); + g_object_ref (r2); + + d1 = gtk_tree_list_row_get_depth (r1); + d2 = gtk_tree_list_row_get_depth (r2); + + /* First, get to the same depth */ + while (d1 > d2) + { + p1 = gtk_tree_list_row_get_parent (r1); + g_object_unref (r1); + r1 = p1; + d1--; + result = GTK_ORDERING_LARGER; + } + while (d2 > d1) + { + p2 = gtk_tree_list_row_get_parent (r2); + g_object_unref (r2); + r2 = p2; + d2--; + result = GTK_ORDERING_SMALLER; + } + + /* Now walk up until we find a common parent */ + if (r1 != r2) + { + while (TRUE) + { + p1 = gtk_tree_list_row_get_parent (r1); + p2 = gtk_tree_list_row_get_parent (r2); + if (p1 == p2) + { + gpointer obj1 = gtk_tree_list_row_get_item (r1); + gpointer obj2 = gtk_tree_list_row_get_item (r2); + + if (self->sorter == NULL) + result = GTK_ORDERING_EQUAL; + else + result = gtk_sorter_compare (self->sorter, obj1, obj2); + + /* We must break ties here because if r1 ever gets a child, + * it would need to go right inbetween r1 and r2. */ + if (result == GTK_ORDERING_EQUAL) + { + if (gtk_tree_list_row_get_position (r1) < gtk_tree_list_row_get_position (r2)) + result = GTK_ORDERING_SMALLER; + else + result = GTK_ORDERING_LARGER; + } + + g_object_unref (obj1); + g_object_unref (obj2); + + break; + } + else + { + g_object_unref (r1); + r1 = p1; + g_object_unref (r2); + r2 = p2; + } + } + } + + g_object_unref (r1); + g_object_unref (r2); + + return result; +} + +static GtkSorterOrder +gtk_tree_list_row_sorter_get_order (GtkSorter *sorter) +{ + /* Must be a total order, because we need an exact position where new items go */ + return GTK_SORTER_ORDER_TOTAL; +} + +static void +propagate_changed (GtkSorter *sorter, GtkSorterChange change, gpointer data) +{ + gtk_sorter_changed (GTK_SORTER (data), change); +} + +static void +gtk_tree_list_row_sorter_dispose (GObject *object) +{ + GtkTreeListRowSorter *self = GTK_TREE_LIST_ROW_SORTER (object); + + if (self->sorter) + g_signal_handlers_disconnect_by_func (self->sorter, propagate_changed, self); + g_clear_object (&self->sorter); + + G_OBJECT_CLASS (gtk_tree_list_row_sorter_parent_class)->dispose (object); +} + +static void +gtk_tree_list_row_sorter_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkTreeListRowSorter *self = GTK_TREE_LIST_ROW_SORTER (object); + + switch (prop_id) + { + case PROP_SORTER: + gtk_tree_list_row_sorter_set_sorter (self, GTK_SORTER (g_value_get_object (value))); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_tree_list_row_sorter_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkTreeListRowSorter *self = GTK_TREE_LIST_ROW_SORTER (object); + + switch (prop_id) + { + case PROP_SORTER: + g_value_set_object (value, self->sorter); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_tree_list_row_sorter_class_init (GtkTreeListRowSorterClass *class) +{ + GtkSorterClass *sorter_class = GTK_SORTER_CLASS (class); + GObjectClass *object_class = G_OBJECT_CLASS (class); + + sorter_class->compare = gtk_tree_list_row_sorter_compare; + sorter_class->get_order = gtk_tree_list_row_sorter_get_order; + + object_class->dispose = gtk_tree_list_row_sorter_dispose; + object_class->set_property = gtk_tree_list_row_sorter_set_property; + object_class->get_property = gtk_tree_list_row_sorter_get_property; + + /** + * GtkTreeListRowSorter:sorter: + * + * The underlying sorter + */ + properties[PROP_SORTER] = + g_param_spec_object ("sorter", + P_("Sorter"), + P_("The underlying sorter"), + GTK_TYPE_SORTER, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); +} + +static void +gtk_tree_list_row_sorter_init (GtkTreeListRowSorter *self) +{ +} + +/** + * gtk_tree_list_row_sorter_new: + * @sorter: (nullable) (transfer full): a #GtkSorter, or %NULL + * + * Create a special-purpose sorter that applies the sorting + * of @sorter to the levels of a #GtkTreeListModel. + * + * Note that this sorter relies on #GtkTreeListModel:passthrough + * being %FALSE as it can only sort #GtkTreeListRows. + * + * Returns: a new #GtkSorter + */ +GtkSorter * +gtk_tree_list_row_sorter_new (GtkSorter *sorter) +{ + GtkSorter *result; + + g_return_val_if_fail (sorter == NULL || GTK_IS_SORTER (sorter), NULL); + + result = g_object_new (GTK_TYPE_TREE_LIST_ROW_SORTER, + "sorter", sorter, + NULL); + + g_clear_object (&sorter); + + return result; +} + +/** + * gtk_tree_list_row_sorter_set_sorter: + * @self: a #GtkTreeListRowSorter + * @sorter: (nullable) (transfer none): The sorter to use, or %NULL + * + * Sets the sorter to use for items with the same parent. + * + * This sorter will be passed the #GtkTreeListRow:item of the tree + * list rows passed to @self. + **/ +void +gtk_tree_list_row_sorter_set_sorter (GtkTreeListRowSorter *self, + GtkSorter *sorter) +{ + g_return_if_fail (GTK_IS_TREE_LIST_ROW_SORTER (self)); + g_return_if_fail (sorter == NULL || GTK_IS_SORTER (sorter)); + + if (self->sorter == sorter) + return; + + if (self->sorter) + g_signal_handlers_disconnect_by_func (self->sorter, propagate_changed, self); + g_set_object (&self->sorter, sorter); + if (self->sorter) + g_signal_connect (sorter, "changed", G_CALLBACK (propagate_changed), self); + + gtk_sorter_changed (GTK_SORTER (self), GTK_SORTER_CHANGE_DIFFERENT); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]); +} + +/** + * gtk_tree_list_row_sorter_get_sorter: + * @self: a #GtkTreeListRowSorter + * + * Returns the sorter used by @self. + * + * Returns: (transfer none) (nullable): the sorter used + **/ +GtkSorter * +gtk_tree_list_row_sorter_get_sorter (GtkTreeListRowSorter *self) +{ + g_return_val_if_fail (GTK_IS_TREE_LIST_ROW_SORTER (self), NULL); + + return self->sorter; +} diff --git a/gtk/gtktreelistrowsorter.h b/gtk/gtktreelistrowsorter.h new file mode 100644 index 0000000000..8f2db0e51a --- /dev/null +++ b/gtk/gtktreelistrowsorter.h @@ -0,0 +1,47 @@ +/* + * Copyright © 2019 Matthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Matthias Clasen + */ + +#ifndef __GTK_TREE_LIST_ROW_SORTER_H__ +#define __GTK_TREE_LIST_ROW_SORTER_H__ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_TREE_LIST_ROW_SORTER (gtk_tree_list_row_sorter_get_type ()) +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkTreeListRowSorter, gtk_tree_list_row_sorter, GTK, TREE_LIST_ROW_SORTER, GtkSorter) + +GDK_AVAILABLE_IN_ALL +GtkSorter * gtk_tree_list_row_sorter_new (GtkSorter *sorter); + +GDK_AVAILABLE_IN_ALL +GtkSorter * gtk_tree_list_row_sorter_get_sorter (GtkTreeListRowSorter *self); +GDK_AVAILABLE_IN_ALL +void gtk_tree_list_row_sorter_set_sorter (GtkTreeListRowSorter *self, + GtkSorter *sorter); + +G_END_DECLS + +#endif /* __GTK_TREE_LIST_ROW_SORTER_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index f53667a891..6c02f597d4 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -402,6 +402,7 @@ gtk_public_sources = files([ 'gtktreednd.c', 'gtktreeexpander.c', 'gtktreelistmodel.c', + 'gtktreelistrowsorter.c', 'gtktreemodel.c', 'gtktreemodelfilter.c', 'gtktreemodelsort.c', @@ -657,6 +658,7 @@ gtk_public_headers = files([ 'gtktreednd.h', 'gtktreeexpander.h', 'gtktreelistmodel.h', + 'gtktreelistrowsorter.h', 'gtktreemodel.h', 'gtktreemodelfilter.h', 'gtktreemodelsort.h', diff --git a/tests/testcolumnview.c b/tests/testcolumnview.c index bb49622313..0b6e8a749e 100644 --- a/tests/testcolumnview.c +++ b/tests/testcolumnview.c @@ -677,6 +677,8 @@ main (int argc, char *argv[]) GtkTreeListModel *tree; GtkFilterListModel *filter; GtkFilter *custom_filter; + GtkSortListModel *sort; + GtkSorter *sorter; GFile *root; GtkBuilderScope *scope; GtkBuilder *builder; @@ -727,8 +729,11 @@ main (int argc, char *argv[]) g_object_unref (dirmodel); g_object_unref (root); + sorter = gtk_tree_list_row_sorter_new (g_object_ref (gtk_column_view_get_sorter (GTK_COLUMN_VIEW (view)))); + sort = gtk_sort_list_model_new (G_LIST_MODEL (tree), sorter); + custom_filter = gtk_custom_filter_new (match_file, g_object_ref (search_entry), g_object_unref); - filter = gtk_filter_list_model_new (G_LIST_MODEL (tree), custom_filter); + filter = gtk_filter_list_model_new (G_LIST_MODEL (sort), custom_filter); g_signal_connect (search_entry, "search-changed", G_CALLBACK (search_changed_cb), custom_filter); g_object_unref (custom_filter); @@ -742,6 +747,7 @@ main (int argc, char *argv[]) gtk_box_append (GTK_BOX (vbox), statusbar); g_object_unref (filter); + g_object_unref (sort); g_object_unref (tree); list = gtk_list_view_new_with_factory ( From 7910271b6c82f0168b94b225e62668af2b5cd895 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 18 Dec 2019 05:39:04 +0100 Subject: [PATCH 161/170] testsuite: Add tests for GtkTreeListSorter --- testsuite/gtk/meson.build | 1 + testsuite/gtk/treesorter.c | 265 +++++++++++++++++++++++++++++++++++++ 2 files changed, 266 insertions(+) create mode 100644 testsuite/gtk/treesorter.c diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build index e0ea2edf0b..156b36d574 100644 --- a/testsuite/gtk/meson.build +++ b/testsuite/gtk/meson.build @@ -65,6 +65,7 @@ tests = [ ['treemodel', ['treemodel.c', 'liststore.c', 'treestore.c', 'filtermodel.c', 'modelrefcount.c', 'sortmodel.c', 'gtktreemodelrefcount.c']], ['treepath'], + ['treesorter'], ['treeview'], ['typename'], ['displayclose'], diff --git a/testsuite/gtk/treesorter.c b/testsuite/gtk/treesorter.c new file mode 100644 index 0000000000..6d1e314d30 --- /dev/null +++ b/testsuite/gtk/treesorter.c @@ -0,0 +1,265 @@ +/* GtkSorter tests. + * + * Copyright (C) 2019, Red Hat, Inc. + * Authors: Benjamin Otte + * Matthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include + +#include + +static GQuark number_quark; + +static guint +get_number (GObject *object) +{ + return GPOINTER_TO_UINT (g_object_get_qdata (object, number_quark)); +} + +static guint +get_with_parents (GObject *object) +{ + guint number; + + if (object == NULL) + return 0; + + if (GTK_IS_TREE_LIST_ROW (object)) + { + GtkTreeListRow *r; + + r = GTK_TREE_LIST_ROW (object); + object = gtk_tree_list_row_get_item (r); + number = 10 * get_with_parents (G_OBJECT (gtk_tree_list_row_get_parent (r))); + g_object_unref (r); + } + else + number = 0; + + number += get_number (object); + g_object_unref (object); + + return number; +} + +static char * +model_to_string (GListModel *model) +{ + GString *string = g_string_new (NULL); + guint i; + + for (i = 0; i < g_list_model_get_n_items (model); i++) + { + GObject *object = g_list_model_get_item (model, i); + + if (i > 0) + g_string_append (string, " "); + g_string_append_printf (string, "%u", get_with_parents (object)); + } + + return g_string_free (string, FALSE); +} + +static GListStore * +new_store (guint start, + guint end, + guint step); + +static void +add (GListStore *store, + guint number) +{ + GObject *object; + + /* 0 cannot be differentiated from NULL, so don't use it */ + g_assert (number != 0); + + object = g_object_new (G_TYPE_OBJECT, NULL); + g_object_set_qdata (object, number_quark, GUINT_TO_POINTER (number)); + g_list_store_append (store, object); + g_object_unref (object); +} + +#define assert_model(model, expected) G_STMT_START{ \ + char *s = model_to_string (G_LIST_MODEL (model)); \ + if (!g_str_equal (s, expected)) \ + g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #model " == " #expected, s, "==", expected); \ + g_free (s); \ +}G_STMT_END + +#define assert_not_model(model, expected) G_STMT_START{ \ + char *s = model_to_string (G_LIST_MODEL (model)); \ + if (g_str_equal (s, expected)) \ + g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #model " != " #expected, s, "!=", expected); \ + g_free (s); \ +}G_STMT_END + +/* This could be faster by foreach()ing through the models and comparing + * the item pointers */ +#define assert_model_equal(model1, model2) G_STMT_START{\ + char *s1 = model_to_string (G_LIST_MODEL (model1)); \ + char *s2 = model_to_string (G_LIST_MODEL (model2)); \ + if (!g_str_equal (s1, s2)) \ + g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #model1 " != " #model2, s1, "==", s2); \ + g_free (s2); \ + g_free (s1); \ +}G_STMT_END + +static GListStore * +new_empty_store (void) +{ + return g_list_store_new (G_TYPE_OBJECT); +} + +static GListStore * +new_store (guint start, + guint end, + guint step) +{ + GListStore *store = new_empty_store (); + guint i; + + for (i = start; i <= end; i += step) + add (store, i); + + return store; +} + +static GListModel * +new_child_model (gpointer item, + gpointer unused) +{ + guint n = get_number (item); + + if (n == 1) + return NULL; + + return G_LIST_MODEL (new_store (1, n - 1, 1)); +} + +static GListModel * +new_model (guint size) +{ + return G_LIST_MODEL (gtk_tree_list_model_new (FALSE, + G_LIST_MODEL (new_store (1, size, 1)), + TRUE, + new_child_model, + NULL, NULL)); +} + +static void +test_simple (void) +{ + GListModel *model; + GtkSortListModel *sort; + GtkSorter *sorter; + + model = new_model (3); + assert_model (model, "1 2 21 3 31 32 321"); + + sorter = gtk_tree_list_row_sorter_new (NULL); + sort = gtk_sort_list_model_new (model, sorter); + assert_model (sort, "1 2 21 3 31 32 321"); + + g_object_unref (sorter); + g_object_unref (sort); + g_object_unref (model); +} + +static GtkSorter * +new_numeric_sorter (void) +{ + return gtk_numeric_sorter_new (gtk_cclosure_expression_new (G_TYPE_UINT, NULL, 0, NULL, (GCallback)get_number, NULL, NULL)); +} + +static void +test_compare_total_order (void) +{ + GListModel *model; + GtkSorter *sorter; + guint i, j, n; + + model = new_model (3); + assert_model (model, "1 2 21 3 31 32 321"); + + sorter = gtk_tree_list_row_sorter_new (new_numeric_sorter ()); + + n = g_list_model_get_n_items (model); + for (i = 0; i < n; i++) + { + gpointer item1 = g_list_model_get_item (model, i); + for (j = 0; j < n; j++) + { + gpointer item2 = g_list_model_get_item (model, j); + + g_assert_cmpint (gtk_sorter_compare (sorter, item1, item2), ==, gtk_ordering_from_cmpfunc ((int) i - j)); + g_object_unref (item2); + } + g_object_unref (item1); + } + + g_object_unref (sorter); + g_object_unref (model); +} + +static void +test_compare_no_order (void) +{ + GListModel *model; + GtkSorter *sorter; + guint i, j, n; + + model = new_model (3); + assert_model (model, "1 2 21 3 31 32 321"); + + sorter = gtk_tree_list_row_sorter_new (NULL); + + n = g_list_model_get_n_items (model); + for (i = 0; i < n; i++) + { + gpointer item1 = g_list_model_get_item (model, i); + for (j = 0; j < n; j++) + { + gpointer item2 = g_list_model_get_item (model, j); + + g_assert_cmpint (gtk_sorter_compare (sorter, item1, item2), ==, gtk_ordering_from_cmpfunc ((int) i - j)); + g_object_unref (item2); + } + g_object_unref (item1); + } + + g_object_unref (sorter); + g_object_unref (model); +} + +int +main (int argc, char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + setlocale (LC_ALL, "C"); + + number_quark = g_quark_from_static_string ("Like a trashcan fire in a prison cell"); + + g_test_add_func ("/sorter/simple", test_simple); + g_test_add_func ("/sorter/compare-total-order", test_compare_total_order); + g_test_add_func ("/sorter/compare-no-order", test_compare_no_order); + + return g_test_run (); +} From f33df84eff9f72a05436abab523f17184d682024 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Wed, 18 Dec 2019 16:58:10 -0500 Subject: [PATCH 162/170] gtk-builder-tool: Minimally validate Check that the toplevel property name is legit. --- gtk/tools/gtk-builder-tool-simplify.c | 37 +++++++++++++++++++-------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/gtk/tools/gtk-builder-tool-simplify.c b/gtk/tools/gtk-builder-tool-simplify.c index 375702e72b..6bc65b5015 100644 --- a/gtk/tools/gtk-builder-tool-simplify.c +++ b/gtk/tools/gtk-builder-tool-simplify.c @@ -576,7 +576,10 @@ get_class_name (Element *element) } else if (g_str_equal (parent->element_name, "template")) { - return get_attribute_value (parent, "parent"); + if (get_attribute_value (parent, "parent")) + return get_attribute_value (parent, "parent"); + else + return get_attribute_value (parent, "class"); } return NULL; @@ -609,6 +612,19 @@ property_is_boolean (Element *element, return FALSE; } +static void +warn_missing_property (Element *element, + MyParserData *data, + const char *class_name, + const char *property_name, + PropKind kind) +{ + const char *kind_str[] = { "", "Packing ", "Cell ", "Layout " }; + + g_printerr (_("%s:%d: %sproperty %s::%s not found\n"), + data->input_filename, element->line_number, kind_str[kind], class_name, property_name); +} + static gboolean property_can_be_omitted (Element *element, MyParserData *data) @@ -651,18 +667,9 @@ property_can_be_omitted (Element *element, return FALSE; pspec = get_property_pspec (data, class_name, property_name, kind); - if (pspec == NULL) { - const char *kind_str[] = { - "", - "Packing ", - "Cell ", - "Layout " - }; - - g_printerr (_("%s:%d: %sproperty %s::%s not found\n"), - data->input_filename, element->line_number, kind_str[kind], class_name, property_name); + warn_missing_property (element, data, class_name, property_name, kind); return FALSE; } @@ -1592,6 +1599,14 @@ simplify_element (Element *element, property_can_be_omitted (element, data)) return TRUE; + if (g_str_equal (element->element_name, "binding")) + { + const char *property_name = get_attribute_value (element, "name"); + const char *class_name = get_class_name (element); + if (!get_property_pspec (data, class_name, property_name, PROP_KIND_OBJECT)) + warn_missing_property (element, data, class_name, property_name, PROP_KIND_OBJECT); + } + return FALSE; } From d717971f4196ffc24f75f1c221f27110b7450aa9 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Fri, 29 May 2020 13:25:36 -0400 Subject: [PATCH 163/170] gtk-demo: Cosmetic improvements to the listview demos Set default sizes, window titles and add more detail to the descriptions. --- demos/gtk-demo/flowbox.c | 2 ++ demos/gtk-demo/listview_clocks.c | 2 ++ demos/gtk-demo/listview_filebrowser.c | 3 ++- demos/gtk-demo/listview_weather.c | 7 +++++-- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/demos/gtk-demo/flowbox.c b/demos/gtk-demo/flowbox.c index 25bfe36258..760923af88 100644 --- a/demos/gtk-demo/flowbox.c +++ b/demos/gtk-demo/flowbox.c @@ -4,6 +4,8 @@ * as needed and support sorting and filtering. * * The children of a GtkFlowBox are regular widgets + * + * The dataset used here has 665 colors. */ #include diff --git a/demos/gtk-demo/listview_clocks.c b/demos/gtk-demo/listview_clocks.c index 95d45dadd7..49108ac280 100644 --- a/demos/gtk-demo/listview_clocks.c +++ b/demos/gtk-demo/listview_clocks.c @@ -450,6 +450,8 @@ do_listview_clocks (GtkWidget *do_widget) /* This is the normal window setup code every demo does */ window = gtk_window_new (); + gtk_window_set_title (GTK_WINDOW (window), "Clocks"); + gtk_window_set_default_size (GTK_WINDOW (window), 600, 400); gtk_window_set_display (GTK_WINDOW (window), gtk_widget_get_display (do_widget)); g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window); diff --git a/demos/gtk-demo/listview_filebrowser.c b/demos/gtk-demo/listview_filebrowser.c index 0bc34e4b8e..8483ec35ff 100644 --- a/demos/gtk-demo/listview_filebrowser.c +++ b/demos/gtk-demo/listview_filebrowser.c @@ -1,7 +1,8 @@ /* Lists/File browser * * This demo shows off the different layouts that are quickly achievable - * with GtkGridView by implementing a file browser with different views. + * with GtkListview and GtkGridView by implementing a file browser with + * different views. */ #include diff --git a/demos/gtk-demo/listview_weather.c b/demos/gtk-demo/listview_weather.c index 48f725b140..4dbd16e37f 100644 --- a/demos/gtk-demo/listview_weather.c +++ b/demos/gtk-demo/listview_weather.c @@ -5,12 +5,13 @@ * * The hourly weather info uses a horizontal listview. This is easy * to achieve because GtkListView implements the GtkOrientable interface. - * * To make the items in the list stand out more, the listview uses * separators. * * A GtkNoSelectionModel is used to make sure no item in the list can be * selected. All other interactions with the items is still possible. + * + * The dataset used here has 70000 items. */ #include @@ -283,6 +284,8 @@ do_listview_weather (GtkWidget *do_widget) GListModel *model, *selection; window = gtk_window_new (); + gtk_window_set_default_size (GTK_WINDOW (window), 600, 400); + gtk_window_set_title (GTK_WINDOW (window), "Weather"); gtk_window_set_display (GTK_WINDOW (window), gtk_widget_get_display (do_widget)); gtk_window_set_title (GTK_WINDOW (window), "Weather"); @@ -290,7 +293,7 @@ do_listview_weather (GtkWidget *do_widget) sw = gtk_scrolled_window_new (NULL, NULL); gtk_window_set_child (GTK_WINDOW (window), sw); - + listview = gtk_list_view_new_with_factory ( gtk_functions_list_item_factory_new (setup_widget, bind_widget, From 88044276770805af97740eb90390ccf5944abe8d Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 14 Dec 2019 11:53:24 -0500 Subject: [PATCH 164/170] gtk-demo: Demo columnview sorting Enhance the settings demo to have a sortable column. --- demos/gtk-demo/listview_settings.c | 23 ++++++++++++++++++----- demos/gtk-demo/listview_settings.ui | 7 ++++--- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/demos/gtk-demo/listview_settings.c b/demos/gtk-demo/listview_settings.c index 8f91853e5a..254313923b 100644 --- a/demos/gtk-demo/listview_settings.c +++ b/demos/gtk-demo/listview_settings.c @@ -2,7 +2,9 @@ * * This demo shows a settings viewer for GSettings. * - * It demonstrates how to implement support for trees with listview. + * It demonstrates how to implement support for trees with GtkListView. + * + * It also shows how to set up sorting for columns in a GtkColumnView. */ #include @@ -131,12 +133,13 @@ static gboolean transform_settings_to_keys (GBinding *binding, const GValue *from_value, GValue *to_value, - gpointer unused) + gpointer data) { GtkTreeListRow *treelistrow; GSettings *settings; GSettingsSchema *schema; GListStore *store; + GtkSortListModel *sort_model; char **keys; guint i; @@ -149,7 +152,6 @@ transform_settings_to_keys (GBinding *binding, store = g_list_store_new (SETTINGS_TYPE_KEY); keys = g_settings_schema_list_keys (schema); - qsort (keys, g_strv_length (keys), sizeof (char *), strvcmp); for (i = 0; keys[i] != NULL; i++) { @@ -164,7 +166,11 @@ transform_settings_to_keys (GBinding *binding, g_settings_schema_unref (schema); g_object_unref (settings); - g_value_take_object (to_value, store); + sort_model = gtk_sort_list_model_new (G_LIST_MODEL (store), + gtk_column_view_get_sorter (GTK_COLUMN_VIEW (data))); + g_object_unref (store); + + g_value_take_object (to_value, sort_model); return TRUE; } @@ -229,6 +235,8 @@ do_listview_settings (GtkWidget *do_widget) GtkTreeListModel *treemodel; GtkSingleSelection *selection; GtkBuilder *builder; + GtkColumnViewColumn *name_column; + GtkSorter *sorter; g_type_ensure (SETTINGS_TYPE_KEY); @@ -253,11 +261,16 @@ do_listview_settings (GtkWidget *do_widget) G_BINDING_SYNC_CREATE, transform_settings_to_keys, NULL, - NULL, NULL); + columnview, NULL); gtk_list_view_set_model (GTK_LIST_VIEW (listview), G_LIST_MODEL (selection)); g_object_unref (selection); g_object_unref (treemodel); g_object_unref (model); + + name_column = GTK_COLUMN_VIEW_COLUMN (gtk_builder_get_object (builder, "name_column")); + sorter = gtk_string_sorter_new (gtk_property_expression_new (SETTINGS_TYPE_KEY, NULL, "name")); + gtk_column_view_column_set_sorter (name_column, sorter); + g_object_unref (sorter); } if (!gtk_widget_get_visible (window)) diff --git a/demos/gtk-demo/listview_settings.ui b/demos/gtk-demo/listview_settings.ui index 529adb4756..2bc197f68f 100644 --- a/demos/gtk-demo/listview_settings.ui +++ b/demos/gtk-demo/listview_settings.ui @@ -2,10 +2,11 @@ Settings - 600 - 400 + 640 + 480 + 300 @@ -47,7 +48,7 @@ - + Name From 4a4c15b2ac3b30635b2e0e2f3aa17546d8121958 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 15 Dec 2019 23:48:34 -0500 Subject: [PATCH 165/170] gtk-demo: Add filtering to the settings demo A demo of filtering with lists was missing so far. --- demos/gtk-demo/listview_settings.c | 59 +++++++++++++++++++++++++-- demos/gtk-demo/listview_settings.ui | 63 +++++++++++++++++++++-------- 2 files changed, 101 insertions(+), 21 deletions(-) diff --git a/demos/gtk-demo/listview_settings.c b/demos/gtk-demo/listview_settings.c index 254313923b..944b764dd6 100644 --- a/demos/gtk-demo/listview_settings.c +++ b/demos/gtk-demo/listview_settings.c @@ -3,8 +3,8 @@ * This demo shows a settings viewer for GSettings. * * It demonstrates how to implement support for trees with GtkListView. - * - * It also shows how to set up sorting for columns in a GtkColumnView. + * It also shows how to set up sorting and filtering for columns in a + * GtkColumnView. */ #include @@ -129,6 +129,8 @@ strvcmp (gconstpointer p1, return strcmp (*s1, *s2); } +static GtkFilter *current_filter; + static gboolean transform_settings_to_keys (GBinding *binding, const GValue *from_value, @@ -140,6 +142,9 @@ transform_settings_to_keys (GBinding *binding, GSettingsSchema *schema; GListStore *store; GtkSortListModel *sort_model; + GtkFilterListModel *filter_model; + GtkFilter *filter; + GtkExpression *expression; char **keys; guint i; @@ -170,7 +175,18 @@ transform_settings_to_keys (GBinding *binding, gtk_column_view_get_sorter (GTK_COLUMN_VIEW (data))); g_object_unref (store); - g_value_take_object (to_value, sort_model); + expression = gtk_property_expression_new (SETTINGS_TYPE_KEY, NULL, "name"); + filter = gtk_string_filter_new (); + gtk_string_filter_set_expression (GTK_STRING_FILTER (filter), expression); + filter_model = gtk_filter_list_model_new (G_LIST_MODEL (sort_model), filter); + gtk_expression_unref (expression); + g_object_unref (sort_model); + + g_set_object (¤t_filter, filter); + + g_object_unref (filter); + + g_value_take_object (to_value, filter_model); return TRUE; } @@ -223,6 +239,32 @@ create_settings_model (gpointer item, return G_LIST_MODEL (result); } +static void +search_enabled (GtkSearchEntry *entry) +{ + gtk_editable_set_text (GTK_EDITABLE (entry), ""); +} + +static void +search_changed (GtkSearchEntry *entry, + gpointer data) +{ + const char *text = gtk_editable_get_text (GTK_EDITABLE (entry)); + + if (current_filter) + gtk_string_filter_set_search (GTK_STRING_FILTER (current_filter), text); +} + +static void +stop_search (GtkSearchEntry *entry, + gpointer data) +{ + gtk_editable_set_text (GTK_EDITABLE (entry), ""); + + if (current_filter) + gtk_string_filter_set_search (GTK_STRING_FILTER (current_filter), ""); +} + static GtkWidget *window = NULL; GtkWidget * @@ -234,13 +276,22 @@ do_listview_settings (GtkWidget *do_widget) GListModel *model; GtkTreeListModel *treemodel; GtkSingleSelection *selection; + GtkBuilderScope *scope; GtkBuilder *builder; GtkColumnViewColumn *name_column; GtkSorter *sorter; g_type_ensure (SETTINGS_TYPE_KEY); - builder = gtk_builder_new_from_resource ("/listview_settings/listview_settings.ui"); + scope = gtk_builder_cscope_new (); + gtk_builder_cscope_add_callback_symbol (GTK_BUILDER_CSCOPE (scope), "search_enabled", (GCallback)search_enabled); + gtk_builder_cscope_add_callback_symbol (GTK_BUILDER_CSCOPE (scope), "search_changed", (GCallback)search_changed); + gtk_builder_cscope_add_callback_symbol (GTK_BUILDER_CSCOPE (scope), "stop_search", (GCallback)stop_search); + + builder = gtk_builder_new (); + gtk_builder_set_scope (builder, scope); + gtk_builder_add_from_resource (builder, "/listview_settings/listview_settings.ui", NULL); + window = GTK_WIDGET (gtk_builder_get_object (builder, "window")); gtk_window_set_display (GTK_WINDOW (window), gtk_widget_get_display (do_widget)); diff --git a/demos/gtk-demo/listview_settings.ui b/demos/gtk-demo/listview_settings.ui index 2bc197f68f..8bf12cf496 100644 --- a/demos/gtk-demo/listview_settings.ui +++ b/demos/gtk-demo/listview_settings.ui @@ -4,6 +4,16 @@ Settings 640 480 + + + 1 + + + system-search-symbolic + + + + 300 @@ -44,15 +54,32 @@ - + + vertical - + + + - - Name - - - + + + + + + + + + 1 + 1 + + + + + Name + + + ]]> + + - - - - - - Value - - - + + + Value + + + ]]> + + - + From 12b1007046fdb510b256444cf1a00706a41a561e Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Fri, 29 May 2020 16:10:34 -0400 Subject: [PATCH 166/170] docs: Add a listview overview section Add a conceptual overview for all the listmodel-based widgets. Fixes: #2214 --- docs/reference/gtk/gtk4-docs.xml | 1 + docs/reference/gtk/lists-overview.md | 10 +-- docs/reference/gtk/meson.build | 1 + docs/reference/gtk/section-list-widget.md | 104 ++++++++++++++++++++++ 4 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 docs/reference/gtk/section-list-widget.md diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index 670d80d49e..ae44def8e5 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -75,6 +75,7 @@ List-based Widgets +
diff --git a/docs/reference/gtk/lists-overview.md b/docs/reference/gtk/lists-overview.md index 0fc533c341..524c41dc7a 100644 --- a/docs/reference/gtk/lists-overview.md +++ b/docs/reference/gtk/lists-overview.md @@ -87,12 +87,12 @@ Finally here's a quick list of equivalent functionality to look for when transit | #GtkListStore | #GListStore | | #GtkTreeStore | #GtkTreeListModel, #GtkTreeExpander | | #GtkTreeSelection | #GtkSelectionModel | -| #GtkTreeViewColumn | FIXME: ColumnView | -| #GtkTreeView | #GtkListView, FIXME: ColumnView | -| #GtkCellView | ? | -| #GtkComboBox | FIXME | +| #GtkTreeViewColumn | #GtkColumnView | +| #GtkTreeView | #GtkListView, #GtkColumnView | +| #GtkCellView | | +| #GtkComboBox | | | #GtkIconView | #GtkGridView | -| #GtkTreeSortable | FIXME: ColumnView? | +| #GtkTreeSortable | | | #GtkTreeModelSort | #GtkSortListModel | | #GtkTreeModelFilter | #GtkFilterListModel | | #GtkCellLayout | #GtkListItemFactory | diff --git a/docs/reference/gtk/meson.build b/docs/reference/gtk/meson.build index ddc9e9c622..2deb4a3d00 100644 --- a/docs/reference/gtk/meson.build +++ b/docs/reference/gtk/meson.build @@ -386,6 +386,7 @@ expand_content_md_files = [ 'css-properties.md', 'section-text-widget.md', 'section-tree-widget.md', + 'section-list-widget.md', 'question_index.md', ] diff --git a/docs/reference/gtk/section-list-widget.md b/docs/reference/gtk/section-list-widget.md new file mode 100644 index 0000000000..6c4fd1898d --- /dev/null +++ b/docs/reference/gtk/section-list-widget.md @@ -0,0 +1,104 @@ +# List Widget Overview {#ListWidget} + +GTK provides powerful widgets to display and edit lists of data. This document gives an overview over the concepts and how they work together to allow developers to implement lists. + +Lists are intended to be used whenever developers want to display lists of objects in roughly the same way. + +Lists are perfectly fine to be used for very short list of only 2 or 3 elements, but generally scale fine to millions of items. Of course, the larger the list grows, the more care needs to be taken to choose the right data structures to keep things running well. + +Lists are meant to be used with changing data, both with the items itself changing as well as the list adding and removing items. Of course, they work just as well with static data. + +## Terminology + +These terms are used throughout the documentation when talking about lists and you should be aware of what they refer to. These are often generic terms that have a specific meaning in this context. + +**_Views_** or **_list widgets_** are the widgets that hold and manage the lists. Examples of thse widgets would be #GtkListView or #GtkGridView. + +Views display data from a **_model_**. A model is a #GListModel and models can be provided in 3 ways or combinations thereof: + + * Many list models implementations already exist. There are models that provide specific data, like #GtkDirectoryList. And there are models like #GListStore that allow building lists manually. + + * Wrapping list models exists like #GtkFilterListModel or #GtkSortListModel that modify or adapt or combine other models. + + * Last but not least, developers are encouraged to create their own #GListModel implementations. The interface is kept deliberately small to make this easy. + +The same model can be used in multiple different views and wrapped with multiple different models at once. + +The elements in a model are called **_items_**. All items are #GObjects. + +Every item in a model has a **_position_** which is the unsigned integer that describes where in the model the item is located. This position can of course change as items are added or removed from the model. + +It is important to be aware of the difference between items and positions because the mapping from position to item is not permanent, so developers should think about whether they want to track items or positions when working with models. Oftentimes some things are really hard to do one way but very easy the other way. + +The other important part of a view is a **_factory_**. Each factory is a #GtkListItemFactory implementation that takes care of mapping the items of the model to widgets that can be shown in the view. + +The way factories do this is by creating a **_listitem_** for each item that is currently in use. Listitems are always #GtkListItem objects. They are only ever created by GTK and provide information about what item they are meant to display. + +Different factory implementations use various different methods to allow developers to add the right widgets to listitems and to link those widgets with the item managed by the listitem. Finding a suitable factory implementation for the data displayed, the programming language and development environment is an important task that can simplify setting up the view tremendously. + +Views support selections via a **_selection model_**. A selection model is an implementation of the #GtkSelectionModel interface on top of the #GListModel interface that allows marking each item in a model as either selected or not selected. Just like regular models, this can be implemented either by implementing #GtkSelectionModel directly or by wrapping a model with one of the GTK models provided for this purposes, such as #GtkNoSelection or #GtkSingleSelection. +The behavior of selection models - ie which items they allow selecting and what effect this has on other items - is completely up to the selection model. As such, single-selections, multi-selections or sharing selection state between different selection models and/or views is possible. +The selection state of an item is exposed in the listitem via the GtkListItem:selected property. + +Views and listitems also support activation. Activation means that double clicking or pressing enter while inside a focused row will cause the view to emit and activation signal such as GtkListView::activate. This provides an easy way to set up lists, but can also be turned off on listitems if undesired. + +Both selections and activation are supported among other things via widget actions (FIXME: Link docs). This allows developers to add widgets to their lists that cause selections to change or to trigger activation via the #GtkActionable interface. For a list of all supported actions see the relevant documentation. (FIXME: where do we document actions and how to I link that?) + +## Behind the scenes + +While for short lists it is not a problem to instantiate widgets for every item in the model, once lists grow to thousands or millions of elements, this gets less feasible. Because of this, the views only create a limited amount of listitems and recycle them by binding them to new items. In general, views try to keep listitems available only for the items that can actually be seen on screen. + +While this behavior allows views to scale effortlessly to huge lists, it has a few implication on what can be done with views. For example, it is not possible to query a view for a listitem used for a certain position - there might not be one and even if there is, that listitem might soon be recycled for a new position. + +It is also important that developers save state they care about in the item and do not rely on the widgets they created as those widgets can be recycled for a new position at any time causing any state to be lost. + +Another important requirement for views is that they need to know which items are not visible so they can be recycled. Views achieve that by implementing the #GtkScrollable interface and expecting to be placed directly into a #GtkScrolledWindow. + +Of course, if you are only using models with few items, this is not important and you can treat views like any other widget. But if you use large lists and your performance suffers, you should be aware of this. Views also allow tuning the number of listitems they create such as with gtk_grid_view_set_max_columns(), and developers running into performance problems should definitely study the tradeoffs of those and experiment with them. + +## Displaying trees + +While #GtkTreeView provided builtin support for trees, the list widgets, and in particular #GListModel do not. This was a design choice because the common use case is displaying lists and not trees and it greatly simplifies the API interface provided. + +However, GTK provides functionality to make trees look and behave like lists for the people who still want to display lists. This is achieved by using the #GtkTreeListModel model to flatten a tree into a list. The #GtkTreeExpander widget can then be used inside a listitem to allow users to expand and collapse rows and provide a similar experience to #GtkTreeView. + +Developers should refer to those objects' API reference for more discussion on the topic. + +## comparison to GtkTreeView + +Developers familiar with #GtkTreeView may wonder how this way of doing lists compares to the way they know. This section will try to outline the similarities and differences between the two. + +This new approach tries to provide roughly the same functionality as the old approach but often uses a very different approach to achieve these goals. + +The main difference and one of the primary reasons for this new development is that items can be displayed using regular widgets and #GtkCellRenderer is no longer necessary. This allows all benefits that widgets provide, such as complex layout and animating widgets and not only makes cell renderers obsolete, but also #GtkCellArea. + +The other big difference is the massive change to the data model. #GtkTreeModel was a rather complex interface for a tree data structure and #GListModel was deliberately designed to be a simple data structure for lists only. (See above (FIXME: link) for how to still do trees with this new model.) Another big change is that the new model allows for bulk changes via the #GListModel:items-changed signal while #GtkTreeModel only allows a single item to change at once. +The goal here is of course to encourage implementation of custom list models. + +Another consequence of the new model is that it is now easily possible to refer to the contents of a row in the model directly by keeping the item, while #GtkTreeRowReference was a very slow mechanism to achieve the same. And because the items are real objects, developers can make them emit change signals causing listitems and their children to update, which wasn't possible with #GtkTreeModel. + +The selection handling is also different. While selections used to be managed via custom code in each widget, selection state is now meant to be managed by the selection models. In particular this allows for complex use cases with specialized requirements (FIXME: Can I add a shoutout to @mitch here because I vividly remember a huge discussion about GtkTreeView's selection behavior and the Gimp). + +Finally here's a quick list of equivalent functionality to look for when transitioning code for easy lookup: + +| old | new | +| ------------------- | ----------------------------------- | +| #GtkTreeModel | #GListModel | +| #GtkTreePath | #guint position, #GtkTreeListRow | +| #GtkTreeIter | #guint position | +| GtkTreeRowReference | #GObject item | +| #GtkListStore | #GListStore | +| #GtkTreeStore | #GtkTreeListModel, #GtkTreeExpander | +| #GtkTreeSelection | #GtkSelectionModel | +| #GtkTreeViewColumn | #GtkColumnView | +| #GtkTreeView | #GtkListView, #GtkColumnView | +| #GtkCellView | | +| #GtkComboBox | | +| #GtkIconView | #GtkGridView | +| #GtkTreeSortable | #GtkColumnView | +| #GtkTreeModelSort | #GtkSortListModel | +| #GtkTreeModelFilter | #GtkFilterListModel | +| #GtkCellLayout | #GtkListItemFactory | +| #GtkCellArea | #GtkWidget | +| #GtkCellRenderer | #GtkWidget | + From f611d55f1631aa26b8c0409cc12c732c59a94758 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 23 Dec 2019 15:22:35 -0500 Subject: [PATCH 167/170] Spread single-click-activate api This makes sense to have in all the views, not just GtkListView. --- docs/reference/gtk/gtk4-sections.txt | 4 ++ gtk/gtkcolumnview.c | 60 ++++++++++++++++++++++++++++ gtk/gtkcolumnview.h | 6 +++ gtk/gtkgridview.c | 59 +++++++++++++++++++++++++++ gtk/gtkgridview.h | 6 +++ 5 files changed, 135 insertions(+) diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 8123359e5e..660ea5b068 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -504,6 +504,8 @@ gtk_column_view_get_sorter gtk_column_view_get_show_separators gtk_column_view_set_show_separators gtk_column_view_sort_by_column +gtk_column_view_set_single_click_activate +gtk_column_view_get_single_click_activate GTK_COLUMN_VIEW GTK_COLUMN_VIEW_CLASS @@ -550,6 +552,8 @@ gtk_grid_view_set_max_columns gtk_grid_view_get_max_columns gtk_grid_view_set_min_columns gtk_grid_view_get_min_columns +gtk_grid_view_set_single_click_activate +gtk_grid_view_get_single_click_activate GTK_GRID_VIEW GTK_GRID_VIEW_CLASS diff --git a/gtk/gtkcolumnview.c b/gtk/gtkcolumnview.c index 32a2bea66c..7ae41f8b1c 100644 --- a/gtk/gtkcolumnview.c +++ b/gtk/gtkcolumnview.c @@ -81,6 +81,7 @@ enum PROP_SORTER, PROP_VADJUSTMENT, PROP_VSCROLL_POLICY, + PROP_SINGLE_CLICK_ACTIVATE, N_PROPS }; @@ -317,6 +318,10 @@ gtk_column_view_get_property (GObject *object, g_value_set_object (value, self->sorter); break; + case PROP_SINGLE_CLICK_ACTIVATE: + g_value_set_boolean (value, gtk_column_view_get_single_click_activate (self)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -373,6 +378,10 @@ gtk_column_view_set_property (GObject *object, } break; + case PROP_SINGLE_CLICK_ACTIVATE: + gtk_column_view_set_single_click_activate (self, g_value_get_boolean (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -457,6 +466,18 @@ gtk_column_view_class_init (GtkColumnViewClass *klass) GTK_TYPE_SORTER, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** + * GtkColumnView:single-click-activate: + * + * Activate rows on single click and select them on hover + */ + properties[PROP_SINGLE_CLICK_ACTIVATE] = + g_param_spec_boolean ("single-click-activate", + P_("Single click activate"), + P_("Activate rows on single click"), + FALSE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_properties (gobject_class, N_PROPS, properties); /** @@ -769,3 +790,42 @@ gtk_column_view_sort_by_column (GtkColumnView *self, column, direction == GTK_SORT_DESCENDING); } + +/** + * gtk_column_view_set_single_click_activate: + * @self: a #GtkColumnView + * @single_click_activate: %TRUE to activate items on single click + * + * Sets whether rows should be activated on single click and + * selected on hover. + */ +void +gtk_column_view_set_single_click_activate (GtkColumnView *self, + gboolean single_click_activate) +{ + g_return_if_fail (GTK_IS_COLUMN_VIEW (self)); + + if (single_click_activate == gtk_list_view_get_single_click_activate (self->listview)) + return; + + gtk_list_view_set_single_click_activate (self->listview, single_click_activate); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SINGLE_CLICK_ACTIVATE]); +} + +/** + * gtk_column_view_get_single_click_activate: + * @self: a #GtkColumnView + * + * Returns whether rows will be activated on single click and + * selected on hover. + * + * Returns: %TRUE if rows are activated on single click + */ +gboolean +gtk_column_view_get_single_click_activate (GtkColumnView *self) +{ + g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), FALSE); + + return gtk_list_view_get_single_click_activate (self->listview); +} diff --git a/gtk/gtkcolumnview.h b/gtk/gtkcolumnview.h index b24dc7d308..8547d41698 100644 --- a/gtk/gtkcolumnview.h +++ b/gtk/gtkcolumnview.h @@ -81,6 +81,12 @@ void gtk_column_view_sort_by_column (GtkColumnView GtkColumnViewColumn *column, GtkSortType direction); +GDK_AVAILABLE_IN_ALL +void gtk_column_view_set_single_click_activate (GtkColumnView *self, + gboolean single_click_activate); +GDK_AVAILABLE_IN_ALL +gboolean gtk_column_view_get_single_click_activate (GtkColumnView *self); + G_END_DECLS #endif /* __GTK_COLUMN_VIEW_H__ */ diff --git a/gtk/gtkgridview.c b/gtk/gtkgridview.c index 4184552d5e..debfcf0946 100644 --- a/gtk/gtkgridview.c +++ b/gtk/gtkgridview.c @@ -90,6 +90,7 @@ enum PROP_MAX_COLUMNS, PROP_MIN_COLUMNS, PROP_MODEL, + PROP_SINGLE_CLICK_ACTIVATE, N_PROPS }; @@ -909,6 +910,10 @@ gtk_grid_view_get_property (GObject *object, g_value_set_object (value, gtk_list_base_get_model (GTK_LIST_BASE (self))); break; + case PROP_SINGLE_CLICK_ACTIVATE: + g_value_set_boolean (value, gtk_list_item_manager_get_single_click_activate (self->item_manager)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -941,6 +946,10 @@ gtk_grid_view_set_property (GObject *object, gtk_grid_view_set_model (self, g_value_get_object (value)); break; + case PROP_SINGLE_CLICK_ACTIVATE: + gtk_grid_view_set_single_click_activate (self, g_value_get_boolean (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -1041,6 +1050,18 @@ gtk_grid_view_class_init (GtkGridViewClass *klass) G_TYPE_LIST_MODEL, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** + * GtkGridView:single-click-activate: + * + * Activate rows on single click and select them on hover + */ + properties[PROP_SINGLE_CLICK_ACTIVATE] = + g_param_spec_boolean ("single-click-activate", + P_("Single click activate"), + P_("Activate rows on single click"), + FALSE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_properties (gobject_class, N_PROPS, properties); /** @@ -1311,3 +1332,41 @@ gtk_grid_view_set_min_columns (GtkGridView *self, g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MIN_COLUMNS]); } +/** + * gtk_grid_view_set_single_click_activate: + * @self: a #GtkGridView + * @single_click_activate: %TRUE to activate items on single click + * + * Sets whether items should be activated on single click and + * selected on hover. + */ +void +gtk_grid_view_set_single_click_activate (GtkGridView *self, + gboolean single_click_activate) +{ + g_return_if_fail (GTK_IS_GRID_VIEW (self)); + + if (single_click_activate == gtk_list_item_manager_get_single_click_activate (self->item_manager)) + return; + + gtk_list_item_manager_set_single_click_activate (self->item_manager, single_click_activate); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SINGLE_CLICK_ACTIVATE]); +} + +/** + * gtk_grid_view_get_single_click_activate: + * @self: a #GtkListView + * + * Returns whether items will be activated on single click and + * selected on hover. + * + * Returns: %TRUE if items are activated on single click + */ +gboolean +gtk_grid_view_get_single_click_activate (GtkGridView *self) +{ + g_return_val_if_fail (GTK_IS_GRID_VIEW (self), FALSE); + + return gtk_list_item_manager_get_single_click_activate (self->item_manager); +} diff --git a/gtk/gtkgridview.h b/gtk/gtkgridview.h index cba7769401..7b78fe15e8 100644 --- a/gtk/gtkgridview.h +++ b/gtk/gtkgridview.h @@ -74,6 +74,12 @@ GDK_AVAILABLE_IN_ALL void gtk_grid_view_set_max_columns (GtkGridView *self, guint max_columns); +GDK_AVAILABLE_IN_ALL +void gtk_grid_view_set_single_click_activate (GtkGridView *self, + gboolean single_click_activate); +GDK_AVAILABLE_IN_ALL +gboolean gtk_grid_view_get_single_click_activate (GtkGridView *self); + G_END_DECLS From b087948933e601568d67c4f0bfcb8ba5d8d958b5 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 30 May 2020 14:50:22 -0400 Subject: [PATCH 168/170] gtk-demo: Use single-click-activate for minesweeper The explanation say we do, so do it. --- demos/gtk-demo/listview_minesweeper.ui | 1 + 1 file changed, 1 insertion(+) diff --git a/demos/gtk-demo/listview_minesweeper.ui b/demos/gtk-demo/listview_minesweeper.ui index b67fc4148f..03ed899493 100644 --- a/demos/gtk-demo/listview_minesweeper.ui +++ b/demos/gtk-demo/listview_minesweeper.ui @@ -30,6 +30,7 @@ game
+ 1 game From c7feae65f17119f11236a0cd09bbd1505d87a123 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 23 Dec 2019 18:23:59 -0500 Subject: [PATCH 169/170] builderlistitemfactory: Precompile the xml This is the one place where we can really take advantage of precompiling, since we instantiate this template over and over. --- gtk/gtkbuilderlistitemfactory.c | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/gtk/gtkbuilderlistitemfactory.c b/gtk/gtkbuilderlistitemfactory.c index d3e5079e59..da07cbe477 100644 --- a/gtk/gtkbuilderlistitemfactory.c +++ b/gtk/gtkbuilderlistitemfactory.c @@ -22,6 +22,7 @@ #include "gtkbuilderlistitemfactory.h" #include "gtkbuilder.h" +#include "gtkbuilderprivate.h" #include "gtkintl.h" #include "gtklistitemfactoryprivate.h" #include "gtklistitemprivate.h" @@ -61,6 +62,7 @@ struct _GtkBuilderListItemFactory GtkBuilderScope *scope; GBytes *bytes; + GBytes *data; char *resource; }; @@ -99,10 +101,10 @@ gtk_builder_list_item_factory_setup (GtkListItemFactory *factory, if (self->scope) gtk_builder_set_scope (builder, self->scope); - if (!gtk_builder_extend_with_template (builder, G_OBJECT (list_item), G_OBJECT_TYPE (list_item), - (const gchar *)g_bytes_get_data (self->bytes, NULL), - g_bytes_get_size (self->bytes), - &error)) + if (!gtk_builder_extend_with_template (builder, G_OBJECT (list_item), G_OBJECT_TYPE (list_item), + (const char *)g_bytes_get_data (self->data, NULL), + g_bytes_get_size (self->data), + &error)) { g_critical ("Error building template for list item: %s", error->message); g_error_free (error); @@ -159,6 +161,27 @@ gtk_builder_list_item_factory_set_bytes (GtkBuilderListItemFactory *self, } self->bytes = g_bytes_ref (bytes); + + if (!_gtk_buildable_parser_is_precompiled (g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes))) + { + GError *error = NULL; + GBytes *data; + + data = _gtk_buildable_parser_precompile (g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes), + &error); + if (data == NULL) + { + g_warning ("Failed to precompile template for GtkBuilderListItemFactory: %s", error->message); + g_error_free (error); + self->data = g_bytes_ref (bytes); + } + else + { + self->data = data; + } + } + return TRUE; } @@ -218,6 +241,7 @@ gtk_builder_list_item_factory_finalize (GObject *object) g_clear_object (&self->scope); g_bytes_unref (self->bytes); + g_bytes_unref (self->data); g_free (self->resource); G_OBJECT_CLASS (gtk_builder_list_item_factory_parent_class)->finalize (object); From 3ea2258ce9e50247a89e93a76e3b4b1aeb4a54df Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 25 May 2020 00:07:07 +0200 Subject: [PATCH 170/170] xxx: isnanf() is some wtf --- gtk/fallback-c89.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/gtk/fallback-c89.c b/gtk/fallback-c89.c index a566e3423a..505d52e13a 100644 --- a/gtk/fallback-c89.c +++ b/gtk/fallback-c89.c @@ -93,15 +93,27 @@ isnan (double x) #endif #ifndef HAVE_DECL_ISNANF +#if 1 +#define isnanf(x) isnan(x) +#else /* it seems of the supported compilers only * MSVC does not have isnanf(), but it does * have _isnanf() which does the same as isnanf() */ +#ifdef _MSC_VER static inline gboolean isnanf (float x) { return _isnanf (x); } +#elif defined (__GNUC__) +/* gcc has an intern function that it warns about when + * using -Wshadow but no header properly declares it, + * so we do it instead. + */ +extern int isnanf (float x); +#endif +#endif #endif #ifndef INFINITY