apprt/gtk-ng: close tab confirmation

This commit is contained in:
Mitchell Hashimoto
2025-07-29 07:30:53 -07:00
parent 431a6328dc
commit c0e7b92e91
4 changed files with 118 additions and 9 deletions

View File

@ -57,6 +57,17 @@ pub const CloseConfirmationDialog = extern struct {
void, void,
); );
}; };
pub const cancel = struct {
pub const name = "cancel";
pub const connect = impl.connect;
const impl = gobject.ext.defineSignal(
name,
Self,
&.{},
void,
);
};
}; };
const Private = struct { const Private = struct {
@ -72,14 +83,15 @@ pub const CloseConfirmationDialog = extern struct {
fn init(self: *Self, _: *Class) callconv(.C) void { fn init(self: *Self, _: *Class) callconv(.C) void {
gtk.Widget.initTemplate(self.as(gtk.Widget)); gtk.Widget.initTemplate(self.as(gtk.Widget));
}
pub fn present(self: *Self, parent: ?*gtk.Widget) void {
// Setup our title/body text. // Setup our title/body text.
const priv = self.private(); const priv = self.private();
self.as(Dialog.Parent).setHeading(priv.target.title()); self.as(Dialog.Parent).setHeading(priv.target.title());
self.as(Dialog.Parent).setBody(priv.target.body()); self.as(Dialog.Parent).setBody(priv.target.body());
}
pub fn present(self: *Self, parent: ?*gtk.Widget) void { // Show it
self.as(Dialog).present(parent); self.as(Dialog).present(parent);
} }
@ -91,13 +103,21 @@ pub const CloseConfirmationDialog = extern struct {
self: *Self, self: *Self,
response_id: [*:0]const u8, response_id: [*:0]const u8,
) callconv(.C) void { ) callconv(.C) void {
if (std.mem.orderZ(u8, response_id, "close") != .eq) return; if (std.mem.orderZ(u8, response_id, "close") == .eq) {
signals.@"close-request".impl.emit( signals.@"close-request".impl.emit(
self, self,
null, null,
.{}, .{},
null, null,
); );
} else {
signals.cancel.impl.emit(
self,
null,
.{},
null,
);
}
} }
fn dispose(self: *Self) callconv(.C) void { fn dispose(self: *Self) callconv(.C) void {
@ -141,6 +161,7 @@ pub const CloseConfirmationDialog = extern struct {
// Signals // Signals
signals.@"close-request".impl.register(.{}); signals.@"close-request".impl.register(.{});
signals.cancel.impl.register(.{});
// Virtual methods // Virtual methods
gobject.Object.virtual_methods.dispose.implement(class, &dispose); gobject.Object.virtual_methods.dispose.implement(class, &dispose);
@ -158,11 +179,13 @@ pub const CloseConfirmationDialog = extern struct {
/// together into one struct that is the sole source of truth. /// together into one struct that is the sole source of truth.
pub const Target = enum(c_int) { pub const Target = enum(c_int) {
app, app,
tab,
window, window,
pub fn title(self: Target) [*:0]const u8 { pub fn title(self: Target) [*:0]const u8 {
return switch (self) { return switch (self) {
.app => i18n._("Quit Ghostty?"), .app => i18n._("Quit Ghostty?"),
.tab => i18n._("Close Tab?"),
.window => i18n._("Close Window?"), .window => i18n._("Close Window?"),
}; };
} }
@ -170,6 +193,7 @@ pub const Target = enum(c_int) {
pub fn body(self: Target) [*:0]const u8 { pub fn body(self: Target) [*:0]const u8 {
return switch (self) { return switch (self) {
.app => i18n._("All terminal sessions will be terminated."), .app => i18n._("All terminal sessions will be terminated."),
.tab => i18n._("All terminal sessions in this tab will be terminated."),
.window => i18n._("All terminal sessions in this window will be terminated."), .window => i18n._("All terminal sessions in this window will be terminated."),
}; };
} }

View File

@ -172,6 +172,14 @@ pub const Tab = extern struct {
return priv.surface; return priv.surface;
} }
/// Returns true if this tab needs confirmation before quitting based
/// on the various Ghostty configurations.
pub fn getNeedsConfirmQuit(self: *Self) bool {
const surface = self.getActiveSurface();
const core_surface = surface.core() orelse return false;
return core_surface.needsConfirmQuit();
}
//--------------------------------------------------------------- //---------------------------------------------------------------
// Virtual methods // Virtual methods

View File

@ -591,6 +591,81 @@ pub const Window = extern struct {
self.as(gtk.Window).destroy(); self.as(gtk.Window).destroy();
} }
fn closeConfirmationCloseTab(
_: *CloseConfirmationDialog,
page: *adw.TabPage,
) callconv(.c) void {
const tab_view_widget = page
.getChild()
.as(gtk.Widget)
.getAncestor(gobject.ext.typeFor(adw.TabView)) orelse {
log.warn("close confirmation caled for non-existent page", .{});
return;
};
const tab_view = gobject.ext.cast(
adw.TabView,
tab_view_widget,
).?;
tab_view.closePageFinish(page, @intFromBool(true));
}
fn closeConfirmationCancelTab(
_: *CloseConfirmationDialog,
page: *adw.TabPage,
) callconv(.c) void {
const tab_view_widget = page
.getChild()
.as(gtk.Widget)
.getAncestor(gobject.ext.typeFor(adw.TabView)) orelse {
log.warn("close confirmation caled for non-existent page", .{});
return;
};
const tab_view = gobject.ext.cast(
adw.TabView,
tab_view_widget,
).?;
tab_view.closePageFinish(page, @intFromBool(false));
}
fn tabViewClosePage(
_: *adw.TabView,
page: *adw.TabPage,
self: *Self,
) callconv(.c) c_int {
const priv = self.private();
const child = page.getChild();
const tab = gobject.ext.cast(Tab, child) orelse
return @intFromBool(false);
// If the tab says it doesn't need confirmation then we go ahead
// and close immediately.
if (!tab.getNeedsConfirmQuit()) {
priv.tab_view.closePageFinish(page, @intFromBool(true));
return @intFromBool(true);
}
// Show a confirmation dialog
const dialog: *CloseConfirmationDialog = .new(.tab);
_ = CloseConfirmationDialog.signals.@"close-request".connect(
dialog,
*adw.TabPage,
closeConfirmationCloseTab,
page,
.{},
);
_ = CloseConfirmationDialog.signals.cancel.connect(
dialog,
*adw.TabPage,
closeConfirmationCancelTab,
page,
.{},
);
// Show it
dialog.present(child);
return @intFromBool(true);
}
fn tabViewSelectedPage( fn tabViewSelectedPage(
_: *adw.TabView, _: *adw.TabView,
_: *gobject.ParamSpec, _: *gobject.ParamSpec,
@ -929,6 +1004,7 @@ pub const Window = extern struct {
// Template Callbacks // Template Callbacks
class.bindTemplateCallback("close_request", &windowCloseRequest); class.bindTemplateCallback("close_request", &windowCloseRequest);
class.bindTemplateCallback("close_page", &tabViewClosePage);
class.bindTemplateCallback("selected_page", &tabViewSelectedPage); class.bindTemplateCallback("selected_page", &tabViewSelectedPage);
class.bindTemplateCallback("page_attached", &tabViewPageAttached); class.bindTemplateCallback("page_attached", &tabViewPageAttached);
class.bindTemplateCallback("page_detached", &tabViewPageDetached); class.bindTemplateCallback("page_detached", &tabViewPageDetached);

View File

@ -79,6 +79,7 @@ template $GhosttyWindow: Adw.ApplicationWindow {
Adw.ToastOverlay toast_overlay { Adw.ToastOverlay toast_overlay {
Adw.TabView tab_view { Adw.TabView tab_view {
notify::selected-page => $selected_page(); notify::selected-page => $selected_page();
close-page => $close_page();
page-attached => $page_attached(); page-attached => $page_attached();
page-detached => $page_detached(); page-detached => $page_detached();
} }