diff --git a/src/apprt/gtk-ng/class/tab.zig b/src/apprt/gtk-ng/class/tab.zig index 0dc104e0e..3d8437115 100644 --- a/src/apprt/gtk-ng/class/tab.zig +++ b/src/apprt/gtk-ng/class/tab.zig @@ -72,12 +72,36 @@ pub const Tab = extern struct { }, ); }; + + pub const title = struct { + pub const name = "title"; + pub const get = impl.get; + pub const set = impl.set; + const impl = gobject.ext.defineProperty( + name, + Self, + ?[:0]const u8, + .{ + .nick = "Title", + .blurb = "The title of the active surface.", + .default = null, + .accessor = C.privateStringFieldAccessor("title"), + }, + ); + }; }; const Private = struct { /// The configuration that this surface is using. config: ?*Config = null, + /// The title to show for this tab. This is usally set to a binding + /// with the active surface but can be manually set to anything. + title: ?[:0]const u8 = null, + + /// The binding groups for the current active surface. + surface_bindings: *gobject.BindingGroup, + // Template bindings surface: *Surface, @@ -106,6 +130,19 @@ pub const Tab = extern struct { priv.config = app.getConfig(); } + // Setup binding groups for surface properties + priv.surface_bindings = gobject.BindingGroup.new(); + priv.surface_bindings.bind( + "title", + self.as(gobject.Object), + "title", + .{}, + ); + + // TODO: Eventually this should be set dynamically based on the + // current active surface. + priv.surface_bindings.setSource(priv.surface.as(gobject.Object)); + // We need to do this so that the title initializes properly, // I think because its a dynamic getter. self.as(gobject.Object).notifyByPspec(properties.@"active-surface".impl.param_spec); @@ -130,6 +167,7 @@ pub const Tab = extern struct { v.unref(); priv.config = null; } + priv.surface_bindings.setSource(null); gtk.Widget.disposeTemplate( self.as(gtk.Widget), @@ -142,6 +180,19 @@ pub const Tab = extern struct { ); } + fn finalize(self: *Self) callconv(.C) void { + const priv = self.private(); + if (priv.title) |v| { + glib.free(@constCast(@ptrCast(v))); + priv.title = null; + } + priv.surface_bindings.unref(); + + gobject.Object.virtual_methods.finalize.call( + Class.parent, + self.as(Parent), + ); + } //--------------------------------------------------------------- // Signal handlers @@ -171,6 +222,7 @@ pub const Tab = extern struct { gobject.ext.registerProperties(class, &.{ properties.@"active-surface".impl, properties.config.impl, + properties.title.impl, }); // Bindings @@ -181,6 +233,7 @@ pub const Tab = extern struct { // Virtual methods gobject.Object.virtual_methods.dispose.implement(class, &dispose); + gobject.Object.virtual_methods.finalize.implement(class, &finalize); } pub const as = C.Class.as; diff --git a/src/apprt/gtk-ng/class/window.zig b/src/apprt/gtk-ng/class/window.zig index b022704a4..548a8b664 100644 --- a/src/apprt/gtk-ng/class/window.zig +++ b/src/apprt/gtk-ng/class/window.zig @@ -204,6 +204,9 @@ pub const Window = extern struct { }; const Private = struct { + /// Binding group for our active tab. + tab_bindings: *gobject.BindingGroup, + /// The configuration that this surface is using. config: ?*Config = null, @@ -221,7 +224,9 @@ pub const Window = extern struct { .application = app, }); - // Create our initial tab + // Create our initial tab. This will trigger the selected-page + // signal handler which will setup the remainder of the bindings + // for this to all work. const priv = self.private(); const tab = gobject.ext.newInstance(Tab, .{ .config = priv.config, @@ -248,6 +253,10 @@ pub const Window = extern struct { self.as(gtk.Widget).addCssClass("devel"); } + // Setup some of our objects that are never null + priv.tab_bindings = gobject.BindingGroup.new(); + priv.tab_bindings.bind("title", self.as(gobject.Object), "title", .{}); + // Set our window icon. We can't set this in the blueprint file // because its dependent on the build config. self.as(gtk.Window).setIconName(build_config.bundle_id); @@ -517,6 +526,7 @@ pub const Window = extern struct { v.unref(); priv.config = null; } + priv.tab_bindings.setSource(null); gtk.Widget.disposeTemplate( self.as(gtk.Widget), @@ -529,6 +539,16 @@ pub const Window = extern struct { ); } + fn finalize(self: *Self) callconv(.C) void { + const priv = self.private(); + priv.tab_bindings.unref(); + + gobject.Object.virtual_methods.finalize.call( + Class.parent, + self.as(Parent), + ); + } + //--------------------------------------------------------------- // Signal handlers @@ -570,6 +590,26 @@ pub const Window = extern struct { self.as(gtk.Window).destroy(); } + fn tabViewSelectedPage( + _: *adw.TabView, + _: *gobject.ParamSpec, + self: *Self, + ) callconv(.c) void { + const priv = self.private(); + + // Always reset our binding source in case we have no pages. + priv.tab_bindings.setSource(null); + + // Get our current page which MUST be a Tab object. + const page = priv.tab_view.getSelectedPage() orelse return; + const child = page.getChild(); + assert(gobject.ext.isA(child, Tab)); + + // Setup our binding group. This ensures things like the title + // are synced from the active tab. + priv.tab_bindings.setSource(child.as(gobject.Object)); + } + fn surfaceClipboardWrite( _: *Surface, clipboard_type: apprt.Clipboard, @@ -768,6 +808,7 @@ pub const Window = extern struct { // Template Callbacks class.bindTemplateCallback("close_request", &windowCloseRequest); + class.bindTemplateCallback("selected_page", &tabViewSelectedPage); class.bindTemplateCallback("surface_clipboard_write", &surfaceClipboardWrite); class.bindTemplateCallback("surface_close_request", &surfaceCloseRequest); class.bindTemplateCallback("surface_toggle_fullscreen", &surfaceToggleFullscreen); @@ -780,6 +821,7 @@ pub const Window = extern struct { // Virtual methods gobject.Object.virtual_methods.dispose.implement(class, &dispose); + gobject.Object.virtual_methods.finalize.implement(class, &finalize); } pub const as = C.Class.as; diff --git a/src/apprt/gtk-ng/ui/1.5/window.blp b/src/apprt/gtk-ng/ui/1.5/window.blp index f4b8b5d36..dbfcbda96 100644 --- a/src/apprt/gtk-ng/ui/1.5/window.blp +++ b/src/apprt/gtk-ng/ui/1.5/window.blp @@ -16,7 +16,6 @@ template $GhosttyWindow: Adw.ApplicationWindow { // GTK4 grabs F10 input by default to focus the menubar icon. We want // to disable this so that terminal programs can capture F10 (such as htop) handle-menubar-accel: false; - title: bind (template.active-surface as <$GhosttySurface>).title; content: Adw.TabOverview tab_overview { enable-new-tab: true; @@ -31,7 +30,7 @@ template $GhosttyWindow: Adw.ApplicationWindow { visible: bind template.headerbar-visible; title-widget: Adw.WindowTitle { - title: bind (template.active-surface as <$GhosttySurface>).title; + title: bind template.title; }; [start] @@ -78,7 +77,9 @@ template $GhosttyWindow: Adw.ApplicationWindow { } Adw.ToastOverlay toast_overlay { - Adw.TabView tab_view {} + Adw.TabView tab_view { + notify::selected-page => $selected_page(); + } } } }