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++

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