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);