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

527 lines
18 KiB
C++

#include "nmWxPebiResultViewWidget.h"
#include "nmDataAnalyzeManager.h"
#include "nmPebiResultSceneController.h"
#include "nmPebiResultSnapshot.h"
#include "nmVTKViewCommandController.h"
#include "nmWxPebiResultControlBar.h"
#include "nmWxVTKRenderContainerWidget.h"
#include <QAction>
#include <QCoreApplication>
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QMenu>
#include <QSize>
#include <QSizePolicy>
#include <QThread>
#include <QToolBar>
#include <QVBoxLayout>
namespace {
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);
}
}
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_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<const nmPebiResultSnapshot>(),
QString(), false);
}
void nmWxPebiResultViewWidget::appendContextMenuActions(QMenu* pMenu)
{
if(pMenu == nullptr) {
return;
}
pMenu->addAction(m_pScalarBarAction);
pMenu->addAction(m_pScaleGridAction);
pMenu->addAction(m_pWellNamesAction);
}
void nmWxPebiResultViewWidget::onControlOperationFailed(
const QString& sError)
{
// 高优先级状态存在时丢弃普通错误,避免其在过期提示解除后突然出现。
if(!m_sBindingError.isEmpty() ||
(m_bObservedStateValid && !m_bObservedSnapshotCurrent)) {
return;
}
m_sOperationError = sError;
refreshStatusBar();
}
void nmWxPebiResultViewWidget::onControlOperationSucceeded()
{
if(m_sOperationError.isEmpty()) {
return;
}
// 这里只清除最低优先级的普通操作错误;刷新函数仍优先显示绑定失败和过期。
m_sOperationError.clear();
refreshStatusBar();
}
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_SaveImage |
nmVTKViewCommandController::ViewCommand_CopyImage |
nmVTKViewCommandController::ViewCommand_Print |
nmVTKViewCommandController::ViewCommand_PrintPreview);
m_pSceneController = new nmPebiResultSceneController(m_pRenderContainer);
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);
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();
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_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()));
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<const nmPebiResultSnapshot>(),
QString(), false);
return;
}
const QSharedPointer<const nmPebiResultSnapshot> 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<const nmPebiResultSnapshot>& 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);
}
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_bObservedStateValid && !m_bObservedSnapshotCurrent) {
m_pStatusLabel->setStyleSheet(
"QLabel { color: #6b5200; background: #fff4bf; } ");
m_pStatusLabel->setText(tr(
"Current inputs have changed. Showing the last successful result."));
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;
}