first commit
This commit is contained in:
225
src/views/unit_editor.cpp
Normal file
225
src/views/unit_editor.cpp
Normal file
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
GREX - A graphical frontend for creating and managing Rex project files.
|
||||
Copyright (C) 2026 SILO GROUP, LLC. Written by Chris Punches
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "views/unit_editor.h"
|
||||
#include "util/unit_picker.h"
|
||||
#include "util/unit_properties_dialog.h"
|
||||
|
||||
namespace grex {
|
||||
|
||||
UnitEditor::UnitEditor(Project& project, GrexConfig& grex_config)
|
||||
: project_(project), grex_config_(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);
|
||||
|
||||
// Task section header
|
||||
auto* task_label = gtk_label_new(nullptr);
|
||||
gtk_label_set_markup(GTK_LABEL(task_label), "<b>Task Properties</b>");
|
||||
gtk_label_set_xalign(GTK_LABEL(task_label), 0.0f);
|
||||
gtk_box_append(GTK_BOX(box), task_label);
|
||||
|
||||
auto* task_grid = gtk_grid_new();
|
||||
gtk_grid_set_row_spacing(GTK_GRID(task_grid), 6);
|
||||
gtk_grid_set_column_spacing(GTK_GRID(task_grid), 12);
|
||||
|
||||
// Name (read-only label)
|
||||
auto* name_label = gtk_label_new("Name");
|
||||
gtk_label_set_xalign(GTK_LABEL(name_label), 1.0f);
|
||||
gtk_grid_attach(GTK_GRID(task_grid), name_label, 0, 0, 1, 1);
|
||||
name_display_ = gtk_label_new("");
|
||||
gtk_label_set_xalign(GTK_LABEL(name_display_), 0.0f);
|
||||
gtk_widget_set_hexpand(name_display_, TRUE);
|
||||
gtk_grid_attach(GTK_GRID(task_grid), name_display_, 1, 0, 1, 1);
|
||||
|
||||
// Comment
|
||||
auto* comment_label = gtk_label_new("Comment");
|
||||
gtk_label_set_xalign(GTK_LABEL(comment_label), 1.0f);
|
||||
gtk_grid_attach(GTK_GRID(task_grid), comment_label, 0, 1, 1, 1);
|
||||
entry_comment_ = gtk_entry_new();
|
||||
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
|
||||
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(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_);
|
||||
|
||||
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<UnitEditor*>(d);
|
||||
self->save_current();
|
||||
}), this);
|
||||
gtk_box_append(GTK_BOX(btn_row), btn_save_unit_);
|
||||
|
||||
gtk_box_append(GTK_BOX(box), btn_row);
|
||||
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(root_), box);
|
||||
|
||||
clear();
|
||||
}
|
||||
|
||||
void UnitEditor::mark_dirty() {
|
||||
dirty_ = true;
|
||||
gtk_widget_add_css_class(btn_save_unit_, "suggested-action");
|
||||
}
|
||||
|
||||
void UnitEditor::clear_dirty() {
|
||||
dirty_ = false;
|
||||
gtk_widget_remove_css_class(btn_save_unit_, "suggested-action");
|
||||
}
|
||||
|
||||
void UnitEditor::clear() {
|
||||
g_signal_handlers_disconnect_by_data(entry_comment_, this);
|
||||
current_task_ = nullptr;
|
||||
current_unit_ = nullptr;
|
||||
current_unit_name_.clear();
|
||||
|
||||
gtk_label_set_text(GTK_LABEL(name_display_), "");
|
||||
gtk_editable_set_text(GTK_EDITABLE(entry_comment_), "");
|
||||
gtk_widget_set_sensitive(entry_comment_, FALSE);
|
||||
gtk_widget_set_sensitive(btn_edit_unit_, FALSE);
|
||||
clear_dirty();
|
||||
}
|
||||
|
||||
void UnitEditor::load(Task* task, Unit* unit) {
|
||||
g_signal_handlers_disconnect_by_data(entry_comment_, this);
|
||||
current_task_ = task;
|
||||
current_unit_ = unit;
|
||||
current_unit_name_ = task ? task->name : "";
|
||||
|
||||
gtk_widget_set_sensitive(entry_comment_, TRUE);
|
||||
if (unit) {
|
||||
gtk_label_set_text(GTK_LABEL(name_display_), task->name.c_str());
|
||||
} else {
|
||||
auto markup = std::string("<span foreground=\"red\">") + task->name + "</span>";
|
||||
gtk_label_set_markup(GTK_LABEL(name_display_), markup.c_str());
|
||||
}
|
||||
gtk_editable_set_text(GTK_EDITABLE(entry_comment_), task->comment.value_or("").c_str());
|
||||
gtk_widget_set_sensitive(btn_edit_unit_, unit != nullptr);
|
||||
|
||||
// Connect comment change signal
|
||||
g_signal_connect(entry_comment_, "changed", G_CALLBACK(+[](GtkEditable* e, gpointer d) {
|
||||
auto* self = static_cast<UnitEditor*>(d);
|
||||
if (!self->current_task_) return;
|
||||
self->mark_dirty();
|
||||
auto text = std::string(gtk_editable_get_text(e));
|
||||
self->current_task_->comment = text.empty() ? std::nullopt : std::optional<std::string>(text);
|
||||
}), this);
|
||||
|
||||
clear_dirty();
|
||||
}
|
||||
|
||||
void UnitEditor::save_current() {
|
||||
if (current_unit_name_.empty()) return;
|
||||
auto* unit = project_.find_unit(current_unit_name_);
|
||||
if (!unit) return;
|
||||
current_unit_ = unit;
|
||||
if (project_.is_unit_name_taken(current_unit_->name, current_unit_)) {
|
||||
project_.report_status("Error: unit name '" + current_unit_->name + "' conflicts with another unit");
|
||||
return;
|
||||
}
|
||||
auto* uf = project_.find_unit_file(current_unit_->name);
|
||||
if (!uf) return;
|
||||
try {
|
||||
uf->save();
|
||||
clear_dirty();
|
||||
project_.report_status("Saved unit file: " + uf->filepath.filename().string());
|
||||
} catch (const std::exception& e) {
|
||||
project_.report_status(std::string("Error: ") + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void UnitEditor::revert_current() {
|
||||
if (current_unit_name_.empty()) return;
|
||||
auto* uf = project_.find_unit_file(current_unit_name_);
|
||||
if (uf) {
|
||||
try {
|
||||
auto reloaded = UnitFile::load(uf->filepath);
|
||||
*uf = std::move(reloaded);
|
||||
} catch (const std::exception& e) {
|
||||
project_.report_status(std::string("Error: ") + e.what());
|
||||
}
|
||||
}
|
||||
if (!project_.plans.empty()) {
|
||||
auto& plan = project_.plans[0];
|
||||
try {
|
||||
auto reloaded = Plan::load(plan.filepath);
|
||||
plan = std::move(reloaded);
|
||||
} catch (const std::exception& e) {
|
||||
project_.report_status(std::string("Error: ") + e.what());
|
||||
}
|
||||
}
|
||||
clear_dirty();
|
||||
}
|
||||
|
||||
void UnitEditor::set_name_changed_callback(NameChangedCallback cb, void* data) {
|
||||
name_cb_ = cb;
|
||||
name_cb_data_ = data;
|
||||
}
|
||||
|
||||
void UnitEditor::on_edit_unit(GtkButton*, gpointer data) {
|
||||
auto* self = static_cast<UnitEditor*>(data);
|
||||
if (!self->current_unit_) return;
|
||||
|
||||
auto* parent = GTK_WINDOW(gtk_widget_get_ancestor(self->root_, GTK_TYPE_WINDOW));
|
||||
auto result = show_unit_properties_dialog(parent, self->current_unit_,
|
||||
self->project_, self->grex_config_, self->project_.shells.shells);
|
||||
|
||||
if (result == UnitDialogResult::Save)
|
||||
self->mark_dirty();
|
||||
}
|
||||
|
||||
void UnitEditor::on_select_unit(GtkButton*, gpointer data) {
|
||||
auto* self = static_cast<UnitEditor*>(data);
|
||||
if (!self->current_task_) return;
|
||||
|
||||
if (self->project_.unit_files.empty())
|
||||
self->project_.load_all_units();
|
||||
|
||||
if (self->project_.unit_files.empty()) {
|
||||
self->project_.report_status("Error: no units loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
auto* parent = GTK_WINDOW(gtk_widget_get_ancestor(self->root_, GTK_TYPE_WINDOW));
|
||||
show_unit_picker(parent, self->project_, [self](const std::string& unit_name) {
|
||||
if (self->current_task_) self->current_task_->name = unit_name;
|
||||
Unit* unit = self->project_.find_unit(unit_name);
|
||||
self->load(self->current_task_, unit);
|
||||
if (self->name_cb_) self->name_cb_(unit_name, self->name_cb_data_);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user