#include "nmWxPebiResultViewWidget.h" #include "iUnitGroup.h" #include "iUnitHelper.h" #include "nmDataAnalyzeManager.h" #include "nmFFmpegVideoWriter.h" #include "nmPebiResultAnimationExporter.h" #include "nmPebiResultDisplaySettings.h" #include "nmPebiResultPlaybackController.h" #include "nmPebiResultPropertyCatalog.h" #include "nmPebiResultPropertyDisplayText.h" #include "nmPebiResultSceneController.h" #include "nmPebiResultSnapshot.h" #include "nmVTKMeshLayer.h" #include "nmVTKViewCommandController.h" #include "nmWxPebiResultControlBar.h" #include "nmWxPebiResultAnimationExportDlg.h" #include "nmWxVTKRenderContainerWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if !defined(_MSC_VER) #include #endif namespace { QAtomicInt g_nPebiAnimationExportActive(0); QString resultViewIconPath(const QString& sFileName) { const QString sApplicationDir = QCoreApplication::applicationDirPath(); return sApplicationDir.section('/', 0, -2) + "/Res/Icon/" + sFileName; } bool isResultViewGuiThread() { QCoreApplication* pApplication = QCoreApplication::instance(); return pApplication == nullptr || pApplication->thread() == QThread::currentThread(); } void setActionCheckedWithoutSignal(QAction* pAction, bool bChecked) { if(pAction == nullptr) { return; } const bool bBlocked = pAction->blockSignals(true); pAction->setChecked(bChecked); pAction->blockSignals(bBlocked); } bool isFiniteFilterEditorValue(double dValue) { #if defined(_MSC_VER) return _finite(dValue) != 0; #else return std::isfinite(dValue); #endif } class AnimationExportGuard { public: explicit AnimationExportGuard(nmWxPebiResultControlBar* pControlBar) : m_pControlBar(pControlBar), m_bAcquired(g_nPebiAnimationExportActive.testAndSetOrdered(0, 1)) { if(m_bAcquired && m_pControlBar != nullptr) { m_pControlBar->setExportInProgress(true); } } ~AnimationExportGuard() { release(); } void release() { if(!m_bAcquired) { return; } if(m_pControlBar != nullptr) { m_pControlBar->setExportInProgress(false); } g_nPebiAnimationExportActive.fetchAndStoreOrdered(0); m_bAcquired = false; } bool isAcquired() const { return m_bAcquired; } private: QPointer m_pControlBar; bool m_bAcquired; }; } nmWxPebiResultFilterSettingsDlg::nmWxPebiResultFilterSettingsDlg( nmPebiResultSceneController* pSceneController, nmWxPebiResultControlBar* pControlBar, QWidget* pParent) : iDlgBase(pParent), m_pSceneController(pSceneController), m_pControlBar(pControlBar), m_nExpectedBindingRevision(0), m_nExpectedDisplayFrameIndex(-1), m_pUnitGroup(nullptr), m_bInitialized(false), m_pEnableCheck(nullptr), m_pPropertyCombo(nullptr), m_pMinimumCheck(nullptr), m_pMinimumEdit(nullptr), m_pMaximumCheck(nullptr), m_pMaximumEdit(nullptr), m_pUnitCombo(nullptr), m_pApplyButton(nullptr), m_pOkButton(nullptr) { createUi(); m_bInitialized = initializeFromScene(); if(!m_bInitialized) { m_pApplyButton->setEnabled(false); m_pOkButton->setEnabled(false); QMessageBox::warning( this, tr("Filtering settings"), tr("Unable to read the current result filter settings.")); } } nmWxPebiResultFilterSettingsDlg::~nmWxPebiResultFilterSettingsDlg() { } void nmWxPebiResultFilterSettingsDlg::createUi() { setWindowTitle(tr("Filtering settings")); setModal(true); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); m_pEnableCheck = new QCheckBox(tr("Enable filtering"), this); m_pPropertyCombo = new QComboBox(this); m_pPropertyCombo->setSizeAdjustPolicy( QComboBox::AdjustToContentsOnFirstShow); m_pPropertyCombo->setMinimumWidth(220); m_pMinimumCheck = new QCheckBox(tr("Above min (>=)"), this); m_pMinimumEdit = new QLineEdit(this); m_pMinimumEdit->setAlignment(Qt::AlignRight); m_pMaximumCheck = new QCheckBox(tr("Below max (<=)"), this); m_pMaximumEdit = new QLineEdit(this); m_pMaximumEdit->setAlignment(Qt::AlignRight); m_pUnitCombo = new QComboBox(this); m_pUnitCombo->setMinimumWidth(80); m_pUnitCombo->setMaximumWidth(140); m_pUnitCombo->hide(); m_pMinimumEdit->setMinimumWidth(150); m_pMaximumEdit->setMinimumWidth(150); QGridLayout* pFieldsLayout = new QGridLayout(); pFieldsLayout->setContentsMargins(0, 0, 0, 0); pFieldsLayout->setHorizontalSpacing(8); pFieldsLayout->setVerticalSpacing(6); pFieldsLayout->addWidget(m_pEnableCheck, 0, 0, 1, 3); QLabel* pPropertyLabel = new QLabel(tr("Filter on"), this); pPropertyLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); pFieldsLayout->addWidget(pPropertyLabel, 1, 0); pFieldsLayout->addWidget(m_pPropertyCombo, 1, 1, 1, 2); pFieldsLayout->addWidget(m_pMinimumCheck, 2, 0); pFieldsLayout->addWidget(m_pMinimumEdit, 2, 1); pFieldsLayout->addWidget(m_pUnitCombo, 2, 2, 2, 1, Qt::AlignVCenter); pFieldsLayout->addWidget(m_pMaximumCheck, 3, 0); pFieldsLayout->addWidget(m_pMaximumEdit, 3, 1); pFieldsLayout->setColumnStretch(1, 1); // 普通布局不受平台按钮顺序策略影响,始终保持应用、确定、取消。 QHBoxLayout* pButtonLayout = new QHBoxLayout(); pButtonLayout->addStretch(); m_pApplyButton = new QPushButton(tr("Apply"), this); m_pOkButton = new QPushButton(tr("OK"), this); QPushButton* pCancelButton = new QPushButton(tr("Cancel"), this); const QSize oFooterIconSize(16, 16); m_pOkButton->setIcon(QIcon(resultViewIconPath("OK.png"))); pCancelButton->setIcon(QIcon(resultViewIconPath("Cancel.png"))); m_pOkButton->setIconSize(oFooterIconSize); pCancelButton->setIconSize(oFooterIconSize); m_pApplyButton->setMinimumWidth(72); m_pOkButton->setMinimumWidth(72); pCancelButton->setMinimumWidth(72); m_pApplyButton->setAutoDefault(false); m_pOkButton->setDefault(true); pCancelButton->setAutoDefault(false); pButtonLayout->setContentsMargins(4, 2, 4, 5); pButtonLayout->setSpacing(6); pButtonLayout->addWidget(m_pApplyButton); pButtonLayout->addWidget(m_pOkButton); pButtonLayout->addWidget(pCancelButton); QVBoxLayout* pMainLayout = new QVBoxLayout(this); pMainLayout->setContentsMargins(12, 10, 12, 0); pMainLayout->setSpacing(8); pMainLayout->addLayout(pFieldsLayout); pMainLayout->addLayout(pButtonLayout); setMinimumWidth(430); connect(m_pApplyButton, SIGNAL(clicked()), this, SLOT(onApplyClicked())); connect(m_pOkButton, SIGNAL(clicked()), this, SLOT(onOkClicked())); connect(pCancelButton, SIGNAL(clicked()), this, SLOT(reject())); connect(m_pPropertyCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onPropertyChanged(int))); connect(m_pUnitCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onDisplayUnitChanged(int))); connect(m_pEnableCheck, SIGNAL(toggled(bool)), this, SLOT(refreshEditorEnabledState())); connect(m_pMinimumCheck, SIGNAL(toggled(bool)), this, SLOT(refreshEditorEnabledState())); connect(m_pMaximumCheck, SIGNAL(toggled(bool)), this, SLOT(refreshEditorEnabledState())); } bool nmWxPebiResultFilterSettingsDlg::initializeFromScene() { if(m_pSceneController == nullptr || m_pControlBar == nullptr || !m_pSceneController->hasSnapshot()) { return false; } m_nExpectedBindingRevision = m_pSceneController->getSnapshotBindingRevision(); m_sExpectedSnapshotId = m_pSceneController->getCurrentSnapshotId(); m_sExpectedDisplayPropertyId = m_pSceneController->getCurrentPropertyId(); m_nExpectedDisplayFrameIndex = m_pSceneController->getCurrentFrameIndex(); if(m_nExpectedBindingRevision == 0 || m_sExpectedSnapshotId.isEmpty() || m_sExpectedDisplayPropertyId.isEmpty() || m_nExpectedDisplayFrameIndex < 0 || !m_pSceneController->getFilterSettings(m_oBaselineSettings)) { return false; } const bool bSignalsBlocked = m_pPropertyCombo->blockSignals(true); const QStringList listPropertyIds = m_pSceneController->getAvailablePropertyIds(); for(int nIndex = 0; nIndex < listPropertyIds.size(); ++nIndex) { const QString& sPropertyId = listPropertyIds[nIndex]; double dRange[2] = { 0.0, 0.0 }; if(!m_pSceneController->getPropertyRange(sPropertyId, dRange)) { m_pPropertyCombo->blockSignals(bSignalsBlocked); return false; } m_mapPropertyRanges.insert( sPropertyId, qMakePair(dRange[0], dRange[1])); QString sDisplayName = nmPebiResultPropertyDisplayText::getDisplayName(sPropertyId); if(sDisplayName.isEmpty()) { sDisplayName = sPropertyId; } m_pPropertyCombo->addItem(sDisplayName, sPropertyId); } m_pPropertyCombo->blockSignals(bSignalsBlocked); if(m_pPropertyCombo->count() <= 0) { return false; } if(m_oBaselineSettings.m_sPropertyId.isEmpty() || !m_mapPropertyRanges.contains( m_oBaselineSettings.m_sPropertyId)) { m_oBaselineSettings.m_sPropertyId = m_sExpectedDisplayPropertyId; const QPair oRange = m_mapPropertyRanges.value( m_oBaselineSettings.m_sPropertyId); m_oBaselineSettings.m_bMinimumEnabled = true; m_oBaselineSettings.m_dMinimum = oRange.first; m_oBaselineSettings.m_bMaximumEnabled = true; m_oBaselineSettings.m_dMaximum = oRange.second; } return populateSettings(m_oBaselineSettings); } bool nmWxPebiResultFilterSettingsDlg::initializeUnitEditor( const QString& sPropertyId) { nmPebiResultPropertyDescriptor oDescriptor; if(!nmPebiResultPropertyCatalog::findDescriptor( sPropertyId, oDescriptor)) { return false; } const bool bSignalsBlocked = m_pUnitCombo->blockSignals(true); m_pUnitCombo->clear(); m_pUnitGroup = nullptr; m_sBaseUnit = oDescriptor.m_sBaseUnit; m_sDisplayUnit = oDescriptor.m_sDefaultDisplayUnit.isEmpty() ? m_sBaseUnit : oDescriptor.m_sDefaultDisplayUnit; if(m_sBaseUnit.isEmpty()) { m_sDisplayUnit.clear(); m_pUnitCombo->setEnabled(false); m_pUnitCombo->hide(); m_pUnitCombo->blockSignals(bSignalsBlocked); return true; } m_pUnitGroup = iUnitHelper::getUnitGroupByUnit(m_sBaseUnit); QStringList listUnits; if(m_pUnitGroup != nullptr && m_pUnitGroup->indexOf(m_sBaseUnit) >= 0) { listUnits = m_pUnitGroup->getAllUnitNames(); } const bool bHasUnitGroup = !listUnits.isEmpty() && listUnits.contains(m_sBaseUnit); if(!bHasUnitGroup) { m_pUnitGroup = nullptr; listUnits.clear(); listUnits.append(m_sBaseUnit); m_sDisplayUnit = m_sBaseUnit; } else if(!listUnits.contains(m_sDisplayUnit)) { m_sDisplayUnit = m_sBaseUnit; } m_pUnitCombo->addItems(listUnits); m_pUnitCombo->setCurrentIndex( m_pUnitCombo->findText(m_sDisplayUnit)); m_pUnitCombo->setEnabled(bHasUnitGroup); m_pUnitCombo->show(); m_pUnitCombo->blockSignals(bSignalsBlocked); return true; } bool nmWxPebiResultFilterSettingsDlg::populateSettings( const nmPebiResultFilterSettings& oSettings) { const bool bPropertySignalsBlocked = m_pPropertyCombo->blockSignals(true); const int nPropertyIndex = m_pPropertyCombo->findData( oSettings.m_sPropertyId); if(nPropertyIndex < 0 || !initializeUnitEditor(oSettings.m_sPropertyId)) { m_pPropertyCombo->blockSignals(bPropertySignalsBlocked); return false; } m_pPropertyCombo->setCurrentIndex(nPropertyIndex); m_pPropertyCombo->blockSignals(bPropertySignalsBlocked); double dDisplayMinimum = 0.0; double dDisplayMaximum = 0.0; if(!convertUnitValue( oSettings.m_dMinimum, m_sBaseUnit, m_sDisplayUnit, dDisplayMinimum) || !convertUnitValue( oSettings.m_dMaximum, m_sBaseUnit, m_sDisplayUnit, dDisplayMaximum)) { return false; } m_pEnableCheck->setChecked(oSettings.m_bEnabled); m_pMinimumCheck->setChecked(oSettings.m_bMinimumEnabled); m_pMaximumCheck->setChecked(oSettings.m_bMaximumEnabled); m_pMinimumEdit->setText(formatValue(dDisplayMinimum)); m_pMaximumEdit->setText(formatValue(dDisplayMaximum)); refreshEditorEnabledState(); return true; } void nmWxPebiResultFilterSettingsDlg::onApplyClicked() { applyEditor(); } void nmWxPebiResultFilterSettingsDlg::onOkClicked() { if(applyEditor()) { accept(); } } void nmWxPebiResultFilterSettingsDlg::onPropertyChanged(int nIndex) { if(nIndex >= 0 && !setPropertyRange( m_pPropertyCombo->itemData(nIndex).toString())) { QMessageBox::warning( this, tr("Filtering settings"), tr("Unable to convert the filter limits " "to the selected unit.")); } } void nmWxPebiResultFilterSettingsDlg::onDisplayUnitChanged(int nIndex) { if(nIndex < 0) { return; } const QString sNewUnit = m_pUnitCombo->itemText(nIndex); if(sNewUnit == m_sDisplayUnit) { return; } double dOldDisplayMinimum = 0.0; double dOldDisplayMaximum = 0.0; double dBaseMinimum = 0.0; double dBaseMaximum = 0.0; double dNewDisplayMinimum = 0.0; double dNewDisplayMaximum = 0.0; const bool bConverted = parseFiniteValue( m_pMinimumEdit->text(), dOldDisplayMinimum) && parseFiniteValue( m_pMaximumEdit->text(), dOldDisplayMaximum) && convertUnitValue( dOldDisplayMinimum, m_sDisplayUnit, m_sBaseUnit, dBaseMinimum) && convertUnitValue( dOldDisplayMaximum, m_sDisplayUnit, m_sBaseUnit, dBaseMaximum) && convertUnitValue( dBaseMinimum, m_sBaseUnit, sNewUnit, dNewDisplayMinimum) && convertUnitValue( dBaseMaximum, m_sBaseUnit, sNewUnit, dNewDisplayMaximum); if(!bConverted) { const bool bSignalsBlocked = m_pUnitCombo->blockSignals(true); m_pUnitCombo->setCurrentIndex( m_pUnitCombo->findText(m_sDisplayUnit)); m_pUnitCombo->blockSignals(bSignalsBlocked); QMessageBox::warning( this, tr("Filtering settings"), tr("Unable to convert the filter limits " "to the selected unit.")); return; } m_sDisplayUnit = sNewUnit; m_pMinimumEdit->setText(formatValue(dNewDisplayMinimum)); m_pMaximumEdit->setText(formatValue(dNewDisplayMaximum)); } void nmWxPebiResultFilterSettingsDlg::refreshEditorEnabledState() { const bool bEnabled = m_pEnableCheck->isChecked(); m_pPropertyCombo->setEnabled(bEnabled); m_pMinimumCheck->setEnabled(bEnabled); m_pMaximumCheck->setEnabled(bEnabled); m_pMinimumEdit->setEnabled( bEnabled && m_pMinimumCheck->isChecked()); m_pMaximumEdit->setEnabled( bEnabled && m_pMaximumCheck->isChecked()); m_pUnitCombo->setEnabled( bEnabled && m_pUnitGroup != nullptr); } bool nmWxPebiResultFilterSettingsDlg::collectSettings( nmPebiResultFilterSettings& oSettings, QString& sError) const { sError.clear(); const int nPropertyIndex = m_pPropertyCombo->currentIndex(); if(nPropertyIndex < 0) { sError = tr("A filter property must be selected."); return false; } oSettings.m_bEnabled = m_pEnableCheck->isChecked(); oSettings.m_sPropertyId = m_pPropertyCombo->itemData(nPropertyIndex).toString(); oSettings.m_bMinimumEnabled = m_pMinimumCheck->isChecked(); oSettings.m_bMaximumEnabled = m_pMaximumCheck->isChecked(); // 复选框只控制边界是否参与筛选;未启用边界的编辑值仍需保留, // 避免重新打开对话框时退回设置对象的默认 0。 double dDisplayMinimum = 0.0; double dDisplayMaximum = 0.0; if(!parseFiniteValue( m_pMinimumEdit->text(), dDisplayMinimum) || !parseFiniteValue( m_pMaximumEdit->text(), dDisplayMaximum)) { sError = tr("Filter limits must be finite numbers."); return false; } if(!convertUnitValue( dDisplayMinimum, m_sDisplayUnit, m_sBaseUnit, oSettings.m_dMinimum) || !convertUnitValue( dDisplayMaximum, m_sDisplayUnit, m_sBaseUnit, oSettings.m_dMaximum)) { sError = tr( "Unable to convert the filter limits " "to the selected unit."); return false; } if(oSettings.m_bMinimumEnabled && oSettings.m_bMaximumEnabled && oSettings.m_dMinimum > oSettings.m_dMaximum) { sError = tr("The minimum filter limit cannot exceed the maximum."); return false; } return true; } bool nmWxPebiResultFilterSettingsDlg::applyEditor() { nmPebiResultPlaybackController* pPlaybackController = m_pControlBar == nullptr ? nullptr : m_pControlBar->getPlaybackController(); if(!m_bInitialized || m_pSceneController == nullptr || m_pControlBar == nullptr || pPlaybackController == nullptr || pPlaybackController->isPlaying() || m_pControlBar->isExportInProgress()) { const QString sError = tr( "Filtering is unavailable during playback or export."); QMessageBox::warning(this, tr("Filtering settings"), sError); return false; } nmPebiResultFilterSettings oSettings; QString sError; if(!collectSettings(oSettings, sError) || !m_pSceneController->applyFilterSettings( m_nExpectedBindingRevision, m_sExpectedSnapshotId, m_sExpectedDisplayPropertyId, m_nExpectedDisplayFrameIndex, oSettings, &sError)) { if(sError.isEmpty()) { sError = tr("Unable to apply the filter settings."); } QMessageBox::warning(this, tr("Filtering settings"), sError); return false; } m_oBaselineSettings = oSettings; emit filterSettingsApplied(); return true; } bool nmWxPebiResultFilterSettingsDlg::setPropertyRange( const QString& sPropertyId) { if(!m_mapPropertyRanges.contains(sPropertyId) || !initializeUnitEditor(sPropertyId)) { return false; } const QPair oRange = m_mapPropertyRanges.value(sPropertyId); double dDisplayMinimum = 0.0; double dDisplayMaximum = 0.0; if(!convertUnitValue( oRange.first, m_sBaseUnit, m_sDisplayUnit, dDisplayMinimum) || !convertUnitValue( oRange.second, m_sBaseUnit, m_sDisplayUnit, dDisplayMaximum)) { return false; } m_pMinimumCheck->setChecked(true); m_pMaximumCheck->setChecked(true); m_pMinimumEdit->setText(formatValue(dDisplayMinimum)); m_pMaximumEdit->setText(formatValue(dDisplayMaximum)); refreshEditorEnabledState(); return true; } QString nmWxPebiResultFilterSettingsDlg::formatValue(double dValue) const { return QLocale::c().toString(dValue, 'g', 12); } bool nmWxPebiResultFilterSettingsDlg::parseFiniteValue( const QString& sText, double& dValue) const { bool bOk = false; const double dCandidate = QLocale::c().toDouble( sText.trimmed(), &bOk); if(!bOk || !isFiniteFilterEditorValue(dCandidate)) { return false; } dValue = dCandidate; return true; } bool nmWxPebiResultFilterSettingsDlg::convertUnitValue( double dSourceValue, const QString& sSourceUnit, const QString& sDestinationUnit, double& dDestinationValue) const { if(!isFiniteFilterEditorValue(dSourceValue)) { return false; } if(sSourceUnit == sDestinationUnit) { dDestinationValue = dSourceValue; return true; } if(sSourceUnit.isEmpty() || sDestinationUnit.isEmpty() || m_pUnitGroup == nullptr || m_pUnitGroup->indexOf(sSourceUnit) < 0 || m_pUnitGroup->indexOf(sDestinationUnit) < 0) { return false; } int nDigit = 12; double dCandidate = 0.0; if(!m_pUnitGroup->convert( sSourceUnit, dSourceValue, sDestinationUnit, dCandidate, nDigit) || !isFiniteFilterEditorValue(dCandidate)) { return false; } dDestinationValue = dCandidate; return true; } nmWxPebiResultViewWidget::nmWxPebiResultViewWidget( nmDataAnalyzeManager* pDataManager, QWidget* pParent) : QWidget(pParent), m_pRenderContainer(nullptr), m_pSceneController(nullptr), m_pControlBar(nullptr), m_pToolBar(nullptr), m_pStatusLabel(nullptr), m_pScalarBarAction(nullptr), m_pScaleGridAction(nullptr), m_pWellNamesAction(nullptr), m_pFilterAction(nullptr), m_bObservedSnapshotCurrent(false), m_bObservedStateValid(false), m_bSynchronizing(false), m_bSyncPending(false) { createUi(); setDataManager(pDataManager); } nmWxPebiResultViewWidget::~nmWxPebiResultViewWidget() { disconnectDataManager(); if(m_pControlBar != nullptr) { m_pControlBar->stopPlayback(); delete m_pControlBar; m_pControlBar = nullptr; } if(m_pSceneController != nullptr) { m_pSceneController->clear(); delete m_pSceneController; m_pSceneController = nullptr; } // 渲染容器、工具栏、状态栏和 QAction 最后由 QWidget 对象树统一释放。 } void nmWxPebiResultViewWidget::setDataManager( nmDataAnalyzeManager* pDataManager) { Q_ASSERT(isResultViewGuiThread()); Q_ASSERT(pDataManager == nullptr || pDataManager->thread() == QThread::currentThread()); if(!isResultViewGuiThread() || (pDataManager != nullptr && pDataManager->thread() != QThread::currentThread())) { // 窗口会同步读取不可变快照引用,不能跨线程直接访问 Manager。 return; } if(m_pDataManager.data() == pDataManager) { // 生产入口可能重复安装同一 Manager;这里只读最新状态,不能清空相机。 synchronizeFromDataManager(); return; } disconnectDataManager(); clearSceneAndControls(tr("No result data.")); m_pDataManager = pDataManager; if(!m_pDataManager.isNull()) { connect(m_pDataManager.data(), SIGNAL(sigPebiResultStateChanged()), this, SLOT(onPebiResultStateChanged())); connect(m_pDataManager.data(), SIGNAL(destroyed(QObject*)), this, SLOT(onDataManagerDestroyed(QObject*))); } synchronizeFromDataManager(); } nmDataAnalyzeManager* nmWxPebiResultViewWidget::getDataManager() const { return m_pDataManager.data(); } void nmWxPebiResultViewWidget::synchronizeFromDataManager() { Q_ASSERT(isResultViewGuiThread()); Q_ASSERT(m_pDataManager.isNull() || m_pDataManager->thread() == QThread::currentThread()); if(!isResultViewGuiThread() || (!m_pDataManager.isNull() && m_pDataManager->thread() != QThread::currentThread())) { return; } if(m_bSynchronizing) { // 同步期间只记录一次待处理请求,当前事务结束后重新读取 Manager 最新值。 m_bSyncPending = true; return; } m_bSynchronizing = true; do { m_bSyncPending = false; synchronizeOnce(); } while(m_bSyncPending); m_bSynchronizing = false; } void nmWxPebiResultViewWidget::onPebiResultStateChanged() { synchronizeFromDataManager(); } void nmWxPebiResultViewWidget::onDataManagerDestroyed(QObject* pObject) { Q_UNUSED(pObject); // destroyed() 到达时 QPointer 可能已经清空,槽内绝不再读取 Manager。 m_pDataManager = nullptr; if(m_bSynchronizing) { // 极端嵌套事件下不得在快照提交栈内递归清场,退出当前事务后再读空状态。 m_bSyncPending = true; return; } clearSceneAndControls(tr("No result data.")); // 记录稳定空状态,后续重复同步空 Manager 时不再清场或切换状态页。 rememberObservedState( QSharedPointer(), QString(), false); } void nmWxPebiResultViewWidget::appendContextMenuActions(QMenu* pMenu) { if(pMenu == nullptr) { return; } pMenu->addAction(m_pFilterAction); pMenu->addSeparator(); pMenu->addAction(m_pScalarBarAction); pMenu->addAction(m_pScaleGridAction); pMenu->addAction(m_pWellNamesAction); if(m_pControlBar != nullptr && m_pControlBar->getExportAnimationAction() != nullptr) { pMenu->addSeparator(); pMenu->addAction(m_pControlBar->getExportAnimationAction()); } } void nmWxPebiResultViewWidget::onControlOperationFailed( const QString& sError) { // 绑定失败优先显示;输入修改不再提示,也不屏蔽后续真实操作错误。 if(!m_sBindingError.isEmpty()) { return; } m_sOperationError = sError; refreshStatusBar(); } void nmWxPebiResultViewWidget::onControlOperationSucceeded() { if(m_sOperationError.isEmpty()) { return; } // 这里只清除普通操作错误;刷新函数仍优先显示绑定失败。 m_sOperationError.clear(); refreshStatusBar(); } void nmWxPebiResultViewWidget::onViewportSizeChanged( int nWidth, int nHeight) { if(m_pSceneController != nullptr) { // SceneController 缓存尺寸,即使当前尚未创建结果色标也不会丢失。 m_pSceneController->setViewportSize(nWidth, nHeight); } } void nmWxPebiResultViewWidget::onShowFilterSettings() { nmPebiResultPlaybackController* pPlaybackController = m_pControlBar == nullptr ? nullptr : m_pControlBar->getPlaybackController(); if(m_pSceneController == nullptr || m_pControlBar == nullptr || pPlaybackController == nullptr || !m_pSceneController->hasSnapshot() || pPlaybackController->isPlaying() || m_pControlBar->isExportInProgress()) { return; } nmWxPebiResultFilterSettingsDlg oDialog( m_pSceneController, m_pControlBar, this); connect(&oDialog, SIGNAL(filterSettingsApplied()), this, SLOT(onControlOperationSucceeded())); oDialog.exec(); synchronizeBusinessActions(); } void nmWxPebiResultViewWidget::onExportAnimationRequested() { nmPebiResultPlaybackController* pPlaybackController = m_pControlBar == nullptr ? nullptr : m_pControlBar->getPlaybackController(); QString sBackendError; if(m_pSceneController == nullptr || m_pRenderContainer == nullptr || pPlaybackController == nullptr || pPlaybackController->isPlaying() || !m_pSceneController->hasSnapshot() || m_pDisplayedSnapshot.isNull() || !nmFFmpegVideoWriter::isBackendAvailable(&sBackendError)) { if(!sBackendError.isEmpty()) { QMessageBox::warning(this, tr("Export Animation"), sBackendError); } return; } AnimationExportGuard oExportGuard(m_pControlBar); if(!oExportGuard.isAcquired()) { QMessageBox::information( this, tr("Export Animation"), tr("Another result animation export is already in progress.")); return; } const QSharedPointer pFrozenSnapshot = m_pDisplayedSnapshot; const quint64 nFrozenBindingRevision = m_pSceneController->getSnapshotBindingRevision(); const QString sFrozenSnapshotId = m_pSceneController->getCurrentSnapshotId(); const QString sFrozenPropertyId = m_pSceneController->getCurrentPropertyId(); const nmVTKMeshLayer::DisplayMode eFrozenMeshDisplayMode = m_pSceneController->getMeshDisplayMode(); nmPebiResultDisplaySettings oFrozenDisplaySettings; nmPebiResultFilterSettings oFrozenFilterSettings; if(!m_pSceneController->getCurrentDisplaySettings( oFrozenDisplaySettings) || !m_pSceneController->getFilterSettings( oFrozenFilterSettings)) { return; } const int nFrozenFilterFrameIndex = m_pSceneController->getFilterFrameIndex(); const int nFrameCount = pFrozenSnapshot->getPropertyFrameCount( sFrozenPropertyId); if(nFrozenBindingRevision == 0 || sFrozenSnapshotId.isEmpty() || sFrozenPropertyId.isEmpty() || nFrameCount <= 1 || !nmVTKMeshLayer::isValidDisplayMode(eFrozenMeshDisplayMode) || (oFrozenFilterSettings.m_bEnabled && nFrozenFilterFrameIndex < 0) || !m_pSceneController->isPropertyTimeDependent(sFrozenPropertyId)) { return; } QVector vecFrameTimes; vecFrameTimes.reserve(nFrameCount); for(int nFrameIndex = 0; nFrameIndex < nFrameCount; ++nFrameIndex) { double dTimeHours = 0.0; if(!pFrozenSnapshot->getPropertyTimeAt( sFrozenPropertyId, nFrameIndex, dTimeHours)) { QMessageBox::warning( this, tr("Export Animation"), tr("Unable to read the result animation timeline.")); return; } vecFrameTimes.append(dTimeHours); } nmWxPebiResultAnimationExportDlg oSettingsDialog( vecFrameTimes, m_pRenderContainer->getViewportSize(), this); if(oSettingsDialog.exec() != QDialog::Accepted) { return; } // 参数窗口内的文件浏览也会形成嵌套事件循环,开始前重新核对完整显示身份。 if(m_pDisplayedSnapshot.data() != pFrozenSnapshot.data() || m_pSceneController->getSnapshotBindingRevision() != nFrozenBindingRevision || m_pSceneController->getCurrentSnapshotId() != sFrozenSnapshotId || m_pSceneController->getCurrentPropertyId() != sFrozenPropertyId || m_pSceneController->getMeshDisplayMode() != eFrozenMeshDisplayMode) { QMessageBox::warning( this, tr("Export Animation"), tr("The displayed result changed before export started.")); return; } nmPebiResultDisplaySettings oCurrentDisplaySettings; nmPebiResultFilterSettings oCurrentFilterSettings; if(!m_pSceneController->getCurrentDisplaySettings( oCurrentDisplaySettings) || oCurrentDisplaySettings != oFrozenDisplaySettings || !m_pSceneController->getFilterSettings(oCurrentFilterSettings) || oCurrentFilterSettings != oFrozenFilterSettings || m_pSceneController->getFilterFrameIndex() != nFrozenFilterFrameIndex) { QMessageBox::warning( this, tr("Export Animation"), tr("The displayed result changed before export started.")); return; } vtkRenderer* pRenderer = m_pRenderContainer->getRenderer(); if(pRenderer == nullptr || pRenderer->GetActiveCamera() == nullptr) { QMessageBox::warning( this, tr("Export Animation"), tr("Unable to freeze the current result display.")); return; } nmPebiResultAnimationExporter::Request oRequest; oRequest.m_pSnapshot = pFrozenSnapshot; oRequest.m_sPropertyId = sFrozenPropertyId; oRequest.m_oDisplaySettings = oFrozenDisplaySettings; oRequest.m_eMeshDisplayMode = eFrozenMeshDisplayMode; oRequest.m_oFilterSettings = oFrozenFilterSettings; oRequest.m_nFilterFrameIndex = nFrozenFilterFrameIndex; oRequest.m_oExportSettings = oSettingsDialog.getSettings(); oRequest.m_sTargetFilePath = oSettingsDialog.getTargetFilePath(); oRequest.m_bScalarBarVisible = m_pSceneController->isScalarBarVisible(); oRequest.m_bScaleGridVisible = m_pSceneController->isScaleGridVisible(); oRequest.m_bWellNamesVisible = m_pSceneController->areWellNamesVisible(); pRenderer->GetBackground(oRequest.m_dBackground); pRenderer->GetBackground2(oRequest.m_dBackground2); oRequest.m_bGradientBackground = pRenderer->GetGradientBackground() != 0; oRequest.m_pCamera = vtkSmartPointer::New(); oRequest.m_pCamera->DeepCopy(pRenderer->GetActiveCamera()); QProgressDialog oProgressDialog( tr("Exporting result animation..."), tr("Cancel"), 0, oRequest.m_oExportSettings.getOutputFrameCount(), this); oProgressDialog.setWindowTitle(tr("Export Animation")); oProgressDialog.setWindowModality(Qt::ApplicationModal); oProgressDialog.setMinimumDuration(0); oProgressDialog.setAutoClose(false); oProgressDialog.setAutoReset(false); nmPebiResultAnimationExporter oExporter; connect(&oExporter, SIGNAL(progressChanged(int,int)), &oProgressDialog, SLOT(setValue(int))); connect(&oProgressDialog, SIGNAL(canceled()), &oExporter, SLOT(cancel())); oProgressDialog.show(); QString sExportError; const nmPebiResultAnimationExporter::ExportResult eResult = oExporter.exportAnimation(oRequest, &sExportError); oProgressDialog.close(); oExportGuard.release(); if(eResult == nmPebiResultAnimationExporter::Export_Succeeded) { onControlOperationSucceeded(); QMessageBox::information( this, tr("Export Animation"), tr("The result animation was saved to:\n%1") .arg(QDir::toNativeSeparators( oRequest.m_sTargetFilePath))); } else if(eResult == nmPebiResultAnimationExporter::Export_Failed) { QMessageBox::warning( this, tr("Export Animation"), sExportError.isEmpty() ? tr("Unable to export the result animation.") : sExportError); } } void nmWxPebiResultViewWidget::onScalarBarToggled(bool bVisible) { if(m_pSceneController != nullptr && m_pSceneController->hasSnapshot()) { m_pSceneController->setScalarBarVisible(bVisible); } } void nmWxPebiResultViewWidget::onScaleGridToggled(bool bVisible) { if(m_pSceneController != nullptr && m_pSceneController->hasSnapshot()) { m_pSceneController->setScaleGridVisible(bVisible); } } void nmWxPebiResultViewWidget::onWellNamesToggled(bool bVisible) { if(m_pSceneController != nullptr && m_pSceneController->hasSnapshot()) { m_pSceneController->setWellNamesVisible(bVisible); } } void nmWxPebiResultViewWidget::createUi() { QVBoxLayout* pMainLayout = new QVBoxLayout(this); pMainLayout->setContentsMargins(0, 0, 0, 0); pMainLayout->setSpacing(0); QWidget* pRenderArea = new QWidget(this); QHBoxLayout* pRenderLayout = new QHBoxLayout(pRenderArea); pRenderLayout->setContentsMargins(0, 0, 0, 0); pRenderLayout->setSpacing(0); // 通用容器是本窗口唯一创建 QVTKWidget、Renderer 和 RenderWindow 的对象。 m_pRenderContainer = new nmWxVTKRenderContainerWidget(pRenderArea); m_pRenderContainer->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding); m_pRenderContainer->setViewCommandCapabilities( nmVTKViewCommandController::ViewCommand_ZoomIn | nmVTKViewCommandController::ViewCommand_ZoomOut | nmVTKViewCommandController::ViewCommand_Fit | nmVTKViewCommandController::ViewCommand_Top | nmVTKViewCommandController::ViewCommand_Front | nmVTKViewCommandController::ViewCommand_SetRotationCenter | nmVTKViewCommandController:: ViewCommand_ShowRotationCenterMarker | nmVTKViewCommandController::ViewCommand_SaveImage | nmVTKViewCommandController::ViewCommand_CopyImage | nmVTKViewCommandController::ViewCommand_Print | nmVTKViewCommandController::ViewCommand_PrintPreview); m_pSceneController = new nmPebiResultSceneController(m_pRenderContainer); connect(m_pRenderContainer, SIGNAL(viewportSizeChanged(int,int)), this, SLOT(onViewportSizeChanged(int,int))); const QSize oInitialViewportSize = m_pRenderContainer->getViewportSize(); onViewportSizeChanged( oInitialViewportSize.width(), oInitialViewportSize.height()); m_pToolBar = new QToolBar(pRenderArea); m_pToolBar->setOrientation(Qt::Vertical); m_pToolBar->setMovable(false); m_pToolBar->setFloatable(false); m_pToolBar->setIconSize(QSize(32, 32)); m_pToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); m_pToolBar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); m_pScalarBarAction = new QAction( QIcon(resultViewIconPath("NmColorScale.png")), tr("Color Scale"), this); m_pScaleGridAction = new QAction( QIcon(resultViewIconPath("NmScaleGrid.png")), tr("Scale Grid"), this); m_pWellNamesAction = new QAction( QIcon(resultViewIconPath("NmShowWellNames.png")), tr("Well Names"), this); m_pFilterAction = new QAction( QIcon(resultViewIconPath("NmFilter.png")), tr("Data Filtering..."), this); m_pFilterAction->setToolTip(m_pFilterAction->text()); QAction* const pBusinessActions[] = { m_pScalarBarAction, m_pScaleGridAction, m_pWellNamesAction }; for(int nIndex = 0; nIndex < 3; ++nIndex) { pBusinessActions[nIndex]->setCheckable(true); pBusinessActions[nIndex]->setToolTip( pBusinessActions[nIndex]->text()); } nmVTKViewCommandController* pCommandController = m_pRenderContainer->getViewCommandController(); if(pCommandController != nullptr) { QAction* pZoomOutAction = pCommandController->getAction( nmVTKViewCommandController::ViewCommand_ZoomOut); QAction* pFitAction = pCommandController->getAction( nmVTKViewCommandController::ViewCommand_Fit); QAction* pZoomInAction = pCommandController->getAction( nmVTKViewCommandController::ViewCommand_ZoomIn); QAction* pTopAction = pCommandController->getAction( nmVTKViewCommandController::ViewCommand_Top); QAction* pFrontAction = pCommandController->getAction( nmVTKViewCommandController::ViewCommand_Front); QAction* pPickCenterAction = pCommandController->getAction( nmVTKViewCommandController::ViewCommand_SetRotationCenter); QAction* pCopyAction = pCommandController->getAction( nmVTKViewCommandController::ViewCommand_CopyImage); if(pZoomOutAction != nullptr) m_pToolBar->addAction(pZoomOutAction); if(pFitAction != nullptr) m_pToolBar->addAction(pFitAction); if(pZoomInAction != nullptr) m_pToolBar->addAction(pZoomInAction); m_pToolBar->addSeparator(); if(pTopAction != nullptr) m_pToolBar->addAction(pTopAction); if(pFrontAction != nullptr) m_pToolBar->addAction(pFrontAction); if(pPickCenterAction != nullptr) m_pToolBar->addAction(pPickCenterAction); m_pToolBar->addSeparator(); m_pToolBar->addAction(m_pFilterAction); for(int nIndex = 0; nIndex < 3; ++nIndex) { m_pToolBar->addAction(pBusinessActions[nIndex]); } m_pToolBar->addSeparator(); if(pCopyAction != nullptr) m_pToolBar->addAction(pCopyAction); } connect(m_pScalarBarAction, SIGNAL(toggled(bool)), this, SLOT(onScalarBarToggled(bool))); connect(m_pScaleGridAction, SIGNAL(toggled(bool)), this, SLOT(onScaleGridToggled(bool))); connect(m_pWellNamesAction, SIGNAL(toggled(bool)), this, SLOT(onWellNamesToggled(bool))); connect(m_pFilterAction, SIGNAL(triggered()), this, SLOT(onShowFilterSettings())); connect(m_pRenderContainer, SIGNAL(contextMenuAboutToShow(QMenu*)), this, SLOT(appendContextMenuActions(QMenu*))); pRenderLayout->addWidget(m_pRenderContainer, 1); pRenderLayout->addWidget(m_pToolBar); pMainLayout->addWidget(pRenderArea, 1); m_pStatusLabel = new QLabel(this); m_pStatusLabel->setWordWrap(true); m_pStatusLabel->setContentsMargins(8, 4, 8, 4); m_pStatusLabel->hide(); pMainLayout->addWidget(m_pStatusLabel); m_pControlBar = new nmWxPebiResultControlBar( m_pSceneController, this); connect(m_pControlBar, SIGNAL(operationFailed(QString)), this, SLOT(onControlOperationFailed(QString))); connect(m_pControlBar, SIGNAL(operationSucceeded()), this, SLOT(onControlOperationSucceeded())); connect(m_pControlBar, SIGNAL(exportAnimationRequested()), this, SLOT(onExportAnimationRequested())); connect(m_pControlBar, SIGNAL(exportInProgressChanged(bool)), this, SLOT(synchronizeBusinessActions())); if(m_pControlBar->getPlaybackController() != nullptr) { connect(m_pControlBar->getPlaybackController(), SIGNAL(stateChanged()), this, SLOT(synchronizeBusinessActions())); } pMainLayout->addWidget(m_pControlBar); synchronizeBusinessActions(); m_pRenderContainer->showEmptyState(tr("No result data.")); } void nmWxPebiResultViewWidget::synchronizeOnce() { if(m_pDataManager.isNull()) { if(m_bObservedStateValid && m_pObservedSnapshot.isNull()) { return; } clearSceneAndControls(tr("No result data.")); rememberObservedState( QSharedPointer(), QString(), false); return; } const QSharedPointer pSnapshot = m_pDataManager->getPebiResultSnapshot(); const QString sSnapshotId = pSnapshot.isNull() ? QString() : pSnapshot->getSnapshotId(); const bool bCurrent = !pSnapshot.isNull() && m_pDataManager->isPebiResultSnapshotCurrent(); const bool bSameIdentity = m_bObservedStateValid && m_pObservedSnapshot.data() == pSnapshot.data() && m_sObservedSnapshotId == sSnapshotId; const bool bSameState = bSameIdentity && m_bObservedSnapshotCurrent == bCurrent; if(bSameState) { return; } const bool bBecameStale = bSameIdentity && m_bObservedSnapshotCurrent && !bCurrent; m_sOperationError.clear(); if(pSnapshot.isNull()) { clearSceneAndControls(tr("No result data.")); rememberObservedState(pSnapshot, sSnapshotId, false); return; } if(!bSameIdentity) { // 候选提交前停止本窗口 Timer;失败时旧 Scene 和最后压力帧记忆仍保留。 m_pControlBar->stopPlayback(); QString sError; const bool bBound = m_pSceneController->setSnapshot( pSnapshot, &sError); rememberObservedState(pSnapshot, sSnapshotId, bCurrent); if(bBound) { m_pDisplayedSnapshot = pSnapshot; m_sDisplayedSnapshotId = sSnapshotId; m_sBindingError.clear(); // 新成功绑定序号会让 Playback 和 ControlBar 同时重置各自缓存。 m_pControlBar->synchronizeFromScene(); synchronizeBusinessActions(); refreshStatusBar(); return; } m_sBindingError = sError.isEmpty() ? tr("Unable to display the latest result.") : tr("Unable to display the latest result: %1").arg(sError); if(!m_pSceneController->hasSnapshot()) { m_pDisplayedSnapshot.clear(); m_sDisplayedSnapshotId.clear(); } m_pControlBar->synchronizeFromScene(); synchronizeBusinessActions(); if(m_pSceneController->hasSnapshot()) { // 旧画面保持 Ready;这里只显示红色非阻塞提示,不能追加 Render。 refreshStatusBar(); } else { m_pStatusLabel->hide(); m_pRenderContainer->showErrorState(m_sBindingError); } return; } rememberObservedState(pSnapshot, sSnapshotId, bCurrent); if(bBecameStale) { // 只在“当前 -> 过期”的边沿暂停一次;用户之后可以手动重新播放旧结果。 m_pControlBar->stopPlayback(); } refreshStatusBar(); } void nmWxPebiResultViewWidget::clearSceneAndControls( const QString& sEmptyMessage) { if(m_pControlBar != nullptr) { m_pControlBar->stopPlayback(); } if(m_pSceneController != nullptr) { m_pSceneController->clear(); } m_pObservedSnapshot.clear(); m_sObservedSnapshotId.clear(); m_bObservedSnapshotCurrent = false; m_bObservedStateValid = false; m_pDisplayedSnapshot.clear(); m_sDisplayedSnapshotId.clear(); m_sBindingError.clear(); m_sOperationError.clear(); if(m_pControlBar != nullptr) { m_pControlBar->synchronizeFromScene(); } synchronizeBusinessActions(); if(m_pStatusLabel != nullptr) { m_pStatusLabel->hide(); } if(m_pRenderContainer != nullptr) { m_pRenderContainer->showEmptyState(sEmptyMessage); } } void nmWxPebiResultViewWidget::rememberObservedState( const QSharedPointer& pSnapshot, const QString& sSnapshotId, bool bCurrent) { m_pObservedSnapshot = pSnapshot; m_sObservedSnapshotId = sSnapshotId; m_bObservedSnapshotCurrent = bCurrent; m_bObservedStateValid = true; } void nmWxPebiResultViewWidget::synchronizeBusinessActions() { if(m_pSceneController == nullptr) { return; } setActionCheckedWithoutSignal( m_pScalarBarAction, m_pSceneController->isScalarBarVisible()); setActionCheckedWithoutSignal( m_pScaleGridAction, m_pSceneController->isScaleGridVisible()); setActionCheckedWithoutSignal( m_pWellNamesAction, m_pSceneController->areWellNamesVisible()); const bool bEnabled = m_pSceneController->hasSnapshot(); m_pScalarBarAction->setEnabled(bEnabled); m_pScaleGridAction->setEnabled(bEnabled); m_pWellNamesAction->setEnabled(bEnabled); nmPebiResultPlaybackController* pPlaybackController = m_pControlBar == nullptr ? nullptr : m_pControlBar->getPlaybackController(); const bool bFilterEnabled = bEnabled && pPlaybackController != nullptr && !pPlaybackController->isPlaying() && !m_pControlBar->isExportInProgress(); if(m_pFilterAction != nullptr) { m_pFilterAction->setEnabled(bFilterEnabled); } } void nmWxPebiResultViewWidget::refreshStatusBar() { if(m_pStatusLabel == nullptr || m_pSceneController == nullptr || !m_pSceneController->hasSnapshot()) { if(m_pStatusLabel != nullptr) { m_pStatusLabel->hide(); } return; } if(!m_sBindingError.isEmpty()) { m_pStatusLabel->setStyleSheet( "QLabel { color: #a32626; background: #fdecec; } "); m_pStatusLabel->setText(m_sBindingError); m_pStatusLabel->show(); return; } if(!m_sOperationError.isEmpty()) { m_pStatusLabel->setStyleSheet( "QLabel { color: #a32626; background: #fdecec; } "); m_pStatusLabel->setText(m_sOperationError); m_pStatusLabel->show(); return; } m_pStatusLabel->hide(); } void nmWxPebiResultViewWidget::disconnectDataManager() { nmDataAnalyzeManager* pDataManager = m_pDataManager.data(); if(pDataManager != nullptr) { disconnect(pDataManager, SIGNAL(sigPebiResultStateChanged()), this, SLOT(onPebiResultStateChanged())); disconnect(pDataManager, SIGNAL(destroyed(QObject*)), this, SLOT(onDataManagerDestroyed(QObject*))); } m_pDataManager = nullptr; }