gtk-ng: add a helper for creating GTK actions (#8228)
- Reduces boilerplate. - Adds type safety. - Adds comptime checks for action and group names which otherwise could cause panics at runtime.pull/8235/head
commit
b7913f09ad
|
|
@ -11,4 +11,5 @@ pub const WeakRef = @import("gtk-ng/weak_ref.zig").WeakRef;
|
||||||
|
|
||||||
test {
|
test {
|
||||||
@import("std").testing.refAllDecls(@This());
|
@import("std").testing.refAllDecls(@This());
|
||||||
|
_ = @import("gtk-ng/ext.zig");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1112,38 +1112,16 @@ pub const Application = extern struct {
|
||||||
const as_variant_type = glib.VariantType.new("as");
|
const as_variant_type = glib.VariantType.new("as");
|
||||||
defer as_variant_type.free();
|
defer as_variant_type.free();
|
||||||
|
|
||||||
// The set of actions. Each action has (in order):
|
const actions = [_]ext.actions.Action(Self){
|
||||||
// [0] The action name
|
.init("new-window", actionNewWindow, null),
|
||||||
// [1] The callback function
|
.init("new-window-command", actionNewWindow, as_variant_type),
|
||||||
// [2] The glib.VariantType of the parameter
|
.init("open-config", actionOpenConfig, null),
|
||||||
//
|
.init("present-surface", actionPresentSurface, t_variant_type),
|
||||||
// For action names:
|
.init("quit", actionQuit, null),
|
||||||
// https://docs.gtk.org/gio/type_func.Action.name_is_valid.html
|
.init("reload-config", actionReloadConfig, null),
|
||||||
const actions = .{
|
|
||||||
.{ "new-window", actionNewWindow, null },
|
|
||||||
.{ "new-window-command", actionNewWindow, as_variant_type },
|
|
||||||
.{ "open-config", actionOpenConfig, null },
|
|
||||||
.{ "present-surface", actionPresentSurface, t_variant_type },
|
|
||||||
.{ "quit", actionQuit, null },
|
|
||||||
.{ "reload-config", actionReloadConfig, null },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const action_map = self.as(gio.ActionMap);
|
ext.actions.add(Self, self, &actions);
|
||||||
inline for (actions) |entry| {
|
|
||||||
const action = gio.SimpleAction.new(
|
|
||||||
entry[0],
|
|
||||||
entry[2],
|
|
||||||
);
|
|
||||||
defer action.unref();
|
|
||||||
_ = gio.SimpleAction.signals.activate.connect(
|
|
||||||
action,
|
|
||||||
*Self,
|
|
||||||
entry[1],
|
|
||||||
self,
|
|
||||||
.{},
|
|
||||||
);
|
|
||||||
action_map.addAction(action.as(gio.Action));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Setup our global shortcuts.
|
/// Setup our global shortcuts.
|
||||||
|
|
|
||||||
|
|
@ -160,62 +160,26 @@ pub const SplitTree = extern struct {
|
||||||
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
||||||
|
|
||||||
// Initialize our actions
|
// Initialize our actions
|
||||||
self.initActions();
|
self.initActionMap();
|
||||||
|
|
||||||
// Initialize some basic state
|
// Initialize some basic state
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
priv.pending_close = null;
|
priv.pending_close = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initActions(self: *Self) void {
|
fn initActionMap(self: *Self) void {
|
||||||
// The set of actions. Each action has (in order):
|
const s_variant_type = glib.ext.VariantType.newFor([:0]const u8);
|
||||||
// [0] The action name
|
defer s_variant_type.free();
|
||||||
// [1] The callback function
|
|
||||||
// [2] The glib.VariantType of the parameter
|
const actions = [_]ext.actions.Action(Self){
|
||||||
//
|
|
||||||
// For action names:
|
|
||||||
// https://docs.gtk.org/gio/type_func.Action.name_is_valid.html
|
|
||||||
const actions: []const struct {
|
|
||||||
[:0]const u8,
|
|
||||||
*const fn (*gio.SimpleAction, ?*glib.Variant, *Self) callconv(.c) void,
|
|
||||||
?*glib.VariantType,
|
|
||||||
} = &.{
|
|
||||||
// All of these will eventually take a target surface parameter.
|
// All of these will eventually take a target surface parameter.
|
||||||
// For now all our targets originate from the focused surface.
|
// For now all our targets originate from the focused surface.
|
||||||
.{ "new-split", &actionNewSplit, glib.ext.VariantType.newFor([:0]const u8) },
|
.init("new-split", actionNewSplit, s_variant_type),
|
||||||
.{ "equalize", &actionEqualize, null },
|
.init("equalize", actionEqualize, null),
|
||||||
.{ "zoom", &actionZoom, null },
|
.init("zoom", actionZoom, null),
|
||||||
};
|
};
|
||||||
|
|
||||||
// We need to collect our actions into a group since we're just
|
ext.actions.addAsGroup(Self, self, "split-tree", &actions);
|
||||||
// a plain widget that doesn't implement ActionGroup directly.
|
|
||||||
const group = gio.SimpleActionGroup.new();
|
|
||||||
errdefer group.unref();
|
|
||||||
const map = group.as(gio.ActionMap);
|
|
||||||
for (actions) |entry| {
|
|
||||||
const action = gio.SimpleAction.new(
|
|
||||||
entry[0],
|
|
||||||
entry[2],
|
|
||||||
);
|
|
||||||
defer {
|
|
||||||
action.unref();
|
|
||||||
if (entry[2]) |ptype| ptype.free();
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = gio.SimpleAction.signals.activate.connect(
|
|
||||||
action,
|
|
||||||
*Self,
|
|
||||||
entry[1],
|
|
||||||
self,
|
|
||||||
.{},
|
|
||||||
);
|
|
||||||
map.addAction(action.as(gio.Action));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.as(gtk.Widget).insertActionGroup(
|
|
||||||
"split-tree",
|
|
||||||
group.as(gio.ActionGroup),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new split in the given direction from the currently
|
/// Create a new split in the given direction from the currently
|
||||||
|
|
|
||||||
|
|
@ -1233,7 +1233,7 @@ pub const Surface = extern struct {
|
||||||
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
||||||
|
|
||||||
// Initialize our actions
|
// Initialize our actions
|
||||||
self.initActions();
|
self.initActionMap();
|
||||||
|
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
|
|
||||||
|
|
@ -1281,43 +1281,12 @@ pub const Surface = extern struct {
|
||||||
self.propConfig(undefined, null);
|
self.propConfig(undefined, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initActions(self: *Self) void {
|
fn initActionMap(self: *Self) void {
|
||||||
// The set of actions. Each action has (in order):
|
const actions = [_]ext.actions.Action(Self){
|
||||||
// [0] The action name
|
.init("prompt-title", actionPromptTitle, null),
|
||||||
// [1] The callback function
|
|
||||||
// [2] The glib.VariantType of the parameter
|
|
||||||
//
|
|
||||||
// For action names:
|
|
||||||
// https://docs.gtk.org/gio/type_func.Action.name_is_valid.html
|
|
||||||
const actions = .{
|
|
||||||
.{ "prompt-title", actionPromptTitle, null },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// We need to collect our actions into a group since we're just
|
ext.actions.addAsGroup(Self, self, "surface", &actions);
|
||||||
// a plain widget that doesn't implement ActionGroup directly.
|
|
||||||
const group = gio.SimpleActionGroup.new();
|
|
||||||
errdefer group.unref();
|
|
||||||
const map = group.as(gio.ActionMap);
|
|
||||||
inline for (actions) |entry| {
|
|
||||||
const action = gio.SimpleAction.new(
|
|
||||||
entry[0],
|
|
||||||
entry[2],
|
|
||||||
);
|
|
||||||
defer action.unref();
|
|
||||||
_ = gio.SimpleAction.signals.activate.connect(
|
|
||||||
action,
|
|
||||||
*Self,
|
|
||||||
entry[1],
|
|
||||||
self,
|
|
||||||
.{},
|
|
||||||
);
|
|
||||||
map.addAction(action.as(gio.Action));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.as(gtk.Widget).insertActionGroup(
|
|
||||||
"surface",
|
|
||||||
group.as(gio.ActionGroup),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispose(self: *Self) callconv(.c) void {
|
fn dispose(self: *Self) callconv(.c) void {
|
||||||
|
|
@ -1780,10 +1749,7 @@ pub const Surface = extern struct {
|
||||||
) callconv(.c) c_int {
|
) callconv(.c) c_int {
|
||||||
const alloc = Application.default().allocator();
|
const alloc = Application.default().allocator();
|
||||||
|
|
||||||
if (g_value_holds(
|
if (ext.gValueHolds(value, gdk.FileList.getGObjectType())) {
|
||||||
value,
|
|
||||||
gdk.FileList.getGObjectType(),
|
|
||||||
)) {
|
|
||||||
var data = std.ArrayList(u8).init(alloc);
|
var data = std.ArrayList(u8).init(alloc);
|
||||||
defer data.deinit();
|
defer data.deinit();
|
||||||
|
|
||||||
|
|
@ -1827,7 +1793,7 @@ pub const Surface = extern struct {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_value_holds(value, gio.File.getGObjectType())) {
|
if (ext.gValueHolds(value, gio.File.getGObjectType())) {
|
||||||
const object = value.getObject() orelse return 0;
|
const object = value.getObject() orelse return 0;
|
||||||
const file = gobject.ext.cast(gio.File, object) orelse return 0;
|
const file = gobject.ext.cast(gio.File, object) orelse return 0;
|
||||||
const path = file.getPath() orelse return 0;
|
const path = file.getPath() orelse return 0;
|
||||||
|
|
@ -1855,7 +1821,7 @@ pub const Surface = extern struct {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_value_holds(value, gobject.ext.types.string)) {
|
if (ext.gValueHolds(value, gobject.ext.types.string)) {
|
||||||
if (value.getString()) |string| {
|
if (value.getString()) |string| {
|
||||||
Clipboard.paste(self, std.mem.span(string));
|
Clipboard.paste(self, std.mem.span(string));
|
||||||
}
|
}
|
||||||
|
|
@ -3039,16 +3005,6 @@ const Clipboard = struct {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Check a GValue to see what's type its wrapping. This is equivalent to GTK's
|
|
||||||
/// `G_VALUE_HOLDS` macro but Zig's C translator does not like it.
|
|
||||||
fn g_value_holds(value_: ?*gobject.Value, g_type: gobject.Type) bool {
|
|
||||||
if (value_) |value| {
|
|
||||||
if (value.f_g_type == g_type) return true;
|
|
||||||
return gobject.typeCheckValueHolds(value, g_type) != 0;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute a fraction [0.0, 1.0] from the supplied progress, which is clamped
|
/// Compute a fraction [0.0, 1.0] from the supplied progress, which is clamped
|
||||||
/// to [0, 100].
|
/// to [0, 100].
|
||||||
fn computeFraction(progress: u8) f64 {
|
fn computeFraction(progress: u8) f64 {
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,7 @@ pub const Tab = extern struct {
|
||||||
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
||||||
|
|
||||||
// Init our actions
|
// Init our actions
|
||||||
self.initActions();
|
self.initActionMap();
|
||||||
|
|
||||||
// If our configuration is null then we get the configuration
|
// If our configuration is null then we get the configuration
|
||||||
// from the application.
|
// from the application.
|
||||||
|
|
@ -198,45 +198,13 @@ pub const Tab = extern struct {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Setup our action map.
|
fn initActionMap(self: *Self) void {
|
||||||
fn initActions(self: *Self) void {
|
const actions = [_]ext.actions.Action(Self){
|
||||||
// The set of actions. Each action has (in order):
|
.init("close", actionClose, null),
|
||||||
// [0] The action name
|
.init("ring-bell", actionRingBell, null),
|
||||||
// [1] The callback function
|
|
||||||
// [2] The glib.VariantType of the parameter
|
|
||||||
//
|
|
||||||
// For action names:
|
|
||||||
// https://docs.gtk.org/gio/type_func.Action.name_is_valid.html
|
|
||||||
const actions = .{
|
|
||||||
.{ "close", actionClose, null },
|
|
||||||
.{ "ring-bell", actionRingBell, null },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// We need to collect our actions into a group since we're just
|
ext.actions.addAsGroup(Self, self, "tab", &actions);
|
||||||
// a plain widget that doesn't implement ActionGroup directly.
|
|
||||||
const group = gio.SimpleActionGroup.new();
|
|
||||||
errdefer group.unref();
|
|
||||||
const map = group.as(gio.ActionMap);
|
|
||||||
inline for (actions) |entry| {
|
|
||||||
const action = gio.SimpleAction.new(
|
|
||||||
entry[0],
|
|
||||||
entry[2],
|
|
||||||
);
|
|
||||||
defer action.unref();
|
|
||||||
_ = gio.SimpleAction.signals.activate.connect(
|
|
||||||
action,
|
|
||||||
*Self,
|
|
||||||
entry[1],
|
|
||||||
self,
|
|
||||||
.{},
|
|
||||||
);
|
|
||||||
map.addAction(action.as(gio.Action));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.as(gtk.Widget).insertActionGroup(
|
|
||||||
"tab",
|
|
||||||
group.as(gio.ActionGroup),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -331,42 +331,27 @@ pub const Window = extern struct {
|
||||||
|
|
||||||
/// Setup our action map.
|
/// Setup our action map.
|
||||||
fn initActionMap(self: *Self) void {
|
fn initActionMap(self: *Self) void {
|
||||||
const actions = .{
|
const actions = [_]ext.actions.Action(Self){
|
||||||
.{ "about", actionAbout, null },
|
.init("about", actionAbout, null),
|
||||||
.{ "close", actionClose, null },
|
.init("close", actionClose, null),
|
||||||
.{ "close-tab", actionCloseTab, null },
|
.init("close-tab", actionCloseTab, null),
|
||||||
.{ "new-tab", actionNewTab, null },
|
.init("new-tab", actionNewTab, null),
|
||||||
.{ "new-window", actionNewWindow, null },
|
.init("new-window", actionNewWindow, null),
|
||||||
.{ "ring-bell", actionRingBell, null },
|
.init("ring-bell", actionRingBell, null),
|
||||||
.{ "split-right", actionSplitRight, null },
|
.init("split-right", actionSplitRight, null),
|
||||||
.{ "split-left", actionSplitLeft, null },
|
.init("split-left", actionSplitLeft, null),
|
||||||
.{ "split-up", actionSplitUp, null },
|
.init("split-up", actionSplitUp, null),
|
||||||
.{ "split-down", actionSplitDown, null },
|
.init("split-down", actionSplitDown, null),
|
||||||
.{ "copy", actionCopy, null },
|
.init("copy", actionCopy, null),
|
||||||
.{ "paste", actionPaste, null },
|
.init("paste", actionPaste, null),
|
||||||
.{ "reset", actionReset, null },
|
.init("reset", actionReset, null),
|
||||||
.{ "clear", actionClear, null },
|
.init("clear", actionClear, null),
|
||||||
// TODO: accept the surface that toggled the command palette
|
// TODO: accept the surface that toggled the command palette
|
||||||
.{ "toggle-command-palette", actionToggleCommandPalette, null },
|
.init("toggle-command-palette", actionToggleCommandPalette, null),
|
||||||
.{ "toggle-inspector", actionToggleInspector, null },
|
.init("toggle-inspector", actionToggleInspector, null),
|
||||||
};
|
};
|
||||||
|
|
||||||
const action_map = self.as(gio.ActionMap);
|
ext.actions.add(Self, self, &actions);
|
||||||
inline for (actions) |entry| {
|
|
||||||
const action = gio.SimpleAction.new(
|
|
||||||
entry[0],
|
|
||||||
entry[2],
|
|
||||||
);
|
|
||||||
defer action.unref();
|
|
||||||
_ = gio.SimpleAction.signals.activate.connect(
|
|
||||||
action,
|
|
||||||
*Self,
|
|
||||||
entry[1],
|
|
||||||
self,
|
|
||||||
.{},
|
|
||||||
);
|
|
||||||
action_map.addAction(action.as(gio.Action));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Winproto backend for this window.
|
/// Winproto backend for this window.
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,15 @@
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
const gio = @import("gio");
|
||||||
const glib = @import("glib");
|
const glib = @import("glib");
|
||||||
const gobject = @import("gobject");
|
const gobject = @import("gobject");
|
||||||
const gtk = @import("gtk");
|
const gtk = @import("gtk");
|
||||||
|
|
||||||
|
pub const actions = @import("ext/actions.zig");
|
||||||
|
|
||||||
/// Wrapper around `gobject.boxedCopy` to copy a boxed type `T`.
|
/// Wrapper around `gobject.boxedCopy` to copy a boxed type `T`.
|
||||||
pub fn boxedCopy(comptime T: type, ptr: *const T) *T {
|
pub fn boxedCopy(comptime T: type, ptr: *const T) *T {
|
||||||
const copy = gobject.boxedCopy(T.getGObjectType(), ptr);
|
const copy = gobject.boxedCopy(T.getGObjectType(), ptr);
|
||||||
|
|
@ -50,3 +54,15 @@ pub fn getAncestor(comptime T: type, widget: *gtk.Widget) ?*T {
|
||||||
// We can assert the unwrap because getAncestor above
|
// We can assert the unwrap because getAncestor above
|
||||||
return gobject.ext.cast(T, ancestor).?;
|
return gobject.ext.cast(T, ancestor).?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check a gobject.Value to see what type it is wrapping. This is equivalent to GTK's
|
||||||
|
/// `G_VALUE_HOLDS()` macro but Zig's C translator does not like it.
|
||||||
|
pub fn gValueHolds(value_: ?*const gobject.Value, g_type: gobject.Type) bool {
|
||||||
|
const value = value_ orelse return false;
|
||||||
|
if (value.f_g_type == g_type) return true;
|
||||||
|
return gobject.typeCheckValueHolds(value, g_type) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
_ = actions;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
const gio = @import("gio");
|
||||||
|
const glib = @import("glib");
|
||||||
|
const gobject = @import("gobject");
|
||||||
|
const gtk = @import("gtk");
|
||||||
|
|
||||||
|
const gValueHolds = @import("../ext.zig").gValueHolds;
|
||||||
|
|
||||||
|
/// Check that an action name is valid.
|
||||||
|
///
|
||||||
|
/// Reimplementation of `g_action_name_is_valid()` so that it can be
|
||||||
|
/// used at comptime.
|
||||||
|
///
|
||||||
|
/// See:
|
||||||
|
/// https://docs.gtk.org/gio/type_func.Action.name_is_valid.html
|
||||||
|
fn gActionNameIsValid(name: [:0]const u8) bool {
|
||||||
|
if (name.len == 0) return false;
|
||||||
|
|
||||||
|
for (name) |c| switch (c) {
|
||||||
|
'-' => continue,
|
||||||
|
'.' => continue,
|
||||||
|
'0'...'9' => continue,
|
||||||
|
'a'...'z' => continue,
|
||||||
|
'A'...'Z' => continue,
|
||||||
|
else => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "gActionNameIsValid" {
|
||||||
|
try testing.expect(gActionNameIsValid("ring-bell"));
|
||||||
|
try testing.expect(!gActionNameIsValid("ring_bell"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Function to create a structure for describing an action.
|
||||||
|
pub fn Action(comptime T: type) type {
|
||||||
|
return struct {
|
||||||
|
pub const Callback = *const fn (*gio.SimpleAction, ?*glib.Variant, *T) callconv(.c) void;
|
||||||
|
|
||||||
|
name: [:0]const u8,
|
||||||
|
callback: Callback,
|
||||||
|
parameter_type: ?*const glib.VariantType,
|
||||||
|
|
||||||
|
/// Function to initialize a new action so that we can comptime check the name.
|
||||||
|
pub fn init(comptime name: [:0]const u8, callback: Callback, parameter_type: ?*const glib.VariantType) @This() {
|
||||||
|
comptime assert(gActionNameIsValid(name));
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.name = name,
|
||||||
|
.callback = callback,
|
||||||
|
.parameter_type = parameter_type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add actions to a widget that implements gio.ActionMap.
|
||||||
|
pub fn add(comptime T: type, self: *T, actions: []const Action(T)) void {
|
||||||
|
addToMap(T, self, self.as(gio.ActionMap), actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add actions to the given map.
|
||||||
|
pub fn addToMap(comptime T: type, self: *T, map: *gio.ActionMap, actions: []const Action(T)) void {
|
||||||
|
for (actions) |entry| {
|
||||||
|
assert(gActionNameIsValid(entry.name));
|
||||||
|
const action = gio.SimpleAction.new(
|
||||||
|
entry.name,
|
||||||
|
entry.parameter_type,
|
||||||
|
);
|
||||||
|
defer action.unref();
|
||||||
|
_ = gio.SimpleAction.signals.activate.connect(
|
||||||
|
action,
|
||||||
|
*T,
|
||||||
|
entry.callback,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
map.addAction(action.as(gio.Action));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add actions to a widget that doesn't implement ActionGroup directly.
|
||||||
|
pub fn addAsGroup(comptime T: type, self: *T, comptime name: [:0]const u8, actions: []const Action(T)) void {
|
||||||
|
comptime assert(gActionNameIsValid(name));
|
||||||
|
|
||||||
|
// Collect our actions into a group since we're just a plain widget that
|
||||||
|
// doesn't implement ActionGroup directly.
|
||||||
|
const group = gio.SimpleActionGroup.new();
|
||||||
|
errdefer group.unref();
|
||||||
|
|
||||||
|
addToMap(T, self, group.as(gio.ActionMap), actions);
|
||||||
|
|
||||||
|
self.as(gtk.Widget).insertActionGroup(
|
||||||
|
name,
|
||||||
|
group.as(gio.ActionGroup),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "adding actions to an object" {
|
||||||
|
// This test requires a connection to an active display environment.
|
||||||
|
if (gtk.initCheck() == 0) return;
|
||||||
|
|
||||||
|
const callbacks = struct {
|
||||||
|
fn callback(_: *gio.SimpleAction, variant_: ?*glib.Variant, self: *gtk.Box) callconv(.c) void {
|
||||||
|
const i32_variant_type = glib.ext.VariantType.newFor(i32);
|
||||||
|
defer i32_variant_type.free();
|
||||||
|
|
||||||
|
const variant = variant_ orelse return;
|
||||||
|
assert(variant.isOfType(i32_variant_type) != 0);
|
||||||
|
|
||||||
|
var value = std.mem.zeroes(gobject.Value);
|
||||||
|
_ = value.init(gobject.ext.types.int);
|
||||||
|
defer value.unset();
|
||||||
|
|
||||||
|
value.setInt(variant.getInt32());
|
||||||
|
|
||||||
|
self.as(gobject.Object).setProperty("spacing", &value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const box = gtk.Box.new(.vertical, 0);
|
||||||
|
_ = box.as(gobject.Object).refSink();
|
||||||
|
defer box.unref();
|
||||||
|
|
||||||
|
{
|
||||||
|
const i32_variant_type = glib.ext.VariantType.newFor(i32);
|
||||||
|
defer i32_variant_type.free();
|
||||||
|
|
||||||
|
const actions = [_]Action(gtk.Box){
|
||||||
|
.init("test", callbacks.callback, i32_variant_type),
|
||||||
|
};
|
||||||
|
|
||||||
|
addAsGroup(gtk.Box, box, "test", &actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
const expected = std.crypto.random.intRangeAtMost(i32, 1, std.math.maxInt(u31));
|
||||||
|
const parameter = glib.Variant.newInt32(expected);
|
||||||
|
|
||||||
|
try testing.expect(box.as(gtk.Widget).activateActionVariant("test.test", parameter) != 0);
|
||||||
|
|
||||||
|
_ = glib.MainContext.iteration(null, @intFromBool(true));
|
||||||
|
|
||||||
|
var value = std.mem.zeroes(gobject.Value);
|
||||||
|
_ = value.init(gobject.ext.types.int);
|
||||||
|
defer value.unset();
|
||||||
|
|
||||||
|
box.as(gobject.Object).getProperty("spacing", &value);
|
||||||
|
|
||||||
|
try testing.expect(gValueHolds(&value, gobject.ext.types.int));
|
||||||
|
|
||||||
|
const actual = value.getInt();
|
||||||
|
try testing.expectEqual(expected, actual);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue