diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index 5f04c48c..ebd8d23e 100644 Binary files a/Bin/Config/Lang/cn/nmNum_cn.qm and b/Bin/Config/Lang/cn/nmNum_cn.qm differ diff --git a/Include/nmNum/nmData/nmDataGaugeRecord.h b/Include/nmNum/nmData/nmDataGaugeRecord.h index c9d46ad7..1ca14758 100644 --- a/Include/nmNum/nmData/nmDataGaugeRecord.h +++ b/Include/nmNum/nmData/nmDataGaugeRecord.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "nmDefines.h" @@ -26,14 +26,14 @@ struct nmPressureGaugeRecord { /** * @brief 工程井下一条流量测量记录的只读快照。 - * @note 单相记录解析 GaugeData 并按井别归入对应相;多相记录从 - * GaugeDataEx2 的 N 行数据中按 [段时长、油、气、水] 解析全部相。 + * @note GaugeDataEx2 非空时按 N 行 [段时长、油、气、水] 解析多相数据; + * 否则解析 GaugeData,并按井别归入对应的单相流量。 */ struct nmFlowGaugeRecord { QString sGaugeCode; // 对应 ZxDataGaugeF::getCode(),选择持久化键 QString sGaugeName; // 对应 ZxDataGaugeF::getGaugeName(),界面展示用 QString sGaugeTime; // 对应 ZxDataGaugeF::getGaugeTime(),界面展示用 - bool bMultiPhase; // 对应 ZxDataGaugeF::getMultiPhase() + bool bMultiPhase; // 实际是否采用 GaugeDataEx2 多相表,不依赖框架多相标志 NM_GAUGE_RECORD_STATUS eStatus; // 记录可用状态 QVector vecOilPoints; // 油相流量点,允许首项为 (0,0) 占位点 QVector vecGasPoints; // 气相流量点,允许首项为 (0,0) 占位点 diff --git a/Include/nmNum/nmSubWxs/nmWxIncludeOtherWells.h b/Include/nmNum/nmSubWxs/nmWxIncludeOtherWells.h index fde7fe25..dbce8eaf 100644 --- a/Include/nmNum/nmSubWxs/nmWxIncludeOtherWells.h +++ b/Include/nmNum/nmSubWxs/nmWxIncludeOtherWells.h @@ -82,6 +82,8 @@ private: void setupLayouts(); /** @brief 连接按钮及表格交互信号。 */ void setupConnections(); + /** @brief 根据列宽和候选井数量设置受屏幕范围约束的初始窗口尺寸。 */ + void adjustDialogSizeToTable(); /** @brief 将一条候选井数据写入指定表格行。 */ void addWellDataToTable(const WellDataRow& data, int row); diff --git a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp index 72fa9d00..d90152d9 100644 --- a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp +++ b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp @@ -18,6 +18,7 @@ #include "ZxBaseUtil.h" #include #include +#include #include "zxLogInstance.h" #include "nmDataReservoir.h" @@ -192,6 +193,21 @@ static NM_GAUGE_RECORD_STATUS readBlockAndClassify( ? NM_GaugeRecord_Usable : NM_GaugeRecord_Corrupt; } +// 油水等非全相记录会把未使用相保存为空单元格,数值层按零产量接收。 +// 非空内容仍必须是有效数字,避免把真正损坏的数据静默转换为零。 +static bool readOptionalPhaseRate(const QVariant& oValue, double& dRate) +{ + if(!oValue.isValid() || oValue.isNull() || + oValue.toString().trimmed().isEmpty()) { + dRate = 0.0; + return true; + } + + bool bOk = false; + dRate = oValue.toDouble(&bOk); + return bOk; +} + // 框架的 GaugeDataEx2 保存完整多相表,格式为 N 行 x 4 列; // 每行依次为 [段时长、油、气、水]。数值模块读取时拆成三组同轴流量点, // 不根据井别或非零值删改任一相的数据。 @@ -231,14 +247,14 @@ static bool readMultiPhaseFlowTable( nPointIndex < nPointCount; ++nPointIndex) { bool bTimeOk = false; - bool bOilOk = false; - bool bGasOk = false; - bool bWaterOk = false; const QVector& vecRow = vvecRows[nPointIndex]; const double dTime = vecRow[0].toDouble(&bTimeOk); - const double dOil = vecRow[1].toDouble(&bOilOk); - const double dGas = vecRow[2].toDouble(&bGasOk); - const double dWater = vecRow[3].toDouble(&bWaterOk); + double dOil = 0.0; + double dGas = 0.0; + double dWater = 0.0; + const bool bOilOk = readOptionalPhaseRate(vecRow[1], dOil); + const bool bGasOk = readOptionalPhaseRate(vecRow[2], dGas); + const bool bWaterOk = readOptionalPhaseRate(vecRow[3], dWater); if(!bTimeOk || !bOilOk || !bGasOk || !bWaterOk) { vecOilPoints.clear(); vecGasPoints.clear(); @@ -400,7 +416,9 @@ bool nmDataAnalyzeManager::readFrameworkWellCurves( oRecord.sGaugeCode = pGaugeF->getCode(); oRecord.sGaugeName = pGaugeF->getGaugeName(); oRecord.sGaugeTime = pGaugeF->getGaugeTime(); - oRecord.bMultiPhase = pGaugeF->getMultiPhase(); + // 框架多相标志可能与实际存储不一致,正式读取只依据数据块: + // Ex2 非空表示多相表,否则按井别解析 GaugeData 单相主块。 + oRecord.bMultiPhase = !pGaugeF->getGaugeDataEx2().isEmpty(); oRecord.eStatus = oRecord.bMultiPhase ? classifyMultiPhaseFlowRecord( pGaugeF, oRecord.vecOilPoints, diff --git a/Src/nmNum/nmSubWxs/nmWxIncludeOtherWells.cpp b/Src/nmNum/nmSubWxs/nmWxIncludeOtherWells.cpp index e270fcd5..5a82e1b7 100644 --- a/Src/nmNum/nmSubWxs/nmWxIncludeOtherWells.cpp +++ b/Src/nmNum/nmSubWxs/nmWxIncludeOtherWells.cpp @@ -3,10 +3,11 @@ #include "nmDataWellBase.h" #include +#include #include +#include #include -#include -#include +#include #include namespace @@ -124,37 +125,6 @@ QString gaugeRecordLabel(const QString& sGaugeName, return sLabel; } -// 异常记录仍显示在下拉列表中,后缀用于说明其不可用于当前选择的原因。 -QString recordStatusSuffix(NM_GAUGE_RECORD_STATUS eStatus) -{ - switch(eStatus) { - case NM_GaugeRecord_Empty: - return nmWxIncludeOtherWells::tr(" (Empty)"); - case NM_GaugeRecord_Corrupt: - return nmWxIncludeOtherWells::tr(" (Corrupt)"); - case NM_GaugeRecord_InvalidIdentity: - return nmWxIncludeOtherWells::tr(" (Invalid)"); - default: - return QString(); - } -} - -// Qt 4.8 的 QComboBox 没有直接禁用单项的接口,通过其标准项模型设置标志。 -void setComboItemEnabled(QComboBox* pComboBox, int nIndex, bool bEnabled) -{ - QStandardItemModel* pModel = qobject_cast( - pComboBox == nullptr ? nullptr : pComboBox->model()); - QStandardItem* pItem = pModel == nullptr ? nullptr : pModel->item(nIndex); - if(pItem == nullptr) { - return; - } - if(bEnabled) { - pItem->setFlags(pItem->flags() | Qt::ItemIsEnabled); - } else { - pItem->setFlags(pItem->flags() & ~Qt::ItemIsEnabled); - } -} - // 流动段索引是一基索引;统计段数时必须先排除框架可能写入的公共占位行。 int flowRecordSegmentCount(const nmFlowGaugeRecord& oRecord) { @@ -252,9 +222,9 @@ void nmWxIncludeOtherWells::initUI() this->setupConnections(); this->setWindowTitle(tr("Include Other Wells")); - // 缩小时依靠横向滚动保留稳定列宽,不让中英文标题挤压下拉内容。 - this->setMinimumSize(760, 280); - this->resize(1240, 420); + // 初始尺寸跟随列宽和井数量,手动缩小时仍由滚动条保护表头及下拉内容。 + this->setMinimumSize(720, 240); + this->adjustDialogSizeToTable(); } void nmWxIncludeOtherWells::setupTable() @@ -262,14 +232,54 @@ void nmWxIncludeOtherWells::setupTable() m_pTableWidget = new QTableWidget(this); m_pTableWidget->setColumnCount(ColumnCount); m_pTableWidget->setRowCount(0); - m_pTableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); - m_pTableWidget->setSelectionMode(QAbstractItemView::SingleSelection); + // 本表以复选框和下拉框作为操作入口,不使用表格行选中态。 + m_pTableWidget->setSelectionMode(QAbstractItemView::NoSelection); + m_pTableWidget->setFocusPolicy(Qt::NoFocus); m_pTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_pTableWidget->setSortingEnabled(false); + m_pTableWidget->setDragEnabled(false); + m_pTableWidget->setAcceptDrops(false); + m_pTableWidget->setDropIndicatorShown(false); m_pTableWidget->setAlternatingRowColors(true); m_pTableWidget->setWordWrap(false); m_pTableWidget->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); m_pTableWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + // 与编辑井流量明细表使用相同的调色板、表头和网格边框。 + // 表格内下拉框采用透明无边框样式,使交替行色连续显示。 + m_pTableWidget->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);" + "}" + "QTableWidget QComboBox {" + " border: none;" + " background: transparent;" + " padding-left: 6px;" + "}" + "QTableWidget QComboBox::drop-down {" + " border: none;" + "}"); + QStringList listHeaders; listHeaders << tr("Name") << tr("Well category") << tr("Included") << tr("Oil production") << tr("Gas production") @@ -277,6 +287,7 @@ void nmWxIncludeOtherWells::setupTable() << tr("Flow segment"); m_pTableWidget->setHorizontalHeaderLabels(listHeaders); m_pTableWidget->horizontalHeader()->setVisible(true); + m_pTableWidget->horizontalHeader()->setMovable(false); m_pTableWidget->horizontalHeader()->setMinimumSectionSize(64); m_pTableWidget->horizontalHeader()->setStretchLastSection(false); m_pTableWidget->horizontalHeader()->setResizeMode( @@ -284,25 +295,21 @@ void nmWxIncludeOtherWells::setupTable() m_pTableWidget->horizontalHeader()->setResizeMode( ColumnWellCategory, QHeaderView::ResizeToContents); m_pTableWidget->horizontalHeader()->setResizeMode( - ColumnIncluded, QHeaderView::Fixed); + ColumnIncluded, QHeaderView::ResizeToContents); m_pTableWidget->horizontalHeader()->setResizeMode( - ColumnOilRate, QHeaderView::Interactive); + ColumnOilRate, QHeaderView::ResizeToContents); m_pTableWidget->horizontalHeader()->setResizeMode( - ColumnGasRate, QHeaderView::Interactive); + ColumnGasRate, QHeaderView::ResizeToContents); m_pTableWidget->horizontalHeader()->setResizeMode( - ColumnWaterRate, QHeaderView::Interactive); + ColumnWaterRate, QHeaderView::ResizeToContents); m_pTableWidget->horizontalHeader()->setResizeMode( - ColumnPressure, QHeaderView::Interactive); + ColumnPressure, QHeaderView::ResizeToContents); m_pTableWidget->horizontalHeader()->setResizeMode( - ColumnFlowSegment, QHeaderView::Fixed); - m_pTableWidget->setColumnWidth(ColumnIncluded, 76); - m_pTableWidget->setColumnWidth(ColumnOilRate, 180); - m_pTableWidget->setColumnWidth(ColumnGasRate, 180); - m_pTableWidget->setColumnWidth(ColumnWaterRate, 180); - m_pTableWidget->setColumnWidth(ColumnPressure, 190); - m_pTableWidget->setColumnWidth(ColumnFlowSegment, 96); - - m_pTableWidget->verticalHeader()->setVisible(false); + ColumnFlowSegment, QHeaderView::ResizeToContents); + + m_pTableWidget->verticalHeader()->setVisible(true); + m_pTableWidget->verticalHeader()->setMovable(false); + m_pTableWidget->verticalHeader()->setDefaultAlignment(Qt::AlignCenter); m_pTableWidget->verticalHeader()->setDefaultSectionSize(32); m_pTableWidget->verticalHeader()->setMinimumSectionSize(28); @@ -396,7 +403,73 @@ void nmWxIncludeOtherWells::setupTable() pEmptyItem->setFlags(Qt::NoItemFlags); pEmptyItem->setTextAlignment(Qt::AlignCenter); m_pTableWidget->setItem(0, 0, pEmptyItem); + // 空状态行不是候选井,不显示容易产生误解的行号“1”。 + m_pTableWidget->verticalHeader()->setVisible(false); + } else { + // Qt 默认按一基显示纵向表头;宽度随最大行号位数增长。 + const int nRowNumberWidth = m_pTableWidget->fontMetrics().width( + QString::number(m_vecWellData.size())) + 18; + m_pTableWidget->verticalHeader()->setFixedWidth( + qMax(32, nRowNumberWidth)); + } + // 单元格控件全部创建完成后再按真实表头和下拉内容计算列宽。 + m_pTableWidget->resizeColumnsToContents(); +} + +void nmWxIncludeOtherWells::adjustDialogSizeToTable() +{ + if(m_pTableWidget == nullptr || m_pMainLayout == nullptr) { + return; + } + + int nTableWidth = m_pTableWidget->frameWidth() * 2; + if(m_pTableWidget->verticalHeader()->isVisible()) { + nTableWidth += m_pTableWidget->verticalHeader()->width(); } + for(int nColumn = 0; nColumn < ColumnCount; ++nColumn) { + nTableWidth += m_pTableWidget->columnWidth(nColumn); + } + + const int nVisibleRows = qMin(qMax(1, m_pTableWidget->rowCount()), 10); + int nTableHeight = m_pTableWidget->frameWidth() * 2 + + m_pTableWidget->horizontalHeader()->sizeHint().height() + + nVisibleRows * m_pTableWidget->verticalHeader()->defaultSectionSize(); + const int nScrollBarExtent = style()->pixelMetric(QStyle::PM_ScrollBarExtent); + if(m_pTableWidget->rowCount() > nVisibleRows) { + nTableWidth += nScrollBarExtent; + } + + int nLeft = 0; + int nTop = 0; + int nRight = 0; + int nBottom = 0; + m_pMainLayout->getContentsMargins(&nLeft, &nTop, &nRight, &nBottom); + const int nLayoutWidth = nLeft + nRight; + const int nButtonHeight = qMax(m_pOkButton->sizeHint().height(), + m_pCancelButton->sizeHint().height()); + const int nLayoutHeight = nTop + nBottom + nButtonHeight + + m_pMainLayout->spacing(); + + QRect oAvailableGeometry; + QDesktopWidget* pDesktop = QApplication::desktop(); + if(pDesktop != nullptr) { + oAvailableGeometry = pDesktop->availableGeometry(this); + } + if(!oAvailableGeometry.isValid()) { + oAvailableGeometry = QRect(0, 0, 1280, 720); + } + + const int nMaximumWidth = qMax(640, oAvailableGeometry.width() * 9 / 10); + const int nMaximumHeight = qMax(260, oAvailableGeometry.height() * 4 / 5); + int nDialogWidth = qMin(nTableWidth + nLayoutWidth, nMaximumWidth); + if(nTableWidth + nLayoutWidth > nDialogWidth) { + nTableHeight += nScrollBarExtent; + } + const int nDialogHeight = qMin(nTableHeight + nLayoutHeight, + nMaximumHeight); + + resize(qMax(minimumWidth(), nDialogWidth), + qMax(minimumHeight(), nDialogHeight)); } void nmWxIncludeOtherWells::addWellDataToTable( @@ -405,6 +478,7 @@ void nmWxIncludeOtherWells::addWellDataToTable( QTableWidgetItem* pNameItem = new QTableWidgetItem(data.m_sName); pNameItem->setData(Qt::UserRole, data.m_sWellCode); pNameItem->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); + pNameItem->setToolTip(data.m_sName); m_pTableWidget->setItem(row, ColumnName, pNameItem); QTableWidgetItem* pCategoryItem = new QTableWidgetItem( @@ -433,6 +507,7 @@ void nmWxIncludeOtherWells::addWellDataToTable( QComboBox* pRateCombo = new QComboBox(m_pTableWidget); pRateCombo->setProperty("row", row); pRateCombo->setProperty("phase", static_cast(arrPhases[nIndex])); + pRateCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents); pRateCombo->setMaxVisibleItems(16); connect(pRateCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onRateSourceChanged(int))); @@ -441,6 +516,7 @@ void nmWxIncludeOtherWells::addWellDataToTable( QComboBox* pPressureCombo = new QComboBox(m_pTableWidget); pPressureCombo->setProperty("row", row); + pPressureCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents); pPressureCombo->setMaxVisibleItems(16); connect(pPressureCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onPressureSourceChanged(int))); @@ -448,6 +524,7 @@ void nmWxIncludeOtherWells::addWellDataToTable( QComboBox* pSegmentCombo = new QComboBox(m_pTableWidget); pSegmentCombo->setProperty("row", row); + pSegmentCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents); connect(pSegmentCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onFlowSegmentChanged(int))); m_pTableWidget->setCellWidget(row, ColumnFlowSegment, pSegmentCombo); @@ -512,18 +589,15 @@ void nmWxIncludeOtherWells::populateRateCombo( pComboBox->clear(); pComboBox->addItem(tr("None"), QString()); - // 三个分相下拉框都列出完整记录集合,但当前相无数据的记录保持可见且禁用。 + // 选择控件只展示当前相真正可用的记录;异常记录仍可在“检查数据”中查看。 for(int nIndex = 0; nIndex < data.m_vecFlowRecords.size(); ++nIndex) { const nmFlowGaugeRecord& oRecord = data.m_vecFlowRecords[nIndex]; - const bool bSelectable = isSelectableFlowPhase(oRecord, ePhase); - QString sLabel = gaugeRecordLabel( - oRecord.sGaugeName, oRecord.sGaugeCode, oRecord.sGaugeTime); - sLabel += recordStatusSuffix(oRecord.eStatus); - if(oRecord.eStatus == NM_GaugeRecord_Usable && !bSelectable) { - sLabel += tr(" (No data)"); + if(!isSelectableFlowPhase(oRecord, ePhase)) { + continue; } - pComboBox->addItem(sLabel, oRecord.sGaugeCode); - setComboItemEnabled(pComboBox, pComboBox->count() - 1, bSelectable); + pComboBox->addItem(gaugeRecordLabel( + oRecord.sGaugeName, oRecord.sGaugeCode, oRecord.sGaugeTime), + oRecord.sGaugeCode); } int nCurrentIndex = 0; @@ -549,15 +623,12 @@ void nmWxIncludeOtherWells::populatePressureCombo( pComboBox->addItem(tr("None"), QString()); for(int nIndex = 0; nIndex < data.m_vecPressureRecords.size(); ++nIndex) { const nmPressureGaugeRecord& oRecord = data.m_vecPressureRecords[nIndex]; - const bool bSelectable = isSelectablePressureRecord(oRecord); - QString sLabel = gaugeRecordLabel( - oRecord.sGaugeName, oRecord.sGaugeCode, oRecord.sGaugeTime); - sLabel += recordStatusSuffix(oRecord.eStatus); - if(oRecord.eStatus == NM_GaugeRecord_Usable && !bSelectable) { - sLabel += tr(" (No data)"); + if(!isSelectablePressureRecord(oRecord)) { + continue; } - pComboBox->addItem(sLabel, oRecord.sGaugeCode); - setComboItemEnabled(pComboBox, pComboBox->count() - 1, bSelectable); + pComboBox->addItem(gaugeRecordLabel( + oRecord.sGaugeName, oRecord.sGaugeCode, oRecord.sGaugeTime), + oRecord.sGaugeCode); } const int nCurrentIndex = pComboBox->findData( @@ -656,7 +727,6 @@ void nmWxIncludeOtherWells::onCheckBoxStateChanged(int state) return; } oData.m_bIsIncluded = state == Qt::Checked; - m_pTableWidget->selectRow(nRow); } void nmWxIncludeOtherWells::onRateSourceChanged(int nIndex) @@ -695,7 +765,6 @@ void nmWxIncludeOtherWells::onRateSourceChanged(int nIndex) // 自动使用同一记录;新记录没有的相自动回到“无”。 normalizeFlowSelection(oData); refreshRowEditors(nRow); - m_pTableWidget->selectRow(nRow); } void nmWxIncludeOtherWells::onPressureSourceChanged(int nIndex) @@ -711,7 +780,6 @@ void nmWxIncludeOtherWells::onPressureSourceChanged(int nIndex) m_vecWellData[nRow].m_sPressureGaugeCode = pComboBox->itemData(nIndex).toString(); pComboBox->setToolTip(pComboBox->currentText()); - m_pTableWidget->selectRow(nRow); } void nmWxIncludeOtherWells::onFlowSegmentChanged(int nIndex) @@ -727,7 +795,6 @@ void nmWxIncludeOtherWells::onFlowSegmentChanged(int nIndex) m_vecWellData[nRow].m_nFlowSegmentIndex = pComboBox->itemData(nIndex).toInt(); pComboBox->setToolTip(pComboBox->currentText()); - m_pTableWidget->selectRow(nRow); } QVector nmWxIncludeOtherWells::getWellData() const @@ -749,7 +816,7 @@ void nmWxIncludeOtherWells::setupLayouts() m_pButtonLayout->addWidget(m_pCancelButton); m_pMainLayout = new QVBoxLayout(this); - m_pMainLayout->addWidget(m_pTableWidget); + m_pMainLayout->addWidget(m_pTableWidget, 1); m_pMainLayout->addLayout(m_pButtonLayout); setLayout(m_pMainLayout); }