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