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

2833 lines
98 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "nmWxTimeDependentSkin.h"
#include "nmWxPerforationClosing.h"
#include "nmWxPressFlowChartWidget.h"
#include "nmWxChartWidget.h"
#include <QVector>
#include <QTableWidget>
#include <QPushButton>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QGridLayout>
#include <QHeaderView>
#include <QLabel>
#include <QMessageBox>
#include <QToolBar>
#include <QIcon>
#include <QApplication>
#include <QRadioButton>
#include <QComboBox>
#include <QLineEdit>
#include <QGroupBox>
#include <QCheckBox>
#include <QDateTime>
#include <QSpacerItem>
#include <QSplitter>
#include <QToolButton>
#include <QDebug>
#include <QFile>
#include <QDir>
#include <QFileInfo>
#include <QPixmap>
#include <QStyle>
#include <QWidget>
#include <QCoreApplication>
// 静态成员变量定义
nmWxTimeDependentSkin* nmWxTimeDependentSkin::s_instance = nullptr;
QString nmWxTimeDependentSkin::s_currentWellName = "";
nmDataWellBase* nmWxTimeDependentSkin::s_currentWellData = nullptr;
void nmWxTimeDependentSkin::showForCurrentWell(nmDataWellBase* pSpecificWellData, QWidget* parent)
{
QString currentWellName;
if (pSpecificWellData) currentWellName = pSpecificWellData->getWellName();
else {
auto* cur = nmDataAnalyzeManager::getCurrentInstance()->getCurWellData();
if (!cur) return;
currentWellName = cur->getWellName();
}
if (currentWellName.isEmpty()) return;
if (s_instance && s_currentWellName == currentWellName) {
bool isSameDataSource = (pSpecificWellData && s_currentWellData == pSpecificWellData)
|| (!pSpecificWellData && !s_currentWellData);
if (isSameDataSource) {
// 同步父子关系和模态
if (parent && s_instance->parent() != parent)
s_instance->setParent(parent, Qt::Dialog);
// 刷新数据
s_instance->reloadFromDataSource(pSpecificWellData);
s_instance->show();
s_instance->raise();
s_instance->activateWindow();
return;
}
s_instance->close();
delete s_instance;
}
// 新建实例
s_instance = new nmWxTimeDependentSkin(pSpecificWellData, parent);
s_currentWellName = currentWellName;
s_currentWellData = pSpecificWellData;
s_instance->show();
s_instance->raise();
s_instance->activateWindow();
}
nmWxTimeDependentSkin::nmWxTimeDependentSkin(nmDataWellBase* pWellData, QWidget *parent)
: iDlgBase(parent)
, m_pChartWidget(nullptr)
, m_pOriginalPressureChart(nullptr)
, m_pOriginalFlowRateChart(nullptr)
, m_isAddMode(false)
, m_isRemoveMode(false)
, m_currentPerforationIndex(0)
, m_pPressureChart(nullptr)
, m_pFlowRateChart(nullptr)
, m_timeDisplayUnit("hr")
, m_dSdQDisplayUnit("1/B/D")
, m_pTimeUnitCombo(nullptr)
, m_pDSdQUnitCombo(nullptr)
, m_rateDependentState(false)
{
// 根据传入的井数据决定使用哪个数据源
if (pWellData != nullptr) {
// 使用传入的特定井数据, 来自nmWxEditWellPlot
m_currentWell = pWellData;
} else {
// 使用当前井数据, 来自nmWxNumericalDesign
m_currentWell = nmDataAnalyzeManager::getCurrentInstance()->getCurWellData();
}
if(m_currentWell != nullptr) {
// 获取压力和流量数据
pressurePoints = m_currentWell->getPressurePoints();
QVector<QPointF> rawFlowData = m_currentWell->getFlowPoints();
flowPoints = processFlowData(rawFlowData);
// 默认选择第一个射孔段
m_currentPerforationIndex = 0;
m_perforationData = m_currentWell->getPerforationCopy(m_currentPerforationIndex);
}
if(m_currentWell != nullptr) {
// 获取压力和流量数据
pressurePoints = m_currentWell->getPressurePoints();
QVector<QPointF> rawFlowData = m_currentWell->getFlowPoints();
flowPoints = processFlowData(rawFlowData);
// 默认选择第一个射孔段
m_currentPerforationIndex = 0;
m_perforationData = m_currentWell->getPerforationCopy(m_currentPerforationIndex);
}
// 如果副本为空,初始化默认数据
if(m_perforationData.getFlowSegments().isEmpty()) {
initializeDefaultFlowSegment();
}
// 确保副本有默认的表皮系数数据
if(m_perforationData.getSkin0().getValue().toString().isEmpty()) {
m_perforationData.getSkin0().setValue("0.0");
}
if(m_perforationData.getdSkindq().getValue().toString().isEmpty()) {
m_perforationData.getdSkindq().setValue("0.0");
}
// 确保线条位置有默认值
double x1, y1, x2, y2;
m_perforationData.getLinePositions(x1, y1, x2, y2);
if(x1 == -999.0 || y1 == -999.0 || x2 == -999.0 || y2 == -999.0) {
m_perforationData.setLinePositions(100.0, 0.0, 200.0, 0.0);
}
setWindowTitle(tr("Time Dependent Skin"));
setMinimumSize(1150, 800);
// 初始化UI
initUI();
// 初始化射孔段下拉框数据
if(m_currentWell != nullptr) {
// 获取射孔段个数并添加到下拉框
int perforationCount = m_currentWell->getPerforationCount();
for(int i = 0; i < perforationCount; ++i) {
nmDataPerforation* perforation = m_currentWell->getPerforation(i);
if(perforation) {
QString name = perforation->getName().getValue().toString();
m_pComboBox->addItem(name);
}
}
// 设置当前选中项为当前射孔段索引
if(m_currentPerforationIndex < perforationCount) {
m_pComboBox->setCurrentIndex(m_currentPerforationIndex);
}
}
// 更新图表
updateCharts();
// 初始化表格数据
updateTableFromFlowSegments();
// 设置信号和槽
connect(m_pAddBtn, SIGNAL(clicked()), this, SLOT(onAddClicked()));
connect(m_pRemoveBtn, SIGNAL(clicked()), this, SLOT(onRemoveClicked()));
connect(m_pSplitStepsBtn, SIGNAL(clicked()), this, SLOT(onSplitStepsClicked()));
connect(m_pComputeBtn, SIGNAL(clicked()), this, SLOT(onComputeClicked()));
connect(m_pPerforationClosingBtn, SIGNAL(clicked()), this, SLOT(onPerforationClosingBtnClicked()));
connect(m_pSkinVsRateBtn, SIGNAL(clicked()), this, SLOT(onSkinVsRateClicked()));
connect(m_pOkBtn, SIGNAL(clicked()), this, SLOT(onAccept()));
connect(m_pCancelBtn, SIGNAL(clicked()), this, SLOT(onReject()));
connect(m_pDataTable, SIGNAL(cellChanged(int, int)), this, SLOT(onTableDataChanged(int, int)));
connect(m_pDataTable, SIGNAL(cellClicked(int, int)), this, SLOT(onTableRowClicked(int, int)));
// 右侧按钮信号连接
connect(m_pRightAddBtn, SIGNAL(clicked()), this, SLOT(onRightAddClicked()));
connect(m_pRightInsertBtn, SIGNAL(clicked()), this, SLOT(onRightInsertClicked()));
connect(m_pRightDeleteBtn, SIGNAL(clicked()), this, SLOT(onRightDeleteClicked()));
// 流动段操作
connect(m_pPressureChart, SIGNAL(flowSegmentClicked(int)), this, SLOT(onFlowSegmentSelected(int)));
connect(m_pFlowRateChart, SIGNAL(flowSegmentClicked(int)), this, SLOT(onFlowSegmentSelected(int)));
connect(m_pPressureChart, SIGNAL(chartPointClicked(double)), this, SLOT(onChartPointClicked(double)));
connect(m_pFlowRateChart, SIGNAL(chartPointClicked(double)), this, SLOT(onChartPointClicked(double)));
// 拖动相关的信号连接
connect(m_pPressureChart, SIGNAL(boundaryMoved(int, double)), this, SLOT(onBoundaryMoved(int, double)));
connect(m_pFlowRateChart, SIGNAL(boundaryMoved(int, double)), this, SLOT(onBoundaryMoved(int, double)));
connect(m_pPressureChart, SIGNAL(segmentsMerged(int)), this, SLOT(onSegmentsMerged(int)));
connect(m_pFlowRateChart, SIGNAL(segmentsMerged(int)), this, SLOT(onSegmentsMerged(int)));
connect(m_pSnapToRateChangesCheckBox, SIGNAL(toggled(bool)), this, SLOT(onSnapToRateChangesToggled(bool)));
connect(m_pPressureChart, SIGNAL(boundaryMovedFinal(int, double)), this, SLOT(onBoundaryMovedFinal(int, double)));
connect(m_pFlowRateChart, SIGNAL(boundaryMovedFinal(int, double)), this, SLOT(onBoundaryMovedFinal(int, double)));
connect(m_pAddRateDependentCheckBox, SIGNAL(toggled(bool)), this, SLOT(onAddRateDependentToggled(bool)));
connect(m_pShowDatesCheckBox, SIGNAL(toggled(bool)), this, SLOT(onShowDatesToggled(bool)));
connect(m_pComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onPerforationChanged(int)));
}
nmWxTimeDependentSkin::~nmWxTimeDependentSkin()
{
// 如果静态实例指针指向当前对象,清空静态变量
if (s_instance == this) {
s_instance = nullptr;
s_currentWellName = "";
s_currentWellData = nullptr;
}
}
void nmWxTimeDependentSkin::reloadFromDataSource(nmDataWellBase* pWellData)
{
// 1) 重新决定数据源
m_currentWell = (pWellData != nullptr)
? pWellData
: nmDataAnalyzeManager::getCurrentInstance()->getCurWellData();
if (!m_currentWell) return;
// 2) 重新抓取并处理数据
pressurePoints = m_currentWell->getPressurePoints();
QVector<QPointF> rawFlowData = m_currentWell->getFlowPoints();
flowPoints = processFlowData(rawFlowData);
// 3) 重新取选中射孔段的副本(保留索引边界)
int perfCount = m_currentWell->getPerforationCount();
if (m_currentPerforationIndex >= perfCount) m_currentPerforationIndex = 0;
m_perforationData = m_currentWell->getPerforationCopy(m_currentPerforationIndex);
// 4) 做一次与构造期相同的默认值兜底
if (m_perforationData.getFlowSegments().isEmpty()) initializeDefaultFlowSegment();
if (m_perforationData.getSkin0().getValue().toString().isEmpty()) m_perforationData.getSkin0().setValue("0.0");
if (m_perforationData.getdSkindq().getValue().toString().isEmpty()) m_perforationData.getdSkindq().setValue("0.0");
double x1,y1,x2,y2; m_perforationData.getLinePositions(x1,y1,x2,y2);
if (x1==-999.0 || y1==-999.0 || x2==-999.0 || y2==-999.0) m_perforationData.setLinePositions(100.0,0.0,200.0,0.0);
// 5) 刷新UI
updateCharts();
updateTableFromFlowSegments();
}
void nmWxTimeDependentSkin::initUI()
{
// 创建主布局
QVBoxLayout* mainLayout = new QVBoxLayout(this);
// 创建工具栏容器
QWidget* toolbarContainer = new QWidget(this);
toolbarContainer->setFixedHeight(70); // 固定工具栏高度
m_pToolbarLayout = new QHBoxLayout(toolbarContainer); // 保存布局指针
// 创建按钮并设置统一样式
auto createButton = [&](QPushButton*& button, const QString & iconPath, const QString & text) {
QWidget* buttonContainer = new QWidget(this);
QVBoxLayout* containerLayout = new QVBoxLayout(buttonContainer);
containerLayout->setSpacing(0);
containerLayout->setContentsMargins(2, 2, 2, 2); // 减小边距
// 图标按钮
button = new QPushButton(buttonContainer);
button->setIcon(QIcon(iconPath));
button->setIconSize(QSize(36, 36));
button->setFixedSize(40, 40);
// 文字标签
QLabel* textLabel = new QLabel(text, buttonContainer);
textLabel->setAlignment(Qt::AlignCenter);
// 将按钮和文字添加到容器
containerLayout->addWidget(button, 0, Qt::AlignHCenter);
containerLayout->addWidget(textLabel);
// 设置容器的大小策略和固定高度
buttonContainer->setFixedSize(100, 70); // 固定宽度和高度
m_pToolbarLayout->addWidget(buttonContainer);
};
QString appDir = QCoreApplication::applicationDirPath();
appDir = appDir.section('/', 0, -2); // 获取上一级目录
// 创建所有按钮
createButton(m_pSkinVsRateBtn, appDir + "/Res/Icon/nmTimeDepSkin1.png", tr("Skin vs Rate"));
createButton(m_pAddBtn, appDir + "/Res/Icon/nmTimeDepSkin2.png", tr("Add"));
createButton(m_pRemoveBtn, appDir + "/Res/Icon/nmTimeDepSkin3.png", tr("Remove"));
createButton(m_pSplitStepsBtn, appDir + "/Res/Icon/nmTimeDepSkin4.png", tr("Split Steps"));
createButton(m_pComputeBtn, appDir + "/Res/Icon/nmTimeDepSkin5.png", tr("Compute"));
createButton(m_pPerforationClosingBtn, appDir + "/Res/Icon/nmTimeDepSkin6.png", tr("Perforation Closing"));
m_pToolbarLayout->addStretch();
// 创建复选框区域
QWidget* checkboxContainer = new QWidget(this);
QHBoxLayout* checkboxLayout = new QHBoxLayout(checkboxContainer);
checkboxLayout->setSpacing(15); // 设置复选框之间的间距
checkboxLayout->setContentsMargins(10, 5, 10, 5);
// 创建三个复选框
m_pSnapToRateChangesCheckBox = new QCheckBox(tr("Snap to rate changes"));
m_pShowDatesCheckBox = new QCheckBox(tr("Show dates"));
m_pAddRateDependentCheckBox = new QCheckBox(tr("Add rate dependent"));
// 将复选框水平添加到布局
checkboxLayout->addWidget(m_pSnapToRateChangesCheckBox);
checkboxLayout->addWidget(m_pShowDatesCheckBox);
checkboxLayout->addWidget(m_pAddRateDependentCheckBox);
// 设置复选框容器的合适宽度以容纳三个复选框
checkboxContainer->setFixedWidth(450); // 增加宽度以容纳水平排列的复选框
checkboxContainer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
// 将复选框容器添加到工具栏布局
m_pToolbarLayout->addWidget(checkboxContainer);
// 将工具栏容器添加到主布局
mainLayout->addWidget(toolbarContainer);
// 创建分割器,用于调整三个区域的大小
QSplitter* splitter = new QSplitter(Qt::Horizontal, this);
splitter->setChildrenCollapsible(false);
splitter->setHandleWidth(1);
// 创建左侧区域(图表+下拉框)
QWidget* leftWidget = new QWidget();
QVBoxLayout* leftLayout = new QVBoxLayout(leftWidget);
// 创建图表区域容器
QWidget* chartWidget = new QWidget();
m_pChartLayout = new QVBoxLayout(chartWidget);
m_pChartLayout->setSpacing(0); // 移除布局间距,让分割器来控制间距
m_pChartLayout->setContentsMargins(0, 0, 0, 0); // 移除边距
// 创建垂直分割器用于调整两个图表的比例
m_pChartSplitter = new QSplitter(Qt::Vertical, chartWidget);
m_pChartSplitter->setChildrenCollapsible(false); // 防止子控件被完全折叠
m_pChartSplitter->setHandleWidth(3); // 设置分割线宽度
// 创建压力图表
m_pPressureChart = new nmWxPressFlowChartWidget(m_pChartSplitter);
m_pPressureChart->setAxisLabels(tr(""), tr("Pressure"));
m_pPressureChart->setAxisUnits(tr("hr"), tr("MPa"));
m_pPressureChart->setLineColor(QColor(50, 200, 50)); // 绿色
m_pPressureChart->setChartType(CHART_TYPE_LINE);
m_pPressureChart->setMinimumHeight(150); // 设置最小高度防止过度压缩
m_pPressureChart->setShowXAxisLabels(false); // 压力图不显示X轴标签
// 创建流量图表
m_pFlowRateChart = new nmWxPressFlowChartWidget(m_pChartSplitter);
m_pFlowRateChart->setAxisLabels(tr("Time"), tr("Flow"));
m_pFlowRateChart->setAxisUnits(tr("hr"), tr("m3/d"));
m_pFlowRateChart->setLineColor(QColor(255, 0, 0)); // 红色
m_pFlowRateChart->setChartType(CHART_TYPE_STEP);
m_pFlowRateChart->setMinimumHeight(150); // 设置最小高度防止过度压缩
// 保存原始图表的引用
m_pOriginalPressureChart = m_pPressureChart;
m_pOriginalFlowRateChart = m_pFlowRateChart;
// 将图表添加到分割器
m_pChartSplitter->addWidget(m_pPressureChart);
m_pChartSplitter->addWidget(m_pFlowRateChart);
// 设置初始比例 (1:1)
m_pChartSplitter->setStretchFactor(0, 1);
m_pChartSplitter->setStretchFactor(1, 1);
// 将分割器添加到布局中
m_pChartLayout->addWidget(m_pChartSplitter, 1);
// 创建图表下方的下拉框
m_pComboBox = new QComboBox();
m_pComboBox->setFixedWidth(200); // 增加宽度以容纳射孔段名称
// 将图表和下拉框添加到左侧布局
leftLayout->addWidget(chartWidget, 1); // 图表占据大部分空间
leftLayout->addWidget(m_pComboBox); // 下拉框固定高度
// 创建中间区域(表格+确定取消按钮)
QWidget* middleWidget = new QWidget();
QVBoxLayout* middleLayout = new QVBoxLayout(middleWidget);
// 创建数据表格
m_pDataTable = new QTableWidget(0, 3);
// 设置列标题
QStringList headers;
headers << tr("") << tr("Start time") << tr("Skin");
m_pDataTable->setHorizontalHeaderLabels(headers);
m_pDataTable->verticalHeader()->setVisible(false);
m_pDataTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_pDataTable->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
QHeaderView* header = m_pDataTable->horizontalHeader();
header->setResizeMode(0, QHeaderView::Fixed); // 第一列(箭头列)固定宽度
header->setResizeMode(1, QHeaderView::Stretch); // 第二列(时间列)拉伸
header->setResizeMode(2, QHeaderView::Stretch); // 第三列Skin列拉伸
// 表头下的“单位”行
m_pDataTable->insertRow(0);
// 第二列:时间单位下拉框(统一用小写,匹配 convertTimeUnit
m_pTimeUnitCombo = new QComboBox(m_pDataTable);
QStringList units;
units << "sec" << "min" << "hr" << "day" << "week" << "month" << "year";
if(m_timeDisplayUnit.isEmpty()) m_timeDisplayUnit = "hr";
int idx = units.indexOf(m_timeDisplayUnit);
m_pTimeUnitCombo->addItems(units);
m_pTimeUnitCombo->setCurrentIndex(idx >= 0 ? idx : units.indexOf("hr"));
m_pTimeUnitCombo->setEditable(true);
m_pTimeUnitCombo->lineEdit()->setAlignment(Qt::AlignCenter);
m_pTimeUnitCombo->lineEdit()->setReadOnly(true);
m_pTimeUnitCombo->setStyleSheet(
"QComboBox { border: none; background: transparent; padding-left: 4px; }"
"QComboBox::drop-down { border: none; }"
);
m_pDataTable->setCellWidget(0, 1, m_pTimeUnitCombo);
// 第三列占位Skin 无单位)
QTableWidgetItem* empty = new QTableWidgetItem("");
empty->setFlags(Qt::ItemIsEnabled);
m_pDataTable->setItem(0, 2, empty);
// 行高
m_pDataTable->setRowHeight(0, 24);
// 信号连接
connect(m_pTimeUnitCombo, SIGNAL(currentIndexChanged(const QString &)),
this, SLOT(onTimeUnitChanged(const QString &)));
// 设置第一列的固定宽度
m_pDataTable->setColumnWidth(0, 20);
// 表格设置
m_pDataTable->setSelectionMode(QAbstractItemView::NoSelection); // 禁用默认选中
m_pDataTable->setFocusPolicy(Qt::NoFocus); // 禁用焦点
// 创建表格下方的确定取消按钮
QHBoxLayout* tableBottomLayout = new QHBoxLayout();
tableBottomLayout->addStretch();
m_pOkBtn = new QPushButton(tr("OK"));
m_pCancelBtn = new QPushButton(tr("Cancel"));
m_pOkBtn->setFixedSize(100, 30);
m_pCancelBtn->setFixedSize(100, 30);
tableBottomLayout->addWidget(m_pOkBtn);
tableBottomLayout->addWidget(m_pCancelBtn);
// 将表格和按钮添加到中间布局
middleLayout->addWidget(m_pDataTable, 1); // 表格占据大部分空间
middleLayout->addLayout(tableBottomLayout); // 确定取消按钮
// 创建右侧按钮区域
QWidget* rightButtonWidget = new QWidget();
QVBoxLayout* rightButtonLayout = new QVBoxLayout(rightButtonWidget);
// 创建三个垂直排布的按钮
m_pRightAddBtn = new QPushButton(tr("Add"));
m_pRightInsertBtn = new QPushButton(tr("Insert"));
m_pRightDeleteBtn = new QPushButton(tr("Delete"));
// 设置按钮的固定大小
m_pRightAddBtn->setFixedSize(100, 30);
m_pRightInsertBtn->setFixedSize(100, 30);
m_pRightDeleteBtn->setFixedSize(100, 30);
// 添加按钮到垂直布局
rightButtonLayout->addWidget(m_pRightAddBtn);
rightButtonLayout->addWidget(m_pRightInsertBtn);
rightButtonLayout->addWidget(m_pRightDeleteBtn);
rightButtonLayout->addStretch(); // 添加弹性空间,将按钮推到顶部
// 将三个区域直接添加到分割器
splitter->addWidget(leftWidget); // 左侧区域(图表+下拉框)
splitter->addWidget(middleWidget); // 中间区域(表格+确定取消按钮)
splitter->addWidget(rightButtonWidget); // 右侧区域Add/Insert/Delete按钮
// 设置比例
splitter->setStretchFactor(0, 6); // 左侧区域占7份
splitter->setStretchFactor(1, 3); // 中间区域占2份
splitter->setStretchFactor(2, 1); // 右侧区域占1份
mainLayout->addWidget(splitter);
// 设置主布局
setLayout(mainLayout);
// 工具栏按钮默认状态
m_pSkinVsRateBtn->setEnabled(false);
m_pAddBtn->setEnabled(true);
m_pRemoveBtn->setEnabled(true);
m_pSplitStepsBtn->setEnabled(true);
m_pComputeBtn->setEnabled(false);
m_pPerforationClosingBtn->setEnabled(true);
// 复选框默认状态
m_pSnapToRateChangesCheckBox->setChecked(true);
// 从数据中读取"对齐到流量段"的状态
bool snapToRateEnabled = m_perforationData.isSnapToRateChangesEnabled();
m_pSnapToRateChangesCheckBox->setChecked(snapToRateEnabled);
// 从数据中读取"显示日期"的状态
bool showDatesEnabled = m_perforationData.isShowDatesEnabled();
m_pShowDatesCheckBox->setChecked(showDatesEnabled);
// 从数据中读取"添加流量相关"的状态
bool rateDependentEnabled = m_perforationData.isRateDependentEnabled();
m_pAddRateDependentCheckBox->setChecked(rateDependentEnabled);
m_rateDependentState = rateDependentEnabled; // 保存初始状态
// 右侧按钮默认状态
m_pRightAddBtn->setEnabled(true);
m_pRightInsertBtn->setEnabled(true);
m_pRightDeleteBtn->setEnabled(true);
// 在初始化时创建图表模式按钮但不显示
createChartModeBtn();
}
void nmWxTimeDependentSkin::updateCharts()
{
if(!m_pPressureChart || !m_pFlowRateChart) {
return;
}
// 同步对齐设置
bool snapToRateChanges = m_pSnapToRateChangesCheckBox->isChecked();
m_pPressureChart->setSnapToRateChanges(snapToRateChanges);
m_pFlowRateChart->setSnapToRateChanges(snapToRateChanges);
// 更新压力数据
if(!pressurePoints.isEmpty()) {
m_pPressureChart->setData(pressurePoints);
// 使用本地副本数据而不是从管理器获取
m_pPressureChart->setFlowSegmentData(m_perforationData);
}
// 更新流量数据
if(!flowPoints.isEmpty()) {
m_pFlowRateChart->setData(flowPoints);
// 使用本地副本数据而不是从管理器获取
m_pFlowRateChart->setFlowSegmentData(m_perforationData);
}
}
void nmWxTimeDependentSkin::updateTableFromFlowSegments()
{
if(!m_pDataTable) return;
const QVector<FlowSegmentData>& segments = m_perforationData.getFlowSegments();
if(segments.isEmpty()) return;
const bool enableRateDep = m_perforationData.isRateDependentEnabled();
const int requiredCols = enableRateDep ? 4 : 3;
// 只有在列数真正改变时才重建表格结构
bool needRefreshStructure = (m_pDataTable->columnCount() != requiredCols);
if(needRefreshStructure) {
refreshTableStructure();
} else {
// 确保单位行存在但不重建ComboBox
ensureHeaderRowExists();
// 更新时间单位显示
updateTimeUnitComboVisibility();
}
// 屏蔽信号,防止填充过程中触发
bool oldTblBlocked = m_pDataTable->blockSignals(true);
bool oldTimeBlocked = false;
bool oldQBlocked = false;
if(m_pTimeUnitCombo) oldTimeBlocked = m_pTimeUnitCombo->blockSignals(true);
if(m_pDSdQUnitCombo) oldQBlocked = m_pDSdQUnitCombo->blockSignals(true);
// 设置总行数
m_pDataTable->setRowCount(segments.size() + 1);
// 填充数据行从第1行开始第0行是单位行
for(int i = 0; i < segments.size(); ++i) {
const int row = i + 1;
const FlowSegmentData& segment = segments[i];
// 更新选择箭头
QTableWidgetItem* arrowItem = m_pDataTable->item(row, 0);
if(!arrowItem) {
arrowItem = new QTableWidgetItem();
arrowItem->setFlags(Qt::ItemIsEnabled);
m_pDataTable->setItem(row, 0, arrowItem);
}
arrowItem->setIcon(segment.isSelected ? createTriangleIcon() : QIcon());
arrowItem->setText("");
// 更新时间
double startHr = segment.segmentStart.getValue().toDouble();
QString timeDisplay = formatTimeDisplay(startHr, i);
QTableWidgetItem* timeItem = m_pDataTable->item(row, 1);
if(!timeItem) {
timeItem = new QTableWidgetItem();
timeItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
// 如果显示日期,时间列设为只读
bool showDates = m_perforationData.isShowDatesEnabled();
Qt::ItemFlags flags = Qt::ItemIsEnabled;
if(!showDates && i > 0) {
flags |= Qt::ItemIsEditable;
}
timeItem->setFlags(flags);
m_pDataTable->setItem(row, 1, timeItem);
}
timeItem->setText(timeDisplay);
// 更新Skin值
double skinValue = segment.skinValue.getValue().toDouble();
QTableWidgetItem* skinItem = m_pDataTable->item(row, 2);
if(!skinItem) {
skinItem = new QTableWidgetItem();
skinItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
skinItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable);
m_pDataTable->setItem(row, 2, skinItem);
}
skinItem->setText(QString::number(skinValue, 'f', 5));
// 更新dS/dQ值如果启用
if(enableRateDep) {
double base = segment.dSdQ.getValue().toDouble();
double disp = convertDSdQ(base, "1/B/D", m_dSdQDisplayUnit);
QTableWidgetItem* dsdqItem = m_pDataTable->item(row, 3);
if(!dsdqItem) {
dsdqItem = new QTableWidgetItem();
dsdqItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
dsdqItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable);
m_pDataTable->setItem(row, 3, dsdqItem);
}
dsdqItem->setText(QString::number(disp, 'f', 5));
}
}
// 恢复信号状态
if(m_pTimeUnitCombo) m_pTimeUnitCombo->blockSignals(oldTimeBlocked);
if(m_pDSdQUnitCombo) m_pDSdQUnitCombo->blockSignals(oldQBlocked);
m_pDataTable->blockSignals(oldTblBlocked);
// 更新按钮状态
m_pSkinVsRateBtn->setEnabled(segments.size() >= 2);
}
void nmWxTimeDependentSkin::ensureHeaderRowExists()
{
// 确保第0行存在但不重建ComboBox
if(m_pDataTable->rowCount() == 0) {
m_pDataTable->insertRow(0);
m_pDataTable->setRowHeight(0, 24);
}
// 如果ComboBox不存在才创建只在首次或结构改变时
if(!m_pTimeUnitCombo) {
createTimeUnitCombo();
}
const bool enableRateDep = m_perforationData.isRateDependentEnabled();
if(enableRateDep && !m_pDSdQUnitCombo) {
createDSdQUnitCombo();
} else if(!enableRateDep && m_pDSdQUnitCombo) {
// 如果禁用了rate dependent删除dS/dQ ComboBox
m_pDSdQUnitCombo->disconnect(this);
delete m_pDSdQUnitCombo;
m_pDSdQUnitCombo = nullptr;
}
}
void nmWxTimeDependentSkin::createTimeUnitCombo()
{
bool showDates = m_perforationData.isShowDatesEnabled();
if(showDates) {
// 如果显示日期,创建一个标签而不是下拉框
QLabel* dateTimeLabel = new QLabel(tr("Date Time"), m_pDataTable);
dateTimeLabel->setAlignment(Qt::AlignCenter);
dateTimeLabel->setStyleSheet("QLabel { color: #333; font-size: 12px; }");
m_pDataTable->setCellWidget(0, 1, dateTimeLabel);
return;
}
// 原有的创建时间单位下拉框的代码
m_pTimeUnitCombo = new QComboBox(m_pDataTable);
QStringList units;
units << "sec" << "min" << "hr" << "day" << "week" << "month" << "year";
if(m_timeDisplayUnit.isEmpty()) m_timeDisplayUnit = "hr";
int idx = units.indexOf(m_timeDisplayUnit);
m_pTimeUnitCombo->addItems(units);
m_pTimeUnitCombo->setCurrentIndex(idx >= 0 ? idx : units.indexOf("hr"));
m_pTimeUnitCombo->setEditable(true);
m_pTimeUnitCombo->lineEdit()->setAlignment(Qt::AlignCenter);
m_pTimeUnitCombo->lineEdit()->setReadOnly(true);
m_pTimeUnitCombo->setStyleSheet(
"QComboBox { border: none; background: transparent; padding-left: 4px; }"
"QComboBox::drop-down { border: none; }"
);
m_pDataTable->setCellWidget(0, 1, m_pTimeUnitCombo);
connect(m_pTimeUnitCombo, SIGNAL(currentIndexChanged(const QString &)),
this, SLOT(onTimeUnitChanged(const QString &)));
}
void nmWxTimeDependentSkin::createDSdQUnitCombo()
{
m_pDSdQUnitCombo = new QComboBox(m_pDataTable);
QStringList qUnits;
qUnits << "1/B/D" << "1/MMm^3/D" << "1/Mcf/D" << "1/Mm^3/D" << "1/Mm^3/hr";
m_pDSdQUnitCombo->addItems(qUnits);
if(m_dSdQDisplayUnit.isEmpty()) m_dSdQDisplayUnit = "1/B/D";
int qIdx = qUnits.indexOf(m_dSdQDisplayUnit);
m_pDSdQUnitCombo->setCurrentIndex(qIdx >= 0 ? qIdx : qUnits.indexOf("1/B/D"));
m_pDSdQUnitCombo->setStyleSheet(
"QComboBox { border: none; background: transparent; padding-left: 4px; }"
"QComboBox::drop-down { border: none; }"
);
m_pDataTable->setCellWidget(0, 3, m_pDSdQUnitCombo);
connect(m_pDSdQUnitCombo, SIGNAL(currentIndexChanged(const QString&)),
this, SLOT(onDSdQUnitChanged(const QString&)));
}
void nmWxTimeDependentSkin::onFlowSegmentSelected(int segmentIndex)
{
// 如果在移除模式,执行合并操作
if(m_isRemoveMode) {
// 尝试与左侧段合并
bool success = mergeSegmentWithPrevious(segmentIndex);
// 无论成功与否,都退出移除模式
m_isRemoveMode = false;
if(success) {
// 如果成功,更新图表和表格显示
updateCharts();
updateTableFromFlowSegments();
}
return;
}
// 如果在添加模式,不处理段选择
if(m_isAddMode) {
return;
}
// 正常的选择流动段
selectFlowSegment(segmentIndex);
// 更新图表显示
updateCharts();
// 更新表格显示
updateTableFromFlowSegments();
}
bool nmWxTimeDependentSkin::addFlowSegmentAtExactTime(double timePoint)
{
// 找到包含该时间点的流动段
int segmentIndex = m_perforationData.findFlowSegmentByTime(timePoint);
if(segmentIndex < 0) {
return false; // 没有找到对应的流动段
}
// 直接在该时间点分割流动段
return splitFlowSegmentAtTime(segmentIndex, timePoint);
}
void nmWxTimeDependentSkin::onChartPointClicked(double timePoint)
{
if(!m_isAddMode) {
return; // 不在添加模式,忽略此事件
}
bool success = false;
// 检查"Snap to rate changes"复选框状态
bool snapToRateChanges = m_pSnapToRateChangesCheckBox->isChecked();
if(snapToRateChanges) {
// 勾选状态:使用现有逻辑,在点击位置所在阶梯段的结束边界添加流动段
success = addFlowSegmentAtTime(timePoint);
} else {
// 未勾选状态:直接在点击位置分割流动段
success = addFlowSegmentAtExactTime(timePoint);
}
// 无论成功与否,都退出添加模式
m_isAddMode = false;
if(success) {
// 如果成功,更新图表和表格显示
updateCharts();
updateTableFromFlowSegments();
}
}
void nmWxTimeDependentSkin::onAddClicked()
{
if(m_isAddMode) {
// 如果已经在添加模式,退出添加模式
m_isAddMode = false;
} else {
// 进入添加模式,同时退出移除模式
m_isAddMode = true;
m_isRemoveMode = false;
}
}
void nmWxTimeDependentSkin::onRemoveClicked()
{
if(m_isRemoveMode) {
// 如果已经在移除模式,退出移除模式
m_isRemoveMode = false;
} else {
// 进入移除模式,同时退出添加模式
m_isRemoveMode = true;
m_isAddMode = false;
}
}
void nmWxTimeDependentSkin::onSplitStepsClicked()
{
// 一次性分割所有流动段
splitAllFlowSegments();
// 更新图表显示
updateCharts();
// 更新表格数据
updateTableFromFlowSegments();
}
void nmWxTimeDependentSkin::onComputeClicked()
{
}
void nmWxTimeDependentSkin::onPerforationClosingBtnClicked()
{
nmWxPerforationClosing* dlg = new nmWxPerforationClosing(m_currentWell, this);
dlg->show();
}
void nmWxTimeDependentSkin::onSkinVsRateClicked()
{
// 如果已经显示了图表,就恢复原始状态
if(m_pChartWidget != nullptr) {
restoreOriginalCharts();
restoreToolbarState();
return;
}
// 进入SkinVsRate模式前保存当前完整状态
saveStateBeforeSkinVsRate();
// 创建交互式图表
showInteractiveChart();
hideToolbarButtons();
}
void nmWxTimeDependentSkin::saveStateBeforeSkinVsRate()
{
// 保存完整的流动段数据副本包括所有dSdQ值
m_savedFlowSegmentData = m_perforationData;
// 保存"添加流量相关"复选框状态
m_savedRateDependentState = m_pAddRateDependentCheckBox ?
m_pAddRateDependentCheckBox->isChecked() : false;
}
void nmWxTimeDependentSkin::restoreStateAfterSkinVsRate()
{
// 只恢复dSdQ值保留skin值的修改
QVector<FlowSegmentData> currentSegments = m_perforationData.getFlowSegments();
const QVector<FlowSegmentData>& savedSegments = m_savedFlowSegmentData.getFlowSegments();
// 确保两个数组大小一致
if(currentSegments.size() == savedSegments.size()) {
for(int i = 0; i < currentSegments.size(); ++i) {
// 只恢复dSdQ值保留当前的skin值
currentSegments[i].dSdQ = savedSegments[i].dSdQ;
// skin值保持不变currentSegments[i].skinValue 不修改
}
// 更新流动段数据
m_perforationData.setFlowSegments(currentSegments);
}
// 恢复RateDependent状态
m_perforationData.setRateDependentEnabled(m_savedRateDependentState);
// 恢复"添加流量相关"复选框状态
if(m_pAddRateDependentCheckBox) {
bool wasBlocked = m_pAddRateDependentCheckBox->blockSignals(true);
m_pAddRateDependentCheckBox->setChecked(m_savedRateDependentState);
m_pAddRateDependentCheckBox->blockSignals(wasBlocked);
}
}
void nmWxTimeDependentSkin::showInteractiveChart()
{
if(m_pChartSplitter == nullptr) return;
// 隐藏分割器
m_pChartSplitter->hide();
// 创建交互式图表widget
m_pChartWidget = new nmWxChartWidget(this);
m_pChartWidget->setMinimumSize(400, 400);
// 连接信号槽
connect(m_pChartWidget, SIGNAL(skinValuesChanged(double, double)),
this, SLOT(onSkinValuesChanged(double, double)));
// 连接过滤完成信号
connect(m_pChartWidget, SIGNAL(crossMarksFiltered()),
this, SLOT(onCrossMarksFiltered()));
// 添加到布局
m_pChartLayout->addWidget(m_pChartWidget, 1);
m_pChartWidget->show();
// 设置对齐到流量段的初始状态
bool snapToRateChanges = m_pSnapToRateChangesCheckBox->isChecked();
m_pChartWidget->setSnapToRateChanges(snapToRateChanges);
m_pChartWidget->setFlowSegmentData(m_perforationData);
// 传递原始流量数据
if(m_currentWell != nullptr) {
QVector<QPointF> rawFlowData = m_currentWell->getFlowPoints();
m_pChartWidget->setRawFlowData(rawFlowData);
}
m_pChartWidget->calculateDataRange();
// 在数据传递完成后,检查是否需要加载已保存的线条位置
// 这里会被延迟执行,确保初始拟合先完成
QTimer::singleShot(10, this, SLOT(loadSavedLinePositionsIfValid()));
// 初始化编辑框
updateEditBoxesFromData();
}
void nmWxTimeDependentSkin::loadSavedLinePositionsIfValid()
{
if(m_pChartWidget == nullptr) return;
// 直接检查数据类中是否有有效的线条位置
double x1, y1, x2, y2;
m_perforationData.getLinePositions(x1, y1, x2, y2);
// 检查是否为有效数据(-999表示未设置或者是默认值100,0,200,0
bool hasValidData = (x1 != -999.0 && y1 != -999.0 && x2 != -999.0 && y2 != -999.0);
// 额外检查:如果是默认值(100,0,200,0),也认为是无效数据
bool isDefaultValue = (qAbs(x1 - 100.0) < 1e-6 && qAbs(y1 - 0.0) < 1e-6 &&
qAbs(x2 - 200.0) < 1e-6 && qAbs(y2 - 0.0) < 1e-6);
if(hasValidData && !isDefaultValue) {
// 有有效且非默认的数据,加载保存的位置(用户之前调整过的位置)
loadChartPositionsFromData();
updateEditBoxesFromData();
} else {
// 没有有效数据或是默认值,图表组件的初始拟合结果已经生效
// 不需要再次保存,因为 onSkinValuesChanged 已经保存过了
updateEditBoxesFromData();
}
}
void nmWxTimeDependentSkin::initializeChartFromData()
{
if(m_pChartWidget == nullptr) return;
// 从 m_skinVsRateData 获取当前值
bool skin0Ok, dSkinDqOk;
double skin0Value = m_perforationData.getSkin0().getValue().toString().toDouble(&skin0Ok);
double dSkinDqValue = m_perforationData.getdSkindq().getValue().toString().toDouble(&dSkinDqOk);
// 如果数据无效,使用默认值
if(!skin0Ok) {
skin0Value = 0.0;
}
if(!dSkinDqOk) {
dSkinDqValue = 0.0;
}
// 设置图表的初始值和线条位置
m_pChartWidget->setSkinValues(skin0Value, dSkinDqValue);
}
void nmWxTimeDependentSkin::loadChartPositionsFromData()
{
if(m_pChartWidget == nullptr) return;
// 获取保存的线条位置
double x1, y1, x2, y2;
m_perforationData.getLinePositions(x1, y1, x2, y2);
// 检查是否有有效数据(-999表示未设置
if(x1 == -999.0 || y1 == -999.0 || x2 == -999.0 || y2 == -999.0) {
// 没有有效数据,不执行加载
return;
}
// 设置图表的线条位置
m_pChartWidget->setLinePositions(x1, y1, x2, y2);
}
void nmWxTimeDependentSkin::saveChartPositionsToData()
{
if(m_pChartWidget == nullptr) return;
// 获取当前图表的线条位置
double x1, y1, x2, y2;
m_pChartWidget->getLinePositions(x1, y1, x2, y2);
// 保存到数据对象
m_perforationData.setLinePositions(x1, y1, x2, y2);
// 同时更新skin0和dSkin/dq值
double skin0Value = m_pChartWidget->getSkin0Value();
double dSkinDqValue = m_pChartWidget->getDSkinDqValue();
m_perforationData.getSkin0().setValue(QString::number(skin0Value, 'f', 5));
m_perforationData.getdSkindq().setValue(QString::number(dSkinDqValue, 'f', 5));
}
void nmWxTimeDependentSkin::onSkinValuesChanged(double skin0Value, double dSkinDqValue)
{
// 实时保存图表位置到数据对象
saveChartPositionsToData();
// 更新编辑框显示
updateEditBoxesFromData();
}
void nmWxTimeDependentSkin::onCrossMarksFiltered()
{
// 过滤完成后,自动保存图表位置
saveChartPositionsToData();
// 更新编辑框显示
updateEditBoxesFromData();
}
void nmWxTimeDependentSkin::updateEditBoxesFromData()
{
if(m_pSkinEdit && m_pDSkinDqEdit) {
QString skin0Text = m_perforationData.getSkin0().getValue().toString();
QString dSkinDqText = m_perforationData.getdSkindq().getValue().toString();
m_pSkinEdit->setText(skin0Text);
m_pDSkinDqEdit->setText(dSkinDqText);
}
}
void nmWxTimeDependentSkin::restoreOriginalCharts()
{
if(m_pChartLayout == nullptr || m_pChartSplitter == nullptr) return;
// 如果图表widget存在且处于套索模式先退出
if(m_pChartWidget != nullptr) {
if(m_pChartWidget->getLassoMode()) {
m_pChartWidget->setLassoMode(false);
}
}
// 移除交互式图表
if(m_pChartWidget != nullptr) {
m_pChartLayout->removeWidget(m_pChartWidget);
m_pChartWidget->deleteLater();
m_pChartWidget = nullptr;
}
// 重新显示分割器(包含原始的两个图表)
m_pChartSplitter->show();
// 确保原始图表在分割器中是可见的
if(m_pOriginalPressureChart != nullptr) {
m_pOriginalPressureChart->show();
}
if(m_pOriginalFlowRateChart != nullptr) {
m_pOriginalFlowRateChart->show();
}
// 恢复默认比例 (1:1)
m_pChartSplitter->setStretchFactor(0, 1);
m_pChartSplitter->setStretchFactor(1, 1);
}
void nmWxTimeDependentSkin::hideToolbarButtons()
{
// 通过按钮的父容器隐藏原有按钮
if(m_pAddBtn && m_pAddBtn->parentWidget()) {
m_pAddBtn->parentWidget()->setVisible(false);
}
if(m_pRemoveBtn && m_pRemoveBtn->parentWidget()) {
m_pRemoveBtn->parentWidget()->setVisible(false);
}
if(m_pSplitStepsBtn && m_pSplitStepsBtn->parentWidget()) {
m_pSplitStepsBtn->parentWidget()->setVisible(false);
}
if(m_pComputeBtn && m_pComputeBtn->parentWidget()) {
m_pComputeBtn->parentWidget()->setVisible(false);
}
if(m_pPerforationClosingBtn && m_pPerforationClosingBtn->parentWidget()) {
m_pPerforationClosingBtn->parentWidget()->setVisible(false);
}
// 显示图表模式下的新按钮
if(m_pFilterContainer) {
m_pFilterContainer->setVisible(true);
}
if(m_pResetFilterContainer) {
m_pResetFilterContainer->setVisible(true);
}
if(m_pResetLinesContainer) {
m_pResetLinesContainer->setVisible(true);
}
// 显示编辑框容器
if(m_pEditContainer) {
m_pEditContainer->setVisible(true);
}
// 隐藏"显示日期"复选框
if(m_pShowDatesCheckBox) {
m_pShowDatesCheckBox->setStyleSheet(
"QCheckBox { color: transparent; }"
"QCheckBox::indicator { width: 0px; height: 0px; }"
"QCheckBox::indicator:unchecked { image: none; }"
"QCheckBox::indicator:checked { image: none; }"
);
m_pShowDatesCheckBox->setEnabled(false);
}
// 保存并禁用"添加流量相关"复选框
if(m_pAddRateDependentCheckBox) {
// 阻止信号触发
bool wasBlocked = m_pAddRateDependentCheckBox->blockSignals(true);
m_pAddRateDependentCheckBox->setChecked(false);
m_pAddRateDependentCheckBox->blockSignals(wasBlocked);
m_pAddRateDependentCheckBox->setEnabled(false);
// 临时禁用数据的RateDependent状态不会清零dSdQ值因为我们有备份
m_perforationData.setRateDependentEnabled(false);
// 刷新表格显示会自动隐藏dSdQ列
updateTableFromFlowSegments();
}
}
void nmWxTimeDependentSkin::restoreToolbarState()
{
// 显示所有隐藏的按钮容器
if(m_pAddBtn && m_pAddBtn->parentWidget()) {
m_pAddBtn->parentWidget()->setVisible(true);
}
if(m_pRemoveBtn && m_pRemoveBtn->parentWidget()) {
m_pRemoveBtn->parentWidget()->setVisible(true);
}
if(m_pSplitStepsBtn && m_pSplitStepsBtn->parentWidget()) {
m_pSplitStepsBtn->parentWidget()->setVisible(true);
}
if(m_pComputeBtn && m_pComputeBtn->parentWidget()) {
m_pComputeBtn->parentWidget()->setVisible(true);
}
if(m_pPerforationClosingBtn && m_pPerforationClosingBtn->parentWidget()) {
m_pPerforationClosingBtn->parentWidget()->setVisible(true);
}
// 隐藏图表模式下的按钮
if(m_pFilterContainer) {
m_pFilterContainer->setVisible(false);
}
if(m_pResetFilterContainer) {
m_pResetFilterContainer->setVisible(false);
}
if(m_pResetLinesContainer) {
m_pResetLinesContainer->setVisible(false);
}
// 隐藏编辑框容器
if(m_pEditContainer) {
m_pEditContainer->setVisible(false);
}
// 恢复"显示日期"复选框
if(m_pShowDatesCheckBox) {
m_pShowDatesCheckBox->setStyleSheet(""); // 清除样式
m_pShowDatesCheckBox->setEnabled(true);
}
restoreStateAfterSkinVsRate();
// 启用"添加流量相关"复选框
if(m_pAddRateDependentCheckBox) {
m_pAddRateDependentCheckBox->setEnabled(true);
}
// 刷新表格显示
updateTableFromFlowSegments();
}
void nmWxTimeDependentSkin::createChartModeBtn()
{
// 创建Filter按钮
m_pFilterContainer = new QWidget(this);
QVBoxLayout* filterLayout = new QVBoxLayout(m_pFilterContainer);
filterLayout->setSpacing(0);
filterLayout->setContentsMargins(2, 2, 2, 2);
m_pFilterBtn = new QPushButton(m_pFilterContainer);
m_pFilterBtn->setIcon(QIcon("D:/01-Projects/01-CNPC/01-nmWTAI-gitea/Bin/Res/Icon/NmFilter.png"));
m_pFilterBtn->setIconSize(QSize(36, 36));
m_pFilterBtn->setFixedSize(40, 40);
m_pFilterBtn->setEnabled(true);
QLabel* filterLabel = new QLabel(tr("Filter"), m_pFilterContainer);
filterLabel->setAlignment(Qt::AlignCenter);
filterLayout->addWidget(m_pFilterBtn, 0, Qt::AlignHCenter);
filterLayout->addWidget(filterLabel);
m_pFilterContainer->setFixedSize(100, 70);
m_pFilterContainer->setVisible(false);
m_pToolbarLayout->insertWidget(1, m_pFilterContainer);
// 创建Reset Filter按钮
m_pResetFilterContainer = new QWidget(this);
QVBoxLayout* resetFilterLayout = new QVBoxLayout(m_pResetFilterContainer);
resetFilterLayout->setSpacing(0);
resetFilterLayout->setContentsMargins(2, 2, 2, 2);
m_pResetFilterBtn = new QPushButton(m_pResetFilterContainer);
m_pResetFilterBtn->setIcon(QIcon("D:/01-Projects/01-CNPC/01-nmWTAI-gitea/Bin/Res/Icon/NmResetFilter.png"));
m_pResetFilterBtn->setIconSize(QSize(36, 36));
m_pResetFilterBtn->setFixedSize(40, 40);
m_pResetFilterBtn->setEnabled(true);
QLabel* resetFilterLabel = new QLabel(tr("Reset Filter"), m_pResetFilterContainer);
resetFilterLabel->setAlignment(Qt::AlignCenter);
resetFilterLayout->addWidget(m_pResetFilterBtn, 0, Qt::AlignHCenter);
resetFilterLayout->addWidget(resetFilterLabel);
m_pResetFilterContainer->setFixedSize(100, 70);
m_pResetFilterContainer->setVisible(false);
m_pToolbarLayout->insertWidget(2, m_pResetFilterContainer);
// 创建Reset Lines按钮
m_pResetLinesContainer = new QWidget(this);
QVBoxLayout* resetLinesLayout = new QVBoxLayout(m_pResetLinesContainer);
resetLinesLayout->setSpacing(0);
resetLinesLayout->setContentsMargins(2, 2, 2, 2);
m_pResetLinesBtn = new QPushButton(m_pResetLinesContainer);
m_pResetLinesBtn->setIcon(QIcon("D:/01-Projects/01-CNPC/01-nmWTAI-gitea/Bin/Res/Icon/NmResetLines.png"));
m_pResetLinesBtn->setIconSize(QSize(36, 36));
m_pResetLinesBtn->setFixedSize(40, 40);
QLabel* resetLinesLabel = new QLabel(tr("Reset Lines"), m_pResetLinesContainer);
resetLinesLabel->setAlignment(Qt::AlignCenter);
resetLinesLayout->addWidget(m_pResetLinesBtn, 0, Qt::AlignHCenter);
resetLinesLayout->addWidget(resetLinesLabel);
m_pResetLinesContainer->setFixedSize(100, 70);
m_pResetLinesContainer->setVisible(false);
m_pToolbarLayout->insertWidget(3, m_pResetLinesContainer);
// 创建编辑框容器
createEditBoxes();
// 连接信号槽(暂时为空实现)
connect(m_pFilterBtn, SIGNAL(clicked()), this, SLOT(onFilterClicked()));
connect(m_pResetFilterBtn, SIGNAL(clicked()), this, SLOT(onResetFilterClicked()));
connect(m_pResetLinesBtn, SIGNAL(clicked()), this, SLOT(onResetLinesClicked()));
}
QVector<QPointF> nmWxTimeDependentSkin::processFlowData(const QVector<QPointF>& rawFlowData)
{
QVector<QPointF> processedData;
if(rawFlowData.isEmpty()) {
return processedData;
}
double totalTime = 0.0;
// 预期的阶梯图应该是:
// 每个时间段内保持恒定流量,在时间段边界处跳跃到新流量值
for(int i = 0; i < rawFlowData.size(); ++i) {
const QPointF& currentSegment = rawFlowData[i];
if(i == 0) {
// 第一个数据点 - 设置初始流量
processedData.append(QPointF(totalTime, currentSegment.y()));
}
// 添加时间间隔到总时间
totalTime += currentSegment.x();
// 添加当前时间段结束时的点(保持当前流量)
processedData.append(QPointF(totalTime, currentSegment.y()));
// 如果有下一个时间段且流量不同,添加垂直跳跃
if(i < rawFlowData.size() - 1) {
double nextFlowRate = rawFlowData[i + 1].y();
if(qAbs(nextFlowRate - currentSegment.y()) > 1e-6) {
processedData.append(QPointF(totalTime, nextFlowRate));
}
}
}
return processedData;
}
void nmWxTimeDependentSkin::createEditBoxes()
{
// 创建一个容器来垂直排布编辑框
QWidget* editContainer = new QWidget(this);
QVBoxLayout* editLayout = new QVBoxLayout(editContainer);
editLayout->setSpacing(3);
editLayout->setContentsMargins(8, 8, 8, 8);
// 创建第一行Skin标签和编辑框
QHBoxLayout* skinLayout = new QHBoxLayout();
skinLayout->setSpacing(5);
QLabel* skinLabel = new QLabel(tr("Skin0:"), editContainer);
skinLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
skinLabel->setFixedWidth(60);
skinLabel->setStyleSheet("QLabel { color: #333; font-size: 12px; }");
m_pSkinEdit = new QLineEdit(editContainer);
m_pSkinEdit->setText("0.00000");
m_pSkinEdit->setFixedSize(100, 22);
m_pSkinEdit->setReadOnly(true); // 设置为只读
m_pSkinEdit->setStyleSheet(
"QLineEdit {"
"border: 1px solid #aaa;"
"border-radius: 2px;"
"padding: 2px 4px;"
"background-color: #f5f5f5;" // 只读背景色
"font-size: 12px;"
"color: #333;"
"}"
);
skinLayout->addWidget(skinLabel);
skinLayout->addWidget(m_pSkinEdit);
skinLayout->addStretch();
// 创建第二行dSkin/dq标签和编辑框
QHBoxLayout* dSkinLayout = new QHBoxLayout();
dSkinLayout->setSpacing(5);
QLabel* dSkinLabel = new QLabel(tr("dSkin/dq:"), editContainer);
dSkinLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
dSkinLabel->setFixedWidth(60);
dSkinLabel->setStyleSheet("QLabel { color: #333; font-size: 12px; }");
m_pDSkinDqEdit = new QLineEdit(editContainer);
m_pDSkinDqEdit->setText("0.00000");
m_pDSkinDqEdit->setFixedSize(100, 22);
m_pDSkinDqEdit->setReadOnly(true); // 设置为只读
m_pDSkinDqEdit->setStyleSheet(
"QLineEdit {"
"border: 1px solid #aaa;"
"border-radius: 2px;"
"padding: 2px 4px;"
"background-color: #f5f5f5;" // 只读背景色
"font-size: 12px;"
"color: #333;"
"}"
);
dSkinLayout->addWidget(dSkinLabel);
dSkinLayout->addWidget(m_pDSkinDqEdit);
dSkinLayout->addStretch();
// 将两行添加到垂直布局
editLayout->addLayout(skinLayout);
editLayout->addLayout(dSkinLayout);
// 设置容器大小
editContainer->setFixedSize(180, 60);
editContainer->setStyleSheet(
"QWidget {"
"background-color: transparent;"
"border: none;"
"}"
);
// 将容器添加到工具栏布局,在复选框之前
int checkboxIndex = m_pToolbarLayout->count() - 1;
m_pToolbarLayout->insertWidget(checkboxIndex, editContainer);
// 保存引用以便后续控制显示/隐藏
m_pEditContainer = editContainer;
m_pSkinLabel = skinLabel;
m_pDSkinLabel = dSkinLabel;
// 初始时隐藏
editContainer->setVisible(false);
}
QIcon nmWxTimeDependentSkin::createTriangleIcon()
{
QPixmap trianglePixmap(16, 16);
trianglePixmap.fill(Qt::transparent);
QPainter painter(&trianglePixmap);
painter.setRenderHint(QPainter::Antialiasing, true);
// 绘制小三角形(指向右的箭头)
QPolygon triangle;
triangle << QPoint(4, 6) << QPoint(4, 10) << QPoint(10, 8);
painter.setBrush(QBrush(QColor(0, 0, 0))); // 黑色三角形
painter.setPen(QPen(QColor(0, 0, 0), 1));
painter.drawPolygon(triangle);
return QIcon(trianglePixmap);
}
void nmWxTimeDependentSkin::onTableDataChanged(int row, int column)
{
if(row == 0) return;
// 如果显示日期模式,禁止编辑时间列
if(column == 1 && m_perforationData.isShowDatesEnabled()) {
// 恢复原值
QVector<FlowSegmentData> segments = m_perforationData.getFlowSegments();
int segRow = row - 1;
if(segRow >= 0 && segRow < segments.size()) {
double startHr = segments[segRow].segmentStart.getValue().toDouble();
QString timeDisplay = formatTimeDisplay(startHr, segRow);
QTableWidgetItem* item = m_pDataTable->item(row, column);
if(item) {
item->setText(timeDisplay);
}
}
return;
}
QVector<FlowSegmentData> segments = m_perforationData.getFlowSegments();
int segRow = row - 1;
if(segRow < 0 || segRow >= segments.size()) return;
QTableWidgetItem* item = m_pDataTable->item(row, column);
if(!item) return;
if(column == 1) {
if(segRow == 0) {
// 第一个段不允许改:按“秒量化”后的原值回显
double origHr = segments[segRow].segmentStart.getValue().toDouble();
double origSec = toSeconds(origHr, "hr");
double origDisp = fromSeconds(origSec, m_timeDisplayUnit);
item->setText(QString::number(origDisp, 'f', 5));
return;
}
bool ok = false;
double newStartDisp = item->text().toDouble(&ok);
if(!ok || newStartDisp < 0) {
double origHr = segments[segRow].segmentStart.getValue().toDouble();
double origSec = toSeconds(origHr, "hr");
double origDisp = fromSeconds(origSec, m_timeDisplayUnit);
item->setText(QString::number(origDisp, 'f', 5));
return;
}
// —— 把用户输入的“显示单位”量值 -> 秒,并四舍五入到整数秒 ——
double newStartSec = toSeconds(newStartDisp, m_timeDisplayUnit);
// 邻接边界(以秒为基准)并做最小/最大约束
double minHr = (segRow > 1) ? segments[segRow - 1].segmentStart.getValue().toDouble() + 0.001 : 0.001;
double maxHr = (segRow < segments.size() - 1)
? segments[segRow + 1].segmentStart.getValue().toDouble() - 0.001
: m_currentWell->getTotalTimeRange();
double minSec = toSeconds(minHr, "hr");
double maxSec = toSeconds(maxHr, "hr");
if(newStartSec <= minSec || newStartSec >= maxSec) {
double origHr = segments[segRow].segmentStart.getValue().toDouble();
double origSec = toSeconds(origHr, "hr");
double origDisp = fromSeconds(origSec, m_timeDisplayUnit);
item->setText(QString::number(origDisp, 'f', 5));
return;
}
// “吸附到流量阶梯边界”的逻辑仍使用小时接口,但最终回到“秒并量化”
double finalSec = newStartSec;
if(m_pSnapToRateChangesCheckBox->isChecked()) {
double guessHr = fromSeconds(newStartSec, "hr");
double snappedHr = m_currentWell->findSmartFlowStepBoundary(guessHr);
// 如果吸附点越界,选择最合适的边界
double snappedSec = toSeconds(snappedHr, "hr");
if(snappedSec <= minSec || snappedSec >= maxSec) {
QVector<double> boundaries = m_currentWell->getAllFlowStepBoundaries();
double bestSec = newStartSec, minDist = 1e20;
for(int i = 0; i < boundaries.size(); ++i) {
double bSec = toSeconds(boundaries[i], "hr");
if(bSec > minSec && bSec < maxSec) {
double d = qAbs(bSec - newStartSec);
if(d < minDist) {
minDist = d;
bestSec = bSec;
}
}
}
snappedSec = bestSec;
}
finalSec = snappedSec;
}
// 夹逼(保险)
finalSec = qMax(minSec, qMin(maxSec, finalSec));
if(moveFlowSegmentBoundarySec(segRow, finalSec)) {
double finalDisp = fromSeconds(finalSec, m_timeDisplayUnit);
item->setText(QString::number(finalDisp, 'f', 5));
updateCharts();
updateTableFromFlowSegments();
} else {
double origHr = segments[segRow].segmentStart.getValue().toDouble();
double origSec = toSeconds(origHr, "hr");
double origDisp = fromSeconds(origSec, m_timeDisplayUnit);
item->setText(QString::number(origDisp, 'f', 5));
}
if(m_pChartWidget) m_pChartWidget->regenerateCrossMarkPoints();
return;
}
if(column == 2) {
bool ok;
double newSkinValue = item->text().toDouble(&ok);
if(!ok) {
double originalValue = segments[segRow].skinValue.getValue().toDouble();
item->setText(QString::number(originalValue, 'f', 5));
return;
}
segments[segRow].skinValue.setValue(QString::number(newSkinValue, 'f', 5));
m_perforationData.setFlowSegments(segments);
item->setText(QString::number(newSkinValue, 'f', 5));
if(m_pChartWidget) m_pChartWidget->regenerateCrossMarkPoints();
return;
}
const bool enableRateDep = m_perforationData.isRateDependentEnabled();
const int dsdqCol = enableRateDep ? 3 : -1;
if(enableRateDep && column == dsdqCol) {
bool ok = false;
double dispVal = item->text().toDouble(&ok);
if(!ok) {
// 回退显示
double base = segments[segRow].dSdQ.getValue().toDouble();
double disp = convertDSdQ(base, "1/B/D", m_dSdQDisplayUnit);
item->setText(QString::number(disp, 'f', 5));
return;
}
// 转回“内部基准单位 1/B/D”存储
double base = convertDSdQ(dispVal, m_dSdQDisplayUnit, "1/B/D");
segments[segRow].dSdQ.setValue(QString::number(base, 'f', 5));
// 强制其单位为基准(可选:不设置也行,因为我们按基准解读)
segments[segRow].dSdQ.setUnit("1/B/D");
// 写回数据类
m_perforationData.setFlowSegments(segments);
// 再把显示值(按当前显示单位)回填一次,确保格式
double disp = convertDSdQ(base, "1/B/D", m_dSdQDisplayUnit);
item->setText(QString::number(disp, 'f', 5));
return;
}
}
void nmWxTimeDependentSkin::onTableRowClicked(int row, int column)
{
if(row == 0 || m_isAddMode || m_isRemoveMode) return;
int segRow = row - 1;
const QVector<FlowSegmentData>& segments = m_perforationData.getFlowSegments();
if(segRow < 0 || segRow >= segments.size() || segments[segRow].isSelected) return;
selectFlowSegment(segRow);
updateCharts();
updateTableFromFlowSegments();
}
void nmWxTimeDependentSkin::onRightInsertClicked()
{
// 获取当前选中的流动段索引(从本地副本)
int selectedIndex = m_perforationData.getSelectedSegmentIndex();
const QVector<FlowSegmentData>& segments = m_perforationData.getFlowSegments();
// 检查是否有有效的选中段
if(selectedIndex < 0 || selectedIndex >= segments.size()) {
return; // 没有选中有效的段
}
// 检查是否为第一个段(第一个段之前无法插入)
if(selectedIndex == 0) {
return;
}
bool success = false;
// 检查"Snap to rate changes"复选框状态
bool snapToRateChanges = m_pSnapToRateChangesCheckBox->isChecked();
if(snapToRateChanges) {
// 勾选状态:在前一个段中的流量阶梯边界处插入
success = insertFlowSegmentBeforeSegment(selectedIndex);
} else {
// 未勾选状态:在前一个段的中间位置插入
success = insertFlowSegmentAtMiddle(selectedIndex);
}
if(success) {
// 更新图表和表格显示
updateCharts();
updateTableFromFlowSegments();
// 如果交互式图表存在,更新×标记
if(m_pChartWidget != nullptr) {
m_pChartWidget->regenerateCrossMarkPoints();
}
}
}
void nmWxTimeDependentSkin::onRightAddClicked()
{
// 检查"Snap to rate changes"复选框状态
bool snapToRateChanges = m_pSnapToRateChangesCheckBox->isChecked();
// 执行添加流动段的操作
addLastFlowSegmentIfMissing(snapToRateChanges);
// 更新图表和表格显示
updateCharts();
updateTableFromFlowSegments();
}
void nmWxTimeDependentSkin::onRightDeleteClicked()
{
// 获取当前选中的流动段索引(从本地副本)
int selectedIndex = m_perforationData.getSelectedSegmentIndex();
const QVector<FlowSegmentData>& segments = m_perforationData.getFlowSegments();
// 检查是否有有效的选中段
if(selectedIndex < 0 || selectedIndex >= segments.size()) {
return; // 没有选中有效的段
}
// 检查是否为第一个段
if(selectedIndex == 0) {
return;
}
// 检查是否只剩一个段
if(segments.size() <= 1) {
return;
}
// 执行向左合并操作
bool success = mergeSegmentWithPrevious(selectedIndex);
if(success) {
// 更新图表和表格显示
updateCharts();
updateTableFromFlowSegments();
}
}
void nmWxTimeDependentSkin::onPerforationChanged(int index)
{
if(!m_currentWell || index < 0 || index >= m_currentWell->getPerforationCount()) {
return;
}
// 如果选择的是当前射孔段,无需切换
if(index == m_currentPerforationIndex) {
return;
}
// 保存当前射孔段的修改到井数据中
m_currentWell->updatePerforation(m_currentPerforationIndex, m_perforationData);
// 切换到新的射孔段
m_currentPerforationIndex = index;
m_perforationData = m_currentWell->getPerforationCopy(index);
// 如果副本为空,初始化默认数据
if(m_perforationData.getFlowSegments().isEmpty()) {
initializeDefaultFlowSegment();
}
// 确保副本有默认的表皮系数数据
if(m_perforationData.getSkin0().getValue().toString().isEmpty()) {
m_perforationData.getSkin0().setValue("0.0");
}
if(m_perforationData.getdSkindq().getValue().toString().isEmpty()) {
m_perforationData.getdSkindq().setValue("0.0");
}
// 确保线条位置有默认值
double x1, y1, x2, y2;
m_perforationData.getLinePositions(x1, y1, x2, y2);
if(x1 == -999.0 || y1 == -999.0 || x2 == -999.0 || y2 == -999.0) {
m_perforationData.setLinePositions(100.0, 0.0, 200.0, 0.0);
}
// 更新UI显示
updateCharts();
updateTableFromFlowSegments();
}
void nmWxTimeDependentSkin::onBoundaryMoved(int segmentIndex, double newTime)
{
// 获取信号发送者
QObject* sender = QObject::sender();
// 根据发送者决定需要更新哪个图表
if(sender == m_pPressureChart && m_pFlowRateChart) {
// 压力图发送的信号,更新流量图的外部拖动状态
m_pFlowRateChart->setExternalDragState(true, segmentIndex, newTime);
} else if(sender == m_pFlowRateChart && m_pPressureChart) {
// 流量图发送的信号,更新压力图的外部拖动状态
m_pPressureChart->setExternalDragState(true, segmentIndex, newTime);
}
}
void nmWxTimeDependentSkin::onBoundaryMovedFinal(int segmentIndex, double newTime)
{
// 先转成秒并四舍五入
double newSec = toSeconds(newTime, "hr");
bool success = moveFlowSegmentBoundarySec(segmentIndex, newSec);
if(success) {
if(m_pPressureChart) m_pPressureChart->clearExternalDragState();
if(m_pFlowRateChart) m_pFlowRateChart->clearExternalDragState();
updateCharts();
updateTableFromFlowSegments();
} else {
if(m_pPressureChart) m_pPressureChart->clearExternalDragState();
if(m_pFlowRateChart) m_pFlowRateChart->clearExternalDragState();
updateCharts();
}
}
bool nmWxTimeDependentSkin::insertFlowSegmentAtMiddle(int segmentIndex)
{
QVector<FlowSegmentData> segments = m_perforationData.getFlowSegments();
if(segmentIndex <= 0 || segmentIndex >= segments.size()) {
return false; // 第一个段或无效索引,无法在前面插入
}
// 获取前一个段的信息
int prevSegmentIndex = segmentIndex - 1;
const FlowSegmentData& prevSegment = segments[prevSegmentIndex];
double prevSegmentStartTime = prevSegment.segmentStart.getValue().toDouble();
double prevSegmentEndTime = prevSegment.segmentEnd.getValue().toDouble();
// 检查前一个段是否足够大可以分割最小间隔0.001小时)
if(prevSegmentEndTime - prevSegmentStartTime <= 0.002) {
return false; // 前一个段太小,无法分割
}
// 计算前一个段的中间时间点
double middleTime = (prevSegmentStartTime + prevSegmentEndTime) / 2.0;
// 保存前一个段的原始属性
double originalSkinValue = prevSegment.skinValue.getValue().toDouble();
double originalDSdQValue = prevSegment.dSdQ.getValue().toDouble();
bool wasSelected = prevSegment.isSelected;
// 创建第一个流动段(前一个段的前半部分,保留原值)
nmDataAttribute newStartTime1("StartTime", prevSegmentStartTime, "hr");
nmDataAttribute newSkinValue1("SkinValue", originalSkinValue, ""); // 保留原来的skin值
nmDataAttribute newSegmentStart1("SegmentStartTime", prevSegmentStartTime, "hr");
nmDataAttribute newSegmentEnd1("SegmentEndTime", middleTime, "hr");
// 创建第二个流动段前一个段的后半部分这是新插入的段值设为0
nmDataAttribute newStartTime2("StartTime", middleTime, "hr");
nmDataAttribute newSkinValue2("SkinValue", 0.0, ""); // 新插入的段默认skin值为0
nmDataAttribute newSegmentStart2("SegmentStartTime", middleTime, "hr");
nmDataAttribute newSegmentEnd2("SegmentEndTime", prevSegmentEndTime, "hr");
FlowSegmentData segment1, segment2;
// 根据是否启用流量相关功能来决定如何创建段
if(m_perforationData.isRateDependentEnabled()) {
// 启用了流量相关功能需要处理dSdQ值
nmDataAttribute newDSdQ1("dSdQ", originalDSdQValue, "1/B/D"); // 第一个段保留原来的dSdQ值
nmDataAttribute newDSdQ2("dSdQ", 0.0, "1/B/D"); // 第二个段的dSdQ值为0
segment1 = FlowSegmentData(newStartTime1, newSkinValue1, newSegmentStart1, newSegmentEnd1, newDSdQ1, wasSelected);
segment2 = FlowSegmentData(newStartTime2, newSkinValue2, newSegmentStart2, newSegmentEnd2, newDSdQ2, false); // 新段不选中
} else {
// 未启用流量相关功能使用不包含dSdQ的构造函数
segment1 = FlowSegmentData(newStartTime1, newSkinValue1, newSegmentStart1, newSegmentEnd1, wasSelected);
segment2 = FlowSegmentData(newStartTime2, newSkinValue2, newSegmentStart2, newSegmentEnd2, false); // 新段不选中
}
// 替换前一个段为两个新段
segments.remove(prevSegmentIndex);
segments.insert(prevSegmentIndex, segment1);
segments.insert(prevSegmentIndex + 1, segment2);
// 更新流动段数据
m_perforationData.setFlowSegments(segments);
// 选中新插入的段segment2
selectFlowSegment(prevSegmentIndex + 1);
return true;
}
void nmWxTimeDependentSkin::onSegmentsMerged(int segmentIndex)
{
bool success = mergeSegmentWithPrevious(segmentIndex);
if(success) {
if(m_pPressureChart) m_pPressureChart->clearExternalDragState();
if(m_pFlowRateChart) m_pFlowRateChart->clearExternalDragState();
updateCharts();
updateTableFromFlowSegments();
}
}
void nmWxTimeDependentSkin::onSnapToRateChangesToggled(bool checked)
{
// 保存状态到数据对象
m_perforationData.setSnapToRateChangesEnabled(checked);
// 原来的同步设置
if(m_pPressureChart) m_pPressureChart->setSnapToRateChanges(checked);
if(m_pFlowRateChart) m_pFlowRateChart->setSnapToRateChanges(checked);
if(m_pChartWidget) m_pChartWidget->setSnapToRateChanges(checked);
// 当勾选时,立即把现有所有分界线吸附到阶梯,并自动合并重复
if(checked) {
snapAllBoundariesToFlowStepsAndMerge();
}
}
void nmWxTimeDependentSkin::onFilterClicked()
{
// 只在图表模式下才能使用filter功能
if(m_pChartWidget == nullptr) {
return;
}
// 如果已经在套索模式,忽略点击(防止重复操作)
if(m_pChartWidget->getLassoMode()) {
return;
}
// 进入套索模式(一次性操作)
m_pChartWidget->setLassoMode(true);
}
void nmWxTimeDependentSkin::onResetFilterClicked()
{
// 只在图表模式下才能使用reset filter功能
if(m_pChartWidget == nullptr) {
return;
}
// 调用图表widget的重置所有×标记点方法
m_pChartWidget->resetAllCrossMarks();
// 保存重置后的位置到数据对象
saveChartPositionsToData();
// 更新编辑框显示
updateEditBoxesFromData();
}
void nmWxTimeDependentSkin::onResetLinesClicked()
{
if(m_pChartWidget != nullptr) {
// 调用图表widget的重置方法让它自己决定重置位置
m_pChartWidget->resetLinesToInitialPositions();
// 保存重置后的位置
saveChartPositionsToData();
// 更新编辑框显示
updateEditBoxesFromData();
}
}
void nmWxTimeDependentSkin::onAccept()
{
if(!m_currentWell) {
accept();
return;
}
// 副本数据同步回原始射孔段
m_currentWell->updatePerforation(m_currentPerforationIndex, m_perforationData);
accept();
}
void nmWxTimeDependentSkin::onReject()
{
reject();
}
void nmWxTimeDependentSkin::initializeDefaultFlowSegment()
{
if(m_currentWell == nullptr) {
return;
}
QVector<QPointF> flowPoints = m_currentWell->getFlowPoints();
if(flowPoints.isEmpty()) {
return;
}
// 计算总时间
double totalTime = m_currentWell->getTotalTimeRange();
// 创建一个覆盖整个时间范围的默认流动段,初始状态为选中
QVector<FlowSegmentData> segments;
nmDataAttribute startTime("StartTime", 0.0, "hr");
nmDataAttribute skinValue("SkinValue", 0.0, "");
nmDataAttribute segmentStart("SegmentStartTime", 0.0, "hr");
nmDataAttribute segmentEnd("SegmentEndTime", totalTime, "hr");
FlowSegmentData defaultSegment;
// 根据是否启用流量相关功能来决定如何创建默认段
if(m_perforationData.isRateDependentEnabled()) {
nmDataAttribute dSdQ("dSdQ", 0.0, "1/B/D");
defaultSegment = FlowSegmentData(startTime, skinValue, segmentStart, segmentEnd, dSdQ, true);
} else {
defaultSegment = FlowSegmentData(startTime, skinValue, segmentStart, segmentEnd, true);
}
segments.append(defaultSegment);
m_perforationData.setFlowSegments(segments);
m_perforationData.setSelectedSegmentIndex(0);
}
void nmWxTimeDependentSkin::splitSelectedFlowSegment()
{
int selectedIndex = m_perforationData.getSelectedSegmentIndex();
QVector<FlowSegmentData> segments = m_perforationData.getFlowSegments();
if(selectedIndex < 0 || selectedIndex >= segments.size()) {
return;
}
// 检查选中的流动段是否可以分割
if(!m_currentWell->canFlowSegmentBeSplit(selectedIndex, m_currentPerforationIndex)) {
return;
}
const FlowSegmentData& selectedSegment = segments[selectedIndex];
double segmentStartTime = selectedSegment.segmentStart.getValue().toDouble();
double segmentEndTime = selectedSegment.segmentEnd.getValue().toDouble();
// 获取该段内的流量阶梯
QVector<QPointF> flowSteps = m_currentWell->getFlowStepsInTimeRange(segmentStartTime, segmentEndTime);
if(flowSteps.size() <= 1) {
return; // 只有一个或没有阶梯段,无法分割
}
// 第一个阶梯段的结束时间就是分割点
double firstStepDuration = flowSteps[0].x();
double splitTime = segmentStartTime + firstStepDuration;
// 创建第一个流动段(第一个阶梯段)
nmDataAttribute newStartTime1("StartTime", segmentStartTime, "hr");
nmDataAttribute newSkinValue1("SkinValue", 0.0, "");
nmDataAttribute newSegmentStart1("SegmentStartTime", segmentStartTime, "hr");
nmDataAttribute newSegmentEnd1("SegmentEndTime", splitTime, "hr");
FlowSegmentData segment1(newStartTime1, newSkinValue1, newSegmentStart1, newSegmentEnd1, true); // 新分离的段自动选中
// 创建第二个流动段(剩余的阶梯段)
nmDataAttribute newStartTime2("StartTime", splitTime, "hr");
nmDataAttribute newSkinValue2("SkinValue", 0.0, "");
nmDataAttribute newSegmentStart2("SegmentStartTime", splitTime, "hr");
nmDataAttribute newSegmentEnd2("SegmentEndTime", segmentEndTime, "hr");
FlowSegmentData segment2(newStartTime2, newSkinValue2, newSegmentStart2, newSegmentEnd2, false);
// 替换原来的段
segments[selectedIndex] = segment1;
segments.insert(selectedIndex + 1, segment2);
m_perforationData.setFlowSegments(segments);
m_perforationData.setSelectedSegmentIndex(selectedIndex); // 选中新分离出来的第一段
}
void nmWxTimeDependentSkin::selectFlowSegment(int segmentIndex)
{
QVector<FlowSegmentData> segments = m_perforationData.getFlowSegments();
if(segmentIndex < 0 || segmentIndex >= segments.size()) {
return;
}
// 取消所有段的选中状态
for(int i = 0; i < segments.size(); ++i) {
segments[i].isSelected = (i == segmentIndex);
}
m_perforationData.setFlowSegments(segments);
m_perforationData.setSelectedSegmentIndex(segmentIndex);
}
void nmWxTimeDependentSkin::splitAllFlowSegments()
{
QVector<double> flowStepBoundaries = m_currentWell->getAllFlowStepBoundaries();
if(flowStepBoundaries.isEmpty()) return;
// 统一量化
for(int i = 0; i < flowStepBoundaries.size(); ++i) {
double sec = toSeconds(flowStepBoundaries[i], "hr");
flowStepBoundaries[i] = fromSeconds(sec, "hr");
}
qSort(flowStepBoundaries);
// 按"秒"去重:把<=1秒的间隔视为重复
const double dedupEpsSec = 1.0;
QVector<double> uniq;
for(int i = 0; i < flowStepBoundaries.size(); ++i) {
if(uniq.isEmpty()) {
uniq.push_back(flowStepBoundaries[i]);
continue;
}
double lastSec = toSeconds(uniq.back(), "hr");
double curSec = toSeconds(flowStepBoundaries[i], "hr");
if(qAbs(curSec - lastSec) > dedupEpsSec) uniq.push_back(flowStepBoundaries[i]);
}
if(uniq.size() < 2) return;
QVector<FlowSegmentData> newSegments;
for(int i = 0; i < uniq.size() - 1; ++i) {
double startTime = uniq[i];
double endTime = uniq[i + 1];
// 额外再做一次量化保险
double startSec = toSeconds(startTime, "hr");
double endSec = toSeconds(endTime, "hr");
startTime = fromSeconds(startSec, "hr");
endTime = fromSeconds(endSec, "hr");
// 修改所有skin值都重置为0不再保留原值
double resetSkinValue = 0.0;
nmDataAttribute newStartTime("StartTime", startTime, "hr");
nmDataAttribute newSkinValue("SkinValue", resetSkinValue, "");
nmDataAttribute newSegmentStart("SegmentStartTime", startTime, "hr");
nmDataAttribute newSegmentEnd("SegmentEndTime", endTime, "hr");
bool selected = (i == 0);
FlowSegmentData newSegment;
// 根据是否启用流量相关功能来决定如何创建段
if(m_perforationData.isRateDependentEnabled()) {
// 启用了流量相关功能需要创建带有dSdQ的段并将dSdQ重置为0
nmDataAttribute newDSdQ("dSdQ", 0.0, "1/B/D"); // dSdQ也重置为0
newSegment = FlowSegmentData(newStartTime, newSkinValue, newSegmentStart, newSegmentEnd, newDSdQ, selected);
} else {
// 未启用流量相关功能使用不包含dSdQ的构造函数
newSegment = FlowSegmentData(newStartTime, newSkinValue, newSegmentStart, newSegmentEnd, selected);
}
newSegments.append(newSegment);
}
for(int i = 0; i < newSegments.size(); ++i) newSegments[i].isSelected = (i == 0);
m_perforationData.setFlowSegments(newSegments);
m_perforationData.setSelectedSegmentIndex(0);
}
int nmWxTimeDependentSkin::splitFlowSegmentAtIndex(int segmentIndex)
{
QVector<FlowSegmentData> segments = m_perforationData.getFlowSegments();
if(segmentIndex < 0 || segmentIndex >= segments.size()) {
return 0;
}
// 检查该段是否可以分割
if(!m_currentWell->canFlowSegmentBeSplit(segmentIndex, m_currentPerforationIndex)) {
return 0;
}
const FlowSegmentData& selectedSegment = segments[segmentIndex];
double segmentStartTime = selectedSegment.segmentStart.getValue().toDouble();
// 获取该段内所有流量阶梯的分界点
QVector<double> boundaries = m_currentWell->getFlowStepBoundariesInSegment(segmentIndex, m_currentPerforationIndex);
if(boundaries.size() <= 1) {
return 0; // 没有分界点或只有一个分界点,无法分割
}
// 移除原来的段
segments.remove(segmentIndex);
// 根据分界点创建新的流动段
int newSegmentsCount = 0;
for(int i = 0; i < boundaries.size(); ++i) {
double currentStart = (i == 0) ? segmentStartTime : boundaries[i - 1];
double currentEnd = boundaries[i];
// 创建新的流动段
nmDataAttribute newStartTime("StartTime", currentStart, "hr");
nmDataAttribute newSkinValue("SkinValue", 0.0, "");
nmDataAttribute newSegmentStart("SegmentStartTime", currentStart, "hr");
nmDataAttribute newSegmentEnd("SegmentEndTime", currentEnd, "hr");
// 第一个新段保持选中状态,其他取消选中
bool isSelected = (i == 0 && selectedSegment.isSelected);
FlowSegmentData newSegment(newStartTime, newSkinValue, newSegmentStart, newSegmentEnd, isSelected);
// 插入到正确的位置
segments.insert(segmentIndex + i, newSegment);
newSegmentsCount++;
}
// 更新流动段数据
m_perforationData.setFlowSegments(segments);
return newSegmentsCount - 1; // 返回新增的段数量
}
bool nmWxTimeDependentSkin::addFlowSegmentAtTime(double timePoint)
{
int segmentIndex = m_perforationData.findFlowSegmentByTime(timePoint);
if(segmentIndex < 0) return false;
// 先找“阶梯结束边界(单位:小时)”
double stepBoundaryTime = m_currentWell->findFlowStepBoundaryAtTime(timePoint);
if(stepBoundaryTime < 0) return false;
// 量化
double stepBoundarySec = toSeconds(stepBoundaryTime, "hr");
stepBoundaryTime = fromSeconds(stepBoundarySec, "hr");
// 再取该段的结束时间做边界校验
const QVector<FlowSegmentData>& segments = m_perforationData.getFlowSegments();
double segmentEndTime = segments.at(segmentIndex).segmentEnd.getValue().toDouble();
if(stepBoundaryTime <= segments.at(segmentIndex).segmentStart.getValue().toDouble()
|| stepBoundaryTime >= segmentEndTime) {
return false;
}
return splitFlowSegmentAtTime(segmentIndex, stepBoundaryTime);
}
bool nmWxTimeDependentSkin::splitFlowSegmentAtTime(int segmentIndex, double splitTimeHr)
{
// 统一量化到整秒,再回到小时
double splitSec = toSeconds(splitTimeHr, "hr");
double splitTime = fromSeconds(splitSec, "hr");
QVector<FlowSegmentData> segments = m_perforationData.getFlowSegments();
if(segmentIndex < 0 || segmentIndex >= segments.count()) return false;
const FlowSegmentData& originalSegment = segments.at(segmentIndex);
double segmentStartTime = originalSegment.segmentStart.getValue().toDouble();
double segmentEndTime = originalSegment.segmentEnd.getValue().toDouble();
if(splitTime <= segmentStartTime || splitTime >= segmentEndTime) return false;
// 获取原段的skin值和dSdQ值用于保留给第一个新段
double originalSkinValue = originalSegment.skinValue.getValue().toDouble();
double originalDSdQValue = originalSegment.dSdQ.getValue().toDouble();
// 第一个新段(保留原段的值,选中)
nmDataAttribute newStartTime1("StartTime", segmentStartTime, "hr");
nmDataAttribute newSkinValue1("SkinValue", originalSkinValue, ""); // 保留原来的skin值
nmDataAttribute newSegmentStart1("SegmentStartTime", segmentStartTime, "hr");
nmDataAttribute newSegmentEnd1("SegmentEndTime", splitTime, "hr");
// 第二个新段新增段值设为0不选中
nmDataAttribute newStartTime2("StartTime", splitTime, "hr");
nmDataAttribute newSkinValue2("SkinValue", 0.0, ""); // 新增段的skin值为0
nmDataAttribute newSegmentStart2("SegmentStartTime", splitTime, "hr");
nmDataAttribute newSegmentEnd2("SegmentEndTime", segmentEndTime, "hr");
FlowSegmentData segment1, segment2;
// 根据是否启用流量相关功能来决定如何创建段
if(m_perforationData.isRateDependentEnabled()) {
// 启用了流量相关功能需要处理dSdQ值
nmDataAttribute newDSdQ1("dSdQ", originalDSdQValue, "1/B/D"); // 第一个段保留原来的dSdQ值
nmDataAttribute newDSdQ2("dSdQ", 0.0, "1/B/D"); // 第二个段的dSdQ值为0
segment1 = FlowSegmentData(newStartTime1, newSkinValue1, newSegmentStart1, newSegmentEnd1, newDSdQ1, true);
segment2 = FlowSegmentData(newStartTime2, newSkinValue2, newSegmentStart2, newSegmentEnd2, newDSdQ2, false);
} else {
// 未启用流量相关功能使用不包含dSdQ的构造函数
segment1 = FlowSegmentData(newStartTime1, newSkinValue1, newSegmentStart1, newSegmentEnd1, true);
segment2 = FlowSegmentData(newStartTime2, newSkinValue2, newSegmentStart2, newSegmentEnd2, false);
}
segments.remove(segmentIndex);
segments.insert(segmentIndex, segment1);
segments.insert(segmentIndex + 1, segment2);
m_perforationData.setFlowSegments(segments);
selectFlowSegment(segmentIndex);
return true;
}
bool nmWxTimeDependentSkin::mergeSegmentWithPrevious(int segmentIndex)
{
QVector<FlowSegmentData> segments = m_perforationData.getFlowSegments();
// 检查索引有效性
if(segmentIndex <= 0 || segmentIndex >= segments.size()) {
return false; // 第一个段无法与前段合并
}
// 获取要合并的段和前一个段
const FlowSegmentData& currentSegment = segments[segmentIndex];
const FlowSegmentData& prevSegment = segments[segmentIndex - 1];
// 创建合并后的新段
double newStartTime = prevSegment.segmentStart.getValue().toDouble();
double newEndTime = currentSegment.segmentEnd.getValue().toDouble();
double prevSkinValue = prevSegment.skinValue.getValue().toDouble();
// 判断选中状态:如果任一段被选中,则合并后的段被选中
bool mergedIsSelected = prevSegment.isSelected || currentSegment.isSelected;
// 创建新的流动段属性
nmDataAttribute mergedStartTime("StartTime", newStartTime, "hr");
nmDataAttribute mergedSkinValue("SkinValue", prevSkinValue, "");
nmDataAttribute mergedSegmentStart("SegmentStartTime", newStartTime, "hr");
nmDataAttribute mergedSegmentEnd("SegmentEndTime", newEndTime, "hr");
FlowSegmentData mergedSegment;
// 根据是否启用流量相关功能来决定如何处理dSdQ
if(m_perforationData.isRateDependentEnabled()) {
// 启用了流量相关功能需要处理dSdQ值
nmDataAttribute mergedDSdQ;
if(currentSegment.isSelected && !prevSegment.isSelected) {
// 当前段被选中,前一个段未选中 -> 保留左边(前一个段)的dSdQ
mergedDSdQ = prevSegment.dSdQ;
} else if(prevSegment.isSelected && !currentSegment.isSelected) {
// 前一个段被选中,当前段未选中 -> 保留选中段(前一个段)的dSdQ
mergedDSdQ = prevSegment.dSdQ;
} else if(prevSegment.isSelected && currentSegment.isSelected) {
// 两个段都被选中 -> 这可能是拖动合并优先保留前一个段的dSdQ
mergedDSdQ = prevSegment.dSdQ;
} else {
// 两个段都未选中 -> 默认保留前一个段的dSdQ
mergedDSdQ = prevSegment.dSdQ;
}
// 使用包含dSdQ的构造函数
mergedSegment = FlowSegmentData(mergedStartTime, mergedSkinValue,
mergedSegmentStart, mergedSegmentEnd, mergedDSdQ, mergedIsSelected);
} else {
// 未启用流量相关功能使用不包含dSdQ的构造函数dSdQ会使用默认值
mergedSegment = FlowSegmentData(mergedStartTime, mergedSkinValue,
mergedSegmentStart, mergedSegmentEnd, mergedIsSelected);
}
// 移除原来的两个段(从后往前移除,避免索引变化)
segments.remove(segmentIndex); // 先移除当前段
segments.remove(segmentIndex - 1); // 再移除前一个段
// 在原前一个段的位置插入合并后的段
segments.insert(segmentIndex - 1, mergedSegment);
// 更新流动段数据
m_perforationData.setFlowSegments(segments);
// 设置选中索引为合并后的段
if(mergedIsSelected) {
m_perforationData.setSelectedSegmentIndex(segmentIndex - 1);
} else {
// 如果合并后的段没有被选中,选择第一个段(如果存在)
if(segments.size() > 0) {
selectFlowSegment(0);
}
}
return true;
}
bool nmWxTimeDependentSkin::insertFlowSegmentBeforeSegment(int segmentIndex)
{
QVector<FlowSegmentData> segments = m_perforationData.getFlowSegments();
if(segmentIndex <= 0 || segmentIndex >= segments.size()) return false;
double insertTime = m_currentWell->findNearestFlowStepBoundaryBeforeSegment(segmentIndex, m_currentPerforationIndex);
if(insertTime < 0) return false;
// 量化
double insertSec = toSeconds(insertTime, "hr");
insertTime = fromSeconds(insertSec, "hr");
int prevSegmentIndex = segmentIndex - 1;
bool success = splitFlowSegmentAtTime(prevSegmentIndex, insertTime);
if(success) selectFlowSegment(prevSegmentIndex + 1);
return success;
}
void nmWxTimeDependentSkin::addLastFlowSegmentIfMissing(bool snapToRateChanges)
{
double totalTime = m_currentWell->getTotalTimeRange();
if(totalTime <= 0) {
return;
}
// 如果没有任何段,初始化默认段并返回
QVector<FlowSegmentData> segments = m_perforationData.getFlowSegments();
if(segments.isEmpty()) {
initializeDefaultFlowSegment();
return;
}
// 找到最后一个理想的100间隔分割点
double lastIdealSplitTime = -1.0;
for(double time = 100.0; time < totalTime; time += 100.0) {
double actualTime = time;
if(snapToRateChanges) {
actualTime = m_currentWell->findNearestFlowStepBoundary(time);
}
if(actualTime > 0 && actualTime < totalTime) {
lastIdealSplitTime = actualTime;
}
}
// 如果没有找到理想分割点说明总时间小于100不需要添加
if(lastIdealSplitTime < 0) {
return;
}
// 检查这个最后的理想分割点是否已经存在
bool exists = false;
for(int i = 0; i < segments.size(); ++i) {
double startTime = segments[i].segmentStart.getValue().toDouble();
if(qAbs(startTime - lastIdealSplitTime) < 1e-6) {
exists = true;
break;
}
}
// 如果不存在,则添加这个分割点
if(!exists) {
int segmentIndex = m_perforationData.findFlowSegmentByTime(lastIdealSplitTime);
if(segmentIndex >= 0) {
splitFlowSegmentAtTime(segmentIndex, lastIdealSplitTime);
}
}
}
void nmWxTimeDependentSkin::snapAllBoundariesToFlowStepsAndMerge()
{
QVector<FlowSegmentData> segments = m_perforationData.getFlowSegments();
if(segments.size() <= 1) return;
const double totalTimeHr = m_currentWell->getTotalTimeRange();
const double totalTimeSec = toSeconds(totalTimeHr, "hr");
const double epsSec = 1.0; // 以 1 秒为“重合”阈值
// 1) 吸附所有内部分界线(结果量化到秒后再回写)
for(int i = 1; i < segments.size(); ++i) {
double curBoundaryHr = segments[i].segmentStart.getValue().toDouble();
double snappedHr = m_currentWell->findNearestFlowStepBoundary(curBoundaryHr);
// 量化
double snappedSec = toSeconds(snappedHr, "hr");
// 边界保护
double nextStartHr = (i < segments.size() - 1)
? segments[i + 1].segmentStart.getValue().toDouble()
: totalTimeHr;
double nextStartSec = toSeconds(nextStartHr, "hr");
if(snappedSec > nextStartSec) snappedSec = nextStartSec;
if(snappedSec < 0.0) snappedSec = 0.0;
if(snappedSec > totalTimeSec) snappedSec = totalTimeSec;
double snappedHrQuant = fromSeconds(snappedSec, "hr");
segments[i].segmentStart.setValue(QString::number(snappedHrQuant, 'f', 5));
segments[i].startTime .setValue(QString::number(snappedHrQuant, 'f', 5));
segments[i - 1].segmentEnd.setValue(QString::number(snappedHrQuant, 'f', 5));
}
// 2) 清洗与合并:按“秒”判断 0 长度段
int selectedIdx = m_perforationData.getSelectedSegmentIndex();
for(int i = segments.size() - 1; i >= 0; --i) {
double stSec = toSeconds(segments[i].segmentStart.getValue().toDouble(), "hr");
double edSec = toSeconds(segments[i].segmentEnd.getValue().toDouble(), "hr");
if(edSec - stSec <= epsSec) {
if(i > 0) {
bool leftOrSelfSelected = segments[i - 1].isSelected || segments[i].isSelected;
segments.remove(i);
if(leftOrSelfSelected) selectedIdx = qMax(0, i - 1);
else if(selectedIdx > i) selectedIdx -= 1;
else if(selectedIdx == i) selectedIdx = qMax(0, i - 1);
} else if(segments.size() > 1) {
bool rightOrSelfSelected = segments[0].isSelected || segments[1].isSelected;
segments.remove(0);
// 把右段起点置 0量化一致
segments[0].segmentStart.setValue(QString::number(0.0, 'f', 5));
segments[0].startTime .setValue(QString::number(0.0, 'f', 5));
if(rightOrSelfSelected) selectedIdx = 0;
else selectedIdx = qMin(selectedIdx, segments.size() - 1);
}
}
}
if(segments.isEmpty()) {
m_perforationData.setFlowSegments(segments);
updateCharts();
updateTableFromFlowSegments();
return;
}
if(selectedIdx < 0 || selectedIdx >= segments.size()) selectedIdx = 0;
for(int i = 0; i < segments.size(); ++i) segments[i].isSelected = (i == selectedIdx);
m_perforationData.setFlowSegments(segments);
m_perforationData.setSelectedSegmentIndex(selectedIdx);
if(m_pPressureChart) m_pPressureChart->clearExternalDragState();
if(m_pFlowRateChart) m_pFlowRateChart->clearExternalDragState();
updateCharts();
updateTableFromFlowSegments();
if(m_pChartWidget) m_pChartWidget->regenerateCrossMarkPoints();
}
double nmWxTimeDependentSkin::convertTime(double value, const QString& fromUnit, const QString& toUnit) const
{
nmDataAttribute a;
a.setAttributeUnitType(UNIT_TYPE_TIME);
return a.convertValueByUnitType(value, fromUnit, toUnit);
}
void nmWxTimeDependentSkin::onTimeUnitChanged(const QString& unit)
{
if(unit == m_timeDisplayUnit) return;
m_timeDisplayUnit = unit;
const QVector<FlowSegmentData>& segments = m_perforationData.getFlowSegments();
for(int i = 0; i < segments.size(); ++i) {
int row = i + 1;
double startHr = segments[i].segmentStart.getValue().toDouble();
double startSec = toSeconds(startHr, "hr"); // 量化
double startDisp = fromSeconds(startSec, m_timeDisplayUnit); // 按新单位显示
QTableWidgetItem* item = m_pDataTable->item(row, 1);
if(item) item->setText(QString::number(startDisp, 'f', 5));
}
}
double nmWxTimeDependentSkin::toSeconds(double value, const QString& unit)
{
nmDataAttribute a;
a.setAttributeUnitType(UNIT_TYPE_TIME);
double sec = a.convertValueByUnitType(value, unit, "sec");
// 四舍五入到整数秒
return static_cast<double>(qRound(sec));
}
double nmWxTimeDependentSkin::fromSeconds(double sec, const QString& unit)
{
nmDataAttribute a;
a.setAttributeUnitType(UNIT_TYPE_TIME);
return a.convertValueByUnitType(sec, "sec", unit);
}
bool nmWxTimeDependentSkin::moveFlowSegmentBoundarySec(int segmentIndex, double newBoundarySec)
{
QVector<FlowSegmentData> segments = m_perforationData.getFlowSegments();
// 索引合法性
if(segmentIndex <= 0 || segmentIndex >= segments.size()) return false;
// 取相邻边界(都转成“秒并四舍五入”后比较)
double prevStartHr = (segmentIndex > 1) ? segments[segmentIndex - 1].segmentStart.getValue().toDouble() : 0.0;
double nextStartHr = (segmentIndex < segments.size() - 1)
? segments[segmentIndex + 1].segmentStart.getValue().toDouble()
: m_currentWell->getTotalTimeRange();
double prevStartSec = toSeconds(prevStartHr, "hr");
double nextStartSec = toSeconds(nextStartHr, "hr");
// 夹逼
if(newBoundarySec <= prevStartSec || newBoundarySec >= nextStartSec) return false;
// 写回:内部仍然存“小时”,但用“秒”为准再转换,保证显示不会抖动
double boundaryHr = fromSeconds(newBoundarySec, "hr");
segments[segmentIndex].segmentStart.setValue(QString::number(boundaryHr, 'f', 5));
segments[segmentIndex].startTime .setValue(QString::number(boundaryHr, 'f', 5));
segments[segmentIndex - 1].segmentEnd.setValue(QString::number(boundaryHr, 'f', 5));
m_perforationData.setFlowSegments(segments);
return true;
}
double nmWxTimeDependentSkin::convertDSdQ(double value, const QString& fromUnit, const QString& toUnit) const
{
nmDataAttribute a;
a.setAttributeUnitType(UNIT_TYPE_FLOW_RATE_RECIPROCAL);
return a.convertValueByUnitType(value, fromUnit, toUnit);
}
void nmWxTimeDependentSkin::onAddRateDependentToggled(bool checked)
{
// 记录到数据类(它会在禁用时把所有 dSdQ 复位为 0
m_perforationData.setRateDependentEnabled(checked);
// 重建表头/单位行,并刷新数据
updateTableFromFlowSegments();
// 更新保存的状态
m_rateDependentState = checked;
}
void nmWxTimeDependentSkin::onDSdQUnitChanged(const QString& unit)
{
if(unit == m_dSdQDisplayUnit) return;
m_dSdQDisplayUnit = unit;
const QVector<FlowSegmentData>& segs = m_perforationData.getFlowSegments();
if(!m_perforationData.isRateDependentEnabled()) return;
for(int i = 0; i < segs.size(); ++i) {
int row = i + 1;
const FlowSegmentData& s = segs[i];
// 内部一律以 "1/B/D" 存储,这里只负责回显
double base = s.dSdQ.getValue().toDouble(); // unit 视为 "1/B/D"
double disp = convertDSdQ(base, "1/B/D", m_dSdQDisplayUnit);
QTableWidgetItem* item = m_pDataTable->item(row, 3);
if(!item) {
item = new QTableWidgetItem();
m_pDataTable->setItem(row, 3, item);
}
item->setText(QString::number(disp, 'f', 5));
item->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable);
}
}
void nmWxTimeDependentSkin::refreshTableStructure()
{
// 1) 屏蔽表格信号
bool oldTblBlocked = m_pDataTable->blockSignals(true);
// 2) 清理旧的ComboBox
if(m_pTimeUnitCombo) {
m_pTimeUnitCombo->disconnect(this);
delete m_pTimeUnitCombo;
m_pTimeUnitCombo = nullptr;
}
if(m_pDSdQUnitCombo) {
m_pDSdQUnitCombo->disconnect(this);
delete m_pDSdQUnitCombo;
m_pDSdQUnitCombo = nullptr;
}
// 3) 重建表头/列
m_pDataTable->clear();
const bool enableRateDep = m_perforationData.isRateDependentEnabled();
const int colCount = enableRateDep ? 4 : 3;
m_pDataTable->setColumnCount(colCount);
QStringList headers;
headers << tr("") << tr("Start time") << tr("Skin");
if(enableRateDep) headers << tr("dS/dQ");
m_pDataTable->setHorizontalHeaderLabels(headers);
QHeaderView* header = m_pDataTable->horizontalHeader();
header->setResizeMode(0, QHeaderView::Fixed);
header->setResizeMode(1, QHeaderView::Stretch);
header->setResizeMode(2, QHeaderView::Stretch);
if(enableRateDep) header->setResizeMode(3, QHeaderView::Stretch);
m_pDataTable->setColumnWidth(0, 20);
// 4) 创建单位行和ComboBox
if(m_pDataTable->rowCount() == 0) {
m_pDataTable->insertRow(0);
m_pDataTable->setRowHeight(0, 24);
}
createTimeUnitCombo();
// Skin列占位
QTableWidgetItem* emptySkin = new QTableWidgetItem("");
emptySkin->setFlags(Qt::ItemIsEnabled);
m_pDataTable->setItem(0, 2, emptySkin);
if(enableRateDep) {
createDSdQUnitCombo();
}
// 5) 恢复表格信号
m_pDataTable->blockSignals(oldTblBlocked);
}
void nmWxTimeDependentSkin::onShowDatesToggled(bool checked)
{
// 更新数据状态
m_perforationData.setShowDatesEnabled(checked);
// 如果启用日期显示,设置默认开始时间
if(checked && m_perforationData.getStartDateTime().isNull()) {
m_perforationData.setStartDateTime(QDateTime::currentDateTime());
}
// 更新时间单位下拉框的可见性
updateTimeUnitComboVisibility();
// 刷新表格显示
updateTableFromFlowSegments();
}
void nmWxTimeDependentSkin::updateTimeUnitComboVisibility()
{
bool showDates = m_perforationData.isShowDatesEnabled();
if(showDates) {
// 在使用setCellWidget替换控件前先将指针置空
if(m_pTimeUnitCombo) {
m_pTimeUnitCombo->disconnect(this);
m_pTimeUnitCombo = nullptr; // 关键:置空指针
}
// 创建并设置标签setCellWidget会自动删除旧控件
QLabel* dateTimeLabel = new QLabel(tr("Date Time"), m_pDataTable);
dateTimeLabel->setAlignment(Qt::AlignCenter);
dateTimeLabel->setStyleSheet("QLabel { color: #333; font-size: 12px; }");
m_pDataTable->setCellWidget(0, 1, dateTimeLabel);
} else {
// 恢复ComboBox显示
if(!m_pTimeUnitCombo) {
createTimeUnitCombo();
} else {
m_pTimeUnitCombo->setVisible(true);
}
}
}
QString nmWxTimeDependentSkin::formatTimeDisplay(double timeInHours, int segmentIndex) const
{
bool showDates = m_perforationData.isShowDatesEnabled();
if(showDates) {
// 显示绝对时间
QDateTime startDateTime = m_perforationData.getStartDateTime();
QDateTime segmentDateTime = startDateTime.addSecs(static_cast<int>(timeInHours * 3600));
return segmentDateTime.toString("yyyy-MM-dd hh:mm:ss");
} else {
// 显示相对时间
double startSec = toSeconds(timeInHours, "hr");
double startDisp = fromSeconds(startSec, m_timeDisplayUnit);
return QString::number(startDisp, 'f', 5);
}
}