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/nmReservoirParameterPage.cpp

552 lines
18 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 "nmReservoirParameterPage.h"
#include "nmReservoirParameterField.h"
#include "nmReservoirPropertiesSession.h"
#include <QComboBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QSizePolicy>
#include <QVBoxLayout>
#include <QtGlobal>
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<nmReservoirPropertyObjectInfo>();
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<QString, QVariant> 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;
}