#include "nmWxPebiResultAnimationExportDlg.h" #include "nmFFmpegVideoWriter.h" #include "ZxBaseUtil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { QString exportDialogIconPath(const QString& sFileName) { const QString sApplicationDir = QCoreApplication::applicationDirPath(); return sApplicationDir.section('/', 0, -2) + "/Res/Icon/" + sFileName; } QLabel* createRightAlignedLabel(const QString& sText, QWidget* pParent) { QLabel* pLabel = new QLabel(sText, pParent); pLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); return pLabel; } QString formatPixelSize(int nWidth, int nHeight) { QString sText = QString::number(nWidth); sText.append(' '); sText.append(QChar(0x00D7)); sText.append(QString::fromLatin1(" %1 px").arg(nHeight)); return sText; } } nmWxPebiResultAnimationExportDlg::nmWxPebiResultAnimationExportDlg( const QVector& vecFrameTimesHours, const QSize& oCurrentViewportSize, QWidget* pParent) : iDlgBase(pParent), m_vecFrameTimesHours(vecFrameTimesHours), m_pFpsSpinBox(nullptr), m_pStartFrameCombo(nullptr), m_pEndFrameCombo(nullptr), m_pSamplingIntervalSpinBox(nullptr), m_pResolutionButtonGroup(nullptr), m_pCurrentSizeRadioButton(nullptr), m_p720pRadioButton(nullptr), m_p1080pRadioButton(nullptr), m_pCustomSizeRadioButton(nullptr), m_pWidthSpinBox(nullptr), m_pHeightSpinBox(nullptr), m_pVideoFormatCombo(nullptr), m_pTargetPathEdit(nullptr), m_pOutputFrameCountLabel(nullptr), m_pEstimatedDurationLabel(nullptr), m_pErrorLabel(nullptr) { int nCurrentWidth = oCurrentViewportSize.width(); int nCurrentHeight = oCurrentViewportSize.height(); if(nCurrentWidth <= 0 || nCurrentHeight <= 0) { nCurrentWidth = 1280; nCurrentHeight = 720; } m_oEffectiveCurrentViewportSize = QSize( normalizeEvenDimension(nCurrentWidth), normalizeEvenDimension(nCurrentHeight)); createUi(); populateFrameItems(); populateVideoFormats(); setTargetFilePath(getSuggestedTargetFilePath()); onResolutionPresetChanged(Resolution_CurrentView); onFrameRangeChanged(); } nmPebiResultAnimationExportSettings nmWxPebiResultAnimationExportDlg::getSettings() const { return m_oAcceptedSettings; } QString nmWxPebiResultAnimationExportDlg::getTargetFilePath() const { return m_sAcceptedTargetFilePath; } void nmWxPebiResultAnimationExportDlg::onFrameRangeChanged() { const int nStartIndex = m_pStartFrameCombo->currentIndex(); if(nStartIndex >= 0 && m_pEndFrameCombo->currentIndex() < nStartIndex) { const bool bBlocked = m_pEndFrameCombo->blockSignals(true); m_pEndFrameCombo->setCurrentIndex(nStartIndex); m_pEndFrameCombo->blockSignals(bBlocked); } const int nRangeLength = qMax(1, m_pEndFrameCombo->currentIndex() - m_pStartFrameCombo->currentIndex()); m_pSamplingIntervalSpinBox->setMaximum(nRangeLength); updateSummary(); } void nmWxPebiResultAnimationExportDlg::onResolutionPresetChanged( int nPresetId) { const bool bCustom = nPresetId == Resolution_Custom; m_pWidthSpinBox->setEnabled(bCustom); m_pHeightSpinBox->setEnabled(bCustom); updateSummary(); } void nmWxPebiResultAnimationExportDlg::normalizeCustomDimensions() { // editingFinished后再回写,避免valueChanged期间干扰用户键盘输入。 m_pWidthSpinBox->setValue( normalizeEvenDimension(m_pWidthSpinBox->value())); m_pHeightSpinBox->setValue( normalizeEvenDimension(m_pHeightSpinBox->value())); updateSummary(); } void nmWxPebiResultAnimationExportDlg::updateSummary() { const nmPebiResultAnimationExportSettings oSettings = collectSettings(); const int nOutputFrameCount = oSettings.getOutputFrameCount(); m_pOutputFrameCountLabel->setText( tr("%1 frames").arg(nOutputFrameCount)); const double dDuration = oSettings.m_nFramesPerSecond > 0 ? static_cast(nOutputFrameCount) / static_cast(oSettings.m_nFramesPerSecond) : 0.0; m_pEstimatedDurationLabel->setText( tr("%1 s").arg(QString::number(dDuration, 'g', 4))); clearError(); } void nmWxPebiResultAnimationExportDlg::onVideoFormatChanged(int nIndex) { Q_UNUSED(nIndex); if(m_pTargetPathEdit != nullptr && !m_pTargetPathEdit->text().isEmpty()) { setTargetFilePath(normalizeTargetFilePath( m_pTargetPathEdit->text())); } clearError(); } void nmWxPebiResultAnimationExportDlg::browseTargetFile() { const QString sExtension = getSelectedVideoExtension(); if(sExtension.isEmpty()) { showError(tr("The selected video format is unsupported.")); return; } const QString sInitialPath = m_pTargetPathEdit->text().isEmpty() ? getSuggestedTargetFilePath() : m_pTargetPathEdit->text(); const QFileInfo oInitialFileInfo(sInitialPath); QFileDialog oFileDialog(this, tr("Save Result Animation")); oFileDialog.setAcceptMode(QFileDialog::AcceptSave); oFileDialog.setFileMode(QFileDialog::AnyFile); oFileDialog.setNameFilter(getSelectedVideoFilter()); oFileDialog.setDefaultSuffix(sExtension); oFileDialog.setOption(QFileDialog::DontConfirmOverwrite, true); oFileDialog.setDirectory(oInitialFileInfo.absolutePath()); oFileDialog.selectFile(oInitialFileInfo.fileName()); if(oFileDialog.exec() != QDialog::Accepted || oFileDialog.selectedFiles().isEmpty()) { return; } const QString sTargetPath = normalizeTargetFilePath( oFileDialog.selectedFiles().first()); const QFileInfo oTargetFileInfo(sTargetPath); setTargetFilePath(oTargetFileInfo.absoluteFilePath()); ZxBaseUtil::setLastDir(oTargetFileInfo.absolutePath()); clearError(); } void nmWxPebiResultAnimationExportDlg::acceptSettings() { // 只有自定义模式需要把编辑值回写为编码器最终使用的偶数尺寸。 if(m_pCustomSizeRadioButton->isChecked()) { normalizeCustomDimensions(); } const nmPebiResultAnimationExportSettings oSettings = collectSettings(); if(!oSettings.isValid(m_vecFrameTimesHours.size())) { showError(tr("The animation export settings are invalid.")); return; } if(m_pTargetPathEdit->text().isEmpty()) { showError(tr("Please select an output file.")); return; } const QString sExtension = getSelectedVideoExtension(); if(sExtension.isEmpty()) { showError(tr("The selected video format is unsupported.")); return; } const QString sTargetPath = QFileInfo(normalizeTargetFilePath( m_pTargetPathEdit->text())).absoluteFilePath(); const QFileInfo oTargetFileInfo(sTargetPath); if(oTargetFileInfo.suffix().compare( sExtension, Qt::CaseInsensitive) != 0) { showError(tr("The selected video file extension does not match " "the selected format.")); return; } if(oTargetFileInfo.isDir()) { showError(tr("The selected output path is a directory.")); return; } if(!QDir(oTargetFileInfo.absolutePath()).exists()) { showError(tr("The selected output directory does not exist.")); return; } setTargetFilePath(sTargetPath); if(oTargetFileInfo.exists() && QMessageBox::question( this, tr("Export Animation"), tr("The selected file already exists. Replace it?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) { return; } m_oAcceptedSettings = oSettings; m_sAcceptedTargetFilePath = sTargetPath; accept(); } void nmWxPebiResultAnimationExportDlg::createUi() { setWindowTitle(tr("Export Animation")); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setMinimumWidth(460); QVBoxLayout* pMainLayout = new QVBoxLayout(this); pMainLayout->setContentsMargins(12, 10, 12, 10); pMainLayout->setSpacing(8); QGroupBox* pFrameGroup = new QGroupBox(tr("Frame Settings"), this); QGridLayout* pFrameGrid = new QGridLayout(pFrameGroup); pFrameGrid->setHorizontalSpacing(8); pFrameGrid->setVerticalSpacing(6); pFrameGrid->setColumnStretch(1, 1); const QSizePolicy oFieldSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed); m_pFpsSpinBox = new QSpinBox(pFrameGroup); m_pFpsSpinBox->setRange(1, 5000); m_pFpsSpinBox->setValue(15); m_pFpsSpinBox->setSuffix(tr(" FPS")); m_pFpsSpinBox->setSizePolicy(oFieldSizePolicy); pFrameGrid->addWidget( createRightAlignedLabel(tr("Frame rate"), pFrameGroup), 0, 0); pFrameGrid->addWidget(m_pFpsSpinBox, 0, 1); m_pStartFrameCombo = new QComboBox(pFrameGroup); m_pStartFrameCombo->setSizePolicy(oFieldSizePolicy); pFrameGrid->addWidget( createRightAlignedLabel(tr("Start time"), pFrameGroup), 1, 0); pFrameGrid->addWidget(m_pStartFrameCombo, 1, 1); m_pEndFrameCombo = new QComboBox(pFrameGroup); m_pEndFrameCombo->setSizePolicy(oFieldSizePolicy); pFrameGrid->addWidget( createRightAlignedLabel(tr("End time"), pFrameGroup), 2, 0); pFrameGrid->addWidget(m_pEndFrameCombo, 2, 1); m_pSamplingIntervalSpinBox = new QSpinBox(pFrameGroup); m_pSamplingIntervalSpinBox->setRange(1, 1); m_pSamplingIntervalSpinBox->setSizePolicy(oFieldSizePolicy); pFrameGrid->addWidget( createRightAlignedLabel(tr("Frame step"), pFrameGroup), 3, 0); pFrameGrid->addWidget(m_pSamplingIntervalSpinBox, 3, 1); pMainLayout->addWidget(pFrameGroup); QGroupBox* pOutputSizeGroup = new QGroupBox(tr("Output Size"), this); QGridLayout* pOutputSizeGrid = new QGridLayout(pOutputSizeGroup); pOutputSizeGrid->setHorizontalSpacing(6); pOutputSizeGrid->setVerticalSpacing(6); m_pResolutionButtonGroup = new QButtonGroup(this); m_pCurrentSizeRadioButton = new QRadioButton( tr("Current image size") + QString::fromLatin1(" ") + formatPixelSize(m_oEffectiveCurrentViewportSize.width(), m_oEffectiveCurrentViewportSize.height()), pOutputSizeGroup); m_p720pRadioButton = new QRadioButton( formatPixelSize(1280, 720), pOutputSizeGroup); m_p1080pRadioButton = new QRadioButton( formatPixelSize(1920, 1080), pOutputSizeGroup); m_pCustomSizeRadioButton = new QRadioButton( tr("Custom size"), pOutputSizeGroup); m_pResolutionButtonGroup->addButton( m_pCurrentSizeRadioButton, Resolution_CurrentView); m_pResolutionButtonGroup->addButton( m_p720pRadioButton, Resolution_720p); m_pResolutionButtonGroup->addButton( m_p1080pRadioButton, Resolution_1080p); m_pResolutionButtonGroup->addButton( m_pCustomSizeRadioButton, Resolution_Custom); m_pCurrentSizeRadioButton->setChecked(true); pOutputSizeGrid->addWidget( m_pCurrentSizeRadioButton, 0, 1, 1, 5, Qt::AlignLeft); pOutputSizeGrid->addWidget( m_p720pRadioButton, 1, 1, 1, 5, Qt::AlignLeft); pOutputSizeGrid->addWidget( m_p1080pRadioButton, 2, 1, 1, 5, Qt::AlignLeft); pOutputSizeGrid->addWidget(m_pCustomSizeRadioButton, 3, 1, Qt::AlignLeft); m_pWidthSpinBox = new QSpinBox(pOutputSizeGroup); m_pHeightSpinBox = new QSpinBox(pOutputSizeGroup); m_pWidthSpinBox->setRange(2, 8192); m_pHeightSpinBox->setRange(2, 8192); m_pWidthSpinBox->setSingleStep(2); m_pHeightSpinBox->setSingleStep(2); m_pWidthSpinBox->setMinimumWidth(90); m_pWidthSpinBox->setMaximumWidth(110); m_pHeightSpinBox->setMinimumWidth(90); m_pHeightSpinBox->setMaximumWidth(110); m_pWidthSpinBox->setValue(m_oEffectiveCurrentViewportSize.width()); m_pHeightSpinBox->setValue(m_oEffectiveCurrentViewportSize.height()); QLabel* pMultiplyLabel = new QLabel( QString(QChar(0x00D7)), pOutputSizeGroup); pMultiplyLabel->setAlignment(Qt::AlignCenter); QLabel* pPixelLabel = new QLabel( QString::fromLatin1("px"), pOutputSizeGroup); pOutputSizeGrid->addWidget(m_pWidthSpinBox, 3, 2); pOutputSizeGrid->addWidget(pMultiplyLabel, 3, 3, Qt::AlignCenter); pOutputSizeGrid->addWidget(m_pHeightSpinBox, 3, 4); pOutputSizeGrid->addWidget(pPixelLabel, 3, 5, Qt::AlignLeft); // 两侧等权留白使完整尺寸选项块居中,块内控件仍保持左对齐。 pOutputSizeGrid->setColumnStretch(0, 1); pOutputSizeGrid->setColumnStretch(6, 1); pMainLayout->addWidget(pOutputSizeGroup); QGroupBox* pOutputFileGroup = new QGroupBox( tr("Output File"), this); QGridLayout* pOutputFileGrid = new QGridLayout(pOutputFileGroup); pOutputFileGrid->setHorizontalSpacing(8); pOutputFileGrid->setVerticalSpacing(6); pOutputFileGrid->setColumnStretch(1, 1); m_pVideoFormatCombo = new QComboBox(pOutputFileGroup); m_pVideoFormatCombo->setSizePolicy(oFieldSizePolicy); pOutputFileGrid->addWidget( createRightAlignedLabel( tr("File format"), pOutputFileGroup), 0, 0); pOutputFileGrid->addWidget(m_pVideoFormatCombo, 0, 1, 1, 2); m_pTargetPathEdit = new QLineEdit(pOutputFileGroup); m_pTargetPathEdit->setReadOnly(true); m_pTargetPathEdit->setSizePolicy(oFieldSizePolicy); QPushButton* pBrowseButton = new QPushButton( tr("Browse..."), pOutputFileGroup); pBrowseButton->setMinimumWidth(72); pOutputFileGrid->addWidget( createRightAlignedLabel( tr("Save location"), pOutputFileGroup), 1, 0); pOutputFileGrid->addWidget(m_pTargetPathEdit, 1, 1); pOutputFileGrid->addWidget(pBrowseButton, 1, 2); pMainLayout->addWidget(pOutputFileGroup); QGroupBox* pInformationGroup = new QGroupBox( tr("Export Information"), this); QGridLayout* pInformationGrid = new QGridLayout(pInformationGroup); pInformationGrid->setHorizontalSpacing(8); pInformationGrid->setVerticalSpacing(6); m_pOutputFrameCountLabel = new QLabel(pInformationGroup); m_pEstimatedDurationLabel = new QLabel(pInformationGroup); m_pOutputFrameCountLabel->setTextInteractionFlags( Qt::TextSelectableByMouse); m_pEstimatedDurationLabel->setTextInteractionFlags( Qt::TextSelectableByMouse); pInformationGrid->addWidget( createRightAlignedLabel( tr("Output frames"), pInformationGroup), 0, 1); pInformationGrid->addWidget( m_pOutputFrameCountLabel, 0, 2, Qt::AlignLeft); pInformationGrid->addWidget( createRightAlignedLabel( tr("Estimated duration"), pInformationGroup), 1, 1); pInformationGrid->addWidget( m_pEstimatedDurationLabel, 1, 2, Qt::AlignLeft); // 标签列和数值列作为一个整体居中,保留内部左右对齐关系。 pInformationGrid->setColumnStretch(0, 1); pInformationGrid->setColumnStretch(3, 1); pMainLayout->addWidget(pInformationGroup); m_pErrorLabel = new QLabel(this); m_pErrorLabel->setStyleSheet("QLabel { color: #a32626; }"); m_pErrorLabel->setWordWrap(true); m_pErrorLabel->hide(); pMainLayout->addWidget(m_pErrorLabel); QDialogButtonBox* pButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this); QPushButton* pOkButton = pButtonBox->button(QDialogButtonBox::Ok); QPushButton* pCancelButton = pButtonBox->button(QDialogButtonBox::Cancel); pOkButton->setIcon(QIcon(exportDialogIconPath("OK.png"))); pCancelButton->setIcon(QIcon(exportDialogIconPath("Cancel.png"))); pOkButton->setIconSize(QSize(16, 16)); pCancelButton->setIconSize(QSize(16, 16)); pOkButton->setMinimumWidth(72); pCancelButton->setMinimumWidth(72); pMainLayout->addWidget(pButtonBox, 0, Qt::AlignRight); connect(m_pStartFrameCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onFrameRangeChanged())); connect(m_pEndFrameCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onFrameRangeChanged())); connect(m_pSamplingIntervalSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateSummary())); connect(m_pFpsSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateSummary())); connect(m_pResolutionButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(onResolutionPresetChanged(int))); connect(m_pVideoFormatCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onVideoFormatChanged(int))); connect(pBrowseButton, SIGNAL(clicked()), this, SLOT(browseTargetFile())); connect(m_pWidthSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateSummary())); connect(m_pHeightSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateSummary())); connect(m_pWidthSpinBox, SIGNAL(editingFinished()), this, SLOT(normalizeCustomDimensions())); connect(m_pHeightSpinBox, SIGNAL(editingFinished()), this, SLOT(normalizeCustomDimensions())); connect(pButtonBox, SIGNAL(accepted()), this, SLOT(acceptSettings())); connect(pButtonBox, SIGNAL(rejected()), this, SLOT(reject())); } void nmWxPebiResultAnimationExportDlg::populateFrameItems() { for(int nFrameIndex = 0; nFrameIndex < m_vecFrameTimesHours.size(); ++nFrameIndex) { const QString sText = tr("%1 h").arg(QString::number( m_vecFrameTimesHours[nFrameIndex], 'g', 8)); m_pStartFrameCombo->addItem(sText, nFrameIndex); m_pEndFrameCombo->addItem(sText, nFrameIndex); } if(!m_vecFrameTimesHours.isEmpty()) { m_pStartFrameCombo->setCurrentIndex(0); m_pEndFrameCombo->setCurrentIndex( m_vecFrameTimesHours.size() - 1); } } void nmWxPebiResultAnimationExportDlg::populateVideoFormats() { // itemData只保存稳定扩展名,界面翻译不会参与格式识别。 m_pVideoFormatCombo->addItem( tr("MP4 Video (*.mp4)"), QString::fromLatin1("mp4")); m_pVideoFormatCombo->addItem( tr("AVI Video (*.avi)"), QString::fromLatin1("avi")); m_pVideoFormatCombo->addItem( tr("QuickTime Video (*.mov)"), QString::fromLatin1("mov")); m_pVideoFormatCombo->addItem( tr("MPEG Video (*.mpg)"), QString::fromLatin1("mpg")); m_pVideoFormatCombo->addItem( tr("WebM Video (*.webm)"), QString::fromLatin1("webm")); m_pVideoFormatCombo->addItem( tr("Matroska Video (*.mkv)"), QString::fromLatin1("mkv")); m_pVideoFormatCombo->addItem( tr("Windows Media Video (*.wmv)"), QString::fromLatin1("wmv")); m_pVideoFormatCombo->addItem( tr("Flash Video (*.flv)"), QString::fromLatin1("flv")); m_pVideoFormatCombo->setCurrentIndex(0); } nmPebiResultAnimationExportSettings nmWxPebiResultAnimationExportDlg::collectSettings() const { nmPebiResultAnimationExportSettings oSettings; oSettings.m_nFramesPerSecond = m_pFpsSpinBox->value(); oSettings.m_nStartFrameIndex = m_pStartFrameCombo->itemData( m_pStartFrameCombo->currentIndex()).toInt(); oSettings.m_nEndFrameIndex = m_pEndFrameCombo->itemData( m_pEndFrameCombo->currentIndex()).toInt(); oSettings.m_nSamplingInterval = m_pSamplingIntervalSpinBox->value(); const QSize oOutputSize = getSelectedOutputSize(); oSettings.m_nOutputWidth = oOutputSize.width(); oSettings.m_nOutputHeight = oOutputSize.height(); return oSettings; } QSize nmWxPebiResultAnimationExportDlg::getSelectedOutputSize() const { const int nPresetId = m_pResolutionButtonGroup == nullptr ? Resolution_CurrentView : m_pResolutionButtonGroup->checkedId(); if(nPresetId == Resolution_720p) { return QSize(1280, 720); } if(nPresetId == Resolution_1080p) { return QSize(1920, 1080); } if(nPresetId == Resolution_Custom) { return QSize( normalizeEvenDimension(m_pWidthSpinBox->value()), normalizeEvenDimension(m_pHeightSpinBox->value())); } return m_oEffectiveCurrentViewportSize; } QString nmWxPebiResultAnimationExportDlg::getSelectedVideoExtension() const { if(m_pVideoFormatCombo == nullptr || m_pVideoFormatCombo->currentIndex() < 0) { return QString(); } const QString sExtension = m_pVideoFormatCombo->itemData( m_pVideoFormatCombo->currentIndex()).toString().toLower(); return nmFFmpegVideoWriter::isSupportedExtension(sExtension) ? sExtension : QString(); } QString nmWxPebiResultAnimationExportDlg::getSelectedVideoFilter() const { return m_pVideoFormatCombo == nullptr ? QString() : m_pVideoFormatCombo->currentText(); } QString nmWxPebiResultAnimationExportDlg::normalizeTargetFilePath( const QString& sPath) const { const QString sExtension = getSelectedVideoExtension(); if(sPath.isEmpty() || sExtension.isEmpty()) { return QString(); } QString sNormalizedPath = QDir::fromNativeSeparators(sPath); const QString sExistingExtension = QFileInfo(sNormalizedPath).suffix().toLower(); if(sExistingExtension.isEmpty()) { if(!sNormalizedPath.endsWith('.')) { sNormalizedPath.append('.'); } sNormalizedPath.append(sExtension); } else if(nmFFmpegVideoWriter::isSupportedExtension( sExistingExtension)) { sNormalizedPath.chop(sExistingExtension.length()); sNormalizedPath.append(sExtension); } else { sNormalizedPath.append( QString::fromLatin1(".%1").arg(sExtension)); } return QDir::cleanPath(sNormalizedPath); } QString nmWxPebiResultAnimationExportDlg::getSuggestedTargetFilePath() const { QString sDirectory = ZxBaseUtil::getLastDir(); if(sDirectory.isEmpty() || !QDir(sDirectory).exists()) { sDirectory = QDir::homePath(); } return QDir(sDirectory).absoluteFilePath( QString::fromLatin1("PEBI-result.%1").arg( getSelectedVideoExtension())); } void nmWxPebiResultAnimationExportDlg::setTargetFilePath( const QString& sPath) { const QString sDisplayPath = QDir::toNativeSeparators(sPath); m_pTargetPathEdit->setText(sDisplayPath); m_pTargetPathEdit->setToolTip(sDisplayPath); } void nmWxPebiResultAnimationExportDlg::showError(const QString& sError) { m_pErrorLabel->setText(sError); m_pErrorLabel->show(); } void nmWxPebiResultAnimationExportDlg::clearError() { m_pErrorLabel->clear(); m_pErrorLabel->hide(); } int nmWxPebiResultAnimationExportDlg::normalizeEvenDimension(int nValue) { nValue = qBound(2, nValue, 8192); if((nValue % 2) != 0) { ++nValue; } return qMin(nValue, 8192); }