|
|
|
|
|
#include "nmWellParameterPanel.h"
|
|
|
|
|
|
|
|
|
|
|
|
#include "nmDataAttribute.h"
|
|
|
|
|
|
#include "nmDataHorizontalFracturedWell.h"
|
|
|
|
|
|
#include "nmDataPerforation.h"
|
|
|
|
|
|
#include "nmDataWellBase.h"
|
|
|
|
|
|
#include "nmParameterField.h"
|
|
|
|
|
|
#include "nmWellParameterCatalog.h"
|
|
|
|
|
|
|
|
|
|
|
|
#include <QComboBox>
|
|
|
|
|
|
#include <QGridLayout>
|
|
|
|
|
|
#include <QGroupBox>
|
|
|
|
|
|
#include <QLabel>
|
|
|
|
|
|
#include <QLayoutItem>
|
|
|
|
|
|
#include <QLineEdit>
|
|
|
|
|
|
#include <QPalette>
|
|
|
|
|
|
#include <QtGlobal>
|
|
|
|
|
|
#include <QVBoxLayout>
|
|
|
|
|
|
#include <QVector>
|
|
|
|
|
|
|
|
|
|
|
|
namespace
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
struct PerforationCandidate
|
|
|
|
|
|
{
|
|
|
|
|
|
QString m_sName;
|
|
|
|
|
|
double m_dStart;
|
|
|
|
|
|
double m_dEnd;
|
|
|
|
|
|
double m_dSkin;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
nmWellParameterPanel::nmWellParameterPanel(
|
|
|
|
|
|
nmIParameterUnitService* pUnitService,
|
|
|
|
|
|
QWidget* pParent)
|
|
|
|
|
|
: QWidget(pParent),
|
|
|
|
|
|
m_pUnitService(pUnitService),
|
|
|
|
|
|
m_pWell(nullptr),
|
|
|
|
|
|
m_pMainLayout(new QVBoxLayout(this)),
|
|
|
|
|
|
m_pPerforationComboBox(nullptr),
|
|
|
|
|
|
m_listFields(),
|
|
|
|
|
|
m_mapFields(),
|
|
|
|
|
|
m_nCurrentPerforationIndex(0),
|
|
|
|
|
|
m_bReloading(false),
|
|
|
|
|
|
m_bUpdatingModel(false)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 参数分组使用透明背景,由面板统一填充主题基础色。
|
|
|
|
|
|
setAutoFillBackground(true);
|
|
|
|
|
|
setBackgroundRole(QPalette::Base);
|
|
|
|
|
|
m_pMainLayout->setContentsMargins(8, 8, 8, 8);
|
|
|
|
|
|
m_pMainLayout->setSpacing(10);
|
|
|
|
|
|
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
nmWellParameterPanel::~nmWellParameterPanel()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 井对象由会话拥有,析构时只解除连接,不在面板中释放。
|
|
|
|
|
|
if (m_pWell != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
disconnect(m_pWell, nullptr, this, nullptr);
|
|
|
|
|
|
}
|
|
|
|
|
|
m_pUnitService = nullptr;
|
|
|
|
|
|
m_pWell = nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool nmWellParameterPanel::setWellData(nmDataWellBase* pWell,
|
|
|
|
|
|
QString& sError)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (pWell == nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
sError = tr("The well working copy is unavailable.");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (m_pWell != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
disconnect(m_pWell, nullptr, this, nullptr);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 第一步:井型切换时只重建一次目录分组。
|
|
|
|
|
|
m_pWell = pWell;
|
|
|
|
|
|
m_nCurrentPerforationIndex = 0;
|
|
|
|
|
|
clearGroups();
|
|
|
|
|
|
QList<nmWellParameterGroupInfo> listGroups =
|
|
|
|
|
|
nmWellParameterCatalog::createGroups(m_pWell->getWellType());
|
|
|
|
|
|
for (int nIndex = 0; nIndex < listGroups.size(); ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!buildGroup(listGroups[nIndex], sError))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
m_pMainLayout->addStretch(1);
|
|
|
|
|
|
|
|
|
|
|
|
// 第二步:数据交互和字段刷新复用现有控件,不重复创建窗口对象。
|
|
|
|
|
|
connect(m_pWell, SIGNAL(sigWellDataChanged()),
|
|
|
|
|
|
this, SLOT(slotWellDataChanged()));
|
|
|
|
|
|
connect(m_pWell, SIGNAL(sigParameterChanged()),
|
|
|
|
|
|
this, SLOT(slotWellDataChanged()));
|
|
|
|
|
|
reloadPerforationSelector();
|
|
|
|
|
|
reloadFromWell();
|
|
|
|
|
|
sError.clear();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool nmWellParameterPanel::commitPendingEdits(QString& sError)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 第一步:先收集并校验全部字段,任何错误都不能产生部分写入。
|
|
|
|
|
|
QMap<QString, QVariant> mapValues;
|
|
|
|
|
|
QVector<nmDataPerforation> vecPerforations;
|
|
|
|
|
|
if (!collectValues(mapValues, sError, &vecPerforations))
|
|
|
|
|
|
{
|
|
|
|
|
|
emit sigValidityChanged(false);
|
|
|
|
|
|
emit sigValidationMessageChanged(sError);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 第二步:普通属性直接更新;井筒范围和射孔留给同一次批量提交。
|
|
|
|
|
|
QString sBottomholeMdId = nmWellParameterCatalog::parameterId(
|
|
|
|
|
|
NM_WELL_PARAMETER_BOTTOMHOLE_MD);
|
|
|
|
|
|
QString sWellLengthId = nmWellParameterCatalog::parameterId(
|
|
|
|
|
|
NM_WELL_PARAMETER_LENGTH);
|
|
|
|
|
|
bool bWellboreSnapshotChanged = false;
|
|
|
|
|
|
m_bUpdatingModel = true;
|
|
|
|
|
|
for (int nIndex = 0; nIndex < m_listFields.size(); ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
nmParameterField* pField = m_listFields[nIndex];
|
|
|
|
|
|
if (pField == nullptr || pField->isEnum())
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
nmDataAttribute* pAttribute = nmWellParameterCatalog::attribute(
|
|
|
|
|
|
m_pWell,
|
|
|
|
|
|
pField->parameterId(),
|
|
|
|
|
|
m_nCurrentPerforationIndex);
|
|
|
|
|
|
if (pAttribute == nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QVariant oValue = mapValues.value(pField->parameterId());
|
|
|
|
|
|
if (pField->isText())
|
|
|
|
|
|
{
|
|
|
|
|
|
oValue = oValue.toString().trimmed();
|
|
|
|
|
|
}
|
|
|
|
|
|
bool bChanged = pField->isText()
|
|
|
|
|
|
? pAttribute->getValue().toString() != oValue.toString()
|
|
|
|
|
|
: qAbs(pAttribute->getValue().toDouble() - oValue.toDouble()) >
|
|
|
|
|
|
qMax(1.0, qAbs(oValue.toDouble())) * 1.0e-12;
|
|
|
|
|
|
if (bChanged)
|
|
|
|
|
|
{
|
|
|
|
|
|
QString sParameterId = pField->parameterId();
|
|
|
|
|
|
if (sParameterId == sBottomholeMdId ||
|
|
|
|
|
|
sParameterId == sWellLengthId ||
|
|
|
|
|
|
nmWellParameterCatalog::isPerforationParameter(
|
|
|
|
|
|
sParameterId))
|
|
|
|
|
|
{
|
|
|
|
|
|
bWellboreSnapshotChanged = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
pAttribute->setValue(oValue);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 第三步:模型跳过井长隐式缩放,原子写入刚刚校验通过的完整快照。
|
|
|
|
|
|
if (bWellboreSnapshotChanged)
|
|
|
|
|
|
{
|
|
|
|
|
|
double dBottomholeMd = mapValues.value(
|
|
|
|
|
|
sBottomholeMdId,
|
|
|
|
|
|
m_pWell->getBottomholeMD().getValue()).toDouble();
|
|
|
|
|
|
double dWellLength = mapValues.value(
|
|
|
|
|
|
sWellLengthId,
|
|
|
|
|
|
m_pWell->getWellLength().getValue()).toDouble();
|
|
|
|
|
|
if (!m_pWell->applyWellboreEdit(
|
|
|
|
|
|
dBottomholeMd, dWellLength, vecPerforations))
|
|
|
|
|
|
{
|
|
|
|
|
|
m_bUpdatingModel = false;
|
|
|
|
|
|
sError = tr("Failed to apply the validated wellbore parameters.");
|
|
|
|
|
|
emit sigValidityChanged(false);
|
|
|
|
|
|
emit sigValidationMessageChanged(sError);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
m_bUpdatingModel = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 第四步:只更新当前射孔名称,避免提交时重建选择器吞掉切换操作。
|
|
|
|
|
|
updateCurrentPerforationItem();
|
|
|
|
|
|
sError.clear();
|
|
|
|
|
|
emit sigValidityChanged(true);
|
|
|
|
|
|
emit sigValidationMessageChanged(QString());
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool nmWellParameterPanel::isInputValid()
|
|
|
|
|
|
{
|
|
|
|
|
|
QMap<QString, QVariant> mapValues;
|
|
|
|
|
|
QString sError;
|
|
|
|
|
|
return collectValues(mapValues, sError);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmWellParameterPanel::focusFirstInvalid()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 按界面显示顺序定位错误,使用户无需自行查找红色字段。
|
|
|
|
|
|
for (int nIndex = 0; nIndex < m_listFields.size(); ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
nmParameterField* pField = m_listFields[nIndex];
|
|
|
|
|
|
if (pField != nullptr && !pField->validate() &&
|
|
|
|
|
|
pField->valueEditor() != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
pField->valueEditor()->setFocus();
|
|
|
|
|
|
pField->valueEditor()->selectAll();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 非当前射孔出错时保留未提交输入,仅将焦点移到射孔选择器供用户定位。
|
|
|
|
|
|
if (m_pPerforationComboBox != nullptr &&
|
|
|
|
|
|
!m_pPerforationComboBox->toolTip().isEmpty())
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pPerforationComboBox->setFocus();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmWellParameterPanel::reloadFromWell()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_pWell == nullptr || m_bUpdatingModel)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 程序回填期间屏蔽编辑槽,避免刷新再次触发模型提交。
|
|
|
|
|
|
m_bReloading = true;
|
|
|
|
|
|
for (int nIndex = 0; nIndex < m_listFields.size(); ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
nmParameterField* pField = m_listFields[nIndex];
|
|
|
|
|
|
nmDataAttribute* pAttribute = pField != nullptr
|
|
|
|
|
|
? nmWellParameterCatalog::attribute(
|
|
|
|
|
|
m_pWell,
|
|
|
|
|
|
pField->parameterId(),
|
|
|
|
|
|
m_nCurrentPerforationIndex)
|
|
|
|
|
|
: nullptr;
|
|
|
|
|
|
// 目录可复用于多个派生井型,不存在的数据属性直接隐藏。
|
|
|
|
|
|
if (pField != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
pField->setFieldVisible(pAttribute != nullptr);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (pField != nullptr && pAttribute != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
pField->setExternalValidationError(QString());
|
|
|
|
|
|
pField->setBaseValue(pAttribute->getValue());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
m_bReloading = false;
|
|
|
|
|
|
emit sigValidityChanged(isInputValid());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmWellParameterPanel::slotFieldValueEdited()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_bReloading)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 输入过程中只更新状态,避免每个按键都重建裂缝或刷新轨迹。
|
|
|
|
|
|
bool bValid = isInputValid();
|
|
|
|
|
|
emit sigValidityChanged(bValid);
|
|
|
|
|
|
if (bValid)
|
|
|
|
|
|
{
|
|
|
|
|
|
emit sigValidationMessageChanged(QString());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmWellParameterPanel::slotFieldEditingFinished()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_bReloading)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 失去焦点时一次性写工作副本,轨迹只响应最终有效值。
|
|
|
|
|
|
QString sError;
|
|
|
|
|
|
if (!commitPendingEdits(sError))
|
|
|
|
|
|
{
|
|
|
|
|
|
focusFirstInvalid();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmWellParameterPanel::slotPerforationChanged(int nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_bReloading || m_pPerforationComboBox == nullptr || nIndex < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QVariant oPerforationIndex =
|
|
|
|
|
|
m_pPerforationComboBox->itemData(nIndex);
|
|
|
|
|
|
if (!oPerforationIndex.isValid())
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
int nTargetPerforationIndex = oPerforationIndex.toInt();
|
|
|
|
|
|
if (nTargetPerforationIndex == m_nCurrentPerforationIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 第一步:离开当前射孔前提交输入,失败则恢复原选择。
|
|
|
|
|
|
int nPreviousPerforationIndex = m_nCurrentPerforationIndex;
|
|
|
|
|
|
QString sError;
|
|
|
|
|
|
if (!commitPendingEdits(sError))
|
|
|
|
|
|
{
|
|
|
|
|
|
int nPreviousComboIndex =
|
|
|
|
|
|
m_pPerforationComboBox->findData(
|
|
|
|
|
|
nPreviousPerforationIndex);
|
|
|
|
|
|
m_bReloading = true;
|
|
|
|
|
|
if (nPreviousComboIndex >= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pPerforationComboBox->setCurrentIndex(
|
|
|
|
|
|
nPreviousComboIndex);
|
|
|
|
|
|
}
|
|
|
|
|
|
m_bReloading = false;
|
|
|
|
|
|
focusFirstInvalid();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 第二步:切换成功后复用现有字段显示目标射孔数据。
|
|
|
|
|
|
m_nCurrentPerforationIndex = nTargetPerforationIndex;
|
|
|
|
|
|
reloadFromWell();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmWellParameterPanel::slotWellDataChanged()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_bUpdatingModel)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 轨迹控制器可能修改射孔集合,选择器和字段必须一起同步。
|
|
|
|
|
|
reloadPerforationSelector();
|
|
|
|
|
|
reloadFromWell();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmWellParameterPanel::clearGroups()
|
|
|
|
|
|
{
|
|
|
|
|
|
m_listFields.clear();
|
|
|
|
|
|
m_mapFields.clear();
|
|
|
|
|
|
m_pPerforationComboBox = nullptr;
|
|
|
|
|
|
|
|
|
|
|
|
// 字段对象以分组为QObject父对象,删除分组即可统一释放字段和控件。
|
|
|
|
|
|
while (m_pMainLayout->count() > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
QLayoutItem* pItem = m_pMainLayout->takeAt(0);
|
|
|
|
|
|
if (pItem->widget() != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete pItem->widget();
|
|
|
|
|
|
}
|
|
|
|
|
|
delete pItem;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool nmWellParameterPanel::buildGroup(
|
|
|
|
|
|
const nmWellParameterGroupInfo& oGroupInfo,
|
|
|
|
|
|
QString& sError)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 第一步:建立与通用参数字段一致的标签、值和单位三列布局。
|
|
|
|
|
|
QGroupBox* pGroupBox = new QGroupBox(oGroupInfo.m_sTitle, this);
|
|
|
|
|
|
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, nmParameterField::editorMinimumWidth());
|
|
|
|
|
|
pFieldLayout->setColumnMinimumWidth(
|
|
|
|
|
|
2, nmParameterField::unitMinimumWidth());
|
|
|
|
|
|
|
|
|
|
|
|
// 第二步:射孔选择器只占第二列,不侵入单位列。
|
|
|
|
|
|
int nRowOffset = 0;
|
|
|
|
|
|
if (oGroupInfo.m_bPerforationGroup)
|
|
|
|
|
|
{
|
|
|
|
|
|
QLabel* pSelectorLabel = new QLabel(tr("Perforation"), pGroupBox);
|
|
|
|
|
|
pSelectorLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
|
|
|
|
|
m_pPerforationComboBox = new QComboBox(pGroupBox);
|
|
|
|
|
|
m_pPerforationComboBox->setFixedHeight(
|
|
|
|
|
|
nmParameterField::editorHeight());
|
|
|
|
|
|
pFieldLayout->addWidget(pSelectorLabel, 0, 0);
|
|
|
|
|
|
pFieldLayout->addWidget(m_pPerforationComboBox, 0, 1);
|
|
|
|
|
|
connect(m_pPerforationComboBox, SIGNAL(currentIndexChanged(int)),
|
|
|
|
|
|
this, SLOT(slotPerforationChanged(int)));
|
|
|
|
|
|
nRowOffset = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 第三步:按目录顺序创建字段,并建立稳定ID到字段对象的索引。
|
|
|
|
|
|
for (int nIndex = 0; nIndex < oGroupInfo.m_listFields.size(); ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
nmParameterField* pField = new nmParameterField(
|
|
|
|
|
|
oGroupInfo.m_listFields[nIndex],
|
|
|
|
|
|
m_pUnitService,
|
|
|
|
|
|
pGroupBox);
|
|
|
|
|
|
if (!pField->initialize(pFieldLayout,
|
|
|
|
|
|
nIndex + nRowOffset,
|
|
|
|
|
|
0,
|
|
|
|
|
|
sError))
|
|
|
|
|
|
{
|
|
|
|
|
|
delete pGroupBox;
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
pField->setValueRightAligned(!pField->isText());
|
|
|
|
|
|
if (pField->valueEditor() != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
connect(pField->valueEditor(), SIGNAL(textChanged(QString)),
|
|
|
|
|
|
this, SLOT(slotFieldValueEdited()));
|
|
|
|
|
|
connect(pField->valueEditor(), SIGNAL(editingFinished()),
|
|
|
|
|
|
this, SLOT(slotFieldEditingFinished()));
|
|
|
|
|
|
}
|
|
|
|
|
|
m_listFields.append(pField);
|
|
|
|
|
|
m_mapFields.insert(pField->parameterId(), pField);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
m_pMainLayout->addWidget(pGroupBox);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmWellParameterPanel::reloadPerforationSelector()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_pPerforationComboBox == nullptr || m_pWell == nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 第一步:增量同步列表,避免清空控件导致焦点切换和点击事件失效。
|
|
|
|
|
|
bool bWasReloading = m_bReloading;
|
|
|
|
|
|
m_bReloading = true;
|
|
|
|
|
|
int nPerforationCount = m_pWell->getPerforationCount();
|
|
|
|
|
|
if (nPerforationCount <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
while (m_pPerforationComboBox->count() > 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pPerforationComboBox->removeItem(
|
|
|
|
|
|
m_pPerforationComboBox->count() - 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (m_pPerforationComboBox->count() <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pPerforationComboBox->addItem(tr("No perforations"));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pPerforationComboBox->setItemText(
|
|
|
|
|
|
0, tr("No perforations"));
|
|
|
|
|
|
m_pPerforationComboBox->setItemData(0, QVariant());
|
|
|
|
|
|
}
|
|
|
|
|
|
m_pPerforationComboBox->setEnabled(false);
|
|
|
|
|
|
m_nCurrentPerforationIndex = -1;
|
|
|
|
|
|
m_pPerforationComboBox->setCurrentIndex(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
while (m_pPerforationComboBox->count() > nPerforationCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pPerforationComboBox->removeItem(
|
|
|
|
|
|
m_pPerforationComboBox->count() - 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int nIndex = 0; nIndex < nPerforationCount; ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
QString sName = perforationDisplayName(nIndex);
|
|
|
|
|
|
if (nIndex >= m_pPerforationComboBox->count())
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pPerforationComboBox->addItem(sName, nIndex);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_pPerforationComboBox->itemText(nIndex) != sName)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pPerforationComboBox->setItemText(nIndex, sName);
|
|
|
|
|
|
}
|
|
|
|
|
|
QVariant oItemData =
|
|
|
|
|
|
m_pPerforationComboBox->itemData(nIndex);
|
|
|
|
|
|
if (!oItemData.isValid() || oItemData.toInt() != nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pPerforationComboBox->setItemData(nIndex, nIndex);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 第二步:集合变化后约束业务索引,再通过itemData定位界面项。
|
|
|
|
|
|
m_pPerforationComboBox->setEnabled(true);
|
|
|
|
|
|
m_nCurrentPerforationIndex = qBound(
|
|
|
|
|
|
0, m_nCurrentPerforationIndex, nPerforationCount - 1);
|
|
|
|
|
|
int nCurrentComboIndex = m_pPerforationComboBox->findData(
|
|
|
|
|
|
m_nCurrentPerforationIndex);
|
|
|
|
|
|
if (nCurrentComboIndex >= 0 &&
|
|
|
|
|
|
m_pPerforationComboBox->currentIndex() !=
|
|
|
|
|
|
nCurrentComboIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pPerforationComboBox->setCurrentIndex(nCurrentComboIndex);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
m_bReloading = bWasReloading;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QString nmWellParameterPanel::perforationDisplayName(
|
|
|
|
|
|
int nPerforationIndex) const
|
|
|
|
|
|
{
|
|
|
|
|
|
nmDataPerforation* pPerforation = m_pWell != nullptr
|
|
|
|
|
|
? m_pWell->getPerforation(nPerforationIndex)
|
|
|
|
|
|
: nullptr;
|
|
|
|
|
|
QString sName = pPerforation != nullptr
|
|
|
|
|
|
? pPerforation->getName().getValue().toString().trimmed()
|
|
|
|
|
|
: QString();
|
|
|
|
|
|
// 旧工程可能没有射孔名称,界面提供稳定的序号回退文本。
|
|
|
|
|
|
return sName.isEmpty()
|
|
|
|
|
|
? tr("Perforation %1").arg(nPerforationIndex + 1)
|
|
|
|
|
|
: sName;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmWellParameterPanel::updateCurrentPerforationItem()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_pPerforationComboBox == nullptr || m_pWell == nullptr ||
|
|
|
|
|
|
m_nCurrentPerforationIndex < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int nComboIndex = m_pPerforationComboBox->findData(
|
|
|
|
|
|
m_nCurrentPerforationIndex);
|
|
|
|
|
|
if (nComboIndex < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QString sName = perforationDisplayName(
|
|
|
|
|
|
m_nCurrentPerforationIndex);
|
|
|
|
|
|
if (m_pPerforationComboBox->itemText(nComboIndex) == sName)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 只修改文本并屏蔽选择信号,不改变当前索引和弹出列表状态。
|
|
|
|
|
|
bool bWasReloading = m_bReloading;
|
|
|
|
|
|
m_bReloading = true;
|
|
|
|
|
|
m_pPerforationComboBox->setItemText(nComboIndex, sName);
|
|
|
|
|
|
m_bReloading = bWasReloading;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool nmWellParameterPanel::collectValues(
|
|
|
|
|
|
QMap<QString, QVariant>& mapValues,
|
|
|
|
|
|
QString& sError,
|
|
|
|
|
|
QVector<nmDataPerforation>* pVecPerforations)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 第一步:清除上一次跨字段校验留下的错误,重新计算完整状态。
|
|
|
|
|
|
mapValues.clear();
|
|
|
|
|
|
if (m_pPerforationComboBox != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pPerforationComboBox->setToolTip(QString());
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int nIndex = 0; nIndex < m_listFields.size(); ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_listFields[nIndex] != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_listFields[nIndex]->setExternalValidationError(QString());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 第二步:字段统一转换为业务基准单位后写入临时映射。
|
|
|
|
|
|
for (int nIndex = 0; nIndex < m_listFields.size(); ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
nmParameterField* pField = m_listFields[nIndex];
|
|
|
|
|
|
if (pField == nullptr || !pField->isFieldVisible() ||
|
|
|
|
|
|
pField->isEnum())
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
bool bValid = false;
|
|
|
|
|
|
QVariant oValue = pField->baseValue(bValid);
|
|
|
|
|
|
if (!bValid || !oValue.isValid())
|
|
|
|
|
|
{
|
|
|
|
|
|
sError = tr("Please correct the invalid parameter values.");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
mapValues.insert(pField->parameterId(), oValue);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 第三步:单字段合法后再执行依赖多个参数的射孔规则。
|
|
|
|
|
|
if (!validatePerforationValues(
|
|
|
|
|
|
mapValues, sError, pVecPerforations))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
sError.clear();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool nmWellParameterPanel::validatePerforationValues(
|
|
|
|
|
|
const QMap<QString, QVariant>& mapValues,
|
|
|
|
|
|
QString& sError,
|
|
|
|
|
|
QVector<nmDataPerforation>* pVecPerforations)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (pVecPerforations != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
pVecPerforations->clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
if (m_pWell == nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用稳定ID读取临时值,校验逻辑不依赖界面语言和XML显示名称。
|
|
|
|
|
|
QString sNameId = nmWellParameterCatalog::parameterId(
|
|
|
|
|
|
NM_WELL_PARAMETER_PERFORATION_NAME);
|
|
|
|
|
|
QString sStartId = nmWellParameterCatalog::parameterId(
|
|
|
|
|
|
NM_WELL_PARAMETER_PERFORATION_MD_START);
|
|
|
|
|
|
QString sEndId = nmWellParameterCatalog::parameterId(
|
|
|
|
|
|
NM_WELL_PARAMETER_PERFORATION_MD_END);
|
|
|
|
|
|
QString sSkinId = nmWellParameterCatalog::parameterId(
|
|
|
|
|
|
NM_WELL_PARAMETER_PERFORATION_SKIN);
|
|
|
|
|
|
QString sBottomholeMdId = nmWellParameterCatalog::parameterId(
|
|
|
|
|
|
NM_WELL_PARAMETER_BOTTOMHOLE_MD);
|
|
|
|
|
|
QString sWellLengthId = nmWellParameterCatalog::parameterId(
|
|
|
|
|
|
NM_WELL_PARAMETER_LENGTH);
|
|
|
|
|
|
int nPerforationCount = m_pWell->getPerforationCount();
|
|
|
|
|
|
if (nPerforationCount <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (dynamic_cast<nmDataHorizontalFracturedWell*>(m_pWell) != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
sError = tr(
|
|
|
|
|
|
"A multistage fractured horizontal well requires at least one perforation.");
|
|
|
|
|
|
if (m_pPerforationComboBox != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pPerforationComboBox->setToolTip(sError);
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 第一步:输入过程只构造轻量候选值,正式提交时才按需深拷贝完整射孔。
|
|
|
|
|
|
QVector<PerforationCandidate> vecCandidates;
|
|
|
|
|
|
vecCandidates.reserve(nPerforationCount);
|
|
|
|
|
|
if (pVecPerforations != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
pVecPerforations->reserve(nPerforationCount);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int nIndex = 0; nIndex < nPerforationCount; ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
nmDataPerforation* pPerforation = m_pWell->getPerforation(nIndex);
|
|
|
|
|
|
if (pPerforation == nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
sError = tr("Please correct the invalid parameter values.");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
PerforationCandidate oCandidate;
|
|
|
|
|
|
oCandidate.m_sName =
|
|
|
|
|
|
pPerforation->getName().getValue().toString().trimmed();
|
|
|
|
|
|
oCandidate.m_dStart =
|
|
|
|
|
|
pPerforation->getMdStart().getValue().toDouble();
|
|
|
|
|
|
oCandidate.m_dEnd =
|
|
|
|
|
|
pPerforation->getMdEnd().getValue().toDouble();
|
|
|
|
|
|
oCandidate.m_dSkin =
|
|
|
|
|
|
pPerforation->getSkin().getValue().toDouble();
|
|
|
|
|
|
if (nIndex == m_nCurrentPerforationIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
oCandidate.m_sName = mapValues.value(
|
|
|
|
|
|
sNameId, oCandidate.m_sName).toString().trimmed();
|
|
|
|
|
|
oCandidate.m_dStart = mapValues.value(
|
|
|
|
|
|
sStartId, oCandidate.m_dStart).toDouble();
|
|
|
|
|
|
oCandidate.m_dEnd = mapValues.value(
|
|
|
|
|
|
sEndId, oCandidate.m_dEnd).toDouble();
|
|
|
|
|
|
oCandidate.m_dSkin = mapValues.value(
|
|
|
|
|
|
sSkinId, oCandidate.m_dSkin).toDouble();
|
|
|
|
|
|
}
|
|
|
|
|
|
vecCandidates.append(oCandidate);
|
|
|
|
|
|
|
|
|
|
|
|
if (pVecPerforations != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
nmDataPerforation oPerforationSnapshot(*pPerforation);
|
|
|
|
|
|
oPerforationSnapshot.getName().setValue(oCandidate.m_sName);
|
|
|
|
|
|
oPerforationSnapshot.getMdStart().setValue(oCandidate.m_dStart);
|
|
|
|
|
|
oPerforationSnapshot.getMdEnd().setValue(oCandidate.m_dEnd);
|
|
|
|
|
|
oPerforationSnapshot.getSkin().setValue(oCandidate.m_dSkin);
|
|
|
|
|
|
pVecPerforations->append(oPerforationSnapshot);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 第二步:逐项检查名称和MD顺序,错误文字带序号便于定位非当前射孔。
|
|
|
|
|
|
for (int nIndex = 0; nIndex < vecCandidates.size(); ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
const PerforationCandidate& oCandidate = vecCandidates[nIndex];
|
|
|
|
|
|
if (oCandidate.m_sName.isEmpty())
|
|
|
|
|
|
{
|
|
|
|
|
|
QString sRuleError = tr("Perforation name cannot be empty.");
|
|
|
|
|
|
sError = perforationErrorText(nIndex, sRuleError);
|
|
|
|
|
|
if (nIndex == m_nCurrentPerforationIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
nmParameterField* pNameField = field(sNameId);
|
|
|
|
|
|
if (pNameField != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
pNameField->setExternalValidationError(sError);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (m_pPerforationComboBox != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pPerforationComboBox->setToolTip(sError);
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (oCandidate.m_dStart > oCandidate.m_dEnd)
|
|
|
|
|
|
{
|
|
|
|
|
|
QString sRuleError = tr(
|
|
|
|
|
|
"Perforation start MD cannot exceed end MD.");
|
|
|
|
|
|
sError = perforationErrorText(nIndex, sRuleError);
|
|
|
|
|
|
setPerforationError(nIndex, sError);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 第三步:以候选井身参数检查全部射孔;水平井从第一段射孔起点延伸。
|
|
|
|
|
|
double dWellTop = mapValues.value(
|
|
|
|
|
|
sBottomholeMdId,
|
|
|
|
|
|
m_pWell->getBottomholeMD().getValue()).toDouble();
|
|
|
|
|
|
double dWellLength = mapValues.value(
|
|
|
|
|
|
sWellLengthId,
|
|
|
|
|
|
m_pWell->getWellLength().getValue()).toDouble();
|
|
|
|
|
|
nmDataHorizontalFracturedWell* pHorizontal =
|
|
|
|
|
|
dynamic_cast<nmDataHorizontalFracturedWell*>(m_pWell);
|
|
|
|
|
|
if (pHorizontal != nullptr && !vecCandidates.isEmpty())
|
|
|
|
|
|
{
|
|
|
|
|
|
dWellTop = vecCandidates[0].m_dStart;
|
|
|
|
|
|
}
|
|
|
|
|
|
double dWellBottom = dWellTop + dWellLength;
|
|
|
|
|
|
double dBoundaryTolerance = qMax(
|
|
|
|
|
|
1.0, qMax(qAbs(dWellTop), qAbs(dWellBottom))) * 1.0e-10;
|
|
|
|
|
|
for (int nIndex = 0; nIndex < vecCandidates.size(); ++nIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
const PerforationCandidate& oCandidate = vecCandidates[nIndex];
|
|
|
|
|
|
if (oCandidate.m_dStart < dWellTop - dBoundaryTolerance ||
|
|
|
|
|
|
oCandidate.m_dEnd > dWellBottom + dBoundaryTolerance)
|
|
|
|
|
|
{
|
|
|
|
|
|
QString sRuleError = tr(
|
|
|
|
|
|
"The perforation interval is outside the wellbore.");
|
|
|
|
|
|
sError = perforationErrorText(nIndex, sRuleError);
|
|
|
|
|
|
setPerforationError(nIndex, sError);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 第四步:两两检查全部候选区间,端点相接允许但区间不能相交。
|
|
|
|
|
|
for (int nFirstIndex = 0;
|
|
|
|
|
|
nFirstIndex < vecCandidates.size();
|
|
|
|
|
|
++nFirstIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int nSecondIndex = nFirstIndex + 1;
|
|
|
|
|
|
nSecondIndex < vecCandidates.size();
|
|
|
|
|
|
++nSecondIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
const PerforationCandidate& oFirst =
|
|
|
|
|
|
vecCandidates[nFirstIndex];
|
|
|
|
|
|
const PerforationCandidate& oSecond =
|
|
|
|
|
|
vecCandidates[nSecondIndex];
|
|
|
|
|
|
if (oFirst.m_dStart < oSecond.m_dEnd - dBoundaryTolerance &&
|
|
|
|
|
|
oFirst.m_dEnd > oSecond.m_dStart + dBoundaryTolerance)
|
|
|
|
|
|
{
|
|
|
|
|
|
int nErrorIndex =
|
|
|
|
|
|
nFirstIndex == m_nCurrentPerforationIndex
|
|
|
|
|
|
? nFirstIndex : nSecondIndex;
|
|
|
|
|
|
QString sRuleError = tr(
|
|
|
|
|
|
"Perforation intervals cannot overlap.");
|
|
|
|
|
|
sError = perforationErrorText(nErrorIndex, sRuleError);
|
|
|
|
|
|
setPerforationError(nErrorIndex, sError);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
nmParameterField* nmWellParameterPanel::field(
|
|
|
|
|
|
const QString& sParameterId) const
|
|
|
|
|
|
{
|
|
|
|
|
|
return m_mapFields.value(sParameterId, nullptr);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QString nmWellParameterPanel::perforationErrorText(
|
|
|
|
|
|
int nPerforationIndex,
|
|
|
|
|
|
const QString& sRuleError) const
|
|
|
|
|
|
{
|
|
|
|
|
|
return QString("%1 %2: %3")
|
|
|
|
|
|
.arg(tr("Perforation"))
|
|
|
|
|
|
.arg(nPerforationIndex + 1)
|
|
|
|
|
|
.arg(sRuleError);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmWellParameterPanel::setPerforationError(
|
|
|
|
|
|
int nPerforationIndex,
|
|
|
|
|
|
const QString& sError)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_pPerforationComboBox != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pPerforationComboBox->setToolTip(sError);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (nPerforationIndex != m_nCurrentPerforationIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
nmParameterField* pStartField = field(
|
|
|
|
|
|
nmWellParameterCatalog::parameterId(
|
|
|
|
|
|
NM_WELL_PARAMETER_PERFORATION_MD_START));
|
|
|
|
|
|
nmParameterField* pEndField = field(
|
|
|
|
|
|
nmWellParameterCatalog::parameterId(
|
|
|
|
|
|
NM_WELL_PARAMETER_PERFORATION_MD_END));
|
|
|
|
|
|
if (pStartField != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
pStartField->setExternalValidationError(sError);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (pEndField != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
pEndField->setExternalValidationError(sError);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|