File indexing completed on 2024-04-28 08:43:39

0001 /*
0002     SPDX-FileCopyrightText: 2021 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003 
0004 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "kdenlivesettingsdialog.h"
0008 #include "clipcreationdialog.h"
0009 #include "core.h"
0010 #include "dialogs/profilesdialog.h"
0011 #include "doc/kdenlivedoc.h"
0012 #include "kdenlivesettings.h"
0013 #include "mainwindow.h"
0014 #include "monitor/monitor.h"
0015 #include "monitor/monitorproxy.h"
0016 #include "profiles/profilemodel.hpp"
0017 #include "profiles/profilerepository.hpp"
0018 #include "profilesdialog.h"
0019 #include "project/dialogs/guidecategories.h"
0020 #include "project/dialogs/profilewidget.h"
0021 #include "timeline2/view/timelinecontroller.h"
0022 #include "timeline2/view/timelinewidget.h"
0023 #include "wizard.h"
0024 
0025 #ifdef USE_V4L
0026 #include "capture/v4lcapture.h"
0027 #endif
0028 
0029 #include "KLocalizedString"
0030 #include "kdenlive_debug.h"
0031 #include <KArchive>
0032 #include <KArchiveDirectory>
0033 #include <KIO/DesktopExecParser>
0034 #include <KIO/FileCopyJob>
0035 #include <kio_version.h>
0036 #if KIO_VERSION >= QT_VERSION_CHECK(5, 98, 0)
0037 #include <KIO/JobUiDelegateFactory>
0038 #else
0039 #include <KIO/JobUiDelegate>
0040 #endif
0041 #include "utils/KMessageBox_KdenliveCompat.h"
0042 #include <KIO/OpenUrlJob>
0043 #include <KLineEdit>
0044 #include <KMessageBox>
0045 #include <KOpenWithDialog>
0046 #include <KService>
0047 #include <KTar>
0048 #include <KUrlRequesterDialog>
0049 #include <KZip>
0050 
0051 #include <QAction>
0052 #include <QButtonGroup>
0053 #include <QDir>
0054 #include <QGuiApplication>
0055 #include <QInputDialog>
0056 #include <QRegularExpression>
0057 #include <QScreen>
0058 #include <QSize>
0059 #include <QThread>
0060 #include <QTimer>
0061 #include <QtConcurrent>
0062 
0063 #include <cstdio>
0064 #include <cstdlib>
0065 #include <fcntl.h>
0066 #include <unistd.h>
0067 
0068 #ifdef USE_JOGSHUTTLE
0069 #include "jogshuttle/jogaction.h"
0070 #include "jogshuttle/jogshuttleconfig.h"
0071 #include <QStandardPaths>
0072 #include <linux/input.h>
0073 #include <memory>
0074 #endif
0075 
0076 SpeechList::SpeechList(QWidget *parent)
0077     : QListWidget(parent)
0078 {
0079     setAlternatingRowColors(true);
0080     setAcceptDrops(true);
0081     setDropIndicatorShown(true);
0082     viewport()->setAcceptDrops(true);
0083 }
0084 
0085 QStringList SpeechList::mimeTypes() const
0086 {
0087     return QStringList() << QStringLiteral("text/uri-list");
0088 }
0089 
0090 void SpeechList::dropEvent(QDropEvent *event)
0091 {
0092     const QMimeData *qMimeData = event->mimeData();
0093     if (qMimeData->hasUrls()) {
0094         QList<QUrl> urls = qMimeData->urls();
0095         if (!urls.isEmpty()) {
0096             Q_EMIT getDictionary(urls.takeFirst());
0097         }
0098     }
0099 }
0100 
0101 KdenliveSettingsDialog::KdenliveSettingsDialog(QMap<QString, QString> mappable_actions, bool gpuAllowed, QWidget *parent)
0102     : KConfigDialog(parent, QStringLiteral("settings"), KdenliveSettings::self())
0103     , m_modified(false)
0104     , m_shuttleModified(false)
0105     , m_voskUpdated(false)
0106     , m_mappable_actions(std::move(mappable_actions))
0107 {
0108     KdenliveSettings::setV4l_format(0);
0109 
0110     initMiscPage();
0111     initProjectPage();
0112     initProxyPage();
0113 
0114     QWidget *p3 = new QWidget;
0115     m_configTimeline.setupUi(p3);
0116     m_pageTimeline = addPage(p3, i18n("Timeline"), QStringLiteral("video-display"));
0117 
0118     QWidget *p4 = new QWidget;
0119     m_configTools.setupUi(p4);
0120     m_pageTools = addPage(p4, i18n("Tools"), QStringLiteral("tools"));
0121 
0122     initEnviromentPage();
0123 
0124     QWidget *p11 = new QWidget;
0125     m_configColors.setupUi(p11);
0126     m_pageColors = addPage(p11, i18n("Colors and Guides"), QStringLiteral("color-management"));
0127     m_guidesCategories = new GuideCategories(nullptr, this);
0128     QVBoxLayout *guidesLayout = new QVBoxLayout(m_configColors.guides_box);
0129     guidesLayout->addWidget(m_guidesCategories);
0130 
0131     QWidget *p12 = new QWidget;
0132     m_configSpeech.setupUi(p12);
0133     m_pageSpeech = addPage(p12, i18n("Speech To Text"), QStringLiteral("text-speak"));
0134 
0135     QWidget *p7 = new QWidget;
0136     m_configSdl.setupUi(p7);
0137     m_pagePlay = addPage(p7, i18n("Playback"), QStringLiteral("media-playback-start"));
0138 
0139     QWidget *p5 = new QWidget;
0140     m_configCapture.setupUi(p5);
0141     m_decklinkProfiles = new EncodingProfilesChooser(this, EncodingProfilesManager::DecklinkCapture, false, QStringLiteral("decklink_profile"));
0142     m_configCapture.decklink_profile_box->addWidget(m_decklinkProfiles);
0143     m_v4lProfiles = new EncodingProfilesChooser(this, EncodingProfilesManager::V4LCapture, false, QStringLiteral("v4l_profile"));
0144     m_configCapture.v4l_profile_box->addWidget(m_v4lProfiles);
0145     m_grabProfiles = new EncodingProfilesChooser(this, EncodingProfilesManager::ScreenCapture, false, QStringLiteral("grab_profile"));
0146     m_configCapture.screen_grab_profile_box->addWidget(m_grabProfiles);
0147     m_pageCapture = addPage(p5, i18n("Capture"), QStringLiteral("media-record"));
0148 
0149     initDevices();
0150     initCapturePage();
0151     initJogShuttlePage();
0152     initTranscodePage();
0153 
0154     initSdlPage(gpuAllowed);
0155     initSpeechPage();
0156 
0157     // Config dialog size
0158     KSharedConfigPtr config = KSharedConfig::openConfig();
0159     KConfigGroup settingsGroup(config, "settings");
0160     QSize optimalSize;
0161 
0162     if (!settingsGroup.exists() || !settingsGroup.hasKey("dialogSize")) {
0163         const QSize screenSize = (QGuiApplication::primaryScreen()->availableSize() * 0.9);
0164         const QSize targetSize = QSize(1024, 708);
0165         optimalSize = targetSize.boundedTo(screenSize);
0166     } else {
0167         optimalSize = settingsGroup.readEntry("dialogSize", QVariant(size())).toSize();
0168     }
0169     resize(optimalSize);
0170     // Use timers so that the Settings window opens before starting the scripts
0171     checkSpeechDependencies();
0172 }
0173 
0174 // static
0175 bool KdenliveSettingsDialog::getBlackMagicDeviceList(QComboBox *devicelist, bool force)
0176 {
0177     if (!force && !KdenliveSettings::decklink_device_found()) {
0178         return false;
0179     }
0180     Mlt::Profile profile;
0181     Mlt::Producer bm(profile, "decklink");
0182     int found_devices = 0;
0183     if (bm.is_valid()) {
0184         bm.set("list_devices", 1);
0185         found_devices = bm.get_int("devices");
0186     } else {
0187         KdenliveSettings::setDecklink_device_found(false);
0188     }
0189     if (found_devices <= 0) {
0190         devicelist->setEnabled(false);
0191         return false;
0192     }
0193     KdenliveSettings::setDecklink_device_found(true);
0194     for (int i = 0; i < found_devices; ++i) {
0195         char *tmp = qstrdup(QStringLiteral("device.%1").arg(i).toUtf8().constData());
0196         devicelist->addItem(bm.get(tmp));
0197         delete[] tmp;
0198     }
0199     return true;
0200 }
0201 
0202 // static
0203 bool KdenliveSettingsDialog::initAudioRecDevice()
0204 {
0205     QStringList audioDevices = pCore->getAudioCaptureDevices();
0206 
0207     // show a hint to the user to know what to check for in case the device list are empty (common issue)
0208     m_configCapture.labelNoAudioDevices->setVisible(audioDevices.empty());
0209 
0210     m_configCapture.kcfg_defaultaudiocapture->addItems(audioDevices);
0211     connect(m_configCapture.kcfg_defaultaudiocapture, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [&]() {
0212         QString currentDevice = m_configCapture.kcfg_defaultaudiocapture->currentText();
0213         KdenliveSettings::setDefaultaudiocapture(currentDevice);
0214     });
0215     QString selectedDevice = KdenliveSettings::defaultaudiocapture();
0216     int selectedIndex = m_configCapture.kcfg_defaultaudiocapture->findText(selectedDevice);
0217     if (!selectedDevice.isEmpty() && selectedIndex > -1) {
0218         m_configCapture.kcfg_defaultaudiocapture->setCurrentIndex(selectedIndex);
0219     }
0220     return true;
0221 }
0222 
0223 void KdenliveSettingsDialog::initMiscPage()
0224 {
0225     QWidget *p1 = new QWidget;
0226     m_configMisc.setupUi(p1);
0227     m_pageMisc = addPage(p1, i18n("Misc"), QStringLiteral("configure"));
0228 
0229     m_configMisc.kcfg_use_exiftool->setEnabled(!QStandardPaths::findExecutable(QStringLiteral("exiftool")).isEmpty());
0230 
0231     static const QRegularExpression reg(R"((\+|-)?\d{2}:\d{2}:\d{2}(:||,)\d{2})");
0232     QValidator *validator = new QRegularExpressionValidator(reg, this);
0233     m_configMisc.kcfg_color_duration->setInputMask(pCore->timecode().mask());
0234     m_configMisc.kcfg_color_duration->setValidator(validator);
0235     m_configMisc.kcfg_title_duration->setInputMask(pCore->timecode().mask());
0236     m_configMisc.kcfg_title_duration->setValidator(validator);
0237     m_configMisc.kcfg_transition_duration->setInputMask(pCore->timecode().mask());
0238     m_configMisc.kcfg_transition_duration->setValidator(validator);
0239     m_configMisc.kcfg_mix_duration->setInputMask(pCore->timecode().mask());
0240     m_configMisc.kcfg_mix_duration->setValidator(validator);
0241     m_configMisc.kcfg_image_duration->setInputMask(pCore->timecode().mask());
0242     m_configMisc.kcfg_image_duration->setValidator(validator);
0243     m_configMisc.kcfg_sequence_duration->setInputMask(pCore->timecode().mask());
0244     m_configMisc.kcfg_sequence_duration->setValidator(validator);
0245     m_configMisc.kcfg_fade_duration->setInputMask(pCore->timecode().mask());
0246     m_configMisc.kcfg_fade_duration->setValidator(validator);
0247     m_configMisc.kcfg_subtitle_duration->setInputMask(pCore->timecode().mask());
0248     m_configMisc.kcfg_subtitle_duration->setValidator(validator);
0249 
0250     m_configMisc.preferredcomposite->clear();
0251     m_configMisc.preferredcomposite->addItem(i18n("auto"));
0252     m_configMisc.preferredcomposite->addItems(KdenliveSettings::compositingList());
0253 
0254     if (!KdenliveSettings::preferredcomposite().isEmpty()) {
0255         int ix = m_configMisc.preferredcomposite->findText(KdenliveSettings::preferredcomposite());
0256         if (ix > -1) {
0257             m_configMisc.preferredcomposite->setCurrentIndex(ix);
0258         }
0259     }
0260     connect(m_configMisc.preferredcomposite, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [&]() {
0261         if (m_configMisc.preferredcomposite->currentText() != KdenliveSettings::preferredcomposite()) {
0262             KdenliveSettings::setPreferredcomposite(m_configMisc.preferredcomposite->currentText());
0263             int mode = pCore->currentDoc()->getDocumentProperty(QStringLiteral("compositing")).toInt();
0264             pCore->window()->getCurrentTimeline()->controller()->switchCompositing(mode);
0265             pCore->currentDoc()->setModified();
0266         }
0267     });
0268 }
0269 
0270 void KdenliveSettingsDialog::initProjectPage()
0271 {
0272     QWidget *p9 = new QWidget;
0273     m_configProject.setupUi(p9);
0274     // Timeline preview
0275     QString currentPreviewData =
0276         KdenliveSettings::previewparams().isEmpty() ? QString() : QString("%1;%2").arg(KdenliveSettings::previewparams(), KdenliveSettings::previewextension());
0277     m_tlPreviewProfiles = new EncodingTimelinePreviewProfilesChooser(p9, true, currentPreviewData, false);
0278     m_configProject.preview_profile_box->addWidget(m_tlPreviewProfiles);
0279     auto *vbox = new QVBoxLayout;
0280     m_pw = new ProfileWidget(this);
0281     vbox->addWidget(m_pw);
0282     connect(m_pw, &ProfileWidget::profileChanged, this, [this]() { m_tlPreviewProfiles->filterPreviewProfiles(m_pw->selectedProfile()); });
0283     m_configProject.profile_box->setLayout(vbox);
0284     m_configProject.profile_box->setTitle(i18n("Select the Default Profile (preset)"));
0285     // Select profile
0286     m_pw->loadProfile(KdenliveSettings::default_profile().isEmpty() ? pCore->getCurrentProfile()->path() : KdenliveSettings::default_profile());
0287     m_tlPreviewProfiles->filterPreviewProfiles(m_pw->selectedProfile());
0288     connect(m_tlPreviewProfiles, &EncodingTimelinePreviewProfilesChooser::currentIndexChanged, this, &KdenliveSettingsDialog::slotDialogModified);
0289     connect(m_pw, &ProfileWidget::profileChanged, this, &KdenliveSettingsDialog::slotDialogModified);
0290     m_configProject.projecturl->setMode(KFile::Directory);
0291     m_configProject.projecturl->setUrl(QUrl::fromLocalFile(KdenliveSettings::defaultprojectfolder()));
0292     connect(m_configProject.projecturl, &KUrlRequester::textChanged, this, &KdenliveSettingsDialog::slotDialogModified);
0293     connect(m_configProject.kcfg_customprojectfolder, &QCheckBox::toggled, m_configProject.projecturl, &KUrlRequester::setEnabled);
0294     connect(m_configProject.kcfg_videotracks, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [this]() {
0295         if (m_configProject.kcfg_videotracks->value() + m_configProject.kcfg_audiotracks->value() <= 0) {
0296             m_configProject.kcfg_videotracks->setValue(1);
0297         }
0298     });
0299     connect(m_configProject.kcfg_audiotracks, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [this]() {
0300         if (m_configProject.kcfg_videotracks->value() + m_configProject.kcfg_audiotracks->value() <= 0) {
0301             m_configProject.kcfg_audiotracks->setValue(1);
0302         }
0303     });
0304 
0305     m_pageProject = addPage(p9, i18n("Project Defaults"), QStringLiteral("project-defaults"));
0306 }
0307 
0308 void KdenliveSettingsDialog::initProxyPage()
0309 {
0310     QWidget *p10 = new QWidget;
0311     m_configProxy.setupUi(p10);
0312     m_proxyProfiles = new EncodingProfilesChooser(p10, EncodingProfilesManager::ProxyClips, true, QStringLiteral("proxy_profile"));
0313     m_configProxy.proxy_profile_box->addWidget(m_proxyProfiles);
0314     addPage(p10, i18n("Proxy Clips"), QStringLiteral("zoom-out"));
0315     connect(m_configProxy.kcfg_generateproxy, &QAbstractButton::toggled, m_configProxy.kcfg_proxyminsize, &QWidget::setEnabled);
0316     m_configProxy.kcfg_proxyminsize->setEnabled(KdenliveSettings::generateproxy());
0317     connect(m_configProxy.kcfg_generateimageproxy, &QAbstractButton::toggled, m_configProxy.kcfg_proxyimageminsize, &QWidget::setEnabled);
0318     m_configProxy.kcfg_proxyimageminsize->setEnabled(KdenliveSettings::generateimageproxy());
0319     loadExternalProxyProfiles();
0320 }
0321 
0322 void KdenliveSettingsDialog::initEnviromentPage()
0323 {
0324     QWidget *p2 = new QWidget;
0325     m_configEnv.setupUi(p2);
0326     m_configEnv.mltpathurl->setMode(KFile::Directory);
0327     m_configEnv.mltpathurl->lineEdit()->setObjectName(QStringLiteral("kcfg_mltpath"));
0328     m_configEnv.rendererpathurl->lineEdit()->setObjectName(QStringLiteral("kcfg_meltpath"));
0329     m_configEnv.ffmpegurl->lineEdit()->setObjectName(QStringLiteral("kcfg_ffmpegpath"));
0330     m_configEnv.ffplayurl->lineEdit()->setObjectName(QStringLiteral("kcfg_ffplaypath"));
0331     m_configEnv.ffprobeurl->lineEdit()->setObjectName(QStringLiteral("kcfg_ffprobepath"));
0332     m_configEnv.mediainfourl->lineEdit()->setObjectName(QStringLiteral("kcfg_mediainfopath"));
0333     m_configEnv.tmppathurl->setMode(KFile::Directory);
0334     m_configEnv.tmppathurl->lineEdit()->setObjectName(QStringLiteral("kcfg_currenttmpfolder"));
0335     m_configEnv.capturefolderurl->setMode(KFile::Directory);
0336     m_configEnv.capturefolderurl->lineEdit()->setObjectName(QStringLiteral("kcfg_capturefolder"));
0337     m_configEnv.capturefolderurl->setEnabled(KdenliveSettings::capturetoprojectfolder() == 2);
0338     m_configEnv.kcfg_capturetoprojectfolder->setItemText(0, i18n("Use default folder: %1", QStandardPaths::writableLocation(QStandardPaths::MoviesLocation)));
0339     if (KdenliveSettings::customprojectfolder()) {
0340         m_configEnv.kcfg_capturetoprojectfolder->setItemText(1, i18n("Always use project folder: %1", KdenliveSettings::defaultprojectfolder()));
0341     } else {
0342         m_configEnv.kcfg_capturetoprojectfolder->setItemText(1, i18n("Always use active project folder"));
0343     }
0344     connect(m_configEnv.kcfg_capturetoprojectfolder, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
0345             &KdenliveSettingsDialog::slotEnableCaptureFolder);
0346 
0347     // Library folder
0348     m_configEnv.libraryfolderurl->setMode(KFile::Directory);
0349     m_configEnv.libraryfolderurl->lineEdit()->setObjectName(QStringLiteral("kcfg_libraryfolder"));
0350     m_configEnv.libraryfolderurl->setEnabled(!KdenliveSettings::librarytodefaultfolder());
0351     m_configEnv.libraryfolderurl->setPlaceholderText(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/library"));
0352     m_configEnv.kcfg_librarytodefaultfolder->setToolTip(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/library"));
0353     connect(m_configEnv.kcfg_librarytodefaultfolder, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotEnableLibraryFolder);
0354 
0355     m_configEnv.kcfg_proxythreads->setMaximum(qMax(1, QThread::idealThreadCount() - 1));
0356 
0357     // Script rendering files folder
0358     m_configEnv.videofolderurl->setMode(KFile::Directory);
0359     m_configEnv.videofolderurl->lineEdit()->setObjectName(QStringLiteral("kcfg_videofolder"));
0360     m_configEnv.videofolderurl->setEnabled(KdenliveSettings::videotodefaultfolder() == 2);
0361     m_configEnv.videofolderurl->setPlaceholderText(QStandardPaths::writableLocation(QStandardPaths::MoviesLocation));
0362     m_configEnv.kcfg_videotodefaultfolder->setItemText(0, i18n("Use default folder: %1", QStandardPaths::writableLocation(QStandardPaths::MoviesLocation)));
0363     if (KdenliveSettings::customprojectfolder()) {
0364         m_configEnv.kcfg_videotodefaultfolder->setItemText(1, i18n("Always use project folder: %1", KdenliveSettings::defaultprojectfolder()));
0365     } else {
0366         m_configEnv.kcfg_videotodefaultfolder->setItemText(1, i18n("Always use active project folder"));
0367     }
0368     connect(m_configEnv.kcfg_videotodefaultfolder, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotEnableVideoFolder);
0369 
0370     // Mime types
0371     QStringList mimes = ClipCreationDialog::getExtensions();
0372     std::sort(mimes.begin(), mimes.end());
0373     m_configEnv.supportedmimes->setPlainText(mimes.join(QLatin1Char(' ')));
0374 
0375     connect(m_configEnv.kp_image, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotEditImageApplication);
0376     connect(m_configEnv.kp_audio, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotEditAudioApplication);
0377     connect(m_configEnv.kp_anim, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotEditGlaxnimateApplication);
0378 
0379     m_pageEnv = addPage(p2, i18n("Environment"), QStringLiteral("application-x-executable-script"));
0380 }
0381 
0382 void KdenliveSettingsDialog::initCapturePage()
0383 {
0384     // Remove ffmpeg tab, unused
0385     m_configCapture.tabWidget->removeTab(0);
0386     m_configCapture.label->setVisible(false);
0387     m_configCapture.kcfg_defaultcapture->setVisible(false);
0388     // m_configCapture.tabWidget->removeTab(2);
0389 #ifdef USE_V4L
0390 
0391     // Video 4 Linux device detection
0392     for (int i = 0; i < 10; ++i) {
0393         QString path = QStringLiteral("/dev/video") + QString::number(i);
0394         if (QFile::exists(path)) {
0395             QStringList deviceInfo = V4lCaptureHandler::getDeviceName(path);
0396             if (!deviceInfo.isEmpty()) {
0397                 m_configCapture.kcfg_detectedv4ldevices->addItem(deviceInfo.at(0), path);
0398                 m_configCapture.kcfg_detectedv4ldevices->setItemData(m_configCapture.kcfg_detectedv4ldevices->count() - 1, deviceInfo.at(1), Qt::UserRole + 1);
0399             }
0400         }
0401     }
0402     connect(m_configCapture.kcfg_detectedv4ldevices, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
0403             &KdenliveSettingsDialog::slotUpdatev4lDevice);
0404     connect(m_configCapture.kcfg_v4l_format, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
0405             &KdenliveSettingsDialog::slotUpdatev4lCaptureProfile);
0406     connect(m_configCapture.config_v4l, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotEditVideo4LinuxProfile);
0407 
0408     slotUpdatev4lDevice();
0409 #endif
0410 
0411     m_configCapture.tabWidget->setCurrentIndex(KdenliveSettings::defaultcapture());
0412 #ifdef Q_WS_MAC
0413     m_configCapture.tabWidget->setEnabled(false);
0414     m_configCapture.kcfg_defaultcapture->setEnabled(false);
0415     m_configCapture.label->setText(i18n("Capture is not yet available on Mac OS X."));
0416 #endif
0417 
0418     connect(m_configCapture.kcfg_grab_capture_type, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
0419             &KdenliveSettingsDialog::slotUpdateGrabRegionStatus);
0420 
0421     slotUpdateGrabRegionStatus();
0422 
0423     // audio capture channels
0424     m_configCapture.audiocapturechannels->clear();
0425     m_configCapture.audiocapturechannels->addItem(i18n("Mono (1 channel)"), 1);
0426     m_configCapture.audiocapturechannels->addItem(i18n("Stereo (2 channels)"), 2);
0427 
0428     int channelsIndex = m_configCapture.audiocapturechannels->findData(KdenliveSettings::audiocapturechannels());
0429     m_configCapture.audiocapturechannels->setCurrentIndex(qMax(channelsIndex, 0));
0430     connect(m_configCapture.audiocapturechannels, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [&]() { updateButtons(); });
0431     // audio capture sample rate
0432     m_configCapture.audiocapturesamplerate->clear();
0433     m_configCapture.audiocapturesamplerate->addItem(i18n("44100 Hz"), 44100);
0434     m_configCapture.audiocapturesamplerate->addItem(i18n("48000 Hz"), 48000);
0435 
0436     int sampleRateIndex = m_configCapture.audiocapturesamplerate->findData(KdenliveSettings::audiocapturesamplerate());
0437     m_configCapture.audiocapturesamplerate->setCurrentIndex(qMax(sampleRateIndex, 0));
0438     connect(m_configCapture.audiocapturesamplerate, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [&]() { updateButtons(); });
0439 
0440     m_configCapture.labelNoAudioDevices->setVisible(false);
0441 
0442     initAudioRecDevice();
0443     getBlackMagicDeviceList(m_configCapture.kcfg_decklink_capturedevice);
0444 }
0445 
0446 void KdenliveSettingsDialog::initJogShuttlePage()
0447 {
0448     QWidget *p6 = new QWidget;
0449     m_configShuttle.setupUi(p6);
0450 #ifdef USE_JOGSHUTTLE
0451     connect(m_configShuttle.kcfg_enableshuttle, &QCheckBox::stateChanged, this, &KdenliveSettingsDialog::slotCheckShuttle);
0452     connect(m_configShuttle.shuttledevicelist, SIGNAL(activated(int)), this, SLOT(slotUpdateShuttleDevice(int)));
0453     connect(m_configShuttle.toolBtnReload, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotReloadShuttleDevices);
0454 
0455     slotCheckShuttle(static_cast<int>(KdenliveSettings::enableshuttle()));
0456     m_configShuttle.shuttledisabled->hide();
0457 
0458     // Store the button pointers into an array for easier handling them in the other functions.
0459     // TODO: impl enumerator or live with cut and paste :-)))
0460     setupJogshuttleBtns(KdenliveSettings::shuttledevice());
0461 #if false
0462     m_shuttle_buttons.push_back(m_configShuttle.shuttle1);
0463     m_shuttle_buttons.push_back(m_configShuttle.shuttle2);
0464     m_shuttle_buttons.push_back(m_configShuttle.shuttle3);
0465     m_shuttle_buttons.push_back(m_configShuttle.shuttle4);
0466     m_shuttle_buttons.push_back(m_configShuttle.shuttle5);
0467     m_shuttle_buttons.push_back(m_configShuttle.shuttle6);
0468     m_shuttle_buttons.push_back(m_configShuttle.shuttle7);
0469     m_shuttle_buttons.push_back(m_configShuttle.shuttle8);
0470     m_shuttle_buttons.push_back(m_configShuttle.shuttle9);
0471     m_shuttle_buttons.push_back(m_configShuttle.shuttle10);
0472     m_shuttle_buttons.push_back(m_configShuttle.shuttle11);
0473     m_shuttle_buttons.push_back(m_configShuttle.shuttle12);
0474     m_shuttle_buttons.push_back(m_configShuttle.shuttle13);
0475     m_shuttle_buttons.push_back(m_configShuttle.shuttle14);
0476     m_shuttle_buttons.push_back(m_configShuttle.shuttle15);
0477 #endif
0478 
0479 #else  /* ! USE_JOGSHUTTLE */
0480     m_configShuttle.kcfg_enableshuttle->hide();
0481     m_configShuttle.kcfg_enableshuttle->setDisabled(true);
0482 #endif /* USE_JOGSHUTTLE */
0483     m_pageJog = addPage(p6, i18n("JogShuttle"), QStringLiteral("dialog-input-devices"));
0484 }
0485 
0486 void KdenliveSettingsDialog::initTranscodePage()
0487 {
0488     QWidget *p8 = new QWidget;
0489     m_configTranscode.setupUi(p8);
0490     m_pageTranscode = addPage(p8, i18n("Transcode"), QStringLiteral("edit-copy"));
0491 
0492     connect(m_configTranscode.button_add, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotAddTranscode);
0493     connect(m_configTranscode.button_delete, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotDeleteTranscode);
0494     connect(m_configTranscode.profiles_list, &QListWidget::itemChanged, this, &KdenliveSettingsDialog::slotDialogModified);
0495     connect(m_configTranscode.profiles_list, &QListWidget::currentRowChanged, this, &KdenliveSettingsDialog::slotSetTranscodeProfile);
0496     connect(m_configTranscode.profile_description, &QLineEdit::textChanged, this, &KdenliveSettingsDialog::slotEnableTranscodeUpdate);
0497     connect(m_configTranscode.profile_extension, &QLineEdit::textChanged, this, &KdenliveSettingsDialog::slotEnableTranscodeUpdate);
0498     connect(m_configTranscode.profile_parameters, &QPlainTextEdit::textChanged, this, &KdenliveSettingsDialog::slotEnableTranscodeUpdate);
0499     connect(m_configTranscode.profile_audioonly, &QCheckBox::stateChanged, this, &KdenliveSettingsDialog::slotEnableTranscodeUpdate);
0500 
0501     connect(m_configTranscode.button_update, &QAbstractButton::pressed, this, &KdenliveSettingsDialog::slotUpdateTranscodingProfile);
0502 
0503     m_configTranscode.profile_parameters->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 5);
0504     loadTranscodeProfiles();
0505 }
0506 
0507 void KdenliveSettingsDialog::initSdlPage(bool gpuAllowed)
0508 {
0509     connect(m_configSdl.reload_blackmagic, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotReloadBlackMagic);
0510     // m_configSdl.kcfg_openglmonitors->setHidden(true);
0511     connect(m_configSdl.fullscreen_monitor, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
0512             &KdenliveSettingsDialog::slotDialogModified);
0513     connect(m_configSdl.kcfg_audio_driver, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
0514             &KdenliveSettingsDialog::slotCheckAlsaDriver);
0515     connect(m_configSdl.kcfg_audio_backend, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
0516             &KdenliveSettingsDialog::slotCheckAudioBackend);
0517 
0518     // enable GPU accel only if Movit is found
0519     m_configSdl.kcfg_gpu_accel->setEnabled(gpuAllowed);
0520     m_configSdl.kcfg_gpu_accel->setToolTip(i18n("GPU processing needs MLT compiled with Movit and Rtaudio modules"));
0521     if (!getBlackMagicOutputDeviceList(m_configSdl.kcfg_blackmagic_output_device)) {
0522         // No blackmagic card found
0523         m_configSdl.kcfg_external_display->setEnabled(false);
0524     }
0525 }
0526 
0527 bool KdenliveSettingsDialog::getBlackMagicOutputDeviceList(QComboBox *devicelist, bool force)
0528 {
0529     if (!force && !KdenliveSettings::decklink_device_found()) {
0530         return false;
0531     }
0532     Mlt::Profile profile;
0533     Mlt::Consumer bm(profile, "decklink");
0534     int found_devices = 0;
0535     if (bm.is_valid()) {
0536         bm.set("list_devices", 1);
0537         found_devices = bm.get_int("devices");
0538     } else {
0539         KdenliveSettings::setDecklink_device_found(false);
0540     }
0541     if (found_devices <= 0) {
0542         devicelist->setEnabled(false);
0543         return false;
0544     }
0545     KdenliveSettings::setDecklink_device_found(true);
0546     for (int i = 0; i < found_devices; ++i) {
0547         char *tmp = qstrdup(QStringLiteral("device.%1").arg(i).toUtf8().constData());
0548         devicelist->addItem(bm.get(tmp));
0549         delete[] tmp;
0550     }
0551     devicelist->addItem(QStringLiteral("test"));
0552     return true;
0553 }
0554 
0555 void KdenliveSettingsDialog::setupJogshuttleBtns(const QString &device)
0556 {
0557     QList<QComboBox *> list;
0558     QList<QLabel *> list1;
0559 
0560     list << m_configShuttle.shuttle1;
0561     list << m_configShuttle.shuttle2;
0562     list << m_configShuttle.shuttle3;
0563     list << m_configShuttle.shuttle4;
0564     list << m_configShuttle.shuttle5;
0565     list << m_configShuttle.shuttle6;
0566     list << m_configShuttle.shuttle7;
0567     list << m_configShuttle.shuttle8;
0568     list << m_configShuttle.shuttle9;
0569     list << m_configShuttle.shuttle10;
0570     list << m_configShuttle.shuttle11;
0571     list << m_configShuttle.shuttle12;
0572     list << m_configShuttle.shuttle13;
0573     list << m_configShuttle.shuttle14;
0574     list << m_configShuttle.shuttle15;
0575 
0576     list1 << m_configShuttle.label_2;  // #1
0577     list1 << m_configShuttle.label_4;  // #2
0578     list1 << m_configShuttle.label_3;  // #3
0579     list1 << m_configShuttle.label_7;  // #4
0580     list1 << m_configShuttle.label_5;  // #5
0581     list1 << m_configShuttle.label_6;  // #6
0582     list1 << m_configShuttle.label_8;  // #7
0583     list1 << m_configShuttle.label_9;  // #8
0584     list1 << m_configShuttle.label_10; // #9
0585     list1 << m_configShuttle.label_11; // #10
0586     list1 << m_configShuttle.label_12; // #11
0587     list1 << m_configShuttle.label_13; // #12
0588     list1 << m_configShuttle.label_14; // #13
0589     list1 << m_configShuttle.label_15; // #14
0590     list1 << m_configShuttle.label_16; // #15
0591 
0592     for (int i = 0; i < list.count(); ++i) {
0593         list[i]->hide();
0594         list1[i]->hide();
0595     }
0596 #ifdef USE_JOGSHUTTLE
0597     if (!m_configShuttle.kcfg_enableshuttle->isChecked()) {
0598         return;
0599     }
0600     int keysCount = JogShuttle::keysCount(device);
0601 
0602     for (int i = 0; i < keysCount; ++i) {
0603         m_shuttle_buttons.push_back(list[i]);
0604         list[i]->show();
0605         list1[i]->show();
0606     }
0607 
0608     // populate the buttons with the current configuration. The items are sorted
0609     // according to the user-selected language, so they do not appear in random order.
0610     QMap<QString, QString> mappable_actions(m_mappable_actions);
0611     QList<QString> action_names = mappable_actions.keys();
0612     QList<QString>::Iterator iter = action_names.begin();
0613     // qCDebug(KDENLIVE_LOG) << "::::::::::::::::";
0614     while (iter != action_names.end()) {
0615         // qCDebug(KDENLIVE_LOG) << *iter;
0616         ++iter;
0617     }
0618 
0619     // qCDebug(KDENLIVE_LOG) << "::::::::::::::::";
0620 
0621     std::sort(action_names.begin(), action_names.end());
0622     iter = action_names.begin();
0623     while (iter != action_names.end()) {
0624         // qCDebug(KDENLIVE_LOG) << *iter;
0625         ++iter;
0626     }
0627     // qCDebug(KDENLIVE_LOG) << "::::::::::::::::";
0628 
0629     // Here we need to compute the action_id -> index-in-action_names. We iterate over the
0630     // action_names, as the sorting may depend on the user-language.
0631     QStringList actions_map = JogShuttleConfig::actionMap(KdenliveSettings::shuttlebuttons());
0632     QMap<QString, int> action_pos;
0633     for (const QString &action_id : qAsConst(actions_map)) {
0634         // This loop find out at what index is the string that would map to the action_id.
0635         for (int i = 0; i < action_names.size(); ++i) {
0636             if (mappable_actions[action_names.at(i)] == action_id) {
0637                 action_pos[action_id] = i;
0638                 break;
0639             }
0640         }
0641     }
0642 
0643     int i = 0;
0644     for (QComboBox *button : qAsConst(m_shuttle_buttons)) {
0645         button->addItems(action_names);
0646         connect(button, SIGNAL(activated(int)), this, SLOT(slotShuttleModified()));
0647         ++i;
0648         if (i < actions_map.size()) {
0649             button->setCurrentIndex(action_pos[actions_map[i]]);
0650         }
0651     }
0652 #else
0653     Q_UNUSED(device)
0654 #endif
0655 }
0656 
0657 KdenliveSettingsDialog::~KdenliveSettingsDialog() = default;
0658 
0659 void KdenliveSettingsDialog::slotUpdateGrabRegionStatus()
0660 {
0661     m_configCapture.region_group->setHidden(m_configCapture.kcfg_grab_capture_type->currentIndex() != 1);
0662 }
0663 
0664 void KdenliveSettingsDialog::slotEnableCaptureFolder(int ix)
0665 {
0666     m_configEnv.capturefolderurl->setEnabled(ix == 2);
0667 }
0668 
0669 void KdenliveSettingsDialog::slotEnableLibraryFolder()
0670 {
0671     m_configEnv.libraryfolderurl->setEnabled(!m_configEnv.kcfg_librarytodefaultfolder->isChecked());
0672 }
0673 
0674 void KdenliveSettingsDialog::slotEnableVideoFolder(int ix)
0675 {
0676     m_configEnv.videofolderurl->setEnabled(ix == 2);
0677 }
0678 
0679 void KdenliveSettingsDialog::initDevices()
0680 {
0681     // Fill audio drivers
0682     m_configSdl.kcfg_audio_driver->addItem(i18n("Automatic"), QString());
0683 #if defined(Q_OS_WIN)
0684     // TODO: i18n
0685     m_configSdl.kcfg_audio_driver->addItem("DirectSound", "directsound");
0686     m_configSdl.kcfg_audio_driver->addItem("WinMM", "winmm");
0687     m_configSdl.kcfg_audio_driver->addItem("Wasapi", "wasapi");
0688 #elif !defined(Q_WS_MAC)
0689     m_configSdl.kcfg_audio_driver->addItem(i18n("ALSA"), "alsa");
0690     m_configSdl.kcfg_audio_driver->addItem(i18n("PulseAudio"), "pulseaudio");
0691     m_configSdl.kcfg_audio_driver->addItem(i18n("PipeWire"), "pipewire");
0692     m_configSdl.kcfg_audio_driver->addItem(i18n("OSS"), "dsp");
0693     // m_configSdl.kcfg_audio_driver->addItem(i18n("OSS with DMA access"), "dma");
0694     m_configSdl.kcfg_audio_driver->addItem(i18n("Esound Daemon"), "esd");
0695     m_configSdl.kcfg_audio_driver->addItem(i18n("ARTS Daemon"), "artsc");
0696 #endif
0697 
0698     if (!KdenliveSettings::audiodrivername().isEmpty())
0699         for (int i = 1; i < m_configSdl.kcfg_audio_driver->count(); ++i) {
0700             if (m_configSdl.kcfg_audio_driver->itemData(i).toString() == KdenliveSettings::audiodrivername()) {
0701                 m_configSdl.kcfg_audio_driver->setCurrentIndex(i);
0702                 KdenliveSettings::setAudio_driver(uint(i));
0703             }
0704         }
0705 
0706     // Fill the list of audio playback / recording devices
0707     m_configSdl.kcfg_audio_device->addItem(i18n("Default"), QString());
0708     m_configCapture.kcfg_v4l_alsadevice->addItem(i18n("Default"), "default");
0709     if (!QStandardPaths::findExecutable(QStringLiteral("aplay")).isEmpty()) {
0710         m_readProcess.setOutputChannelMode(KProcess::OnlyStdoutChannel);
0711         m_readProcess.setProgram(QStringLiteral("aplay"), QStringList() << QStringLiteral("-l"));
0712         connect(&m_readProcess, &KProcess::readyReadStandardOutput, this, &KdenliveSettingsDialog::slotReadAudioDevices);
0713         m_readProcess.execute(5000);
0714     } else {
0715         // If aplay is not installed on the system, parse the /proc/asound/pcm file
0716         QFile file(QStringLiteral("/proc/asound/pcm"));
0717         if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
0718             QTextStream stream(&file);
0719             QString line = stream.readLine();
0720             QString deviceId;
0721             while (!line.isNull()) {
0722                 if (line.contains(QStringLiteral("playback"))) {
0723                     deviceId = line.section(QLatin1Char(':'), 0, 0);
0724                     m_configSdl.kcfg_audio_device->addItem(line.section(QLatin1Char(':'), 1, 1), QStringLiteral("plughw:%1,%2")
0725                                                                                                      .arg(deviceId.section(QLatin1Char('-'), 0, 0).toInt())
0726                                                                                                      .arg(deviceId.section(QLatin1Char('-'), 1, 1).toInt()));
0727                 }
0728                 if (line.contains(QStringLiteral("capture"))) {
0729                     deviceId = line.section(QLatin1Char(':'), 0, 0);
0730                     m_configCapture.kcfg_v4l_alsadevice->addItem(
0731                         line.section(QLatin1Char(':'), 1, 1).simplified(),
0732                         QStringLiteral("hw:%1,%2").arg(deviceId.section(QLatin1Char('-'), 0, 0).toInt()).arg(deviceId.section(QLatin1Char('-'), 1, 1).toInt()));
0733                 }
0734                 line = stream.readLine();
0735             }
0736             file.close();
0737         } else {
0738             qCDebug(KDENLIVE_LOG) << " / / / /CANNOT READ PCM";
0739         }
0740     }
0741 
0742     // Add pulseaudio capture option
0743     m_configCapture.kcfg_v4l_alsadevice->addItem(i18n("PulseAudio"), "pulse");
0744 
0745     if (!KdenliveSettings::audiodevicename().isEmpty()) {
0746         // Select correct alsa device
0747         int ix = m_configSdl.kcfg_audio_device->findData(KdenliveSettings::audiodevicename());
0748         m_configSdl.kcfg_audio_device->setCurrentIndex(ix);
0749         KdenliveSettings::setAudio_device(ix);
0750     }
0751 
0752     if (!KdenliveSettings::v4l_alsadevicename().isEmpty()) {
0753         // Select correct alsa device
0754         int ix = m_configCapture.kcfg_v4l_alsadevice->findData(KdenliveSettings::v4l_alsadevicename());
0755         m_configCapture.kcfg_v4l_alsadevice->setCurrentIndex(ix);
0756         KdenliveSettings::setV4l_alsadevice(ix);
0757     }
0758 
0759     m_configSdl.kcfg_audio_backend->addItem(i18n("SDL"), KdenliveSettings::sdlAudioBackend());
0760     if (KdenliveSettings::consumerslist().isEmpty()) {
0761         Mlt::Properties *consumers = pCore->getMltRepository()->consumers();
0762         QStringList consumersItemList;
0763         consumersItemList.reserve(consumers->count());
0764         for (int i = 0; i < consumers->count(); ++i) {
0765             consumersItemList << consumers->get_name(i);
0766         }
0767         delete consumers;
0768         KdenliveSettings::setConsumerslist(consumersItemList);
0769     }
0770     if (KdenliveSettings::consumerslist().contains("rtaudio")) {
0771         m_configSdl.kcfg_audio_backend->addItem(i18n("RtAudio"), "rtaudio");
0772     }
0773 
0774     if (!KdenliveSettings::audiobackend().isEmpty()) {
0775         int ix = m_configSdl.kcfg_audio_backend->findData(KdenliveSettings::audiobackend());
0776         m_configSdl.kcfg_audio_backend->setCurrentIndex(ix);
0777         KdenliveSettings::setAudio_backend(ix);
0778     }
0779     slotCheckAudioBackend();
0780 
0781     // Fill monitors data
0782     fillMonitorData();
0783     connect(qApp, &QApplication::screenAdded, this, &KdenliveSettingsDialog::fillMonitorData);
0784     connect(qApp, &QApplication::screenRemoved, this, &KdenliveSettingsDialog::fillMonitorData);
0785     loadCurrentV4lProfileInfo();
0786 }
0787 
0788 void KdenliveSettingsDialog::fillMonitorData()
0789 {
0790     QSignalBlocker bk(m_configSdl.fullscreen_monitor);
0791     m_configSdl.fullscreen_monitor->clear();
0792     m_configSdl.fullscreen_monitor->addItem(i18n("auto"));
0793     int ix = 0;
0794     for (const QScreen *screen : qApp->screens()) {
0795         QString screenName = screen->name().isEmpty() ? i18n("Monitor %1", ix + 1) : QString("%1: %2").arg(ix + 1).arg(screen->name());
0796         if (!screen->model().isEmpty()) {
0797             screenName.append(QString(" - %1").arg(screen->model()));
0798         }
0799         if (!screen->manufacturer().isEmpty()) {
0800             screenName.append(QString(" (%1)").arg(screen->manufacturer()));
0801         }
0802         m_configSdl.fullscreen_monitor->addItem(screenName, QString("%1:%2").arg(QString::number(ix), screen->serialNumber()));
0803         ix++;
0804     }
0805     if (!KdenliveSettings::fullscreen_monitor().isEmpty()) {
0806         int ix = m_configSdl.fullscreen_monitor->findData(KdenliveSettings::fullscreen_monitor());
0807         if (ix > -1) {
0808             m_configSdl.fullscreen_monitor->setCurrentIndex(ix);
0809         } else {
0810             // Monitor not found, reset
0811             m_configSdl.fullscreen_monitor->setCurrentIndex(0);
0812             KdenliveSettings::setFullscreen_monitor(QString());
0813         }
0814     }
0815 }
0816 
0817 void KdenliveSettingsDialog::slotReadAudioDevices()
0818 {
0819     QString result = QString(m_readProcess.readAllStandardOutput());
0820     // qCDebug(KDENLIVE_LOG) << "// / / / / / READING APLAY: ";
0821     // qCDebug(KDENLIVE_LOG) << result;
0822     const QStringList lines = result.split(QLatin1Char('\n'));
0823     for (const QString &devicestr : lines) {
0824         ////qCDebug(KDENLIVE_LOG) << "// READING LINE: " << data;
0825         if (!devicestr.startsWith(QLatin1Char(' ')) && devicestr.count(QLatin1Char(':')) > 1) {
0826             QString card = devicestr.section(QLatin1Char(':'), 0, 0).section(QLatin1Char(' '), -1);
0827             QString device = devicestr.section(QLatin1Char(':'), 1, 1).section(QLatin1Char(' '), -1);
0828             m_configSdl.kcfg_audio_device->addItem(devicestr.section(QLatin1Char(':'), -1).simplified(), QStringLiteral("plughw:%1,%2").arg(card, device));
0829             m_configCapture.kcfg_v4l_alsadevice->addItem(devicestr.section(QLatin1Char(':'), -1).simplified(), QStringLiteral("hw:%1,%2").arg(card, device));
0830         }
0831     }
0832 }
0833 
0834 void KdenliveSettingsDialog::showPage(Kdenlive::ConfigPage page, int option)
0835 {
0836     switch (page) {
0837     case Kdenlive::PageMisc:
0838         setCurrentPage(m_pageMisc);
0839         break;
0840     case Kdenlive::PageEnv:
0841         setCurrentPage(m_pageEnv);
0842         break;
0843     case Kdenlive::PageTimeline:
0844         setCurrentPage(m_pageTimeline);
0845         break;
0846     case Kdenlive::PageTools:
0847         setCurrentPage(m_pageTools);
0848         break;
0849     case Kdenlive::PageCapture:
0850         setCurrentPage(m_pageCapture);
0851         m_configCapture.tabWidget->setCurrentIndex(option);
0852         break;
0853     case Kdenlive::PageJogShuttle:
0854         setCurrentPage(m_pageJog);
0855         break;
0856     case Kdenlive::PagePlayback:
0857         setCurrentPage(m_pagePlay);
0858         break;
0859     case Kdenlive::PageTranscode:
0860         setCurrentPage(m_pageTranscode);
0861         break;
0862     case Kdenlive::PageProjectDefaults:
0863         setCurrentPage(m_pageProject);
0864         break;
0865     case Kdenlive::PageColorsGuides:
0866         setCurrentPage(m_pageColors);
0867         break;
0868     case Kdenlive::PageSpeech:
0869         checkSpeechDependencies();
0870         setCurrentPage(m_pageSpeech);
0871         break;
0872     default:
0873         setCurrentPage(m_pageMisc);
0874     }
0875 }
0876 
0877 void KdenliveSettingsDialog::slotEditAudioApplication()
0878 {
0879     QUrl url = KUrlRequesterDialog::getUrl(QUrl::fromLocalFile(KdenliveSettings::defaultaudioapp()), this, i18n("Enter path to the audio editing application"));
0880     if (!url.isEmpty()) {
0881         m_configEnv.kcfg_defaultaudioapp->setText(url.toLocalFile());
0882     }
0883 }
0884 
0885 void KdenliveSettingsDialog::slotEditImageApplication()
0886 {
0887     QUrl url = KUrlRequesterDialog::getUrl(QUrl::fromLocalFile(KdenliveSettings::defaultimageapp()), this, i18n("Enter path to the image editing application"));
0888     if (!url.isEmpty()) {
0889         m_configEnv.kcfg_defaultimageapp->setText(url.toLocalFile());
0890     }
0891 }
0892 
0893 void KdenliveSettingsDialog::slotEditGlaxnimateApplication()
0894 {
0895     QUrl url = KUrlRequesterDialog::getUrl(QUrl::fromLocalFile(KdenliveSettings::glaxnimatePath()), this, i18n("Enter path to the Glaxnimate application"));
0896     if (!url.isEmpty()) {
0897         m_configEnv.kcfg_glaxnimatePath->setText(url.toLocalFile());
0898     }
0899 }
0900 
0901 void KdenliveSettingsDialog::slotCheckShuttle(int state)
0902 {
0903 #ifdef USE_JOGSHUTTLE
0904     m_configShuttle.config_group->setEnabled(state != Qt::Unchecked);
0905     m_configShuttle.shuttledevicelist->clear();
0906 
0907     QStringList devNames = KdenliveSettings::shuttledevicenames();
0908     QStringList devPaths = KdenliveSettings::shuttledevicepaths();
0909 
0910     if (devNames.count() != devPaths.count()) {
0911         return;
0912     }
0913     for (int i = 0; i < devNames.count(); ++i) {
0914         m_configShuttle.shuttledevicelist->addItem(devNames.at(i), devPaths.at(i));
0915     }
0916     if (state != Qt::Unchecked) {
0917         setupJogshuttleBtns(m_configShuttle.shuttledevicelist->itemData(m_configShuttle.shuttledevicelist->currentIndex()).toString());
0918     }
0919 #else
0920     Q_UNUSED(state)
0921 #endif /* USE_JOGSHUTTLE */
0922 }
0923 
0924 void KdenliveSettingsDialog::slotUpdateShuttleDevice(int ix)
0925 {
0926 #ifdef USE_JOGSHUTTLE
0927     QString device = m_configShuttle.shuttledevicelist->itemData(ix).toString();
0928     // KdenliveSettings::setShuttledevice(device);
0929     setupJogshuttleBtns(device);
0930     m_configShuttle.kcfg_shuttledevice->setText(device);
0931 #else
0932     Q_UNUSED(ix)
0933 #endif /* USE_JOGSHUTTLE */
0934 }
0935 
0936 void KdenliveSettingsDialog::updateWidgets()
0937 {
0938 // Revert widgets to last saved state (for example when user pressed "Cancel")
0939 // //qCDebug(KDENLIVE_LOG) << "// // // KCONFIG Revert called";
0940 #ifdef USE_JOGSHUTTLE
0941     // revert jog shuttle device
0942     if (m_configShuttle.shuttledevicelist->count() > 0) {
0943         for (int i = 0; i < m_configShuttle.shuttledevicelist->count(); ++i) {
0944             if (m_configShuttle.shuttledevicelist->itemData(i) == KdenliveSettings::shuttledevice()) {
0945                 m_configShuttle.shuttledevicelist->setCurrentIndex(i);
0946                 break;
0947             }
0948         }
0949     }
0950 
0951     // Revert jog shuttle buttons
0952     QList<QString> action_names = m_mappable_actions.keys();
0953     std::sort(action_names.begin(), action_names.end());
0954     QStringList actions_map = JogShuttleConfig::actionMap(KdenliveSettings::shuttlebuttons());
0955     QMap<QString, int> action_pos;
0956     for (const QString &action_id : qAsConst(actions_map)) {
0957         // This loop find out at what index is the string that would map to the action_id.
0958         for (int i = 0; i < action_names.size(); ++i) {
0959             if (m_mappable_actions[action_names[i]] == action_id) {
0960                 action_pos[action_id] = i;
0961                 break;
0962             }
0963         }
0964     }
0965     int i = 0;
0966     for (QComboBox *button : qAsConst(m_shuttle_buttons)) {
0967         ++i;
0968         if (i < actions_map.size()) {
0969             button->setCurrentIndex(action_pos[actions_map[i]]);
0970         }
0971     }
0972 #endif /* USE_JOGSHUTTLE */
0973 }
0974 
0975 void KdenliveSettingsDialog::accept()
0976 {
0977     if (m_pw->selectedProfile().isEmpty()) {
0978         KMessageBox::error(this, i18n("Please select a video profile"));
0979         return;
0980     }
0981     KConfigDialog::accept();
0982 }
0983 
0984 void KdenliveSettingsDialog::updateExternalApps()
0985 {
0986     m_configEnv.kcfg_defaultimageapp->setText(KdenliveSettings::defaultimageapp());
0987     m_configEnv.kcfg_defaultaudioapp->setText(KdenliveSettings::defaultaudioapp());
0988     m_configEnv.kcfg_glaxnimatePath->setText(KdenliveSettings::glaxnimatePath());
0989 }
0990 
0991 void KdenliveSettingsDialog::updateSettings()
0992 {
0993     // Save changes to settings (for example when user pressed "Apply" or "Ok")
0994     // //qCDebug(KDENLIVE_LOG) << "// // // KCONFIG UPDATE called";
0995     if (m_pw->selectedProfile().isEmpty()) {
0996         KMessageBox::error(this, i18n("Please select a video profile"));
0997         return;
0998     }
0999     KdenliveSettings::setDefault_profile(m_pw->selectedProfile());
1000 
1001     if (m_configEnv.ffmpegurl->text().isEmpty()) {
1002         QString infos;
1003         QString warnings;
1004         QString errors;
1005         Wizard::slotCheckPrograms(infos, warnings, errors);
1006         m_configEnv.ffmpegurl->setText(KdenliveSettings::ffmpegpath());
1007         m_configEnv.ffplayurl->setText(KdenliveSettings::ffplaypath());
1008         m_configEnv.ffprobeurl->setText(KdenliveSettings::ffprobepath());
1009     }
1010     if (m_configEnv.mediainfourl->text().isEmpty()) {
1011         m_configEnv.mediainfourl->setText(KdenliveSettings::mediainfopath());
1012     }
1013 
1014     if (m_configTimeline.kcfg_trackheight->value() == 0) {
1015         QFont ft = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont);
1016         // Default unit for timeline.qml objects size
1017         int baseUnit = qMax(28, qRound(QFontInfo(ft).pixelSize() * 1.8));
1018         int trackHeight = qMax(50, int(2.2 * baseUnit + 6));
1019         m_configTimeline.kcfg_trackheight->setValue(trackHeight);
1020     } else if (m_configTimeline.kcfg_trackheight->value() != KdenliveSettings::trackheight()) {
1021         QFont ft = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont);
1022         // Default unit for timeline.qml objects size
1023         int baseUnit = qMax(28, qRound(QFontInfo(ft).pixelSize() * 1.8));
1024         if (m_configTimeline.kcfg_trackheight->value() < baseUnit) {
1025             m_configTimeline.kcfg_trackheight->setValue(baseUnit);
1026         }
1027     }
1028 
1029     bool resetConsumer = false;
1030     bool fullReset = false;
1031     bool updateLibrary = false;
1032 
1033     // Guide categories
1034     const QStringList updatedGuides = m_guidesCategories->updatedGuides();
1035     if (KdenliveSettings::guidesCategories() != updatedGuides) {
1036         KdenliveSettings::setGuidesCategories(updatedGuides);
1037     }
1038 
1039     /*if (m_configShuttle.shuttledevicelist->count() > 0) {
1040     QString device = m_configShuttle.shuttledevicelist->itemData(m_configShuttle.shuttledevicelist->currentIndex()).toString();
1041     if (device != KdenliveSettings::shuttledevice()) KdenliveSettings::setShuttledevice(device);
1042     }*/
1043     m_tlPreviewProfiles->hideMessage();
1044     if (m_configProject.projecturl->url().toLocalFile() != KdenliveSettings::defaultprojectfolder()) {
1045         KdenliveSettings::setDefaultprojectfolder(m_configProject.projecturl->url().toLocalFile());
1046         if (!KdenliveSettings::sameprojectfolder()) {
1047             m_configEnv.kcfg_videotodefaultfolder->setItemText(1, i18n("Always use project folder: %1", KdenliveSettings::defaultprojectfolder()));
1048             m_configEnv.kcfg_capturetoprojectfolder->setItemText(1, i18n("Always use project folder: %1", KdenliveSettings::defaultprojectfolder()));
1049         }
1050     }
1051 
1052     if (m_configProject.kcfg_customprojectfolder->isChecked() != KdenliveSettings::customprojectfolder()) {
1053         if (KdenliveSettings::customprojectfolder()) {
1054             m_configEnv.kcfg_videotodefaultfolder->setItemText(1, i18n("Always use active project folder"));
1055             m_configEnv.kcfg_capturetoprojectfolder->setItemText(1, i18n("Always use active project folder"));
1056         } else {
1057             m_configEnv.kcfg_videotodefaultfolder->setItemText(1, i18n("Always use project folder: %1", KdenliveSettings::defaultprojectfolder()));
1058             m_configEnv.kcfg_capturetoprojectfolder->setItemText(1, i18n("Always use project folder: %1", KdenliveSettings::defaultprojectfolder()));
1059         }
1060     }
1061 
1062     if (m_configSdl.fullscreen_monitor->currentData().toString() != KdenliveSettings::fullscreen_monitor()) {
1063         KdenliveSettings::setFullscreen_monitor(m_configSdl.fullscreen_monitor->currentData().toString());
1064     }
1065 
1066     if (m_configEnv.capturefolderurl->url().toLocalFile() != KdenliveSettings::capturefolder()) {
1067         KdenliveSettings::setCapturefolder(m_configEnv.capturefolderurl->url().toLocalFile());
1068     }
1069 
1070     // Library default folder
1071     if (m_configEnv.kcfg_librarytodefaultfolder->isChecked() != KdenliveSettings::librarytodefaultfolder()) {
1072         KdenliveSettings::setLibrarytodefaultfolder(m_configEnv.kcfg_librarytodefaultfolder->isChecked());
1073         updateLibrary = true;
1074     }
1075 
1076     if (m_configEnv.libraryfolderurl->url().toLocalFile() != KdenliveSettings::libraryfolder()) {
1077         KdenliveSettings::setLibraryfolder(m_configEnv.libraryfolderurl->url().toLocalFile());
1078         if (!KdenliveSettings::librarytodefaultfolder()) {
1079             updateLibrary = true;
1080         }
1081     }
1082 
1083     if (m_configCapture.kcfg_v4l_format->currentIndex() != int(KdenliveSettings::v4l_format())) {
1084         saveCurrentV4lProfile();
1085         KdenliveSettings::setV4l_format(0);
1086     }
1087 
1088     // Check if screengrab is fullscreen
1089     if (m_configCapture.kcfg_grab_capture_type->currentIndex() != KdenliveSettings::grab_capture_type()) {
1090         KdenliveSettings::setGrab_capture_type(m_configCapture.kcfg_grab_capture_type->currentIndex());
1091         Q_EMIT updateFullScreenGrab();
1092     }
1093 
1094     // Check audio capture changes
1095     if (KdenliveSettings::audiocapturechannels() != m_configCapture.audiocapturechannels->currentData().toInt() ||
1096         KdenliveSettings::audiocapturevolume() != m_configCapture.kcfg_audiocapturevolume->value() ||
1097         KdenliveSettings::audiocapturesamplerate() != m_configCapture.audiocapturesamplerate->currentData().toInt()) {
1098         KdenliveSettings::setAudiocapturechannels(m_configCapture.audiocapturechannels->currentData().toInt());
1099         KdenliveSettings::setAudiocapturevolume(m_configCapture.kcfg_audiocapturevolume->value());
1100         KdenliveSettings::setAudiocapturesamplerate(m_configCapture.audiocapturesamplerate->currentData().toInt());
1101         Q_EMIT resetAudioMonitoring();
1102     }
1103 
1104     // Check encoding profiles
1105     // FFmpeg
1106     QString string = m_v4lProfiles->currentParams();
1107     if (string != KdenliveSettings::v4l_parameters()) {
1108         KdenliveSettings::setV4l_parameters(string);
1109     }
1110     string = m_v4lProfiles->currentExtension();
1111     if (string != KdenliveSettings::v4l_extension()) {
1112         KdenliveSettings::setV4l_extension(string);
1113     }
1114 
1115     // screengrab
1116     string = m_grabProfiles->currentParams();
1117     if (string != KdenliveSettings::grab_parameters()) {
1118         KdenliveSettings::setGrab_parameters(string);
1119     }
1120     string = m_grabProfiles->currentExtension();
1121     if (string != KdenliveSettings::grab_extension()) {
1122         KdenliveSettings::setGrab_extension(string);
1123     }
1124 
1125     // decklink
1126     string = m_decklinkProfiles->currentParams();
1127     if (string != KdenliveSettings::decklink_parameters()) {
1128         KdenliveSettings::setDecklink_parameters(string);
1129     }
1130     string = m_decklinkProfiles->currentExtension();
1131     if (string != KdenliveSettings::decklink_extension()) {
1132         KdenliveSettings::setDecklink_extension(string);
1133     }
1134 
1135     // proxies
1136     string = m_proxyProfiles->currentParams();
1137     if (string != KdenliveSettings::proxyparams()) {
1138         KdenliveSettings::setProxyparams(string);
1139     }
1140     string = m_proxyProfiles->currentExtension();
1141     if (string != KdenliveSettings::proxyextension()) {
1142         KdenliveSettings::setProxyextension(string);
1143     }
1144 
1145     // external proxies
1146     QString profilestr = m_configProxy.kcfg_external_proxy_profile->currentData().toString();
1147     if (!profilestr.isEmpty() && (profilestr != KdenliveSettings::externalProxyProfile())) {
1148         KdenliveSettings::setExternalProxyProfile(profilestr);
1149     }
1150 
1151     // timeline preview
1152     string = m_tlPreviewProfiles->currentParams();
1153     if (string != KdenliveSettings::previewparams()) {
1154         KdenliveSettings::setPreviewparams(string);
1155     }
1156     string = m_tlPreviewProfiles->currentExtension();
1157     if (string != KdenliveSettings::previewextension()) {
1158         KdenliveSettings::setPreviewextension(string);
1159     }
1160 
1161     if (updateLibrary) {
1162         Q_EMIT updateLibraryFolder();
1163     }
1164 
1165     QString value = m_configCapture.kcfg_v4l_alsadevice->currentData().toString();
1166     if (value != KdenliveSettings::v4l_alsadevicename()) {
1167         KdenliveSettings::setV4l_alsadevicename(value);
1168     }
1169 
1170     if (m_configSdl.kcfg_external_display->isChecked() != KdenliveSettings::external_display()) {
1171         KdenliveSettings::setExternal_display(m_configSdl.kcfg_external_display->isChecked());
1172         resetConsumer = true;
1173         fullReset = true;
1174     } else if (KdenliveSettings::external_display() &&
1175                KdenliveSettings::blackmagic_output_device() != m_configSdl.kcfg_blackmagic_output_device->currentIndex()) {
1176         resetConsumer = true;
1177         fullReset = true;
1178     }
1179 
1180     value = m_configSdl.kcfg_audio_driver->currentData().toString();
1181     if (value != KdenliveSettings::audiodrivername()) {
1182         KdenliveSettings::setAudiodrivername(value);
1183         resetConsumer = true;
1184     }
1185 
1186     if (value == QLatin1String("alsa")) {
1187         // Audio device setting is only valid for alsa driver
1188         value = m_configSdl.kcfg_audio_device->currentData().toString();
1189         if (value != KdenliveSettings::audiodevicename()) {
1190             KdenliveSettings::setAudiodevicename(value);
1191             resetConsumer = true;
1192         }
1193     } else if (!KdenliveSettings::audiodevicename().isEmpty()) {
1194         KdenliveSettings::setAudiodevicename(QString());
1195         resetConsumer = true;
1196     }
1197 
1198     value = m_configSdl.kcfg_audio_backend->currentData().toString();
1199     if (value != KdenliveSettings::audiobackend()) {
1200         KdenliveSettings::setAudiobackend(value);
1201         resetConsumer = true;
1202         fullReset = true;
1203     }
1204 
1205     if (m_configColors.kcfg_window_background->color() != KdenliveSettings::window_background()) {
1206         KdenliveSettings::setWindow_background(m_configColors.kcfg_window_background->color());
1207         Q_EMIT updateMonitorBg();
1208     }
1209 
1210     if (m_configColors.kcfg_thumbColor1->color() != KdenliveSettings::thumbColor1() ||
1211         m_configColors.kcfg_thumbColor2->color() != KdenliveSettings::thumbColor2()) {
1212         KdenliveSettings::setThumbColor1(m_configColors.kcfg_thumbColor1->color());
1213         KdenliveSettings::setThumbColor2(m_configColors.kcfg_thumbColor2->color());
1214         Q_EMIT pCore->window()->getCurrentTimeline()->controller()->colorsChanged();
1215         pCore->getMonitor(Kdenlive::ClipMonitor)->refreshAudioThumbs();
1216     }
1217 
1218     if (m_configColors.kcfg_overlayColor->color() != KdenliveSettings::overlayColor()) {
1219         KdenliveSettings::setOverlayColor(m_configColors.kcfg_overlayColor->color());
1220         pCore->getMonitor(Kdenlive::ProjectMonitor)->getControllerProxy()->colorsChanged();
1221         pCore->getMonitor(Kdenlive::ClipMonitor)->getControllerProxy()->colorsChanged();
1222     }
1223 
1224     if (m_configMisc.kcfg_tabposition->currentIndex() != KdenliveSettings::tabposition()) {
1225         KdenliveSettings::setTabposition(m_configMisc.kcfg_tabposition->currentIndex());
1226     }
1227 
1228     if (m_configTimeline.kcfg_displayallchannels->isChecked() != KdenliveSettings::displayallchannels()) {
1229         KdenliveSettings::setDisplayallchannels(m_configTimeline.kcfg_displayallchannels->isChecked());
1230         Q_EMIT audioThumbFormatChanged();
1231         pCore->getMonitor(Kdenlive::ClipMonitor)->refreshAudioThumbs();
1232     }
1233 
1234     if (m_modified) {
1235         // The transcoding profiles were modified, save.
1236         m_modified = false;
1237         saveTranscodeProfiles();
1238     }
1239 
1240 #ifdef USE_JOGSHUTTLE
1241     m_shuttleModified = false;
1242 
1243     QStringList actions;
1244     actions << QStringLiteral("monitor_pause"); // the Job rest position action.
1245     for (QComboBox *button : qAsConst(m_shuttle_buttons)) {
1246         actions << m_mappable_actions[button->currentText()];
1247     }
1248     QString maps = JogShuttleConfig::actionMap(actions);
1249     // fprintf(stderr, "Shuttle config: %s\n", JogShuttleConfig::actionMap(actions).toLatin1().constData());
1250     if (KdenliveSettings::shuttlebuttons() != maps) {
1251         KdenliveSettings::setShuttlebuttons(maps);
1252     }
1253 #endif
1254 
1255     bool restart = false;
1256     if (m_configSdl.kcfg_gpu_accel->isChecked() != KdenliveSettings::gpu_accel()) {
1257         // GPU setting was changed, we need to restart Kdenlive or everything will be corrupted
1258         if (KMessageBox::warningContinueCancel(this, i18n("Kdenlive must be restarted to change this setting")) == KMessageBox::Continue) {
1259             restart = true;
1260         } else {
1261             m_configSdl.kcfg_gpu_accel->setChecked(KdenliveSettings::gpu_accel());
1262         }
1263     }
1264 
1265     if (m_configTimeline.kcfg_trackheight->value() != KdenliveSettings::trackheight()) {
1266         KdenliveSettings::setTrackheight(m_configTimeline.kcfg_trackheight->value());
1267         Q_EMIT resetView();
1268     }
1269 
1270     if (m_configTimeline.kcfg_autoscroll->isChecked() != KdenliveSettings::autoscroll()) {
1271         KdenliveSettings::setAutoscroll(m_configTimeline.kcfg_autoscroll->isChecked());
1272         Q_EMIT pCore->autoScrollChanged();
1273     }
1274 
1275     if (m_configTimeline.kcfg_pauseonseek->isChecked() != KdenliveSettings::pauseonseek()) {
1276         KdenliveSettings::setPauseonseek(m_configTimeline.kcfg_pauseonseek->isChecked());
1277     }
1278 
1279     if (m_configTimeline.kcfg_seekonaddeffect->isChecked() != KdenliveSettings::seekonaddeffect()) {
1280         KdenliveSettings::setSeekonaddeffect(m_configTimeline.kcfg_seekonaddeffect->isChecked());
1281     }
1282 
1283     if (m_configTimeline.kcfg_scrollvertically->isChecked() != KdenliveSettings::scrollvertically()) {
1284         KdenliveSettings::setScrollvertically(m_configTimeline.kcfg_scrollvertically->isChecked());
1285         Q_EMIT pCore->window()->getCurrentTimeline()->controller()->scrollVerticallyChanged();
1286     }
1287 
1288     // Speech
1289     switch (m_configSpeech.speech_stack->currentIndex()) {
1290     case 1:
1291         if (KdenliveSettings::speechEngine() != QLatin1String("whisper")) {
1292             KdenliveSettings::setSpeechEngine(QStringLiteral("whisper"));
1293             Q_EMIT pCore->speechEngineChanged();
1294         }
1295         break;
1296     default:
1297         if (!KdenliveSettings::speechEngine().isEmpty()) {
1298             KdenliveSettings::setSpeechEngine(QString());
1299             Q_EMIT pCore->speechEngineChanged();
1300         }
1301         break;
1302     }
1303 
1304     if (m_configSpeech.combo_wr_lang->currentData().toString() != KdenliveSettings::whisperLanguage()) {
1305         KdenliveSettings::setWhisperLanguage(m_configSpeech.combo_wr_lang->currentData().toString());
1306     }
1307     if (m_configSpeech.combo_wr_model->currentData().toString() != KdenliveSettings::whisperModel()) {
1308         KdenliveSettings::setWhisperModel(m_configSpeech.combo_wr_model->currentData().toString());
1309     }
1310     if (m_configSpeech.combo_wr_device->currentData().toString() != KdenliveSettings::whisperDevice()) {
1311         KdenliveSettings::setWhisperDevice(m_configSpeech.combo_wr_device->currentData().toString());
1312     }
1313 
1314     // Mimes
1315     if (m_configEnv.kcfg_addedExtensions->text() != KdenliveSettings::addedExtensions()) {
1316         // Update list
1317         KdenliveSettings::setAddedExtensions(m_configEnv.kcfg_addedExtensions->text());
1318         QStringList mimes = ClipCreationDialog::getExtensions();
1319         std::sort(mimes.begin(), mimes.end());
1320         m_configEnv.supportedmimes->setPlainText(mimes.join(QLatin1Char(' ')));
1321     }
1322 
1323     // proxy/transcode max concurrent jobs
1324     if (m_configEnv.kcfg_proxythreads->value() != KdenliveSettings::proxythreads()) {
1325         KdenliveSettings::setProxythreads(m_configEnv.kcfg_proxythreads->value());
1326         pCore->taskManager.updateConcurrency();
1327     }
1328 
1329     KConfigDialog::settingsChangedSlot();
1330     // KConfigDialog::updateSettings();
1331     if (resetConsumer) {
1332         Q_EMIT doResetConsumer(fullReset);
1333     }
1334     if (restart) {
1335         Q_EMIT restartKdenlive();
1336     }
1337     Q_EMIT checkTabPosition();
1338 
1339     // remembering Config dialog size
1340     KSharedConfigPtr config = KSharedConfig::openConfig();
1341     KConfigGroup settingsGroup(config, "settings");
1342     settingsGroup.writeEntry("dialogSize", QVariant(size()));
1343 }
1344 
1345 void KdenliveSettingsDialog::slotCheckAlsaDriver()
1346 {
1347     QString value = m_configSdl.kcfg_audio_driver->currentData().toString();
1348     m_configSdl.kcfg_audio_device->setEnabled(value == QLatin1String("alsa"));
1349 }
1350 
1351 void KdenliveSettingsDialog::slotCheckAudioBackend()
1352 {
1353     bool isSdl = m_configSdl.kcfg_audio_backend->currentData().toString().startsWith(QLatin1String("sdl"));
1354     m_configSdl.label_audio_driver->setEnabled(isSdl);
1355     m_configSdl.kcfg_audio_driver->setEnabled(isSdl);
1356     m_configSdl.label_audio_device->setEnabled(isSdl);
1357     m_configSdl.kcfg_audio_device->setEnabled(isSdl);
1358 }
1359 
1360 void KdenliveSettingsDialog::loadTranscodeProfiles()
1361 {
1362     KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("kdenlivetranscodingrc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
1363     KConfigGroup transConfig(config, "Transcoding");
1364     // read the entries
1365     m_configTranscode.profiles_list->blockSignals(true);
1366     m_configTranscode.profiles_list->clear();
1367     QMap<QString, QString> profiles = transConfig.entryMap();
1368     QMapIterator<QString, QString> i(profiles);
1369     while (i.hasNext()) {
1370         i.next();
1371         auto *item = new QListWidgetItem(i.key());
1372         QString profilestr = i.value();
1373         if (profilestr.contains(QLatin1Char(';'))) {
1374             item->setToolTip(profilestr.section(QLatin1Char(';'), 1, 1));
1375         }
1376         item->setData(Qt::UserRole, profilestr);
1377         m_configTranscode.profiles_list->addItem(item);
1378         // item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable);
1379     }
1380     m_configTranscode.profiles_list->blockSignals(false);
1381     m_configTranscode.profiles_list->setCurrentRow(0);
1382 }
1383 
1384 void KdenliveSettingsDialog::saveTranscodeProfiles()
1385 {
1386     QString transcodeFile = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/kdenlivetranscodingrc");
1387     KSharedConfigPtr config = KSharedConfig::openConfig(transcodeFile);
1388     KConfigGroup transConfig(config, "Transcoding");
1389     // read the entries
1390     transConfig.deleteGroup();
1391     int max = m_configTranscode.profiles_list->count();
1392     for (int i = 0; i < max; ++i) {
1393         QListWidgetItem *item = m_configTranscode.profiles_list->item(i);
1394         transConfig.writeEntry(item->text(), item->data(Qt::UserRole).toString());
1395     }
1396     config->sync();
1397 }
1398 
1399 void KdenliveSettingsDialog::slotAddTranscode()
1400 {
1401     bool ok;
1402     QString presetName =
1403         QInputDialog::getText(this, i18nc("@title:window", "Enter Preset Name"), i18n("Enter the name of this preset:"), QLineEdit::Normal, QString(), &ok);
1404     if (!ok) return;
1405     if (!m_configTranscode.profiles_list->findItems(presetName, Qt::MatchExactly).isEmpty()) {
1406         KMessageBox::error(this, i18n("A profile with that name already exists"));
1407         return;
1408     }
1409     QListWidgetItem *item = new QListWidgetItem(presetName);
1410     QString profilestr = m_configTranscode.profile_parameters->toPlainText();
1411     profilestr.append(" %1." + m_configTranscode.profile_extension->text());
1412     profilestr.append(';');
1413     if (!m_configTranscode.profile_description->text().isEmpty()) {
1414         profilestr.append(m_configTranscode.profile_description->text());
1415     }
1416     if (m_configTranscode.profile_audioonly->isChecked()) {
1417         profilestr.append(";audio");
1418     }
1419     item->setData(Qt::UserRole, profilestr);
1420     m_configTranscode.profiles_list->addItem(item);
1421     m_configTranscode.profiles_list->setCurrentItem(item);
1422     slotDialogModified();
1423 }
1424 
1425 void KdenliveSettingsDialog::slotUpdateTranscodingProfile()
1426 {
1427     QListWidgetItem *item = m_configTranscode.profiles_list->currentItem();
1428     if (!item) {
1429         return;
1430     }
1431     m_configTranscode.button_update->setEnabled(false);
1432     QString profilestr = m_configTranscode.profile_parameters->toPlainText();
1433     profilestr.append(" %1." + m_configTranscode.profile_extension->text());
1434     profilestr.append(';');
1435     if (!m_configTranscode.profile_description->text().isEmpty()) {
1436         profilestr.append(m_configTranscode.profile_description->text());
1437     }
1438     if (m_configTranscode.profile_audioonly->isChecked()) {
1439         profilestr.append(QStringLiteral(";audio"));
1440     }
1441     item->setData(Qt::UserRole, profilestr);
1442     slotDialogModified();
1443 }
1444 
1445 void KdenliveSettingsDialog::slotDeleteTranscode()
1446 {
1447     QListWidgetItem *item = m_configTranscode.profiles_list->currentItem();
1448     if (item == nullptr) {
1449         return;
1450     }
1451     delete item;
1452     slotDialogModified();
1453 }
1454 
1455 void KdenliveSettingsDialog::slotEnableTranscodeUpdate()
1456 {
1457     if (!m_configTranscode.profile_box->isEnabled()) {
1458         return;
1459     }
1460     bool allow = true;
1461     if (m_configTranscode.profile_extension->text().isEmpty()) {
1462         allow = false;
1463     }
1464     m_configTranscode.button_update->setEnabled(allow);
1465 }
1466 
1467 void KdenliveSettingsDialog::slotSetTranscodeProfile()
1468 {
1469     m_configTranscode.profile_box->setEnabled(false);
1470     m_configTranscode.button_update->setEnabled(false);
1471     m_configTranscode.profile_description->clear();
1472     m_configTranscode.profile_extension->clear();
1473     m_configTranscode.profile_parameters->clear();
1474     m_configTranscode.profile_audioonly->setChecked(false);
1475     QListWidgetItem *item = m_configTranscode.profiles_list->currentItem();
1476     if (!item) {
1477         return;
1478     }
1479     QString profilestr = item->data(Qt::UserRole).toString();
1480     if (profilestr.contains(QLatin1Char(';'))) {
1481         m_configTranscode.profile_description->setText(profilestr.section(QLatin1Char(';'), 1, 1));
1482         if (profilestr.section(QLatin1Char(';'), 2, 2) == QLatin1String("audio")) {
1483             m_configTranscode.profile_audioonly->setChecked(true);
1484         }
1485         profilestr = profilestr.section(QLatin1Char(';'), 0, 0).simplified();
1486     }
1487     m_configTranscode.profile_extension->setText(profilestr.section(QLatin1Char('.'), -1));
1488     m_configTranscode.profile_parameters->setPlainText(profilestr.section(QLatin1Char(' '), 0, -2));
1489     m_configTranscode.profile_box->setEnabled(true);
1490 }
1491 
1492 void KdenliveSettingsDialog::slotShuttleModified()
1493 {
1494 #ifdef USE_JOGSHUTTLE
1495     QStringList actions;
1496     actions << QStringLiteral("monitor_pause"); // the Job rest position action.
1497     for (QComboBox *button : qAsConst(m_shuttle_buttons)) {
1498         actions << m_mappable_actions[button->currentText()];
1499     }
1500     QString maps = JogShuttleConfig::actionMap(actions);
1501     m_shuttleModified = KdenliveSettings::shuttlebuttons() != maps;
1502 #endif
1503     KConfigDialog::updateButtons();
1504 }
1505 
1506 void KdenliveSettingsDialog::slotDialogModified()
1507 {
1508     m_modified = true;
1509     KConfigDialog::updateButtons();
1510 }
1511 
1512 // virtual
1513 bool KdenliveSettingsDialog::hasChanged()
1514 {
1515     if (m_modified || m_shuttleModified) {
1516         return true;
1517     }
1518     if (KdenliveSettings::audiocapturechannels() != m_configCapture.audiocapturechannels->currentData().toInt()) {
1519         return true;
1520     }
1521     if (KdenliveSettings::audiocapturesamplerate() != m_configCapture.audiocapturesamplerate->currentData().toInt()) {
1522         return true;
1523     }
1524     return KConfigDialog::hasChanged();
1525 }
1526 
1527 void KdenliveSettingsDialog::slotUpdatev4lDevice()
1528 {
1529     QString device = m_configCapture.kcfg_detectedv4ldevices->itemData(m_configCapture.kcfg_detectedv4ldevices->currentIndex()).toString();
1530     if (!device.isEmpty()) {
1531         m_configCapture.kcfg_video4vdevice->setText(device);
1532     }
1533     QString info = m_configCapture.kcfg_detectedv4ldevices->itemData(m_configCapture.kcfg_detectedv4ldevices->currentIndex(), Qt::UserRole + 1).toString();
1534 
1535     m_configCapture.kcfg_v4l_format->blockSignals(true);
1536     m_configCapture.kcfg_v4l_format->clear();
1537 
1538     QString vl4ProfilePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/video4linux");
1539     if (QFile::exists(vl4ProfilePath)) {
1540         m_configCapture.kcfg_v4l_format->addItem(i18n("Current settings"));
1541     }
1542 
1543     QStringList pixelformats = info.split('>', Qt::SkipEmptyParts);
1544     QString itemSize;
1545     QStringList itemRates;
1546     for (int i = 0; i < pixelformats.count(); ++i) {
1547         QString format = pixelformats.at(i).section(QLatin1Char(':'), 0, 0);
1548         QStringList sizes = pixelformats.at(i).split(':', Qt::SkipEmptyParts);
1549         sizes.takeFirst();
1550         for (int j = 0; j < sizes.count(); ++j) {
1551             itemSize = sizes.at(j).section(QLatin1Char('='), 0, 0);
1552             itemRates = sizes.at(j).section(QLatin1Char('='), 1, 1).split(QLatin1Char(','), Qt::SkipEmptyParts);
1553             for (int k = 0; k < itemRates.count(); ++k) {
1554                 m_configCapture.kcfg_v4l_format->addItem(
1555                     QLatin1Char('[') + format + QStringLiteral("] ") + itemSize + QStringLiteral(" (") + itemRates.at(k) + QLatin1Char(')'),
1556                     QStringList() << format << itemSize.section('x', 0, 0) << itemSize.section('x', 1, 1) << itemRates.at(k).section(QLatin1Char('/'), 0, 0)
1557                                   << itemRates.at(k).section(QLatin1Char('/'), 1, 1));
1558             }
1559         }
1560     }
1561     m_configCapture.kcfg_v4l_format->blockSignals(false);
1562     slotUpdatev4lCaptureProfile();
1563 }
1564 
1565 void KdenliveSettingsDialog::slotUpdatev4lCaptureProfile()
1566 {
1567     QStringList info = m_configCapture.kcfg_v4l_format->itemData(m_configCapture.kcfg_v4l_format->currentIndex(), Qt::UserRole).toStringList();
1568     if (info.isEmpty()) {
1569         // No auto info, display the current ones
1570         loadCurrentV4lProfileInfo();
1571         return;
1572     }
1573     m_configCapture.p_size->setText(info.at(1) + QLatin1Char('x') + info.at(2));
1574     m_configCapture.p_fps->setText(info.at(3) + QLatin1Char('/') + info.at(4));
1575     m_configCapture.p_aspect->setText(QStringLiteral("1/1"));
1576     m_configCapture.p_display->setText(info.at(1) + QLatin1Char('/') + info.at(2));
1577     m_configCapture.p_colorspace->setText(ProfileRepository::getColorspaceDescription(601));
1578     m_configCapture.p_progressive->setText(i18n("Progressive"));
1579 
1580     QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/"));
1581     if (!dir.exists() || !dir.exists(QStringLiteral("video4linux"))) {
1582         saveCurrentV4lProfile();
1583     }
1584 }
1585 
1586 void KdenliveSettingsDialog::loadCurrentV4lProfileInfo()
1587 {
1588     QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/"));
1589     if (!dir.exists()) {
1590         dir.mkpath(QStringLiteral("."));
1591     }
1592     if (!ProfileRepository::get()->profileExists(dir.absoluteFilePath(QStringLiteral("video4linux")))) {
1593         // No default formats found, build one
1594         std::unique_ptr<ProfileParam> prof(new ProfileParam(pCore->getCurrentProfile().get()));
1595         prof->m_width = 320;
1596         prof->m_height = 200;
1597         prof->m_frame_rate_num = 15;
1598         prof->m_frame_rate_den = 1;
1599         prof->m_display_aspect_num = 4;
1600         prof->m_display_aspect_den = 3;
1601         prof->m_sample_aspect_num = 1;
1602         prof->m_sample_aspect_den = 1;
1603         prof->m_progressive = true;
1604         prof->m_colorspace = 601;
1605         ProfileRepository::get()->saveProfile(prof.get(), dir.absoluteFilePath(QStringLiteral("video4linux")));
1606     }
1607     auto &prof = ProfileRepository::get()->getProfile(dir.absoluteFilePath(QStringLiteral("video4linux")));
1608     m_configCapture.p_size->setText(QString::number(prof->width()) + QLatin1Char('x') + QString::number(prof->height()));
1609     m_configCapture.p_fps->setText(QString::number(prof->frame_rate_num()) + QLatin1Char('/') + QString::number(prof->frame_rate_den()));
1610     m_configCapture.p_aspect->setText(QString::number(prof->sample_aspect_num()) + QLatin1Char('/') + QString::number(prof->sample_aspect_den()));
1611     m_configCapture.p_display->setText(QString::number(prof->display_aspect_num()) + QLatin1Char('/') + QString::number(prof->display_aspect_den()));
1612     m_configCapture.p_colorspace->setText(ProfileRepository::getColorspaceDescription(prof->colorspace()));
1613     if (prof->progressive()) {
1614         m_configCapture.p_progressive->setText(i18n("Progressive"));
1615     }
1616 }
1617 
1618 void KdenliveSettingsDialog::saveCurrentV4lProfile()
1619 {
1620     std::unique_ptr<ProfileParam> profile(new ProfileParam(pCore->getCurrentProfile().get()));
1621     profile->m_description = QStringLiteral("Video4Linux capture");
1622     profile->m_colorspace = ProfileRepository::getColorspaceFromDescription(m_configCapture.p_colorspace->text());
1623     profile->m_width = m_configCapture.p_size->text().section('x', 0, 0).toInt();
1624     profile->m_height = m_configCapture.p_size->text().section('x', 1, 1).toInt();
1625     profile->m_sample_aspect_num = m_configCapture.p_aspect->text().section(QLatin1Char('/'), 0, 0).toInt();
1626     profile->m_sample_aspect_den = m_configCapture.p_aspect->text().section(QLatin1Char('/'), 1, 1).toInt();
1627     profile->m_display_aspect_num = m_configCapture.p_display->text().section(QLatin1Char('/'), 0, 0).toInt();
1628     profile->m_display_aspect_den = m_configCapture.p_display->text().section(QLatin1Char('/'), 1, 1).toInt();
1629     profile->m_frame_rate_num = m_configCapture.p_fps->text().section(QLatin1Char('/'), 0, 0).toInt();
1630     profile->m_frame_rate_den = m_configCapture.p_fps->text().section(QLatin1Char('/'), 1, 1).toInt();
1631     profile->m_progressive = m_configCapture.p_progressive->text() == i18n("Progressive");
1632     QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/"));
1633     if (!dir.exists()) {
1634         dir.mkpath(QStringLiteral("."));
1635     }
1636     ProfileRepository::get()->saveProfile(profile.get(), dir.absoluteFilePath(QStringLiteral("video4linux")));
1637 }
1638 
1639 void KdenliveSettingsDialog::loadExternalProxyProfiles()
1640 {
1641     // load proxy profiles
1642     KConfig conf(QStringLiteral("externalproxies.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
1643     KConfigGroup group(&conf, "proxy");
1644     QMap<QString, QString> values = group.entryMap();
1645     QMapIterator<QString, QString> k(values);
1646     QString currentItem = KdenliveSettings::externalProxyProfile();
1647     m_configProxy.kcfg_external_proxy_profile->blockSignals(true);
1648     m_configProxy.kcfg_external_proxy_profile->clear();
1649     while (k.hasNext()) {
1650         k.next();
1651         if (!k.key().isEmpty()) {
1652             if (k.value().contains(QLatin1Char(';'))) {
1653                 m_configProxy.kcfg_external_proxy_profile->addItem(k.key(), k.value());
1654             }
1655         }
1656     }
1657     if (!currentItem.isEmpty()) {
1658         m_configProxy.kcfg_external_proxy_profile->setCurrentIndex(m_configProxy.kcfg_external_proxy_profile->findText(currentItem));
1659     }
1660     m_configProxy.kcfg_external_proxy_profile->blockSignals(false);
1661 }
1662 
1663 void KdenliveSettingsDialog::slotEditVideo4LinuxProfile()
1664 {
1665     QString vl4ProfilePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/video4linux");
1666     QPointer<ProfilesDialog> w = new ProfilesDialog(vl4ProfilePath, true);
1667     if (w->exec() == QDialog::Accepted) {
1668         // save and update profile
1669         loadCurrentV4lProfileInfo();
1670     }
1671     delete w;
1672 }
1673 
1674 void KdenliveSettingsDialog::slotReloadBlackMagic()
1675 {
1676     getBlackMagicDeviceList(m_configCapture.kcfg_decklink_capturedevice, true);
1677     if (!getBlackMagicOutputDeviceList(m_configSdl.kcfg_blackmagic_output_device, true)) {
1678         // No blackmagic card found
1679         m_configSdl.kcfg_external_display->setEnabled(false);
1680     }
1681     m_configSdl.kcfg_external_display->setEnabled(KdenliveSettings::decklink_device_found());
1682 }
1683 
1684 void KdenliveSettingsDialog::checkProfile()
1685 {
1686     m_pw->loadProfile(KdenliveSettings::default_profile().isEmpty() ? pCore->getCurrentProfile()->path() : KdenliveSettings::default_profile());
1687 }
1688 
1689 void KdenliveSettingsDialog::slotReloadShuttleDevices()
1690 {
1691 #ifdef USE_JOGSHUTTLE
1692     QString devDirStr = QStringLiteral("/dev/input/by-id");
1693     QDir devDir(devDirStr);
1694     if (!devDir.exists()) {
1695         devDirStr = QStringLiteral("/dev/input");
1696     }
1697 
1698     QStringList devNamesList;
1699     QStringList devPathList;
1700     m_configShuttle.shuttledevicelist->clear();
1701 
1702     DeviceMap devMap = JogShuttle::enumerateDevices(devDirStr);
1703     DeviceMapIter iter = devMap.begin();
1704     while (iter != devMap.end()) {
1705         m_configShuttle.shuttledevicelist->addItem(iter.key(), iter.value());
1706         devNamesList << iter.key();
1707         devPathList << iter.value();
1708         ++iter;
1709     }
1710     KdenliveSettings::setShuttledevicenames(devNamesList);
1711     KdenliveSettings::setShuttledevicepaths(devPathList);
1712     QTimer::singleShot(200, this, [&]() { slotUpdateShuttleDevice(); });
1713 #endif // USE_JOGSHUTTLE
1714 }
1715 
1716 void KdenliveSettingsDialog::initSpeechPage()
1717 {
1718     m_stt = new SpeechToText(SpeechToText::EngineType::EngineVosk, this);
1719     m_sttWhisper = new SpeechToText(SpeechToText::EngineType::EngineWhisper, this);
1720     // Python env info label
1721     PythonDependencyMessage *pythonEnvLabel = new PythonDependencyMessage(this, m_sttWhisper, true);
1722     m_configEnv.message_layout_2->addWidget(pythonEnvLabel);
1723     // Also show VOSK setup messages in the python env page
1724     connect(m_stt, &AbstractPythonInterface::setupMessage,
1725             [pythonEnvLabel](const QString message, int type) { pythonEnvLabel->doShowMessage(message, KMessageWidget::MessageType(type)); });
1726     connect(m_stt, &AbstractPythonInterface::venvSetupChanged, this, [this]() {
1727         QSignalBlocker bk(m_configEnv.kcfg_usePythonVenv);
1728         m_configEnv.kcfg_usePythonVenv->setChecked(KdenliveSettings::usePythonVenv());
1729     });
1730     connect(m_sttWhisper, &AbstractPythonInterface::venvSetupChanged, this, [this]() {
1731         QSignalBlocker bk(m_configEnv.kcfg_usePythonVenv);
1732         m_configEnv.kcfg_usePythonVenv->setChecked(KdenliveSettings::usePythonVenv());
1733     });
1734     m_sttWhisper->checkPython(KdenliveSettings::usePythonVenv(), true);
1735     QButtonGroup *speechEngineSelection = new QButtonGroup(this);
1736     speechEngineSelection->addButton(m_configSpeech.engine_vosk);
1737     speechEngineSelection->addButton(m_configSpeech.engine_whisper);
1738     connect(speechEngineSelection, &QButtonGroup::buttonClicked, [this](QAbstractButton *button) {
1739         if (button == m_configSpeech.engine_vosk) {
1740             m_configSpeech.speech_stack->setCurrentIndex(0);
1741             m_stt->checkDependencies(false);
1742         } else if (button == m_configSpeech.engine_whisper) {
1743             m_configSpeech.speech_stack->setCurrentIndex(1);
1744             m_sttWhisper->checkDependencies(false);
1745         }
1746     });
1747     if (KdenliveSettings::speechEngine() == QLatin1String("whisper")) {
1748         m_configSpeech.engine_whisper->setChecked(true);
1749         m_configSpeech.speech_stack->setCurrentIndex(1);
1750     } else {
1751         m_configSpeech.engine_vosk->setChecked(true);
1752         m_configSpeech.speech_stack->setCurrentIndex(0);
1753     }
1754     // Python setup
1755     m_configEnv.pythonSetupMessage->hide();
1756     connect(m_sttWhisper, &SpeechToText::gotPythonSize, m_configEnv.label_python_size, &QLabel::setText);
1757     connect(m_configEnv.kcfg_usePythonVenv, &QCheckBox::stateChanged, this, [this](int state) {
1758         if (m_sttWhisper->installInProcess()) {
1759             return;
1760         }
1761         m_configEnv.pythonSetupMessage->setMessageType(KMessageWidget::Information);
1762         if (state == Qt::Checked) {
1763             m_configEnv.pythonSetupMessage->setText(i18n("Setting up virtual environment…"));
1764         } else {
1765             m_configEnv.pythonSetupMessage->setText(i18n("Disabling virtual environment"));
1766         }
1767         m_configEnv.pythonSetupMessage->show();
1768         qApp->processEvents();
1769         bool updatePython = m_sttWhisper->checkPython(state == Qt::Checked, true);
1770         if (!updatePython) {
1771             // Setting up venv failed
1772             m_configEnv.kcfg_usePythonVenv->setChecked(false);
1773         } else {
1774             // Venv setting changed, refresh all speech interfaces
1775             m_stt->checkDependencies(true);
1776             m_sttWhisper->checkDependencies(true);
1777         }
1778         m_configEnv.pythonSetupMessage->hide();
1779     });
1780     connect(m_configEnv.button_python_delete, &QPushButton::clicked, this, [this]() {
1781         m_configEnv.pythonSetupMessage->setMessageType(KMessageWidget::Information);
1782         m_configEnv.pythonSetupMessage->setText(i18nc("@label:textbox", "Removing the virtual environment folder."));
1783         m_configEnv.pythonSetupMessage->show();
1784         if (m_sttWhisper->removePythonVenv()) {
1785             m_configEnv.kcfg_usePythonVenv->setChecked(false);
1786             m_configEnv.pythonSetupMessage->hide();
1787             m_configEnv.label_python_size->setText(i18nc("@label:textbox", "No python venv found"));
1788         } else {
1789             m_configEnv.pythonSetupMessage->setMessageType(KMessageWidget::Warning);
1790             m_configEnv.pythonSetupMessage->setText(i18nc("@label:textbox", "Failed to remove the virtual environment folder."));
1791         }
1792     });
1793 
1794     // Whisper
1795     m_configSpeech.combo_wr_device->addItem(i18n("Probing..."));
1796     PythonDependencyMessage *msgWhisper = new PythonDependencyMessage(this, m_sttWhisper);
1797     m_configSpeech.message_layout_wr->addWidget(msgWhisper);
1798     QList<std::pair<QString, QString>> whisperModels = m_sttWhisper->whisperModels();
1799     for (auto &w : whisperModels) {
1800         m_configSpeech.combo_wr_model->addItem(w.first, w.second);
1801     }
1802     int ix = m_configSpeech.combo_wr_model->findData(KdenliveSettings::whisperModel());
1803     if (ix > -1) {
1804         m_configSpeech.combo_wr_model->setCurrentIndex(ix);
1805     }
1806     QMap<QString, QString> whisperLanguages = m_sttWhisper->whisperLanguages();
1807     QMapIterator<QString, QString> j(whisperLanguages);
1808     while (j.hasNext()) {
1809         j.next();
1810         m_configSpeech.combo_wr_lang->addItem(j.key(), j.value());
1811     }
1812     ix = m_configSpeech.combo_wr_lang->findData(KdenliveSettings::whisperLanguage());
1813     if (ix > -1) {
1814         m_configSpeech.combo_wr_lang->setCurrentIndex(ix);
1815     }
1816     m_configSpeech.script_log->hide();
1817     connect(m_sttWhisper, &SpeechToText::scriptStarted, [this]() { QMetaObject::invokeMethod(m_configSpeech.script_log, "clear"); });
1818     connect(m_sttWhisper, &SpeechToText::installFeedback, [this](const QString jobData) {
1819         QMetaObject::invokeMethod(m_configSpeech.script_log, "show", Qt::QueuedConnection);
1820         QMetaObject::invokeMethod(m_configSpeech.script_log, "appendPlainText", Q_ARG(QString, jobData));
1821     });
1822     connect(m_sttWhisper, &SpeechToText::scriptFinished, [msgWhisper]() { QMetaObject::invokeMethod(msgWhisper, "checkAfterInstall", Qt::QueuedConnection); });
1823 
1824     connect(m_sttWhisper, &SpeechToText::scriptFeedback, [this](const QStringList jobData) {
1825         m_configSpeech.combo_wr_device->clear();
1826         for (auto &s : jobData) {
1827             if (s.contains(QLatin1Char('#'))) {
1828                 m_configSpeech.combo_wr_device->addItem(s.section(QLatin1Char('#'), 1).simplified(), s.section(QLatin1Char('#'), 0, 0).simplified());
1829             } else {
1830                 m_configSpeech.combo_wr_device->addItem(s.simplified(), s.simplified());
1831             }
1832         }
1833     });
1834     connect(m_sttWhisper, &SpeechToText::scriptGpuCheckFinished, [this]() {
1835         int ix = m_configSpeech.combo_wr_device->findData(KdenliveSettings::whisperDevice());
1836         if (ix > -1) {
1837             m_configSpeech.combo_wr_device->setCurrentIndex(ix);
1838         }
1839     });
1840     connect(m_sttWhisper, &SpeechToText::dependenciesAvailable, this, [&]() { m_sttWhisper->runConcurrentScript(QStringLiteral("checkgpu.py"), {}); });
1841 
1842     // VOSK
1843     m_configSpeech.vosk_folder->setPlaceholderText(
1844         QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("speechmodels"), QStandardPaths::LocateDirectory));
1845     PythonDependencyMessage *msgVosk = new PythonDependencyMessage(this, m_stt);
1846     m_configSpeech.message_layout->addWidget(msgVosk);
1847 
1848     connect(m_stt, &SpeechToText::dependenciesAvailable, this, [&]() {
1849         if (m_speechListWidget->count() == 0) {
1850             doShowSpeechMessage(i18n("Please add a speech model."), KMessageWidget::Information);
1851         }
1852     });
1853     connect(m_stt, &SpeechToText::dependenciesMissing, this, [&](const QStringList &) { m_configSpeech.speech_info->animatedHide(); });
1854     connect(m_stt, &SpeechToText::scriptStarted, [this]() { QMetaObject::invokeMethod(m_configSpeech.script_log, "clear"); });
1855     connect(m_stt, &SpeechToText::installFeedback, [this](const QString jobData) {
1856         QMetaObject::invokeMethod(m_configSpeech.script_log, "show", Qt::QueuedConnection);
1857         QMetaObject::invokeMethod(m_configSpeech.script_log, "appendPlainText", Q_ARG(QString, jobData));
1858     });
1859     connect(m_stt, &SpeechToText::scriptFinished, [msgVosk]() { QMetaObject::invokeMethod(msgVosk, "checkAfterInstall", Qt::QueuedConnection); });
1860 
1861     m_speechListWidget = new SpeechList(this);
1862     connect(m_speechListWidget, &SpeechList::getDictionary, this, &KdenliveSettingsDialog::getDictionary);
1863     QVBoxLayout *l = new QVBoxLayout(m_configSpeech.list_frame);
1864     l->setContentsMargins(0, 0, 0, 0);
1865     l->addWidget(m_speechListWidget);
1866     m_configSpeech.speech_info->setWordWrap(true);
1867     connect(m_configSpeech.check_config, &QPushButton::clicked, this, &KdenliveSettingsDialog::slotCheckSttConfig);
1868 
1869     connect(m_configSpeech.custom_vosk_folder, &QCheckBox::stateChanged, this, [this](int state) {
1870         m_configSpeech.vosk_folder->setEnabled(state != Qt::Unchecked);
1871         if (state == Qt::Unchecked) {
1872             // Clear custom folder
1873             m_configSpeech.vosk_folder->clear();
1874             KdenliveSettings::setVosk_folder_path(QString());
1875             slotParseVoskDictionaries();
1876         }
1877     });
1878     connect(m_configSpeech.vosk_folder, &KUrlRequester::urlSelected, this, [this](const QUrl &url) {
1879         KdenliveSettings::setVosk_folder_path(url.toLocalFile());
1880         slotParseVoskDictionaries();
1881     });
1882     m_configSpeech.models_url->setText(
1883         i18n("Download speech models from: <a href=\"https://alphacephei.com/vosk/models\">https://alphacephei.com/vosk/models</a>"));
1884     connect(m_configSpeech.models_url, &QLabel::linkActivated, this, [&](const QString &contents) {
1885         qDebug() << "=== LINK CLICKED: " << contents;
1886         auto *job = new KIO::OpenUrlJob(QUrl(contents));
1887 #if KIO_VERSION >= QT_VERSION_CHECK(5, 98, 0)
1888         job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
1889 #else
1890         job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
1891 #endif
1892         // methods like setRunExecutables, setSuggestedFilename, setEnableExternalBrowser, setFollowRedirections
1893         // exist in both classes
1894         job->start();
1895     });
1896     connect(m_configSpeech.button_add, &QToolButton::clicked, this, [this]() { this->getDictionary(); });
1897     connect(m_configSpeech.button_delete, &QToolButton::clicked, this, &KdenliveSettingsDialog::removeDictionary);
1898     connect(this, &KdenliveSettingsDialog::parseDictionaries, this, &KdenliveSettingsDialog::slotParseVoskDictionaries);
1899     slotParseVoskDictionaries();
1900 }
1901 
1902 void KdenliveSettingsDialog::slotCheckSttConfig()
1903 {
1904     m_configSpeech.check_config->setEnabled(false);
1905     qApp->processEvents();
1906     if (m_configSpeech.engine_vosk->isChecked()) {
1907         m_stt->checkDependencies(true);
1908     } else {
1909         m_sttWhisper->checkDependencies(true);
1910     }
1911     // Leave button disabled for 3 seconds so that the user doesn't trigger it again while it is processing
1912     QTimer::singleShot(3000, this, [&]() { m_configSpeech.check_config->setEnabled(true); });
1913 }
1914 
1915 void KdenliveSettingsDialog::doShowSpeechMessage(const QString &message, int messageType)
1916 {
1917     m_configSpeech.speech_info->setMessageType(static_cast<KMessageWidget::MessageType>(messageType));
1918     m_configSpeech.speech_info->setText(message);
1919     m_configSpeech.speech_info->animatedShow();
1920 }
1921 
1922 void KdenliveSettingsDialog::getDictionary(const QUrl &sourceUrl)
1923 {
1924     QUrl url = KUrlRequesterDialog::getUrl(sourceUrl, this, i18n("Enter url for the new dictionary"));
1925     if (url.isEmpty()) {
1926         return;
1927     }
1928     if (!url.isLocalFile()) {
1929         KIO::FileCopyJob *copyjob = KIO::file_copy(url, QUrl::fromLocalFile(QDir::temp().absoluteFilePath(url.fileName())));
1930         doShowSpeechMessage(i18n("Downloading model…"), KMessageWidget::Information);
1931         connect(copyjob, &KIO::FileCopyJob::result, this, &KdenliveSettingsDialog::downloadModelFinished);
1932     } else {
1933         processArchive(url.toLocalFile());
1934     }
1935 }
1936 
1937 void KdenliveSettingsDialog::removeDictionary()
1938 {
1939     if (!KdenliveSettings::vosk_folder_path().isEmpty()) {
1940         doShowSpeechMessage(i18n("We do not allow deleting custom folder models, please do it manually."), KMessageWidget::Warning);
1941         return;
1942     }
1943     QString modelDirectory = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
1944     QDir dir(modelDirectory);
1945     if (!dir.cd(QStringLiteral("speechmodels"))) {
1946         doShowSpeechMessage(i18n("Cannot access dictionary folder."), KMessageWidget::Warning);
1947         return;
1948     }
1949 
1950     if (!m_speechListWidget->currentItem()) {
1951         return;
1952     }
1953     QString currentModel = m_speechListWidget->currentItem()->text();
1954     if (!currentModel.isEmpty() && dir.cd(currentModel)) {
1955         if (KMessageBox::questionTwoActions(this, i18n("Delete folder:\n%1", dir.absolutePath()), {}, KStandardGuiItem::del(), KStandardGuiItem::cancel()) ==
1956             KMessageBox::PrimaryAction) {
1957             // Make sure we don't accidentally delete a folder that is not ours
1958             if (dir.absolutePath().contains(QLatin1String("speechmodels"))) {
1959                 dir.removeRecursively();
1960                 slotParseVoskDictionaries();
1961             }
1962         }
1963     }
1964 }
1965 
1966 void KdenliveSettingsDialog::downloadModelFinished(KJob *job)
1967 {
1968     qDebug() << "=== DOWNLOAD FINISHED!!";
1969     if (job->error() == 0 || job->error() == 112) {
1970         qDebug() << "=== NO ERROR ON DWNLD!!";
1971         auto *jb = static_cast<KIO::FileCopyJob *>(job);
1972         if (jb) {
1973             qDebug() << "=== JOB FOUND!!";
1974             QString archiveFile = jb->destUrl().toLocalFile();
1975             processArchive(archiveFile);
1976         } else {
1977             qDebug() << "=== JOB NOT FOUND!!";
1978             m_configSpeech.speech_info->setMessageType(KMessageWidget::Warning);
1979             m_configSpeech.speech_info->setText(i18n("Download error"));
1980         }
1981     } else {
1982         qDebug() << "=== GOT JOB ERROR: " << job->error();
1983         m_configSpeech.speech_info->setMessageType(KMessageWidget::Warning);
1984         m_configSpeech.speech_info->setText(i18n("Download error %1", job->errorString()));
1985     }
1986 }
1987 
1988 void KdenliveSettingsDialog::processArchive(const QString &archiveFile)
1989 {
1990     QMimeDatabase db;
1991     QMimeType type = db.mimeTypeForFile(archiveFile);
1992     std::unique_ptr<KArchive> archive;
1993     if (type.inherits(QStringLiteral("application/zip"))) {
1994         archive = std::make_unique<KZip>(archiveFile);
1995     } else {
1996         archive = std::make_unique<KTar>(archiveFile);
1997     }
1998     QString modelDirectory = KdenliveSettings::vosk_folder_path();
1999     QDir dir;
2000     if (modelDirectory.isEmpty()) {
2001         modelDirectory = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
2002         dir = QDir(modelDirectory);
2003         dir.mkdir(QStringLiteral("speechmodels"));
2004         if (!dir.cd(QStringLiteral("speechmodels"))) {
2005             doShowSpeechMessage(i18n("Cannot access dictionary folder."), KMessageWidget::Warning);
2006             return;
2007         }
2008     } else {
2009         dir = QDir(modelDirectory);
2010     }
2011     if (archive->open(QIODevice::ReadOnly)) {
2012         doShowSpeechMessage(i18n("Extracting archive…"), KMessageWidget::Information);
2013         const KArchiveDirectory *archiveDir = archive->directory();
2014         if (!archiveDir->copyTo(dir.absolutePath())) {
2015             qDebug() << "=== Error extracting archive!!";
2016         } else {
2017             QFile::remove(archiveFile);
2018             Q_EMIT parseDictionaries();
2019             doShowSpeechMessage(i18n("New dictionary installed."), KMessageWidget::Positive);
2020         }
2021     } else {
2022         // Test if it is a folder
2023         QDir testDir(archiveFile);
2024         if (testDir.exists()) {
2025         }
2026         qDebug() << "=== CANNOT OPEN ARCHIVE!!";
2027     }
2028 }
2029 
2030 void KdenliveSettingsDialog::slotParseVoskDictionaries()
2031 {
2032     m_speechListWidget->clear();
2033     QStringList final = m_stt->parseVoskDictionaries();
2034     m_speechListWidget->addItems(final);
2035     if (!KdenliveSettings::vosk_folder_path().isEmpty()) {
2036         m_configSpeech.custom_vosk_folder->setChecked(true);
2037         m_configSpeech.vosk_folder->setUrl(QUrl::fromLocalFile(KdenliveSettings::vosk_folder_path()));
2038     }
2039     if (!final.isEmpty() && m_stt->missingDependencies().isEmpty()) {
2040         m_configSpeech.speech_info->animatedHide();
2041     } else if (final.isEmpty()) {
2042         doShowSpeechMessage(i18n("Please add a speech model."), KMessageWidget::Information);
2043     }
2044 }
2045 
2046 void KdenliveSettingsDialog::showHelp()
2047 {
2048     pCore->window()->appHelpActivated();
2049 }
2050 
2051 void KdenliveSettingsDialog::checkSpeechDependencies()
2052 {
2053     m_sttWhisper->checkDependenciesConcurrently();
2054     m_stt->checkDependenciesConcurrently();
2055 }