mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
gtk: use builder ui files and popovers for menus
This commit is contained in:
@ -57,12 +57,6 @@ single_instance: bool,
|
|||||||
/// The "none" cursor. We use one that is shared across the entire app.
|
/// The "none" cursor. We use one that is shared across the entire app.
|
||||||
cursor_none: ?*c.GdkCursor,
|
cursor_none: ?*c.GdkCursor,
|
||||||
|
|
||||||
/// The shared application menu.
|
|
||||||
menu: ?*c.GMenu = null,
|
|
||||||
|
|
||||||
/// The shared context menu.
|
|
||||||
context_menu: ?*c.GMenu = null,
|
|
||||||
|
|
||||||
/// The configuration errors window, if it is currently open.
|
/// The configuration errors window, if it is currently open.
|
||||||
config_errors_window: ?*ConfigErrorsWindow = null,
|
config_errors_window: ?*ConfigErrorsWindow = null,
|
||||||
|
|
||||||
@ -426,8 +420,6 @@ pub fn terminate(self: *App) void {
|
|||||||
c.g_object_unref(self.app);
|
c.g_object_unref(self.app);
|
||||||
|
|
||||||
if (self.cursor_none) |cursor| c.g_object_unref(cursor);
|
if (self.cursor_none) |cursor| c.g_object_unref(cursor);
|
||||||
if (self.menu) |menu| c.g_object_unref(menu);
|
|
||||||
if (self.context_menu) |context_menu| c.g_object_unref(context_menu);
|
|
||||||
if (self.transient_cgroup_base) |path| self.core_app.alloc.free(path);
|
if (self.transient_cgroup_base) |path| self.core_app.alloc.free(path);
|
||||||
|
|
||||||
for (self.custom_css_providers.items) |provider| {
|
for (self.custom_css_providers.items) |provider| {
|
||||||
@ -456,7 +448,6 @@ pub fn performAction(
|
|||||||
}),
|
}),
|
||||||
.toggle_maximize => self.toggleMaximize(target),
|
.toggle_maximize => self.toggleMaximize(target),
|
||||||
.toggle_fullscreen => self.toggleFullscreen(target, value),
|
.toggle_fullscreen => self.toggleFullscreen(target, value),
|
||||||
|
|
||||||
.new_tab => try self.newTab(target),
|
.new_tab => try self.newTab(target),
|
||||||
.close_tab => try self.closeTab(target),
|
.close_tab => try self.closeTab(target),
|
||||||
.goto_tab => return self.gotoTab(target, value),
|
.goto_tab => return self.gotoTab(target, value),
|
||||||
@ -990,17 +981,19 @@ fn syncActionAccelerators(self: *App) !void {
|
|||||||
try self.syncActionAccelerator("app.quit", .{ .quit = {} });
|
try self.syncActionAccelerator("app.quit", .{ .quit = {} });
|
||||||
try self.syncActionAccelerator("app.open-config", .{ .open_config = {} });
|
try self.syncActionAccelerator("app.open-config", .{ .open_config = {} });
|
||||||
try self.syncActionAccelerator("app.reload-config", .{ .reload_config = {} });
|
try self.syncActionAccelerator("app.reload-config", .{ .reload_config = {} });
|
||||||
try self.syncActionAccelerator("win.toggle_inspector", .{ .inspector = .toggle });
|
try self.syncActionAccelerator("win.toggle-inspector", .{ .inspector = .toggle });
|
||||||
try self.syncActionAccelerator("win.close", .{ .close_surface = {} });
|
try self.syncActionAccelerator("win.close", .{ .close_window = {} });
|
||||||
try self.syncActionAccelerator("win.new_window", .{ .new_window = {} });
|
try self.syncActionAccelerator("win.new-window", .{ .new_window = {} });
|
||||||
try self.syncActionAccelerator("win.new_tab", .{ .new_tab = {} });
|
try self.syncActionAccelerator("win.new-tab", .{ .new_tab = {} });
|
||||||
try self.syncActionAccelerator("win.split_right", .{ .new_split = .right });
|
try self.syncActionAccelerator("win.close-tab", .{ .close_tab = {} });
|
||||||
try self.syncActionAccelerator("win.split_down", .{ .new_split = .down });
|
try self.syncActionAccelerator("win.split-right", .{ .new_split = .right });
|
||||||
try self.syncActionAccelerator("win.split_left", .{ .new_split = .left });
|
try self.syncActionAccelerator("win.split-down", .{ .new_split = .down });
|
||||||
try self.syncActionAccelerator("win.split_up", .{ .new_split = .up });
|
try self.syncActionAccelerator("win.split-left", .{ .new_split = .left });
|
||||||
|
try self.syncActionAccelerator("win.split-up", .{ .new_split = .up });
|
||||||
try self.syncActionAccelerator("win.copy", .{ .copy_to_clipboard = {} });
|
try self.syncActionAccelerator("win.copy", .{ .copy_to_clipboard = {} });
|
||||||
try self.syncActionAccelerator("win.paste", .{ .paste_from_clipboard = {} });
|
try self.syncActionAccelerator("win.paste", .{ .paste_from_clipboard = {} });
|
||||||
try self.syncActionAccelerator("win.reset", .{ .reset = {} });
|
try self.syncActionAccelerator("win.reset", .{ .reset = {} });
|
||||||
|
try self.syncActionAccelerator("win.clear", .{ .clear_screen = {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn syncActionAccelerator(
|
fn syncActionAccelerator(
|
||||||
@ -1232,10 +1225,8 @@ pub fn run(self: *App) !void {
|
|||||||
// and asynchronously request the initial color scheme
|
// and asynchronously request the initial color scheme
|
||||||
self.initDbus();
|
self.initDbus();
|
||||||
|
|
||||||
// Setup our menu items
|
// Setup our actions
|
||||||
self.initActions();
|
self.initActions();
|
||||||
self.initMenu();
|
|
||||||
self.initContextMenu();
|
|
||||||
|
|
||||||
// On startup, we want to check for configuration errors right away
|
// On startup, we want to check for configuration errors right away
|
||||||
// so we can show our error window. We also need to setup other initial
|
// so we can show our error window. We also need to setup other initial
|
||||||
@ -1753,87 +1744,6 @@ fn initActions(self: *App) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initializes and populates the provided GMenu with sections and actions.
|
|
||||||
/// This function is used to set up the application's menu structure, either for
|
|
||||||
/// the main menu button or as a context menu when window decorations are disabled.
|
|
||||||
fn initMenuContent(menu: *c.GMenu) void {
|
|
||||||
{
|
|
||||||
const section = c.g_menu_new();
|
|
||||||
defer c.g_object_unref(section);
|
|
||||||
c.g_menu_append_section(menu, null, @ptrCast(@alignCast(section)));
|
|
||||||
c.g_menu_append(section, "New Window", "win.new_window");
|
|
||||||
c.g_menu_append(section, "New Tab", "win.new_tab");
|
|
||||||
c.g_menu_append(section, "Close Tab", "win.close_tab");
|
|
||||||
c.g_menu_append(section, "Split Right", "win.split_right");
|
|
||||||
c.g_menu_append(section, "Split Down", "win.split_down");
|
|
||||||
c.g_menu_append(section, "Close Window", "win.close");
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const section = c.g_menu_new();
|
|
||||||
defer c.g_object_unref(section);
|
|
||||||
c.g_menu_append_section(menu, null, @ptrCast(@alignCast(section)));
|
|
||||||
c.g_menu_append(section, "Terminal Inspector", "win.toggle_inspector");
|
|
||||||
c.g_menu_append(section, "Open Configuration", "app.open-config");
|
|
||||||
c.g_menu_append(section, "Reload Configuration", "app.reload-config");
|
|
||||||
c.g_menu_append(section, "About Ghostty", "win.about");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This sets the self.menu property to the application menu that can be
|
|
||||||
/// shared by all application windows.
|
|
||||||
fn initMenu(self: *App) void {
|
|
||||||
const menu = c.g_menu_new();
|
|
||||||
errdefer c.g_object_unref(menu);
|
|
||||||
initMenuContent(@ptrCast(menu));
|
|
||||||
self.menu = menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn initContextMenu(self: *App) void {
|
|
||||||
const menu = c.g_menu_new();
|
|
||||||
errdefer c.g_object_unref(menu);
|
|
||||||
|
|
||||||
{
|
|
||||||
const section = c.g_menu_new();
|
|
||||||
defer c.g_object_unref(section);
|
|
||||||
c.g_menu_append_section(menu, null, @ptrCast(@alignCast(section)));
|
|
||||||
c.g_menu_append(section, "Copy", "win.copy");
|
|
||||||
c.g_menu_append(section, "Paste", "win.paste");
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const section = c.g_menu_new();
|
|
||||||
defer c.g_object_unref(section);
|
|
||||||
c.g_menu_append_section(menu, null, @ptrCast(@alignCast(section)));
|
|
||||||
c.g_menu_append(section, "Split Right", "win.split_right");
|
|
||||||
c.g_menu_append(section, "Split Down", "win.split_down");
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const section = c.g_menu_new();
|
|
||||||
defer c.g_object_unref(section);
|
|
||||||
c.g_menu_append_section(menu, null, @ptrCast(@alignCast(section)));
|
|
||||||
c.g_menu_append(section, "Reset", "win.reset");
|
|
||||||
c.g_menu_append(section, "Terminal Inspector", "win.toggle_inspector");
|
|
||||||
}
|
|
||||||
|
|
||||||
const section = c.g_menu_new();
|
|
||||||
defer c.g_object_unref(section);
|
|
||||||
const submenu = c.g_menu_new();
|
|
||||||
defer c.g_object_unref(submenu);
|
|
||||||
|
|
||||||
initMenuContent(@ptrCast(submenu));
|
|
||||||
c.g_menu_append_submenu(section, "Menu", @ptrCast(@alignCast(submenu)));
|
|
||||||
c.g_menu_append_section(menu, null, @ptrCast(@alignCast(section)));
|
|
||||||
|
|
||||||
self.context_menu = menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn refreshContextMenu(_: *App, window: ?*c.GtkWindow, has_selection: bool) void {
|
|
||||||
const action: ?*c.GSimpleAction = @ptrCast(c.g_action_map_lookup_action(@ptrCast(window), "copy"));
|
|
||||||
c.g_simple_action_set_enabled(action, if (has_selection) 1 else 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn isValidAppId(app_id: [:0]const u8) bool {
|
fn isValidAppId(app_id: [:0]const u8) bool {
|
||||||
if (app_id.len > 255 or app_id.len == 0) return false;
|
if (app_id.len > 255 or app_id.len == 0) return false;
|
||||||
if (app_id[0] == '.') return false;
|
if (app_id[0] == '.') return false;
|
||||||
|
@ -56,7 +56,7 @@ pub fn setWidgetClassTemplate(self: *const Builder, class: *gtk.WidgetClass) voi
|
|||||||
class.setTemplateFromResource(self.resource_name);
|
class.setTemplateFromResource(self.resource_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getObject(self: *Builder, name: [:0]const u8) ?*gobject.Object {
|
pub fn getObject(self: *Builder, comptime T: type, name: [:0]const u8) ?*T {
|
||||||
const builder = builder: {
|
const builder = builder: {
|
||||||
if (self.builder) |builder| break :builder builder;
|
if (self.builder) |builder| break :builder builder;
|
||||||
const builder = gtk.Builder.newFromResource(self.resource_name);
|
const builder = gtk.Builder.newFromResource(self.resource_name);
|
||||||
@ -64,7 +64,7 @@ pub fn getObject(self: *Builder, name: [:0]const u8) ?*gobject.Object {
|
|||||||
break :builder builder;
|
break :builder builder;
|
||||||
};
|
};
|
||||||
|
|
||||||
return builder.getObject(name);
|
return gobject.ext.cast(T, builder.getObject(name) orelse return null);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *const Builder) void {
|
pub fn deinit(self: *const Builder) void {
|
||||||
|
@ -20,6 +20,7 @@ const App = @import("App.zig");
|
|||||||
const Split = @import("Split.zig");
|
const Split = @import("Split.zig");
|
||||||
const Tab = @import("Tab.zig");
|
const Tab = @import("Tab.zig");
|
||||||
const Window = @import("Window.zig");
|
const Window = @import("Window.zig");
|
||||||
|
const Menu = @import("menu.zig").Menu;
|
||||||
const ClipboardConfirmationWindow = @import("ClipboardConfirmationWindow.zig");
|
const ClipboardConfirmationWindow = @import("ClipboardConfirmationWindow.zig");
|
||||||
const ResizeOverlay = @import("ResizeOverlay.zig");
|
const ResizeOverlay = @import("ResizeOverlay.zig");
|
||||||
const inspector = @import("inspector.zig");
|
const inspector = @import("inspector.zig");
|
||||||
@ -378,6 +379,9 @@ im_len: u7 = 0,
|
|||||||
/// details on what this is.
|
/// details on what this is.
|
||||||
cgroup_path: ?[]const u8 = null,
|
cgroup_path: ?[]const u8 = null,
|
||||||
|
|
||||||
|
/// Our context menu.
|
||||||
|
context_menu: Menu(Surface, "context_menu", false),
|
||||||
|
|
||||||
/// The state of the key event while we're doing IM composition.
|
/// The state of the key event while we're doing IM composition.
|
||||||
/// See gtkKeyPressed for detailed descriptions.
|
/// See gtkKeyPressed for detailed descriptions.
|
||||||
pub const IMKeyEvent = enum {
|
pub const IMKeyEvent = enum {
|
||||||
@ -576,9 +580,14 @@ pub fn init(self: *Surface, app: *App, opts: Options) !void {
|
|||||||
.cursor_pos = .{ .x = -1, .y = -1 },
|
.cursor_pos = .{ .x = -1, .y = -1 },
|
||||||
.im_context = im_context,
|
.im_context = im_context,
|
||||||
.cgroup_path = cgroup_path,
|
.cgroup_path = cgroup_path,
|
||||||
|
.context_menu = undefined,
|
||||||
};
|
};
|
||||||
errdefer self.* = undefined;
|
errdefer self.* = undefined;
|
||||||
|
|
||||||
|
// initialize the context menu
|
||||||
|
self.context_menu.init(self);
|
||||||
|
self.context_menu.setParent(@ptrCast(@alignCast(overlay)));
|
||||||
|
|
||||||
// Set our default mouse shape
|
// Set our default mouse shape
|
||||||
try self.setMouseShape(.text);
|
try self.setMouseShape(.text);
|
||||||
|
|
||||||
@ -913,7 +922,7 @@ fn updateTitleLabels(self: *Surface) void {
|
|||||||
|
|
||||||
// If we have a tab and are the focused child, then we have to update the tab
|
// If we have a tab and are the focused child, then we have to update the tab
|
||||||
if (self.container.tab()) |tab| {
|
if (self.container.tab()) |tab| {
|
||||||
if (tab.focus_child == self) tab.setLabelText(title);
|
if (tab.focus_child == self) tab.setTitleText(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a window and are focused, then we have to update the window title.
|
// If we have a window and are focused, then we have to update the window title.
|
||||||
@ -1224,6 +1233,7 @@ fn getClipboard(widget: *c.GtkWidget, clipboard: apprt.Clipboard) ?*c.GdkClipboa
|
|||||||
.selection, .primary => c.gtk_widget_get_primary_clipboard(widget),
|
.selection, .primary => c.gtk_widget_get_primary_clipboard(widget),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getCursorPos(self: *const Surface) !apprt.CursorPos {
|
pub fn getCursorPos(self: *const Surface) !apprt.CursorPos {
|
||||||
return self.cursor_pos;
|
return self.cursor_pos;
|
||||||
}
|
}
|
||||||
@ -1261,40 +1271,6 @@ pub fn showDesktopNotification(
|
|||||||
c.g_application_send_notification(g_app, body.ptr, notification);
|
c.g_application_send_notification(g_app, body.ptr, notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn showContextMenu(self: *Surface, x: f32, y: f32) void {
|
|
||||||
const window: *Window = self.container.window() orelse {
|
|
||||||
log.info(
|
|
||||||
"showContextMenu invalid for container={s}",
|
|
||||||
.{@tagName(self.container)},
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Convert surface coordinate into coordinate space of the
|
|
||||||
// context menu's parent
|
|
||||||
var point: c.graphene_point_t = .{ .x = x, .y = y };
|
|
||||||
if (c.gtk_widget_compute_point(
|
|
||||||
self.primaryWidget(),
|
|
||||||
c.gtk_widget_get_parent(@ptrCast(window.context_menu)),
|
|
||||||
&c.GRAPHENE_POINT_INIT(point.x, point.y),
|
|
||||||
@ptrCast(&point),
|
|
||||||
) == 0) {
|
|
||||||
log.warn("failed computing point for context menu", .{});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rect: c.GdkRectangle = .{
|
|
||||||
.x = @intFromFloat(point.x),
|
|
||||||
.y = @intFromFloat(point.y),
|
|
||||||
.width = 1,
|
|
||||||
.height = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
c.gtk_popover_set_pointing_to(@ptrCast(@alignCast(window.context_menu)), &rect);
|
|
||||||
self.app.refreshContextMenu(window.window, self.core_surface.hasSelection());
|
|
||||||
c.gtk_popover_popup(@ptrCast(@alignCast(window.context_menu)));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gtkRealize(area: *c.GtkGLArea, ud: ?*anyopaque) callconv(.C) void {
|
fn gtkRealize(area: *c.GtkGLArea, ud: ?*anyopaque) callconv(.C) void {
|
||||||
log.debug("gl surface realized", .{});
|
log.debug("gl surface realized", .{});
|
||||||
|
|
||||||
@ -1465,7 +1441,7 @@ fn gtkMouseDown(
|
|||||||
// word and returns false. We can use this to handle the context menu
|
// word and returns false. We can use this to handle the context menu
|
||||||
// opening under normal scenarios.
|
// opening under normal scenarios.
|
||||||
if (!consumed and button == .right) {
|
if (!consumed and button == .right) {
|
||||||
self.showContextMenu(@floatCast(x), @floatCast(y));
|
self.context_menu.popupAt(@intFromFloat(x), @intFromFloat(y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2073,15 +2049,14 @@ fn gtkFocusLeave(_: *c.GtkEventControllerFocus, ud: ?*anyopaque) callconv(.C) vo
|
|||||||
/// Adds the unfocused_widget to the overlay. If the unfocused_widget has already been added, this
|
/// Adds the unfocused_widget to the overlay. If the unfocused_widget has already been added, this
|
||||||
/// is a no-op
|
/// is a no-op
|
||||||
pub fn dimSurface(self: *Surface) void {
|
pub fn dimSurface(self: *Surface) void {
|
||||||
const window = self.container.window() orelse {
|
_ = self.container.window() orelse {
|
||||||
log.warn("dimSurface invalid for container={}", .{self.container});
|
log.warn("dimSurface invalid for container={}", .{self.container});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Don't dim surface if context menu is open.
|
// Don't dim surface if context menu is open.
|
||||||
// This means we got unfocused due to it opening.
|
// This means we got unfocused due to it opening.
|
||||||
const context_menu_open = c.gtk_widget_get_visible(window.context_menu);
|
if (self.context_menu.isVisible()) return;
|
||||||
if (context_menu_open == 1) return;
|
|
||||||
|
|
||||||
if (self.unfocused_widget != null) return;
|
if (self.unfocused_widget != null) return;
|
||||||
self.unfocused_widget = c.gtk_drawing_area_new();
|
self.unfocused_widget = c.gtk_drawing_area_new();
|
||||||
|
@ -108,8 +108,8 @@ pub fn replaceElem(self: *Tab, elem: Surface.Container.Elem) void {
|
|||||||
self.elem = elem;
|
self.elem = elem;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setLabelText(self: *Tab, title: [:0]const u8) void {
|
pub fn setTitleText(self: *Tab, title: [:0]const u8) void {
|
||||||
self.window.notebook.setTabLabel(self, title);
|
self.window.notebook.setTabTitle(self, title);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setTooltipText(self: *Tab, tooltip: [:0]const u8) void {
|
pub fn setTooltipText(self: *Tab, tooltip: [:0]const u8) void {
|
||||||
|
@ -165,7 +165,7 @@ pub fn reorderPage(self: *TabView, tab: *Tab, position: c_int) void {
|
|||||||
_ = self.tab_view.reorderPage(page, position);
|
_ = self.tab_view.reorderPage(page, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setTabLabel(self: *TabView, tab: *Tab, title: [:0]const u8) void {
|
pub fn setTabTitle(self: *TabView, tab: *Tab, title: [:0]const u8) void {
|
||||||
const page = self.tab_view.getPage(@ptrCast(tab.box));
|
const page = self.tab_view.getPage(@ptrCast(tab.box));
|
||||||
page.setTitle(title.ptr);
|
page.setTitle(title.ptr);
|
||||||
}
|
}
|
||||||
@ -188,7 +188,7 @@ pub fn addTab(self: *TabView, tab: *Tab, title: [:0]const u8) void {
|
|||||||
const position = self.newTabInsertPosition(tab);
|
const position = self.newTabInsertPosition(tab);
|
||||||
const box_widget: *gtk.Widget = @ptrCast(tab.box);
|
const box_widget: *gtk.Widget = @ptrCast(tab.box);
|
||||||
const page = self.tab_view.insert(box_widget, position);
|
const page = self.tab_view.insert(box_widget, position);
|
||||||
self.setTabLabel(tab, title);
|
self.setTabTitle(tab, title);
|
||||||
self.tab_view.setSelectedPage(page);
|
self.tab_view.setSelectedPage(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ const CoreSurface = @import("../../Surface.zig");
|
|||||||
const App = @import("App.zig");
|
const App = @import("App.zig");
|
||||||
const Color = configpkg.Config.Color;
|
const Color = configpkg.Config.Color;
|
||||||
const Surface = @import("Surface.zig");
|
const Surface = @import("Surface.zig");
|
||||||
|
const Menu = @import("menu.zig").Menu;
|
||||||
const Tab = @import("Tab.zig");
|
const Tab = @import("Tab.zig");
|
||||||
const c = @import("c.zig").c;
|
const c = @import("c.zig").c;
|
||||||
const adwaita = @import("adwaita.zig");
|
const adwaita = @import("adwaita.zig");
|
||||||
@ -46,7 +47,8 @@ tab_overview: ?*c.GtkWidget,
|
|||||||
/// The notebook (tab grouping) for this window.
|
/// The notebook (tab grouping) for this window.
|
||||||
notebook: TabView,
|
notebook: TabView,
|
||||||
|
|
||||||
context_menu: *c.GtkWidget,
|
/// The "main" menu that is attached to a button in the headerbar.
|
||||||
|
titlebar_menu: Menu(Window, "titlebar_menu", true),
|
||||||
|
|
||||||
/// The libadwaita widget for receiving toast send requests.
|
/// The libadwaita widget for receiving toast send requests.
|
||||||
toast_overlay: *c.GtkWidget,
|
toast_overlay: *c.GtkWidget,
|
||||||
@ -112,7 +114,7 @@ pub fn init(self: *Window, app: *App) !void {
|
|||||||
.headerbar = undefined,
|
.headerbar = undefined,
|
||||||
.tab_overview = null,
|
.tab_overview = null,
|
||||||
.notebook = undefined,
|
.notebook = undefined,
|
||||||
.context_menu = undefined,
|
.titlebar_menu = undefined,
|
||||||
.toast_overlay = undefined,
|
.toast_overlay = undefined,
|
||||||
.winproto = .none,
|
.winproto = .none,
|
||||||
};
|
};
|
||||||
@ -137,6 +139,9 @@ pub fn init(self: *Window, app: *App) !void {
|
|||||||
// Create our box which will hold our widgets in the main content area.
|
// Create our box which will hold our widgets in the main content area.
|
||||||
const box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0);
|
const box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0);
|
||||||
|
|
||||||
|
// Set up the menus
|
||||||
|
self.titlebar_menu.init(self);
|
||||||
|
|
||||||
// Setup our notebook
|
// Setup our notebook
|
||||||
self.notebook.init(self);
|
self.notebook.init(self);
|
||||||
|
|
||||||
@ -174,7 +179,15 @@ pub fn init(self: *Window, app: *App) !void {
|
|||||||
const btn = c.gtk_menu_button_new();
|
const btn = c.gtk_menu_button_new();
|
||||||
c.gtk_widget_set_tooltip_text(btn, "Main Menu");
|
c.gtk_widget_set_tooltip_text(btn, "Main Menu");
|
||||||
c.gtk_menu_button_set_icon_name(@ptrCast(btn), "open-menu-symbolic");
|
c.gtk_menu_button_set_icon_name(@ptrCast(btn), "open-menu-symbolic");
|
||||||
c.gtk_menu_button_set_menu_model(@ptrCast(btn), @ptrCast(@alignCast(app.menu)));
|
c.gtk_menu_button_set_popover(@ptrCast(btn), @ptrCast(@alignCast(self.titlebar_menu.asWidget())));
|
||||||
|
_ = c.g_signal_connect_data(
|
||||||
|
btn,
|
||||||
|
"notify::active",
|
||||||
|
c.G_CALLBACK(>kTitlebarMenuActivate),
|
||||||
|
self,
|
||||||
|
null,
|
||||||
|
c.G_CONNECT_DEFAULT,
|
||||||
|
);
|
||||||
self.headerbar.packEnd(btn);
|
self.headerbar.packEnd(btn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,11 +272,6 @@ pub fn init(self: *Window, app: *App) !void {
|
|||||||
c.adw_tab_overview_set_view(@ptrCast(tab_overview), @ptrCast(@alignCast(self.notebook.tab_view)));
|
c.adw_tab_overview_set_view(@ptrCast(tab_overview), @ptrCast(@alignCast(self.notebook.tab_view)));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.context_menu = c.gtk_popover_menu_new_from_model(@ptrCast(@alignCast(self.app.context_menu)));
|
|
||||||
c.gtk_widget_set_parent(self.context_menu, box);
|
|
||||||
c.gtk_popover_set_has_arrow(@ptrCast(@alignCast(self.context_menu)), 0);
|
|
||||||
c.gtk_widget_set_halign(self.context_menu, c.GTK_ALIGN_START);
|
|
||||||
|
|
||||||
// We register a key event controller with the window so
|
// We register a key event controller with the window so
|
||||||
// we can catch key events when our surface may not be
|
// we can catch key events when our surface may not be
|
||||||
// focused (i.e. when the libadw tab overview is shown).
|
// focused (i.e. when the libadw tab overview is shown).
|
||||||
@ -272,7 +280,6 @@ pub fn init(self: *Window, app: *App) !void {
|
|||||||
c.gtk_widget_add_controller(gtk_widget, ec_key_press);
|
c.gtk_widget_add_controller(gtk_widget, ec_key_press);
|
||||||
|
|
||||||
// All of our events
|
// All of our events
|
||||||
_ = c.g_signal_connect_data(self.context_menu, "closed", c.G_CALLBACK(>kRefocusTerm), self, null, c.G_CONNECT_DEFAULT);
|
|
||||||
_ = c.g_signal_connect_data(self.window, "realize", c.G_CALLBACK(>kRealize), self, null, c.G_CONNECT_DEFAULT);
|
_ = c.g_signal_connect_data(self.window, "realize", c.G_CALLBACK(>kRealize), self, null, c.G_CONNECT_DEFAULT);
|
||||||
_ = c.g_signal_connect_data(self.window, "close-request", c.G_CALLBACK(>kCloseRequest), self, null, c.G_CONNECT_DEFAULT);
|
_ = c.g_signal_connect_data(self.window, "close-request", c.G_CALLBACK(>kCloseRequest), self, null, c.G_CONNECT_DEFAULT);
|
||||||
_ = c.g_signal_connect_data(self.window, "destroy", c.G_CALLBACK(>kDestroy), self, null, c.G_CONNECT_DEFAULT);
|
_ = c.g_signal_connect_data(self.window, "destroy", c.G_CALLBACK(>kDestroy), self, null, c.G_CONNECT_DEFAULT);
|
||||||
@ -459,16 +466,18 @@ fn initActions(self: *Window) void {
|
|||||||
const actions = .{
|
const actions = .{
|
||||||
.{ "about", >kActionAbout },
|
.{ "about", >kActionAbout },
|
||||||
.{ "close", >kActionClose },
|
.{ "close", >kActionClose },
|
||||||
.{ "new_window", >kActionNewWindow },
|
.{ "new-window", >kActionNewWindow },
|
||||||
.{ "new_tab", >kActionNewTab },
|
.{ "new-tab", >kActionNewTab },
|
||||||
.{ "split_right", >kActionSplitRight },
|
.{ "close-tab", >kActionCloseTab },
|
||||||
.{ "split_down", >kActionSplitDown },
|
.{ "split-right", >kActionSplitRight },
|
||||||
.{ "split_left", >kActionSplitLeft },
|
.{ "split-down", >kActionSplitDown },
|
||||||
.{ "split_up", >kActionSplitUp },
|
.{ "split-left", >kActionSplitLeft },
|
||||||
.{ "toggle_inspector", >kActionToggleInspector },
|
.{ "split-up", >kActionSplitUp },
|
||||||
|
.{ "toggle-inspector", >kActionToggleInspector },
|
||||||
.{ "copy", >kActionCopy },
|
.{ "copy", >kActionCopy },
|
||||||
.{ "paste", >kActionPaste },
|
.{ "paste", >kActionPaste },
|
||||||
.{ "reset", >kActionReset },
|
.{ "reset", >kActionReset },
|
||||||
|
.{ "clear", >kActionClear },
|
||||||
};
|
};
|
||||||
|
|
||||||
inline for (actions) |entry| {
|
inline for (actions) |entry| {
|
||||||
@ -487,8 +496,6 @@ fn initActions(self: *Window) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Window) void {
|
pub fn deinit(self: *Window) void {
|
||||||
c.gtk_widget_unparent(@ptrCast(self.context_menu));
|
|
||||||
|
|
||||||
self.winproto.deinit(self.app.core_app.alloc);
|
self.winproto.deinit(self.app.core_app.alloc);
|
||||||
|
|
||||||
if (self.adw_tab_overview_focus_timer) |timer| {
|
if (self.adw_tab_overview_focus_timer) |timer| {
|
||||||
@ -752,16 +759,6 @@ fn adwTabOverviewFocusTimer(
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gtkRefocusTerm(v: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool {
|
|
||||||
_ = v;
|
|
||||||
log.debug("refocus term request", .{});
|
|
||||||
const self = userdataSelf(ud.?);
|
|
||||||
|
|
||||||
self.focusCurrentTab();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gtkCloseRequest(v: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool {
|
fn gtkCloseRequest(v: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool {
|
||||||
_ = v;
|
_ = v;
|
||||||
log.debug("window close request", .{});
|
log.debug("window close request", .{});
|
||||||
@ -919,11 +916,7 @@ fn gtkActionClose(
|
|||||||
ud: ?*anyopaque,
|
ud: ?*anyopaque,
|
||||||
) callconv(.C) void {
|
) callconv(.C) void {
|
||||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
||||||
const surface = self.actionSurface() orelse return;
|
c.gtk_window_destroy(self.window);
|
||||||
_ = surface.performBindingAction(.{ .close_surface = {} }) catch |err| {
|
|
||||||
log.warn("error performing binding action error={}", .{err});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gtkActionNewWindow(
|
fn gtkActionNewWindow(
|
||||||
@ -948,6 +941,19 @@ fn gtkActionNewTab(
|
|||||||
gtkTabNewClick(undefined, ud);
|
gtkTabNewClick(undefined, ud);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn gtkActionCloseTab(
|
||||||
|
_: *c.GSimpleAction,
|
||||||
|
_: *c.GVariant,
|
||||||
|
ud: ?*anyopaque,
|
||||||
|
) callconv(.C) void {
|
||||||
|
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
||||||
|
const surface = self.actionSurface() orelse return;
|
||||||
|
_ = surface.performBindingAction(.{ .close_tab = {} }) catch |err| {
|
||||||
|
log.warn("error performing binding action error={}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn gtkActionSplitRight(
|
fn gtkActionSplitRight(
|
||||||
_: *c.GSimpleAction,
|
_: *c.GSimpleAction,
|
||||||
_: *c.GVariant,
|
_: *c.GVariant,
|
||||||
@ -1052,13 +1058,40 @@ fn gtkActionReset(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn gtkActionClear(
|
||||||
|
_: *c.GSimpleAction,
|
||||||
|
_: *c.GVariant,
|
||||||
|
ud: ?*anyopaque,
|
||||||
|
) callconv(.C) void {
|
||||||
|
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
||||||
|
const surface = self.actionSurface() orelse return;
|
||||||
|
_ = surface.performBindingAction(.{ .clear_screen = {} }) catch |err| {
|
||||||
|
log.warn("error performing binding action error={}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the surface to use for an action.
|
/// Returns the surface to use for an action.
|
||||||
fn actionSurface(self: *Window) ?*CoreSurface {
|
pub fn actionSurface(self: *Window) ?*CoreSurface {
|
||||||
const tab = self.notebook.currentTab() orelse return null;
|
const tab = self.notebook.currentTab() orelse return null;
|
||||||
const surface = tab.focus_child orelse return null;
|
const surface = tab.focus_child orelse return null;
|
||||||
return &surface.core_surface;
|
return &surface.core_surface;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn gtkTitlebarMenuActivate(
|
||||||
|
btn: *c.GtkMenuButton,
|
||||||
|
_: *c.GParamSpec,
|
||||||
|
ud: ?*anyopaque,
|
||||||
|
) callconv(.C) void {
|
||||||
|
const active = c.gtk_menu_button_get_active(btn) != 0;
|
||||||
|
const self = userdataSelf(ud orelse return);
|
||||||
|
if (active) {
|
||||||
|
self.titlebar_menu.refresh();
|
||||||
|
} else {
|
||||||
|
self.focusCurrentTab();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn userdataSelf(ud: *anyopaque) *Window {
|
fn userdataSelf(ud: *anyopaque) *Window {
|
||||||
return @ptrCast(@alignCast(ud));
|
return @ptrCast(@alignCast(ud));
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,10 @@ const icons = [_]struct {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ui_files = [_][]const u8{};
|
pub const ui_files = [_][]const u8{
|
||||||
|
"menu-window-titlebar_menu",
|
||||||
|
"menu-surface-context_menu",
|
||||||
|
};
|
||||||
pub const blueprint_files = [_][]const u8{};
|
pub const blueprint_files = [_][]const u8{};
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
|
135
src/apprt/gtk/menu.zig
Normal file
135
src/apprt/gtk/menu.zig
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const gtk = @import("gtk");
|
||||||
|
const gdk = @import("gdk");
|
||||||
|
const gio = @import("gio");
|
||||||
|
const gobject = @import("gobject");
|
||||||
|
|
||||||
|
const apprt = @import("../../apprt.zig");
|
||||||
|
const App = @import("App.zig");
|
||||||
|
const Window = @import("Window.zig");
|
||||||
|
const Surface = @import("Surface.zig");
|
||||||
|
const Builder = @import("Builder.zig");
|
||||||
|
|
||||||
|
/// Abstract GTK menus to take advantage of machinery for buildtime/comptime
|
||||||
|
/// error checking.
|
||||||
|
pub fn Menu(
|
||||||
|
/// GTK apprt type that the menu is "for". Window and Surface are supported
|
||||||
|
/// right now.
|
||||||
|
comptime T: type,
|
||||||
|
/// Name of the menu. Along with the apprt type, this is used to look up the
|
||||||
|
/// builder ui definitions of the menu.
|
||||||
|
comptime menu_name: []const u8,
|
||||||
|
/// Should the popup have a pointer pointing to the location that it's
|
||||||
|
/// attached to.
|
||||||
|
comptime arrow: bool,
|
||||||
|
) type {
|
||||||
|
return struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
/// parent apprt object
|
||||||
|
parent: *T,
|
||||||
|
|
||||||
|
/// our widget
|
||||||
|
menu_widget: *gtk.PopoverMenu,
|
||||||
|
|
||||||
|
/// initialize the menu
|
||||||
|
pub fn init(self: *Self, parent: *T) void {
|
||||||
|
const object_type = switch (T) {
|
||||||
|
Window => "window",
|
||||||
|
Surface => "surface",
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
|
||||||
|
var builder = Builder.init("menu-" ++ object_type ++ "-" ++ menu_name, .ui);
|
||||||
|
defer builder.deinit();
|
||||||
|
|
||||||
|
const menu_model = builder.getObject(gio.MenuModel, "menu").?;
|
||||||
|
|
||||||
|
const menu_widget = gtk.PopoverMenu.newFromModelFull(menu_model, .{ .nested = true });
|
||||||
|
menu_widget.as(gtk.Popover).setHasArrow(@intFromBool(arrow));
|
||||||
|
_ = gtk.Popover.signals.closed.connect(
|
||||||
|
menu_widget,
|
||||||
|
*Self,
|
||||||
|
gtkRefocusTerm,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
|
||||||
|
self.* = .{
|
||||||
|
.parent = parent,
|
||||||
|
.menu_widget = menu_widget,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setParent(self: *const Self, widget: *gtk.Widget) void {
|
||||||
|
self.menu_widget.as(gtk.Widget).setParent(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn asWidget(self: *const Self) *gtk.Widget {
|
||||||
|
return self.menu_widget.as(gtk.Widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isVisible(self: *const Self) bool {
|
||||||
|
return self.menu_widget.as(gtk.Widget).getVisible() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setVisible(self: *const Self, visible: bool) void {
|
||||||
|
self.menu_widget.as(gtk.Widget).setVisible(@intFromBool(visible));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refresh the menu. Right now that means enabling/disabling the "Copy"
|
||||||
|
/// menu item based on whether there is an active selection or not, but
|
||||||
|
/// that may change in the future.
|
||||||
|
pub fn refresh(self: *const Self) void {
|
||||||
|
const window: *gtk.Window, const has_selection: bool = switch (T) {
|
||||||
|
Window => window: {
|
||||||
|
const core_surface = self.parent.actionSurface() orelse break :window .{
|
||||||
|
@ptrCast(@alignCast(self.parent.window)),
|
||||||
|
false,
|
||||||
|
};
|
||||||
|
const has_selection = core_surface.hasSelection();
|
||||||
|
break :window .{ @ptrCast(@alignCast(self.parent.window)), has_selection };
|
||||||
|
},
|
||||||
|
Surface => surface: {
|
||||||
|
const window = self.parent.container.window() orelse return;
|
||||||
|
const has_selection = self.parent.core_surface.hasSelection();
|
||||||
|
break :surface .{ @ptrCast(@alignCast(window.window)), has_selection };
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
|
||||||
|
const action_map: *gio.ActionMap = gobject.ext.cast(gio.ActionMap, window) orelse return;
|
||||||
|
const action: *gio.SimpleAction = gobject.ext.cast(
|
||||||
|
gio.SimpleAction,
|
||||||
|
action_map.lookupAction("copy") orelse return,
|
||||||
|
) orelse return;
|
||||||
|
action.setEnabled(@intFromBool(has_selection));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pop up the menu at the given coordinates
|
||||||
|
pub fn popupAt(self: *const Self, x: c_int, y: c_int) void {
|
||||||
|
const rect: gdk.Rectangle = .{
|
||||||
|
.f_x = x,
|
||||||
|
.f_y = y,
|
||||||
|
.f_width = 1,
|
||||||
|
.f_height = 1,
|
||||||
|
};
|
||||||
|
const popover = self.menu_widget.as(gtk.Popover);
|
||||||
|
popover.setPointingTo(&rect);
|
||||||
|
self.refresh();
|
||||||
|
popover.popup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refocus tab that lost focus because of the popover menu
|
||||||
|
fn gtkRefocusTerm(_: *gtk.PopoverMenu, self: *Self) callconv(.C) void {
|
||||||
|
const window: *Window = switch (T) {
|
||||||
|
Window => self.parent,
|
||||||
|
Surface => self.parent.container.window() orelse return,
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
|
||||||
|
window.focusCurrentTab();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
90
src/apprt/gtk/ui/menu-surface-context_menu.ui
Normal file
90
src/apprt/gtk/ui/menu-surface-context_menu.ui
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface domain="com.mitchellh.ghostty">
|
||||||
|
<requires lib="gtk" version="4.0"/>
|
||||||
|
<menu id="menu">
|
||||||
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Copy</attribute>
|
||||||
|
<attribute name="action">win.copy</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Paste</attribute>
|
||||||
|
<attribute name="action">win.paste</attribute>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Clear</attribute>
|
||||||
|
<attribute name="action">win.clear</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Reset</attribute>
|
||||||
|
<attribute name="action">win.reset</attribute>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<submenu>
|
||||||
|
<attribute name="label">Split</attribute>
|
||||||
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Split Up</attribute>
|
||||||
|
<attribute name="action">win.split-up</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Split Down</attribute>
|
||||||
|
<attribute name="action">win.split-down</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Split Left</attribute>
|
||||||
|
<attribute name="action">win.split-left</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Split Right</attribute>
|
||||||
|
<attribute name="action">win.split-right</attribute>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
</submenu>
|
||||||
|
<submenu>
|
||||||
|
<attribute name="label">Tab</attribute>
|
||||||
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">New Tab</attribute>
|
||||||
|
<attribute name="action">win.new-tab</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Close Tab</attribute>
|
||||||
|
<attribute name="action">win.close-tab</attribute>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
</submenu>
|
||||||
|
<submenu>
|
||||||
|
<attribute name="label">Window</attribute>
|
||||||
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">New Window</attribute>
|
||||||
|
<attribute name="action">win.new-window</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Close Window</attribute>
|
||||||
|
<attribute name="action">win.close</attribute>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
</submenu>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<submenu>
|
||||||
|
<attribute name="label">Config</attribute>
|
||||||
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Open Configuration</attribute>
|
||||||
|
<attribute name="action">app.open-config</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Reload Configuration</attribute>
|
||||||
|
<attribute name="action">app.reload-config</attribute>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
</submenu>
|
||||||
|
</section>
|
||||||
|
</menu>
|
||||||
|
</interface>
|
93
src/apprt/gtk/ui/menu-window-titlebar_menu.ui
Normal file
93
src/apprt/gtk/ui/menu-window-titlebar_menu.ui
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface domain="com.mitchellh.ghostty">
|
||||||
|
<requires lib="gtk" version="4.0"/>
|
||||||
|
<menu id="menu">
|
||||||
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Copy</attribute>
|
||||||
|
<attribute name="action">win.copy</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Paste</attribute>
|
||||||
|
<attribute name="action">win.paste</attribute>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">New Window</attribute>
|
||||||
|
<attribute name="action">win.new-window</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Close Window</attribute>
|
||||||
|
<attribute name="action">win.close</attribute>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">New Tab</attribute>
|
||||||
|
<attribute name="action">win.new-tab</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Close Tab</attribute>
|
||||||
|
<attribute name="action">win.close-tab</attribute>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<submenu>
|
||||||
|
<attribute name="label">Split</attribute>
|
||||||
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Split Up</attribute>
|
||||||
|
<attribute name="action">win.split-up</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Split Down</attribute>
|
||||||
|
<attribute name="action">win.split-down</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Split Left</attribute>
|
||||||
|
<attribute name="action">win.split-left</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Split Right</attribute>
|
||||||
|
<attribute name="action">win.split-right</attribute>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
</submenu>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Clear</attribute>
|
||||||
|
<attribute name="action">win.clear</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Reset</attribute>
|
||||||
|
<attribute name="action">win.reset</attribute>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Terminal Inspector</attribute>
|
||||||
|
<attribute name="action">win.toggle-inspector</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Open Configuration</attribute>
|
||||||
|
<attribute name="action">app.open-config</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Reload Configuration</attribute>
|
||||||
|
<attribute name="action">app.reload-config</attribute>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">About Ghostty</attribute>
|
||||||
|
<attribute name="action">win.about</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Quit</attribute>
|
||||||
|
<attribute name="action">app.quit</attribute>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
</menu>
|
||||||
|
</interface>
|
Reference in New Issue
Block a user