1 /*
2     SPDX-FileCopyrightText: 2021 Jean-Baptiste Mardelle <jb@kdenlive.org>
3 
4 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5 */
6 
7 #include "kdenlivesettingsdialog.h"
8 #include "clipcreationdialog.h"
9 #include "core.h"
10 #include "dialogs/profilesdialog.h"
11 #include "encodingprofilesdialog.h"
12 #include "kdenlivesettings.h"
13 #include "mainwindow.h"
14 #include "timeline2/view/timelinewidget.h"
15 #include "timeline2/view/timelinecontroller.h"
16 #include "profiles/profilemodel.hpp"
17 #include "profiles/profilerepository.hpp"
18 #include "profilesdialog.h"
19 #include "project/dialogs/profilewidget.h"
20 #include "wizard.h"
21 #include "monitor/monitor.h"
22 #include "doc/kdenlivedoc.h"
23 
24 #ifdef USE_V4L
25 #include "capture/v4lcapture.h"
26 #endif
27 
28 #include "kdenlive_debug.h"
29 #include "klocalizedstring.h"
30 #include <KIO/DesktopExecParser>
31 #include <kio_version.h>
32 
33 #if KIO_VERSION > QT_VERSION_CHECK(5, 70, 0)
34 #include <KIO/OpenUrlJob>
35 #else
36 #include <KRun>
37 #endif
38 
39 #include <KUrlRequesterDialog>
40 #include <KArchive>
41 #include <KZip>
42 #include <KTar>
43 #include <KIO/FileCopyJob>
44 #include <KLineEdit>
45 #include <KMessageBox>
46 #include <KOpenWithDialog>
47 #include <KIO/JobUiDelegate>
48 #include <KArchiveDirectory>
49 #include <KService>
50 #include <QAction>
51 #include <QDir>
52 #include <QGuiApplication>
53 #include <QScreen>
54 #include <QSize>
55 #include <QThread>
56 #include <QTimer>
57 #include <QtConcurrent>
58 #include <QRegularExpression>
59 #include <cstdio>
60 #include <cstdlib>
61 #include <fcntl.h>
62 #include <unistd.h>
63 
64 #ifdef USE_JOGSHUTTLE
65 #include "jogshuttle/jogaction.h"
66 #include "jogshuttle/jogshuttleconfig.h"
67 #include <QStandardPaths>
68 #include <linux/input.h>
69 #include <memory>
70 #endif
71 
SpeechList(QWidget * parent)72 SpeechList::SpeechList(QWidget *parent)
73     : QListWidget(parent)
74 {
75     setAlternatingRowColors(true);
76     setAcceptDrops(true);
77     setDropIndicatorShown(true);
78     viewport()->setAcceptDrops(true);
79 }
80 
mimeTypes() const81 QStringList SpeechList::mimeTypes() const
82 {
83     return QStringList() << QStringLiteral("text/uri-list");
84 }
85 
dropEvent(QDropEvent * event)86 void SpeechList::dropEvent(QDropEvent *event)
87 {
88     const QMimeData *qMimeData = event->mimeData();
89     if (qMimeData->hasUrls()) {
90         QList<QUrl> urls = qMimeData->urls();
91         if (!urls.isEmpty()) {
92             emit getDictionary(urls.takeFirst());
93         }
94     }
95 }
96 
KdenliveSettingsDialog(QMap<QString,QString> mappable_actions,bool gpuAllowed,QWidget * parent)97 KdenliveSettingsDialog::KdenliveSettingsDialog(QMap<QString, QString> mappable_actions, bool gpuAllowed, QWidget *parent)
98     : KConfigDialog(parent, QStringLiteral("settings"), KdenliveSettings::self())
99     , m_modified(false)
100     , m_shuttleModified(false)
101     , m_voskUpdated(false)
102     , m_mappable_actions(std::move(mappable_actions))
103 {
104     KdenliveSettings::setV4l_format(0);
105     QWidget *p1 = new QWidget;
106     m_configMisc.setupUi(p1);
107     m_page1 = addPage(p1, i18n("Misc"));
108     m_page1->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
109 
110     m_configMisc.kcfg_use_exiftool->setEnabled(!QStandardPaths::findExecutable(QStringLiteral("exiftool")).isEmpty());
111 
112     QRegularExpression reg(R"((\+|-)?\d{2}:\d{2}:\d{2}(:||,)\d{2})");
113     QValidator *validator = new QRegularExpressionValidator(reg, this);
114     m_configMisc.kcfg_color_duration->setInputMask(pCore->timecode().mask());
115     m_configMisc.kcfg_color_duration->setValidator(validator);
116     m_configMisc.kcfg_title_duration->setInputMask(pCore->timecode().mask());
117     m_configMisc.kcfg_title_duration->setValidator(validator);
118     m_configMisc.kcfg_transition_duration->setInputMask(pCore->timecode().mask());
119     m_configMisc.kcfg_transition_duration->setValidator(validator);
120     m_configMisc.kcfg_mix_duration->setInputMask(pCore->timecode().mask());
121     m_configMisc.kcfg_mix_duration->setValidator(validator);
122     m_configMisc.kcfg_image_duration->setInputMask(pCore->timecode().mask());
123     m_configMisc.kcfg_image_duration->setValidator(validator);
124     m_configMisc.kcfg_sequence_duration->setInputMask(pCore->timecode().mask());
125     m_configMisc.kcfg_sequence_duration->setValidator(validator);
126     m_configMisc.kcfg_fade_duration->setInputMask(pCore->timecode().mask());
127     m_configMisc.kcfg_fade_duration->setValidator(validator);
128     m_configMisc.kcfg_subtitle_duration->setInputMask(pCore->timecode().mask());
129     m_configMisc.kcfg_subtitle_duration->setValidator(validator);
130 
131     if (!KdenliveSettings::preferredcomposite().isEmpty()) {
132         int ix = m_configMisc.preferredcomposite->findData(KdenliveSettings::preferredcomposite());
133         if (ix > -1) {
134             m_configMisc.preferredcomposite->setCurrentIndex(ix);
135         }
136     }
137     connect(m_configMisc.preferredcomposite, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,[&](){
138         if (m_configMisc.preferredcomposite->currentText() != KdenliveSettings::preferredcomposite()) {
139             KdenliveSettings::setPreferredcomposite(m_configMisc.preferredcomposite->currentText());
140             int mode = pCore->currentDoc()->getDocumentProperty(QStringLiteral("compositing")).toInt();
141             pCore->window()->getMainTimeline()->controller()->switchCompositing(mode);
142             pCore->currentDoc()->setModified();
143         }
144     });
145 
146     QWidget *p8 = new QWidget;
147     m_configProject.setupUi(p8);
148     m_page8 = addPage(p8, i18n("Project Defaults"));
149     auto *vbox = new QVBoxLayout;
150     m_pw = new ProfileWidget(this);
151     vbox->addWidget(m_pw);
152     m_configProject.profile_box->setLayout(vbox);
153     m_configProject.profile_box->setTitle(i18n("Select the default profile (preset)"));
154     // Select profile
155     m_pw->loadProfile(KdenliveSettings::default_profile().isEmpty() ? pCore->getCurrentProfile()->path() : KdenliveSettings::default_profile());
156     connect(m_pw, &ProfileWidget::profileChanged, this, &KdenliveSettingsDialog::slotDialogModified);
157     m_page8->setIcon(QIcon::fromTheme(QStringLiteral("project-defaults")));
158     m_configProject.projecturl->setMode(KFile::Directory);
159     m_configProject.projecturl->setUrl(QUrl::fromLocalFile(KdenliveSettings::defaultprojectfolder()));
160     connect(m_configProject.kcfg_customprojectfolder, &QCheckBox::stateChanged, this, [this](int state){
161         m_configProject.kcfg_sameprojectfolder->setEnabled(state == Qt::Unchecked);
162     });
163     connect(m_configProject.kcfg_sameprojectfolder, &QCheckBox::stateChanged, this, [this](int state){
164         m_configProject.kcfg_customprojectfolder->setEnabled(state == Qt::Unchecked);
165         m_configProject.projecturl->setEnabled(state == Qt::Unchecked);
166     });
167     connect(m_configProject.kcfg_videotracks, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [this]() {
168         if (m_configProject.kcfg_videotracks->value() + m_configProject.kcfg_audiotracks->value() <= 0) {
169             m_configProject.kcfg_videotracks->setValue(1);
170         }
171     });
172     connect(m_configProject.kcfg_audiotracks, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [this] () {
173         if (m_configProject.kcfg_videotracks->value() + m_configProject.kcfg_audiotracks->value() <= 0) {
174             m_configProject.kcfg_audiotracks->setValue(1);
175         }
176     });
177 
178     QWidget *p9 = new QWidget;
179     m_configProxy.setupUi(p9);
180     KPageWidgetItem *page9 = addPage(p9, i18n("Proxy Clips"));
181     page9->setIcon(QIcon::fromTheme(QStringLiteral("zoom-out")));
182     connect(m_configProxy.kcfg_generateproxy, &QAbstractButton::toggled, m_configProxy.kcfg_proxyminsize, &QWidget::setEnabled);
183     m_configProxy.kcfg_proxyminsize->setEnabled(KdenliveSettings::generateproxy());
184     connect(m_configProxy.kcfg_generateimageproxy, &QAbstractButton::toggled, m_configProxy.kcfg_proxyimageminsize, &QWidget::setEnabled);
185     m_configProxy.kcfg_proxyimageminsize->setEnabled(KdenliveSettings::generateimageproxy());
186     loadExternalProxyProfiles();
187 
188     QWidget *p3 = new QWidget;
189     m_configTimeline.setupUi(p3);
190     m_page3 = addPage(p3, i18n("Timeline"));
191     m_page3->setIcon(QIcon::fromTheme(QStringLiteral("video-display")));
192 
193     QWidget *p2 = new QWidget;
194     m_configEnv.setupUi(p2);
195     m_configEnv.mltpathurl->setMode(KFile::Directory);
196     m_configEnv.mltpathurl->lineEdit()->setObjectName(QStringLiteral("kcfg_mltpath"));
197     m_configEnv.rendererpathurl->lineEdit()->setObjectName(QStringLiteral("kcfg_rendererpath"));
198     m_configEnv.ffmpegurl->lineEdit()->setObjectName(QStringLiteral("kcfg_ffmpegpath"));
199     m_configEnv.ffplayurl->lineEdit()->setObjectName(QStringLiteral("kcfg_ffplaypath"));
200     m_configEnv.ffprobeurl->lineEdit()->setObjectName(QStringLiteral("kcfg_ffprobepath"));
201     m_configEnv.mediainfourl->lineEdit()->setObjectName(QStringLiteral("kcfg_mediainfopath"));
202     m_configEnv.tmppathurl->setMode(KFile::Directory);
203     m_configEnv.tmppathurl->lineEdit()->setObjectName(QStringLiteral("kcfg_currenttmpfolder"));
204     m_configEnv.capturefolderurl->setMode(KFile::Directory);
205     m_configEnv.capturefolderurl->lineEdit()->setObjectName(QStringLiteral("kcfg_capturefolder"));
206     m_configEnv.capturefolderurl->setEnabled(!KdenliveSettings::capturetoprojectfolder());
207     connect(m_configEnv.kcfg_capturetoprojectfolder, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotEnableCaptureFolder);
208     // Library folder
209     m_configEnv.libraryfolderurl->setMode(KFile::Directory);
210     m_configEnv.libraryfolderurl->lineEdit()->setObjectName(QStringLiteral("kcfg_libraryfolder"));
211     m_configEnv.libraryfolderurl->setEnabled(!KdenliveSettings::librarytodefaultfolder());
212     m_configEnv.libraryfolderurl->setPlaceholderText(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/library"));
213     m_configEnv.kcfg_librarytodefaultfolder->setToolTip(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/library"));
214     connect(m_configEnv.kcfg_librarytodefaultfolder, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotEnableLibraryFolder);
215 
216     m_configEnv.kcfg_proxythreads->setMaximum(qMax(1, QThread::idealThreadCount() - 1));
217 
218     // Script rendering folder
219     m_configEnv.videofolderurl->setMode(KFile::Directory);
220     m_configEnv.videofolderurl->lineEdit()->setObjectName(QStringLiteral("kcfg_videofolder"));
221     m_configEnv.videofolderurl->setEnabled(!KdenliveSettings::videotodefaultfolder());
222     m_configEnv.videofolderurl->setPlaceholderText(QStandardPaths::writableLocation(QStandardPaths::MoviesLocation));
223     m_configEnv.kcfg_videotodefaultfolder->setToolTip(QStandardPaths::writableLocation(QStandardPaths::MoviesLocation));
224     connect(m_configEnv.kcfg_videotodefaultfolder, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotEnableVideoFolder);
225 
226     // Mime types
227     QStringList mimes = ClipCreationDialog::getExtensions();
228     std::sort(mimes.begin(), mimes.end());
229     m_configEnv.supportedmimes->setPlainText(mimes.join(QLatin1Char(' ')));
230 
231     m_page2 = addPage(p2, i18n("Environment"));
232     m_page2->setIcon(QIcon::fromTheme(QStringLiteral("application-x-executable-script")));
233 
234     QWidget *p10 = new QWidget;
235     m_configColors.setupUi(p10);
236     m_page10 = addPage(p10, i18n("Colors"));
237     m_page10->setIcon(QIcon::fromTheme(QStringLiteral("color-management")));
238 
239     QWidget *p11 = new QWidget;
240     m_configSpeech.setupUi(p11);
241     m_page11 = addPage(p11, i18n("Speech To Text"));
242     m_page11->setIcon(QIcon::fromTheme(QStringLiteral("text-speak")));
243 
244     QWidget *p4 = new QWidget;
245     m_configCapture.setupUi(p4);
246     // Remove ffmpeg tab, unused
247     m_configCapture.tabWidget->removeTab(0);
248     m_configCapture.label->setVisible(false);
249     m_configCapture.kcfg_defaultcapture->setVisible(false);
250     //m_configCapture.tabWidget->removeTab(2);
251 #ifdef USE_V4L
252 
253     // Video 4 Linux device detection
254     for (int i = 0; i < 10; ++i) {
255         QString path = QStringLiteral("/dev/video") + QString::number(i);
256         if (QFile::exists(path)) {
257             QStringList deviceInfo = V4lCaptureHandler::getDeviceName(path);
258             if (!deviceInfo.isEmpty()) {
259                 m_configCapture.kcfg_detectedv4ldevices->addItem(deviceInfo.at(0), path);
260                 m_configCapture.kcfg_detectedv4ldevices->setItemData(m_configCapture.kcfg_detectedv4ldevices->count() - 1, deviceInfo.at(1), Qt::UserRole + 1);
261             }
262         }
263     }
264     connect(m_configCapture.kcfg_detectedv4ldevices, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
265             &KdenliveSettingsDialog::slotUpdatev4lDevice);
266     connect(m_configCapture.kcfg_v4l_format, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
267             &KdenliveSettingsDialog::slotUpdatev4lCaptureProfile);
268     connect(m_configCapture.config_v4l, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotEditVideo4LinuxProfile);
269 
270     slotUpdatev4lDevice();
271 #endif
272 
273     m_page4 = addPage(p4, i18n("Capture"));
274     m_page4->setIcon(QIcon::fromTheme(QStringLiteral("media-record")));
275     m_configCapture.tabWidget->setCurrentIndex(KdenliveSettings::defaultcapture());
276 #ifdef Q_WS_MAC
277     m_configCapture.tabWidget->setEnabled(false);
278     m_configCapture.kcfg_defaultcapture->setEnabled(false);
279     m_configCapture.label->setText(i18n("Capture is not yet available on Mac OS X."));
280 #endif
281 
282     QWidget *p5 = new QWidget;
283     m_configShuttle.setupUi(p5);
284 #ifdef USE_JOGSHUTTLE
285     m_configShuttle.toolBtnReload->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
286     connect(m_configShuttle.kcfg_enableshuttle, &QCheckBox::stateChanged, this, &KdenliveSettingsDialog::slotCheckShuttle);
287     connect(m_configShuttle.shuttledevicelist, SIGNAL(activated(int)), this, SLOT(slotUpdateShuttleDevice(int)));
288     connect(m_configShuttle.toolBtnReload, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotReloadShuttleDevices);
289 
290     slotCheckShuttle(static_cast<int>(KdenliveSettings::enableshuttle()));
291     m_configShuttle.shuttledisabled->hide();
292 
293     // Store the button pointers into an array for easier handling them in the other functions.
294     // TODO: impl enumerator or live with cut and paste :-)))
295     setupJogshuttleBtns(KdenliveSettings::shuttledevice());
296 #if 0
297     m_shuttle_buttons.push_back(m_configShuttle.shuttle1);
298     m_shuttle_buttons.push_back(m_configShuttle.shuttle2);
299     m_shuttle_buttons.push_back(m_configShuttle.shuttle3);
300     m_shuttle_buttons.push_back(m_configShuttle.shuttle4);
301     m_shuttle_buttons.push_back(m_configShuttle.shuttle5);
302     m_shuttle_buttons.push_back(m_configShuttle.shuttle6);
303     m_shuttle_buttons.push_back(m_configShuttle.shuttle7);
304     m_shuttle_buttons.push_back(m_configShuttle.shuttle8);
305     m_shuttle_buttons.push_back(m_configShuttle.shuttle9);
306     m_shuttle_buttons.push_back(m_configShuttle.shuttle10);
307     m_shuttle_buttons.push_back(m_configShuttle.shuttle11);
308     m_shuttle_buttons.push_back(m_configShuttle.shuttle12);
309     m_shuttle_buttons.push_back(m_configShuttle.shuttle13);
310     m_shuttle_buttons.push_back(m_configShuttle.shuttle14);
311     m_shuttle_buttons.push_back(m_configShuttle.shuttle15);
312 #endif
313 
314 #else  /* ! USE_JOGSHUTTLE */
315     m_configShuttle.kcfg_enableshuttle->hide();
316     m_configShuttle.kcfg_enableshuttle->setDisabled(true);
317 #endif /* USE_JOGSHUTTLE */
318     m_page5 = addPage(p5, i18n("JogShuttle"));
319     m_page5->setIcon(QIcon::fromTheme(QStringLiteral("dialog-input-devices")));
320 
321     QWidget *p6 = new QWidget;
322     m_configSdl.setupUi(p6);
323     m_configSdl.reload_blackmagic->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
324     connect(m_configSdl.reload_blackmagic, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotReloadBlackMagic);
325 
326     // m_configSdl.kcfg_openglmonitors->setHidden(true);
327 
328     m_page6 = addPage(p6, i18n("Playback"));
329     m_page6->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
330 
331     QWidget *p7 = new QWidget;
332     m_configTranscode.setupUi(p7);
333     m_page7 = addPage(p7, i18n("Transcode"));
334     m_page7->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
335 
336     connect(m_configTranscode.button_add, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotAddTranscode);
337     connect(m_configTranscode.button_delete, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotDeleteTranscode);
338     connect(m_configTranscode.profiles_list, &QListWidget::itemChanged, this, &KdenliveSettingsDialog::slotDialogModified);
339     connect(m_configTranscode.profiles_list, &QListWidget::currentRowChanged, this, &KdenliveSettingsDialog::slotSetTranscodeProfile);
340     connect(m_configTranscode.profile_description, &QLineEdit::textChanged, this, &KdenliveSettingsDialog::slotEnableTranscodeUpdate);
341     connect(m_configTranscode.profile_extension, &QLineEdit::textChanged, this, &KdenliveSettingsDialog::slotEnableTranscodeUpdate);
342     connect(m_configTranscode.profile_parameters, &QPlainTextEdit::textChanged, this, &KdenliveSettingsDialog::slotEnableTranscodeUpdate);
343     connect(m_configTranscode.profile_audioonly, &QCheckBox::stateChanged, this, &KdenliveSettingsDialog::slotEnableTranscodeUpdate);
344 
345     connect(m_configTranscode.button_update, &QAbstractButton::pressed, this, &KdenliveSettingsDialog::slotUpdateTranscodingProfile);
346 
347     m_configTranscode.profile_parameters->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 5);
348 
349     connect(m_configEnv.kp_image, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotEditImageApplication);
350     connect(m_configEnv.kp_audio, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotEditAudioApplication);
351 
352     loadEncodingProfiles();
353 
354     connect(m_configSdl.fullscreen_monitor, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
355             &KdenliveSettingsDialog::slotSetFullscreenMonitor);
356     connect(m_configSdl.kcfg_audio_driver, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
357             &KdenliveSettingsDialog::slotCheckAlsaDriver);
358     connect(m_configSdl.kcfg_audio_backend, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
359             &KdenliveSettingsDialog::slotCheckAudioBackend);
360     initDevices();
361     connect(m_configCapture.kcfg_grab_capture_type, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
362             &KdenliveSettingsDialog::slotUpdateGrabRegionStatus);
363 
364     slotUpdateGrabRegionStatus();
365     loadTranscodeProfiles();
366 
367     // decklink profile
368     QAction *act = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure profiles"), this);
369     act->setData(4);
370     connect(act, &QAction::triggered, this, &KdenliveSettingsDialog::slotManageEncodingProfile);
371     m_configCapture.decklink_manageprofile->setDefaultAction(act);
372     m_configCapture.decklink_showprofileinfo->setIcon(QIcon::fromTheme(QStringLiteral("help-about")));
373     m_configCapture.decklink_parameters->setVisible(false);
374     m_configCapture.decklink_parameters->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 4);
375     m_configCapture.decklink_parameters->setPlainText(KdenliveSettings::decklink_parameters());
376     connect(m_configCapture.kcfg_decklink_profile, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
377             &KdenliveSettingsDialog::slotUpdateDecklinkProfile);
378     connect(m_configCapture.decklink_showprofileinfo, &QAbstractButton::clicked, m_configCapture.decklink_parameters, &QWidget::setVisible);
379 
380     // ffmpeg profile
381     m_configCapture.v4l_showprofileinfo->setIcon(QIcon::fromTheme(QStringLiteral("help-about")));
382     m_configCapture.v4l_parameters->setVisible(false);
383     m_configCapture.v4l_parameters->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 4);
384     m_configCapture.v4l_parameters->setPlainText(KdenliveSettings::v4l_parameters());
385 
386     act = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure profiles"), this);
387     act->setData(2);
388     connect(act, &QAction::triggered, this, &KdenliveSettingsDialog::slotManageEncodingProfile);
389     m_configCapture.v4l_manageprofile->setDefaultAction(act);
390     connect(m_configCapture.kcfg_v4l_profile, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
391             &KdenliveSettingsDialog::slotUpdateV4lProfile);
392     connect(m_configCapture.v4l_showprofileinfo, &QAbstractButton::clicked, m_configCapture.v4l_parameters, &QWidget::setVisible);
393 
394     // screen grab profile
395     m_configCapture.grab_showprofileinfo->setIcon(QIcon::fromTheme(QStringLiteral("help-about")));
396     m_configCapture.grab_parameters->setVisible(false);
397     m_configCapture.grab_parameters->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 4);
398     m_configCapture.grab_parameters->setPlainText(KdenliveSettings::grab_parameters());
399     act = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure profiles"), this);
400     act->setData(3);
401     connect(act, &QAction::triggered, this, &KdenliveSettingsDialog::slotManageEncodingProfile);
402     m_configCapture.grab_manageprofile->setDefaultAction(act);
403     connect(m_configCapture.kcfg_grab_profile, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
404             &KdenliveSettingsDialog::slotUpdateGrabProfile);
405     connect(m_configCapture.grab_showprofileinfo, &QAbstractButton::clicked, m_configCapture.grab_parameters, &QWidget::setVisible);
406 
407     // audio capture channels
408     m_configCapture.audiocapturechannels->clear();
409     m_configCapture.audiocapturechannels->addItem(i18n("Mono (1 channel)"), 1);
410     m_configCapture.audiocapturechannels->addItem(i18n("Stereo (2 channels)"), 2);
411 
412     int channelsIndex = m_configCapture.audiocapturechannels->findData(KdenliveSettings::audiocapturechannels());
413     m_configCapture.audiocapturechannels->setCurrentIndex(qMax(channelsIndex, 0));
414     connect(m_configCapture.audiocapturechannels, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
415             &KdenliveSettingsDialog::slotUpdateAudioCaptureChannels);
416 
417     // audio capture sample rate
418     m_configCapture.audiocapturesamplerate->clear();
419     m_configCapture.audiocapturesamplerate->addItem(i18n("44100 Hz"), 44100);
420     m_configCapture.audiocapturesamplerate->addItem(i18n("48000 Hz"), 48000);
421 
422     int sampleRateIndex = m_configCapture.audiocapturesamplerate->findData(KdenliveSettings::audiocapturesamplerate());
423     m_configCapture.audiocapturesamplerate->setCurrentIndex(qMax(sampleRateIndex, 0));
424     connect(m_configCapture.audiocapturesamplerate, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
425             &KdenliveSettingsDialog::slotUpdateAudioCaptureSampleRate);
426 
427     m_configCapture.labelNoAudioDevices->setVisible(false);
428 
429     // Timeline preview
430     act = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure profiles"), this);
431     act->setData(1);
432     connect(act, &QAction::triggered, this, &KdenliveSettingsDialog::slotManageEncodingProfile);
433     m_configProject.preview_manageprofile->setDefaultAction(act);
434     connect(m_configProject.kcfg_preview_profile, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
435             &KdenliveSettingsDialog::slotUpdatePreviewProfile);
436     connect(m_configProject.preview_showprofileinfo, &QAbstractButton::clicked, m_configProject.previewparams, &QWidget::setVisible);
437     m_configProject.previewparams->setVisible(false);
438     m_configProject.previewparams->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 3);
439     m_configProject.previewparams->setPlainText(KdenliveSettings::previewparams());
440     m_configProject.preview_showprofileinfo->setIcon(QIcon::fromTheme(QStringLiteral("help-about")));
441     m_configProject.preview_showprofileinfo->setToolTip(i18n("Show default timeline preview parameters"));
442     m_configProject.preview_manageprofile->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
443     m_configProject.preview_manageprofile->setToolTip(i18n("Manage timeline preview profiles"));
444     m_configProject.kcfg_preview_profile->setToolTip(i18n("Select default timeline preview profile"));
445 
446     // proxy profile stuff
447     m_configProxy.proxy_showprofileinfo->setIcon(QIcon::fromTheme(QStringLiteral("help-about")));
448     m_configProxy.proxy_showprofileinfo->setToolTip(i18n("Show default profile parameters"));
449     m_configProxy.proxy_manageprofile->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
450     m_configProxy.proxy_manageprofile->setToolTip(i18n("Manage proxy profiles"));
451     m_configProxy.kcfg_proxy_profile->setToolTip(i18n("Select default proxy profile"));
452     m_configProxy.proxyparams->setVisible(false);
453     m_configProxy.proxyparams->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 3);
454     m_configProxy.proxyparams->setPlainText(KdenliveSettings::proxyparams());
455 
456     act = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure profiles"), this);
457     act->setData(0);
458     connect(act, &QAction::triggered, this, &KdenliveSettingsDialog::slotManageEncodingProfile);
459     m_configProxy.proxy_manageprofile->setDefaultAction(act);
460 
461     connect(m_configProxy.proxy_showprofileinfo, &QAbstractButton::clicked, m_configProxy.proxyparams, &QWidget::setVisible);
462     connect(m_configProxy.kcfg_proxy_profile, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
463             &KdenliveSettingsDialog::slotUpdateProxyProfile);
464 
465     slotUpdateProxyProfile(-1);
466     slotUpdateV4lProfile(-1);
467     slotUpdateGrabProfile(-1);
468     slotUpdateDecklinkProfile(-1);
469 
470     // enable GPU accel only if Movit is found
471     m_configSdl.kcfg_gpu_accel->setEnabled(gpuAllowed);
472     m_configSdl.kcfg_gpu_accel->setToolTip(i18n("GPU processing needs MLT compiled with Movit and Rtaudio modules"));
473 
474     getBlackMagicDeviceList(m_configCapture.kcfg_decklink_capturedevice);
475     if (!getBlackMagicOutputDeviceList(m_configSdl.kcfg_blackmagic_output_device)) {
476         // No blackmagic card found
477         m_configSdl.kcfg_external_display->setEnabled(false);
478     }
479 
480     initAudioRecDevice();
481     initSpeechPage();
482 
483     // Config dialog size
484     KSharedConfigPtr config = KSharedConfig::openConfig();
485     KConfigGroup settingsGroup(config, "settings");
486     QSize optimalSize;
487 
488     if (!settingsGroup.exists() || !settingsGroup.hasKey("dialogSize")) {
489         const QSize screenSize = (QGuiApplication::primaryScreen()->availableSize() * 0.9);
490         const QSize targetSize = QSize(1024, 700);
491         optimalSize = targetSize.boundedTo(screenSize);
492     } else {
493         optimalSize = settingsGroup.readEntry("dialogSize", QVariant(size())).toSize();
494     }
495     resize(optimalSize);
496 }
497 
498 // static
getBlackMagicDeviceList(QComboBox * devicelist,bool force)499 bool KdenliveSettingsDialog::getBlackMagicDeviceList(QComboBox *devicelist, bool force)
500 {
501     if (!force && !KdenliveSettings::decklink_device_found()) {
502         return false;
503     }
504     Mlt::Profile profile;
505     Mlt::Producer bm(profile, "decklink");
506     int found_devices = 0;
507     if (bm.is_valid()) {
508         bm.set("list_devices", 1);
509         found_devices = bm.get_int("devices");
510     } else {
511         KdenliveSettings::setDecklink_device_found(false);
512     }
513     if (found_devices <= 0) {
514         devicelist->setEnabled(false);
515         return false;
516     }
517     KdenliveSettings::setDecklink_device_found(true);
518     for (int i = 0; i < found_devices; ++i) {
519         char *tmp = qstrdup(QStringLiteral("device.%1").arg(i).toUtf8().constData());
520         devicelist->addItem(bm.get(tmp));
521         delete[] tmp;
522     }
523     return true;
524 }
525 
526 // static
initAudioRecDevice()527 bool KdenliveSettingsDialog::initAudioRecDevice()
528 {
529     QStringList audioDevices = pCore->getAudioCaptureDevices();
530 
531     //show a hint to the user to know what to check for in case the device list are empty (common issue)
532     m_configCapture.labelNoAudioDevices->setVisible(audioDevices.empty());
533 
534     m_configCapture.kcfg_defaultaudiocapture->addItems(audioDevices);
535     connect(m_configCapture.kcfg_defaultaudiocapture, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [&]() {
536         QString currentDevice = m_configCapture.kcfg_defaultaudiocapture->currentText();
537         KdenliveSettings::setDefaultaudiocapture(currentDevice);
538     });
539     QString selectedDevice = KdenliveSettings::defaultaudiocapture();
540     int selectedIndex = m_configCapture.kcfg_defaultaudiocapture->findText(selectedDevice);
541     if (!selectedDevice.isEmpty() && selectedIndex > -1) {
542         m_configCapture.kcfg_defaultaudiocapture->setCurrentIndex(selectedIndex);
543     }
544     return true;
545 }
546 
getBlackMagicOutputDeviceList(QComboBox * devicelist,bool force)547 bool KdenliveSettingsDialog::getBlackMagicOutputDeviceList(QComboBox *devicelist, bool force)
548 {
549     if (!force && !KdenliveSettings::decklink_device_found()) {
550         return false;
551     }
552     Mlt::Profile profile;
553     Mlt::Consumer bm(profile, "decklink");
554     int found_devices = 0;
555     if (bm.is_valid()) {
556         bm.set("list_devices", 1);
557         found_devices = bm.get_int("devices");
558     } else {
559         KdenliveSettings::setDecklink_device_found(false);
560     }
561     if (found_devices <= 0) {
562         devicelist->setEnabled(false);
563         return false;
564     }
565     KdenliveSettings::setDecklink_device_found(true);
566     for (int i = 0; i < found_devices; ++i) {
567         char *tmp = qstrdup(QStringLiteral("device.%1").arg(i).toUtf8().constData());
568         devicelist->addItem(bm.get(tmp));
569         delete[] tmp;
570     }
571     devicelist->addItem(QStringLiteral("test"));
572     return true;
573 }
574 
setupJogshuttleBtns(const QString & device)575 void KdenliveSettingsDialog::setupJogshuttleBtns(const QString &device)
576 {
577     QList<QComboBox *> list;
578     QList<QLabel *> list1;
579 
580     list << m_configShuttle.shuttle1;
581     list << m_configShuttle.shuttle2;
582     list << m_configShuttle.shuttle3;
583     list << m_configShuttle.shuttle4;
584     list << m_configShuttle.shuttle5;
585     list << m_configShuttle.shuttle6;
586     list << m_configShuttle.shuttle7;
587     list << m_configShuttle.shuttle8;
588     list << m_configShuttle.shuttle9;
589     list << m_configShuttle.shuttle10;
590     list << m_configShuttle.shuttle11;
591     list << m_configShuttle.shuttle12;
592     list << m_configShuttle.shuttle13;
593     list << m_configShuttle.shuttle14;
594     list << m_configShuttle.shuttle15;
595 
596     list1 << m_configShuttle.label_2;  // #1
597     list1 << m_configShuttle.label_4;  // #2
598     list1 << m_configShuttle.label_3;  // #3
599     list1 << m_configShuttle.label_7;  // #4
600     list1 << m_configShuttle.label_5;  // #5
601     list1 << m_configShuttle.label_6;  // #6
602     list1 << m_configShuttle.label_8;  // #7
603     list1 << m_configShuttle.label_9;  // #8
604     list1 << m_configShuttle.label_10; // #9
605     list1 << m_configShuttle.label_11; // #10
606     list1 << m_configShuttle.label_12; // #11
607     list1 << m_configShuttle.label_13; // #12
608     list1 << m_configShuttle.label_14; // #13
609     list1 << m_configShuttle.label_15; // #14
610     list1 << m_configShuttle.label_16; // #15
611 
612     for (int i = 0; i < list.count(); ++i) {
613         list[i]->hide();
614         list1[i]->hide();
615     }
616 #ifdef USE_JOGSHUTTLE
617     if (!m_configShuttle.kcfg_enableshuttle->isChecked()) {
618         return;
619     }
620     int keysCount = JogShuttle::keysCount(device);
621 
622     for (int i = 0; i < keysCount; ++i) {
623         m_shuttle_buttons.push_back(list[i]);
624         list[i]->show();
625         list1[i]->show();
626     }
627 
628     // populate the buttons with the current configuration. The items are sorted
629     // according to the user-selected language, so they do not appear in random order.
630     QMap<QString, QString> mappable_actions(m_mappable_actions);
631     QList<QString> action_names = mappable_actions.keys();
632     QList<QString>::Iterator iter = action_names.begin();
633     // qCDebug(KDENLIVE_LOG) << "::::::::::::::::";
634     while (iter != action_names.end()) {
635         // qCDebug(KDENLIVE_LOG) << *iter;
636         ++iter;
637     }
638 
639     // qCDebug(KDENLIVE_LOG) << "::::::::::::::::";
640 
641     std::sort(action_names.begin(), action_names.end());
642     iter = action_names.begin();
643     while (iter != action_names.end()) {
644         // qCDebug(KDENLIVE_LOG) << *iter;
645         ++iter;
646     }
647     // qCDebug(KDENLIVE_LOG) << "::::::::::::::::";
648 
649     // Here we need to compute the action_id -> index-in-action_names. We iterate over the
650     // action_names, as the sorting may depend on the user-language.
651     QStringList actions_map = JogShuttleConfig::actionMap(KdenliveSettings::shuttlebuttons());
652     QMap<QString, int> action_pos;
653     for (const QString &action_id : qAsConst(actions_map)) {
654         // This loop find out at what index is the string that would map to the action_id.
655         for (int i = 0; i < action_names.size(); ++i) {
656             if (mappable_actions[action_names.at(i)] == action_id) {
657                 action_pos[action_id] = i;
658                 break;
659             }
660         }
661     }
662 
663     int i = 0;
664     for (QComboBox *button : qAsConst(m_shuttle_buttons)) {
665         button->addItems(action_names);
666         connect(button, SIGNAL(activated(int)), this, SLOT(slotShuttleModified()));
667         ++i;
668         if (i < actions_map.size()) {
669             button->setCurrentIndex(action_pos[actions_map[i]]);
670         }
671     }
672 #else
673     Q_UNUSED(device)
674 #endif
675 }
676 
677 KdenliveSettingsDialog::~KdenliveSettingsDialog() = default;
678 
slotUpdateGrabRegionStatus()679 void KdenliveSettingsDialog::slotUpdateGrabRegionStatus()
680 {
681     m_configCapture.region_group->setHidden(m_configCapture.kcfg_grab_capture_type->currentIndex() != 1);
682 }
683 
slotEnableCaptureFolder()684 void KdenliveSettingsDialog::slotEnableCaptureFolder()
685 {
686     m_configEnv.capturefolderurl->setEnabled(!m_configEnv.kcfg_capturetoprojectfolder->isChecked());
687 }
688 
slotEnableLibraryFolder()689 void KdenliveSettingsDialog::slotEnableLibraryFolder()
690 {
691     m_configEnv.libraryfolderurl->setEnabled(!m_configEnv.kcfg_librarytodefaultfolder->isChecked());
692 }
693 
slotEnableVideoFolder()694 void KdenliveSettingsDialog::slotEnableVideoFolder()
695 {
696     m_configEnv.videofolderurl->setEnabled(!m_configEnv.kcfg_videotodefaultfolder->isChecked());
697 }
698 
initDevices()699 void KdenliveSettingsDialog::initDevices()
700 {
701     // Fill audio drivers
702     m_configSdl.kcfg_audio_driver->addItem(i18n("Automatic"), QString());
703 #if defined(Q_OS_WIN)
704     //TODO: i18n
705     m_configSdl.kcfg_audio_driver->addItem("DirectSound", "directsound");
706     m_configSdl.kcfg_audio_driver->addItem("WinMM", "winmm");
707     m_configSdl.kcfg_audio_driver->addItem("Wasapi", "wasapi");
708 #elif !defined(Q_WS_MAC)
709     m_configSdl.kcfg_audio_driver->addItem(i18n("ALSA"), "alsa");
710     m_configSdl.kcfg_audio_driver->addItem(i18n("PulseAudio"), "pulseaudio");
711     m_configSdl.kcfg_audio_driver->addItem(i18n("OSS"), "dsp");
712     //m_configSdl.kcfg_audio_driver->addItem(i18n("OSS with DMA access"), "dma");
713     m_configSdl.kcfg_audio_driver->addItem(i18n("Esound daemon"), "esd");
714     m_configSdl.kcfg_audio_driver->addItem(i18n("ARTS daemon"), "artsc");
715 #endif
716 
717     if (!KdenliveSettings::audiodrivername().isEmpty())
718         for (int i = 1; i < m_configSdl.kcfg_audio_driver->count(); ++i) {
719             if (m_configSdl.kcfg_audio_driver->itemData(i).toString() == KdenliveSettings::audiodrivername()) {
720                 m_configSdl.kcfg_audio_driver->setCurrentIndex(i);
721                 KdenliveSettings::setAudio_driver(uint(i));
722             }
723         }
724 
725     // Fill the list of audio playback / recording devices
726     m_configSdl.kcfg_audio_device->addItem(i18n("Default"), QString());
727     m_configCapture.kcfg_v4l_alsadevice->addItem(i18n("Default"), "default");
728     if (!QStandardPaths::findExecutable(QStringLiteral("aplay")).isEmpty()) {
729         m_readProcess.setOutputChannelMode(KProcess::OnlyStdoutChannel);
730         m_readProcess.setProgram(QStringLiteral("aplay"), QStringList() << QStringLiteral("-l"));
731         connect(&m_readProcess, &KProcess::readyReadStandardOutput, this, &KdenliveSettingsDialog::slotReadAudioDevices);
732         m_readProcess.execute(5000);
733     } else {
734         // If aplay is not installed on the system, parse the /proc/asound/pcm file
735         QFile file(QStringLiteral("/proc/asound/pcm"));
736         if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
737             QTextStream stream(&file);
738             QString line = stream.readLine();
739             QString deviceId;
740             while (!line.isNull()) {
741                 if (line.contains(QStringLiteral("playback"))) {
742                     deviceId = line.section(QLatin1Char(':'), 0, 0);
743                     m_configSdl.kcfg_audio_device->addItem(line.section(QLatin1Char(':'), 1, 1), QStringLiteral("plughw:%1,%2")
744                                                                                                      .arg(deviceId.section(QLatin1Char('-'), 0, 0).toInt())
745                                                                                                      .arg(deviceId.section(QLatin1Char('-'), 1, 1).toInt()));
746                 }
747                 if (line.contains(QStringLiteral("capture"))) {
748                     deviceId = line.section(QLatin1Char(':'), 0, 0);
749                     m_configCapture.kcfg_v4l_alsadevice->addItem(
750                         line.section(QLatin1Char(':'), 1, 1).simplified(),
751                         QStringLiteral("hw:%1,%2").arg(deviceId.section(QLatin1Char('-'), 0, 0).toInt()).arg(deviceId.section(QLatin1Char('-'), 1, 1).toInt()));
752                 }
753                 line = stream.readLine();
754             }
755             file.close();
756         } else {
757             qCDebug(KDENLIVE_LOG) << " / / / /CANNOT READ PCM";
758         }
759     }
760 
761     // Add pulseaudio capture option
762     m_configCapture.kcfg_v4l_alsadevice->addItem(i18n("PulseAudio"), "pulse");
763 
764     if (!KdenliveSettings::audiodevicename().isEmpty()) {
765         // Select correct alsa device
766         int ix = m_configSdl.kcfg_audio_device->findData(KdenliveSettings::audiodevicename());
767         m_configSdl.kcfg_audio_device->setCurrentIndex(ix);
768         KdenliveSettings::setAudio_device(ix);
769     }
770 
771     if (!KdenliveSettings::v4l_alsadevicename().isEmpty()) {
772         // Select correct alsa device
773         int ix = m_configCapture.kcfg_v4l_alsadevice->findData(KdenliveSettings::v4l_alsadevicename());
774         m_configCapture.kcfg_v4l_alsadevice->setCurrentIndex(ix);
775         KdenliveSettings::setV4l_alsadevice(ix);
776     }
777 
778     m_configSdl.kcfg_audio_backend->addItem(i18n("SDL"), KdenliveSettings::sdlAudioBackend());
779     m_configSdl.kcfg_audio_backend->addItem(i18n("RtAudio"), "rtaudio");
780 
781     if (!KdenliveSettings::audiobackend().isEmpty()) {
782         int ix = m_configSdl.kcfg_audio_backend->findData(KdenliveSettings::audiobackend());
783         m_configSdl.kcfg_audio_backend->setCurrentIndex(ix);
784         KdenliveSettings::setAudio_backend(ix);
785     }
786     m_configSdl.group_sdl->setEnabled(KdenliveSettings::audiobackend().startsWith(QLatin1String("sdl")));
787 
788     // Fill monitors data
789     QSignalBlocker bk(m_configSdl.fullscreen_monitor);
790     m_configSdl.fullscreen_monitor->clear();
791     m_configSdl.fullscreen_monitor->addItem(i18n("auto"));
792     for (const QScreen* screen : qApp->screens()) {
793         m_configSdl.fullscreen_monitor->addItem(QString("%1 %2 (%3)").arg(screen->manufacturer(), screen->model(), screen->name()), screen->serialNumber());
794     }
795     if (!KdenliveSettings::fullscreen_monitor().isEmpty()) {
796         int ix = m_configSdl.fullscreen_monitor->findData(KdenliveSettings::fullscreen_monitor());
797         if (ix > -1) {
798             m_configSdl.fullscreen_monitor->setCurrentIndex(ix);
799         }
800     }
801 
802     loadCurrentV4lProfileInfo();
803 }
804 
slotReadAudioDevices()805 void KdenliveSettingsDialog::slotReadAudioDevices()
806 {
807     QString result = QString(m_readProcess.readAllStandardOutput());
808     // qCDebug(KDENLIVE_LOG) << "// / / / / / READING APLAY: ";
809     // qCDebug(KDENLIVE_LOG) << result;
810     const QStringList lines = result.split(QLatin1Char('\n'));
811     for (const QString &devicestr : lines) {
812         ////qCDebug(KDENLIVE_LOG) << "// READING LINE: " << data;
813         if (!devicestr.startsWith(QLatin1Char(' ')) && devicestr.count(QLatin1Char(':')) > 1) {
814             QString card = devicestr.section(QLatin1Char(':'), 0, 0).section(QLatin1Char(' '), -1);
815             QString device = devicestr.section(QLatin1Char(':'), 1, 1).section(QLatin1Char(' '), -1);
816             m_configSdl.kcfg_audio_device->addItem(devicestr.section(QLatin1Char(':'), -1).simplified(), QStringLiteral("plughw:%1,%2").arg(card, device));
817             m_configCapture.kcfg_v4l_alsadevice->addItem(devicestr.section(QLatin1Char(':'), -1).simplified(),
818                                                          QStringLiteral("hw:%1,%2").arg(card, device));
819         }
820     }
821 }
822 
showPage(int page,int option)823 void KdenliveSettingsDialog::showPage(int page, int option)
824 {
825     switch (page) {
826     case 1:
827         setCurrentPage(m_page1);
828         break;
829     case 2:
830         setCurrentPage(m_page2);
831         break;
832     case 3:
833         setCurrentPage(m_page3);
834         break;
835     case 4:
836         setCurrentPage(m_page4);
837         m_configCapture.tabWidget->setCurrentIndex(option);
838         break;
839     case 5:
840         setCurrentPage(m_page5);
841         break;
842     case 6:
843         setCurrentPage(m_page6);
844         break;
845     case 7:
846         setCurrentPage(m_page7);
847         break;
848     case 8:
849         setCurrentPage(m_page11);
850         checkVoskDependencies();
851         break;
852     default:
853         setCurrentPage(m_page1);
854     }
855 }
856 
slotEditAudioApplication()857 void KdenliveSettingsDialog::slotEditAudioApplication()
858 {
859     KService::Ptr service;
860     QPointer<KOpenWithDialog> dlg = new KOpenWithDialog(QList<QUrl>(), i18n("Select default audio editor"), m_configEnv.kcfg_defaultaudioapp->text(), this);
861     if (dlg->exec() == QDialog::Accepted) {
862         service = dlg->service();
863         m_configEnv.kcfg_defaultaudioapp->setText(KIO::DesktopExecParser::executablePath(service->exec()));
864     }
865 
866     delete dlg;
867 }
868 
slotEditImageApplication()869 void KdenliveSettingsDialog::slotEditImageApplication()
870 {
871     QPointer<KOpenWithDialog> dlg = new KOpenWithDialog(QList<QUrl>(), i18n("Select default image editor"), m_configEnv.kcfg_defaultimageapp->text(), this);
872     if (dlg->exec() == QDialog::Accepted) {
873         KService::Ptr service = dlg->service();
874         m_configEnv.kcfg_defaultimageapp->setText(KIO::DesktopExecParser::executablePath(service->exec()));
875     }
876     delete dlg;
877 }
878 
slotCheckShuttle(int state)879 void KdenliveSettingsDialog::slotCheckShuttle(int state)
880 {
881 #ifdef USE_JOGSHUTTLE
882     m_configShuttle.config_group->setEnabled(state != Qt::Unchecked);
883     m_configShuttle.shuttledevicelist->clear();
884 
885     QStringList devNames = KdenliveSettings::shuttledevicenames();
886     QStringList devPaths = KdenliveSettings::shuttledevicepaths();
887 
888     if (devNames.count() != devPaths.count()) {
889         return;
890     }
891     for (int i = 0; i < devNames.count(); ++i) {
892         m_configShuttle.shuttledevicelist->addItem(devNames.at(i), devPaths.at(i));
893     }
894     if (state != Qt::Unchecked) {
895         setupJogshuttleBtns(m_configShuttle.shuttledevicelist->itemData(m_configShuttle.shuttledevicelist->currentIndex()).toString());
896     }
897 #else
898     Q_UNUSED(state)
899 #endif /* USE_JOGSHUTTLE */
900 }
901 
slotUpdateShuttleDevice(int ix)902 void KdenliveSettingsDialog::slotUpdateShuttleDevice(int ix)
903 {
904 #ifdef USE_JOGSHUTTLE
905     QString device = m_configShuttle.shuttledevicelist->itemData(ix).toString();
906     // KdenliveSettings::setShuttledevice(device);
907     setupJogshuttleBtns(device);
908     m_configShuttle.kcfg_shuttledevice->setText(device);
909 #else
910     Q_UNUSED(ix)
911 #endif /* USE_JOGSHUTTLE */
912 }
913 
updateWidgets()914 void KdenliveSettingsDialog::updateWidgets()
915 {
916 // Revert widgets to last saved state (for example when user pressed "Cancel")
917 // //qCDebug(KDENLIVE_LOG) << "// // // KCONFIG Revert called";
918 #ifdef USE_JOGSHUTTLE
919     // revert jog shuttle device
920     if (m_configShuttle.shuttledevicelist->count() > 0) {
921         for (int i = 0; i < m_configShuttle.shuttledevicelist->count(); ++i) {
922             if (m_configShuttle.shuttledevicelist->itemData(i) == KdenliveSettings::shuttledevice()) {
923                 m_configShuttle.shuttledevicelist->setCurrentIndex(i);
924                 break;
925             }
926         }
927     }
928 
929     // Revert jog shuttle buttons
930     QList<QString> action_names = m_mappable_actions.keys();
931     std::sort(action_names.begin(), action_names.end());
932     QStringList actions_map = JogShuttleConfig::actionMap(KdenliveSettings::shuttlebuttons());
933     QMap<QString, int> action_pos;
934     for (const QString &action_id : qAsConst(actions_map)) {
935         // This loop find out at what index is the string that would map to the action_id.
936         for (int i = 0; i < action_names.size(); ++i) {
937             if (m_mappable_actions[action_names[i]] == action_id) {
938                 action_pos[action_id] = i;
939                 break;
940             }
941         }
942     }
943     int i = 0;
944     for (QComboBox *button : qAsConst(m_shuttle_buttons)) {
945         ++i;
946         if (i < actions_map.size()) {
947             button->setCurrentIndex(action_pos[actions_map[i]]);
948         }
949     }
950 #endif /* USE_JOGSHUTTLE */
951 }
952 
accept()953 void KdenliveSettingsDialog::accept()
954 {
955     if (m_pw->selectedProfile().isEmpty()) {
956         KMessageBox::error(this, i18n("Please select a video profile"));
957         return;
958     }
959     KConfigDialog::accept();
960 }
961 
updateSettings()962 void KdenliveSettingsDialog::updateSettings()
963 {
964     // Save changes to settings (for example when user pressed "Apply" or "Ok")
965     // //qCDebug(KDENLIVE_LOG) << "// // // KCONFIG UPDATE called";
966     if (m_pw->selectedProfile().isEmpty()) {
967         KMessageBox::error(this, i18n("Please select a video profile"));
968         return;
969     }
970     KdenliveSettings::setDefault_profile(m_pw->selectedProfile());
971 
972     if (m_configEnv.ffmpegurl->text().isEmpty()) {
973         QString infos;
974         QString warnings;
975         Wizard::slotCheckPrograms(infos, warnings);
976         m_configEnv.ffmpegurl->setText(KdenliveSettings::ffmpegpath());
977         m_configEnv.ffplayurl->setText(KdenliveSettings::ffplaypath());
978         m_configEnv.ffprobeurl->setText(KdenliveSettings::ffprobepath());
979     }
980     if (m_configEnv.mediainfourl->text().isEmpty()) {
981         m_configEnv.mediainfourl->setText(KdenliveSettings::mediainfopath());
982     }
983 
984     if (m_configTimeline.kcfg_trackheight->value() == 0) {
985         QFont ft = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont);
986         // Default unit for timeline.qml objects size
987         int baseUnit = qMax(28, int(QFontInfo(ft).pixelSize() * 1.8 + 0.5));
988         int trackHeight = qMax(50, int(2.2 * baseUnit + 6));
989         m_configTimeline.kcfg_trackheight->setValue(trackHeight);
990     } else if (m_configTimeline.kcfg_trackheight->value() != KdenliveSettings::trackheight()) {
991         QFont ft = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont);
992         // Default unit for timeline.qml objects size
993         int baseUnit = qMax(28, int(QFontInfo(ft).pixelSize() * 1.8 + 0.5));
994         if (m_configTimeline.kcfg_trackheight->value() < baseUnit) {
995             m_configTimeline.kcfg_trackheight->setValue(baseUnit);
996         }
997     }
998 
999     bool resetConsumer = false;
1000     bool fullReset = false;
1001     bool updateCapturePath = false;
1002     bool updateLibrary = false;
1003 
1004     /*if (m_configShuttle.shuttledevicelist->count() > 0) {
1005     QString device = m_configShuttle.shuttledevicelist->itemData(m_configShuttle.shuttledevicelist->currentIndex()).toString();
1006     if (device != KdenliveSettings::shuttledevice()) KdenliveSettings::setShuttledevice(device);
1007     }*/
1008 
1009     // Capture default folder
1010     if (m_configEnv.kcfg_capturetoprojectfolder->isChecked() != KdenliveSettings::capturetoprojectfolder()) {
1011         KdenliveSettings::setCapturetoprojectfolder(m_configEnv.kcfg_capturetoprojectfolder->isChecked());
1012         updateCapturePath = true;
1013     }
1014 
1015     if (m_configProject.projecturl->url().toLocalFile() != KdenliveSettings::defaultprojectfolder()) {
1016         KdenliveSettings::setDefaultprojectfolder(m_configProject.projecturl->url().toLocalFile());
1017     }
1018 
1019     if (m_configEnv.capturefolderurl->url().toLocalFile() != KdenliveSettings::capturefolder()) {
1020         KdenliveSettings::setCapturefolder(m_configEnv.capturefolderurl->url().toLocalFile());
1021         updateCapturePath = true;
1022     }
1023 
1024     // Library default folder
1025     if (m_configEnv.kcfg_librarytodefaultfolder->isChecked() != KdenliveSettings::librarytodefaultfolder()) {
1026         KdenliveSettings::setLibrarytodefaultfolder(m_configEnv.kcfg_librarytodefaultfolder->isChecked());
1027         updateLibrary = true;
1028     }
1029 
1030     if (m_configEnv.libraryfolderurl->url().toLocalFile() != KdenliveSettings::libraryfolder()) {
1031         KdenliveSettings::setLibraryfolder(m_configEnv.libraryfolderurl->url().toLocalFile());
1032         if (!KdenliveSettings::librarytodefaultfolder()) {
1033             updateLibrary = true;
1034         }
1035     }
1036 
1037     if (m_configCapture.kcfg_v4l_format->currentIndex() != int(KdenliveSettings::v4l_format())) {
1038         saveCurrentV4lProfile();
1039         KdenliveSettings::setV4l_format(0);
1040     }
1041 
1042     // Check if screengrab is fullscreen
1043     if (m_configCapture.kcfg_grab_capture_type->currentIndex() != KdenliveSettings::grab_capture_type()) {
1044         KdenliveSettings::setGrab_capture_type(m_configCapture.kcfg_grab_capture_type->currentIndex());
1045         emit updateFullScreenGrab();
1046     }
1047 
1048     // Check encoding profiles
1049     // FFmpeg
1050     QString profilestr = m_configCapture.kcfg_v4l_profile->currentData().toString();
1051     if (!profilestr.isEmpty() && (profilestr.section(QLatin1Char(';'), 0, 0) != KdenliveSettings::v4l_parameters() ||
1052                                   profilestr.section(QLatin1Char(';'), 1, 1) != KdenliveSettings::v4l_extension())) {
1053         KdenliveSettings::setV4l_parameters(profilestr.section(QLatin1Char(';'), 0, 0));
1054         KdenliveSettings::setV4l_extension(profilestr.section(QLatin1Char(';'), 1, 1));
1055     }
1056     // screengrab
1057     profilestr = m_configCapture.kcfg_grab_profile->currentData().toString();
1058     if (!profilestr.isEmpty() && (profilestr.section(QLatin1Char(';'), 0, 0) != KdenliveSettings::grab_parameters() ||
1059                                   profilestr.section(QLatin1Char(';'), 1, 1) != KdenliveSettings::grab_extension())) {
1060         KdenliveSettings::setGrab_parameters(profilestr.section(QLatin1Char(';'), 0, 0));
1061         KdenliveSettings::setGrab_extension(profilestr.section(QLatin1Char(';'), 1, 1));
1062     }
1063 
1064     // decklink
1065     profilestr = m_configCapture.kcfg_decklink_profile->currentData().toString();
1066     if (!profilestr.isEmpty() && (profilestr.section(QLatin1Char(';'), 0, 0) != KdenliveSettings::decklink_parameters() ||
1067                                   profilestr.section(QLatin1Char(';'), 1, 1) != KdenliveSettings::decklink_extension())) {
1068         KdenliveSettings::setDecklink_parameters(profilestr.section(QLatin1Char(';'), 0, 0));
1069         KdenliveSettings::setDecklink_extension(profilestr.section(QLatin1Char(';'), 1, 1));
1070     }
1071     // proxies
1072     profilestr = m_configProxy.kcfg_proxy_profile->currentData().toString();
1073     if (!profilestr.isEmpty() && (profilestr.section(QLatin1Char(';'), 0, 0) != KdenliveSettings::proxyparams() ||
1074                                   profilestr.section(QLatin1Char(';'), 1, 1) != KdenliveSettings::proxyextension())) {
1075         KdenliveSettings::setProxyparams(profilestr.section(QLatin1Char(';'), 0, 0));
1076         KdenliveSettings::setProxyextension(profilestr.section(QLatin1Char(';'), 1, 1));
1077     }
1078 
1079     // external proxies
1080     profilestr = m_configProxy.kcfg_external_proxy_profile->currentData().toString();
1081     if (!profilestr.isEmpty() && (profilestr != KdenliveSettings::externalProxyProfile())) {
1082         KdenliveSettings::setExternalProxyProfile(profilestr);
1083     }
1084 
1085     // timeline preview
1086     profilestr = m_configProject.kcfg_preview_profile->currentData().toString();
1087     if (!profilestr.isEmpty() && (profilestr.section(QLatin1Char(';'), 0, 0) != KdenliveSettings::previewparams() ||
1088                                   profilestr.section(QLatin1Char(';'), 1, 1) != KdenliveSettings::previewextension())) {
1089         KdenliveSettings::setPreviewparams(profilestr.section(QLatin1Char(';'), 0, 0));
1090         KdenliveSettings::setPreviewextension(profilestr.section(QLatin1Char(';'), 1, 1));
1091     }
1092 
1093     if (updateCapturePath) {
1094         emit updateCaptureFolder();
1095     }
1096     if (updateLibrary) {
1097         emit updateLibraryFolder();
1098     }
1099 
1100     QString value = m_configCapture.kcfg_v4l_alsadevice->currentData().toString();
1101     if (value != KdenliveSettings::v4l_alsadevicename()) {
1102         KdenliveSettings::setV4l_alsadevicename(value);
1103     }
1104 
1105     if (m_configSdl.kcfg_external_display->isChecked() != KdenliveSettings::external_display()) {
1106         KdenliveSettings::setExternal_display(m_configSdl.kcfg_external_display->isChecked());
1107         resetConsumer = true;
1108         fullReset = true;
1109     } else if (KdenliveSettings::external_display() &&
1110                KdenliveSettings::blackmagic_output_device() != m_configSdl.kcfg_blackmagic_output_device->currentIndex()) {
1111         resetConsumer = true;
1112         fullReset = true;
1113     }
1114 
1115     value = m_configSdl.kcfg_audio_driver->currentData().toString();
1116     if (value != KdenliveSettings::audiodrivername()) {
1117         KdenliveSettings::setAudiodrivername(value);
1118         resetConsumer = true;
1119     }
1120 
1121     if (value == QLatin1String("alsa")) {
1122         // Audio device setting is only valid for alsa driver
1123         value = m_configSdl.kcfg_audio_device->currentData().toString();
1124         if (value != KdenliveSettings::audiodevicename()) {
1125             KdenliveSettings::setAudiodevicename(value);
1126             resetConsumer = true;
1127         }
1128     } else if (!KdenliveSettings::audiodevicename().isEmpty()) {
1129         KdenliveSettings::setAudiodevicename(QString());
1130         resetConsumer = true;
1131     }
1132 
1133     value = m_configSdl.kcfg_audio_backend->currentData().toString();
1134     if (value != KdenliveSettings::audiobackend()) {
1135         KdenliveSettings::setAudiobackend(value);
1136         resetConsumer = true;
1137         fullReset = true;
1138     }
1139 
1140     if (m_configSdl.kcfg_window_background->color() != KdenliveSettings::window_background()) {
1141         KdenliveSettings::setWindow_background(m_configSdl.kcfg_window_background->color());
1142         emit updateMonitorBg();
1143     }
1144 
1145     if (m_configColors.kcfg_thumbColor1->color() != KdenliveSettings::thumbColor1() || m_configColors.kcfg_thumbColor2->color() != KdenliveSettings::thumbColor2()) {
1146         KdenliveSettings::setThumbColor1(m_configColors.kcfg_thumbColor1->color());
1147         KdenliveSettings::setThumbColor2(m_configColors.kcfg_thumbColor2->color());
1148         emit pCore->window()->getMainTimeline()->controller()->colorsChanged();
1149         pCore->getMonitor(Kdenlive::ClipMonitor)->refreshAudioThumbs();
1150     }
1151 
1152     if (m_configSdl.kcfg_volume->value() != KdenliveSettings::volume()) {
1153         KdenliveSettings::setVolume(m_configSdl.kcfg_volume->value());
1154         resetConsumer = true;
1155     }
1156 
1157     if (m_configMisc.kcfg_tabposition->currentIndex() != KdenliveSettings::tabposition()) {
1158         KdenliveSettings::setTabposition(m_configMisc.kcfg_tabposition->currentIndex());
1159     }
1160 
1161     if (m_configTimeline.kcfg_displayallchannels->isChecked() != KdenliveSettings::displayallchannels()) {
1162         KdenliveSettings::setDisplayallchannels(m_configTimeline.kcfg_displayallchannels->isChecked());
1163         emit audioThumbFormatChanged();
1164         pCore->getMonitor(Kdenlive::ClipMonitor)->refreshAudioThumbs();
1165     }
1166 
1167     if (m_modified) {
1168         // The transcoding profiles were modified, save.
1169         m_modified = false;
1170         saveTranscodeProfiles();
1171     }
1172 
1173 #ifdef USE_JOGSHUTTLE
1174     m_shuttleModified = false;
1175 
1176     QStringList actions;
1177     actions << QStringLiteral("monitor_pause"); // the Job rest position action.
1178     for (QComboBox *button : qAsConst(m_shuttle_buttons)) {
1179         actions << m_mappable_actions[button->currentText()];
1180     }
1181     QString maps = JogShuttleConfig::actionMap(actions);
1182     // fprintf(stderr, "Shuttle config: %s\n", JogShuttleConfig::actionMap(actions).toLatin1().constData());
1183     if (KdenliveSettings::shuttlebuttons() != maps) {
1184         KdenliveSettings::setShuttlebuttons(maps);
1185     }
1186 #endif
1187 
1188     bool restart = false;
1189     if (m_configSdl.kcfg_gpu_accel->isChecked() != KdenliveSettings::gpu_accel()) {
1190         // GPU setting was changed, we need to restart Kdenlive or everything will be corrupted
1191         if (KMessageBox::warningContinueCancel(this, i18n("Kdenlive must be restarted to change this setting")) == KMessageBox::Continue) {
1192             restart = true;
1193         } else {
1194             m_configSdl.kcfg_gpu_accel->setChecked(KdenliveSettings::gpu_accel());
1195         }
1196     }
1197 
1198     if (m_configTimeline.kcfg_trackheight->value() != KdenliveSettings::trackheight()) {
1199         KdenliveSettings::setTrackheight(m_configTimeline.kcfg_trackheight->value());
1200         emit resetView();
1201     }
1202 
1203     if (m_configTimeline.kcfg_autoscroll->isChecked() != KdenliveSettings::autoscroll()) {
1204         KdenliveSettings::setAutoscroll(m_configTimeline.kcfg_autoscroll->isChecked());
1205         emit pCore->autoScrollChanged();
1206     }
1207 
1208     if (m_configTimeline.kcfg_pauseonseek->isChecked() != KdenliveSettings::pauseonseek()) {
1209         KdenliveSettings::setPauseonseek(m_configTimeline.kcfg_pauseonseek->isChecked());
1210     }
1211 
1212     if (m_configTimeline.kcfg_scrollvertically->isChecked() != KdenliveSettings::scrollvertically()) {
1213         KdenliveSettings::setScrollvertically(m_configTimeline.kcfg_scrollvertically->isChecked());
1214         emit pCore->window()->getMainTimeline()->controller()->scrollVerticallyChanged();
1215     }
1216 
1217     // Mimes
1218     if (m_configEnv.kcfg_addedExtensions->text() != KdenliveSettings::addedExtensions()) {
1219         // Update list
1220         KdenliveSettings::setAddedExtensions(m_configEnv.kcfg_addedExtensions->text());
1221         QStringList mimes = ClipCreationDialog::getExtensions();
1222         std::sort(mimes.begin(), mimes.end());
1223         m_configEnv.supportedmimes->setPlainText(mimes.join(QLatin1Char(' ')));
1224     }
1225 
1226     // proxy/transcode max concurrent jobs
1227     if (m_configEnv.kcfg_proxythreads->value() != KdenliveSettings::proxythreads()) {
1228         KdenliveSettings::setProxythreads(m_configEnv.kcfg_proxythreads->value());
1229         pCore->taskManager.updateConcurrency();
1230     }
1231 
1232 
1233     KConfigDialog::settingsChangedSlot();
1234     // KConfigDialog::updateSettings();
1235     if (resetConsumer) {
1236         emit doResetConsumer(fullReset);
1237     }
1238     if (restart) {
1239         emit restartKdenlive();
1240     }
1241     emit checkTabPosition();
1242 
1243     // remembering Config dialog size
1244     KSharedConfigPtr config = KSharedConfig::openConfig();
1245     KConfigGroup settingsGroup(config, "settings");
1246     settingsGroup.writeEntry("dialogSize", QVariant(size()));
1247 }
1248 
slotSetFullscreenMonitor()1249 void KdenliveSettingsDialog::slotSetFullscreenMonitor()
1250 {
1251     QString value = m_configSdl.fullscreen_monitor->currentData().toString();
1252     KdenliveSettings::setFullscreen_monitor(value);
1253 }
1254 
slotCheckAlsaDriver()1255 void KdenliveSettingsDialog::slotCheckAlsaDriver()
1256 {
1257     QString value = m_configSdl.kcfg_audio_driver->currentData().toString();
1258     m_configSdl.kcfg_audio_device->setEnabled(value == QLatin1String("alsa"));
1259 }
1260 
slotCheckAudioBackend()1261 void KdenliveSettingsDialog::slotCheckAudioBackend()
1262 {
1263     QString value = m_configSdl.kcfg_audio_backend->currentData().toString();
1264     m_configSdl.group_sdl->setEnabled(value.startsWith(QLatin1String("sdl")));
1265 }
1266 
loadTranscodeProfiles()1267 void KdenliveSettingsDialog::loadTranscodeProfiles()
1268 {
1269     KSharedConfigPtr config =
1270         KSharedConfig::openConfig(QStringLiteral("kdenlivetranscodingrc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
1271     KConfigGroup transConfig(config, "Transcoding");
1272     // read the entries
1273     m_configTranscode.profiles_list->blockSignals(true);
1274     m_configTranscode.profiles_list->clear();
1275     QMap<QString, QString> profiles = transConfig.entryMap();
1276     QMapIterator<QString, QString> i(profiles);
1277     while (i.hasNext()) {
1278         i.next();
1279         auto *item = new QListWidgetItem(i.key());
1280         QString profilestr = i.value();
1281         if (profilestr.contains(QLatin1Char(';'))) {
1282             item->setToolTip(profilestr.section(QLatin1Char(';'), 1, 1));
1283         }
1284         item->setData(Qt::UserRole, profilestr);
1285         m_configTranscode.profiles_list->addItem(item);
1286         // item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable);
1287     }
1288     m_configTranscode.profiles_list->blockSignals(false);
1289     m_configTranscode.profiles_list->setCurrentRow(0);
1290 }
1291 
saveTranscodeProfiles()1292 void KdenliveSettingsDialog::saveTranscodeProfiles()
1293 {
1294     QString transcodeFile = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/kdenlivetranscodingrc");
1295     KSharedConfigPtr config = KSharedConfig::openConfig(transcodeFile);
1296     KConfigGroup transConfig(config, "Transcoding");
1297     // read the entries
1298     transConfig.deleteGroup();
1299     int max = m_configTranscode.profiles_list->count();
1300     for (int i = 0; i < max; ++i) {
1301         QListWidgetItem *item = m_configTranscode.profiles_list->item(i);
1302         transConfig.writeEntry(item->text(), item->data(Qt::UserRole).toString());
1303     }
1304     config->sync();
1305 }
1306 
slotAddTranscode()1307 void KdenliveSettingsDialog::slotAddTranscode()
1308 {
1309 
1310     bool ok;
1311     QString presetName = QInputDialog::getText(this, i18n("Enter preset name"), i18n("Enter the name of this preset"), QLineEdit::Normal, QString(), &ok);
1312     if (!ok) return;
1313     if (!m_configTranscode.profiles_list->findItems(presetName, Qt::MatchExactly).isEmpty()) {
1314         KMessageBox::sorry(this, i18n("A profile with that name already exists"));
1315         return;
1316     }
1317     QListWidgetItem *item = new QListWidgetItem(presetName);
1318     QString profilestr = m_configTranscode.profile_parameters->toPlainText();
1319     profilestr.append(" %1." + m_configTranscode.profile_extension->text());
1320     profilestr.append(';');
1321     if (!m_configTranscode.profile_description->text().isEmpty()) {
1322         profilestr.append(m_configTranscode.profile_description->text());
1323     }
1324     if (m_configTranscode.profile_audioonly->isChecked()) {
1325         profilestr.append(";audio");
1326     }
1327     item->setData(Qt::UserRole, profilestr);
1328     m_configTranscode.profiles_list->addItem(item);
1329     m_configTranscode.profiles_list->setCurrentItem(item);
1330     slotDialogModified();
1331 }
1332 
slotUpdateTranscodingProfile()1333 void KdenliveSettingsDialog::slotUpdateTranscodingProfile()
1334 {
1335     QListWidgetItem *item = m_configTranscode.profiles_list->currentItem();
1336     if (!item) {
1337         return;
1338     }
1339     m_configTranscode.button_update->setEnabled(false);
1340     QString profilestr = m_configTranscode.profile_parameters->toPlainText();
1341     profilestr.append(" %1." + m_configTranscode.profile_extension->text());
1342     profilestr.append(';');
1343     if (!m_configTranscode.profile_description->text().isEmpty()) {
1344         profilestr.append(m_configTranscode.profile_description->text());
1345     }
1346     if (m_configTranscode.profile_audioonly->isChecked()) {
1347         profilestr.append(QStringLiteral(";audio"));
1348     }
1349     item->setData(Qt::UserRole, profilestr);
1350     slotDialogModified();
1351 }
1352 
slotDeleteTranscode()1353 void KdenliveSettingsDialog::slotDeleteTranscode()
1354 {
1355     QListWidgetItem *item = m_configTranscode.profiles_list->currentItem();
1356     if (item == nullptr) {
1357         return;
1358     }
1359     delete item;
1360     slotDialogModified();
1361 }
1362 
slotEnableTranscodeUpdate()1363 void KdenliveSettingsDialog::slotEnableTranscodeUpdate()
1364 {
1365     if (!m_configTranscode.profile_box->isEnabled()) {
1366         return;
1367     }
1368     bool allow = true;
1369     if (m_configTranscode.profile_extension->text().isEmpty()) {
1370         allow = false;
1371     }
1372     m_configTranscode.button_update->setEnabled(allow);
1373 }
1374 
slotSetTranscodeProfile()1375 void KdenliveSettingsDialog::slotSetTranscodeProfile()
1376 {
1377     m_configTranscode.profile_box->setEnabled(false);
1378     m_configTranscode.button_update->setEnabled(false);
1379     m_configTranscode.profile_description->clear();
1380     m_configTranscode.profile_extension->clear();
1381     m_configTranscode.profile_parameters->clear();
1382     m_configTranscode.profile_audioonly->setChecked(false);
1383     QListWidgetItem *item = m_configTranscode.profiles_list->currentItem();
1384     if (!item) {
1385         return;
1386     }
1387     QString profilestr = item->data(Qt::UserRole).toString();
1388     if (profilestr.contains(QLatin1Char(';'))) {
1389         m_configTranscode.profile_description->setText(profilestr.section(QLatin1Char(';'), 1, 1));
1390         if (profilestr.section(QLatin1Char(';'), 2, 2) == QLatin1String("audio")) {
1391             m_configTranscode.profile_audioonly->setChecked(true);
1392         }
1393         profilestr = profilestr.section(QLatin1Char(';'), 0, 0).simplified();
1394     }
1395     m_configTranscode.profile_extension->setText(profilestr.section(QLatin1Char('.'), -1));
1396     m_configTranscode.profile_parameters->setPlainText(profilestr.section(QLatin1Char(' '), 0, -2));
1397     m_configTranscode.profile_box->setEnabled(true);
1398 }
1399 
slotShuttleModified()1400 void KdenliveSettingsDialog::slotShuttleModified()
1401 {
1402 #ifdef USE_JOGSHUTTLE
1403     QStringList actions;
1404     actions << QStringLiteral("monitor_pause"); // the Job rest position action.
1405     for (QComboBox *button : qAsConst(m_shuttle_buttons)) {
1406         actions << m_mappable_actions[button->currentText()];
1407     }
1408     QString maps = JogShuttleConfig::actionMap(actions);
1409     m_shuttleModified = KdenliveSettings::shuttlebuttons() != maps;
1410 #endif
1411     KConfigDialog::updateButtons();
1412 }
1413 
slotDialogModified()1414 void KdenliveSettingsDialog::slotDialogModified()
1415 {
1416     m_modified = true;
1417     KConfigDialog::updateButtons();
1418 }
1419 
1420 // virtual
hasChanged()1421 bool KdenliveSettingsDialog::hasChanged()
1422 {
1423     if (m_modified || m_shuttleModified) {
1424         return true;
1425     }
1426     return KConfigDialog::hasChanged();
1427 }
1428 
slotUpdatev4lDevice()1429 void KdenliveSettingsDialog::slotUpdatev4lDevice()
1430 {
1431     QString device = m_configCapture.kcfg_detectedv4ldevices->itemData(m_configCapture.kcfg_detectedv4ldevices->currentIndex()).toString();
1432     if (!device.isEmpty()) {
1433         m_configCapture.kcfg_video4vdevice->setText(device);
1434     }
1435     QString info = m_configCapture.kcfg_detectedv4ldevices->itemData(m_configCapture.kcfg_detectedv4ldevices->currentIndex(), Qt::UserRole + 1).toString();
1436 
1437     m_configCapture.kcfg_v4l_format->blockSignals(true);
1438     m_configCapture.kcfg_v4l_format->clear();
1439 
1440     QString vl4ProfilePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/video4linux");
1441     if (QFile::exists(vl4ProfilePath)) {
1442         m_configCapture.kcfg_v4l_format->addItem(i18n("Current settings"));
1443     }
1444 
1445 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1446     QStringList pixelformats = info.split('>', QString::SkipEmptyParts);
1447 #else
1448     QStringList pixelformats = info.split('>', Qt::SkipEmptyParts);
1449 #endif
1450     QString itemSize;
1451     QString pixelFormat;
1452     QStringList itemRates;
1453     for (int i = 0; i < pixelformats.count(); ++i) {
1454         QString format = pixelformats.at(i).section(QLatin1Char(':'), 0, 0);
1455 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1456         QStringList sizes = pixelformats.at(i).split(':', QString::SkipEmptyParts);
1457 #else
1458         QStringList sizes = pixelformats.at(i).split(':', Qt::SkipEmptyParts);
1459 #endif
1460         pixelFormat = sizes.takeFirst();
1461         for (int j = 0; j < sizes.count(); ++j) {
1462             itemSize = sizes.at(j).section(QLatin1Char('='), 0, 0);
1463 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1464             itemRates = sizes.at(j).section(QLatin1Char('='), 1, 1).split(QLatin1Char(','), QString::SkipEmptyParts);
1465 #else
1466             itemRates = sizes.at(j).section(QLatin1Char('='), 1, 1).split(QLatin1Char(','), Qt::SkipEmptyParts);
1467 #endif
1468             for (int k = 0; k < itemRates.count(); ++k) {
1469                 m_configCapture.kcfg_v4l_format->addItem(
1470                     QLatin1Char('[') + format + QStringLiteral("] ") + itemSize + QStringLiteral(" (") + itemRates.at(k) + QLatin1Char(')'),
1471                     QStringList() << format << itemSize.section('x', 0, 0) << itemSize.section('x', 1, 1) << itemRates.at(k).section(QLatin1Char('/'), 0, 0)
1472                                   << itemRates.at(k).section(QLatin1Char('/'), 1, 1));
1473             }
1474         }
1475     }
1476     m_configCapture.kcfg_v4l_format->blockSignals(false);
1477     slotUpdatev4lCaptureProfile();
1478 }
1479 
slotUpdatev4lCaptureProfile()1480 void KdenliveSettingsDialog::slotUpdatev4lCaptureProfile()
1481 {
1482     QStringList info = m_configCapture.kcfg_v4l_format->itemData(m_configCapture.kcfg_v4l_format->currentIndex(), Qt::UserRole).toStringList();
1483     if (info.isEmpty()) {
1484         // No auto info, display the current ones
1485         loadCurrentV4lProfileInfo();
1486         return;
1487     }
1488     m_configCapture.p_size->setText(info.at(1) + QLatin1Char('x') + info.at(2));
1489     m_configCapture.p_fps->setText(info.at(3) + QLatin1Char('/') + info.at(4));
1490     m_configCapture.p_aspect->setText(QStringLiteral("1/1"));
1491     m_configCapture.p_display->setText(info.at(1) + QLatin1Char('/') + info.at(2));
1492     m_configCapture.p_colorspace->setText(ProfileRepository::getColorspaceDescription(601));
1493     m_configCapture.p_progressive->setText(i18n("Progressive"));
1494 
1495     QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/"));
1496     if (!dir.exists() || !dir.exists(QStringLiteral("video4linux"))) {
1497         saveCurrentV4lProfile();
1498     }
1499 }
1500 
loadCurrentV4lProfileInfo()1501 void KdenliveSettingsDialog::loadCurrentV4lProfileInfo()
1502 {
1503     QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/"));
1504     if (!dir.exists()) {
1505         dir.mkpath(QStringLiteral("."));
1506     }
1507     if (!ProfileRepository::get()->profileExists(dir.absoluteFilePath(QStringLiteral("video4linux")))) {
1508         // No default formats found, build one
1509         std::unique_ptr<ProfileParam> prof(new ProfileParam(pCore->getCurrentProfile().get()));
1510         prof->m_width = 320;
1511         prof->m_height = 200;
1512         prof->m_frame_rate_num = 15;
1513         prof->m_frame_rate_den = 1;
1514         prof->m_display_aspect_num = 4;
1515         prof->m_display_aspect_den = 3;
1516         prof->m_sample_aspect_num = 1;
1517         prof->m_sample_aspect_den = 1;
1518         prof->m_progressive = true;
1519         prof->m_colorspace = 601;
1520         ProfileRepository::get()->saveProfile(prof.get(), dir.absoluteFilePath(QStringLiteral("video4linux")));
1521     }
1522     auto &prof = ProfileRepository::get()->getProfile(dir.absoluteFilePath(QStringLiteral("video4linux")));
1523     m_configCapture.p_size->setText(QString::number(prof->width()) + QLatin1Char('x') + QString::number(prof->height()));
1524     m_configCapture.p_fps->setText(QString::number(prof->frame_rate_num()) + QLatin1Char('/') + QString::number(prof->frame_rate_den()));
1525     m_configCapture.p_aspect->setText(QString::number(prof->sample_aspect_num()) + QLatin1Char('/') + QString::number(prof->sample_aspect_den()));
1526     m_configCapture.p_display->setText(QString::number(prof->display_aspect_num()) + QLatin1Char('/') + QString::number(prof->display_aspect_den()));
1527     m_configCapture.p_colorspace->setText(ProfileRepository::getColorspaceDescription(prof->colorspace()));
1528     if (prof->progressive()) {
1529         m_configCapture.p_progressive->setText(i18n("Progressive"));
1530     }
1531 }
1532 
saveCurrentV4lProfile()1533 void KdenliveSettingsDialog::saveCurrentV4lProfile()
1534 {
1535     std::unique_ptr<ProfileParam> profile(new ProfileParam(pCore->getCurrentProfile().get()));
1536     profile->m_description = QStringLiteral("Video4Linux capture");
1537     profile->m_colorspace = ProfileRepository::getColorspaceFromDescription(m_configCapture.p_colorspace->text());
1538     profile->m_width = m_configCapture.p_size->text().section('x', 0, 0).toInt();
1539     profile->m_height = m_configCapture.p_size->text().section('x', 1, 1).toInt();
1540     profile->m_sample_aspect_num = m_configCapture.p_aspect->text().section(QLatin1Char('/'), 0, 0).toInt();
1541     profile->m_sample_aspect_den = m_configCapture.p_aspect->text().section(QLatin1Char('/'), 1, 1).toInt();
1542     profile->m_display_aspect_num = m_configCapture.p_display->text().section(QLatin1Char('/'), 0, 0).toInt();
1543     profile->m_display_aspect_den = m_configCapture.p_display->text().section(QLatin1Char('/'), 1, 1).toInt();
1544     profile->m_frame_rate_num = m_configCapture.p_fps->text().section(QLatin1Char('/'), 0, 0).toInt();
1545     profile->m_frame_rate_den = m_configCapture.p_fps->text().section(QLatin1Char('/'), 1, 1).toInt();
1546     profile->m_progressive = m_configCapture.p_progressive->text() == i18n("Progressive");
1547     QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/"));
1548     if (!dir.exists()) {
1549         dir.mkpath(QStringLiteral("."));
1550     }
1551     ProfileRepository::get()->saveProfile(profile.get(), dir.absoluteFilePath(QStringLiteral("video4linux")));
1552 }
1553 
slotManageEncodingProfile()1554 void KdenliveSettingsDialog::slotManageEncodingProfile()
1555 {
1556     auto *act = qobject_cast<QAction *>(sender());
1557     int type = 0;
1558     if (act) {
1559         type = act->data().toInt();
1560     }
1561     QPointer<EncodingProfilesDialog> dia = new EncodingProfilesDialog(type);
1562     dia->exec();
1563     delete dia;
1564     loadEncodingProfiles();
1565 }
1566 
loadExternalProxyProfiles()1567 void KdenliveSettingsDialog::loadExternalProxyProfiles()
1568 {
1569     // load proxy profiles
1570     KConfig conf(QStringLiteral("externalproxies.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
1571     KConfigGroup group(&conf, "proxy");
1572     QMap<QString, QString> values = group.entryMap();
1573     QMapIterator<QString, QString> k(values);
1574     QString currentItem = KdenliveSettings::externalProxyProfile();
1575     m_configProxy.kcfg_external_proxy_profile->blockSignals(true);
1576     m_configProxy.kcfg_external_proxy_profile->clear();
1577     while (k.hasNext()) {
1578         k.next();
1579         if (!k.key().isEmpty()) {
1580             if (k.value().contains(QLatin1Char(';'))) {
1581                 m_configProxy.kcfg_external_proxy_profile->addItem(k.key(), k.value());
1582             }
1583         }
1584     }
1585     if (!currentItem.isEmpty()) {
1586         m_configProxy.kcfg_external_proxy_profile->setCurrentIndex(m_configProxy.kcfg_external_proxy_profile->findText(currentItem));
1587     }
1588     m_configProxy.kcfg_external_proxy_profile->blockSignals(false);
1589 }
1590 
loadEncodingProfiles()1591 void KdenliveSettingsDialog::loadEncodingProfiles()
1592 {
1593     KConfig conf(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
1594 
1595     // Load v4l profiles
1596     m_configCapture.kcfg_v4l_profile->blockSignals(true);
1597     QString currentItem = m_configCapture.kcfg_v4l_profile->currentText();
1598     m_configCapture.kcfg_v4l_profile->clear();
1599     KConfigGroup group(&conf, "video4linux");
1600     QMap<QString, QString> values = group.entryMap();
1601     QMapIterator<QString, QString> i(values);
1602     while (i.hasNext()) {
1603         i.next();
1604         if (!i.key().isEmpty()) {
1605             m_configCapture.kcfg_v4l_profile->addItem(i.key(), i.value());
1606         }
1607     }
1608     m_configCapture.kcfg_v4l_profile->blockSignals(false);
1609     if (!currentItem.isEmpty()) {
1610         m_configCapture.kcfg_v4l_profile->setCurrentIndex(m_configCapture.kcfg_v4l_profile->findText(currentItem));
1611     }
1612 
1613     // Load Screen Grab profiles
1614     m_configCapture.kcfg_grab_profile->blockSignals(true);
1615     currentItem = m_configCapture.kcfg_grab_profile->currentText();
1616     m_configCapture.kcfg_grab_profile->clear();
1617     KConfigGroup group2(&conf, "screengrab");
1618     values = group2.entryMap();
1619     QMapIterator<QString, QString> j(values);
1620     while (j.hasNext()) {
1621         j.next();
1622         if (!j.key().isEmpty()) {
1623             m_configCapture.kcfg_grab_profile->addItem(j.key(), j.value());
1624         }
1625     }
1626     m_configCapture.kcfg_grab_profile->blockSignals(false);
1627     if (!currentItem.isEmpty()) {
1628         m_configCapture.kcfg_grab_profile->setCurrentIndex(m_configCapture.kcfg_grab_profile->findText(currentItem));
1629     }
1630 
1631     // Load Decklink profiles
1632     m_configCapture.kcfg_decklink_profile->blockSignals(true);
1633     currentItem = m_configCapture.kcfg_decklink_profile->currentText();
1634     m_configCapture.kcfg_decklink_profile->clear();
1635     KConfigGroup group3(&conf, "decklink");
1636     values = group3.entryMap();
1637     QMapIterator<QString, QString> k(values);
1638     while (k.hasNext()) {
1639         k.next();
1640         if (!k.key().isEmpty()) {
1641             m_configCapture.kcfg_decklink_profile->addItem(k.key(), k.value());
1642         }
1643     }
1644     m_configCapture.kcfg_decklink_profile->blockSignals(false);
1645     if (!currentItem.isEmpty()) {
1646         m_configCapture.kcfg_decklink_profile->setCurrentIndex(m_configCapture.kcfg_decklink_profile->findText(currentItem));
1647     }
1648 
1649     // Load Timeline Preview profiles
1650     m_configProject.kcfg_preview_profile->blockSignals(true);
1651     currentItem = m_configProject.kcfg_preview_profile->currentText();
1652     m_configProject.kcfg_preview_profile->clear();
1653     KConfigGroup group5(&conf, "timelinepreview");
1654     values = group5.entryMap();
1655     m_configProject.kcfg_preview_profile->addItem(i18n("Automatic"));
1656     QMapIterator<QString, QString> l(values);
1657     while (l.hasNext()) {
1658         l.next();
1659         if (!l.key().isEmpty()) {
1660             m_configProject.kcfg_preview_profile->addItem(l.key(), l.value());
1661         }
1662     }
1663     if (!currentItem.isEmpty()) {
1664         m_configProject.kcfg_preview_profile->setCurrentIndex(m_configProject.kcfg_preview_profile->findText(currentItem));
1665     }
1666     m_configProject.kcfg_preview_profile->blockSignals(false);
1667     QString profilestr = m_configProject.kcfg_preview_profile->itemData(m_configProject.kcfg_preview_profile->currentIndex()).toString();
1668     if (profilestr.isEmpty()) {
1669         m_configProject.previewparams->clear();
1670     } else {
1671         m_configProject.previewparams->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0));
1672     }
1673 
1674     // Load Proxy profiles
1675     m_configProxy.kcfg_proxy_profile->blockSignals(true);
1676     currentItem = m_configProxy.kcfg_proxy_profile->currentText();
1677     m_configProxy.kcfg_proxy_profile->clear();
1678     KConfigGroup group4(&conf, "proxy");
1679     values = group4.entryMap();
1680     m_configProxy.kcfg_proxy_profile->addItem(i18n("Automatic"));
1681     QMapIterator<QString, QString> m(values);
1682     while (m.hasNext()) {
1683         m.next();
1684         if (!m.key().isEmpty()) {
1685             m_configProxy.kcfg_proxy_profile->addItem(m.key(), m.value());
1686         }
1687     }
1688     if (!currentItem.isEmpty()) {
1689         m_configProxy.kcfg_proxy_profile->setCurrentIndex(m_configProxy.kcfg_proxy_profile->findText(currentItem));
1690     }
1691     m_configProxy.kcfg_proxy_profile->blockSignals(false);
1692     profilestr = m_configProxy.kcfg_proxy_profile->itemData(m_configProxy.kcfg_proxy_profile->currentIndex()).toString();
1693     if (profilestr.isEmpty()) {
1694         m_configProxy.proxyparams->clear();
1695     } else {
1696         m_configProxy.proxyparams->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0));
1697     }
1698 }
1699 
slotUpdateDecklinkProfile(int ix)1700 void KdenliveSettingsDialog::slotUpdateDecklinkProfile(int ix)
1701 {
1702     if (ix == -1) {
1703         ix = KdenliveSettings::decklink_profile();
1704     } else {
1705         ix = m_configCapture.kcfg_decklink_profile->currentIndex();
1706     }
1707     QString profilestr = m_configCapture.kcfg_decklink_profile->itemData(ix).toString();
1708     if (profilestr.isEmpty()) {
1709         return;
1710     }
1711     m_configCapture.decklink_parameters->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0));
1712     //
1713 }
1714 
slotUpdateV4lProfile(int ix)1715 void KdenliveSettingsDialog::slotUpdateV4lProfile(int ix)
1716 {
1717     if (ix == -1) {
1718         ix = KdenliveSettings::v4l_profile();
1719     } else {
1720         ix = m_configCapture.kcfg_v4l_profile->currentIndex();
1721     }
1722     QString profilestr = m_configCapture.kcfg_v4l_profile->itemData(ix).toString();
1723     if (profilestr.isEmpty()) {
1724         return;
1725     }
1726     m_configCapture.v4l_parameters->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0));
1727     //
1728 }
1729 
slotUpdateGrabProfile(int ix)1730 void KdenliveSettingsDialog::slotUpdateGrabProfile(int ix)
1731 {
1732     if (ix == -1) {
1733         ix = KdenliveSettings::grab_profile();
1734     } else {
1735         ix = m_configCapture.kcfg_grab_profile->currentIndex();
1736     }
1737     QString profilestr = m_configCapture.kcfg_grab_profile->itemData(ix).toString();
1738     if (profilestr.isEmpty()) {
1739         return;
1740     }
1741     m_configCapture.grab_parameters->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0));
1742     //
1743 }
1744 
slotUpdateProxyProfile(int ix)1745 void KdenliveSettingsDialog::slotUpdateProxyProfile(int ix)
1746 {
1747     if (ix == -1) {
1748         ix = KdenliveSettings::proxy_profile();
1749     } else {
1750         ix = m_configProxy.kcfg_proxy_profile->currentIndex();
1751     }
1752     QString profilestr = m_configProxy.kcfg_proxy_profile->itemData(ix).toString();
1753     if (profilestr.isEmpty()) {
1754         return;
1755     }
1756     m_configProxy.proxyparams->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0));
1757 }
1758 
slotUpdatePreviewProfile(int ix)1759 void KdenliveSettingsDialog::slotUpdatePreviewProfile(int ix)
1760 {
1761     if (ix == -1) {
1762         ix = KdenliveSettings::preview_profile();
1763     } else {
1764         ix = m_configProject.kcfg_preview_profile->currentIndex();
1765     }
1766     QString profilestr = m_configProject.kcfg_preview_profile->itemData(ix).toString();
1767     if (profilestr.isEmpty()) {
1768         return;
1769     }
1770     m_configProject.previewparams->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0));
1771 }
1772 
slotEditVideo4LinuxProfile()1773 void KdenliveSettingsDialog::slotEditVideo4LinuxProfile()
1774 {
1775     QString vl4ProfilePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/video4linux");
1776     QPointer<ProfilesDialog> w = new ProfilesDialog(vl4ProfilePath, true);
1777     if (w->exec() == QDialog::Accepted) {
1778         // save and update profile
1779         loadCurrentV4lProfileInfo();
1780     }
1781     delete w;
1782 }
1783 
slotReloadBlackMagic()1784 void KdenliveSettingsDialog::slotReloadBlackMagic()
1785 {
1786     getBlackMagicDeviceList(m_configCapture.kcfg_decklink_capturedevice, true);
1787     if (!getBlackMagicOutputDeviceList(m_configSdl.kcfg_blackmagic_output_device, true)) {
1788         // No blackmagic card found
1789         m_configSdl.kcfg_external_display->setEnabled(false);
1790     }
1791     m_configSdl.kcfg_external_display->setEnabled(KdenliveSettings::decklink_device_found());
1792 }
1793 
checkProfile()1794 void KdenliveSettingsDialog::checkProfile()
1795 {
1796     m_pw->loadProfile(KdenliveSettings::default_profile().isEmpty() ? pCore->getCurrentProfile()->path() : KdenliveSettings::default_profile());
1797 }
1798 
slotReloadShuttleDevices()1799 void KdenliveSettingsDialog::slotReloadShuttleDevices()
1800 {
1801 #ifdef USE_JOGSHUTTLE
1802     QString devDirStr = QStringLiteral("/dev/input/by-id");
1803     QDir devDir(devDirStr);
1804     if (!devDir.exists()) {
1805         devDirStr = QStringLiteral("/dev/input");
1806     }
1807 
1808     QStringList devNamesList;
1809     QStringList devPathList;
1810     m_configShuttle.shuttledevicelist->clear();
1811 
1812     DeviceMap devMap = JogShuttle::enumerateDevices(devDirStr);
1813     DeviceMapIter iter = devMap.begin();
1814     while (iter != devMap.end()) {
1815         m_configShuttle.shuttledevicelist->addItem(iter.key(), iter.value());
1816         devNamesList << iter.key();
1817         devPathList << iter.value();
1818         ++iter;
1819     }
1820     KdenliveSettings::setShuttledevicenames(devNamesList);
1821     KdenliveSettings::setShuttledevicepaths(devPathList);
1822     QTimer::singleShot(200, this, SLOT(slotUpdateShuttleDevice()));
1823 #endif // USE_JOGSHUTTLE
1824 }
1825 
slotUpdateAudioCaptureChannels(int index)1826 void KdenliveSettingsDialog::slotUpdateAudioCaptureChannels(int index)
1827 {
1828     KdenliveSettings::setAudiocapturechannels(m_configCapture.audiocapturechannels->itemData(index).toInt());
1829 }
1830 
slotUpdateAudioCaptureSampleRate(int index)1831 void KdenliveSettingsDialog::slotUpdateAudioCaptureSampleRate(int index)
1832 {
1833     KdenliveSettings::setAudiocapturesamplerate(m_configCapture.audiocapturesamplerate->itemData(index).toInt());
1834 }
1835 
initSpeechPage()1836 void KdenliveSettingsDialog::initSpeechPage()
1837 {
1838     m_voskAction = new QAction(i18n("Install missing dependencies"), this);
1839     m_speechListWidget = new SpeechList(this);
1840     connect(m_speechListWidget, &SpeechList::getDictionary, this, &KdenliveSettingsDialog::getDictionary);
1841     QVBoxLayout *l = new QVBoxLayout(m_configSpeech.list_frame);
1842     l->setContentsMargins(0, 0, 0, 0);
1843     l->addWidget(m_speechListWidget);
1844     m_configSpeech.speech_info->setWordWrap(true);
1845     m_configSpeech.check_vosk->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
1846     m_configSpeech.check_vosk->setToolTip(i18n("Check VOSK installation"));
1847     connect(m_configSpeech.check_vosk, &QPushButton::clicked, this, [this]() {
1848         m_configSpeech.check_vosk->setEnabled(false);
1849         KdenliveSettings::setVosk_found(false);
1850         KdenliveSettings::setVosk_srt_found(false);
1851         checkVoskDependencies();
1852         m_configSpeech.check_vosk->setEnabled(true);
1853     });
1854     connect(this, &KdenliveSettingsDialog::showSpeechMessage, this, &KdenliveSettingsDialog::doShowSpeechMessage);
1855     connect(m_voskAction, &QAction::triggered, this, [this]() {
1856 #ifdef Q_OS_WIN
1857         QString pyExec = QStandardPaths::findExecutable(QStringLiteral("python"));
1858 #else
1859         QString pyExec = QStandardPaths::findExecutable(QStringLiteral("python3"));
1860 #endif
1861         if (pyExec.isEmpty()) {
1862             doShowSpeechMessage(i18n("Please install python3 and pip on your system."), KMessageWidget::Warning);
1863             return;
1864         }
1865         QString speechScript = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("scripts/checkvosk.py"));
1866         if (speechScript.isEmpty()) {
1867             doShowSpeechMessage(i18n("The speech script was not found, check your install."), KMessageWidget::Warning);
1868             return;
1869         }
1870         QProcess checkJob;
1871         if (!KdenliveSettings::vosk_found() || !KdenliveSettings::vosk_srt_found()) {
1872             m_voskAction->setEnabled(false);
1873             doShowSpeechMessage(i18n("Installing modules..."), KMessageWidget::Information);
1874             qApp->processEvents();
1875             checkJob.start(pyExec, {speechScript, QStringLiteral("install")});
1876             checkJob.waitForFinished();
1877             checkVoskDependencies();
1878         } else {
1879             // upgrade
1880             m_voskUpdated = true;
1881             m_voskAction->setEnabled(false);
1882             doShowSpeechMessage(i18n("Updating modules..."), KMessageWidget::Information);
1883             qApp->processEvents();
1884             checkJob.start(pyExec, {speechScript, QStringLiteral("upgrade")});
1885             checkJob.waitForFinished();
1886             m_configSpeech.speech_info->removeAction(m_voskAction);
1887             checkVoskVersion(pyExec);
1888         }
1889     });
1890     checkVoskDependencies();
1891     connect(m_configSpeech.custom_vosk_folder, &QCheckBox::stateChanged, this, [this](int state) {
1892         m_configSpeech.vosk_folder->setEnabled(state != Qt::Unchecked);
1893         if (state == Qt::Unchecked) {
1894             // Clear custom folder
1895             m_configSpeech.vosk_folder->clear();
1896             KdenliveSettings::setVosk_folder_path(QString());
1897             slotParseVoskDictionaries();
1898         }
1899     });
1900     connect(m_configSpeech.vosk_folder, &KUrlRequester::urlSelected, this, [this](QUrl url) {
1901         KdenliveSettings::setVosk_folder_path(url.toLocalFile());
1902         slotParseVoskDictionaries();
1903     });
1904     m_configSpeech.models_url->setText(i18n("Download speech models from: <a href=\"https://alphacephei.com/vosk/models\">https://alphacephei.com/vosk/models</a>"));
1905     connect(m_configSpeech.models_url, &QLabel::linkActivated, this, [&](const QString &contents) {
1906         qDebug()<<"=== LINK CLICKED: "<<contents;
1907 #if KIO_VERSION > QT_VERSION_CHECK(5, 70, 0)
1908         auto *job = new KIO::OpenUrlJob(QUrl(contents));
1909         job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
1910         // methods like setRunExecutables, setSuggestedFilename, setEnableExternalBrowser, setFollowRedirections
1911         // exist in both classes
1912         job->start();
1913 #else
1914         new KRun(QUrl(contents), this);
1915 #endif
1916     });
1917     m_configSpeech.button_add->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
1918     m_configSpeech.button_delete->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
1919     m_configSpeech.button_add->setToolTip(i18n("Add a new speech model from an archive file"));
1920     m_configSpeech.button_delete->setToolTip(i18n("Delete the selected speech model"));
1921     connect(m_configSpeech.button_add, &QToolButton::clicked, this, [this] () {
1922         this->getDictionary();
1923     });
1924     connect(m_configSpeech.button_delete, &QToolButton::clicked, this, &KdenliveSettingsDialog::removeDictionary);
1925     connect(this, &KdenliveSettingsDialog::parseDictionaries, this, &KdenliveSettingsDialog::slotParseVoskDictionaries);
1926     slotParseVoskDictionaries();
1927 }
1928 
checkVoskDependencies()1929 void KdenliveSettingsDialog::checkVoskDependencies()
1930 {
1931 #ifdef Q_OS_WIN
1932     QString pyExec = QStandardPaths::findExecutable(QStringLiteral("python"));
1933     QString pip3Exec = QStandardPaths::findExecutable(QStringLiteral("pip"));
1934 #else
1935     QString pyExec = QStandardPaths::findExecutable(QStringLiteral("python3"));
1936     QString pip3Exec = QStandardPaths::findExecutable(QStringLiteral("pip3"));
1937 #endif
1938     if (pyExec.isEmpty()) {
1939         doShowSpeechMessage(i18n("Cannot find python3, please install it on your system."), KMessageWidget::Warning);
1940         return;
1941     } else if (!KdenliveSettings::vosk_found() || !KdenliveSettings::vosk_srt_found()) {
1942         QProcess checkJob;
1943         QString speechScript = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("scripts/checkvosk.py"));
1944         if (speechScript.isEmpty()) {
1945             doShowSpeechMessage(i18n("The speech script was not found, check your install."), KMessageWidget::Warning);
1946             return;
1947         } else {
1948             m_configSpeech.speech_info->removeAction(m_voskAction);
1949             checkJob.start(pyExec, {speechScript});
1950             checkJob.waitForFinished();
1951             QString output = checkJob.readAllStandardOutput();
1952             QString missingModules;
1953             if (!output.contains(QLatin1String("vosk"))) {
1954                 KdenliveSettings::setVosk_found(true);
1955             } else {
1956                 KdenliveSettings::setVosk_found(false);
1957                 missingModules = i18n("The VOSK python module is required for speech features. ");
1958             }
1959             if (!output.contains(QLatin1String("srt"))) {
1960                 KdenliveSettings::setVosk_srt_found(true);
1961             } else {
1962                 KdenliveSettings::setVosk_srt_found(false);
1963                 missingModules.append(i18n("The SRT python module is required for automated subtitling."));
1964             }
1965             if (!missingModules.isEmpty()) {
1966                 m_voskAction->setEnabled(true);
1967                 m_voskAction->setText(i18n("Install missing dependencies"));
1968                 m_configSpeech.speech_info->addAction(m_voskAction);
1969                 doShowSpeechMessage(missingModules, KMessageWidget::Warning);
1970             } else {
1971                 if (m_speechListWidget->count() == 0) {
1972                     doShowSpeechMessage(i18n("Please add a speech model."), KMessageWidget::Information);
1973                 } else if (!pip3Exec.isEmpty()) {
1974                     if (!m_voskUpdated) {
1975                         // only allow upgrading python modules once
1976                         m_voskAction->setText(i18n("Check for update"));
1977                         m_configSpeech.speech_info->addAction(m_voskAction);
1978                     }
1979                     QtConcurrent::run(this, &KdenliveSettingsDialog::checkVoskVersion, pyExec);
1980                 }
1981             }
1982             emit pCore->updateVoskAvailability();
1983         }
1984     } else {
1985         if (m_speechListWidget->count() == 0) {
1986             doShowSpeechMessage(i18n("Please add a speech model."), KMessageWidget::Information);
1987         } else if (!pip3Exec.isEmpty()) {
1988             if (!m_voskUpdated) {
1989                 // only allow upgrading python modules once
1990                 m_voskAction->setText(i18n("Check for update"));
1991                 m_configSpeech.speech_info->addAction(m_voskAction);
1992             }
1993             QtConcurrent::run(this, &KdenliveSettingsDialog::checkVoskVersion, pyExec);
1994         }
1995     }
1996 }
1997 
checkVoskVersion(const QString pyExec)1998 void KdenliveSettingsDialog::checkVoskVersion(const QString pyExec)
1999 {
2000     emit showSpeechMessage(i18n("Checking configuration..."), KMessageWidget::Information);
2001     QString speechScript = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("scripts/checkvosk.py"));
2002     QProcess checkJob;
2003     checkJob.start(pyExec, {speechScript, QStringLiteral("check")});
2004     checkJob.waitForFinished();
2005     QString message;
2006     QString output = checkJob.readAllStandardOutput();
2007     QStringList versions = output.split(QStringLiteral("Version: "));
2008     if (versions.count() >= 3) {
2009         bool voskFirst = versions.takeFirst().contains(QLatin1String("vosk"));
2010         QString voskVersion;
2011         QString srtVersion;
2012         QString cut = versions.takeFirst();
2013         if (voskFirst) {
2014             voskVersion = cut.simplified().section(QLatin1Char(' '), 0, 0);
2015             cut = versions.takeFirst();
2016             srtVersion = cut.simplified().section(QLatin1Char(' '), 0, 0);
2017         } else {
2018             srtVersion = cut.simplified().section(QLatin1Char(' '), 0, 0);
2019             cut = versions.takeFirst();
2020             voskVersion = cut.simplified().section(QLatin1Char(' '), 0, 0);
2021         }
2022         message = i18n("Speech to text is configured. Vosk %1, Srt %2", voskVersion, srtVersion);
2023     } else {
2024         message = i18n("Speech to text is properly configured.");
2025     }
2026     emit showSpeechMessage(message, KMessageWidget::Positive);
2027 }
2028 
doShowSpeechMessage(const QString & message,int messageType)2029 void KdenliveSettingsDialog::doShowSpeechMessage(const QString &message, int messageType)
2030 {
2031     m_configSpeech.speech_info->setMessageType(static_cast<KMessageWidget::MessageType>(messageType));
2032     m_configSpeech.speech_info->setText(message);
2033     m_configSpeech.speech_info->animatedShow();
2034 }
2035 
getDictionary(const QUrl sourceUrl)2036 void KdenliveSettingsDialog::getDictionary(const QUrl sourceUrl)
2037 {
2038     QUrl url = KUrlRequesterDialog::getUrl(sourceUrl, this, i18n("Enter url for the new dictionary"));
2039     if (url.isEmpty()) {
2040         return;
2041     }
2042     if (!url.isLocalFile()) {
2043         KIO::FileCopyJob *copyjob = KIO::file_copy(url, QUrl::fromLocalFile(QDir::temp().absoluteFilePath(url.fileName())));
2044         doShowSpeechMessage(i18n("Downloading model..."), KMessageWidget::Information);
2045         connect(copyjob, &KIO::FileCopyJob::result, this, &KdenliveSettingsDialog::downloadModelFinished);
2046     } else {
2047         processArchive(url.toLocalFile());
2048     }
2049 
2050 }
2051 
removeDictionary()2052 void KdenliveSettingsDialog::removeDictionary()
2053 {
2054     if (!KdenliveSettings::vosk_folder_path().isEmpty()) {
2055         doShowSpeechMessage(i18n("We do not allow deleting custom folder models, please do it manually."), KMessageWidget::Warning);
2056         return;
2057     }
2058     QString modelDirectory = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
2059     QDir dir(modelDirectory);
2060     if (!dir.cd(QStringLiteral("speechmodels"))) {
2061         doShowSpeechMessage(i18n("Cannot access dictionary folder."), KMessageWidget::Warning);
2062         return;
2063     }
2064 
2065     if (!m_speechListWidget->currentItem()) {
2066         return;
2067     }
2068     QString currentModel = m_speechListWidget->currentItem()->text();
2069     if (!currentModel.isEmpty() && dir.cd(currentModel)) {
2070         if (KMessageBox::questionYesNo(this, i18n("Delete folder:\n%1", dir.absolutePath())) == KMessageBox::Yes) {
2071             // Make sure we don't accidentally delete a folder that is not ours
2072             if (dir.absolutePath().contains(QLatin1String("speechmodels"))) {
2073                 dir.removeRecursively();
2074                 slotParseVoskDictionaries();
2075             }
2076         }
2077     }
2078 }
2079 
downloadModelFinished(KJob * job)2080 void KdenliveSettingsDialog::downloadModelFinished(KJob* job)
2081 {
2082     qDebug()<<"=== DOWNLOAD FINISHED!!";
2083     if (job->error() == 0 || job->error() == 112) {
2084         qDebug()<<"=== NO ERROR ON DWNLD!!";
2085         auto *jb = static_cast<KIO::FileCopyJob*>(job);
2086         if (jb) {
2087             qDebug()<<"=== JOB FOUND!!";
2088             QString archiveFile = jb->destUrl().toLocalFile();
2089             processArchive(archiveFile);
2090         } else {
2091             qDebug()<<"=== JOB NOT FOUND!!";
2092             m_configSpeech.speech_info->setMessageType(KMessageWidget::Warning);
2093             m_configSpeech.speech_info->setText(i18n("Download error"));
2094         }
2095     } else {
2096         qDebug()<<"=== GOT JOB ERROR: "<<job->error();
2097         m_configSpeech.speech_info->setMessageType(KMessageWidget::Warning);
2098         m_configSpeech.speech_info->setText(i18n("Download error %1", job->errorString()));
2099     }
2100 }
2101 
processArchive(const QString archiveFile)2102 void KdenliveSettingsDialog::processArchive(const QString archiveFile)
2103 {
2104     QMimeDatabase db;
2105     QMimeType type = db.mimeTypeForFile(archiveFile);
2106     std::unique_ptr<KArchive> archive;
2107     if (type.inherits(QStringLiteral("application/zip"))) {
2108         archive = std::make_unique<KZip>(archiveFile);
2109     } else {
2110         archive = std::make_unique<KTar>(archiveFile);
2111     }
2112     QString modelDirectory = KdenliveSettings::vosk_folder_path();
2113     QDir dir;
2114     if (modelDirectory.isEmpty()) {
2115         modelDirectory = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
2116         dir = QDir(modelDirectory);
2117         dir.mkdir(QStringLiteral("speechmodels"));
2118         if (!dir.cd(QStringLiteral("speechmodels"))) {
2119             doShowSpeechMessage(i18n("Cannot access dictionary folder."), KMessageWidget::Warning);
2120             return;
2121         }
2122     } else {
2123         dir = QDir(modelDirectory);
2124     }
2125     if (archive->open(QIODevice::ReadOnly)) {
2126         doShowSpeechMessage(i18n("Extracting archive..."), KMessageWidget::Information);
2127         const KArchiveDirectory *archiveDir = archive->directory();
2128         if (!archiveDir->copyTo(dir.absolutePath())) {
2129             qDebug()<<"=== Error extracting archive!!";
2130         } else {
2131             QFile::remove(archiveFile);
2132             emit parseDictionaries();
2133             doShowSpeechMessage(i18n("New dictionary installed."), KMessageWidget::Positive);
2134         }
2135     } else {
2136         // Test if it is a folder
2137         QDir dir(archiveFile);
2138         if (dir.exists()) {
2139 
2140         }
2141         qDebug()<<"=== CANNOT OPEN ARCHIVE!!";
2142     }
2143 }
2144 
slotParseVoskDictionaries()2145 void KdenliveSettingsDialog::slotParseVoskDictionaries()
2146 {
2147     m_speechListWidget->clear();
2148     QString modelDirectory = KdenliveSettings::vosk_folder_path();
2149     QDir dir;
2150     if (modelDirectory.isEmpty()) {
2151         modelDirectory = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
2152         dir = QDir(modelDirectory);
2153         if (!dir.cd(QStringLiteral("speechmodels"))) {
2154             qDebug()<<"=== /// CANNOT ACCESS SPEECH DICTIONARIES FOLDER";
2155             emit pCore->voskModelUpdate({});
2156             return;
2157         }
2158     } else {
2159         dir = QDir(modelDirectory);
2160     }
2161     QStringList dicts = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
2162     QStringList final;
2163     for (auto &d : dicts) {
2164         QDir sub(dir.absoluteFilePath(d));
2165         if (sub.exists(QStringLiteral("mfcc.conf")) || (sub.exists(QStringLiteral("conf/mfcc.conf")))) {
2166             final << d;
2167         }
2168     }
2169     m_speechListWidget->addItems(final);
2170     if (!KdenliveSettings::vosk_folder_path().isEmpty()) {
2171         m_configSpeech.custom_vosk_folder->setChecked(true);
2172         m_configSpeech.vosk_folder->setUrl(QUrl::fromLocalFile(KdenliveSettings::vosk_folder_path()));
2173     }
2174     if (!final.isEmpty() && KdenliveSettings::vosk_found() && KdenliveSettings::vosk_srt_found()) {
2175         m_configSpeech.speech_info->animatedHide();
2176     } else if (final.isEmpty()) {
2177         doShowSpeechMessage(i18n("Please add a speech model."), KMessageWidget::Information);
2178     }
2179     emit pCore->voskModelUpdate(final);
2180 }
2181 
2182