337 lines
14 KiB
C++
337 lines
14 KiB
C++
#include "panels/chart/GridWizardDialog.hpp"
|
||
|
||
#include <cmath>
|
||
#include <utility>
|
||
|
||
#include <QComboBox>
|
||
|
||
#include "EmptyAwareComboBox.hpp"
|
||
#include <QDoubleSpinBox>
|
||
#include <QFrame>
|
||
#include <QGridLayout>
|
||
#include <QHBoxLayout>
|
||
#include <QLabel>
|
||
#include <QListWidget>
|
||
#include <QMessageBox>
|
||
#include <QPointer>
|
||
#include <QPushButton>
|
||
#include <QSpinBox>
|
||
#include <QStackedWidget>
|
||
#include <QVBoxLayout>
|
||
|
||
#include "Theme.hpp"
|
||
#include "panels/chart/InversionProcessOps.hpp" // buildGridToBody
|
||
#include "repo/IDatasetCommandRepository.hpp"
|
||
|
||
namespace geopro::app {
|
||
|
||
namespace {
|
||
constexpr double kCoordRange = 1e9; // 坐标范围(足够宽)
|
||
constexpr int kSizeMin = 1, kSizeMax = 300; // 点数范围(对照原版 1~300)
|
||
constexpr int kDefaultXSize = 100; // 默认点数(原版 xPoints 默认 100)
|
||
constexpr int kStep1W = 500, kStep2W = 800; // 原版步骤 1/2 弹窗宽
|
||
constexpr int kParamLabelW = 60; // 原版 .param-group label min-width:60px
|
||
constexpr int kParamFieldW = 100; // 原版输入框宽 100px
|
||
|
||
// 配一个坐标 spinbox(6 位小数,宽范围)。
|
||
QDoubleSpinBox* makeCoordSpin(QWidget* parent) {
|
||
auto* sp = new QDoubleSpinBox(parent);
|
||
sp->setRange(-kCoordRange, kCoordRange);
|
||
sp->setDecimals(6);
|
||
sp->setFixedWidth(kParamFieldW);
|
||
return sp;
|
||
}
|
||
|
||
// 分组卡片标题(原版 .section-title:14px 半粗 + 标题下 1px divider)。
|
||
void addSectionTitle(QVBoxLayout* into, const QString& title, QWidget* parent) {
|
||
auto* lbl = new QLabel(title, parent);
|
||
auto f = lbl->font();
|
||
f.setBold(true);
|
||
lbl->setFont(f);
|
||
into->addWidget(lbl);
|
||
auto* line = new QFrame(parent);
|
||
line->setFrameShape(QFrame::HLine);
|
||
line->setFrameShadow(QFrame::Plain);
|
||
into->addWidget(line);
|
||
}
|
||
|
||
// 原版 .param-group:定宽右标签 + 紧随输入框,多个并排成一行栅格。
|
||
void addParamCell(QGridLayout* grid, int row, int col, const QString& label, QWidget* field,
|
||
QWidget* parent) {
|
||
auto* cell = new QHBoxLayout();
|
||
cell->setSpacing(geopro::app::space::kSm);
|
||
auto* lbl = new QLabel(label, parent);
|
||
lbl->setMinimumWidth(kParamLabelW);
|
||
lbl->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||
cell->addWidget(lbl);
|
||
cell->addWidget(field);
|
||
grid->addLayout(cell, row, col);
|
||
}
|
||
} // namespace
|
||
|
||
GridWizardDialog::GridWizardDialog(geopro::data::IDatasetCommandRepository* repo, QString dsId,
|
||
QWidget* parent)
|
||
: QDialog(parent), repo_(repo), dsId_(std::move(dsId)) {
|
||
setWindowTitle(QStringLiteral("网格配置")); // 原版 gridSetting
|
||
setModal(true);
|
||
setFixedWidth(kStep1W);
|
||
|
||
auto* root = new QVBoxLayout(this);
|
||
root->setContentsMargins(geopro::app::space::kLg, geopro::app::space::kLg,
|
||
geopro::app::space::kLg, geopro::app::space::kLg);
|
||
root->setSpacing(geopro::app::space::kMd);
|
||
stack_ = new QStackedWidget(this);
|
||
root->addWidget(stack_, 1);
|
||
|
||
buildStep1();
|
||
buildStep2();
|
||
|
||
// ── 底部按钮(上一步 / 确认(主) / 取消,原版步骤 2 三按钮)────────────
|
||
auto* btnLay = new QHBoxLayout();
|
||
btnLay->setSpacing(geopro::app::space::kMd);
|
||
btnLay->addStretch();
|
||
prevBtn_ = new QPushButton(QStringLiteral("上一步"), this);
|
||
okBtn_ = new QPushButton(QStringLiteral("确认"), this);
|
||
okBtn_->setDefault(true);
|
||
nextBtn_ = new QPushButton(QStringLiteral("下一步"), this);
|
||
auto* cancelBtn = new QPushButton(QStringLiteral("取消"), this);
|
||
btnLay->addWidget(prevBtn_);
|
||
btnLay->addWidget(okBtn_);
|
||
btnLay->addWidget(nextBtn_);
|
||
btnLay->addWidget(cancelBtn);
|
||
root->addLayout(btnLay);
|
||
prevBtn_->setVisible(false);
|
||
okBtn_->setVisible(false);
|
||
|
||
connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
|
||
connect(nextBtn_, &QPushButton::clicked, this, &GridWizardDialog::goToStep2);
|
||
connect(prevBtn_, &QPushButton::clicked, this, [this]() {
|
||
stack_->setCurrentIndex(0);
|
||
setFixedWidth(kStep1W);
|
||
prevBtn_->setVisible(false); okBtn_->setVisible(false); nextBtn_->setVisible(true);
|
||
});
|
||
connect(okBtn_, &QPushButton::clicked, this, &GridWizardDialog::onConfirm);
|
||
|
||
loadAlgorithms();
|
||
}
|
||
|
||
void GridWizardDialog::buildStep1() {
|
||
auto* page1 = new QWidget(this);
|
||
auto* p1Lay = new QVBoxLayout(page1);
|
||
p1Lay->setContentsMargins(0, 0, 0, 0);
|
||
p1Lay->setSpacing(geopro::app::space::kMd);
|
||
p1Lay->addWidget(new QLabel(QStringLiteral("请选择网格方法:"), page1));
|
||
algoList_ = new QListWidget(page1);
|
||
p1Lay->addWidget(algoList_, 1);
|
||
stack_->addWidget(page1);
|
||
}
|
||
|
||
void GridWizardDialog::buildStep2() {
|
||
auto* page2 = new QWidget(this);
|
||
auto* p2Lay = new QVBoxLayout(page2);
|
||
p2Lay->setContentsMargins(0, 0, 0, 0);
|
||
p2Lay->setSpacing(geopro::app::space::kLg);
|
||
|
||
xMin_ = makeCoordSpin(page2); xMax_ = makeCoordSpin(page2);
|
||
yMin_ = makeCoordSpin(page2); yMax_ = makeCoordSpin(page2);
|
||
vMin_ = makeCoordSpin(page2); vMax_ = makeCoordSpin(page2);
|
||
xSize_ = new QSpinBox(page2); xSize_->setRange(kSizeMin, kSizeMax);
|
||
xSize_->setValue(kDefaultXSize); xSize_->setFixedWidth(100);
|
||
ySize_ = new QSpinBox(page2); ySize_->setRange(kSizeMin, kSizeMax);
|
||
ySize_->setValue(kDefaultXSize); ySize_->setFixedWidth(100);
|
||
xSpacing_ = makeCoordSpin(page2); xSpacing_->setRange(0.0, kCoordRange);
|
||
ySpacing_ = makeCoordSpin(page2); ySpacing_->setRange(0.0, kCoordRange);
|
||
|
||
// 分组 1:网格参数(栅格:Xmax→Xmin→X间距→X点数 同行;Y 同理)。
|
||
addSectionTitle(p2Lay, QStringLiteral("网格参数"), page2);
|
||
auto* grid = new QGridLayout();
|
||
grid->setHorizontalSpacing(geopro::app::space::kMd);
|
||
grid->setVerticalSpacing(geopro::app::space::kMd);
|
||
addParamCell(grid, 0, 0, QStringLiteral("Xmax"), xMax_, page2);
|
||
addParamCell(grid, 0, 1, QStringLiteral("Xmin"), xMin_, page2);
|
||
addParamCell(grid, 0, 2, QStringLiteral("X间距"), xSpacing_, page2);
|
||
addParamCell(grid, 0, 3, QStringLiteral("X点数"), xSize_, page2);
|
||
addParamCell(grid, 1, 0, QStringLiteral("Ymax"), yMax_, page2);
|
||
addParamCell(grid, 1, 1, QStringLiteral("Ymin"), yMin_, page2);
|
||
addParamCell(grid, 1, 2, QStringLiteral("Y间距"), ySpacing_, page2);
|
||
addParamCell(grid, 1, 3, QStringLiteral("Y点数"), ySize_, page2);
|
||
grid->setColumnStretch(4, 1);
|
||
p2Lay->addLayout(grid);
|
||
|
||
// 分组 2:数据值设置(数据值max/min + 恢复默认值 + 数据值保存为)。
|
||
addSectionTitle(p2Lay, QStringLiteral("数据值设置"), page2);
|
||
saveFormat_ = new EmptyAwareComboBox(page2);
|
||
saveFormat_->addItem(QStringLiteral("线性"), QStringLiteral("linear"));
|
||
saveFormat_->addItem(QStringLiteral("对数"), QStringLiteral("log"));
|
||
saveFormat_->setFixedWidth(120);
|
||
auto* restoreBtn = new QPushButton(QStringLiteral("恢复默认值"), page2);
|
||
|
||
auto* dv = new QGridLayout();
|
||
dv->setHorizontalSpacing(geopro::app::space::kLg);
|
||
dv->setVerticalSpacing(geopro::app::space::kMd);
|
||
auto* vmaxLbl = new QLabel(QStringLiteral("数据值max"), page2);
|
||
vmaxLbl->setMinimumWidth(kParamLabelW + 20);
|
||
vmaxLbl->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||
dv->addWidget(vmaxLbl, 0, 0);
|
||
dv->addWidget(vMax_, 0, 1);
|
||
dv->addWidget(restoreBtn, 0, 3);
|
||
auto* vminLbl = new QLabel(QStringLiteral("数据值min"), page2);
|
||
vminLbl->setMinimumWidth(kParamLabelW + 20);
|
||
vminLbl->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||
dv->addWidget(vminLbl, 1, 0);
|
||
dv->addWidget(vMin_, 1, 1);
|
||
auto* fmtLbl = new QLabel(QStringLiteral("数据值保存为"), page2); // 原版 saveFormat
|
||
fmtLbl->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||
dv->addWidget(fmtLbl, 1, 2);
|
||
dv->addWidget(saveFormat_, 1, 3);
|
||
dv->setColumnStretch(4, 1);
|
||
p2Lay->addLayout(dv);
|
||
p2Lay->addStretch();
|
||
stack_->addWidget(page2);
|
||
|
||
// 联动:Xmax/Xmin/X点数变 → 反算 X间距(round);X间距变 → 反算 X点数(round)。
|
||
connect(xMax_, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
|
||
[this](double) { calcXInterval(); });
|
||
connect(xMin_, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
|
||
[this](double) { calcXInterval(); });
|
||
connect(xSize_, QOverload<int>::of(&QSpinBox::valueChanged), this,
|
||
[this](int) { calcXInterval(); });
|
||
connect(xSpacing_, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
|
||
[this](double) { calcXPoints(); });
|
||
connect(yMax_, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
|
||
[this](double) { calcYInterval(); });
|
||
connect(yMin_, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
|
||
[this](double) { calcYInterval(); });
|
||
connect(ySize_, QOverload<int>::of(&QSpinBox::valueChanged), this,
|
||
[this](int) { calcYInterval(); });
|
||
connect(ySpacing_, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
|
||
[this](double) { calcYPoints(); });
|
||
connect(restoreBtn, &QPushButton::clicked, this, &GridWizardDialog::loadParams);
|
||
}
|
||
|
||
void GridWizardDialog::loadAlgorithms() {
|
||
if (!repo_) return;
|
||
QPointer<GridWizardDialog> self(this);
|
||
repo_->listGridAlgorithm(dsId_, [self](bool ok, QJsonArray list, QString) {
|
||
if (!self || !ok) return;
|
||
self->algoList_->clear();
|
||
for (const QJsonValue& v : list) {
|
||
const QJsonObject o = v.toObject();
|
||
const QString name = o.value(QStringLiteral("scriptName")).toString();
|
||
const QString code = o.value(QStringLiteral("scriptCode")).toString();
|
||
auto* item = new QListWidgetItem(name, self->algoList_);
|
||
item->setData(Qt::UserRole, code);
|
||
}
|
||
if (self->algoList_->count() > 0) self->algoList_->setCurrentRow(0); // 默认首项
|
||
});
|
||
}
|
||
|
||
void GridWizardDialog::goToStep2() {
|
||
if (!algoList_->currentItem()) {
|
||
QMessageBox::warning(this, windowTitle(), QStringLiteral("请选择网格方法"));
|
||
return;
|
||
}
|
||
stack_->setCurrentIndex(1);
|
||
setFixedWidth(kStep2W);
|
||
nextBtn_->setVisible(false);
|
||
prevBtn_->setVisible(true);
|
||
okBtn_->setVisible(true);
|
||
loadParams();
|
||
}
|
||
|
||
void GridWizardDialog::loadParams() {
|
||
if (!repo_) return;
|
||
QPointer<GridWizardDialog> self(this);
|
||
repo_->getGridRawDataParams(dsId_, [self](bool ok, QJsonObject d, QString) {
|
||
if (!self || !ok) return;
|
||
self->xMin_->setValue(d.value(QStringLiteral("xmin")).toDouble());
|
||
self->xMax_->setValue(d.value(QStringLiteral("xmax")).toDouble());
|
||
self->yMin_->setValue(d.value(QStringLiteral("ymin")).toDouble());
|
||
self->yMax_->setValue(d.value(QStringLiteral("ymax")).toDouble());
|
||
self->vMin_->setValue(d.value(QStringLiteral("vmin")).toDouble());
|
||
self->vMax_->setValue(d.value(QStringLiteral("vmax")).toDouble());
|
||
// 原版 onMounted:X点数固定 100 → 算 X间距 → Y间距=X间距 → Y点数=ceil。
|
||
self->xSize_->setValue(kDefaultXSize);
|
||
self->calcXInterval();
|
||
const double dx = self->xSpacing_->value();
|
||
if (dx > 0) self->ySpacing_->setValue(dx); // 触发 calcYPoints
|
||
});
|
||
}
|
||
|
||
void GridWizardDialog::calcXInterval() {
|
||
const int n = xSize_->value();
|
||
const double range = xMax_->value() - xMin_->value();
|
||
if (n > 0 && range > 0) {
|
||
QSignalBlocker b(xSpacing_); // 防与 calcXPoints 互触发
|
||
xSpacing_->setValue(range / n);
|
||
}
|
||
}
|
||
|
||
void GridWizardDialog::calcXPoints() {
|
||
const double iv = xSpacing_->value();
|
||
const double range = xMax_->value() - xMin_->value();
|
||
if (iv > 0 && range > 0) {
|
||
QSignalBlocker b(xSize_);
|
||
xSize_->setValue(static_cast<int>(std::lround(range / iv))); // 原版 round
|
||
}
|
||
}
|
||
|
||
void GridWizardDialog::calcYInterval() {
|
||
const int n = ySize_->value();
|
||
const double range = yMax_->value() - yMin_->value();
|
||
if (n > 0 && range > 0) {
|
||
QSignalBlocker b(ySpacing_);
|
||
ySpacing_->setValue(range / n);
|
||
}
|
||
}
|
||
|
||
void GridWizardDialog::calcYPoints() {
|
||
const double iv = ySpacing_->value();
|
||
const double range = yMax_->value() - yMin_->value();
|
||
if (iv > 0 && range > 0) {
|
||
QSignalBlocker b(ySize_);
|
||
ySize_->setValue(static_cast<int>(std::ceil(range / iv))); // 原版 ceil(与 X 的 round 不同)
|
||
}
|
||
}
|
||
|
||
void GridWizardDialog::onConfirm() {
|
||
if (!repo_ || !algoList_->currentItem()) { reject(); return; }
|
||
// 原版按 Xmax/Ymax/数据值分别提示。
|
||
if (xMax_->value() < xMin_->value()) {
|
||
QMessageBox::warning(this, windowTitle(), QStringLiteral("Xmax不能小于Xmin"));
|
||
return;
|
||
}
|
||
if (yMax_->value() < yMin_->value()) {
|
||
QMessageBox::warning(this, windowTitle(), QStringLiteral("Ymax不能小于Ymin"));
|
||
return;
|
||
}
|
||
if (vMax_->value() < vMin_->value()) {
|
||
QMessageBox::warning(this, windowTitle(), QStringLiteral("数据值max不能小于数据值min"));
|
||
return;
|
||
}
|
||
GridToParams p;
|
||
p.dsObjectId = dsId_;
|
||
p.actionCode = algoList_->currentItem()->data(Qt::UserRole).toString();
|
||
p.xMin = xMin_->value(); p.xMax = xMax_->value();
|
||
p.yMin = yMin_->value(); p.yMax = yMax_->value();
|
||
p.vMin = vMin_->value(); p.vMax = vMax_->value();
|
||
p.xSize = xSize_->value(); p.ySize = ySize_->value();
|
||
p.xSpacing = xSpacing_->value(); p.ySpacing = ySpacing_->value();
|
||
p.logFormat = (saveFormat_->currentData().toString() == QStringLiteral("log"));
|
||
|
||
okBtn_->setEnabled(false);
|
||
QPointer<GridWizardDialog> self(this);
|
||
repo_->toGrid(buildGridToBody(p), [self](bool ok, QString msg) {
|
||
if (!self) return;
|
||
self->okBtn_->setEnabled(true);
|
||
if (ok) {
|
||
self->accept();
|
||
} else {
|
||
QMessageBox::warning(self, self->windowTitle(),
|
||
msg.isEmpty() ? QStringLiteral("网格化失败") : msg);
|
||
}
|
||
});
|
||
}
|
||
|
||
} // namespace geopro::app
|