GAction

A GAction is a representation of a single user-interesting action in an application.

To use GAction you should be using GtkApplication. See HowDoI/GtkApplication.

It's possible to use GAction without GtkApplication but this is not discussed here. This page is about using GAction within your GtkApplication.

What it is, what it isn't

A GAction is essentially a way to tell the toolkit about a piece of functionality in your program, and to give it a name.

Actions are purely functional. They do not contain any presentational information.

An action has four pieces of information associated with it:

  • a name as an identifier (usually all-lowercase, untranslated English string)

  • an enabled flag indicating if the action can be activated or not (like "sensitive")

  • an optional state value, for stateful actions (like a boolean for toggles)

  • an optional parameter type, used when activating the action

An action supports two operations:

  • activation, invoked with an optional parameter (of the correct type, see above)

  • state change request, invoked with a new requested state value (of the correct type). Only supported for stateful actions.

Here are some rules about an action:

  • the name is immutable (in the sense that it will never change) and it is never NULL

  • the enabled flag can change

  • the parameter type is immutable

  • the parameter type is optional: it can be NULL
  • if the parameter type is NULL then action activation must be done without a parameter (ie: a NULL GVariant pointer)

  • if the parameter type is non-NULL then the parameter must have this type

  • the state can change, but it cannot change type

  • if the action was stateful when it was created, it will always have a state and it will always have exactly the same type (such as boolean or string)
  • if the action was stateless when it was created, it can never have a state
  • you can only request state changes on stateful actions and it is only possible to request that the state change to a value of the same type as the existing state

An action does not have any of the following:

  • a label
  • an icon
  • a way of creating a widget corresponding to it
  • any other sort of presentational information

Action state and parameters

Most actions in your application will be stateless actions with no parameters. These typically appear as menu items with no special decoration. An example is "quit".

Stateful actions are used to represent an action which has a closely-associated state of some kind. A good example is a "fullscreen" action. For this case, you'd expect to see a checkmark next to the menu item when the fullscreen option is active. This is usually called a toggle action, and it has a boolean state. By convention, toggle actions have no parameter type for activation: activating the action always toggles the state.

Another common case is to have an action representing a enumeration of possible values of a given type (typically string). This is often called a radio action and is usually represented in the user interface with radio buttons or radio menu items, or sometimes a combobox. A good example is "text-justify" with possible values "left", "center", and "right". By convention, these types of actions have a parameter type equal to their state type, and activating them with a particular parameter value is equivalent to changing their state to that value.

This approach to handling radio buttons is different than many other action systems such as GtkAction. With GAction, there is only one action for "text-justify" and "left", "center" and "right" are possible states on that action. There are not three separate "justify-left", "justify-center" and "justify-right" actions.

The final common type of action is a stateless action with a parameter. This is typically used for actions like "open-bookmark" where the parameter to the action would be the identifier of the bookmark to open.

Action target and detailed names

Because some types of actions cannot be invoked without a parameter, it is often important to specify a parameter when referring to the action from a place where it will be invoked (such as from a radio button that sets the state to a particular value or from a menu item that opens a specific bookmark). From these contexts, the value used for the action parameter is typically called the target of the action.

Even though toggle actions have a state, they do not have a parameter. Therefore, a target value is not needed when referring to them -- they will always be toggled on activation.

Most APIs that allow using a GAction (such as GMenuModel and GtkActionable) allow use of detailed action names. This is a convenient way of specifying an action name and an action target with a single string.

In the case that the action target is a string with no unusual characters (ie: only alpha-numeric, plus '-' and '.') then you can use a detailed action name of the form "justify::left" to specify the justify action with a target of left.

In the case that the action target is not a string, or contains unusual characters, you can use the more general format "action-name(5)", where the "5" here is any valid text-format GVariant (ie: a string that can be parsed by g_variant_parse()). Another example is "open-bookmark('http://gnome.org/')".

You can convert between detailed action names and split-out action names and target values using g_action_parse_detailed_action_name() and g_action_print_detailed_action_name() but usually you will not need to. Most APIs will provide both ways of specifying actions with targets.

Action scopes

Actions are always scoped to a particular object on which they operate.

The two things that it's possible to scope an action to with GTK+ are applications and windows.

Actions scoped to windows should be the actions that specifically impact that window. These are actions like "fullscreen" and "close", or in the case that a window contains a document, "save" and "print".

Actions that impact the application as a whole rather than one specific window are scoped to the application. These are actions like "about" and "preferences".

If a particular action is scoped to a window then it is scoped to a specific window. Another way of saying this: if your application has a "fullscreen" action that applies to windows and it has three windows, then it will have three fullscreen actions: one for each window.

Having a separate action per-window allows for each window to have a separate state for each instance of the action as well as being able to control the enabled state of the action on a per-window basis.

Actions are added to their relevant scope (application or window) using the GActionMap interface.

GActionMap

GActionMap is an interface exposing a mapping of action names to actions. It is implemented by GtkApplication and GtkApplicationWindow. Actions can be added, removed, or looked up.

void                g_action_map_add_action             (GActionMap  *action_map,
                                                         GAction     *action);
void                g_action_map_remove_action          (GActionMap  *action_map,
                                                         const gchar *action_name);
GAction *           g_action_map_lookup_action          (GActionMap  *action_map,
                                                         const gchar *action_name);

If you want to insert several actions at the same time, it is typically faster and easier to use GActionEntry.

