geopro/src/app/ColorScaleConfigDialog.cpp

565 lines
26 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "ColorScaleConfigDialog.hpp"
#include <algorithm>
#include <cmath>
#include <utility>
#include <QCheckBox>
#include <QColor>
#include <QColorDialog>
#include <QDialogButtonBox>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QInputDialog>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QVBoxLayout>
#include <QFile>
#include <QFileDialog>
#include <QMessageBox>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
#include <QPointer>
#include <QRegularExpression>
#include "ColorGradientDialog.hpp"
#include "ColorScaleIO.hpp"
#include "ContourLevelDialog.hpp"
#include "ContourLevels.hpp"
#include "ContourLineDialog.hpp"
#include "repo/IColorTemplateRepository.hpp"
namespace geopro::app {
namespace {
QColor toQColor(const geopro::core::Rgba& c) {
return QColor(c.r, c.g, c.b, c.a);
}
geopro::core::Rgba fromQColor(const QColor& c) {
return geopro::core::Rgba{static_cast<unsigned char>(c.red()),
static_cast<unsigned char>(c.green()),
static_cast<unsigned char>(c.blue()),
static_cast<unsigned char>(c.alpha())};
}
// 两端按比例 t∈[0,1] 线性插值(含 alpha供「新增」取中间色。
geopro::core::Rgba lerp(const geopro::core::Rgba& a, const geopro::core::Rgba& b, double t) {
auto mix = [t](unsigned char x, unsigned char y) {
return static_cast<unsigned char>(x + (y - x) * t + 0.5);
};
return geopro::core::Rgba{mix(a.r, b.r), mix(a.g, b.g), mix(a.b, b.b), mix(a.a, b.a)};
}
// core::Rgba → 颜色串:不透明用 #RRGGBB半透明用 rgba(r,g,b,a∈0..1)(与后端 colorBar 互通)。
QString rgbaToCss(const geopro::core::Rgba& c) {
if (c.a >= 255)
return QStringLiteral("#%1%2%3")
.arg(c.r, 2, 16, QLatin1Char('0'))
.arg(c.g, 2, 16, QLatin1Char('0'))
.arg(c.b, 2, 16, QLatin1Char('0'))
.toUpper();
return QStringLiteral("rgba(%1, %2, %3, %4)")
.arg(c.r)
.arg(c.g)
.arg(c.b)
.arg(QString::number(c.a / 255.0, 'g', 3));
}
// 颜色串 → core::Rgba支持 #RRGGBB / #AARRGGBB / rgb()/rgba()alpha 0..1/命名黑。
geopro::core::Rgba parseCssColor(const QString& s) {
const QString t = s.trimmed();
if (t.startsWith('#')) {
const QColor q(t); // QColor 识别 #RRGGBB / #AARRGGBB
if (q.isValid()) return fromQColor(q);
}
static const QRegularExpression re(
QStringLiteral("rgba?\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*(?:,\\s*([0-9.]+))?\\)"),
QRegularExpression::CaseInsensitiveOption);
const auto m = re.match(t);
if (m.hasMatch()) {
const int r = m.captured(1).toInt();
const int g = m.captured(2).toInt();
const int b = m.captured(3).toInt();
double a = m.captured(4).isEmpty() ? 1.0 : m.captured(4).toDouble();
if (a > 1.0) a = a / 255.0; // 容错:偶有 0..255 alpha
return geopro::core::Rgba{static_cast<unsigned char>(r), static_cast<unsigned char>(g),
static_cast<unsigned char>(b),
static_cast<unsigned char>(a * 255.0 + 0.5)};
}
return geopro::core::Rgba{0, 0, 0, 255};
}
} // namespace
ColorScaleConfigDialog::ColorScaleConfigDialog(const geopro::core::ColorScale& init, double vmin,
double vmax, std::vector<double> samples,
const ContourLineConfig& lineInit,
geopro::data::IColorTemplateRepository* tplRepo,
QString projectId, QString lvlTemplateId,
QWidget* parent)
: QDialog(parent),
vmin_(vmin),
vmax_(vmax),
samples_(std::move(samples)),
lineCfg_(lineInit),
tplRepo_(tplRepo),
projectId_(std::move(projectId)),
lvlTemplateId_(std::move(lvlTemplateId)) {
setWindowTitle(QStringLiteral("色阶配置"));
setModal(true);
resize(560, 420);
// 用初始色阶的升序断点填模型;空色阶兜底成 vmin/vmax 两端蓝红。
globalOpacity_ = init.globalOpacity(); // 回显真实整体透明度(两级第二级),不再硬编码 1
for (const auto& [value, color] : init.stops()) rows_.push_back({value, color});
if (rows_.empty()) {
rows_.push_back({vmin_, geopro::core::Rgba{0, 0, 255, 255}});
rows_.push_back({vmax_, geopro::core::Rgba{255, 0, 0, 255}});
}
auto* root = new QVBoxLayout(this);
auto* mid = new QHBoxLayout();
root->addLayout(mid, 1);
// 左:三列表格(层级 / 线形 / 颜色),每列表头带 ⚙,点击表头打开对应子对话框。
table_ = new QTableWidget(this);
table_->setColumnCount(3);
table_->setHorizontalHeaderLabels(
{QStringLiteral("层级 ⚙"), QStringLiteral("线形 ⚙"), QStringLiteral("颜色 ⚙")});
table_->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
table_->horizontalHeader()->setSectionsClickable(true);
table_->verticalHeader()->setVisible(false);
table_->setSortingEnabled(false);
table_->setSelectionBehavior(QAbstractItemView::SelectRows);
table_->setSelectionMode(QAbstractItemView::SingleSelection);
table_->setEditTriggers(QAbstractItemView::NoEditTriggers); // 改值/改色走双击
connect(table_, &QTableWidget::cellDoubleClicked, this,
&ColorScaleConfigDialog::onCellDoubleClicked);
connect(table_->horizontalHeader(), &QHeaderView::sectionClicked, this, [this](int section) {
if (section == 0)
onLevelScheme();
else if (section == 1)
onLineScheme();
else
onColorScheme();
});
mid->addWidget(table_, 1);
// 右:竖排按钮 新增 / 删除 / 另存为 / 导出 / 导入 / 打开(复刻 colorLevel.vue 操作列)。
auto* rightCol = new QVBoxLayout();
auto* btnAdd = new QPushButton(QStringLiteral("新增"), this);
auto* btnDel = new QPushButton(QStringLiteral("删除"), this);
btnSaveOther_ = new QPushButton(QStringLiteral("另存"), this);
auto* btnExport = new QPushButton(QStringLiteral("导出"), this);
auto* btnImport = new QPushButton(QStringLiteral("导入"), this);
btnOpen_ = new QPushButton(QStringLiteral("打开"), this);
connect(btnAdd, &QPushButton::clicked, this, &ColorScaleConfigDialog::onAdd);
connect(btnDel, &QPushButton::clicked, this, &ColorScaleConfigDialog::onRemove);
connect(btnSaveOther_, &QPushButton::clicked, this, &ColorScaleConfigDialog::onSaveOther);
connect(btnExport, &QPushButton::clicked, this, &ColorScaleConfigDialog::onExportLvl);
connect(btnImport, &QPushButton::clicked, this, &ColorScaleConfigDialog::onImportLvl);
connect(btnOpen_, &QPushButton::clicked, this, &ColorScaleConfigDialog::onOpen);
for (auto* b : {btnAdd, btnDel, btnSaveOther_, btnExport, btnImport, btnOpen_})
rightCol->addWidget(b);
rightCol->addStretch();
mid->addLayout(rightCol);
// 「另存为 / 打开」依赖后端 lvl 模板库(走仓储),无仓储/无项目时禁用。
const bool hasBackend = tplRepo_ != nullptr && !projectId_.isEmpty();
btnSaveOther_->setEnabled(hasBackend);
btnOpen_->setEnabled(hasBackend);
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
buttons->button(QDialogButtonBox::Ok)->setText(QStringLiteral("应用"));
buttons->button(QDialogButtonBox::Cancel)->setText(QStringLiteral("取消"));
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
root->addWidget(buttons);
rebuildTable();
}
void ColorScaleConfigDialog::rebuildTable() {
const int n = static_cast<int>(rows_.size());
table_->setRowCount(n);
const QString solid = QStringLiteral("——————");
const QString dashed = QStringLiteral("- - - - - - - - -");
const QColor lineQc = toQColor(lineCfg_.lineColor);
// 升序显示:低值在上(复刻原版 tableData 自然数组序)。
for (int r = 0; r < n; ++r) {
const Row& row = rows_[static_cast<std::size_t>(r)];
auto* valItem = new QTableWidgetItem(QString::number(row.value, 'g', 6));
valItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
table_->setItem(r, 0, valItem);
auto* lineItem = new QTableWidgetItem(lineCfg_.dashed ? dashed : solid);
lineItem->setForeground(lineQc);
lineItem->setTextAlignment(Qt::AlignCenter);
table_->setItem(r, 1, lineItem);
auto* colItem = new QTableWidgetItem();
colItem->setBackground(toQColor(row.color));
table_->setItem(r, 2, colItem);
}
}
int ColorScaleConfigDialog::selectedModelIndex() const {
const int r = table_->currentRow();
if (r < 0 || r >= static_cast<int>(rows_.size())) return -1;
return r; // 升序显示,行号即模型下标
}
void ColorScaleConfigDialog::onCellDoubleClicked(int row, int col) {
if (row < 0 || row >= static_cast<int>(rows_.size())) return;
const int idx = row;
if (col == 0) { // 改层级值(复刻 handleLevelDblClick
bool ok = false;
const double v = QInputDialog::getDouble(this, QStringLiteral("修改层级值"),
QStringLiteral("数据值"), rows_[idx].value, -1e12,
1e12, 6, &ok);
if (!ok) return;
const geopro::core::Rgba color = rows_[idx].color;
rows_[idx].value = v;
std::sort(rows_.begin(), rows_.end(),
[](const Row& a, const Row& b) { return a.value < b.value; });
rebuildTable();
for (int i = 0; i < static_cast<int>(rows_.size()); ++i) {
const Row& ri = rows_[static_cast<std::size_t>(i)];
if (ri.value == v && ri.color.r == color.r && ri.color.g == color.g &&
ri.color.b == color.b && ri.color.a == color.a) {
table_->selectRow(i);
break;
}
}
} else if (col == 2) { // 改颜色(复刻 handleColorDblClick
const QColor cur = toQColor(rows_[idx].color);
const QColor picked = QColorDialog::getColor(cur, this, QStringLiteral("选择颜色"),
QColorDialog::ShowAlphaChannel);
if (!picked.isValid()) return;
rows_[idx].color = fromQColor(picked);
rebuildTable();
table_->selectRow(row);
}
// 线形列col==1双击无动作复刻原版线形改动走表头 ⚙)。
}
void ColorScaleConfigDialog::onAdd() {
// 复刻 handleAdd选中行上方插入中点断点未选中则提示。
const int idx = selectedModelIndex();
if (idx < 0) {
QMessageBox::warning(this, QStringLiteral("新增"), QStringLiteral("请先选择要插入的行。"));
return;
}
const Row& sel = rows_[static_cast<std::size_t>(idx)];
double newLevel = sel.value;
if (idx > 0) // 升序:上一行(idx-1)为更低值,取两者中点
newLevel = (rows_[static_cast<std::size_t>(idx - 1)].value + sel.value) / 2.0;
rows_.insert(rows_.begin() + idx, Row{newLevel, sel.color});
rebuildTable();
table_->selectRow(idx); // 选中新插入行
}
void ColorScaleConfigDialog::onRemove() {
// 复刻 handleDelete未选中提示至少保留 2 行。
const int idx = selectedModelIndex();
if (idx < 0) {
QMessageBox::warning(this, QStringLiteral("删除"), QStringLiteral("请先选择要删除的行。"));
return;
}
if (rows_.size() <= 2) {
QMessageBox::warning(this, QStringLiteral("删除"), QStringLiteral("至少需要保留两行数据。"));
return;
}
rows_.erase(rows_.begin() + idx);
rebuildTable();
table_->clearSelection(); // 复刻 handleDelete删除后清空选中
}
geopro::core::Rgba ColorScaleConfigDialog::interpColor(double value) const {
// 复刻 colorUtils.js mapColors升序断点上钳位 + 找区间 + 线性 RGBA 插值。
if (rows_.empty()) return geopro::core::Rgba{0, 0, 0, 255};
const double ysMin = rows_.front().value, ysMax = rows_.back().value;
if (value <= ysMin) return rows_.front().color;
if (value >= ysMax) return rows_.back().color;
std::size_t i = 0;
while (i + 1 < rows_.size() && value > rows_[i + 1].value) ++i;
const double x0 = rows_[i].value, x1 = rows_[i + 1].value;
const double ratio = (x1 > x0) ? (value - x0) / (x1 - x0) : 0.0;
return lerp(rows_[i].color, rows_[i + 1].color, ratio);
}
void ColorScaleConfigDialog::onLevelScheme() {
// 由当前断点推导 contourLevel 初值(复刻 colorLevel.vue case 'level')。
ContourLevelParams init;
init.method = ContourLevelParams::Method::Normal;
if (lvlSchemeType_ == QStringLiteral("logarithmic"))
init.method = ContourLevelParams::Method::Logarithmic;
else if (lvlSchemeType_ == QStringLiteral("equalArea"))
init.method = ContourLevelParams::Method::EqualArea;
init.minValue = rows_.front().value;
init.maxValue = rows_.back().value;
init.layerCount = static_cast<int>(rows_.size());
init.interval =
(rows_.size() >= 2) ? std::abs(rows_[1].value - rows_[0].value) : (vmax_ - vmin_);
init.logLinesCount = logLinesCount_;
init.equalAreaLayerCount = equalAreaLayerCount_;
const double totalArea =
samples_.empty() ? 1000.0 : static_cast<double>(samples_.size()); // 等积「区间面积」分母
ContourLevelDialog dlg(init, vmin_, vmax_, totalArea, this);
if (dlg.exec() != QDialog::Accepted) return;
const ContourLevelParams p = dlg.params();
// 记录方案字段(另存为 properties 透传,复刻原版)。
switch (p.method) {
case ContourLevelParams::Method::Logarithmic:
lvlSchemeType_ = QStringLiteral("logarithmic");
break;
case ContourLevelParams::Method::EqualArea:
lvlSchemeType_ = QStringLiteral("equalArea");
break;
default:
lvlSchemeType_ = QStringLiteral("normal");
break;
}
logLinesCount_ = p.logLinesCount;
equalAreaLayerCount_ = p.equalAreaLayerCount;
// 1) 按分层方式生成新层级(纯算法)。 2) 旧色阶上插值取色mapColors重建表。
const std::vector<double> levels = generateContourLevels(p, samples_);
std::vector<Row> next;
next.reserve(levels.size());
for (double lv : levels) next.push_back({lv, interpColor(lv)});
if (next.size() < 2) return; // 退化保护
std::sort(next.begin(), next.end(),
[](const Row& a, const Row& b) { return a.value < b.value; });
rows_ = std::move(next);
rebuildTable();
}
void ColorScaleConfigDialog::onLineScheme() {
ContourLineDialog dlg(lineCfg_, this);
if (dlg.exec() == QDialog::Accepted) {
lineCfg_ = dlg.config();
rebuildTable(); // 线形列文字/颜色随之刷新
}
}
void ColorScaleConfigDialog::onColorScheme() {
if (rows_.size() < 2) return; // 防御front/back
// 用当前断点归一化位置作渐变初值lo..hi → 0..1)。
const double lo = rows_.front().value, hi = rows_.back().value;
const double span = (hi > lo) ? (hi - lo) : 1.0;
std::vector<GradientEditWidget::Stop> seed;
for (const auto& r : rows_) seed.push_back({(r.value - lo) / span, r.color});
ColorGradientDialog dlg(seed, lo, hi, vmin_, vmax_, samples_, globalOpacity_, tplRepo_, projectId_,
this);
if (dlg.exec() != QDialog::Accepted) return;
const auto grad = dlg.stops();
if (grad.size() < 2) return;
globalOpacity_ = dlg.opacity(); // 两级第二级:整体透明度单独存,不烘焙进每色 alpha
// 在新渐变上按各层级位置连续采样回填颜色(复刻 mapColors含每色自有 alpha
auto sampleGrad = [&](double pos) -> geopro::core::Rgba {
if (pos <= grad.front().pos) return grad.front().color;
if (pos >= grad.back().pos) return grad.back().color;
std::size_t i = 0;
while (i + 1 < grad.size() && pos > grad[i + 1].pos) ++i;
const double x0 = grad[i].pos, x1 = grad[i + 1].pos;
const double t = (x1 > x0) ? (pos - x0) / (x1 - x0) : 0.0;
return lerp(grad[i].color, grad[i + 1].color, t);
};
// 只回填颜色(含每色自有 alpha整体透明度单独存于 globalOpacity_、渲染时才相乘两级
for (auto& r : rows_) r.color = sampleGrad((r.value - lo) / span);
rebuildTable();
}
void ColorScaleConfigDialog::onImportLvl() {
const QString path = QFileDialog::getOpenFileName(this, QStringLiteral("导入 .lvl"), {},
QStringLiteral("色阶层级文件 (*.lvl)"));
if (path.isEmpty()) return;
QFile f(path);
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
QMessageBox::warning(this, QStringLiteral("导入"), QStringLiteral("无法打开文件。"));
return;
}
const std::vector<LvlRow> parsed = parseLvl(f.readAll().toStdString());
if (parsed.size() < 2) {
QMessageBox::warning(this, QStringLiteral("导入"),
QStringLiteral("文件格式不正确或层级不足。"));
return;
}
rows_.clear();
for (const auto& lr : parsed) rows_.push_back({lr.level, lr.color});
std::sort(rows_.begin(), rows_.end(),
[](const Row& a, const Row& b) { return a.value < b.value; });
lineCfg_.dashed = parsed.front().dashed; // 线形从首行带入
lineCfg_.lineColor = parsed.front().lineColor;
rebuildTable();
}
void ColorScaleConfigDialog::onExportLvl() {
const QString path = QFileDialog::getSaveFileName(this, QStringLiteral("导出 .lvl"),
QStringLiteral("等值线配置.lvl"),
QStringLiteral("色阶层级文件 (*.lvl)"));
if (path.isEmpty()) return;
std::vector<LvlRow> out;
for (const auto& r : rows_)
out.push_back({r.value, r.color, lineCfg_.dashed, lineCfg_.lineColor});
QFile f(path);
if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::warning(this, QStringLiteral("导出"), QStringLiteral("无法写入文件。"));
return;
}
const std::string text = generateLvl(out);
if (f.write(text.c_str(), static_cast<qint64>(text.size())) < 0)
QMessageBox::warning(this, QStringLiteral("导出"), QStringLiteral("写入失败。"));
}
void ColorScaleConfigDialog::loadColorBar(
const std::vector<std::pair<double, geopro::core::Rgba>>& bar) {
if (bar.size() < 2) return;
rows_.clear();
for (const auto& [level, color] : bar) rows_.push_back({level, color});
std::sort(rows_.begin(), rows_.end(),
[](const Row& a, const Row& b) { return a.value < b.value; });
rebuildTable();
if (!rows_.empty()) table_->selectRow(0); // 复刻 handleOpen载入后默认选中首行
}
void ColorScaleConfigDialog::onSaveOther() {
if (tplRepo_ == nullptr || projectId_.isEmpty()) return;
// 自定义另存为弹窗(复刻 handleSaveOther名称输入 + 覆盖复选框。
// 「覆盖」仅当有来源模板 idlvlTemplateId_ 非空)时可勾选,对照原版 props.data.lvlTemplateId。
QDialog askDlg(this);
askDlg.setWindowTitle(QStringLiteral("另存模板配置"));
askDlg.setModal(true);
auto* askRoot = new QVBoxLayout(&askDlg);
auto* nameRow = new QHBoxLayout();
nameRow->addWidget(new QLabel(QStringLiteral("模板名称:"), &askDlg));
auto* nameEdit = new QLineEdit(QStringLiteral("等值线配置.lvl"), &askDlg);
nameRow->addWidget(nameEdit, 1);
askRoot->addLayout(nameRow);
auto* overwriteCheck = new QCheckBox(QStringLiteral("覆盖原模板"), &askDlg);
overwriteCheck->setEnabled(!lvlTemplateId_.isEmpty()); // 无来源模板 → 禁用覆盖
askRoot->addWidget(overwriteCheck);
auto* askBtns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &askDlg);
askBtns->button(QDialogButtonBox::Ok)->setText(QStringLiteral("应用"));
askBtns->button(QDialogButtonBox::Cancel)->setText(QStringLiteral("取消"));
connect(askBtns, &QDialogButtonBox::accepted, &askDlg, &QDialog::accept);
connect(askBtns, &QDialogButtonBox::rejected, &askDlg, &QDialog::reject);
askRoot->addWidget(askBtns);
if (askDlg.exec() != QDialog::Accepted) return;
const QString name = nameEdit->text().trimmed();
if (name.isEmpty()) return;
const bool overwrite = overwriteCheck->isChecked() && !lvlTemplateId_.isEmpty();
// 组装 properties复刻 handleSaveOther
QJsonArray colorBar;
for (const auto& r : rows_)
colorBar.append(QJsonArray{QString::number(r.value, 'f', 2), rgbaToCss(r.color)});
QJsonObject lineConfig{{QStringLiteral("showLines"), lineCfg_.lineShow},
{QStringLiteral("color"), rgbaToCss(lineCfg_.lineColor)},
{QStringLiteral("lineType"),
lineCfg_.dashed ? QStringLiteral("dashed") : QStringLiteral("solid")}};
QJsonObject labelConfig{{QStringLiteral("showLabels"), lineCfg_.labelShow},
{QStringLiteral("color"), rgbaToCss(lineCfg_.labelColor)}};
QJsonObject properties{{QStringLiteral("lineConfig"), lineConfig},
{QStringLiteral("labelConfig"), labelConfig},
{QStringLiteral("lvlSchemeType"), lvlSchemeType_},
{QStringLiteral("logLinesCount"), logLinesCount_},
{QStringLiteral("equalAreaLayerCount"), equalAreaLayerCount_},
{QStringLiteral("colorBar"), colorBar}};
// 走仓储传输;回调里用 QPointer 守卫 this模态对话框可能已关
// 勾选覆盖 → PUT 更新来源模板updateLvlTemplate否则 → POST 新建saveLvlTemplate
QPointer<ColorScaleConfigDialog> self(this);
auto onDone = [self, overwrite](bool ok, QString msg) {
if (!self) return;
if (ok)
QMessageBox::information(
self, QStringLiteral("另存"),
overwrite ? QStringLiteral("更新成功。") : QStringLiteral("另存成功。"));
else
QMessageBox::warning(self, QStringLiteral("另存"),
QStringLiteral("另存失败:%1").arg(msg));
};
if (overwrite)
tplRepo_->updateLvlTemplate(lvlTemplateId_, name, properties, std::move(onDone));
else
tplRepo_->saveLvlTemplate(projectId_, name, properties, std::move(onDone));
}
void ColorScaleConfigDialog::onOpen() {
if (tplRepo_ == nullptr || projectId_.isEmpty()) return;
QPointer<ColorScaleConfigDialog> self(this);
tplRepo_->listLvlTemplates(projectId_, [self](bool ok, QJsonArray list, QString msg) {
if (!self) return;
if (!ok) {
QMessageBox::warning(self, QStringLiteral("打开"),
QStringLiteral("获取色阶列表失败:%1").arg(msg));
return;
}
if (list.isEmpty()) {
QMessageBox::information(self, QStringLiteral("打开"),
QStringLiteral("暂无可用色阶模板。"));
return;
}
QStringList names;
for (const auto& it : list)
names << it.toObject().value(QStringLiteral("templateName")).toString();
bool picked = false;
const QString chosen = QInputDialog::getItem(
self, QStringLiteral("引用色阶"), QStringLiteral("请选择色阶:"), names, 0,
false, &picked);
if (!picked) return;
const int sel = names.indexOf(chosen);
if (sel < 0) return;
const QJsonObject props =
list[sel].toObject().value(QStringLiteral("properties")).toObject();
const QJsonArray colorBar = props.value(QStringLiteral("colorBar")).toArray();
std::vector<std::pair<double, geopro::core::Rgba>> bar;
for (const auto& e : colorBar) {
const QJsonArray pair = e.toArray();
if (pair.size() < 2) continue;
bar.emplace_back(pair[0].toVariant().toDouble(),
parseCssColor(pair[1].toString()));
}
if (bar.size() < 2) {
QMessageBox::warning(self, QStringLiteral("打开"),
QStringLiteral("色阶数据无效。"));
return;
}
// 透传方案字段。
self->lvlSchemeType_ =
props.value(QStringLiteral("lvlSchemeType")).toString(QStringLiteral("normal"));
self->logLinesCount_ =
props.value(QStringLiteral("logLinesCount")).toInt(8);
self->equalAreaLayerCount_ =
props.value(QStringLiteral("equalAreaLayerCount")).toInt(10);
const QJsonObject lc = props.value(QStringLiteral("lineConfig")).toObject();
if (!lc.isEmpty()) {
self->lineCfg_.lineShow = lc.value(QStringLiteral("showLines")).toBool(true);
self->lineCfg_.dashed =
lc.value(QStringLiteral("lineType")).toString() == QStringLiteral("dashed");
self->lineCfg_.lineColor =
parseCssColor(lc.value(QStringLiteral("color")).toString());
}
self->loadColorBar(bar);
});
}
geopro::core::ColorScale ColorScaleConfigDialog::colorScale() const {
geopro::core::ColorScale cs;
for (const auto& row : rows_) cs.addStop(row.value, row.color); // 内部按 value 升序
cs.setGlobalOpacity(globalOpacity_); // 整体透明度独立带出(渲染时与每色 alpha 相乘)
return cs;
}
} // namespace geopro::app