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; +}