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

1388 lines
44 KiB
C++

#include "nmWellPressureFlowPage.h"
#include "iUnitGroup.h"
#include "iUnitHelper.h"
#include "iUnitItem.h"
#include "nmDataWellBase.h"
#include "nmWxPressFlowChartWidget.h"
#include "ZxBaseUtil.h"
#include "ZxTableHeaderViewUnit.h"
#include <QAbstractItemView>
#include <QComboBox>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QList>
#include <QPalette>
#include <QSplitter>
#include <QStackedWidget>
#include <QStandardItem>
#include <QStandardItemModel>
#include <QStringList>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QVBoxLayout>
namespace
{
QString preferredTimeUnit()
{
return QLatin1String("h");
}
QString preferredFlowUnit()
{
return QString::fromUtf8("m\xC2\xB3/d");
}
iUnitGroup* resolveUnitGroup(const QStringList& listCandidates,
QString& sResolvedUnit)
{
for (int nIndex = 0; nIndex < listCandidates.size(); ++nIndex)
{
const QString& sCandidate = listCandidates[nIndex];
iUnitGroup* pUnitGroup =
iUnitHelper::getUnitGroupByUnit(sCandidate);
if (pUnitGroup == nullptr)
{
continue;
}
int nUnitIndex = pUnitGroup->indexOf(sCandidate);
QStringList listUnitNames = pUnitGroup->getAllUnitNames();
if (nUnitIndex < 0 || nUnitIndex >= listUnitNames.size())
{
continue;
}
sResolvedUnit = listUnitNames[nUnitIndex];
return pUnitGroup;
}
sResolvedUnit = listCandidates.isEmpty()
? QString() : listCandidates.first();
return nullptr;
}
QLabel* createEmptyLabel(const QString& sText, QWidget* pParent)
{
QLabel* pLabel = new QLabel(sText, pParent);
pLabel->setAlignment(Qt::AlignCenter);
QPalette oPalette = pLabel->palette();
oPalette.setColor(QPalette::WindowText,
pParent->palette().color(QPalette::Mid));
pLabel->setPalette(oPalette);
return pLabel;
}
QString displayValueText(double dValue, int nDigit)
{
if (nDigit <= 0)
{
return QString::number(dValue, 'f', 0);
}
return ZxBaseUtil::getValidStr(dValue, nDigit);
}
// 显示相下拉框只列出实际出现过非零流量的相;多相表中的固定全零列
// 仍保留在井记录和求解输入中,但不应显示成该井存在对应相产量。
bool hasRealPhaseData(const QVector<QPointF>& vecPoints)
{
int nStartIndex = 0;
if (!vecPoints.isEmpty() &&
vecPoints.first().x() == 0.0 &&
vecPoints.first().y() == 0.0)
{
nStartIndex = 1;
}
for (int nIndex = nStartIndex; nIndex < vecPoints.size(); ++nIndex)
{
if (vecPoints[nIndex].y() != 0.0)
{
return true;
}
}
return false;
}
// 压力/流量记录下拉框只显示稳定的 Gauge Code避免顶部工具栏过宽。
// 异常旧数据没有 Code 时,才回退到框架名称。
QString formatGaugeRecordLabel(const QString& sGaugeName,
const QString& sGaugeCode,
const QString& sGaugeTime)
{
Q_UNUSED(sGaugeTime);
return sGaugeCode.isEmpty() ? sGaugeName : sGaugeCode;
}
QString gaugeMetadataText(const QString& sText)
{
return sText.trimmed().isEmpty()
? nmWellPressureFlowPage::tr("(none)") : sText;
}
QString gaugeNameText(const QString& sGaugeName)
{
return sGaugeName.trimmed().isEmpty()
? nmWellPressureFlowPage::tr("(unnamed)") : sGaugeName;
}
QString gaugeRecordToolTip(const QString& sGaugeName,
const QString& sGaugeCode,
const QString& sGaugeTime)
{
QStringList listLines;
listLines << nmWellPressureFlowPage::tr("Curve name: %1")
.arg(gaugeNameText(sGaugeName));
listLines << nmWellPressureFlowPage::tr("Gauge Code: %1")
.arg(gaugeMetadataText(sGaugeCode));
listLines << nmWellPressureFlowPage::tr("Measurement time: %1")
.arg(gaugeMetadataText(sGaugeTime));
return listLines.join(QLatin1String("\n"));
}
void disableGaugeComboItem(QComboBox* pCombo, int nItemIndex)
{
QStandardItemModel* pModel =
qobject_cast<QStandardItemModel*>(pCombo->model());
if (pModel == nullptr)
{
return;
}
QStandardItem* pItem = pModel->item(nItemIndex);
if (pItem != nullptr)
{
pItem->setFlags(pItem->flags() & ~Qt::ItemIsEnabled);
}
}
void updatePreviewComboToolTip(QComboBox* pCombo)
{
if (pCombo == nullptr || pCombo->currentIndex() < 0 ||
pCombo->itemData(pCombo->currentIndex()).toString().isEmpty())
{
if (pCombo != nullptr)
{
pCombo->setToolTip(QString());
}
return;
}
QString sToolTip = pCombo->itemData(
pCombo->currentIndex(), Qt::ToolTipRole).toString();
if (!sToolTip.isEmpty())
{
sToolTip += QLatin1Char('\n');
}
sToolTip += nmWellPressureFlowPage::tr(
"Preview only; calculation settings are unchanged.");
pCombo->setToolTip(sToolTip);
}
// 统一向压力/流量记录下拉框追加一项;异常记录保留状态提示但不可选。
void appendGaugeComboItem(QComboBox* pCombo,
const QString& sGaugeName,
const QString& sGaugeCode,
const QString& sGaugeTime,
NM_GAUGE_RECORD_STATUS eStatus)
{
QString sLabel = formatGaugeRecordLabel(sGaugeName, sGaugeCode, sGaugeTime);
switch (eStatus)
{
case NM_GaugeRecord_InvalidIdentity:
sLabel += nmWellPressureFlowPage::tr(" (Invalid)");
break;
case NM_GaugeRecord_Empty:
sLabel += nmWellPressureFlowPage::tr(" (Empty)");
break;
case NM_GaugeRecord_Corrupt:
sLabel += nmWellPressureFlowPage::tr(" (Corrupt)");
break;
default:
break;
}
pCombo->addItem(sLabel, sGaugeCode);
const int nItemIndex = pCombo->count() - 1;
pCombo->setItemData(nItemIndex,
gaugeRecordToolTip(sGaugeName, sGaugeCode, sGaugeTime),
Qt::ToolTipRole);
if (eStatus != NM_GaugeRecord_Usable)
{
disableGaugeComboItem(pCombo, nItemIndex);
}
}
}
nmWellPressureFlowPage::nmWellPressureFlowPage(QWidget* pParent)
: QWidget(pParent),
m_pWell(nullptr),
m_pContentStack(nullptr),
m_pHorizontalSplitter(nullptr),
m_pChartSplitter(nullptr),
m_pPressureChartStack(nullptr),
m_pFlowChartStack(nullptr),
m_pFlowTableStack(nullptr),
m_pPressureChart(nullptr),
m_pFlowChart(nullptr),
m_pFlowTable(nullptr),
m_pFlowHeader(nullptr),
m_pTimeUnitGroup(nullptr),
m_pFlowUnitGroup(nullptr),
m_sTimeBaseUnit(preferredTimeUnit()),
m_sFlowBaseUnit(preferredFlowUnit()),
m_sTimeUnit(preferredTimeUnit()),
m_sFlowUnit(preferredFlowUnit()),
m_nTimeDigit(6),
m_nFlowDigit(6),
m_nSelectedFlowSegment(-1),
m_pRecordSelectionBar(nullptr),
m_pPressureGaugeCombo(nullptr),
m_pFlowGaugeCombo(nullptr),
m_pDisplayPhaseCombo(nullptr),
m_pDisplayPhaseContainer(nullptr),
m_eDisplayPhase(PHASE_UNKNOWN)
{
createUi();
initializeUnitSupport();
resetDataViews();
}
void nmWellPressureFlowPage::setWell(nmDataWellBase* pWell)
{
if (m_pWell == pWell)
{
return;
}
m_pWell = pWell;
m_sPreviewPressureGaugeCode = pWell == nullptr
? QString() : pWell->getSelectedPressureGaugeCode();
m_sPreviewFlowGaugeCode = pWell == nullptr
? QString() : pWell->getSelectedFlowGaugeCode();
resetDataViews();
}
void nmWellPressureFlowPage::refreshData()
{
if (m_pWell == nullptr)
{
resetDataViews();
return;
}
// 第一步:恢复页面自己的预览记录,不修改井对象保存的计算记录选择。
populatePressureGaugeCombo();
populateFlowGaugeCombo();
populateDisplayPhaseCombo(currentFlowRecord());
// 第二步:直接从预览记录复制数据,后续记录、相别和单位切换都不回写井对象。
const nmPressureGaugeRecord* pPressureRecord = currentPressureRecord();
const nmFlowGaugeRecord* pFlowRecord = currentFlowRecord();
QVector<QPointF> vecRawPressurePoints = pPressureRecord == nullptr
? QVector<QPointF>() : pPressureRecord->vecPressurePoints;
QVector<QPointF> vecRawFlowPoints;
if (pFlowRecord != nullptr)
{
switch (m_eDisplayPhase)
{
case PHASE_Oil:
vecRawFlowPoints = pFlowRecord->vecOilPoints;
break;
case PHASE_Gas:
vecRawFlowPoints = pFlowRecord->vecGasPoints;
break;
case PHASE_Water:
vecRawFlowPoints = pFlowRecord->vecWaterPoints;
break;
default:
break;
}
}
// 第三步:单位服务可能晚于页面构造完成,再刷新一次可用单位组。
initializeUnitSupport();
m_vecRawPressurePoints = vecRawPressurePoints;
m_vecRawFlowPoints = vecRawFlowPoints;
QVector<QPointF> vecTableFlowPoints;
int nTimeDigit = m_nTimeDigit;
int nFlowDigit = m_nFlowDigit;
if (!buildFlowTableData(m_sTimeUnit,
m_sFlowUnit,
vecTableFlowPoints,
nTimeDigit,
nFlowDigit))
{
// 当前显示单位不可用时回退基准单位,禁止继续展示旧井快照。
m_sTimeUnit = m_sTimeBaseUnit;
m_sFlowUnit = m_sFlowBaseUnit;
if (!buildFlowTableData(m_sTimeUnit,
m_sFlowUnit,
vecTableFlowPoints,
nTimeDigit,
nFlowDigit))
{
resetDataViews();
updateUnitHeader();
return;
}
}
// 第三步:换算与精度读取全部成功后再提交,曲线始终读取原始数据。
m_vecTableFlowPoints = vecTableFlowPoints;
m_nTimeDigit = nTimeDigit;
m_nFlowDigit = nFlowDigit;
updateUnitHeader();
updateViews();
}
void nmWellPressureFlowPage::clearData()
{
m_pWell = nullptr;
m_sPreviewPressureGaugeCode.clear();
m_sPreviewFlowGaugeCode.clear();
resetDataViews();
}
void nmWellPressureFlowPage::slotUnitChanged(
int nColumn,
const iUnitItem* pSourceUnit,
const iUnitItem* pDestinationUnit)
{
Q_UNUSED(pSourceUnit);
if (pDestinationUnit == nullptr ||
(nColumn != 0 && nColumn != 1))
{
updateUnitHeader();
return;
}
QString sTimeUnit = m_sTimeUnit;
QString sFlowUnit = m_sFlowUnit;
if (nColumn == 0)
{
if (m_pTimeUnitGroup == nullptr)
{
updateUnitHeader();
return;
}
sTimeUnit = pDestinationUnit->m_sName;
}
else
{
if (m_pFlowUnitGroup == nullptr)
{
updateUnitHeader();
return;
}
sFlowUnit = pDestinationUnit->m_sName;
}
// 每次都从基准副本换算,禁止使用当前显示值继续换算。
QVector<QPointF> vecTableFlowPoints;
int nTimeDigit = m_nTimeDigit;
int nFlowDigit = m_nFlowDigit;
if (!buildFlowTableData(sTimeUnit,
sFlowUnit,
vecTableFlowPoints,
nTimeDigit,
nFlowDigit))
{
updateUnitHeader();
return;
}
m_sTimeUnit = sTimeUnit;
m_sFlowUnit = sFlowUnit;
m_vecTableFlowPoints = vecTableFlowPoints;
m_nTimeDigit = nTimeDigit;
m_nFlowDigit = nFlowDigit;
updateUnitHeader();
updateFlowTable();
}
void nmWellPressureFlowPage::slotFlowRowChanged(
int nCurrentRow,
int nCurrentColumn,
int nPreviousRow,
int nPreviousColumn)
{
Q_UNUSED(nCurrentColumn);
Q_UNUSED(nPreviousRow);
Q_UNUSED(nPreviousColumn);
applyFlowSegmentSelection(nCurrentRow);
}
void nmWellPressureFlowPage::slotFlowChartPointClicked(double dTimePoint)
{
// 曲线保持基准单位,点击时间先换算为表格当前单位再定位行。
double dTableTimePoint = 0.0;
if (!convertValue(m_pTimeUnitGroup,
m_sTimeBaseUnit,
m_sTimeUnit,
dTimePoint,
dTableTimePoint))
{
applyFlowSegmentSelection(-1);
return;
}
applyFlowSegmentSelection(
findFlowSegmentByTableTime(dTableTimePoint));
}
void nmWellPressureFlowPage::slotPressureGaugePreviewChanged(int nIndex)
{
if (m_pWell == nullptr || nIndex < 0)
{
return;
}
const QString sGaugeCode =
m_pPressureGaugeCombo->itemData(nIndex).toString();
if (sGaugeCode.isEmpty() ||
sGaugeCode == m_sPreviewPressureGaugeCode)
{
updatePreviewComboToolTip(m_pPressureGaugeCombo);
return;
}
m_sPreviewPressureGaugeCode = sGaugeCode;
refreshData();
}
void nmWellPressureFlowPage::slotFlowGaugePreviewChanged(int nIndex)
{
if (m_pWell == nullptr || nIndex < 0)
{
return;
}
const QString sGaugeCode =
m_pFlowGaugeCombo->itemData(nIndex).toString();
if (sGaugeCode.isEmpty() || sGaugeCode == m_sPreviewFlowGaugeCode)
{
updatePreviewComboToolTip(m_pFlowGaugeCombo);
return;
}
m_sPreviewFlowGaugeCode = sGaugeCode;
m_eDisplayPhase = defaultDisplayPhase(currentFlowRecord());
m_nSelectedFlowSegment = -1;
refreshData();
}
void nmWellPressureFlowPage::slotDisplayPhaseChanged(int nIndex)
{
if (m_pWell == nullptr || nIndex < 0)
{
return;
}
NM_PHASE_TYPE eNewPhase = static_cast<NM_PHASE_TYPE>(
m_pDisplayPhaseCombo->itemData(nIndex).toInt());
if (eNewPhase == m_eDisplayPhase)
{
return;
}
// 仅重新按新相取点刷新视图不改变记录内容、井别、m_nIndexF 或求解器输入。
m_eDisplayPhase = eNewPhase;
refreshData();
}
void nmWellPressureFlowPage::createUi()
{
QVBoxLayout* pMainLayout = new QVBoxLayout(this);
pMainLayout->setContentsMargins(8, 6, 8, 8);
pMainLayout->setSpacing(0);
m_pContentStack = new QStackedWidget(this);
m_pHorizontalSplitter = new QSplitter(
Qt::Horizontal, m_pContentStack);
m_pHorizontalSplitter->setChildrenCollapsible(false);
// 左侧上下两图共享时间范围,但各自保留独立空状态。
m_pChartSplitter = new QSplitter(
Qt::Vertical, m_pHorizontalSplitter);
m_pChartSplitter->setChildrenCollapsible(false);
m_pPressureChartStack = new QStackedWidget(m_pChartSplitter);
m_pPressureChart = new nmWxPressFlowChartWidget(
m_pPressureChartStack);
m_pPressureChart->setReadOnly(true);
m_pPressureChart->setTimeSelectionEnabled(false);
m_pPressureChart->setAxisLabels(tr("Time"), tr("Pressure"));
m_pPressureChart->setAxisUnits(
m_sTimeBaseUnit, tr("MPa"));
m_pPressureChart->setLineColor(QColor(50, 200, 50));
m_pPressureChart->setChartType(CHART_TYPE_LINE);
m_pPressureChartStack->addWidget(m_pPressureChart);
m_pPressureChartStack->addWidget(createEmptyLabel(
tr("No pressure data"), m_pPressureChartStack));
m_pFlowChartStack = new QStackedWidget(m_pChartSplitter);
m_pFlowChart = new nmWxPressFlowChartWidget(m_pFlowChartStack);
m_pFlowChart->setReadOnly(true);
m_pFlowChart->setTimeSelectionEnabled(true);
m_pFlowChart->setAxisLabels(tr("Time"), tr("Flow"));
m_pFlowChart->setAxisUnits(m_sTimeBaseUnit, m_sFlowBaseUnit);
m_pFlowChart->setLineColor(QColor(255, 0, 0));
m_pFlowChart->setChartType(CHART_TYPE_STEP);
m_pFlowChartStack->addWidget(m_pFlowChart);
m_pFlowChartStack->addWidget(createEmptyLabel(
tr("No flow data"), m_pFlowChartStack));
m_pChartSplitter->addWidget(m_pPressureChartStack);
m_pChartSplitter->addWidget(m_pFlowChartStack);
m_pChartSplitter->setStretchFactor(0, 1);
m_pChartSplitter->setStretchFactor(1, 1);
// 右侧仅显示原始流量段对应的时间和流量,不提供任何编辑入口。
m_pFlowTableStack = new QStackedWidget(m_pHorizontalSplitter);
m_pFlowTable = new QTableWidget(m_pFlowTableStack);
m_pFlowTable->setColumnCount(2);
m_pFlowTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_pFlowTable->setSelectionBehavior(QAbstractItemView::SelectRows);
m_pFlowTable->setSelectionMode(QAbstractItemView::SingleSelection);
m_pFlowTable->setSortingEnabled(false);
m_pFlowTable->setDragEnabled(false);
m_pFlowTable->setAcceptDrops(false);
m_pFlowTable->setDropIndicatorShown(false);
m_pFlowTable->setAlternatingRowColors(true);
m_pFlowTable->setStyleSheet(
"QTableWidget {"
" background-color: palette(base);"
" alternate-background-color: rgb(232, 232, 232);"
" gridline-color: palette(midlight);"
" border: 1px solid palette(mid);"
"}"
"QHeaderView {"
" background-color: palette(button);"
" color: palette(button-text);"
"}"
"QHeaderView::section {"
" background-color: palette(button);"
" color: palette(button-text);"
" border: none;"
" border-right: 1px solid palette(mid);"
" border-bottom: 1px solid palette(mid);"
"}"
"QTableCornerButton::section {"
" background-color: palette(base);"
" border: none;"
" border-right: 1px solid palette(mid);"
" border-bottom: 1px solid palette(mid);"
"}");
m_pFlowHeader = new ZxTableHeaderViewUnit(m_pFlowTable);
m_pFlowTable->setHorizontalHeader(m_pFlowHeader);
m_pFlowTable->horizontalHeader()->setMovable(false);
m_pFlowTable->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
m_pFlowTable->verticalHeader()->setMovable(false);
m_pFlowTable->verticalHeader()->setDefaultAlignment(Qt::AlignCenter);
m_pFlowTable->verticalHeader()->setMinimumWidth(18);
m_pFlowTableStack->addWidget(m_pFlowTable);
m_pFlowTableStack->addWidget(createEmptyLabel(
tr("No flow data"), m_pFlowTableStack));
m_pHorizontalSplitter->addWidget(m_pChartSplitter);
m_pHorizontalSplitter->addWidget(m_pFlowTableStack);
m_pHorizontalSplitter->setStretchFactor(0, 7);
m_pHorizontalSplitter->setStretchFactor(1, 3);
QList<int> listHorizontalSizes;
listHorizontalSizes << 700 << 300;
m_pHorizontalSplitter->setSizes(listHorizontalSizes);
m_pContentStack->addWidget(m_pHorizontalSplitter);
m_pContentStack->addWidget(createEmptyLabel(
tr("No pressure or flow data"), m_pContentStack));
// 顶部插入压力/流量记录与显示相三个下拉框所在行,不改变下方图表/表格结构。
createRecordSelectionBar();
pMainLayout->addWidget(m_pRecordSelectionBar);
pMainLayout->addWidget(m_pContentStack, 1);
connect(m_pFlowHeader,
SIGNAL(sigCbxValueChanged(int,const iUnitItem*,const iUnitItem*)),
this,
SLOT(slotUnitChanged(int,const iUnitItem*,const iUnitItem*)));
connect(m_pFlowTable,
SIGNAL(currentCellChanged(int,int,int,int)),
this,
SLOT(slotFlowRowChanged(int,int,int,int)));
connect(m_pFlowChart,
SIGNAL(chartPointClicked(double)),
this,
SLOT(slotFlowChartPointClicked(double)));
}
void nmWellPressureFlowPage::createRecordSelectionBar()
{
m_pRecordSelectionBar = new QWidget(this);
QHBoxLayout* pBarLayout = new QHBoxLayout(m_pRecordSelectionBar);
pBarLayout->setContentsMargins(0, 0, 0, 6);
pBarLayout->setSpacing(6);
QLabel* pPressureLabel = new QLabel(
tr("Pressure Record"), m_pRecordSelectionBar);
m_pPressureGaugeCombo = new QComboBox(m_pRecordSelectionBar);
m_pPressureGaugeCombo->setEnabled(false);
m_pPressureGaugeCombo->setMinimumWidth(160);
m_pPressureGaugeCombo->setMaxVisibleItems(16);
QLabel* pFlowLabel = new QLabel(
tr("Flow Record"), m_pRecordSelectionBar);
m_pFlowGaugeCombo = new QComboBox(m_pRecordSelectionBar);
m_pFlowGaugeCombo->setEnabled(false);
m_pFlowGaugeCombo->setMinimumWidth(160);
m_pFlowGaugeCombo->setMaxVisibleItems(16);
// 显示相是纯展示状态,整体容器随记录是否为多相含数据而隐藏/显示。
m_pDisplayPhaseContainer = new QWidget(m_pRecordSelectionBar);
QHBoxLayout* pPhaseLayout = new QHBoxLayout(m_pDisplayPhaseContainer);
pPhaseLayout->setContentsMargins(0, 0, 0, 0);
pPhaseLayout->setSpacing(6);
QLabel* pPhaseLabel = new QLabel(
tr("Display Phase"), m_pDisplayPhaseContainer);
m_pDisplayPhaseCombo = new QComboBox(m_pDisplayPhaseContainer);
m_pDisplayPhaseCombo->setMinimumWidth(80);
pPhaseLayout->addWidget(pPhaseLabel);
pPhaseLayout->addWidget(m_pDisplayPhaseCombo);
m_pDisplayPhaseContainer->setVisible(false);
pBarLayout->addWidget(pPressureLabel);
pBarLayout->addWidget(m_pPressureGaugeCombo);
pBarLayout->addSpacing(12);
pBarLayout->addWidget(pFlowLabel);
pBarLayout->addWidget(m_pFlowGaugeCombo);
pBarLayout->addSpacing(12);
pBarLayout->addWidget(m_pDisplayPhaseContainer);
pBarLayout->addStretch(1);
connect(m_pPressureGaugeCombo,
SIGNAL(currentIndexChanged(int)),
this,
SLOT(slotPressureGaugePreviewChanged(int)));
connect(m_pFlowGaugeCombo,
SIGNAL(currentIndexChanged(int)),
this,
SLOT(slotFlowGaugePreviewChanged(int)));
connect(m_pDisplayPhaseCombo,
SIGNAL(currentIndexChanged(int)),
this,
SLOT(slotDisplayPhaseChanged(int)));
}
void nmWellPressureFlowPage::populatePressureGaugeCombo()
{
if (m_pPressureGaugeCombo == nullptr)
{
return;
}
bool bSignalsWereBlocked = m_pPressureGaugeCombo->blockSignals(true);
m_pPressureGaugeCombo->clear();
if (m_pWell == nullptr)
{
m_pPressureGaugeCombo->setEnabled(false);
m_pPressureGaugeCombo->blockSignals(bSignalsWereBlocked);
return;
}
m_vecPressureGaugeRecords = m_pWell->getPressureRecords();
int nPreviewIndex = -1;
int nCalculationIndex = -1;
int nFirstUsableIndex = -1;
const QString sCalculationCode =
m_pWell->getSelectedPressureGaugeCode();
for (int nIndex = 0; nIndex < m_vecPressureGaugeRecords.size(); ++nIndex)
{
const nmPressureGaugeRecord& oRecord =
m_vecPressureGaugeRecords[nIndex];
appendGaugeComboItem(m_pPressureGaugeCombo,
oRecord.sGaugeName, oRecord.sGaugeCode,
oRecord.sGaugeTime, oRecord.eStatus);
const int nItemIndex = m_pPressureGaugeCombo->count() - 1;
if (oRecord.eStatus != NM_GaugeRecord_Usable)
{
continue;
}
if (nFirstUsableIndex < 0)
{
nFirstUsableIndex = nItemIndex;
}
if (oRecord.sGaugeCode == m_sPreviewPressureGaugeCode)
{
nPreviewIndex = nItemIndex;
}
if (oRecord.sGaugeCode == sCalculationCode)
{
nCalculationIndex = nItemIndex;
}
}
const int nCurrentIndex = nPreviewIndex >= 0
? nPreviewIndex : (nCalculationIndex >= 0
? nCalculationIndex : (nFirstUsableIndex >= 0
? nFirstUsableIndex : -1));
m_pPressureGaugeCombo->setCurrentIndex(nCurrentIndex);
m_pPressureGaugeCombo->setEnabled(nFirstUsableIndex >= 0);
m_sPreviewPressureGaugeCode = nCurrentIndex >= 0
? m_pPressureGaugeCombo->itemData(nCurrentIndex).toString()
: QString();
updatePreviewComboToolTip(m_pPressureGaugeCombo);
m_pPressureGaugeCombo->blockSignals(bSignalsWereBlocked);
}
void nmWellPressureFlowPage::populateFlowGaugeCombo()
{
if (m_pFlowGaugeCombo == nullptr)
{
return;
}
bool bSignalsWereBlocked = m_pFlowGaugeCombo->blockSignals(true);
m_pFlowGaugeCombo->clear();
if (m_pWell == nullptr)
{
m_vecFlowGaugeRecords.clear();
m_pFlowGaugeCombo->setEnabled(false);
m_pFlowGaugeCombo->blockSignals(bSignalsWereBlocked);
return;
}
// 缓存整份记录列表供 currentFlowRecord() 安全返回内部指针,记录内容切换记录时不变。
m_vecFlowGaugeRecords = m_pWell->getFlowRecords();
int nPreviewIndex = -1;
int nCalculationIndex = -1;
int nFirstUsableIndex = -1;
const QString sCalculationCode = m_pWell->getSelectedFlowGaugeCode();
for (int nIndex = 0; nIndex < m_vecFlowGaugeRecords.size(); ++nIndex)
{
const nmFlowGaugeRecord& oRecord = m_vecFlowGaugeRecords[nIndex];
appendGaugeComboItem(m_pFlowGaugeCombo,
oRecord.sGaugeName, oRecord.sGaugeCode,
oRecord.sGaugeTime, oRecord.eStatus);
const int nItemIndex = m_pFlowGaugeCombo->count() - 1;
if (oRecord.eStatus != NM_GaugeRecord_Usable)
{
continue;
}
if (nFirstUsableIndex < 0)
{
nFirstUsableIndex = nItemIndex;
}
if (oRecord.sGaugeCode == m_sPreviewFlowGaugeCode)
{
nPreviewIndex = nItemIndex;
}
if (oRecord.sGaugeCode == sCalculationCode)
{
nCalculationIndex = nItemIndex;
}
}
const int nCurrentIndex = nPreviewIndex >= 0
? nPreviewIndex : (nCalculationIndex >= 0
? nCalculationIndex : (nFirstUsableIndex >= 0
? nFirstUsableIndex : -1));
m_pFlowGaugeCombo->setCurrentIndex(nCurrentIndex);
m_pFlowGaugeCombo->setEnabled(nFirstUsableIndex >= 0);
m_sPreviewFlowGaugeCode = nCurrentIndex >= 0
? m_pFlowGaugeCombo->itemData(nCurrentIndex).toString()
: QString();
updatePreviewComboToolTip(m_pFlowGaugeCombo);
m_pFlowGaugeCombo->blockSignals(bSignalsWereBlocked);
}
void nmWellPressureFlowPage::populateDisplayPhaseCombo(
const nmFlowGaugeRecord* pRecord)
{
if (m_pDisplayPhaseCombo == nullptr ||
m_pDisplayPhaseContainer == nullptr)
{
return;
}
bool bSignalsWereBlocked = m_pDisplayPhaseCombo->blockSignals(true);
m_pDisplayPhaseCombo->clear();
if (pRecord == nullptr)
{
m_pDisplayPhaseContainer->setVisible(false);
m_eDisplayPhase = PHASE_UNKNOWN;
m_pDisplayPhaseCombo->blockSignals(bSignalsWereBlocked);
return;
}
// 仅列出记录中真实含数据的相;若记录只有 1 个相有数据,整行隐藏但仍取该相的点。
QVector<NM_PHASE_TYPE> vecAvailablePhases;
QStringList listAvailableLabels;
if (hasRealPhaseData(pRecord->vecOilPoints))
{
vecAvailablePhases.append(PHASE_Oil);
listAvailableLabels.append(tr("Oil"));
}
if (hasRealPhaseData(pRecord->vecGasPoints))
{
vecAvailablePhases.append(PHASE_Gas);
listAvailableLabels.append(tr("Gas"));
}
if (hasRealPhaseData(pRecord->vecWaterPoints))
{
vecAvailablePhases.append(PHASE_Water);
listAvailableLabels.append(tr("Water"));
}
if (vecAvailablePhases.size() <= 1)
{
m_pDisplayPhaseContainer->setVisible(false);
m_eDisplayPhase = vecAvailablePhases.isEmpty()
? PHASE_UNKNOWN : vecAvailablePhases.first();
m_pDisplayPhaseCombo->blockSignals(bSignalsWereBlocked);
return;
}
m_pDisplayPhaseContainer->setVisible(true);
int nCurrentIndex = -1;
for (int nIndex = 0; nIndex < vecAvailablePhases.size(); ++nIndex)
{
m_pDisplayPhaseCombo->addItem(listAvailableLabels[nIndex],
static_cast<int>(vecAvailablePhases[nIndex]));
if (vecAvailablePhases[nIndex] == m_eDisplayPhase)
{
nCurrentIndex = nIndex;
}
}
if (nCurrentIndex < 0)
{
NM_PHASE_TYPE eDefaultPhase = defaultDisplayPhase(pRecord);
for (int nIndex = 0; nIndex < vecAvailablePhases.size(); ++nIndex)
{
if (vecAvailablePhases[nIndex] == eDefaultPhase)
{
nCurrentIndex = nIndex;
break;
}
}
}
if (nCurrentIndex < 0)
{
nCurrentIndex = 0;
}
m_pDisplayPhaseCombo->setCurrentIndex(nCurrentIndex);
m_eDisplayPhase = static_cast<NM_PHASE_TYPE>(
m_pDisplayPhaseCombo->itemData(nCurrentIndex).toInt());
m_pDisplayPhaseCombo->blockSignals(bSignalsWereBlocked);
}
NM_PHASE_TYPE nmWellPressureFlowPage::defaultDisplayPhase(
const nmFlowGaugeRecord* pRecord) const
{
if (pRecord == nullptr)
{
return PHASE_UNKNOWN;
}
// 优先使用井别对应的参考相,前提是该相在本条记录中确有真实数据。
NM_PHASE_TYPE eReferencePhase = (m_pWell != nullptr)
? m_pWell->getReferenceFlowPhase() : PHASE_UNKNOWN;
if (eReferencePhase == PHASE_Oil && hasRealPhaseData(pRecord->vecOilPoints))
{
return PHASE_Oil;
}
if (eReferencePhase == PHASE_Gas && hasRealPhaseData(pRecord->vecGasPoints))
{
return PHASE_Gas;
}
if (eReferencePhase == PHASE_Water && hasRealPhaseData(pRecord->vecWaterPoints))
{
return PHASE_Water;
}
// 参考相无数据时,退回记录中第一个有真实数据的相,固定按油、气、水顺序。
if (hasRealPhaseData(pRecord->vecOilPoints))
{
return PHASE_Oil;
}
if (hasRealPhaseData(pRecord->vecGasPoints))
{
return PHASE_Gas;
}
if (hasRealPhaseData(pRecord->vecWaterPoints))
{
return PHASE_Water;
}
return PHASE_UNKNOWN;
}
const nmPressureGaugeRecord* nmWellPressureFlowPage::currentPressureRecord() const
{
for (int nIndex = 0; nIndex < m_vecPressureGaugeRecords.size(); ++nIndex)
{
if (m_vecPressureGaugeRecords[nIndex].sGaugeCode ==
m_sPreviewPressureGaugeCode)
{
return &m_vecPressureGaugeRecords[nIndex];
}
}
return nullptr;
}
const nmFlowGaugeRecord* nmWellPressureFlowPage::currentFlowRecord() const
{
for (int nIndex = 0; nIndex < m_vecFlowGaugeRecords.size(); ++nIndex)
{
if (m_vecFlowGaugeRecords[nIndex].sGaugeCode ==
m_sPreviewFlowGaugeCode)
{
return &m_vecFlowGaugeRecords[nIndex];
}
}
return nullptr;
}
void nmWellPressureFlowPage::initializeUnitSupport()
{
QStringList listTimeCandidates;
listTimeCandidates << preferredTimeUnit() << QLatin1String("hr");
QStringList listFlowCandidates;
listFlowCandidates << preferredFlowUnit()
<< QLatin1String("m^3/d")
<< QLatin1String("m^3/D");
// 优先使用当前单位包的h、m³/d旧英文单位包自动兼容hr、m^3/d。
m_pTimeUnitGroup = resolveUnitGroup(
listTimeCandidates, m_sTimeBaseUnit);
m_pFlowUnitGroup = resolveUnitGroup(
listFlowCandidates, m_sFlowBaseUnit);
// 单位组缺失或当前单位不再存在时回退基准单位,入口由框架表头禁用。
if (m_pTimeUnitGroup == nullptr ||
m_pTimeUnitGroup->indexOf(m_sTimeUnit) < 0)
{
m_sTimeUnit = m_sTimeBaseUnit;
}
if (m_pFlowUnitGroup == nullptr ||
m_pFlowUnitGroup->indexOf(m_sFlowUnit) < 0)
{
m_sFlowUnit = m_sFlowBaseUnit;
}
updateUnitHeader();
}
void nmWellPressureFlowPage::updateUnitHeader()
{
if (m_pFlowHeader == nullptr)
{
return;
}
QString sFlowTitle = tr("Flow");
switch (m_eDisplayPhase)
{
case PHASE_Oil:
sFlowTitle = tr("Oil phase flow");
break;
case PHASE_Gas:
sFlowTitle = tr("Gas phase flow");
break;
case PHASE_Water:
sFlowTitle = tr("Water phase flow");
break;
default:
break;
}
QStringList listTitles;
listTitles << tr("Time") << sFlowTitle;
QStringList listUnits;
listUnits << m_sTimeUnit << m_sFlowUnit;
m_pFlowHeader->setHeaderData(listTitles, listUnits);
}
bool nmWellPressureFlowPage::buildFlowTableData(
const QString& sTimeUnit,
const QString& sFlowUnit,
QVector<QPointF>& vecFlowPoints,
int& nTimeDigit,
int& nFlowDigit) const
{
vecFlowPoints = m_vecRawFlowPoints;
// 空数据也必须验证目标单位,避免表头切换后留下不可用单位状态。
double dConvertedZero = 0.0;
if (!convertValue(m_pTimeUnitGroup,
m_sTimeBaseUnit,
sTimeUnit,
0.0,
dConvertedZero,
&nTimeDigit) ||
!convertValue(m_pFlowUnitGroup,
m_sFlowBaseUnit,
sFlowUnit,
0.0,
dConvertedZero,
&nFlowDigit))
{
return false;
}
// 表格每次从原始副本换算,禁止使用当前显示值继续换算。
for (int nIndex = 0; nIndex < vecFlowPoints.size(); ++nIndex)
{
double dDuration = 0.0;
double dFlow = 0.0;
if (!convertValue(m_pTimeUnitGroup,
m_sTimeBaseUnit,
sTimeUnit,
m_vecRawFlowPoints[nIndex].x(),
dDuration) ||
!convertValue(m_pFlowUnitGroup,
m_sFlowBaseUnit,
sFlowUnit,
m_vecRawFlowPoints[nIndex].y(),
dFlow))
{
return false;
}
vecFlowPoints[nIndex] = QPointF(dDuration, dFlow);
}
return true;
}
bool nmWellPressureFlowPage::convertValue(
iUnitGroup* pUnitGroup,
const QString& sBaseUnit,
const QString& sDestinationUnit,
double dSourceValue,
double& dDestinationValue,
int* pDestinationDigit) const
{
int nDigit = 6;
if (pUnitGroup != nullptr)
{
iUnitItem* pUnitItem =
pUnitGroup->getUnitItem(sDestinationUnit);
if (pUnitItem != nullptr && pUnitItem->m_nDigit >= 0)
{
nDigit = pUnitItem->m_nDigit;
}
}
if (sDestinationUnit == sBaseUnit)
{
dDestinationValue = dSourceValue;
if (pDestinationDigit != nullptr)
{
*pDestinationDigit = nDigit;
}
return true;
}
if (pUnitGroup == nullptr ||
pUnitGroup->indexOf(sBaseUnit) < 0 ||
pUnitGroup->indexOf(sDestinationUnit) < 0)
{
return false;
}
int nFallbackDigit = nDigit;
bool bConverted = pUnitGroup->convert(sBaseUnit,
dSourceValue,
sDestinationUnit,
dDestinationValue,
nDigit);
if (bConverted && pDestinationDigit != nullptr)
{
*pDestinationDigit = nDigit >= 0
? nDigit : nFallbackDigit;
}
return bConverted;
}
void nmWellPressureFlowPage::updateViews()
{
QVector<QPointF> vecFlowChartPoints = createFlowChartPoints();
m_pPressureChart->setAxisUnits(
m_sTimeBaseUnit, tr("MPa"));
m_pFlowChart->setAxisUnits(m_sTimeBaseUnit, m_sFlowBaseUnit);
m_pPressureChart->setData(m_vecRawPressurePoints);
m_pFlowChart->setData(vecFlowChartPoints);
updateXAxisRange(vecFlowChartPoints);
updateFlowTable();
bool bPressureEmpty = m_vecRawPressurePoints.isEmpty();
bool bFlowEmpty = m_vecRawFlowPoints.isEmpty();
m_pPressureChartStack->setCurrentIndex(bPressureEmpty ? 1 : 0);
m_pFlowChartStack->setCurrentIndex(bFlowEmpty ? 1 : 0);
m_pFlowTableStack->setCurrentIndex(bFlowEmpty ? 1 : 0);
m_pContentStack->setCurrentIndex(
bPressureEmpty && bFlowEmpty ? 1 : 0);
updateSelectedFlowSegment();
}
QVector<QPointF> nmWellPressureFlowPage::createFlowChartPoints() const
{
QVector<QPointF> vecChartPoints;
double dTotalTime = 0.0;
for (int nIndex = 0;
nIndex < m_vecRawFlowPoints.size();
++nIndex)
{
const QPointF& oSegment = m_vecRawFlowPoints[nIndex];
if (nIndex == 0)
{
vecChartPoints.append(QPointF(0.0, oSegment.y()));
}
dTotalTime += oSegment.x();
vecChartPoints.append(QPointF(dTotalTime, oSegment.y()));
if (nIndex < m_vecRawFlowPoints.size() - 1 &&
qAbs(m_vecRawFlowPoints[nIndex + 1].y() -
oSegment.y()) > 1e-6)
{
vecChartPoints.append(QPointF(
dTotalTime,
m_vecRawFlowPoints[nIndex + 1].y()));
}
}
return vecChartPoints;
}
void nmWellPressureFlowPage::updateFlowTable()
{
bool bSignalsWereBlocked = m_pFlowTable->blockSignals(true);
m_pFlowTable->clearContents();
m_pFlowTable->setRowCount(m_vecTableFlowPoints.size());
// 纵向表头显示Qt默认的1N序号内部仍使用零基流量点索引。
for (int nIndex = 0;
nIndex < m_vecTableFlowPoints.size();
++nIndex)
{
QTableWidgetItem* pTimeItem = new QTableWidgetItem(
displayValueText(m_vecTableFlowPoints[nIndex].x(),
m_nTimeDigit));
pTimeItem->setTextAlignment(Qt::AlignCenter);
m_pFlowTable->setItem(nIndex, 0, pTimeItem);
QTableWidgetItem* pFlowItem = new QTableWidgetItem(
displayValueText(m_vecTableFlowPoints[nIndex].y(),
m_nFlowDigit));
pFlowItem->setTextAlignment(Qt::AlignCenter);
m_pFlowTable->setItem(nIndex, 1, pFlowItem);
}
if (m_nSelectedFlowSegment >= 0 &&
m_nSelectedFlowSegment < m_pFlowTable->rowCount())
{
m_pFlowTable->setCurrentCell(m_nSelectedFlowSegment, 0);
m_pFlowTable->selectRow(m_nSelectedFlowSegment);
}
else
{
m_nSelectedFlowSegment = -1;
m_pFlowTable->setCurrentCell(-1, -1);
m_pFlowTable->clearSelection();
}
m_pFlowTable->blockSignals(bSignalsWereBlocked);
}
void nmWellPressureFlowPage::updateXAxisRange(
const QVector<QPointF>& vecFlowChartPoints)
{
bool bHasRange = false;
double dMinimum = 0.0;
double dMaximum = 0.0;
for (int nIndex = 0;
nIndex < m_vecRawPressurePoints.size();
++nIndex)
{
double dTime = m_vecRawPressurePoints[nIndex].x();
dMinimum = bHasRange ? qMin(dMinimum, dTime) : dTime;
dMaximum = bHasRange ? qMax(dMaximum, dTime) : dTime;
bHasRange = true;
}
for (int nIndex = 0; nIndex < vecFlowChartPoints.size(); ++nIndex)
{
double dTime = vecFlowChartPoints[nIndex].x();
dMinimum = bHasRange ? qMin(dMinimum, dTime) : dTime;
dMaximum = bHasRange ? qMax(dMaximum, dTime) : dTime;
bHasRange = true;
}
if (bHasRange)
{
m_pPressureChart->setXAxisRange(dMinimum, dMaximum);
m_pFlowChart->setXAxisRange(dMinimum, dMaximum);
}
else
{
m_pPressureChart->clearXAxisRange();
m_pFlowChart->clearXAxisRange();
}
}
void nmWellPressureFlowPage::updateSelectedFlowSegment()
{
if (m_nSelectedFlowSegment < 0 ||
m_nSelectedFlowSegment >= m_vecRawFlowPoints.size())
{
m_pPressureChart->clearHighlightedTimeRange();
m_pFlowChart->clearHighlightedTimeRange();
return;
}
// 流量点X值是持续时间段区间必须从第0段开始逐段累加。
double dStartTime = 0.0;
for (int nIndex = 0;
nIndex < m_nSelectedFlowSegment;
++nIndex)
{
dStartTime += m_vecRawFlowPoints[nIndex].x();
}
double dEndTime = dStartTime +
m_vecRawFlowPoints[m_nSelectedFlowSegment].x();
m_pPressureChart->setHighlightedTimeRange(
dStartTime, dEndTime);
m_pFlowChart->setHighlightedTimeRange(dStartTime, dEndTime);
}
void nmWellPressureFlowPage::applyFlowSegmentSelection(int nSegmentIndex)
{
if (nSegmentIndex < 0 ||
nSegmentIndex >= m_vecRawFlowPoints.size() ||
nSegmentIndex >= m_pFlowTable->rowCount())
{
nSegmentIndex = -1;
}
m_nSelectedFlowSegment = nSegmentIndex;
bool bSignalsWereBlocked = m_pFlowTable->blockSignals(true);
if (m_nSelectedFlowSegment >= 0)
{
m_pFlowTable->setCurrentCell(m_nSelectedFlowSegment, 0);
m_pFlowTable->selectRow(m_nSelectedFlowSegment);
QTableWidgetItem* pItem = m_pFlowTable->item(
m_nSelectedFlowSegment, 0);
if (pItem != nullptr)
{
m_pFlowTable->scrollToItem(
pItem, QAbstractItemView::EnsureVisible);
}
}
else
{
m_pFlowTable->setCurrentCell(-1, -1);
m_pFlowTable->clearSelection();
}
m_pFlowTable->blockSignals(bSignalsWereBlocked);
updateSelectedFlowSegment();
}
int nmWellPressureFlowPage::findFlowSegmentByTableTime(
double dTimePoint) const
{
double dTotalTime = 0.0;
for (int nIndex = 0;
nIndex < m_vecTableFlowPoints.size();
++nIndex)
{
dTotalTime += qMax(0.0, m_vecTableFlowPoints[nIndex].x());
}
const double dTolerance = qMax(1.0, qAbs(dTotalTime)) * 1e-9;
if (dTimePoint < 0.0 ||
dTimePoint > dTotalTime + dTolerance)
{
return -1;
}
// 零时长点不占区间;分界点继续查找后一有效段。
double dStartTime = 0.0;
int nLastValidSegment = -1;
for (int nIndex = 0;
nIndex < m_vecTableFlowPoints.size();
++nIndex)
{
double dDuration = m_vecTableFlowPoints[nIndex].x();
if (dDuration <= dTolerance)
{
continue;
}
nLastValidSegment = nIndex;
double dEndTime = dStartTime + dDuration;
if (dTimePoint < dEndTime - dTolerance)
{
return nIndex;
}
dStartTime = dEndTime;
}
// 最终结束点没有后一段,归入最后一个有效流量段。
if (nLastValidSegment >= 0 &&
qAbs(dTimePoint - dTotalTime) <= dTolerance)
{
return nLastValidSegment;
}
return -1;
}
void nmWellPressureFlowPage::resetDataViews()
{
m_vecRawPressurePoints.clear();
m_vecRawFlowPoints.clear();
m_vecTableFlowPoints.clear();
m_nSelectedFlowSegment = -1;
if (m_pWell == nullptr)
{
m_sPreviewPressureGaugeCode.clear();
m_sPreviewFlowGaugeCode.clear();
}
// 井绑定切换或清空时先清空并禁用三个记录/相下拉框,等待 refreshData() 重新填充。
if (m_pPressureGaugeCombo != nullptr)
{
bool bSignalsWereBlocked = m_pPressureGaugeCombo->blockSignals(true);
m_pPressureGaugeCombo->clear();
m_pPressureGaugeCombo->setEnabled(false);
m_pPressureGaugeCombo->setToolTip(QString());
m_pPressureGaugeCombo->blockSignals(bSignalsWereBlocked);
}
if (m_pFlowGaugeCombo != nullptr)
{
bool bSignalsWereBlocked = m_pFlowGaugeCombo->blockSignals(true);
m_pFlowGaugeCombo->clear();
m_pFlowGaugeCombo->setEnabled(false);
m_pFlowGaugeCombo->setToolTip(QString());
m_pFlowGaugeCombo->blockSignals(bSignalsWereBlocked);
}
m_vecPressureGaugeRecords.clear();
m_vecFlowGaugeRecords.clear();
if (m_pDisplayPhaseCombo != nullptr)
{
bool bSignalsWereBlocked = m_pDisplayPhaseCombo->blockSignals(true);
m_pDisplayPhaseCombo->clear();
m_pDisplayPhaseCombo->blockSignals(bSignalsWereBlocked);
}
if (m_pDisplayPhaseContainer != nullptr)
{
m_pDisplayPhaseContainer->setVisible(false);
}
m_eDisplayPhase = PHASE_UNKNOWN;
updateViews();
}