You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmSubWxs/nmWxForecast.cpp

1334 lines
48 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "nmWxForecast.h"
#include <QApplication>
#include <QStyle>
#include <QSpacerItem>
#include <QSizePolicy>
#include <QLabel>
#include <QMessageBox>
#include <QCloseEvent>
#include <QtCore/qnumeric.h>
#include "nmCalculationDllPebiSolverTask.h"
#include "nmGUIComponentLineEdit.h"
#include "nmPebiResultSnapshot.h"
nmWxForecast::nmWxForecast(QWidget *parent, nmDataAnalyzeManager* manager)
: iDlgBase(parent)
, m_manager(manager ? manager : nmDataAnalyzeManager::getCurrentInstance())
, m_task(NULL)
, m_calculationRunning(false)
, m_initialPressureComponent(nullptr)
, m_abandonmentRateComponent(nullptr)
, m_maximumRateComponent(nullptr)
, m_forecastDurationComponent(nullptr)
, m_producingPressureComponent(nullptr)
, m_producingPressureEndComponent(nullptr)
, m_pressureChangePeriodComponent(nullptr)
, m_pressureTableWidget(nullptr)
, m_pDurationUnitCombo(nullptr)
, m_pPressureUnitCombo(nullptr)
, m_rateTableWidget(nullptr)
, m_rDurationUnitCombo(nullptr)
, m_rRateUnitCombo(nullptr)
, m_extMultiR(nullptr)
, m_extStackedWidget(nullptr)
, m_extEmpty(nullptr)
{
setWindowTitle(tr("Forecast"));
setFixedSize(520, 560);
setAttribute(Qt::WA_DeleteOnClose);
PressureRow tempDefaults;
m_currentDurationUnit = tempDefaults.duration.getUnit();
m_currentPressureUnit = tempDefaults.pressure.getUnit();
FlowRow tempFlowDefaults;
m_currentRateUnit = tempFlowDefaults.q0.getUnit();
// 先构建UI
setupUI();
setupConnections();
// 从数据中心加载数据
loadDataFromManager();
// 根据加载的数据更新界面
updateParametersForOption();
if(m_manager) {
connect(m_manager, SIGNAL(sigPebiResultSnapshotChanged()), this, SLOT(onModelChanged()));
connect(m_manager, SIGNAL(sigPebiResultStateChanged()), this, SLOT(onModelChanged()));
connect(m_manager, SIGNAL(destroyed()), this, SLOT(close()));
}
}
nmWxForecast::~nmWxForecast()
{
// DLL 内部不能强行中断,析构前等待任务退出,避免悬空回调。
if(m_task) {
m_task->requestCancel();
m_task->wait();
delete m_task;
}
}
void nmWxForecast::loadDataFromManager()
{
// 当前先开放分段产量,初始压力默认取已生成模型,输入只保留在内存中。
if(m_manager) m_forecastData = m_manager->getForecastDataCopy();
nmDataAttribute& pressure = m_forecastData.getMultipleRatesData().getInitialPressure();
if(m_manager && m_forecastData.getForecastOption() != nmDataForecast::MultipleRatesOption) {
const QSharedPointer<const nmPebiResultSnapshot> solved = m_manager->getPebiResultSnapshot();
if(!solved.isNull()) {
pressure.setValue(pressure.convertValueByUnitType(
solved->getReservoirParameters().m_dInitialPressure, "MPa", pressure.getUnit()));
}
}
// 本页使用绝对压力,避免通用单位控件把表压当作绝压换算。
pressure.setListUnitSelections(QStringList() << "MPa" << "psia" << "kPa" << "bara");
m_forecastData.setForecastOption(nmDataForecast::MultipleRatesOption);
m_multipleRatesRadio->blockSignals(true);
m_multipleRatesRadio->setChecked(true);
m_multipleRatesRadio->blockSignals(false);
}
void nmWxForecast::saveDataToManager()
{
// 先从界面更新数据
updateDataFromUI();
// 保存到数据中心
nmDataAnalyzeManager* pManager = m_manager.data();
if(pManager) {
pManager->updateForecastData(m_forecastData);
}
}
void nmWxForecast::setupUI()
{
m_mainLayout = new QVBoxLayout(this);
// ========== Forecast options ==========
m_forecastOptionsGroup = new QGroupBox(tr("Forecast options"), this);
m_forecastOptionsLayout = new QVBoxLayout(m_forecastOptionsGroup);
m_constantPressureRadio = new QRadioButton(tr("Constant pressure"), this);
m_decliningPressureRadio = new QRadioButton(tr("Declining pressure (ramp)"), this);
m_multiplePressuresRadio = new QRadioButton(tr("Multiple pressures"), this);
m_multipleRatesRadio = new QRadioButton(tr("Multiple rates"), this);
// 求解器目前仅接通产量控制;压力控制保留入口但不允许生成。
m_constantPressureRadio->setEnabled(false);
m_decliningPressureRadio->setEnabled(false);
m_multiplePressuresRadio->setEnabled(false);
m_forecastButtonGroup = new QButtonGroup(this);
m_forecastButtonGroup->addButton(m_constantPressureRadio, 0);
m_forecastButtonGroup->addButton(m_decliningPressureRadio, 1);
m_forecastButtonGroup->addButton(m_multiplePressuresRadio, 2);
m_forecastButtonGroup->addButton(m_multipleRatesRadio, 3);
m_usePastHistoryCheck = new QCheckBox(tr("Use past history"), this);
m_surfacePressuresCheck = new QCheckBox(tr("Surface pressures"), this);
m_usePastHistoryCheck->setEnabled(false);
m_surfacePressuresCheck->setEnabled(false);
m_forecastOptionsLayout->addWidget(m_constantPressureRadio);
m_forecastOptionsLayout->addWidget(m_decliningPressureRadio);
m_forecastOptionsLayout->addWidget(m_multiplePressuresRadio);
m_forecastOptionsLayout->addWidget(m_multipleRatesRadio);
QHBoxLayout *checkBoxLayout = new QHBoxLayout();
checkBoxLayout->addWidget(m_usePastHistoryCheck);
checkBoxLayout->addWidget(m_surfacePressuresCheck);
checkBoxLayout->addStretch();
m_forecastOptionsLayout->addLayout(checkBoxLayout);
// ========== Parameters ==========
m_parametersGroup = new QGroupBox(tr("Parameters"), this);
QVBoxLayout *parametersOuterLayout = new QVBoxLayout(m_parametersGroup);
parametersOuterLayout->setContentsMargins(6, 6, 6, 6);
// 基础区
QWidget *baseWidget = new QWidget(this);
m_parametersLayout = new QGridLayout(baseWidget);
m_parametersLayout->setHorizontalSpacing(8);
m_parametersLayout->setVerticalSpacing(8);
m_parametersLayout->setColumnStretch(0, 0); // 复选框列
m_parametersLayout->setColumnStretch(1, 1); // 组件列(会自动扩展)
int row = 0;
// 1. 创建一个“占位符” (strut),用于永久撑开第0列
QWidget* column0Strut = new QWidget(this);
// 2. 将占位符添加到 (row 0, col 0)
m_parametersLayout->addWidget(column0Strut, row, 0);
// (row 0) Initial pressure
ConstantPressure& constantPressureData = m_forecastData.getConstantPressureData();
m_initialPressureComponent = new nmGUIComponentLineEdit(
&constantPressureData.getInitialPressure(),
true,
this
);
m_parametersLayout->addWidget(m_initialPressureComponent, row, 1);
row++;
// (row 1) Abandonment rate
m_abandonmentRateCheck = new QCheckBox(this);
m_abandonmentRateComponent = new nmGUIComponentLineEdit(&constantPressureData.getAbandonmentRate(), true, this);
// 创建 QStackedWidget 来包裹 组件
m_abandonmentStack = new QStackedWidget(this);
m_abandonmentSpacer = new QWidget(this);
// 使用组件的高度来设置占位符高度
m_abandonmentSpacer->setFixedHeight(m_abandonmentRateComponent->sizeHint().height());
m_abandonmentStack->addWidget(m_abandonmentRateComponent); // 第 0 页 (组件)
m_abandonmentStack->addWidget(m_abandonmentSpacer); // 第 1 页 (占位符)
m_parametersLayout->addWidget(m_abandonmentRateCheck, row, 0); // 将复选框添加到第 0 列
m_parametersLayout->addWidget(m_abandonmentStack, row, 1); // 将堆栈(内含组件)添加到第 1 列
row++;
// (row 2) Maximum rate
m_maximumRateCheck = new QCheckBox(this);
m_maximumRateComponent = new nmGUIComponentLineEdit(&m_forecastData.getConstantPressureData().getMaximumRate(), true, this);
// 创建 QStackedWidget 来包裹组件
m_maximumStack = new QStackedWidget(this);
m_maximumSpacer = new QWidget(this);
m_maximumSpacer->setFixedHeight(m_maximumRateComponent->sizeHint().height());
m_maximumStack->addWidget(m_maximumRateComponent); // 第 0 页 (组件)
m_maximumStack->addWidget(m_maximumSpacer); // 第 1 页 (占位符)
m_parametersLayout->addWidget(m_maximumRateCheck, row, 0); // 将复选框添加到第 0 列
m_parametersLayout->addWidget(m_maximumStack, row, 1); // 将堆栈(内含组件)添加到第 1 列
row++;
// (row 3) Forecast duration
m_forecastDurationComponent = new nmGUIComponentLineEdit(&m_forecastData.getConstantPressureData().getForecastDuration(), true, this);
m_parametersLayout->addWidget(m_forecastDurationComponent, row, 1);
row++;
// 设置占位符的固定宽度,使其等于复选框的宽度
int chkBoxWidth = m_abandonmentRateCheck->minimumSizeHint().width();
column0Strut->setFixedWidth(chkBoxWidth);
// (row 4) Producing pressure
m_producingPressureComponent = new nmGUIComponentLineEdit(&m_forecastData.getConstantPressureData().getProducingPressure(), true, this);
m_parametersLayout->addWidget(m_producingPressureComponent, row, 1);
row++;
// ========== 扩展区:固定位置添加所有扩展组件 ==========
// --- 1. 先构建所有扩展页面 ---
// (row 5) Ramp 扩展组件 (原row 5)
m_extRamp = buildExtRampPage();
// (row 6) Multiple Pressures 表格 (原row 6)
m_extMultiP = buildExtMultiPressurePage();
m_extMultiP->setMinimumHeight(80);
// (row 7) Multiple Rates 表格
m_extMultiR = buildExtMultiRatePage();
m_extMultiR->setMinimumHeight(80);
// --- 2. 创建一个空页面---
m_extEmpty = buildExtEmptyPage("");
// --- 3. 创建 QStackedWidget 并添加所有页面 ---
m_extStackedWidget = new QStackedWidget(this);
m_extStackedWidget->addWidget(m_extEmpty); // 页面 0
m_extStackedWidget->addWidget(m_extRamp); // 页面 1
m_extStackedWidget->addWidget(m_extMultiP); // 页面 2
m_extStackedWidget->addWidget(m_extMultiR); // 页面 3
// --- 4. 将 QStackedWidget 添加到布局的固定位置 (row 5) ---
m_parametersLayout->addWidget(m_extStackedWidget, row, 1);
row++; // row 变为 6
parametersOuterLayout->addWidget(baseWidget);
//parametersOuterLayout->addWidget(m_extStackedWidget);
// ========== Buttons ==========
m_buttonLayout = new QHBoxLayout();
m_loadDataButton = new QPushButton(tr("Load data"), this);
m_generateButton = new QPushButton(tr("Generate"), this);
m_cancelButton = new QPushButton(tr("Cancel"), this);
m_loadDataButton->setEnabled(false);
m_buttonLayout->addWidget(m_loadDataButton);
m_buttonLayout->addStretch();
m_buttonLayout->addWidget(m_generateButton);
m_buttonLayout->addWidget(m_cancelButton);
// ========== Main ==========
m_mainLayout->addWidget(m_forecastOptionsGroup);
m_mainLayout->addWidget(m_parametersGroup);
m_mainLayout->addStretch();
m_mainLayout->addLayout(m_buttonLayout);
// 收集所有组件
QList<nmGUIComponentLineEdit*> allComponents;
allComponents.append(m_initialPressureComponent);
allComponents.append(m_abandonmentRateComponent);
allComponents.append(m_maximumRateComponent);
allComponents.append(m_forecastDurationComponent);
allComponents.append(m_producingPressureComponent);
allComponents.append(m_producingPressureEndComponent);
allComponents.append(m_pressureChangePeriodComponent);
// 计算最大标签宽度
int maxLabelWidth = 0;
for(int i = 0; i < allComponents.size(); ++i) {
if(allComponents.at(i)) {
// 获取标签控件(假设是第一个子控件)
QLabel* label = allComponents.at(i)->findChild<QLabel*>();
if(label) {
int w = label->sizeHint().width();
if(w > maxLabelWidth) {
maxLabelWidth = w;
}
}
}
}
// 设置统一的标签宽度(+10 像素余量)
for(int i = 0; i < allComponents.size(); ++i) {
if(allComponents.at(i)) {
QLabel* label = allComponents.at(i)->findChild<QLabel*>();
if(label) {
label->setMinimumWidth(maxLabelWidth + 10);
label->setMaximumWidth(maxLabelWidth + 10);
}
}
}
}
QWidget* nmWxForecast::buildExtEmptyPage(const QString& text)
{
QWidget *root = new QWidget(this);
QVBoxLayout *v = new QVBoxLayout(root);
v->setContentsMargins(0, 0, 0, 0);
if(!text.isEmpty()) {
QLabel *lbl = new QLabel(text, root);
lbl->setAlignment(Qt::AlignCenter);
v->addStretch();
v->addWidget(lbl);
v->addStretch();
} else {
v->addSpacing(1);
}
root->setMinimumHeight(80);
return root;
}
QWidget* nmWxForecast::buildExtMultiPressurePage()
{
QWidget *root = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(root);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(8);
// 创建表格
m_pressureTableWidget = new QTableWidget(0, 2);
m_pressureTableWidget->setHorizontalHeaderLabels(QStringList() << tr("Duration") << tr("Pressure"));
m_pressureTableWidget->verticalHeader()->setVisible(false);
m_pressureTableWidget->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
// 表头下的“单位”行
m_pressureTableWidget->insertRow(0);
m_pressureTableWidget->setRowHeight(0, 24); // 设置一个合适的高度
PressureRow tempDefaults; // 用于获取单位列表
QStringList durationUnits = tempDefaults.duration.getListUnitSelections();
QStringList pressureUnits = tempDefaults.pressure.getListUnitSelections();
// --- 第0列:Duration 单位下拉框 ---
m_pDurationUnitCombo = new QComboBox(m_pressureTableWidget);
if(!durationUnits.isEmpty()) {
m_pDurationUnitCombo->addItems(durationUnits);
}
m_pDurationUnitCombo->setCurrentIndex(m_pDurationUnitCombo->findText(m_currentDurationUnit)); // 设置当前单位
// 设置样式
m_pDurationUnitCombo->setEditable(true);
m_pDurationUnitCombo->lineEdit()->setAlignment(Qt::AlignCenter);
m_pDurationUnitCombo->lineEdit()->setReadOnly(true);
m_pDurationUnitCombo->setStyleSheet(
"QComboBox { border: none; background: transparent; padding-left: 4px; }"
"QComboBox::drop-down { border: none; }"
);
m_pressureTableWidget->setCellWidget(0, 0, m_pDurationUnitCombo);
// --- 第1列:Pressure 单位下拉框 ---
m_pPressureUnitCombo = new QComboBox(m_pressureTableWidget);
if(!pressureUnits.isEmpty()) {
m_pPressureUnitCombo->addItems(pressureUnits);
}
m_pPressureUnitCombo->setCurrentIndex(m_pPressureUnitCombo->findText(m_currentPressureUnit)); // 设置当前单位
// 设置样式
m_pPressureUnitCombo->setEditable(true);
m_pPressureUnitCombo->lineEdit()->setAlignment(Qt::AlignCenter);
m_pPressureUnitCombo->lineEdit()->setReadOnly(true);
m_pPressureUnitCombo->setStyleSheet(
"QComboBox { border: none; background: transparent; padding-left: 4px; }"
"QComboBox::drop-down { border: none; }"
);
m_pressureTableWidget->setCellWidget(0, 1, m_pPressureUnitCombo);
layout->addWidget(m_pressureTableWidget);
return root;
}
QTableWidgetItem* nmWxForecast::makeCenteredEmptyItem()
{
QTableWidgetItem* it = new QTableWidgetItem();
it->setTextAlignment(Qt::AlignCenter);
return it;
}
QWidget* nmWxForecast::buildExtMultiRatePage()
{
QWidget *root = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(root);
layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(8);
// 创建表格
m_rateTableWidget = new QTableWidget(0, 2);
// 根据截图设置表头
m_rateTableWidget->setHorizontalHeaderLabels(QStringList() << tr("Duration") << tr("Rate"));
m_rateTableWidget->verticalHeader()->setVisible(false);
m_rateTableWidget->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
// 表头下的“单位”行
m_rateTableWidget->insertRow(0);
m_rateTableWidget->setRowHeight(0, 24);
FlowRow tempDefaults; // 使用 FlowRow 获取单位
QStringList durationUnits = tempDefaults.duration.getListUnitSelections();
QStringList rateUnits = tempDefaults.q0.getListUnitSelections(); // 获取速率单位
// --- 第0列:Duration 单位下拉框 ---
m_rDurationUnitCombo = new QComboBox(m_rateTableWidget);
if(!durationUnits.isEmpty()) {
m_rDurationUnitCombo->addItems(durationUnits);
}
m_rDurationUnitCombo->setCurrentIndex(m_rDurationUnitCombo->findText(m_currentDurationUnit));
m_rDurationUnitCombo->setEditable(true);
m_rDurationUnitCombo->lineEdit()->setAlignment(Qt::AlignCenter);
m_rDurationUnitCombo->lineEdit()->setReadOnly(true);
m_rDurationUnitCombo->setStyleSheet(
"QComboBox { border: none; background: transparent; padding-left: 4px; }"
"QComboBox::drop-down { border: none; }"
);
m_rateTableWidget->setCellWidget(0, 0, m_rDurationUnitCombo);
// --- 第1列:Rate (Qo) 单位下拉框 ---
m_rRateUnitCombo = new QComboBox(m_rateTableWidget);
if(!rateUnits.isEmpty()) {
m_rRateUnitCombo->addItems(rateUnits);
}
m_rRateUnitCombo->setCurrentIndex(m_rRateUnitCombo->findText(m_currentRateUnit)); // 设置当前速率单位
m_rRateUnitCombo->setEditable(true);
m_rRateUnitCombo->lineEdit()->setAlignment(Qt::AlignCenter);
m_rRateUnitCombo->lineEdit()->setReadOnly(true);
m_rRateUnitCombo->setStyleSheet(
"QComboBox { border: none; background: transparent; padding-left: 4px; }"
"QComboBox::drop-down { border: none; }"
);
m_rateTableWidget->setCellWidget(0, 1, m_rRateUnitCombo);
layout->addWidget(m_rateTableWidget);
return root;
}
QWidget* nmWxForecast::buildExtRampPage()
{
QWidget *root = new QWidget(this);
root->setContentsMargins(0, 0, 0, 0);
QVBoxLayout *layout = new QVBoxLayout(root);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(8); // 与基础区一致
// Producing pressure at end
m_producingPressureEndComponent = new nmGUIComponentLineEdit(
&m_forecastData.getDecliningPressureData().getProducingPressureEnd(),
true,
root
);
layout->addWidget(m_producingPressureEndComponent);
// Pressure change period
m_pressureChangePeriodComponent = new nmGUIComponentLineEdit(
&m_forecastData.getDecliningPressureData().getPressureChangePeriod(),
true,
root
);
layout->addWidget(m_pressureChangePeriodComponent);
layout->addStretch(); // 底部弹性空间
root->setMinimumHeight(80);
return root;
}
void nmWxForecast::setupConnections()
{
// Forecast option connections
connect(m_constantPressureRadio, SIGNAL(clicked()), this, SLOT(onConstantPressureSelected()));
connect(m_decliningPressureRadio, SIGNAL(clicked()), this, SLOT(onDecliningPressureSelected()));
connect(m_multiplePressuresRadio, SIGNAL(clicked()), this, SLOT(onMultiplePressuresSelected()));
connect(m_multipleRatesRadio, SIGNAL(clicked()), this, SLOT(onMultipleRatesSelected()));
connect(m_usePastHistoryCheck, SIGNAL(toggled(bool)), this, SLOT(onUsePastHistoryToggled(bool)));
connect(m_surfacePressuresCheck, SIGNAL(toggled(bool)), this, SLOT(onSurfacePressuresToggled(bool)));
// 复选框联动
connect(m_abandonmentRateCheck, SIGNAL(toggled(bool)), this, SLOT(onAbandonmentRateCheckToggled(bool)));
connect(m_maximumRateCheck, SIGNAL(toggled(bool)), this, SLOT(onMaximumRateCheckToggled(bool)));
// Buttons
connect(m_loadDataButton, SIGNAL(clicked()), this, SLOT(onLoadDataClicked()));
connect(m_generateButton, SIGNAL(clicked()), this, SLOT(onGenerateClicked()));
connect(m_cancelButton, SIGNAL(clicked()), this, SLOT(onCancelClicked()));
// Pressure Table
if(m_pressureTableWidget) {
connect(m_pressureTableWidget, SIGNAL(cellChanged(int, int)), this, SLOT(onPressureCellChanged(int, int)));
}
if(m_pDurationUnitCombo) {
connect(m_pDurationUnitCombo, SIGNAL(currentIndexChanged(const QString &)),
this, SLOT(onSharedDurationUnitChanged(const QString &)));
}
if(m_pPressureUnitCombo) {
connect(m_pPressureUnitCombo, SIGNAL(currentIndexChanged(const QString &)),
this, SLOT(onPressureUnitChanged(const QString &)));
}
// Rate Table Connections
if(m_rateTableWidget) {
connect(m_rateTableWidget, SIGNAL(cellChanged(int, int)), this, SLOT(onRateCellChanged(int, int)));
}
if(m_rDurationUnitCombo) {
connect(m_rDurationUnitCombo, SIGNAL(currentIndexChanged(const QString &)),
this, SLOT(onSharedDurationUnitChanged(const QString &)));
}
if(m_rRateUnitCombo) {
connect(m_rRateUnitCombo, SIGNAL(currentIndexChanged(const QString &)),
this, SLOT(onRateUnitChanged(const QString &)));
}
}
void nmWxForecast::onAbandonmentRateCheckToggled(bool checked)
{
if(m_abandonmentRateComponent) {
m_abandonmentRateComponent->setEnabled(checked);
}
// 同步到数据模型
nmDataForecast::ForecastOption option = m_forecastData.getForecastOption(); // Get current option
if(option == nmDataForecast::ConstantPressureOption) {
m_forecastData.getConstantPressureData().setIsAbandonmentRate(checked);
} else if(option == nmDataForecast::DecliningPressureOption) {
m_forecastData.getDecliningPressureData().setIsAbandonmentRate(checked);
} else if(option == nmDataForecast::MultiplePressuresOption) {
m_forecastData.getMultiplePressuresData().setIsAbandonmentRate(checked);
}
}
void nmWxForecast::onMaximumRateCheckToggled(bool checked)
{
if(m_maximumRateComponent) {
m_maximumRateComponent->setEnabled(checked);
}
// 同步到数据模型
nmDataForecast::ForecastOption option = m_forecastData.getForecastOption(); // Get current option
if(option == nmDataForecast::ConstantPressureOption) {
m_forecastData.getConstantPressureData().setIsMaximumRate(checked);
} else if(option == nmDataForecast::DecliningPressureOption) {
m_forecastData.getDecliningPressureData().setIsMaximumRate(checked);
} else if(option == nmDataForecast::MultiplePressuresOption) {
m_forecastData.getMultiplePressuresData().setIsMaximumRate(checked);
}
}
void nmWxForecast::onConstantPressureSelected()
{
updateDataFromUI(); // 保存当前界面数据
m_forecastData.setForecastOption(nmDataForecast::ConstantPressureOption);
updateParametersForOption();
}
void nmWxForecast::onDecliningPressureSelected()
{
updateDataFromUI(); // 保存当前界面数据
m_forecastData.setForecastOption(nmDataForecast::DecliningPressureOption);
updateParametersForOption();
}
void nmWxForecast::onMultiplePressuresSelected()
{
updateDataFromUI(); // 保存当前界面数据
m_forecastData.setForecastOption(nmDataForecast::MultiplePressuresOption);
updateParametersForOption();
}
void nmWxForecast::onMultipleRatesSelected()
{
updateDataFromUI(); // 保存当前界面数据
m_forecastData.setForecastOption(nmDataForecast::MultipleRatesOption);
updateParametersForOption();
}
void nmWxForecast::onUsePastHistoryToggled(bool checked)
{
Q_UNUSED(checked);
}
void nmWxForecast::onSurfacePressuresToggled(bool checked)
{
Q_UNUSED(checked);
}
void nmWxForecast::updateParametersForOption()
{
this->setUpdatesEnabled(false);
const nmDataForecast::ForecastOption option = m_forecastData.getForecastOption();
// 默认显示所有基本组件
if (m_abandonmentRateCheck) m_abandonmentRateCheck->setVisible(true);
if (m_abandonmentStack) m_abandonmentStack->setCurrentIndex(0);
if (m_maximumRateCheck) m_maximumRateCheck->setVisible(true);
if (m_maximumStack) m_maximumStack->setCurrentIndex(0);
if (m_forecastDurationComponent) m_forecastDurationComponent->setVisible(true);
if (m_producingPressureComponent) m_producingPressureComponent->setVisible(true);
// 更新各组件的数据源指针并控制显示
if(option == nmDataForecast::ConstantPressureOption) {
ConstantPressure& cpData = m_forecastData.getConstantPressureData();
if(m_initialPressureComponent)
m_initialPressureComponent->setOriginValue(&cpData.getInitialPressure());
if(m_abandonmentRateComponent)
m_abandonmentRateComponent->setOriginValue(&cpData.getAbandonmentRate());
if(m_maximumRateComponent)
m_maximumRateComponent->setOriginValue(&cpData.getMaximumRate());
if(m_forecastDurationComponent)
m_forecastDurationComponent->setOriginValue(&cpData.getForecastDuration());
if(m_producingPressureComponent)
m_producingPressureComponent->setOriginValue(&cpData.getProducingPressure());
if (m_extStackedWidget) m_extStackedWidget->setCurrentWidget(m_extEmpty);
} else if(option == nmDataForecast::DecliningPressureOption) {
DecliningPressure& dpData = m_forecastData.getDecliningPressureData();
if(m_initialPressureComponent)
m_initialPressureComponent->setOriginValue(&dpData.getInitialPressure());
if(m_abandonmentRateComponent)
m_abandonmentRateComponent->setOriginValue(&dpData.getAbandonmentRate());
if(m_maximumRateComponent)
m_maximumRateComponent->setOriginValue(&dpData.getMaximumRate());
if(m_forecastDurationComponent)
m_forecastDurationComponent->setOriginValue(&dpData.getForecastDuration());
if(m_producingPressureComponent)
m_producingPressureComponent->setOriginValue(&dpData.getProducingPressureStart());
if (m_extStackedWidget) m_extStackedWidget->setCurrentWidget(m_extRamp);
} else if(option == nmDataForecast::MultiplePressuresOption) {
MultiplePressures& mpData = m_forecastData.getMultiplePressuresData();
if(m_initialPressureComponent)
m_initialPressureComponent->setOriginValue(&mpData.getInitialPressure());
if(m_abandonmentRateComponent)
m_abandonmentRateComponent->setOriginValue(&mpData.getAbandonmentRate());
if(m_maximumRateComponent)
m_maximumRateComponent->setOriginValue(&mpData.getMaximumRate());
if(m_abandonmentStack) m_abandonmentStack->setCurrentIndex(0);
if(m_maximumStack) m_maximumStack->setCurrentIndex(0);
// 隐藏"Duration"和"Pressure"输入框
if(m_forecastDurationComponent)
m_forecastDurationComponent->setVisible(false);
if(m_producingPressureComponent)
m_producingPressureComponent->setVisible(false);
if (m_extStackedWidget) m_extStackedWidget->setCurrentWidget(m_extMultiP);
} else if(option == nmDataForecast::MultipleRatesOption) {
MultipleRates& mrData = m_forecastData.getMultipleRatesData();
if(m_initialPressureComponent)
m_initialPressureComponent->setOriginValue(&mrData.getInitialPressure());
if (m_abandonmentRateCheck) m_abandonmentRateCheck->setVisible(false);
if (m_abandonmentStack) m_abandonmentStack->setCurrentIndex(1);
if (m_maximumRateCheck) m_maximumRateCheck->setVisible(false);
if (m_maximumStack) m_maximumStack->setCurrentIndex(1);
if (m_forecastDurationComponent) m_forecastDurationComponent->setVisible(false);
if (m_producingPressureComponent) m_producingPressureComponent->setVisible(false);
if (m_extStackedWidget) m_extStackedWidget->setCurrentWidget(m_extMultiR);
}
// 更新界面显示
updateUIFromData();
this->setUpdatesEnabled(true);
this->update();
}
void nmWxForecast::updateUIFromData()
{
const nmDataForecast::ForecastOption option = m_forecastData.getForecastOption();
// 更新复选框状态
// 更新复选框状态
if(option == nmDataForecast::ConstantPressureOption) {
const ConstantPressure& cpData = m_forecastData.getConstantPressureData();
bool isAbandoned = cpData.getIsAbandonmentRate();
bool isMaxRate = cpData.getIsMaximumRate();
m_abandonmentRateCheck->setChecked(isAbandoned);
m_maximumRateCheck->setChecked(isMaxRate);
// 手动设置初始状态
if (m_abandonmentRateComponent) m_abandonmentRateComponent->setEnabled(isAbandoned);
if (m_maximumRateComponent) m_maximumRateComponent->setEnabled(isMaxRate);
} else if(option == nmDataForecast::DecliningPressureOption) {
const DecliningPressure& dpData = m_forecastData.getDecliningPressureData();
bool isAbandoned = dpData.getIsAbandonmentRate();
bool isMaxRate = dpData.getIsMaximumRate();
m_abandonmentRateCheck->setChecked(isAbandoned);
m_maximumRateCheck->setChecked(isMaxRate);
// 手动设置初始状态
if (m_abandonmentRateComponent) m_abandonmentRateComponent->setEnabled(isAbandoned);
if (m_maximumRateComponent) m_maximumRateComponent->setEnabled(isMaxRate);
} else if(option == nmDataForecast::MultiplePressuresOption) {
const MultiplePressures& mpData = m_forecastData.getMultiplePressuresData();
bool isAbandoned = mpData.getIsAbandonmentRate();
bool isMaxRate = mpData.getIsMaximumRate();
m_abandonmentRateCheck->setChecked(isAbandoned);
m_maximumRateCheck->setChecked(isMaxRate);
// 手动设置初始状态
if (m_abandonmentRateComponent) m_abandonmentRateComponent->setEnabled(isAbandoned);
if (m_maximumRateComponent) m_maximumRateComponent->setEnabled(isMaxRate);
}
// 刷新所有组件的显示值
if(m_initialPressureComponent)
m_initialPressureComponent->updateValueFromOrigin();
if(m_abandonmentRateComponent)
m_abandonmentRateComponent->updateValueFromOrigin();
if(m_maximumRateComponent)
m_maximumRateComponent->updateValueFromOrigin();
if(m_forecastDurationComponent)
m_forecastDurationComponent->updateValueFromOrigin();
if(m_producingPressureComponent)
m_producingPressureComponent->updateValueFromOrigin();
if(option == nmDataForecast::DecliningPressureOption) {
if(m_producingPressureEndComponent)
m_producingPressureEndComponent->updateValueFromOrigin();
if(m_pressureChangePeriodComponent)
m_pressureChangePeriodComponent->updateValueFromOrigin();
} else if(option == nmDataForecast::MultiplePressuresOption) {
// 填充压力表格
if(m_pressureTableWidget) {
m_pressureTableWidget->blockSignals(true);
if(m_pDurationUnitCombo) m_pDurationUnitCombo->blockSignals(true);
if(m_pPressureUnitCombo) m_pPressureUnitCombo->blockSignals(true);
// 清空数据行 (保留第0行单位行)
while(m_pressureTableWidget->rowCount() > 1) {
m_pressureTableWidget->removeRow(1);
}
const QVector<PressureRow>& tableData = m_forecastData.getMultiplePressuresData().getPressureTable();
PressureRow tempDefaults; // 用于获取单位列表和默认值
int dataSize = tableData.size();
// 如果数据为空,至少显示1行(使用默认值),否则显示所有数据
int displayRowCount = (dataSize == 0) ? 1 : dataSize;
m_pressureTableWidget->setRowCount(1 + displayRowCount + 1);
if(m_pDurationUnitCombo) m_pDurationUnitCombo->setCurrentIndex(m_pDurationUnitCombo->findText(m_currentDurationUnit));
if(m_pPressureUnitCombo) m_pPressureUnitCombo->setCurrentIndex(m_pPressureUnitCombo->findText(m_currentPressureUnit));
for(int i = 0; i < displayRowCount; ++i) {
// 如果i < dataSize,使用加载的数据;否则 (即 dataSize==0) 使用默认值
const PressureRow& rowData = (i < dataSize) ? tableData.at(i) : tempDefaults;
int tableRow = i + 1;
// 转换值:从 "数据的基础单位" 转换为 "当前显示的单位"
double baseDurValue = rowData.duration.getValue().toDouble();
QString baseDurUnit = rowData.duration.getUnit();
double displayDurValue = tempDefaults.duration.convertValueByUnitType(baseDurValue, baseDurUnit, m_currentDurationUnit);
double basePresValue = rowData.pressure.getValue().toDouble();
QString basePresUnit = rowData.pressure.getUnit();
double displayPresValue = tempDefaults.pressure.convertValueByUnitType(basePresValue, basePresUnit, m_currentPressureUnit);
QTableWidgetItem* durationItem = new QTableWidgetItem(QString::number(displayDurValue, 'g', 15));
durationItem->setTextAlignment(Qt::AlignCenter);
m_pressureTableWidget->setItem(tableRow, 0, durationItem);
QTableWidgetItem* pressureItem = new QTableWidgetItem(QString::number(displayPresValue));
pressureItem->setTextAlignment(Qt::AlignCenter);
m_pressureTableWidget->setItem(tableRow, 1, pressureItem);
}
if (m_pDurationUnitCombo) m_pDurationUnitCombo->blockSignals(false);
if (m_pPressureUnitCombo) m_pPressureUnitCombo->blockSignals(false);
m_pressureTableWidget->blockSignals(false);
}
}
else if(option == nmDataForecast::MultipleRatesOption) {
// 填充速率表格
if(m_rateTableWidget) {
// 锁定信号
m_rateTableWidget->blockSignals(true);
if(m_rDurationUnitCombo) m_rDurationUnitCombo->blockSignals(true);
if(m_rRateUnitCombo) m_rRateUnitCombo->blockSignals(true);
// 清空数据行 (保留第0行单位行)
while(m_rateTableWidget->rowCount() > 1) {
m_rateTableWidget->removeRow(1);
}
const QVector<FlowRow>& tableData = m_forecastData.getMultipleRatesData().getFlowTable();
FlowRow tempDefaults; // 用于获取单位列表和默认值
int dataSize = tableData.size();
int displayRowCount = (dataSize == 0) ? 1 : dataSize;
// 设置行数: 1(单位) + N(数据) + 1(空白)
m_rateTableWidget->setRowCount(1 + displayRowCount + 1);
// 设置下拉框为当前单位
if(m_rDurationUnitCombo) m_rDurationUnitCombo->setCurrentIndex(m_rDurationUnitCombo->findText(m_currentDurationUnit));
if(m_rRateUnitCombo) m_rRateUnitCombo->setCurrentIndex(m_rRateUnitCombo->findText(m_currentRateUnit));
for(int i = 0; i < displayRowCount; ++i) {
// 如果i < dataSize,使用加载的数据;否则 (即 dataSize==0) 使用默认值
const FlowRow& rowData = (i < dataSize) ? tableData.at(i) : tempDefaults;
int tableRow = i + 1;
// 转换值:从 "数据的基础单位" 转换为 "当前显示的单位"
double baseDurValue = rowData.duration.getValue().toDouble();
QString baseDurUnit = rowData.duration.getUnit();
double displayDurValue = tempDefaults.duration.convertValueByUnitType(baseDurValue, baseDurUnit, m_currentDurationUnit);
double baseRateValue = rowData.q0.getValue().toDouble();
QString baseRateUnit = rowData.q0.getUnit();
double displayRateValue = tempDefaults.q0.convertValueByUnitType(baseRateValue, baseRateUnit, m_currentRateUnit);
QTableWidgetItem* durationItem = new QTableWidgetItem(QString::number(displayDurValue, 'g', 15));
durationItem->setTextAlignment(Qt::AlignCenter);
m_rateTableWidget->setItem(tableRow, 0, durationItem);
QTableWidgetItem* rateItem = new QTableWidgetItem(QString::number(displayRateValue, 'g', 15));
rateItem->setTextAlignment(Qt::AlignCenter);
m_rateTableWidget->setItem(tableRow, 1, rateItem);
}
// 解锁信号
if (m_rDurationUnitCombo) m_rDurationUnitCombo->blockSignals(false);
if (m_rRateUnitCombo) m_rRateUnitCombo->blockSignals(false);
m_rateTableWidget->blockSignals(false);
}
}
}
void nmWxForecast::updateDataFromUI()
{
// 将界面数据写回到数据模型
if(m_initialPressureComponent)
m_initialPressureComponent->updateValueToOrigin();
if(m_abandonmentRateComponent)
m_abandonmentRateComponent->updateValueToOrigin();
if(m_maximumRateComponent)
m_maximumRateComponent->updateValueToOrigin();
if(m_forecastDurationComponent)
m_forecastDurationComponent->updateValueToOrigin();
if(m_producingPressureComponent)
m_producingPressureComponent->updateValueToOrigin();
const nmDataForecast::ForecastOption option = m_forecastData.getForecastOption();
if(option == nmDataForecast::DecliningPressureOption) {
if(m_producingPressureEndComponent)
m_producingPressureEndComponent->updateValueToOrigin();
if(m_pressureChangePeriodComponent)
m_pressureChangePeriodComponent->updateValueToOrigin();
} else if(option == nmDataForecast::MultiplePressuresOption) {
// 从表格保存数据
if(m_pressureTableWidget) {
QVector<PressureRow>& tableData = m_forecastData.getMultiplePressuresData().getPressureTable();
tableData.clear();
PressureRow tempDefaults;
QString baseDurUnit = tempDefaults.duration.getUnit();
QString basePresUnit = tempDefaults.pressure.getUnit();
// 循环到 rowCount() - 1 来跳过最后那个空白行
for(int i = 1; i < m_pressureTableWidget->rowCount() - 1; ++i) {
QTableWidgetItem* durationItem = m_pressureTableWidget->item(i, 0);
QTableWidgetItem* pressureItem = m_pressureTableWidget->item(i, 1);
bool durationEmpty = (durationItem == NULL || durationItem->text().trimmed().isEmpty());
bool pressureEmpty = (pressureItem == NULL || pressureItem->text().trimmed().isEmpty());
if(durationEmpty && pressureEmpty) {
continue;
}
PressureRow newRow; // newRow 包含基础单位 (hr, psia)
if(!durationEmpty) {
double displayVal = durationItem->text().toDouble();
// 转换值:从 "当前显示的单位" 转换回 "数据的基础单位"
double storageVal = tempDefaults.duration.convertValueByUnitType(displayVal, m_currentDurationUnit, baseDurUnit);
newRow.duration.setValue(storageVal);
}
if(!pressureEmpty) {
double displayVal = pressureItem->text().toDouble();
double storageVal = tempDefaults.pressure.convertValueByUnitType(displayVal, m_currentPressureUnit, basePresUnit);
newRow.pressure.setValue(storageVal);
}
tableData.append(newRow);
}
}
}
else if (option == nmDataForecast::MultipleRatesOption) {
// 从速率表格保存数据
if(m_rateTableWidget) {
// 获取数据模型的引用
QVector<FlowRow>& tableData = m_forecastData.getMultipleRatesData().getFlowTable();
tableData.clear();
FlowRow tempDefaults;
// 获取数据模型的基础单位
QString baseDurUnit = tempDefaults.duration.getUnit();
QString baseRateUnit = tempDefaults.q0.getUnit();
// 循环到 rowCount() - 1 来跳过最后那个空白行
for(int i = 1; i < m_rateTableWidget->rowCount() - 1; ++i) {
QTableWidgetItem* durationItem = m_rateTableWidget->item(i, 0);
QTableWidgetItem* rateItem = m_rateTableWidget->item(i, 1);
bool durationEmpty = (durationItem == NULL || durationItem->text().trimmed().isEmpty());
bool rateEmpty = (rateItem == NULL || rateItem->text().trimmed().isEmpty());
// 如果两个单元格都为空,则跳过该行
if(durationEmpty && rateEmpty) {
continue;
}
FlowRow newRow; // newRow 将使用基础单位
if(!durationEmpty) {
double displayVal = durationItem->text().toDouble();
// 转换值:从 "当前显示的单位" 转换回 "数据的基础单位"
double storageVal = tempDefaults.duration.convertValueByUnitType(displayVal, m_currentDurationUnit, baseDurUnit);
newRow.duration.setValue(storageVal);
}
if(!rateEmpty) {
double displayVal = rateItem->text().toDouble();
double storageVal = tempDefaults.q0.convertValueByUnitType(displayVal, m_currentRateUnit, baseRateUnit);
newRow.q0.setValue(storageVal);
}
tableData.append(newRow);
}
}
}
}
void nmWxForecast::onLoadDataClicked()
{
// TODO: Implement load data functionality
}
void nmWxForecast::onGenerateClicked()
{
// 先校验原始输入,避免空单元格被旧的数据同步逻辑补成默认值。
if(m_task) return;
if(!m_manager || !m_manager->isPebiResultSnapshotCurrent()) {
QMessageBox::warning(this, tr("Forecast"), tr("Generate the current model before forecasting."));
return;
}
const QSharedPointer<const nmPebiResultSnapshot> solved = m_manager->getPebiResultSnapshot();
if(solved->getSolverSettings().m_nSolverModelType < 1 ||
solved->getSolverSettings().m_nSolverModelType > 6) {
QMessageBox::warning(this, tr("Forecast"), tr("Rate forecast currently supports single-phase models only."));
return;
}
QString error;
QVector<double> durations, rates;
if(!readRateSchedule(durations, rates, error)) {
QMessageBox::warning(this, tr("Forecast"), error);
return;
}
nmDataAttribute& pressure = m_forecastData.getMultipleRatesData().getInitialPressure();
if(!m_initialPressureComponent->updateValueToOrigin()) {
QMessageBox::warning(this, tr("Forecast"), tr("Enter a positive initial pressure."));
return;
}
const double pi = pressure.convertValueByUnitType(pressure.getValue().toDouble(), pressure.getUnit(), "MPa");
// 与主界面的结果井选择保持一致,预测制度只替换当前井,其他井共同参与求解。
const nmPebiResultWellSnapshot* well = solved->findWell(m_manager->getCurrentResultWellInstanceId());
if(!well) {
QMessageBox::warning(this, tr("Forecast"), tr("The target well is unavailable."));
return;
}
m_wellName = well->m_sWellName;
m_task = new nmCalculationDllPebiSolverTask(QString(), m_manager, m_wellName, this);
if(!m_task->configureRateForecast(pi, durations, rates)) {
error = m_task->getFailureReason();
delete m_task;
m_task = NULL;
QMessageBox::warning(this, tr("Forecast"), error);
return;
}
saveDataToManager();
m_durations = durations;
m_rates = rates;
m_sourceSnapshotId = solved->getSnapshotId();
// 与敏感性分析一样,主窗口接管成功后才启动,忙碌时释放未启动任务。
emit sigForecastTaskReady(this, m_task);
if(!m_calculationRunning) {
delete m_task;
m_task = NULL;
}
}
void nmWxForecast::startForecastCalculation()
{
// 先连接主窗口的完成处理,再由本窗口读取结果和释放任务。
connect(m_task, SIGNAL(finished()), this, SLOT(onForecastFinished()), Qt::QueuedConnection);
m_calculationRunning = true;
m_parametersGroup->setEnabled(false);
m_generateButton->setEnabled(false);
// 与敏感性分析一致,隐藏输入窗口,保留对象接收计算结果。
hide();
m_task->start();
}
void nmWxForecast::onCancelClicked()
{
// 关闭按钮和进度框共用协作式停止,不在 DLL 内部强制终止线程。
reject();
}
void nmWxForecast::reject()
{
// 正在求解时保留窗口,等线程结束后恢复输入,避免关闭操作阻塞界面。
if(m_calculationRunning) {
emit sigForecastCancelRequested();
return;
}
iDlgBase::reject();
}
void nmWxForecast::closeEvent(QCloseEvent* event)
{
// 非模态进度框允许操作输入窗口,点关闭时同样请求停止并等待线程退出。
if(m_calculationRunning) {
emit sigForecastCancelRequested();
event->ignore();
return;
}
iDlgBase::closeEvent(event);
}
bool nmWxForecast::readRateSchedule(QVector<double>& durations, QVector<double>& rates, QString& error) const
{
// 求解器统一使用小时及标准体积产量,完整空行跳过,半空行必须补全。
durations.clear();
rates.clear();
FlowRow units;
for(int row = 1; row < m_rateTableWidget->rowCount(); ++row) {
const QTableWidgetItem* durationItem = m_rateTableWidget->item(row, 0);
const QTableWidgetItem* rateItem = m_rateTableWidget->item(row, 1);
const QString durationText = durationItem ? durationItem->text().trimmed() : QString();
const QString rateText = rateItem ? rateItem->text().trimmed() : QString();
if(durationText.isEmpty() && rateText.isEmpty()) continue;
bool durationOk = false, rateOk = false;
double duration = durationText.toDouble(&durationOk);
double rate = rateText.toDouble(&rateOk);
duration = units.duration.convertValueByUnitType(duration, m_currentDurationUnit, "hr");
rate = units.q0.convertValueByUnitType(rate, m_currentRateUnit, "m^3/D");
if(!durationOk || !rateOk || !qIsFinite(duration) || duration <= 0.0 ||
!qIsFinite(rate) || rate < 0.0) {
error = tr("Row %1: enter a positive duration and a non-negative production rate (0 for shut-in).").arg(row);
return false;
}
durations.append(duration);
rates.append(rate);
}
if(durations.isEmpty()) {
error = tr("Enter at least one duration and production rate.");
return false;
}
return true;
}
void nmWxForecast::onForecastFinished()
{
// finished 后才读取及销毁任务;输入变化或模型重新生成时丢弃本次预测。
m_task->wait();
const bool cancelled = m_task->wasCancelled();
const bool current = m_manager && m_manager->isPebiResultSnapshotCurrent() &&
!m_task->hasInputChanged(m_manager) &&
m_manager->getPebiResultSnapshot()->getSnapshotId() == m_sourceSnapshotId;
const bool success = m_task->wasSuccessful();
const QString error = m_task->getFailureReason();
const QVector<QVector<double> > pressure = m_task->getRateForecastPressure();
delete m_task;
m_task = NULL;
m_calculationRunning = false;
m_parametersGroup->setEnabled(true);
m_generateButton->setEnabled(true);
if(cancelled) return;
if(!current || !success) {
QMessageBox::warning(this, tr("Forecast"), current ? error : tr("The model changed. Generate the forecast again."));
return;
}
emit sigForecastReady(m_wellName, pressure[0], pressure[1], m_durations, m_rates);
}
void nmWxForecast::onModelChanged()
{
// 模型或输入改变时停止本次计算;已显示的结果由所属主界面清理。
if(m_calculationRunning) emit sigForecastCancelRequested();
}
void nmWxForecast::onPressureCellChanged(int row, int column)
{
Q_UNUSED(column);
if (!m_pressureTableWidget) return;
if (row == 0) return; // 忽略单位行
// 让被编辑行的两列都居中
QTableWidgetItem* it0 = m_pressureTableWidget->item(row, 0);
if (it0) it0->setTextAlignment(Qt::AlignCenter);
QTableWidgetItem* it1 = m_pressureTableWidget->item(row, 1);
if (it1) it1->setTextAlignment(Qt::AlignCenter);
// 如果编辑的是最后一行,追加一个新的空白行,并预置居中 item
int lastRow = m_pressureTableWidget->rowCount() - 1;
if (row == lastRow) {
m_pressureTableWidget->blockSignals(true);
int newRow = m_pressureTableWidget->rowCount();
m_pressureTableWidget->insertRow(newRow);
// 给新行两列预置空 item,这样用户一输入就居中
m_pressureTableWidget->setItem(newRow, 0, makeCenteredEmptyItem());
m_pressureTableWidget->setItem(newRow, 1, makeCenteredEmptyItem());
m_pressureTableWidget->blockSignals(false);
}
}
void nmWxForecast::onPressureUnitChanged(const QString& unit)
{
if(unit == m_currentPressureUnit) return; // 单位没变
QString oldUnit = m_currentPressureUnit;
m_currentPressureUnit = unit;
// 锁定信号
m_pressureTableWidget->blockSignals(true);
PressureRow tempDefaults; // 用于获取转换函数
// 遍历所有数据行 (从 1 到 rowCount - 2)
for(int i = 1; i < m_pressureTableWidget->rowCount() - 1; ++i) {
QTableWidgetItem* dataItem = m_pressureTableWidget->item(i, 1); // 第1列
if(dataItem && !dataItem->text().isEmpty()) {
double oldDisplayValue = dataItem->text().toDouble();
double newDisplayValue = tempDefaults.pressure.convertValueByUnitType(oldDisplayValue, oldUnit, unit);
dataItem->setText(QString::number(newDisplayValue));
}
}
// 解锁信号
m_pressureTableWidget->blockSignals(false);
}
void nmWxForecast::onRateCellChanged(int row, int column)
{
Q_UNUSED(column);
if (!m_rateTableWidget) return;
if (row == 0) return; // 忽略单位行
// 让被编辑行的两列都居中
QTableWidgetItem* it0 = m_rateTableWidget->item(row, 0);
if (it0) it0->setTextAlignment(Qt::AlignCenter);
QTableWidgetItem* it1 = m_rateTableWidget->item(row, 1);
if (it1) it1->setTextAlignment(Qt::AlignCenter);
// 若编辑的是最后一行,则在末尾追加一个新的空白行,并预置居中 item
int lastRow = m_rateTableWidget->rowCount() - 1;
if (row == lastRow) {
m_rateTableWidget->blockSignals(true);
int newRow = m_rateTableWidget->rowCount();
m_rateTableWidget->insertRow(newRow);
m_rateTableWidget->setItem(newRow, 0, makeCenteredEmptyItem());
m_rateTableWidget->setItem(newRow, 1, makeCenteredEmptyItem());
m_rateTableWidget->blockSignals(false);
}
}
void nmWxForecast::onSharedDurationUnitChanged(const QString& unit)
{
// 检查单位是否真的改变了
if(unit.isEmpty() || unit == m_currentDurationUnit) return;
QString oldUnit = m_currentDurationUnit;
m_currentDurationUnit = unit; // 更新全局的当前单位
// --- 1. 更新 Pressure 表格 ---
if(m_pressureTableWidget) {
// 锁定信号,防止在setText时触发 cellChanged
m_pressureTableWidget->blockSignals(true);
PressureRow tempDefaults; // 用于获取转换函数
// 遍历所有数据行 (从 1 到 rowCount - 2,跳过单位行和最后空白行)
for(int i = 1; i < m_pressureTableWidget->rowCount() - 1; ++i) {
QTableWidgetItem* dataItem = m_pressureTableWidget->item(i, 0); // 第0列 (Duration)
if(dataItem && !dataItem->text().isEmpty()) {
double oldDisplayValue = dataItem->text().toDouble();
// 转换值
double newDisplayValue = tempDefaults.duration.convertValueByUnitType(oldDisplayValue, oldUnit, unit);
dataItem->setText(QString::number(newDisplayValue));
}
}
m_pressureTableWidget->blockSignals(false);
}
// --- 2. 更新 Rate 表格 ---
if(m_rateTableWidget) {
// 锁定信号
m_rateTableWidget->blockSignals(true);
FlowRow tempDefaults; // 用于获取转换函数
// 遍历所有数据行
for(int i = 1; i < m_rateTableWidget->rowCount() - 1; ++i) {
QTableWidgetItem* dataItem = m_rateTableWidget->item(i, 0); // 第0列 (Duration)
if(dataItem && !dataItem->text().isEmpty()) {
bool ok = false;
double oldDisplayValue = dataItem->text().toDouble(&ok);
if(!ok || !qIsFinite(oldDisplayValue)) continue;
// 转换值
double newDisplayValue = tempDefaults.duration.convertValueByUnitType(oldDisplayValue, oldUnit, unit);
dataItem->setText(QString::number(newDisplayValue, 'g', 15));
}
}
m_rateTableWidget->blockSignals(false);
}
if(m_pDurationUnitCombo) {
m_pDurationUnitCombo->blockSignals(true);
m_pDurationUnitCombo->setCurrentIndex(m_pDurationUnitCombo->findText(unit));
m_pDurationUnitCombo->blockSignals(false);
}
if(m_rDurationUnitCombo) {
m_rDurationUnitCombo->blockSignals(true);
m_rDurationUnitCombo->setCurrentIndex(m_rDurationUnitCombo->findText(unit));
m_rDurationUnitCombo->blockSignals(false);
}
}
void nmWxForecast::onRateUnitChanged(const QString& unit)
{
if(unit == m_currentRateUnit) return; // 单位没变
QString oldUnit = m_currentRateUnit;
m_currentRateUnit = unit;
// 锁定信号
m_rateTableWidget->blockSignals(true);
FlowRow tempDefaults; // 用于获取转换函数
// 遍历所有数据行 (从 1 到 rowCount - 2)
for(int i = 1; i < m_rateTableWidget->rowCount() - 1; ++i) {
QTableWidgetItem* dataItem = m_rateTableWidget->item(i, 1); // 第1列
if(dataItem && !dataItem->text().isEmpty()) {
bool ok = false;
double oldDisplayValue = dataItem->text().toDouble(&ok);
if(!ok || !qIsFinite(oldDisplayValue)) continue;
// 使用 q0 (FlowRate) 进行单位转换
double newDisplayValue = tempDefaults.q0.convertValueByUnitType(oldDisplayValue, oldUnit, unit);
dataItem->setText(QString::number(newDisplayValue, 'g', 15));
}
}
// 解锁信号
m_rateTableWidget->blockSignals(false);
}