diff --git a/docs/reference/gtk/meson.build b/docs/reference/gtk/meson.build index 542ddf6a8d..1fa4e7c5e3 100644 --- a/docs/reference/gtk/meson.build +++ b/docs/reference/gtk/meson.build @@ -135,6 +135,7 @@ private_headers = [ 'gtklistitemprivate.h', 'gtklistitemfactoryprivate.h', 'gtklistitemmanagerprivate.h', + 'gtklistitemwidgetprivate.h', 'gtklockbuttonprivate.h', 'gtkmagnifierprivate.h', 'gtkmediafileprivate.h', diff --git a/gtk/gtkbuilderlistitemfactory.c b/gtk/gtkbuilderlistitemfactory.c index 4ce710e1e2..b8a9861e7b 100644 --- a/gtk/gtkbuilderlistitemfactory.c +++ b/gtk/gtkbuilderlistitemfactory.c @@ -69,7 +69,7 @@ gtk_builder_list_item_factory_setup (GtkListItemFactory *factory, if (self->scope) gtk_builder_set_scope (builder, self->scope); - if (!gtk_builder_extend_with_template (builder, GTK_WIDGET (list_item), G_OBJECT_TYPE (list_item), + if (!gtk_builder_extend_with_template (builder, G_OBJECT (list_item), GTK_TYPE_LIST_ITEM, (const gchar *)g_bytes_get_data (self->bytes, NULL), g_bytes_get_size (self->bytes), &error)) diff --git a/gtk/gtklistbase.c b/gtk/gtklistbase.c index fef41dda87..fde6ff498b 100644 --- a/gtk/gtklistbase.c +++ b/gtk/gtklistbase.c @@ -24,6 +24,7 @@ #include "gtkadjustment.h" #include "gtkbindings.h" #include "gtkintl.h" +#include "gtklistitemwidgetprivate.h" #include "gtkorientableprivate.h" #include "gtkscrollable.h" #include "gtksingleselection.h" @@ -750,10 +751,10 @@ gtk_list_base_update_focus_tracker (GtkListBase *self) guint pos; focus_child = gtk_widget_get_focus_child (GTK_WIDGET (self)); - if (!GTK_IS_LIST_ITEM (focus_child)) + if (!GTK_IS_LIST_ITEM_WIDGET (focus_child)) return; - pos = gtk_list_item_get_position (GTK_LIST_ITEM (focus_child)); + pos = gtk_list_item_widget_get_position (GTK_LIST_ITEM_WIDGET (focus_child)); if (pos != gtk_list_item_tracker_get_position (priv->item_manager, priv->focus)) { gtk_list_item_tracker_set_position (priv->item_manager, diff --git a/gtk/gtklistbase.h b/gtk/gtklistbase.h index 7b33edeec7..24e616184c 100644 --- a/gtk/gtklistbase.h +++ b/gtk/gtklistbase.h @@ -25,7 +25,6 @@ #endif #include -#include G_BEGIN_DECLS diff --git a/gtk/gtklistitem.c b/gtk/gtklistitem.c index b13b44843a..ee50eb42ab 100644 --- a/gtk/gtklistitem.c +++ b/gtk/gtklistitem.c @@ -21,15 +21,7 @@ #include "gtklistitemprivate.h" -#include "gtkbindings.h" -#include "gtkbinlayout.h" -#include "gtkcssnodeprivate.h" -#include "gtkeventcontrollerkey.h" -#include "gtkgestureclick.h" #include "gtkintl.h" -#include "gtkmain.h" -#include "gtkwidget.h" -#include "gtkwidgetprivate.h" /** * SECTION:gtklistitem @@ -54,24 +46,9 @@ * The #GtkListItem:item property is not %NULL. */ -struct _GtkListItem -{ - GtkWidget parent_instance; - - GObject *item; - GtkWidget *child; - guint position; - - guint activatable : 1; - guint selectable : 1; - guint selected : 1; -}; - struct _GtkListItemClass { - GtkWidgetClass parent_class; - - void (* activate_signal) (GtkListItem *self); + GObjectClass parent_class; }; enum @@ -87,81 +64,17 @@ enum N_PROPS }; -enum -{ - ACTIVATE_SIGNAL, - LAST_SIGNAL -}; - -G_DEFINE_TYPE (GtkListItem, gtk_list_item, GTK_TYPE_WIDGET) +G_DEFINE_TYPE (GtkListItem, gtk_list_item, G_TYPE_OBJECT) static GParamSpec *properties[N_PROPS] = { NULL, }; -static guint signals[LAST_SIGNAL] = { 0 }; - -static void -gtk_list_item_activate_signal (GtkListItem *self) -{ - if (!self->activatable) - return; - - gtk_widget_activate_action (GTK_WIDGET (self), - "list.activate-item", - "u", - self->position); -} - -static gboolean -gtk_list_item_focus (GtkWidget *widget, - GtkDirectionType direction) -{ - GtkListItem *self = GTK_LIST_ITEM (widget); - - /* 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. - */ - if (self->child) - { - if (gtk_widget_get_focus_child (widget)) - return FALSE; - if (gtk_widget_child_focus (self->child, direction)) - return TRUE; - } - - if (gtk_widget_is_focus (widget)) - return FALSE; - - if (!gtk_widget_get_can_focus (widget) || - !self->selectable) - return FALSE; - - return gtk_widget_grab_focus (widget); -} - -static gboolean -gtk_list_item_grab_focus (GtkWidget *widget) -{ - GtkListItem *self = GTK_LIST_ITEM (widget); - - if (self->child && gtk_widget_grab_focus (self->child)) - return TRUE; - - return GTK_WIDGET_CLASS (gtk_list_item_parent_class)->grab_focus (widget); -} static void gtk_list_item_dispose (GObject *object) { GtkListItem *self = GTK_LIST_ITEM (object); - g_assert (self->item == NULL); - g_clear_pointer (&self->child, gtk_widget_unparent); + g_assert (self->owner == NULL); /* would hold a reference */ + g_clear_object (&self->child); G_OBJECT_CLASS (gtk_list_item_parent_class)->dispose (object); } @@ -234,36 +147,10 @@ gtk_list_item_set_property (GObject *object, } } -static void -gtk_list_item_select_action (GtkWidget *widget, - const char *action_name, - GVariant *parameter) -{ - GtkListItem *self = GTK_LIST_ITEM (widget); - gboolean modify, extend; - - if (!self->selectable) - return; - - g_variant_get (parameter, "(bb)", &modify, &extend); - - gtk_widget_activate_action (GTK_WIDGET (self), - "list.select-item", - "(ubb)", - self->position, modify, extend); -} - static void gtk_list_item_class_init (GtkListItemClass *klass) { - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - GtkBindingSet *binding_set; - - klass->activate_signal = gtk_list_item_activate_signal; - - widget_class->focus = gtk_list_item_focus; - widget_class->grab_focus = gtk_list_item_grab_focus; gobject_class->dispose = gtk_list_item_dispose; gobject_class->get_property = gtk_list_item_get_property; @@ -342,207 +229,25 @@ gtk_list_item_class_init (GtkListItemClass *klass) G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, N_PROPS, properties); - - /** - * GtkListItem::activate-signal: - * - * This is a keybinding signal, which will cause this row to be activated. - * - * Do not use it, it is an implementation detail. - * - * If you want to be notified when the user activates a listitem (by key or not), - * look at the list widget this item is contained in. - */ - signals[ACTIVATE_SIGNAL] = - g_signal_new (I_("activate-keybinding"), - G_OBJECT_CLASS_TYPE (gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (GtkListItemClass, activate_signal), - NULL, NULL, - NULL, - G_TYPE_NONE, 0); - - widget_class->activate_signal = signals[ACTIVATE_SIGNAL]; - - /** - * GtkListItem|listitem.select: - * @modify: %TRUE to toggle the existing selection, %FALSE to select - * @extend: %TRUE to extend the selection - * - * Changes selection if the item is selectable. - * If the item is not selectable, nothing happens. - * - * This function will emit the list.select-item action and the resulting - * behavior, in particular the interpretation of @modify and @extend - * depends on the view containing this listitem. See for example - * GtkListView|list.select-item or GtkGridView|list.select-item. - */ - gtk_widget_class_install_action (widget_class, - "listitem.select", - "(bb)", - gtk_list_item_select_action); - - binding_set = gtk_binding_set_by_class (klass); - - gtk_binding_entry_add_signal (binding_set, GDK_KEY_Return, 0, - "activate-keybinding", 0); - gtk_binding_entry_add_signal (binding_set, GDK_KEY_ISO_Enter, 0, - "activate-keybinding", 0); - gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Enter, 0, - "activate-keybinding", 0); - - /* note that some of these may get overwritten by child widgets, - * such as GtkTreeExpander */ - gtk_binding_entry_add_action (binding_set, GDK_KEY_space, 0, - "listitem.select", "(bb)", TRUE, FALSE); - gtk_binding_entry_add_action (binding_set, GDK_KEY_space, GDK_CONTROL_MASK, - "listitem.select", "(bb)", TRUE, FALSE); - gtk_binding_entry_add_action (binding_set, GDK_KEY_space, GDK_SHIFT_MASK, - "listitem.select", "(bb)", TRUE, FALSE); - gtk_binding_entry_add_action (binding_set, GDK_KEY_space, GDK_CONTROL_MASK | GDK_SHIFT_MASK, - "listitem.select", "(bb)", TRUE, FALSE); - gtk_binding_entry_add_action (binding_set, GDK_KEY_KP_Space, 0, - "listitem.select", "(bb)", TRUE, FALSE); - gtk_binding_entry_add_action (binding_set, GDK_KEY_KP_Space, GDK_CONTROL_MASK, - "listitem.select", "(bb)", TRUE, FALSE); - gtk_binding_entry_add_action (binding_set, GDK_KEY_KP_Space, GDK_SHIFT_MASK, - "listitem.select", "(bb)", TRUE, FALSE); - gtk_binding_entry_add_action (binding_set, GDK_KEY_KP_Space, GDK_CONTROL_MASK | GDK_SHIFT_MASK, - "listitem.select", "(bb)", TRUE, FALSE); - - /* This gets overwritten by gtk_list_item_new() but better safe than sorry */ - gtk_widget_class_set_css_name (widget_class, I_("row")); - gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); -} - -static void -gtk_list_item_click_gesture_pressed (GtkGestureClick *gesture, - int n_press, - double x, - double y, - GtkListItem *self) -{ - GtkWidget *widget = GTK_WIDGET (self); - - if (!self->selectable && !self->activatable) - { - gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED); - return; - } - - if (self->selectable) - { - GdkModifierType state; - GdkModifierType mask; - gboolean extend = FALSE, modify = FALSE; - - if (gtk_get_current_event_state (&state)) - { - mask = gtk_widget_get_modifier_mask (widget, GDK_MODIFIER_INTENT_MODIFY_SELECTION); - if ((state & mask) == mask) - modify = TRUE; - mask = gtk_widget_get_modifier_mask (widget, GDK_MODIFIER_INTENT_EXTEND_SELECTION); - if ((state & mask) == mask) - extend = TRUE; - } - - gtk_widget_activate_action (GTK_WIDGET (self), - "list.select-item", - "(ubb)", - self->position, modify, extend); - } - - if (self->activatable) - { - if (n_press == 2) - { - gtk_widget_activate_action (GTK_WIDGET (self), - "list.activate-item", - "u", - self->position); - } - } - - gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_ACTIVE, FALSE); - - if (gtk_widget_get_focus_on_click (widget)) - gtk_widget_grab_focus (widget); -} - -static void -gtk_list_item_focus_changed_cb (GtkEventControllerKey *controller, - GParamSpec *psepc, - GtkListItem *self) -{ - GtkWidget *widget = GTK_WIDGET (self); - - if (gtk_event_controller_key_contains_focus (controller)) - { - gtk_widget_activate_action (widget, - "list.scroll-to-item", - "u", - self->position); - } -} - -static void -gtk_list_item_click_gesture_released (GtkGestureClick *gesture, - int n_press, - double x, - double y, - GtkListItem *self) -{ - gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE); -} - -static void -gtk_list_item_click_gesture_canceled (GtkGestureClick *gesture, - GdkEventSequence *sequence, - GtkListItem *self) -{ - gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE); } static void gtk_list_item_init (GtkListItem *self) { - GtkEventController *controller; - GtkGesture *gesture; - self->selectable = TRUE; self->activatable = TRUE; - gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE); - - gesture = gtk_gesture_click_new (); - gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), - GTK_PHASE_BUBBLE); - gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), - FALSE); - gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), - GDK_BUTTON_PRIMARY); - g_signal_connect (gesture, "pressed", - G_CALLBACK (gtk_list_item_click_gesture_pressed), self); - g_signal_connect (gesture, "released", - G_CALLBACK (gtk_list_item_click_gesture_released), self); - g_signal_connect (gesture, "cancel", - G_CALLBACK (gtk_list_item_click_gesture_canceled), self); - gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture)); - - controller = gtk_event_controller_key_new (); - g_signal_connect (controller, "notify::contains-focus", G_CALLBACK (gtk_list_item_focus_changed_cb), self); - gtk_widget_add_controller (GTK_WIDGET (self), controller); } GtkListItem * -gtk_list_item_new (const char *css_name) +gtk_list_item_new (GtkListItemWidget *owner) { GtkListItem *result; - g_return_val_if_fail (css_name != NULL, NULL); + g_return_val_if_fail (owner != NULL, NULL); result = g_object_new (GTK_TYPE_LIST_ITEM, - "css-name", css_name, NULL); + result->owner = owner; return result; } @@ -602,12 +307,17 @@ gtk_list_item_set_child (GtkListItem *self, if (self->child == child) return; - g_clear_pointer (&self->child, gtk_widget_unparent); + if (self->child && self->owner) + gtk_list_item_widget_remove_child (self->owner, self->child); + + g_clear_object (&self->child); if (child) { - gtk_widget_insert_after (child, GTK_WIDGET (self), NULL); + g_object_ref_sink (child); self->child = child; + if (self->owner) + gtk_list_item_widget_add_child (self->owner, child); } g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]); @@ -627,8 +337,6 @@ gtk_list_item_set_item (GtkListItem *self, if (item) self->item = g_object_ref (item); - gtk_css_node_invalidate (gtk_widget_get_css_node (GTK_WIDGET (self)), GTK_CSS_CHANGE_ANIMATIONS); - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]); } @@ -692,11 +400,6 @@ gtk_list_item_set_selected (GtkListItem *self, self->selected = selected; - if (selected) - gtk_widget_set_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED, FALSE); - else - gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED); - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED]); } diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index 65705260ea..1236aaf91e 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -21,7 +21,7 @@ #include "gtklistitemmanagerprivate.h" -#include "gtklistitemprivate.h" +#include "gtklistitemwidgetprivate.h" #include "gtkwidgetprivate.h" #define GTK_LIST_VIEW_MAX_LIST_ITEMS 200 @@ -47,7 +47,7 @@ struct _GtkListItemManagerClass struct _GtkListItemTracker { guint position; - GtkListItem *widget; + GtkListItemWidget *widget; guint n_before; guint n_after; }; @@ -595,7 +595,7 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model, if (tracker->widget == NULL) continue; - if (g_hash_table_lookup (change, gtk_list_item_get_item (tracker->widget))) + if (g_hash_table_lookup (change, gtk_list_item_widget_get_item (tracker->widget))) break; } @@ -678,7 +678,7 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model, } else if (tracker->position >= position) { - if (g_hash_table_lookup (change, gtk_list_item_get_item (tracker->widget))) + if (g_hash_table_lookup (change, gtk_list_item_widget_get_item (tracker->widget))) { /* The item is gone. Guess a good new position */ tracker->position = position + (tracker->position - position) * added / removed; @@ -696,7 +696,7 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model, /* item was put in its right place in the expensive loop above, * and we updated its position while at it. So grab it from there. */ - tracker->position = gtk_list_item_get_position (tracker->widget); + tracker->position = gtk_list_item_widget_get_position (tracker->widget); } } else @@ -723,7 +723,7 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model, item = gtk_list_item_manager_get_nth (self, tracker->position, NULL); g_assert (item != NULL); g_assert (item->widget); - tracker->widget = GTK_LIST_ITEM (item->widget); + tracker->widget = GTK_LIST_ITEM_WIDGET (item->widget); } g_hash_table_iter_init (&iter, change); @@ -850,7 +850,7 @@ gtk_list_item_manager_set_factory (GtkListItemManager *self, item = gtk_list_item_manager_get_nth (self, tracker->position, NULL); g_assert (item); - tracker->widget = GTK_LIST_ITEM (item->widget); + tracker->widget = GTK_LIST_ITEM_WIDGET (item->widget); } } @@ -922,23 +922,21 @@ gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, guint position, GtkWidget *prev_sibling) { - GtkListItem *result; + GtkWidget *result; gpointer item; gboolean selected; g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL); g_return_val_if_fail (prev_sibling == NULL || GTK_IS_WIDGET (prev_sibling), NULL); - result = gtk_list_item_new (self->item_css_name); - if (self->factory) - gtk_list_item_factory_setup (self->factory, result); + result = gtk_list_item_widget_new (self->factory, + self->item_css_name); item = g_list_model_get_item (G_LIST_MODEL (self->model), position); selected = gtk_selection_model_is_selected (self->model, position); - if (self->factory) - gtk_list_item_factory_bind (self->factory, result, position, item, selected); + gtk_list_item_widget_bind (GTK_LIST_ITEM_WIDGET (result), position, item, selected); g_object_unref (item); - gtk_widget_insert_after (GTK_WIDGET (result), self->widget, prev_sibling); + gtk_widget_insert_after (result, self->widget, prev_sibling); return GTK_WIDGET (result); } @@ -964,7 +962,7 @@ gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self, guint position, GtkWidget *prev_sibling) { - GtkListItem *result; + GtkWidget *result; gpointer item; g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL); @@ -974,11 +972,10 @@ gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self, item = g_list_model_get_item (G_LIST_MODEL (self->model), position); if (g_hash_table_steal_extended (change, item, NULL, (gpointer *) &result)) { - if (self->factory) - gtk_list_item_factory_update (self->factory, result, position, FALSE); - gtk_widget_insert_after (GTK_WIDGET (result), self->widget, prev_sibling); + gtk_list_item_widget_update (GTK_LIST_ITEM_WIDGET (result), position, FALSE); + gtk_widget_insert_after (result, self->widget, prev_sibling); /* XXX: Should we let the listview do this? */ - gtk_widget_queue_resize (GTK_WIDGET (result)); + gtk_widget_queue_resize (result); } else { @@ -986,7 +983,7 @@ gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self, } g_object_unref (item); - return GTK_WIDGET (result); + return result; } /** @@ -1013,8 +1010,7 @@ gtk_list_item_manager_move_list_item (GtkListItemManager *self, item = g_list_model_get_item (G_LIST_MODEL (self->model), position); selected = gtk_selection_model_is_selected (self->model, position); - if (self->factory) - gtk_list_item_factory_rebind (self->factory, GTK_LIST_ITEM (list_item), position, item, selected); + gtk_list_item_widget_rebind (GTK_LIST_ITEM_WIDGET (list_item), position, item, selected); gtk_widget_insert_after (list_item, _gtk_widget_get_parent (list_item), prev_sibling); g_object_unref (item); } @@ -1036,11 +1032,10 @@ gtk_list_item_manager_update_list_item (GtkListItemManager *self, gboolean selected; g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); - g_return_if_fail (GTK_IS_LIST_ITEM (item)); + g_return_if_fail (GTK_IS_LIST_ITEM_WIDGET (item)); selected = gtk_selection_model_is_selected (self->model, position); - if (self->factory) - gtk_list_item_factory_update (self->factory, GTK_LIST_ITEM (item), position, selected); + gtk_list_item_widget_update (GTK_LIST_ITEM_WIDGET (item), position, selected); } /* @@ -1060,21 +1055,16 @@ gtk_list_item_manager_release_list_item (GtkListItemManager *self, GtkWidget *item) { g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); - g_return_if_fail (GTK_IS_LIST_ITEM (item)); + g_return_if_fail (GTK_IS_LIST_ITEM_WIDGET (item)); if (change != NULL) { - if (g_hash_table_insert (change, gtk_list_item_get_item (GTK_LIST_ITEM (item)), item)) + if (g_hash_table_insert (change, gtk_list_item_widget_get_item (GTK_LIST_ITEM_WIDGET (item)), item)) return; g_warning ("FIXME: Handle the same item multiple times in the list.\nLars says this totally should not happen, but here we are."); } - if (self->factory) - { - gtk_list_item_factory_unbind (self->factory, GTK_LIST_ITEM (item)); - gtk_list_item_factory_teardown (self->factory, GTK_LIST_ITEM (item)); - } gtk_widget_unparent (item); } @@ -1136,7 +1126,7 @@ gtk_list_item_tracker_set_position (GtkListItemManager *self, item = gtk_list_item_manager_get_nth (self, position, NULL); if (item) - tracker->widget = GTK_LIST_ITEM (item->widget); + tracker->widget = GTK_LIST_ITEM_WIDGET (item->widget); gtk_widget_queue_resize (self->widget); } diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h index a41ea84472..18cdc6d9ff 100644 --- a/gtk/gtklistitemmanagerprivate.h +++ b/gtk/gtklistitemmanagerprivate.h @@ -23,7 +23,7 @@ #include "gtk/gtktypes.h" -#include "gtk/gtklistitemfactoryprivate.h" +#include "gtk/gtklistitemfactory.h" #include "gtk/gtkrbtreeprivate.h" #include "gtk/gtkselectionmodel.h" diff --git a/gtk/gtklistitemprivate.h b/gtk/gtklistitemprivate.h index 3a2ac463d1..5912e406be 100644 --- a/gtk/gtklistitemprivate.h +++ b/gtk/gtklistitemprivate.h @@ -22,11 +22,26 @@ #include "gtklistitem.h" -#include "gtklistitemmanagerprivate.h" +#include "gtklistitemwidgetprivate.h" G_BEGIN_DECLS -GtkListItem * gtk_list_item_new (const char *css_name); +struct _GtkListItem +{ + GObject parent_instance; + + GtkListItemWidget *owner; /* has a reference */ + + GObject *item; + GtkWidget *child; + guint position; + + guint activatable : 1; + guint selectable : 1; + guint selected : 1; +}; + +GtkListItem * gtk_list_item_new (GtkListItemWidget *owner); void gtk_list_item_set_item (GtkListItem *self, gpointer item); diff --git a/gtk/gtklistitemwidget.c b/gtk/gtklistitemwidget.c new file mode 100644 index 0000000000..a4b8815b89 --- /dev/null +++ b/gtk/gtklistitemwidget.c @@ -0,0 +1,438 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gtklistitemwidgetprivate.h" + +#include "gtkbindings.h" +#include "gtkbinlayout.h" +#include "gtkcssnodeprivate.h" +#include "gtkeventcontrollerkey.h" +#include "gtkgestureclick.h" +#include "gtkintl.h" +#include "gtklistitemfactoryprivate.h" +#include "gtklistitemprivate.h" +#include "gtkmain.h" +#include "gtkwidget.h" +#include "gtkwidgetprivate.h" + +enum +{ + ACTIVATE_SIGNAL, + LAST_SIGNAL +}; + +G_DEFINE_TYPE (GtkListItemWidget, gtk_list_item_widget, GTK_TYPE_WIDGET) + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void +gtk_list_item_widget_activate_signal (GtkListItemWidget *self) +{ + if (!self->item->activatable) + return; + + gtk_widget_activate_action (GTK_WIDGET (self), + "list.activate-item", + "u", + self->item->position); +} + +static gboolean +gtk_list_item_widget_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + GtkListItemWidget *self = GTK_LIST_ITEM_WIDGET (widget); + + /* 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. + */ + if (self->item && self->item->child) + { + if (gtk_widget_get_focus_child (widget)) + return FALSE; + if (gtk_widget_child_focus (self->item->child, direction)) + return TRUE; + } + + if (gtk_widget_is_focus (widget)) + return FALSE; + + if (!gtk_widget_get_can_focus (widget) || + !self->item->selectable) + 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); + + if (self->item->child && gtk_widget_grab_focus (self->item->child)) + return TRUE; + + return GTK_WIDGET_CLASS (gtk_list_item_widget_parent_class)->grab_focus (widget); +} + +static void +gtk_list_item_widget_dispose (GObject *object) +{ + GtkListItemWidget *self = GTK_LIST_ITEM_WIDGET (object); + + if (self->item) + { + if (self->item->item) + gtk_list_item_factory_unbind (self->factory, self->item); + gtk_list_item_factory_teardown (self->factory, self->item); + self->item->owner = NULL; + g_clear_object (&self->item); + } + g_clear_object (&self->factory); + + G_OBJECT_CLASS (gtk_list_item_widget_parent_class)->dispose (object); +} + +static void +gtk_list_item_widget_select_action (GtkWidget *widget, + const char *action_name, + GVariant *parameter) +{ + GtkListItemWidget *self = GTK_LIST_ITEM_WIDGET (widget); + gboolean modify, extend; + + if (!self->item->selectable) + return; + + g_variant_get (parameter, "(bb)", &modify, &extend); + + gtk_widget_activate_action (GTK_WIDGET (self), + "list.select-item", + "(ubb)", + self->item->position, modify, extend); +} + +static void +gtk_list_item_widget_class_init (GtkListItemWidgetClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkBindingSet *binding_set; + + klass->activate_signal = gtk_list_item_widget_activate_signal; + + widget_class->focus = gtk_list_item_widget_focus; + widget_class->grab_focus = gtk_list_item_widget_grab_focus; + + gobject_class->dispose = gtk_list_item_widget_dispose; + + signals[ACTIVATE_SIGNAL] = + g_signal_new (I_("activate-keybinding"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkListItemWidgetClass, activate_signal), + NULL, NULL, + NULL, + G_TYPE_NONE, 0); + + widget_class->activate_signal = signals[ACTIVATE_SIGNAL]; + + /** + * GtkListItem|listitem.select: + * @modify: %TRUE to toggle the existing selection, %FALSE to select + * @extend: %TRUE to extend the selection + * + * Changes selection if the item is selectable. + * If the item is not selectable, nothing happens. + * + * This function will emit the list.select-item action and the resulting + * behavior, in particular the interpretation of @modify and @extend + * depends on the view containing this listitem. See for example + * GtkListView|list.select-item or GtkGridView|list.select-item. + */ + gtk_widget_class_install_action (widget_class, + "listitem.select", + "(bb)", + gtk_list_item_widget_select_action); + + binding_set = gtk_binding_set_by_class (klass); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_Return, 0, + "activate-keybinding", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_ISO_Enter, 0, + "activate-keybinding", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Enter, 0, + "activate-keybinding", 0); + + /* note that some of these may get overwritten by child widgets, + * such as GtkTreeExpander */ + gtk_binding_entry_add_action (binding_set, GDK_KEY_space, 0, + "listitem.select", "(bb)", TRUE, FALSE); + gtk_binding_entry_add_action (binding_set, GDK_KEY_space, GDK_CONTROL_MASK, + "listitem.select", "(bb)", TRUE, FALSE); + gtk_binding_entry_add_action (binding_set, GDK_KEY_space, GDK_SHIFT_MASK, + "listitem.select", "(bb)", TRUE, FALSE); + gtk_binding_entry_add_action (binding_set, GDK_KEY_space, GDK_CONTROL_MASK | GDK_SHIFT_MASK, + "listitem.select", "(bb)", TRUE, FALSE); + gtk_binding_entry_add_action (binding_set, GDK_KEY_KP_Space, 0, + "listitem.select", "(bb)", TRUE, FALSE); + gtk_binding_entry_add_action (binding_set, GDK_KEY_KP_Space, GDK_CONTROL_MASK, + "listitem.select", "(bb)", TRUE, FALSE); + gtk_binding_entry_add_action (binding_set, GDK_KEY_KP_Space, GDK_SHIFT_MASK, + "listitem.select", "(bb)", TRUE, FALSE); + gtk_binding_entry_add_action (binding_set, GDK_KEY_KP_Space, GDK_CONTROL_MASK | GDK_SHIFT_MASK, + "listitem.select", "(bb)", TRUE, FALSE); + + /* This gets overwritten by gtk_list_item_widget_new() but better safe than sorry */ + gtk_widget_class_set_css_name (widget_class, I_("row")); + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); +} + +static void +gtk_list_item_widget_click_gesture_pressed (GtkGestureClick *gesture, + int n_press, + double x, + double y, + GtkListItemWidget *self) +{ + GtkWidget *widget = GTK_WIDGET (self); + + if (!self->item->selectable && !self->item->activatable) + { + gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED); + return; + } + + if (self->item->selectable) + { + GdkModifierType state; + GdkModifierType mask; + gboolean extend = FALSE, modify = FALSE; + + if (gtk_get_current_event_state (&state)) + { + mask = gtk_widget_get_modifier_mask (widget, GDK_MODIFIER_INTENT_MODIFY_SELECTION); + if ((state & mask) == mask) + modify = TRUE; + mask = gtk_widget_get_modifier_mask (widget, GDK_MODIFIER_INTENT_EXTEND_SELECTION); + if ((state & mask) == mask) + extend = TRUE; + } + + gtk_widget_activate_action (GTK_WIDGET (self), + "list.select-item", + "(ubb)", + self->item->position, modify, extend); + } + + if (self->item->activatable) + { + if (n_press == 2) + { + gtk_widget_activate_action (GTK_WIDGET (self), + "list.activate-item", + "u", + self->item->position); + } + } + + gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_ACTIVE, FALSE); + + if (gtk_widget_get_focus_on_click (widget)) + gtk_widget_grab_focus (widget); +} + +static void +gtk_list_item_widget_focus_changed_cb (GtkEventControllerKey *controller, + GParamSpec *psepc, + GtkListItemWidget *self) +{ + GtkWidget *widget = GTK_WIDGET (self); + + if (gtk_event_controller_key_contains_focus (controller)) + { + gtk_widget_activate_action (widget, + "list.scroll-to-item", + "u", + self->item->position); + } +} + +static void +gtk_list_item_widget_click_gesture_released (GtkGestureClick *gesture, + int n_press, + double x, + double y, + GtkListItemWidget *self) +{ + gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE); +} + +static void +gtk_list_item_widget_click_gesture_canceled (GtkGestureClick *gesture, + GdkEventSequence *sequence, + GtkListItemWidget *self) +{ + gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE); +} + +static void +gtk_list_item_widget_init (GtkListItemWidget *self) +{ + GtkEventController *controller; + GtkGesture *gesture; + + gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE); + + gesture = gtk_gesture_click_new (); + gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), + GTK_PHASE_BUBBLE); + gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), + FALSE); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), + GDK_BUTTON_PRIMARY); + g_signal_connect (gesture, "pressed", + G_CALLBACK (gtk_list_item_widget_click_gesture_pressed), self); + g_signal_connect (gesture, "released", + G_CALLBACK (gtk_list_item_widget_click_gesture_released), self); + g_signal_connect (gesture, "cancel", + G_CALLBACK (gtk_list_item_widget_click_gesture_canceled), self); + gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture)); + + controller = gtk_event_controller_key_new (); + g_signal_connect (controller, "notify::contains-focus", G_CALLBACK (gtk_list_item_widget_focus_changed_cb), self); + gtk_widget_add_controller (GTK_WIDGET (self), controller); + + self->item = gtk_list_item_new (self); +} + +GtkWidget * +gtk_list_item_widget_new (GtkListItemFactory *factory, + const char *css_name) +{ + GtkListItemWidget *result; + + g_return_val_if_fail (css_name != NULL, NULL); + + result = g_object_new (GTK_TYPE_LIST_ITEM_WIDGET, + "css-name", css_name, + NULL); + if (factory) + { + result->factory = g_object_ref (factory); + + gtk_list_item_factory_setup (factory, result->item); + } + + return GTK_WIDGET (result); +} + +void +gtk_list_item_widget_bind (GtkListItemWidget *self, + guint position, + gpointer item, + gboolean selected) +{ + if (self->factory) + gtk_list_item_factory_bind (self->factory, self->item, position, item, selected); + + if (selected) + gtk_widget_set_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED, FALSE); + else + gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED); +} + +void +gtk_list_item_widget_rebind (GtkListItemWidget *self, + guint position, + gpointer item, + gboolean selected) +{ + if (self->factory) + gtk_list_item_factory_rebind (self->factory, self->item, position, item, selected); + + if (selected) + gtk_widget_set_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED, FALSE); + else + gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED); + + gtk_css_node_invalidate (gtk_widget_get_css_node (GTK_WIDGET (self)), GTK_CSS_CHANGE_ANIMATIONS); +} + +void +gtk_list_item_widget_update (GtkListItemWidget *self, + guint position, + gboolean selected) +{ + if (self->factory) + gtk_list_item_factory_update (self->factory, self->item, position, selected); + + if (selected) + gtk_widget_set_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED, FALSE); + else + gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED); + +} + +void +gtk_list_item_widget_unbind (GtkListItemWidget *self) +{ + if (self->factory) + gtk_list_item_factory_unbind (self->factory, self->item); + + gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED); + + gtk_css_node_invalidate (gtk_widget_get_css_node (GTK_WIDGET (self)), GTK_CSS_CHANGE_ANIMATIONS); +} + +void +gtk_list_item_widget_add_child (GtkListItemWidget *self, + GtkWidget *child) +{ + gtk_widget_set_parent (child, GTK_WIDGET (self)); +} + +void +gtk_list_item_widget_remove_child (GtkListItemWidget *self, + GtkWidget *child) +{ + gtk_widget_unparent (child); +} + +guint +gtk_list_item_widget_get_position (GtkListItemWidget *self) +{ + return self->item->position; +} + +gpointer +gtk_list_item_widget_get_item (GtkListItemWidget *self) +{ + return self->item->item; +} + diff --git a/gtk/gtklistitemwidgetprivate.h b/gtk/gtklistitemwidgetprivate.h new file mode 100644 index 0000000000..77db256cb0 --- /dev/null +++ b/gtk/gtklistitemwidgetprivate.h @@ -0,0 +1,81 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __GTK_LIST_ITEM_WIDGET_PRIVATE_H__ +#define __GTK_LIST_ITEM_WIDGET_PRIVATE_H__ + +#include "gtklistitemfactory.h" +#include "gtkwidget.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_LIST_ITEM_WIDGET (gtk_list_item_widget_get_type ()) +#define GTK_LIST_ITEM_WIDGET(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_LIST_ITEM_WIDGET, GtkListItemWidget)) +#define GTK_LIST_ITEM_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_LIST_ITEM_WIDGET, GtkListItemWidgetClass)) +#define GTK_IS_LIST_ITEM_WIDGET(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_LIST_ITEM_WIDGET)) +#define GTK_IS_LIST_ITEM_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_LIST_ITEM_WIDGET)) +#define GTK_LIST_ITEM_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_LIST_ITEM_WIDGET, GtkListItemWidgetClass)) + +typedef struct _GtkListItemWidget GtkListItemWidget; +typedef struct _GtkListItemWidgetClass GtkListItemWidgetClass; + +struct _GtkListItemWidget +{ + GtkWidget parent_instance; + + GtkListItemFactory *factory; + GtkListItem *item; +}; + +struct _GtkListItemWidgetClass +{ + GtkWidgetClass parent_class; + + void (* activate_signal) (GtkListItemWidget *self); +}; + +GType gtk_list_item_widget_get_type (void) G_GNUC_CONST; + +GtkWidget * gtk_list_item_widget_new (GtkListItemFactory *factory, + const char *css_name); + +void gtk_list_item_widget_bind (GtkListItemWidget *self, + guint position, + gpointer item, + gboolean selected); +void gtk_list_item_widget_rebind (GtkListItemWidget *self, + guint position, + gpointer item, + gboolean selected); +void gtk_list_item_widget_update (GtkListItemWidget *self, + guint position, + gboolean selected); +void gtk_list_item_widget_unbind (GtkListItemWidget *self); + +void gtk_list_item_widget_add_child (GtkListItemWidget *self, + GtkWidget *child); +void gtk_list_item_widget_remove_child (GtkListItemWidget *self, + GtkWidget *child); + +guint gtk_list_item_widget_get_position (GtkListItemWidget *self); +gpointer gtk_list_item_widget_get_item (GtkListItemWidget *self); + +G_END_DECLS + +#endif /* __GTK_LIST_ITEM_WIDGET_PRIVATE_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index f6f6899461..64601b270a 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -291,6 +291,7 @@ gtk_public_sources = files([ 'gtklistitem.c', 'gtklistitemfactory.c', 'gtklistitemmanager.c', + 'gtklistitemwidget.c', 'gtklistlistmodel.c', 'gtkliststore.c', 'gtklistview.c', diff --git a/tests/testlistview.c b/tests/testlistview.c index 28a43678a0..591c091e71 100644 --- a/tests/testlistview.c +++ b/tests/testlistview.c @@ -493,8 +493,6 @@ setup_widget (GtkListItem *list_item, RowData *data; data = g_slice_new0 (RowData); - g_signal_connect (list_item, "notify::item", G_CALLBACK (row_data_notify_item), data); - g_object_set_data_full (G_OBJECT (list_item), "row-data", data, row_data_free); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); gtk_list_item_set_child (list_item, box); @@ -516,6 +514,9 @@ setup_widget (GtkListItem *list_item, gtk_label_set_max_width_chars (GTK_LABEL (data->name), 25); gtk_label_set_ellipsize (GTK_LABEL (data->name), PANGO_ELLIPSIZE_END); gtk_container_add (GTK_CONTAINER (box), data->name); + + g_signal_connect (list_item, "notify::item", G_CALLBACK (row_data_notify_item), data); + g_object_set_data_full (G_OBJECT (list_item), "row-data", data, row_data_free); } static GListModel *