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

542 lines
19 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "nmWxPebiResultControlBar.h"
#include "nmFFmpegVideoWriter.h"
#include "nmPebiResultPlaybackController.h"
#include "nmPebiResultPropertyDisplayText.h"
#include "nmPebiResultSceneController.h"
#include "nmWxPebiResultAnimationSettingsDlg.h"
#include "nmWxPebiResultColorScaleSettingsDlg.h"
#include <QAction>
#include <QComboBox>
#include <QCoreApplication>
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QSize>
#include <QSizePolicy>
#include <QToolButton>
#include <QVariant>
namespace {
const int PROPERTY_COMBO_MINIMUM_WIDTH = 110;
const int PROPERTY_COMBO_MAXIMUM_WIDTH = 140;
const int TIME_COMBO_MINIMUM_WIDTH = 160;
const int TIME_COMBO_MAXIMUM_WIDTH = 240;
const int CONTROL_GROUP_GAP = 8;
// Qt 4 按 20 个“X”字符估算 SizeHint,常用字体下约为 190px。
const int TIME_COMBO_MINIMUM_CONTENT_LENGTH = 20;
QString resultControlIconPath(const QString& sFileName)
{
const QString sApplicationDir = QCoreApplication::applicationDirPath();
return sApplicationDir.section('/', 0, -2) +
"/Res/Icon/" + sFileName;
}
QToolButton* createControlButton(QWidget* pParent,
const QString& sIconFile,
const QString& sToolTip)
{
QToolButton* pButton = new QToolButton(pParent);
pButton->setAutoRaise(true);
pButton->setFixedSize(24, 24);
pButton->setIconSize(QSize(16, 16));
pButton->setIcon(QIcon(resultControlIconPath(sIconFile)));
pButton->setToolTip(sToolTip);
return pButton;
}
}
nmWxPebiResultControlBar::nmWxPebiResultControlBar(
nmPebiResultSceneController* pSceneController,
QWidget* pParent)
: QWidget(pParent),
m_pPlaybackController(nullptr),
m_pSceneController(pSceneController),
m_pPropertyCombo(nullptr),
m_pColorSettingsButton(nullptr),
m_pFirstButton(nullptr),
m_pPreviousButton(nullptr),
m_pTimeCombo(nullptr),
m_pNextButton(nullptr),
m_pLastButton(nullptr),
m_pPlayPauseButton(nullptr),
m_pSettingsButton(nullptr),
m_pExportAction(nullptr),
m_pExportButton(nullptr),
m_bExportInProgress(false),
m_nDisplayedBindingRevision(0),
m_nDisplayedTimeFrameCount(-1)
{
Q_ASSERT(pSceneController != nullptr);
createUi();
m_pPlaybackController = new nmPebiResultPlaybackController(
pSceneController, this);
connect(m_pPlaybackController, SIGNAL(stateChanged()),
this, SLOT(refreshFromController()));
connect(m_pPlaybackController, SIGNAL(operationFailed(QString)),
this, SLOT(forwardOperationFailure(QString)));
m_pPlaybackController->synchronizeFromScene();
}
nmWxPebiResultControlBar::~nmWxPebiResultControlBar()
{
// QObject 随后删除子控制器;先停表可保证析构期间不再排入超时请求。
if(m_pPlaybackController != nullptr) {
m_pPlaybackController->blockSignals(true);
m_pPlaybackController->pause();
}
}
void nmWxPebiResultControlBar::stopPlayback()
{
if(m_pPlaybackController != nullptr) {
m_pPlaybackController->pause();
}
}
void nmWxPebiResultControlBar::synchronizeFromScene()
{
if(m_pPlaybackController != nullptr) {
m_pPlaybackController->synchronizeFromScene();
}
}
nmPebiResultPlaybackController*
nmWxPebiResultControlBar::getPlaybackController() const
{
return m_pPlaybackController;
}
QAction* nmWxPebiResultControlBar::getExportAnimationAction() const
{
return m_pExportAction;
}
void nmWxPebiResultControlBar::setExportInProgress(bool bInProgress)
{
if(m_bExportInProgress == bInProgress) {
return;
}
m_bExportInProgress = bInProgress;
refreshFromController();
}
void nmWxPebiResultControlBar::onPropertyIndexChanged(int nIndex)
{
if(m_pPlaybackController == nullptr || m_bExportInProgress ||
m_pPlaybackController->isPlaying() || nIndex < 0) {
return;
}
if(m_pPlaybackController->selectProperty(
m_pPropertyCombo->itemData(nIndex).toString())) {
emit operationSucceeded();
}
}
void nmWxPebiResultControlBar::onTimeIndexChanged(int nIndex)
{
if(m_pPlaybackController == nullptr || m_bExportInProgress ||
m_pPlaybackController->isPlaying() || nIndex < 0) {
return;
}
if(m_pPlaybackController->selectFrame(
m_pTimeCombo->itemData(nIndex).toInt())) {
emit operationSucceeded();
}
}
void nmWxPebiResultControlBar::onColorSettingsClicked()
{
if(m_pPlaybackController == nullptr || m_pSceneController == nullptr ||
m_bExportInProgress ||
m_pPlaybackController->isPlaying() ||
!m_pSceneController->hasSnapshot()) {
return;
}
nmWxPebiResultColorScaleSettingsDlg oDialog(
m_pSceneController, this);
connect(&oDialog, SIGNAL(displaySettingsApplied()),
this, SIGNAL(operationSucceeded()));
connect(&oDialog, SIGNAL(operationFailed(QString)),
this, SLOT(forwardOperationFailure(QString)));
oDialog.exec();
}
void nmWxPebiResultControlBar::onFirstFrameClicked()
{
if(m_pPlaybackController == nullptr || m_bExportInProgress ||
m_pPlaybackController->isPlaying()) {
return;
}
if(m_pPlaybackController->selectFirstFrame()) {
emit operationSucceeded();
}
}
void nmWxPebiResultControlBar::onPreviousFrameClicked()
{
if(m_pPlaybackController == nullptr || m_bExportInProgress ||
m_pPlaybackController->isPlaying()) {
return;
}
if(m_pPlaybackController->selectPreviousFrame()) {
emit operationSucceeded();
}
}
void nmWxPebiResultControlBar::onNextFrameClicked()
{
if(m_pPlaybackController == nullptr || m_bExportInProgress ||
m_pPlaybackController->isPlaying()) {
return;
}
if(m_pPlaybackController->selectNextFrame()) {
emit operationSucceeded();
}
}
void nmWxPebiResultControlBar::onLastFrameClicked()
{
if(m_pPlaybackController == nullptr || m_bExportInProgress ||
m_pPlaybackController->isPlaying()) {
return;
}
if(m_pPlaybackController->selectLastFrame()) {
emit operationSucceeded();
}
}
void nmWxPebiResultControlBar::onPlayPauseClicked()
{
if(m_pPlaybackController == nullptr || m_bExportInProgress) {
return;
}
if(m_pPlaybackController->togglePlayback()) {
emit operationSucceeded();
}
}
void nmWxPebiResultControlBar::onAnimationSettingsClicked()
{
if(m_pPlaybackController == nullptr || m_bExportInProgress ||
m_pPlaybackController->isPlaying()) {
return;
}
nmWxPebiResultAnimationSettingsDlg oDialog(
m_pPlaybackController->getPlaybackSpeed(),
m_pPlaybackController->isLoopEnabled(), this);
if(oDialog.exec() == QDialog::Accepted) {
if(m_pPlaybackController->setPlaybackSettings(
oDialog.getPlaybackSpeed(),
oDialog.isLoopEnabled())) {
emit operationSucceeded();
}
}
}
void nmWxPebiResultControlBar::onExportAnimationTriggered()
{
if(m_pPlaybackController == nullptr || m_bExportInProgress ||
m_pPlaybackController->isPlaying() ||
!m_pPlaybackController->isCurrentPropertyTimeDependent() ||
m_pPlaybackController->getCurrentPropertyFrameCount() <= 1) {
return;
}
emit exportAnimationRequested();
}
void nmWxPebiResultControlBar::refreshFromController()
{
// 控件状态严格镜像四类 Scene 状态:无快照、静态、单帧动态、多帧动态。
const bool bAvailable = m_pPlaybackController != nullptr &&
m_pPlaybackController->isAvailable();
if(!bAvailable) {
const bool bPropertyBlocked = m_pPropertyCombo->blockSignals(true);
m_pPropertyCombo->clear();
m_pPropertyCombo->blockSignals(bPropertyBlocked);
clearTimeItems();
m_sDisplayedSnapshotId.clear();
m_nDisplayedBindingRevision = 0;
m_listDisplayedPropertyIds.clear();
m_pPropertyCombo->setEnabled(false);
m_pColorSettingsButton->setEnabled(false);
m_pFirstButton->setEnabled(false);
m_pPreviousButton->setEnabled(false);
m_pNextButton->setEnabled(false);
m_pLastButton->setEnabled(false);
m_pPlayPauseButton->setEnabled(false);
m_pSettingsButton->setEnabled(false);
m_pExportAction->setEnabled(false);
m_pPlayPauseButton->setIcon(QIcon(resultControlIconPath(
QString::fromLatin1("NmStartAnimation.png"))));
m_pPlayPauseButton->setToolTip(tr("Play"));
return;
}
const QString sSnapshotId =
m_pPlaybackController->getObservedSnapshotId();
const quint64 nBindingRevision =
m_pPlaybackController->getObservedBindingRevision();
const QStringList listPropertyIds =
m_pPlaybackController->getAvailablePropertyIds();
const bool bSnapshotChanged =
m_nDisplayedBindingRevision != nBindingRevision ||
m_sDisplayedSnapshotId != sSnapshotId;
if(bSnapshotChanged ||
m_listDisplayedPropertyIds != listPropertyIds) {
rebuildPropertyItems(listPropertyIds);
m_sDisplayedSnapshotId = sSnapshotId;
m_nDisplayedBindingRevision = nBindingRevision;
m_listDisplayedPropertyIds = listPropertyIds;
// 即使属性和帧数相同,新快照的真实时间值也可能不同,不能复用旧列表。
if(bSnapshotChanged) {
m_sDisplayedTimePropertyId.clear();
m_nDisplayedTimeFrameCount = -1;
}
}
const QString sPropertyId =
m_pPlaybackController->getCurrentPropertyId();
const int nPropertyIndex = findItemByData(
m_pPropertyCombo, QVariant(sPropertyId));
const bool bPropertyBlocked = m_pPropertyCombo->blockSignals(true);
m_pPropertyCombo->setCurrentIndex(nPropertyIndex);
m_pPropertyCombo->blockSignals(bPropertyBlocked);
const bool bTimeDependent =
m_pPlaybackController->isPropertyTimeDependent(sPropertyId);
const int nFrameCount = bTimeDependent
? m_pPlaybackController->getPropertyFrameCount(sPropertyId) : 0;
if(!bTimeDependent) {
clearTimeItems();
} else if(m_sDisplayedTimePropertyId != sPropertyId ||
m_nDisplayedTimeFrameCount != nFrameCount) {
rebuildTimeItems(sPropertyId, nFrameCount);
}
const int nCurrentFrame =
m_pPlaybackController->getCurrentFrameIndex();
if(bTimeDependent) {
const int nTimeIndex = findItemByData(
m_pTimeCombo, QVariant(nCurrentFrame));
const bool bTimeBlocked = m_pTimeCombo->blockSignals(true);
m_pTimeCombo->setCurrentIndex(nTimeIndex);
m_pTimeCombo->blockSignals(bTimeBlocked);
}
const bool bHasCompleteTimeList = bTimeDependent &&
nFrameCount > 0 && m_pTimeCombo->count() == nFrameCount;
const bool bHasTime = bHasCompleteTimeList;
const bool bHasMultipleFrames = bHasCompleteTimeList &&
nFrameCount > 1;
const bool bPlaying = m_pPlaybackController->isPlaying();
const bool bLocked = bPlaying || m_bExportInProgress;
m_pPropertyCombo->setEnabled(!bLocked);
m_pColorSettingsButton->setEnabled(!bLocked);
m_pTimeCombo->setEnabled(bHasTime && !bLocked);
m_pFirstButton->setEnabled(
bHasMultipleFrames && nCurrentFrame > 0 && !bLocked);
m_pPreviousButton->setEnabled(
bHasMultipleFrames && nCurrentFrame > 0 && !bLocked);
m_pNextButton->setEnabled(
bHasMultipleFrames &&
nCurrentFrame < nFrameCount - 1 && !bLocked);
m_pLastButton->setEnabled(
bHasMultipleFrames &&
nCurrentFrame < nFrameCount - 1 && !bLocked);
m_pPlayPauseButton->setEnabled(
bHasMultipleFrames && !m_bExportInProgress);
m_pSettingsButton->setEnabled(bHasMultipleFrames && !bLocked);
QString sBackendError;
const bool bBackendAvailable =
nmFFmpegVideoWriter::isBackendAvailable(&sBackendError);
m_pExportAction->setEnabled(
bHasMultipleFrames && !bLocked && bBackendAvailable);
const QString sExportTip = bBackendAvailable
? tr("Export animation") : sBackendError;
m_pExportAction->setToolTip(sExportTip);
m_pExportAction->setStatusTip(sExportTip);
m_pPlayPauseButton->setIcon(QIcon(resultControlIconPath(
bPlaying ? QString::fromLatin1("NmStopAnimation.png")
: QString::fromLatin1("NmStartAnimation.png"))));
m_pPlayPauseButton->setToolTip(
bPlaying ? tr("Pause") : tr("Play"));
}
void nmWxPebiResultControlBar::forwardOperationFailure(
const QString& sError)
{
emit operationFailed(sError);
}
void nmWxPebiResultControlBar::createUi()
{
QHBoxLayout* pLayout = new QHBoxLayout(this);
pLayout->setContentsMargins(4, 3, 4, 3);
pLayout->setSpacing(4);
QLabel* pPropertyLabel = new QLabel(tr("Property"), this);
pPropertyLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
m_pPropertyCombo = new QComboBox(this);
m_pPropertyCombo->setMinimumWidth(PROPERTY_COMBO_MINIMUM_WIDTH);
m_pPropertyCombo->setMaximumWidth(PROPERTY_COMBO_MAXIMUM_WIDTH);
m_pPropertyCombo->setFixedHeight(24);
m_pPropertyCombo->setSizePolicy(
QSizePolicy::Preferred, QSizePolicy::Fixed);
m_pPropertyCombo->setToolTip(tr("Display property"));
m_pColorSettingsButton = createControlButton(
this, QString::fromLatin1("NmColorScaleSettings.png"),
tr("Color settings"));
m_pFirstButton = createControlButton(
this, QString::fromLatin1("NmFirstStep.png"),
tr("First frame"));
m_pPreviousButton = createControlButton(
this, QString::fromLatin1("NmPreviousStep.png"),
tr("Previous frame"));
m_pTimeCombo = new QComboBox(this);
m_pTimeCombo->setMinimumWidth(TIME_COMBO_MINIMUM_WIDTH);
m_pTimeCombo->setMaximumWidth(TIME_COMBO_MAXIMUM_WIDTH);
m_pTimeCombo->setMinimumContentsLength(
TIME_COMBO_MINIMUM_CONTENT_LENGTH);
m_pTimeCombo->setSizeAdjustPolicy(
QComboBox::AdjustToMinimumContentsLengthWithIcon);
m_pTimeCombo->setFixedHeight(24);
m_pTimeCombo->setSizePolicy(
QSizePolicy::Preferred, QSizePolicy::Fixed);
m_pTimeCombo->setToolTip(tr("Result time"));
m_pNextButton = createControlButton(
this, QString::fromLatin1("NmNextStep.png"),
tr("Next frame"));
m_pLastButton = createControlButton(
this, QString::fromLatin1("NmLastStep.png"),
tr("Last frame"));
m_pPlayPauseButton = createControlButton(
this, QString::fromLatin1("NmStartAnimation.png"),
tr("Play"));
m_pSettingsButton = createControlButton(
this, QString::fromLatin1("NmAnimationSettings.png"),
tr("Animation settings"));
m_pExportAction = new QAction(
QIcon(resultControlIconPath("Export.png")),
tr("Export animation"), this);
m_pExportAction->setToolTip(tr("Export animation"));
m_pExportButton = new QToolButton(this);
m_pExportButton->setAutoRaise(true);
m_pExportButton->setFixedSize(24, 24);
m_pExportButton->setIconSize(QSize(16, 16));
m_pExportButton->setDefaultAction(m_pExportAction);
pLayout->addWidget(pPropertyLabel, 0, Qt::AlignVCenter);
pLayout->addWidget(m_pPropertyCombo);
pLayout->addWidget(m_pColorSettingsButton);
pLayout->addSpacing(CONTROL_GROUP_GAP);
pLayout->addWidget(m_pFirstButton);
pLayout->addWidget(m_pPreviousButton);
pLayout->addWidget(m_pTimeCombo);
pLayout->addWidget(m_pNextButton);
pLayout->addWidget(m_pLastButton);
pLayout->addWidget(m_pPlayPauseButton);
pLayout->addWidget(m_pSettingsButton);
pLayout->addWidget(m_pExportButton);
pLayout->addStretch(1);
connect(m_pPropertyCombo, SIGNAL(currentIndexChanged(int)),
this, SLOT(onPropertyIndexChanged(int)));
connect(m_pColorSettingsButton, SIGNAL(clicked()),
this, SLOT(onColorSettingsClicked()));
connect(m_pTimeCombo, SIGNAL(currentIndexChanged(int)),
this, SLOT(onTimeIndexChanged(int)));
connect(m_pFirstButton, SIGNAL(clicked()),
this, SLOT(onFirstFrameClicked()));
connect(m_pPreviousButton, SIGNAL(clicked()),
this, SLOT(onPreviousFrameClicked()));
connect(m_pNextButton, SIGNAL(clicked()),
this, SLOT(onNextFrameClicked()));
connect(m_pLastButton, SIGNAL(clicked()),
this, SLOT(onLastFrameClicked()));
connect(m_pPlayPauseButton, SIGNAL(clicked()),
this, SLOT(onPlayPauseClicked()));
connect(m_pSettingsButton, SIGNAL(clicked()),
this, SLOT(onAnimationSettingsClicked()));
connect(m_pExportAction, SIGNAL(triggered()),
this, SLOT(onExportAnimationTriggered()));
}
void nmWxPebiResultControlBar::rebuildPropertyItems(
const QStringList& listPropertyIds)
{
const bool bBlocked = m_pPropertyCombo->blockSignals(true);
m_pPropertyCombo->clear();
for(int nIndex = 0; nIndex < listPropertyIds.size(); ++nIndex) {
const QString& sPropertyId = listPropertyIds[nIndex];
QString sDisplayName =
nmPebiResultPropertyDisplayText::getDisplayName(sPropertyId);
if(sDisplayName.isEmpty()) {
sDisplayName = sPropertyId;
}
m_pPropertyCombo->addItem(sDisplayName, sPropertyId);
}
m_pPropertyCombo->blockSignals(bBlocked);
}
void nmWxPebiResultControlBar::rebuildTimeItems(
const QString& sPropertyId,
int nFrameCount)
{
const bool bBlocked = m_pTimeCombo->blockSignals(true);
m_pTimeCombo->clear();
bool bComplete = true;
for(int nFrameIndex = 0; nFrameIndex < nFrameCount; ++nFrameIndex) {
double dTimeHours = 0.0;
if(!m_pPlaybackController->getPropertyTimeAt(
sPropertyId, nFrameIndex, dTimeHours)) {
// 时间轴来自完整快照;防御性失败时不留下索引错位的半列表。
m_pTimeCombo->clear();
bComplete = false;
break;
}
m_pTimeCombo->addItem(
tr("t = %1 h").arg(
QString::number(dTimeHours, 'g', 8)),
nFrameIndex);
}
m_pTimeCombo->blockSignals(bBlocked);
m_sDisplayedTimePropertyId = bComplete ? sPropertyId : QString();
m_nDisplayedTimeFrameCount = bComplete ? nFrameCount : -1;
}
void nmWxPebiResultControlBar::clearTimeItems()
{
const bool bBlocked = m_pTimeCombo->blockSignals(true);
m_pTimeCombo->clear();
m_pTimeCombo->blockSignals(bBlocked);
m_pTimeCombo->setEnabled(false);
m_sDisplayedTimePropertyId.clear();
m_nDisplayedTimeFrameCount = -1;
}
int nmWxPebiResultControlBar::findItemByData(
QComboBox* pComboBox,
const QVariant& oData) const
{
if(pComboBox == nullptr) {
return -1;
}
for(int nIndex = 0; nIndex < pComboBox->count(); ++nIndex) {
if(pComboBox->itemData(nIndex) == oData) {
return nIndex;
}
}
return -1;
}