You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmSubWxs/nmWxPerforationClosing.cpp

930 lines
34 KiB
C++

This file contains ambiguous Unicode characters!

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

#include "nmWxPerforationClosing.h"
#include "iUnitGroup.h"
#include "iUnitHelper.h"
#include <QStyledItemDelegate>
#include <QPainter>
#include "iBase_global.h"
#include <QVBoxLayout>
#include "nmDialogPreviewGuard.h"
#include <QHBoxLayout>
#include <QSplitter>
#include <QTreeWidgetItem>
#include <QHeaderView>
#include <QLabel>
#include <QSpacerItem>
#include <QIcon>
#include <QMessageBox>
#include <cmath>
#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<QLineEdit*>(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<QStyleOptionViewItemV4*>(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<double> 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<double> 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<double> 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<qint64>(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<double>(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<double> 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<double>::max();
const double maxAllowed = (sortedIndex < sorted.size() - 1) ? sorted[sortedIndex + 1] : std::numeric_limits<double>::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<double> 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<double>::max();
const double maxAllowed = (sortedIndex < sorted.size() - 1) ? sorted[sortedIndex + 1] : std::numeric_limits<double>::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<int, nmDataPerforation>::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);
}