#include "nmWxPerforationClosing.h" #include "iUnitGroup.h" #include "iUnitHelper.h" #include #include #include "iBase_global.h" #include #include "nmDialogPreviewGuard.h" #include #include #include #include #include #include #include #include #include #include "nmDataAnalyzeManager.h" namespace { // 树形分组保留原生折叠行为,只为组内两列表格补画网格和当前行标记。 class PerforationClosingTree : public QTreeWidget { public: explicit PerforationClosingTree(QWidget* parent) : QTreeWidget(parent) {} protected: void drawRow(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override { QTreeWidget::drawRow(painter, option, index); painter->save(); painter->setPen(palette().color(QPalette::Mid)); // 分组行跨列;子行从缩进后的表格左边缘开始绘制,包含箭头栏。 const int left = index.parent().isValid() ? visualRect(index).left() - indentation() : option.rect.left(); painter->drawLine(left, option.rect.bottom(), option.rect.right(), option.rect.bottom()); if(index.parent().isValid()) { if(index.row() == 0) painter->drawLine(left, option.rect.top(), option.rect.right(), option.rect.top()); painter->drawLine(left, option.rect.top(), left, option.rect.bottom()); const int markerRight = visualRect(index).left() - 1; painter->drawLine(markerRight, option.rect.top(), markerRight, option.rect.bottom()); const int divider = columnViewportPosition(1) - 1; painter->drawLine(divider, option.rect.top(), divider, option.rect.bottom()); } painter->restore(); } void drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const override { // 射孔分组使用系统展开箭头,数据行的缩进区域显示当前行三角标记。 if(!index.parent().isValid()) { QTreeWidget::drawBranches(painter, rect, index); return; } if(index.row() == currentIndex().row() && index.parent() == currentIndex().parent() && (index.flags() & Qt::ItemIsEditable)) { painter->save(); const QPoint center(rect.right() - indentation() / 2, rect.center().y()); QPolygon arrow; arrow << center + QPoint(-2, -4) << center + QPoint(3, 0) << center + QPoint(-2, 4); painter->setPen(Qt::NoPen); painter->setBrush(palette().color(QPalette::Text)); painter->drawPolygon(arrow); painter->restore(); } } }; class PerforationClosingDelegate : public QStyledItemDelegate { public: explicit PerforationClosingDelegate(QObject* parent) : QStyledItemDelegate(parent) {} QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override { // 射孔状态由时间顺序决定,仅开始时间允许编辑。 if(index.column() != 0) return nullptr; QWidget* editor = QStyledItemDelegate::createEditor(parent, option, index); if(QLineEdit* line = qobject_cast(editor)) { line->setAlignment(Qt::Alignment(index.data(Qt::TextAlignmentRole).toInt())); } return editor; } protected: void initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const override { QStyledItemDelegate::initStyleOption(option, index); // 三位小数仅用于绘制,编辑与保存仍读取模型中的完整数值。 if(index.column() == 0 && index.parent().isValid() && (index.flags() & Qt::ItemIsEditable)) { bool ok = false; const double value = index.data(Qt::EditRole).toString().toDouble(&ok); QStyleOptionViewItemV4* textOption = qstyleoption_cast(option); if(ok && textOption) textOption->text = QString::number(value, 'f', 3); } } }; } nmWxPerforationClosing::nmWxPerforationClosing(nmDataWellBase* pWellData, QWidget *parent) : iDlgBase(parent) { setWindowTitle(tr("Perforation closing intervals")); new nmDialogPreviewGuard(this); setMinimumSize(500, 625); // 初始化成员变量 m_currentTimeUnit = "hr"; m_perforationCounter = 1; m_timeEntryCounter = 0; m_showDatesMode = false; m_hasBaseTime = false; m_pPerforationRoot = 0; m_pCurrentPerforationItem = 0; m_currentWell = pWellData; m_currentPerforationIndex = -1; // 按射孔保存独立副本,切换目标不会覆盖其他射孔的封堵记录。 if(m_currentWell) { for(int i = 0; i < m_currentWell->getPerforationCount(); ++i) { m_perforations.insert(i, m_currentWell->getPerforationCopy(i)); } m_currentPerforationIndex = m_currentWell->getPerforationCount() - 1; } loadDataFromModel(); // 初始化UI initUI(); // 连接信号 connect(m_pAddBtn, SIGNAL(clicked()), this, SLOT(onAddClicked())); connect(m_pRemoveBtn, SIGNAL(clicked()), this, SLOT(onRemoveClicked())); connect(m_pShowDatesCheckBox, SIGNAL(toggled(bool)), this, SLOT(onShowDatesToggled(bool))); connect(m_pTimeUnitComboBox, SIGNAL(currentIndexChanged(QString)), this, SLOT(onTimeUnitChanged(QString))); connect(m_pAllComboBox, SIGNAL(currentIndexChanged(QString)), this, SLOT(onAllComboChanged(QString))); connect(m_pOkBtn, SIGNAL(clicked()), this, SLOT(onOkClicked())); connect(m_pCancelBtn, SIGNAL(clicked()), this, SLOT(onCancelClicked())); connect(m_pTreeWidget, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(onItemChanged(QTreeWidgetItem*, int))); connect(m_pTreeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), this, SLOT(onCurrentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*))); refreshDisplay(); m_pAddBtn->setEnabled(m_pCurrentPerforationItem != nullptr); m_pRemoveBtn->setEnabled(m_pCurrentPerforationItem != nullptr); } nmWxPerforationClosing::~nmWxPerforationClosing() { } void nmWxPerforationClosing::initUI() { // 创建主布局 QVBoxLayout* mainLayout = new QVBoxLayout(this); // 创建工具栏容器 QWidget* toolbarContainer = new QWidget(this); toolbarContainer->setMinimumHeight(70); toolbarContainer->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); QHBoxLayout* toolbarLayout = new QHBoxLayout(toolbarContainer); toolbarLayout->setContentsMargins(0, 0, 0, 0); // 创建Add按钮组合 QWidget* addButtonContainer = new QWidget(this); QVBoxLayout* addContainerLayout = new QVBoxLayout(addButtonContainer); addContainerLayout->setSpacing(0); addContainerLayout->setContentsMargins(2, 2, 2, 2); m_pAddBtn = new QPushButton(addButtonContainer); // 复用框架图标加载及缓存,保持原有工具栏布局。 m_pAddBtn->setIcon(zxLoadIcon("nmTimeDepSkin2")); m_pAddBtn->setIconSize(QSize(36, 36)); m_pAddBtn->setFixedSize(40, 40); QLabel* addTextLabel = new QLabel(tr("Add"), addButtonContainer); addTextLabel->setAlignment(Qt::AlignCenter); addContainerLayout->addWidget(m_pAddBtn, 0, Qt::AlignHCenter); addContainerLayout->addWidget(addTextLabel); addButtonContainer->setMinimumWidth(100); addButtonContainer->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); toolbarLayout->addWidget(addButtonContainer); // 创建Remove按钮组合 QWidget* removeButtonContainer = new QWidget(this); QVBoxLayout* removeContainerLayout = new QVBoxLayout(removeButtonContainer); removeContainerLayout->setSpacing(0); removeContainerLayout->setContentsMargins(2, 2, 2, 2); m_pRemoveBtn = new QPushButton(removeButtonContainer); m_pRemoveBtn->setIcon(zxLoadIcon("nmTimeDepSkin3")); m_pRemoveBtn->setIconSize(QSize(36, 36)); m_pRemoveBtn->setFixedSize(40, 40); QLabel* removeTextLabel = new QLabel(tr("Remove"), removeButtonContainer); removeTextLabel->setAlignment(Qt::AlignCenter); removeContainerLayout->addWidget(m_pRemoveBtn, 0, Qt::AlignHCenter); removeContainerLayout->addWidget(removeTextLabel); removeButtonContainer->setMinimumWidth(100); removeButtonContainer->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); toolbarLayout->addWidget(removeButtonContainer); // 添加复选框和时间单位控件组 m_pShowDatesCheckBox = new QCheckBox(tr("Show dates")); toolbarLayout->addWidget(m_pShowDatesCheckBox); toolbarLayout->addSpacing(15); // 复选框和时间单位之间的固定间距 // 单位控件单独保留占位,切换日期显示时前面的控件不移动。 QWidget* timeUnitContainer = new QWidget(toolbarContainer); QHBoxLayout* timeUnitLayout = new QHBoxLayout(timeUnitContainer); timeUnitLayout->setContentsMargins(0, 0, 0, 0); timeUnitLayout->setSpacing(toolbarLayout->spacing()); m_pTimeUnitLabel = new QLabel(tr("Time unit:")); timeUnitLayout->addWidget(m_pTimeUnitLabel); timeUnitLayout->addSpacing(5); // 标签和下拉框之间的小间距 // 单位和主窗口统一从底层配置读取。 m_pTimeUnitComboBox = new QComboBox(); iUnitGroup* group = iUnitHelper::getUnitGroupByUnit("s"); const QStringList units = group ? group->getAllUnitNames() : (QStringList() << "hr"); m_pTimeUnitComboBox->addItems(units); if(m_currentTimeUnit == "hr" && units.contains("h")) m_currentTimeUnit = "h"; int unitIndex = m_pTimeUnitComboBox->findText(m_currentTimeUnit); m_pTimeUnitComboBox->setCurrentIndex(unitIndex >= 0 ? unitIndex : 0); m_currentTimeUnit = m_pTimeUnitComboBox->currentText(); m_pTimeUnitComboBox->setFixedWidth(60); timeUnitLayout->addWidget(m_pTimeUnitComboBox); timeUnitContainer->setFixedSize(timeUnitContainer->sizeHint()); toolbarLayout->addWidget(timeUnitContainer); // 避免控件贴边 toolbarLayout->addSpacing(10); // 将工具栏添加到主布局 mainLayout->addWidget(toolbarContainer); // 创建单个树形表格控件 m_pTreeWidget = new PerforationClosingTree(this); // 数值时间和日期共用第一列,第二列显示射孔状态。 m_pTreeWidget->setColumnCount(2); m_pTreeWidget->setItemDelegate(new PerforationClosingDelegate(m_pTreeWidget)); // 设置表头 QStringList headers; headers << "" << ""; m_pTreeWidget->setHeaderLabels(headers); m_pTreeWidget->setHeaderHidden(true); // 列标题显示在各射孔分组内部 // 设置列宽 m_pTreeWidget->setColumnWidth(0, 260); // 分组和开始时间,预留日期文字及树缩进宽度 m_pTreeWidget->setColumnWidth(1, 180); // 射孔状态 // 设置表头拉伸策略 m_pTreeWidget->header()->setStretchLastSection(true); m_pTreeWidget->header()->setHighlightSections(false); m_pTreeWidget->header()->setDefaultAlignment(Qt::AlignCenter); // 设置树形控件的其他属性 m_pTreeWidget->setRootIsDecorated(true); // 显示根节点的展开/折叠图标 m_pTreeWidget->setAlternatingRowColors(true); // 交替行颜色 m_pTreeWidget->setSelectionBehavior(QAbstractItemView::SelectRows); // 整行选择 m_pTreeWidget->setSelectionMode(QAbstractItemView::SingleSelection); m_pTreeWidget->setIndentation(20); // 设置缩进距离 // 选中行保持浅蓝底色;网格由树控件统一绘制,不依赖单元格代理。 m_pTreeWidget->setEditTriggers(QAbstractItemView::DoubleClicked); m_pTreeWidget->setStyleSheet( "QTreeWidget {" " background-color: palette(base);" " alternate-background-color: rgb(232, 232, 232);" " border: 1px solid palette(mid);" "}" "QHeaderView {" " background-color: palette(button);" " color: palette(button-text);" "}" "QHeaderView::section {" " background-color: palette(button);" " color: palette(button-text);" " border: none;" " border-right: 1px solid palette(mid);" " border-bottom: 1px solid palette(mid);" "}" "QTreeWidget::item:selected {" " background-color: rgb(225, 235, 252);" " color: palette(text);" "}" ); // 将树形控件添加到主布局 mainLayout->addWidget(m_pTreeWidget); if(m_currentWell != nullptr) { int perforationCount = m_currentWell->getPerforationCount(); // 添加表头行 m_pPerforationRoot = new QTreeWidgetItem(m_pTreeWidget); m_pPerforationRoot->setText(0, tr("Perforation")); m_pPerforationRoot->setFirstColumnSpanned(true); m_pPerforationRoot->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicator); // 为每个射孔段添加树形项目 for(int i = 0; i < perforationCount; ++i) { nmDataPerforation* perforation = m_currentWell->getPerforation(i); if(perforation) { QString perforationName = perforation->getName().getValue().toString(); if(perforationName.isEmpty()) { perforationName = tr("Perforation #%1").arg(i + 1); } QTreeWidgetItem* perforationItem = new QTreeWidgetItem(m_pTreeWidget); perforationItem->setText(0, perforationName); perforationItem->setData(0, Qt::UserRole, i); perforationItem->setFirstColumnSpanned(true); perforationItem->setExpanded(false); // 默认收起 // 如果是当前选中的射孔段,保存引用 if(i == m_currentPerforationIndex) { m_pCurrentPerforationItem = perforationItem; } } } } //// 添加第一行"Perforation" //m_pPerforationRoot = new QTreeWidgetItem(m_pTreeWidget); //m_pPerforationRoot->setText(0, tr("Perforation")); //// 设置第一行不可展开(通过设置ChildIndicatorPolicy) //m_pPerforationRoot->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicator); //// 添加第二行"Perforation #1" //m_pCurrentPerforationItem = new QTreeWidgetItem(m_pTreeWidget); //m_pCurrentPerforationItem->setText(0, tr("Perforation #%1").arg(m_perforationCounter)); //m_pCurrentPerforationItem->setExpanded(false); // 默认收起 ////m_pCurrentPerforationItem->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicator); // 隐藏展开箭头 // 创建底部按钮区域 QHBoxLayout* bottomLayout = new QHBoxLayout(); bottomLayout->setSpacing(8); // All下拉框 m_pAllComboBox = new QComboBox(); m_pAllComboBox->addItem(tr("All")); 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 perforationName = perforation->getName().getValue().toString(); if(perforationName.isEmpty()) { perforationName = tr("Perforation #%1").arg(i + 1); } m_pAllComboBox->addItem(perforationName); } } } m_pAllComboBox->setFixedWidth(180); // 加宽下拉框,便于查看射孔段名称 //m_pAllComboBox = new QComboBox(); //m_pAllComboBox->addItem(tr("All")); //m_pAllComboBox->addItem(tr("Option 1")); //m_pAllComboBox->addItem(tr("Option 2")); //m_pAllComboBox->setFixedWidth(80); bottomLayout->addWidget(m_pAllComboBox); bottomLayout->addStretch(); // OK和Cancel按钮 m_pOkBtn = new QPushButton(tr("OK")); m_pCancelBtn = new QPushButton(tr("Cancel")); m_pOkBtn->setFixedWidth(75); m_pCancelBtn->setFixedWidth(75); bottomLayout->addWidget(m_pOkBtn); bottomLayout->addWidget(m_pCancelBtn); // 将底部布局添加到主布局 mainLayout->addLayout(bottomLayout); // 设置主布局的边距和间距 mainLayout->setContentsMargins(10, 10, 10, 10); mainLayout->setSpacing(5); } void nmWxPerforationClosing::onAddClicked() { if(!m_pCurrentPerforationItem) return; // 只在第一次Add时记录基准时间 if(!m_hasBaseTime) { m_baseTime = QDateTime::currentDateTime(); m_hasBaseTime = true; } // 子行的第一行是表头,实际时间记录从第二行开始。 QTreeWidgetItem* selected = m_pTreeWidget->currentItem(); int selectedIndex = selected && selected->parent() == m_pCurrentPerforationItem ? m_pCurrentPerforationItem->indexOfChild(selected) - 1 : -1; QList newValuesInHr; if(m_baseTimeValues.isEmpty()) { // 第一次Add:添加100.0小时 newValuesInHr.append(100.0); } else if(m_baseTimeValues.size() == 1 || selectedIndex == m_baseTimeValues.size() - 1) { // 最后一行没有后续区间,沿用初始的100小时步长向后追加一条。 newValuesInHr.append(m_baseTimeValues.last() + 100.0); } else { // 保留三等分插入两个点的规则,但使用所选行与下一行之间的区间。 // 只选中射孔名称或表头时,沿用原来的第一个区间。 if(selectedIndex < 0) selectedIndex = 0; const double start = m_baseTimeValues[selectedIndex]; const double end = m_baseTimeValues[selectedIndex + 1]; const double interval = (end - start) / 3.0; newValuesInHr.append(start + interval); newValuesInHr.append(start + 2 * interval); } // 合并原有数值和新数值,然后排序 m_baseTimeValues.append(newValuesInHr); std::sort(m_baseTimeValues.begin(), m_baseTimeValues.end()); // 刷新显示 refreshDisplay(); // 展开当前选中的射孔分组以显示新添加的内容 m_pCurrentPerforationItem->setExpanded(true); // 更新计数器 m_timeEntryCounter = m_baseTimeValues.size(); } void nmWxPerforationClosing::onRemoveClicked() { if(!m_pCurrentPerforationItem) return; QTreeWidgetItem* selectedItem = m_pTreeWidget->currentItem(); bool hasSelection = false; int selectedIndex = -1; // 在日期模式和数值模式下都能正确识别有效行 if(selectedItem && selectedItem->parent() == m_pCurrentPerforationItem && selectedItem->text(0) != tr("Start time")) { // 检查是否是有效的数据行 bool isValidDataRow = false; if(m_showDatesMode) { // 日期模式下检查第0列是否有时间数据 isValidDataRow = !selectedItem->text(0).isEmpty() && QDateTime::fromString(selectedItem->text(0), "yyyy-MM-dd hh:mm:ss").isValid(); } else { // 数值时间也位于第一列。 isValidDataRow = !selectedItem->text(0).isEmpty(); } if(isValidDataRow) { // 计算选中项在数据中的索引 int dataIndex = 0; for(int i = 0; i < m_pCurrentPerforationItem->childCount(); i++) { QTreeWidgetItem* child = m_pCurrentPerforationItem->child(i); if(child == nullptr) { continue; } if(child == selectedItem) { selectedIndex = dataIndex; break; } if(child->text(0) != tr("Start time")) { dataIndex++; } } hasSelection = true; } } QList timeValuesInHr = m_baseTimeValues; if(timeValuesInHr.isEmpty()) return; qSort(timeValuesInHr.begin(), timeValuesInHr.end()); // 未选中具体事件时,沿用删除末尾事件的入口。 if(!hasSelection || selectedIndex < 0 || selectedIndex >= timeValuesInHr.size()) { selectedIndex = timeValuesInHr.size() - 1; } // 按Saphir的行操作规则删除当前行及下一行,先删下一行以免索引移位。 if(selectedIndex + 1 < timeValuesInHr.size()) { timeValuesInHr.removeAt(selectedIndex + 1); } // 当前行位于末尾时,只删除自身。 timeValuesInHr.removeAt(selectedIndex); m_baseTimeValues = timeValuesInHr; if(m_baseTimeValues.isEmpty()) { m_hasBaseTime = false; } refreshDisplay(); m_timeEntryCounter = timeValuesInHr.size(); } void nmWxPerforationClosing::refreshDisplay() { // 显示单位和日期模式属于整个窗口,刷新时同时更新所有射孔分组。 storeCurrentPerforation(); const bool blocked = m_pTreeWidget->blockSignals(true); QTreeWidgetItem* selected = m_pTreeWidget->currentItem(); QTreeWidgetItem* selectedGroup = selected && selected->parent() ? selected->parent() : selected; const int selectedRow = selected && selected->parent() ? selectedGroup->indexOfChild(selected) : -1; for(int groupIndex = 0; groupIndex < m_pTreeWidget->topLevelItemCount(); ++groupIndex) { QTreeWidgetItem* group = m_pTreeWidget->topLevelItem(groupIndex); if(group == m_pPerforationRoot) continue; const nmDataPerforation& data = m_perforations[group->data(0, Qt::UserRole).toInt()]; const QList values = data.getTimeValues(); const QDateTime baseTime = data.getBaseTime(); const bool wasExpanded = group->isExpanded(); const bool hadRows = group->childCount() > 0; qDeleteAll(group->takeChildren()); if(!values.isEmpty()) { QTreeWidgetItem* heading = new QTreeWidgetItem(group); heading->setText(0, tr("Start time")); heading->setText(1, tr("Perforation state")); heading->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicator); } for(int i = 0; i < values.size(); ++i) { QTreeWidgetItem* row = new QTreeWidgetItem(group); const double seconds = toSecondsQuantized(values[i], "hr"); if(m_showDatesMode && baseTime.isValid()) { row->setText(0, baseTime.addSecs(static_cast(seconds)).toString("yyyy-MM-dd hh:mm:ss")); row->setTextAlignment(0, Qt::AlignLeft | Qt::AlignVCenter); } else { row->setText(0, QString::number(fromSeconds(seconds, m_currentTimeUnit), 'g', 15)); row->setTextAlignment(0, Qt::AlignRight | Qt::AlignVCenter); } row->setFlags(row->flags() | Qt::ItemIsEditable); row->setText(1, i % 2 == 0 ? tr("Closed") : tr("Open")); row->setTextAlignment(1, Qt::AlignLeft | Qt::AlignVCenter); row->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicator); } group->setExpanded(wasExpanded || (!hadRows && !values.isEmpty())); } // 重建子行后按分组及行号恢复选择,不能继续使用已释放的行指针。 if(selectedGroup) { m_pTreeWidget->setCurrentItem(selectedRow >= 0 && selectedGroup->childCount() > 0 ? selectedGroup->child(qMin(selectedRow, selectedGroup->childCount() - 1)) : selectedGroup); } else if(m_pCurrentPerforationItem) { m_pTreeWidget->setCurrentItem(m_pCurrentPerforationItem); } m_pTreeWidget->blockSignals(blocked); } void nmWxPerforationClosing::onCurrentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous) { Q_UNUSED(previous); // 点击射孔名称、组内表头或数据行,都以其所属射孔作为增删和编辑目标。 QTreeWidgetItem* group = current && current->parent() ? current->parent() : current; if(group == m_pPerforationRoot) group = nullptr; if(group == m_pCurrentPerforationItem) return; storeCurrentPerforation(); m_pCurrentPerforationItem = group; m_currentPerforationIndex = group ? group->data(0, Qt::UserRole).toInt() : -1; loadDataFromModel(); m_pAddBtn->setEnabled(group != nullptr); m_pRemoveBtn->setEnabled(group != nullptr); } void nmWxPerforationClosing::onTimeUnitChanged(const QString &unit) { // 更新当前时间单位 m_currentTimeUnit = unit; // 刷新显示 refreshDisplay(); } void nmWxPerforationClosing::onShowDatesToggled(bool checked) { m_showDatesMode = checked; // 根据复选框状态隐藏/显示时间单位相关控件 m_pTimeUnitLabel->setVisible(!checked); m_pTimeUnitComboBox->setVisible(!checked); // 刷新显示 refreshDisplay(); } void nmWxPerforationClosing::onAllComboChanged(const QString &text) { if(!m_currentWell) return; if(text == tr("All")) { // 显示所有射孔段的树形项目 showAllPerforationItems(); } else { // 显示特定射孔段的树形项目 int perforationIndex = findPerforationIndexByName(text); if(perforationIndex >= 0) { showSpecificPerforationItem(perforationIndex); } } } void nmWxPerforationClosing::showAllPerforationItems() { if(!m_pTreeWidget) return; // 显示所有射孔段项目 for(int i = 0; i < m_pTreeWidget->topLevelItemCount(); ++i) { QTreeWidgetItem* item = m_pTreeWidget->topLevelItem(i); if(item && item != m_pPerforationRoot) { item->setHidden(false); } } } void nmWxPerforationClosing::showSpecificPerforationItem(int perforationIndex) { if(!m_pTreeWidget || !m_currentWell) return; // 隐藏所有射孔段项目 for(int i = 0; i < m_pTreeWidget->topLevelItemCount(); ++i) { QTreeWidgetItem* item = m_pTreeWidget->topLevelItem(i); if(item && item != m_pPerforationRoot) { item->setHidden(true); } } // 只显示指定的射孔段项目 int itemIndex = perforationIndex + 1; // +1是因为第0项是表头 if(itemIndex < m_pTreeWidget->topLevelItemCount()) { QTreeWidgetItem* item = m_pTreeWidget->topLevelItem(itemIndex); if(item) { item->setHidden(false); item->setExpanded(true); // 展开显示内容 m_pTreeWidget->setCurrentItem(item); // 筛选射孔时同步切换操作目标 } } } int nmWxPerforationClosing::findPerforationIndexByName(const QString& name) { if(!m_currentWell) return -1; int perforationCount = m_currentWell->getPerforationCount(); for(int i = 0; i < perforationCount; ++i) { nmDataPerforation* perforation = m_currentWell->getPerforation(i); if(perforation) { QString perforationName = perforation->getName().getValue().toString(); if(perforationName.isEmpty()) { perforationName = tr("Perforation #%1").arg(i + 1); } if(perforationName == name) { return i; } } } return -1; } void nmWxPerforationClosing::onOkClicked() { saveDataToModel(); // 验证数据并接受对话框 accept(); } void nmWxPerforationClosing::onCancelClicked() { // 取消对话框 reject(); } void nmWxPerforationClosing::onItemChanged(QTreeWidgetItem* item, int column) { // 添加一个静态变量防止递归调用 static bool isProcessingItemChange = false; if(isProcessingItemChange) return; // 只处理数值行的编辑(排除Start time行) if(!item || item->text(0) == tr("Start time")) return; // 确保是子项 if(item->parent() != m_pCurrentPerforationItem) return; // 两种模式都编辑开始时间列,按显示模式选择解析方式。 if(m_showDatesMode && column == 0) { // 日期模式下编辑第0列(时间字符串) processDateEdit(item); } else if(!m_showDatesMode && column == 0) { // 数值模式下编辑第0列(数值) processValueEdit(item); } } void nmWxPerforationClosing::processDateEdit(QTreeWidgetItem* item) { static bool guard = false; if(guard) return; guard = true; const QString s = item->text(0); const QDateTime dt = QDateTime::fromString(s, "yyyy-MM-dd hh:mm:ss"); if(!dt.isValid()) { guard = false; refreshDisplay(); return; } // Qt 返回整数秒差(内部是 qint64),我们直接转 double 使用,不声明 qint64 变量 const double newHr = static_cast(m_baseTime.secsTo(dt)) / 3600.0; // 定位该项索引 int itemIndex = -1, dataIdx = 0; for(int i = 0; i < m_pCurrentPerforationItem->childCount(); ++i) { QTreeWidgetItem* ch = m_pCurrentPerforationItem->child(i); if(ch == nullptr) { continue; } if(ch == item) { itemIndex = dataIdx; break; } if(ch->text(0) != tr("Start time")) dataIdx++; } if(itemIndex >= 0 && itemIndex < m_baseTimeValues.size()) { QList sorted = m_baseTimeValues; std::sort(sorted.begin(), sorted.end()); const double currentHr = m_baseTimeValues[itemIndex]; int sortedIndex = -1; for(int i = 0; i < sorted.size(); ++i) { if(qAbs(sorted[i] - currentHr) < 1e-6) { sortedIndex = i; break; } } if(sortedIndex >= 0) { const double minAllowed = (sortedIndex > 0) ? sorted[sortedIndex - 1] : -std::numeric_limits::max(); const double maxAllowed = (sortedIndex < sorted.size() - 1) ? sorted[sortedIndex + 1] : std::numeric_limits::max(); if(newHr <= minAllowed || newHr >= maxAllowed) { guard = false; refreshDisplay(); return; } m_baseTimeValues[itemIndex] = newHr; std::sort(m_baseTimeValues.begin(), m_baseTimeValues.end()); refreshDisplay(); } else { guard = false; refreshDisplay(); return; } } guard = false; } void nmWxPerforationClosing::processValueEdit(QTreeWidgetItem* item) { static bool guard = false; if(guard) return; guard = true; bool ok = false; // 数值时间与日期统一从第一列读取。 const double newDisp = item->text(0).toDouble(&ok); if(!ok) { guard = false; refreshDisplay(); return; } // 显示单位 -> “秒(四舍五入为整数秒,double)” -> “小时” const double secQuant = toSecondsQuantized(newDisp, m_currentTimeUnit); const double newHr = fromSeconds(secQuant, "hr"); // 找到该行对应的数据索引(跳过 Start time 行) int itemIndex = -1, dataIdx = 0; for(int i = 0; i < m_pCurrentPerforationItem->childCount(); ++i) { QTreeWidgetItem* ch = m_pCurrentPerforationItem->child(i); if(ch == nullptr) { continue; } if(ch == item) { itemIndex = dataIdx; break; } if(ch->text(0) != tr("Start time")) dataIdx++; } if(itemIndex >= 0 && itemIndex < m_baseTimeValues.size()) { // 相邻不越界检查(基于排序副本) QList sorted = m_baseTimeValues; std::sort(sorted.begin(), sorted.end()); const double currentHr = m_baseTimeValues[itemIndex]; int sortedIndex = -1; for(int i = 0; i < sorted.size(); ++i) { if(qAbs(sorted[i] - currentHr) < 1e-6) { sortedIndex = i; break; } } if(sortedIndex >= 0) { const double minAllowed = (sortedIndex > 0) ? sorted[sortedIndex - 1] : -std::numeric_limits::max(); const double maxAllowed = (sortedIndex < sorted.size() - 1) ? sorted[sortedIndex + 1] : std::numeric_limits::max(); if(newHr <= minAllowed || newHr >= maxAllowed) { guard = false; refreshDisplay(); return; } m_baseTimeValues[itemIndex] = newHr; std::sort(m_baseTimeValues.begin(), m_baseTimeValues.end()); refreshDisplay(); } else { guard = false; refreshDisplay(); return; } } guard = false; } void nmWxPerforationClosing::storeCurrentPerforation() { // 切换分组和刷新前只更新本地副本,取消窗口时可丢弃全部修改。 if(!m_perforations.contains(m_currentPerforationIndex)) return; nmDataPerforation& data = m_perforations[m_currentPerforationIndex]; data.setTimeValues(m_baseTimeValues); QStringList states; for(int i = 0; i < m_baseTimeValues.size(); ++i) states << (i % 2 == 0 ? tr("Closed") : tr("Open")); data.setTimeStates(states); data.setBaseTime(m_hasBaseTime ? m_baseTime : QDateTime()); } void nmWxPerforationClosing::saveDataToModel() { // 统一提交各射孔的封堵字段,保留井对象中其他参数的最新值。 storeCurrentPerforation(); if(!m_currentWell) return; for(QMap::const_iterator it = m_perforations.constBegin(); it != m_perforations.constEnd(); ++it) { nmDataPerforation data = m_currentWell->getPerforationCopy(it.key()); data.setTimeValues(it.value().getTimeValues()); data.setTimeStates(it.value().getTimeStates()); data.setBaseTime(it.value().getBaseTime()); m_currentWell->updatePerforation(it.key(), data); } } void nmWxPerforationClosing::loadDataFromModel() { // 切换编辑缓冲区时不重建树,避免删除正在点击或编辑的子行。 const nmDataPerforation data = m_perforations.value(m_currentPerforationIndex); m_baseTimeValues = data.getTimeValues(); m_baseTime = data.getBaseTime(); m_hasBaseTime = m_baseTime.isValid(); m_timeEntryCounter = m_baseTimeValues.size(); } double nmWxPerforationClosing::convertTime(double value, const QString& fromUnit, const QString& toUnit) const { // 直接使用底层单位组,兼容内部 hr/sec 与配置中的 h/s 写法。 iUnitGroup* group = iUnitHelper::getUnitGroupByUnit("s"); if(group) { QString source = fromUnit == "sec" ? "s" : fromUnit; QString destination = toUnit == "sec" ? "s" : toUnit; if(group->indexOf("h") >= 0) { if(source == "hr") source = "h"; if(destination == "hr") destination = "h"; } double converted = value; int digits = 6; if(group->convert(source, value, destination, converted, digits)) return converted; } // 单位配置未加载时,沿用原有的内部时间换算。 nmDataAttribute attribute; attribute.setAttributeUnitType(UNIT_TYPE_TIME); return attribute.convertValueByUnitType(value, fromUnit, toUnit); } double nmWxPerforationClosing::roundToSecondDouble(double sec) const { return (sec >= 0.0) ? std::floor(sec + 0.5) : std::ceil(sec - 0.5); } double nmWxPerforationClosing::toSecondsQuantized(double value, const QString& unit) const { const double sec = convertTime(value, unit, "sec"); return roundToSecondDouble(sec); // 量化 } double nmWxPerforationClosing::fromSeconds(double sec, const QString& unit) const { return convertTime(sec, "sec", unit); }