apprt/gtk-ng: abstract our alert vs msg dialog into a superclass (#7995)

This introduces a new `GhosttyDialog` class that either inherits from
`adw.MessageDialog` or `adw.AlertDialog`, depending on the version of
libadwaita we compile against. This is the same logic we used
previously.

This lets us have a single libadw 1.2 blueprint file for all dialogs and
we just do the right thing at compile time!
This commit is contained in:
Mitchell Hashimoto
2025-07-20 13:21:22 -07:00
committed by GitHub
5 changed files with 106 additions and 61 deletions

View File

@ -32,7 +32,6 @@ pub const icon_sizes: []const comptime_int = &.{ 16, 32, 128, 256, 512, 1024 };
pub const blueprints: []const Blueprint = &.{
.{ .major = 1, .minor = 2, .name = "config-errors-dialog" },
.{ .major = 1, .minor = 2, .name = "surface" },
.{ .major = 1, .minor = 5, .name = "config-errors-dialog" },
.{ .major = 1, .minor = 5, .name = "window" },
};

View File

@ -7,18 +7,14 @@ const gresource = @import("../build/gresource.zig");
const adw_version = @import("../adw_version.zig");
const Common = @import("../class.zig").Common;
const Config = @import("config.zig").Config;
const Dialog = @import("dialog.zig").Dialog;
const log = std.log.scoped(.gtk_ghostty_config_errors_dialog);
pub const ConfigErrorsDialog = extern struct {
const Self = @This();
parent_instance: Parent,
pub const Parent = if (adw_version.supportsDialogs())
adw.AlertDialog
else
adw.MessageDialog;
pub const Parent = Dialog;
pub const getGObjectType = gobject.ext.defineClass(Self, .{
.name = "GhosttyConfigErrorsDialog",
.instanceInit = &init,
@ -76,19 +72,11 @@ pub const ConfigErrorsDialog = extern struct {
}
pub fn present(self: *Self, parent: ?*gtk.Widget) void {
switch (Parent) {
adw.AlertDialog => self.as(adw.Dialog).present(parent),
adw.MessageDialog => self.as(gtk.Window).present(),
else => comptime unreachable,
}
self.as(Dialog).present(parent);
}
pub fn close(self: *Self) void {
switch (Parent) {
adw.AlertDialog => self.as(adw.Dialog).forceClose(),
adw.MessageDialog => self.as(gtk.Window).close(),
else => comptime unreachable,
}
self.as(Dialog).close();
}
fn response(
@ -147,23 +135,14 @@ pub const ConfigErrorsDialog = extern struct {
pub const Instance = Self;
fn init(class: *Class) callconv(.C) void {
gobject.ext.ensureType(Dialog);
gtk.Widget.Class.setTemplateFromResource(
class.as(gtk.Widget.Class),
switch (Parent) {
adw.AlertDialog => comptime gresource.blueprint(.{
.major = 1,
.minor = 5,
.name = "config-errors-dialog",
}),
adw.MessageDialog => comptime gresource.blueprint(.{
.major = 1,
.minor = 2,
.name = "config-errors-dialog",
}),
else => comptime unreachable,
},
comptime gresource.blueprint(.{
.major = 1,
.minor = 2,
.name = "config-errors-dialog",
}),
);
// Properties
@ -176,7 +155,7 @@ pub const ConfigErrorsDialog = extern struct {
// Virtual methods
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
Parent.virtual_methods.response.implement(class, &response);
Dialog.virtual_methods.response.implement(class, &response);
}
pub fn as(class: *Class, comptime T: type) *T {

View File

@ -0,0 +1,93 @@
const std = @import("std");
const adw = @import("adw");
const gobject = @import("gobject");
const gtk = @import("gtk");
const gresource = @import("../build/gresource.zig");
const adw_version = @import("../adw_version.zig");
const Common = @import("../class.zig").Common;
const Config = @import("config.zig").Config;
const log = std.log.scoped(.gtk_ghostty_dialog);
/// Dialog is a simple abstraction over the `adw.AlertDialog` and
/// `adw.MessageDialog` widgets, chosen at comptime based on the linked
/// Adwaita version.
///
/// Once we drop support for Adwaita <= 1.2, this can be fully removed
/// and we can use `adw.AlertDialog` directly.
pub const Dialog = extern struct {
const Self = @This();
parent_instance: Parent,
pub const Parent = if (adw_version.supportsDialogs())
adw.AlertDialog
else
adw.MessageDialog;
pub const getGObjectType = gobject.ext.defineClass(Self, .{
.name = "GhosttyDialog",
.classInit = &Class.init,
.parent_class = &Class.parent,
});
pub const virtual_methods = struct {
/// Forwarded from parent so subclasses can reference this
/// directly. This will make it easier to remove Dialog in the future.
pub const response = Parent.virtual_methods.response;
};
pub fn present(self: *Self, parent: ?*gtk.Widget) void {
switch (Parent) {
adw.AlertDialog => self.as(adw.Dialog).present(parent),
adw.MessageDialog => {
// Reset the previous parent window
self.as(gtk.Window).setTransientFor(null);
// We need to get the window for the parent in order
// to set the transient-for property on the MessageDialog.
if (parent) |widget| parent: {
const root = gtk.Widget.getRoot(widget) orelse break :parent;
const window = gobject.ext.cast(
gtk.Window,
root,
) orelse break :parent;
self.as(gtk.Window).setTransientFor(window);
}
self.as(gtk.Window).present();
},
else => comptime unreachable,
}
}
pub fn close(self: *Self) void {
switch (Parent) {
adw.AlertDialog => self.as(adw.Dialog).forceClose(),
adw.MessageDialog => self.as(gtk.Window).close(),
else => comptime unreachable,
}
}
const C = Common(Self, null);
pub const as = C.as;
pub const ref = C.ref;
pub const unref = C.unref;
const private = C.private;
pub const Class = extern struct {
parent_class: Parent.Class,
var parent: *Parent.Class = undefined;
pub const Instance = Self;
fn init(class: *Class) callconv(.C) void {
_ = class;
}
pub fn as(class: *Class, comptime T: type) *T {
return gobject.ext.as(T, class);
}
};
};

View File

@ -1,7 +1,8 @@
using Gtk 4.0;
// This is unused but if we remove it we get a blueprint-compiler error.
using Adw 1;
template $GhosttyConfigErrorsDialog: Adw.MessageDialog {
template $GhosttyConfigErrorsDialog: $GhosttyDialog {
heading: _("Configuration Errors");
body: _("One or more configuration errors were found. Please review the errors below, and either reload your configuration or ignore these errors.");

View File

@ -1,27 +0,0 @@
using Gtk 4.0;
using Adw 1;
template $GhosttyConfigErrorsDialog: Adw.AlertDialog {
heading: _("Configuration Errors");
body: _("One or more configuration errors were found. Please review the errors below, and either reload your configuration or ignore these errors.");
responses [
ignore: _("Ignore"),
reload: _("Reload Configuration") suggested,
]
extra-child: ScrolledWindow {
min-content-width: 500;
min-content-height: 100;
TextView {
editable: false;
cursor-visible: false;
top-margin: 8;
bottom-margin: 8;
left-margin: 8;
right-margin: 8;
buffer: bind (template.config as <$GhosttyConfig>).diagnostics-buffer;
}
};
}