-
Notifications
You must be signed in to change notification settings - Fork 0
Simpler Actions
Gio.SimpleAction and Gio.PropertyAction are GObject's mechanisms for exposing named, invokable operations on a widget or application. They are similar to signals, but feature different options and use cases. They integrate with GTK's action system, meaning they can be triggered from menus and keyboard shortcuts without needing access to the source object.
In plain GJS, setting up Simple Actions on a widget requires some manual wiring.
class MyWdiget extends Gtk.Box {
static {
GObject.registerClass({ GTypeName: "MyWidget" }, this)
}
readonly #save_action = new Gio.SimpleAction({ name: "save" })
readonly #open_action = new Gio.SimpleAction({ name: "open", parameter_type: new GLib.VariantType("s") })
constructor(params?: Partial<Gtk.Box.ConstructorParams>) {
super(params)
const group = new Gio.SimpleActionGroup()
this.#save_action.connect("activate", this.#on_save.bind(this))
group.add_action(this.#save_action)
this.#open_action.connect("activate", this.#on_open.bind(this))
group.add_action(this.#open_action)
this.insert_action_group(group)
}
#on_save(): void {
print("saving!")
}
#on_open(action: Gio.SimpleAction, path: GLib.Variant<"s">): void {
print("opening:", path)
}
}Every action requires manual instantiation, connection, and the action group itself needs to be created and inserted manually as well.
@GClass()
class MyWidget extends from(Gtk.Box, {
save: SimplerAction.void(),
open: SimplerAction.param.string(),
}) {
@OnSimplerAction("save")
#on_save(): void {
print("saving!")
}
@OnSimplerAction("open")
#on_open(path: string): void {
print("opening", path)
}
}GObjectify creates and inserts the action group, creates each action and adds them to the group, and through the use of OnSimplerAction, connects the action's activation or state-change to the method. Also, each action is available on the instance by name (example: this.save is the save SimplerAction).
Every action created through SimplerAction falls into one of four kinds:
- void:
SimplerAction.void(config?)no parameters and no stored state. - param:
SimplerAction.param.<type>(config?)takes on parameter of the specified type when activated, but does not store the value. - state:
SimplerAction.state.<type>(cofnig?)takes a parameter of the specified type when activated, and does store the value, which can be read and written through with the.stateproperty on the action. - property:
SimplerAction.property(field, transformer, config?). This is aGio.PropertyAction, a type of action which bases off of a GObject instance's property of the givenfieldname. The field used must be a readwrite property of type boolean, string, or number. See the Property Actions section below for more info.
SimplerAction.property creates a Gio.PropertyAction, whose state always mirros the value of the backing property on the class. The backing property must be a readwrite property of type boolean, string, or number. This kind of action is primarily useful for Gio.Menu bindings, which allow an application's menu to change options of operation, or other settings, such as a Theme setting.
Example:
@GClass()
class MyWidget extends from(Gtk.Box, {
dark_mode: Property.readwrite.bool(),
dark_mode_act: SimplerAction.property("dark_mode", (v) => GLib.Variant.new_boolean(v)),
}) { ... }The transformer function (second parameter to property) converts an incoming JS value into the GLib.Variant needed to update the underlying property. Property actions cannot be connected with @OnSimplerAction, since the changes are done to the underlying property. The @WatchProp decorator can be used insidead for this behavior.
Each action is available on the instance by name, with the Action's kind defining what methods and fields are available on it.
| Kind | Members |
|---|---|
void |
.activate(), .on_activated(callback), .enabled
|
param |
.activate(param), .on_activated(callback), .enabled
|
state |
.activate(new_state), .on_state_changed(callback), .state, .enabled
|
prop |
.activate(new_state), .enabled (read-only) |
The method decorator @OnSimplerAction connects a method to the specified action's activate or state-change event. The decorator accepts the name of the action field on the class. Multiple may be stacked upon a method to connect it to multiple actions.
@OnSimplerAction("save")
#on_save(): void { ... }
// param action, recieves the unpacked parameter directly
@OnSimplerAction("rename")
#on_rename(new_name: string): void { ... }
// state action, recieves the new unpacked state directly
@OnSimplerAction("sort_order")
#on_sort_order_changed(order: string): void { ... }@OnSimplerAction cannot target prop-kind actions, since they rely on the underlying property changing instead of their own state changing.
On subclasses of GioApplication and GtkApplicationWindow, Simple Actions may optionally have accelerators, which allows them to be activated with keyboard shortcuts.
@GClass()
export class MyApp extends from(Gtk.Application, {
save: SimplerAction.void({ accels: ["<Control>s"] }),
open: SimplerAction.void({ accels: ["<Control>o"] }),
}) { ... }The save and open actions can be globally activated by pressing the corresponding keyboard shortcut. The string format for the accels array follows the gtk_accelerator_parse format used in gtk_application_set_accels_for_action. Attempting to use accels on any other kind of subclass throws an error as soon as the class is decorated.
GObjectify automatically determines where to register actions based on the class being extended:
-
Gtk.Widget-> actions go into aGio.SimpleActionGroupinserted into the instance -
Gtk.ApplicationWindow-> actions are added directly to the window instance -
Gtk.Application-> actions are added directly to the application instance, and accelerators are registered
Every action is ultimately addressed by a detailed action name, which is a prefix + name to refer to the action. GTK expects these string-based IDs Gio.MenuItem.set_detailed_action and widget.activate_action. GObjectify chooses the prefix automatically based on what the class extends:
| Extends | Prefix | Example |
|---|---|---|
Gtk.ApplicationWindow |
win |
save: SimplerAction... -> "win.save"
|
Gio.Application & Gtk.Application
|
app |
quit: SimplerAction... -> "app.quit"
|
| Any other subclass | The class's own name |
refresh: SimplerAction... -> "MyWidget.refresh"
|
For plain widgets, this isn't an arbitrary choice. GObjectify uses the class name as the name of the Gio.SimpleActionGroup it inserts onto the instance, so the group and the prefix always agree.
You will rarely need to type a detailed action name yourself, as the action's own .activate() method, @OnSimplerAction, Menu.item/Menu.item_group/Menu.items_for, and $actions.<name>.activate() (below) all resolve it internally. Base-GTK/GIO methods will still ask for this detailed name, so it's useful to know.
Every class that extends from also exposes a static $actions object, giving access to each action's fully-qualified detailed_name, an activate method, and the underly GLibVariant format string, all without needing an instance at all. The static descriptor holds the static activate method, the detailed action.
type SortOrder = "name" | "date-created"
@GClass()
class MyBox extends from(Gtk.Box, {
save: SimplerAction.void(),
sort_order: Property.readwrite.string("name").as<SortOrder>(),
sort_order_act: SimplerAction.property("sort_order", (s: SortOrder) => GLib.Variant.new_string(s)),
}) { ... }
// No need to have a reference to a MyBox object to activate its actions in a type-safe manner
const w = new SomeWidgetClass()
MyBox.$actions.save.activate(w)
MyBox.$actions.sort_order_act.activate(w, "date-created")