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

495 lines
16 KiB
C++

#include "nmReservoirParameterPage.h"
#include "nmReservoirParameterField.h"
#include "nmReservoirPropertiesSession.h"
#include <QComboBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QSizePolicy>
#include <QVBoxLayout>
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);
// 标签列由本组最长标签的自然宽度决定,数值列和单位列按比例使用剩余空间。
pFieldLayout->setColumnStretch(0, 0);
pFieldLayout->setColumnStretch(1, 8);
pFieldLayout->setColumnStretch(2, 5);
pFieldLayout->setColumnMinimumWidth(
1, nmReservoirParameterField::editorMinimumWidth());
pFieldLayout->setColumnMinimumWidth(
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, 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()
{
for (int nFieldIndex = 0;
nFieldIndex < m_listFields.size();
++nFieldIndex)
{
nmReservoirParameterField* pField =
m_listFields[nFieldIndex];
if (pField != nullptr && pField->isFieldVisible() &&
!pField->validate())
{
return false;
}
}
return true;
}
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;
}