|
|
#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)
|
|
|
: QDialog(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);
|
|
|
}
|
|
|
}
|