diff --git a/gtk/gtkactionmuxer.c b/gtk/gtkactionmuxer.c index 06660ddf89dc07cfe1b9ba8bd2f40d0f3bc955b4..46414e228e6c5e564c0ed6484123fe6ab6cce163 100644 --- a/gtk/gtkactionmuxer.c +++ b/gtk/gtkactionmuxer.c @@ -991,13 +991,15 @@ gtk_action_muxer_unregister_observer (GtkActionObservable *observable, GtkActionObserver *observer) { GtkActionMuxer *muxer = GTK_ACTION_MUXER (observable); - Action *action; + Action *action = find_observers (muxer, name); - action = find_observers (muxer, name); if (action) { - g_object_weak_unref (G_OBJECT (observer), gtk_action_muxer_weak_notify, action); - gtk_action_muxer_unregister_internal (action, observer); + if (g_slist_find (action->watchers, observer) != NULL) + { + g_object_weak_unref (G_OBJECT (observer), gtk_action_muxer_weak_notify, action); + gtk_action_muxer_unregister_internal (action, observer); + } } } @@ -1269,7 +1271,7 @@ gtk_action_muxer_insert (GtkActionMuxer *muxer, if (!muxer->groups) muxer->groups = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, gtk_action_muxer_free_group); - group = g_slice_new (Group); + group = g_slice_new0 (Group); group->muxer = muxer; group->group = g_object_ref (action_group); group->prefix = g_strdup (prefix); @@ -1356,6 +1358,23 @@ gtk_action_muxer_get_parent (GtkActionMuxer *muxer) return muxer->parent; } +static gboolean +muxer_will_cycle (GtkActionMuxer *muxer, + GtkActionMuxer *parent) +{ + GtkActionMuxer *ancestor; + + for (ancestor = parent; + ancestor != NULL; + ancestor = gtk_action_muxer_get_parent (ancestor)) + { + if (ancestor == muxer) + return TRUE; + } + + return FALSE; +} + /*< private > * gtk_action_muxer_set_parent: * @muxer: a `GtkActionMuxer` @@ -1369,6 +1388,7 @@ gtk_action_muxer_set_parent (GtkActionMuxer *muxer, { g_return_if_fail (GTK_IS_ACTION_MUXER (muxer)); g_return_if_fail (parent == NULL || GTK_IS_ACTION_MUXER (parent)); + g_return_if_fail (parent == NULL || !muxer_will_cycle (muxer, parent)); if (muxer->parent == parent) return; diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index afe7e9224e8b6d8e20e2b94df9ff93280b3df572..f3c51db26830472919ffb006d29ac1fff8ce8437 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -7390,6 +7390,8 @@ gtk_widget_dispose (GObject *object) GSList *sizegroups; GtkATContext *at_context; + g_clear_object (&priv->action_parent); + if (priv->muxer != NULL) g_object_run_dispose (G_OBJECT (priv->muxer)); @@ -10808,13 +10810,20 @@ void _gtk_widget_update_parent_muxer (GtkWidget *widget) { GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget); + GtkActionMuxer *parent_muxer; GtkWidget *child; if (priv->muxer == NULL) return; - gtk_action_muxer_set_parent (priv->muxer, - gtk_widget_get_parent_muxer (widget, FALSE)); + if (priv->action_parent != NULL && + !gtk_widget_is_ancestor (priv->action_parent, widget)) + parent_muxer = _gtk_widget_get_action_muxer (priv->action_parent, FALSE); + else + parent_muxer = gtk_widget_get_parent_muxer (widget, FALSE); + + gtk_action_muxer_set_parent (priv->muxer, parent_muxer); + for (child = gtk_widget_get_first_child (widget); child != NULL; child = gtk_widget_get_next_sibling (child)) @@ -12934,3 +12943,59 @@ gtk_widget_set_active_state (GtkWidget *widget, gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_ACTIVE); } } + +/** + * gtk_widget_set_action_parent: + * @widget: a [class@Gtk.Widget] + * @action_parent: (nullable): a [class@Gtk.Widget] + * + * Sets the action parent for @widget. + * + * Actions will resolve through @action_parent for @widget and all of + * it's descendants unless otherwise specified with + * [method@Gtk.Widget.set_action_parent]. + * + * Setting an action parent can be useful when you want actions within + * a menu or toolbar to resolve through a document widget. + * + * To unset an action parent, use `NULL` for @action_parent and the widget + * will resume using the parent widget as the action parent. + * + * It is a programming error to set an action parent which will cause a + * cycle to occur. + * + * Since: 4.8 + */ +void +gtk_widget_set_action_parent (GtkWidget *widget, + GtkWidget *action_parent) +{ + GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget); + GtkActionMuxer *muxer; + GtkActionMuxer *parent_muxer; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (action_parent != widget); + g_return_if_fail (!action_parent || GTK_IS_WIDGET (action_parent)); + g_return_if_fail (!action_parent || !gtk_widget_is_ancestor (action_parent, widget)); + + muxer = _gtk_widget_get_action_muxer (widget, FALSE); + + if (action_parent == NULL) + { + if (muxer != NULL) + { + parent_muxer = gtk_widget_get_parent_muxer (widget, FALSE); + gtk_action_muxer_set_parent (muxer, parent_muxer); + } + } + else + { + if (muxer == NULL) + muxer = _gtk_widget_get_action_muxer (widget, TRUE); + parent_muxer = _gtk_widget_get_action_muxer (action_parent, TRUE); + gtk_action_muxer_set_parent (muxer, parent_muxer); + } + + g_set_object (&priv->action_parent, action_parent); +} diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h index 0788fcdd8179a9a78c3b599bee1eb9c481be5173..1a9793f2969f8471feaad79fa04f86ade389354b 100644 --- a/gtk/gtkwidget.h +++ b/gtk/gtkwidget.h @@ -924,6 +924,9 @@ char ** gtk_widget_get_css_classes (GtkWidget *widget); GDK_AVAILABLE_IN_ALL void gtk_widget_set_css_classes (GtkWidget *widget, const char **classes); +GDK_AVAILABLE_IN_4_8 +void gtk_widget_set_action_parent (GtkWidget *widget, + GtkWidget *action_parent); diff --git a/gtk/gtkwidgetprivate.h b/gtk/gtkwidgetprivate.h index e1e336e6e6571ba54c58b995ac5b69140bc57f92..b8f17c8f23550736e5395f3d8457e19f70710782 100644 --- a/gtk/gtkwidgetprivate.h +++ b/gtk/gtkwidgetprivate.h @@ -185,6 +185,7 @@ struct _GtkWidgetPrivate GtkListListModel *children_observer; GtkListListModel *controller_observer; GtkActionMuxer *muxer; + GtkWidget *action_parent; GtkWidget *focus_child; diff --git a/testsuite/gtk/action.c b/testsuite/gtk/action.c index 95bb071d4b9dcfb966b21e0cc040002559908f7b..7024f353411feb6e6f44220c88bd60b60926ba56 100644 --- a/testsuite/gtk/action.c +++ b/testsuite/gtk/action.c @@ -15,6 +15,8 @@ */ #include +#include "gtkwidgetprivate.h" + static void activate (GSimpleAction *action, GVariant *parameter, @@ -717,6 +719,109 @@ test_enabled (void) g_object_unref (g_object_ref_sink (text)); } +static void +test_action_parent_action1 (GSimpleAction *action, + GVariant *param, + gpointer user_data) +{ + guint *count = user_data; + (*count)++; +} + +static void +test_action_parent_action2 (GSimpleAction *action, + GVariant *param, + gpointer user_data) +{ + g_simple_action_set_state (action, param); +} + +static void +test_action_parent (void) +{ + static const GActionEntry test_actions[] = { + { "action1", test_action_parent_action1 }, + { "action2", test_action_parent_action2, "s", "'initial'", test_action_parent_action2 }, + }; + GSimpleActionGroup *group; + GAction *action2; + GtkWidget *window; + GtkWidget *header; + GtkWidget *content; + GtkWidget *label1; + GtkWidget *label2; + GVariant *state = NULL; + const GVariantType *state_type = NULL; + GtkActionMuxer *muxer; + guint count = 0; + + window = g_object_new (GTK_TYPE_WINDOW, NULL); + g_object_ref_sink (window); + + header = g_object_new (GTK_TYPE_BUTTON, NULL); + content = g_object_new (GTK_TYPE_BOX, NULL); + label1 = g_object_new (GTK_TYPE_LABEL, NULL); + label2 = g_object_new (GTK_TYPE_LABEL, NULL); + gtk_box_append (GTK_BOX (content), label1); + gtk_box_append (GTK_BOX (content), label2); + gtk_window_set_titlebar (GTK_WINDOW (window), header); + gtk_window_set_child (GTK_WINDOW (window), content); + group = g_simple_action_group_new (); + g_action_map_add_action_entries (G_ACTION_MAP (group), + test_actions, + G_N_ELEMENTS (test_actions), + &count); + action2 = g_action_map_lookup_action (G_ACTION_MAP (group), "action2"); + + gtk_widget_insert_action_group (content, "test", G_ACTION_GROUP (group)); + gtk_widget_activate_action (label1, "test.action1", NULL); + g_assert_cmpint (count, ==, 1); + + gtk_widget_activate_action (header, "test.action1", NULL); + g_assert_cmpint (count, ==, 1); + + gtk_widget_set_action_parent (header, label1); + gtk_widget_activate_action (header, "test.action1", NULL); + g_assert_cmpint (count, ==, 2); + + gtk_widget_activate_action (header, "test.action2", "s", "changed"); + g_assert_cmpstr ("changed", ==, g_variant_get_string (g_action_get_state (action2), NULL)); + muxer = _gtk_widget_get_action_muxer (header, FALSE); + gtk_action_muxer_query_action (muxer, "test.action2", NULL, NULL, &state_type, NULL, &state); + g_assert_nonnull (state_type); + g_assert_nonnull (state); + g_assert_cmpstr ((const char *)state_type, ==, "s"); + g_assert_cmpstr ("changed", ==, g_variant_get_string (state, NULL)); + g_variant_unref (state); + + gtk_widget_set_action_parent (header, label2); + gtk_widget_activate_action (header, "test.action1", NULL); + g_assert_cmpint (count, ==, 3); + + gtk_widget_set_action_parent (header, NULL); + gtk_widget_activate_action (header, "test.action1", NULL); + gtk_widget_activate_action (header, "test.action2", "s", "third"); + g_assert_cmpint (count, ==, 3); + g_assert_cmpstr ("changed", ==, g_variant_get_string (g_action_get_state (action2), NULL)); + + gtk_widget_set_action_parent (label2, header); + gtk_widget_activate_action (label2, "test.action1", NULL); + g_assert_cmpint (count, ==, 3); + + gtk_widget_insert_action_group (content, "test", NULL); + gtk_widget_activate_action (label1, "test.action1", NULL); + gtk_widget_activate_action (header, "test.action1", NULL); + g_assert_cmpint (count, ==, 3); + + gtk_widget_activate_action (label2, "test.action2", "s", "third"); + g_assert_cmpstr ("changed", ==, g_variant_get_string (g_action_get_state (action2), NULL)); + + gtk_window_destroy (GTK_WINDOW (window)); + + g_assert_finalize_object (group); + g_assert_finalize_object (window); +} + int main (int argc, char *argv[]) @@ -732,6 +837,7 @@ main (int argc, g_test_add_func ("/action/overlap2", test_overlap2); g_test_add_func ("/action/introspection", test_introspection); g_test_add_func ("/action/enabled", test_enabled); + g_test_add_func ("/action/action_parent", test_action_parent); return g_test_run(); } diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build index 3324664cac6ab382c2c987eec32d08eaf42b8fd9..da513ef22b33d52db5e7d6fdf67f5a4fa5e375f5 100644 --- a/testsuite/gtk/meson.build +++ b/testsuite/gtk/meson.build @@ -23,7 +23,6 @@ tests = [ { 'name': 'accel' }, # sadly, mesons xfail support seems busted # { 'name': 'accessor-apis' }, - { 'name': 'action' }, { 'name': 'adjustment' }, { 'name': 'bitset' }, { @@ -105,6 +104,7 @@ tests = [ # Tests that test private apis and therefore are linked against libgtk-4.a internal_tests = [ + { 'name': 'action' }, { 'name': 'bitmask' }, { 'name': 'composetable',