255 lines
10 KiB
C++
255 lines
10 KiB
C++
#include "ContourLevelDialog.hpp"
|
||
|
||
#include <cmath>
|
||
|
||
#include <QComboBox>
|
||
#include <QDialogButtonBox>
|
||
#include <QDoubleValidator>
|
||
#include <QFormLayout>
|
||
#include <QHBoxLayout>
|
||
#include <QLabel>
|
||
#include <QLineEdit>
|
||
#include <QLocale>
|
||
#include <QMessageBox>
|
||
#include <QPushButton>
|
||
#include <QVBoxLayout>
|
||
#include <QWidget>
|
||
|
||
namespace geopro::app {
|
||
|
||
namespace {
|
||
constexpr int kMaxLayers = 50; // 原版上限:分层层数 ≤ 50
|
||
} // namespace
|
||
|
||
ContourLevelDialog::ContourLevelDialog(const ContourLevelParams& init, double originMin,
|
||
double originMax, double totalArea, QWidget* parent)
|
||
: QDialog(parent), originMin_(originMin), originMax_(originMax), totalArea_(totalArea) {
|
||
setWindowTitle(QStringLiteral("等值线层级"));
|
||
setModal(true);
|
||
|
||
auto* root = new QVBoxLayout(this);
|
||
auto* form = new QFormLayout();
|
||
root->addLayout(form);
|
||
|
||
// 数据范围(原始,只读展示)。
|
||
form->addRow(QStringLiteral("数据范围"),
|
||
new QLabel(QStringLiteral("%1 ~ %2").arg(originMin_).arg(originMax_)));
|
||
|
||
// 分层方式。
|
||
methodCombo_ = new QComboBox(this);
|
||
methodCombo_->addItem(QStringLiteral("一般的"), 0);
|
||
methodCombo_->addItem(QStringLiteral("对数"), 1);
|
||
methodCombo_->addItem(QStringLiteral("等积"), 2);
|
||
methodCombo_->setCurrentIndex(static_cast<int>(init.method));
|
||
form->addRow(QStringLiteral("分层方式"), methodCombo_);
|
||
|
||
auto* validator = new QDoubleValidator(this);
|
||
validator->setNotation(QDoubleValidator::StandardNotation);
|
||
validator->setLocale(QLocale::c()); // 锁定 C locale:与 toDouble() 一致,避免中文系统逗号歧义
|
||
|
||
// 最大/最小等值线(equalArea 时整行隐藏)。
|
||
minEdit_ = new QLineEdit(QString::number(init.minValue), this);
|
||
maxEdit_ = new QLineEdit(QString::number(init.maxValue), this);
|
||
minEdit_->setValidator(validator);
|
||
maxEdit_->setValidator(validator);
|
||
rangeRow_ = new QWidget(this);
|
||
auto* rangeForm = new QFormLayout(rangeRow_);
|
||
rangeForm->setContentsMargins(0, 0, 0, 0);
|
||
rangeForm->addRow(QStringLiteral("最大等值线"), maxEdit_);
|
||
rangeForm->addRow(QStringLiteral("最小等值线"), minEdit_);
|
||
root->addWidget(rangeRow_);
|
||
|
||
// normal:间隔数 + 层数(双向联动)。
|
||
intervalEdit_ = new QLineEdit(QString::number(init.interval), this);
|
||
layerCountEdit_ = new QLineEdit(QString::number(init.layerCount), this);
|
||
intervalEdit_->setValidator(validator);
|
||
normalRow_ = new QWidget(this);
|
||
auto* normalForm = new QFormLayout(normalRow_);
|
||
normalForm->setContentsMargins(0, 0, 0, 0);
|
||
normalForm->addRow(QStringLiteral("数值间隔"), intervalEdit_);
|
||
normalForm->addRow(QStringLiteral("层数"), layerCountEdit_);
|
||
root->addWidget(normalRow_);
|
||
|
||
// logarithmic:每数量级次要等值线数。
|
||
logLinesEdit_ = new QLineEdit(QString::number(init.logLinesCount), this);
|
||
logRow_ = new QWidget(this);
|
||
auto* logForm = new QFormLayout(logRow_);
|
||
logForm->setContentsMargins(0, 0, 0, 0);
|
||
logForm->addRow(QStringLiteral("每数量级次要等值线数"), logLinesEdit_);
|
||
root->addWidget(logRow_);
|
||
|
||
// equalArea:等积分层层数 + 区间面积(只读,自动算)。
|
||
equalAreaCountEdit_ = new QLineEdit(QString::number(init.equalAreaLayerCount), this);
|
||
intervalAreaLabel_ = new QLabel(this);
|
||
equalAreaRow_ = new QWidget(this);
|
||
auto* eaForm = new QFormLayout(equalAreaRow_);
|
||
eaForm->setContentsMargins(0, 0, 0, 0);
|
||
eaForm->addRow(QStringLiteral("层数"), equalAreaCountEdit_);
|
||
eaForm->addRow(QStringLiteral("区间面积"), intervalAreaLabel_);
|
||
root->addWidget(equalAreaRow_);
|
||
|
||
auto* reset = new QPushButton(QStringLiteral("恢复默认值"), this);
|
||
auto* resetRow = new QHBoxLayout();
|
||
resetRow->addStretch();
|
||
resetRow->addWidget(reset);
|
||
root->addLayout(resetRow);
|
||
|
||
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||
buttons->button(QDialogButtonBox::Ok)->setText(QStringLiteral("应用"));
|
||
buttons->button(QDialogButtonBox::Cancel)->setText(QStringLiteral("取消"));
|
||
root->addWidget(buttons);
|
||
|
||
// 联动接线(normal 双向;equalArea 区间面积随层数)。
|
||
connect(methodCombo_, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||
[this](int) { onMethodChanged(); });
|
||
connect(intervalEdit_, &QLineEdit::textEdited, this,
|
||
[this](const QString&) { recalcLayerCountFromInterval(); });
|
||
connect(layerCountEdit_, &QLineEdit::textEdited, this,
|
||
[this](const QString&) { recalcIntervalFromLayerCount(); });
|
||
connect(minEdit_, &QLineEdit::textEdited, this,
|
||
[this](const QString&) { recalcLayerCountFromInterval(); });
|
||
connect(maxEdit_, &QLineEdit::textEdited, this,
|
||
[this](const QString&) { recalcLayerCountFromInterval(); });
|
||
connect(equalAreaCountEdit_, &QLineEdit::textEdited, this,
|
||
[this](const QString&) { updateIntervalArea(); });
|
||
connect(reset, &QPushButton::clicked, this, &ContourLevelDialog::onReset);
|
||
connect(buttons, &QDialogButtonBox::accepted, this, &ContourLevelDialog::onAccept);
|
||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||
|
||
updateIntervalArea();
|
||
updateVisibility(); // 初始仅按方式显隐(不重算,保留传入初值)
|
||
}
|
||
|
||
double ContourLevelDialog::parsed(const QLineEdit* e, double fallback) const {
|
||
bool ok = false;
|
||
const double v = e->text().toDouble(&ok);
|
||
return ok ? v : fallback;
|
||
}
|
||
|
||
void ContourLevelDialog::updateVisibility() {
|
||
const int m = methodCombo_->currentIndex();
|
||
const bool equalArea = (m == 2);
|
||
rangeRow_->setVisible(!equalArea); // 最大/最小只在 normal/log 显示
|
||
normalRow_->setVisible(m == 0);
|
||
logRow_->setVisible(m == 1);
|
||
equalAreaRow_->setVisible(equalArea);
|
||
adjustSize();
|
||
}
|
||
|
||
void ContourLevelDialog::onMethodChanged() {
|
||
updateVisibility();
|
||
if (methodCombo_->currentIndex() == 0)
|
||
recalcIntervalFromLayerCount(); // 切回一般时按层数刷间隔(复刻 watch)
|
||
}
|
||
|
||
void ContourLevelDialog::recalcLayerCountFromInterval() {
|
||
if (autoUpdating_ || methodCombo_->currentIndex() != 0) return;
|
||
const double mn = parsed(minEdit_, NAN), mx = parsed(maxEdit_, NAN);
|
||
const double iv = parsed(intervalEdit_, NAN);
|
||
if (std::isfinite(mn) && std::isfinite(mx) && std::isfinite(iv) && iv > 0 && mx > mn) {
|
||
autoUpdating_ = true;
|
||
layerCountEdit_->setText(QString::number(static_cast<int>(std::ceil((mx - mn) / iv))));
|
||
autoUpdating_ = false;
|
||
}
|
||
}
|
||
|
||
void ContourLevelDialog::recalcIntervalFromLayerCount() {
|
||
if (autoUpdating_ || methodCombo_->currentIndex() != 0) return;
|
||
const double mn = parsed(minEdit_, NAN), mx = parsed(maxEdit_, NAN);
|
||
bool ok = false;
|
||
const int cnt = layerCountEdit_->text().toInt(&ok);
|
||
if (std::isfinite(mn) && std::isfinite(mx) && ok && cnt > 0 && mx > mn) {
|
||
autoUpdating_ = true;
|
||
intervalEdit_->setText(QString::number((mx - mn) / cnt, 'f', 2));
|
||
autoUpdating_ = false;
|
||
}
|
||
}
|
||
|
||
void ContourLevelDialog::updateIntervalArea() {
|
||
bool ok = false;
|
||
const int cnt = equalAreaCountEdit_->text().toInt(&ok);
|
||
if (ok && cnt > 0)
|
||
intervalAreaLabel_->setText(QString::number(totalArea_ / cnt, 'f', 2));
|
||
else
|
||
intervalAreaLabel_->setText(QStringLiteral("0"));
|
||
}
|
||
|
||
void ContourLevelDialog::onReset() {
|
||
// 复刻 handleReset:统一回 normal + 原始范围 + 间隔=(max-min)/20。
|
||
methodCombo_->setCurrentIndex(0);
|
||
minEdit_->setText(QString::number(originMin_));
|
||
maxEdit_->setText(QString::number(originMax_));
|
||
// 常值场(max==min)默认间隔取 1,避免间隔=0 导致"恢复默认→应用即报错"。
|
||
const double span = originMax_ - originMin_;
|
||
intervalEdit_->setText(QString::number(span > 0 ? span / 20.0 : 1.0, 'f', 2));
|
||
logLinesEdit_->setText(QStringLiteral("8"));
|
||
equalAreaCountEdit_->setText(QStringLiteral("10"));
|
||
recalcLayerCountFromInterval();
|
||
updateIntervalArea();
|
||
}
|
||
|
||
void ContourLevelDialog::onAccept() {
|
||
const int m = methodCombo_->currentIndex();
|
||
const double mn = parsed(minEdit_, NAN), mx = parsed(maxEdit_, NAN);
|
||
|
||
if (m != 2 && !(mx > mn)) {
|
||
QMessageBox::warning(this, QStringLiteral("等值线层级"),
|
||
QStringLiteral("最大值必须大于最小值"));
|
||
return;
|
||
}
|
||
ContourLevelParams r;
|
||
r.method = static_cast<ContourLevelParams::Method>(m);
|
||
r.minValue = mn;
|
||
r.maxValue = mx;
|
||
|
||
if (m == 0) { // normal
|
||
const double iv = parsed(intervalEdit_, NAN);
|
||
if (!(iv > 0)) {
|
||
QMessageBox::warning(this, QStringLiteral("等值线层级"),
|
||
QStringLiteral("数据间隔必须大于0"));
|
||
return;
|
||
}
|
||
const int layers = static_cast<int>(std::ceil((mx - mn) / iv));
|
||
if (layers > kMaxLayers) {
|
||
QMessageBox::warning(this, QStringLiteral("等值线层级"),
|
||
QStringLiteral("数据间隔设置过小,分层层数不能超过50"));
|
||
return;
|
||
}
|
||
r.interval = iv;
|
||
r.layerCount = layers;
|
||
} else if (m == 1) { // logarithmic
|
||
bool ok = false;
|
||
const int lc = logLinesEdit_->text().toInt(&ok);
|
||
if (!ok || lc <= 0) {
|
||
QMessageBox::warning(this, QStringLiteral("等值线层级"),
|
||
QStringLiteral("每数量级次要等值线数必须大于0"));
|
||
return;
|
||
}
|
||
if (!(mx > 0)) {
|
||
QMessageBox::warning(this, QStringLiteral("等值线层级"),
|
||
QStringLiteral("对数分层方式下,最大等值线必须大于0"));
|
||
return;
|
||
}
|
||
if (mn < 0) {
|
||
QMessageBox::warning(this, QStringLiteral("等值线层级"),
|
||
QStringLiteral("对数分层方式下,最小等值线必须大于0"));
|
||
return;
|
||
}
|
||
r.logLinesCount = lc;
|
||
} else { // equalArea
|
||
bool ok = false;
|
||
const int cnt = equalAreaCountEdit_->text().toInt(&ok);
|
||
if (!ok || cnt <= 0) {
|
||
QMessageBox::warning(this, QStringLiteral("等值线层级"),
|
||
QStringLiteral("层数必须大于0"));
|
||
return;
|
||
}
|
||
r.equalAreaLayerCount = cnt;
|
||
}
|
||
|
||
result_ = r;
|
||
accept();
|
||
}
|
||
|
||
} // namespace geopro::app
|