When referring to actions on a GActionMap only the name of the action itself is used (ie: "quit", not "app.quit"). The "app.quit" form is only used when referring to actions from places like a GMenu or GtkActionable widget where the scope of the action is not already known. Because you're using the GtkApplication or GtkApplicationWindow as the GActionMap it is clear which object your action is scoped to, so the prefix is not needed.

GSimpleAction vs. GAction

GAction is an interface with several implementations. The one that you are most likely to use directly is GSimpleAction.

A good way to think about the split between GAction and GSimpleAction is that GAction is the "consumer interface" and GSimpleAction is the provider interface. The GAction interface provides the functions that are consumed by users/callers/displayers of the action (such as menus and widgets). The GSimpleAction interface is only accessed by the code that provides the implementation for the action itself.

Note that GActionMap takes a GAction. Your action will only be "consumed" as a result of you putting it in a GActionMap.

Compare:

  • GAction has a function for checking if an action is enabled (g_action_get_enabled()) but only the GSimpleAction API can enable or disable an action (g_simple_action_set_enabled()).

  • GAction has a function to query the state of the action (g_action_get_state()) and request changes to it (g_action_change_state()) but only GSimpleAction has the API to directly set the state value (g_simple_action_set_state()).

If you want to provide a custom GAction implementation then you can have your own mechanism to control access to state setting and enabled. The GSettings GAction implementation, for example, gets its state directly from the value in GSettings and is enabled according to lockdown in effect on the key. It is not possible to directly modify these values in any way (although it is possible to indirectly affect the state by changing the value of the setting, if you have permission to do so).

Using GSimpleAction

If you are implementing actions, probably you will do it with GSimpleAction.

GSimpleAction has two interesting signals: activate and change-state. These correspond directly to g_action_activate() and g_action_change_state(). You will almost certainly need to connect a handler to the activate signal in order to handle the action being activated. The signal handler takes a GVariant parameter which is the parameter that was passed to g_action_activate().

If your action is stateful, you may also want to connect a change-state handler to deal with state change requests. If your action is stateful and you do not connect a handler for the change-state signal then the default is that all state change requests will always change the state to the requested value. Even if you always want the state to be set to the requested value, you will probably want to connect a handler so that you can take some action in response to the state being changed.

The default behaviour of setting the state in response to g_action_change_state() is disabled when connecting a handler to change-state. You therefore need to be sure to call g_simple_action_set_state() from your handler if you actually want the state to change.

A convenient way to bulk-create all the GSimpleActions you need to add to a GActionMap is to use a GActionEntry array and `g_action_map_add_action_entries()'.

static GActionEntry app_entries[] =
{
  { "preferences", preferences_activated, NULL, NULL, NULL },
  { "quit", quit_activated, NULL, NULL, NULL }
};

static void
example_app_startup (GApplication *app)
{
  ...
  g_action_map_add_action_entries (G_ACTION_MAP (app),
                                   app_entries, G_N_ELEMENTS (app_entries),
                                   app);
  ...
}

Other kinds of actions

Besides GSimpleAction, GIO provides some other implementations of GAction. One is GSettingsAction, which wraps a GSettings key with an action that represents the value of the setting and lets you set the key to a new value when activated. Another is GPropertyAction, which similarly wraps a GObject property.

Both GSettingsAction and GPropertyAction implement toggle-on-activate behaviour for boolean states - note that GSimpleAction does not, you have to implement an activate handler yourself for that.

Adding actions to your GtkApplication

You can add a GAction to anything implementing the GActionMap interface, including GtkApplication. This is done with g_action_map_add_action(). For GSimpleActions, you can also use g_action_map_add_action_entries().

Typically, you will want to do this during the startup phase of your application. See HowDoI/GtkApplication for more information on that.

It's possible to add or remove actions at any time, but doing it before startup is wasteful in case the application is a remote instance (and will just exit anyway). It is also possible to dynamically add and remove actions any time after startup, when the application is running.

Adding actions to your GtkApplicationWindow

GtkApplicationWindow also implements GActionMap. You will typically want to add most actions to your window when it is constructed. It is possible to add and remove actions at any time while the window exists.

For example:

   1 from gi.repository import Gio, Gtk
   2 
   3 def saveCb(action, param):
   4     print "You are welcome"
   5 
   6 def activateCb(app):
   7     window = Gtk.ApplicationWindow()
   8     app.add_window(window)
   9     window.show()
  10 
  11     action = Gio.SimpleAction.new("save", None)
  12     action.connect("activate", saveCb)
  13     window.add_action(action)
  14 
  15     button = Gtk.Button.new()
  16     button.set_label("Save")
  17     button.show()
  18     window.add(button)
  19     button.set_action_name("win.save")
  20 
  21 app = Gtk.Application()
  22 app.connect("activate", activateCb)
  23 app.run([])

Accelerators (keybindings) for actions

Use gtk_application_add_accelerator inside your application's startup implementation. For example:

   const char *accels[] = {"<Contol><Shift>T", NULL};
   gtk_application_set_accels_for_action (app, "win.new_tab", accels);

What can be done with actions

GActions that you add to your application or window can be used in several different ways.

  • used with GMenu
  • used with GtkActionable widgets

  • remotely activated from a remote GApplication instance (only for application actions)
  • listed as "Additional application actions" in desktop files (only for application actions)
  • remotely activated from other D-Bus callers (such as Ubuntu's HUD)
  • used with GNotification notifications (only for application actions, available since GLib 2.39)

HowDoI/GAction (last edited 2017-05-24 16:11:14 by NielsDeGraef)