diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 786bcde361..a7d0d15787 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -4549,10 +4549,6 @@ gtk_widget_get_opacity gtk_widget_set_opacity gtk_widget_get_overflow gtk_widget_set_overflow -gtk_widget_insert_action_group -gtk_widget_list_action_prefixes -gtk_widget_activate_action -gtk_widget_activate_default gtk_widget_measure gtk_widget_snapshot_child gtk_widget_get_next_sibling @@ -4629,6 +4625,18 @@ gtk_widget_class_set_connect_func gtk_widget_observe_children gtk_widget_observe_controllers + +gtk_widget_insert_action_group +gtk_widget_activate_action +gtk_widget_activate_default +GtkWidgetActionActivateFunc +GtkWidgetActionSetStateFunc +GtkWidgetActionGetStateFunc +gtk_widget_class_install_action +gtk_widget_class_install_stateful_action +gtk_widget_action_enabled_changed +gtk_widget_action_state_changed + GTK_WIDGET GTK_IS_WIDGET diff --git a/gtk/gtkactionmuxer.c b/gtk/gtkactionmuxer.c index f358f8cbbd..33951ce463 100644 --- a/gtk/gtkactionmuxer.c +++ b/gtk/gtkactionmuxer.c @@ -72,6 +72,8 @@ struct _GtkActionMuxer GtkActionMuxer *parent; GtkWidget *widget; + GPtrArray *widget_actions; + gboolean *widget_actions_enabled; }; G_DEFINE_TYPE_WITH_CODE (GtkActionMuxer, gtk_action_muxer, G_TYPE_OBJECT, @@ -83,6 +85,7 @@ enum PROP_0, PROP_PARENT, PROP_WIDGET, + PROP_WIDGET_ACTIONS, NUM_PROPERTIES }; @@ -108,7 +111,7 @@ typedef struct static void gtk_action_muxer_append_group_actions (const char *prefix, Group *group, - GArray *actions) + GHashTable *actions) { gchar **group_actions; gchar **action; @@ -116,10 +119,8 @@ gtk_action_muxer_append_group_actions (const char *prefix, group_actions = g_action_group_list_actions (group->group); for (action = group_actions; *action; action++) { - gchar *fullname; - - fullname = g_strconcat (prefix, ".", *action, NULL); - g_array_append_val (actions, fullname); + char *name = g_strconcat (prefix, ".", *action, NULL); + g_hash_table_add (actions, name); } g_strfreev (group_actions); @@ -129,9 +130,11 @@ static gchar ** gtk_action_muxer_list_actions (GActionGroup *action_group) { GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group); - GArray *actions; + GHashTable *actions; + char **keys; - actions = g_array_new (TRUE, FALSE, sizeof (gchar *)); + actions = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); for ( ; muxer != NULL; muxer = muxer->parent) { @@ -139,12 +142,28 @@ gtk_action_muxer_list_actions (GActionGroup *action_group) const char *prefix; Group *group; + if (muxer->widget_actions) + { + int i; + + for (i = 0; i < muxer->widget_actions->len; i++) + { + GtkWidgetAction *action = g_ptr_array_index (muxer->widget_actions, i); + g_hash_table_add (actions, g_strdup (action->name)); + } + } + g_hash_table_iter_init (&iter, muxer->groups); while (g_hash_table_iter_next (&iter, (gpointer *)&prefix, (gpointer *)&group)) gtk_action_muxer_append_group_actions (prefix, group, actions); } - return (gchar **)(void *) g_array_free (actions, FALSE); + keys = (char **)g_hash_table_get_keys_as_array (actions, NULL); + + g_hash_table_steal_all (actions); + g_hash_table_unref (actions); + + return (char **)keys; } static Group * @@ -183,7 +202,7 @@ gtk_action_muxer_find (GtkActionMuxer *muxer, return group->group; } -static void +void gtk_action_muxer_action_enabled_changed (GtkActionMuxer *muxer, const gchar *action_name, gboolean enabled) @@ -191,6 +210,19 @@ gtk_action_muxer_action_enabled_changed (GtkActionMuxer *muxer, Action *action; GSList *node; + if (muxer->widget_actions) + { + int i; + for (i = 0; i < muxer->widget_actions->len; i++) + { + GtkWidgetAction *a = g_ptr_array_index (muxer->widget_actions, i); + if (strcmp (a->name, action_name) == 0) + { + muxer->widget_actions_enabled[i] = enabled; + break; + } + } + } action = g_hash_table_lookup (muxer->observed_actions, action_name); for (node = action ? action->watchers : NULL; node; node = node->next) gtk_action_observer_action_enabled_changed (node->data, GTK_ACTION_OBSERVABLE (muxer), action_name, enabled); @@ -223,7 +255,7 @@ gtk_action_muxer_parent_action_enabled_changed (GActionGroup *action_group, gtk_action_muxer_action_enabled_changed (muxer, action_name, enabled); } -static void +void gtk_action_muxer_action_state_changed (GtkActionMuxer *muxer, const gchar *action_name, GVariant *state) @@ -401,6 +433,46 @@ gtk_action_muxer_query_action (GActionGroup *action_group, Group *group; const gchar *unprefixed_name; + if (muxer->widget_actions) + { + int i; + + for (i = 0; i < muxer->widget_actions->len; i++) + { + GtkWidgetAction *action = g_ptr_array_index (muxer->widget_actions, i); + if (strcmp (action->name, action_name) == 0) + { + if (enabled) + *enabled = muxer->widget_actions_enabled[i]; + if (parameter_type) + *parameter_type = action->parameter_type; + + if (state_hint) + *state_hint = NULL; + if (state_type) + *state_type = NULL; + if (state) + *state = NULL; + + if (action->get_state) + { + GVariant *s; + + s = g_variant_ref_sink (action->get_state (muxer->widget, action->name)); + + if (state_type) + *state_type = g_variant_get_type (s); + if (state) + *state = g_variant_ref (s); + + g_variant_unref (s); + } + + return TRUE; + } + } + } + group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name); if (group) @@ -424,6 +496,22 @@ gtk_action_muxer_activate_action (GActionGroup *action_group, Group *group; const gchar *unprefixed_name; + if (muxer->widget_actions) + { + int i; + + for (i = 0; i < muxer->widget_actions->len; i++) + { + GtkWidgetAction *action = g_ptr_array_index (muxer->widget_actions, i); + if (strcmp (action->name, action_name) == 0) + { + action->activate (muxer->widget, action->name, parameter); + + return; + } + } + } + group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name); if (group) @@ -441,6 +529,23 @@ gtk_action_muxer_change_action_state (GActionGroup *action_group, Group *group; const gchar *unprefixed_name; + if (muxer->widget_actions) + { + int i; + + for (i = 0; i < muxer->widget_actions->len; i++) + { + GtkWidgetAction *action = g_ptr_array_index (muxer->widget_actions, i); + if (strcmp (action->name, action_name) == 0) + { + if (action->set_state) + action->set_state (muxer->widget, action->name, state); + + return; + } + } + } + group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name); if (group) @@ -600,6 +705,10 @@ gtk_action_muxer_get_property (GObject *object, g_value_set_object (value, muxer->widget); break; + case PROP_WIDGET_ACTIONS: + g_value_set_boxed (value, muxer->widget_actions); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -623,6 +732,18 @@ gtk_action_muxer_set_property (GObject *object, muxer->widget = g_value_get_object (value); break; + case PROP_WIDGET_ACTIONS: + muxer->widget_actions = g_value_get_boxed (value); + if (muxer->widget_actions) + { + int i; + + muxer->widget_actions_enabled = g_new (gboolean, muxer->widget_actions->len); + for (i = 0; i < muxer->widget_actions->len; i++) + muxer->widget_actions_enabled[i] = TRUE; + } + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -683,6 +804,13 @@ gtk_action_muxer_class_init (GObjectClass *class) G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + properties[PROP_WIDGET_ACTIONS] = g_param_spec_boxed ("widget-actions", "Widget actions", + "Widget actions", + G_TYPE_PTR_ARRAY, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (class, NUM_PROPERTIES, properties); } @@ -791,14 +919,17 @@ gtk_action_muxer_lookup (GtkActionMuxer *muxer, /*< private > * gtk_action_muxer_new: * @widget: the widget to which the muxer belongs + * @actions: widget actions * * Creates a new #GtkActionMuxer. */ GtkActionMuxer * -gtk_action_muxer_new (GtkWidget *widget) +gtk_action_muxer_new (GtkWidget *widget, + GPtrArray *actions) { return g_object_new (GTK_TYPE_ACTION_MUXER, "widget", widget, + "widget-actions", actions, NULL); } diff --git a/gtk/gtkactionmuxerprivate.h b/gtk/gtkactionmuxerprivate.h index 190728fafd..325c1f817a 100644 --- a/gtk/gtkactionmuxerprivate.h +++ b/gtk/gtkactionmuxerprivate.h @@ -31,10 +31,21 @@ G_BEGIN_DECLS #define GTK_IS_ACTION_MUXER(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \ GTK_TYPE_ACTION_MUXER)) +typedef struct { + char *name; + + GVariantType *parameter_type; + + GtkWidgetActionActivateFunc activate; + GtkWidgetActionSetStateFunc set_state; + GtkWidgetActionGetStateFunc get_state; +} GtkWidgetAction; + typedef struct _GtkActionMuxer GtkActionMuxer; GType gtk_action_muxer_get_type (void); -GtkActionMuxer * gtk_action_muxer_new (GtkWidget *widget); +GtkActionMuxer * gtk_action_muxer_new (GtkWidget *widget, + GPtrArray *actions); void gtk_action_muxer_insert (GtkActionMuxer *muxer, const gchar *prefix, @@ -59,6 +70,16 @@ void gtk_action_muxer_set_primary_accel (GtkActi const gchar * gtk_action_muxer_get_primary_accel (GtkActionMuxer *muxer, const gchar *action_and_target); +void +gtk_action_muxer_action_enabled_changed (GtkActionMuxer *muxer, + const char *action_name, + gboolean enabled); +void +gtk_action_muxer_action_state_changed (GtkActionMuxer *muxer, + const gchar *action_name, + GVariant *state); + + /* No better place for these... */ gchar * gtk_print_action_and_target (const gchar *action_namespace, const gchar *action_name, diff --git a/gtk/gtkapplication.c b/gtk/gtkapplication.c index f90fd827fc..3b3fe1e5ac 100644 --- a/gtk/gtkapplication.c +++ b/gtk/gtkapplication.c @@ -394,7 +394,7 @@ gtk_application_init (GtkApplication *application) { GtkApplicationPrivate *priv = gtk_application_get_instance_private (application); - priv->muxer = gtk_action_muxer_new (NULL); + priv->muxer = gtk_action_muxer_new (NULL, NULL); priv->accels = gtk_application_accels_new (); } diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index 444ecbe4dd..f2a8c9710c 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -501,6 +501,7 @@ struct _GtkWidgetClassPrivate AtkRole accessible_role; const char *css_name; GType layout_manager_type; + GPtrArray *actions; }; enum { @@ -11897,14 +11898,16 @@ _gtk_widget_get_action_muxer (GtkWidget *widget, gboolean create) { GtkActionMuxer *muxer; + GtkWidgetClass *widget_class = GTK_WIDGET_GET_CLASS (widget); + GtkWidgetClassPrivate *priv = widget_class->priv; muxer = (GtkActionMuxer*)g_object_get_qdata (G_OBJECT (widget), quark_action_muxer); if (muxer) return muxer; - if (create) + if (create || priv->actions) { - muxer = gtk_action_muxer_new (widget); + muxer = gtk_action_muxer_new (widget, priv->actions); g_object_set_qdata_full (G_OBJECT (widget), quark_action_muxer, muxer, @@ -13427,3 +13430,133 @@ gtk_widget_should_layout (GtkWidget *widget) return TRUE; } +/* + * gtk_widget_class_install_action: + * @widget_class: a #GtkWidgetClass + * @action_name: a prefixed action name, such as "clipboard.paste" + * @activate: callback to use when the action is activated + * + * This should be called at class initialization time to specify + * actions to be added for all instances of this class. + * + * Actions installed by this function are stateless. The only state + * they have is whether they are enabled or not. For more complicated + * actions, see gtk_widget_class_install_stateful_action(). + */ +void +gtk_widget_class_install_action (GtkWidgetClass *widget_class, + const char *action_name, + GtkWidgetActionActivateFunc activate) +{ + gtk_widget_class_install_stateful_action (widget_class, action_name, activate, + NULL, NULL, NULL); +} + +/* + * gtk_widget_class_install_stateful_action: + * @widget_class: a #GtkWidgetClass + * @action_name: a prefixed action name, such as "clipboard.paste" + * @activate: callback to use when the action is activated + * @parameter_type: (allow-none): the parameter type, or %NULL + * @query: (allow-none): callback to use when the action properties + are queried, or %NULL for always-enabled stateless actions + * @query_state: (allow-none): callback to use when the action state + is queried, or %NULL for stateless actions + * + * This should be called at class initialization time to specify + * actions to be added for all instances of this class. + * + * Actions installed in this way can be simple or stateful. + * See the #GAction documentation for more information. + */ +void +gtk_widget_class_install_stateful_action (GtkWidgetClass *widget_class, + const char *action_name, + GtkWidgetActionActivateFunc activate, + const char *parameter_type, + GtkWidgetActionSetStateFunc set_state, + GtkWidgetActionGetStateFunc get_state) +{ + GtkWidgetClassPrivate *priv = widget_class->priv; + GtkWidgetAction *action; + + g_return_if_fail (GTK_IS_WIDGET_CLASS (widget_class)); + + if (priv->actions == NULL) + priv->actions = g_ptr_array_new (); + else if (GTK_IS_WIDGET_CLASS (&widget_class->parent_class)) + { + GtkWidgetClass *parent_class = GTK_WIDGET_CLASS (&widget_class->parent_class); + GtkWidgetClassPrivate *parent_priv = parent_class->priv; + GPtrArray *parent_actions = parent_priv->actions; + + if (priv->actions == parent_actions) + { + int i; + + priv->actions = g_ptr_array_new (); + for (i = 0; i < parent_actions->len; i++) + g_ptr_array_add (priv->actions, g_ptr_array_index (parent_actions, i)); + } + } + + action = g_new0 (GtkWidgetAction, 1); + action->name = g_strdup (action_name); + action->activate = activate; + action->parameter_type = parameter_type ? g_variant_type_new (parameter_type) : NULL; + action->set_state = set_state; + action->get_state = get_state; + + GTK_NOTE(ACTIONS, + g_message ("%sClass: Adding %s action\n", + g_type_name (G_TYPE_FROM_CLASS (widget_class)), + action_name)); + + g_ptr_array_add (priv->actions, action); +} + +/** + * gtk_widget_action_enabled_changed: + * @widget: a #GtkWidget + * @action_name: action name, such as "clipboard.paste" + * @enabled: whether the action is now enabled + * + * Notify when an action installed with + * gtk_widget_class_install_action() changes its + * enabled state. + */ +void +gtk_widget_action_enabled_changed (GtkWidget *widget, + const char *action_name, + gboolean enabled) +{ + GtkActionMuxer *muxer; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + + muxer = _gtk_widget_get_action_muxer (widget, TRUE); + gtk_action_muxer_action_enabled_changed (muxer, action_name, enabled); +} + +/** + * gtk_widget_action_state_changed: + * @widget: a #GtkWidget + * @action_name: action name, such as "clipboard.paste" + * @state: the new state + * + * Notify when an action installed with + * gtk_widget_class_install_stateful_action() changes + * its state. + */ +void +gtk_widget_action_state_changed (GtkWidget *widget, + const char *action_name, + GVariant *state) +{ + GtkActionMuxer *muxer; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + + muxer = _gtk_widget_get_action_muxer (widget, TRUE); + gtk_action_muxer_action_state_changed (muxer, action_name, state); +} diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h index ca9d768ea6..73e92ac1cb 100644 --- a/gtk/gtkwidget.h +++ b/gtk/gtkwidget.h @@ -1023,6 +1023,76 @@ GDK_AVAILABLE_IN_ALL gboolean gtk_widget_should_layout (GtkWidget *widget); +/** + * GtkWidgetActionActivateFunc: + * @widget: the widget to which the action belongs + * @action_name: the action name + * @parameter: parameter for activation + * + * The type of the callback functions used for activating + * actions installed with gtk_widget_class_install_action(). + * + * The @parameter must match the @parameter_type of the action. + */ +typedef void (* GtkWidgetActionActivateFunc) (GtkWidget *widget, + const char *action_name, + GVariant *parameter); + +/** + * GtkWidgetActionGetStateFunc: + * @widget: the widget to which the action belongs + * @action_name: the action name + * + * The type of the callback functions used to query the state + * of stateful actions installed with gtk_widget_class_install_action(). + * + * See the #GAction documentation for more details about the + * meaning of these properties. + */ +typedef GVariant * (* GtkWidgetActionGetStateFunc) (GtkWidget *widget, + const char *action_name); + +/** + * GtkWidgetActionSetStateFunc: + * @widget: the widget to which the action belongs + * @action_name: the action name + * @state: the new state + * + * The type of the callback functions used to change the + * state of actions installed with gtk_widget_class_install_action(). + * + * The @state must match the @state_type of the action. + * + * This callback is used when the action state is + * changed via the #GActionGroup API. + */ +typedef void (*GtkWidgetActionSetStateFunc) (GtkWidget *widget, + const char *action_name, + GVariant *state); + +GDK_AVAILABLE_IN_ALL +void gtk_widget_class_install_action (GtkWidgetClass *widget_class, + const char *action_name, + GtkWidgetActionActivateFunc activate); + +GDK_AVAILABLE_IN_ALL +void gtk_widget_class_install_stateful_action (GtkWidgetClass *widget_class, + const char *action_name, + GtkWidgetActionActivateFunc activate, + const char *parameter_type, + GtkWidgetActionSetStateFunc set_state, + GtkWidgetActionGetStateFunc get_state); + +GDK_AVAILABLE_IN_ALL +void gtk_widget_action_enabled_changed (GtkWidget *widget, + const char *action_name, + gboolean enabled); +GDK_AVAILABLE_IN_ALL +void gtk_widget_action_state_changed (GtkWidget *widget, + const char *action_name, + GVariant *state); + + G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkWidget, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkRequisition, gtk_requisition_free)