#include "nmReservoirParameterPage.h" #include "nmReservoirParameterField.h" #include "nmReservoirPropertiesSession.h" #include #include #include #include #include #include #include #include nmReservoirParameterPage::nmReservoirParameterPage( nmReservoirPropertiesCategory eCategory, nmReservoirPropertiesSession* pSession, nmIReservoirParameterUnitService* pUnitService, QWidget* pParent) : QWidget(pParent), m_eCategory(eCategory), m_pSession(pSession), m_pUnitService(pUnitService), m_nCurrentObjectIndex(-1), m_bUpdatingObject(false), m_bBuildingFields(false), m_pObjectDisplayGroup(nullptr), m_pObjectLabel(nullptr), m_pObjectComboBox(nullptr), m_pGroupsWidget(nullptr), m_pGroupsLayout(nullptr), m_pMainLayout(nullptr), m_pEmptyLabel(nullptr) { createUi(); } nmReservoirParameterPage::~nmReservoirParameterPage() { m_listFields.clear(); m_mapFields.clear(); m_mapEditorFields.clear(); m_pSession = nullptr; m_pUnitService = nullptr; } void nmReservoirParameterPage::createUi() { // 第一步:创建单行对象显示控制区,储层页自动隐藏该分组。 m_pMainLayout = new QVBoxLayout(this); m_pMainLayout->setContentsMargins(8, 6, 8, 8); m_pMainLayout->setSpacing(8); m_pObjectDisplayGroup = new QGroupBox(tr("Object display"), this); m_pObjectDisplayGroup->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed); QHBoxLayout* pObjectLayout = new QHBoxLayout(m_pObjectDisplayGroup); pObjectLayout->setContentsMargins(8, 15, 8, 8); pObjectLayout->setSpacing(9); m_pObjectLabel = new QLabel(tr("Display:"), m_pObjectDisplayGroup); m_pObjectLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_pObjectComboBox = new QComboBox(m_pObjectDisplayGroup); m_pObjectComboBox->setMinimumWidth(260); m_pObjectComboBox->setMaximumWidth(420); m_pObjectComboBox->setMinimumHeight(27); pObjectLayout->addStretch(1); pObjectLayout->addWidget(m_pObjectLabel); pObjectLayout->addWidget(m_pObjectComboBox); pObjectLayout->addStretch(1); m_pMainLayout->addWidget(m_pObjectDisplayGroup); m_pObjectDisplayGroup->setVisible( m_eCategory != NM_RESERVOIR_PAGE_RESERVOIR); // 第二步:分组载体没有边框,仅用于实现原型中的两列网格。 m_pGroupsWidget = new QWidget(this); m_pGroupsLayout = new QGridLayout(m_pGroupsWidget); m_pGroupsLayout->setContentsMargins(0, 0, 0, 0); m_pGroupsLayout->setHorizontalSpacing(14); m_pGroupsLayout->setVerticalSpacing(14); m_pGroupsLayout->setColumnStretch(0, 1); m_pGroupsLayout->setColumnStretch(1, 1); m_pMainLayout->addWidget(m_pGroupsWidget); m_pMainLayout->addStretch(1); connect(m_pObjectComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotObjectChanged(int))); } bool nmReservoirParameterPage::reloadObjects(QString& sError) { // 第一步:重新取得会话对象描述,并把稳定对象code放入UserRole。 m_listObjectInfos = m_pSession != nullptr ? m_pSession->objectInfos(m_eCategory) : QList(); m_bUpdatingObject = true; m_pObjectComboBox->clear(); for (int nIndex = 0; nIndex < m_listObjectInfos.size(); ++nIndex) { const nmReservoirPropertyObjectInfo& oInfo = m_listObjectInfos[nIndex]; m_pObjectComboBox->addItem(oInfo.m_sDisplayName, oInfo.m_sObjectCode); } m_bUpdatingObject = false; bool bShowSelector = m_eCategory != NM_RESERVOIR_PAGE_RESERVOIR; m_pObjectDisplayGroup->setVisible(bShowSelector); // 第二步:构建首个对象;空分类显示无额外面板的空状态。 return buildObject(m_listObjectInfos.isEmpty() ? -1 : 0, sError); } bool nmReservoirParameterPage::hasEditableObjects() const { return !m_listObjectInfos.isEmpty(); } void nmReservoirParameterPage::slotObjectChanged(int nIndex) { if (m_bUpdatingObject || nIndex == m_nCurrentObjectIndex) { return; } // 第一步:离开旧对象前校验并保存,非法输入时回退对象选择。 int nPreviousIndex = m_nCurrentObjectIndex; if (nPreviousIndex >= 0 && !commitPendingEdits()) { m_bUpdatingObject = true; m_pObjectComboBox->setCurrentIndex(nPreviousIndex); m_bUpdatingObject = false; return; } // 第二步:构建新对象;目录已在初始化阶段验证,正常切换不会失败。 QString sError; if (!buildObject(nIndex, sError)) { m_bUpdatingObject = true; m_pObjectComboBox->setCurrentIndex(nPreviousIndex); m_bUpdatingObject = false; } } bool nmReservoirParameterPage::buildObject(int nObjectIndex, QString& sError) { m_bBuildingFields = true; clearObjectEditors(); m_nCurrentObjectIndex = nObjectIndex; if (nObjectIndex < 0 || nObjectIndex >= m_listObjectInfos.size()) { m_pEmptyLabel = new QLabel( tr("No editable objects in this category."), m_pGroupsWidget); m_pEmptyLabel->setAlignment(Qt::AlignCenter); m_pEmptyLabel->setMinimumHeight(260); m_pGroupsLayout->addWidget(m_pEmptyLabel, 0, 0, 1, 2); m_bBuildingFields = false; sError.clear(); updateValidity(); return true; } // 第一步:按目录直接创建分组,页面不再包含对象类型专用布局代码。 const nmReservoirPropertyObjectInfo& oObjectInfo = m_listObjectInfos[nObjectIndex]; for (int nGroupIndex = 0; nGroupIndex < oObjectInfo.m_listGroups.size(); ++nGroupIndex) { if (!buildGroup(oObjectInfo.m_listGroups[nGroupIndex], sError)) { m_bBuildingFields = false; return false; } } // 第二步:加载工作副本,随后只调整行可见性,不重建页面。 for (int nFieldIndex = 0; nFieldIndex < m_listFields.size(); ++nFieldIndex) { loadFieldValue(m_listFields[nFieldIndex]); } applyVisibilityRules(); m_bBuildingFields = false; sError.clear(); updateValidity(); return true; } void nmReservoirParameterPage::clearObjectEditors() { m_listFields.clear(); m_mapFields.clear(); m_mapEditorFields.clear(); m_pEmptyLabel = nullptr; // 分组载体本身保留,只删除其直接子控件和布局项。 while (m_pGroupsLayout->count() > 0) { QLayoutItem* pItem = m_pGroupsLayout->takeAt(0); if (pItem->widget() != nullptr) { delete pItem->widget(); } delete pItem; } } bool nmReservoirParameterPage::buildGroup( const nmReservoirParameterGroupInfo& oGroupInfo, QString& sError) { // 承载器始终横向铺满网格单元;只通过内部弹簧控制垂直位置, // 避免直接设置对齐标志后分组框退回建议宽度。 QWidget* pGroupContainer = new QWidget(m_pGroupsWidget); pGroupContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); QVBoxLayout* pGroupContainerLayout = new QVBoxLayout(pGroupContainer); pGroupContainerLayout->setContentsMargins(0, 0, 0, 0); pGroupContainerLayout->setSpacing(0); QGroupBox* pGroupBox = new QGroupBox( oGroupInfo.m_sTitle, pGroupContainer); // 参数框只在水平方向随页面宽度调整,垂直方向始终采用内容建议高度, // 避免跨行分组被网格拉伸成大面积空白框。 pGroupBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); QGridLayout* pFieldLayout = new QGridLayout(pGroupBox); pFieldLayout->setContentsMargins(8, 15, 8, 10); pFieldLayout->setHorizontalSpacing(6); pFieldLayout->setVerticalSpacing(6); int nFieldColumnOffset = 0; // 字段较少的通栏分组使用独立的左右弹性列, // 使参数区域保持居中,输入列仍可随窗口宽度适度伸展。 if (oGroupInfo.m_bFieldsCentered) { nFieldColumnOffset = 1; pFieldLayout->setColumnStretch(0, 1); pFieldLayout->setColumnStretch(1, 0); pFieldLayout->setColumnStretch(2, 1); pFieldLayout->setColumnStretch(3, 0); pFieldLayout->setColumnStretch(4, 1); } else { // 常规分组由最长标签决定标签列宽,数值列和单位列按比例伸展。 pFieldLayout->setColumnStretch(0, 0); pFieldLayout->setColumnStretch(1, 8); pFieldLayout->setColumnStretch(2, 5); } pFieldLayout->setColumnMinimumWidth( nFieldColumnOffset + 1, nmReservoirParameterField::editorMinimumWidth()); pFieldLayout->setColumnMinimumWidth( nFieldColumnOffset + 2, nmReservoirParameterField::unitMinimumWidth()); // 每个字段只把三个直接控件放入此网格,不产生字段外框或子面板。 for (int nFieldIndex = 0; nFieldIndex < oGroupInfo.m_listFields.size(); ++nFieldIndex) { nmReservoirParameterField* pField = new nmReservoirParameterField( oGroupInfo.m_listFields[nFieldIndex], m_pUnitService, pGroupBox); if (!pField->initialize(pFieldLayout, nFieldIndex, nFieldColumnOffset, sError)) { delete pGroupContainer; return false; } pField->setValueRightAligned( oGroupInfo.m_bValueRightAligned); if (pField->valueEditor() != nullptr) { connect(pField->valueEditor(), SIGNAL(textChanged(QString)), this, SLOT(slotFieldValueEdited())); m_mapEditorFields.insert(pField->valueEditor(), pField); } if (pField->enumEditor() != nullptr) { connect(pField->enumEditor(), SIGNAL(currentIndexChanged(int)), this, SLOT(slotEnumIndexChanged(int))); m_mapEditorFields.insert(pField->enumEditor(), pField); } m_listFields.append(pField); m_mapFields.insert(pField->parameterId(), pField); } // 跨越多行的分组在承载区域垂直居中,普通分组保持顶部排列。 if (oGroupInfo.m_nRowSpan > 1) { pGroupContainerLayout->addStretch(1); } pGroupContainerLayout->addWidget(pGroupBox); pGroupContainerLayout->addStretch(1); m_pGroupsLayout->addWidget( pGroupContainer, oGroupInfo.m_nRow, oGroupInfo.m_nColumn, oGroupInfo.m_nRowSpan, oGroupInfo.m_nColumnSpan); return true; } void nmReservoirParameterPage::loadFieldValue( nmReservoirParameterField* pField) { if (pField == nullptr || m_pSession == nullptr) { return; } if (pField->isEnum()) { pField->setEnumCode(m_pSession->enumCode( pField->parameterId())); } else { pField->setBaseValue(m_pSession->value( pField->parameterId())); } } bool nmReservoirParameterPage::commitPendingEdits() { if (m_pSession == nullptr) { return false; } // 第一步:先完整校验,避免部分字段已写入而后续字段失败。 if (!isInputValid()) { updateValidity(); return false; } // 第二步:逐行按编辑器指针获取基准值,再统一写入工作副本。 QMap mapValues; for (int nFieldIndex = 0; nFieldIndex < m_listFields.size(); ++nFieldIndex) { nmReservoirParameterField* pField = m_listFields[nFieldIndex]; if (pField == nullptr || pField->isEnum() || !pField->isFieldVisible()) { continue; } bool bValid = false; QVariant oValue = pField->baseValue(bValid); if (!bValid || !oValue.isValid()) { updateValidity(); return false; } mapValues.insert(pField->parameterId(), oValue); } m_pSession->setValues(mapValues); updateValidity(); return true; } bool nmReservoirParameterPage::isInputValid() { nmReservoirParameterField* pSoiField = m_mapFields.value("Soi", nullptr); nmReservoirParameterField* pSwiField = m_mapFields.value("Swi", nullptr); if (pSoiField != nullptr) { pSoiField->setExternalValidationError(QString()); } if (pSwiField != nullptr) { pSwiField->setExternalValidationError(QString()); } bool bAllValid = true; for (int nFieldIndex = 0; nFieldIndex < m_listFields.size(); ++nFieldIndex) { nmReservoirParameterField* pField = m_listFields[nFieldIndex]; if (pField != nullptr && pField->isFieldVisible() && !pField->validate()) { bAllValid = false; } } // 油水两相同时显示两个初始饱和度时,两者之和必须为1。 if (pSoiField != nullptr && pSwiField != nullptr && pSoiField->isFieldVisible() && pSwiField->isFieldVisible()) { bool bSoiValid = false; bool bSwiValid = false; QVariant oSoi = pSoiField->baseValue(bSoiValid); QVariant oSwi = pSwiField->baseValue(bSwiValid); const double dTolerance = 1.0e-6; if (bSoiValid && bSwiValid && qAbs(oSoi.toDouble() + oSwi.toDouble() - 1.0) > dTolerance) { QString sError = tr( "Initial oil and water saturations must sum to 1."); pSoiField->setExternalValidationError(sError); pSwiField->setExternalValidationError(sError); bAllValid = false; } } return bAllValid; } void nmReservoirParameterPage::slotFieldValueEdited() { if (!m_bBuildingFields) { nmReservoirParameterField* pField = m_mapEditorFields.value(sender(), nullptr); if (pField != nullptr) { pField->validate(); } updateValidity(); } } void nmReservoirParameterPage::slotEnumIndexChanged(int nIndex) { Q_UNUSED(nIndex); if (m_bBuildingFields || m_pSession == nullptr) { return; } nmReservoirParameterField* pField = m_mapEditorFields.value(sender(), nullptr); if (pField == nullptr) { return; } QString sParameterId = pField->parameterId(); // 第一步:联动前保存当前可见数值;非法时回滚枚举code。 QString sPreviousCode = m_pSession->enumCode(sParameterId); if (!commitPendingEdits()) { m_bBuildingFields = true; pField->setEnumCode(sPreviousCode); m_bBuildingFields = false; return; } // 第二步:只用稳定code更新工作副本,显示文字不参与业务判断。 if (!m_pSession->setEnumCode(sParameterId, pField->enumCode())) { m_bBuildingFields = true; pField->setEnumCode(sPreviousCode); m_bBuildingFields = false; return; } // 第三步:仅显示或隐藏行,隐藏参数继续保留在工作副本中。 applyVisibilityRules(); updateValidity(); } void nmReservoirParameterPage::applyVisibilityRules() { if (m_pSession == nullptr || m_nCurrentObjectIndex < 0 || m_nCurrentObjectIndex >= m_listObjectInfos.size()) { return; } const nmReservoirPropertyObjectInfo& oInfo = m_listObjectInfos[m_nCurrentObjectIndex]; // 区域标记为均质储层时隐藏双重介质参数。 if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_REGION_MARK) { QString sModelId = oInfo.m_sObjectCode + "_RM_ReservoirType"; bool bDual = m_pSession->enumCode(sModelId) != "homogeneous"; setFieldVisible(oInfo.m_sObjectCode + "_RM_ComW", bDual); setFieldVisible(oInfo.m_sObjectCode + "_RM_ComKr", bDual); setFieldVisible(oInfo.m_sObjectCode + "_RM_NetToGross", bDual); } // 无限导流裂缝不显示有限导流能力和裂缝宽度参数。 if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_FRACTURE) { QString sModelId = oInfo.m_sObjectCode + "_F_FlowModel"; bool bFinite = m_pSession->enumCode(sModelId) != "infinite_conductivity"; setFieldVisible(oInfo.m_sObjectCode + "_F_FC", bFinite); setFieldVisible(oInfo.m_sObjectCode + "_F_DW", bFinite); } // 非泄漏断层或复合区不显示泄漏系数。 if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_FAULT) { bool bLeaky = m_pSession->enumCode( oInfo.m_sObjectCode + "_FT_FlowModel") == "leaky"; setFieldVisible(oInfo.m_sObjectCode + "_FT_Leakage", bLeaky); } if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_REGION) { bool bLeaky = m_pSession->enumCode( oInfo.m_sObjectCode + "_R_FlowModel") == "leaky"; setFieldVisible(oInfo.m_sObjectCode + "_R_Leakage", bLeaky); } } void nmReservoirParameterPage::setFieldVisible( const QString& sParameterId, bool bVisible) { nmReservoirParameterField* pField = m_mapFields.value(sParameterId, nullptr); if (pField != nullptr) { pField->setFieldVisible(bVisible); } } void nmReservoirParameterPage::updateValidity() { emit sigValidityChanged(isInputValid()); } nmReservoirPropertiesCategory nmReservoirParameterPage::category() const { return m_eCategory; }