#include "nmWxNumericalDesign.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iUnitGroup.h" #include "iUnitWxBinder.h" #include "mModuleDefines.h" #include "nmWxIncludeOtherWells.h" #include "nmWxTimeDependentSkin.h" #include "nmWxAutomaticFitting.h" #include "nmWxParameterProperty.h" #include "nmWxForecast.h" #include "nmWxSensitive.h" #include "nmDataWellBase.h" #include "nmDataReservoir.h" #include "nmDataAnalyzeManager.h" #include "nmDataTimeStepSetting.h" #include "nmDataDiagnostic.h" namespace { const char* const s_pTimeGrowthExponentId = "NM_TimeGrowthExponent"; const char* const s_pDtMinId = "NM_DtMin"; const char* const s_pDtMaxId = "NM_DtMax"; nmParameterFieldInfo createTimeStepFieldInfo( const char* pParameterId, bool bUnitEnabled) { nmParameterFieldInfo oFieldInfo; oFieldInfo.m_sParameterId = QLatin1String(pParameterId); oFieldInfo.m_sBaseParameterId = QLatin1String(pParameterId); oFieldInfo.m_eEditorType = NM_PARAMETER_EDITOR_NUMBER; oFieldInfo.m_bUnitEnabled = bUnitEnabled; return oFieldInfo; } // 判断其他 Map 井是否具备进入 Include Other Wells 候选集的数值井型。 bool isSupportedAdditionalWell(nmDataWellBase* pWellData) { if(pWellData == nullptr || pWellData->getWellCode().isEmpty()) { return false; } const NM_WELL_MODEL eWellType = pWellData->getWellType(); return eWellType == Vertical_Well || eWellType == Vertical_Fractured_Well || eWellType == Horizontal_Fractured_Well; } } nmWxNumericalDesign* nmWxNumericalDesign::s_pCurrentInstance = nullptr; static bool isNumericalDesignDebugOptionsVisible() { #ifdef QT_DEBUG return true; #endif return false; } // 显示当前模型类型 static QString solverModelDisplayName(NM_SOLVER_MODEL_TYPE eType) { switch(eType) { case SMT_Oil_ConstPvt: return nmWxNumericalDesign::tr("Oil single-phase constant pvt"); case SMT_Oil_VariablePvt: return nmWxNumericalDesign::tr("Oil single-phase variable pvt"); case SMT_Water_ConstPvt: return nmWxNumericalDesign::tr("Water single-phase constant pvt"); case SMT_Water_VariablePvt: return nmWxNumericalDesign::tr("Water single-phase variable pvt"); case SMT_Gas_VariablePvt: return nmWxNumericalDesign::tr("Gas single-phase variable pvt"); case SMT_Oil_Water_TwoPhase: return nmWxNumericalDesign::tr("Oil-water two-phase"); default: return QString(); } } nmWxNumericalDesign::nmWxNumericalDesign( nmDataAnalyzeManager* pDataManager, QWidget* pParent) : iDlgBase(pParent), m_pDataManager(pDataManager), m_pTimeGrowthExponentField(nullptr), m_pDtMinField(nullptr), m_pDtMaxField(nullptr) { Q_ASSERT(m_pDataManager != nullptr); // 设置当前实例为静态指针 s_pCurrentInstance = this; m_pModelAndResultGroup = nullptr; m_pResultWellCombo = nullptr; m_pCurrentModelCombo = nullptr; m_bFillingResultWellCombo = false; m_bFillingCurrentModelCombo = false; // 单位绑定器必须先切换到数值试井参数系列,再创建时间步字段。 setParaSrcTag(s_Nm_Serie); // 初始化图标路径 m_sIconDir = QCoreApplication::applicationDirPath(); m_sIconDir = m_sIconDir.section('/', 0, -2); // 获取上一级目录 m_sIconDir += "/Res/Icon/"; // 设置窗口的尺寸策略为可扩展 this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // 创建滚动区域 QScrollArea* pScrollArea = new QScrollArea(this); pScrollArea->setWidgetResizable(true); QWidget* pScrollWidget = new QWidget(); pScrollWidget->setObjectName("nmNumericalDesignScrollWidget"); pScrollWidget->setStyleSheet("#nmNumericalDesignScrollWidget { background-color: white; }"); QVBoxLayout* pScrollLayout = new QVBoxLayout(pScrollWidget); // 初始化所有UI组件 initMainOptionsGroup(); // 初始化主选项组 initModelAndResultGroup(); initOutputGroup(); // 初始化输出组 (新) initAdvancedGroup(); // 初始化高级组 (新) initTimeSteppingGroup(); // 初始化时间步进组 initNumericalSettingsGroup(); // 初始化数值设置组 (新) initPebiSolverSettingsGroup(); // 初始化PEBI求解器设置组 initButton(); // 初始化生成按钮 // 将各个组添加到滚动区域的布局中 pScrollLayout->addWidget(m_pMainOptionsGroup); pScrollLayout->addWidget(m_pModelAndResultGroup); pScrollLayout->addWidget(m_pOutputGroup); pScrollLayout->addWidget(m_pAdvancedGroup); pScrollLayout->addWidget(m_pTimeSteppingGroup); pScrollLayout->addWidget(m_pNumericalSettingsGroup); // 新的数值设置组 pScrollLayout->addWidget(m_pPebiSolverSettingsGroup); pScrollLayout->addStretch(); // 添加拉伸器,使内容靠上 pScrollWidget->setLayout(pScrollLayout); // 设置滚动容器的布局 pScrollArea->setWidget(pScrollWidget); // 将滚动容器设置给滚动区域 // 设置主布局并添加滚动区域 m_pMainLayout = new QVBoxLayout(this); // 窗口的主布局 m_pMainLayout->addWidget(pScrollArea); // 将滚动区域添加到主布局 m_pMainLayout->addLayout(m_pGenerateLayout); // 将生成按钮布局添加到主布局底部 updateUiFromData(); // 更新时间步进组的组件值 // 设置信号槽连接 setupConnections(); //对于没有功能的组件,release下隐藏,debug下显示 bool bDebugOptionsVisible = isNumericalDesignDebugOptionsVisible(); m_pImposePiCheck->setVisible(bDebugOptionsVisible); m_pShowAveragePressureCheck->setVisible(bDebugOptionsVisible); m_pOutputGroup->setVisible(bDebugOptionsVisible); m_pUseWellIntakeCheck->setVisible(bDebugOptionsVisible); m_pRedefineKrPcCheck->setVisible(bDebugOptionsVisible); m_pRedefineKrPcIconButton->setVisible(bDebugOptionsVisible); m_pApplyUnconsolidationCheck->setVisible(bDebugOptionsVisible); m_pApplyUnconsolidationIconButton->setVisible(bDebugOptionsVisible); m_pUseDFNCheck->setVisible(bDebugOptionsVisible); m_pNumericalSettingsGroup->setVisible(bDebugOptionsVisible); m_pForecastBtn->setVisible(bDebugOptionsVisible); m_pSensitiveBtn->setVisible(bDebugOptionsVisible); } nmWxNumericalDesign::~nmWxNumericalDesign() { // 所有子控件将由Qt的父子对象机制自动删除 // 如果当前实例是静态指针指向的实例,则清空静态指针 if(s_pCurrentInstance == this) { s_pCurrentInstance = nullptr; } } bool nmWxNumericalDesign::bindUnitField( QLineEdit* pLineEdit, QComboBox* pUnitComboBox, const QString& sBaseParameterId) { return bindUnitPairWxs(pLineEdit, pUnitComboBox, sBaseParameterId, false); } void nmWxNumericalDesign::setUnitBaseValue( QLineEdit* pLineEdit, double dBaseValue) { if(m_pUnitBinder != nullptr) { m_pUnitBinder->setBaseValue(pLineEdit, dBaseValue); } } double nmWxNumericalDesign::unitBaseValue(QLineEdit* pLineEdit) const { return m_pUnitBinder != nullptr ? m_pUnitBinder->getBaseValue(pLineEdit) : 0.0; } bool nmWxNumericalDesign::isUnitInputValid(QLineEdit* pLineEdit) { return m_pUnitBinder != nullptr && m_pUnitBinder->isInputOK(pLineEdit, true, nullptr); } QString nmWxNumericalDesign::currentUnit(QLineEdit* pLineEdit) const { return m_pUnitBinder != nullptr ? m_pUnitBinder->getCurrentUnit(pLineEdit) : QString(); } iUnitGroup* nmWxNumericalDesign::unitGroup(QLineEdit* pLineEdit) const { return m_pUnitBinder != nullptr ? m_pUnitBinder->getUnitGroup(pLineEdit) : nullptr; } // 激活当前成果面板,并刷新查看井结果下拉框 void nmWxNumericalDesign::activateCurrentInstance() { // 一个QMdiArea中可以同时打开多个成果窗口,每个成果都有自己的数值解面板。 // 已经打开过的成果再次被激活时不会重新构造面板,因此需要在切换时更新静态指针。 s_pCurrentInstance = this; // 调用前已经切换到当前成果,此处统一刷新该Dock中的全部数据控件。 updateUiFromData(); } // 外部刷新当前查看井下拉框 void nmWxNumericalDesign::notifyResultWellSelectorChanged( nmDataAnalyzeManager* pDataManager, const QString& sWellCode) { // 静态活动面板可能已经切换到另一个成果,只刷新所属数据管理器一致的实例。 if(s_pCurrentInstance == nullptr || s_pCurrentInstance->m_pDataManager != pDataManager) { return; } s_pCurrentInstance->fillResultWellCombo(sWellCode); } // 初始化模型与结果切换控件 void nmWxNumericalDesign::initModelAndResultGroup() { m_pModelAndResultGroup = new QGroupBox(tr("Model and result"), this); QGridLayout* pGridLayout = new QGridLayout(m_pModelAndResultGroup); pGridLayout->setContentsMargins(8, 15, 8, 10); pGridLayout->setHorizontalSpacing(6); pGridLayout->setVerticalSpacing(6); QLabel* pCurrentModelLabel = new QLabel( tr("Current model"), m_pModelAndResultGroup); QLabel* pResultWellLabel = new QLabel( tr("Result well"), m_pModelAndResultGroup); pCurrentModelLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); pResultWellLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_pCurrentModelCombo = new QComboBox(m_pModelAndResultGroup); m_pResultWellCombo = new QComboBox(m_pModelAndResultGroup); QList listCombos; listCombos << m_pCurrentModelCombo << m_pResultWellCombo; for(int nIndex = 0; nIndex < listCombos.size(); ++nIndex) { listCombos[nIndex]->setFixedHeight(nmParameterField::editorHeight()); listCombos[nIndex]->setMinimumWidth( nmParameterField::editorMinimumWidth()); listCombos[nIndex]->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed); } // 按当前成果回填模型和结果井 updateCurrentModelDisplay(); fillResultWellCombo(); pGridLayout->addWidget(pCurrentModelLabel, 0, 0); pGridLayout->addWidget(m_pCurrentModelCombo, 0, 1); pGridLayout->addWidget(pResultWellLabel, 1, 0); pGridLayout->addWidget(m_pResultWellCombo, 1, 1); pGridLayout->setColumnStretch(0, 0); pGridLayout->setColumnStretch(1, 1); m_pModelAndResultGroup->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred); } // 按当前成果对应的数据管理器刷新模型下拉框选中项 void nmWxNumericalDesign::updateCurrentModelDisplay() { if(m_pCurrentModelCombo == nullptr) { return; } m_bFillingCurrentModelCombo = true; m_pCurrentModelCombo->clear(); nmDataAnalyzeManager* pManager = m_pDataManager; if(pManager == nullptr) { m_pCurrentModelCombo->setEnabled(false); m_bFillingCurrentModelCombo = false; return; } // 根据储层相态过滤可选模型类型 nmDataReservoir* pReservoir = pManager->getReservoirData(); NM_PHASE_TYPE ePhase = (pReservoir != nullptr) ? pReservoir->getPhaseType() : PHASE_UNKNOWN; NM_SOLVER_MODEL_TYPE eCurrentType = pManager->getSolverModelType(); switch(ePhase) { case PHASE_Oil: m_pCurrentModelCombo->addItem(solverModelDisplayName(SMT_Oil_ConstPvt), SMT_Oil_ConstPvt); m_pCurrentModelCombo->addItem(solverModelDisplayName(SMT_Oil_VariablePvt), SMT_Oil_VariablePvt); break; case PHASE_Water: m_pCurrentModelCombo->addItem(solverModelDisplayName(SMT_Water_ConstPvt), SMT_Water_ConstPvt); m_pCurrentModelCombo->addItem(solverModelDisplayName(SMT_Water_VariablePvt), SMT_Water_VariablePvt); break; case PHASE_Gas: m_pCurrentModelCombo->addItem(solverModelDisplayName(SMT_Gas_VariablePvt), SMT_Gas_VariablePvt); break; case PHASE_Oil_Water: m_pCurrentModelCombo->addItem(solverModelDisplayName(SMT_Oil_Water_TwoPhase), SMT_Oil_Water_TwoPhase); break; default: break; } int nIndex = m_pCurrentModelCombo->findData(eCurrentType); if(nIndex >= 0) { m_pCurrentModelCombo->setCurrentIndex(nIndex); } m_pCurrentModelCombo->setEnabled(true); m_bFillingCurrentModelCombo = false; } // 按参与计算井刷新下拉框 void nmWxNumericalDesign::fillResultWellCombo(const QString& sSelectedWellCode) { if(m_pResultWellCombo == nullptr) { return; } nmDataAnalyzeManager* pManager = m_pDataManager; m_bFillingResultWellCombo = true; m_pResultWellCombo->clear(); if(pManager == nullptr) { m_pResultWellCombo->setEnabled(false); m_bFillingResultWellCombo = false; return; } QVector vecResultWellCodes; QVector vecSolverOrder = pManager->getSolverWellOrder(); // 第一步:已有网格时按 DLL 的固定下标顺序收集可展示结果井。 // 无流量观察井仍在求解器顺序中,但不能出现在本下拉框。 for(int nOrderIndex = 0; nOrderIndex < vecSolverOrder.size(); ++nOrderIndex) { const nmSolverWellRef& oWellRef = vecSolverOrder[nOrderIndex]; if(oWellRef.m_eEntryKind == NM_SolverEntry_Well && pManager->isWellAvailableForResult(oWellRef.m_sWellCode) && !vecResultWellCodes.contains(oWellRef.m_sWellCode)) { vecResultWellCodes.append(oWellRef.m_sWellCode); } } // 第二步:尚未生成网格时从当前有效计算井中执行同一结果资格过滤。 if(vecResultWellCodes.isEmpty()) { QVector vecEffectiveWells = pManager->getEffectiveCalculationWells(); for(int nWellIndex = 0; nWellIndex < vecEffectiveWells.size(); ++nWellIndex) { const QString sWellCode = vecEffectiveWells[nWellIndex].m_sWellCode; if(pManager->isWellAvailableForResult(sWellCode)) { vecResultWellCodes.append(sWellCode); } } } for(int nWellIndex = 0; nWellIndex < vecResultWellCodes.size(); ++nWellIndex) { nmDataWellBase* pWellData = pManager->findWellByCode(vecResultWellCodes[nWellIndex]); if(pWellData != nullptr) { m_pResultWellCombo->addItem(pWellData->getWellName(), pWellData->getWellCode()); } } QString sCurrentWellCode = sSelectedWellCode; if(sCurrentWellCode.isEmpty()) { sCurrentWellCode = pManager->getCurrentResultWellCode(); } if(sCurrentWellCode.isEmpty()) { sCurrentWellCode = pManager->getPrimaryWellCode(); } int nIndex = m_pResultWellCombo->findData(sCurrentWellCode); if(nIndex < 0 && m_pResultWellCombo->count() > 0) { nIndex = 0; } if(nIndex >= 0) { m_pResultWellCombo->setCurrentIndex(nIndex); pManager->setCurrentResultWellCode( m_pResultWellCombo->itemData(nIndex).toString()); } m_pResultWellCombo->setEnabled(m_pResultWellCombo->count() > 1); m_bFillingResultWellCombo = false; } void nmWxNumericalDesign::initMainOptionsGroup() { m_pMainOptionsGroup = new QGroupBox(tr("Main options"), this); QGridLayout* pGridLayout = new QGridLayout(m_pMainOptionsGroup); // 使用 QGridLayout pGridLayout->setContentsMargins(8, 15, 8, 10); pGridLayout->setHorizontalSpacing(6); pGridLayout->setVerticalSpacing(6); // 第一行: Include other wells + Gear Icon (左侧), Reset from diagnostic (右侧) m_pIncludeOtherWellsCheck = new QCheckBox(tr("Include other wells"), this); if(m_pDataManager != nullptr) { m_pIncludeOtherWellsCheck->setChecked( m_pDataManager->getIncludeOtherWells()); } m_pOptionsGearButton = new QPushButton(this); m_pOptionsGearButton->setIcon(QIcon(m_sIconDir + "NmAnalDesign1.png")); m_pOptionsGearButton->setIconSize(QSize(20, 20)); m_pOptionsGearButton->setFixedSize(22, 22); m_pOptionsGearButton->setEnabled(m_pIncludeOtherWellsCheck->isChecked()); m_pResetDiagnosticButton = new QPushButton(tr("Reset from diagnostic"), this); m_pResetDiagnosticButton->setFixedHeight(nmParameterField::editorHeight()); m_pResetDiagnosticButton->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed); // 为左侧的复选框和齿轮创建一个QHBoxLayout,以便它们可以紧密排列 QHBoxLayout* pIncludeWellsLayout = new QHBoxLayout(); pIncludeWellsLayout->addWidget(m_pIncludeOtherWellsCheck); pIncludeWellsLayout->addWidget(m_pOptionsGearButton); pIncludeWellsLayout->addStretch(); // 将它们推到左侧 pGridLayout->addLayout(pIncludeWellsLayout, 0, 0, Qt::AlignLeft); // 第0行,第0列,左对齐 pGridLayout->addWidget(m_pResetDiagnosticButton, 0, 1); // 第二行: Impose pi (左侧), Reset from analytical (右侧) m_pImposePiCheck = new QCheckBox(tr("Impose pi"), this); m_pImposePiCheck->setChecked(true); // 默认选中 m_pResetAnalyticalButton = new QPushButton(tr("Reset from analytical"), this); m_pResetAnalyticalButton->setFixedHeight(nmParameterField::editorHeight()); m_pResetAnalyticalButton->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed); pGridLayout->addWidget(m_pImposePiCheck, 1, 0, Qt::AlignLeft); // 第1行,第0列,左对齐 pGridLayout->addWidget(m_pResetAnalyticalButton, 1, 1); // 第三行: Show average pressure (左侧), 右侧留空 m_pShowAveragePressureCheck = new QCheckBox(tr("Show average pressure"), this); m_pShowAveragePressureCheck->setChecked(false); // 默认未选中 pGridLayout->addWidget(m_pShowAveragePressureCheck, 2, 0, Qt::AlignLeft); // 第2行,第0列,左对齐 pGridLayout->setColumnStretch(0, 0); pGridLayout->setColumnStretch(1, 1); m_pMainOptionsGroup->setLayout(pGridLayout); // 设置分组框布局 m_pMainOptionsGroup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); } void nmWxNumericalDesign::initOutputGroup() { m_pOutputGroup = new QGroupBox(tr("Output"), this); QGridLayout* pGridLayout = new QGridLayout(m_pOutputGroup); // 设置网格的边距和间距,确保内容不会太贴近边缘 pGridLayout->setContentsMargins(10, 10, 10, 10); // 调整内边距 pGridLayout->setHorizontalSpacing(10); // 水平间距 pGridLayout->setVerticalSpacing(5); // 垂直间距 // 第一行: Output result fields (复选框) + Automatic (单选按钮) m_pOutputResultFieldsCheck = new QCheckBox(tr("Output result fields"), this); m_pOutputResultFieldsCheck->setChecked(true); // 默认选中 m_pOutputAutomaticRadio = new QRadioButton(tr("Automatic"), this); m_pOutputAutomaticRadio->setChecked(true); // 默认选中Automatic // 根据 m_pOutputResultFieldsCheck 的初始状态设置单选按钮的初始启用状态 bool isOutputChecked = m_pOutputResultFieldsCheck->isChecked(); m_pOutputAutomaticRadio->setEnabled(isOutputChecked); // // 将 Output result fields 放在第0行,第0列 pGridLayout->addWidget(m_pOutputResultFieldsCheck, 0, 0, Qt::AlignLeft); // 将 Automatic 放在第0行,第1列 pGridLayout->addWidget(m_pOutputAutomaticRadio, 0, 1, Qt::AlignLeft); // 第二行: Manual (单选按钮) 和 齿轮图标,挨在一起,并且整体在右边 m_pOutputManualRadio = new QRadioButton(tr("Manual"), this); m_pOutputManualIconButton = new QPushButton(this); m_pOutputManualIconButton->setIcon(QIcon(m_sIconDir + "NmAnalDesign1.png")); // 确认图标路径正确 m_pOutputManualIconButton->setIconSize(QSize(20, 20)); m_pOutputManualIconButton->setFixedSize(22, 22); // m_pOutputManualIconButton 的初始启用状态将由两个条件决定: // 1. m_pOutputResultFieldsCheck 是否被勾选 // 2. m_pOutputManualRadio 是否被选中 // 这里先根据 m_pOutputResultFieldsCheck 设置一个初始值,后续连接会完善逻辑 m_pOutputManualIconButton->setEnabled(isOutputChecked && m_pOutputManualRadio->isChecked()); // // 将 Manual 和 齿轮图标放在一个 QHBoxLayout 中,让它们挨在一起 QHBoxLayout* pManualAndIconButtonLayout = new QHBoxLayout(); pManualAndIconButtonLayout->addWidget(m_pOutputManualRadio); pManualAndIconButtonLayout->addWidget(m_pOutputManualIconButton); pManualAndIconButtonLayout->addStretch(); // 使它们紧靠在一起,并推到该布局的左侧 // 将包含 Manual 和图标的 QHBoxLayout 放在第1行,第1列,并右对齐 pGridLayout->addLayout(pManualAndIconButtonLayout, 1, 1, Qt::AlignRight); // 第三行: Output well drainage results (复选框) m_pOutputWellDrainageCheck = new QCheckBox(tr("Output well drainage results"), this); m_pOutputWellDrainageCheck->setChecked(false); // 默认未选中 m_pOutputWellDrainageCheck->setEnabled(isOutputChecked); // 初始状态也受主复选框控制 // 跨越所有列(0到1,共2列),左对齐 pGridLayout->addWidget(m_pOutputWellDrainageCheck, 2, 0, 1, 2, Qt::AlignLeft); // 设置列拉伸: // 第0列 (Output result fields): 不拉伸,只占其内容宽度 pGridLayout->setColumnStretch(0, 0); // 第1列 (Automatic / Manual & Icon group): 拉伸,以提供足够的空间并将右侧的 Manual & Icon group 推到右边 pGridLayout->setColumnStretch(1, 1); m_pOutputGroup->setLayout(pGridLayout); m_pOutputGroup->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); } void nmWxNumericalDesign::initAdvancedGroup() { m_pAdvancedGroup = new QGroupBox(tr("Advanced"), this); // 创建高级分组框 QVBoxLayout* pMainVLayout = new QVBoxLayout(m_pAdvancedGroup); // 高级组的垂直布局 // 时间相关复选框和关联的时钟图标按钮 QHBoxLayout* pTimeDependentLayout = new QHBoxLayout(); // 为时间相关复选框和图标创建水平布局 m_pTimeDependentCheck = new QCheckBox(tr("Time dependent"), this); // "时间相关"复选框 m_pTimeDependentCheck->setChecked(false); // 默认未选中 m_pTimeDependentIconButton = new QPushButton(this); // 时钟图标按钮 m_pTimeDependentIconButton->setIcon(QIcon(m_sIconDir + "NmAnalDesign3.png")); // 使用时钟图标 m_pTimeDependentIconButton->setIconSize(QSize(20, 20)); m_pTimeDependentIconButton->setFixedSize(22, 22); m_pTimeDependentIconButton->setEnabled(false); // 默认禁用,根据复选框状态启用 pTimeDependentLayout->addWidget(m_pTimeDependentCheck); // 添加复选框 pTimeDependentLayout->addWidget(m_pTimeDependentIconButton); // 添加图标按钮 pTimeDependentLayout->addStretch(); // 添加拉伸器,将它们推到左侧 pMainVLayout->addLayout(pTimeDependentLayout); // 将这个水平布局添加到主垂直布局 m_pUseWellIntakeCheck = new QCheckBox(tr("Use well intake"), this); // "使用井筒进水"复选框 m_pUseWellIntakeCheck->setChecked(false); // 默认未选中 pMainVLayout->addWidget(m_pUseWellIntakeCheck); // 添加到主垂直布局 QHBoxLayout* pRedefineKrPcLayout = new QHBoxLayout(); // "重新定义 KrPc"的水平布局 m_pRedefineKrPcCheck = new QCheckBox(tr("Redefine KrPc in hydraulic fractures"), this); // 复选框 m_pRedefineKrPcIconButton = new QPushButton(this); // 图标按钮 m_pRedefineKrPcIconButton->setIcon(QIcon(m_sIconDir + "KK.png")); // 根据图片,这个图标应该是曲线图 m_pRedefineKrPcIconButton->setIconSize(QSize(40, 40)); m_pRedefineKrPcIconButton->setFixedSize(42, 42); m_pRedefineKrPcIconButton->setEnabled(false); // 默认禁用 pRedefineKrPcLayout->addWidget(m_pRedefineKrPcCheck); pRedefineKrPcLayout->addWidget(m_pRedefineKrPcIconButton); pRedefineKrPcLayout->addStretch(); // 添加拉伸器 pMainVLayout->addLayout(pRedefineKrPcLayout); // 添加到主垂直布局 QHBoxLayout* pApplyUnconsolidationLayout = new QHBoxLayout(); // "应用未固结"的水平布局 m_pApplyUnconsolidationCheck = new QCheckBox(tr("Apply unconsolidation in hydraulic fractures"), this); // 复选框 m_pApplyUnconsolidationIconButton = new QPushButton(this); // 图标按钮 m_pApplyUnconsolidationIconButton->setIcon(QIcon(m_sIconDir + "SS.png")); // 根据图片,这个图标应该是方块压缩 m_pApplyUnconsolidationIconButton->setIconSize(QSize(47, 42)); m_pApplyUnconsolidationIconButton->setFixedSize(49, 44); m_pApplyUnconsolidationIconButton->setEnabled(false); // 默认禁用 pApplyUnconsolidationLayout->addWidget(m_pApplyUnconsolidationCheck); pApplyUnconsolidationLayout->addWidget(m_pApplyUnconsolidationIconButton); pApplyUnconsolidationLayout->addStretch(); // 添加拉伸器 pMainVLayout->addLayout(pApplyUnconsolidationLayout); // 添加到主垂直布局 m_pUseDFNCheck = new QCheckBox(tr("Use DFN"), this); // "使用 DFN"复选框 m_pUseDFNCheck->setChecked(false); // 默认未选中 pMainVLayout->addWidget(m_pUseDFNCheck); // 添加到主垂直布局 m_pAdvancedGroup->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); // 设置尺寸策略 } void nmWxNumericalDesign::initTimeSteppingGroup() { m_pTimeSteppingGroup = new QGroupBox(tr("Time stepping"), this); QGridLayout* pFieldLayout = new QGridLayout(m_pTimeSteppingGroup); pFieldLayout->setContentsMargins(8, 15, 8, 10); pFieldLayout->setHorizontalSpacing(6); pFieldLayout->setVerticalSpacing(6); pFieldLayout->setColumnStretch(0, 0); pFieldLayout->setColumnStretch(1, 8); pFieldLayout->setColumnStretch(2, 5); pFieldLayout->setColumnMinimumWidth( 1, nmParameterField::editorMinimumWidth()); pFieldLayout->setColumnMinimumWidth( 2, nmParameterField::unitMinimumWidth()); // 第一步:按稳定参数ID创建名称、数值和单位三列字段。 m_pTimeGrowthExponentField = new nmParameterField( createTimeStepFieldInfo(s_pTimeGrowthExponentId, false), this, m_pTimeSteppingGroup); m_pDtMinField = new nmParameterField( createTimeStepFieldInfo(s_pDtMinId, true), this, m_pTimeSteppingGroup); m_pDtMaxField = new nmParameterField( createTimeStepFieldInfo(s_pDtMaxId, true), this, m_pTimeSteppingGroup); // 第二步:逐项接入NM参数定义和框架单位绑定器。 QString sError; bool bInitialized = m_pTimeGrowthExponentField->initialize( pFieldLayout, 0, 0, sError) && m_pDtMinField->initialize( pFieldLayout, 1, 0, sError) && m_pDtMaxField->initialize( pFieldLayout, 2, 0, sError); if(!bInitialized) { m_pTimeSteppingGroup->setEnabled(false); m_pTimeSteppingGroup->setToolTip(sError); } m_pTimeGrowthExponentField->setValueRightAligned(true); m_pDtMinField->setValueRightAligned(true); m_pDtMaxField->setValueRightAligned(true); m_pTimeSteppingGroup->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred); } void nmWxNumericalDesign::initNumericalSettingsGroup() { m_pNumericalSettingsGroup = new QGroupBox(tr("Numerical settings"), this); // 创建数值设置分组框 QVBoxLayout* pMainVLayout = new QVBoxLayout(m_pNumericalSettingsGroup); // 主垂直布局,用于放置子布局 // 第一行:Use automatic settings 单选按钮 // 直接添加到主垂直布局 m_pUseAutomaticSettingsRadio = new QRadioButton(tr("Use automatic settings"), this); // "使用自动设置"单选按钮 m_pUseAutomaticSettingsRadio->setChecked(true); // 默认选中"使用自动设置" pMainVLayout->addWidget(m_pUseAutomaticSettingsRadio); // 第二行:Use custom settings 单选按钮 + 齿轮图标 QHBoxLayout* pCustomSettingsHLayout = new QHBoxLayout(); // 为第二行创建一个水平布局 m_pUseCustomSettingsRadio = new QRadioButton(tr("Use custom settings"), this); // "使用自定义设置"单选按钮 m_pCustomSettingsIconButton = new QPushButton(this); // 自定义设置旁边的图标按钮 m_pCustomSettingsIconButton->setIcon(QIcon(m_sIconDir + "NmAnalDesign1.png")); // 占位符图标 m_pCustomSettingsIconButton->setIconSize(QSize(20, 20)); m_pCustomSettingsIconButton->setFixedSize(22, 22); m_pCustomSettingsIconButton->setEnabled(false); // 默认禁用,当选择"使用自定义设置"时启用 // 这个值可以根据实际显示效果调整 pCustomSettingsHLayout->addWidget(m_pUseCustomSettingsRadio); pCustomSettingsHLayout->addWidget(m_pCustomSettingsIconButton); pCustomSettingsHLayout->addStretch(); // 添加拉伸器,确保按钮和图标靠左对齐 pMainVLayout->addLayout(pCustomSettingsHLayout); // 将第二行的水平布局添加到主垂直布局 m_pNumericalSettingsGroup->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); // 设置尺寸策略 } void nmWxNumericalDesign::initPebiSolverSettingsGroup() { m_pPebiSolverSettingsGroup = new QGroupBox(tr("Solver settings"), this); QGridLayout* pGridLayout = new QGridLayout(m_pPebiSolverSettingsGroup); pGridLayout->setContentsMargins(8, 15, 8, 10); pGridLayout->setHorizontalSpacing(6); pGridLayout->setVerticalSpacing(6); QLabel* pSolverTypeLabel = new QLabel(tr("Solver library"), this); QLabel* pOmpThreadsLabel = new QLabel(tr("OpenMP threads"), this); QLabel* pIluReuseStepsLabel = new QLabel(tr("ILU reuse steps"), this); pSolverTypeLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); pOmpThreadsLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); pIluReuseStepsLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_pPebiSolverTypeCombo = new QComboBox(this); m_pPebiSolverTypeCombo->addItem(tr("CPU accelerated solver"), nmDataAnalyzeManager::PebiSolverCpuAccelerated); m_pPebiSolverTypeCombo->addItem(tr("Original solver"), nmDataAnalyzeManager::PebiSolverOriginal); m_pPebiOmpThreadsCombo = new QComboBox(this); m_pPebiOmpThreadsCombo->addItem("1", 1); m_pPebiOmpThreadsCombo->addItem("2", 2); m_pPebiOmpThreadsCombo->addItem("4", 4); m_pPebiOmpThreadsCombo->addItem("8", 8); m_pPebiOmpThreadsCombo->addItem("16", 16); m_pPebiIluReuseStepsSpin = new QSpinBox(this); m_pPebiIluReuseStepsSpin->setRange(1, 100); m_pPebiSolverTypeCombo->setFixedHeight(nmParameterField::editorHeight()); m_pPebiOmpThreadsCombo->setFixedHeight(nmParameterField::editorHeight()); m_pPebiIluReuseStepsSpin->setFixedHeight(nmParameterField::editorHeight()); m_pPebiSolverTypeCombo->setMinimumWidth( nmParameterField::editorMinimumWidth()); m_pPebiOmpThreadsCombo->setMinimumWidth( nmParameterField::editorMinimumWidth()); m_pPebiIluReuseStepsSpin->setMinimumWidth( nmParameterField::editorMinimumWidth()); m_pPebiSolverTypeCombo->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed); m_pPebiOmpThreadsCombo->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed); m_pPebiIluReuseStepsSpin->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed); pGridLayout->addWidget(pSolverTypeLabel, 0, 0); pGridLayout->addWidget(m_pPebiSolverTypeCombo, 0, 1); pGridLayout->addWidget(pOmpThreadsLabel, 1, 0); pGridLayout->addWidget(m_pPebiOmpThreadsCombo, 1, 1); pGridLayout->addWidget(pIluReuseStepsLabel, 2, 0); pGridLayout->addWidget(m_pPebiIluReuseStepsSpin, 2, 1); pGridLayout->setColumnStretch(0, 0); pGridLayout->setColumnStretch(1, 1); m_pPebiSolverSettingsGroup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); } void nmWxNumericalDesign::updatePebiSolverSettingEnabled() { bool bAccelerationEnabled = m_pPebiSolverTypeCombo->itemData( m_pPebiSolverTypeCombo->currentIndex()).toInt() == nmDataAnalyzeManager::PebiSolverCpuAccelerated; m_pPebiOmpThreadsCombo->setEnabled(bAccelerationEnabled); m_pPebiIluReuseStepsSpin->setEnabled(bAccelerationEnabled); } void nmWxNumericalDesign::updatePebiSolverSettingsToData() { nmDataAnalyzeManager* pManager = m_pDataManager; if(pManager == nullptr) { return; } pManager->setPebiSolverType(m_pPebiSolverTypeCombo->itemData( m_pPebiSolverTypeCombo->currentIndex()).toInt()); pManager->setPebiOmpThreads(m_pPebiOmpThreadsCombo->itemData( m_pPebiOmpThreadsCombo->currentIndex()).toInt()); pManager->setPebiIluReuseSteps(m_pPebiIluReuseStepsSpin->value()); } void nmWxNumericalDesign::initButton() { // 创建自动拟合按钮 m_pAutomaticfittingBtn = new QPushButton(tr("Automatic fitting"), this); m_pAutomaticfittingBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); // 创建预测按钮 m_pForecastBtn = new QPushButton(tr("Forecast"), this); m_pForecastBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); // 创建敏感性分析按钮 m_pSensitiveBtn = new QPushButton(tr("Sensitive"), this); m_pSensitiveBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); // 创建右下角按钮 m_pGenerateButton = new QPushButton(tr("Generate"), this); // "生成"按钮 m_pGenerateButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); // 固定尺寸 // 创建按钮布局 m_pGenerateLayout = new QHBoxLayout(); // 水平布局 m_pGenerateLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum)); // 左侧添加拉伸器 m_pGenerateLayout->addWidget(m_pAutomaticfittingBtn); m_pGenerateLayout->addSpacing(10); m_pGenerateLayout->addWidget(m_pForecastBtn); m_pGenerateLayout->addSpacing(10); m_pGenerateLayout->addWidget(m_pSensitiveBtn); m_pGenerateLayout->addSpacing(10); m_pGenerateLayout->addWidget(m_pGenerateButton); } void nmWxNumericalDesign::setupConnections() { // 连接生成按钮的点击信号到槽函数 connect(m_pGenerateButton, SIGNAL(clicked()), this, SLOT(onGenerateClicked())); connect(m_pResultWellCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onResultWellChanged(int))); connect(m_pCurrentModelCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onCurrentModelChanged(int))); // 主选项组连接 // 连接齿轮图标按钮的槽函数 connect(m_pOptionsGearButton, SIGNAL(clicked()), this, SLOT(onOptionsIconClicked())); connect(m_pAutomaticfittingBtn, SIGNAL(clicked()), this, SLOT(onAutomaticFittingClicked())); connect(m_pForecastBtn, SIGNAL(clicked()), this, SLOT(onForecastClicked())); connect(m_pSensitiveBtn, SIGNAL(clicked()), this, SLOT(onSensitiveClicked())); // 更新齿轮图标按钮的启用状态 connect(m_pIncludeOtherWellsCheck, SIGNAL(toggled(bool)), m_pOptionsGearButton, SLOT(setEnabled(bool))); connect(m_pIncludeOtherWellsCheck, SIGNAL(toggled(bool)), this, SLOT(onIncludeOtherWellsToggled(bool))); // Map井增删或产量、压力目标变化后,立即更新多井选项是否可用。 if(m_pDataManager != nullptr) { connect(m_pDataManager, SIGNAL(dataChanged()), this, SLOT(onWellCollectionChanged()), Qt::UniqueConnection); // createWell() 会在井对象登记时先发送 dataChanged(),此时 WellCode、 // 坐标和历史流量可能尚未填充。sigWellAdded 表示井已完整建立,必须再次 // 刷新 Include Other Wells,才能正确识别刚加入 Map 的有产量井。 connect(m_pDataManager, SIGNAL(sigWellAdded(QString,QString,QStringList)), this, SLOT(onWellCollectionChanged()), Qt::UniqueConnection); } connect(m_pResetDiagnosticButton, SIGNAL(clicked()), this, SLOT(onResetFromDiagnosticClicked())); // "从诊断复位"按钮连接 connect(m_pResetAnalyticalButton, SIGNAL(clicked()), this, SLOT(onResetFromAnalyticalClicked())); // "从解析复位"按钮连接 // 输出组连接 // 新增连接:m_pOutputResultFieldsCheck 控制其他相关组件的启用/禁用 connect(m_pOutputResultFieldsCheck, SIGNAL(toggled(bool)), this, SLOT(onOutputResultFieldsToggled(bool))); // connect(m_pOutputAutomaticRadio, SIGNAL(toggled(bool)), this, SLOT(onOutputResultFieldsAutomaticToggled(bool))); // "自动"单选按钮连接 connect(m_pOutputManualRadio, SIGNAL(toggled(bool)), this, SLOT(onOutputResultFieldsManualToggled(bool))); // "手动"单选按钮连接 connect(m_pOutputManualIconButton, SIGNAL(clicked()), this, SLOT(onManualOutputSettingsClicked())); // "手动"图标按钮连接 // 高级组连接 // 在 setupConnections() 中连接 pTimeDependentIconButton 的信号槽,或者在这里连接 connect(m_pTimeDependentCheck, SIGNAL(toggled(bool)), m_pTimeDependentIconButton, SLOT(setEnabled(bool))); connect(m_pTimeDependentCheck, SIGNAL(toggled(bool)), this, SLOT(onTimeDependentCheck(bool))); // 如果这个图标按钮有自己的点击逻辑,需要连接到槽函数 connect(m_pTimeDependentIconButton, SIGNAL(clicked()), this, SLOT(onTimeIconClicked())); // "重新定义 KrPc"图标按钮的启用状态取决于复选框 connect(m_pRedefineKrPcCheck, SIGNAL(toggled(bool)), m_pRedefineKrPcIconButton, SLOT(setEnabled(bool))); connect(m_pRedefineKrPcIconButton, SIGNAL(clicked()), this, SLOT(onRedefineKrPcIconClicked())); // 图标按钮点击连接 // "应用未固结"图标按钮的启用状态取决于复选框 connect(m_pApplyUnconsolidationCheck, SIGNAL(toggled(bool)), m_pApplyUnconsolidationIconButton, SLOT(setEnabled(bool))); connect(m_pApplyUnconsolidationIconButton, SIGNAL(clicked()), this, SLOT(onApplyUnconsolidationIconClicked())); // 图标按钮点击连接 // 时间步字段输入过程中只刷新校验,编辑完成后再整体提交。 QList listTimeStepFields; listTimeStepFields << m_pTimeGrowthExponentField << m_pDtMinField << m_pDtMaxField; for(int nIndex = 0; nIndex < listTimeStepFields.size(); ++nIndex) { QLineEdit* pLineEdit = listTimeStepFields[nIndex] != nullptr ? listTimeStepFields[nIndex]->valueEditor() : nullptr; if(pLineEdit == nullptr) { continue; } connect(pLineEdit, SIGNAL(textChanged(QString)), this, SLOT(onTimeStepValueChanged(QString))); connect(pLineEdit, SIGNAL(editingFinished()), this, SLOT(onTimeStepEditingFinished())); } // 数值设置组连接 // "自定义设置"图标按钮的启用状态取决于"使用自定义设置"单选按钮 connect(m_pUseCustomSettingsRadio, SIGNAL(toggled(bool)), m_pCustomSettingsIconButton, SLOT(setEnabled(bool))); connect(m_pCustomSettingsIconButton, SIGNAL(clicked()), this, SLOT(onCustomNumericalSettingsClicked())); // 图标按钮点击连接 // PEBI求解器设置组连接 connect(m_pPebiSolverTypeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onPebiSolverTypeChanged(int))); connect(m_pPebiOmpThreadsCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onPebiSolverSettingsChanged())); connect(m_pPebiIluReuseStepsSpin, SIGNAL(valueChanged(int)), this, SLOT(onPebiSolverSettingsChanged())); } void nmWxNumericalDesign::onPebiSolverTypeChanged(int index) { if(index < 0) { return; } updatePebiSolverSettingEnabled(); updatePebiSolverSettingsToData(); } void nmWxNumericalDesign::onPebiSolverSettingsChanged() { updatePebiSolverSettingsToData(); } void nmWxNumericalDesign::onTimeStepValueChanged(const QString& sText) { Q_UNUSED(sText); double dTimeGrowthExponent = 0.0; double dDtMin = 0.0; double dDtMax = 0.0; QString sError; collectTimeStepValues(dTimeGrowthExponent, dDtMin, dDtMax, sError); } void nmWxNumericalDesign::onTimeStepEditingFinished() { QString sError; if(!updateDataFromUi(sError)) { focusFirstInvalidTimeStepField(); } } void nmWxNumericalDesign::onTimeDependentCheck(bool checked) { // 更新当前井的时间变表皮状态 nmDataWellBase* pCurrentWell = m_pDataManager != nullptr ? m_pDataManager->getCurWellData() : nullptr; if(pCurrentWell) { pCurrentWell->setTimeDependentSkin(checked); } } // 切换查看井后同步当前井,并刷新结果曲线 void nmWxNumericalDesign::onResultWellChanged(int index) { if(m_bFillingResultWellCombo || index < 0 || m_pResultWellCombo == nullptr) { return; } QString sWellCode = m_pResultWellCombo->itemData(index).toString(); if(sWellCode.isEmpty()) { return; } nmDataAnalyzeManager* pManager = m_pDataManager; if(pManager != nullptr) { // 结果井切换只更新查看状态,不改变主分析井和参数编辑井。 pManager->setCurrentResultWellCode(sWellCode); } // 复用主窗口已有的井结果刷新逻辑 emit sigResultWellChanged(sWellCode); } void nmWxNumericalDesign::onIncludeOtherWellsToggled(bool bChecked) { if(m_pDataManager == nullptr) { return; } // 总开关只控制有效计算井集合;已经勾选的井保留,重新打开时无需再次选择。 m_pDataManager->setIncludeOtherWells(bChecked); fillResultWellCombo(m_pDataManager->getPrimaryWellCode()); } void nmWxNumericalDesign::onWellCollectionChanged() { // 这里只刷新多井入口,避免Map编辑时覆盖用户尚未提交的其他数值设置。 updateIncludeOtherWellsAvailability(); } // 当前模型下拉框切换槽,将用户选择的模型类型同步到数据管理器 void nmWxNumericalDesign::onCurrentModelChanged(int index) { if(m_bFillingCurrentModelCombo || index < 0 || m_pCurrentModelCombo == nullptr) { return; } NM_SOLVER_MODEL_TYPE eType = static_cast( m_pCurrentModelCombo->itemData(index).toInt()); if(eType <= 0) { return; } nmDataAnalyzeManager* pManager = m_pDataManager; if(pManager != nullptr) { pManager->setSolverModelType(eType); } } void nmWxNumericalDesign::onGenerateClicked() { QString sError; if(!updateDataFromUi(sError)) { focusFirstInvalidTimeStepField(); QMessageBox::warning(this, tr("Invalid time step"), sError); return; } emit sigGenerateClicked(); } void nmWxNumericalDesign::onResetFromDiagnosticClicked() { nmDataAnalyzeManager* pManager = m_pDataManager; if(!pManager) { QMessageBox::warning(this, tr("Warning"), tr("No data manager available.")); return; } if(pManager->resetFromDiagnostic()) { // 通知参数面板更新显示 nmWxParameterProperty::notifyUpdateTable(); } else { QMessageBox::warning(this, tr("Warning"), tr("Failed to reset from diagnostic. Please check if log-log data is available.")); } } void nmWxNumericalDesign::onResetFromAnalyticalClicked() { nmDataAnalyzeManager* pManager = m_pDataManager; if(!pManager) { QMessageBox::warning(this, tr("Warning"), tr("No data manager available.")); return; } if(pManager->resetFromAnalytical()) { // 通知参数面板更新显示 nmWxParameterProperty::notifyUpdateTable(); } else { QMessageBox::warning(this, tr("Warning"), tr("Failed to reset to analytical defaults.")); } } void nmWxNumericalDesign::onManualOutputSettingsClicked() { // 占位符:显示消息框 QMessageBox::information(this, tr("Output Settings"), tr("Manual output settings clicked.")); } void nmWxNumericalDesign::onRedefineKrPcIconClicked() { // 占位符:显示消息框 QMessageBox::information(this, tr("Advanced Settings"), tr("Redefine KrPc settings clicked.")); } void nmWxNumericalDesign::onApplyUnconsolidationIconClicked() { // 占位符:显示消息框 QMessageBox::information(this, tr("Advanced Settings"), tr("Apply unconsolidation settings clicked.")); } void nmWxNumericalDesign::onCustomNumericalSettingsClicked() { // 占位符:显示消息框 QMessageBox::information(this, tr("Numerical Settings"), tr("Custom numerical settings clicked.")); } // 针对高级组中的"时间相关"按钮的槽函数 void nmWxNumericalDesign::onTimeIconClicked() { nmWxTimeDependentSkin::showForCurrentWell(); } void nmWxNumericalDesign::onOutputResultFieldsAutomaticToggled(bool checked) { // 当"自动"单选按钮被选中时,禁用手动输出设置按钮 if(checked) { m_pOutputManualIconButton->setEnabled(false); } } void nmWxNumericalDesign::onOutputResultFieldsManualToggled(bool checked) { // 当"手动"单选按钮被选中时,启用手动输出设置按钮 if(checked) { m_pOutputManualIconButton->setEnabled(true); } } void nmWxNumericalDesign::onOptionsIconClicked() { nmWxIncludeOtherWells dlg(m_pDataManager, this); // 创建"包含其他井"对话框实例 if(m_pDataManager == nullptr) { return; } if(dlg.exec() == QDialog::Accepted) { QVector vecWellRows = dlg.getWellData(); QVector vecIncludedWells; // 第一步:这里只收集 Map 中被勾选的有产量干扰井。主井由分析方案 // 单独保存;无产量观察井由数据管理层自动加入,与本对话框无关。 for(int nRowIndex = 0; nRowIndex < vecWellRows.size(); ++nRowIndex) { const WellDataRow& oWellRow = vecWellRows[nRowIndex]; if(oWellRow.m_bIsPrimary || !oWellRow.m_bIsIncluded || !oWellRow.canCalculate()) { continue; } nmDataWellBase* pWellData = m_pDataManager->findWellByCode(oWellRow.m_sWellCode); if(pWellData == nullptr) { // Map 中已经移除的井不能被 Include 对话框重新创建。 continue; } if(pWellData->getFlowPoints().size() < 2) { // 对话框打开后若产量数据发生变化,提交时仍要阻止它混入包含列表。 continue; } vecIncludedWells.append( nmCalculationWellRef(oWellRow.m_sWellCode, NM_CaseWell_RateControlled)); // 第二步:为被包含的有产量井同步历史压力、双对数和半对数数据。 QVector > vecHistoryPressure; QVector > vecHistoryLogLog; QVector > vecHistorySemiLog; m_pDataManager->calculationLogData(pWellData, vecHistoryPressure, vecHistoryLogLog, vecHistorySemiLog); pWellData->setHistoryPressure(vecHistoryPressure); pWellData->setHistoryLogLog(vecHistoryLogLog); pWellData->setHistorySemiLog(vecHistorySemiLog); } // 第三步:一次性提交选择,内部会使旧网格和旧结果失效。 m_pDataManager->setIncludedCalculationWells(vecIncludedWells); m_pDataManager->setIncludeOtherWells( m_pIncludeOtherWellsCheck->isChecked()); nmWxParameterProperty::notifyUpdateTable(); fillResultWellCombo(m_pDataManager->getPrimaryWellCode()); } } void nmWxNumericalDesign::onOutputResultFieldsToggled(bool checked) { // 根据主复选框的状态启用或禁用相关组件 m_pOutputAutomaticRadio->setEnabled(checked); // m_pOutputManualRadio->setEnabled(checked); // // 特别处理 m_pOutputManualIconButton: // 它只有在 m_pOutputResultFieldsCheck 勾选 且 m_pOutputManualRadio 选中时才启用 if(checked && m_pOutputManualRadio->isChecked()) { // m_pOutputManualIconButton->setEnabled(true); // } else { m_pOutputManualIconButton->setEnabled(false); // } } void nmWxNumericalDesign::updateUiFromData() { nmDataAnalyzeManager* pManager = m_pDataManager; if(pManager == nullptr) { return; } // 第一步:刷新与当前成果、模型和井相关的选择控件。 updateIncludeOtherWellsAvailability(); fillResultWellCombo(); updateCurrentModelDisplay(); updateTimeDependentSkinDisplay(); // 第二步:业务层始终保存小时基准值,单位绑定器负责转换显示。 nmDataTimeStepSetting* pTimeStepSetting = pManager->getTimeStep(); if(pTimeStepSetting != nullptr) { m_pTimeGrowthExponentField->setBaseValue( pTimeStepSetting->getTimeGrowthExponent().getValue()); m_pDtMinField->setBaseValue( pTimeStepSetting->getMinDeltaTAttribute().getValue()); m_pDtMaxField->setBaseValue( pTimeStepSetting->getMaxDeltaTAttribute().getValue()); } // 第三步:回填PEBI设置时屏蔽即时提交信号。 m_pPebiSolverTypeCombo->blockSignals(true); m_pPebiOmpThreadsCombo->blockSignals(true); m_pPebiIluReuseStepsSpin->blockSignals(true); int nSolverTypeIndex = m_pPebiSolverTypeCombo->findData(pManager->getPebiSolverType()); if(nSolverTypeIndex < 0) { nSolverTypeIndex = m_pPebiSolverTypeCombo->findData( nmDataAnalyzeManager::PebiSolverCpuAccelerated); } m_pPebiSolverTypeCombo->setCurrentIndex(nSolverTypeIndex); int nOmpThreadsIndex = m_pPebiOmpThreadsCombo->findData(pManager->getPebiOmpThreads()); if(nOmpThreadsIndex < 0) { nOmpThreadsIndex = m_pPebiOmpThreadsCombo->findData(4); } m_pPebiOmpThreadsCombo->setCurrentIndex(nOmpThreadsIndex); m_pPebiIluReuseStepsSpin->setValue(pManager->getPebiIluReuseSteps()); m_pPebiSolverTypeCombo->blockSignals(false); m_pPebiOmpThreadsCombo->blockSignals(false); m_pPebiIluReuseStepsSpin->blockSignals(false); updatePebiSolverSettingEnabled(); } void nmWxNumericalDesign::updateIncludeOtherWellsAvailability() { if(m_pDataManager == nullptr || m_pIncludeOtherWellsCheck == nullptr || m_pOptionsGearButton == nullptr) { return; } // 第一步:只有参考井以外、已经加入 Map 且有有效产量制度的井才是 // Include Other Wells 候选。无产量观察井不控制这个开关。 bool bHasEligibleOtherWell = false; const QString sPrimaryWellCode = m_pDataManager->getPrimaryWellCode(); QVector vecMapWells = m_pDataManager->getWellDataList(); for(int nIndex = 0; nIndex < vecMapWells.size(); ++nIndex) { nmDataWellBase* pWellData = vecMapWells[nIndex]; if(!isSupportedAdditionalWell(pWellData) || pWellData->getWellCode() == sPrimaryWellCode) { continue; } const bool bHasRateTarget = pWellData->getFlowPoints().size() >= 2; if(bHasRateTarget) { bHasEligibleOtherWell = true; break; } } // 第二步:没有有产量候选井时关闭该开关;自动观察井仍会继续参与网格和计算。 if(!bHasEligibleOtherWell && m_pDataManager->getIncludeOtherWells()) { m_pDataManager->setIncludeOtherWells(false); } // 第三步:屏蔽界面信号后同步状态;齿轮只在总开关已打开时可用。 const bool bIncludeChecked = bHasEligibleOtherWell && m_pDataManager->getIncludeOtherWells(); const bool bSignalsWereBlocked = m_pIncludeOtherWellsCheck->blockSignals(true); m_pIncludeOtherWellsCheck->setEnabled(bHasEligibleOtherWell); m_pIncludeOtherWellsCheck->setChecked(bIncludeChecked); m_pIncludeOtherWellsCheck->blockSignals(bSignalsWereBlocked); m_pOptionsGearButton->setEnabled( bHasEligibleOtherWell && bIncludeChecked); } bool nmWxNumericalDesign::updateDataFromUi(QString& sError) { nmDataAnalyzeManager* pManager = m_pDataManager; if(pManager == nullptr) { sError = tr("No data manager available."); return false; } nmDataTimeStepSetting* pTimeStepSetting = pManager->getTimeStep(); if(pTimeStepSetting == nullptr) { sError = tr("Time step settings are unavailable."); return false; } // 第一步:先读取全部基准值,任何错误都不能产生部分写入。 double dTimeGrowthExponent = 0.0; double dDtMin = 0.0; double dDtMax = 0.0; if(!collectTimeStepValues(dTimeGrowthExponent, dDtMin, dDtMax, sError)) { return false; } // 第二步:单位绑定器返回小时基准值,数据单位及持久化结构保持不变。 const double dOldTimeGrowthExponent = pTimeStepSetting->getTimeGrowthExponent().getValue().toDouble(); const double dOldDtMin = pTimeStepSetting->getMinDeltaTAttribute().getValue().toDouble(); const double dOldDtMax = pTimeStepSetting->getMaxDeltaTAttribute().getValue().toDouble(); pTimeStepSetting->getTimeGrowthExponent().setValue( dTimeGrowthExponent); pTimeStepSetting->getMinDeltaTAttribute().setValue(dDtMin); pTimeStepSetting->getMaxDeltaTAttribute().setValue(dDtMax); // 第三步:时间离散改变时旧结果失效,但二维 PEBI 网格仍可直接复用。 if(!qFuzzyCompare(dOldTimeGrowthExponent, dTimeGrowthExponent) || !qFuzzyCompare(dOldDtMin, dDtMin) || !qFuzzyCompare(dOldDtMax, dDtMax)) { pManager->invalidatePebiResults(); } updatePebiSolverSettingsToData(); sError.clear(); return true; } bool nmWxNumericalDesign::collectTimeStepValues( double& dTimeGrowthExponent, double& dDtMin, double& dDtMax, QString& sError) { if(m_pTimeGrowthExponentField == nullptr || m_pDtMinField == nullptr || m_pDtMaxField == nullptr) { sError = tr("Time step fields are unavailable."); return false; } // 第一步:清除上一次跨字段校验错误,再执行各字段格式和范围校验。 m_pTimeGrowthExponentField->setExternalValidationError(QString()); m_pDtMinField->setExternalValidationError(QString()); m_pDtMaxField->setExternalValidationError(QString()); bool bGrowthValid = false; bool bDtMinValid = false; bool bDtMaxValid = false; QVariant oGrowthValue = m_pTimeGrowthExponentField->baseValue(bGrowthValid); QVariant oDtMinValue = m_pDtMinField->baseValue(bDtMinValid); QVariant oDtMaxValue = m_pDtMaxField->baseValue(bDtMaxValid); if(!bGrowthValid || !bDtMinValid || !bDtMaxValid || !oGrowthValue.isValid() || !oDtMinValue.isValid() || !oDtMaxValue.isValid()) { sError = tr("Please correct the invalid time step values."); return false; } dTimeGrowthExponent = oGrowthValue.toDouble(); dDtMin = oDtMinValue.toDouble(); dDtMax = oDtMaxValue.toDouble(); // 第二步:单字段合法后再校验最小、最大时间步的业务关系。 if(dDtMin >= dDtMax) { sError = tr("Dt min must be less than Dt max."); m_pDtMaxField->setExternalValidationError(sError); return false; } sError.clear(); return true; } void nmWxNumericalDesign::focusFirstInvalidTimeStepField() { QList listFields; listFields << m_pTimeGrowthExponentField << m_pDtMinField << m_pDtMaxField; for(int nIndex = 0; nIndex < listFields.size(); ++nIndex) { nmParameterField* pField = listFields[nIndex]; if(pField != nullptr && !pField->validate() && pField->valueEditor() != nullptr) { pField->valueEditor()->setFocus(); pField->valueEditor()->selectAll(); return; } } } void nmWxNumericalDesign::updateTimeDependentSkinDisplay() { if(m_pTimeDependentCheck == nullptr || m_pTimeDependentIconButton == nullptr) { return; } nmDataWellBase* pCurrentWell = m_pDataManager != nullptr ? m_pDataManager->getCurWellData() : nullptr; bool bTimeDependent = pCurrentWell != nullptr && pCurrentWell->isTimeDependentSkin(); bool bSignalsWereBlocked = m_pTimeDependentCheck->blockSignals(true); m_pTimeDependentCheck->setChecked(bTimeDependent); m_pTimeDependentCheck->setEnabled(pCurrentWell != nullptr); m_pTimeDependentCheck->blockSignals(bSignalsWereBlocked); m_pTimeDependentIconButton->setEnabled(bTimeDependent); } void nmWxNumericalDesign::onAutomaticFittingClicked() { nmWxAutomaticFitting* dlg = new nmWxAutomaticFitting; dlg->show(); } void nmWxNumericalDesign::onForecastClicked() { nmWxForecast* dlg = new nmWxForecast(); dlg->show(); } void nmWxNumericalDesign::onSensitiveClicked() { nmWxSensitive* dlg = new nmWxSensitive(); dlg->show(); } // 实现通知方法 void nmWxNumericalDesign::notifyTimeDependentSkinChanged(const QString& wellName, bool checked) { // 检查当前是否有活动实例 if(!s_pCurrentInstance) { return; } // 使用活动Dock所属的数据管理器,避免多成果窗口间读取到其他成果的数据。 nmDataAnalyzeManager* pDataManager = s_pCurrentInstance->m_pDataManager; nmDataWellBase* pCurrentWell = pDataManager != nullptr ? pDataManager->getCurWellData() : nullptr; if(!pCurrentWell) { return; } // 检查是否是同一口井 if(pCurrentWell->getWellName() == wellName) { // 阻塞信号,避免循环触发 s_pCurrentInstance->m_pTimeDependentCheck->blockSignals(true); // 更新复选框状态 s_pCurrentInstance->m_pTimeDependentCheck->setChecked(checked); // 恢复信号 s_pCurrentInstance->m_pTimeDependentCheck->blockSignals(false); // 同时更新图标按钮的启用状态 s_pCurrentInstance->m_pTimeDependentIconButton->setEnabled(checked); } }