From 85ad80988763977da62360e06b60ddbd085999b6 Mon Sep 17 00:00:00 2001 From: Chris Punches Date: Sat, 14 Mar 2026 17:58:36 -0400 Subject: [PATCH] Fix file selection alignment bug, add refresh buttons, polish UI Replace fragile index-based file selection with direct pointer binding via g_object_set_data on listbox rows in both UnitsView and ShellsView. Add Refresh buttons to all three tabs (Units, Shells, Plans). Add status bar notifications for shell and plan file loads. Wrap control panels in labeled GtkFrame containers across all tabs. Improve config tab layout with better typography, spacing, width constraint, and resolved path status indicators. --- src/models/project.cpp | 19 ++++- src/views/config_view.cpp | 148 ++++++++++++++++++++--------------- src/views/plan_view.cpp | 32 ++++++-- src/views/shells_view.cpp | 120 ++++++++++++++++++----------- src/views/shells_view.h | 3 +- src/views/units_view.cpp | 158 ++++++++++++++++++++++---------------- src/views/units_view.h | 3 +- 7 files changed, 300 insertions(+), 183 deletions(-) diff --git a/src/models/project.cpp b/src/models/project.cpp index d298e3d..c7a9e50 100644 --- a/src/models/project.cpp +++ b/src/models/project.cpp @@ -166,19 +166,32 @@ void Project::load_plan(const fs::path& plan_path) { void Project::reload_shells() { auto sp = resolved_shells_path(); - if (sp.empty()) return; - if (!fs::is_directory(sp)) return; + if (sp.empty()) { + report_status("Error: shells path not resolved"); + return; + } + if (!fs::is_directory(sp)) { + report_status("Error: shells path is not a directory: " + sp.string()); + return; + } shell_files.clear(); + int file_count = 0; + int total_shells = 0; for (auto& entry : fs::directory_iterator(sp)) { if (entry.path().extension() == ".shells") { try { - shell_files.push_back(ShellsFile::load(entry.path())); + auto sf = ShellsFile::load(entry.path()); + total_shells += (int)sf.shells.size(); + file_count++; + shell_files.push_back(std::move(sf)); } catch (const std::exception& e) { report_status("Error loading shells: " + std::string(e.what())); } } } + report_status("Loaded " + std::to_string(total_shells) + " shells from " + + std::to_string(file_count) + " files at '" + sp.string() + "'"); } std::vector Project::all_shells() const { diff --git a/src/views/config_view.cpp b/src/views/config_view.cpp index 285c65d..fa848fe 100644 --- a/src/views/config_view.cpp +++ b/src/views/config_view.cpp @@ -39,15 +39,23 @@ ConfigView::ConfigView(Project& project) : project_(project) { root_ = gtk_scrolled_window_new(); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(root_), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); - auto* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12); - gtk_widget_set_margin_start(box, 16); - gtk_widget_set_margin_end(box, 16); - gtk_widget_set_margin_top(box, 16); - gtk_widget_set_margin_bottom(box, 16); + // Outer centering wrapper — keeps form from stretching on wide screens + auto* outer = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_set_vexpand(outer, TRUE); - // === Config file label + Open/Close buttons === + auto* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 16); + gtk_widget_set_margin_start(box, 48); + gtk_widget_set_margin_end(box, 48); + gtk_widget_set_margin_top(box, 24); + gtk_widget_set_margin_bottom(box, 24); + gtk_widget_set_hexpand(box, TRUE); + gtk_widget_set_size_request(box, -1, -1); + + // === Config file header === config_label_ = gtk_label_new(nullptr); gtk_label_set_xalign(GTK_LABEL(config_label_), 0.0f); + gtk_label_set_selectable(GTK_LABEL(config_label_), TRUE); + gtk_widget_set_margin_bottom(config_label_, 4); gtk_box_append(GTK_BOX(box), config_label_); auto* btn_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); @@ -68,63 +76,61 @@ ConfigView::ConfigView(Project& project) : project_(project) { g_signal_connect(btn_close_, "clicked", G_CALLBACK(on_close_config), this); // === Config content — greyed out when no config loaded === - config_content_ = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12); - - gtk_box_append(GTK_BOX(config_content_), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)); - - // === Config fields section === - auto* config_header = gtk_label_new(nullptr); - gtk_label_set_markup(GTK_LABEL(config_header), "Configuration"); - gtk_label_set_xalign(GTK_LABEL(config_header), 0.0f); - gtk_box_append(GTK_BOX(config_content_), config_header); + config_content_ = gtk_box_new(GTK_ORIENTATION_VERTICAL, 16); + // === Configuration fields === + auto* config_frame = gtk_frame_new("Configuration"); config_grid_ = gtk_grid_new(); - gtk_grid_set_row_spacing(GTK_GRID(config_grid_), 8); - gtk_grid_set_column_spacing(GTK_GRID(config_grid_), 12); - gtk_box_append(GTK_BOX(config_content_), config_grid_); + gtk_grid_set_row_spacing(GTK_GRID(config_grid_), 10); + gtk_grid_set_column_spacing(GTK_GRID(config_grid_), 16); + gtk_widget_set_margin_start(config_grid_, 12); + gtk_widget_set_margin_end(config_grid_, 12); + gtk_widget_set_margin_top(config_grid_, 12); + gtk_widget_set_margin_bottom(config_grid_, 12); + gtk_frame_set_child(GTK_FRAME(config_frame), config_grid_); + gtk_box_append(GTK_BOX(config_content_), config_frame); build_config_fields(); - // === Resolved paths section === - gtk_box_append(GTK_BOX(config_content_), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)); - - auto* resolved_header = gtk_label_new(nullptr); - gtk_label_set_markup(GTK_LABEL(resolved_header), "Resolved Paths"); - gtk_label_set_xalign(GTK_LABEL(resolved_header), 0.0f); - gtk_box_append(GTK_BOX(config_content_), resolved_header); - + // === Resolved Paths === + auto* resolved_frame = gtk_frame_new("Resolved Paths"); resolved_grid_ = gtk_grid_new(); - gtk_grid_set_row_spacing(GTK_GRID(resolved_grid_), 4); - gtk_grid_set_column_spacing(GTK_GRID(resolved_grid_), 12); - gtk_box_append(GTK_BOX(config_content_), resolved_grid_); + gtk_grid_set_row_spacing(GTK_GRID(resolved_grid_), 6); + gtk_grid_set_column_spacing(GTK_GRID(resolved_grid_), 16); + gtk_widget_set_margin_start(resolved_grid_, 12); + gtk_widget_set_margin_end(resolved_grid_, 12); + gtk_widget_set_margin_top(resolved_grid_, 12); + gtk_widget_set_margin_bottom(resolved_grid_, 12); + gtk_frame_set_child(GTK_FRAME(resolved_frame), resolved_grid_); + gtk_box_append(GTK_BOX(config_content_), resolved_frame); - // === Variables section === - gtk_box_append(GTK_BOX(config_content_), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)); - - auto* vars_header = gtk_label_new(nullptr); - gtk_label_set_markup(GTK_LABEL(vars_header), "Variables"); - gtk_label_set_xalign(GTK_LABEL(vars_header), 0.0f); - gtk_box_append(GTK_BOX(config_content_), vars_header); + // === Variables === + auto* vars_frame = gtk_frame_new("Variables"); + auto* vars_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); + gtk_widget_set_margin_start(vars_box, 12); + gtk_widget_set_margin_end(vars_box, 12); + gtk_widget_set_margin_top(vars_box, 12); + gtk_widget_set_margin_bottom(vars_box, 12); auto* vars_desc = gtk_label_new("Set values for variables found in config fields. Environment variables are used automatically."); gtk_label_set_xalign(GTK_LABEL(vars_desc), 0.0f); gtk_label_set_wrap(GTK_LABEL(vars_desc), TRUE); gtk_widget_add_css_class(vars_desc, "dim-label"); - gtk_box_append(GTK_BOX(config_content_), vars_desc); + gtk_box_append(GTK_BOX(vars_box), vars_desc); vars_grid_ = gtk_grid_new(); - gtk_grid_set_row_spacing(GTK_GRID(vars_grid_), 8); - gtk_grid_set_column_spacing(GTK_GRID(vars_grid_), 12); - gtk_box_append(GTK_BOX(config_content_), vars_grid_); + gtk_grid_set_row_spacing(GTK_GRID(vars_grid_), 10); + gtk_grid_set_column_spacing(GTK_GRID(vars_grid_), 16); + gtk_box_append(GTK_BOX(vars_box), vars_grid_); + + gtk_frame_set_child(GTK_FRAME(vars_frame), vars_box); + gtk_box_append(GTK_BOX(config_content_), vars_frame); build_variables_section(); update_resolved_labels(); - // === Save button === - gtk_box_append(GTK_BOX(config_content_), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)); - + // === Save button — full width for presence === btn_save_ = gtk_button_new_with_label("Save Config"); - gtk_widget_set_halign(btn_save_, GTK_ALIGN_END); gtk_box_append(GTK_BOX(config_content_), btn_save_); g_signal_connect(btn_save_, "clicked", G_CALLBACK(+[](GtkButton*, gpointer d) { @@ -132,8 +138,9 @@ ConfigView::ConfigView(Project& project) : project_(project) { }), this); gtk_box_append(GTK_BOX(box), config_content_); + gtk_box_append(GTK_BOX(outer), box); - gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(root_), box); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(root_), outer); update_config_buttons(); } @@ -153,6 +160,8 @@ void ConfigView::build_config_fields() { for (auto& [key, val] : project_.config.data().items()) { auto* label = gtk_label_new(key.c_str()); gtk_label_set_xalign(GTK_LABEL(label), 1.0f); + gtk_widget_set_size_request(label, 140, -1); + gtk_widget_add_css_class(label, "dim-label"); gtk_grid_attach(GTK_GRID(config_grid_), label, 0, row, 1, 1); auto* entry = gtk_entry_new(); @@ -209,6 +218,8 @@ void ConfigView::build_variables_section() { auto var_label = "${" + name + "}"; auto* lbl = gtk_label_new(var_label.c_str()); gtk_label_set_xalign(GTK_LABEL(lbl), 1.0f); + gtk_widget_set_size_request(lbl, 140, -1); + gtk_widget_add_css_class(lbl, "dim-label"); gtk_grid_attach(GTK_GRID(vars_grid_), lbl, 0, row, 1, 1); auto* entry = gtk_entry_new(); @@ -277,9 +288,25 @@ void ConfigView::update_resolved_labels() { display = resolved; } - auto* lbl = gtk_label_new(key.c_str()); - gtk_label_set_xalign(GTK_LABEL(lbl), 1.0f); - gtk_grid_attach(GTK_GRID(resolved_grid_), lbl, 0, row, 1, 1); + auto* key_lbl = gtk_label_new(key.c_str()); + gtk_label_set_xalign(GTK_LABEL(key_lbl), 1.0f); + gtk_widget_set_size_request(key_lbl, 140, -1); + gtk_widget_add_css_class(key_lbl, "dim-label"); + gtk_grid_attach(GTK_GRID(resolved_grid_), key_lbl, 0, row, 1, 1); + + // Status indicator + bool path_ok = (display != "(unresolved)") && + (std::filesystem::exists(display) || std::filesystem::is_directory(display)); + auto* indicator = gtk_label_new(nullptr); + 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"); + else + gtk_label_set_markup(GTK_LABEL(indicator), "\u2718"); + gtk_grid_attach(GTK_GRID(resolved_grid_), indicator, 1, row, 1, 1); + auto* val_label = gtk_label_new(nullptr); gtk_label_set_xalign(GTK_LABEL(val_label), 0.0f); gtk_label_set_selectable(GTK_LABEL(val_label), TRUE); @@ -287,26 +314,22 @@ void ConfigView::update_resolved_labels() { if (display == "(unresolved)") { gtk_label_set_markup(GTK_LABEL(val_label), - "(unresolved)"); - } else if (std::filesystem::is_directory(display)) { - auto markup = "" + display + ""; - gtk_label_set_markup(GTK_LABEL(val_label), markup.c_str()); - } else if (std::filesystem::exists(display)) { - auto markup = "" + display + ""; - gtk_label_set_markup(GTK_LABEL(val_label), markup.c_str()); + "(unresolved)"); + } else if (path_ok) { + gtk_label_set_text(GTK_LABEL(val_label), display.c_str()); } else { - auto markup = "" + display + ""; + auto markup = "" + display + ""; gtk_label_set_markup(GTK_LABEL(val_label), markup.c_str()); } - gtk_grid_attach(GTK_GRID(resolved_grid_), val_label, 1, row, 1, 1); + gtk_grid_attach(GTK_GRID(resolved_grid_), val_label, 2, row, 1, 1); row++; } if (row == 0) { auto* lbl = gtk_label_new("No resolvable paths in config."); gtk_label_set_xalign(GTK_LABEL(lbl), 0.0f); - gtk_grid_attach(GTK_GRID(resolved_grid_), lbl, 0, 0, 2, 1); + gtk_grid_attach(GTK_GRID(resolved_grid_), lbl, 0, 0, 3, 1); } } @@ -338,10 +361,15 @@ void ConfigView::apply_config() { void ConfigView::update_config_buttons() { bool has_config = !project_.config_path.empty(); if (has_config) { - auto markup = std::string("Current Rex Config: ") + project_.config_path.filename().string(); + auto markup = std::string("Rex Config: ") + + project_.config_path.filename().string() + "\n" + + "" + + project_.config_path.string() + ""; gtk_label_set_markup(GTK_LABEL(config_label_), markup.c_str()); } else { - gtk_label_set_markup(GTK_LABEL(config_label_), "Current Rex Config: No config loaded"); + gtk_label_set_markup(GTK_LABEL(config_label_), + "Rex Config\n" + "No config loaded"); } gtk_widget_set_visible(btn_open_, !has_config); gtk_widget_set_visible(btn_create_, !has_config); diff --git a/src/views/plan_view.cpp b/src/views/plan_view.cpp index 728af8a..fa7e636 100644 --- a/src/views/plan_view.cpp +++ b/src/views/plan_view.cpp @@ -74,7 +74,12 @@ PlanView::PlanView(Project& project, GrexConfig& grex_config) gtk_box_append(GTK_BOX(task_controls_), scroll); // Task action buttons — grouped by function + auto* plan_ctrl_frame = gtk_frame_new("Plan Controls"); auto* btn_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8); + gtk_widget_set_margin_start(btn_box, 8); + gtk_widget_set_margin_end(btn_box, 8); + gtk_widget_set_margin_top(btn_box, 8); + gtk_widget_set_margin_bottom(btn_box, 8); auto* task_edit_group = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_add_css_class(task_edit_group, "linked"); @@ -86,8 +91,8 @@ PlanView::PlanView(Project& project, GrexConfig& grex_config) auto* move_group = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_add_css_class(move_group, "linked"); - auto* btn_up = gtk_button_new_with_label("Up"); - auto* btn_down = gtk_button_new_with_label("Down"); + auto* btn_up = gtk_button_new_with_label("Move Up"); + auto* btn_down = gtk_button_new_with_label("Move Down"); gtk_box_append(GTK_BOX(move_group), btn_up); gtk_box_append(GTK_BOX(move_group), btn_down); gtk_box_append(GTK_BOX(btn_box), move_group); @@ -95,7 +100,11 @@ PlanView::PlanView(Project& project, GrexConfig& grex_config) btn_save_plan_ = gtk_button_new_with_label("Save Plan"); gtk_box_append(GTK_BOX(btn_box), btn_save_plan_); - gtk_box_append(GTK_BOX(task_controls_), btn_box); + auto* btn_refresh = gtk_button_new_with_label("Refresh"); + gtk_box_append(GTK_BOX(btn_box), btn_refresh); + + gtk_frame_set_child(GTK_FRAME(plan_ctrl_frame), btn_box); + gtk_box_append(GTK_BOX(task_controls_), plan_ctrl_frame); gtk_box_append(GTK_BOX(left), task_controls_); gtk_paned_set_start_child(GTK_PANED(root_), left); @@ -136,6 +145,11 @@ PlanView::PlanView(Project& project, GrexConfig& grex_config) } }), this); + g_signal_connect(btn_refresh, "clicked", G_CALLBACK(+[](GtkButton*, gpointer d) { + auto* self = static_cast(d); + self->refresh(); + }), this); + update_plan_buttons(); } @@ -461,11 +475,19 @@ void PlanView::refresh() { // reload units if paths now resolve project_.load_all_units(); + // reload the plan from disk if one is loaded auto* plan = current_plan(); - if (plan) + if (plan) { + try { + *plan = Plan::load(plan->filepath); + project_.report_status("Reloaded plan: " + plan->filepath.filename().string()); + } catch (const std::exception& e) { + project_.report_status("Error reloading plan: " + std::string(e.what())); + } gtk_label_set_markup(GTK_LABEL(plan_label_), (std::string("Plan: ") + plan->filepath.filename().string()).c_str()); - else + } else { gtk_label_set_markup(GTK_LABEL(plan_label_), "Plan: No plan loaded"); + } populate_task_list(); update_plan_buttons(); diff --git a/src/views/shells_view.cpp b/src/views/shells_view.cpp index 2116ad0..e25b066 100644 --- a/src/views/shells_view.cpp +++ b/src/views/shells_view.cpp @@ -17,14 +17,9 @@ */ #include "views/shells_view.h" +#include #include -static int sort_listbox_alpha(GtkListBoxRow* a, GtkListBoxRow* b, gpointer) { - auto* la = GTK_LABEL(gtk_list_box_row_get_child(GTK_LIST_BOX_ROW(a))); - auto* lb = GTK_LABEL(gtk_list_box_row_get_child(GTK_LIST_BOX_ROW(b))); - return std::strcmp(gtk_label_get_text(la), gtk_label_get_text(lb)); -} - namespace grex { ShellsView::ShellsView(Project& project) : project_(project) { @@ -50,11 +45,15 @@ ShellsView::ShellsView(Project& project) : project_(project) { gtk_widget_add_css_class(file_scroll, "frame"); file_listbox_ = gtk_list_box_new(); gtk_list_box_set_selection_mode(GTK_LIST_BOX(file_listbox_), GTK_SELECTION_SINGLE); - gtk_list_box_set_sort_func(GTK_LIST_BOX(file_listbox_), sort_listbox_alpha, nullptr, nullptr); gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(file_scroll), file_listbox_); gtk_box_append(GTK_BOX(left), file_scroll); + auto* file_ctrl_frame = gtk_frame_new("File Controls"); auto* file_btn_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8); + gtk_widget_set_margin_start(file_btn_box, 8); + gtk_widget_set_margin_end(file_btn_box, 8); + gtk_widget_set_margin_top(file_btn_box, 8); + gtk_widget_set_margin_bottom(file_btn_box, 8); auto* file_edit_group = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_add_css_class(file_edit_group, "linked"); @@ -67,7 +66,11 @@ ShellsView::ShellsView(Project& project) : project_(project) { btn_save_ = gtk_button_new_with_label("Save File"); gtk_box_append(GTK_BOX(file_btn_box), btn_save_); - gtk_box_append(GTK_BOX(left), file_btn_box); + auto* btn_refresh = gtk_button_new_with_label("Refresh"); + gtk_box_append(GTK_BOX(file_btn_box), btn_refresh); + + gtk_frame_set_child(GTK_FRAME(file_ctrl_frame), file_btn_box); + gtk_box_append(GTK_BOX(left), file_ctrl_frame); gtk_paned_set_start_child(GTK_PANED(root_), left); gtk_paned_set_shrink_start_child(GTK_PANED(root_), FALSE); @@ -94,7 +97,12 @@ ShellsView::ShellsView(Project& project) : project_(project) { gtk_box_append(GTK_BOX(right), shell_scroll); // Shell action buttons + auto* shell_ctrl_frame = gtk_frame_new("Shell Controls"); auto* shell_btn_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8); + gtk_widget_set_margin_start(shell_btn_box, 8); + gtk_widget_set_margin_end(shell_btn_box, 8); + gtk_widget_set_margin_top(shell_btn_box, 8); + gtk_widget_set_margin_bottom(shell_btn_box, 8); auto* shell_edit_group = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_add_css_class(shell_edit_group, "linked"); @@ -106,13 +114,14 @@ ShellsView::ShellsView(Project& project) : project_(project) { auto* move_group = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_add_css_class(move_group, "linked"); - auto* btn_move_up = gtk_button_new_with_label("Up"); - auto* btn_move_down = gtk_button_new_with_label("Down"); + auto* btn_move_up = gtk_button_new_with_label("Move Up"); + auto* btn_move_down = gtk_button_new_with_label("Move Down"); gtk_box_append(GTK_BOX(move_group), btn_move_up); gtk_box_append(GTK_BOX(move_group), btn_move_down); gtk_box_append(GTK_BOX(shell_btn_box), move_group); - gtk_box_append(GTK_BOX(right), shell_btn_box); + gtk_frame_set_child(GTK_FRAME(shell_ctrl_frame), shell_btn_box); + gtk_box_append(GTK_BOX(right), shell_ctrl_frame); // Shell properties editor gtk_box_append(GTK_BOX(right), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)); @@ -155,6 +164,11 @@ ShellsView::ShellsView(Project& project) : project_(project) { g_signal_connect(btn_move_up, "clicked", G_CALLBACK(on_move_up), this); g_signal_connect(btn_move_down, "clicked", G_CALLBACK(on_move_down), this); + g_signal_connect(btn_refresh, "clicked", G_CALLBACK(+[](GtkButton*, gpointer d) { + auto* self = static_cast(d); + self->refresh(); + }), this); + g_signal_connect(shell_listbox_, "row-selected", G_CALLBACK(+[](GtkListBox*, GtkListBoxRow* row, gpointer d) { auto* self = static_cast(d); if (!row) { self->clear_editor(); return; } @@ -163,11 +177,11 @@ ShellsView::ShellsView(Project& project) : project_(project) { g_signal_connect(btn_save_, "clicked", G_CALLBACK(+[](GtkButton*, gpointer d) { auto* self = static_cast(d); - if (self->current_file_idx_ < 0 || self->current_file_idx_ >= (int)self->project_.shell_files.size()) { + if (!self->selected_file_) { self->project_.report_status("Error: no shell file selected"); return; } - auto& sf = self->project_.shell_files[self->current_file_idx_]; + auto& sf = *self->selected_file_; try { sf.save(); self->file_dirty_ = false; @@ -182,9 +196,9 @@ ShellsView::ShellsView(Project& project) : project_(project) { auto bind = [this](GtkWidget* entry) { g_signal_connect(entry, "changed", G_CALLBACK(+[](GtkEditable* e, gpointer d) { auto* self = static_cast(d); - if (self->loading_ || self->current_file_idx_ < 0 || self->current_shell_idx_ < 0) + if (self->loading_ || !self->selected_file_ || self->current_shell_idx_ < 0) return; - auto& sf = self->project_.shell_files[self->current_file_idx_]; + auto& sf = *self->selected_file_; if (self->current_shell_idx_ >= (int)sf.shells.size()) return; auto& s = sf.shells[self->current_shell_idx_]; auto text = std::string(gtk_editable_get_text(e)); @@ -215,15 +229,29 @@ void ShellsView::mark_file_dirty() { gtk_widget_add_css_class(btn_save_, "suggested-action"); } +GtkListBoxRow* ShellsView::find_file_row(ShellsFile* sf) { + for (int i = 0; ; i++) { + auto* row = gtk_list_box_get_row_at_index(GTK_LIST_BOX(file_listbox_), i); + if (!row) return nullptr; + if (g_object_get_data(G_OBJECT(row), "shell-file") == sf) return row; + } +} + void ShellsView::populate_file_list() { GtkWidget* child; while ((child = gtk_widget_get_first_child(file_listbox_)) != nullptr) gtk_list_box_remove(GTK_LIST_BOX(file_listbox_), child); - current_file_idx_ = -1; + selected_file_ = nullptr; + + std::sort(project_.shell_files.begin(), project_.shell_files.end(), + [](const ShellsFile& a, const ShellsFile& b) { + return a.filepath.filename().string() < b.filepath.filename().string(); + }); for (auto& sf : project_.shell_files) { auto* row = gtk_list_box_row_new(); + g_object_set_data(G_OBJECT(row), "shell-file", &sf); auto text = std::string("\u25C6 ") + sf.filepath.filename().string(); auto* label = gtk_label_new(text.c_str()); gtk_label_set_xalign(GTK_LABEL(label), 0.0f); @@ -246,16 +274,15 @@ void ShellsView::populate_shell_list() { current_shell_idx_ = -1; clear_editor(); - if (current_file_idx_ < 0 || current_file_idx_ >= (int)project_.shell_files.size()) { + if (!selected_file_) { gtk_label_set_markup(GTK_LABEL(file_label_), "No file selected"); return; } - auto& sf = project_.shell_files[current_file_idx_]; gtk_label_set_markup(GTK_LABEL(file_label_), - (std::string("") + sf.filepath.filename().string() + "").c_str()); + (std::string("") + selected_file_->filepath.filename().string() + "").c_str()); - for (auto& s : sf.shells) { + for (auto& s : selected_file_->shells) { auto* row = gtk_list_box_row_new(); auto text = std::string("\u25B8 ") + s.name; auto* label = gtk_label_new(text.c_str()); @@ -280,11 +307,11 @@ void ShellsView::clear_editor() { } void ShellsView::load_shell(int idx) { - if (current_file_idx_ < 0 || current_file_idx_ >= (int)project_.shell_files.size()) { + if (!selected_file_) { clear_editor(); return; } - auto& sf = project_.shell_files[current_file_idx_]; + auto& sf = *selected_file_; if (idx < 0 || idx >= (int)sf.shells.size()) { clear_editor(); return; @@ -308,11 +335,11 @@ void ShellsView::refresh() { void ShellsView::on_file_selected(GtkListBox*, GtkListBoxRow* row, gpointer data) { auto* self = static_cast(data); if (!row) { - self->current_file_idx_ = -1; + self->selected_file_ = nullptr; self->populate_shell_list(); return; } - self->current_file_idx_ = gtk_list_box_row_get_index(row); + self->selected_file_ = static_cast(g_object_get_data(G_OBJECT(row), "shell-file")); self->file_dirty_ = false; gtk_widget_remove_css_class(self->btn_save_, "suggested-action"); self->populate_shell_list(); @@ -371,10 +398,14 @@ void ShellsView::on_new_file_response(GObject* source, GAsyncResult* res, gpoint self->project_.shell_files.push_back(std::move(sf)); self->populate_file_list(); - int last = (int)self->project_.shell_files.size() - 1; - auto* row = gtk_list_box_get_row_at_index(GTK_LIST_BOX(self->file_listbox_), last); - if (row) - gtk_list_box_select_row(GTK_LIST_BOX(self->file_listbox_), row); + // Select the new file by matching filepath + for (auto& f : self->project_.shell_files) { + if (f.filepath == fp) { + auto* row = self->find_file_row(&f); + if (row) gtk_list_box_select_row(GTK_LIST_BOX(self->file_listbox_), row); + break; + } + } self->project_.report_status("Created shell file: " + fp.filename().string()); } catch (const std::exception& e) { @@ -384,26 +415,28 @@ void ShellsView::on_new_file_response(GObject* source, GAsyncResult* res, gpoint void ShellsView::on_delete_file(GtkButton*, gpointer data) { auto* self = static_cast(data); - if (self->current_file_idx_ < 0 || self->current_file_idx_ >= (int)self->project_.shell_files.size()) - return; + if (!self->selected_file_) return; - auto& sf = self->project_.shell_files[self->current_file_idx_]; - auto name = sf.filepath.filename().string(); + auto name = self->selected_file_->filepath.filename().string(); - self->project_.shell_files.erase(self->project_.shell_files.begin() + self->current_file_idx_); - self->current_file_idx_ = -1; + auto it = std::find_if(self->project_.shell_files.begin(), self->project_.shell_files.end(), + [&](const ShellsFile& sf) { return &sf == self->selected_file_; }); + if (it != self->project_.shell_files.end()) + self->project_.shell_files.erase(it); + + self->selected_file_ = nullptr; self->populate_file_list(); self->project_.report_status("Removed shell file from project: " + name + " (file not deleted from disk)"); } void ShellsView::on_new_shell(GtkButton*, gpointer data) { auto* self = static_cast(data); - if (self->current_file_idx_ < 0 || self->current_file_idx_ >= (int)self->project_.shell_files.size()) { + if (!self->selected_file_) { self->project_.report_status("Error: select a shell file first"); return; } - auto& sf = self->project_.shell_files[self->current_file_idx_]; + auto& sf = *self->selected_file_; ShellDef s; s.name = "new_shell"; s.path = "/usr/bin/new_shell"; @@ -421,14 +454,13 @@ void ShellsView::on_new_shell(GtkButton*, gpointer data) { void ShellsView::on_delete_shell(GtkButton*, gpointer data) { auto* self = static_cast(data); - if (self->current_file_idx_ < 0 || self->current_file_idx_ >= (int)self->project_.shell_files.size()) - return; + if (!self->selected_file_) return; auto* row = gtk_list_box_get_selected_row(GTK_LIST_BOX(self->shell_listbox_)); if (!row) return; int idx = gtk_list_box_row_get_index(row); - auto& sf = self->project_.shell_files[self->current_file_idx_]; + auto& sf = *self->selected_file_; if (idx < 0 || idx >= (int)sf.shells.size()) return; auto name = sf.shells[idx].name; @@ -440,14 +472,13 @@ void ShellsView::on_delete_shell(GtkButton*, gpointer data) { void ShellsView::on_move_up(GtkButton*, gpointer data) { auto* self = static_cast(data); - if (self->current_file_idx_ < 0 || self->current_file_idx_ >= (int)self->project_.shell_files.size()) - return; + if (!self->selected_file_) return; auto* row = gtk_list_box_get_selected_row(GTK_LIST_BOX(self->shell_listbox_)); if (!row) return; int idx = gtk_list_box_row_get_index(row); - auto& sf = self->project_.shell_files[self->current_file_idx_]; + auto& sf = *self->selected_file_; if (idx <= 0) return; std::swap(sf.shells[idx], sf.shells[idx - 1]); @@ -460,14 +491,13 @@ void ShellsView::on_move_up(GtkButton*, gpointer data) { void ShellsView::on_move_down(GtkButton*, gpointer data) { auto* self = static_cast(data); - if (self->current_file_idx_ < 0 || self->current_file_idx_ >= (int)self->project_.shell_files.size()) - return; + if (!self->selected_file_) return; auto* row = gtk_list_box_get_selected_row(GTK_LIST_BOX(self->shell_listbox_)); if (!row) return; int idx = gtk_list_box_row_get_index(row); - auto& sf = self->project_.shell_files[self->current_file_idx_]; + auto& sf = *self->selected_file_; if (idx < 0 || idx >= (int)sf.shells.size() - 1) return; std::swap(sf.shells[idx], sf.shells[idx + 1]); diff --git a/src/views/shells_view.h b/src/views/shells_view.h index 921848c..238c6de 100644 --- a/src/views/shells_view.h +++ b/src/views/shells_view.h @@ -47,7 +47,7 @@ private: GtkWidget* entry_exec_arg_; GtkWidget* entry_source_cmd_; - int current_file_idx_ = -1; + ShellsFile* selected_file_ = nullptr; int current_shell_idx_ = -1; bool loading_ = false; bool file_dirty_ = false; @@ -57,6 +57,7 @@ private: void load_shell(int idx); void clear_editor(); void mark_file_dirty(); + GtkListBoxRow* find_file_row(ShellsFile* sf); static void on_file_selected(GtkListBox* box, GtkListBoxRow* row, gpointer data); static void on_new_file(GtkButton* btn, gpointer data); diff --git a/src/views/units_view.cpp b/src/views/units_view.cpp index cbce62e..0743270 100644 --- a/src/views/units_view.cpp +++ b/src/views/units_view.cpp @@ -19,15 +19,10 @@ #include "views/units_view.h" #include "util/unsaved_dialog.h" #include "util/unit_properties_dialog.h" +#include #include #include -static int sort_listbox_alpha(GtkListBoxRow* a, GtkListBoxRow* b, gpointer) { - auto* la = GTK_LABEL(gtk_list_box_row_get_child(GTK_LIST_BOX_ROW(a))); - auto* lb = GTK_LABEL(gtk_list_box_row_get_child(GTK_LIST_BOX_ROW(b))); - return std::strcmp(gtk_label_get_text(la), gtk_label_get_text(lb)); -} - namespace grex { UnitsView::UnitsView(Project& project, GrexConfig& grex_config) @@ -54,12 +49,16 @@ UnitsView::UnitsView(Project& project, GrexConfig& grex_config) gtk_widget_add_css_class(file_scroll, "frame"); file_listbox_ = gtk_list_box_new(); gtk_list_box_set_selection_mode(GTK_LIST_BOX(file_listbox_), GTK_SELECTION_SINGLE); - gtk_list_box_set_sort_func(GTK_LIST_BOX(file_listbox_), sort_listbox_alpha, nullptr, nullptr); gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(file_scroll), file_listbox_); gtk_box_append(GTK_BOX(left), file_scroll); // File lifecycle buttons — grouped + auto* file_ctrl_frame = gtk_frame_new("File Controls"); auto* file_btn_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8); + gtk_widget_set_margin_start(file_btn_box, 8); + gtk_widget_set_margin_end(file_btn_box, 8); + gtk_widget_set_margin_top(file_btn_box, 8); + gtk_widget_set_margin_bottom(file_btn_box, 8); auto* file_edit_group = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_add_css_class(file_edit_group, "linked"); @@ -72,7 +71,11 @@ UnitsView::UnitsView(Project& project, GrexConfig& grex_config) btn_save_ = gtk_button_new_with_label("Save File"); gtk_box_append(GTK_BOX(file_btn_box), btn_save_); - gtk_box_append(GTK_BOX(left), file_btn_box); + auto* btn_refresh = gtk_button_new_with_label("Refresh"); + gtk_box_append(GTK_BOX(file_btn_box), btn_refresh); + + gtk_frame_set_child(GTK_FRAME(file_ctrl_frame), file_btn_box); + gtk_box_append(GTK_BOX(left), file_ctrl_frame); gtk_paned_set_start_child(GTK_PANED(root_), left); gtk_paned_set_shrink_start_child(GTK_PANED(root_), FALSE); @@ -100,7 +103,12 @@ UnitsView::UnitsView(Project& project, GrexConfig& grex_config) gtk_box_append(GTK_BOX(right), unit_scroll); // Unit action buttons — grouped by function + auto* unit_ctrl_frame = gtk_frame_new("Unit Controls"); auto* unit_btn_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8); + gtk_widget_set_margin_start(unit_btn_box, 8); + gtk_widget_set_margin_end(unit_btn_box, 8); + gtk_widget_set_margin_top(unit_btn_box, 8); + gtk_widget_set_margin_bottom(unit_btn_box, 8); auto* unit_edit_group = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_add_css_class(unit_edit_group, "linked"); @@ -115,13 +123,14 @@ UnitsView::UnitsView(Project& project, GrexConfig& grex_config) auto* move_group = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_add_css_class(move_group, "linked"); - auto* btn_move_up = gtk_button_new_with_label("Up"); - auto* btn_move_down = gtk_button_new_with_label("Down"); + auto* btn_move_up = gtk_button_new_with_label("Move Up"); + auto* btn_move_down = gtk_button_new_with_label("Move Down"); gtk_box_append(GTK_BOX(move_group), btn_move_up); gtk_box_append(GTK_BOX(move_group), btn_move_down); gtk_box_append(GTK_BOX(unit_btn_box), move_group); - gtk_box_append(GTK_BOX(right), unit_btn_box); + gtk_frame_set_child(GTK_FRAME(unit_ctrl_frame), unit_btn_box); + gtk_box_append(GTK_BOX(right), unit_ctrl_frame); gtk_paned_set_end_child(GTK_PANED(root_), right); gtk_paned_set_shrink_end_child(GTK_PANED(root_), FALSE); @@ -137,13 +146,18 @@ UnitsView::UnitsView(Project& project, GrexConfig& grex_config) g_signal_connect(btn_move_down, "clicked", G_CALLBACK(on_move_down), this); g_signal_connect(unit_listbox_, "row-activated", G_CALLBACK(on_unit_activated), this); + g_signal_connect(btn_refresh, "clicked", G_CALLBACK(+[](GtkButton*, gpointer d) { + auto* self = static_cast(d); + self->refresh(); + }), this); + g_signal_connect(btn_save_, "clicked", G_CALLBACK(+[](GtkButton*, gpointer d) { auto* self = static_cast(d); - if (self->current_file_idx_ < 0 || self->current_file_idx_ >= (int)self->project_.unit_files.size()) { + if (!self->selected_file_) { self->project_.report_status("Error: no unit file selected"); return; } - auto& uf = self->project_.unit_files[self->current_file_idx_]; + auto& uf = *self->selected_file_; // Check for cross-file duplicates before saving for (auto& u : uf.units) { if (self->project_.is_unit_name_taken(u.name, &u)) { @@ -162,15 +176,29 @@ UnitsView::UnitsView(Project& project, GrexConfig& grex_config) }), this); } +GtkListBoxRow* UnitsView::find_file_row(UnitFile* uf) { + for (int i = 0; ; i++) { + auto* row = gtk_list_box_get_row_at_index(GTK_LIST_BOX(file_listbox_), i); + if (!row) return nullptr; + if (g_object_get_data(G_OBJECT(row), "unit-file") == uf) return row; + } +} + void UnitsView::populate_file_list() { GtkWidget* child; while ((child = gtk_widget_get_first_child(file_listbox_)) != nullptr) gtk_list_box_remove(GTK_LIST_BOX(file_listbox_), child); - current_file_idx_ = -1; + selected_file_ = nullptr; + + std::sort(project_.unit_files.begin(), project_.unit_files.end(), + [](const UnitFile& a, const UnitFile& b) { + return a.filepath.filename().string() < b.filepath.filename().string(); + }); for (auto& uf : project_.unit_files) { auto* row = gtk_list_box_row_new(); + g_object_set_data(G_OBJECT(row), "unit-file", &uf); auto text = std::string("\u25C6 ") + uf.filepath.filename().string(); auto* label = gtk_label_new(text.c_str()); gtk_label_set_xalign(GTK_LABEL(label), 0.0f); @@ -190,16 +218,15 @@ void UnitsView::populate_unit_list() { while ((child = gtk_widget_get_first_child(unit_listbox_)) != nullptr) gtk_list_box_remove(GTK_LIST_BOX(unit_listbox_), child); - if (current_file_idx_ < 0 || current_file_idx_ >= (int)project_.unit_files.size()) { + if (!selected_file_) { gtk_label_set_markup(GTK_LABEL(file_label_), "No file selected"); return; } - auto& uf = project_.unit_files[current_file_idx_]; gtk_label_set_markup(GTK_LABEL(file_label_), - (std::string("") + uf.filepath.filename().string() + "").c_str()); + (std::string("") + selected_file_->filepath.filename().string() + "").c_str()); - for (auto& u : uf.units) { + for (auto& u : selected_file_->units) { auto* row = gtk_list_box_row_new(); auto text = std::string("\u2022 ") + u.name; auto* label = gtk_label_new(text.c_str()); @@ -219,14 +246,12 @@ void UnitsView::refresh() { } void UnitsView::save_current_file() { - if (current_file_idx_ < 0 || current_file_idx_ >= (int)project_.unit_files.size()) - return; - auto& uf = project_.unit_files[current_file_idx_]; + if (!selected_file_) return; try { - uf.save(); + selected_file_->save(); file_dirty_ = false; gtk_widget_remove_css_class(btn_save_, "suggested-action"); - project_.report_status("Saved unit file: " + uf.filepath.filename().string()); + project_.report_status("Saved unit file: " + selected_file_->filepath.filename().string()); } catch (const std::exception& e) { project_.report_status(std::string("Error: ") + e.what()); } @@ -237,17 +262,17 @@ void UnitsView::on_file_selected(GtkListBox*, GtkListBoxRow* row, gpointer data) if (self->suppress_selection_) return; if (!row) { - self->current_file_idx_ = -1; + self->selected_file_ = nullptr; self->populate_unit_list(); return; } - int new_idx = gtk_list_box_row_get_index(row); + auto* new_file = static_cast(g_object_get_data(G_OBJECT(row), "unit-file")); - if (self->file_dirty_ && self->current_file_idx_ >= 0 && new_idx != self->current_file_idx_) { - int old_idx = self->current_file_idx_; + if (self->file_dirty_ && self->selected_file_ && new_file != self->selected_file_) { + // Snap selection back to the old file while showing the dialog self->suppress_selection_ = true; - auto* old_row = gtk_list_box_get_row_at_index(GTK_LIST_BOX(self->file_listbox_), old_idx); + auto* old_row = self->find_file_row(self->selected_file_); if (old_row) gtk_list_box_select_row(GTK_LIST_BOX(self->file_listbox_), old_row); self->suppress_selection_ = false; @@ -256,31 +281,28 @@ void UnitsView::on_file_selected(GtkListBox*, GtkListBoxRow* row, gpointer data) if (result == UnsavedResult::Cancel) return; if (result == UnsavedResult::Save) { - auto& uf = self->project_.unit_files[self->current_file_idx_]; - try { uf.save(); } catch (const std::exception& e) { + try { self->selected_file_->save(); } catch (const std::exception& e) { self->project_.report_status(std::string("Error: ") + e.what()); } } else { - auto idx = self->current_file_idx_; - if (idx >= 0 && idx < (int)self->project_.unit_files.size()) { - auto& uf = self->project_.unit_files[idx]; - try { uf = UnitFile::load(uf.filepath); } catch (const std::exception& e) { - self->project_.report_status(std::string("Error: ") + e.what()); - } + try { + *self->selected_file_ = UnitFile::load(self->selected_file_->filepath); + } catch (const std::exception& e) { + self->project_.report_status(std::string("Error: ") + e.what()); } } self->file_dirty_ = false; gtk_widget_remove_css_class(self->btn_save_, "suggested-action"); + // Now snap to the new selection self->suppress_selection_ = true; - auto* target_row = gtk_list_box_get_row_at_index(GTK_LIST_BOX(self->file_listbox_), new_idx); - if (target_row) gtk_list_box_select_row(GTK_LIST_BOX(self->file_listbox_), target_row); + gtk_list_box_select_row(GTK_LIST_BOX(self->file_listbox_), row); self->suppress_selection_ = false; - self->current_file_idx_ = new_idx; + self->selected_file_ = new_file; self->populate_unit_list(); return; } - self->current_file_idx_ = new_idx; + self->selected_file_ = new_file; self->file_dirty_ = false; gtk_widget_remove_css_class(self->btn_save_, "suggested-action"); self->populate_unit_list(); } @@ -340,11 +362,14 @@ void UnitsView::on_new_file_response(GObject* source, GAsyncResult* res, gpointe self->project_.unit_files.push_back(std::move(uf)); self->populate_file_list(); - // Select the new file - int last = (int)self->project_.unit_files.size() - 1; - auto* row = gtk_list_box_get_row_at_index(GTK_LIST_BOX(self->file_listbox_), last); - if (row) - gtk_list_box_select_row(GTK_LIST_BOX(self->file_listbox_), row); + // Select the new file by matching filepath + for (auto& f : self->project_.unit_files) { + if (f.filepath == fp) { + auto* row = self->find_file_row(&f); + if (row) gtk_list_box_select_row(GTK_LIST_BOX(self->file_listbox_), row); + break; + } + } self->project_.report_status("Created unit file: " + fp.filename().string()); } catch (const std::exception& e) { @@ -354,21 +379,23 @@ void UnitsView::on_new_file_response(GObject* source, GAsyncResult* res, gpointe void UnitsView::on_delete_file(GtkButton*, gpointer data) { auto* self = static_cast(data); - if (self->current_file_idx_ < 0 || self->current_file_idx_ >= (int)self->project_.unit_files.size()) - return; + if (!self->selected_file_) return; - auto& uf = self->project_.unit_files[self->current_file_idx_]; - auto name = uf.filepath.filename().string(); + auto name = self->selected_file_->filepath.filename().string(); - self->project_.unit_files.erase(self->project_.unit_files.begin() + self->current_file_idx_); - self->current_file_idx_ = -1; + auto it = std::find_if(self->project_.unit_files.begin(), self->project_.unit_files.end(), + [&](const UnitFile& uf) { return &uf == self->selected_file_; }); + if (it != self->project_.unit_files.end()) + self->project_.unit_files.erase(it); + + self->selected_file_ = nullptr; self->populate_file_list(); self->project_.report_status("Removed unit file from project: " + name + " (file not deleted from disk)"); } void UnitsView::on_new_unit(GtkButton*, gpointer data) { auto* self = static_cast(data); - if (self->current_file_idx_ < 0 || self->current_file_idx_ >= (int)self->project_.unit_files.size()) { + if (!self->selected_file_) { self->project_.report_status("Error: select a unit file first"); return; } @@ -435,7 +462,7 @@ void UnitsView::on_new_unit(GtkButton*, gpointer data) { return; } - auto& uf = nd->view->project_.unit_files[nd->view->current_file_idx_]; + auto& uf = *nd->view->selected_file_; Unit u; u.name = name; @@ -453,14 +480,13 @@ void UnitsView::on_new_unit(GtkButton*, gpointer data) { void UnitsView::on_delete_unit(GtkButton*, gpointer data) { auto* self = static_cast(data); - if (self->current_file_idx_ < 0 || self->current_file_idx_ >= (int)self->project_.unit_files.size()) - return; + if (!self->selected_file_) return; auto* row = gtk_list_box_get_selected_row(GTK_LIST_BOX(self->unit_listbox_)); if (!row) return; int idx = gtk_list_box_row_get_index(row); - auto& uf = self->project_.unit_files[self->current_file_idx_]; + auto& uf = *self->selected_file_; if (idx < 0 || idx >= (int)uf.units.size()) return; auto name = uf.units[idx].name; @@ -472,10 +498,9 @@ void UnitsView::on_delete_unit(GtkButton*, gpointer data) { void UnitsView::on_unit_activated(GtkListBox*, GtkListBoxRow* row, gpointer data) { auto* self = static_cast(data); - if (self->current_file_idx_ < 0 || self->current_file_idx_ >= (int)self->project_.unit_files.size()) - return; + if (!self->selected_file_) return; - auto& uf = self->project_.unit_files[self->current_file_idx_]; + auto& uf = *self->selected_file_; // Get unit name from the row's label (strip bullet prefix) auto* label = gtk_list_box_row_get_child(GTK_LIST_BOX_ROW(row)); @@ -503,13 +528,12 @@ void UnitsView::on_unit_activated(GtkListBox*, GtkListBoxRow* row, gpointer data void UnitsView::on_move_up(GtkButton*, gpointer data) { auto* self = static_cast(data); - if (self->current_file_idx_ < 0 || self->current_file_idx_ >= (int)self->project_.unit_files.size()) - return; + if (!self->selected_file_) return; auto* row = gtk_list_box_get_selected_row(GTK_LIST_BOX(self->unit_listbox_)); if (!row) return; - auto& uf = self->project_.unit_files[self->current_file_idx_]; + auto& uf = *self->selected_file_; // Find unit index by name from the row label auto* label = gtk_list_box_row_get_child(GTK_LIST_BOX_ROW(row)); @@ -535,13 +559,12 @@ void UnitsView::on_move_up(GtkButton*, gpointer data) { void UnitsView::on_move_down(GtkButton*, gpointer data) { auto* self = static_cast(data); - if (self->current_file_idx_ < 0 || self->current_file_idx_ >= (int)self->project_.unit_files.size()) - return; + if (!self->selected_file_) return; auto* row = gtk_list_box_get_selected_row(GTK_LIST_BOX(self->unit_listbox_)); if (!row) return; - auto& uf = self->project_.unit_files[self->current_file_idx_]; + auto& uf = *self->selected_file_; // Find unit index by name from the row label auto* label = gtk_list_box_row_get_child(GTK_LIST_BOX_ROW(row)); @@ -567,8 +590,7 @@ void UnitsView::on_move_down(GtkButton*, gpointer data) { void UnitsView::on_edit_unit(GtkButton*, gpointer data) { auto* self = static_cast(data); - if (self->current_file_idx_ < 0 || self->current_file_idx_ >= (int)self->project_.unit_files.size()) - return; + if (!self->selected_file_) return; auto* row = gtk_list_box_get_selected_row(GTK_LIST_BOX(self->unit_listbox_)); if (!row) { @@ -583,7 +605,7 @@ void UnitsView::on_edit_unit(GtkButton*, gpointer data) { label_text = label_text.substr(strlen("\u2022 ")); // Find unit by name - auto& uf = self->project_.unit_files[self->current_file_idx_]; + auto& uf = *self->selected_file_; Unit* unit = nullptr; for (auto& u : uf.units) { if (u.name == label_text) { unit = &u; break; } diff --git a/src/views/units_view.h b/src/views/units_view.h index 2b5f441..11ed1d8 100644 --- a/src/views/units_view.h +++ b/src/views/units_view.h @@ -40,12 +40,13 @@ private: GtkWidget* file_label_; GtkWidget* btn_save_; - int current_file_idx_ = -1; + UnitFile* selected_file_ = nullptr; bool file_dirty_ = false; bool suppress_selection_ = false; void populate_file_list(); void populate_unit_list(); + GtkListBoxRow* find_file_row(UnitFile* uf); static void on_file_selected(GtkListBox* box, GtkListBoxRow* row, gpointer data); static void on_new_file(GtkButton* btn, gpointer data);