From bd3b5d533269088a6e0bdfde533486f7c9985293 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 17 Sep 2023 21:37:57 -0700 Subject: [PATCH 1/7] apprt/gtk: working on config errors window --- src/apprt/gtk/App.zig | 35 +++++- src/apprt/gtk/ConfigErrorsWindow.zig | 163 +++++++++++++++++++++++++++ 2 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 src/apprt/gtk/ConfigErrorsWindow.zig diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index fba7bf89e..f2377248e 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -11,6 +11,7 @@ const App = @This(); const std = @import("std"); +const assert = std.debug.assert; const builtin = @import("builtin"); const glfw = @import("glfw"); const configpkg = @import("../../config.zig"); @@ -20,6 +21,7 @@ const CoreSurface = @import("../../Surface.zig"); const Surface = @import("Surface.zig"); const Window = @import("Window.zig"); +const ConfigErrorsWindow = @import("ConfigErrorsWindow.zig"); const c = @import("c.zig"); const log = std.log.scoped(.gtk); @@ -35,6 +37,9 @@ ctx: *c.GMainContext, /// The "none" cursor. We use one that is shared across the entire app. cursor_none: ?*c.GdkCursor, +/// The configuration errors window, if it is currently open. +config_errors_window: ?*ConfigErrorsWindow = null, + /// This is set to false when the main loop should exit. running: bool = true, @@ -80,7 +85,7 @@ pub fn init(core_app: *CoreApp, opts: Options) !App { _ = c.g_signal_connect_data( app, "activate", - c.G_CALLBACK(&activate), + c.G_CALLBACK(>kActivate), core_app, null, c.G_CONNECT_DEFAULT, @@ -155,9 +160,27 @@ pub fn reloadConfig(self: *App) !?*const Config { self.config.deinit(); self.config = config; + // If there were errors, report them + self.updateConfigErrors() catch |err| { + log.warn("error handling configuration errors err={}", .{err}); + }; + return &self.config; } +/// 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._errors.empty()) { + if (self.config_errors_window == null) { + try ConfigErrorsWindow.create(self); + assert(self.config_errors_window != null); + } + } +} + /// Called by CoreApp to wake up the event loop. pub fn wakeup(self: App) void { _ = self; @@ -166,6 +189,14 @@ pub fn wakeup(self: App) void { /// Run the event loop. This doesn't return until the app exits. pub fn run(self: *App) !void { + if (!self.running) return; + + // On startup, we want to check for configuration errors right away + // so we can show our error window. + self.updateConfigErrors() catch |err| { + log.warn("error handling configuration errors err={}", .{err}); + }; + while (self.running) { _ = c.g_main_context_iteration(self.ctx, 1); @@ -286,7 +317,7 @@ fn gtkQuitConfirmation( /// This is called by the "activate" signal. This is sent on program /// startup and also when a secondary instance launches and requests /// a new window. -fn activate(app: *c.GtkApplication, ud: ?*anyopaque) callconv(.C) void { +fn gtkActivate(app: *c.GtkApplication, ud: ?*anyopaque) callconv(.C) void { _ = app; const core_app: *CoreApp = @ptrCast(@alignCast(ud orelse return)); diff --git a/src/apprt/gtk/ConfigErrorsWindow.zig b/src/apprt/gtk/ConfigErrorsWindow.zig new file mode 100644 index 000000000..2228205f5 --- /dev/null +++ b/src/apprt/gtk/ConfigErrorsWindow.zig @@ -0,0 +1,163 @@ +/// Configuration errors window. +const ConfigErrors = @This(); + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const configpkg = @import("../../config.zig"); +const Config = configpkg.Config; + +const App = @import("App.zig"); +const c = @import("c.zig"); + +const log = std.log.scoped(.gtk); + +app: *App, + +layout: *c.GtkConstraintLayout, + +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; +} + +/// Not public because this should be called by the GTK lifecycle. +fn destroy(self: *ConfigErrors) void { + c.g_object_unref(self.layout); + + 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_application_window_new(app.app); + 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, 300); + + // Box to store our widgets + const box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 12); + c.gtk_widget_set_vexpand(box, 1); + c.gtk_widget_set_hexpand(box, 1); + c.gtk_window_set_child(@ptrCast(window), box); + + // We use a constraint-based layout so the window is resizeable + const layout = c.gtk_constraint_layout_new(); + errdefer c.g_object_unref(layout); + c.gtk_widget_set_layout_manager(@ptrCast(box), layout); + + // Create all of 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.", + ); + c.gtk_label_set_wrap(@ptrCast(label), 1); + + const buf = try contentsBuffer(&app.config); + defer c.g_object_unref(buf); + const text = c.gtk_text_view_new_with_buffer(buf); + errdefer c.g_object_unref(text); + c.gtk_text_view_set_editable(@ptrCast(text), 0); + c.gtk_text_view_set_cursor_visible(@ptrCast(text), 0); + + 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); + + // This hooks up all our widgets to the window so they can be laid out + // using the constraint-based layout. + c.gtk_widget_set_parent(label, box); + c.gtk_widget_set_name(label, "label"); + c.gtk_widget_set_parent(text, box); + c.gtk_widget_set_name(text, "text"); + c.gtk_widget_set_parent(ignore_button, box); + c.gtk_widget_set_name(ignore_button, "ignorebutton"); + c.gtk_widget_set_parent(reload_button, box); + c.gtk_widget_set_name(reload_button, "reloadbutton"); + + var gerr: ?*c.GError = null; + const list = c.gtk_constraint_layout_add_constraints_from_description( + @ptrCast(layout), + &vfl, + vfl.len, + 8, + 8, + &gerr, + "label", + label, + "text", + text, + "ignorebutton", + ignore_button, + "reloadbutton", + reload_button, + @as(?*anyopaque, null), + ); + if (gerr) |err| { + defer c.g_error_free(err); + log.warn("error building window message={s}", .{err.message}); + return error.OperationFailed; + } + c.g_list_free(list); + + // We can do additional settings once the layout is setup + 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); + + // Signals + _ = c.g_signal_connect_data(window, "destroy", c.G_CALLBACK(>kDestroy), self, null, c.G_CONNECT_DEFAULT); + + // Show the window + c.gtk_widget_show(window); + + // Set some state + self.* = .{ + .app = app, + .layout = @ptrCast(layout), + }; +} + +fn gtkDestroy(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void { + const self = userdataSelf(ud.?); + self.destroy(); +} + +fn userdataSelf(ud: *anyopaque) *ConfigErrors { + return @ptrCast(@alignCast(ud)); +} + +/// 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); + + for (config._errors.list.items) |err| { + c.gtk_text_buffer_insert_at_cursor(buf, err.message, @intCast(err.message.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:[ignorebutton]-8-[reloadbutton]-8-|", + "V:|[label(<=100)][text(>=100)]-[ignorebutton]-|", + "V:|[label(<=100)][text(>=100)]-[reloadbutton]-|", + "V:[label][text]-[ignorebutton]", + "V:[label][text]-[reloadbutton]", +}; From fe15e7d5fdd6a8bea8b25f264da9878cedd22cbe Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 17 Sep 2023 21:59:25 -0700 Subject: [PATCH 2/7] apprt/gtk: add View to help with auto-layout views --- src/apprt/gtk/ConfigErrorsWindow.zig | 120 ++++++++++++--------------- src/apprt/gtk/View.zig | 77 +++++++++++++++++ 2 files changed, 128 insertions(+), 69 deletions(-) create mode 100644 src/apprt/gtk/View.zig diff --git a/src/apprt/gtk/ConfigErrorsWindow.zig b/src/apprt/gtk/ConfigErrorsWindow.zig index 2228205f5..92a02a817 100644 --- a/src/apprt/gtk/ConfigErrorsWindow.zig +++ b/src/apprt/gtk/ConfigErrorsWindow.zig @@ -7,14 +7,13 @@ const configpkg = @import("../../config.zig"); const Config = configpkg.Config; const App = @import("App.zig"); +const View = @import("View.zig"); const c = @import("c.zig"); const log = std.log.scoped(.gtk); app: *App, -layout: *c.GtkConstraintLayout, - pub fn create(app: *App) !void { if (app.config_errors_window != null) return error.InvalidOperation; @@ -28,8 +27,6 @@ pub fn create(app: *App) !void { /// Not public because this should be called by the GTK lifecycle. fn destroy(self: *ConfigErrors) void { - c.g_object_unref(self.layout); - const alloc = self.app.core_app.alloc; self.app.config_errors_window = null; alloc.destroy(self); @@ -43,75 +40,33 @@ fn init(self: *ConfigErrors, app: *App) !void { c.gtk_window_set_title(gtk_window, "Configuration Errors"); c.gtk_window_set_default_size(gtk_window, 600, 300); - // Box to store our widgets - const box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 12); - c.gtk_widget_set_vexpand(box, 1); - c.gtk_widget_set_hexpand(box, 1); - c.gtk_window_set_child(@ptrCast(window), box); - - // We use a constraint-based layout so the window is resizeable - const layout = c.gtk_constraint_layout_new(); - errdefer c.g_object_unref(layout); - c.gtk_widget_set_layout_manager(@ptrCast(box), layout); - - // Create all of our widgets + // 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.", ); - c.gtk_label_set_wrap(@ptrCast(label), 1); const buf = try contentsBuffer(&app.config); defer c.g_object_unref(buf); const text = c.gtk_text_view_new_with_buffer(buf); errdefer c.g_object_unref(text); - c.gtk_text_view_set_editable(@ptrCast(text), 0); - c.gtk_text_view_set_cursor_visible(@ptrCast(text), 0); - const ignore_button = c.gtk_button_new_with_label("Ignore"); - errdefer c.g_object_unref(ignore_button); + var buttons = try ButtonsView.init(self); + errdefer buttons.deinit(); - const reload_button = c.gtk_button_new_with_label("Reload Configuration"); - errdefer c.g_object_unref(reload_button); - - // This hooks up all our widgets to the window so they can be laid out - // using the constraint-based layout. - c.gtk_widget_set_parent(label, box); - c.gtk_widget_set_name(label, "label"); - c.gtk_widget_set_parent(text, box); - c.gtk_widget_set_name(text, "text"); - c.gtk_widget_set_parent(ignore_button, box); - c.gtk_widget_set_name(ignore_button, "ignorebutton"); - c.gtk_widget_set_parent(reload_button, box); - c.gtk_widget_set_name(reload_button, "reloadbutton"); - - var gerr: ?*c.GError = null; - const list = c.gtk_constraint_layout_add_constraints_from_description( - @ptrCast(layout), - &vfl, - vfl.len, - 8, - 8, - &gerr, - "label", - label, - "text", - text, - "ignorebutton", - ignore_button, - "reloadbutton", - reload_button, - @as(?*anyopaque, null), - ); - if (gerr) |err| { - defer c.g_error_free(err); - log.warn("error building window message={s}", .{err.message}); - return error.OperationFailed; - } - c.g_list_free(list); + // Create our view + const view = try View.init(&.{ + .{ .name = "label", .widget = label }, + .{ .name = "text", .widget = text }, + .{ .name = "buttons", .widget = buttons.view.root }, + }, &rootvfl); + 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); @@ -121,13 +76,11 @@ fn init(self: *ConfigErrors, app: *App) !void { _ = c.g_signal_connect_data(window, "destroy", c.G_CALLBACK(>kDestroy), self, null, c.G_CONNECT_DEFAULT); // Show the window + c.gtk_window_set_child(@ptrCast(window), view.root); c.gtk_widget_show(window); // Set some state - self.* = .{ - .app = app, - .layout = @ptrCast(layout), - }; + self.* = .{ .app = app }; } fn gtkDestroy(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void { @@ -152,12 +105,41 @@ fn contentsBuffer(config: *const Config) !*c.GtkTextBuffer { return buf; } -const vfl = [_][*:0]const u8{ +const ButtonsView = struct { + view: View, + + pub fn init(root: *ConfigErrors) !ButtonsView { + // Todo for events + _ = root; + + 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); + errdefer view.deinit(); + + return .{ .view = view }; + } + + pub fn deinit(self: *ButtonsView) void { + self.view.deinit(); + } + + const vfl = [_][*:0]const u8{ + "H:[ignore]-8-[reload]-8-|", + }; +}; + +const rootvfl = [_][*:0]const u8{ "H:|-8-[label]-8-|", "H:|[text]|", - "H:[ignorebutton]-8-[reloadbutton]-8-|", - "V:|[label(<=100)][text(>=100)]-[ignorebutton]-|", - "V:|[label(<=100)][text(>=100)]-[reloadbutton]-|", - "V:[label][text]-[ignorebutton]", - "V:[label][text]-[reloadbutton]", + "H:|[buttons]|", + "V:|[label(<=100)][text(>=100)]-[buttons]-|", }; diff --git a/src/apprt/gtk/View.zig b/src/apprt/gtk/View.zig new file mode 100644 index 000000000..f90ed0d16 --- /dev/null +++ b/src/apprt/gtk/View.zig @@ -0,0 +1,77 @@ +/// View helps with creating a view with a constraint layout by +/// managing all the boilerplate. The caller is responsible for +/// providing the widgets, their names, and the VFL code and gets +/// a root box as a result ready to be used. +const View = @This(); + +const std = @import("std"); +const c = @import("c.zig"); + +const log = std.log.scoped(.gtk); + +/// The box that contains all of the widgets. +root: *c.GtkWidget, + +/// A single widget used in the view. +pub const Widget = struct { + /// The name of the widget used for the layout code. This is also + /// the name set for the widget for CSS styling. + name: [:0]const u8, + + /// The widget itself. + widget: *c.GtkWidget, +}; + +/// Initialize a new constraint layout view with the given widgets +/// and VFL. +pub fn init(widgets: []const Widget, vfl: []const [*:0]const u8) !View { + // Box to store all our widgets + const box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0); + errdefer c.g_object_unref(box); + c.gtk_widget_set_vexpand(box, 1); + c.gtk_widget_set_hexpand(box, 1); + + // Setup our constraint layout and attach it to the box + const layout = c.gtk_constraint_layout_new(); + errdefer c.g_object_unref(layout); + c.gtk_widget_set_layout_manager(@ptrCast(box), layout); + + // Setup our views table + const views = c.g_hash_table_new(c.g_str_hash, c.g_str_equal); + defer c.g_hash_table_unref(views); + + // Add our widgets + for (widgets) |widget| { + c.gtk_widget_set_parent(widget.widget, box); + c.gtk_widget_set_name(widget.widget, widget.name); + _ = c.g_hash_table_insert( + views, + @constCast(@ptrCast(widget.name.ptr)), + widget.widget, + ); + } + + // Add all of our constraints for layout + var err_: ?*c.GError = null; + const list = c.gtk_constraint_layout_add_constraints_from_descriptionv( + @ptrCast(layout), + vfl.ptr, + vfl.len, + 8, + 8, + views, + &err_, + ); + if (err_) |err| { + defer c.g_error_free(err); + log.warn("error building view message={s}", .{err.message}); + return error.OperationFailed; + } + c.g_list_free(list); + + return .{ .root = box }; +} + +pub fn deinit(self: *View) void { + _ = self; +} From 40315afe72c23dbd89040b18d1f86073acd12eed Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 17 Sep 2023 22:03:43 -0700 Subject: [PATCH 3/7] apprt/gtk: extract config errors window into views --- src/apprt/gtk/ConfigErrorsWindow.zig | 119 +++++++++++++-------------- src/apprt/gtk/View.zig | 4 - 2 files changed, 59 insertions(+), 64 deletions(-) diff --git a/src/apprt/gtk/ConfigErrorsWindow.zig b/src/apprt/gtk/ConfigErrorsWindow.zig index 92a02a817..4a6440a8c 100644 --- a/src/apprt/gtk/ConfigErrorsWindow.zig +++ b/src/apprt/gtk/ConfigErrorsWindow.zig @@ -33,54 +33,21 @@ fn destroy(self: *ConfigErrors) void { } fn init(self: *ConfigErrors, app: *App) !void { + // Set some state + self.* = .{ .app = app }; + // Create the window const window = c.gtk_application_window_new(app.app); 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, 300); - - // 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 = try contentsBuffer(&app.config); - defer c.g_object_unref(buf); - const text = c.gtk_text_view_new_with_buffer(buf); - errdefer c.g_object_unref(text); - - var buttons = try ButtonsView.init(self); - errdefer buttons.deinit(); - - // Create our view - const view = try View.init(&.{ - .{ .name = "label", .widget = label }, - .{ .name = "text", .widget = text }, - .{ .name = "buttons", .widget = buttons.view.root }, - }, &rootvfl); - 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); - - // Signals _ = c.g_signal_connect_data(window, "destroy", c.G_CALLBACK(>kDestroy), self, null, c.G_CONNECT_DEFAULT); // Show the window + const view = try PrimaryView.init(self); c.gtk_window_set_child(@ptrCast(window), view.root); c.gtk_widget_show(window); - - // Set some state - self.* = .{ .app = app }; } fn gtkDestroy(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void { @@ -92,21 +59,65 @@ fn userdataSelf(ud: *anyopaque) *ConfigErrors { return @ptrCast(@alignCast(ud)); } -/// 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); +const PrimaryView = struct { + root: *c.GtkWidget, - for (config._errors.list.items) |err| { - c.gtk_text_buffer_insert_at_cursor(buf, err.message, @intCast(err.message.len)); - c.gtk_text_buffer_insert_at_cursor(buf, "\n", -1); + 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 = try contentsBuffer(&root.app.config); + defer c.g_object_unref(buf); + const text = c.gtk_text_view_new_with_buffer(buf); + errdefer c.g_object_unref(text); + const buttons = try ButtonsView.init(root); + + // Create our view + const view = try View.init(&.{ + .{ .name = "label", .widget = label }, + .{ .name = "text", .widget = text }, + .{ .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 }; } - return 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); + + for (config._errors.list.items) |err| { + c.gtk_text_buffer_insert_at_cursor(buf, err.message, @intCast(err.message.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(<=100)][text(>=100)]-[buttons]-|", + }; +}; const ButtonsView = struct { - view: View, + root: *c.GtkWidget, pub fn init(root: *ConfigErrors) !ButtonsView { // Todo for events @@ -123,23 +134,11 @@ const ButtonsView = struct { .{ .name = "ignore", .widget = ignore_button }, .{ .name = "reload", .widget = reload_button }, }, &vfl); - errdefer view.deinit(); - return .{ .view = view }; - } - - pub fn deinit(self: *ButtonsView) void { - self.view.deinit(); + return .{ .root = view.root }; } const vfl = [_][*:0]const u8{ "H:[ignore]-8-[reload]-8-|", }; }; - -const rootvfl = [_][*:0]const u8{ - "H:|-8-[label]-8-|", - "H:|[text]|", - "H:|[buttons]|", - "V:|[label(<=100)][text(>=100)]-[buttons]-|", -}; diff --git a/src/apprt/gtk/View.zig b/src/apprt/gtk/View.zig index f90ed0d16..2287b4737 100644 --- a/src/apprt/gtk/View.zig +++ b/src/apprt/gtk/View.zig @@ -71,7 +71,3 @@ pub fn init(widgets: []const Widget, vfl: []const [*:0]const u8) !View { return .{ .root = box }; } - -pub fn deinit(self: *View) void { - _ = self; -} From f82fd5693321ec58962e95deda5b297ccb1e690b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 17 Sep 2023 22:09:44 -0700 Subject: [PATCH 4/7] apprt/gtk: hook up buttons --- src/apprt/gtk/ConfigErrorsWindow.zig | 48 +++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/src/apprt/gtk/ConfigErrorsWindow.zig b/src/apprt/gtk/ConfigErrorsWindow.zig index 4a6440a8c..b7c659d46 100644 --- a/src/apprt/gtk/ConfigErrorsWindow.zig +++ b/src/apprt/gtk/ConfigErrorsWindow.zig @@ -13,6 +13,7 @@ const c = @import("c.zig"); const log = std.log.scoped(.gtk); app: *App, +window: *c.GtkWindow, pub fn create(app: *App) !void { if (app.config_errors_window != null) return error.InvalidOperation; @@ -33,17 +34,20 @@ fn destroy(self: *ConfigErrors) void { } fn init(self: *ConfigErrors, app: *App) !void { - // Set some state - self.* = .{ .app = app }; - // Create the window const window = c.gtk_application_window_new(app.app); 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, 300); + c.gtk_window_set_default_size(gtk_window, 600, 275); _ = 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, + }; + // Show the window const view = try PrimaryView.init(self); c.gtk_window_set_child(@ptrCast(window), view.root); @@ -112,7 +116,7 @@ const PrimaryView = struct { "H:|-8-[label]-8-|", "H:|[text]|", "H:|[buttons]|", - "V:|[label(<=100)][text(>=100)]-[buttons]-|", + "V:|[label(<=80)][text(>=100)]-[buttons]-|", }; }; @@ -120,9 +124,6 @@ const ButtonsView = struct { root: *c.GtkWidget, pub fn init(root: *ConfigErrors) !ButtonsView { - // Todo for events - _ = root; - const ignore_button = c.gtk_button_new_with_label("Ignore"); errdefer c.g_object_unref(ignore_button); @@ -135,9 +136,40 @@ const ButtonsView = struct { .{ .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() catch |err| { + log.warn("error reloading config error={}", .{err}); + return; + }; + } + const vfl = [_][*:0]const u8{ "H:[ignore]-8-[reload]-8-|", }; From 92465f57ea73faf0a6327451845909f13428b8da Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 17 Sep 2023 22:28:58 -0700 Subject: [PATCH 5/7] apprt/gtk: grab focus of config window when reloading --- src/apprt/gtk/App.zig | 3 +++ src/apprt/gtk/ConfigErrorsWindow.zig | 13 +++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index f2377248e..8d02c9185 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -178,6 +178,9 @@ fn updateConfigErrors(self: *App) !void { try ConfigErrorsWindow.create(self); assert(self.config_errors_window != null); } + + const window = self.config_errors_window.?; + window.update(); } } diff --git a/src/apprt/gtk/ConfigErrorsWindow.zig b/src/apprt/gtk/ConfigErrorsWindow.zig index b7c659d46..c0cee5b08 100644 --- a/src/apprt/gtk/ConfigErrorsWindow.zig +++ b/src/apprt/gtk/ConfigErrorsWindow.zig @@ -26,6 +26,11 @@ pub fn create(app: *App) !void { app.config_errors_window = self; } +pub fn update(self: *ConfigErrors) void { + _ = 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; @@ -40,6 +45,7 @@ fn init(self: *ConfigErrors, app: *App) !void { 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.g_signal_connect_data(window, "destroy", c.G_CALLBACK(>kDestroy), self, null, c.G_CONNECT_DEFAULT); // Set some state @@ -75,14 +81,17 @@ const PrimaryView = struct { ); const buf = try 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); - const buttons = try ButtonsView.init(root); + 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 }, + .{ .name = "text", .widget = text_scroll }, .{ .name = "buttons", .widget = buttons.root }, }, &vfl); errdefer view.deinit(); From 80e02c6c9fe8142373e2b1f40f910faf54b4e310 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 17 Sep 2023 22:31:52 -0700 Subject: [PATCH 6/7] apprt/gtk: update config error contents on reload --- src/apprt/gtk/ConfigErrorsWindow.zig | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/apprt/gtk/ConfigErrorsWindow.zig b/src/apprt/gtk/ConfigErrorsWindow.zig index c0cee5b08..7a113ff07 100644 --- a/src/apprt/gtk/ConfigErrorsWindow.zig +++ b/src/apprt/gtk/ConfigErrorsWindow.zig @@ -14,6 +14,7 @@ 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; @@ -27,6 +28,7 @@ pub fn create(app: *App) !void { } pub fn update(self: *ConfigErrors) void { + self.view.update(&self.app.config); _ = c.gtk_window_present(self.window); _ = c.gtk_widget_grab_focus(@ptrCast(self.window)); } @@ -52,10 +54,12 @@ fn init(self: *ConfigErrors, app: *App) !void { 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); } @@ -71,6 +75,7 @@ fn userdataSelf(ud: *anyopaque) *ConfigErrors { const PrimaryView = struct { root: *c.GtkWidget, + text: *c.GtkTextView, pub fn init(root: *ConfigErrors) !PrimaryView { // All our widgets @@ -79,7 +84,7 @@ const PrimaryView = struct { "the configuration. Please review the errors below and reload " ++ "your configuration or ignore the erroneous lines.", ); - const buf = try contentsBuffer(&root.app.config); + 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(); @@ -105,11 +110,17 @@ const PrimaryView = struct { c.gtk_text_view_set_left_margin(@ptrCast(text), 8); c.gtk_text_view_set_right_margin(@ptrCast(text), 8); - return .{ .root = view.root }; + 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 { + fn contentsBuffer(config: *const Config) *c.GtkTextBuffer { const buf = c.gtk_text_buffer_new(null); errdefer c.g_object_unref(buf); From 6df01b87c1d1591571aa60e21d7cd2d2ed2b28d8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 17 Sep 2023 22:34:34 -0700 Subject: [PATCH 7/7] apprt/gtk: if errors fixed, hide window --- src/apprt/gtk/App.zig | 3 ++- src/apprt/gtk/ConfigErrorsWindow.zig | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 8d02c9185..2505810ef 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -178,8 +178,9 @@ fn updateConfigErrors(self: *App) !void { try ConfigErrorsWindow.create(self); assert(self.config_errors_window != null); } + } - const window = self.config_errors_window.?; + if (self.config_errors_window) |window| { window.update(); } } diff --git a/src/apprt/gtk/ConfigErrorsWindow.zig b/src/apprt/gtk/ConfigErrorsWindow.zig index 7a113ff07..f582c459a 100644 --- a/src/apprt/gtk/ConfigErrorsWindow.zig +++ b/src/apprt/gtk/ConfigErrorsWindow.zig @@ -28,6 +28,11 @@ pub fn create(app: *App) !void { } pub fn update(self: *ConfigErrors) void { + if (self.app.config._errors.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));