#include "nmWxTimeDependentSkin.h" #include "nmWxPerforationClosing.h" #include "nmWxPressFlowChartWidget.h" #include "nmWxChartWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // 静态成员变量定义 nmWxTimeDependentSkin* nmWxTimeDependentSkin::s_instance = nullptr; QString nmWxTimeDependentSkin::s_currentWellName = ""; nmDataWellBase* nmWxTimeDependentSkin::s_currentWellData = nullptr; void nmWxTimeDependentSkin::showForCurrentWell(nmDataWellBase* pSpecificWellData, QWidget* parent) { QString currentWellName; if (pSpecificWellData) currentWellName = pSpecificWellData->getWellName(); else { auto* cur = nmDataAnalyzeManager::getCurrentInstance()->getCurWellData(); if (!cur) return; currentWellName = cur->getWellName(); } if (currentWellName.isEmpty()) return; if (s_instance && s_currentWellName == currentWellName) { bool isSameDataSource = (pSpecificWellData && s_currentWellData == pSpecificWellData) || (!pSpecificWellData && !s_currentWellData); if (isSameDataSource) { // 同步父子关系和模态 if (parent && s_instance->parent() != parent) s_instance->setParent(parent, Qt::Dialog); // 刷新数据 s_instance->reloadFromDataSource(pSpecificWellData); s_instance->show(); s_instance->raise(); s_instance->activateWindow(); return; } s_instance->close(); delete s_instance; } // 新建实例 s_instance = new nmWxTimeDependentSkin(pSpecificWellData, parent); s_currentWellName = currentWellName; s_currentWellData = pSpecificWellData; s_instance->show(); s_instance->raise(); s_instance->activateWindow(); } nmWxTimeDependentSkin::nmWxTimeDependentSkin(nmDataWellBase* pWellData, QWidget *parent) : iDlgBase(parent) , m_pChartWidget(nullptr) , m_pOriginalPressureChart(nullptr) , m_pOriginalFlowRateChart(nullptr) , m_isAddMode(false) , m_isRemoveMode(false) , m_currentPerforationIndex(0) , m_pPressureChart(nullptr) , m_pFlowRateChart(nullptr) , m_timeDisplayUnit("hr") , m_dSdQDisplayUnit("1/B/D") , m_pTimeUnitCombo(nullptr) , m_pDSdQUnitCombo(nullptr) , m_rateDependentState(false) { // 根据传入的井数据决定使用哪个数据源 if (pWellData != nullptr) { // 使用传入的特定井数据, 来自nmWxEditWellPlot m_currentWell = pWellData; } else { // 使用当前井数据, 来自nmWxNumericalDesign m_currentWell = nmDataAnalyzeManager::getCurrentInstance()->getCurWellData(); } if(m_currentWell != nullptr) { // 获取压力和流量数据 pressurePoints = m_currentWell->getPressurePoints(); QVector 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 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 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& 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 currentSegments = m_perforationData.getFlowSegments(); const QVector& 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 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 nmWxTimeDependentSkin::processFlowData(const QVector& rawFlowData) { QVector 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 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 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 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& 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& 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& 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 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 flowPoints = m_currentWell->getFlowPoints(); if(flowPoints.isEmpty()) { return; } // 计算总时间 double totalTime = m_currentWell->getTotalTimeRange(); // 创建一个覆盖整个时间范围的默认流动段,初始状态为选中 QVector 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 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 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 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 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 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 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 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 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& 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 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 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 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 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 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& 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(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 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& 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(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); } }