File config_editor.cpp
File List > gui > config_editor.cpp
Go to the documentation of this file
#include "config_editor.hpp"
#include <qdebug.h>
#include <QColorDialog>
#include <QDoubleValidator>
#include <QFileDialog>
#include <QIntValidator>
#include <QListWidget>
#include <QPainter>
#include <QPushButton>
#include <QSpinBox>
#include <QTableWidget>
#include <filesystem>
#include "assert/assert.hpp"
#include "config_input/config_input.hpp"
#include "printing/to_string.hpp"
#include "ui_config_editor.h"
class ParentFolderExistsValidator : public QValidator {
public:
QValidator::State validate(QString& input, [[maybe_unused]] int& pos) const override {
if (fs::exists(fs::path(input.toStdString()).parent_path())) {
return QValidator::Acceptable;
}
return QValidator::Invalid;
}
};
template <typename T>
bool validated(T* box) {
QString text;
if constexpr (std::is_same_v<T, QLineEdit>) {
text = box->text();
} else if constexpr (std::is_same_v<T, QComboBox>) {
text = box->currentText();
}
int pos = 0;
QValidator::State state = box->validator()->validate(text, pos);
switch (state) {
case QValidator::Acceptable:
box->setStyleSheet("");
return true;
case QValidator::Intermediate:
case QValidator::Invalid:
box->setStyleSheet("QComboBox { border: 2px solid red; }");
return false;
}
unreachable();
}
bool color_equals(const ColorVariant& v1, const ColorVariant& v2) {
if (v1.index() != v2.index()) return false;
if (std::holds_alternative<RGBColor>(v1)) {
const auto& c1 = std::get<RGBColor>(v1);
const auto& c2 = std::get<RGBColor>(v2);
return c1.getRed() == c2.getRed() && c1.getGreen() == c2.getGreen() &&
c1.getBlue() == c2.getBlue() && c1.getAlpha() == c2.getAlpha();
} else {
const auto& c1 = std::get<CMYKColor>(v1);
const auto& c2 = std::get<CMYKColor>(v2);
return c1.getCyan() == c2.getCyan() && c1.getMagenta() == c2.getMagenta() &&
c1.getYellow() == c2.getYellow() && c1.getBlack() == c2.getBlack();
}
}
QString get_color_name(const ColorVariant& color) {
for (const auto& [name, map_color] : COLOR_MAP) {
if (color_equals(color, map_color)) {
return QString::fromStdString(name);
}
}
return "";
}
QIcon create_color_icon(const ColorVariant& color) {
QPixmap pixmap(16, 16);
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
RGBColor rgb = to_rgb(color);
QColor qcolor(rgb.getRed(), rgb.getGreen(), rgb.getBlue(), rgb.getAlpha());
painter.setBrush(qcolor);
painter.setPen(Qt::black);
painter.drawRect(0, 0, 15, 15);
return QIcon(pixmap);
}
bool ConfigEditor::is_valid() const {
bool all_las_files_exist = std::all_of(
m_config->las_files.begin(), m_config->las_files.end(),
[this](const fs::path& las_file) { return m_config->get_las_files(las_file).size(); });
return validated(ui->scale_dropdown) && validated(ui->dpi_dropdown) &&
validated(ui->out_dir_line_edit) && !m_config->processing_steps.empty() &&
!m_config->las_files.empty() && all_las_files_exist;
}
ConfigEditor::ConfigEditor(QWidget* parent)
: QWidget(parent),
ui(new Ui::ConfigEditor),
m_config(std::make_unique<Config>(Config::Default())) {
ui->setupUi(this);
ui->scale_dropdown->setValidator(new QDoubleValidator(100.0, 100000.0, 2, this));
connect(ui->scale_dropdown, &QComboBox::currentTextChanged, [this](const QString& text) {
if (validated(ui->scale_dropdown)) {
m_config->render.scale = std::stod(text.toStdString());
}
config_changed();
});
ui->out_dir_line_edit->setValidator(new ParentFolderExistsValidator());
connect(ui->out_dir_line_edit, &QLineEdit::textChanged, [this](const QString& text) {
if (validated(ui->out_dir_line_edit)) {
m_config->set_output_directory(text.toStdString());
config_changed();
}
});
ui->dpi_dropdown->setValidator(new QDoubleValidator(1.0, 2400.0, 2, this));
connect(ui->dpi_dropdown, &QComboBox::currentTextChanged, [this](const QString& text) {
if (validated(ui->dpi_dropdown)) {
m_config->render.dpi = std::stod(text.toStdString());
}
config_changed();
});
connect(ui->add_las_button, &QPushButton::clicked, this, &ConfigEditor::add_las_file);
connect(ui->add_las_folder_button, &QPushButton::clicked, this, &ConfigEditor::add_las_folder);
connect(ui->remove_las_button, &QPushButton::clicked, this, &ConfigEditor::remove_las_file);
connect(ui->out_dir_button, &QPushButton::clicked, this, &ConfigEditor::open_output_directory);
for (const auto& [checkbox, step] : std::vector<std::pair<QCheckBox*, ProcessingStep>>{
{ui->process_tiles_checkbox, ProcessingStep::Tiles},
{ui->combine_tiles_checkbox, ProcessingStep::Combine}}) {
ProcessingStep ps = step;
connect(checkbox, &QCheckBox::stateChanged, [this, ps](int state) {
if (state == Qt::Checked) {
m_config->processing_steps.insert(ps);
} else {
m_config->processing_steps.erase(ps);
}
config_changed();
});
}
// General Tab
ui->grid_bin_resolution->setValidator(new QDoubleValidator(0.0, 1000.0, 3, this));
ui->ground_outlier_removal->setValidator(new QDoubleValidator(0.0, 1000.0, 3, this));
ui->border_width->setValidator(new QDoubleValidator(0.0, 10000.0, 2, this));
auto connect_general = [this](QWidget* widget) {
if (auto* le = qobject_cast<QLineEdit*>(widget)) {
connect(le, &QLineEdit::textChanged, this, &ConfigEditor::update_general_from_ui);
} else if (auto* sb = qobject_cast<QSpinBox*>(widget)) {
connect(sb, QOverload<int>::of(&QSpinBox::valueChanged), this,
&ConfigEditor::update_general_from_ui);
}
};
connect_general(ui->grid_bin_resolution);
connect_general(ui->grid_downsample_factor);
connect_general(ui->ground_outlier_removal);
connect_general(ui->ground_min_intensity);
connect_general(ui->ground_max_intensity);
connect(ui->buildings_color, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ConfigEditor::update_general_from_ui);
connect_general(ui->border_width);
// Contours Tab
connect(ui->contours_list_widget, &QListWidget::itemSelectionChanged, this,
&ConfigEditor::on_contour_selected);
connect(ui->add_contour_button, &QPushButton::clicked, this, &ConfigEditor::add_contour);
connect(ui->remove_contour_button, &QPushButton::clicked, this, &ConfigEditor::remove_contour);
// Use currentIndexChanged for combos to handle "Add new color"
connect(ui->contour_color_combo, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ConfigEditor::update_contour_from_ui);
auto connect_contour = [this](QWidget* widget) {
if (auto* le = qobject_cast<QLineEdit*>(widget)) {
connect(le, &QLineEdit::textChanged, this, &ConfigEditor::update_contour_from_ui);
} else if (auto* sb = qobject_cast<QSpinBox*>(widget)) {
connect(sb, QOverload<int>::of(&QSpinBox::valueChanged), this,
&ConfigEditor::update_contour_from_ui);
}
};
connect_contour(ui->contour_name_edit);
connect_contour(ui->contour_interval_edit);
connect_contour(ui->contour_min_points_edit);
// contour_color_combo connected separately
connect_contour(ui->contour_width_edit);
// Water Tab
connect(ui->water_list_widget, &QListWidget::itemSelectionChanged, this,
&ConfigEditor::on_water_selected);
connect(ui->add_water_button, &QPushButton::clicked, this, &ConfigEditor::add_water);
connect(ui->remove_water_button, &QPushButton::clicked, this, &ConfigEditor::remove_water);
connect(ui->water_color_combo, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ConfigEditor::update_water_from_ui);
auto connect_water = [this](QWidget* widget) {
if (auto* le = qobject_cast<QLineEdit*>(widget)) {
connect(le, &QLineEdit::textChanged, this, &ConfigEditor::update_water_from_ui);
}
};
connect_water(ui->water_name_edit);
connect_water(ui->water_catchment_edit);
// water_color_combo connected separately
connect_water(ui->water_width_edit);
// Vegetation Tab
connect(ui->vege_list_widget, &QListWidget::itemSelectionChanged, this,
&ConfigEditor::on_vege_selected);
connect(ui->add_vege_button, &QPushButton::clicked, this, &ConfigEditor::add_vege);
connect(ui->remove_vege_button, &QPushButton::clicked, this, &ConfigEditor::remove_vege);
connect(ui->add_vege_color_button, &QPushButton::clicked, this, &ConfigEditor::add_vege_color);
connect(ui->remove_vege_color_button, &QPushButton::clicked, this,
&ConfigEditor::remove_vege_color);
connect(ui->vege_colors_table, &QTableWidget::cellChanged, this,
&ConfigEditor::update_vege_color_from_ui);
connect(ui->vege_bg_color_combo, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ConfigEditor::update_vege_from_ui);
auto connect_vege = [this](QWidget* widget) {
if (auto* le = qobject_cast<QLineEdit*>(widget)) {
connect(le, &QLineEdit::textChanged, this, &ConfigEditor::update_vege_from_ui);
}
};
connect_vege(ui->vege_name_edit);
connect_vege(ui->vege_min_height_edit);
connect_vege(ui->vege_max_height_edit);
// Colors Tab
connect(ui->colors_list_widget, &QListWidget::itemSelectionChanged, this,
&ConfigEditor::on_color_selected);
connect(ui->add_color_button, &QPushButton::clicked, this, &ConfigEditor::add_color);
connect(ui->remove_color_button, &QPushButton::clicked, this, &ConfigEditor::remove_color);
connect(ui->pick_color_button, &QPushButton::clicked, this, &ConfigEditor::pick_color);
connect(ui->color_type_combo, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ConfigEditor::on_color_type_changed);
auto connect_color = [this](QWidget* widget) {
if (auto* le = qobject_cast<QLineEdit*>(widget)) {
connect(le, &QLineEdit::textChanged, this, &ConfigEditor::update_color_from_ui);
} else if (auto* sb = qobject_cast<QSpinBox*>(widget)) {
connect(sb, QOverload<int>::of(&QSpinBox::valueChanged), this,
&ConfigEditor::update_color_from_ui);
}
};
connect_color(ui->color_name_edit);
connect_color(ui->color_c1_spin);
connect_color(ui->color_c2_spin);
connect_color(ui->color_c3_spin);
connect_color(ui->color_c4_spin);
set_ui_to_config(*m_config);
}
ConfigEditor::~ConfigEditor() {}
void ConfigEditor::open_config_file() {
QString config_file_name = QFileDialog::getOpenFileName(
this, ("Open Config"), m_config->relative_path_to_config.string().c_str(),
("Config Files (*.json *.jsonc);;All files (*)"), nullptr, QFileDialog::ReadOnly);
if (config_file_name.isEmpty()) {
return;
}
m_config = std::make_unique<Config>(Config::FromFile(config_file_name.toStdString()));
set_ui_to_config(*m_config);
}
void ConfigEditor::add_las_file() {
fs::path directory;
QStringList las_file_names = QFileDialog::getOpenFileNames(
this, ("Open LAS file/s"), m_config->relative_path_to_config.string().c_str(),
("LAS Files (*.las *.laz);;All files (*)"), nullptr, QFileDialog::ReadOnly);
if (las_file_names.isEmpty()) {
return;
}
for (const QString& las_file_name : las_file_names) {
m_config->las_files.push_back(las_file_name.toStdString());
}
set_ui_to_config(*m_config);
}
void ConfigEditor::remove_las_file() {
QList<QTreeWidgetItem*> items = ui->treeWidget->selectedItems();
for (QTreeWidgetItem* item : items) {
m_config->las_files.erase(std::remove(m_config->las_files.begin(), m_config->las_files.end(),
item->text(1).toStdString()),
m_config->las_files.end());
delete item;
}
set_ui_to_config(*m_config);
}
void ConfigEditor::add_las_folder() {
fs::path directory;
QString las_folder_name = QFileDialog::getExistingDirectory(
this, tr("Choose LAS Folder"), m_config->relative_path_to_config.string().c_str(),
QFileDialog::DontResolveSymlinks);
if (las_folder_name.isEmpty()) {
return;
}
m_config->las_files.push_back(las_folder_name.toStdString());
set_ui_to_config(*m_config);
}
void ConfigEditor::save_config_file() {
QString config_file_name = QFileDialog::getSaveFileName(
this, ("Save Config"), m_config->relative_path_to_config.string().c_str(),
("Config Files (*.json *.jsonc);;All files (*)"), nullptr, QFileDialog::ReadOnly);
if (config_file_name.isEmpty()) {
return;
}
m_config->write_to_file(config_file_name.toStdString());
}
void ConfigEditor::open_output_directory() {
QString output_dir_name = QFileDialog::getExistingDirectory(
this, tr("Choose Output Directory"), m_config->output_path().string().c_str(),
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
if (output_dir_name.isEmpty()) {
return;
}
m_config->set_output_directory(output_dir_name.toStdString());
ui->out_dir_line_edit->setText(output_dir_name);
config_changed();
}
void ConfigEditor::populate_color_combo(QComboBox* combo) {
bool was_blocked = combo->blockSignals(true);
combo->clear();
for (const auto& [name, color] : COLOR_MAP) {
combo->addItem(create_color_icon(color), QString::fromStdString(name));
}
combo->addItem("Add new color...");
combo->blockSignals(was_blocked);
}
void ConfigEditor::set_ui_to_config(const Config& config) {
m_updating_ui = true;
// 1. Populate all color combos first
populate_color_combo(ui->buildings_color);
populate_color_combo(ui->contour_color_combo);
populate_color_combo(ui->water_color_combo);
populate_color_combo(ui->vege_bg_color_combo);
ui->out_dir_line_edit->setText(config.output_path().string().c_str());
ui->scale_dropdown->setCurrentText(double_to_string(config.render.scale).c_str());
ui->dpi_dropdown->setCurrentText(double_to_string(config.render.dpi).c_str());
ui->process_tiles_checkbox->setChecked(
std::find(config.processing_steps.begin(), config.processing_steps.end(),
ProcessingStep::Tiles) != config.processing_steps.end());
ui->combine_tiles_checkbox->setChecked(
std::find(config.processing_steps.begin(), config.processing_steps.end(),
ProcessingStep::Combine) != config.processing_steps.end());
ui->treeWidget->clear();
ui->treeWidget->setColumnWidth(0, 60);
for (const fs::path& path : config.las_files) {
QTreeWidgetItem* item = new QTreeWidgetItem(ui->treeWidget);
item->setText(1, path.string().c_str());
std::vector<fs::path> las_files = config.get_las_files(path);
item->setExpanded(las_files.size() > 1);
item->setText(0, QString::number(las_files.size()));
item->setTextAlignment(0, Qt::AlignCenter);
ui->treeWidget->addTopLevelItem(item);
if (las_files.size() == 0) {
item->setForeground(0, QBrush(Qt::red));
item->setForeground(1, QBrush(Qt::red));
} else if (fs::is_directory(path)) {
for (const fs::path& entry : las_files) {
QTreeWidgetItem* child = new QTreeWidgetItem(item);
child->setText(1, entry.string().c_str());
child->setDisabled(true);
item->addChild(child);
}
}
}
// 2. Set General Tab fields and specific color selections
ui->grid_bin_resolution->setText(QString::number(config.grid.bin_resolution));
ui->grid_downsample_factor->setValue(config.grid.downsample_factor);
ui->ground_outlier_removal->setText(QString::number(config.ground.outlier_removal_height_diff));
ui->ground_min_intensity->setValue(config.ground.min_ground_intensity);
ui->ground_max_intensity->setValue(config.ground.max_ground_intensity);
ui->buildings_color->setCurrentText(get_color_name(config.buildings.color));
ui->border_width->setText(QString::number(config.border_width));
ui->vege_bg_color_combo->setCurrentText(get_color_name(config.vege.background_color));
// 3. Populate Lists (these trigger detail loading for the first items via signals)
// Since combos are already populated, details loading will correctly set the selections.
populate_contour_list();
populate_water_list();
populate_vege_list();
populate_color_list();
m_updating_ui = false;
config_changed();
}
// Helpers for General Tab
void ConfigEditor::update_general_from_ui() {
if (m_updating_ui) return;
if (ui->buildings_color->currentText() == "Add new color...") {
ui->tabWidget->setCurrentWidget(ui->Colors_tab);
add_color();
return;
}
m_config->grid.bin_resolution = ui->grid_bin_resolution->text().toDouble();
m_config->grid.downsample_factor = ui->grid_downsample_factor->value();
m_config->ground.outlier_removal_height_diff = ui->ground_outlier_removal->text().toDouble();
m_config->ground.min_ground_intensity = ui->ground_min_intensity->value();
m_config->ground.max_ground_intensity = ui->ground_max_intensity->value();
m_config->border_width = ui->border_width->text().toDouble();
QString b_color = ui->buildings_color->currentText();
if (COLOR_MAP.count(b_color.toStdString())) {
m_config->buildings.color = COLOR_MAP.at(b_color.toStdString());
}
config_changed();
}
// Helpers for Contours
void ConfigEditor::populate_contour_list() {
QString selected;
if (auto* item = ui->contours_list_widget->currentItem()) {
selected = item->text();
}
ui->contours_list_widget->clear();
for (const auto& [name, config] : m_config->contours.configs) {
ui->contours_list_widget->addItem(QString::fromStdString(name));
}
if (!selected.isEmpty()) {
auto items = ui->contours_list_widget->findItems(selected, Qt::MatchExactly);
if (!items.empty()) {
ui->contours_list_widget->setCurrentItem(items.front());
} else if (ui->contours_list_widget->count() > 0) {
ui->contours_list_widget->setCurrentRow(0);
}
} else if (ui->contours_list_widget->count() > 0) {
ui->contours_list_widget->setCurrentRow(0);
}
}
void ConfigEditor::add_contour() {
std::string name = "new_contour";
int i = 1;
while (m_config->contours.configs.count(name)) {
name = "new_contour_" + std::to_string(i++);
}
m_config->contours.configs[name] = ContourConfig();
populate_contour_list();
}
void ConfigEditor::remove_contour() {
auto items = ui->contours_list_widget->selectedItems();
if (items.empty()) return;
std::string name = items.front()->text().toStdString();
m_config->contours.configs.erase(name);
populate_contour_list();
}
void ConfigEditor::on_contour_selected() {
auto items = ui->contours_list_widget->selectedItems();
if (items.empty()) return;
load_contour_details(items.front()->text().toStdString());
}
void ConfigEditor::load_contour_details(const std::string& name) {
bool was_updating = m_updating_ui;
m_updating_ui = true;
const auto& config = m_config->contours.configs.at(name);
ui->contour_name_edit->setText(QString::fromStdString(name));
ui->contour_interval_edit->setText(QString::number(config.interval));
ui->contour_min_points_edit->setValue(config.min_points);
ui->contour_width_edit->setText(QString::number(config.width));
ui->contour_color_combo->setCurrentText(get_color_name(config.color));
m_updating_ui = was_updating;
}
void ConfigEditor::update_contour_from_ui() {
if (m_updating_ui) return;
auto items = ui->contours_list_widget->selectedItems();
if (items.empty()) return;
if (ui->contour_color_combo->currentText() == "Add new color...") {
ui->tabWidget->setCurrentWidget(ui->Colors_tab);
add_color();
return;
}
std::string old_name = items.front()->text().toStdString();
std::string new_name = ui->contour_name_edit->text().toStdString();
ContourConfig config = m_config->contours.configs.at(old_name);
config.interval = ui->contour_interval_edit->text().toDouble();
config.min_points = ui->contour_min_points_edit->value();
config.width = ui->contour_width_edit->text().toDouble();
QString color = ui->contour_color_combo->currentText();
if (COLOR_MAP.count(color.toStdString())) {
config.color = COLOR_MAP.at(color.toStdString());
}
if (old_name != new_name && !new_name.empty() && !m_config->contours.configs.count(new_name)) {
m_config->contours.configs.erase(old_name);
m_config->contours.configs[new_name] = config;
items.front()->setText(QString::fromStdString(new_name));
} else {
m_config->contours.configs[old_name] = config;
}
config_changed();
}
// Helpers for Water
void ConfigEditor::populate_water_list() {
QString selected;
if (auto* item = ui->water_list_widget->currentItem()) {
selected = item->text();
}
ui->water_list_widget->clear();
for (const auto& [name, config] : m_config->water.configs) {
ui->water_list_widget->addItem(QString::fromStdString(name));
}
if (!selected.isEmpty()) {
auto items = ui->water_list_widget->findItems(selected, Qt::MatchExactly);
if (!items.empty()) {
ui->water_list_widget->setCurrentItem(items.front());
} else if (ui->water_list_widget->count() > 0) {
ui->water_list_widget->setCurrentRow(0);
}
} else if (ui->water_list_widget->count() > 0) {
ui->water_list_widget->setCurrentRow(0);
}
}
void ConfigEditor::add_water() {
std::string name = "new_water";
int i = 1;
while (m_config->water.configs.count(name)) {
name = "new_water_" + std::to_string(i++);
}
m_config->water.configs[name] = WaterConfig();
populate_water_list();
}
void ConfigEditor::remove_water() {
auto items = ui->water_list_widget->selectedItems();
if (items.empty()) return;
std::string name = items.front()->text().toStdString();
m_config->water.configs.erase(name);
populate_water_list();
}
void ConfigEditor::on_water_selected() {
auto items = ui->water_list_widget->selectedItems();
if (items.empty()) return;
load_water_details(items.front()->text().toStdString());
}
void ConfigEditor::load_water_details(const std::string& name) {
bool was_updating = m_updating_ui;
m_updating_ui = true;
const auto& config = m_config->water.configs.at(name);
ui->water_name_edit->setText(QString::fromStdString(name));
ui->water_catchment_edit->setText(QString::number(config.catchment));
ui->water_width_edit->setText(QString::number(config.width));
ui->water_color_combo->setCurrentText(get_color_name(config.color));
m_updating_ui = was_updating;
}
void ConfigEditor::update_water_from_ui() {
if (m_updating_ui) return;
auto items = ui->water_list_widget->selectedItems();
if (items.empty()) return;
if (ui->water_color_combo->currentText() == "Add new color...") {
ui->tabWidget->setCurrentWidget(ui->Colors_tab);
add_color();
return;
}
std::string old_name = items.front()->text().toStdString();
std::string new_name = ui->water_name_edit->text().toStdString();
WaterConfig config = m_config->water.configs.at(old_name);
config.catchment = ui->water_catchment_edit->text().toDouble();
config.width = ui->water_width_edit->text().toDouble();
QString color = ui->water_color_combo->currentText();
if (COLOR_MAP.count(color.toStdString())) {
config.color = COLOR_MAP.at(color.toStdString());
}
if (old_name != new_name && !new_name.empty() && !m_config->water.configs.count(new_name)) {
m_config->water.configs.erase(old_name);
m_config->water.configs[new_name] = config;
items.front()->setText(QString::fromStdString(new_name));
} else {
m_config->water.configs[old_name] = config;
}
config_changed();
}
// Helpers for Vegetation
void ConfigEditor::populate_vege_list() {
QString selected;
if (auto* item = ui->vege_list_widget->currentItem()) {
selected = item->text();
}
ui->vege_list_widget->clear();
for (const auto& config : m_config->vege.height_configs) {
ui->vege_list_widget->addItem(QString::fromStdString(config.name));
}
if (!selected.isEmpty()) {
auto items = ui->vege_list_widget->findItems(selected, Qt::MatchExactly);
if (!items.empty()) {
ui->vege_list_widget->setCurrentItem(items.front());
} else if (ui->vege_list_widget->count() > 0) {
ui->vege_list_widget->setCurrentRow(0);
}
} else if (ui->vege_list_widget->count() > 0) {
ui->vege_list_widget->setCurrentRow(0);
}
}
void ConfigEditor::add_vege() {
VegeHeightConfig config;
config.name = "new_vege";
m_config->vege.height_configs.push_back(config);
populate_vege_list();
}
void ConfigEditor::remove_vege() {
int row = ui->vege_list_widget->currentRow();
if (row < 0 || row >= (int)m_config->vege.height_configs.size()) return;
m_config->vege.height_configs.erase(m_config->vege.height_configs.begin() + row);
populate_vege_list();
}
void ConfigEditor::on_vege_selected() {
int row = ui->vege_list_widget->currentRow();
if (row < 0) return;
load_vege_details(row);
}
void ConfigEditor::load_vege_details(int index) {
if (index < 0 || index >= (int)m_config->vege.height_configs.size()) return;
bool was_updating = m_updating_ui;
m_updating_ui = true;
const auto& config = m_config->vege.height_configs[index];
ui->vege_name_edit->setText(QString::fromStdString(config.name));
ui->vege_min_height_edit->setText(QString::number(config.min_height));
ui->vege_max_height_edit->setText(QString::number(config.max_height));
ui->vege_colors_table->setRowCount(0);
for (const auto& pair : config.colors) {
int r = ui->vege_colors_table->rowCount();
ui->vege_colors_table->insertRow(r);
ui->vege_colors_table->setItem(r, 0,
new QTableWidgetItem(QString::number(pair.blocking_threshold)));
QComboBox* combo = new QComboBox();
populate_color_combo(combo);
combo->setCurrentText(get_color_name(pair.color));
connect(combo, QOverload<int>::of(&QComboBox::currentIndexChanged),
[this, r]() { update_vege_color_from_ui(r, 1); });
ui->vege_colors_table->setCellWidget(r, 1, combo);
}
m_updating_ui = was_updating;
}
void ConfigEditor::update_vege_from_ui() {
if (m_updating_ui) return;
if (ui->vege_bg_color_combo->currentText() == "Add new color...") {
ui->tabWidget->setCurrentWidget(ui->Colors_tab);
add_color();
return;
}
int row = ui->vege_list_widget->currentRow();
if (row < 0 || row >= (int)m_config->vege.height_configs.size()) return;
auto& config = m_config->vege.height_configs[row];
config.name = ui->vege_name_edit->text().toStdString();
config.min_height = ui->vege_min_height_edit->text().toDouble();
config.max_height = ui->vege_max_height_edit->text().toDouble();
QString bg_color = ui->vege_bg_color_combo->currentText();
if (COLOR_MAP.count(bg_color.toStdString())) {
m_config->vege.background_color = COLOR_MAP.at(bg_color.toStdString());
}
ui->vege_list_widget->item(row)->setText(QString::fromStdString(config.name));
config_changed();
}
void ConfigEditor::add_vege_color() {
int row = ui->vege_list_widget->currentRow();
if (row < 0 || row >= (int)m_config->vege.height_configs.size()) return;
m_config->vege.height_configs[row].colors.push_back({0.0, RGBColor(255, 255, 255)});
load_vege_details(row);
config_changed();
}
void ConfigEditor::remove_vege_color() {
int row = ui->vege_list_widget->currentRow();
if (row < 0 || row >= (int)m_config->vege.height_configs.size()) return;
int color_row = ui->vege_colors_table->currentRow();
if (color_row < 0 || color_row >= (int)m_config->vege.height_configs[row].colors.size()) return;
auto& colors = m_config->vege.height_configs[row].colors;
colors.erase(colors.begin() + color_row);
load_vege_details(row);
config_changed();
}
void ConfigEditor::update_vege_color_from_ui(int row, int column) {
if (m_updating_ui) return;
int vege_row = ui->vege_list_widget->currentRow();
if (vege_row < 0 || vege_row >= (int)m_config->vege.height_configs.size()) return;
auto& pair = m_config->vege.height_configs[vege_row].colors[row];
if (column == 0) {
pair.blocking_threshold = ui->vege_colors_table->item(row, column)->text().toDouble();
} else if (column == 1) {
QComboBox* combo = qobject_cast<QComboBox*>(ui->vege_colors_table->cellWidget(row, column));
if (combo) {
if (combo->currentText() == "Add new color...") {
ui->tabWidget->setCurrentWidget(ui->Colors_tab);
add_color();
return;
}
QString color = combo->currentText();
if (COLOR_MAP.count(color.toStdString())) {
pair.color = COLOR_MAP.at(color.toStdString());
}
}
}
config_changed();
}
// Helpers for Colors
void ConfigEditor::populate_color_list() {
QString selected;
if (auto* item = ui->colors_list_widget->currentItem()) {
selected = item->text();
}
ui->colors_list_widget->clear();
for (const auto& [name, color] : COLOR_MAP) {
ui->colors_list_widget->addItem(
new QListWidgetItem(create_color_icon(color), QString::fromStdString(name)));
}
if (!selected.isEmpty()) {
auto items = ui->colors_list_widget->findItems(selected, Qt::MatchExactly);
if (!items.empty()) {
ui->colors_list_widget->setCurrentItem(items.front());
} else if (ui->colors_list_widget->count() > 0) {
ui->colors_list_widget->setCurrentRow(0);
}
} else if (ui->colors_list_widget->count() > 0) {
ui->colors_list_widget->setCurrentRow(0);
}
}
void ConfigEditor::add_color() {
std::string name = "new_color";
int i = 1;
while (COLOR_MAP.count(name)) {
name = "new_color_" + std::to_string(i++);
}
COLOR_MAP[name] = RGBColor(255, 255, 255);
set_ui_to_config(*m_config); // Re-populate all lists
// Select the new color in the Colors tab
auto items = ui->colors_list_widget->findItems(QString::fromStdString(name), Qt::MatchExactly);
if (!items.empty()) {
ui->colors_list_widget->setCurrentItem(items.front());
}
}
void ConfigEditor::remove_color() {
auto items = ui->colors_list_widget->selectedItems();
if (items.empty()) return;
std::string name = items.front()->text().toStdString();
COLOR_MAP.erase(name);
set_ui_to_config(*m_config); // Re-populate all lists
config_changed();
}
void ConfigEditor::on_color_selected() {
auto items = ui->colors_list_widget->selectedItems();
if (items.empty()) return;
load_color_details(items.front()->text().toStdString());
}
void ConfigEditor::load_color_details(const std::string& name) {
if (!COLOR_MAP.count(name)) return;
bool was_updating = m_updating_ui;
m_updating_ui = true;
const auto& color = COLOR_MAP.at(name);
ui->color_name_edit->setText(QString::fromStdString(name));
if (std::holds_alternative<RGBColor>(color)) {
ui->color_type_combo->setCurrentIndex(0); // RGB
const auto& rgb = std::get<RGBColor>(color);
ui->color_c1_spin->setValue(rgb.getRed());
ui->color_c2_spin->setValue(rgb.getGreen());
ui->color_c3_spin->setValue(rgb.getBlue());
ui->color_c4_spin->setValue(rgb.getAlpha());
} else {
ui->color_type_combo->setCurrentIndex(1); // CMYK
const auto& cmyk = std::get<CMYKColor>(color);
ui->color_c1_spin->setValue(cmyk.getCyan());
ui->color_c2_spin->setValue(cmyk.getMagenta());
ui->color_c3_spin->setValue(cmyk.getYellow());
ui->color_c4_spin->setValue(cmyk.getBlack());
}
on_color_type_changed(ui->color_type_combo->currentIndex()); // Update labels
m_updating_ui = was_updating;
}
void ConfigEditor::on_color_type_changed(int index) {
if (m_updating_ui) {
// Just update labels and maximums during loading
if (index == 0) { // RGB
ui->label_c1->setText("Red:");
ui->label_c2->setText("Green:");
ui->label_c3->setText("Blue:");
ui->label_c4->setText("Alpha:");
ui->color_c1_spin->setMaximum(255);
ui->color_c2_spin->setMaximum(255);
ui->color_c3_spin->setMaximum(255);
ui->color_c4_spin->setMaximum(255);
ui->pick_color_button->setEnabled(true);
} else { // CMYK
ui->label_c1->setText("Cyan:");
ui->label_c2->setText("Magenta:");
ui->label_c3->setText("Yellow:");
ui->label_c4->setText("Black:");
ui->color_c1_spin->setMaximum(100);
ui->color_c2_spin->setMaximum(100);
ui->color_c3_spin->setMaximum(100);
ui->color_c4_spin->setMaximum(100);
ui->pick_color_button->setEnabled(false);
}
return;
}
// Capture "old" values for conversion
int v1 = ui->color_c1_spin->value();
int v2 = ui->color_c2_spin->value();
int v3 = ui->color_c3_spin->value();
int v4 = ui->color_c4_spin->value();
m_updating_ui = true;
if (index == 0) { // Switching TO RGB (from CMYK)
CMYKColor cmyk(v1, v2, v3, v4);
RGBColor rgb = RGBColor::FromCMYK(cmyk);
ui->label_c1->setText("Red:");
ui->label_c2->setText("Green:");
ui->label_c3->setText("Blue:");
ui->label_c4->setText("Alpha:");
ui->color_c1_spin->setMaximum(255);
ui->color_c2_spin->setMaximum(255);
ui->color_c3_spin->setMaximum(255);
ui->color_c4_spin->setMaximum(255);
ui->color_c1_spin->setValue(rgb.getRed());
ui->color_c2_spin->setValue(rgb.getGreen());
ui->color_c3_spin->setValue(rgb.getBlue());
ui->color_c4_spin->setValue(rgb.getAlpha());
ui->pick_color_button->setEnabled(true);
} else { // Switching TO CMYK (from RGB)
RGBColor rgb(v1, v2, v3, v4);
CMYKColor cmyk = CMYKColor::FromRGB(rgb);
ui->label_c1->setText("Cyan:");
ui->label_c2->setText("Magenta:");
ui->label_c3->setText("Yellow:");
ui->label_c4->setText("Black:");
ui->color_c1_spin->setMaximum(100);
ui->color_c2_spin->setMaximum(100);
ui->color_c3_spin->setMaximum(100);
ui->color_c4_spin->setMaximum(100);
ui->color_c1_spin->setValue(cmyk.getCyan());
ui->color_c2_spin->setValue(cmyk.getMagenta());
ui->color_c3_spin->setValue(cmyk.getYellow());
ui->color_c4_spin->setValue(cmyk.getBlack());
ui->pick_color_button->setEnabled(false);
}
m_updating_ui = false;
update_color_from_ui();
}
void ConfigEditor::update_color_from_ui() {
if (m_updating_ui) return;
auto items = ui->colors_list_widget->selectedItems();
if (items.empty()) return;
std::string old_name = items.front()->text().toStdString();
std::string new_name = ui->color_name_edit->text().toStdString();
ColorVariant new_color;
if (ui->color_type_combo->currentIndex() == 0) { // RGB
new_color = RGBColor(ui->color_c1_spin->value(), ui->color_c2_spin->value(),
ui->color_c3_spin->value(), ui->color_c4_spin->value());
} else { // CMYK
new_color = CMYKColor(ui->color_c1_spin->value(), ui->color_c2_spin->value(),
ui->color_c3_spin->value(), ui->color_c4_spin->value());
}
if (old_name != new_name && !new_name.empty()) {
if (!COLOR_MAP.count(new_name)) {
COLOR_MAP.erase(old_name);
COLOR_MAP[new_name] = new_color;
items.front()->setText(QString::fromStdString(new_name));
set_ui_to_config(*m_config); // Name changed, refresh combos
}
} else {
COLOR_MAP[old_name] = new_color;
// Color value changed, refresh icons?
// Yes, need to refresh UI to show new color icons
set_ui_to_config(*m_config);
}
config_changed();
}
void ConfigEditor::pick_color() {
QColor color =
QColorDialog::getColor(Qt::white, this, "Pick Color", QColorDialog::ShowAlphaChannel);
if (color.isValid()) {
ui->color_type_combo->setCurrentIndex(0); // Ensure RGB
ui->color_c1_spin->setValue(color.red());
ui->color_c2_spin->setValue(color.green());
ui->color_c3_spin->setValue(color.blue());
ui->color_c4_spin->setValue(color.alpha());
// update_color_from_ui called by value changes
}
}