#include "nmWxVolumeSliceSettingsDlg.h" #include "nmParameterField.h" #include #include #include #include #include #include #include #include #include #include #include #include #include nmWxVolumeSliceSettingsDlg::nmWxVolumeSliceSettingsDlg(QWidget* pParent) : iDlgBase(pParent), m_pShowPlaneCheck(nullptr), m_pShowSourceCheck(nullptr), m_pErrorLabel(nullptr), m_pApplyButton(nullptr), m_bUpdating(false) { createUi(); writeSettings(m_oApplied); resize(minimumWidth(), sizeHint().height()); } void nmWxVolumeSliceSettingsDlg::createUi() { setWindowTitle(tr("Slice Settings")); setObjectName(QLatin1String("volumeSliceSettingsDialog")); setModal(false); setAttribute(Qt::WA_DeleteOnClose, false); // 复用编辑井、储层特性的基类主题,同时保留工具窗口的非模态交互及 Esc 关闭行为。 setWindowFlags((windowFlags() & ~Qt::WindowType_Mask & ~Qt::WindowContextHelpButtonHint) | Qt::Tool); setIgnoreEsc(false); // 六个分量各占一行,共用标签列和输入列;原点区与法向区分别放置对应快捷操作。 const int nEditorHeight = nmParameterField::editorHeight(); const int nEditorMinimumWidth = nmParameterField::editorMinimumWidth(); QGroupBox* pPlaneGroup = new QGroupBox(tr("Plane Parameters"), this); pPlaneGroup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); QGridLayout* pPlaneLayout = new QGridLayout(pPlaneGroup); pPlaneLayout->setContentsMargins(8, 15, 8, 10); pPlaneLayout->setHorizontalSpacing(6); pPlaneLayout->setVerticalSpacing(6); pPlaneLayout->setColumnStretch(1, 1); int nFieldLabelWidth = 0; const char* pAxes[3] = { "X", "Y", "Z" }; for(int nAxis = 0; nAxis < 3; ++nAxis) { QLabel* pOriginLabel = new QLabel(tr("Origin %1").arg(QLatin1String(pAxes[nAxis])), pPlaneGroup); QLabel* pNormalLabel = new QLabel(tr("Normal %1").arg(QLatin1String(pAxes[nAxis])), pPlaneGroup); pOriginLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); pNormalLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); nFieldLabelWidth = qMax(nFieldLabelWidth, qMax(pOriginLabel->sizeHint().width(), pNormalLabel->sizeHint().width())); pPlaneLayout->addWidget(pOriginLabel, nAxis, 0); pPlaneLayout->addWidget(pNormalLabel, nAxis + 5, 0); m_pOriginEdits[nAxis] = new QLineEdit(pPlaneGroup); m_pNormalEdits[nAxis] = new QLineEdit(pPlaneGroup); m_pOriginEdits[nAxis]->setObjectName(QString::fromLatin1("sliceOrigin") + QLatin1String(pAxes[nAxis])); m_pNormalEdits[nAxis]->setObjectName(QString::fromLatin1("sliceNormal") + QLatin1String(pAxes[nAxis])); m_pOriginEdits[nAxis]->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); m_pNormalEdits[nAxis]->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); m_pOriginEdits[nAxis]->setMinimumWidth(nEditorMinimumWidth); m_pNormalEdits[nAxis]->setMinimumWidth(nEditorMinimumWidth); m_pOriginEdits[nAxis]->setFixedHeight(nEditorHeight); m_pNormalEdits[nAxis]->setFixedHeight(nEditorHeight); m_pOriginEdits[nAxis]->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_pNormalEdits[nAxis]->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); pPlaneLayout->addWidget(m_pOriginEdits[nAxis], nAxis, 1); pPlaneLayout->addWidget(m_pNormalEdits[nAxis], nAxis + 5, 1); connect(m_pOriginEdits[nAxis], SIGNAL(textChanged(QString)), this, SLOT(onDraftChanged())); connect(m_pNormalEdits[nAxis], SIGNAL(textChanged(QString)), this, SLOT(onDraftChanged())); } // 模型中心仅调整原点,放在原点 Z 下方,保持按钮紧凑并与输入框右边界对齐。 QToolButton* pCenterButton = new QToolButton(pPlaneGroup); pCenterButton->setText(tr("Model Center")); pCenterButton->setAutoRaise(true); pCenterButton->setMinimumWidth(72); pCenterButton->setFixedHeight(nEditorHeight); pCenterButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); pPlaneLayout->addWidget(pCenterButton, 3, 1, Qt::AlignRight); pPlaneLayout->setRowMinimumHeight(4, 6); connect(pCenterButton, SIGNAL(clicked()), this, SLOT(onCenterRequested())); // 控件按轴成对创建,显式调整 Tab 顺序,使键盘输入顺序与从上到下的布局一致。 setTabOrder(m_pOriginEdits[0], m_pOriginEdits[1]); setTabOrder(m_pOriginEdits[1], m_pOriginEdits[2]); setTabOrder(m_pOriginEdits[2], pCenterButton); setTabOrder(pCenterButton, m_pNormalEdits[0]); setTabOrder(m_pNormalEdits[0], m_pNormalEdits[1]); setTabOrder(m_pNormalEdits[1], m_pNormalEdits[2]); // 三个方向按钮放在法向 Z 下方,等宽分配输入列空间,整体左右边界与输入框一致。 QHBoxLayout* pShortcutLayout = new QHBoxLayout; pShortcutLayout->setContentsMargins(0, 0, 0, 0); pShortcutLayout->setSpacing(6); const char* pPlanes[3] = { "XY", "XZ", "YZ" }; QWidget* pPreviousShortcutWidget = m_pNormalEdits[2]; for(int nPlane = 0; nPlane < 3; ++nPlane) { QToolButton* pButton = new QToolButton(pPlaneGroup); pButton->setText(QString::fromLatin1(pPlanes[nPlane])); pButton->setProperty("normalAxis", 2 - nPlane); pButton->setAutoRaise(true); pButton->setMinimumWidth(44); pButton->setFixedHeight(nEditorHeight); pButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); pShortcutLayout->addWidget(pButton, 1); setTabOrder(pPreviousShortcutWidget, pButton); pPreviousShortcutWidget = pButton; connect(pButton, SIGNAL(clicked()), this, SLOT(onAxisRequested())); } pPlaneLayout->addLayout(pShortcutLayout, 8, 1); // 着色属性及渲染模式统一使用旧底栏;浮动窗口只保留平面相关的显示选项。 QGroupBox* pDisplayGroup = new QGroupBox(tr("Display Options"), this); pDisplayGroup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); QGridLayout* pDisplayLayout = new QGridLayout(pDisplayGroup); pDisplayLayout->setContentsMargins(8, 15, 8, 10); pDisplayLayout->setHorizontalSpacing(6); pDisplayLayout->setVerticalSpacing(6); pPlaneLayout->setColumnMinimumWidth(0, nFieldLabelWidth); pDisplayLayout->setColumnMinimumWidth(0, nFieldLabelWidth); pDisplayLayout->setColumnStretch(1, 1); m_pShowPlaneCheck = new QCheckBox(tr("Show Plane"), pDisplayGroup); m_pShowSourceCheck = new QCheckBox(tr("Show Original Model"), pDisplayGroup); m_pShowPlaneCheck->setObjectName(QLatin1String("sliceShowPlane")); m_pShowSourceCheck->setObjectName(QLatin1String("sliceShowSource")); // 拖动说明放在显示平面的悬停提示中,减少常驻文字对参数布局的干扰。 m_pShowPlaneCheck->setToolTip(tr("Drag the center or outline to move the plane; drag arrows to rotate.")); QHBoxLayout* pDisplayOptionsLayout = new QHBoxLayout; pDisplayOptionsLayout->setContentsMargins(0, 0, 0, 0); pDisplayOptionsLayout->setSpacing(12); pDisplayOptionsLayout->addWidget(m_pShowPlaneCheck); pDisplayOptionsLayout->addWidget(m_pShowSourceCheck); pDisplayOptionsLayout->addStretch(); pDisplayLayout->addLayout(pDisplayOptionsLayout, 0, 1); setTabOrder(pPreviousShortcutWidget, m_pShowPlaneCheck); setTabOrder(m_pShowPlaneCheck, m_pShowSourceCheck); connect(m_pShowPlaneCheck, SIGNAL(toggled(bool)), this, SLOT(onDisplayOptionsChanged())); connect(m_pShowSourceCheck, SIGNAL(toggled(bool)), this, SLOT(onDisplayOptionsChanged())); m_pErrorLabel = new QLabel(this); m_pErrorLabel->setWordWrap(true); QPalette oErrorPalette = m_pErrorLabel->palette(); oErrorPalette.setColor(QPalette::WindowText, QColor(Qt::red)); m_pErrorLabel->setPalette(oErrorPalette); m_pErrorLabel->hide(); // 底部沿用编辑井的边距及框架图标,应用型窗口的三个操作集中靠右排列。 QHBoxLayout* pFooterLayout = new QHBoxLayout; pFooterLayout->setContentsMargins(4, 5, 4, 5); pFooterLayout->setSpacing(8); pFooterLayout->addStretch(); QPushButton* pResetButton = new QPushButton(tr("Reset"), this); m_pApplyButton = new QPushButton(tr("Apply"), this); QPushButton* pCloseButton = new QPushButton(tr("Close"), this); m_pApplyButton->setObjectName(QLatin1String("sliceApplyButton")); m_pApplyButton->setDefault(true); pResetButton->setAutoDefault(false); pCloseButton->setAutoDefault(false); pResetButton->setMinimumWidth(72); m_pApplyButton->setMinimumWidth(72); pCloseButton->setMinimumWidth(72); pCloseButton->setIcon(zxLoadIcon("Cancel")); pCloseButton->setIconSize(QSize(16, 16)); pFooterLayout->addWidget(pResetButton); pFooterLayout->addWidget(m_pApplyButton); pFooterLayout->addWidget(pCloseButton); connect(m_pApplyButton, SIGNAL(clicked()), this, SLOT(onApplyRequested())); connect(pResetButton, SIGNAL(clicked()), this, SLOT(onResetRequested())); connect(pCloseButton, SIGNAL(clicked()), this, SLOT(close())); QVBoxLayout* pMainLayout = new QVBoxLayout(this); pMainLayout->setContentsMargins(12, 10, 12, 0); pMainLayout->setSpacing(10); pMainLayout->addWidget(pPlaneGroup); pMainLayout->addWidget(pDisplayGroup); pMainLayout->addWidget(m_pErrorLabel); pMainLayout->addStretch(); pMainLayout->addLayout(pFooterLayout); setMinimumWidth(400); } void nmWxVolumeSliceSettingsDlg::setData(const QSharedPointer& pData, const nmVolumeSliceSettings& oApplied) { m_pData = pData; setAppliedSettings(oApplied); } void nmWxVolumeSliceSettingsDlg::setAppliedSettings(const nmVolumeSliceSettings& oSettings) { m_oApplied = oSettings; writeSettings(oSettings); } void nmWxVolumeSliceSettingsDlg::setAppliedProperty(const QString& sPropertyId) { // 底栏属性立即生效;重置、关闭窗口及再次应用平面时也应继续使用此属性。 m_oApplied.m_sPropertyId = sPropertyId; } bool nmWxVolumeSliceSettingsDlg::getDraft(nmVolumeSliceSettings& oSettings, QString* pError) const { if(pError != nullptr) pError->clear(); if(m_pData.isNull()) return false; nmVolumeSliceSettings oDraft = m_oApplied; for(int nAxis = 0; nAxis < 3; ++nAxis) { bool bOriginOk = false; bool bNormalOk = false; oDraft.m_dOrigin[nAxis] = m_pOriginEdits[nAxis]->text().trimmed().toDouble(&bOriginOk); oDraft.m_dNormal[nAxis] = m_pNormalEdits[nAxis]->text().trimmed().toDouble(&bNormalOk); if(!bOriginOk || !bNormalOk) { if(pError != nullptr) *pError = tr("Enter valid numbers for origin and normal."); return false; } } if(!oDraft.normalize(pError)) return false; oDraft.m_bShowPlane = m_pShowPlaneCheck->isChecked(); oDraft.m_bShowSource = m_pShowSourceCheck->isChecked(); oSettings = oDraft; return true; } void nmWxVolumeSliceSettingsDlg::writeSettings(const nmVolumeSliceSettings& oSettings) { m_bUpdating = true; for(int nAxis = 0; nAxis < 3; ++nAxis) { m_pOriginEdits[nAxis]->setText(QString::number(oSettings.m_dOrigin[nAxis], 'g', 17)); m_pNormalEdits[nAxis]->setText(QString::number(oSettings.m_dNormal[nAxis], 'g', 17)); } m_pShowPlaneCheck->setChecked(oSettings.m_bShowPlane); m_pShowSourceCheck->setChecked(oSettings.m_bShowSource); m_bUpdating = false; updateValidation(); } void nmWxVolumeSliceSettingsDlg::setPlanePreview(const nmVolumeSliceSettings& oSettings) { m_bUpdating = true; for(int nAxis = 0; nAxis < 3; ++nAxis) { m_pOriginEdits[nAxis]->setText(QString::number(oSettings.m_dOrigin[nAxis], 'g', 17)); m_pNormalEdits[nAxis]->setText(QString::number(oSettings.m_dNormal[nAxis], 'g', 17)); } m_bUpdating = false; updateValidation(); } void nmWxVolumeSliceSettingsDlg::setError(const QString& sError) { m_pErrorLabel->setText(sError); m_pErrorLabel->setVisible(!sError.isEmpty()); } void nmWxVolumeSliceSettingsDlg::updateValidation() { nmVolumeSliceSettings oDraft; QString sError; m_pApplyButton->setEnabled(getDraft(oDraft, &sError)); setError(sError); } void nmWxVolumeSliceSettingsDlg::onDraftChanged() { if(m_bUpdating) return; updateValidation(); if(m_pApplyButton->isEnabled()) emit previewChanged(); } void nmWxVolumeSliceSettingsDlg::onAxisRequested() { const int nNormalAxis = sender()->property("normalAxis").toInt(); m_bUpdating = true; for(int nAxis = 0; nAxis < 3; ++nAxis) m_pNormalEdits[nAxis]->setText(nAxis == nNormalAxis ? "1" : "0"); m_bUpdating = false; onDraftChanged(); } void nmWxVolumeSliceSettingsDlg::onCenterRequested() { if(m_pData.isNull()) return; const nmVolumeSliceSettings oCenter = nmVolumeSliceSettings::fromData(*m_pData); m_bUpdating = true; for(int nAxis = 0; nAxis < 3; ++nAxis) m_pOriginEdits[nAxis]->setText(QString::number(oCenter.m_dOrigin[nAxis], 'g', 17)); m_bUpdating = false; onDraftChanged(); } void nmWxVolumeSliceSettingsDlg::onResetRequested() { writeSettings(m_oApplied); emit previewChanged(); } void nmWxVolumeSliceSettingsDlg::onApplyRequested() { updateValidation(); if(m_pApplyButton->isEnabled()) emit applyRequested(); } void nmWxVolumeSliceSettingsDlg::onDisplayOptionsChanged() { if(m_bUpdating) return; m_oApplied.m_bShowPlane = m_pShowPlaneCheck->isChecked(); m_oApplied.m_bShowSource = m_pShowSourceCheck->isChecked(); emit displayOptionsChanged(m_oApplied.m_bShowPlane, m_oApplied.m_bShowSource); } void nmWxVolumeSliceSettingsDlg::hideEvent(QHideEvent* pEvent) { // 最小化产生的系统隐藏并非用户关闭,保留草稿与绑定以便还原窗口后继续操作。 if(pEvent->spontaneous()) { iDlgBase::hideEvent(pEvent); return; } writeSettings(m_oApplied); iDlgBase::hideEvent(pEvent); emit dialogHidden(); }