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/nmWxGeometryLayerDlg.cpp

565 lines
30 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 "nmWxGeometryLayerDlg.h"
#include "nmLayerGeometryDataSource.h"
#include "nmWxLayerGeometryPreview.h"
#include "iUnitHelper.h"
#include "iUnitGroup.h"
#include "ZxBaseUtil.h"
#include <QApplication>
#include <QAbstractItemModel>
#include <QClipboard>
#include <QColorDialog>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QHeaderView>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QKeySequence>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QPersistentModelIndex>
#include <QStyledItemDelegate>
#include <QTableWidget>
#include <QVBoxLayout>
#include <QtCore/qnumeric.h>
namespace {
// 表格的高度只决定可见行数,行高由字体和主题确定;支持复制只读顶底深度。
class LayerTable : public QTableWidget
{
public:
explicit LayerTable(QWidget* parent) : QTableWidget(parent) {}
virtual QSize sizeHint() const
{
// 按典型数值文本和七行内容估算初始尺寸;这只是推荐值,不限制层数和窗口拉伸。
const int width = fontMetrics().width("-0000000.0000") * 4
+ verticalHeader()->sizeHint().width() + 48;
return QSize(width, horizontalHeader()->sizeHint().height()
+ verticalHeader()->defaultSectionSize() * 7 + frameWidth() * 2);
}
protected:
virtual void keyPressEvent(QKeyEvent* event)
{
if (event->matches(QKeySequence::Copy)) {
// 使用制表符分列、换行分行,复制后可直接粘贴到表格软件;只读单元格也能复制。
QStringList lines; // 剪贴板中的逐行文本,每行内部用制表符分隔列。
const QList<QTableWidgetSelectionRange> ranges = selectedRanges(); // 当前复制范围,含只读列。
foreach (const QTableWidgetSelectionRange& range, ranges) {
for (int row = range.topRow(); row <= range.bottomRow(); ++row) {
QStringList cells; // 当前行选中单元格的显示文本,复制不改变米制数据。
for (int column = range.leftColumn(); column <= range.rightColumn(); ++column)
cells.append(item(row, column) ? item(row, column)->text() : QString());
lines.append(cells.join("\t"));
}
}
QApplication::clipboard()->setText(lines.join("\n"));
event->accept();
return;
}
QTableWidget::keyPressEvent(event);
}
};
}
// 使用文本编辑器,避免默认浮点微调框的范围和精度截断。
// 编辑起始值取完整精度;没有改动就不 setData,不触发显示值反写。
class nmLayerCellDelegate : public QStyledItemDelegate
{
public:
explicit nmLayerCellDelegate(QObject* parent) : QStyledItemDelegate(parent) {}
virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem&,
const QModelIndex& index) const
{
m_pActiveEditor = new QLineEdit(parent);
m_oEditorIndex = index;
return m_pActiveEditor;
}
virtual void setEditorData(QWidget* editor, const QModelIndex& index) const
{
QLineEdit* line = static_cast<QLineEdit*>(editor); // 视图拥有的编辑器,仅在本次回调中借用。
// UserRole 保存编辑文本,DisplayRole 仅供格式化显示;非法输入也保留原文供再次修正。
const QString text = index.data(Qt::UserRole).toString(); // 完整精度的初始文本,或待修正的原始输入。
line->setText(text);
line->setProperty("initialText", text);
line->selectAll();
}
virtual void setModelData(QWidget* editor, QAbstractItemModel* model,
const QModelIndex& index) const
{
// initialText 区分“进入编辑后直接离开”和真正修改,防止未操作时进行数值往返换算。
QLineEdit* line = static_cast<QLineEdit*>(editor); // 用户当前编辑内容的来源,不拥有该对象。
if (line->text() != line->property("initialText").toString()) {
model->setData(index, line->text(), Qt::EditRole);
line->setProperty("initialText", line->text());
}
}
void finish()
{
// Qt 4 的编辑器在关闭后延迟删除;不要再次提交已经隐藏的旧编辑器。
if (!m_pActiveEditor || !m_pActiveEditor->isVisible() || !m_oEditorIndex.isValid()) return;
QLineEdit* editor = m_pActiveEditor; // 当前有效编辑器,交给视图提交及关闭,不在此处释放。
// 先通知表格写入并触发输入检查,再关闭编辑器;关闭时不自动跳到其他单元格。
emit commitData(editor);
emit closeEditor(editor, QAbstractItemDelegate::NoHint);
m_pActiveEditor = 0;
}
private:
// 编辑器由视图创建/销毁,此处只弱引用;持久索引可在行结构变化后检测原单元格是否仍有效。
mutable QPointer<QLineEdit> m_pActiveEditor; // 当前活动编辑器的弱引用,视图销毁它后自动置空。
mutable QPersistentModelIndex m_oEditorIndex; // 活动编辑器对应的单元格,结构变化后用于判断索引是否有效。
};
nmWxGeometryLayerDlg::nmWxGeometryLayerDlg(nmLayerGeometryDataSource* source, QWidget* parent)
: iDlgBase(parent), m_pDataSource(source), m_pLengthUnitGroup(iUnitHelper::getUnitGroupByUnit("m")),
m_sDisplayUnit("m"), m_bRefreshing(false), m_bInitialized(false), m_bCommitBlocked(false)
{
setWindowTitle(tr("Layer Setting"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
initUI();
}
void nmWxGeometryLayerDlg::initUI()
{
// 普通字段采用主题推荐高度,纵向剩余空间交给表格与预览;整窗不固定像素尺寸。
QVBoxLayout* main = new QVBoxLayout(this); // 根布局,按字段、操作、表格、统计、底栏顺序排列。
main->setContentsMargins(12, 10, 12, 0);
main->setSpacing(10);
QGridLayout* fields = new QGridLayout; // 首层顶深及统一长度单位,加入根布局后由 Qt 管理。
fields->setHorizontalSpacing(6);
fields->addWidget(new QLabel(tr("First layer top"), this), 0, 0);
m_pFirstTopEdit = new QLineEdit(this);
m_pFirstTopEdit->setObjectName("layerFirstTop");
m_pFirstTopEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
fields->addWidget(m_pFirstTopEdit, 0, 1);
m_pLengthUnitComboBox = new QComboBox(this);
m_pLengthUnitComboBox->setObjectName("layerLengthUnit");
m_pLengthUnitComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents);
m_pLengthUnitComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
// “m”只作为框架长度单位组的识别入口;可选单位列表和换算规则均由框架维护。
if (m_pLengthUnitGroup) m_pLengthUnitComboBox->addItems(m_pLengthUnitGroup->getAllUnitNames());
m_pLengthUnitComboBox->setCurrentIndex(m_pLengthUnitComboBox->findText(m_sDisplayUnit));
fields->addWidget(m_pLengthUnitComboBox, 0, 2);
// 单位下拉框紧随顶深输入框,不另设单位标签;两者沿用已统一对话框的 8:5 伸展比例。
fields->setColumnStretch(1, 8);
fields->setColumnStretch(2, 5);
main->addLayout(fields);
QHBoxLayout* actions = new QHBoxLayout; // 表格上方的分层编辑操作区。
m_pAddButton = new QPushButton(tr("Add"), this);
m_pInsertButton = new QPushButton(tr("Insert"), this);
m_pDeleteButton = new QPushButton(tr("Delete"), this);
// 仅借用三个层结构操作按钮,以同一规则设置非默认按钮属性和排列顺序。
QPushButton* buttons[] = { m_pAddButton, m_pInsertButton, m_pDeleteButton };
for (int i = 0; i < 3; ++i) {
buttons[i]->setAutoDefault(false);
actions->addWidget(buttons[i]);
}
actions->addStretch();
main->addLayout(actions);
m_pLayerTable = new LayerTable(this);
m_pLayerTable->setObjectName("layerGeometryTable");
m_pLayerTable->setColumnCount(4);
m_pLayerTable->setSelectionBehavior(QAbstractItemView::SelectItems);
m_pLayerTable->setSelectionMode(QAbstractItemView::ContiguousSelection);
// 行顺序就是实际上下层顺序,排序或拖放都会破坏几何含义,因此只提供显式插入/删除。
m_pLayerTable->setSortingEnabled(false);
m_pLayerTable->setDragEnabled(false);
m_pLayerTable->setAcceptDrops(false);
m_pLayerTable->setDropIndicatorShown(false);
m_pLayerTable->setAlternatingRowColors(true);
m_pLayerTable->setStyleSheet(
"QTableWidget { background-color: palette(base);"
"alternate-background-color: rgb(232,232,232);"
"gridline-color: palette(midlight); border: 1px solid palette(mid); }"
"QHeaderView::section { background-color: palette(button); color: palette(button-text);"
"border: none; border-right: 1px solid palette(mid); border-bottom: 1px solid palette(mid); }"
"QTableCornerButton::section { background-color: palette(base); border: none;"
"border-right: 1px solid palette(mid); border-bottom: 1px solid palette(mid); }");
m_pLayerTable->horizontalHeader()->setMovable(false);
m_pLayerTable->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
m_pLayerTable->verticalHeader()->setMovable(false);
m_pLayerTable->verticalHeader()->setResizeMode(QHeaderView::Fixed);
m_pLayerTable->verticalHeader()->setDefaultAlignment(Qt::AlignCenter);
// 行高取实际输入控件的推荐高度,适应字体/DPI,纵向拉伸时不拉长每个输入格。
m_pLayerTable->verticalHeader()->setDefaultSectionSize(m_pFirstTopEdit->sizeHint().height() + 4);
m_pLayerDelegate = new nmLayerCellDelegate(m_pLayerTable);
m_pLayerTable->setItemDelegate(m_pLayerDelegate);
m_pLayerPreview = new nmWxLayerGeometryPreview(this);
// 预览只负责绘制/命中查询;窗口接管双击并复用统一的选色流程。
m_pLayerPreview->installEventFilter(this);
QHBoxLayout* body = new QHBoxLayout; // 接收纵向余量的主体区,横向按 5:1 分配表格和预览。
// 主要宽度用于数值表格,侧边预览同步伸展;纵向增大时增加可见行,而非拉高输入格。
body->addWidget(m_pLayerTable, 5);
body->addWidget(m_pLayerPreview, 1);
main->addLayout(body, 1);
QGridLayout* summary = new QGridLayout; // 层数与总厚度统计区,不承担几何编辑。
// 统计使用只读输入框以支持复制;内容来自米制副本,不从表格显示字符串累计。
summary->setHorizontalSpacing(6);
summary->addWidget(new QLabel(tr("Layer count"), this), 0, 0);
m_pLayerCountEdit = new QLineEdit(this);
m_pLayerCountEdit->setReadOnly(true);
m_pLayerCountEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
summary->addWidget(m_pLayerCountEdit, 0, 1);
summary->addWidget(new QLabel(tr("Total thickness"), this), 0, 2);
m_pTotalThicknessEdit = new QLineEdit(this);
m_pTotalThicknessEdit->setReadOnly(true);
m_pTotalThicknessEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
summary->addWidget(m_pTotalThicknessEdit, 0, 3);
summary->setColumnStretch(1, 1);
summary->setColumnStretch(3, 1);
main->addLayout(summary);
QWidget* footer = new QWidget(this); // 底部操作区,随主题采用推荐高度,由对话框负责释放。
footer->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
QHBoxLayout* footerLayout = new QHBoxLayout(footer); // 左侧重置,右侧标准确定/取消按钮。
footerLayout->setContentsMargins(4, 5, 4, 5);
m_pResetButton = new QPushButton(tr("Reset from analytical"), footer);
m_pResetButton->setAutoDefault(false);
footerLayout->addWidget(m_pResetButton);
footerLayout->addStretch();
// 标准按钮容器拥有确定/取消按钮,统一转发接受和拒绝操作。
QDialogButtonBox* box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
Qt::Horizontal, footer);
m_pOkButton = box->button(QDialogButtonBox::Ok);
m_pOkButton->setText(tr("OK"));
m_pOkButton->setIcon(zxLoadIcon("OK"));
m_pOkButton->setDefault(true);
box->button(QDialogButtonBox::Cancel)->setText(tr("Cancel"));
box->button(QDialogButtonBox::Cancel)->setIcon(zxLoadIcon("Cancel"));
footerLayout->addWidget(box);
main->addWidget(footer);
connect(m_pLayerTable, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(onItemChanged(QTableWidgetItem*)));
connect(m_pLayerTable, SIGNAL(itemSelectionChanged()), this, SLOT(onSelectionChanged()));
connect(m_pLengthUnitComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onUnitChanged(int)));
connect(m_pFirstTopEdit, SIGNAL(textEdited(QString)), this, SLOT(onTopEdited()));
connect(m_pFirstTopEdit, SIGNAL(editingFinished()), this, SLOT(onTopFinished()));
connect(m_pAddButton, SIGNAL(clicked()), this, SLOT(onAdd()));
connect(m_pInsertButton, SIGNAL(clicked()), this, SLOT(onInsert()));
connect(m_pDeleteButton, SIGNAL(clicked()), this, SLOT(onDelete()));
connect(m_pResetButton, SIGNAL(clicked()), this, SLOT(onReset()));
connect(box, SIGNAL(accepted()), this, SLOT(accept()));
connect(box, SIGNAL(rejected()), this, SLOT(reject()));
updateActions();
}
bool nmWxGeometryLayerDlg::initialize()
{
// 单位服务与初始副本同时准备成功才开放操作;构造窗口本身不会触发工程数据写入。
if (!m_pDataSource || !m_pLengthUnitGroup || m_pLengthUnitComboBox->currentIndex() < 0
|| !m_pDataSource->load(m_oGeometry) || !canDisplayUnit(m_sDisplayUnit)) return false;
m_bInitialized = true;
refreshTable(0);
adjustSize();
return true;
}
QString nmWxGeometryLayerDlg::displayValue(double meters, bool fullPrecision) const
{
double value = meters; // 框架换算的输出缓冲;成功后为当前显示单位下的数值。
int digits = -1; // 框架返回的目标单位小数位;负数表示未配置,采用有效数字格式。
if (!m_pLengthUnitGroup || !m_pLengthUnitGroup->convert("m", meters, m_sDisplayUnit, value, digits)
|| !qIsFinite(value)) return QString();
// 完整精度用于编辑起始值;普通显示遵循框架小数位设置,两者始终共用同一米制原值。
if (fullPrecision) return QString::number(value, 'g', 17);
// 极小的正厚度不能被显示成零;计算始终使用米制原值,显示精度不参与回写。
const QString text = digits >= 0 ? QString::number(value, 'f', qMin(digits, 15))
: QString::number(value, 'g', 12);
return value != 0.0 && text.toDouble() == 0.0 ? QString::number(value, 'g', 12) : text;
}
bool nmWxGeometryLayerDlg::toMeters(const QString& text, double& value) const
{
// 文本解析成功不代表可用:NaN/无穷大和换算后的溢出都必须在写回前排除。
bool ok = false; // 仅表示文本解析成功,不代替有限值及单位换算检查。
const double input = text.trimmed().toDouble(&ok); // 当前显示单位的输入数值,尚未换算为米。
int digits = -1; // 接收框架附带的精度信息;回写米制值时不按该精度舍入。
return ok && qIsFinite(input) && m_pLengthUnitGroup
&& m_pLengthUnitGroup->convert(m_sDisplayUnit, input, "m", value, digits) && qIsFinite(value);
}
bool nmWxGeometryLayerDlg::canDisplayUnit(const QString& unit) const
{
// 不仅检查单位名称存在,还验证当前各值能在该单位下表达;过程中不改 m_sDisplayUnit 和副本。
if (!m_pLengthUnitGroup || m_oGeometry.m_vecLayers.isEmpty()) return false;
for (int i = 0; i < m_oGeometry.m_vecLayers.size(); ++i) {
const nmLayerGeometryRow& row = m_oGeometry.m_vecLayers.at(i);
// 三项均为米制原值,顺序为顶深、底深、厚度;第 2 项额外检查正厚度换算后没有下溢。
const double lengths[] = { row.m_dTopDepth, row.m_dBottomDepth, row.m_dThickness };
for (int j = 0; j < 3; ++j) {
double converted = 0.0; // 候选显示单位下的值,仅用于可表示性预检。
int digits = -1; // 框架接口的精度输出,此处不生成显示文本。
if (!m_pLengthUnitGroup->convert("m", lengths[j], unit, converted, digits)
|| !qIsFinite(converted) || (j == 2 && row.m_dThickness > 0.0 && converted <= 0.0)) return false;
}
}
double total = 0.0; // 候选单位下的累计层厚度,与单层值分开检查换算溢出。
int digits = -1; // 框架接口的精度输出,不影响累计值本身。
return m_pLengthUnitGroup->convert("m", m_oGeometry.totalThickness(), unit, total, digits) && qIsFinite(total);
}
void nmWxGeometryLayerDlg::refreshTable(int selectedRow)
{
// 批量填表必须屏蔽 itemChanged,避免格式化文本被当作用户编辑再次换算写入。
// 此函数会重建厚度显示并清空错误行,仅用于编辑已收齐或用户明确放弃旧输入的场景。
m_bRefreshing = true;
m_pLayerTable->blockSignals(true);
m_pLayerTable->setRowCount(m_oGeometry.m_vecLayers.size());
m_pLayerTable->setHorizontalHeaderLabels(QStringList() << tr("Layer name")
<< tr("Thickness") + " (" + m_sDisplayUnit + ")"
<< tr("Top depth") + " (" + m_sDisplayUnit + ")"
<< tr("Bottom depth") + " (" + m_sDisplayUnit + ")");
for (int row = 0; row < m_oGeometry.m_vecLayers.size(); ++row) {
const nmLayerGeometryRow& layer = m_oGeometry.m_vecLayers.at(row);
for (int column = 0; column < 4; ++column) {
QTableWidgetItem* item = m_pLayerTable->item(row, column); // 表格拥有的单元格,优先复用已有项。
if (!item) { item = new QTableWidgetItem; m_pLayerTable->setItem(row, column, item); }
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable
| (column < 2 ? Qt::ItemIsEditable : Qt::NoItemFlags));
item->setTextAlignment(column == 0 ? Qt::AlignLeft | Qt::AlignVCenter
: Qt::AlignRight | Qt::AlignVCenter);
}
QTableWidgetItem* name = m_pLayerTable->item(row, 0); // 名称列单元格,同时通过 DecorationRole 显示层色块。
name->setText(layer.m_sName.isEmpty() ? QString("Layer%1").arg(row + 1) : layer.m_sName);
name->setData(Qt::UserRole, name->text());
name->setData(Qt::DecorationRole, layer.m_clrLayer);
m_pLayerTable->item(row, 1)->setText(displayValue(layer.m_dThickness));
// 同一格分别保存“给用户看的精度”和“进入编辑时的精度”,显示舍入不影响底层数据。
m_pLayerTable->item(row, 1)->setData(Qt::UserRole, displayValue(layer.m_dThickness, true));
}
m_setInvalidThicknessRows.clear();
m_sFirstTopDisplayText = m_oGeometry.m_vecLayers.isEmpty()
? QString() : displayValue(m_oGeometry.m_vecLayers.first().m_dTopDepth);
m_pFirstTopEdit->setText(m_sFirstTopDisplayText);
if (selectedRow >= 0 && selectedRow < m_pLayerTable->rowCount()) m_pLayerTable->setCurrentCell(selectedRow, 0);
m_pLayerTable->blockSignals(false);
m_bRefreshing = false;
refreshDerived();
}
void nmWxGeometryLayerDlg::refreshDerived()
{
// 首层顶深尚未提交时暂不展示旧边界,避免输入值与剖面出现矛盾。
const bool valid = m_setInvalidThicknessRows.isEmpty() && m_oGeometry.isValid() && canDisplayUnit(m_sDisplayUnit)
&& m_pFirstTopEdit->text() == m_sFirstTopDisplayText && topIsValid();
m_pLayerTable->blockSignals(true);
for (int row = 0; row < m_oGeometry.m_vecLayers.size(); ++row) {
m_pLayerTable->item(row, 2)->setText(valid ? displayValue(m_oGeometry.m_vecLayers.at(row).m_dTopDepth) : QString());
m_pLayerTable->item(row, 3)->setText(valid ? displayValue(m_oGeometry.m_vecLayers.at(row).m_dBottomDepth) : QString());
}
m_pLayerTable->blockSignals(false);
m_pLayerCountEdit->setText(QString::number(m_oGeometry.m_vecLayers.size()));
m_pTotalThicknessEdit->setText(valid ? displayValue(m_oGeometry.totalThickness()) : QString());
m_pLayerPreview->setGeometryData(valid ? m_oGeometry : nmLayerGeometry(), m_pLayerTable->currentRow());
updateActions();
}
bool nmWxGeometryLayerDlg::topIsValid() const
{
// 文本没有变化时使用原值;文本改变时在候选副本上试算,按钮状态检查不能提前提交。
if (m_pFirstTopEdit->text() == m_sFirstTopDisplayText)
return !m_oGeometry.m_vecLayers.isEmpty()
&& qIsFinite(m_oGeometry.m_vecLayers.first().m_dTopDepth);
double top = 0.0; // 文本换算后的米制首层顶深;只有 toMeters 成功后才使用。
nmLayerGeometry candidate(m_oGeometry); // 仅用于合法性试算,不能替换当前编辑副本。
return toMeters(m_pFirstTopEdit->text(), top) && candidate.setFirstTop(top);
}
void nmWxGeometryLayerDlg::updateActions()
{
const bool valid = m_bInitialized && m_setInvalidThicknessRows.isEmpty() && m_oGeometry.isValid()
&& canDisplayUnit(m_sDisplayUnit) && topIsValid();
const bool selected = m_pLayerTable->currentRow() >= 0; // 是否存在当前层,不表示整行都被选中。
m_pOkButton->setEnabled(valid && !m_bCommitBlocked);
m_pAddButton->setEnabled(valid);
m_pInsertButton->setEnabled(valid && selected);
m_pDeleteButton->setEnabled(m_bInitialized && selected && m_oGeometry.m_vecLayers.size() > 1 && topIsValid());
m_pResetButton->setEnabled(m_bInitialized);
}
bool nmWxGeometryLayerDlg::applyTop()
{
if (m_pFirstTopEdit->text() == m_sFirstTopDisplayText) return topIsValid();
double top = 0.0; // 本次实际准备应用的首层顶深,已通过 toMeters 转换时单位为 m。
if (!toMeters(m_pFirstTopEdit->text(), top) || !m_oGeometry.setFirstTop(top)) return false;
// 只有应用成功才更新文本基线,失败时保留用户原输入以便修正。
m_sFirstTopDisplayText = m_pFirstTopEdit->text();
m_bCommitBlocked = false;
return true;
}
void nmWxGeometryLayerDlg::onTopEdited()
{
m_bCommitBlocked = false;
refreshDerived();
}
void nmWxGeometryLayerDlg::onTopFinished()
{
if (m_bRefreshing || !m_bInitialized) return;
applyTop();
refreshDerived();
}
bool nmWxGeometryLayerDlg::finishEditing()
{
// 委托提交会同步触发 onItemChanged,随后再判断错误行集合,不能提前用旧副本判断成功。
m_pLayerDelegate->finish();
const bool valid = applyTop() && m_setInvalidThicknessRows.isEmpty()
&& m_oGeometry.isValid() && canDisplayUnit(m_sDisplayUnit);
refreshDerived();
return valid;
}
void nmWxGeometryLayerDlg::onItemChanged(QTableWidgetItem* item)
{
if (m_bRefreshing || !m_bInitialized || !item) return;
const int row = item->row(); // 表格与几何副本共用的零起始层索引。
if (row < 0 || row >= m_oGeometry.m_vecLayers.size()) return;
m_bCommitBlocked = false;
if (item->column() == 0) {
// 层名是显示属性,直接更新副本即可,不触发顶底深度重排。
m_oGeometry.m_vecLayers[row].m_sName = item->text();
m_pLayerTable->blockSignals(true);
item->setData(Qt::UserRole, item->text());
m_pLayerTable->blockSignals(false);
} else if (item->column() == 1) {
double thickness = 0.0; // 换算后的米制厚度候选值,合法时才用于重排副本。
const bool valid = toMeters(item->text(), thickness) && m_oGeometry.setThickness(row, thickness);
// 几何函数失败时副本保持原值,但错误标记必须阻止保存,不能悄悄提交旧厚度。
if (valid) m_setInvalidThicknessRows.remove(row);
else m_setInvalidThicknessRows.insert(row);
m_pLayerTable->blockSignals(true);
// 非法文本留在单元格和再次编辑的起始值中,禁止确定,不用弹窗打断用户。
item->setData(Qt::UserRole, valid ? displayValue(m_oGeometry.m_vecLayers.at(row).m_dThickness, true) : item->text());
if (valid) item->setText(displayValue(m_oGeometry.m_vecLayers.at(row).m_dThickness));
m_pLayerTable->blockSignals(false);
}
refreshDerived();
}
void nmWxGeometryLayerDlg::onUnitChanged(int index)
{
if (m_bRefreshing || !m_bInitialized || index < 0) return;
const QString nextUnit = m_pLengthUnitComboBox->itemText(index); // 候选单位,通过输入检查后才成为当前单位。
if (nextUnit == m_sDisplayUnit) return;
// 下拉框此时已显示新选项,但活动文本仍属于旧单位;先收齐输入再改 m_sDisplayUnit。
// 失败时仅恢复选项,不全量刷新表格,否则会覆盖用户尚待修正的非法文本。
if (!finishEditing() || !canDisplayUnit(nextUnit)) {
m_pLengthUnitComboBox->blockSignals(true);
m_pLengthUnitComboBox->setCurrentIndex(m_pLengthUnitComboBox->findText(m_sDisplayUnit));
m_pLengthUnitComboBox->blockSignals(false);
return;
}
m_sDisplayUnit = nextUnit;
refreshTable(m_pLayerTable->currentRow());
}
void nmWxGeometryLayerDlg::onSelectionChanged()
{
if (!m_bRefreshing && m_bInitialized) refreshDerived();
}
QString nmWxGeometryLayerDlg::nextLayerName() const
{
// 从现有名称查重,删除中间层后可复用空闲编号,但不能重命名其他层。
QStringList names; // 当前层的实际显示名称,包含旧工程空名称对应的默认 LayerN。
for (int i = 0; i < m_oGeometry.m_vecLayers.size(); ++i)
names.append(m_oGeometry.m_vecLayers.at(i).m_sName.isEmpty() ? QString("Layer%1").arg(i + 1)
: m_oGeometry.m_vecLayers.at(i).m_sName);
int suffix = 1; // 从 1 起查找未占用的 LayerN 编号,不等同于新层的行号。
while (names.contains(QString("Layer%1").arg(suffix))) ++suffix;
return QString("Layer%1").arg(suffix);
}
void nmWxGeometryLayerDlg::insertAt(int row)
{
if (!finishEditing()) return;
// 新层使用当前层厚度作为起始值,不再引入与分析数据无关的固定厚度。
const int reference = qBound(0, m_pLayerTable->currentRow(), m_oGeometry.m_vecLayers.size() - 1); // 参考层索引。
const double thickness = m_oGeometry.m_vecLayers.at(reference).m_dThickness; // 新层采用的初始米制厚度。
if (m_oGeometry.insertLayer(row, nextLayerName(), thickness,
QColor::fromHsv((105 + m_oGeometry.m_vecLayers.size() * 47) % 360, 90, 210))) {
m_bCommitBlocked = false;
refreshTable(row);
}
}
void nmWxGeometryLayerDlg::onAdd() { insertAt(m_oGeometry.m_vecLayers.size()); }
void nmWxGeometryLayerDlg::onInsert() { insertAt(m_pLayerTable->currentRow()); }
void nmWxGeometryLayerDlg::onDelete()
{
m_pLayerDelegate->finish();
if (!applyTop()) return;
const int row = m_pLayerTable->currentRow();
// 允许删除含非法厚度文本的当前行,但不能丢弃其他行尚未修正的输入。
QSet<int> remaining = m_setInvalidThicknessRows; // 临时错误行集合,排除被删行后判断是否还有其他未修正输入。
remaining.remove(row);
if (!remaining.isEmpty() || !m_oGeometry.removeLayer(row)) return;
m_bCommitBlocked = false;
refreshTable(qMin(row, m_oGeometry.m_vecLayers.size() - 1));
}
void nmWxGeometryLayerDlg::chooseLayerColor(int nRow)
{
if (nRow < 0 || nRow >= m_oGeometry.m_vecLayers.size()) return;
// 选色结果只写入编辑副本;取消选色返回无效 QColor,保持原有层颜色。
const QColor color = QColorDialog::getColor(m_oGeometry.m_vecLayers.at(nRow).m_clrLayer, this, tr("Layer color"));
if (!color.isValid()) return;
m_oGeometry.m_vecLayers[nRow].m_clrLayer = color;
m_bCommitBlocked = false;
refreshTable(nRow);
}
bool nmWxGeometryLayerDlg::eventFilter(QObject* pWatched, QEvent* pEvent)
{
if (pWatched == m_pLayerPreview && pEvent->type() == QEvent::MouseButtonDblClick) {
QMouseEvent* pMouseEvent = static_cast<QMouseEvent*>(pEvent); // 借用本次鼠标事件,坐标属于右侧预览。
if (pMouseEvent->button() == Qt::LeftButton) {
// 先完成可能尚未失焦的厚度/顶深编辑,让命中计算基于最新有效边界。
if (m_bInitialized && finishEditing()) {
const int nRow = m_pLayerPreview->layerAtPoint(pMouseEvent->pos()); // -1 表示点在间隙或边距。
if (nRow >= 0) {
m_pLayerTable->setCurrentCell(nRow, 0);
chooseLayerColor(nRow);
}
}
pMouseEvent->accept();
return true;
}
}
return iDlgBase::eventFilter(pWatched, pEvent);
}
void nmWxGeometryLayerDlg::onReset()
{
// 重置是显式放弃当前层编辑,非法厚度也允许直接用所属分析重新初始化。
m_pLayerDelegate->finish();
applyTop();
// 顶深输入合法时沿用新值;非法时沿用副本中上一次合法顶深,重置不采用非法文本。
const double top = m_oGeometry.m_vecLayers.isEmpty() ? 6000.0 : m_oGeometry.m_vecLayers.first().m_dTopDepth;
nmLayerGeometry reset; // 从分析读取的候选副本,完整读取并校验后才替换当前编辑内容。
if (!m_pDataSource || !m_pDataSource->loadFromAnalysis(top, reset) || !reset.isValid()) return;
m_oGeometry = reset;
m_bCommitBlocked = false;
refreshTable(0);
}
void nmWxGeometryLayerDlg::accept()
{
// 包括键盘回车和活动单元格尚未失焦的情况,都必须先收齐输入。
if (!m_bInitialized || !finishEditing()) return;
if (!m_pDataSource || !m_pDataSource->commit(m_oGeometry)) {
// 数据源拒绝提交时保留完整编辑现场,不把窗口关闭伪装成保存成功。
m_bCommitBlocked = true;
updateActions();
return;
}
iDlgBase::accept();
}