#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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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& 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(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 vecRawPressurePoints = pPressureRecord == nullptr ? QVector() : pPressureRecord->vecPressurePoints; QVector 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 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 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( 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 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 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(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( 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& 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 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 nmWellPressureFlowPage::createFlowChartPoints() const { QVector 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默认的1~N序号,内部仍使用零基流量点索引。 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& 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(); }