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

2861 lines
99 KiB
C++

#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);
// 为右侧按钮也设置样式
QString buttonStyle =
"QPushButton {"
" border: 1px solid #ccc;"
" background-color: #f8f8f8;"
" border-radius: 3px;"
" padding: 2px;"
"}"
"QPushButton:hover {"
" background-color: #e8e8e8;"
" border-color: #999;"
"}"
"QPushButton:pressed {"
" background-color: #d8d8d8;"
"}"
"QPushButton:disabled {"
" background-color: #f0f0f0;"
" color: #aaa;"
" border-color: #ddd;"
"}";
m_pRightAddBtn->setStyleSheet(buttonStyle);
m_pRightInsertBtn->setStyleSheet(buttonStyle);
m_pRightDeleteBtn->setStyleSheet(buttonStyle);
m_pOkBtn->setStyleSheet(buttonStyle);
m_pCancelBtn->setStyleSheet(buttonStyle);
// 在初始化时创建图表模式按钮但不显示
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);
}
}