#include "panels/chart/GridWizardDialog.hpp" #include #include #include #include "EmptyAwareComboBox.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #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::of(&QDoubleSpinBox::valueChanged), this, [this](double) { calcXInterval(); }); connect(xMin_, QOverload::of(&QDoubleSpinBox::valueChanged), this, [this](double) { calcXInterval(); }); connect(xSize_, QOverload::of(&QSpinBox::valueChanged), this, [this](int) { calcXInterval(); }); connect(xSpacing_, QOverload::of(&QDoubleSpinBox::valueChanged), this, [this](double) { calcXPoints(); }); connect(yMax_, QOverload::of(&QDoubleSpinBox::valueChanged), this, [this](double) { calcYInterval(); }); connect(yMin_, QOverload::of(&QDoubleSpinBox::valueChanged), this, [this](double) { calcYInterval(); }); connect(ySize_, QOverload::of(&QSpinBox::valueChanged), this, [this](int) { calcYInterval(); }); connect(ySpacing_, QOverload::of(&QDoubleSpinBox::valueChanged), this, [this](double) { calcYPoints(); }); connect(restoreBtn, &QPushButton::clicked, this, &GridWizardDialog::loadParams); } void GridWizardDialog::loadAlgorithms() { if (!repo_) return; QPointer 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 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(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(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 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