diff options
author | Benjamin Otte <otte.benjamin@googlemail.com> | 2023-04-01 19:33:00 +0000 |
---|---|---|
committer | Benjamin Otte <otte.benjamin@googlemail.com> | 2023-04-01 19:33:00 +0000 |
commit | 378df6556b36a9f74ac3095e0445a0c726cd3e0a (patch) | |
tree | ae8205922f97e1400edc946ba70f11b0cd8c04b0 | |
parent | 60921db76f05177a8d3795314bd50a3fc2248c77 (diff) | |
parent | 40869137959e7058bb86302a0931f28db4f7df12 (diff) | |
download | gtk+-378df6556b36a9f74ac3095e0445a0c726cd3e0a.tar.gz |
Merge branch 'wip/otte/listitem-focus' into 'main'
GtkColumnView factories and focus rework
See merge request GNOME/gtk!5728
35 files changed, 2221 insertions, 544 deletions
diff --git a/demos/gtk-demo/listview_settings.c b/demos/gtk-demo/listview_settings.c index fc484f3f45..4e8e69bfbf 100644 --- a/demos/gtk-demo/listview_settings.c +++ b/demos/gtk-demo/listview_settings.c @@ -148,9 +148,9 @@ settings_key_new (GSettings *settings, } static void -item_value_changed (GtkEditableLabel *label, - GParamSpec *pspec, - GtkListItem *item) +item_value_changed (GtkEditableLabel *label, + GParamSpec *pspec, + GtkColumnViewCell *cell) { SettingsKey *self; const char *text; @@ -162,8 +162,7 @@ item_value_changed (GtkEditableLabel *label, text = gtk_editable_get_text (GTK_EDITABLE (label)); - g_object_get (item, "item", &self, NULL); - g_object_unref (self); + self = gtk_column_view_cell_get_item (cell); type = g_settings_schema_key_get_value_type (self->key); name = g_settings_schema_key_get_name (self->key); diff --git a/demos/gtk-demo/listview_settings.ui b/demos/gtk-demo/listview_settings.ui index 15cbb67d9e..49080e5df2 100644 --- a/demos/gtk-demo/listview_settings.ui +++ b/demos/gtk-demo/listview_settings.ui @@ -90,13 +90,13 @@ <property name="bytes"><![CDATA[ <?xml version="1.0" encoding="UTF-8"?> <interface> - <template class="GtkListItem"> + <template class="GtkColumnViewCell"> <property name="child"> <object class="GtkLabel"> <property name="xalign">0</property> <binding name="label"> <lookup name="name" type="SettingsKey"> - <lookup name="item">GtkListItem</lookup> + <lookup name="item">GtkColumnViewCell</lookup> </lookup> </binding> </object> @@ -118,12 +118,12 @@ <property name="bytes"><![CDATA[ <?xml version="1.0" encoding="UTF-8"?> <interface> - <template class="GtkListItem"> + <template class="GtkColumnViewCell"> <property name="child"> <object class="GtkEditableLabel"> <binding name="text"> <lookup name="value" type="SettingsKey"> - <lookup name="item">GtkListItem</lookup> + <lookup name="item">GtkColumnViewCell</lookup> </lookup> </binding> <signal name="notify::label" handler="item_value_changed"/> @@ -146,13 +146,13 @@ <property name="bytes"><![CDATA[ <?xml version="1.0" encoding="UTF-8"?> <interface> - <template class="GtkListItem"> + <template class="GtkColumnViewCell"> <property name="child"> <object class="GtkLabel"> <property name="xalign">0</property> <binding name="label"> <lookup name="type" type="SettingsKey"> - <lookup name="item">GtkListItem</lookup> + <lookup name="item">GtkColumnViewCell</lookup> </lookup> </binding> </object> @@ -175,13 +175,13 @@ <property name="bytes"><![CDATA[ <?xml version="1.0" encoding="UTF-8"?> <interface> - <template class="GtkListItem"> + <template class="GtkColumnViewCell"> <property name="child"> <object class="GtkLabel"> <property name="xalign">0</property> <binding name="label"> <lookup name="default-value" type="SettingsKey"> - <lookup name="item">GtkListItem</lookup> + <lookup name="item">GtkColumnViewCell</lookup> </lookup> </binding> </object> @@ -205,14 +205,14 @@ <property name="bytes"><![CDATA[ <?xml version="1.0" encoding="UTF-8"?> <interface> - <template class="GtkListItem"> + <template class="GtkColumnViewCell"> <property name="child"> <object class="GtkLabel"> <property name="xalign">0</property> <property name="wrap">1</property> <binding name="label"> <lookup name="summary" type="SettingsKey"> - <lookup name="item">GtkListItem</lookup> + <lookup name="item">GtkColumnViewCell</lookup> </lookup> </binding> </object> @@ -236,14 +236,14 @@ <property name="bytes"><![CDATA[ <?xml version="1.0" encoding="UTF-8"?> <interface> - <template class="GtkListItem"> + <template class="GtkColumnViewCell"> <property name="child"> <object class="GtkLabel"> <property name="xalign">0</property> <property name="wrap">1</property> <binding name="label"> <lookup name="description" type="SettingsKey"> - <lookup name="item">GtkListItem</lookup> + <lookup name="item">GtkColumnViewCell</lookup> </lookup> </binding> </object> diff --git a/demos/gtk-demo/main-listitem.ui b/demos/gtk-demo/main-listitem.ui index aab22e3e20..b3427611ed 100644 --- a/demos/gtk-demo/main-listitem.ui +++ b/demos/gtk-demo/main-listitem.ui @@ -1,6 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <interface> <template class="GtkListItem"> + <property name="focusable">0</property> <property name="child"> <object class="GtkTreeExpander" id="expander"> <binding name="list-row"> @@ -86,7 +86,9 @@ #include <gtk/gtkcolordialogbutton.h> #include <gtk/gtkcolorutils.h> #include <gtk/gtkcolumnview.h> +#include <gtk/gtkcolumnviewcell.h> #include <gtk/gtkcolumnviewcolumn.h> +#include <gtk/gtkcolumnviewrow.h> #include <gtk/gtkcolumnviewsorter.h> #include <gtk/deprecated/gtkcombobox.h> #include <gtk/deprecated/gtkcomboboxtext.h> diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c index fd221f79c8..8171cb8bf9 100644 --- a/gtk/gtkbuilder.c +++ b/gtk/gtkbuilder.c @@ -264,6 +264,7 @@ typedef struct char *filename; char *resource_prefix; GType template_type; + gboolean allow_template_parents; GObject *current_object; GtkBuilderScope *scope; } GtkBuilderPrivate; @@ -1334,6 +1335,15 @@ gtk_builder_add_objects_from_file (GtkBuilder *builder, return TRUE; } +void +gtk_builder_set_allow_template_parents (GtkBuilder *builder, + gboolean allow_parents) +{ + GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder); + + priv->allow_template_parents = allow_parents; +} + /** * gtk_builder_extend_with_template: * @builder: a `GtkBuilder` @@ -1385,6 +1395,18 @@ gtk_builder_extend_with_template (GtkBuilder *builder, name = g_type_name (template_type); if (gtk_builder_get_object (builder, name) != object) gtk_builder_expose_object (builder, name, object); + if (priv->allow_template_parents) + { + GType subtype; + for (subtype = g_type_parent (template_type); + subtype != G_TYPE_OBJECT; + subtype = g_type_parent (subtype)) + { + name = g_type_name (subtype); + if (gtk_builder_get_object (builder, name) != object) + gtk_builder_expose_object (builder, name, object); + } + } filename = g_strconcat ("<", name, " template>", NULL); _gtk_builder_parser_parse_buffer (builder, filename, @@ -2815,10 +2837,13 @@ _gtk_builder_get_absolute_filename (GtkBuilder *builder, } GType -_gtk_builder_get_template_type (GtkBuilder *builder) +gtk_builder_get_template_type (GtkBuilder *builder, + gboolean *out_allow_parents) { GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder); + *out_allow_parents = priv->allow_template_parents; + return priv->template_type; } diff --git a/gtk/gtkbuilderlistitemfactory.c b/gtk/gtkbuilderlistitemfactory.c index cf3f59abaf..de4cf42172 100644 --- a/gtk/gtkbuilderlistitemfactory.c +++ b/gtk/gtkbuilderlistitemfactory.c @@ -101,6 +101,7 @@ gtk_builder_list_item_factory_setup (GtkListItemFactory *factory, if (self->scope) gtk_builder_set_scope (builder, self->scope); + gtk_builder_set_allow_template_parents (builder, TRUE); if (!gtk_builder_extend_with_template (builder, G_OBJECT (item), G_OBJECT_TYPE (item), (const char *)g_bytes_get_data (self->data, NULL), g_bytes_get_size (self->data), diff --git a/gtk/gtkbuilderparser.c b/gtk/gtkbuilderparser.c index a1a7bd68c0..d4f1f66f5e 100644 --- a/gtk/gtkbuilderparser.c +++ b/gtk/gtkbuilderparser.c @@ -697,11 +697,11 @@ parse_template (GtkBuildableParseContext *context, const char *parent_class = NULL; int line; gpointer line_ptr; - gboolean has_duplicate; + gboolean has_duplicate, allow_parents; GType template_type; GType parsed_type; - template_type = _gtk_builder_get_template_type (data->builder); + template_type = gtk_builder_get_template_type (data->builder, &allow_parents); if (!g_markup_collect_attributes (element_name, names, values, error, G_MARKUP_COLLECT_STRING, "class", &object_class, @@ -729,7 +729,8 @@ parse_template (GtkBuildableParseContext *context, } parsed_type = g_type_from_name (object_class); - if (template_type != parsed_type) + if (template_type != parsed_type && + (!allow_parents || !g_type_is_a (template_type, parsed_type))) { g_set_error (error, GTK_BUILDER_ERROR, @@ -768,10 +769,11 @@ parse_template (GtkBuildableParseContext *context, object_info = g_new0 (ObjectInfo, 1); object_info->tag_type = TAG_TEMPLATE; - object_info->type = parsed_type; - object_info->oclass = g_type_class_ref (parsed_type); - object_info->id = g_strdup (object_class); object_info->object = gtk_builder_get_object (data->builder, object_class); + object_info->type = template_type; + object_info->oclass = g_type_class_ref (template_type); + object_info->id = g_strdup (object_class); + g_assert (object_info->object); state_push (data, object_info); has_duplicate = g_hash_table_lookup_extended (data->object_ids, object_class, NULL, &line_ptr); diff --git a/gtk/gtkbuilderprivate.h b/gtk/gtkbuilderprivate.h index 7e717eaf2a..218e33628a 100644 --- a/gtk/gtkbuilderprivate.h +++ b/gtk/gtkbuilderprivate.h @@ -264,7 +264,10 @@ void _gtk_builder_menu_start (ParserData *parser_data, GError **error); void _gtk_builder_menu_end (ParserData *parser_data); -GType _gtk_builder_get_template_type (GtkBuilder *builder); +GType gtk_builder_get_template_type (GtkBuilder *builder, + gboolean *out_allow_parents); +void gtk_builder_set_allow_template_parents (GtkBuilder *builder, + gboolean allow_parents); void _gtk_builder_prefix_error (GtkBuilder *builder, GtkBuildableParseContext *context, diff --git a/gtk/gtkcolumnview.c b/gtk/gtkcolumnview.c index b79ae627e9..5437d684c4 100644 --- a/gtk/gtkcolumnview.c +++ b/gtk/gtkcolumnview.c @@ -113,6 +113,45 @@ * some parameters for item creation. */ +struct _GtkColumnView +{ + GtkWidget parent_instance; + + GListStore *columns; + + GtkColumnViewColumn *focus_column; + + GtkWidget *header; + + GtkListView *listview; + + GtkSorter *sorter; + + GtkAdjustment *hadjustment; + + guint reorderable : 1; + guint show_column_separators : 1; + guint in_column_resize : 1; + guint in_column_reorder : 1; + + int drag_pos; + int drag_x; + int drag_offset; + int drag_column_x; + + guint autoscroll_id; + double autoscroll_x; + double autoscroll_delta; + + GtkGesture *drag_gesture; +}; + +struct _GtkColumnViewClass +{ + GtkWidgetClass parent_class; +}; + + #define GTK_TYPE_COLUMN_LIST_VIEW (gtk_column_list_view_get_type ()) G_DECLARE_FINAL_TYPE (GtkColumnListView, gtk_column_list_view, GTK, COLUMN_LIST_VIEW, GtkListView) @@ -136,12 +175,28 @@ gtk_column_list_view_init (GtkColumnListView *view) static GtkListItemBase * gtk_column_list_view_create_list_widget (GtkListBase *base) { - GtkListView *self = GTK_LIST_VIEW (base); + GtkColumnView *self = GTK_COLUMN_VIEW (gtk_widget_get_parent (GTK_WIDGET (base))); GtkWidget *result; + guint i; - result = gtk_column_view_row_widget_new (FALSE); + result = gtk_column_view_row_widget_new (gtk_list_view_get_factory (self->listview), FALSE); - gtk_list_factory_widget_set_single_click_activate (GTK_LIST_FACTORY_WIDGET (result), self->single_click_activate); + gtk_list_factory_widget_set_single_click_activate (GTK_LIST_FACTORY_WIDGET (result), GTK_LIST_VIEW (base)->single_click_activate); + + for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (self->columns)); i++) + { + GtkColumnViewColumn *column = g_list_model_get_item (G_LIST_MODEL (self->columns), i); + + if (gtk_column_view_column_get_visible (column)) + { + GtkWidget *cell; + + cell = gtk_column_view_cell_widget_new (column, gtk_column_view_is_inert (self)); + gtk_column_view_row_widget_add_child (GTK_COLUMN_VIEW_ROW_WIDGET (result), cell); + } + + g_object_unref (column); + } return GTK_LIST_ITEM_BASE (result); } @@ -159,44 +214,6 @@ gtk_column_list_view_class_init (GtkColumnListViewClass *klass) } -struct _GtkColumnView -{ - GtkWidget parent_instance; - - GListStore *columns; - - GtkColumnViewColumn *focus_column; - - GtkWidget *header; - - GtkListView *listview; - - GtkSorter *sorter; - - GtkAdjustment *hadjustment; - - guint reorderable : 1; - guint show_column_separators : 1; - guint in_column_resize : 1; - guint in_column_reorder : 1; - - int drag_pos; - int drag_x; - int drag_offset; - int drag_column_x; - - guint autoscroll_id; - double autoscroll_x; - double autoscroll_delta; - - GtkGesture *drag_gesture; -}; - -struct _GtkColumnViewClass -{ - GtkWidgetClass parent_class; -}; - enum { PROP_0, @@ -206,6 +223,7 @@ enum PROP_HSCROLL_POLICY, PROP_MODEL, PROP_REORDERABLE, + PROP_ROW_FACTORY, PROP_SHOW_ROW_SEPARATORS, PROP_SHOW_COLUMN_SEPARATORS, PROP_SINGLE_CLICK_ACTIVATE, @@ -280,6 +298,31 @@ G_DEFINE_TYPE_WITH_CODE (GtkColumnView, gtk_column_view, GTK_TYPE_WIDGET, static GParamSpec *properties[N_PROPS] = { NULL, }; static guint signals[LAST_SIGNAL] = { 0 }; +gboolean +gtk_column_view_is_inert (GtkColumnView *self) +{ + GtkWidget *widget = GTK_WIDGET (self); + + return !gtk_widget_get_visible (widget) || + gtk_widget_get_root (widget) == NULL; +} + +static void +gtk_column_view_update_cell_factories (GtkColumnView *self, + gboolean inert) +{ + guint i, n; + + n = g_list_model_get_n_items (G_LIST_MODEL (self->columns)); + + for (i = 0; i < n; i++) + { + GtkColumnViewColumn *column = g_list_model_get_item (G_LIST_MODEL (self->columns), i); + + gtk_column_view_column_update_factory (column, inert); + } +} + static void gtk_column_view_measure (GtkWidget *widget, GtkOrientation orientation, @@ -459,6 +502,50 @@ gtk_column_view_allocate (GtkWidget *widget, } static void +gtk_column_view_root (GtkWidget *widget) +{ + GtkColumnView *self = GTK_COLUMN_VIEW (widget); + + GTK_WIDGET_CLASS (gtk_column_view_parent_class)->root (widget); + + if (!gtk_column_view_is_inert (self)) + gtk_column_view_update_cell_factories (self, FALSE); +} + +static void +gtk_column_view_unroot (GtkWidget *widget) +{ + GtkColumnView *self = GTK_COLUMN_VIEW (widget); + + if (!gtk_column_view_is_inert (self)) + gtk_column_view_update_cell_factories (self, TRUE); + + GTK_WIDGET_CLASS (gtk_column_view_parent_class)->unroot (widget); +} + +static void +gtk_column_view_show (GtkWidget *widget) +{ + GtkColumnView *self = GTK_COLUMN_VIEW (widget); + + GTK_WIDGET_CLASS (gtk_column_view_parent_class)->show (widget); + + if (!gtk_column_view_is_inert (self)) + gtk_column_view_update_cell_factories (self, FALSE); +} + +static void +gtk_column_view_hide (GtkWidget *widget) +{ + GtkColumnView *self = GTK_COLUMN_VIEW (widget); + + if (!gtk_column_view_is_inert (self)) + gtk_column_view_update_cell_factories (self, TRUE); + + GTK_WIDGET_CLASS (gtk_column_view_parent_class)->hide (widget); +} + +static void gtk_column_view_activate_cb (GtkListView *listview, guint pos, GtkColumnView *self) @@ -549,6 +636,14 @@ gtk_column_view_get_property (GObject *object, g_value_set_object (value, gtk_list_view_get_model (self->listview)); break; + case PROP_REORDERABLE: + g_value_set_boolean (value, gtk_column_view_get_reorderable (self)); + break; + + case PROP_ROW_FACTORY: + g_value_set_object (value, gtk_column_view_get_row_factory (self)); + break; + case PROP_SHOW_ROW_SEPARATORS: g_value_set_boolean (value, gtk_list_view_get_show_separators (self->listview)); break; @@ -573,10 +668,6 @@ gtk_column_view_get_property (GObject *object, g_value_set_boolean (value, gtk_column_view_get_single_click_activate (self)); break; - case PROP_REORDERABLE: - g_value_set_boolean (value, gtk_column_view_get_reorderable (self)); - break; - case PROP_TAB_BEHAVIOR: g_value_set_enum (value, gtk_list_view_get_tab_behavior (self->listview)); break; @@ -632,6 +723,14 @@ gtk_column_view_set_property (GObject *object, gtk_column_view_set_model (self, g_value_get_object (value)); break; + case PROP_REORDERABLE: + gtk_column_view_set_reorderable (self, g_value_get_boolean (value)); + break; + + case PROP_ROW_FACTORY: + gtk_column_view_set_row_factory (self, g_value_get_object (value)); + break; + case PROP_SHOW_ROW_SEPARATORS: gtk_column_view_set_show_row_separators (self, g_value_get_boolean (value)); break; @@ -660,10 +759,6 @@ gtk_column_view_set_property (GObject *object, gtk_column_view_set_single_click_activate (self, g_value_get_boolean (value)); break; - case PROP_REORDERABLE: - gtk_column_view_set_reorderable (self, g_value_get_boolean (value)); - break; - case PROP_TAB_BEHAVIOR: gtk_column_view_set_tab_behavior (self, g_value_get_enum (value)); break; @@ -685,6 +780,10 @@ gtk_column_view_class_init (GtkColumnViewClass *klass) widget_class->grab_focus = gtk_widget_grab_focus_child; widget_class->measure = gtk_column_view_measure; widget_class->size_allocate = gtk_column_view_allocate; + widget_class->root = gtk_column_view_root; + widget_class->unroot = gtk_column_view_unroot; + widget_class->show = gtk_column_view_show; + widget_class->hide = gtk_column_view_hide; gobject_class->dispose = gtk_column_view_dispose; gobject_class->finalize = gtk_column_view_finalize; @@ -737,6 +836,28 @@ gtk_column_view_class_init (GtkColumnViewClass *klass) G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** + * GtkColumnView:reorderable: (attributes org.gtk.Property.get=gtk_column_view_get_reorderable org.gtk.Property.set=gtk_column_view_set_reorderable) + * + * Whether columns are reorderable. + */ + properties[PROP_REORDERABLE] = + g_param_spec_boolean ("reorderable", NULL, NULL, + TRUE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * GtkColumnView:row-factory: (attributes org.gtk.Property.get=gtk_column_view_get_row_factory org.gtk.Property.set=gtk_column_view_set_row_factory) + * + * The factory used for configuring rows. + * + * Since: 4.12 + */ + properties[PROP_ROW_FACTORY] = + g_param_spec_object ("row-factory", NULL, NULL, + GTK_TYPE_LIST_ITEM_FACTORY, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** * GtkColumnView:show-row-separators: (attributes org.gtk.Property.get=gtk_column_view_get_show_row_separators org.gtk.Property.set=gtk_column_view_set_show_row_separators) * * Show separators between rows. @@ -777,16 +898,6 @@ gtk_column_view_class_init (GtkColumnViewClass *klass) G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** - * GtkColumnView:reorderable: (attributes org.gtk.Property.get=gtk_column_view_get_reorderable org.gtk.Property.set=gtk_column_view_set_reorderable) - * - * Whether columns are reorderable. - */ - properties[PROP_REORDERABLE] = - g_param_spec_boolean ("reorderable", NULL, NULL, - TRUE, - G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); - - /** * GtkColumnView:tab-behavior: (attributes org.gtk.Property.get=gtk_column_view_get_tab_behavior org.gtk.Property.set=gtk_column_view_set_tab_behavior) * * Behavior of the <kbd>Tab</kbd> key @@ -1312,7 +1423,7 @@ gtk_column_view_init (GtkColumnView *self) self->columns = g_list_store_new (GTK_TYPE_COLUMN_VIEW_COLUMN); - self->header = gtk_column_view_row_widget_new (TRUE); + self->header = gtk_column_view_row_widget_new (NULL, TRUE); gtk_widget_set_can_focus (self->header, FALSE); gtk_widget_set_parent (self->header, GTK_WIDGET (self)); @@ -1908,6 +2019,53 @@ gtk_column_view_get_enable_rubberband (GtkColumnView *self) } /** + * gtk_column_view_set_row_factory: (attributes org.gtk.Method.set_property=row-factory) + * @self: a `GtkColumnView` + * @factory: (nullable): The row factory + * + * Sets the factory used for configuring rows. The factory must be for configuring + * [class@Gtk.ColumnViewRow] objects. + * + * If this factory is not set - which is the default - then the defaults will be used. + * + * This factory is not used to set the widgets displayed in the individual cells. For + * that see [method@GtkColumnViewColumn.set_factory] and [class@GtkColumnViewCell]. + * + * Since: 4.12 + */ +void +gtk_column_view_set_row_factory (GtkColumnView *self, + GtkListItemFactory *factory) +{ + g_return_if_fail (GTK_IS_COLUMN_VIEW (self)); + + if (factory == gtk_list_view_get_factory (self->listview)) + return; + + gtk_list_view_set_factory (self->listview, factory); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ROW_FACTORY]); +} + +/** + * gtk_column_view_get_row_factory: (attributes org.gtk.Method.get_property=row-factory) + * @self: a `GtkColumnView` + * + * Gets the factory set via [method@Gtk.ColumnView.set_row_factory]. + * + * Returns: (nullable) (transfer none): The factory + * + * Since: 4.12 + */ +GtkListItemFactory * +gtk_column_view_get_row_factory (GtkColumnView *self) +{ + g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), FALSE); + + return gtk_list_view_get_factory (self->listview); +} + +/** * gtk_column_view_set_tab_behavior: (attributes org.gtk.Method.set_property=tab-behavior) * @self: a `GtkColumnView` * @tab_behavior: The desired tab behavior diff --git a/gtk/gtkcolumnview.h b/gtk/gtkcolumnview.h index aa04cd9363..afccfcfd9d 100644 --- a/gtk/gtkcolumnview.h +++ b/gtk/gtkcolumnview.h @@ -114,5 +114,12 @@ void gtk_column_view_set_tab_behavior (GtkColumnView GDK_AVAILABLE_IN_4_12 gboolean gtk_column_view_get_tab_behavior (GtkColumnView *self); +GDK_AVAILABLE_IN_4_12 +void gtk_column_view_set_row_factory (GtkColumnView *self, + GtkListItemFactory *factory); +GDK_AVAILABLE_IN_4_12 +GtkListItemFactory * + gtk_column_view_get_row_factory (GtkColumnView *self); + G_END_DECLS diff --git a/gtk/gtkcolumnviewcell.c b/gtk/gtkcolumnviewcell.c index 97f153a09b..c32bc0fd09 100644 --- a/gtk/gtkcolumnviewcell.c +++ b/gtk/gtkcolumnviewcell.c @@ -1,5 +1,5 @@ /* - * Copyright © 2019 Benjamin Otte + * Copyright © 2023 Benjamin Otte * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -21,235 +21,404 @@ #include "gtkcolumnviewcellprivate.h" -#include "gtkcolumnviewcolumnprivate.h" -#include "gtkcolumnviewrowwidgetprivate.h" -#include "gtkcssnodeprivate.h" -#include "gtkcssnumbervalueprivate.h" -#include "gtklistitemwidgetprivate.h" -#include "gtkprivate.h" -#include "gtkwidgetprivate.h" +#include "gtklistitembaseprivate.h" - -struct _GtkColumnViewCell -{ - GtkListItemWidget parent_instance; - - GtkColumnViewColumn *column; - - /* This list isn't sorted - next/prev refer to list elements, not rows in the list */ - GtkColumnViewCell *next_cell; - GtkColumnViewCell *prev_cell; -}; +/** + * GtkColumnViewCell: + * + * `GtkColumnViewCell` is used by [class@Gtk.ColumnViewColumn] to represent items + * in a cell in [class@Gtk.ColumnView]. + * + * The `GtkColumnViewCell`s are managed by the columnview widget (with its factory) + * and cannot be created by applications, but they need to be populated + * by application code. This is done by calling [method@Gtk.ColumnViewCell.set_child]. + * + * `GtkColumnViewCell`s 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 [property@Gtk.ColumnViewCell:item] + * property is set to %NULL. + * + * 2. The bound stage where the listitem references an item from the list. + * The [property@Gtk.ColumnViewCell:item] property is not %NULL. + * + * Since: 4.12 + */ struct _GtkColumnViewCellClass { - GtkListItemWidgetClass parent_class; + GtkListItemClass parent_class; }; -G_DEFINE_TYPE (GtkColumnViewCell, gtk_column_view_cell, GTK_TYPE_LIST_ITEM_WIDGET) - -static int -get_number (GtkCssValue *value) +enum { - double d = _gtk_css_number_value_get (value, 100); - - if (d < 1) - return ceil (d); - else - return floor (d); -} + PROP_0, + PROP_CHILD, + PROP_FOCUSABLE, + PROP_ITEM, + PROP_POSITION, + PROP_SELECTED, + + N_PROPS +}; -static int -unadjust_width (GtkWidget *widget, - int width) -{ - GtkCssStyle *style; - int widget_margins; - int css_extra; +G_DEFINE_TYPE (GtkColumnViewCell, gtk_column_view_cell, GTK_TYPE_LIST_ITEM) - style = gtk_css_node_get_style (gtk_widget_get_css_node (widget)); - css_extra = get_number (style->size->margin_left) + - get_number (style->size->margin_right) + - get_number (style->border->border_left_width) + - get_number (style->border->border_right_width) + - get_number (style->size->padding_left) + - get_number (style->size->padding_right); - widget_margins = widget->priv->margin.left + widget->priv->margin.right; - - return MAX (0, width - widget_margins - css_extra); -} +static GParamSpec *properties[N_PROPS] = { NULL, }; static void -gtk_column_view_cell_measure (GtkWidget *widget, - GtkOrientation orientation, - int for_size, - int *minimum, - int *natural, - int *minimum_baseline, - int *natural_baseline) -{ - GtkColumnViewCell *cell = GTK_COLUMN_VIEW_CELL (widget); - GtkWidget *child = gtk_widget_get_first_child (widget); - int fixed_width = gtk_column_view_column_get_fixed_width (cell->column); - int unadj_width; - - unadj_width = unadjust_width (widget, fixed_width); - - if (orientation == GTK_ORIENTATION_VERTICAL) - { - if (fixed_width > -1) - { - if (for_size == -1) - for_size = unadj_width; - else - for_size = MIN (for_size, unadj_width); - } - } +gtk_column_view_cell_dispose (GObject *object) +{ + GtkColumnViewCell *self = GTK_COLUMN_VIEW_CELL (object); - if (child) - gtk_widget_measure (child, orientation, for_size, minimum, natural, minimum_baseline, natural_baseline); + g_assert (self->cell == NULL); /* would hold a reference */ + g_clear_object (&self->child); - if (orientation == GTK_ORIENTATION_HORIZONTAL) - { - if (fixed_width > -1) - { - *minimum = 0; - *natural = unadj_width; - } - } + G_OBJECT_CLASS (gtk_column_view_cell_parent_class)->dispose (object); } static void -gtk_column_view_cell_size_allocate (GtkWidget *widget, - int width, - int height, - int baseline) +gtk_column_view_cell_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) { - GtkWidget *child = gtk_widget_get_first_child (widget); + GtkColumnViewCell *self = GTK_COLUMN_VIEW_CELL (object); - if (child) + switch (property_id) { - int min; - - gtk_widget_measure (child, GTK_ORIENTATION_HORIZONTAL, height, &min, NULL, NULL, NULL); - - gtk_widget_allocate (child, MAX (min, width), height, baseline, NULL); + case PROP_CHILD: + g_value_set_object (value, self->child); + break; + + case PROP_FOCUSABLE: + g_value_set_boolean (value, self->focusable); + break; + + case PROP_ITEM: + if (self->cell) + g_value_set_object (value, gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (self->cell))); + break; + + case PROP_POSITION: + if (self->cell) + g_value_set_uint (value, gtk_list_item_base_get_position (GTK_LIST_ITEM_BASE (self->cell))); + else + g_value_set_uint (value, GTK_INVALID_LIST_POSITION); + break; + + case PROP_SELECTED: + if (self->cell) + g_value_set_boolean (value, gtk_list_item_base_get_selected (GTK_LIST_ITEM_BASE (self->cell))); + else + g_value_set_boolean (value, FALSE); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; } } static void -gtk_column_view_cell_dispose (GObject *object) +gtk_column_view_cell_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) { GtkColumnViewCell *self = GTK_COLUMN_VIEW_CELL (object); - if (self->column) + switch (property_id) { - gtk_column_view_column_remove_cell (self->column, self); - - if (self->prev_cell) - self->prev_cell->next_cell = self->next_cell; - if (self->next_cell) - self->next_cell->prev_cell = self->prev_cell; + case PROP_CHILD: + gtk_column_view_cell_set_child (self, g_value_get_object (value)); + break; - self->prev_cell = NULL; - self->next_cell = NULL; + case PROP_FOCUSABLE: + gtk_column_view_cell_set_focusable (self, g_value_get_boolean (value)); + break; - g_clear_object (&self->column); + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; } - - G_OBJECT_CLASS (gtk_column_view_cell_parent_class)->dispose (object); -} - -static GtkSizeRequestMode -gtk_column_view_cell_get_request_mode (GtkWidget *widget) -{ - GtkWidget *child = gtk_widget_get_first_child (widget); - - if (child) - return gtk_widget_get_request_mode (child); - else - return GTK_SIZE_REQUEST_CONSTANT_SIZE; } static void gtk_column_view_cell_class_init (GtkColumnViewCellClass *klass) { - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - widget_class->measure = gtk_column_view_cell_measure; - widget_class->size_allocate = gtk_column_view_cell_size_allocate; - widget_class->get_request_mode = gtk_column_view_cell_get_request_mode; - gobject_class->dispose = gtk_column_view_cell_dispose; - - gtk_widget_class_set_css_name (widget_class, I_("cell")); - gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GRID_CELL); + gobject_class->get_property = gtk_column_view_cell_get_property; + gobject_class->set_property = gtk_column_view_cell_set_property; + + /** + * GtkColumnViewCell:child: (attributes org.gtk.Property.get=gtk_column_view_cell_get_child org.gtk.Property.set=gtk_column_view_cell_set_child) + * + * Widget used for display. + * + * Since: 4.12 + */ + properties[PROP_CHILD] = + g_param_spec_object ("child", NULL, NULL, + GTK_TYPE_WIDGET, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * GtkColumnViewCell:focusable: (attributes org.gtk.Property.get=gtk_column_view_cell_get_focusable org.gtk.Property.set=gtk_column_view_cell_set_focusable) + * + * If the item can be focused with the keyboard. + * + * Since: 4.12 + */ + properties[PROP_FOCUSABLE] = + g_param_spec_boolean ("focusable", NULL, NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * GtkColumnViewCell:item: (attributes org.gtk.Property.get=gtk_column_view_cell_get_item) + * + * Displayed item. + * + * Since: 4.12 + */ + properties[PROP_ITEM] = + g_param_spec_object ("item", NULL, NULL, + G_TYPE_OBJECT, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * GtkColumnViewCell:position: (attributes org.gtk.Property.get=gtk_column_view_cell_get_position) + * + * Position of the item. + * + * Since: 4.12 + */ + properties[PROP_POSITION] = + g_param_spec_uint ("position", NULL, NULL, + 0, G_MAXUINT, GTK_INVALID_LIST_POSITION, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * GtkColumnViewCell:selected: (attributes org.gtk.Property.get=gtk_column_view_cell_get_selected) + * + * If the item is currently selected. + * + * Since: 4.12 + */ + properties[PROP_SELECTED] = + g_param_spec_boolean ("selected", NULL, NULL, + FALSE, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPS, properties); } static void -gtk_column_view_cell_resize_func (GtkWidget *widget) +gtk_column_view_cell_init (GtkColumnViewCell *self) { - GtkColumnViewCell *self = GTK_COLUMN_VIEW_CELL (widget); + self->focusable = FALSE; +} - if (self->column) - gtk_column_view_column_queue_resize (self->column); +GtkColumnViewCell * +gtk_column_view_cell_new (void) +{ + return g_object_new (GTK_TYPE_COLUMN_VIEW_CELL, NULL); } -static void -gtk_column_view_cell_init (GtkColumnViewCell *self) +void +gtk_column_view_cell_do_notify (GtkColumnViewCell *column_view_cell, + gboolean notify_item, + gboolean notify_position, + gboolean notify_selected) +{ + GObject *object = G_OBJECT (column_view_cell); + + if (notify_item) + g_object_notify_by_pspec (object, properties[PROP_ITEM]); + if (notify_position) + g_object_notify_by_pspec (object, properties[PROP_POSITION]); + if (notify_selected) + g_object_notify_by_pspec (object, properties[PROP_SELECTED]); +} + +/** + * gtk_column_view_cell_get_item: (attributes org.gtk.Method.get_property=item) + * @self: a `GtkColumnViewCell` + * + * Gets the model item that associated with @self. + * + * If @self is unbound, this function returns %NULL. + * + * Returns: (nullable) (transfer none) (type GObject): The item displayed + * + * Since: 4.12 + **/ +gpointer +gtk_column_view_cell_get_item (GtkColumnViewCell *self) { - GtkWidget *widget = GTK_WIDGET (self); + g_return_val_if_fail (GTK_IS_COLUMN_VIEW_CELL (self), NULL); - gtk_widget_set_focusable (widget, FALSE); - gtk_widget_set_overflow (widget, GTK_OVERFLOW_HIDDEN); - /* FIXME: Figure out if setting the manager class to INVALID should work */ - gtk_widget_set_layout_manager (widget, NULL); - widget->priv->resize_func = gtk_column_view_cell_resize_func; + if (self->cell == NULL) + return NULL; + + return gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (self->cell)); } +/** + * gtk_column_view_cell_get_child: (attributes org.gtk.Method.get_property=child) + * @self: a `GtkColumnViewCell` + * + * Gets the child previously set via gtk_column_view_cell_set_child() or + * %NULL if none was set. + * + * Returns: (transfer none) (nullable): The child + * + * Since: 4.12 + */ GtkWidget * -gtk_column_view_cell_new (GtkColumnViewColumn *column) +gtk_column_view_cell_get_child (GtkColumnViewCell *self) { - GtkColumnViewCell *self; + g_return_val_if_fail (GTK_IS_COLUMN_VIEW_CELL (self), NULL); - self = g_object_new (GTK_TYPE_COLUMN_VIEW_CELL, - "factory", gtk_column_view_column_get_factory (column), - NULL); + return self->child; +} - self->column = g_object_ref (column); +/** + * gtk_column_view_cell_set_child: (attributes org.gtk.Method.set_property=child) + * @self: a `GtkColumnViewCell` + * @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. + * + * Since: 4.12 + */ +void +gtk_column_view_cell_set_child (GtkColumnViewCell *self, + GtkWidget *child) +{ + g_return_if_fail (GTK_IS_COLUMN_VIEW_CELL (self)); + g_return_if_fail (child == NULL || GTK_IS_WIDGET (child)); + + if (self->child == child) + return; + + g_clear_object (&self->child); - self->next_cell = gtk_column_view_column_get_first_cell (self->column); - if (self->next_cell) - self->next_cell->prev_cell = self; + if (child) + { + g_object_ref_sink (child); + self->child = child; + } - gtk_column_view_column_add_cell (self->column, self); + if (self->cell) + gtk_column_view_cell_widget_set_child (self->cell, child); - return GTK_WIDGET (self); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CHILD]); } -void -gtk_column_view_cell_remove (GtkColumnViewCell *self) +/** + * gtk_column_view_cell_get_position: (attributes org.gtk.Method.get_property=position) + * @self: a `GtkColumnViewCell` + * + * Gets the position in the model that @self currently displays. + * + * If @self is unbound, %GTK_INVALID_LIST_POSITION is returned. + * + * Returns: The position of this item + * + * Since: 4.12 + */ +guint +gtk_column_view_cell_get_position (GtkColumnViewCell *self) { - GtkWidget *widget = GTK_WIDGET (self); + g_return_val_if_fail (GTK_IS_COLUMN_VIEW_CELL (self), GTK_INVALID_LIST_POSITION); - gtk_column_view_row_widget_remove_child (GTK_COLUMN_VIEW_ROW_WIDGET (gtk_widget_get_parent (widget)), widget); + if (self->cell == NULL) + return GTK_INVALID_LIST_POSITION; + + return gtk_list_item_base_get_position (GTK_LIST_ITEM_BASE (self->cell)); } -GtkColumnViewCell * -gtk_column_view_cell_get_next (GtkColumnViewCell *self) +/** + * gtk_column_view_cell_get_selected: (attributes org.gtk.Method.get_property=selected) + * @self: a `GtkColumnViewCell` + * + * Checks if the item is displayed as selected. + * + * The selected state is maintained by the liste widget and its model + * and cannot be set otherwise. + * + * Returns: %TRUE if the item is selected. + * + * Since: 4.12 + */ +gboolean +gtk_column_view_cell_get_selected (GtkColumnViewCell *self) { - return self->next_cell; + g_return_val_if_fail (GTK_IS_COLUMN_VIEW_CELL (self), FALSE); + + if (self->cell == NULL) + return FALSE; + + return gtk_list_item_base_get_selected (GTK_LIST_ITEM_BASE (self->cell)); } -GtkColumnViewCell * -gtk_column_view_cell_get_prev (GtkColumnViewCell *self) +/** + * gtk_column_view_cell_get_focusable: (attributes org.gtk.Method.get_property=focusable) + * @self: a `GtkColumnViewCell` + * + * Checks if a list item has been set to be focusable via + * gtk_column_view_cell_set_focusable(). + * + * Returns: %TRUE if the item is focusable + * + * Since: 4.12 + */ +gboolean +gtk_column_view_cell_get_focusable (GtkColumnViewCell *self) { - return self->prev_cell; + g_return_val_if_fail (GTK_IS_COLUMN_VIEW_CELL (self), FALSE); + + return self->focusable; } -GtkColumnViewColumn * -gtk_column_view_cell_get_column (GtkColumnViewCell *self) +/** + * gtk_column_view_cell_set_focusable: (attributes org.gtk.Method.set_property=focusable) + * @self: a `GtkColumnViewCell` + * @focusable: if the item should be focusable + * + * Sets @self to be focusable. + * + * If an item is focusable, it can be focused using the keyboard. + * This works similar to [method@Gtk.Widget.set_focusable]. + * + * Note that if items are not focusable, the keyboard cannot be used to activate + * them and selecting only works if one of the listitem's children is focusable. + * + * By default, list items are focusable. + * + * Since: 4.12 + */ +void +gtk_column_view_cell_set_focusable (GtkColumnViewCell *self, + gboolean focusable) { - return self->column; + g_return_if_fail (GTK_IS_COLUMN_VIEW_CELL (self)); + + if (self->focusable == focusable) + return; + + self->focusable = focusable; + + if (self->cell) + gtk_widget_set_focusable (GTK_WIDGET (self->cell), focusable); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FOCUSABLE]); } diff --git a/gtk/gtkcolumnviewcell.h b/gtk/gtkcolumnviewcell.h new file mode 100644 index 0000000000..f1490ea80d --- /dev/null +++ b/gtk/gtkcolumnviewcell.h @@ -0,0 +1,54 @@ +/* + * Copyright © 2023 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 <http://www.gnu.org/licenses/>. + * + * Authors: Benjamin Otte <otte@gnome.org> + */ + +#pragma once + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only <gtk/gtk.h> can be included directly." +#endif + +#include <gtk/gtktypes.h> +#include <gtk/gtklistitem.h> + +G_BEGIN_DECLS + +#define GTK_TYPE_COLUMN_VIEW_CELL (gtk_column_view_cell_get_type ()) +GDK_AVAILABLE_IN_4_12 +GDK_DECLARE_INTERNAL_TYPE(GtkColumnViewCell, gtk_column_view_cell, GTK, COLUMN_VIEW_CELL, GtkListItem); + +GDK_AVAILABLE_IN_4_12 +gpointer gtk_column_view_cell_get_item (GtkColumnViewCell *self); +GDK_AVAILABLE_IN_4_12 +guint gtk_column_view_cell_get_position (GtkColumnViewCell *self) G_GNUC_PURE; +GDK_AVAILABLE_IN_4_12 +gboolean gtk_column_view_cell_get_selected (GtkColumnViewCell *self) G_GNUC_PURE; +GDK_AVAILABLE_IN_4_12 +gboolean gtk_column_view_cell_get_focusable (GtkColumnViewCell *self) G_GNUC_PURE; +GDK_AVAILABLE_IN_4_12 +void gtk_column_view_cell_set_focusable (GtkColumnViewCell *self, + gboolean focusable); + +GDK_AVAILABLE_IN_4_12 +void gtk_column_view_cell_set_child (GtkColumnViewCell *self, + GtkWidget *child); +GDK_AVAILABLE_IN_4_12 +GtkWidget * gtk_column_view_cell_get_child (GtkColumnViewCell *self); + +G_END_DECLS + diff --git a/gtk/gtkcolumnviewcellprivate.h b/gtk/gtkcolumnviewcellprivate.h index 5e78ea4901..40964d25d4 100644 --- a/gtk/gtkcolumnviewcellprivate.h +++ b/gtk/gtkcolumnviewcellprivate.h @@ -1,5 +1,5 @@ /* - * Copyright © 2019 Benjamin Otte + * Copyright © 2023 Benjamin Otte * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -19,29 +19,31 @@ #pragma once -#include "gtkcolumnviewcolumn.h" +#include "gtkcolumnviewcell.h" + +#include "gtkcolumnviewcellwidgetprivate.h" +#include "gtklistitemprivate.h" G_BEGIN_DECLS -#define GTK_TYPE_COLUMN_VIEW_CELL (gtk_column_view_cell_get_type ()) -#define GTK_COLUMN_VIEW_CELL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_COLUMN_VIEW_CELL, GtkColumnViewCell)) -#define GTK_COLUMN_VIEW_CELL_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_COLUMN_VIEW_CELL, GtkColumnViewCellClass)) -#define GTK_IS_COLUMN_VIEW_CELL(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_COLUMN_VIEW_CELL)) -#define GTK_IS_COLUMN_VIEW_CELL_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_COLUMN_VIEW_CELL)) -#define GTK_COLUMN_VIEW_CELL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_COLUMN_VIEW_CELL, GtkColumnViewCellClass)) +struct _GtkColumnViewCell +{ + GtkListItem parent_instance; + + GtkColumnViewCellWidget *cell; /* has a reference */ -typedef struct _GtkColumnViewCell GtkColumnViewCell; -typedef struct _GtkColumnViewCellClass GtkColumnViewCellClass; + GtkWidget *child; -GType gtk_column_view_cell_get_type (void) G_GNUC_CONST; + guint focusable : 1; +}; -GtkWidget * gtk_column_view_cell_new (GtkColumnViewColumn *column); +GtkColumnViewCell * gtk_column_view_cell_new (void); -void gtk_column_view_cell_remove (GtkColumnViewCell *self); +void gtk_column_view_cell_do_notify (GtkColumnViewCell *column_view_cell, + gboolean notify_item, + gboolean notify_position, + gboolean notify_selected); -GtkColumnViewCell * gtk_column_view_cell_get_next (GtkColumnViewCell *self); -GtkColumnViewCell * gtk_column_view_cell_get_prev (GtkColumnViewCell *self); -GtkColumnViewColumn * gtk_column_view_cell_get_column (GtkColumnViewCell *self); G_END_DECLS diff --git a/gtk/gtkcolumnviewcellwidget.c b/gtk/gtkcolumnviewcellwidget.c new file mode 100644 index 0000000000..eb3ab7835e --- /dev/null +++ b/gtk/gtkcolumnviewcellwidget.c @@ -0,0 +1,411 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + * + * Authors: Benjamin Otte <otte@gnome.org> + */ + +#include "config.h" + +#include "gtkcolumnviewcellwidgetprivate.h" + +#include "gtkcolumnviewcellprivate.h" +#include "gtkcolumnviewcolumnprivate.h" +#include "gtkcolumnviewrowwidgetprivate.h" +#include "gtkcssboxesprivate.h" +#include "gtkcssnodeprivate.h" +#include "gtklistfactorywidgetprivate.h" +#include "gtkprivate.h" +#include "gtkwidgetprivate.h" + + +struct _GtkColumnViewCellWidget +{ + GtkListItemWidget parent_instance; + + GtkColumnViewColumn *column; + + /* This list isn't sorted - next/prev refer to list elements, not rows in the list */ + GtkColumnViewCellWidget *next_cell; + GtkColumnViewCellWidget *prev_cell; +}; + +struct _GtkColumnViewCellWidgetClass +{ + GtkListItemWidgetClass parent_class; +}; + +G_DEFINE_TYPE (GtkColumnViewCellWidget, gtk_column_view_cell_widget, GTK_TYPE_LIST_FACTORY_WIDGET) + +static gboolean +gtk_column_view_cell_widget_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + GtkWidget *child = gtk_widget_get_first_child (widget); + + if (gtk_widget_get_focus_child (widget)) + { + /* focus is in the child */ + if (direction == GTK_DIR_TAB_BACKWARD) + return gtk_widget_grab_focus_self (widget); + else + return FALSE; + } + else if (gtk_widget_is_focus (widget)) + { + /* The widget has focus */ + if (direction == GTK_DIR_TAB_FORWARD) + { + if (child) + return gtk_widget_child_focus (child, direction); + } + + return FALSE; + } + else + { + /* focus coming in from the outside */ + if (direction == GTK_DIR_TAB_BACKWARD) + { + if (child && + gtk_widget_child_focus (child, direction)) + return TRUE; + + return gtk_widget_grab_focus_self (widget); + } + else + { + if (gtk_widget_grab_focus_self (widget)) + return TRUE; + + if (child && + gtk_widget_child_focus (child, direction)) + return TRUE; + + return FALSE; + } + } +} + +static gboolean +gtk_column_view_cell_widget_grab_focus (GtkWidget *widget) +{ + GtkWidget *child; + + if (GTK_WIDGET_CLASS (gtk_column_view_cell_widget_parent_class)->grab_focus (widget)) + return TRUE; + + child = gtk_widget_get_first_child (widget); + if (child && gtk_widget_grab_focus (child)) + return TRUE; + + return FALSE; +} + +static gpointer +gtk_column_view_cell_widget_create_object (GtkListFactoryWidget *fw) +{ + return gtk_column_view_cell_new (); +} + +static void +gtk_column_view_cell_widget_setup_object (GtkListFactoryWidget *fw, + gpointer object) +{ + GtkColumnViewCellWidget *self = GTK_COLUMN_VIEW_CELL_WIDGET (fw); + GtkColumnViewCell *cell = object; + + GTK_LIST_FACTORY_WIDGET_CLASS (gtk_column_view_cell_widget_parent_class)->setup_object (fw, object); + + cell->cell = self; + + gtk_column_view_cell_widget_set_child (GTK_COLUMN_VIEW_CELL_WIDGET (self), cell->child); + + gtk_widget_set_focusable (GTK_WIDGET (self), cell->focusable); + + gtk_column_view_cell_do_notify (cell, + gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (self)) != NULL, + gtk_list_item_base_get_position (GTK_LIST_ITEM_BASE (self)) != GTK_INVALID_LIST_POSITION, + gtk_list_item_base_get_selected (GTK_LIST_ITEM_BASE (self))); +} + +static void +gtk_column_view_cell_widget_teardown_object (GtkListFactoryWidget *fw, + gpointer object) +{ + GtkColumnViewCellWidget *self = GTK_COLUMN_VIEW_CELL_WIDGET (fw); + GtkColumnViewCell *cell = object; + + GTK_LIST_FACTORY_WIDGET_CLASS (gtk_column_view_cell_widget_parent_class)->teardown_object (fw, object); + + cell->cell = NULL; + + gtk_column_view_cell_widget_set_child (GTK_COLUMN_VIEW_CELL_WIDGET (self), NULL); + + gtk_widget_set_focusable (GTK_WIDGET (self), FALSE); + + gtk_column_view_cell_do_notify (cell, + gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (self)) != NULL, + gtk_list_item_base_get_position (GTK_LIST_ITEM_BASE (self)) != GTK_INVALID_LIST_POSITION, + gtk_list_item_base_get_selected (GTK_LIST_ITEM_BASE (self))); +} + +static void +gtk_column_view_cell_widget_update_object (GtkListFactoryWidget *fw, + gpointer object, + guint position, + gpointer item, + gboolean selected) +{ + GtkColumnViewCellWidget *self = GTK_COLUMN_VIEW_CELL_WIDGET (fw); + GtkListItemBase *base = GTK_LIST_ITEM_BASE (self); + GtkColumnViewCell *cell = object; + /* Track notify manually instead of freeze/thaw_notify for performance reasons. */ + gboolean notify_item = FALSE, notify_position = FALSE, notify_selected = FALSE; + + /* FIXME: It's kinda evil to notify external objects from here... */ + notify_item = gtk_list_item_base_get_item (base) != item; + notify_position = gtk_list_item_base_get_position (base) != position; + notify_selected = gtk_list_item_base_get_selected (base) != selected; + + GTK_LIST_FACTORY_WIDGET_CLASS (gtk_column_view_cell_widget_parent_class)->update_object (fw, + object, + position, + item, + selected); + + if (cell) + gtk_column_view_cell_do_notify (cell, notify_item, notify_position, notify_selected); +} + +static int +unadjust_width (GtkWidget *widget, + int width) +{ + GtkCssBoxes boxes; + + if (width <= -1) + return -1; + + gtk_css_boxes_init_border_box (&boxes, + gtk_css_node_get_style (gtk_widget_get_css_node (widget)), + 0, 0, + width, 100000); + return MAX (0, floor (gtk_css_boxes_get_content_rect (&boxes)->size.width)); +} + +static void +gtk_column_view_cell_widget_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + GtkColumnViewCellWidget *cell = GTK_COLUMN_VIEW_CELL_WIDGET (widget); + GtkWidget *child = gtk_widget_get_first_child (widget); + int fixed_width, unadj_width; + + fixed_width = gtk_column_view_column_get_fixed_width (cell->column); + unadj_width = unadjust_width (widget, fixed_width); + + if (orientation == GTK_ORIENTATION_VERTICAL) + { + if (fixed_width > -1) + { + int min; + + if (for_size == -1) + for_size = unadj_width; + else + for_size = MIN (for_size, unadj_width); + + gtk_widget_measure (child, GTK_ORIENTATION_HORIZONTAL, -1, &min, NULL, NULL, NULL); + for_size = MAX (for_size, min); + } + } + + if (child) + gtk_widget_measure (child, orientation, for_size, minimum, natural, minimum_baseline, natural_baseline); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + if (fixed_width > -1) + { + *minimum = 0; + *natural = unadj_width; + } + } +} + +static void +gtk_column_view_cell_widget_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + GtkWidget *child = gtk_widget_get_first_child (widget); + + if (child) + { + int min; + + gtk_widget_measure (child, GTK_ORIENTATION_HORIZONTAL, height, &min, NULL, NULL, NULL); + + gtk_widget_allocate (child, MAX (min, width), height, baseline, NULL); + } +} + +static void +gtk_column_view_cell_widget_dispose (GObject *object) +{ + GtkColumnViewCellWidget *self = GTK_COLUMN_VIEW_CELL_WIDGET (object); + + if (self->column) + { + gtk_column_view_column_remove_cell (self->column, self); + + if (self->prev_cell) + self->prev_cell->next_cell = self->next_cell; + if (self->next_cell) + self->next_cell->prev_cell = self->prev_cell; + + self->prev_cell = NULL; + self->next_cell = NULL; + + g_clear_object (&self->column); + } + + G_OBJECT_CLASS (gtk_column_view_cell_widget_parent_class)->dispose (object); +} + +static GtkSizeRequestMode +gtk_column_view_cell_widget_get_request_mode (GtkWidget *widget) +{ + GtkWidget *child = gtk_widget_get_first_child (widget); + + if (child) + return gtk_widget_get_request_mode (child); + else + return GTK_SIZE_REQUEST_CONSTANT_SIZE; +} + +static void +gtk_column_view_cell_widget_class_init (GtkColumnViewCellWidgetClass *klass) +{ + GtkListFactoryWidgetClass *factory_class = GTK_LIST_FACTORY_WIDGET_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + factory_class->create_object = gtk_column_view_cell_widget_create_object; + factory_class->setup_object = gtk_column_view_cell_widget_setup_object; + factory_class->update_object = gtk_column_view_cell_widget_update_object; + factory_class->teardown_object = gtk_column_view_cell_widget_teardown_object; + + widget_class->focus = gtk_column_view_cell_widget_focus; + widget_class->grab_focus = gtk_column_view_cell_widget_grab_focus; + widget_class->measure = gtk_column_view_cell_widget_measure; + widget_class->size_allocate = gtk_column_view_cell_widget_size_allocate; + widget_class->get_request_mode = gtk_column_view_cell_widget_get_request_mode; + + gobject_class->dispose = gtk_column_view_cell_widget_dispose; + + gtk_widget_class_set_css_name (widget_class, I_("cell")); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GRID_CELL); +} + +static void +gtk_column_view_cell_widget_resize_func (GtkWidget *widget) +{ + GtkColumnViewCellWidget *self = GTK_COLUMN_VIEW_CELL_WIDGET (widget); + + if (self->column) + gtk_column_view_column_queue_resize (self->column); +} + +static void +gtk_column_view_cell_widget_init (GtkColumnViewCellWidget *self) +{ + GtkWidget *widget = GTK_WIDGET (self); + + gtk_widget_set_focusable (widget, FALSE); + gtk_widget_set_overflow (widget, GTK_OVERFLOW_HIDDEN); + /* FIXME: Figure out if setting the manager class to INVALID should work */ + gtk_widget_set_layout_manager (widget, NULL); + widget->priv->resize_func = gtk_column_view_cell_widget_resize_func; +} + +GtkWidget * +gtk_column_view_cell_widget_new (GtkColumnViewColumn *column, + gboolean inert) +{ + GtkColumnViewCellWidget *self; + + self = g_object_new (GTK_TYPE_COLUMN_VIEW_CELL_WIDGET, + "factory", inert ? NULL : gtk_column_view_column_get_factory (column), + NULL); + + self->column = g_object_ref (column); + + self->next_cell = gtk_column_view_column_get_first_cell (self->column); + if (self->next_cell) + self->next_cell->prev_cell = self; + + gtk_column_view_column_add_cell (self->column, self); + + return GTK_WIDGET (self); +} + +void +gtk_column_view_cell_widget_remove (GtkColumnViewCellWidget *self) +{ + GtkWidget *widget = GTK_WIDGET (self); + + gtk_column_view_row_widget_remove_child (GTK_COLUMN_VIEW_ROW_WIDGET (gtk_widget_get_parent (widget)), widget); +} + +GtkColumnViewCellWidget * +gtk_column_view_cell_widget_get_next (GtkColumnViewCellWidget *self) +{ + return self->next_cell; +} + +GtkColumnViewCellWidget * +gtk_column_view_cell_widget_get_prev (GtkColumnViewCellWidget *self) +{ + return self->prev_cell; +} + +GtkColumnViewColumn * +gtk_column_view_cell_widget_get_column (GtkColumnViewCellWidget *self) +{ + return self->column; +} + +void +gtk_column_view_cell_widget_set_child (GtkColumnViewCellWidget *self, + GtkWidget *child) +{ + GtkWidget *cur_child = gtk_widget_get_first_child (GTK_WIDGET (self)); + + if (cur_child == child) + return; + + g_clear_pointer (&cur_child, gtk_widget_unparent); + + if (child) + gtk_widget_set_parent (child, GTK_WIDGET (self)); +} diff --git a/gtk/gtkcolumnviewcellwidgetprivate.h b/gtk/gtkcolumnviewcellwidgetprivate.h new file mode 100644 index 0000000000..57632adf9b --- /dev/null +++ b/gtk/gtkcolumnviewcellwidgetprivate.h @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + * + * Authors: Benjamin Otte <otte@gnome.org> + */ + +#pragma once + +#include "gtkcolumnviewcolumn.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_COLUMN_VIEW_CELL_WIDGET (gtk_column_view_cell_widget_get_type ()) +#define GTK_COLUMN_VIEW_CELL_WIDGET(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_COLUMN_VIEW_CELL_WIDGET, GtkColumnViewCellWidget)) +#define GTK_COLUMN_VIEW_CELL_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_COLUMN_VIEW_CELL_WIDGET, GtkColumnViewCellWidgetClass)) +#define GTK_IS_COLUMN_VIEW_CELL_WIDGET(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_COLUMN_VIEW_CELL_WIDGET)) +#define GTK_IS_COLUMN_VIEW_CELL_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_COLUMN_VIEW_CELL_WIDGET)) +#define GTK_COLUMN_VIEW_CELL_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_COLUMN_VIEW_CELL_WIDGET, GtkColumnViewCellWidgetClass)) + +typedef struct _GtkColumnViewCellWidget GtkColumnViewCellWidget; +typedef struct _GtkColumnViewCellWidgetClass GtkColumnViewCellWidgetClass; + +GType gtk_column_view_cell_widget_get_type (void) G_GNUC_CONST; + +GtkWidget * gtk_column_view_cell_widget_new (GtkColumnViewColumn *column, + gboolean inert); + +void gtk_column_view_cell_widget_set_child (GtkColumnViewCellWidget *self, + GtkWidget *child); + +void gtk_column_view_cell_widget_remove (GtkColumnViewCellWidget *self); + +GtkColumnViewCellWidget * gtk_column_view_cell_widget_get_next (GtkColumnViewCellWidget *self); +GtkColumnViewCellWidget * gtk_column_view_cell_widget_get_prev (GtkColumnViewCellWidget *self); +GtkColumnViewColumn * gtk_column_view_cell_widget_get_column (GtkColumnViewCellWidget *self); + +G_END_DECLS diff --git a/gtk/gtkcolumnviewcolumn.c b/gtk/gtkcolumnviewcolumn.c index beb88fd95c..32d8e1d562 100644 --- a/gtk/gtkcolumnviewcolumn.c +++ b/gtk/gtkcolumnviewcolumn.c @@ -78,7 +78,7 @@ struct _GtkColumnViewColumn GMenuModel *menu; /* This list isn't sorted - this is just caching for performance */ - GtkColumnViewCell *first_cell; /* no reference, just caching */ + GtkColumnViewCellWidget *first_cell; /* no reference, just caching */ }; struct _GtkColumnViewColumnClass @@ -400,7 +400,7 @@ gtk_column_view_column_new (const char *title, return result; } -GtkColumnViewCell * +GtkColumnViewCellWidget * gtk_column_view_column_get_first_cell (GtkColumnViewColumn *self) { return self->first_cell; @@ -408,7 +408,7 @@ gtk_column_view_column_get_first_cell (GtkColumnViewColumn *self) void gtk_column_view_column_add_cell (GtkColumnViewColumn *self, - GtkColumnViewCell *cell) + GtkColumnViewCellWidget *cell) { self->first_cell = cell; @@ -418,10 +418,10 @@ gtk_column_view_column_add_cell (GtkColumnViewColumn *self, void gtk_column_view_column_remove_cell (GtkColumnViewColumn *self, - GtkColumnViewCell *cell) + GtkColumnViewCellWidget *cell) { if (cell == self->first_cell) - self->first_cell = gtk_column_view_cell_get_next (cell); + self->first_cell = gtk_column_view_cell_widget_get_next (cell); gtk_column_view_column_queue_resize (self); gtk_widget_queue_resize (GTK_WIDGET (cell)); @@ -430,7 +430,7 @@ gtk_column_view_column_remove_cell (GtkColumnViewColumn *self, void gtk_column_view_column_queue_resize (GtkColumnViewColumn *self) { - GtkColumnViewCell *cell; + GtkColumnViewCellWidget *cell; if (self->minimum_size_request < 0) return; @@ -441,7 +441,7 @@ gtk_column_view_column_queue_resize (GtkColumnViewColumn *self) if (self->header) gtk_widget_queue_resize (self->header); - for (cell = self->first_cell; cell; cell = gtk_column_view_cell_get_next (cell)) + for (cell = self->first_cell; cell; cell = gtk_column_view_cell_widget_get_next (cell)) { gtk_widget_queue_resize (GTK_WIDGET (cell)); } @@ -460,7 +460,7 @@ gtk_column_view_column_measure (GtkColumnViewColumn *self, if (self->minimum_size_request < 0) { - GtkColumnViewCell *cell; + GtkColumnViewCellWidget *cell; int min, nat, cell_min, cell_nat; if (self->header) @@ -473,7 +473,7 @@ gtk_column_view_column_measure (GtkColumnViewColumn *self, nat = 0; } - for (cell = self->first_cell; cell; cell = gtk_column_view_cell_get_next (cell)) + for (cell = self->first_cell; cell; cell = gtk_column_view_cell_widget_get_next (cell)) { gtk_widget_measure (GTK_WIDGET (cell), GTK_ORIENTATION_HORIZONTAL, @@ -532,12 +532,9 @@ gtk_column_view_column_create_cells (GtkColumnViewColumn *self) GtkListItemBase *base; GtkWidget *cell; - if (!gtk_widget_get_root (row)) - continue; - list_item = GTK_COLUMN_VIEW_ROW_WIDGET (row); base = GTK_LIST_ITEM_BASE (row); - cell = gtk_column_view_cell_new (self); + cell = gtk_column_view_cell_widget_new (self, gtk_column_view_is_inert (self->view)); gtk_column_view_row_widget_add_child (list_item, cell); gtk_list_item_base_update (GTK_LIST_ITEM_BASE (cell), gtk_list_item_base_get_position (base), @@ -550,7 +547,7 @@ static void gtk_column_view_column_remove_cells (GtkColumnViewColumn *self) { while (self->first_cell) - gtk_column_view_cell_remove (self->first_cell); + gtk_column_view_cell_widget_remove (self->first_cell); } static void @@ -581,8 +578,7 @@ gtk_column_view_column_remove_header (GtkColumnViewColumn *self) static void gtk_column_view_column_ensure_cells (GtkColumnViewColumn *self) { - if (self->view && gtk_widget_get_root (GTK_WIDGET (self->view)) && - gtk_column_view_column_get_visible (self)) + if (self->view && gtk_column_view_column_get_visible (self)) gtk_column_view_column_create_cells (self); else gtk_column_view_column_remove_cells (self); @@ -632,13 +628,13 @@ void gtk_column_view_column_set_position (GtkColumnViewColumn *self, guint position) { - GtkColumnViewCell *cell; + GtkColumnViewCellWidget *cell; gtk_column_view_row_widget_reorder_child (gtk_column_view_get_header_widget (self->view), self->header, position); - for (cell = self->first_cell; cell; cell = gtk_column_view_cell_get_next (cell)) + for (cell = self->first_cell; cell; cell = gtk_column_view_cell_widget_get_next (cell)) { GtkColumnViewRowWidget *list_item; @@ -664,6 +660,29 @@ gtk_column_view_column_get_factory (GtkColumnViewColumn *self) return self->factory; } +void +gtk_column_view_column_update_factory (GtkColumnViewColumn *self, + gboolean inert) +{ + GtkListItemFactory *factory; + GtkColumnViewCellWidget *cell; + + if (self->factory == NULL) + return; + + if (inert) + factory = NULL; + else + factory = self->factory; + + for (cell = self->first_cell; + cell; + cell = gtk_column_view_cell_widget_get_next (cell)) + { + gtk_list_factory_widget_set_factory (GTK_LIST_FACTORY_WIDGET (cell), factory); + } +} + /** * gtk_column_view_column_set_factory: (attributes org.gtk.Method.set_property=factory) * @self: a `GtkColumnViewColumn` @@ -679,9 +698,15 @@ gtk_column_view_column_set_factory (GtkColumnViewColumn *self, g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self)); g_return_if_fail (factory == NULL || GTK_LIST_ITEM_FACTORY (factory)); + if (self->factory && !factory) + gtk_column_view_column_update_factory (self, TRUE); + if (!g_set_object (&self->factory, factory)) return; + if (self->view && !gtk_column_view_is_inert (self->view)) + gtk_column_view_column_update_factory (self, FALSE); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]); } diff --git a/gtk/gtkcolumnviewcolumnprivate.h b/gtk/gtkcolumnviewcolumnprivate.h index aa7f1c73fd..fa0c66b2b5 100644 --- a/gtk/gtkcolumnviewcolumnprivate.h +++ b/gtk/gtkcolumnviewcolumnprivate.h @@ -21,7 +21,7 @@ #include "gtk/gtkcolumnviewcolumn.h" -#include "gtk/gtkcolumnviewcellprivate.h" +#include "gtk/gtkcolumnviewcellwidgetprivate.h" void gtk_column_view_column_set_column_view (GtkColumnViewColumn *self, @@ -31,12 +31,14 @@ void gtk_column_view_column_set_position (GtkColu guint position); void gtk_column_view_column_add_cell (GtkColumnViewColumn *self, - GtkColumnViewCell *cell); + GtkColumnViewCellWidget *cell); void gtk_column_view_column_remove_cell (GtkColumnViewColumn *self, - GtkColumnViewCell *cell); -GtkColumnViewCell * gtk_column_view_column_get_first_cell (GtkColumnViewColumn *self); + GtkColumnViewCellWidget *cell); +GtkColumnViewCellWidget * gtk_column_view_column_get_first_cell (GtkColumnViewColumn *self); GtkWidget * gtk_column_view_column_get_header (GtkColumnViewColumn *self); +void gtk_column_view_column_update_factory (GtkColumnViewColumn *self, + gboolean inert); void gtk_column_view_column_queue_resize (GtkColumnViewColumn *self); void gtk_column_view_column_measure (GtkColumnViewColumn *self, int *minimum, diff --git a/gtk/gtkcolumnviewprivate.h b/gtk/gtkcolumnviewprivate.h index 679b582aed..1a3061e332 100644 --- a/gtk/gtkcolumnviewprivate.h +++ b/gtk/gtkcolumnviewprivate.h @@ -26,6 +26,8 @@ #include "gtk/gtkcolumnviewsorterprivate.h" #include "gtk/gtkcolumnviewrowwidgetprivate.h" +gboolean gtk_column_view_is_inert (GtkColumnView *self); + GtkColumnViewRowWidget *gtk_column_view_get_header_widget (GtkColumnView *self); GtkListView * gtk_column_view_get_list_view (GtkColumnView *self); diff --git a/gtk/gtkcolumnviewrow.c b/gtk/gtkcolumnviewrow.c new file mode 100644 index 0000000000..1570d626a0 --- /dev/null +++ b/gtk/gtkcolumnviewrow.c @@ -0,0 +1,477 @@ +/* + * Copyright © 2023 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 <http://www.gnu.org/licenses/>. + * + * Authors: Benjamin Otte <otte@gnome.org> + */ + +#include "config.h" + +#include "gtkcolumnviewrowprivate.h" + + +/** + * GtkColumnViewRow: + * + * `GtkColumnViewRow` is used by [class@Gtk.ColumnView] to allow configuring + * how rows are displayed. + * + * It is not used to set the widgets displayed in the individual cells. For that + * see [method@GtkColumnViewColumn.set_factory] and [class@GtkColumnViewCell]. + * + * Since: 4.12 + */ + +struct _GtkColumnViewRowClass +{ + GObjectClass parent_class; +}; + +enum +{ + PROP_0, + PROP_ACTIVATABLE, + PROP_FOCUSABLE, + PROP_ITEM, + PROP_POSITION, + PROP_SELECTABLE, + PROP_SELECTED, + + N_PROPS +}; + +G_DEFINE_TYPE (GtkColumnViewRow, gtk_column_view_row, G_TYPE_OBJECT) + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +static void +gtk_column_view_row_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GtkColumnViewRow *self = GTK_COLUMN_VIEW_ROW (object); + + switch (property_id) + { + case PROP_ACTIVATABLE: + g_value_set_boolean (value, self->activatable); + break; + + case PROP_FOCUSABLE: + g_value_set_boolean (value, self->focusable); + break; + + case PROP_ITEM: + if (self->owner) + g_value_set_object (value, gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (self->owner))); + break; + + case PROP_POSITION: + if (self->owner) + g_value_set_uint (value, gtk_list_item_base_get_position (GTK_LIST_ITEM_BASE (self->owner))); + else + g_value_set_uint (value, GTK_INVALID_LIST_POSITION); + break; + + case PROP_SELECTABLE: + g_value_set_boolean (value, self->selectable); + break; + + case PROP_SELECTED: + if (self->owner) + g_value_set_boolean (value, gtk_list_item_base_get_selected (GTK_LIST_ITEM_BASE (self->owner))); + else + g_value_set_boolean (value, FALSE); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gtk_column_view_row_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkColumnViewRow *self = GTK_COLUMN_VIEW_ROW (object); + + switch (property_id) + { + case PROP_ACTIVATABLE: + gtk_column_view_row_set_activatable (self, g_value_get_boolean (value)); + break; + + case PROP_FOCUSABLE: + gtk_column_view_row_set_focusable (self, g_value_get_boolean (value)); + break; + + case PROP_SELECTABLE: + gtk_column_view_row_set_selectable (self, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gtk_column_view_row_class_init (GtkColumnViewRowClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->get_property = gtk_column_view_row_get_property; + gobject_class->set_property = gtk_column_view_row_set_property; + + /** + * GtkColumnViewRow:activatable: (attributes org.gtk.Property.get=gtk_column_view_row_get_activatable org.gtk.Property.set=gtk_column_view_row_set_activatable) + * + * If the row can be activated by the user. + * + * Since: 4.12 + */ + properties[PROP_ACTIVATABLE] = + g_param_spec_boolean ("activatable", NULL, NULL, + TRUE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * GtkColumnViewRow:focusable: (attributes org.gtk.Property.get=gtk_column_view_row_get_focusable org.gtk.Property.set=gtk_column_view_row_set_focusable) + * + * If the row can be focused with the keyboard. + * + * Since: 4.12 + */ + properties[PROP_FOCUSABLE] = + g_param_spec_boolean ("focusable", NULL, NULL, + TRUE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * GtkColumnViewRow:item: (attributes org.gtk.Property.get=gtk_column_view_row_get_item) + * + * The item for this row. + * + * Since: 4.12 + */ + properties[PROP_ITEM] = + g_param_spec_object ("item", NULL, NULL, + G_TYPE_OBJECT, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * GtkColumnViewRow:position: (attributes org.gtk.Property.get=gtk_column_view_row_get_position) + * + * Position of the row. + * + * Since: 4.12 + */ + properties[PROP_POSITION] = + g_param_spec_uint ("position", NULL, NULL, + 0, G_MAXUINT, GTK_INVALID_LIST_POSITION, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * GtkColumnViewRow:selectable: (attributes org.gtk.Property.get=gtk_column_view_row_get_selectable org.gtk.Property.set=gtk_column_view_row_set_selectable) + * + * If the row can be selected by the user. + * + * Since: 4.12 + */ + properties[PROP_SELECTABLE] = + g_param_spec_boolean ("selectable", NULL, NULL, + TRUE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * GtkColumnViewRow:selected: (attributes org.gtk.Property.get=gtk_column_view_row_get_selected) + * + * If the item in the row is currently selected. + * + * Since: 4.12 + */ + properties[PROP_SELECTED] = + g_param_spec_boolean ("selected", NULL, NULL, + FALSE, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPS, properties); +} + +static void +gtk_column_view_row_init (GtkColumnViewRow *self) +{ + self->selectable = TRUE; + self->activatable = TRUE; + self->focusable = TRUE; +} + +GtkColumnViewRow * +gtk_column_view_row_new (void) +{ + return g_object_new (GTK_TYPE_COLUMN_VIEW_ROW, NULL); +} + +void +gtk_column_view_row_do_notify (GtkColumnViewRow *column_view_row, + gboolean notify_item, + gboolean notify_position, + gboolean notify_selected) +{ + GObject *object = G_OBJECT (column_view_row); + + if (notify_item) + g_object_notify_by_pspec (object, properties[PROP_ITEM]); + if (notify_position) + g_object_notify_by_pspec (object, properties[PROP_POSITION]); + if (notify_selected) + g_object_notify_by_pspec (object, properties[PROP_SELECTED]); +} + +/** + * gtk_column_view_row_get_item: (attributes org.gtk.Method.get_property=item) + * @self: a `GtkColumnViewRow` + * + * Gets the model item that associated with @self. + * + * If @self is unbound, this function returns %NULL. + * + * Returns: (nullable) (transfer none) (type GObject): The item displayed + * + * Since: 4.12 + **/ +gpointer +gtk_column_view_row_get_item (GtkColumnViewRow *self) +{ + g_return_val_if_fail (GTK_IS_COLUMN_VIEW_ROW (self), NULL); + + if (self->owner == NULL) + return NULL; + + return gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (self->owner)); +} + +/** + * gtk_column_view_row_get_position: (attributes org.gtk.Method.get_property=position) + * @self: a `GtkColumnViewRow` + * + * Gets the position in the model that @self currently displays. + * + * If @self is unbound, %GTK_INVALID_LIST_POSITION is returned. + * + * Returns: The position of this row + * + * Since: 4.12 + */ +guint +gtk_column_view_row_get_position (GtkColumnViewRow *self) +{ + g_return_val_if_fail (GTK_IS_COLUMN_VIEW_ROW (self), GTK_INVALID_LIST_POSITION); + + if (self->owner == NULL) + return GTK_INVALID_LIST_POSITION; + + return gtk_list_item_base_get_position (GTK_LIST_ITEM_BASE (self->owner)); +} + +/** + * gtk_column_view_row_get_selected: (attributes org.gtk.Method.get_property=selected) + * @self: a `GtkColumnViewRow` + * + * Checks if the item is selected that this row corresponds to. + * + * The selected state is maintained by the list widget and its model + * and cannot be set otherwise. + * + * Returns: %TRUE if the item is selected. + * + * Since: 4.12 + */ +gboolean +gtk_column_view_row_get_selected (GtkColumnViewRow *self) +{ + g_return_val_if_fail (GTK_IS_COLUMN_VIEW_ROW (self), FALSE); + + if (self->owner == NULL) + return FALSE; + + return gtk_list_item_base_get_selected (GTK_LIST_ITEM_BASE (self->owner)); +} + +/** + * gtk_column_view_row_get_selectable: (attributes org.gtk.Method.get_property=selectable) + * @self: a `GtkColumnViewRow` + * + * Checks if the row has been set to be selectable via + * gtk_column_view_row_set_selectable(). + * + * Do not confuse this function with [method@Gtk.ColumnViewRow.get_selected]. + * + * Returns: %TRUE if the row is selectable + * + * Since: 4.12 + */ +gboolean +gtk_column_view_row_get_selectable (GtkColumnViewRow *self) +{ + g_return_val_if_fail (GTK_IS_COLUMN_VIEW_ROW (self), FALSE); + + return self->selectable; +} + +/** + * gtk_column_view_row_set_selectable: (attributes org.gtk.Method.set_property=selectable) + * @self: a `GtkColumnViewRow` + * @selectable: if the row should be selectable + * + * Sets @self to be selectable. + * + * If a row is selectable, clicking on the row or using the keyboard + * will try to select or unselect the row. Whether this succeeds is up to + * the model to determine, as it is managing the selected state. + * + * Note that this means that making a row non-selectable has no + * influence on the selected state at all. A non-selectable row + * may still be selected. + * + * By default, rows are selectable. + * + * Since: 4.12 + */ +void +gtk_column_view_row_set_selectable (GtkColumnViewRow *self, + gboolean selectable) +{ + g_return_if_fail (GTK_IS_COLUMN_VIEW_ROW (self)); + + if (self->selectable == selectable) + return; + + self->selectable = selectable; + + if (self->owner) + gtk_list_factory_widget_set_selectable (GTK_LIST_FACTORY_WIDGET (self->owner), selectable); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTABLE]); +} + +/** + * gtk_column_view_row_get_activatable: (attributes org.gtk.Method.get_property=activatable) + * @self: a `GtkColumnViewRow` + * + * Checks if the row has been set to be activatable via + * gtk_column_view_row_set_activatable(). + * + * Returns: %TRUE if the row is activatable + * + * Since: 4.12 + */ +gboolean +gtk_column_view_row_get_activatable (GtkColumnViewRow *self) +{ + g_return_val_if_fail (GTK_IS_COLUMN_VIEW_ROW (self), FALSE); + + return self->activatable; +} + +/** + * gtk_column_view_row_set_activatable: (attributes org.gtk.Method.set_property=activatable) + * @self: a `GtkColumnViewRow` + * @activatable: if the row should be activatable + * + * Sets @self to be activatable. + * + * If a row is activatable, double-clicking on the row, using + * the Return key or calling gtk_widget_activate() will activate + * the row. Activating instructs the containing columnview to + * emit the [signal@Gtk.ColumnView::activate] signal. + * + * By default, row are activatable. + * + * Since: 4.12 + */ +void +gtk_column_view_row_set_activatable (GtkColumnViewRow *self, + gboolean activatable) +{ + g_return_if_fail (GTK_IS_COLUMN_VIEW_ROW (self)); + + if (self->activatable == activatable) + return; + + self->activatable = activatable; + + if (self->owner) + gtk_list_factory_widget_set_activatable (GTK_LIST_FACTORY_WIDGET (self->owner), activatable); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ACTIVATABLE]); +} + +/** + * gtk_column_view_row_get_focusable: (attributes org.gtk.Method.get_property=focusable) + * @self: a `GtkColumnViewRow` + * + * Checks if a row item has been set to be focusable via + * gtk_column_view_row_set_focusable(). + * + * Returns: %TRUE if the row is focusable + * + * Since: 4.12 + */ +gboolean +gtk_column_view_row_get_focusable (GtkColumnViewRow *self) +{ + g_return_val_if_fail (GTK_IS_COLUMN_VIEW_ROW (self), FALSE); + + return self->focusable; +} + +/** + * gtk_column_view_row_set_focusable: (attributes org.gtk.Method.set_property=focusable) + * @self: a `GtkColumnViewRow` + * @focusable: if the row should be focusable + * + * Sets @self to be focusable. + * + * If a row is focusable, it can be focused using the keyboard. + * This works similar to [method@Gtk.Widget.set_focusable]. + * + * Note that if row are not focusable, the contents of cells can still be focused if + * they are focusable. + * + * By default, rows are focusable. + * + * Since: 4.12 + */ +void +gtk_column_view_row_set_focusable (GtkColumnViewRow *self, + gboolean focusable) +{ + g_return_if_fail (GTK_IS_COLUMN_VIEW_ROW (self)); + + if (self->focusable == focusable) + return; + + self->focusable = focusable; + + if (self->owner) + gtk_widget_set_focusable (GTK_WIDGET (self->owner), focusable); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FOCUSABLE]); +} diff --git a/gtk/gtkcolumnviewrow.h b/gtk/gtkcolumnviewrow.h new file mode 100644 index 0000000000..99fb7c9a7b --- /dev/null +++ b/gtk/gtkcolumnviewrow.h @@ -0,0 +1,57 @@ +/* + * Copyright © 2023 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 <http://www.gnu.org/licenses/>. + * + * Authors: Benjamin Otte <otte@gnome.org> + */ + +#pragma once + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only <gtk/gtk.h> can be included directly." +#endif + +#include <gtk/gtktypes.h> + +G_BEGIN_DECLS + +#define GTK_TYPE_COLUMN_VIEW_ROW (gtk_column_view_row_get_type ()) +GDK_AVAILABLE_IN_4_12 +GDK_DECLARE_INTERNAL_TYPE(GtkColumnViewRow, gtk_column_view_row, GTK, COLUMN_VIEW_ROW, GObject); + +GDK_AVAILABLE_IN_4_12 +gpointer gtk_column_view_row_get_item (GtkColumnViewRow *self); +GDK_AVAILABLE_IN_4_12 +guint gtk_column_view_row_get_position (GtkColumnViewRow *self) G_GNUC_PURE; +GDK_AVAILABLE_IN_4_12 +gboolean gtk_column_view_row_get_selected (GtkColumnViewRow *self) G_GNUC_PURE; +GDK_AVAILABLE_IN_4_12 +gboolean gtk_column_view_row_get_selectable (GtkColumnViewRow *self) G_GNUC_PURE; +GDK_AVAILABLE_IN_4_12 +void gtk_column_view_row_set_selectable (GtkColumnViewRow *self, + gboolean selectable); +GDK_AVAILABLE_IN_4_12 +gboolean gtk_column_view_row_get_activatable (GtkColumnViewRow *self) G_GNUC_PURE; +GDK_AVAILABLE_IN_4_12 +void gtk_column_view_row_set_activatable (GtkColumnViewRow *self, + gboolean activatable); +GDK_AVAILABLE_IN_4_12 +gboolean gtk_column_view_row_get_focusable (GtkColumnViewRow *self) G_GNUC_PURE; +GDK_AVAILABLE_IN_4_12 +void gtk_column_view_row_set_focusable (GtkColumnViewRow *self, + gboolean focusable); + +G_END_DECLS + diff --git a/gtk/gtkcolumnviewrowprivate.h b/gtk/gtkcolumnviewrowprivate.h new file mode 100644 index 0000000000..4370231884 --- /dev/null +++ b/gtk/gtkcolumnviewrowprivate.h @@ -0,0 +1,48 @@ +/* + * Copyright © 2023 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 <http://www.gnu.org/licenses/>. + * + * Authors: Benjamin Otte <otte@gnome.org> + */ + +#pragma once + +#include "gtkcolumnviewrow.h" + +#include "gtkcolumnviewrowwidgetprivate.h" + +G_BEGIN_DECLS + +struct _GtkColumnViewRow +{ + GObject parent_instance; + + GtkColumnViewRowWidget *owner; /* has a reference */ + + guint activatable : 1; + guint selectable : 1; + guint focusable : 1; +}; + +GtkColumnViewRow * gtk_column_view_row_new (void); + +void gtk_column_view_row_do_notify (GtkColumnViewRow *self, + gboolean notify_item, + gboolean notify_position, + gboolean notify_selected); + + +G_END_DECLS + diff --git a/gtk/gtkcolumnviewrowwidget.c b/gtk/gtkcolumnviewrowwidget.c index 234759f036..62cec81b0e 100644 --- a/gtk/gtkcolumnviewrowwidget.c +++ b/gtk/gtkcolumnviewrowwidget.c @@ -23,8 +23,9 @@ #include "gtkbinlayout.h" #include "gtkcolumnviewprivate.h" -#include "gtkcolumnviewcellprivate.h" +#include "gtkcolumnviewcellwidgetprivate.h" #include "gtkcolumnviewcolumnprivate.h" +#include "gtkcolumnviewrowprivate.h" #include "gtkcolumnviewtitleprivate.h" #include "gtklistitemfactoryprivate.h" #include "gtklistbaseprivate.h" @@ -56,8 +57,8 @@ gtk_column_view_row_widget_is_header (GtkColumnViewRowWidget *self) static GtkColumnViewColumn * gtk_column_view_row_child_get_column (GtkWidget *child) { - if (GTK_IS_COLUMN_VIEW_CELL (child)) - return gtk_column_view_cell_get_column (GTK_COLUMN_VIEW_CELL (child)); + if (GTK_IS_COLUMN_VIEW_CELL_WIDGET (child)) + return gtk_column_view_cell_widget_get_column (GTK_COLUMN_VIEW_CELL_WIDGET (child)); else return gtk_column_view_title_get_column (GTK_COLUMN_VIEW_TITLE (child)); @@ -88,8 +89,6 @@ gtk_column_view_row_widget_update (GtkListItemBase *base, gboolean selected) { GtkColumnViewRowWidget *self = GTK_COLUMN_VIEW_ROW_WIDGET (base); - GtkListFactoryWidget *fw = GTK_LIST_FACTORY_WIDGET (base); - gboolean selectable, activatable; GtkWidget *child; if (gtk_column_view_row_widget_is_header (self)) @@ -97,31 +96,99 @@ gtk_column_view_row_widget_update (GtkListItemBase *base, GTK_LIST_ITEM_BASE_CLASS (gtk_column_view_row_widget_parent_class)->update (base, position, item, selected); - /* This really does not belong here, but doing better - * requires considerable plumbing that we don't have now, - * and something like this is needed to fix the filechooser - * in select_folder mode. - */ - selectable = TRUE; - activatable = TRUE; - for (child = gtk_widget_get_first_child (GTK_WIDGET (self)); child; child = gtk_widget_get_next_sibling (child)) { gtk_list_item_base_update (GTK_LIST_ITEM_BASE (child), position, item, selected); - - selectable &= gtk_list_factory_widget_get_selectable (GTK_LIST_FACTORY_WIDGET (child)); - activatable &= gtk_list_factory_widget_get_activatable (GTK_LIST_FACTORY_WIDGET (child)); } +} + +static gpointer +gtk_column_view_row_widget_create_object (GtkListFactoryWidget *fw) +{ + return gtk_column_view_row_new (); +} + +static void +gtk_column_view_row_widget_setup_object (GtkListFactoryWidget *fw, + gpointer object) +{ + GtkColumnViewRowWidget *self = GTK_COLUMN_VIEW_ROW_WIDGET (fw); + GtkColumnViewRow *row = object; + + g_assert (!gtk_column_view_row_widget_is_header (self)); + + GTK_LIST_FACTORY_WIDGET_CLASS (gtk_column_view_row_widget_parent_class)->setup_object (fw, object); + + row->owner = self; + + gtk_list_factory_widget_set_activatable (fw, row->activatable); + gtk_list_factory_widget_set_selectable (fw, row->selectable); + gtk_widget_set_focusable (GTK_WIDGET (self), row->focusable); + + gtk_column_view_row_do_notify (row, + gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (self)) != NULL, + gtk_list_item_base_get_position (GTK_LIST_ITEM_BASE (self)) != GTK_INVALID_LIST_POSITION, + gtk_list_item_base_get_selected (GTK_LIST_ITEM_BASE (self))); +} + +static void +gtk_column_view_row_widget_teardown_object (GtkListFactoryWidget *fw, + gpointer object) +{ + GtkColumnViewRowWidget *self = GTK_COLUMN_VIEW_ROW_WIDGET (fw); + GtkColumnViewRow *row = object; + + g_assert (!gtk_column_view_row_widget_is_header (self)); + + GTK_LIST_FACTORY_WIDGET_CLASS (gtk_column_view_row_widget_parent_class)->teardown_object (fw, object); + + row->owner = NULL; + + gtk_list_factory_widget_set_activatable (fw, FALSE); + gtk_list_factory_widget_set_selectable (fw, FALSE); + gtk_widget_set_focusable (GTK_WIDGET (self), TRUE); - gtk_list_factory_widget_set_selectable (fw, selectable); - gtk_list_factory_widget_set_activatable (fw, activatable); + gtk_column_view_row_do_notify (row, + gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (self)) != NULL, + gtk_list_item_base_get_position (GTK_LIST_ITEM_BASE (self)) != GTK_INVALID_LIST_POSITION, + gtk_list_item_base_get_selected (GTK_LIST_ITEM_BASE (self))); +} + +static void +gtk_column_view_row_widget_update_object (GtkListFactoryWidget *fw, + gpointer object, + guint position, + gpointer item, + gboolean selected) +{ + GtkColumnViewRowWidget *self = GTK_COLUMN_VIEW_ROW_WIDGET (fw); + GtkListItemBase *base = GTK_LIST_ITEM_BASE (self); + GtkColumnViewRow *row = object; + /* Track notify manually instead of freeze/thaw_notify for performance reasons. */ + gboolean notify_item = FALSE, notify_position = FALSE, notify_selected = FALSE; + + g_assert (!gtk_column_view_row_widget_is_header (self)); + + /* FIXME: It's kinda evil to notify external objects from here... */ + notify_item = gtk_list_item_base_get_item (base) != item; + notify_position = gtk_list_item_base_get_position (base) != position; + notify_selected = gtk_list_item_base_get_selected (base) != selected; + + GTK_LIST_FACTORY_WIDGET_CLASS (gtk_column_view_row_widget_parent_class)->update_object (fw, + object, + position, + item, + selected); + + if (row) + gtk_column_view_row_do_notify (row, notify_item, notify_position, notify_selected); } static GtkWidget * gtk_column_view_next_focus_widget (GtkWidget *widget, - GtkWidget *child, + GtkWidget *current, GtkDirectionType direction) { gboolean forward; @@ -149,17 +216,27 @@ gtk_column_view_next_focus_widget (GtkWidget *widget, if (forward) { - if (child) - return gtk_widget_get_next_sibling (child); - else + if (current == NULL) + return widget; + else if (current == widget) return gtk_widget_get_first_child (widget); + else + return gtk_widget_get_next_sibling (current); } else { - if (child) - return gtk_widget_get_prev_sibling (child); - else + if (current == NULL) return gtk_widget_get_last_child (widget); + else if (current == widget) + return NULL; + else + { + current = gtk_widget_get_prev_sibling (current); + if (current) + return current; + else + return widget; + } } } @@ -168,76 +245,53 @@ gtk_column_view_row_widget_focus (GtkWidget *widget, GtkDirectionType direction) { GtkColumnViewRowWidget *self = GTK_COLUMN_VIEW_ROW_WIDGET (widget); - GtkWidget *child, *focus_child; + GtkWidget *child, *current; GtkColumnView *view; - /* The idea of this function is the following: - * 1. If any child can take focus, do not ever attempt - * to take focus. - * 2. Otherwise, if this item is selectable or activatable, - * allow focusing this widget. - * - * This makes sure every item in a list is focusable for - * activation and selection handling, but no useless widgets - * get focused and moving focus is as fast as possible. - */ - - focus_child = gtk_widget_get_focus_child (widget); - if (focus_child && gtk_widget_child_focus (focus_child, direction)) - return TRUE; + current = gtk_widget_get_focus_child (widget); view = gtk_column_view_row_widget_get_column_view (self); if (gtk_column_view_get_tab_behavior (view) == GTK_LIST_TAB_CELL && (direction == GTK_DIR_TAB_FORWARD || direction == GTK_DIR_TAB_BACKWARD)) { - if (focus_child || gtk_widget_is_focus (widget)) + if (current || gtk_widget_is_focus (widget)) return FALSE; } - if (focus_child == NULL) + if (current == NULL) { GtkColumnViewColumn *focus_column = gtk_column_view_get_focus_column (view); if (focus_column) { - focus_child = gtk_column_view_row_widget_find_child (self, focus_column); - if (focus_child && gtk_widget_child_focus (focus_child, direction)) + current = gtk_column_view_row_widget_find_child (self, focus_column); + if (current && gtk_widget_child_focus (current, direction)) return TRUE; } } - for (child = gtk_column_view_next_focus_widget (widget, focus_child, direction); + if (gtk_widget_is_focus (widget)) + current = widget; + + for (child = gtk_column_view_next_focus_widget (widget, current, direction); child; child = gtk_column_view_next_focus_widget (widget, child, direction)) { - if (gtk_widget_child_focus (child, direction)) - return TRUE; - } - - switch (direction) - { - case GTK_DIR_TAB_FORWARD: - case GTK_DIR_TAB_BACKWARD: - gtk_column_view_set_focus_column (view, NULL); - break; - - case GTK_DIR_LEFT: - case GTK_DIR_RIGHT: - return TRUE; - - default: - g_assert_not_reached (); - case GTK_DIR_UP: - case GTK_DIR_DOWN: - break; + if (child == widget) + { + if (gtk_widget_grab_focus_self (widget)) + { + gtk_column_view_set_focus_column (view, NULL); + return TRUE; + } + } + else if (child) + { + if (gtk_widget_child_focus (child, direction)) + return TRUE; + } } - if (focus_child) - return FALSE; - - if (gtk_widget_is_focus (widget)) - return FALSE; - - return gtk_widget_grab_focus (widget); + return FALSE; } static gboolean @@ -259,6 +313,12 @@ gtk_column_view_row_widget_grab_focus (GtkWidget *widget) else focus_child = NULL; + if (gtk_widget_grab_focus_self (widget)) + { + gtk_column_view_set_focus_column (view, NULL); + return TRUE; + } + for (child = focus_child ? gtk_widget_get_next_sibling (focus_child) : gtk_widget_get_first_child (widget); child != focus_child; child = child ? gtk_widget_get_next_sibling (child) : gtk_widget_get_first_child (widget)) @@ -272,10 +332,7 @@ gtk_column_view_row_widget_grab_focus (GtkWidget *widget) return TRUE; } - if (!gtk_list_factory_widget_get_selectable (GTK_LIST_FACTORY_WIDGET (widget))) - return FALSE; - - return GTK_WIDGET_CLASS (gtk_column_view_row_widget_parent_class)->grab_focus (widget); + return FALSE; } static void @@ -294,56 +351,17 @@ gtk_column_view_row_widget_set_focus_child (GtkWidget *widget, } static void -gtk_column_view_row_widget_root (GtkWidget *widget) +gtk_column_view_row_widget_dispose (GObject *object) { - GtkColumnViewRowWidget *self = GTK_COLUMN_VIEW_ROW_WIDGET (widget); - - GTK_WIDGET_CLASS (gtk_column_view_row_widget_parent_class)->root (widget); - - if (!gtk_column_view_row_widget_is_header (self)) - { - GtkListItemBase *base = GTK_LIST_ITEM_BASE (self); - GListModel *columns; - guint i; - - columns = gtk_column_view_get_columns (gtk_column_view_row_widget_get_column_view (self)); - - for (i = 0; i < g_list_model_get_n_items (columns); i++) - { - GtkColumnViewColumn *column = g_list_model_get_item (columns, i); - - if (gtk_column_view_column_get_visible (column)) - { - GtkWidget *cell; - - cell = gtk_column_view_cell_new (column); - gtk_column_view_row_widget_add_child (self, cell); - gtk_list_item_base_update (GTK_LIST_ITEM_BASE (cell), - gtk_list_item_base_get_position (base), - gtk_list_item_base_get_item (base), - gtk_list_item_base_get_selected (base)); - } - - g_object_unref (column); - } - } -} - -static void -gtk_column_view_row_widget_unroot (GtkWidget *widget) -{ - GtkColumnViewRowWidget *self = GTK_COLUMN_VIEW_ROW_WIDGET (widget); + GtkColumnViewRowWidget *self = GTK_COLUMN_VIEW_ROW_WIDGET (object); GtkWidget *child; - if (!gtk_column_view_row_widget_is_header (self)) + while ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) { - while ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) - { - gtk_column_view_row_widget_remove_child (self, child); - } + gtk_column_view_row_widget_remove_child (self, child); } - GTK_WIDGET_CLASS (gtk_column_view_row_widget_parent_class)->unroot (widget); + G_OBJECT_CLASS (gtk_column_view_row_widget_parent_class)->dispose (object); } static void @@ -479,8 +497,15 @@ add_arrow_bindings (GtkWidgetClass *widget_class, static void gtk_column_view_row_widget_class_init (GtkColumnViewRowWidgetClass *klass) { + GtkListFactoryWidgetClass *factory_class = GTK_LIST_FACTORY_WIDGET_CLASS (klass); GtkListItemBaseClass *base_class = GTK_LIST_ITEM_BASE_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + factory_class->create_object = gtk_column_view_row_widget_create_object; + factory_class->setup_object = gtk_column_view_row_widget_setup_object; + factory_class->update_object = gtk_column_view_row_widget_update_object; + factory_class->teardown_object = gtk_column_view_row_widget_teardown_object; base_class->update = gtk_column_view_row_widget_update; @@ -489,8 +514,8 @@ gtk_column_view_row_widget_class_init (GtkColumnViewRowWidgetClass *klass) widget_class->set_focus_child = gtk_column_view_row_widget_set_focus_child; widget_class->measure = gtk_column_view_row_widget_measure; widget_class->size_allocate = gtk_column_view_row_widget_allocate; - widget_class->root = gtk_column_view_row_widget_root; - widget_class->unroot = gtk_column_view_row_widget_unroot; + + object_class->dispose = gtk_column_view_row_widget_dispose; add_arrow_bindings (widget_class, GDK_KEY_Left, GTK_DIR_LEFT); add_arrow_bindings (widget_class, GDK_KEY_Right, GTK_DIR_RIGHT); @@ -507,9 +532,11 @@ gtk_column_view_row_widget_init (GtkColumnViewRowWidget *self) } GtkWidget * -gtk_column_view_row_widget_new (gboolean is_header) +gtk_column_view_row_widget_new (GtkListItemFactory *factory, + gboolean is_header) { return g_object_new (GTK_TYPE_COLUMN_VIEW_ROW_WIDGET, + "factory", factory, "css-name", is_header ? "header" : "row", "selectable", TRUE, "activatable", TRUE, diff --git a/gtk/gtkcolumnviewrowwidgetprivate.h b/gtk/gtkcolumnviewrowwidgetprivate.h index 5cc4aa1dad..4174fb8601 100644 --- a/gtk/gtkcolumnviewrowwidgetprivate.h +++ b/gtk/gtkcolumnviewrowwidgetprivate.h @@ -45,7 +45,8 @@ struct _GtkColumnViewRowWidgetClass GType gtk_column_view_row_widget_get_type (void) G_GNUC_CONST; -GtkWidget * gtk_column_view_row_widget_new (gboolean is_header); +GtkWidget * gtk_column_view_row_widget_new (GtkListItemFactory *factory, + gboolean is_header); void gtk_column_view_row_widget_add_child (GtkColumnViewRowWidget *self, GtkWidget *child); diff --git a/gtk/gtkcolumnviewtitle.c b/gtk/gtkcolumnviewtitle.c index 6c7f729f95..43ae56224f 100644 --- a/gtk/gtkcolumnviewtitle.c +++ b/gtk/gtkcolumnviewtitle.c @@ -24,6 +24,8 @@ #include "gtkcolumnviewprivate.h" #include "gtkcolumnviewcolumnprivate.h" #include "gtkcolumnviewsorterprivate.h" +#include "gtkcssboxesprivate.h" +#include "gtkcssnodeprivate.h" #include "gtkprivate.h" #include "gtklabel.h" #include "gtkwidgetprivate.h" @@ -32,8 +34,6 @@ #include "gtkgestureclick.h" #include "gtkpopovermenu.h" #include "gtknative.h" -#include "gtkcssnodeprivate.h" -#include "gtkcssnumbervalueprivate.h" struct _GtkColumnViewTitle { @@ -55,34 +55,19 @@ struct _GtkColumnViewTitleClass G_DEFINE_TYPE (GtkColumnViewTitle, gtk_column_view_title, GTK_TYPE_WIDGET) static int -get_number (GtkCssValue *value) -{ - double d = _gtk_css_number_value_get (value, 100); - - if (d < 1) - return ceil (d); - else - return floor (d); -} - -static int unadjust_width (GtkWidget *widget, int width) { - GtkCssStyle *style; - int widget_margins; - int css_extra; - - style = gtk_css_node_get_style (gtk_widget_get_css_node (widget)); - css_extra = get_number (style->size->margin_left) + - get_number (style->size->margin_right) + - get_number (style->border->border_left_width) + - get_number (style->border->border_right_width) + - get_number (style->size->padding_left) + - get_number (style->size->padding_right); - widget_margins = widget->priv->margin.left + widget->priv->margin.right; - - return MAX (0, width - widget_margins - css_extra); + GtkCssBoxes boxes; + + if (width <= -1) + return -1; + + gtk_css_boxes_init_border_box (&boxes, + gtk_css_node_get_style (gtk_widget_get_css_node (widget)), + 0, 0, + width, 100000); + return MAX (0, floor (gtk_css_boxes_get_content_rect (&boxes)->size.width)); } static void @@ -96,19 +81,24 @@ gtk_column_view_title_measure (GtkWidget *widget, { GtkColumnViewTitle *self = GTK_COLUMN_VIEW_TITLE (widget); GtkWidget *child = gtk_widget_get_first_child (widget); - int fixed_width = gtk_column_view_column_get_fixed_width (self->column); - int unadj_width; + int fixed_width, unadj_width; + fixed_width = gtk_column_view_column_get_fixed_width (self->column); unadj_width = unadjust_width (widget, fixed_width); if (orientation == GTK_ORIENTATION_VERTICAL) { if (fixed_width > -1) { + int min; + if (for_size == -1) for_size = unadj_width; else for_size = MIN (for_size, unadj_width); + + gtk_widget_measure (child, GTK_ORIENTATION_HORIZONTAL, -1, &min, NULL, NULL, NULL); + for_size = MAX (for_size, min); } } diff --git a/gtk/gtkfilechooserwidget.c b/gtk/gtkfilechooserwidget.c index ace31f4bd8..cc7136f156 100644 --- a/gtk/gtkfilechooserwidget.c +++ b/gtk/gtkfilechooserwidget.c @@ -28,6 +28,7 @@ #include "gtkdropdown.h" #include "gtkcolumnview.h" #include "gtkcolumnviewcolumn.h" +#include "gtkcolumnviewrow.h" #include "gtkcssnumbervalueprivate.h" #include "gtkdroptarget.h" #include "gtkentry.h" @@ -2023,6 +2024,20 @@ column_view_get_file_type (GtkListItem *item, return get_type_information (impl, info); } +static void +column_view_row_bind (GtkListItemFactory *factory, + GtkColumnViewRow *row, + gpointer unused) +{ + GFileInfo *info; + gboolean selectable; + + info = gtk_column_view_row_get_item (row); + selectable = g_file_info_get_attribute_boolean (info, "filechooser::selectable"); + + gtk_column_view_row_set_selectable (row, selectable); +} + static char * file_chooser_get_location (GtkFileChooserWidget *impl, GFileInfo *info) @@ -6798,8 +6813,8 @@ gtk_file_chooser_widget_class_init (GtkFileChooserWidgetClass *class) gtk_widget_class_bind_template_child (widget_class, GtkFileChooserWidget, column_view_location_column); gtk_widget_class_bind_template_child (widget_class, GtkFileChooserWidget, column_view_size_column); gtk_widget_class_bind_template_child (widget_class, GtkFileChooserWidget, column_view_time_column); - gtk_widget_class_bind_template_child (widget_class, GtkFileChooserWidget, column_view_type_column); gtk_widget_class_bind_template_child (widget_class, GtkFileChooserWidget, filter_combo_hbox); + gtk_widget_class_bind_template_child (widget_class, GtkFileChooserWidget, column_view_type_column); gtk_widget_class_bind_template_child (widget_class, GtkFileChooserWidget, filter_combo); gtk_widget_class_bind_template_child (widget_class, GtkFileChooserWidget, extra_align); gtk_widget_class_bind_template_child (widget_class, GtkFileChooserWidget, extra_and_filters); @@ -6840,6 +6855,7 @@ gtk_file_chooser_widget_class_init (GtkFileChooserWidgetClass *class) gtk_widget_class_bind_template_callback (widget_class, column_view_get_location); gtk_widget_class_bind_template_callback (widget_class, column_view_get_size); gtk_widget_class_bind_template_callback (widget_class, column_view_get_tooltip_text); + gtk_widget_class_bind_template_callback (widget_class, column_view_row_bind); gtk_widget_class_set_css_name (widget_class, I_("filechooser")); diff --git a/gtk/gtklistitem.c b/gtk/gtklistitem.c index a44752a4da..267b9ea6c5 100644 --- a/gtk/gtklistitem.c +++ b/gtk/gtklistitem.c @@ -21,6 +21,7 @@ #include "gtklistitemprivate.h" +#include "gtkcolumnviewcell.h" /** * GtkListItem: @@ -41,16 +42,12 @@ * The [property@Gtk.ListItem:item] property is not %NULL. */ -struct _GtkListItemClass -{ - GObjectClass parent_class; -}; - enum { PROP_0, PROP_ACTIVATABLE, PROP_CHILD, + PROP_FOCUSABLE, PROP_ITEM, PROP_POSITION, PROP_SELECTABLE, @@ -92,6 +89,10 @@ gtk_list_item_get_property (GObject *object, g_value_set_object (value, self->child); break; + case PROP_FOCUSABLE: + g_value_set_boolean (value, self->focusable); + break; + case PROP_ITEM: if (self->owner) g_value_set_object (value, gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (self->owner))); @@ -139,6 +140,10 @@ gtk_list_item_set_property (GObject *object, gtk_list_item_set_child (self, g_value_get_object (value)); break; + case PROP_FOCUSABLE: + gtk_list_item_set_focusable (self, g_value_get_boolean (value)); + break; + case PROP_SELECTABLE: gtk_list_item_set_selectable (self, g_value_get_boolean (value)); break; @@ -179,6 +184,18 @@ gtk_list_item_class_init (GtkListItemClass *klass) G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** + * GtkListItem:focusable: (attributes org.gtk.Property.get=gtk_list_item_get_focusable org.gtk.Property.set=gtk_list_item_set_focusable) + * + * If the item can be focused with the keyboard. + * + * Since: 4.12 + */ + properties[PROP_FOCUSABLE] = + g_param_spec_boolean ("focusable", NULL, NULL, + TRUE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** * GtkListItem:item: (attributes org.gtk.Property.get=gtk_list_item_get_item) * * Displayed item. @@ -226,6 +243,7 @@ gtk_list_item_init (GtkListItem *self) { self->selectable = TRUE; self->activatable = TRUE; + self->focusable = TRUE; } GtkListItem * @@ -265,10 +283,12 @@ gtk_list_item_get_item (GtkListItem *self) { g_return_val_if_fail (GTK_IS_LIST_ITEM (self), NULL); - if (self->owner == NULL) + if (self->owner) + return gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (self->owner)); + else if (GTK_IS_COLUMN_VIEW_CELL (self)) + return gtk_column_view_cell_get_item (GTK_COLUMN_VIEW_CELL (self)); + else return NULL; - - return gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (self->owner)); } /** @@ -285,6 +305,9 @@ gtk_list_item_get_child (GtkListItem *self) { g_return_val_if_fail (GTK_IS_LIST_ITEM (self), NULL); + if (GTK_IS_COLUMN_VIEW_CELL (self)) + return gtk_column_view_cell_get_child (GTK_COLUMN_VIEW_CELL (self)); + return self->child; } @@ -306,6 +329,12 @@ gtk_list_item_set_child (GtkListItem *self, g_return_if_fail (GTK_IS_LIST_ITEM (self)); g_return_if_fail (child == NULL || gtk_widget_get_parent (child) == NULL); + if (GTK_IS_COLUMN_VIEW_CELL (self)) + { + gtk_column_view_cell_set_child (GTK_COLUMN_VIEW_CELL (self), child); + return; + } + if (self->child == child) return; @@ -315,6 +344,12 @@ gtk_list_item_set_child (GtkListItem *self, { g_object_ref_sink (child); self->child = child; + + /* Workaround that hopefully achieves good enough backwards + * compatibility with people using expanders. + */ + if (!self->focusable_set) + gtk_list_item_set_focusable (self, !gtk_widget_get_focusable (child)); } if (self->owner) @@ -338,10 +373,12 @@ gtk_list_item_get_position (GtkListItem *self) { g_return_val_if_fail (GTK_IS_LIST_ITEM (self), GTK_INVALID_LIST_POSITION); - if (self->owner == NULL) + if (self->owner) + return gtk_list_item_base_get_position (GTK_LIST_ITEM_BASE (self->owner)); + else if (GTK_IS_COLUMN_VIEW_CELL (self)) + return gtk_column_view_cell_get_position (GTK_COLUMN_VIEW_CELL (self)); + else return GTK_INVALID_LIST_POSITION; - - return gtk_list_item_base_get_position (GTK_LIST_ITEM_BASE (self->owner)); } /** @@ -360,10 +397,12 @@ gtk_list_item_get_selected (GtkListItem *self) { g_return_val_if_fail (GTK_IS_LIST_ITEM (self), FALSE); - if (self->owner == NULL) + if (self->owner) + return gtk_list_item_base_get_selected (GTK_LIST_ITEM_BASE (self->owner)); + else if (GTK_IS_COLUMN_VIEW_CELL (self)) + return gtk_column_view_cell_get_selected (GTK_COLUMN_VIEW_CELL (self)); + else return FALSE; - - return gtk_list_item_base_get_selected (GTK_LIST_ITEM_BASE (self->owner)); } /** @@ -468,3 +507,58 @@ gtk_list_item_set_activatable (GtkListItem *self, g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ACTIVATABLE]); } + +/** + * gtk_list_item_get_focusable: (attributes org.gtk.Method.get_property=focusable) + * @self: a `GtkListItem` + * + * Checks if a list item has been set to be focusable via + * gtk_list_item_set_focusable(). + * + * Returns: %TRUE if the item is focusable + * + * Since: 4.12 + */ +gboolean +gtk_list_item_get_focusable (GtkListItem *self) +{ + g_return_val_if_fail (GTK_IS_LIST_ITEM (self), FALSE); + + return self->focusable; +} + +/** + * gtk_list_item_set_focusable: (attributes org.gtk.Method.set_property=focusable) + * @self: a `GtkListItem` + * @focusable: if the item should be focusable + * + * Sets @self to be focusable. + * + * If an item is focusable, it can be focused using the keyboard. + * This works similar to [method@Gtk.Widget.set_focusable]. + * + * Note that if items are not focusable, the keyboard cannot be used to activate + * them and selecting only works if one of the listitem's children is focusable. + * + * By default, list items are focusable. + * + * Since: 4.12 + */ +void +gtk_list_item_set_focusable (GtkListItem *self, + gboolean focusable) +{ + g_return_if_fail (GTK_IS_LIST_ITEM (self)); + + self->focusable_set = TRUE; + + if (self->focusable == focusable) + return; + + self->focusable = focusable; + + if (self->owner) + gtk_widget_set_focusable (GTK_WIDGET (self->owner), focusable); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FOCUSABLE]); +} diff --git a/gtk/gtklistitem.h b/gtk/gtklistitem.h index f66da44d19..9c5fb844e1 100644 --- a/gtk/gtklistitem.h +++ b/gtk/gtklistitem.h @@ -27,18 +27,9 @@ G_BEGIN_DECLS -#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; - +#define GTK_TYPE_LIST_ITEM (gtk_list_item_get_type ()) GDK_AVAILABLE_IN_ALL -GType gtk_list_item_get_type (void) G_GNUC_CONST; +GDK_DECLARE_INTERNAL_TYPE (GtkListItem, gtk_list_item, GTK, LIST_ITEM, GObject) GDK_AVAILABLE_IN_ALL gpointer gtk_list_item_get_item (GtkListItem *self); @@ -56,6 +47,11 @@ gboolean gtk_list_item_get_activatable (GtkListItem GDK_AVAILABLE_IN_ALL void gtk_list_item_set_activatable (GtkListItem *self, gboolean activatable); +GDK_AVAILABLE_IN_4_12 +gboolean gtk_list_item_get_focusable (GtkListItem *self) G_GNUC_PURE; +GDK_AVAILABLE_IN_4_12 +void gtk_list_item_set_focusable (GtkListItem *self, + gboolean focusable); GDK_AVAILABLE_IN_ALL void gtk_list_item_set_child (GtkListItem *self, @@ -63,7 +59,5 @@ void gtk_list_item_set_child (GtkListItem GDK_AVAILABLE_IN_ALL GtkWidget * gtk_list_item_get_child (GtkListItem *self); -G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkListItem, g_object_unref) - G_END_DECLS diff --git a/gtk/gtklistitemprivate.h b/gtk/gtklistitemprivate.h index af56b37a37..60e122bc10 100644 --- a/gtk/gtklistitemprivate.h +++ b/gtk/gtklistitemprivate.h @@ -22,6 +22,8 @@ #include "gtklistitem.h" #include "gtklistitemwidgetprivate.h" +#include "gtkcolumnviewcellwidgetprivate.h" +#include "gtkversion.h" G_BEGIN_DECLS @@ -35,6 +37,15 @@ struct _GtkListItem guint activatable : 1; guint selectable : 1; + guint focusable : 1; +#if !GTK_CHECK_VERSION (5, 0, 0) + guint focusable_set : 1; +#endif +}; + +struct _GtkListItemClass +{ + GObjectClass parent_class; }; GtkListItem * gtk_list_item_new (void); diff --git a/gtk/gtklistitemwidget.c b/gtk/gtklistitemwidget.c index ccd56c1b3a..b5fac6f7b8 100644 --- a/gtk/gtklistitemwidget.c +++ b/gtk/gtklistitemwidget.c @@ -28,6 +28,7 @@ #include "gtklistitemprivate.h" #include "gtklistbaseprivate.h" #include "gtkwidget.h" +#include "gtkwidgetprivate.h" G_DEFINE_TYPE (GtkListItemWidget, gtk_list_item_widget, GTK_TYPE_LIST_FACTORY_WIDGET) @@ -35,59 +36,65 @@ static gboolean gtk_list_item_widget_focus (GtkWidget *widget, GtkDirectionType direction) { - GtkWidget *child, *focus_child; - - /* The idea of this function is the following: - * 1. If any child can take focus, do not ever attempt - * to take focus. - * 2. Otherwise, if this item is selectable or activatable, - * allow focusing this widget. - * - * This makes sure every item in a list is focusable for - * activation and selection handling, but no useless widgets - * get focused and moving focus is as fast as possible. - */ - - focus_child = gtk_widget_get_focus_child (widget); - if (focus_child && gtk_widget_child_focus (focus_child, direction)) - return TRUE; + GtkWidget *child = gtk_widget_get_first_child (widget); - for (child = focus_child ? gtk_widget_get_next_sibling (focus_child) - : gtk_widget_get_first_child (widget); - child; - child = gtk_widget_get_next_sibling (child)) + if (gtk_widget_get_focus_child (widget)) { - if (gtk_widget_child_focus (child, direction)) - return TRUE; + /* focus is in the child */ + if (direction == GTK_DIR_TAB_BACKWARD) + return gtk_widget_grab_focus_self (widget); + else + return FALSE; + } + else if (gtk_widget_is_focus (widget)) + { + /* The widget has focus */ + if (direction == GTK_DIR_TAB_FORWARD) + { + if (child) + return gtk_widget_child_focus (child, direction); + } + + return FALSE; + } + else + { + /* focus coming in from the outside */ + if (direction == GTK_DIR_TAB_BACKWARD) + { + if (child && + gtk_widget_child_focus (child, direction)) + return TRUE; + + return gtk_widget_grab_focus_self (widget); + } + else + { + if (gtk_widget_grab_focus_self (widget)) + return TRUE; + + if (child && + gtk_widget_child_focus (child, direction)) + return TRUE; + + return FALSE; + } } - - if (focus_child) - return FALSE; - - if (gtk_widget_is_focus (widget)) - return FALSE; - - return gtk_widget_grab_focus (widget); } static gboolean gtk_list_item_widget_grab_focus (GtkWidget *widget) { - GtkListItemWidget *self = GTK_LIST_ITEM_WIDGET (widget); GtkWidget *child; - for (child = gtk_widget_get_first_child (widget); - child; - child = gtk_widget_get_next_sibling (child)) - { - if (gtk_widget_grab_focus (child)) - return TRUE; - } + if (GTK_WIDGET_CLASS (gtk_list_item_widget_parent_class)->grab_focus (widget)) + return TRUE; - if (!gtk_list_factory_widget_get_selectable (GTK_LIST_FACTORY_WIDGET (self))) - return FALSE; + child = gtk_widget_get_first_child (widget); + if (child && gtk_widget_grab_focus (child)) + return TRUE; - return GTK_WIDGET_CLASS (gtk_list_item_widget_parent_class)->grab_focus (widget); + return FALSE; } static gpointer @@ -111,6 +118,7 @@ gtk_list_item_widget_setup_object (GtkListFactoryWidget *fw, gtk_list_factory_widget_set_activatable (fw, list_item->activatable); gtk_list_factory_widget_set_selectable (fw, list_item->selectable); + gtk_widget_set_focusable (GTK_WIDGET (self), list_item->focusable); gtk_list_item_do_notify (list_item, gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (self)) != NULL, @@ -133,6 +141,7 @@ gtk_list_item_widget_teardown_object (GtkListFactoryWidget *fw, gtk_list_factory_widget_set_activatable (fw, FALSE); gtk_list_factory_widget_set_selectable (fw, FALSE); + gtk_widget_set_focusable (GTK_WIDGET (self), TRUE); gtk_list_item_do_notify (list_item, gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (self)) != NULL, diff --git a/gtk/gtktreeexpander.c b/gtk/gtktreeexpander.c index 86230e4028..66a520b95c 100644 --- a/gtk/gtktreeexpander.c +++ b/gtk/gtktreeexpander.c @@ -45,6 +45,11 @@ * "listitem.toggle-expand" actions are provided to allow adding custom * UI for managing expanded state. * + * It is important to mention that you want to set the + * [property@Gtk.ListItem:focusable] property to FALSE when using this + * widget, as you want the keyboard focus to be in the treexpander, and not + * inside the list to make use of the keybindings. + * * The `GtkTreeListModel` must be set to not be passthrough. Then it * will provide [class@Gtk.TreeListRow] items which can be set via * [method@Gtk.TreeExpander.set_list_row] on the expander. diff --git a/gtk/meson.build b/gtk/meson.build index bfe69528a3..b3975fb3fc 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -38,7 +38,7 @@ gtk_private_sources = files([ 'gtkcolorpickershell.c', 'gtkcolorscale.c', 'gtkcolorswatch.c', - 'gtkcolumnviewcell.c', + 'gtkcolumnviewcellwidget.c', 'gtkcolumnviewrowwidget.c', 'gtkcolumnviewtitle.c', 'gtkconstraintexpression.c', @@ -187,7 +187,9 @@ gtk_public_sources = files([ 'gtkcolordialogbutton.c', 'gtkcolorutils.c', 'gtkcolumnview.c', + 'gtkcolumnviewcell.c', 'gtkcolumnviewcolumn.c', + 'gtkcolumnviewrow.c', 'gtkcolumnviewsorter.c', 'gtkcomposetable.c', 'gtkconstraintguide.c', @@ -440,7 +442,9 @@ gtk_public_headers = files([ 'gtkcolordialogbutton.h', 'gtkcolorutils.h', 'gtkcolumnview.h', + 'gtkcolumnviewcell.h', 'gtkcolumnviewcolumn.h', + 'gtkcolumnviewrow.h', 'gtkcolumnviewsorter.h', 'gtkconstraintguide.h', 'gtkconstraintlayout.h', diff --git a/gtk/ui/gtkfilechooserwidget.ui b/gtk/ui/gtkfilechooserwidget.ui index 41f03a6def..71a0d1d258 100644 --- a/gtk/ui/gtkfilechooserwidget.ui +++ b/gtk/ui/gtkfilechooserwidget.ui @@ -169,6 +169,11 @@ <style> <class name="complex"/> </style> + <property name="row-factory"> + <object class="GtkSignalListItemFactory"> + <signal name="bind" handler="column_view_row_bind" swapped="no"/> + </object> + </property> <signal name="activate" handler="browse_files_view_row_activated_cb" swapped="no"/> <signal name="keynav-failed" handler="browse_files_view_keynav_failed_cb"/> <child> @@ -181,18 +186,18 @@ <property name="bytes"><![CDATA[ <?xml version="1.0" encoding="UTF-8"?> <interface> - <template class="GtkListItem"> + <template class="GtkColumnViewCell"> <property name="child"> <object class="GtkFileChooserCell"> <binding name="item"> - <lookup name="item">GtkListItem</lookup> + <lookup name="item">GtkColumnViewCell</lookup> </binding> - <property name="list-item">GtkListItem</property> + <property name="list-item">GtkColumnViewCell</property> <child> <object class="GtkBox"> <binding name="tooltip-text"> <closure type="gchararray" function="column_view_get_tooltip_text"> - <lookup name="item">GtkListItem</lookup> + <lookup name="item">GtkColumnViewCell</lookup> </closure> </binding> <child> @@ -200,7 +205,7 @@ <property name="margin-start">6</property> <property name="margin-end">6</property> <binding name="file-info"> - <lookup name="item">GtkListItem</lookup> + <lookup name="item">GtkColumnViewCell</lookup> </binding> </object> </child> @@ -211,7 +216,7 @@ <property name="min-chars">10</property> <binding name="text"> <closure type="gchararray" function="column_view_get_file_display_name"> - <lookup name="item">GtkListItem</lookup> + <lookup name="item">GtkColumnViewCell</lookup> </closure> </binding> </object> @@ -239,13 +244,13 @@ <property name="bytes"><![CDATA[ <?xml version="1.0" encoding="UTF-8"?> <interface> - <template class="GtkListItem"> + <template class="GtkColumnViewCell"> <property name="child"> <object class="GtkFileChooserCell"> <binding name="item"> - <lookup name="item">GtkListItem</lookup> + <lookup name="item">GtkColumnViewCell</lookup> </binding> - <property name="list-item">GtkListItem</property> + <property name="list-item">GtkColumnViewCell</property> <child> <object class="GtkInscription"> <property name="hexpand">1</property> @@ -255,12 +260,12 @@ <property name="margin-end">6</property> <binding name="text"> <closure type="gchararray" function="column_view_get_location"> - <lookup name="item">GtkListItem</lookup> + <lookup name="item">GtkColumnViewCell</lookup> </closure> </binding> <binding name="tooltip-text"> <closure type="gchararray" function="column_view_get_tooltip_text"> - <lookup name="item">GtkListItem</lookup> + <lookup name="item">GtkColumnViewCell</lookup> </closure> </binding> </object> @@ -283,25 +288,25 @@ <property name="bytes"><![CDATA[ <?xml version="1.0" encoding="UTF-8"?> <interface> - <template class="GtkListItem"> + <template class="GtkColumnViewCell"> <property name="child"> <object class="GtkFileChooserCell"> <binding name="item"> - <lookup name="item">GtkListItem</lookup> + <lookup name="item">GtkColumnViewCell</lookup> </binding> - <property name="list-item">GtkListItem</property> + <property name="list-item">GtkColumnViewCell</property> <child> <object class="GtkLabel"> <property name="hexpand">1</property> <property name="xalign">0</property> <binding name="label"> <closure type="gchararray" function="column_view_get_size"> - <lookup name="item">GtkListItem</lookup> + <lookup name="item">GtkColumnViewCell</lookup> </closure> </binding> <binding name="tooltip-text"> <closure type="gchararray" function="column_view_get_tooltip_text"> - <lookup name="item">GtkListItem</lookup> + <lookup name="item">GtkColumnViewCell</lookup> </closure> </binding> </object> @@ -366,26 +371,26 @@ <property name="bytes"><![CDATA[ <?xml version="1.0" encoding="UTF-8"?> <interface> - <template class="GtkListItem"> + <template class="GtkColumnViewCell"> <property name="child"> <object class="GtkFileChooserCell" id="file_chooser_cell"> <binding name="item"> - <lookup name="item">GtkListItem</lookup> + <lookup name="item">GtkColumnViewCell</lookup> </binding> - <property name="list-item">GtkListItem</property> + <property name="list-item">GtkColumnViewCell</property> <child> <object class="GtkBox"> <property name="spacing">6</property> <binding name="tooltip-text"> <closure type="gchararray" function="column_view_get_tooltip_text"> - <lookup name="item">GtkListItem</lookup> + <lookup name="item">GtkColumnViewCell</lookup> </closure> </binding> <child> <object class="GtkLabel"> <binding name="label"> <closure type="gchararray" function="column_view_get_file_date"> - <lookup name="item">GtkListItem</lookup> + <lookup name="item">GtkColumnViewCell</lookup> </closure> </binding> </object> @@ -395,7 +400,7 @@ <property name="visible" bind-source="file_chooser_cell" bind-property="show-time" bind-flags="sync-create"/> <binding name="label"> <closure type="gchararray" function="column_view_get_file_time"> - <lookup name="item">GtkListItem</lookup> + <lookup name="item">GtkColumnViewCell</lookup> </closure> </binding> </object> diff --git a/po/POTFILES.in b/po/POTFILES.in index 1d87546711..2fe8f10bf5 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -142,7 +142,7 @@ gtk/gtkcolorplane.c gtk/gtkcolorscale.c gtk/gtkcolorswatch.c gtk/gtkcolumnview.c -gtk/gtkcolumnviewcell.c +gtk/gtkcolumnviewcellwidget.c gtk/gtkcolumnviewcolumn.c gtk/gtkcolumnviewtitle.c gtk/gtkconstraint.c diff --git a/tests/testcolumnview.c b/tests/testcolumnview.c index b09888de8e..932de560aa 100644 --- a/tests/testcolumnview.c +++ b/tests/testcolumnview.c @@ -423,11 +423,11 @@ const char *ui_file = " <property name='bytes'><![CDATA[\n" "<?xml version='1.0' encoding='UTF-8'?>\n" "<interface>\n" -" <template class='GtkListItem'>\n" +" <template class='GtkColumnViewCell'>\n" " <property name='child'>\n" " <object class='GtkTreeExpander' id='expander'>\n" " <binding name='list-row'>\n" -" <lookup name='item'>GtkListItem</lookup>\n" +" <lookup name='item'>GtkColumnViewCell</lookup>\n" " </binding>\n" " <property name='child'>\n" " <object class='GtkBox'>\n" @@ -477,12 +477,12 @@ const char *ui_file = #define SIMPLE_STRING_FACTORY(attr, type) \ "<?xml version='1.0' encoding='UTF-8'?>\n" \ "<interface>\n" \ -" <template class='GtkListItem'>\n" \ +" <template class='GtkColumnViewCell'>\n" \ " <property name='child'>\n" \ " <object class='GtkInscription'>\n" \ " <binding name='text'>\n" \ " <closure type='gchararray' function='get_string'>\n" \ -" <lookup name='item' type='GtkTreeListRow'><lookup name='item'>GtkListItem</lookup></lookup>\n" \ +" <lookup name='item' type='GtkTreeListRow'><lookup name='item'>GtkColumnViewCell</lookup></lookup>\n" \ " <constant type='gchararray'>" attr "</constant>" \ " </closure>\n" \ " </binding>\n" \ @@ -494,12 +494,12 @@ const char *ui_file = #define BOOLEAN_FACTORY(attr) \ "<?xml version='1.0' encoding='UTF-8'?>\n" \ "<interface>\n" \ -" <template class='GtkListItem'>\n" \ +" <template class='GtkColumnViewCell'>\n" \ " <property name='child'>\n" \ " <object class='GtkCheckButton'>\n" \ " <binding name='active'>\n" \ " <closure type='gboolean' function='get_boolean'>\n" \ -" <lookup name='item' type='GtkTreeListRow'><lookup name='item'>GtkListItem</lookup></lookup>\n" \ +" <lookup name='item' type='GtkTreeListRow'><lookup name='item'>GtkColumnViewCell</lookup></lookup>\n" \ " <constant type='gchararray'>" attr "</constant>" \ " </closure>\n" \ " </binding>\n" \ @@ -511,12 +511,12 @@ const char *ui_file = #define ICON_FACTORY(attr) \ "<?xml version='1.0' encoding='UTF-8'?>\n" \ "<interface>\n" \ -" <template class='GtkListItem'>\n" \ +" <template class='GtkColumnViewCell'>\n" \ " <property name='child'>\n" \ " <object class='GtkImage'>\n" \ " <binding name='gicon'>\n" \ " <closure type='GIcon' function='get_object'>\n" \ -" <lookup name='item' type='GtkTreeListRow'><lookup name='item'>GtkListItem</lookup></lookup>\n" \ +" <lookup name='item' type='GtkTreeListRow'><lookup name='item'>GtkColumnViewCell</lookup></lookup>\n" \ " <constant type='gchararray'>" attr "</constant>" \ " </closure>\n" \ " </binding>\n" \ diff --git a/tests/testdatatable.c b/tests/testdatatable.c index 72de5829e4..6e3ca992d2 100644 --- a/tests/testdatatable.c +++ b/tests/testdatatable.c @@ -5,6 +5,11 @@ #include "frame-stats.h" +static gboolean no_auto_scroll = FALSE; +static gint n_columns = 20; +static double scroll_pages = 0; + + /* This is our dummy item for the model. */ #define DATA_TABLE_TYPE_ITEM (data_table_item_get_type ()) G_DECLARE_FINAL_TYPE (DataTableItem, data_table_item, DATA_TABLE, ITEM, GObject) @@ -48,6 +53,18 @@ set_adjustment_to_fraction (GtkAdjustment *adjustment, fraction * (upper - page_size)); } +static void +move_adjustment_by_pages (GtkAdjustment *adjustment, + double n_pages) +{ + double page_size = gtk_adjustment_get_page_size (adjustment); + double value = gtk_adjustment_get_value (adjustment); + + value += page_size * n_pages; + /* the adjustment will clamp properly */ + gtk_adjustment_set_value (adjustment, value); +} + static gboolean scroll_column_view (GtkWidget *column_view, GdkFrameClock *frame_clock, @@ -57,7 +74,10 @@ scroll_column_view (GtkWidget *column_view, vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (column_view)); - set_adjustment_to_fraction (vadjustment, g_random_double ()); + if (scroll_pages == 0.0) + set_adjustment_to_fraction (vadjustment, g_random_double ()); + else + move_adjustment_by_pages (vadjustment, (g_random_double () * 2 - 1) * scroll_pages); return TRUE; } @@ -172,9 +192,6 @@ parse_widget_arg (const gchar* option_name, } } -static gboolean no_auto_scroll = FALSE; -static gint n_columns = 20; - static GOptionEntry options[] = { { "widget", @@ -203,6 +220,15 @@ static GOptionEntry options[] = { "Column count", "COUNT" }, + { + "pages", + 'p', + G_OPTION_FLAG_NONE, + G_OPTION_ARG_DOUBLE, + &scroll_pages, + "Maximum number of pages to scroll (or 0 for random)", + "COUNT" + }, { NULL } }; |