diff --git a/src/views/plan_view.cpp b/src/views/plan_view.cpp index fa7e636..aed46eb 100644 --- a/src/views/plan_view.cpp +++ b/src/views/plan_view.cpp @@ -73,37 +73,25 @@ PlanView::PlanView(Project& project, GrexConfig& grex_config) gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), task_listbox_); gtk_box_append(GTK_BOX(task_controls_), scroll); - // Task action buttons — grouped by function + // Plan Controls — plan-level actions 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"); - auto* btn_add = gtk_button_new_with_label("Add"); - auto* btn_del = gtk_button_new_with_label("Delete"); - gtk_box_append(GTK_BOX(task_edit_group), btn_add); - gtk_box_append(GTK_BOX(task_edit_group), btn_del); - gtk_box_append(GTK_BOX(btn_box), task_edit_group); - - 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("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); + auto* plan_btn_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8); + gtk_widget_set_margin_start(plan_btn_box, 8); + gtk_widget_set_margin_end(plan_btn_box, 8); + gtk_widget_set_margin_top(plan_btn_box, 8); + gtk_widget_set_margin_bottom(plan_btn_box, 8); 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(plan_btn_box), btn_save_plan_); - auto* btn_refresh = gtk_button_new_with_label("Refresh"); - gtk_box_append(GTK_BOX(btn_box), btn_refresh); + auto* btn_refresh = gtk_button_new_with_label("Reload Plan"); + gtk_box_append(GTK_BOX(plan_btn_box), btn_refresh); - gtk_frame_set_child(GTK_FRAME(plan_ctrl_frame), btn_box); + auto* btn_delete_plan = gtk_button_new_with_label("Delete Plan"); + gtk_widget_add_css_class(btn_delete_plan, "destructive-action"); + gtk_box_append(GTK_BOX(plan_btn_box), btn_delete_plan); + + gtk_frame_set_child(GTK_FRAME(plan_ctrl_frame), plan_btn_box); gtk_box_append(GTK_BOX(task_controls_), plan_ctrl_frame); gtk_box_append(GTK_BOX(left), task_controls_); @@ -115,6 +103,38 @@ PlanView::PlanView(Project& project, GrexConfig& grex_config) gtk_paned_set_end_child(GTK_PANED(root_), unit_editor_->widget()); gtk_paned_set_shrink_end_child(GTK_PANED(root_), FALSE); + // Task Controls — appended inside UnitEditor's content area + task_ctrl_frame_ = gtk_frame_new("Task Controls"); + auto* task_ctrl_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8); + gtk_widget_set_margin_start(task_ctrl_box, 8); + gtk_widget_set_margin_end(task_ctrl_box, 8); + gtk_widget_set_margin_top(task_ctrl_box, 8); + gtk_widget_set_margin_bottom(task_ctrl_box, 8); + + auto* task_btn_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8); + + auto* task_edit_group = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_add_css_class(task_edit_group, "linked"); + auto* btn_add = gtk_button_new_with_label("Add Task"); + auto* btn_del = gtk_button_new_with_label("Delete Task"); + gtk_box_append(GTK_BOX(task_edit_group), btn_add); + gtk_box_append(GTK_BOX(task_edit_group), btn_del); + gtk_box_append(GTK_BOX(task_btn_row), task_edit_group); + + auto* move_group = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_add_css_class(move_group, "linked"); + btn_move_up_ = gtk_button_new_with_label("Move Up"); + 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(task_btn_row), move_group); + + gtk_box_append(GTK_BOX(task_ctrl_box), task_btn_row); + gtk_box_append(GTK_BOX(task_ctrl_box), unit_editor_->unit_controls()); + + gtk_frame_set_child(GTK_FRAME(task_ctrl_frame_), task_ctrl_box); + gtk_box_append(GTK_BOX(unit_editor_->content_box()), task_ctrl_frame_); + // Name change callback to refresh the task list row label unit_editor_->set_name_changed_callback([](const std::string&, void* data) { auto* self = static_cast(data); @@ -131,8 +151,8 @@ PlanView::PlanView(Project& project, GrexConfig& grex_config) g_signal_connect(task_listbox_, "row-selected", G_CALLBACK(on_task_selected), this); g_signal_connect(btn_add, "clicked", G_CALLBACK(on_add_task), this); g_signal_connect(btn_del, "clicked", G_CALLBACK(on_delete_task), this); - g_signal_connect(btn_up, "clicked", G_CALLBACK(on_move_up), this); - g_signal_connect(btn_down, "clicked", G_CALLBACK(on_move_down), this); + 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_save_plan_, "clicked", G_CALLBACK(+[](GtkButton*, gpointer d) { auto* self = static_cast(d); @@ -150,6 +170,8 @@ PlanView::PlanView(Project& project, GrexConfig& grex_config) self->refresh(); }), this); + g_signal_connect(btn_delete_plan, "clicked", G_CALLBACK(on_delete_plan), this); + update_plan_buttons(); } @@ -183,6 +205,7 @@ void PlanView::populate_task_list() { gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), label); gtk_list_box_append(GTK_LIST_BOX(task_listbox_), row); } + update_move_buttons(); } void PlanView::refresh_task_row(int idx) { @@ -216,6 +239,7 @@ void PlanView::select_task(int idx) { Unit* unit = project_.find_unit(task.name); unit_editor_->load(&task, unit); + update_move_buttons(); } // --- Open Plan --- @@ -403,6 +427,50 @@ void PlanView::on_close_plan(GtkButton*, gpointer data) { self->close_plan_impl(); } +void PlanView::on_delete_plan(GtkButton*, gpointer data) { + auto* self = static_cast(data); + auto* plan = self->current_plan(); + if (!plan) { + self->project_.report_status("Error: no plan loaded"); + return; + } + + auto filepath = plan->filepath; + auto name = filepath.filename().string(); + + // Confirm deletion + auto* parent = GTK_WINDOW(gtk_widget_get_ancestor(self->root_, GTK_TYPE_WINDOW)); + auto* dialog = gtk_alert_dialog_new("Delete plan file '%s' from disk?", name.c_str()); + gtk_alert_dialog_set_detail(dialog, "This action cannot be undone."); + gtk_alert_dialog_set_buttons(dialog, (const char*[]){"Cancel", "Delete", nullptr}); + gtk_alert_dialog_set_cancel_button(dialog, 0); + gtk_alert_dialog_set_default_button(dialog, 0); + + struct DeleteCtx { PlanView* view; std::filesystem::path path; std::string name; }; + auto* ctx = new DeleteCtx{self, filepath, name}; + + gtk_alert_dialog_choose(dialog, parent, nullptr, + +[](GObject* source, GAsyncResult* res, gpointer d) { + auto* ctx = static_cast(d); + GError* error = nullptr; + int choice = gtk_alert_dialog_choose_finish(GTK_ALERT_DIALOG(source), res, &error); + if (error) { g_error_free(error); delete ctx; return; } + if (choice != 1) { delete ctx; return; } // not "Delete" + + // Close the plan first + ctx->view->close_plan_impl(); + + // Delete from disk + std::error_code ec; + if (std::filesystem::remove(ctx->path, ec)) + ctx->view->project_.report_status("Deleted plan file: " + ctx->name); + else + ctx->view->project_.report_status("Error: could not delete " + ctx->name + + (ec ? ": " + ec.message() : "")); + delete ctx; + }, ctx); +} + void PlanView::on_create_plan(GtkButton*, gpointer data) { auto* self = static_cast(data); auto* window = gtk_widget_get_ancestor(self->root_, GTK_TYPE_WINDOW); @@ -467,10 +535,23 @@ void PlanView::update_plan_buttons() { gtk_widget_set_visible(btn_create_plan_, !has_plan); gtk_widget_set_visible(btn_close_plan_, has_plan); gtk_widget_set_sensitive(task_controls_, has_plan); + gtk_widget_set_sensitive(task_ctrl_frame_, has_plan); if (!has_plan) unit_editor_->clear(); } +void PlanView::update_move_buttons() { + auto* plan = current_plan(); + if (!plan || current_task_idx_ < 0) { + gtk_widget_set_sensitive(btn_move_up_, FALSE); + gtk_widget_set_sensitive(btn_move_down_, FALSE); + return; + } + int count = (int)plan->tasks.size(); + gtk_widget_set_sensitive(btn_move_up_, current_task_idx_ > 0); + gtk_widget_set_sensitive(btn_move_down_, current_task_idx_ < count - 1); +} + void PlanView::refresh() { // reload units if paths now resolve project_.load_all_units(); diff --git a/src/views/plan_view.h b/src/views/plan_view.h index 3f21420..30d469a 100644 --- a/src/views/plan_view.h +++ b/src/views/plan_view.h @@ -45,6 +45,9 @@ private: GtkWidget* btn_close_plan_; GtkWidget* btn_save_plan_; GtkWidget* task_controls_; // container for task list + buttons + GtkWidget* task_ctrl_frame_; // Task Controls frame in right panel + GtkWidget* btn_move_up_; + GtkWidget* btn_move_down_; UnitEditor* unit_editor_; int current_task_idx_ = -1; @@ -64,11 +67,13 @@ private: static void on_move_up(GtkButton* btn, gpointer data); static void on_move_down(GtkButton* btn, gpointer data); static void on_close_plan(GtkButton* btn, gpointer data); + static void on_delete_plan(GtkButton* btn, gpointer data); static void on_create_plan(GtkButton* btn, gpointer data); static void on_create_plan_response(GObject* source, GAsyncResult* res, gpointer data); void refresh_task_row(int idx); void update_plan_buttons(); + void update_move_buttons(); void close_plan_impl(); }; diff --git a/src/views/unit_editor.cpp b/src/views/unit_editor.cpp index 216b970..2f3f2d2 100644 --- a/src/views/unit_editor.cpp +++ b/src/views/unit_editor.cpp @@ -27,17 +27,20 @@ UnitEditor::UnitEditor(Project& project, GrexConfig& grex_config) 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, 8); - 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); + content_box_ = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8); + gtk_widget_set_margin_start(content_box_, 16); + gtk_widget_set_margin_end(content_box_, 16); + gtk_widget_set_margin_top(content_box_, 16); + gtk_widget_set_margin_bottom(content_box_, 16); + auto* box = content_box_; + + // Task properties container — disabled when no task selected + task_section_ = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8); - // Task section header auto* task_label = gtk_label_new(nullptr); gtk_label_set_markup(GTK_LABEL(task_label), "Task Properties"); gtk_label_set_xalign(GTK_LABEL(task_label), 0.0f); - gtk_box_append(GTK_BOX(box), task_label); + gtk_box_append(GTK_BOX(task_section_), task_label); auto* task_grid = gtk_grid_new(); gtk_grid_set_row_spacing(GTK_GRID(task_grid), 6); @@ -60,30 +63,33 @@ UnitEditor::UnitEditor(Project& project, GrexConfig& grex_config) gtk_widget_set_hexpand(entry_comment_, TRUE); gtk_grid_attach(GTK_GRID(task_grid), entry_comment_, 1, 1, 1, 1); - // Change/Select Unit button — aligned with the value column + gtk_box_append(GTK_BOX(task_section_), task_grid); + gtk_box_append(GTK_BOX(box), task_section_); + + // Underlying Unit controls — exposed for external placement + unit_controls_ = gtk_frame_new("Underlying Unit"); + 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); + btn_select_unit_ = gtk_button_new_with_label("Change/Select Unit..."); - gtk_widget_set_halign(btn_select_unit_, GTK_ALIGN_START); g_signal_connect(btn_select_unit_, "clicked", G_CALLBACK(on_select_unit), this); - gtk_grid_attach(GTK_GRID(task_grid), btn_select_unit_, 1, 2, 1, 1); + gtk_box_append(GTK_BOX(unit_btn_box), btn_select_unit_); - gtk_box_append(GTK_BOX(box), task_grid); - - // Buttons below task properties - gtk_box_append(GTK_BOX(box), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)); - - auto* btn_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); btn_edit_unit_ = gtk_button_new_with_label("Edit Unit..."); g_signal_connect(btn_edit_unit_, "clicked", G_CALLBACK(on_edit_unit), this); - gtk_box_append(GTK_BOX(btn_row), btn_edit_unit_); + gtk_box_append(GTK_BOX(unit_btn_box), btn_edit_unit_); btn_save_unit_ = gtk_button_new_with_label("Save Unit"); g_signal_connect(btn_save_unit_, "clicked", G_CALLBACK(+[](GtkButton*, gpointer d) { auto* self = static_cast(d); self->save_current(); }), this); - gtk_box_append(GTK_BOX(btn_row), btn_save_unit_); + gtk_box_append(GTK_BOX(unit_btn_box), btn_save_unit_); - gtk_box_append(GTK_BOX(box), btn_row); + gtk_frame_set_child(GTK_FRAME(unit_controls_), unit_btn_box); gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(root_), box); @@ -108,12 +114,14 @@ void UnitEditor::clear() { gtk_label_set_text(GTK_LABEL(name_display_), ""); gtk_editable_set_text(GTK_EDITABLE(entry_comment_), ""); - gtk_widget_set_sensitive(root_, FALSE); + gtk_widget_set_sensitive(task_section_, FALSE); + gtk_widget_set_sensitive(unit_controls_, FALSE); clear_dirty(); } void UnitEditor::load(Task* task, Unit* unit) { - gtk_widget_set_sensitive(root_, TRUE); + gtk_widget_set_sensitive(task_section_, TRUE); + gtk_widget_set_sensitive(unit_controls_, TRUE); g_signal_handlers_disconnect_by_data(entry_comment_, this); current_task_ = task; current_unit_ = unit; diff --git a/src/views/unit_editor.h b/src/views/unit_editor.h index 0db4c87..08b04ea 100644 --- a/src/views/unit_editor.h +++ b/src/views/unit_editor.h @@ -29,6 +29,8 @@ public: UnitEditor(Project& project, GrexConfig& grex_config); ~UnitEditor() = default; GtkWidget* widget() { return root_; } + GtkWidget* content_box() { return content_box_; } + GtkWidget* unit_controls() { return unit_controls_; } void load(Task* task, Unit* unit); void clear(); @@ -49,6 +51,9 @@ private: Unit* current_unit_ = nullptr; std::string current_unit_name_; GtkWidget* root_; + GtkWidget* content_box_; + GtkWidget* unit_controls_; + GtkWidget* task_section_; // task fields GtkWidget* name_display_; diff --git a/src/views/units_view.cpp b/src/views/units_view.cpp index 0743270..f1cf828 100644 --- a/src/views/units_view.cpp +++ b/src/views/units_view.cpp @@ -123,10 +123,10 @@ 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("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); + btn_move_up_ = gtk_button_new_with_label("Move Up"); + 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_frame_set_child(GTK_FRAME(unit_ctrl_frame), unit_btn_box); @@ -142,9 +142,12 @@ UnitsView::UnitsView(Project& project, GrexConfig& grex_config) g_signal_connect(btn_new_unit, "clicked", G_CALLBACK(on_new_unit), this); g_signal_connect(btn_del_unit, "clicked", G_CALLBACK(on_delete_unit), this); g_signal_connect(btn_edit_unit, "clicked", G_CALLBACK(on_edit_unit), this); - 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_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(unit_listbox_, "row-activated", G_CALLBACK(on_unit_activated), this); + g_signal_connect(unit_listbox_, "row-selected", G_CALLBACK(+[](GtkListBox*, GtkListBoxRow*, gpointer d) { + static_cast(d)->update_move_buttons(); + }), this); g_signal_connect(btn_refresh, "clicked", G_CALLBACK(+[](GtkButton*, gpointer d) { auto* self = static_cast(d); @@ -238,6 +241,25 @@ void UnitsView::populate_unit_list() { gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), label); gtk_list_box_append(GTK_LIST_BOX(unit_listbox_), row); } + update_move_buttons(); +} + +void UnitsView::update_move_buttons() { + if (!selected_file_) { + gtk_widget_set_sensitive(btn_move_up_, FALSE); + gtk_widget_set_sensitive(btn_move_down_, FALSE); + return; + } + auto* row = gtk_list_box_get_selected_row(GTK_LIST_BOX(unit_listbox_)); + if (!row) { + gtk_widget_set_sensitive(btn_move_up_, FALSE); + gtk_widget_set_sensitive(btn_move_down_, FALSE); + return; + } + int idx = gtk_list_box_row_get_index(row); + int count = (int)selected_file_->units.size(); + gtk_widget_set_sensitive(btn_move_up_, idx > 0); + gtk_widget_set_sensitive(btn_move_down_, idx < count - 1); } void UnitsView::refresh() { diff --git a/src/views/units_view.h b/src/views/units_view.h index 11ed1d8..aad5ec9 100644 --- a/src/views/units_view.h +++ b/src/views/units_view.h @@ -39,6 +39,8 @@ private: GtkWidget* unit_listbox_; GtkWidget* file_label_; GtkWidget* btn_save_; + GtkWidget* btn_move_up_; + GtkWidget* btn_move_down_; UnitFile* selected_file_ = nullptr; bool file_dirty_ = false; @@ -46,6 +48,7 @@ private: void populate_file_list(); void populate_unit_list(); + void update_move_buttons(); GtkListBoxRow* find_file_row(UnitFile* uf); static void on_file_selected(GtkListBox* box, GtkListBoxRow* row, gpointer data);