From 4027f40851da065d09157cf58a00ffe0126a59e3 Mon Sep 17 00:00:00 2001 From: Vekhir Date: Thu, 9 Apr 2026 00:26:08 +0200 Subject: [PATCH 1/5] feat: New podcast preferences dialog The new podcast preferences dialog replaces the small checkbox list. It allows direct adjustment of audio recording device, bit rate, and frame rate. For each profile of "Full", "Medium", and "Small", the bit rate, frame rate and video size are reported based on the chosen settings. The dialog is based off the recently added color preferences dialog. Each profile has its own tab. The selected tabs' profile is used for recording. The audio device can be no input, default input, or a specified device. The UI handles the invalid states, so that the user cannot enter invalid combinations. --- resources/darkTheme.qss | 100 ++++++ resources/forms/mainWindow.ui | 12 + resources/lightTheme.qss | 118 +++++++ src/podcast/CMakeLists.txt | 2 + src/podcast/UBPodcastController.cpp | 93 ++++- src/podcast/UBPodcastController.h | 6 + src/podcast/UBPodcastPreferencesDialog.cpp | 386 +++++++++++++++++++++ src/podcast/UBPodcastPreferencesDialog.h | 101 ++++++ src/podcast/UBPodcastRecordingPalette.cpp | 2 +- src/podcast/podcast.pri | 2 + 10 files changed, 807 insertions(+), 15 deletions(-) create mode 100644 src/podcast/UBPodcastPreferencesDialog.cpp create mode 100644 src/podcast/UBPodcastPreferencesDialog.h diff --git a/resources/darkTheme.qss b/resources/darkTheme.qss index 944b7eeac..63d8dde85 100644 --- a/resources/darkTheme.qss +++ b/resources/darkTheme.qss @@ -1020,6 +1020,106 @@ QDialog#colorPreferencesDialog QDialogButtonBox#colorPreferencesButtonBox QPushB font-weight: 600; } +QDialog#podcastPreferencesDialog { + background-color: #353535; +} + +QDialog#podcastPreferencesDialog QWidget#podcastPreferencesHeader { + background-color: #2b2b2b; + border: 1px solid #555555; + border-radius: 8px; +} + +QDialog#podcastPreferencesDialog QLabel#podcastPreferencesHeaderLabel { + color: #f8fafc; + font-size: 14px; + font-weight: 600; + background: transparent; +} + +QDialog#podcastPreferencesDialog QSlider#podcastPreferencesSpinBox { + min-width: 190px; + background: transparent; +} + +QDialog#podcastPreferencesDialog QTabWidget#podcastPreferencesTabWidget::pane { + border: none; + background: transparent; + top: -1px; +} + +QDialog#podcastPreferencesDialog QTabBar::tab, +QDialog#podcastPreferencesDialog QTabBar::tab:top { + background: #2b2b2b; + color: #b7c3d4; + border: 1px solid #555555; + border-radius: 4px; + padding: 8px 18px; + margin-right: 8px; + font-weight: 600; +} + +QDialog#podcastPreferencesDialog QTabBar::tab:selected { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #2a82da, stop: 1 #1e5fa0); + color: #ffffff; + border: 1px solid #2a82da; +} + +QDialog#podcastPreferencesDialog QTabBar::tab:hover:!selected { + background: #404040; + color: #ffffff; +} + +QDialog#podcastPreferencesDialog QLabel#podcastPreferencesProfileTabValue { + min-width: 32px; + padding: 4px 8px; + background: #2a2a2a; + color: #f8fafc; + border: 1px solid #555555; + border-radius: 4px; + font-size: 14px; + font-weight: 600; +} + +QDialog#podcastPreferencesDialog QWidget#podcastPreferencesHint { + background: #4b4324; + border: 1px solid #7b6b33; + border-radius: 6px; +} + +QDialog#podcastPreferencesDialog QLabel#podcastPreferencesHintIcon { + background: transparent; + min-width: 20px; +} + +QDialog#podcastPreferencesDialog QLabel#podcastPreferencesHintText { + background: transparent; + color: #f1e3a8; + font-size: 12px; + line-height: 1.3em; +} + +QDialog#podcastPreferencesDialog QPushButton#podcastPreferencesResetButton { + padding: 7px 14px; + background: #454545; + color: #f8fafc; + border: 1px solid #555555; + border-radius: 4px; + font-weight: 600; +} + +QDialog#podcastPreferencesDialog QPushButton#podcastPreferencesResetButton:hover { + background: #505050; + border: 1px solid #6a6a6a; +} + +QDialog#podcastPreferencesDialog QDialogButtonBox#podcastPreferencesButtonBox QPushButton { + min-width: 92px; + padding: 8px 18px; + border-radius: 4px; + font-weight: 600; +} + QWidget#UBFeaturesActionBar QToolButton { padding: 0px; diff --git a/resources/forms/mainWindow.ui b/resources/forms/mainWindow.ui index 09673691f..5f1e0ab53 100644 --- a/resources/forms/mainWindow.ui +++ b/resources/forms/mainWindow.ui @@ -1671,6 +1671,18 @@ Pause Podcast Recording + + + + :/images/toolbar/settings.png:/images/toolbar/settings.png + + + Podcast Config + + + Configure Podcast Recording + + diff --git a/resources/lightTheme.qss b/resources/lightTheme.qss index dd81d453d..3f3d50e23 100644 --- a/resources/lightTheme.qss +++ b/resources/lightTheme.qss @@ -563,6 +563,124 @@ QDialog#colorPreferencesDialog QDialogButtonBox#colorPreferencesButtonBox QPushB border: 1px solid #2a82da; } +QDialog#podcastPreferencesDialog { + background-color: #f2f6fa; +} + +QDialog#podcastPreferencesDialog QWidget#podcastPreferencesHeader { + background-color: #edf2f7; + border: 1px solid #d9e0e8; + border-radius: 10px; +} + +QDialog#podcastPreferencesDialog QLabel#podcastPreferencesHeaderLabel { + color: #0f172a; + font-size: 14px; + font-weight: 600; + background: transparent; +} + +QDialog#podcastPreferencesDialog QSlider#podcastPreferencesSpinBox { + min-width: 190px; + background: transparent; +} + +QDialog#podcastPreferencesDialog QTabWidget#podcastPreferencesTabWidget::pane { + border: none; + background: transparent; + top: -1px; +} + +QDialog#podcastPreferencesDialog QTabBar::tab, +QDialog#podcastPreferencesDialog QTabBar::tab:top { + background: #e2eaf3; + color: #475569; + border: 1px solid #d9e0e8; + border-radius: 6px; + padding: 8px 18px; + margin-right: 8px; + font-weight: 600; +} + +QDialog#podcastPreferencesDialog QTabBar::tab:selected { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #5aa5e0, stop: 1 #3d8fd1); + color: #ffffff; + border: 1px solid #2a82da; +} + +QDialog#podcastPreferencesDialog QTabBar::tab:hover:!selected { + background: #e8eff7; + color: #1e293b; +} + +QDialog#podcastPreferencesDialog QLabel#podcastPreferencesProfileTabValue { + min-width: 32px; + padding: 4px 8px; + background: #ffffff; + color: #0f172a; + border: 1px solid #d9e0e8; + border-radius: 6px; + font-size: 14px; + font-weight: 600; +} + +QDialog#podcastPreferencesDialog QWidget#podcastPreferencesHint { + background: #fff8db; + border: 1px solid #ead9a2; + border-radius: 8px; +} + +QDialog#podcastPreferencesDialog QLabel#podcastPreferencesHintIcon { + background: transparent; + min-width: 20px; +} + +QDialog#podcastPreferencesDialog QLabel#podcastPreferencesHintText { + background: transparent; + color: #5b4b1f; + font-size: 12px; + line-height: 1.3em; +} + +QDialog#podcastPreferencesDialog QPushButton#podcastPreferencesResetButton { + padding: 7px 14px; + background: #f7f9fc; + color: #334155; + border: 1px solid #d9e0e8; + border-radius: 6px; + font-weight: 600; +} + +QDialog#podcastPreferencesDialog QPushButton#podcastPreferencesResetButton:hover { + background: #eef6ff; + border: 1px solid #d3e4f6; +} + +QDialog#podcastPreferencesDialog QPushButton#podcastPreferencesResetButton:focus, +QDialog#podcastPreferencesDialog QPushButton#podcastPreferencesResetButton:pressed, +QDialog#podcastPreferencesDialog QPushButton#podcastPreferencesResetButton:checked, +QDialog#podcastPreferencesDialog QPushButton#podcastPreferencesResetButton:default { + background: #3d8fd1; + color: #ffffff; + border: 1px solid #2a82da; +} + +QDialog#podcastPreferencesDialog QDialogButtonBox#podcastPreferencesButtonBox QPushButton { + min-width: 92px; + padding: 8px 18px; + border-radius: 6px; + font-weight: 600; +} + +QDialog#podcastPreferencesDialog QDialogButtonBox#podcastPreferencesButtonBox QPushButton:focus, +QDialog#podcastPreferencesDialog QDialogButtonBox#podcastPreferencesButtonBox QPushButton:pressed, +QDialog#podcastPreferencesDialog QDialogButtonBox#podcastPreferencesButtonBox QPushButton:checked, +QDialog#podcastPreferencesDialog QDialogButtonBox#podcastPreferencesButtonBox QPushButton:default { + background-color: #3d8fd1; + color: #ffffff; + border: 1px solid #2a82da; +} + QCheckBox, QRadioButton { color: #1f2937; diff --git a/src/podcast/CMakeLists.txt b/src/podcast/CMakeLists.txt index ca7087809..e5d81e906 100644 --- a/src/podcast/CMakeLists.txt +++ b/src/podcast/CMakeLists.txt @@ -3,6 +3,8 @@ target_sources(${PROJECT_NAME} PRIVATE UBAbstractVideoEncoder.h UBPodcastController.cpp UBPodcastController.h + UBPodcastPreferencesDialog.cpp + UBPodcastPreferencesDialog.h UBPodcastRecordingPalette.cpp UBPodcastRecordingPalette.h intranet/UBIntranetPodcastPublisher.cpp diff --git a/src/podcast/UBPodcastController.cpp b/src/podcast/UBPodcastController.cpp index b15caa706..fd7b1e02f 100644 --- a/src/podcast/UBPodcastController.cpp +++ b/src/podcast/UBPodcastController.cpp @@ -111,6 +111,8 @@ UBPodcastController::UBPodcastController(QObject* pParent) connect(UBApplication::webController, SIGNAL(activeWebPageChanged(WebView*)), this, SLOT(webActiveWebPageChanged(WebView*))); + connect(UBApplication::mainWindow->actionPodcastPreferences, SIGNAL(triggered()), this, SLOT(openPodcastPreferencesDialog())); + connect(UBApplication::mainWindow, &UBMainWindow::closeEvent_Signal, this, &UBPodcastController::applicationAboutToQuit); } @@ -151,6 +153,73 @@ void UBPodcastController::actionToggled(bool checked) updateActionState(); } +void UBPodcastController::openPodcastPreferencesDialog() +{ + if (mPodcastPreferencesDialog) + { + mPodcastPreferencesDialog->raise(); + mPodcastPreferencesDialog->activateWindow(); + return; + } + + UBSettings* settings = UBSettings::settings(); + + QString podcastAudioRecordingDeviceSetting = settings->podcastAudioRecordingDevice->get().toString(); + if (podcastAudioRecordingDeviceSetting != "Default" && + podcastAudioRecordingDeviceSetting != "None" && + !audioRecordingDevices().contains(podcastAudioRecordingDeviceSetting)) + { + podcastAudioRecordingDeviceSetting = settings->podcastAudioRecordingDevice->reset().toString(); + } + const QString podcastAudioRecordingDevice = podcastAudioRecordingDeviceSetting; + const QStringList podcastProfileNames = QStringList({"Full", "Medium", "Small"}); + if (!podcastProfileNames.contains(settings->podcastVideoSize->get().toString())) + { + settings->podcastVideoSize->reset().toString(); + } + const QString podcastProfile = settings->podcastVideoSize->get().toString(); + mVideoFramesPerSecondAtStart = settings->podcastFramesPerSecond->get().toInt(); + const int fullBitRate = settings->podcastWindowsMediaBitsPerSecond->get().toInt(); + const QSize viewportSize = UBApplication::boardController->controlView()->size(); + + const QList podcastBitRateDivisors = QList({1, 2, 4}); + const QList podcastVerticalResolution = QList({viewportSize.height(), 768, 480}); + const QList podcastHorizontalResolution = QList({viewportSize.width(), 1024, 640}); + + mPodcastPreferencesDialog = new UBPodcastPreferencesDialog(UBApplication::mainWindow, + audioRecordingDevices(), + podcastAudioRecordingDevice, + podcastProfileNames, + podcastProfile, + mVideoFramesPerSecondAtStart, + fullBitRate, + podcastBitRateDivisors, + podcastVerticalResolution, + podcastHorizontalResolution); + mPodcastPreferencesDialog->setAttribute(Qt::WA_DeleteOnClose); + connect(mPodcastPreferencesDialog, SIGNAL(accepted()), this, SLOT(podcastPreferencesDialogAccepted())); + mPodcastPreferencesDialog->open(); +} + +void UBPodcastController::podcastPreferencesDialogAccepted() +{ + UBPodcastPreferencesDialog* dialog = qobject_cast(sender()); + if (!dialog) + return; + + UBSettings* settings = UBSettings::settings(); + const QString podcastAudioRecordingDevice = dialog->podcastAudioRecordingOption(); + settings->podcastAudioRecordingDevice->set(podcastAudioRecordingDevice); + + const QString podcastProfile = dialog->podcastProfile(); + settings->podcastVideoSize->set(podcastProfile); + + mVideoBitsPerSecondAtStart = dialog->podcastBitRate(); + settings->podcastWindowsMediaBitsPerSecond->set(QVariant::fromValue(mVideoBitsPerSecondAtStart)); + + mVideoFramesPerSecondAtStart = dialog->podcastFrameRate(); + settings->podcastFramesPerSecond->set(mVideoFramesPerSecondAtStart); +} void UBPodcastController::updateActionState() { @@ -282,19 +351,21 @@ void UBPodcastController::start() QSize recommendedSize(1024, 768); - int fullBitRate = UBSettings::settings()->podcastWindowsMediaBitsPerSecond->get().toInt(); + UBSettings* settings = UBSettings::settings(); + int fullBitRate = settings->podcastWindowsMediaBitsPerSecond->get().toInt(); + QString podcastProfile = settings->podcastVideoSize->get().toString(); - if (mSmallVideoSizeAction && mSmallVideoSizeAction->isChecked()) + if (podcastProfile == "Small") { recommendedSize = QSize(640, 480); mVideoBitsPerSecondAtStart = fullBitRate / 4; } - else if (mMediumVideoSizeAction && mMediumVideoSizeAction->isChecked()) + else if (podcastProfile == "Medium") { recommendedSize = QSize(1024, 768); mVideoBitsPerSecondAtStart = fullBitRate / 2; } - else if (mFullVideoSizeAction && mFullVideoSizeAction->isChecked()) + else if (podcastProfile == "Full") { recommendedSize = UBApplication::boardController->controlView()->size(); mVideoBitsPerSecondAtStart = fullBitRate; @@ -337,20 +408,14 @@ void UBPodcastController::start() , mRecordingPalette, SLOT(audioLevelChanged(quint8))); } - mVideoEncoder->setRecordAudio(!mNoAudioInputDeviceAction->isChecked()); + QString podcastAudioRecordingOption = settings->podcastAudioRecordingDevice->get().toString(); + mVideoEncoder->setRecordAudio(podcastAudioRecordingOption != "None"); QString recordingDevice = ""; - if (!mNoAudioInputDeviceAction->isChecked() && !mDefaultAudioInputDeviceAction->isChecked()) + if (podcastAudioRecordingOption != "None" && podcastAudioRecordingOption != "Default") { - foreach(QAction* audioDevice, mAudioInputDevicesActions) - { - if (audioDevice->isChecked()) - { - recordingDevice = audioDevice->text(); - break; - } - } + recordingDevice = podcastAudioRecordingOption; } mVideoEncoder->setAudioRecordingDevice(recordingDevice); diff --git a/src/podcast/UBPodcastController.h b/src/podcast/UBPodcastController.h index 89143892b..948fdd736 100644 --- a/src/podcast/UBPodcastController.h +++ b/src/podcast/UBPodcastController.h @@ -33,6 +33,7 @@ #include #include "UBAbstractVideoEncoder.h" +#include "UBPodcastPreferencesDialog.h" #include "core/UBApplicationController.h" @@ -88,6 +89,9 @@ class UBPodcastController : public QObject void recordToggled(bool record); void pauseToggled(bool pause); + void openPodcastPreferencesDialog(); + void podcastPreferencesDialogAccepted(); + protected: virtual void setSourceWidget(QWidget* pWidget); @@ -180,6 +184,8 @@ class UBPodcastController : public QObject QList mAudioInputDevicesActions; QList mVideoSizesActions; + QPointer mPodcastPreferencesDialog; + QAction* mSmallVideoSizeAction; QAction* mMediumVideoSizeAction; QAction* mFullVideoSizeAction; diff --git a/src/podcast/UBPodcastPreferencesDialog.cpp b/src/podcast/UBPodcastPreferencesDialog.cpp new file mode 100644 index 000000000..39c801d90 --- /dev/null +++ b/src/podcast/UBPodcastPreferencesDialog.cpp @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2015-2022 Département de l'Instruction Publique (DIP-SEM) + * + * Copyright (C) 2013 Open Education Foundation + * + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour + * l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of OpenBoard. + * + * OpenBoard is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * OpenBoard is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OpenBoard. If not, see . + */ + +#include "UBPodcastPreferencesDialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/UBApplication.h" +#include "core/UBSettings.h" +#include "gui/UBMainWindow.h" + + +UBPodcastPreferencesDialog::UBPodcastPreferencesDialog(QWidget* parent, + const QStringList& podcastAudioRecordingDevices, + const QString podcastAudioRecordingOption, + const QStringList& podcastProfileNames, + const QString podcastProfile, + const int podcastFrameRate, + const int podcastBitRate, + const QList& podcastBitRateDivisor, + const QList& podcastVerticalResolution, + const QList& podcastHorizontalResolution) + : QDialog(parent) + , mPodcastAudioRecordingDevices(podcastAudioRecordingDevices) + , mPodcastProfileNames(podcastProfileNames) + , mPodcastBitRateDivisor(podcastBitRateDivisor) +{ + setModal(true); + setWindowTitle(tr("Podcast Preferences")); + setMinimumWidth(520); + setObjectName(QStringLiteral("podcastPreferencesDialog")); + + QVBoxLayout* mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(20, 18, 20, 18); + mainLayout->setSpacing(16); + + const QString audioDeviceSelectorToolTip = tr("The selected audio device applies to all profiles."); + + QWidget* audioWidget = new QWidget(this); + audioWidget->setObjectName(QStringLiteral("podcastPreferencesHeader")); + QHBoxLayout* audioLayout = new QHBoxLayout(audioWidget); + audioLayout->setContentsMargins(14, 12, 14, 12); + audioLayout->setSpacing(12); + + QLabel* audioLabel = new QLabel(tr("Audio Recording Device"), audioWidget); + audioLabel->setObjectName(QStringLiteral("podcastPreferencesHeaderLabel")); + audioLabel->setToolTip(audioDeviceSelectorToolTip); + mNoAudioCheckBox = new QCheckBox(tr("Don't record audio")); + mNoAudioCheckBox->setToolTip(audioDeviceSelectorToolTip); + mDefaultAudioCheckBox = new QCheckBox(tr("Use default audio input")); + mDefaultAudioCheckBox->setToolTip(audioDeviceSelectorToolTip); + mAudioDeviceSelector = new QComboBox(audioWidget); + mAudioDeviceSelector->addItems(mPodcastAudioRecordingDevices); + mAudioDeviceSelector->setToolTip(audioDeviceSelectorToolTip); + connect(mNoAudioCheckBox, SIGNAL(toggled(bool)), this, SLOT(toggleNoAudioDevice(bool))); + connect(mDefaultAudioCheckBox, SIGNAL(toggled(bool)), this, SLOT(toggleDefaultAudioDevice(bool))); + if (podcastAudioRecordingOption == "None") + { + setNoAudioDevice(); + } + else if (podcastAudioRecordingOption == "Default") + { + setDefaultAudioDevice(); + } + else + { + mAudioDeviceSelector->setCurrentIndex(mPodcastAudioRecordingDevices.indexOf(podcastAudioRecordingOption)); + } + audioLayout->addWidget(audioLabel); + audioLayout->addWidget(mNoAudioCheckBox); + audioLayout->addWidget(mDefaultAudioCheckBox); + audioLayout->addWidget(mAudioDeviceSelector); + mainLayout->addWidget(audioWidget); + + const QString frameRateFormToolTip = tr("The global frame rate applies to all profiles."); + + QWidget* frameRateFormWidget = new QWidget(this); + frameRateFormWidget->setObjectName(QStringLiteral("podcastPreferencesSpinBox")); + QHBoxLayout* frameRateFormLayout = new QHBoxLayout(frameRateFormWidget); + frameRateFormLayout->setContentsMargins(14, 12, 14, 12); + frameRateFormLayout->setSpacing(12); + mFrameRateSpinBox = new QSpinBox(frameRateFormWidget); + mFrameRateSpinBox->setRange(0, 1000000); + mFrameRateSpinBox->setValue(podcastFrameRate); + mFrameRateSpinBox->setSingleStep(1); + mFrameRateSpinBox->setToolTip(frameRateFormToolTip); + connect(mFrameRateSpinBox, SIGNAL(valueChanged(int)), this, SLOT(frameRateChanged(int))); + QLabel* frameRateLabel = new QLabel(tr("Global frame rate"), frameRateFormWidget); + frameRateLabel->setObjectName(QStringLiteral("podcastPreferencesHeaderLabel")); + frameRateLabel->setToolTip(frameRateFormToolTip); + frameRateFormLayout->addWidget(frameRateLabel); + frameRateFormLayout->addWidget(mFrameRateSpinBox); + mainLayout->addWidget(frameRateFormWidget); + + const QString bitRateFormToolTip = tr("The base bit rate is scaled depending on the profile."); + + QWidget* bitRateFormWidget = new QWidget(this); + bitRateFormWidget->setObjectName(QStringLiteral("podcastPreferencesSpinBox")); + QHBoxLayout* bitRateFormLayout = new QHBoxLayout(bitRateFormWidget); + bitRateFormLayout->setContentsMargins(14, 12, 14, 12); + bitRateFormLayout->setSpacing(12); + mBitRateSpinBox = new QSpinBox(bitRateFormWidget); + mBitRateSpinBox->setRange(0, 1000000000); + mBitRateSpinBox->setValue(podcastBitRate); + mBitRateSpinBox->setSingleStep(1000); + mBitRateSpinBox->setToolTip(bitRateFormToolTip); + connect(mBitRateSpinBox, SIGNAL(valueChanged(int)), this, SLOT(bitRateChanged(int))); + QLabel* bitRateLabel = new QLabel(tr("Base bit rate"), bitRateFormWidget); + bitRateLabel->setObjectName(QStringLiteral("podcastPreferencesHeaderLabel")); + bitRateLabel->setToolTip(bitRateFormToolTip); + bitRateFormLayout->addWidget(bitRateLabel); + bitRateFormLayout->addWidget(mBitRateSpinBox); + mainLayout->addWidget(bitRateFormWidget); + + mTabWidget = new QTabWidget(this); + mTabWidget->setObjectName(QStringLiteral("podcastPreferencesTabWidget")); + mTabWidget->setIconSize(QSize(18, 18)); + + for (int i = 0; i < mPodcastProfileNames.size(); ++i) + { + const QString tabLabel = mPodcastProfileNames[i] == QString("Full") ? tr("Full") + : mPodcastProfileNames[i] == QString("Medium") ? tr("Medium") + : mPodcastProfileNames[i] == QString("Small") ? tr("Small") + : mPodcastProfileNames[i]; + QWidget* tab = createProfileTab(mBitRateProfileValues.emplaceBack(), + mFrameRateProfileValues.emplaceBack(), + podcastBitRate / mPodcastBitRateDivisor[i], + podcastFrameRate, + podcastVerticalResolution[i], + podcastHorizontalResolution[i]); + tab->setObjectName(QStringLiteral("podcastPreferencesPenTab")); + mTabWidget->addTab(tab, tabLabel); + } + mTabWidget->setCurrentIndex(mPodcastProfileNames.indexOf(podcastProfile)); + mainLayout->addWidget(mTabWidget); + + QWidget* hintWidget = new QWidget(this); + hintWidget->setObjectName(QStringLiteral("podcastPreferencesHint")); + QHBoxLayout* hintLayout = new QHBoxLayout(hintWidget); + hintLayout->setContentsMargins(14, 10, 14, 10); + hintLayout->setSpacing(10); + + QLabel* hintIcon = new QLabel(hintWidget); + hintIcon->setObjectName(QStringLiteral("podcastPreferencesHintIcon")); + hintIcon->setPixmap(QIcon(QStringLiteral(":/images/toolbar/tip.png")).pixmap(20, 20)); + hintIcon->setAlignment(Qt::AlignTop | Qt::AlignHCenter); + + QLabel* hintLabel = new QLabel( + tr("Frame rate and bit rate are dynamically adjusted during recording to record changes while keeping the file size small."), + hintWidget); + hintLabel->setObjectName(QStringLiteral("podcastPreferencesHintText")); + hintLabel->setWordWrap(true); + + hintLayout->addWidget(hintIcon, 0, Qt::AlignTop); + hintLayout->addWidget(hintLabel, 1); + mainLayout->addWidget(hintWidget); + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + buttons->setObjectName(QStringLiteral("colorPreferencesButtonBox")); + QPushButton* defaultSettingsButton = buttons->addButton(tr("Default settings"), QDialogButtonBox::ResetRole); + defaultSettingsButton->setObjectName(QStringLiteral("colorPreferencesResetButton")); + connect(defaultSettingsButton, SIGNAL(clicked()), this, SLOT(resetDefaultSettings())); + connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); + mainLayout->addWidget(buttons); +} + + +QString UBPodcastPreferencesDialog::podcastAudioRecordingOption() const +{ + if (mNoAudioCheckBox && mNoAudioCheckBox->isChecked()) + { + return "None"; + } + if (mDefaultAudioCheckBox && mDefaultAudioCheckBox->isChecked()) + { + return "Default"; + } + const int currentIndex = mAudioDeviceSelector->currentIndex(); + if (0 <= currentIndex && currentIndex < mPodcastAudioRecordingDevices.size()) + { + return mPodcastAudioRecordingDevices[currentIndex]; + } + return ""; +} + +QString UBPodcastPreferencesDialog::podcastProfile() const +{ + const int currentIndex = mTabWidget->currentIndex(); + if (0 <= currentIndex && currentIndex < mPodcastProfileNames.size()) + { + return mPodcastProfileNames[currentIndex]; + } + return ""; +} + +int UBPodcastPreferencesDialog::podcastBitRate() const +{ + return mBitRateSpinBox->value(); +} + +int UBPodcastPreferencesDialog::podcastFrameRate() const +{ + return mFrameRateSpinBox->value(); +} + +void UBPodcastPreferencesDialog::bitRateChanged(int bitRate) +{ + for (int i = 0; i < mBitRateProfileValues.size(); ++i) + { + mBitRateProfileValues[i]->setNum(bitRate / mPodcastBitRateDivisor[i]); + } +} + +void UBPodcastPreferencesDialog::frameRateChanged(int frameRate) +{ + for (QLabel* label : mFrameRateProfileValues) + { + label->setNum(frameRate); + } +} + +void UBPodcastPreferencesDialog::toggleNoAudioDevice(bool checked) +{ + if (checked) + { + setNoAudioDevice(); + } + else + { + setDefaultAudioDevice(); + } +} + +void UBPodcastPreferencesDialog::setNoAudioDevice() +{ + mNoAudioCheckBox->setChecked(true); + mNoAudioCheckBox->setEnabled(true); + mDefaultAudioCheckBox->setChecked(false); + mDefaultAudioCheckBox->setEnabled(false); + mAudioDeviceSelector->setEnabled(false); +} + +void UBPodcastPreferencesDialog::toggleDefaultAudioDevice(bool checked) +{ + if (checked) + { + setDefaultAudioDevice(); + } + else if (mNoAudioCheckBox && !mNoAudioCheckBox->isChecked()) + { + mAudioDeviceSelector->setEnabled(true); + } +} + +void UBPodcastPreferencesDialog::setDefaultAudioDevice() +{ + mNoAudioCheckBox->setChecked(false); + mNoAudioCheckBox->setEnabled(true); + mDefaultAudioCheckBox->setChecked(true); + mDefaultAudioCheckBox->setEnabled(true); + mAudioDeviceSelector->setEnabled(false); +} + +void UBPodcastPreferencesDialog::resetDefaultSettings() +{ + setDefaultAudioDevice(); + mTabWidget->setCurrentIndex(mPodcastProfileNames.indexOf("Medium")); + mBitRateSpinBox->setValue(1700000); + mFrameRateSpinBox->setValue(10); +} + + +QWidget* UBPodcastPreferencesDialog::createProfileTab(QLabel* &bitRateValue, + QLabel* &frameRateValue, + const int bitRate, + const int frameRate, + const int height, + const int width) +{ + QWidget* tab = new QWidget(this); + QVBoxLayout* tabLayout = new QVBoxLayout(tab); + tabLayout->setContentsMargins(8, 14, 8, 6); + tabLayout->setSpacing(14); + + const QString bitRateToolTip = tr("The bit rate determines the quality of each frame."); + QWidget* bitRateWidget = new QWidget(this); + bitRateWidget->setObjectName(QStringLiteral("podcastPreferencesHeader")); + QHBoxLayout* bitRateLayout = new QHBoxLayout(bitRateWidget); + bitRateLayout->setContentsMargins(14, 12, 14, 12); + bitRateLayout->setSpacing(12); + QLabel* bitRateLabel = new QLabel(tr("Bit rate"), bitRateWidget); + bitRateLabel->setAlignment(Qt::AlignCenter); + bitRateLabel->setToolTip(bitRateToolTip); + bitRateValue = new QLabel(QString::number(bitRate), bitRateWidget); + bitRateValue->setObjectName(QStringLiteral("podcastPreferencesProfileTabValue")); + bitRateValue->setAlignment(Qt::AlignCenter); + bitRateValue->setToolTip(bitRateToolTip); + bitRateLayout->addWidget(bitRateLabel); + bitRateLayout->addWidget(bitRateValue); + tabLayout->addWidget(bitRateWidget); + + const QString frameRateToolTip = tr("The frame rate determines how fast changes are recorded."); + QWidget* frameRateWidget = new QWidget(this); + frameRateWidget->setObjectName(QStringLiteral("podcastPreferencesHeader")); + QHBoxLayout* frameRateLayout = new QHBoxLayout(frameRateWidget); + frameRateLayout->setContentsMargins(14, 12, 14, 12); + frameRateLayout->setSpacing(12); + QLabel* frameRateLabel = new QLabel(tr("Frame rate"), frameRateWidget); + frameRateLabel->setAlignment(Qt::AlignCenter); + frameRateLabel->setToolTip(frameRateToolTip); + frameRateValue = new QLabel(QString::number(frameRate), frameRateWidget); + frameRateValue->setObjectName(QStringLiteral("podcastPreferencesProfileTabValue")); + frameRateValue->setAlignment(Qt::AlignCenter); + frameRateValue->setToolTip(frameRateToolTip); + frameRateLayout->addWidget(frameRateLabel); + frameRateLayout->addWidget(frameRateValue); + tabLayout->addWidget(frameRateWidget); + + const QString widthToolTip = tr("The width determines the horizontal detail of each frame."); + QWidget* widthWidget = new QWidget(this); + widthWidget->setObjectName(QStringLiteral("podcastPreferencesHeader")); + QHBoxLayout* widthLayout = new QHBoxLayout(widthWidget); + widthLayout->setContentsMargins(14, 12, 14, 12); + widthLayout->setSpacing(12); + QLabel* widthLabel = new QLabel(tr("Horizontal Resolution"), widthWidget); + widthLabel->setAlignment(Qt::AlignCenter); + widthLabel->setToolTip(widthToolTip); + QLabel* widthValue = new QLabel(QString::number(width), widthWidget); + widthValue->setObjectName(QStringLiteral("podcastPreferencesProfileTabValue")); + widthValue->setAlignment(Qt::AlignCenter); + widthValue->setToolTip(widthToolTip); + widthLayout->addWidget(widthLabel); + widthLayout->addWidget(widthValue); + tabLayout->addWidget(widthWidget); + + const QString heightToolTip = tr("The height determines the vertical detail of each frame."); + QWidget* heightWidget = new QWidget(this); + heightWidget->setObjectName(QStringLiteral("podcastPreferencesHeader")); + QHBoxLayout* heightLayout = new QHBoxLayout(heightWidget); + heightLayout->setContentsMargins(14, 12, 14, 12); + heightLayout->setSpacing(12); + QLabel* heightLabel = new QLabel(tr("Vertical Resolution"), heightWidget); + heightLabel->setAlignment(Qt::AlignCenter); + heightLabel->setToolTip(heightToolTip); + QLabel* heightValue = new QLabel(QString::number(height), heightWidget); + heightValue->setObjectName(QStringLiteral("podcastPreferencesProfileTabValue")); + heightValue->setAlignment(Qt::AlignCenter); + heightValue->setToolTip(heightToolTip); + heightLayout->addWidget(heightLabel); + heightLayout->addWidget(heightValue); + tabLayout->addWidget(heightWidget); + + return tab; +} diff --git a/src/podcast/UBPodcastPreferencesDialog.h b/src/podcast/UBPodcastPreferencesDialog.h new file mode 100644 index 000000000..bdfda42b7 --- /dev/null +++ b/src/podcast/UBPodcastPreferencesDialog.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2015-2022 Département de l'Instruction Publique (DIP-SEM) + * + * Copyright (C) 2013 Open Education Foundation + * + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour + * l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of OpenBoard. + * + * OpenBoard is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * OpenBoard is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OpenBoard. If not, see . + */ + + + + +#ifndef UBPODCASTPREFERENCESDIALOG_H +#define UBPODCASTPREFERENCESDIALOG_H + +#include +#include +#include +#include +#include +#include +#include + +class QWidget; +class QLabel; +class QTabWidget; +class QPushButton; + +class UBPodcastPreferencesDialog : public QDialog +{ + Q_OBJECT + + public: + UBPodcastPreferencesDialog(QWidget* parent, + const QStringList& podcastAudioRecordingOptions, + const QString podcastAudioRecordingDevice, + const QStringList& podcastProfileNames, + const QString podcastProfile, + const int podcastFramerate, + const int podcastBitRate, + const QList& podcastBitRateDivisors, + const QList& podcastVerticalResolution, + const QList& podcastHorizontalResolution); + + QString podcastAudioRecordingOption() const; + QString podcastProfile() const; + int podcastBitRate() const; + int podcastFrameRate() const; + + private slots: + void bitRateChanged(int bitRate); + void frameRateChanged(int frameRate); + void toggleNoAudioDevice(bool checked); + void toggleDefaultAudioDevice(bool checked); + void resetDefaultSettings(); + + private: + QWidget* createProfileTab(QLabel* &bitRateValue, + QLabel* &frameRateValue, + const int bitRate, + const int frameRate, + const int height, + const int width); + void setNoAudioDevice(); + void setDefaultAudioDevice(); + + QCheckBox* mDefaultAudioCheckBox{nullptr}; + QCheckBox* mNoAudioCheckBox{nullptr}; + QComboBox* mAudioDeviceSelector{nullptr}; + QSpinBox* mBitRateSpinBox{nullptr}; + QSpinBox* mFrameRateSpinBox{nullptr}; + QTabWidget* mTabWidget{nullptr}; + QPushButton* mResetPenButton{nullptr}; + QPushButton* mResetMarkerButton{nullptr}; + QList mBitRateProfileValues; + QList mFrameRateProfileValues; + QStringList mPodcastAudioRecordingDevices; + QStringList mPodcastProfileNames; + QList mPodcastBitRateDivisor; +}; + + + +#endif // UBPODCASTPREFERENCESDIALOG_H diff --git a/src/podcast/UBPodcastRecordingPalette.cpp b/src/podcast/UBPodcastRecordingPalette.cpp index 8082f27b2..b561b1b6b 100644 --- a/src/podcast/UBPodcastRecordingPalette.cpp +++ b/src/podcast/UBPodcastRecordingPalette.cpp @@ -56,7 +56,7 @@ UBPodcastRecordingPalette::UBPodcastRecordingPalette(QWidget *parent) mLevelMeter->setMinimumSize(6, 32); layout()->addWidget(mLevelMeter); - + addAction(UBApplication::mainWindow->actionPodcastPreferences); addAction(UBApplication::mainWindow->actionPodcastConfig); #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) diff --git a/src/podcast/podcast.pri b/src/podcast/podcast.pri index 22bab613e..561565686 100644 --- a/src/podcast/podcast.pri +++ b/src/podcast/podcast.pri @@ -1,12 +1,14 @@ HEADERS += src/podcast/UBPodcastController.h \ src/podcast/UBAbstractVideoEncoder.h \ + src/podcast/UBPodcastPreferencesDialog.h \ src/podcast/UBPodcastRecordingPalette.h \ src/podcast/youtube/UBYouTubePublisher.h \ src/podcast/intranet/UBIntranetPodcastPublisher.h \ SOURCES += src/podcast/UBPodcastController.cpp \ src/podcast/UBAbstractVideoEncoder.cpp \ + src/podcast/UBPodcastPreferencesDialog.cpp \ src/podcast/UBPodcastRecordingPalette.cpp \ src/podcast/youtube/UBYouTubePublisher.cpp \ src/podcast/intranet/UBIntranetPodcastPublisher.cpp \ From 0b4770adf3b766d202f7632b79bba821c433ae2c Mon Sep 17 00:00:00 2001 From: Vekhir Date: Thu, 9 Apr 2026 02:49:13 +0200 Subject: [PATCH 2/5] feat: Upload dialog in podcast preferences The upload dialog contains options related to uploading the recorded videos. --- src/podcast/UBPodcastController.cpp | 18 +++++-- src/podcast/UBPodcastPreferencesDialog.cpp | 60 ++++++++++++++++++---- src/podcast/UBPodcastPreferencesDialog.h | 8 ++- 3 files changed, 70 insertions(+), 16 deletions(-) diff --git a/src/podcast/UBPodcastController.cpp b/src/podcast/UBPodcastController.cpp index fd7b1e02f..061a6ffad 100644 --- a/src/podcast/UBPodcastController.cpp +++ b/src/podcast/UBPodcastController.cpp @@ -186,6 +186,9 @@ void UBPodcastController::openPodcastPreferencesDialog() const QList podcastVerticalResolution = QList({viewportSize.height(), 768, 480}); const QList podcastHorizontalResolution = QList({viewportSize.width(), 1024, 640}); + const bool podcastPublishToIntranet = settings->podcastPublishToIntranet->get().toBool(); + const bool podcastPublishToYoutube = settings->podcastPublishToYoutube->get().toBool(); + mPodcastPreferencesDialog = new UBPodcastPreferencesDialog(UBApplication::mainWindow, audioRecordingDevices(), podcastAudioRecordingDevice, @@ -195,7 +198,9 @@ void UBPodcastController::openPodcastPreferencesDialog() fullBitRate, podcastBitRateDivisors, podcastVerticalResolution, - podcastHorizontalResolution); + podcastHorizontalResolution, + podcastPublishToIntranet, + podcastPublishToYoutube); mPodcastPreferencesDialog->setAttribute(Qt::WA_DeleteOnClose); connect(mPodcastPreferencesDialog, SIGNAL(accepted()), this, SLOT(podcastPreferencesDialogAccepted())); mPodcastPreferencesDialog->open(); @@ -219,6 +224,11 @@ void UBPodcastController::podcastPreferencesDialogAccepted() mVideoFramesPerSecondAtStart = dialog->podcastFrameRate(); settings->podcastFramesPerSecond->set(mVideoFramesPerSecondAtStart); + + const bool podcastPublishToIntranet = dialog->podcastPublishToIntranet(); + UBSettings::settings()->podcastPublishToIntranet->set(podcastPublishToIntranet); + const bool podcastPublishToYoutube = dialog->podcastPublishToYoutube(); + UBSettings::settings()->podcastPublishToYoutube->set(podcastPublishToYoutube); } void UBPodcastController::updateActionState() @@ -432,7 +442,7 @@ void UBPodcastController::start() QString videoFileName; - if (mIntranetPublicationAction && mIntranetPublicationAction->isChecked()) + if (settings->podcastPublishToIntranet->get().toBool()) { videoFileName = mPodcastRecordingPath + "/" + "Podcast-" + QDateTime::currentDateTime().toString("yyyyMMddhhmmss") @@ -822,13 +832,13 @@ void UBPodcastController::encodingFinished(bool ok) UBApplication::showMessage(tr("Podcast created %1").arg(location), false); - if (mIntranetPublicationAction && mIntranetPublicationAction->isChecked()) + if (UBSettings::settings()->podcastPublishToIntranet->get().toBool()) { UBIntranetPodcastPublisher* intranet = new UBIntranetPodcastPublisher(this); // Self destroyed intranet->publishVideo(mVideoEncoder->videoFileName(), elapsedRecordingMs()); } - if (mYoutubePublicationAction && mYoutubePublicationAction->isChecked()) + if (UBSettings::settings()->podcastPublishToYoutube->get().toBool()) { UBYouTubePublisher* youTube = new UBYouTubePublisher(this); // Self destroyed youTube->uploadVideo(mVideoEncoder->videoFileName()); diff --git a/src/podcast/UBPodcastPreferencesDialog.cpp b/src/podcast/UBPodcastPreferencesDialog.cpp index 39c801d90..b23cd4d8d 100644 --- a/src/podcast/UBPodcastPreferencesDialog.cpp +++ b/src/podcast/UBPodcastPreferencesDialog.cpp @@ -50,7 +50,9 @@ UBPodcastPreferencesDialog::UBPodcastPreferencesDialog(QWidget* parent, const int podcastBitRate, const QList& podcastBitRateDivisor, const QList& podcastVerticalResolution, - const QList& podcastHorizontalResolution) + const QList& podcastHorizontalResolution, + const bool podcastPublishToIntranet, + const bool podcastPublishToYoutube) : QDialog(parent) , mPodcastAudioRecordingDevices(podcastAudioRecordingDevices) , mPodcastProfileNames(podcastProfileNames) @@ -65,9 +67,14 @@ UBPodcastPreferencesDialog::UBPodcastPreferencesDialog(QWidget* parent, mainLayout->setContentsMargins(20, 18, 20, 18); mainLayout->setSpacing(16); + QWidget* recordingWidget = new QWidget(this); + QVBoxLayout* recordingLayout = new QVBoxLayout(recordingWidget); + recordingLayout->setContentsMargins(20, 18, 20, 18); + recordingLayout->setSpacing(16); + const QString audioDeviceSelectorToolTip = tr("The selected audio device applies to all profiles."); - QWidget* audioWidget = new QWidget(this); + QWidget* audioWidget = new QWidget(recordingWidget); audioWidget->setObjectName(QStringLiteral("podcastPreferencesHeader")); QHBoxLayout* audioLayout = new QHBoxLayout(audioWidget); audioLayout->setContentsMargins(14, 12, 14, 12); @@ -101,11 +108,11 @@ UBPodcastPreferencesDialog::UBPodcastPreferencesDialog(QWidget* parent, audioLayout->addWidget(mNoAudioCheckBox); audioLayout->addWidget(mDefaultAudioCheckBox); audioLayout->addWidget(mAudioDeviceSelector); - mainLayout->addWidget(audioWidget); + recordingLayout->addWidget(audioWidget); const QString frameRateFormToolTip = tr("The global frame rate applies to all profiles."); - QWidget* frameRateFormWidget = new QWidget(this); + QWidget* frameRateFormWidget = new QWidget(recordingWidget); frameRateFormWidget->setObjectName(QStringLiteral("podcastPreferencesSpinBox")); QHBoxLayout* frameRateFormLayout = new QHBoxLayout(frameRateFormWidget); frameRateFormLayout->setContentsMargins(14, 12, 14, 12); @@ -121,11 +128,11 @@ UBPodcastPreferencesDialog::UBPodcastPreferencesDialog(QWidget* parent, frameRateLabel->setToolTip(frameRateFormToolTip); frameRateFormLayout->addWidget(frameRateLabel); frameRateFormLayout->addWidget(mFrameRateSpinBox); - mainLayout->addWidget(frameRateFormWidget); + recordingLayout->addWidget(frameRateFormWidget); const QString bitRateFormToolTip = tr("The base bit rate is scaled depending on the profile."); - QWidget* bitRateFormWidget = new QWidget(this); + QWidget* bitRateFormWidget = new QWidget(recordingWidget); bitRateFormWidget->setObjectName(QStringLiteral("podcastPreferencesSpinBox")); QHBoxLayout* bitRateFormLayout = new QHBoxLayout(bitRateFormWidget); bitRateFormLayout->setContentsMargins(14, 12, 14, 12); @@ -141,9 +148,9 @@ UBPodcastPreferencesDialog::UBPodcastPreferencesDialog(QWidget* parent, bitRateLabel->setToolTip(bitRateFormToolTip); bitRateFormLayout->addWidget(bitRateLabel); bitRateFormLayout->addWidget(mBitRateSpinBox); - mainLayout->addWidget(bitRateFormWidget); + recordingLayout->addWidget(bitRateFormWidget); - mTabWidget = new QTabWidget(this); + mTabWidget = new QTabWidget(recordingWidget); mTabWidget->setObjectName(QStringLiteral("podcastPreferencesTabWidget")); mTabWidget->setIconSize(QSize(18, 18)); @@ -163,9 +170,9 @@ UBPodcastPreferencesDialog::UBPodcastPreferencesDialog(QWidget* parent, mTabWidget->addTab(tab, tabLabel); } mTabWidget->setCurrentIndex(mPodcastProfileNames.indexOf(podcastProfile)); - mainLayout->addWidget(mTabWidget); + recordingLayout->addWidget(mTabWidget); - QWidget* hintWidget = new QWidget(this); + QWidget* hintWidget = new QWidget(recordingWidget); hintWidget->setObjectName(QStringLiteral("podcastPreferencesHint")); QHBoxLayout* hintLayout = new QHBoxLayout(hintWidget); hintLayout->setContentsMargins(14, 10, 14, 10); @@ -184,7 +191,26 @@ UBPodcastPreferencesDialog::UBPodcastPreferencesDialog(QWidget* parent, hintLayout->addWidget(hintIcon, 0, Qt::AlignTop); hintLayout->addWidget(hintLabel, 1); - mainLayout->addWidget(hintWidget); + recordingLayout->addWidget(hintWidget); + + QWidget* publish = new QWidget(this); + QVBoxLayout* publishLayout = new QVBoxLayout(publish); + publishLayout->setContentsMargins(8, 14, 8, 6); + publishLayout->setSpacing(14); + + mPublishToIntranet = new QCheckBox(tr("Publish to Intranet")); + mPublishToIntranet->setChecked(podcastPublishToIntranet); + mPublishToYoutube = new QCheckBox(tr("Publish to Youtube")); + mPublishToYoutube->setChecked(podcastPublishToYoutube); + publishLayout->addWidget(mPublishToIntranet); + publishLayout->addWidget(mPublishToYoutube); + + publishLayout->addStretch(); + + QTabWidget* mainTabs = new QTabWidget(this); + mainTabs->addTab(recordingWidget, tr("Recording")); + mainTabs->addTab(publish, tr("Upload")); + mainLayout->addWidget(mainTabs); QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); buttons->setObjectName(QStringLiteral("colorPreferencesButtonBox")); @@ -235,6 +261,16 @@ int UBPodcastPreferencesDialog::podcastFrameRate() const return mFrameRateSpinBox->value(); } +bool UBPodcastPreferencesDialog::podcastPublishToIntranet() const +{ + return mPublishToIntranet->isChecked(); +} + +bool UBPodcastPreferencesDialog::podcastPublishToYoutube() const +{ + return mPublishToYoutube->isChecked(); +} + void UBPodcastPreferencesDialog::bitRateChanged(int bitRate) { for (int i = 0; i < mBitRateProfileValues.size(); ++i) @@ -299,6 +335,8 @@ void UBPodcastPreferencesDialog::resetDefaultSettings() mTabWidget->setCurrentIndex(mPodcastProfileNames.indexOf("Medium")); mBitRateSpinBox->setValue(1700000); mFrameRateSpinBox->setValue(10); + mPublishToIntranet->setChecked(false); + mPublishToYoutube->setChecked(false); } diff --git a/src/podcast/UBPodcastPreferencesDialog.h b/src/podcast/UBPodcastPreferencesDialog.h index bdfda42b7..e174ec722 100644 --- a/src/podcast/UBPodcastPreferencesDialog.h +++ b/src/podcast/UBPodcastPreferencesDialog.h @@ -57,12 +57,16 @@ class UBPodcastPreferencesDialog : public QDialog const int podcastBitRate, const QList& podcastBitRateDivisors, const QList& podcastVerticalResolution, - const QList& podcastHorizontalResolution); + const QList& podcastHorizontalResolution, + const bool podcastPublishToIntranet, + const bool podcastPublishToYoutube); QString podcastAudioRecordingOption() const; QString podcastProfile() const; int podcastBitRate() const; int podcastFrameRate() const; + bool podcastPublishToIntranet() const; + bool podcastPublishToYoutube() const; private slots: void bitRateChanged(int bitRate); @@ -87,6 +91,8 @@ class UBPodcastPreferencesDialog : public QDialog QSpinBox* mBitRateSpinBox{nullptr}; QSpinBox* mFrameRateSpinBox{nullptr}; QTabWidget* mTabWidget{nullptr}; + QCheckBox* mPublishToIntranet{nullptr}; + QCheckBox* mPublishToYoutube{nullptr}; QPushButton* mResetPenButton{nullptr}; QPushButton* mResetMarkerButton{nullptr}; QList mBitRateProfileValues; From cd265bb7e6f18e988888852bf2ce530f51ec2f63 Mon Sep 17 00:00:00 2001 From: Vekhir Date: Thu, 9 Apr 2026 03:30:27 +0200 Subject: [PATCH 3/5] refactor: Remove redundant recording preferences --- resources/forms/mainWindow.ui | 12 -- src/podcast/UBPodcastController.cpp | 162 ---------------------- src/podcast/UBPodcastController.h | 25 ---- src/podcast/UBPodcastRecordingPalette.cpp | 50 +------ 4 files changed, 4 insertions(+), 245 deletions(-) diff --git a/resources/forms/mainWindow.ui b/resources/forms/mainWindow.ui index 5f1e0ab53..557cc8f81 100644 --- a/resources/forms/mainWindow.ui +++ b/resources/forms/mainWindow.ui @@ -1683,18 +1683,6 @@ Configure Podcast Recording - - - - :/images/toolbar/settings.png:/images/toolbar/settings.png - - - Podcast Config - - - Configure Podcast Recording - - diff --git a/src/podcast/UBPodcastController.cpp b/src/podcast/UBPodcastController.cpp index 061a6ffad..5e7f8341f 100644 --- a/src/podcast/UBPodcastController.cpp +++ b/src/podcast/UBPodcastController.cpp @@ -94,13 +94,6 @@ UBPodcastController::UBPodcastController(QObject* pParent) , mRecordingState(Stopped) , mApplicationIsClosing(false) , mRecordingTimestampOffset(0) - , mDefaultAudioInputDeviceAction(0) - , mNoAudioInputDeviceAction(0) - , mSmallVideoSizeAction(0) - , mMediumVideoSizeAction(0) - , mFullVideoSizeAction(0) - , mYoutubePublicationAction(0) - , mIntranetPublicationAction(0) { connect(UBApplication::applicationController, SIGNAL(mainModeChanged(UBApplicationController::MainMode)), this, SLOT(applicationMainModeChanged(UBApplicationController::MainMode))); @@ -139,20 +132,6 @@ void UBPodcastController::applicationAboutToQuit() } } - -void UBPodcastController::groupActionTriggered(QAction* action) -{ - Q_UNUSED(action); - updateActionState(); -} - - -void UBPodcastController::actionToggled(bool checked) -{ - Q_UNUSED(checked); - updateActionState(); -} - void UBPodcastController::openPodcastPreferencesDialog() { if (mPodcastPreferencesDialog) @@ -231,37 +210,6 @@ void UBPodcastController::podcastPreferencesDialogAccepted() UBSettings::settings()->podcastPublishToYoutube->set(podcastPublishToYoutube); } -void UBPodcastController::updateActionState() -{ - if (mSmallVideoSizeAction && mSmallVideoSizeAction->isChecked()) - UBSettings::settings()->podcastVideoSize->set("Small"); - else if (mFullVideoSizeAction && mFullVideoSizeAction->isChecked()) - UBSettings::settings()->podcastVideoSize->set("Full"); - else - UBSettings::settings()->podcastVideoSize->reset(); - - UBSettings::settings()->podcastAudioRecordingDevice->reset(); - - if (mDefaultAudioInputDeviceAction && mDefaultAudioInputDeviceAction->isChecked()) - UBSettings::settings()->podcastAudioRecordingDevice->set("Default"); - else if (mNoAudioInputDeviceAction && mNoAudioInputDeviceAction->isChecked()) - UBSettings::settings()->podcastAudioRecordingDevice->set("None"); - else - { - foreach(QAction* action, mAudioInputDevicesActions) - { - if (action->isChecked()) - { - UBSettings::settings()->podcastAudioRecordingDevice->set(action->text()); - break; - } - } - } - - UBSettings::settings()->podcastPublishToYoutube->set(mYoutubePublicationAction && mYoutubePublicationAction->isChecked()); - UBSettings::settings()->podcastPublishToIntranet->set(mIntranetPublicationAction && mIntranetPublicationAction->isChecked()); - -} void UBPodcastController::widgetSizeChanged(const QSizeF size) { @@ -1001,113 +949,3 @@ void UBPodcastController::setRecordingState(RecordingState pRecordingState) } } - -QList UBPodcastController::audioRecordingDevicesActions() -{ - if (mAudioInputDevicesActions.length() == 0) - { - QString settingsDevice = UBSettings::settings()->podcastAudioRecordingDevice->get().toString(); - - mDefaultAudioInputDeviceAction = new QAction(tr("Default Audio Input"), this); - QAction *checkedAction = mDefaultAudioInputDeviceAction; - - mNoAudioInputDeviceAction = new QAction(tr("No Audio Recording"), this); - - if (settingsDevice == "None") - checkedAction = mNoAudioInputDeviceAction; - - mAudioInputDevicesActions << mNoAudioInputDeviceAction; - mAudioInputDevicesActions << mDefaultAudioInputDeviceAction; - - foreach(QString audioDevice, audioRecordingDevices()) - { - QAction* act = new QAction(audioDevice, this); - act->setCheckable(true); - mAudioInputDevicesActions << act; - if (settingsDevice == audioDevice) - checkedAction = act; - } - - QActionGroup* audioInputActionGroup = new QActionGroup(this); - audioInputActionGroup->setExclusive(true); - - foreach(QAction* action, mAudioInputDevicesActions) - { - audioInputActionGroup->addAction(action); - action->setCheckable(true); - } - checkedAction->setChecked(true); - - connect(audioInputActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(groupActionTriggered(QAction*))); - } - - return mAudioInputDevicesActions; - -} - - -QList UBPodcastController::videoSizeActions() -{ - if (mVideoSizesActions.length() == 0) - { - mSmallVideoSizeAction = new QAction(tr("Small"), this); - mMediumVideoSizeAction = new QAction(tr("Medium"), this); - mFullVideoSizeAction = new QAction(tr("Full"), this); - - mVideoSizesActions << mSmallVideoSizeAction; - mVideoSizesActions << mMediumVideoSizeAction; - mVideoSizesActions << mFullVideoSizeAction; - - QActionGroup* videoSizeActionGroup = new QActionGroup(this); - videoSizeActionGroup->setExclusive(true); - - foreach(QAction* videoSizeAction, mVideoSizesActions) - { - videoSizeAction->setCheckable(true); - videoSizeActionGroup->addAction(videoSizeAction); - } - - QString videoSize = UBSettings::settings()->podcastVideoSize->get().toString(); - - if (videoSize == "Small") - mSmallVideoSizeAction->setChecked(true); - else if (videoSize == "Full") - mFullVideoSizeAction->setChecked(true); - else - mMediumVideoSizeAction->setChecked(true); - - connect(videoSizeActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(groupActionTriggered(QAction*))); - } - - return mVideoSizesActions; -} - - -QList UBPodcastController::podcastPublicationActions() -{ - if (mPodcastPublicationActions.length() == 0) - { - mIntranetPublicationAction = new QAction(tr("Publish to Intranet"), this); - - mIntranetPublicationAction->setCheckable(true); - mIntranetPublicationAction->setChecked(UBSettings::settings()->podcastPublishToIntranet->get().toBool()); - - mPodcastPublicationActions << mIntranetPublicationAction; - - mYoutubePublicationAction = new QAction(tr("Publish to Youtube"), this); - mYoutubePublicationAction->setCheckable(true); - mYoutubePublicationAction->setChecked(UBSettings::settings()->podcastPublishToYoutube->get().toBool()); - - mPodcastPublicationActions << mYoutubePublicationAction; - - foreach(QAction* publicationAction, mPodcastPublicationActions) - { - connect(publicationAction, SIGNAL(toggled(bool)), this, SLOT(actionToggled(bool))); - } - } - - return mPodcastPublicationActions; -} - - - diff --git a/src/podcast/UBPodcastController.h b/src/podcast/UBPodcastController.h index 948fdd736..90fb1d45e 100644 --- a/src/podcast/UBPodcastController.h +++ b/src/podcast/UBPodcastController.h @@ -60,11 +60,6 @@ class UBPodcastController : public QObject virtual QStringList audioRecordingDevices(); - QList audioRecordingDevicesActions(); - QList videoSizeActions(); - - QList podcastPublicationActions(); - enum RecordingState { Stopped = 0, Recording, Paused, Stopping @@ -122,11 +117,6 @@ class UBPodcastController : public QObject void applicationAboutToQuit(); - void groupActionTriggered(QAction*); - void actionToggled(bool); - - void updateActionState(); - private: void widgetSizeChanged(const QSizeF size); @@ -178,23 +168,8 @@ class UBPodcastController : public QObject QTime mTimeAtPaused; long mRecordingTimestampOffset; - QAction *mDefaultAudioInputDeviceAction; - QAction *mNoAudioInputDeviceAction; - - QList mAudioInputDevicesActions; - QList mVideoSizesActions; - QPointer mPodcastPreferencesDialog; - QAction* mSmallVideoSizeAction; - QAction* mMediumVideoSizeAction; - QAction* mFullVideoSizeAction; - - QList mPodcastPublicationActions; - - QAction *mYoutubePublicationAction; - QAction *mIntranetPublicationAction; - QString mPodcastRecordingPath; #ifdef Q_OS_LINUX diff --git a/src/podcast/UBPodcastRecordingPalette.cpp b/src/podcast/UBPodcastRecordingPalette.cpp index b561b1b6b..caf42f272 100644 --- a/src/podcast/UBPodcastRecordingPalette.cpp +++ b/src/podcast/UBPodcastRecordingPalette.cpp @@ -57,48 +57,6 @@ UBPodcastRecordingPalette::UBPodcastRecordingPalette(QWidget *parent) layout()->addWidget(mLevelMeter); addAction(UBApplication::mainWindow->actionPodcastPreferences); - addAction(UBApplication::mainWindow->actionPodcastConfig); - -#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) - foreach(QObject* menuWidget, UBApplication::mainWindow->actionPodcastConfig->associatedObjects()) -#else - foreach(QWidget* menuWidget, UBApplication::mainWindow->actionPodcastConfig->associatedWidgets()) -#endif - { - QToolButton *tb = qobject_cast(menuWidget); - - tb->setIconSize(QSize(16, 16)); - - if (tb && !tb->menu()) - { - tb->setObjectName("ubButtonMenu"); - tb->setPopupMode(QToolButton::InstantPopup); - QMenu* menu = new QMenu(this); - - foreach(QAction* audioInputAction, UBPodcastController::instance()->audioRecordingDevicesActions()) - { - menu->addAction(audioInputAction); - } - - menu->addSeparator(); - - foreach(QAction* videoSizeAction, UBPodcastController::instance()->videoSizeActions()) - { - menu->addAction(videoSizeAction); - } - - menu->addSeparator(); - - QList podcastPublication = UBPodcastController::instance()->podcastPublicationActions(); - - foreach(QAction* publicationAction, podcastPublication) - { - menu->addAction(publicationAction); - } - - tb->setMenu(menu); - } - } } @@ -120,7 +78,7 @@ void UBPodcastRecordingPalette::recordingStateChanged(UBPodcastController::Recor //UBApplication::mainWindow->actionPodcastMic->setEnabled(false); - UBApplication::mainWindow->actionPodcastConfig->setEnabled(false); + UBApplication::mainWindow->actionPodcastPreferences->setEnabled(false); } else if (state == UBPodcastController::Stopped) { @@ -131,7 +89,7 @@ void UBPodcastRecordingPalette::recordingStateChanged(UBPodcastController::Recor UBApplication::mainWindow->actionPodcastPause->setEnabled(false); //UBApplication::mainWindow->actionPodcastMic->setEnabled(true); - UBApplication::mainWindow->actionPodcastConfig->setEnabled(true); + UBApplication::mainWindow->actionPodcastPreferences->setEnabled(true); } else if (state == UBPodcastController::Paused) { @@ -142,13 +100,13 @@ void UBPodcastRecordingPalette::recordingStateChanged(UBPodcastController::Recor UBApplication::mainWindow->actionPodcastPause->setEnabled(true); //UBApplication::mainWindow->actionPodcastMic->setEnabled(false); - UBApplication::mainWindow->actionPodcastConfig->setEnabled(false); + UBApplication::mainWindow->actionPodcastPreferences->setEnabled(false); } else { UBApplication::mainWindow->actionPodcastRecord->setEnabled(false); UBApplication::mainWindow->actionPodcastPause->setEnabled(false); - UBApplication::mainWindow->actionPodcastConfig->setEnabled(false); + UBApplication::mainWindow->actionPodcastPreferences->setEnabled(false); } } From cdcac492d02004038b762f98ae16b06d2b41769b Mon Sep 17 00:00:00 2001 From: Vekhir Date: Thu, 9 Apr 2026 03:55:46 +0200 Subject: [PATCH 4/5] feat: Read initial podcast fps from settings Currently, the podcast recording is set to a fixed 10 fps. While there exists a setting to allow for customisation, it remains unused. This change takes the user setting into account from the start. The actual fps are determined by the activity on the board, so the value represents a maximum frame rate. --- src/podcast/UBPodcastController.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/podcast/UBPodcastController.cpp b/src/podcast/UBPodcastController.cpp index 5e7f8341f..d901213a6 100644 --- a/src/podcast/UBPodcastController.cpp +++ b/src/podcast/UBPodcastController.cpp @@ -157,7 +157,7 @@ void UBPodcastController::openPodcastPreferencesDialog() settings->podcastVideoSize->reset().toString(); } const QString podcastProfile = settings->podcastVideoSize->get().toString(); - mVideoFramesPerSecondAtStart = settings->podcastFramesPerSecond->get().toInt(); + const int frameRate = settings->podcastFramesPerSecond->get().toInt(); const int fullBitRate = settings->podcastWindowsMediaBitsPerSecond->get().toInt(); const QSize viewportSize = UBApplication::boardController->controlView()->size(); @@ -173,7 +173,7 @@ void UBPodcastController::openPodcastPreferencesDialog() podcastAudioRecordingDevice, podcastProfileNames, podcastProfile, - mVideoFramesPerSecondAtStart, + frameRate, fullBitRate, podcastBitRateDivisors, podcastVerticalResolution, @@ -198,11 +198,11 @@ void UBPodcastController::podcastPreferencesDialogAccepted() const QString podcastProfile = dialog->podcastProfile(); settings->podcastVideoSize->set(podcastProfile); - mVideoBitsPerSecondAtStart = dialog->podcastBitRate(); - settings->podcastWindowsMediaBitsPerSecond->set(QVariant::fromValue(mVideoBitsPerSecondAtStart)); + const int fullBitRate = dialog->podcastBitRate(); + settings->podcastWindowsMediaBitsPerSecond->set(QVariant::fromValue(fullBitRate)); - mVideoFramesPerSecondAtStart = dialog->podcastFrameRate(); - settings->podcastFramesPerSecond->set(mVideoFramesPerSecondAtStart); + const int frameRate = dialog->podcastFrameRate(); + settings->podcastFramesPerSecond->set(frameRate); const bool podcastPublishToIntranet = dialog->podcastPublishToIntranet(); UBSettings::settings()->podcastPublishToIntranet->set(podcastPublishToIntranet); @@ -310,6 +310,7 @@ void UBPodcastController::start() QSize recommendedSize(1024, 768); UBSettings* settings = UBSettings::settings(); + mVideoFramesPerSecondAtStart = settings->podcastFramesPerSecond->get().toInt(); int fullBitRate = settings->podcastWindowsMediaBitsPerSecond->get().toInt(); QString podcastProfile = settings->podcastVideoSize->get().toString(); From a04ac5af042acb7dc65326b811597d8c42367de2 Mon Sep 17 00:00:00 2001 From: Vekhir Date: Thu, 9 Apr 2026 04:36:20 +0200 Subject: [PATCH 5/5] feat: Increase default fps The default has been bumped up to 100 fps. This value is never actually reached - the resulting fps hover mostly around 40-70. During testing on my rather powerful machine (with values exceeding 250 fps), the observed frame rate never got appreciably higher than 100 fps (maximum was 127 fps for short periods at 1000). Thus, the profiles in their default configuration capture all activity. Choosing a lower frame rate decreases the fps accordingly. 25 fps is at the lower end of what is perceived as fluid motion, though using that yields an actual frame rate around 12-18. Further tuning is possible via the "Podcast->FramesPerSecond" setting and the preferences dialog. A max value of 100 yields around 40-70 fps --- resources/etc/OpenBoard.config | 2 +- src/core/UBSettings.cpp | 2 +- src/podcast/UBPodcastController.cpp | 2 +- src/podcast/UBPodcastPreferencesDialog.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/etc/OpenBoard.config b/resources/etc/OpenBoard.config index ffc35de9b..8ba0ef46c 100644 --- a/resources/etc/OpenBoard.config +++ b/resources/etc/OpenBoard.config @@ -130,7 +130,7 @@ UsePDFMerger=true [Podcast] AudioRecordingDevice=Default -FramesPerSecond=10 +FramesPerSecond=100 PublishToYouTube=false QuickTimeQuality=High VideoSize=Medium diff --git a/src/core/UBSettings.cpp b/src/core/UBSettings.cpp index 0d6cd120c..003a4478a 100644 --- a/src/core/UBSettings.cpp +++ b/src/core/UBSettings.cpp @@ -461,7 +461,7 @@ void UBSettings::init() exportBackgroundGrid = new UBSetting(this, "PDF", "ExportBackgroundGrid", false); exportBackgroundColor = new UBSetting(this, "PDF", "ExportBackgroundColor", false); - podcastFramesPerSecond = new UBSetting(this, "Podcast", "FramesPerSecond", 10); + podcastFramesPerSecond = new UBSetting(this, "Podcast", "FramesPerSecond", 100); podcastVideoSize = new UBSetting(this, "Podcast", "VideoSize", "Medium"); podcastAudioRecordingDevice = new UBSetting(this, "Podcast", "AudioRecordingDevice", "Default"); diff --git a/src/podcast/UBPodcastController.cpp b/src/podcast/UBPodcastController.cpp index d901213a6..f7d6624e5 100644 --- a/src/podcast/UBPodcastController.cpp +++ b/src/podcast/UBPodcastController.cpp @@ -82,7 +82,7 @@ UBPodcastController::UBPodcastController(QObject* pParent) , mVideoEncoder(0) , mInitialized(false) , mEmptyChapter(true) - , mVideoFramesPerSecondAtStart(10) + , mVideoFramesPerSecondAtStart(100) , mVideoFrameSizeAtStart(1024, 768) , mVideoBitsPerSecondAtStart(1700000) , mSourceWidget(0) diff --git a/src/podcast/UBPodcastPreferencesDialog.cpp b/src/podcast/UBPodcastPreferencesDialog.cpp index b23cd4d8d..86028136e 100644 --- a/src/podcast/UBPodcastPreferencesDialog.cpp +++ b/src/podcast/UBPodcastPreferencesDialog.cpp @@ -334,7 +334,7 @@ void UBPodcastPreferencesDialog::resetDefaultSettings() setDefaultAudioDevice(); mTabWidget->setCurrentIndex(mPodcastProfileNames.indexOf("Medium")); mBitRateSpinBox->setValue(1700000); - mFrameRateSpinBox->setValue(10); + mFrameRateSpinBox->setValue(100); mPublishToIntranet->setChecked(false); mPublishToYoutube->setChecked(false); }