diff --git a/src/grex.cpp b/src/grex.cpp index dbc6904..5f3922e 100644 --- a/src/grex.cpp +++ b/src/grex.cpp @@ -19,10 +19,23 @@ #include #include #include +#include #include "models/project.h" #include "models/grex_config.h" #include "views/main_window.h" +// Suppress known GTK4 bug: GtkDropDown's internal arrow GtkImage reports +// INT_MIN baselines instead of -1, triggering a spurious warning. +static GLogWriterOutput grex_log_writer(GLogLevelFlags level, const GLogField* fields, + gsize n_fields, gpointer) { + for (gsize i = 0; i < n_fields; i++) { + if (std::strcmp(fields[i].key, "MESSAGE") == 0 && + std::strstr(static_cast(fields[i].value), "reported baselines") != nullptr) + return G_LOG_WRITER_HANDLED; + } + return g_log_writer_default(level, fields, n_fields, nullptr); +} + static grex::Project* g_project = nullptr; static grex::GrexConfig* g_grex_config = nullptr; static grex::MainWindow* g_main_window = nullptr; @@ -65,6 +78,7 @@ static void on_activate(GtkApplication* app, gpointer) { } int main(int argc, char* argv[]) { + g_log_set_writer_func(grex_log_writer, nullptr, nullptr); auto* app = gtk_application_new("org.darkhorselinux.grex", G_APPLICATION_HANDLES_COMMAND_LINE); GOptionEntry entries[] = { diff --git a/src/util/unit_properties_dialog.cpp b/src/util/unit_properties_dialog.cpp index 413c095..ca4a311 100644 --- a/src/util/unit_properties_dialog.cpp +++ b/src/util/unit_properties_dialog.cpp @@ -96,6 +96,11 @@ struct DialogState { static void update_sensitivity(DialogState* s); static void validate_fields(DialogState* s); +static std::string extract_path(const std::string& s) { + auto pos = s.find(' '); + return pos == std::string::npos ? s : s.substr(0, pos); +} + static gboolean dlg_switch_state_set_cb(GtkSwitch* sw, gboolean new_state, gpointer data) { auto* b = static_cast(data); gtk_switch_set_state(sw, new_state); @@ -277,7 +282,7 @@ static GtkWidget* make_file_row(DialogState* s, GtkWidget* grid, int row, const return; } namespace fs = std::filesystem; - fs::path p(raw); + fs::path p(extract_path(raw)); if (p.is_relative()) { auto root = fbd->state->project->resolved_project_root(); if (!root.empty()) @@ -365,11 +370,6 @@ static void update_sensitivity(DialogState* s) { show(active && s->working_copy.supply_environment, {s->label_environment, s->box_environment}); } -static std::string extract_path(const std::string& s) { - auto pos = s.find(' '); - return pos == std::string::npos ? s : s.substr(0, pos); -} - static void validate_fields(DialogState* s) { if (s->loading) return; diff --git a/src/views/config_view.cpp b/src/views/config_view.cpp index fa848fe..f32ea1b 100644 --- a/src/views/config_view.cpp +++ b/src/views/config_view.cpp @@ -19,8 +19,12 @@ #include "views/config_view.h" #include "util/unsaved_dialog.h" #include "util/json_helpers.h" +#include +#include #include +#include #include +#include namespace grex { @@ -123,6 +127,124 @@ ConfigView::ConfigView(Project& project) : project_(project) { gtk_grid_set_column_spacing(GTK_GRID(vars_grid_), 16); gtk_box_append(GTK_BOX(vars_box), vars_grid_); + // --- Current Working Directory subsection --- + gtk_box_append(GTK_BOX(vars_box), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)); + + auto* cwd_label = gtk_label_new(nullptr); + gtk_label_set_markup(GTK_LABEL(cwd_label), "Current Working Directory"); + gtk_label_set_xalign(GTK_LABEL(cwd_label), 0.0f); + gtk_box_append(GTK_BOX(vars_box), cwd_label); + + auto* cwd_desc = gtk_label_new("Set the working directory for this session. Relative paths in unit validation resolve against this."); + gtk_label_set_xalign(GTK_LABEL(cwd_desc), 0.0f); + gtk_label_set_wrap(GTK_LABEL(cwd_desc), TRUE); + gtk_widget_add_css_class(cwd_desc, "dim-label"); + gtk_box_append(GTK_BOX(vars_box), cwd_desc); + + auto* cwd_grid = gtk_grid_new(); + gtk_grid_set_row_spacing(GTK_GRID(cwd_grid), 8); + gtk_grid_set_column_spacing(GTK_GRID(cwd_grid), 12); + + auto* cwd_key = gtk_label_new("Directory"); + gtk_label_set_xalign(GTK_LABEL(cwd_key), 1.0f); + gtk_widget_set_size_request(cwd_key, 140, -1); + gtk_widget_add_css_class(cwd_key, "dim-label"); + gtk_grid_attach(GTK_GRID(cwd_grid), cwd_key, 0, 0, 1, 1); + + auto* cwd_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); + gtk_widget_set_hexpand(cwd_hbox, TRUE); + + char cwd_buf[PATH_MAX]; + const char* initial_cwd = getcwd(cwd_buf, sizeof(cwd_buf)); + cwd_entry_ = gtk_entry_new(); + gtk_widget_set_hexpand(cwd_entry_, TRUE); + gtk_editable_set_text(GTK_EDITABLE(cwd_entry_), initial_cwd ? initial_cwd : ""); + gtk_box_append(GTK_BOX(cwd_hbox), cwd_entry_); + + auto* cwd_browse = gtk_button_new_with_label("Browse..."); + gtk_box_append(GTK_BOX(cwd_hbox), cwd_browse); + + auto* cwd_apply = gtk_button_new_with_label("Apply"); + gtk_box_append(GTK_BOX(cwd_hbox), cwd_apply); + + gtk_grid_attach(GTK_GRID(cwd_grid), cwd_hbox, 1, 0, 1, 1); + + cwd_status_ = gtk_label_new(nullptr); + gtk_label_set_xalign(GTK_LABEL(cwd_status_), 0.0f); + gtk_widget_add_css_class(cwd_status_, "dim-label"); + gtk_grid_attach(GTK_GRID(cwd_grid), cwd_status_, 1, 1, 1, 1); + + gtk_box_append(GTK_BOX(vars_box), cwd_grid); + + // Apply button — chdir and report + g_signal_connect(cwd_apply, "clicked", G_CALLBACK(+[](GtkButton*, gpointer d) { + auto* self = static_cast(d); + auto dir = std::string(gtk_editable_get_text(GTK_EDITABLE(self->cwd_entry_))); + if (dir.empty()) { + self->project_.report_status("Error: working directory is empty"); + return; + } + if (chdir(dir.c_str()) == 0) { + char buf[PATH_MAX]; + const char* actual = getcwd(buf, sizeof(buf)); + gtk_editable_set_text(GTK_EDITABLE(self->cwd_entry_), actual ? actual : dir.c_str()); + gtk_label_set_text(GTK_LABEL(self->cwd_status_), ""); + self->project_.report_status("Working directory set to: " + std::string(actual ? actual : dir.c_str())); + self->update_resolved_labels(); + } else { + auto msg = "Error: cannot chdir to '" + dir + "': " + std::string(strerror(errno)); + self->project_.report_status(msg); + gtk_label_set_markup(GTK_LABEL(self->cwd_status_), + ("" + msg + "").c_str()); + } + }), this); + + // Browse button — folder chooser + g_signal_connect(cwd_browse, "clicked", G_CALLBACK(+[](GtkButton*, gpointer d) { + auto* self = static_cast(d); + auto* window = GTK_WINDOW(gtk_widget_get_ancestor(self->root_, GTK_TYPE_WINDOW)); + auto* dialog = gtk_file_dialog_new(); + gtk_file_dialog_set_title(dialog, "Select Working Directory"); + + struct CwdBrowseCtx { ConfigView* view; }; + auto* ctx = new CwdBrowseCtx{self}; + gtk_file_dialog_select_folder(dialog, window, nullptr, + +[](GObject* source, GAsyncResult* res, gpointer data) { + auto* ctx = static_cast(data); + GError* error = nullptr; + auto* file = gtk_file_dialog_select_folder_finish(GTK_FILE_DIALOG(source), res, &error); + if (file) { + auto* path = g_file_get_path(file); + gtk_editable_set_text(GTK_EDITABLE(ctx->view->cwd_entry_), path); + g_free(path); + g_object_unref(file); + } else if (error) { + g_error_free(error); + } + delete ctx; + }, ctx); + }), this); + + // Enter in entry triggers apply + g_signal_connect(cwd_entry_, "activate", G_CALLBACK(+[](GtkEntry*, gpointer d) { + auto* self = static_cast(d); + auto dir = std::string(gtk_editable_get_text(GTK_EDITABLE(self->cwd_entry_))); + if (dir.empty()) return; + if (chdir(dir.c_str()) == 0) { + char buf[PATH_MAX]; + const char* actual = getcwd(buf, sizeof(buf)); + gtk_editable_set_text(GTK_EDITABLE(self->cwd_entry_), actual ? actual : dir.c_str()); + gtk_label_set_text(GTK_LABEL(self->cwd_status_), ""); + self->project_.report_status("Working directory set to: " + std::string(actual ? actual : dir.c_str())); + self->update_resolved_labels(); + } else { + auto msg = "Error: cannot chdir to '" + dir + "': " + std::string(strerror(errno)); + self->project_.report_status(msg); + gtk_label_set_markup(GTK_LABEL(self->cwd_status_), + ("" + msg + "").c_str()); + } + }), this); + gtk_frame_set_child(GTK_FRAME(vars_frame), vars_box); gtk_box_append(GTK_BOX(config_content_), vars_frame); @@ -297,14 +419,12 @@ void ConfigView::update_resolved_labels() { // Status indicator bool path_ok = (display != "(unresolved)") && (std::filesystem::exists(display) || std::filesystem::is_directory(display)); - auto* indicator = gtk_label_new(nullptr); + auto* indicator = gtk_label_new(path_ok ? "OK" : "ERR"); gtk_label_set_xalign(GTK_LABEL(indicator), 0.5f); - if (display == "(unresolved)") - gtk_label_set_markup(GTK_LABEL(indicator), "\u2718"); - else if (path_ok) - gtk_label_set_markup(GTK_LABEL(indicator), "\u2714"); + if (path_ok) + gtk_widget_add_css_class(indicator, "success"); else - gtk_label_set_markup(GTK_LABEL(indicator), "\u2718"); + gtk_widget_add_css_class(indicator, "error"); gtk_grid_attach(GTK_GRID(resolved_grid_), indicator, 1, row, 1, 1); auto* val_label = gtk_label_new(nullptr); diff --git a/src/views/config_view.h b/src/views/config_view.h index 823be05..4d35a25 100644 --- a/src/views/config_view.h +++ b/src/views/config_view.h @@ -19,6 +19,7 @@ #pragma once #include #include +#include #include "models/project.h" namespace grex { @@ -58,6 +59,8 @@ private: GtkWidget* btn_close_; GtkWidget* btn_save_; GtkWidget* config_content_; + GtkWidget* cwd_entry_; + GtkWidget* cwd_status_; void build_config_fields(); void build_variables_section();