gtk: port ConfigErrorsWindow to dialogs
parent
1ee9c85954
commit
73341b052b
|
|
@ -36,7 +36,7 @@ const CoreSurface = @import("../../Surface.zig");
|
|||
const cgroup = @import("cgroup.zig");
|
||||
const Surface = @import("Surface.zig");
|
||||
const Window = @import("Window.zig");
|
||||
const ConfigErrorsWindow = @import("ConfigErrorsWindow.zig");
|
||||
const ConfigErrorsDialog = @import("ConfigErrorsDialog.zig");
|
||||
const ClipboardConfirmationWindow = @import("ClipboardConfirmationWindow.zig");
|
||||
const CloseDialog = @import("CloseDialog.zig");
|
||||
const Split = @import("Split.zig");
|
||||
|
|
@ -71,9 +71,6 @@ single_instance: bool,
|
|||
/// The "none" cursor. We use one that is shared across the entire app.
|
||||
cursor_none: ?*gdk.Cursor,
|
||||
|
||||
/// The configuration errors window, if it is currently open.
|
||||
config_errors_window: ?*ConfigErrorsWindow = null,
|
||||
|
||||
/// The clipboard confirmation window, if it is currently open.
|
||||
clipboard_confirmation_window: ?*ClipboardConfirmationWindow = null,
|
||||
|
||||
|
|
@ -956,16 +953,22 @@ fn configChange(
|
|||
log.warn("error cloning configuration err={}", .{err});
|
||||
}
|
||||
|
||||
self.syncConfigChanges() catch |err| {
|
||||
log.warn("error handling configuration changes err={}", .{err});
|
||||
};
|
||||
|
||||
// App changes needs to show a toast that our configuration
|
||||
// has reloaded.
|
||||
if (self.core_app.focusedSurface()) |core_surface| {
|
||||
const surface = core_surface.rt_surface;
|
||||
if (surface.container.window()) |window| window.onConfigReloaded();
|
||||
}
|
||||
const window = window: {
|
||||
if (self.core_app.focusedSurface()) |core_surface| {
|
||||
const surface = core_surface.rt_surface;
|
||||
if (surface.container.window()) |window| {
|
||||
window.onConfigReloaded();
|
||||
break :window window;
|
||||
}
|
||||
}
|
||||
break :window null;
|
||||
};
|
||||
|
||||
self.syncConfigChanges(window) catch |err| {
|
||||
log.warn("error handling configuration changes err={}", .{err});
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -1001,8 +1004,8 @@ pub fn reloadConfig(
|
|||
}
|
||||
|
||||
/// Call this anytime the configuration changes.
|
||||
fn syncConfigChanges(self: *App) !void {
|
||||
try self.updateConfigErrors();
|
||||
fn syncConfigChanges(self: *App, window: ?*Window) !void {
|
||||
ConfigErrorsDialog.maybePresent(self, window);
|
||||
try self.syncActionAccelerators();
|
||||
|
||||
// Load our runtime and custom CSS. If this fails then our window is just stuck
|
||||
|
|
@ -1018,23 +1021,6 @@ fn syncConfigChanges(self: *App) !void {
|
|||
};
|
||||
}
|
||||
|
||||
/// This should be called whenever the configuration changes to update
|
||||
/// the state of our config errors window. This will show the window if
|
||||
/// there are new configuration errors and hide the window if the errors
|
||||
/// are resolved.
|
||||
fn updateConfigErrors(self: *App) !void {
|
||||
if (!self.config._diagnostics.empty()) {
|
||||
if (self.config_errors_window == null) {
|
||||
try ConfigErrorsWindow.create(self);
|
||||
assert(self.config_errors_window != null);
|
||||
}
|
||||
}
|
||||
|
||||
if (self.config_errors_window) |window| {
|
||||
window.update();
|
||||
}
|
||||
}
|
||||
|
||||
fn syncActionAccelerators(self: *App) !void {
|
||||
try self.syncActionAccelerator("app.quit", .{ .quit = {} });
|
||||
try self.syncActionAccelerator("app.open-config", .{ .open_config = {} });
|
||||
|
|
@ -1309,13 +1295,6 @@ pub fn run(self: *App) !void {
|
|||
// Setup our actions
|
||||
self.initActions();
|
||||
|
||||
// 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
|
||||
// state.
|
||||
self.syncConfigChanges() catch |err| {
|
||||
log.warn("error handling configuration changes err={}", .{err});
|
||||
};
|
||||
|
||||
while (self.running) {
|
||||
_ = glib.MainContext.iteration(self.ctx, 1);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
/// Configuration errors window.
|
||||
const ConfigErrorsDialog = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const gobject = @import("gobject");
|
||||
const gio = @import("gio");
|
||||
const gtk = @import("gtk");
|
||||
const adw = @import("adw");
|
||||
|
||||
const build_config = @import("../../build_config.zig");
|
||||
const configpkg = @import("../../config.zig");
|
||||
const Config = configpkg.Config;
|
||||
|
||||
const App = @import("App.zig");
|
||||
const Window = @import("Window.zig");
|
||||
const Builder = @import("Builder.zig");
|
||||
const adwaita = @import("adwaita.zig");
|
||||
|
||||
const log = std.log.scoped(.gtk);
|
||||
|
||||
const DialogType = if (adwaita.supportsDialogs()) adw.AlertDialog else adw.MessageDialog;
|
||||
|
||||
builder: Builder,
|
||||
dialog: *DialogType,
|
||||
error_message: *gtk.TextBuffer,
|
||||
|
||||
pub fn maybePresent(app: *App, window: ?*Window) void {
|
||||
if (app.config._diagnostics.empty()) return;
|
||||
|
||||
var builder = switch (DialogType) {
|
||||
adw.AlertDialog => Builder.init("config-errors-dialog", 1, 5, .blp),
|
||||
adw.MessageDialog => Builder.init("config-errors-dialog", 1, 2, .ui),
|
||||
else => unreachable,
|
||||
};
|
||||
defer builder.deinit();
|
||||
|
||||
const dialog = builder.getObject(DialogType, "config_errors_dialog").?;
|
||||
const error_message = builder.getObject(gtk.TextBuffer, "error_message").?;
|
||||
|
||||
var msg_buf: [4095:0]u8 = undefined;
|
||||
var fbs = std.io.fixedBufferStream(&msg_buf);
|
||||
|
||||
for (app.config._diagnostics.items()) |diag| {
|
||||
fbs.reset();
|
||||
diag.write(fbs.writer()) catch |err| {
|
||||
log.warn(
|
||||
"error writing diagnostic to buffer err={}",
|
||||
.{err},
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
error_message.insertAtCursor(&msg_buf, @intCast(fbs.pos));
|
||||
error_message.insertAtCursor("\n", 1);
|
||||
}
|
||||
|
||||
_ = DialogType.signals.response.connect(dialog, *App, onResponse, app, .{});
|
||||
|
||||
const parent: ?*gtk.Widget = if (window) |w| @ptrCast(w.window) else null;
|
||||
|
||||
switch (DialogType) {
|
||||
adw.AlertDialog => dialog.as(adw.Dialog).present(parent),
|
||||
adw.MessageDialog => dialog.as(gtk.Window).present(),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn onResponse(_: *DialogType, response: [*:0]const u8, app: *App) callconv(.C) void {
|
||||
if (std.mem.orderZ(u8, response, "reload") == .eq) {
|
||||
app.reloadConfig(.app, .{}) catch |err| {
|
||||
log.warn("error reloading config error={}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,218 +0,0 @@
|
|||
/// Configuration errors window.
|
||||
const ConfigErrors = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const build_config = @import("../../build_config.zig");
|
||||
const configpkg = @import("../../config.zig");
|
||||
const Config = configpkg.Config;
|
||||
|
||||
const App = @import("App.zig");
|
||||
const View = @import("View.zig");
|
||||
const c = @import("c.zig").c;
|
||||
|
||||
const log = std.log.scoped(.gtk);
|
||||
|
||||
app: *App,
|
||||
window: *c.GtkWindow,
|
||||
view: PrimaryView,
|
||||
|
||||
pub fn create(app: *App) !void {
|
||||
if (app.config_errors_window != null) return error.InvalidOperation;
|
||||
|
||||
const alloc = app.core_app.alloc;
|
||||
const self = try alloc.create(ConfigErrors);
|
||||
errdefer alloc.destroy(self);
|
||||
try self.init(app);
|
||||
|
||||
app.config_errors_window = self;
|
||||
}
|
||||
|
||||
pub fn update(self: *ConfigErrors) void {
|
||||
if (self.app.config._diagnostics.empty()) {
|
||||
c.gtk_window_destroy(@ptrCast(self.window));
|
||||
return;
|
||||
}
|
||||
|
||||
self.view.update(&self.app.config);
|
||||
_ = c.gtk_window_present(self.window);
|
||||
_ = c.gtk_widget_grab_focus(@ptrCast(self.window));
|
||||
}
|
||||
|
||||
/// Not public because this should be called by the GTK lifecycle.
|
||||
fn destroy(self: *ConfigErrors) void {
|
||||
const alloc = self.app.core_app.alloc;
|
||||
self.app.config_errors_window = null;
|
||||
alloc.destroy(self);
|
||||
}
|
||||
|
||||
fn init(self: *ConfigErrors, app: *App) !void {
|
||||
// Create the window
|
||||
const window = c.gtk_window_new();
|
||||
const gtk_window: *c.GtkWindow = @ptrCast(window);
|
||||
errdefer c.gtk_window_destroy(gtk_window);
|
||||
c.gtk_window_set_title(gtk_window, "Configuration Errors");
|
||||
c.gtk_window_set_default_size(gtk_window, 600, 275);
|
||||
c.gtk_window_set_resizable(gtk_window, 0);
|
||||
c.gtk_window_set_icon_name(gtk_window, build_config.bundle_id);
|
||||
c.gtk_widget_add_css_class(@ptrCast(@alignCast(gtk_window)), "window");
|
||||
c.gtk_widget_add_css_class(@ptrCast(@alignCast(gtk_window)), "config-errors-window");
|
||||
_ = c.g_signal_connect_data(window, "destroy", c.G_CALLBACK(>kDestroy), self, null, c.G_CONNECT_DEFAULT);
|
||||
|
||||
// Set some state
|
||||
self.* = .{
|
||||
.app = app,
|
||||
.window = gtk_window,
|
||||
.view = undefined,
|
||||
};
|
||||
|
||||
// Show the window
|
||||
const view = try PrimaryView.init(self);
|
||||
self.view = view;
|
||||
c.gtk_window_set_child(@ptrCast(window), view.root);
|
||||
c.gtk_widget_show(window);
|
||||
}
|
||||
|
||||
fn gtkDestroy(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void {
|
||||
const self = userdataSelf(ud.?);
|
||||
self.destroy();
|
||||
}
|
||||
|
||||
fn userdataSelf(ud: *anyopaque) *ConfigErrors {
|
||||
return @ptrCast(@alignCast(ud));
|
||||
}
|
||||
|
||||
const PrimaryView = struct {
|
||||
root: *c.GtkWidget,
|
||||
text: *c.GtkTextView,
|
||||
|
||||
pub fn init(root: *ConfigErrors) !PrimaryView {
|
||||
// All our widgets
|
||||
const label = c.gtk_label_new(
|
||||
"One or more configuration errors were found while loading " ++
|
||||
"the configuration. Please review the errors below and reload " ++
|
||||
"your configuration or ignore the erroneous lines.",
|
||||
);
|
||||
const buf = contentsBuffer(&root.app.config);
|
||||
defer c.g_object_unref(buf);
|
||||
const buttons = try ButtonsView.init(root);
|
||||
const text_scroll = c.gtk_scrolled_window_new();
|
||||
errdefer c.g_object_unref(text_scroll);
|
||||
const text = c.gtk_text_view_new_with_buffer(buf);
|
||||
errdefer c.g_object_unref(text);
|
||||
c.gtk_scrolled_window_set_child(@ptrCast(text_scroll), text);
|
||||
|
||||
// Create our view
|
||||
const view = try View.init(&.{
|
||||
.{ .name = "label", .widget = label },
|
||||
.{ .name = "text", .widget = text_scroll },
|
||||
.{ .name = "buttons", .widget = buttons.root },
|
||||
}, &vfl);
|
||||
errdefer view.deinit();
|
||||
|
||||
// We can do additional settings once the layout is setup
|
||||
c.gtk_label_set_wrap(@ptrCast(label), 1);
|
||||
c.gtk_text_view_set_editable(@ptrCast(text), 0);
|
||||
c.gtk_text_view_set_cursor_visible(@ptrCast(text), 0);
|
||||
c.gtk_text_view_set_top_margin(@ptrCast(text), 8);
|
||||
c.gtk_text_view_set_bottom_margin(@ptrCast(text), 8);
|
||||
c.gtk_text_view_set_left_margin(@ptrCast(text), 8);
|
||||
c.gtk_text_view_set_right_margin(@ptrCast(text), 8);
|
||||
|
||||
return .{ .root = view.root, .text = @ptrCast(text) };
|
||||
}
|
||||
|
||||
pub fn update(self: *PrimaryView, config: *const Config) void {
|
||||
const buf = contentsBuffer(config);
|
||||
defer c.g_object_unref(buf);
|
||||
c.gtk_text_view_set_buffer(@ptrCast(self.text), buf);
|
||||
}
|
||||
|
||||
/// Returns the GtkTextBuffer for the config errors that we want to show.
|
||||
fn contentsBuffer(config: *const Config) *c.GtkTextBuffer {
|
||||
const buf = c.gtk_text_buffer_new(null);
|
||||
errdefer c.g_object_unref(buf);
|
||||
|
||||
var msg_buf: [4096]u8 = undefined;
|
||||
var fbs = std.io.fixedBufferStream(&msg_buf);
|
||||
|
||||
for (config._diagnostics.items()) |diag| {
|
||||
fbs.reset();
|
||||
diag.write(fbs.writer()) catch |err| {
|
||||
log.warn(
|
||||
"error writing diagnostic to buffer err={}",
|
||||
.{err},
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
const msg = fbs.getWritten();
|
||||
c.gtk_text_buffer_insert_at_cursor(buf, msg.ptr, @intCast(msg.len));
|
||||
c.gtk_text_buffer_insert_at_cursor(buf, "\n", -1);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
const vfl = [_][*:0]const u8{
|
||||
"H:|-8-[label]-8-|",
|
||||
"H:|[text]|",
|
||||
"H:|[buttons]|",
|
||||
"V:|[label(<=80)][text(>=100)]-[buttons]-|",
|
||||
};
|
||||
};
|
||||
|
||||
const ButtonsView = struct {
|
||||
root: *c.GtkWidget,
|
||||
|
||||
pub fn init(root: *ConfigErrors) !ButtonsView {
|
||||
const ignore_button = c.gtk_button_new_with_label("Ignore");
|
||||
errdefer c.g_object_unref(ignore_button);
|
||||
|
||||
const reload_button = c.gtk_button_new_with_label("Reload Configuration");
|
||||
errdefer c.g_object_unref(reload_button);
|
||||
|
||||
// Create our view
|
||||
const view = try View.init(&.{
|
||||
.{ .name = "ignore", .widget = ignore_button },
|
||||
.{ .name = "reload", .widget = reload_button },
|
||||
}, &vfl);
|
||||
|
||||
// Signals
|
||||
_ = c.g_signal_connect_data(
|
||||
ignore_button,
|
||||
"clicked",
|
||||
c.G_CALLBACK(>kIgnoreClick),
|
||||
root,
|
||||
null,
|
||||
c.G_CONNECT_DEFAULT,
|
||||
);
|
||||
_ = c.g_signal_connect_data(
|
||||
reload_button,
|
||||
"clicked",
|
||||
c.G_CALLBACK(>kReloadClick),
|
||||
root,
|
||||
null,
|
||||
c.G_CONNECT_DEFAULT,
|
||||
);
|
||||
|
||||
return .{ .root = view.root };
|
||||
}
|
||||
|
||||
fn gtkIgnoreClick(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void {
|
||||
const self: *ConfigErrors = @ptrCast(@alignCast(ud));
|
||||
c.gtk_window_destroy(@ptrCast(self.window));
|
||||
}
|
||||
|
||||
fn gtkReloadClick(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void {
|
||||
const self: *ConfigErrors = @ptrCast(@alignCast(ud));
|
||||
self.app.reloadConfig(.app, .{}) catch |err| {
|
||||
log.warn("error reloading config error={}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
const vfl = [_][*:0]const u8{
|
||||
"H:[ignore]-8-[reload]-8-|",
|
||||
};
|
||||
};
|
||||
|
|
@ -60,6 +60,7 @@ pub const VersionedBuilderXML = struct {
|
|||
};
|
||||
|
||||
pub const ui_files = [_]VersionedBuilderXML{
|
||||
.{ .major = 1, .minor = 2, .name = "config-errors-dialog" },
|
||||
.{ .major = 1, .minor = 2, .name = "ccw-osc-52-read" },
|
||||
.{ .major = 1, .minor = 2, .name = "ccw-osc-52-write" },
|
||||
.{ .major = 1, .minor = 2, .name = "ccw-paste" },
|
||||
|
|
@ -73,6 +74,7 @@ pub const VersionedBlueprint = struct {
|
|||
|
||||
pub const blueprint_files = [_]VersionedBlueprint{
|
||||
.{ .major = 1, .minor = 5, .name = "prompt-title-dialog" },
|
||||
.{ .major = 1, .minor = 5, .name = "config-errors-dialog" },
|
||||
.{ .major = 1, .minor = 0, .name = "menu-surface-context_menu" },
|
||||
.{ .major = 1, .minor = 0, .name = "menu-window-titlebar_menu" },
|
||||
.{ .major = 1, .minor = 5, .name = "ccw-osc-52-read" },
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
Adw.MessageDialog config_errors_dialog {
|
||||
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: TextBuffer error_message { };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
DO NOT EDIT!
|
||||
This file was @generated by blueprint-compiler. Instead, edit the
|
||||
corresponding .blp file and regenerate this file with blueprint-compiler.
|
||||
-->
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<object class="AdwMessageDialog" id="config_errors_dialog">
|
||||
<property name="heading" translatable="yes">Configuration Errors</property>
|
||||
<property name="body" translatable="yes">One or more configuration errors were found. Please review the errors below, and either reload your configuration or ignore these errors.</property>
|
||||
<responses>
|
||||
<response id="ignore" translatable="yes">Ignore</response>
|
||||
<response id="reload" translatable="yes" appearance="suggested">Reload Configuration</response>
|
||||
</responses>
|
||||
<property name="extra-child">
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="min-content-width">500</property>
|
||||
<property name="min-content-height">100</property>
|
||||
<child>
|
||||
<object class="GtkTextView">
|
||||
<property name="editable">false</property>
|
||||
<property name="cursor-visible">false</property>
|
||||
<property name="top-margin">8</property>
|
||||
<property name="bottom-margin">8</property>
|
||||
<property name="left-margin">8</property>
|
||||
<property name="right-margin">8</property>
|
||||
<property name="buffer">
|
||||
<object class="GtkTextBuffer" id="error_message"></object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</interface>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
Adw.AlertDialog config_errors_dialog {
|
||||
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: TextBuffer error_message { };
|
||||
}
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue