File indexing completed on 2024-12-08 04:27:21

0001 /*
0002     SPDX-FileCopyrightText: 2007 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003 
0004 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "mainwindow.h"
0008 #include "assets/assetpanel.hpp"
0009 #include "audiomixer/mixermanager.hpp"
0010 #include "bin/clipcreator.hpp"
0011 #include "bin/generators/generators.h"
0012 #include "bin/mediabrowser.h"
0013 #include "bin/model/subtitlemodel.hpp"
0014 #include "bin/projectclip.h"
0015 #include "bin/projectfolder.h"
0016 #include "bin/projectitemmodel.h"
0017 #include "core.h"
0018 #include "dialogs/clipcreationdialog.h"
0019 #include "dialogs/clipjobmanager.h"
0020 #include "dialogs/kdenlivesettingsdialog.h"
0021 #include "dialogs/renderwidget.h"
0022 #include "dialogs/subtitleedit.h"
0023 #include "dialogs/wizard.h"
0024 #include "doc/docundostack.hpp"
0025 #include "doc/kdenlivedoc.h"
0026 #include "docktitlebarmanager.h"
0027 #include "effects/effectbasket.h"
0028 #include "effects/effectlist/view/effectlistwidget.hpp"
0029 #include "jobs/audiolevelstask.h"
0030 #include "jobs/customjobtask.h"
0031 #include "jobs/scenesplittask.h"
0032 #include "jobs/speedtask.h"
0033 #include "jobs/stabilizetask.h"
0034 #include "jobs/transcodetask.h"
0035 #include "kdenlivesettings.h"
0036 #include "layoutmanagement.h"
0037 #include "library/librarywidget.h"
0038 #ifdef NODBUS
0039 #include "render/renderserver.h"
0040 #else
0041 #include "mainwindowadaptor.h"
0042 #endif
0043 #include "dialogs/textbasededit.h"
0044 #include "dialogs/timeremap.h"
0045 #include "lib/localeHandling.h"
0046 #include "mltconnection.h"
0047 #include "mltcontroller/clipcontroller.h"
0048 #include "monitor/monitor.h"
0049 #include "monitor/monitormanager.h"
0050 #include "monitor/scopes/audiographspectrum.h"
0051 #include "onlineresources/resourcewidget.hpp"
0052 #include "profiles/profilemodel.hpp"
0053 #include "profiles/profilerepository.hpp"
0054 #include "project/cliptranscode.h"
0055 #include "project/dialogs/archivewidget.h"
0056 #include "project/dialogs/guideslist.h"
0057 #include "project/dialogs/projectsettings.h"
0058 #include "project/dialogs/temporarydata.h"
0059 #include "project/projectmanager.h"
0060 #include "scopes/scopemanager.h"
0061 #include "timeline2/view/timelinecontroller.h"
0062 #include "timeline2/view/timelinetabs.hpp"
0063 #include "timeline2/view/timelinewidget.h"
0064 #include "titler/titlewidget.h"
0065 #include "transitions/transitionlist/view/transitionlistwidget.hpp"
0066 #include "transitions/transitionsrepository.hpp"
0067 #include "utils/thememanager.h"
0068 #include "widgets/progressbutton.h"
0069 #include <config-kdenlive.h>
0070 
0071 #ifdef USE_JOGSHUTTLE
0072 #include "jogshuttle/jogmanager.h"
0073 #endif
0074 
0075 #include "knewstuff_version.h"
0076 #include "kwidgetsaddons_version.h"
0077 #include "utils/KMessageBox_KdenliveCompat.h"
0078 #include <KAboutData>
0079 #include <KActionCollection>
0080 #include <KActionMenu>
0081 #include <KColorScheme>
0082 #include <KConfigDialog>
0083 #include <KCoreAddons>
0084 #include <KDualAction>
0085 #include <KEditToolBar>
0086 #include <KIconTheme>
0087 #include <KLocalizedString>
0088 #include <KMessageBox>
0089 #if KNEWSTUFF_VERSION >= QT_VERSION_CHECK(5, 240, 0)
0090 #include <KNSWidgets/Dialog>
0091 #else
0092 #include <KNS3/QtQuickDialogWrapper>
0093 #endif
0094 #include <KNotifyConfigWidget>
0095 #include <KRecentDirs>
0096 #include <KShortcutsDialog>
0097 #include <KStandardAction>
0098 #include <KToggleFullScreenAction>
0099 #include <KToolBar>
0100 #include <KXMLGUIFactory>
0101 
0102 #include <KConfigGroup>
0103 #include <QAction>
0104 #include <QClipboard>
0105 #include <QDesktopServices>
0106 #include <QDialogButtonBox>
0107 #include <QFileDialog>
0108 #include <QMenu>
0109 #include <QMenuBar>
0110 #include <QPushButton>
0111 #include <QScreen>
0112 #include <QStandardPaths>
0113 #include <QStatusBar>
0114 #include <QStyleFactory>
0115 #include <QUndoGroup>
0116 #include <QVBoxLayout>
0117 
0118 static const char version[] = KDENLIVE_VERSION;
0119 namespace Mlt {
0120 class Producer;
0121 }
0122 
0123 QMap<QString, QImage> MainWindow::m_lumacache;
0124 QMap<QString, QStringList> MainWindow::m_lumaFiles;
0125 
0126 /*static bool sortByNames(const QPair<QString, QAction *> &a, const QPair<QString, QAction*> &b)
0127 {
0128     return a.first < b.first;
0129 }*/
0130 
0131 // determine the default KDE style as defined BY THE USER
0132 // (as opposed to whatever style KDE considers default)
0133 static QString defaultStyle(const char *fallback = nullptr)
0134 {
0135     KSharedConfigPtr kdeGlobals = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::NoGlobals);
0136     KConfigGroup cg(kdeGlobals, "KDE");
0137     return cg.readEntry("widgetStyle", fallback);
0138 }
0139 
0140 MainWindow::MainWindow(QWidget *parent)
0141     : KXmlGuiWindow(parent)
0142     , m_activeTool(ToolType::SelectTool)
0143     , m_mousePosition(0)
0144     , m_effectBasket(nullptr)
0145 {
0146     // Init all action categories that are used by other parts of the software
0147     // before we call MainWindow::init and therefore can't be initialized there
0148     KActionCategory *category = new KActionCategory(i18n("Monitor"), actionCollection());
0149     kdenliveCategoryMap.insert(QStringLiteral("monitor"), category);
0150     category = new KActionCategory(i18n("Add Clip"), actionCollection());
0151     kdenliveCategoryMap.insert(QStringLiteral("addclip"), category);
0152     category = new KActionCategory(i18n("Navigation and Playback"), actionCollection());
0153     kdenliveCategoryMap.insert(QStringLiteral("navandplayback"), category);
0154     category = new KActionCategory(i18n("Bin Tags"), actionCollection());
0155     kdenliveCategoryMap.insert(QStringLiteral("bintags"), category);
0156 }
0157 
0158 void MainWindow::init(const QString &mltPath)
0159 {
0160     QString desktopStyle = QApplication::style()->objectName();
0161     // Load themes
0162     auto themeManager = new ThemeManager(actionCollection());
0163     actionCollection()->addAction(QStringLiteral("themes_menu"), themeManager->menu());
0164     connect(themeManager, &ThemeManager::themeChanged, this, &MainWindow::slotThemeChanged);
0165     Q_EMIT pCore->updatePalette();
0166 
0167     if (!KdenliveSettings::widgetstyle().isEmpty() && QString::compare(desktopStyle, KdenliveSettings::widgetstyle(), Qt::CaseInsensitive) != 0) {
0168         // User wants a custom widget style, init
0169         doChangeStyle();
0170     }
0171 
0172     // Widget themes for non KDE users
0173     KActionMenu *stylesAction = new KActionMenu(i18n("Style"), this);
0174     auto *stylesGroup = new QActionGroup(stylesAction);
0175 
0176     // GTK theme does not work well with Kdenlive, and does not support color theming, so avoid it
0177     QStringList availableStyles = QStyleFactory::keys();
0178     if (KdenliveSettings::widgetstyle().isEmpty()) {
0179         // First run
0180         QStringList incompatibleStyles = {QStringLiteral("GTK+"), QStringLiteral("windowsvista"), QStringLiteral("Windows"), QStringLiteral("macintosh")};
0181 
0182         if (incompatibleStyles.contains(desktopStyle, Qt::CaseInsensitive)) {
0183             if (availableStyles.contains(QStringLiteral("breeze"), Qt::CaseInsensitive)) {
0184                 // Auto switch to Breeze theme
0185                 KdenliveSettings::setWidgetstyle(QStringLiteral("Breeze"));
0186                 QApplication::setStyle(QStyleFactory::create(QStringLiteral("Breeze")));
0187             } else if (availableStyles.contains(QStringLiteral("fusion"), Qt::CaseInsensitive)) {
0188                 KdenliveSettings::setWidgetstyle(QStringLiteral("Fusion"));
0189                 QApplication::setStyle(QStyleFactory::create(QStringLiteral("Fusion")));
0190             }
0191         } else {
0192             KdenliveSettings::setWidgetstyle(QStringLiteral("Default"));
0193         }
0194     }
0195 
0196     // Add default style action
0197     QAction *defaultStyle = new QAction(i18n("Default"), stylesGroup);
0198     defaultStyle->setData(QStringLiteral("Default"));
0199     defaultStyle->setCheckable(true);
0200     stylesAction->addAction(defaultStyle);
0201     if (KdenliveSettings::widgetstyle() == QLatin1String("Default") || KdenliveSettings::widgetstyle().isEmpty()) {
0202         defaultStyle->setChecked(true);
0203     }
0204 
0205     for (const QString &style : qAsConst(availableStyles)) {
0206         auto *a = new QAction(style, stylesGroup);
0207         a->setCheckable(true);
0208         a->setData(style);
0209         if (KdenliveSettings::widgetstyle() == style) {
0210             a->setChecked(true);
0211         }
0212         stylesAction->addAction(a);
0213     }
0214     connect(stylesGroup, &QActionGroup::triggered, this, &MainWindow::slotChangeStyle);
0215     // QIcon::setThemeSearchPaths(QStringList() <<QStringLiteral(":/icons/"));
0216 #ifdef NODBUS
0217     new RenderServer(this);
0218 #else
0219     new RenderingAdaptor(this);
0220 #endif
0221     QString defaultProfile = KdenliveSettings::default_profile();
0222 
0223     // Initialise MLT connection
0224     MltConnection::construct(mltPath);
0225     pCore->setCurrentProfile(defaultProfile.isEmpty() ? ProjectManager::getDefaultProjectFormat() : defaultProfile);
0226     m_commandStack = new QUndoGroup();
0227 
0228     // If using a custom profile, make sure the file exists or fallback to default
0229     QString currentProfilePath = pCore->getCurrentProfile()->path();
0230     if (currentProfilePath.startsWith(QLatin1Char('/')) && !QFile::exists(currentProfilePath)) {
0231         KMessageBox::error(this, i18n("Cannot find your default profile, switching to ATSC 1080p 25"));
0232         pCore->setCurrentProfile(QStringLiteral("atsc_1080p_25"));
0233         KdenliveSettings::setDefault_profile(QStringLiteral("atsc_1080p_25"));
0234     }
0235 
0236     // Disable movit until it's stable
0237     m_gpuAllowed = false;
0238     KdenliveSettings::setGpu_accel(false);
0239 
0240     // m_gpuAllowed = EffectsRepository::get()->hasInternalEffect(QStringLiteral("glsl.manager"));
0241 
0242     m_shortcutRemoveFocus = new QShortcut(QKeySequence(QStringLiteral("Esc")), this);
0243     connect(m_shortcutRemoveFocus, &QShortcut::activated, this, &MainWindow::slotRemoveFocus);
0244 
0245     /// Add Widgets
0246     setDockOptions(dockOptions() | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks);
0247     setDockOptions(dockOptions() | QMainWindow::GroupedDragging);
0248     setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::TabPosition(KdenliveSettings::tabposition()));
0249     m_timelineToolBar = toolBar(QStringLiteral("timelineToolBar"));
0250     m_timelineToolBarContainer = new TimelineContainer(this);
0251     auto *ctnLay = new QVBoxLayout;
0252     ctnLay->setSpacing(0);
0253     ctnLay->setContentsMargins(0, 0, 0, 0);
0254     m_timelineToolBarContainer->setLayout(ctnLay);
0255     QFrame *topFrame = new QFrame(this);
0256     topFrame->setFrameShape(QFrame::HLine);
0257     topFrame->setFixedHeight(1);
0258     topFrame->setLineWidth(1);
0259     connect(this, &MainWindow::showTimelineFocus, this, [topFrame](bool focus, bool highlight) {
0260         if (focus) {
0261             KColorScheme scheme(QApplication::palette().currentColorGroup(), KColorScheme::Tooltip);
0262             if (highlight) {
0263                 QColor col = scheme.decoration(KColorScheme::HoverColor).color();
0264                 topFrame->setStyleSheet(QString("QFrame {border: 1px solid rgba(%1,%2,%3,70)}").arg(col.red()).arg(col.green()).arg(col.blue()));
0265             } else {
0266                 QColor col = scheme.decoration(KColorScheme::FocusColor).color();
0267                 topFrame->setStyleSheet(QString("QFrame {border: 1px solid rgba(%1,%2,%3,100)}").arg(col.red()).arg(col.green()).arg(col.blue()));
0268             }
0269         } else {
0270             topFrame->setStyleSheet(QString());
0271         }
0272     });
0273     ctnLay->addWidget(topFrame);
0274     ctnLay->addWidget(m_timelineToolBar);
0275     KSharedConfigPtr config = KSharedConfig::openConfig();
0276     KConfigGroup mainConfig(config, QStringLiteral("MainWindow"));
0277     KConfigGroup tbGroup(&mainConfig, QStringLiteral("Toolbar timelineToolBar"));
0278     m_timelineToolBar->applySettings(tbGroup);
0279     QFrame *fr = new QFrame(this);
0280     fr->setFrameShape(QFrame::HLine);
0281     fr->setMaximumHeight(1);
0282     fr->setLineWidth(1);
0283     ctnLay->addWidget(fr);
0284     setupActions();
0285     auto *layoutManager = new LayoutManagement(this);
0286     pCore->bin()->setupMenu();
0287     pCore->buildDocks();
0288 
0289     QDockWidget *libraryDock = addDock(i18n("Library"), QStringLiteral("library"), pCore->library());
0290     QDockWidget *subtitlesDock = addDock(i18n("Subtitles"), QStringLiteral("Subtitles"), pCore->subtitleWidget());
0291     QDockWidget *textEditingDock = addDock(i18n("Speech Editor"), QStringLiteral("textedit"), pCore->textEditWidget());
0292     QDockWidget *timeRemapDock = addDock(i18n("Time Remapping"), QStringLiteral("timeremap"), pCore->timeRemapWidget());
0293     QDockWidget *guidesDock = addDock(i18n("Guides"), QStringLiteral("guides"), pCore->guidesList());
0294     connect(pCore.get(), &Core::remapClip, this, [&, timeRemapDock](int id) {
0295         if (id > -1) {
0296             timeRemapDock->show();
0297             timeRemapDock->raise();
0298         }
0299         pCore->timeRemapWidget()->selectedClip(id, pCore->currentTimelineId());
0300     });
0301     m_clipMonitor = new Monitor(Kdenlive::ClipMonitor, pCore->monitorManager(), this);
0302     pCore->bin()->setMonitor(m_clipMonitor);
0303     connect(m_clipMonitor, &Monitor::addMarker, this, &MainWindow::slotAddMarkerGuideQuickly);
0304     connect(m_clipMonitor, &Monitor::deleteMarker, this, &MainWindow::slotDeleteClipMarker);
0305     connect(m_clipMonitor, &Monitor::seekToPreviousSnap, this, &MainWindow::slotSnapRewind);
0306     connect(m_clipMonitor, &Monitor::seekToNextSnap, this, &MainWindow::slotSnapForward);
0307     connect(m_clipMonitor, &Monitor::passKeyPress, this, &MainWindow::triggerKey);
0308 
0309     m_projectMonitor = new Monitor(Kdenlive::ProjectMonitor, pCore->monitorManager(), this);
0310     connect(m_projectMonitor, &Monitor::passKeyPress, this, &MainWindow::triggerKey);
0311     connect(m_projectMonitor, &Monitor::addMarker, this, &MainWindow::slotAddMarkerGuideQuickly);
0312     connect(m_projectMonitor, &Monitor::deleteMarker, this, &MainWindow::slotDeleteGuide);
0313     connect(m_projectMonitor, &Monitor::seekToPreviousSnap, this, &MainWindow::slotSnapRewind);
0314     connect(m_projectMonitor, &Monitor::seekToNextSnap, this, &MainWindow::slotSnapForward);
0315     connect(m_loopClip, &QAction::triggered, this, [&]() {
0316         QPoint inOut = getCurrentTimeline()->controller()->selectionInOut();
0317         m_projectMonitor->slotLoopClip(inOut);
0318     });
0319     installEventFilter(this);
0320     pCore->monitorManager()->initMonitors(m_clipMonitor, m_projectMonitor);
0321 
0322     m_timelineTabs = new TimelineTabs(this);
0323     ctnLay->addWidget(m_timelineTabs);
0324     setCentralWidget(m_timelineToolBarContainer);
0325 
0326     // Screen grab widget
0327     QWidget *grabWidget = new QWidget(this);
0328     auto *grabLayout = new QVBoxLayout;
0329     grabWidget->setLayout(grabLayout);
0330     auto *recToolbar = new QToolBar(grabWidget);
0331     grabLayout->addWidget(recToolbar);
0332     grabLayout->addStretch(10);
0333     // Check number of monitors for FFmpeg screen capture
0334     int screens = QApplication::screens().count();
0335     if (screens > 1) {
0336         auto *screenCombo = new QComboBox(recToolbar);
0337         for (int ix = 0; ix < screens; ix++) {
0338             screenCombo->addItem(i18n("Monitor %1", ix));
0339         }
0340         connect(screenCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), m_clipMonitor, &Monitor::slotSetScreen);
0341         recToolbar->addWidget(screenCombo);
0342         // Update screen grab monitor choice in case we changed from fullscreen
0343         screenCombo->setEnabled(KdenliveSettings::grab_capture_type() == 0);
0344     }
0345     QAction *recAction = m_clipMonitor->recAction();
0346     addAction(QStringLiteral("screengrab_record"), recAction);
0347     recToolbar->addAction(recAction);
0348     QAction *recConfig = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure Recording"), this);
0349     recToolbar->addAction(recConfig);
0350     connect(recConfig, &QAction::triggered, [&]() { Q_EMIT pCore->showConfigDialog(Kdenlive::PageCapture, 0); });
0351     QDockWidget *screenGrabDock = addDock(i18n("Screen Grab"), QStringLiteral("screengrab"), grabWidget);
0352 
0353     // Sequence actions
0354     QAction *nextSequence = new QAction(QIcon::fromTheme(QStringLiteral("go-next")), i18n("Switch to next Sequence"), this);
0355     addAction(QStringLiteral("sequence_next"), nextSequence, QKeySequence::NextChild);
0356     connect(nextSequence, &QAction::triggered, m_timelineTabs, &TimelineTabs::slotNextSequence);
0357 
0358     QAction *prevSequence = new QAction(QIcon::fromTheme(QStringLiteral("go-previous")), i18n("Switch to previous Sequence"), this);
0359     addAction(QStringLiteral("sequence_previous"), prevSequence, QKeySequence::PreviousChild);
0360     connect(prevSequence, &QAction::triggered, m_timelineTabs, &TimelineTabs::slotPreviousSequence);
0361 
0362     // Audio spectrum scope
0363     m_audioSpectrum = new AudioGraphSpectrum(pCore->monitorManager());
0364     QDockWidget *spectrumDock = addDock(i18n("Audio Spectrum"), QStringLiteral("audiospectrum"), m_audioSpectrum);
0365     connect(spectrumDock, &QDockWidget::visibilityChanged, this, [&](bool visible) { m_audioSpectrum->dockVisible(visible); });
0366 
0367     // Project bin
0368     m_projectBinDock = addDock(i18n("Project Bin"), QStringLiteral("project_bin"), pCore->bin());
0369 
0370     // Media browser widget
0371     QDockWidget *clipDockWidget = addDock(i18n("Media Browser"), QStringLiteral("bin_clip"), pCore->mediaBrowser());
0372 
0373     // Online resources widget
0374     auto *onlineResources = new ResourceWidget(this);
0375     m_onlineResourcesDock = addDock(i18n("Online Resources"), QStringLiteral("onlineresources"), onlineResources);
0376     connect(onlineResources, &ResourceWidget::previewClip, this, [&](const QString &path, const QString &title) {
0377         m_clipMonitor->slotPreviewResource(path, title);
0378         m_clipMonitorDock->show();
0379         m_clipMonitorDock->raise();
0380     });
0381 
0382     connect(onlineResources, &ResourceWidget::addClip, this, &MainWindow::slotAddProjectClip);
0383     connect(onlineResources, &ResourceWidget::addLicenseInfo, this, &MainWindow::slotAddTextNote);
0384 
0385     // Close library and audiospectrum and others on first run
0386     screenGrabDock->close();
0387     libraryDock->close();
0388     subtitlesDock->close();
0389     textEditingDock->close();
0390     timeRemapDock->close();
0391     spectrumDock->close();
0392     clipDockWidget->close();
0393     guidesDock->close();
0394     m_onlineResourcesDock->close();
0395 
0396     m_effectStackDock = addDock(i18n("Effect/Composition Stack"), QStringLiteral("effect_stack"), m_assetPanel);
0397     connect(m_assetPanel, &AssetPanel::doSplitEffect, m_projectMonitor, &Monitor::slotSwitchCompare);
0398     connect(m_assetPanel, &AssetPanel::doSplitBinEffect, m_clipMonitor, &Monitor::slotSwitchCompare);
0399     connect(m_assetPanel, &AssetPanel::switchCurrentComposition, this,
0400             [&](int cid, const QString &compositionId) { getCurrentTimeline()->model()->switchComposition(cid, compositionId); });
0401     connect(pCore->bin(), &Bin::updateTabName, m_timelineTabs, &TimelineTabs::renameTab);
0402     connect(m_timelineTabs, &TimelineTabs::showMixModel, m_assetPanel, &AssetPanel::showMix);
0403     connect(m_timelineTabs, &TimelineTabs::showTransitionModel, this, [&](int tid, std::shared_ptr<AssetParameterModel> model) {
0404         m_assetPanel->showTransition(tid, model);
0405         m_effectStackDock->raise();
0406     });
0407     connect(m_timelineTabs, &TimelineTabs::showItemEffectStack, this,
0408             [&](const QString &clipName, std::shared_ptr<EffectStackModel> model, QSize size, bool showKeyframes) {
0409                 if (model == nullptr && m_assetPanel->effectStackOwner().type == KdenliveObjectType::BinClip) {
0410                     // Effect stask is currently displaying a bin clip, do nothing
0411                     return;
0412                 }
0413                 m_assetPanel->showEffectStack(clipName, model, size, showKeyframes);
0414                 m_effectStackDock->raise();
0415             });
0416 
0417     connect(m_timelineTabs, &TimelineTabs::updateAssetPosition, m_assetPanel, &AssetPanel::updateAssetPosition);
0418 
0419     connect(m_timelineTabs, &TimelineTabs::showSubtitle, this, [&, subtitlesDock](int id) {
0420         if (id > -1) {
0421             subtitlesDock->show();
0422             subtitlesDock->raise();
0423         }
0424         pCore->subtitleWidget()->setActiveSubtitle(id);
0425     });
0426 
0427     connect(m_timelineTabs, &TimelineTabs::updateZoom, this, &MainWindow::updateZoomSlider);
0428     connect(pCore->bin(), &Bin::requestShowEffectStack, [&]() {
0429         // Don't raise effect stack on clip bin in case it is docked with bin or clip monitor
0430         // m_effectStackDock->raise();
0431     });
0432     connect(this, &MainWindow::clearAssetPanel, m_assetPanel, &AssetPanel::clearAssetPanel, Qt::DirectConnection);
0433     connect(this, &MainWindow::assetPanelWarning, m_assetPanel, &AssetPanel::assetPanelWarning);
0434     connect(m_assetPanel, &AssetPanel::seekToPos, this, [this](int pos) {
0435         ObjectId oId = m_assetPanel->effectStackOwner();
0436         switch (oId.type) {
0437         case KdenliveObjectType::TimelineTrack:
0438         case KdenliveObjectType::TimelineClip:
0439         case KdenliveObjectType::TimelineComposition:
0440         case KdenliveObjectType::Master:
0441         case KdenliveObjectType::TimelineMix:
0442             m_projectMonitor->requestSeek(pos);
0443             break;
0444         case KdenliveObjectType::BinClip:
0445             m_clipMonitor->requestSeek(pos);
0446             break;
0447         default:
0448             qDebug() << "ERROR unhandled object type";
0449             break;
0450         }
0451     });
0452 
0453     m_effectList2 = new EffectListWidget(this);
0454     connect(m_effectList2, &EffectListWidget::activateAsset, pCore->projectManager(), &ProjectManager::activateAsset);
0455     connect(m_assetPanel, &AssetPanel::reloadEffect, m_effectList2, &EffectListWidget::reloadCustomEffect);
0456     m_effectListDock = addDock(i18n("Effects"), QStringLiteral("effect_list"), m_effectList2);
0457 
0458     m_compositionList = new TransitionListWidget(this);
0459     m_compositionListDock = addDock(i18n("Compositions"), QStringLiteral("transition_list"), m_compositionList);
0460 
0461     // Add monitors here to keep them at the right of the window
0462     m_clipMonitorDock = addDock(i18n("Clip Monitor"), QStringLiteral("clip_monitor"), m_clipMonitor);
0463     m_projectMonitorDock = addDock(i18n("Project Monitor"), QStringLiteral("project_monitor"), m_projectMonitor);
0464 
0465     m_undoView = new QUndoView();
0466     m_undoView->setCleanIcon(QIcon::fromTheme(QStringLiteral("edit-clear")));
0467     m_undoView->setEmptyLabel(i18n("Clean"));
0468     m_undoView->setGroup(m_commandStack);
0469     m_undoViewDock = addDock(i18n("Undo History"), QStringLiteral("undo_history"), m_undoView);
0470 
0471     // Color and icon theme stuff
0472     connect(m_commandStack, &QUndoGroup::cleanChanged, m_saveAction, &QAction::setDisabled);
0473     addAction(QStringLiteral("styles_menu"), stylesAction);
0474 
0475     QAction *iconAction = new QAction(i18n("Force Breeze Icon Theme"), this);
0476     iconAction->setCheckable(true);
0477     iconAction->setChecked(KdenliveSettings::force_breeze());
0478     addAction(QStringLiteral("force_icon_theme"), iconAction);
0479     connect(iconAction, &QAction::triggered, this, &MainWindow::forceIconSet);
0480 
0481     m_mixerDock = addDock(i18n("Audio Mixer"), QStringLiteral("mixer"), pCore->mixer());
0482     m_mixerDock->setWhatsThis(xi18nc("@info:whatsthis", "Toggles the audio mixer panel/widget."));
0483     QAction *showMixer = new QAction(QIcon::fromTheme(QStringLiteral("view-media-equalizer")), i18n("Audio Mixer"), this);
0484     showMixer->setCheckable(true);
0485     addAction(QStringLiteral("audiomixer_button"), showMixer);
0486     connect(m_mixerDock, &QDockWidget::visibilityChanged, this, [&, showMixer](bool visible) {
0487         pCore->mixer()->connectMixer(visible);
0488         pCore->audioMixerVisible = visible;
0489         m_projectMonitor->displayAudioMonitor(m_projectMonitor->isActive());
0490         showMixer->setChecked(visible);
0491     });
0492     connect(showMixer, &QAction::triggered, this, [&]() {
0493         if (m_mixerDock->isVisible() && !m_mixerDock->visibleRegion().isEmpty()) {
0494             m_mixerDock->close();
0495         } else {
0496             m_mixerDock->show();
0497             m_mixerDock->raise();
0498         }
0499     });
0500 
0501     // Close non-general docks for the initial layout
0502     // only show important ones
0503     m_undoViewDock->close();
0504     m_mixerDock->close();
0505 
0506     // Tabify Widgets
0507     tabifyDockWidget(m_clipMonitorDock, m_projectMonitorDock);
0508     tabifyDockWidget(m_compositionListDock, m_effectListDock);
0509     tabifyDockWidget(m_effectStackDock, pCore->bin()->clipPropertiesDock());
0510     bool firstRun = readOptions();
0511     if (KdenliveSettings::lastCacheCheck().isNull()) {
0512         // Define a date for first check
0513         KdenliveSettings::setLastCacheCheck(QDateTime::currentDateTime());
0514     }
0515 
0516     // Build effects menu
0517     m_effectsMenu = new QMenu(i18n("Add Effect"), this);
0518     m_effectActions = new KActionCategory(i18n("Effects"), actionCollection());
0519     m_effectList2->reloadEffectMenu(m_effectsMenu, m_effectActions);
0520 
0521     m_transitionsMenu = new QMenu(i18n("Add Transition"), this);
0522     m_transitionActions = new KActionCategory(i18n("Transitions"), actionCollection());
0523 
0524     auto *scmanager = new ScopeManager(this);
0525 
0526     auto *titleBars = new DockTitleBarManager(this);
0527     connect(layoutManager, &LayoutManagement::updateTitleBars, titleBars, [&]() { titleBars->slotUpdateTitleBars(); });
0528     connect(layoutManager, &LayoutManagement::connectDocks, titleBars, &DockTitleBarManager::connectDocks);
0529     m_extraFactory = new KXMLGUIClient(this);
0530     buildDynamicActions();
0531 
0532     // Create Effect Basket (dropdown list of favorites)
0533     m_effectBasket = new EffectBasket(this);
0534     connect(m_effectBasket, &EffectBasket::activateAsset, pCore->projectManager(), &ProjectManager::activateAsset);
0535     connect(m_effectList2, &EffectListWidget::reloadFavorites, m_effectBasket, &EffectBasket::slotReloadBasket);
0536     auto *widgetlist = new QWidgetAction(this);
0537     widgetlist->setDefaultWidget(m_effectBasket);
0538     // widgetlist->setText(i18n("Favorite Effects"));
0539     widgetlist->setToolTip(i18n("Favorite Effects"));
0540     widgetlist->setWhatsThis(xi18nc("@info:whatsthis", "Click to show a list of favorite effects. Double-click on an effect to add it to the selected clip."));
0541     widgetlist->setIcon(QIcon::fromTheme(QStringLiteral("favorite")));
0542     auto *menu = new QMenu(this);
0543     menu->addAction(widgetlist);
0544 
0545     auto *basketButton = new QToolButton(this);
0546     basketButton->setMenu(menu);
0547     basketButton->setToolButtonStyle(toolBar()->toolButtonStyle());
0548     basketButton->setDefaultAction(widgetlist);
0549     basketButton->setPopupMode(QToolButton::InstantPopup);
0550     // basketButton->setText(i18n("Favorite Effects"));
0551     basketButton->setToolTip(i18n("Favorite Effects"));
0552     basketButton->setWhatsThis(
0553         xi18nc("@info:whatsthis", "Click to show a list of favorite effects. Double-click on an effect to add it to the selected clip."));
0554 
0555     basketButton->setIcon(QIcon::fromTheme(QStringLiteral("favorite")));
0556 
0557     auto *toolButtonAction = new QWidgetAction(this);
0558     toolButtonAction->setText(i18n("Favorite Effects"));
0559     toolButtonAction->setIcon(QIcon::fromTheme(QStringLiteral("favorite")));
0560     toolButtonAction->setDefaultWidget(basketButton);
0561     addAction(QStringLiteral("favorite_effects"), toolButtonAction);
0562     connect(toolButtonAction, &QAction::triggered, basketButton, &QToolButton::showMenu);
0563     connect(m_effectBasket, &EffectBasket::activateAsset, menu, &QMenu::close);
0564 
0565     // Render button
0566     ProgressButton *timelineRender = new ProgressButton(i18n("Render…"), 100, this);
0567     auto *tlrMenu = new QMenu(this);
0568     timelineRender->setMenu(tlrMenu);
0569     connect(this, &MainWindow::setRenderProgress, timelineRender, &ProgressButton::setProgress);
0570     auto *renderButtonAction = new QWidgetAction(this);
0571     renderButtonAction->setText(i18n("Render Button"));
0572     renderButtonAction->setIcon(QIcon::fromTheme(QStringLiteral("media-record")));
0573     renderButtonAction->setDefaultWidget(timelineRender);
0574     addAction(QStringLiteral("project_render_button"), renderButtonAction);
0575 
0576     // Timeline preview button
0577     ProgressButton *timelinePreview = new ProgressButton(i18n("Rendering preview"), 1000, this);
0578     auto *tlMenu = new QMenu(this);
0579     timelinePreview->setMenu(tlMenu);
0580     connect(this, &MainWindow::setPreviewProgress, timelinePreview, &ProgressButton::setProgress);
0581     auto *previewButtonAction = new QWidgetAction(this);
0582     previewButtonAction->setText(i18n("Timeline Preview"));
0583     previewButtonAction->setIcon(QIcon::fromTheme(QStringLiteral("preview-render-on")));
0584     previewButtonAction->setDefaultWidget(timelinePreview);
0585     addAction(QStringLiteral("timeline_preview_button"), previewButtonAction);
0586     setupGUI(KXmlGuiWindow::ToolBar | KXmlGuiWindow::StatusBar | KXmlGuiWindow::Save | KXmlGuiWindow::Create);
0587 
0588     LocaleHandling::resetLocale();
0589     if (firstRun) {
0590         if (QScreen *current = QApplication::primaryScreen()) {
0591             int screenHeight = current->availableSize().height();
0592             if (screenHeight < 1000) {
0593                 resize(current->availableSize());
0594             } else if (screenHeight < 2000) {
0595                 resize(current->availableSize() / 1.2);
0596             } else {
0597                 resize(current->availableSize() / 1.6);
0598             }
0599         }
0600     }
0601 
0602     m_timelineToolBar->setToolButtonStyle(Qt::ToolButtonFollowStyle);
0603     m_timelineToolBar->setProperty("otherToolbar", true);
0604     timelinePreview->setToolButtonStyle(m_timelineToolBar->toolButtonStyle());
0605     connect(m_timelineToolBar, &QToolBar::toolButtonStyleChanged, timelinePreview, &ProgressButton::setToolButtonStyle);
0606 
0607     timelineRender->setToolButtonStyle(toolBar()->toolButtonStyle());
0608     /*ScriptingPart* sp = new ScriptingPart(this, QStringList());
0609     guiFactory()->addClient(sp);*/
0610 
0611     loadDockActions();
0612     loadClipActions();
0613     loadContainerActions();
0614 
0615     // Timeline composition menu
0616     auto *compositionMenu = new QMenu(this);
0617     compositionMenu->addAction(actionCollection()->action(QStringLiteral("edit_item_duration")));
0618     compositionMenu->addAction(actionCollection()->action(QStringLiteral("edit_copy")));
0619     compositionMenu->addAction(actionCollection()->action(QStringLiteral("delete_timeline_clip")));
0620 
0621     // Timeline main menu
0622     auto *timelineMenu = new QMenu(this);
0623     timelineMenu->addAction(actionCollection()->action(QStringLiteral("edit_paste")));
0624     timelineMenu->addAction(actionCollection()->action(QStringLiteral("insert_space")));
0625     timelineMenu->addAction(actionCollection()->action(QStringLiteral("delete_space")));
0626     timelineMenu->addAction(actionCollection()->action(QStringLiteral("delete_space_all_tracks")));
0627     timelineMenu->addAction(actionCollection()->action(QStringLiteral("add_guide")));
0628     timelineMenu->addAction(actionCollection()->action(QStringLiteral("edit_guide")));
0629     QMenu *guideMenu = new QMenu(i18n("Go to Guide…"), this);
0630     timelineMenu->addMenu(guideMenu);
0631 
0632     // Timeline ruler menu
0633     auto *timelineRulerMenu = new QMenu(this);
0634     timelineRulerMenu->addAction(actionCollection()->action(QStringLiteral("add_guide")));
0635     timelineRulerMenu->addAction(actionCollection()->action(QStringLiteral("edit_guide")));
0636     timelineRulerMenu->addAction(actionCollection()->action(QStringLiteral("lock_guides")));
0637     timelineRulerMenu->addAction(actionCollection()->action(QStringLiteral("export_guides")));
0638     timelineRulerMenu->addMenu(guideMenu);
0639     timelineRulerMenu->addAction(actionCollection()->action(QStringLiteral("mark_in")));
0640     timelineRulerMenu->addAction(actionCollection()->action(QStringLiteral("mark_out")));
0641     timelineRulerMenu->addAction(actionCollection()->action(QStringLiteral("select_timeline_zone")));
0642     timelineRulerMenu->addAction(actionCollection()->action(QStringLiteral("add_project_note")));
0643     timelineRulerMenu->addAction(actionCollection()->action(QStringLiteral("add_subtitle")));
0644 
0645     // Timeline subtitle menu
0646     auto *timelineSubtitleMenu = new QMenu(this);
0647     timelineSubtitleMenu->addAction(actionCollection()->action(QStringLiteral("edit_copy")));
0648     timelineSubtitleMenu->addAction(actionCollection()->action(QStringLiteral("delete_subtitle_clip")));
0649 
0650     // Timeline headers menu
0651     auto *timelineHeadersMenu = new QMenu(this);
0652     timelineHeadersMenu->addAction(actionCollection()->action(QStringLiteral("insert_track")));
0653     timelineHeadersMenu->addAction(actionCollection()->action(QStringLiteral("delete_track")));
0654     timelineHeadersMenu->addAction(actionCollection()->action(QStringLiteral("fit_all_tracks")));
0655     timelineHeadersMenu->addAction(actionCollection()->action(QStringLiteral("show_track_record")));
0656 
0657     QAction *separate_channels = new QAction(QIcon(), i18n("Separate Channels"), this);
0658     separate_channels->setCheckable(true);
0659     separate_channels->setChecked(KdenliveSettings::displayallchannels());
0660     separate_channels->setData("separate_channels");
0661     connect(separate_channels, &QAction::triggered, this, &MainWindow::slotSeparateAudioChannel);
0662     timelineHeadersMenu->addAction(separate_channels);
0663 
0664     QAction *normalize_channels = new QAction(QIcon(), i18n("Normalize Audio Thumbnails"), this);
0665     normalize_channels->setCheckable(true);
0666     normalize_channels->setChecked(KdenliveSettings::normalizechannels());
0667     normalize_channels->setData("normalize_channels");
0668     connect(normalize_channels, &QAction::triggered, this, &MainWindow::slotNormalizeAudioChannel);
0669     timelineHeadersMenu->addAction(normalize_channels);
0670 
0671     QMenu *thumbsMenu = new QMenu(i18n("Thumbnails"), this);
0672     auto *thumbGroup = new QActionGroup(this);
0673     QAction *inFrame = new QAction(i18n("In Frame"), thumbGroup);
0674     inFrame->setData(QStringLiteral("2"));
0675     inFrame->setCheckable(true);
0676     thumbsMenu->addAction(inFrame);
0677     QAction *inOutFrame = new QAction(i18n("In/Out Frames"), thumbGroup);
0678     inOutFrame->setData(QStringLiteral("0"));
0679     inOutFrame->setCheckable(true);
0680     thumbsMenu->addAction(inOutFrame);
0681     QAction *allFrame = new QAction(i18n("All Frames"), thumbGroup);
0682     allFrame->setData(QStringLiteral("1"));
0683     allFrame->setCheckable(true);
0684     thumbsMenu->addAction(allFrame);
0685     QAction *noFrame = new QAction(i18n("No Thumbnails"), thumbGroup);
0686     noFrame->setData(QStringLiteral("3"));
0687     noFrame->setCheckable(true);
0688     thumbsMenu->addAction(noFrame);
0689     pCore->bin()->setupGeneratorMenu();
0690 
0691     connect(pCore->monitorManager(), &MonitorManager::updateOverlayInfos, this, &MainWindow::slotUpdateMonitorOverlays);
0692 
0693     // Setup and fill effects and transitions menus.
0694     connect(m_effectsMenu, &QMenu::triggered, this, &MainWindow::slotAddEffect);
0695     connect(m_transitionsMenu, &QMenu::triggered, this, &MainWindow::slotAddTransition);
0696 
0697     m_timelineContextMenu = new QMenu(this);
0698 
0699     m_timelineContextMenu->addAction(actionCollection()->action(QStringLiteral("insert_space")));
0700     m_timelineContextMenu->addAction(actionCollection()->action(QStringLiteral("delete_space")));
0701     m_timelineContextMenu->addAction(actionCollection()->action(QStringLiteral("delete_space_all_tracks")));
0702     m_timelineContextMenu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Paste)));
0703     slotConnectMonitors();
0704 
0705     m_timelineToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
0706     // TODO: let user select timeline toolbar toolbutton style
0707     // connect(toolBar(), &QToolBar::iconSizeChanged, m_timelineToolBar, &QToolBar::setToolButtonStyle);
0708     m_timelineToolBar->setContextMenuPolicy(Qt::CustomContextMenu);
0709     connect(m_timelineToolBar, &QWidget::customContextMenuRequested, this, &MainWindow::showTimelineToolbarMenu);
0710 
0711     QAction *prevRender = actionCollection()->action(QStringLiteral("prerender_timeline_zone"));
0712     QAction *stopPrevRender = actionCollection()->action(QStringLiteral("stop_prerender_timeline"));
0713     tlMenu->addAction(stopPrevRender);
0714     tlMenu->addAction(actionCollection()->action(QStringLiteral("set_render_timeline_zone")));
0715     tlMenu->addAction(actionCollection()->action(QStringLiteral("unset_render_timeline_zone")));
0716     tlMenu->addAction(actionCollection()->action(QStringLiteral("clear_render_timeline_zone")));
0717 
0718     // Automatic timeline preview action
0719     QAction *proxyRender = new QAction(i18n("Preview Using Proxy Clips"), this);
0720     proxyRender->setCheckable(true);
0721     proxyRender->setChecked(KdenliveSettings::proxypreview());
0722     connect(proxyRender, &QAction::triggered, this, [&](bool checked) { KdenliveSettings::setProxypreview(checked); });
0723     tlMenu->addAction(proxyRender);
0724 
0725     // Automatic timeline preview action
0726     QAction *autoRender = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Automatic Preview"), this);
0727     autoRender->setCheckable(true);
0728     autoRender->setChecked(KdenliveSettings::autopreview());
0729     connect(autoRender, &QAction::triggered, this, &MainWindow::slotToggleAutoPreview);
0730     tlMenu->addAction(autoRender);
0731     tlMenu->addSeparator();
0732     tlMenu->addAction(actionCollection()->action(QStringLiteral("disable_preview")));
0733     tlMenu->addAction(actionCollection()->action(QStringLiteral("manage_cache")));
0734     timelinePreview->defineDefaultAction(prevRender, stopPrevRender);
0735     timelinePreview->setAutoRaise(true);
0736 
0737     QAction *showRender = actionCollection()->action(QStringLiteral("project_render"));
0738     tlrMenu->addAction(showRender);
0739     tlrMenu->addAction(actionCollection()->action(QStringLiteral("stop_project_render")));
0740     timelineRender->defineDefaultAction(showRender, showRender);
0741     timelineRender->setAutoRaise(true);
0742 
0743     // Populate encoding profiles
0744     KConfig conf(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
0745     /*KConfig conf(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
0746     if (KdenliveSettings::proxyparams().isEmpty() || KdenliveSettings::proxyextension().isEmpty()) {
0747         KConfigGroup group(&conf, "proxy");
0748         QMap<QString, QString> values = group.entryMap();
0749         QMapIterator<QString, QString> i(values);
0750         if (i.hasNext()) {
0751             i.next();
0752             QString proxystring = i.value();
0753             KdenliveSettings::setProxyparams(proxystring.section(QLatin1Char(';'), 0, 0));
0754             KdenliveSettings::setProxyextension(proxystring.section(QLatin1Char(';'), 1, 1));
0755         }
0756     }*/
0757     if (KdenliveSettings::v4l_parameters().isEmpty() || KdenliveSettings::v4l_extension().isEmpty()) {
0758         KConfigGroup group(&conf, "video4linux");
0759         QMap<QString, QString> values = group.entryMap();
0760         QMapIterator<QString, QString> i(values);
0761         if (i.hasNext()) {
0762             i.next();
0763             QString v4lstring = i.value();
0764             KdenliveSettings::setV4l_parameters(v4lstring.section(QLatin1Char(';'), 0, 0));
0765             KdenliveSettings::setV4l_extension(v4lstring.section(QLatin1Char(';'), 1, 1));
0766         }
0767     }
0768     if (KdenliveSettings::grab_parameters().isEmpty() || KdenliveSettings::grab_extension().isEmpty()) {
0769         KConfigGroup group(&conf, "screengrab");
0770         QMap<QString, QString> values = group.entryMap();
0771         QMapIterator<QString, QString> i(values);
0772         if (i.hasNext()) {
0773             i.next();
0774             QString grabstring = i.value();
0775             KdenliveSettings::setGrab_parameters(grabstring.section(QLatin1Char(';'), 0, 0));
0776             KdenliveSettings::setGrab_extension(grabstring.section(QLatin1Char(';'), 1, 1));
0777         }
0778     }
0779     if (KdenliveSettings::decklink_parameters().isEmpty() || KdenliveSettings::decklink_extension().isEmpty()) {
0780         KConfigGroup group(&conf, "decklink");
0781         QMap<QString, QString> values = group.entryMap();
0782         QMapIterator<QString, QString> i(values);
0783         if (i.hasNext()) {
0784             i.next();
0785             QString decklinkstring = i.value();
0786             KdenliveSettings::setDecklink_parameters(decklinkstring.section(QLatin1Char(';'), 0, 0));
0787             KdenliveSettings::setDecklink_extension(decklinkstring.section(QLatin1Char(';'), 1, 1));
0788         }
0789     }
0790     if (!QDir(KdenliveSettings::currenttmpfolder()).isReadable())
0791         KdenliveSettings::setCurrenttmpfolder(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
0792 
0793     if (firstRun) {
0794         // Load editing layout
0795         layoutManager->loadLayout(QStringLiteral("kdenlive_editing"), true);
0796     }
0797 
0798 #ifdef USE_JOGSHUTTLE
0799     new JogManager(this);
0800 #endif
0801     m_timelineTabs->setTimelineMenu(compositionMenu, timelineMenu, guideMenu, timelineRulerMenu, actionCollection()->action(QStringLiteral("edit_guide")),
0802                                     timelineHeadersMenu, thumbsMenu, timelineSubtitleMenu);
0803     scmanager->slotCheckActiveScopes();
0804     connect(qApp, &QGuiApplication::applicationStateChanged, this, [&](Qt::ApplicationState state) {
0805         if (state == Qt::ApplicationActive && getCurrentTimeline()) {
0806             getCurrentTimeline()->regainFocus();
0807         }
0808     });
0809     connect(this, &MainWindow::removeBinDock, this, &MainWindow::slotRemoveBinDock);
0810     // m_messageLabel->setMessage(QStringLiteral("This is a beta version. Always backup your data"), MltError);
0811 
0812     QAction *const showMenuBarAction = actionCollection()->action(KStandardAction::name(KStandardAction::ShowMenubar));
0813     // FIXME: workaround for BUG 171080
0814     showMenuBarAction->setChecked(!menuBar()->isHidden());
0815 
0816     m_hamburgerMenu = KStandardAction::hamburgerMenu(nullptr, nullptr, actionCollection());
0817     // after the QMenuBar has been initialised
0818     m_hamburgerMenu->setMenuBar(menuBar());
0819     m_hamburgerMenu->setShowMenuBarAction(showMenuBarAction);
0820 
0821     // Detect shortcut conflicts bewtween mainwindow and media browser
0822     pCore->mediaBrowser()->detectShortcutConflicts();
0823 
0824     connect(toolBar(), &KToolBar::visibilityChanged, this, [&, showMenuBarAction](bool visible) {
0825         if (visible && !toolBar()->actions().contains(m_hamburgerMenu)) {
0826             // hack to be able to insert the hamburger menu at the first position
0827             QAction *const firstChild = toolBar()->actionAt(toolBar()->height() / 2, toolBar()->height() / 2);
0828             QAction *const seperator = toolBar()->insertSeparator(firstChild);
0829             toolBar()->insertAction(seperator, m_hamburgerMenu);
0830             m_hamburgerMenu->hideActionsOf(toolBar());
0831         }
0832     });
0833 
0834     m_loadingDialog = new QProgressDialog(this);
0835     m_loadingDialog->setWindowFlags((m_loadingDialog->windowFlags() | Qt::CustomizeWindowHint) & ~Qt::WindowCloseButtonHint & ~Qt::WindowSystemMenuHint);
0836     m_loadingDialog->setMinimumDuration(0);
0837     m_loadingDialog->setMaximum(0);
0838     m_loadingDialog->setWindowTitle(i18nc("@title:window", "Loading Project"));
0839     m_loadingDialog->setCancelButton(nullptr);
0840     m_loadingDialog->setAutoClose(false);
0841     m_loadingDialog->setAutoReset(false);
0842     m_loadingDialog->setModal(true);
0843     m_loadingDialog->close();
0844     connect(pCore.get(), &Core::loadingMessageNewStage, this, [&](const QString &message, int max = -1) {
0845         if (max > -1) {
0846             m_loadingDialog->reset();
0847             m_loadingDialog->setMaximum(max);
0848         }
0849         if (!message.isEmpty()) {
0850             m_loadingDialog->setLabelText(message);
0851         }
0852         m_loadingDialog->show();
0853     });
0854     connect(pCore.get(), &Core::loadingMessageIncrease, this, [&]() { m_loadingDialog->setValue(m_loadingDialog->value() + 1); });
0855     connect(pCore.get(), &Core::loadingMessageHide, this, [&]() {
0856         m_loadingDialog->reset();
0857         m_loadingDialog->setMaximum(0);
0858         m_loadingDialog->close();
0859     });
0860 }
0861 
0862 void MainWindow::loadContainerActions()
0863 {
0864     // Build all actions using the static_cast<QMenu *>(factory()->container method
0865     // That need to be rebuild when updating the ui.rc file
0866 
0867     // Redirect help entry to our own function
0868     // First delete the default help action
0869     QAction *officialHelp = actionCollection()->action(KStandardAction::name(KStandardAction::HelpContents));
0870     actionCollection()->removeAction(officialHelp);
0871     // Now recreate our own
0872     KStandardAction::helpContents(this, &MainWindow::appHelpActivated, actionCollection());
0873     officialHelp = actionCollection()->action(KStandardAction::name(KStandardAction::HelpContents));
0874     // Replug it in the Help menu
0875     QMenu *helpMenu = static_cast<QMenu *>(factory()->container(QStringLiteral("help"), this));
0876     if (helpMenu) {
0877         QAction *whatsThis = actionCollection()->action(KStandardAction::name(KStandardAction::WhatsThis));
0878         helpMenu->insertAction(whatsThis, officialHelp);
0879     }
0880 
0881     // rebuild timeline clip menu
0882     m_timelineTabs->buildClipMenu();
0883 
0884     // Ensure the "Monitor Config" menu doesn't replace the settings dialog on Mac because of text heuristics
0885     auto *monitorConfig = qobject_cast<QMenu *>(factory()->container(QStringLiteral("monitor_config"), this));
0886     if (monitorConfig) {
0887         monitorConfig->menuAction()->setMenuRole(QAction::NoRole);
0888     }
0889 
0890     // Connect monitor overlay info menu.
0891     QMenu *monitorOverlay = static_cast<QMenu *>(factory()->container(QStringLiteral("monitor_config_overlay"), this));
0892     if (monitorOverlay) {
0893         connect(monitorOverlay, &QMenu::triggered, this, &MainWindow::slotSwitchMonitorOverlay);
0894 
0895         m_projectMonitor->setupMenu(static_cast<QMenu *>(factory()->container(QStringLiteral("monitor_go"), this)), monitorOverlay, m_playZone, m_loopZone,
0896                                     nullptr, m_loopClip);
0897         m_clipMonitor->setupMenu(static_cast<QMenu *>(factory()->container(QStringLiteral("monitor_go"), this)), monitorOverlay, m_playZone, m_loopZone,
0898                                  static_cast<QMenu *>(factory()->container(QStringLiteral("marker_menu"), this)), nullptr);
0899     }
0900 
0901     QMenu *clipInTimeline = static_cast<QMenu *>(factory()->container(QStringLiteral("clip_in_timeline"), this));
0902     clipInTimeline->setIcon(QIcon::fromTheme(QStringLiteral("go-jump")));
0903 
0904     QMenu *m = static_cast<QMenu *>(factory()->container(QStringLiteral("video_effects_menu"), this));
0905     connect(m, &QMenu::triggered, this, &MainWindow::slotAddEffect);
0906 
0907     QMenu *addMenu = static_cast<QMenu *>(factory()->container(QStringLiteral("generators"), this));
0908     Generators::getGenerators(KdenliveSettings::producerslist(), addMenu);
0909     connect(addMenu, &QMenu::triggered, this, &MainWindow::buildGenerator);
0910 }
0911 
0912 void MainWindow::slotThemeChanged(const QString &name)
0913 {
0914     KSharedConfigPtr config = KSharedConfig::openConfig(name);
0915     QPalette plt = KColorScheme::createApplicationPalette(config);
0916     // qApp->setPalette(plt);
0917     // Required for qml palette change
0918     QGuiApplication::setPalette(plt);
0919 
0920     QColor background = plt.window().color();
0921     bool useDarkIcons = background.value() < 100;
0922 
0923     if (m_assetPanel) {
0924         m_assetPanel->updatePalette();
0925     }
0926     if (m_clipMonitor) {
0927         m_clipMonitor->setPalette(plt);
0928     }
0929     if (m_projectMonitor) {
0930         m_projectMonitor->setPalette(plt);
0931     }
0932     if (m_timelineTabs) {
0933         m_timelineTabs->setPalette(plt);
0934         getCurrentTimeline()->controller()->resetView();
0935     }
0936     if (m_audioSpectrum) {
0937         m_audioSpectrum->refreshPixmap();
0938     }
0939     Q_EMIT pCore->updatePalette();
0940 
0941     KSharedConfigPtr kconfig = KSharedConfig::openConfig();
0942     KConfigGroup initialGroup(kconfig, "version");
0943     QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
0944     bool isAppimage = pCore->packageType() == QStringLiteral("appimage");
0945     bool isKDE = env.value(QStringLiteral("XDG_CURRENT_DESKTOP")).toLower() == QLatin1String("kde");
0946     bool forceBreeze = initialGroup.exists() && KdenliveSettings::force_breeze();
0947     if ((!isKDE || isAppimage || forceBreeze) &&
0948         ((useDarkIcons && QIcon::themeName() == QStringLiteral("breeze")) || (!useDarkIcons && QIcon::themeName() == QStringLiteral("breeze-dark")))) {
0949         // We need to reload icon theme, on KDE desktops this is not necessary, however for the Appimage it is even on KDE Desktop
0950         // See also https://kate-editor.org/post/2021/2021-03-07-cross-platform-light-dark-themes-and-icons/
0951         QIcon::setThemeName(useDarkIcons ? QStringLiteral("breeze-dark") : QStringLiteral("breeze"));
0952         KdenliveSettings::setUse_dark_breeze(useDarkIcons);
0953     }
0954 }
0955 
0956 MainWindow::~MainWindow()
0957 {
0958     pCore->prepareShutdown();
0959     delete m_timelineTabs;
0960     delete m_audioSpectrum;
0961     if (m_projectMonitor) {
0962         m_projectMonitor->stop();
0963     }
0964     if (m_clipMonitor) {
0965         m_clipMonitor->stop();
0966     }
0967     ClipController::mediaUnavailable.reset();
0968     delete m_projectMonitor;
0969     delete m_clipMonitor;
0970     delete m_shortcutRemoveFocus;
0971     delete m_effectList2;
0972     delete m_compositionList;
0973     delete m_loadingDialog;
0974     pCore->finishShutdown();
0975     qDeleteAll(m_transitions);
0976     Mlt::Factory::close();
0977 }
0978 
0979 // virtual
0980 bool MainWindow::queryClose()
0981 {
0982     if (m_renderWidget) {
0983         int waitingJobs = m_renderWidget->waitingJobsCount();
0984         if (waitingJobs > 0) {
0985             switch (KMessageBox::warningTwoActionsCancel(this,
0986                                                          i18np("You have 1 rendering job waiting in the queue.\nWhat do you want to do with this job?",
0987                                                                "You have %1 rendering jobs waiting in the queue.\nWhat do you want to do with these jobs?",
0988                                                                waitingJobs),
0989                                                          QString(), KGuiItem(i18n("Start them now")), KGuiItem(i18n("Delete them")))) {
0990             case KMessageBox::PrimaryAction:
0991                 // create script with waiting jobs and start it
0992                 if (!m_renderWidget->startWaitingRenderJobs()) {
0993                     return false;
0994                 }
0995                 break;
0996             case KMessageBox::SecondaryAction:
0997                 // Don't do anything, jobs will be deleted
0998                 break;
0999             default:
1000                 return false;
1001             }
1002         }
1003     }
1004     saveOptions();
1005 
1006     // WARNING: According to KMainWindow::queryClose documentation we are not supposed to close the document here?
1007     return pCore->projectManager()->closeCurrentDocument(true, true);
1008 }
1009 
1010 void MainWindow::buildGenerator(QAction *action)
1011 {
1012     Generators gen(action->data().toString(), this);
1013     if (gen.exec() == QDialog::Accepted) {
1014         pCore->bin()->slotAddClipToProject(gen.getSavedClip());
1015     }
1016 }
1017 
1018 void MainWindow::saveProperties(KConfigGroup &config)
1019 {
1020     // save properties here
1021     KXmlGuiWindow::saveProperties(config);
1022     // TODO: fix session management
1023     if (qApp->isSavingSession() && pCore->projectManager()) {
1024         if (pCore->currentDoc() && !pCore->currentDoc()->url().isEmpty()) {
1025             config.writeEntry("kdenlive_lastUrl", pCore->currentDoc()->url().toLocalFile());
1026         }
1027     }
1028 }
1029 
1030 void MainWindow::saveNewToolbarConfig()
1031 {
1032     KXmlGuiWindow::saveNewToolbarConfig();
1033     // TODO for some reason all dynamically inserted actions are removed by the save toolbar
1034     // So we currently re-add them manually....
1035     loadDockActions();
1036     loadClipActions();
1037     loadContainerActions();
1038     for (auto &bin : m_binWidgets) {
1039         bin->setupGeneratorMenu();
1040     }
1041 
1042     // hack to be able to insert the hamburger menu at the first position
1043     QAction *const firstChild = toolBar()->actionAt(toolBar()->height() / 2, toolBar()->height() / 2);
1044     QAction *const seperator = toolBar()->insertSeparator(firstChild);
1045     toolBar()->insertAction(seperator, m_hamburgerMenu);
1046     m_hamburgerMenu->hideActionsOf(toolBar());
1047 }
1048 
1049 void MainWindow::slotReloadEffects(const QStringList &paths)
1050 {
1051     for (const QString &p : paths) {
1052         EffectsRepository::get()->reloadCustom(p);
1053     }
1054     m_effectList2->reloadEffectMenu(m_effectsMenu, m_effectActions);
1055 }
1056 
1057 void MainWindow::configureNotifications()
1058 {
1059     KNotifyConfigWidget::configure(this);
1060 }
1061 
1062 void MainWindow::slotFullScreen()
1063 {
1064     KToggleFullScreenAction::setFullScreen(this, actionCollection()->action(QStringLiteral("fullscreen"))->isChecked());
1065 }
1066 
1067 void MainWindow::slotConnectMonitors()
1068 {
1069     // connect(m_projectList, SIGNAL(deleteProjectClips(QStringList,QMap<QString,QString>)), this,
1070     // SLOT(slotDeleteProjectClips(QStringList,QMap<QString,QString>)));
1071     connect(m_clipMonitor, &Monitor::refreshClipThumbnail, pCore->bin(), &Bin::slotRefreshClipThumbnail);
1072     connect(m_projectMonitor, &Monitor::requestFrameForAnalysis, this, &MainWindow::slotMonitorRequestRenderFrame);
1073     connect(m_projectMonitor, &Monitor::createSplitOverlay, this, &MainWindow::createSplitOverlay, Qt::DirectConnection);
1074     connect(m_projectMonitor, &Monitor::removeSplitOverlay, this, &MainWindow::removeSplitOverlay, Qt::DirectConnection);
1075 }
1076 
1077 void MainWindow::createSplitOverlay(std::shared_ptr<Mlt::Filter> filter)
1078 {
1079     if (m_assetPanel->effectStackOwner().type == KdenliveObjectType::TimelineClip) {
1080         getCurrentTimeline()->controller()->createSplitOverlay(m_assetPanel->effectStackOwner().itemId, filter);
1081         m_projectMonitor->activateSplit();
1082     } else {
1083         pCore->displayMessage(i18n("Select a clip to compare effect"), ErrorMessage);
1084     }
1085 }
1086 
1087 void MainWindow::removeSplitOverlay()
1088 {
1089     getCurrentTimeline()->controller()->removeSplitOverlay();
1090 }
1091 
1092 void MainWindow::addAction(const QString &name, QAction *action, const QKeySequence &shortcut, KActionCategory *category)
1093 {
1094     m_actionNames.append(name);
1095     if (category) {
1096         category->addAction(name, action);
1097     } else {
1098         actionCollection()->addAction(name, action);
1099     }
1100     actionCollection()->setDefaultShortcut(action, shortcut);
1101 }
1102 
1103 void MainWindow::addAction(const QString &name, QAction *action, const QKeySequence &shortcut, const QString &category)
1104 {
1105     addAction(name, action, shortcut, kdenliveCategoryMap.value(category, nullptr));
1106 }
1107 
1108 QAction *MainWindow::addAction(const QString &name, const QString &text, const QObject *receiver, const char *member, const QIcon &icon,
1109                                const QKeySequence &shortcut, KActionCategory *category)
1110 {
1111     auto *action = new QAction(text, this);
1112     if (!icon.isNull()) {
1113         action->setIcon(icon);
1114     }
1115     addAction(name, action, shortcut, category);
1116     connect(action, SIGNAL(triggered(bool)), receiver, member);
1117 
1118     return action;
1119 }
1120 
1121 QAction *MainWindow::addAction(const QString &name, const QString &text, const QObject *receiver, const char *member, const QIcon &icon,
1122                                const QKeySequence &shortcut, const QString &category)
1123 {
1124     return addAction(name, text, receiver, member, icon, shortcut, kdenliveCategoryMap.value(category, nullptr));
1125 }
1126 
1127 void MainWindow::setupActions()
1128 {
1129     // create edit mode buttons
1130     m_normalEditTool = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-normal-edit")), i18n("Normal Mode"), this);
1131     m_normalEditTool->setCheckable(true);
1132     m_normalEditTool->setChecked(true);
1133 
1134     m_overwriteEditTool = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-overwrite-edit")), i18n("Overwrite Mode"), this);
1135     m_overwriteEditTool->setCheckable(true);
1136     m_overwriteEditTool->setChecked(false);
1137 
1138     m_insertEditTool = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-insert-edit")), i18n("Insert Mode"), this);
1139     m_insertEditTool->setCheckable(true);
1140     m_insertEditTool->setChecked(false);
1141 
1142     KSelectAction *sceneMode = new KSelectAction(i18n("Timeline Edit Mode"), this);
1143     sceneMode->setWhatsThis(
1144         xi18nc("@info:whatsthis", "Switches between Normal, Overwrite and Insert Mode. Determines the default action when handling clips in the timeline."));
1145     sceneMode->addAction(m_normalEditTool);
1146     sceneMode->addAction(m_overwriteEditTool);
1147     sceneMode->addAction(m_insertEditTool);
1148     sceneMode->setCurrentItem(0);
1149 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 240, 0)
1150     connect(sceneMode, &KSelectAction::actionTriggered, this, &MainWindow::slotChangeEdit);
1151 #else
1152     connect(sceneMode, static_cast<void (KSelectAction::*)(QAction *)>(&KSelectAction::triggered), this, &MainWindow::slotChangeEdit);
1153 #endif
1154     addAction(QStringLiteral("timeline_mode"), sceneMode);
1155     actionCollection()->setShortcutsConfigurable(sceneMode, false);
1156 
1157     m_useTimelineZone = new KDualAction(i18n("Do not Use Timeline Zone for Insert"), i18n("Use Timeline Zone for Insert"), this);
1158     m_useTimelineZone->setWhatsThis(xi18nc("@info:whatsthis", "Toggles between using the timeline zone for inserting (on) or not (off)."));
1159     m_useTimelineZone->setActiveIcon(QIcon::fromTheme(QStringLiteral("timeline-use-zone-on")));
1160     m_useTimelineZone->setInactiveIcon(QIcon::fromTheme(QStringLiteral("timeline-use-zone-off")));
1161     m_useTimelineZone->setAutoToggle(true);
1162     connect(m_useTimelineZone, &KDualAction::activeChangedByUser, this, &MainWindow::slotSwitchTimelineZone);
1163     addAction(QStringLiteral("use_timeline_zone_in_edit"), m_useTimelineZone);
1164 
1165     m_compositeAction = new QAction(i18n("Enable Track Compositing"), this);
1166     m_compositeAction->setCheckable(true);
1167     connect(m_compositeAction, &QAction::triggered, this, &MainWindow::slotUpdateCompositing);
1168     addAction(QStringLiteral("timeline_compositing"), m_compositeAction);
1169     actionCollection()->setShortcutsConfigurable(m_compositeAction, false);
1170 
1171     QAction *splitView = new QAction(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")), i18n("Split Audio Tracks"), this);
1172     addAction(QStringLiteral("timeline_view_split"), splitView);
1173     splitView->setData(QVariant::fromValue(1));
1174     splitView->setCheckable(true);
1175     splitView->setChecked(KdenliveSettings::audiotracksbelow() == 1);
1176 
1177     QAction *splitView2 = new QAction(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")), i18n("Split Audio Tracks (reverse)"), this);
1178     addAction(QStringLiteral("timeline_view_split_reverse"), splitView2);
1179     splitView2->setData(QVariant::fromValue(2));
1180     splitView2->setCheckable(true);
1181     splitView2->setChecked(KdenliveSettings::audiotracksbelow() == 2);
1182 
1183     QAction *mixedView = new QAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Mixed Audio tracks"), this);
1184     addAction(QStringLiteral("timeline_mixed_view"), mixedView);
1185     mixedView->setData(QVariant::fromValue(0));
1186     mixedView->setCheckable(true);
1187     mixedView->setChecked(KdenliveSettings::audiotracksbelow() == 0);
1188 
1189     auto *clipTypeGroup = new QActionGroup(this);
1190     clipTypeGroup->addAction(mixedView);
1191     clipTypeGroup->addAction(splitView);
1192     clipTypeGroup->addAction(splitView2);
1193     connect(clipTypeGroup, &QActionGroup::triggered, this, &MainWindow::slotUpdateTimelineView);
1194 
1195     auto tlsettings = new QMenu(this);
1196     tlsettings->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
1197     tlsettings->addAction(m_compositeAction);
1198     tlsettings->addSeparator();
1199     tlsettings->addAction(mixedView);
1200     tlsettings->addAction(splitView);
1201     tlsettings->addAction(splitView2);
1202 
1203     auto *timelineSett = new QToolButton(this);
1204     timelineSett->setPopupMode(QToolButton::InstantPopup);
1205     timelineSett->setMenu(tlsettings);
1206     timelineSett->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
1207     auto *tlButtonAction = new QWidgetAction(this);
1208     tlButtonAction->setDefaultWidget(timelineSett);
1209     tlButtonAction->setText(i18n("Track menu"));
1210     addAction(QStringLiteral("timeline_settings"), tlButtonAction);
1211 
1212     m_timeFormatButton = new KSelectAction(QStringLiteral("00:00:00:00 / 00:00:00:00"), this);
1213     m_timeFormatButton->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
1214     m_timeFormatButton->addAction(i18n("hh:mm:ss:ff"));
1215     m_timeFormatButton->addAction(i18n("Frames"));
1216     if (KdenliveSettings::frametimecode()) {
1217         m_timeFormatButton->setCurrentItem(1);
1218     } else {
1219         m_timeFormatButton->setCurrentItem(0);
1220     }
1221     connect(m_timeFormatButton, &KSelectAction::indexTriggered, this, &MainWindow::slotUpdateTimecodeFormat);
1222     m_timeFormatButton->setToolBarMode(KSelectAction::MenuMode);
1223     m_timeFormatButton->setToolButtonPopupMode(QToolButton::InstantPopup);
1224     addAction(QStringLiteral("timeline_timecode"), m_timeFormatButton);
1225     actionCollection()->setShortcutsConfigurable(m_timeFormatButton, false);
1226 
1227     m_buttonSubtitleEditTool = new QAction(QIcon::fromTheme(QStringLiteral("add-subtitle")), i18n("Edit Subtitle Tool"), this);
1228     m_buttonSubtitleEditTool->setWhatsThis(xi18nc("@info:whatsthis", "Toggles the subtitle track in the timeline."));
1229     m_buttonSubtitleEditTool->setCheckable(true);
1230     m_buttonSubtitleEditTool->setChecked(false);
1231     addAction(QStringLiteral("subtitle_tool"), m_buttonSubtitleEditTool);
1232     connect(m_buttonSubtitleEditTool, &QAction::triggered, this, &MainWindow::slotShowSubtitles);
1233 
1234     // create tools buttons
1235     m_buttonSelectTool = new QAction(QIcon::fromTheme(QStringLiteral("cursor-arrow")), i18n("Selection Tool"), this);
1236     // toolbar->addAction(m_buttonSelectTool);
1237     m_buttonSelectTool->setCheckable(true);
1238     m_buttonSelectTool->setChecked(true);
1239 
1240     m_buttonRazorTool = new QAction(QIcon::fromTheme(QStringLiteral("edit-cut")), i18n("Razor Tool"), this);
1241     // toolbar->addAction(m_buttonRazorTool);
1242     m_buttonRazorTool->setCheckable(true);
1243     m_buttonRazorTool->setChecked(false);
1244 
1245     m_buttonSpacerTool = new QAction(QIcon::fromTheme(QStringLiteral("distribute-horizontal-x")), i18n("Spacer Tool"), this);
1246     m_buttonSpacerTool->setWhatsThis(
1247         xi18nc("@info:whatsthis",
1248                "When selected, clicking and dragging the mouse in the timeline temporarily groups separate clips and creates or removes space between clips."));
1249     // toolbar->addAction(m_buttonSpacerTool);
1250     m_buttonSpacerTool->setCheckable(true);
1251     m_buttonSpacerTool->setChecked(false);
1252 
1253     m_buttonRippleTool = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-ripple")), i18n("Ripple Tool"), this);
1254     m_buttonRippleTool->setWhatsThis(
1255         xi18nc("@info:whatsthis",
1256                "When selected, dragging the edges of a clip lengthens or shortens the clip and moves adjacent clips back and forth while doing that."));
1257     m_buttonRippleTool->setCheckable(true);
1258     m_buttonRippleTool->setChecked(false);
1259 
1260     /* TODO Implement Roll
1261     // TODO icon available (and properly working) in KF 5.86
1262     m_buttonRollTool = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-rolling")), i18n("Roll Tool"), this);
1263 
1264     m_buttonRollTool->setCheckable(true);
1265     m_buttonRollTool->setChecked(false);*/
1266 
1267     m_buttonSlipTool = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-slip")), i18n("Slip Tool"), this);
1268     m_buttonSlipTool->setWhatsThis(xi18nc("@info:whatsthis", "When selected, dragging a clip slips the clip beneath the given window back and forth."));
1269     m_buttonSlipTool->setCheckable(true);
1270     m_buttonSlipTool->setChecked(false);
1271 
1272     m_buttonMulticamTool = new QAction(QIcon::fromTheme(QStringLiteral("view-split-left-right")), i18n("Multicam Tool"), this);
1273     m_buttonMulticamTool->setCheckable(true);
1274     m_buttonMulticamTool->setChecked(false);
1275 
1276     /* TODO Implement Slide
1277     // TODO icon available (and properly working) in KF 5.86
1278     m_buttonSlideTool = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-slide")), i18n("Slide Tool"), this);
1279     m_buttonSlideTool->setCheckable(true);
1280     m_buttonSlideTool->setChecked(false);*/
1281 
1282     auto *toolGroup = new QActionGroup(this);
1283     toolGroup->addAction(m_buttonSelectTool);
1284     toolGroup->addAction(m_buttonRazorTool);
1285     toolGroup->addAction(m_buttonSpacerTool);
1286     toolGroup->addAction(m_buttonRippleTool);
1287     // toolGroup->addAction(m_buttonRollTool);
1288     toolGroup->addAction(m_buttonSlipTool);
1289     // toolGroup->addAction(m_buttonSlideTool);
1290     toolGroup->addAction(m_buttonMulticamTool);
1291 
1292     toolGroup->setExclusive(true);
1293 
1294     QAction *collapseItem = new QAction(QIcon::fromTheme(QStringLiteral("collapse-all")), i18n("Collapse/Expand Item"), this);
1295     addAction(QStringLiteral("collapse_expand"), collapseItem, Qt::Key_Less);
1296     connect(collapseItem, &QAction::triggered, this, &MainWindow::slotCollapse);
1297 
1298     QAction *sameTrack = new QAction(QIcon::fromTheme(QStringLiteral("composite-track-preview")), i18n("Mix Clips"), this);
1299     sameTrack->setWhatsThis(
1300         xi18nc("@info:whatsthis", "Creates a same-track transition between the selected clip and the adjacent one closest to the playhead."));
1301     addAction(QStringLiteral("mix_clip"), sameTrack, Qt::Key_U);
1302     connect(sameTrack, &QAction::triggered, this, [this]() { getCurrentTimeline()->controller()->mixClip(); });
1303 
1304     // toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
1305 
1306     connect(toolGroup, &QActionGroup::triggered, this, &MainWindow::slotChangeTool);
1307 
1308     m_buttonVideoThumbs = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-show-videothumb")), i18n("Show Video Thumbnails"), this);
1309     m_buttonVideoThumbs->setWhatsThis(xi18nc("@info:whatsthis", "Toggles the display of video thumbnails for the clips in the timeline (default is On)."));
1310 
1311     m_buttonVideoThumbs->setCheckable(true);
1312     m_buttonVideoThumbs->setChecked(KdenliveSettings::videothumbnails());
1313     connect(m_buttonVideoThumbs, &QAction::triggered, this, &MainWindow::slotSwitchVideoThumbs);
1314 
1315     m_buttonAudioThumbs = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-show-audiothumb")), i18n("Show Audio Thumbnails"), this);
1316     m_buttonAudioThumbs->setWhatsThis(xi18nc("@info:whatsthis", "Toggles the display of audio thumbnails for the clips in the timeline (default is On)."));
1317 
1318     m_buttonAudioThumbs->setCheckable(true);
1319     m_buttonAudioThumbs->setChecked(KdenliveSettings::audiothumbnails());
1320     connect(m_buttonAudioThumbs, &QAction::triggered, this, &MainWindow::slotSwitchAudioThumbs);
1321 
1322     m_buttonShowMarkers = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-show-markers")), i18n("Show Markers Comments"), this);
1323 
1324     m_buttonShowMarkers->setCheckable(true);
1325     m_buttonShowMarkers->setChecked(KdenliveSettings::showmarkers());
1326     connect(m_buttonShowMarkers, &QAction::triggered, this, &MainWindow::slotSwitchMarkersComments);
1327 
1328     m_buttonSnap = new QAction(QIcon::fromTheme(QStringLiteral("snap")), i18n("Snap"), this);
1329     m_buttonSnap->setWhatsThis(xi18nc("@info:whatsthis", "Toggles the snap function (clips snap to playhead, edges, markers, guides and others)."));
1330 
1331     m_buttonSnap->setCheckable(true);
1332     m_buttonSnap->setChecked(KdenliveSettings::snaptopoints());
1333     connect(m_buttonSnap, &QAction::triggered, this, &MainWindow::slotSwitchSnap);
1334 
1335     m_buttonTimelineTags = new QAction(QIcon::fromTheme(QStringLiteral("tag")), i18n("Show Color Tags in Timeline"), this);
1336     m_buttonTimelineTags->setWhatsThis(xi18nc("@info:whatsthis", "Toggles the display of clip tags in the timeline (default is On)."));
1337 
1338     m_buttonTimelineTags->setCheckable(true);
1339     m_buttonTimelineTags->setChecked(KdenliveSettings::tagsintimeline());
1340     connect(m_buttonTimelineTags, &QAction::triggered, this, &MainWindow::slotShowTimelineTags);
1341 
1342     m_buttonFitZoom = new QAction(QIcon::fromTheme(QStringLiteral("zoom-fit-best")), i18n("Fit Zoom to Project"), this);
1343     m_buttonFitZoom->setWhatsThis(xi18nc("@info:whatsthis", "Adjusts the zoom level to fit the entire project into the timeline windows."));
1344 
1345     m_buttonFitZoom->setCheckable(false);
1346 
1347     m_zoomSlider = new QSlider(Qt::Horizontal, this);
1348     m_zoomSlider->setRange(0, 20);
1349     m_zoomSlider->setPageStep(1);
1350     m_zoomSlider->setInvertedAppearance(true);
1351     m_zoomSlider->setInvertedControls(true);
1352 
1353     m_zoomSlider->setMaximumWidth(150);
1354     m_zoomSlider->setMinimumWidth(100);
1355 
1356     m_zoomIn = KStandardAction::zoomIn(this, SLOT(slotZoomIn()), actionCollection());
1357     m_zoomOut = KStandardAction::zoomOut(this, SLOT(slotZoomOut()), actionCollection());
1358 
1359     connect(m_zoomSlider, &QSlider::valueChanged, this, [&](int value) { slotSetZoom(value); });
1360     connect(m_zoomSlider, &QAbstractSlider::sliderMoved, this, &MainWindow::slotShowZoomSliderToolTip);
1361     connect(m_buttonFitZoom, &QAction::triggered, this, &MainWindow::slotFitZoom);
1362 
1363     KToolBar *toolbar = new KToolBar(QStringLiteral("statusToolBar"), this, Qt::BottomToolBarArea);
1364     toolbar->setMovable(false);
1365     toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
1366 
1367     if (KdenliveSettings::gpu_accel()) {
1368         QLabel *warnLabel = new QLabel(i18n("Experimental GPU processing enabled - not for production"), this);
1369         warnLabel->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
1370         warnLabel->setAlignment(Qt::AlignHCenter);
1371         warnLabel->setStyleSheet(QStringLiteral("QLabel { background-color :red; color:black;padding-left:2px;padding-right:2px}"));
1372         toolbar->addWidget(warnLabel);
1373     }
1374 
1375     m_trimLabel = new QLabel(QString(), this);
1376     m_trimLabel->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
1377     m_trimLabel->setAlignment(Qt::AlignHCenter);
1378     m_trimLabel->setMinimumWidth(m_trimLabel->fontMetrics().boundingRect(i18n("Multicam")).width() + 8);
1379     m_trimLabel->setToolTip(i18n("Active tool and editing mode"));
1380 
1381     toolbar->addWidget(m_trimLabel);
1382     toolbar->addSeparator();
1383     toolbar->addAction(m_buttonTimelineTags);
1384     toolbar->addAction(m_buttonVideoThumbs);
1385     toolbar->addAction(m_buttonAudioThumbs);
1386     toolbar->addAction(m_buttonShowMarkers);
1387     toolbar->addAction(m_buttonSnap);
1388     toolbar->addSeparator();
1389     toolbar->addAction(m_buttonFitZoom);
1390     toolbar->addAction(m_zoomOut);
1391     toolbar->addWidget(m_zoomSlider);
1392     toolbar->addAction(m_zoomIn);
1393 
1394     int small = style()->pixelMetric(QStyle::PM_SmallIconSize);
1395     statusBar()->setMaximumHeight(2 * small);
1396     m_messageLabel = new StatusBarMessageLabel(this);
1397     m_messageLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
1398     connect(this, &MainWindow::displayMessage, m_messageLabel, &StatusBarMessageLabel::setMessage);
1399     connect(this, &MainWindow::displaySelectionMessage, m_messageLabel, &StatusBarMessageLabel::setSelectionMessage);
1400     connect(this, &MainWindow::displayProgressMessage, m_messageLabel, &StatusBarMessageLabel::setProgressMessage);
1401     statusBar()->addWidget(m_messageLabel, 10);
1402     statusBar()->addPermanentWidget(toolbar);
1403     toolbar->setIconSize(QSize(small, small));
1404     toolbar->layout()->setContentsMargins(0, 0, 0, 0);
1405     statusBar()->setContentsMargins(0, 0, 0, 0);
1406 
1407     addAction(QStringLiteral("normal_mode"), m_normalEditTool);
1408     addAction(QStringLiteral("overwrite_mode"), m_overwriteEditTool);
1409     addAction(QStringLiteral("insert_mode"), m_insertEditTool);
1410 
1411     KActionCategory *toolsActionCategory = new KActionCategory(i18n("Tools"), actionCollection());
1412     addAction(QStringLiteral("select_tool"), m_buttonSelectTool, Qt::Key_S, toolsActionCategory);
1413     addAction(QStringLiteral("razor_tool"), m_buttonRazorTool, Qt::Key_X, toolsActionCategory);
1414     addAction(QStringLiteral("spacer_tool"), m_buttonSpacerTool, Qt::Key_M, toolsActionCategory);
1415     addAction(QStringLiteral("ripple_tool"), m_buttonRippleTool, {}, toolsActionCategory);
1416     // addAction(QStringLiteral("roll_tool"), m_buttonRollTool, QKeySequence(), toolsActionCategory);
1417     addAction(QStringLiteral("slip_tool"), m_buttonSlipTool, {}, toolsActionCategory);
1418     addAction(QStringLiteral("multicam_tool"), m_buttonMulticamTool, {}, toolsActionCategory);
1419     // addAction(QStringLiteral("slide_tool"), m_buttonSlideTool);
1420 
1421     addAction(QStringLiteral("automatic_transition"), m_buttonTimelineTags);
1422     addAction(QStringLiteral("show_video_thumbs"), m_buttonVideoThumbs);
1423     addAction(QStringLiteral("show_audio_thumbs"), m_buttonAudioThumbs);
1424     addAction(QStringLiteral("show_markers"), m_buttonShowMarkers);
1425     addAction(QStringLiteral("snap"), m_buttonSnap);
1426     addAction(QStringLiteral("zoom_fit"), m_buttonFitZoom);
1427 
1428 #if defined(Q_OS_WIN)
1429     int glBackend = KdenliveSettings::opengl_backend();
1430     QAction *openGLAuto = new QAction(i18n("Auto"), this);
1431     openGLAuto->setData(0);
1432     openGLAuto->setCheckable(true);
1433     openGLAuto->setChecked(glBackend == 0);
1434 
1435     QAction *openGLDesktop = new QAction(i18n("OpenGL"), this);
1436     openGLDesktop->setData(Qt::AA_UseDesktopOpenGL);
1437     openGLDesktop->setCheckable(true);
1438     openGLDesktop->setChecked(glBackend == Qt::AA_UseDesktopOpenGL);
1439 
1440     QAction *openGLES = new QAction(i18n("DirectX (ANGLE)"), this);
1441     openGLES->setData(Qt::AA_UseOpenGLES);
1442     openGLES->setCheckable(true);
1443     openGLES->setChecked(glBackend == Qt::AA_UseOpenGLES);
1444 
1445     QAction *openGLSoftware = new QAction(i18n("Software OpenGL"), this);
1446     openGLSoftware->setData(Qt::AA_UseSoftwareOpenGL);
1447     openGLSoftware->setCheckable(true);
1448     openGLSoftware->setChecked(glBackend == Qt::AA_UseSoftwareOpenGL);
1449     addAction(QStringLiteral("opengl_auto"), openGLAuto);
1450     addAction(QStringLiteral("opengl_desktop"), openGLDesktop);
1451     addAction(QStringLiteral("opengl_es"), openGLES);
1452     addAction(QStringLiteral("opengl_software"), openGLSoftware);
1453 #endif
1454 
1455     addAction(QStringLiteral("run_wizard"), i18n("Run Config Wizard…"), this, SLOT(slotRunWizard()), QIcon::fromTheme(QStringLiteral("tools-wizard")));
1456     addAction(QStringLiteral("project_settings"), i18n("Project Settings…"), this, SLOT(slotEditProjectSettings()),
1457               QIcon::fromTheme(QStringLiteral("configure")));
1458 
1459     addAction(QStringLiteral("project_render"), i18n("Render…"), this, SLOT(slotRenderProject()), QIcon::fromTheme(QStringLiteral("media-record")),
1460               Qt::CTRL | Qt::Key_Return);
1461 
1462     addAction(QStringLiteral("stop_project_render"), i18n("Stop Render"), this, SLOT(slotStopRenderProject()),
1463               QIcon::fromTheme(QStringLiteral("media-record")));
1464 
1465     addAction(QStringLiteral("project_clean"), i18n("Clean Project"), this, SLOT(slotCleanProject()), QIcon::fromTheme(QStringLiteral("edit-clear")));
1466 
1467     QAction *resetAction = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Reset Configuration…"), this);
1468     addAction(QStringLiteral("reset_config"), resetAction);
1469     connect(resetAction, &QAction::triggered, this, [&]() { slotRestart(true); });
1470 
1471     m_playZone = addAction(QStringLiteral("monitor_play_zone"), i18n("Play Zone"), pCore->monitorManager(), SLOT(slotPlayZone()),
1472                            QIcon::fromTheme(QStringLiteral("media-playback-start")), Qt::CTRL | Qt::Key_Space, QStringLiteral("navandplayback"));
1473     m_loopZone = addAction(QStringLiteral("monitor_loop_zone"), i18n("Loop Zone"), pCore->monitorManager(), SLOT(slotLoopZone()),
1474                            QIcon::fromTheme(QStringLiteral("media-playback-start")), Qt::CTRL | Qt::SHIFT | Qt::Key_Space, QStringLiteral("navandplayback"));
1475     m_loopClip = new QAction(QIcon::fromTheme(QStringLiteral("media-playback-start")), i18n("Loop Selected Clip"), this);
1476     addAction(QStringLiteral("monitor_loop_clip"), m_loopClip);
1477     m_loopClip->setEnabled(false);
1478 
1479     addAction(QStringLiteral("transcode_clip"), i18n("Transcode Clips…"), this, SLOT(slotTranscodeClip()), QIcon::fromTheme(QStringLiteral("edit-copy")));
1480     QAction *exportAction = new QAction(QIcon::fromTheme(QStringLiteral("document-export")), i18n("OpenTimelineIO E&xport…"), this);
1481     connect(exportAction, &QAction::triggered, &m_otioConvertions, &OtioConvertions::slotExportProject);
1482     addAction(QStringLiteral("export_project"), exportAction);
1483     QAction *importAction = new QAction(QIcon::fromTheme(QStringLiteral("document-import")), i18n("OpenTimelineIO &Import…"), this);
1484     connect(importAction, &QAction::triggered, &m_otioConvertions, &OtioConvertions::slotImportProject);
1485     addAction(QStringLiteral("import_project"), importAction);
1486 
1487     addAction(QStringLiteral("archive_project"), i18n("Archive Project…"), this, SLOT(slotArchiveProject()),
1488               QIcon::fromTheme(QStringLiteral("document-save-all")));
1489     addAction(QStringLiteral("switch_monitor"), i18n("Switch Monitor"), this, SLOT(slotSwitchMonitors()), QIcon(), Qt::Key_T);
1490     addAction(QStringLiteral("focus_timecode"), i18n("Focus Timecode"), this, SLOT(slotFocusTimecode()), QIcon(), Qt::Key_Equal);
1491     addAction(QStringLiteral("expand_timeline_clip"), i18n("Expand Clip"), this, SLOT(slotExpandClip()), QIcon::fromTheme(QStringLiteral("document-open")));
1492 
1493     QAction *overlayInfo = new QAction(QIcon::fromTheme(QStringLiteral("help-hint")), i18n("Monitor Info Overlay"), this);
1494     addAction(QStringLiteral("monitor_overlay"), overlayInfo, {}, QStringLiteral("monitor"));
1495     overlayInfo->setCheckable(true);
1496     overlayInfo->setData(0x01);
1497 
1498     QAction *overlayTCInfo = new QAction(QIcon::fromTheme(QStringLiteral("help-hint")), i18n("Monitor Overlay Timecode"), this);
1499     addAction(QStringLiteral("monitor_overlay_tc"), overlayTCInfo, {}, QStringLiteral("monitor"));
1500     overlayTCInfo->setCheckable(true);
1501     overlayTCInfo->setData(0x02);
1502 
1503     QAction *overlayFpsInfo = new QAction(QIcon::fromTheme(QStringLiteral("help-hint")), i18n("Monitor Overlay Playback Fps"), this);
1504     addAction(QStringLiteral("monitor_overlay_fps"), overlayFpsInfo, {}, QStringLiteral("monitor"));
1505     overlayFpsInfo->setCheckable(true);
1506     overlayFpsInfo->setData(0x20);
1507 
1508     QAction *overlayMarkerInfo = new QAction(QIcon::fromTheme(QStringLiteral("help-hint")), i18n("Monitor Overlay Markers"), this);
1509     addAction(QStringLiteral("monitor_overlay_markers"), overlayMarkerInfo, {}, QStringLiteral("monitor"));
1510     overlayMarkerInfo->setCheckable(true);
1511     overlayMarkerInfo->setData(0x04);
1512 
1513     QAction *overlayAudioInfo = new QAction(QIcon::fromTheme(QStringLiteral("help-hint")), i18n("Monitor Overlay Audio Waveform"), this);
1514     addAction(QStringLiteral("monitor_overlay_audiothumb"), overlayAudioInfo, {}, QStringLiteral("monitor"));
1515     overlayAudioInfo->setCheckable(true);
1516     overlayAudioInfo->setData(0x10);
1517 
1518     QAction *overlayClipJobs = new QAction(QIcon::fromTheme(QStringLiteral("help-hint")), i18n("Monitor Overlay Clip Jobs"), this);
1519     addAction(QStringLiteral("monitor_overlay_clipjobs"), overlayClipJobs, {}, QStringLiteral("monitor"));
1520     overlayClipJobs->setCheckable(true);
1521     overlayClipJobs->setData(0x40);
1522 
1523     connect(overlayInfo, &QAction::toggled, this, [&, overlayTCInfo, overlayFpsInfo, overlayMarkerInfo, overlayAudioInfo, overlayClipJobs](bool toggled) {
1524         overlayTCInfo->setEnabled(toggled);
1525         overlayFpsInfo->setEnabled(toggled);
1526         overlayMarkerInfo->setEnabled(toggled);
1527         overlayAudioInfo->setEnabled(toggled);
1528         overlayClipJobs->setEnabled(toggled);
1529     });
1530 
1531     // Monitor resolution scaling
1532     KActionCategory *resolutionActionCategory = new KActionCategory(i18n("Preview Resolution"), actionCollection());
1533     m_scaleGroup = new QActionGroup(this);
1534     m_scaleGroup->setExclusive(true);
1535     m_scaleGroup->setEnabled(!KdenliveSettings::external_display());
1536     QAction *scale_no = new QAction(i18n("Full Resolution (1:1)"), m_scaleGroup);
1537     addAction(QStringLiteral("scale_no_preview"), scale_no, QKeySequence(), resolutionActionCategory);
1538     scale_no->setCheckable(true);
1539     scale_no->setData(1);
1540     QAction *scale_2 = new QAction(i18n("720p"), m_scaleGroup);
1541     addAction(QStringLiteral("scale_2_preview"), scale_2, QKeySequence(), resolutionActionCategory);
1542     scale_2->setCheckable(true);
1543     scale_2->setData(2);
1544     QAction *scale_4 = new QAction(i18n("540p"), m_scaleGroup);
1545     addAction(QStringLiteral("scale_4_preview"), scale_4, QKeySequence(), resolutionActionCategory);
1546     scale_4->setCheckable(true);
1547     scale_4->setData(4);
1548     QAction *scale_8 = new QAction(i18n("360p"), m_scaleGroup);
1549     addAction(QStringLiteral("scale_8_preview"), scale_8, QKeySequence(), resolutionActionCategory);
1550     scale_8->setCheckable(true);
1551     scale_8->setData(8);
1552     QAction *scale_16 = new QAction(i18n("270p"), m_scaleGroup);
1553     addAction(QStringLiteral("scale_16_preview"), scale_16, QKeySequence(), resolutionActionCategory);
1554     scale_16->setCheckable(true);
1555     scale_16->setData(16);
1556     connect(pCore->monitorManager(), &MonitorManager::scalingChanged, this, [scale_2, scale_4, scale_8, scale_16, scale_no]() {
1557         switch (KdenliveSettings::previewScaling()) {
1558         case 2:
1559             scale_2->setChecked(true);
1560             break;
1561         case 4:
1562             scale_4->setChecked(true);
1563             break;
1564         case 8:
1565             scale_8->setChecked(true);
1566             break;
1567         case 16:
1568             scale_16->setChecked(true);
1569             break;
1570         default:
1571             scale_no->setChecked(true);
1572             break;
1573         }
1574     });
1575     Q_EMIT pCore->monitorManager()->scalingChanged();
1576     connect(m_scaleGroup, &QActionGroup::triggered, this, [](QAction *ac) {
1577         int scaling = ac->data().toInt();
1578         KdenliveSettings::setPreviewScaling(scaling);
1579         // Clear timeline selection so that any qml monitor scene is reset
1580         Q_EMIT pCore->monitorManager()->updatePreviewScaling();
1581     });
1582 
1583     QAction *dropFrames = new QAction(QIcon(), i18n("Real Time (drop frames)"), this);
1584     dropFrames->setCheckable(true);
1585     dropFrames->setChecked(KdenliveSettings::monitor_dropframes());
1586     addAction(QStringLiteral("mlt_realtime"), dropFrames);
1587     connect(dropFrames, &QAction::toggled, this, &MainWindow::slotSwitchDropFrames);
1588 
1589     KSelectAction *monitorGamma = new KSelectAction(i18n("Monitor Gamma"), this);
1590     monitorGamma->addAction(i18n("sRGB (computer)"));
1591     monitorGamma->addAction(i18n("Rec. 709 (TV)"));
1592     addAction(QStringLiteral("mlt_gamma"), monitorGamma, {}, QStringLiteral("monitor"));
1593     monitorGamma->setCurrentItem(KdenliveSettings::monitor_gamma());
1594     connect(monitorGamma, &KSelectAction::indexTriggered, this, &MainWindow::slotSetMonitorGamma);
1595     actionCollection()->setShortcutsConfigurable(monitorGamma, false);
1596 
1597     QAction *insertBinZone = addAction(QStringLiteral("insert_project_tree"), i18n("Insert Zone in Project Bin"), this, SLOT(slotInsertZoneToTree()),
1598                                        QIcon::fromTheme(QStringLiteral("kdenlive-add-clip")), Qt::CTRL | Qt::Key_I);
1599     insertBinZone->setWhatsThis(xi18nc("@info:whatsthis", "Creates a new clip in the project bin from the defined zone."));
1600 
1601     // TODO: Make these 2 shortcuts context aware to avoid conflict with media browser
1602     addAction(QStringLiteral("monitor_seek_snap_backward"), i18n("Go to Previous Snap Point"), this, SLOT(slotSnapRewind()),
1603               QIcon::fromTheme(QStringLiteral("media-seek-backward")), Qt::ALT | Qt::Key_Left, QStringLiteral("navandplayback"));
1604     addAction(QStringLiteral("monitor_seek_snap_forward"), i18n("Go to Next Snap Point"), this, SLOT(slotSnapForward()),
1605               QIcon::fromTheme(QStringLiteral("media-seek-forward")), Qt::ALT | Qt::Key_Right, QStringLiteral("navandplayback"));
1606 
1607     addAction(QStringLiteral("seek_clip_start"), i18n("Go to Clip Start"), this, SLOT(slotClipStart()), QIcon::fromTheme(QStringLiteral("media-seek-backward")),
1608               Qt::Key_Home, QStringLiteral("navandplayback"));
1609     addAction(QStringLiteral("seek_clip_end"), i18n("Go to Clip End"), this, SLOT(slotClipEnd()), QIcon::fromTheme(QStringLiteral("media-seek-forward")),
1610               Qt::Key_End, QStringLiteral("navandplayback"));
1611     addAction(QStringLiteral("monitor_seek_guide_backward"), i18n("Go to Previous Guide"), this, SLOT(slotGuideRewind()),
1612               QIcon::fromTheme(QStringLiteral("media-seek-backward")), Qt::CTRL | Qt::Key_Left, QStringLiteral("navandplayback"));
1613     addAction(QStringLiteral("monitor_seek_guide_forward"), i18n("Go to Next Guide"), this, SLOT(slotGuideForward()),
1614               QIcon::fromTheme(QStringLiteral("media-seek-forward")), Qt::CTRL | Qt::Key_Right, QStringLiteral("navandplayback"));
1615     addAction(QStringLiteral("align_playhead"), i18n("Align Playhead to Mouse Position"), this, SLOT(slotAlignPlayheadToMousePos()), QIcon(), Qt::Key_P,
1616               QStringLiteral("navandplayback"));
1617 
1618     addAction(QStringLiteral("grab_item"), i18n("Grab Current Item"), this, SLOT(slotGrabItem()), QIcon::fromTheme(QStringLiteral("transform-move")),
1619               Qt::SHIFT | Qt::Key_G);
1620 
1621     QAction *stickTransition = new QAction(i18n("Automatic Transition"), this);
1622     stickTransition->setData(QStringLiteral("auto"));
1623     stickTransition->setCheckable(true);
1624     stickTransition->setEnabled(false);
1625     addAction(QStringLiteral("auto_transition"), stickTransition);
1626     connect(stickTransition, &QAction::triggered, this, &MainWindow::slotAutoTransition);
1627 
1628     QAction *overwriteZone = addAction(QStringLiteral("overwrite_to_in_point"), i18n("Overwrite Clip Zone in Timeline"), this, SLOT(slotInsertClipOverwrite()),
1629                                        QIcon::fromTheme(QStringLiteral("timeline-overwrite")), Qt::Key_B);
1630     overwriteZone->setWhatsThis(xi18nc("@info:whatsthis", "When clicked the zone of the clip currently selected in the project bin is inserted at the playhead "
1631                                                           "position in the active timeline. Clips at the insert position are cut and overwritten."));
1632     QAction *insertZone = addAction(QStringLiteral("insert_to_in_point"), i18n("Insert Clip Zone in Timeline"), this, SLOT(slotInsertClipInsert()),
1633                                     QIcon::fromTheme(QStringLiteral("timeline-insert")), Qt::Key_V);
1634     insertZone->setWhatsThis(xi18nc("@info:whatsthis", "When clicked the zone of the clip currently selected in the project bin is inserted at the playhead "
1635                                                        "position in the active timeline. Clips at the insert position are cut and shifted to the right."));
1636     QAction *extractZone = addAction(QStringLiteral("remove_extract"), i18n("Extract Timeline Zone"), this, SLOT(slotExtractZone()),
1637                                      QIcon::fromTheme(QStringLiteral("timeline-extract")), Qt::SHIFT | Qt::Key_X);
1638     extractZone->setWhatsThis(xi18nc("@info:whatsthis", "Click to delete the timeline zone from the timeline. All clips to the right are shifted left."));
1639     QAction *liftZone = addAction(QStringLiteral("remove_lift"), i18n("Lift Timeline Zone"), this, SLOT(slotLiftZone()),
1640                                   QIcon::fromTheme(QStringLiteral("timeline-lift")), Qt::Key_Z);
1641     liftZone->setWhatsThis(xi18nc("@info:whatsthis", "Click to delete the timeline zone from the timeline. All clips to the right stay in position."));
1642     QAction *addPreviewZone = addAction(QStringLiteral("set_render_timeline_zone"), i18n("Add Preview Zone"), this, SLOT(slotDefinePreviewRender()),
1643                                         QIcon::fromTheme(QStringLiteral("preview-add-zone")));
1644     addPreviewZone->setWhatsThis(xi18nc("@info:whatsthis", "Add the currently defined timeline/selection zone as a preview render zone"));
1645     QAction *removePreviewZone = addAction(QStringLiteral("unset_render_timeline_zone"), i18n("Remove Preview Zone"), this, SLOT(slotRemovePreviewRender()),
1646                                            QIcon::fromTheme(QStringLiteral("preview-remove-zone")));
1647     removePreviewZone->setWhatsThis(xi18nc(
1648         "@info:whatsthis",
1649         "Removes the currently defined timeline/selection zone from the preview render zone. Note that this can leave gaps in the preview render zones."));
1650     QAction *removeAllPreviewZone = addAction(QStringLiteral("clear_render_timeline_zone"), i18n("Remove All Preview Zones"), this,
1651                                               SLOT(slotClearPreviewRender()), QIcon::fromTheme(QStringLiteral("preview-remove-all")));
1652     removeAllPreviewZone->setWhatsThis(xi18nc("@info:whatsthis", "Remove all preview render zones."));
1653     QAction *startPreviewRender = addAction(QStringLiteral("prerender_timeline_zone"), i18n("Start Preview Render"), this, SLOT(slotPreviewRender()),
1654                                             QIcon::fromTheme(QStringLiteral("preview-render-on")), QKeySequence(Qt::SHIFT | Qt::Key_Return));
1655     startPreviewRender->setWhatsThis(xi18nc("@info:whatsthis",
1656                                             "Click to start the rendering of all preview zones (recommended for areas with complex and many effects).<nl/>"
1657                                             "Click on the down-arrow icon to get a list of options (for example: add preview render zone, remove all zones)."));
1658     addAction(QStringLiteral("stop_prerender_timeline"), i18n("Stop Preview Render"), this, SLOT(slotStopPreviewRender()),
1659               QIcon::fromTheme(QStringLiteral("preview-render-off")));
1660 
1661     addAction(QStringLiteral("select_timeline_zone"), i18n("Adjust Timeline Zone to Selection"), this, SLOT(slotSelectTimelineZone()),
1662               QIcon::fromTheme(QStringLiteral("edit-select")), Qt::SHIFT | Qt::Key_Z);
1663     addAction(QStringLiteral("select_timeline_clip"), i18n("Select Clip"), this, SLOT(slotSelectTimelineClip()),
1664               QIcon::fromTheme(QStringLiteral("edit-select")), Qt::Key_Plus);
1665     addAction(QStringLiteral("deselect_timeline_clip"), i18n("Deselect Clip"), this, SLOT(slotDeselectTimelineClip()),
1666               QIcon::fromTheme(QStringLiteral("edit-select")), Qt::Key_Minus);
1667     addAction(QStringLiteral("select_add_timeline_clip"), i18n("Add Clip to Selection"), this, SLOT(slotSelectAddTimelineClip()),
1668               QIcon::fromTheme(QStringLiteral("edit-select")), Qt::ALT | Qt::Key_Plus);
1669     addAction(QStringLiteral("select_timeline_transition"), i18n("Select Transition"), this, SLOT(slotSelectTimelineTransition()),
1670               QIcon::fromTheme(QStringLiteral("edit-select")), Qt::SHIFT | Qt::Key_Plus);
1671     addAction(QStringLiteral("deselect_timeline_transition"), i18n("Deselect Transition"), this, SLOT(slotDeselectTimelineTransition()),
1672               QIcon::fromTheme(QStringLiteral("edit-select")), Qt::SHIFT | Qt::Key_Minus);
1673     addAction(QStringLiteral("select_add_timeline_transition"), i18n("Add Transition to Selection"), this, SLOT(slotSelectAddTimelineTransition()),
1674               QIcon::fromTheme(QStringLiteral("edit-select")), Qt::ALT | Qt::SHIFT | Qt::Key_Plus);
1675 
1676     addAction(QStringLiteral("delete_all_clip_markers"), i18n("Delete All Markers"), this, SLOT(slotDeleteAllClipMarkers()),
1677               QIcon::fromTheme(QStringLiteral("edit-delete")));
1678     addAction(QStringLiteral("add_marker_guide_quickly"), i18n("Add Marker/Guide quickly"), this, SLOT(slotAddMarkerGuideQuickly()),
1679               QIcon::fromTheme(QStringLiteral("bookmark-new")), QKeySequence(Qt::KeypadModifier | Qt::Key_Asterisk));
1680 
1681     // Clip actions. We set some category info on the action data to enable/disable it contextually in timelinecontroller
1682     KActionCategory *clipActionCategory = new KActionCategory(i18n("Current Selection"), actionCollection());
1683 
1684     QAction *addMarker = addAction(QStringLiteral("add_clip_marker"), i18n("Add Marker"), this, SLOT(slotAddClipMarker()),
1685                                    QIcon::fromTheme(QStringLiteral("bookmark-new")), QKeySequence(), clipActionCategory);
1686     addMarker->setData('P');
1687 
1688     QAction *delMarker = addAction(QStringLiteral("delete_clip_marker"), i18n("Delete Marker"), this, SLOT(slotDeleteClipMarker()),
1689                                    QIcon::fromTheme(QStringLiteral("edit-delete")), QKeySequence(), clipActionCategory);
1690     delMarker->setData('P');
1691 
1692     QAction *editClipMarker = addAction(QStringLiteral("edit_clip_marker"), i18n("Edit Marker…"), this, SLOT(slotEditClipMarker()),
1693                                         QIcon::fromTheme(QStringLiteral("document-properties")), QKeySequence(), clipActionCategory);
1694     editClipMarker->setObjectName(QStringLiteral("edit_marker"));
1695     editClipMarker->setData('P');
1696 
1697     QAction *splitAudio = addAction(QStringLiteral("clip_split"), i18n("Restore Audio"), this, SLOT(slotSplitAV()),
1698                                     QIcon::fromTheme(QStringLiteral("document-new")), QKeySequence(), clipActionCategory);
1699     // "S" will be handled specifically to change the action name depending on current selection
1700     splitAudio->setData('S');
1701     splitAudio->setEnabled(false);
1702 
1703     QAction *extractClip = addAction(QStringLiteral("extract_clip"), i18n("Extract Clip"), this, SLOT(slotExtractClip()),
1704                                      QIcon::fromTheme(QStringLiteral("timeline-extract")), QKeySequence(), clipActionCategory);
1705     extractClip->setData('C');
1706     extractClip->setEnabled(false);
1707 
1708     QAction *extractToBin =
1709         addAction(QStringLiteral("save_to_bin"), i18n("Save Clip Part to Bin"), this, SLOT(slotSaveZoneToBin()), QIcon(), QKeySequence(), clipActionCategory);
1710     extractToBin->setData('C');
1711     extractToBin->setEnabled(false);
1712 
1713     QAction *switchEnable =
1714         addAction(QStringLiteral("clip_switch"), i18n("Disable Clip"), this, SLOT(slotSwitchClip()), QIcon(), QKeySequence(), clipActionCategory);
1715     // "W" will be handled specifically to change the action name depending on current selection
1716     switchEnable->setData('W');
1717     switchEnable->setEnabled(false);
1718 
1719     QAction *setAudioAlignReference = addAction(QStringLiteral("set_audio_align_ref"), i18n("Set Audio Reference"), this, SLOT(slotSetAudioAlignReference()),
1720                                                 QIcon(), QKeySequence(), clipActionCategory);
1721     // "A" as data means this action should only be available for clips with audio
1722     setAudioAlignReference->setData('A');
1723     setAudioAlignReference->setEnabled(false);
1724 
1725     QAction *alignAudio =
1726         addAction(QStringLiteral("align_audio"), i18n("Align Audio to Reference"), this, SLOT(slotAlignAudio()), QIcon(), QKeySequence(), clipActionCategory);
1727     // "A" as data means this action should only be available for clips with audio
1728     // alignAudio->setData('A');
1729     alignAudio->setEnabled(false);
1730 
1731     QAction *act = addAction(QStringLiteral("edit_item_duration"), i18n("Edit Duration"), this, SLOT(slotEditItemDuration()),
1732                              QIcon::fromTheme(QStringLiteral("measure")), QKeySequence(), clipActionCategory);
1733     act->setEnabled(false);
1734 
1735     act = addAction(QStringLiteral("edit_item_speed"), i18n("Change Speed"), this, SLOT(slotEditItemSpeed()), QIcon::fromTheme(QStringLiteral("speedometer")),
1736                     QKeySequence(), clipActionCategory);
1737     // "Q" as data means this action should only be available if the item is not endless and has no time remap
1738     act->setData('Q');
1739     act->setEnabled(false);
1740 
1741     act = addAction(QStringLiteral("edit_item_remap"), i18n("Time Remap"), this, SLOT(slotRemapItemTime()), QIcon::fromTheme(QStringLiteral("speedometer")),
1742                     QKeySequence(), clipActionCategory);
1743     // "R" as data means this action should only be available if the item is not endless and has no speed effect
1744     act->setData('R');
1745     act->setCheckable(true);
1746     act->setEnabled(false);
1747 
1748     act = addAction(QStringLiteral("clip_in_project_tree"), i18n("Clip in Project Bin"), this, SLOT(slotClipInProjectTree()),
1749                     QIcon::fromTheme(QStringLiteral("find-location")), QKeySequence(), clipActionCategory);
1750     act->setEnabled(false);
1751     // "C" as data means this action should only be available for clips - not for compositions
1752     act->setData('C');
1753 
1754     addAction(QStringLiteral("cut_timeline_clip"), i18n("Cut Clip"), this, SLOT(slotCutTimelineClip()), QIcon::fromTheme(QStringLiteral("edit-cut")),
1755               Qt::SHIFT | Qt::Key_R);
1756 
1757     addAction(QStringLiteral("cut_timeline_all_clips"), i18n("Cut All Clips"), this, SLOT(slotCutTimelineAllClips()),
1758               QIcon::fromTheme(QStringLiteral("edit-cut")), Qt::CTRL | Qt::SHIFT | Qt::Key_R);
1759 
1760     addAction(QStringLiteral("delete_timeline_clip"), i18n("Delete Selected Item"), this, SLOT(slotDeleteItem()),
1761               QIcon::fromTheme(QStringLiteral("edit-delete")), Qt::Key_Delete);
1762 
1763     QAction *resizeStart = new QAction(QIcon(), i18n("Resize Item Start"), this);
1764     addAction(QStringLiteral("resize_timeline_clip_start"), resizeStart, QKeySequence(Qt::Key_ParenLeft));
1765     connect(resizeStart, &QAction::triggered, this, &MainWindow::slotResizeItemStart);
1766 
1767     QAction *resizeEnd = new QAction(QIcon(), i18n("Resize Item End"), this);
1768     addAction(QStringLiteral("resize_timeline_clip_end"), resizeEnd, QKeySequence(Qt::Key_ParenRight));
1769     connect(resizeEnd, &QAction::triggered, this, &MainWindow::slotResizeItemEnd);
1770 
1771     QAction *pasteEffects = addAction(QStringLiteral("paste_effects"), i18n("Paste Effects"), this, SLOT(slotPasteEffects()),
1772                                       QIcon::fromTheme(QStringLiteral("edit-paste")), QKeySequence(), clipActionCategory);
1773     pasteEffects->setEnabled(false);
1774     // "C" as data means this action should only be available for clips - not for compositions
1775     pasteEffects->setData('C');
1776 
1777     QAction *delEffects = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete Effects"), this);
1778     addAction(QStringLiteral("delete_effects"), delEffects, QKeySequence(), clipActionCategory);
1779     delEffects->setEnabled(false);
1780     // "C" as data means this action should only be available for clips - not for compositions
1781     delEffects->setData('C');
1782     connect(delEffects, &QAction::triggered, this, [this]() { getCurrentTimeline()->controller()->deleteEffects(); });
1783 
1784     QAction *groupClip = addAction(QStringLiteral("group_clip"), i18n("Group Clips"), this, SLOT(slotGroupClips()),
1785                                    QIcon::fromTheme(QStringLiteral("object-group")), Qt::CTRL | Qt::Key_G, clipActionCategory);
1786     // "G" as data means this action should only be available for multiple items selection
1787     groupClip->setData('G');
1788     groupClip->setEnabled(false);
1789 
1790     QAction *ungroupClip = addAction(QStringLiteral("ungroup_clip"), i18n("Ungroup Clips"), this, SLOT(slotUnGroupClips()),
1791                                      QIcon::fromTheme(QStringLiteral("object-ungroup")), QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_G), clipActionCategory);
1792     // "U" as data means this action should only be available if selection is a group
1793     ungroupClip->setData('U');
1794     ungroupClip->setEnabled(false);
1795 
1796     QAction *sentToSequence = addAction(QStringLiteral("send_sequence"), i18n("Create Sequence from Selection"), this, SLOT(slotCreateSequenceFromSelection()),
1797                                         QIcon::fromTheme(QStringLiteral("bookmark-new")), QKeySequence(), clipActionCategory);
1798     sentToSequence->setWhatsThis(
1799         xi18nc("@info:whatsthis", "Adds the clip(s) currently selected in the timeline to a new sequence clip that can be opened in another timeline tab."));
1800     sentToSequence->setData('G');
1801     sentToSequence->setEnabled(false);
1802 
1803     act = clipActionCategory->addAction(KStandardAction::Copy, this, SLOT(slotCopy()));
1804     act->setEnabled(false);
1805 
1806     KStandardAction::paste(this, SLOT(slotPaste()), actionCollection());
1807 
1808     // Keyframe actions
1809     m_assetPanel = new AssetPanel(this);
1810     connect(getBin(), &Bin::requestShowEffectStack, m_assetPanel, &AssetPanel::showEffectStack);
1811     KActionCategory *kfActions = new KActionCategory(i18n("Effect Keyframes"), actionCollection());
1812     addAction(QStringLiteral("keyframe_add"), i18n("Add/Remove Keyframe"), m_assetPanel, SLOT(slotAddRemoveKeyframe()),
1813               QIcon::fromTheme(QStringLiteral("keyframe-add")), QKeySequence(), kfActions);
1814     addAction(QStringLiteral("keyframe_next"), i18n("Go to next keyframe"), m_assetPanel, SLOT(slotNextKeyframe()),
1815               QIcon::fromTheme(QStringLiteral("keyframe-next")), QKeySequence(), kfActions);
1816     addAction(QStringLiteral("keyframe_previous"), i18n("Go to previous keyframe"), m_assetPanel, SLOT(slotPreviousKeyframe()),
1817               QIcon::fromTheme(QStringLiteral("keyframe-previous")), QKeySequence(), kfActions);
1818 
1819     /*act = KStandardAction::copy(this, SLOT(slotCopy()), actionCollection());
1820     clipActionCategory->addAction(KStandardAction::name(KStandardAction::Copy), act);
1821     act->setEnabled(false);
1822     act = KStandardAction::paste(this, SLOT(slotPaste()), actionCollection());
1823     clipActionCategory->addAction(KStandardAction::name(KStandardAction::Paste), act);
1824     act->setEnabled(false);*/
1825 
1826     kdenliveCategoryMap.insert(QStringLiteral("timelineselection"), clipActionCategory);
1827 
1828     addAction(QStringLiteral("insert_space"), i18n("Insert Space…"), this, SLOT(slotInsertSpace()));
1829     addAction(QStringLiteral("delete_space"), i18n("Remove Space"), this, SLOT(slotRemoveSpace()));
1830     addAction(QStringLiteral("delete_all_spaces"), i18n("Remove All Spaces After Cursor"), this, SLOT(slotRemoveAllSpacesInTrack()));
1831     addAction(QStringLiteral("delete_all_clips"), i18n("Remove All Clips After Cursor"), this, SLOT(slotRemoveAllClipsInTrack()));
1832     addAction(QStringLiteral("delete_space_all_tracks"), i18n("Remove Space in All Tracks"), this, SLOT(slotRemoveSpaceInAllTracks()));
1833 
1834     KActionCategory *timelineActions = new KActionCategory(i18n("Tracks"), actionCollection());
1835     QAction *insertTrack = new QAction(QIcon(), i18nc("@action", "Insert Track…"), this);
1836     connect(insertTrack, &QAction::triggered, this, &MainWindow::slotInsertTrack);
1837     timelineActions->addAction(QStringLiteral("insert_track"), insertTrack);
1838 
1839     QAction *autoTrackHeight = new QAction(QIcon(), i18n("Fit all Tracks in View"), this);
1840     autoTrackHeight->setCheckable(true);
1841     autoTrackHeight->setChecked(KdenliveSettings::autotrackheight());
1842     connect(autoTrackHeight, &QAction::triggered, this, &MainWindow::slotAutoTrackHeight);
1843     timelineActions->addAction(QStringLiteral("fit_all_tracks"), autoTrackHeight);
1844 
1845     QAction *masterEffectStack = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-composite")), i18n("Master effects"), this);
1846     connect(masterEffectStack, &QAction::triggered, this, [&]() {
1847         pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor);
1848         getCurrentTimeline()->controller()->showMasterEffects();
1849     });
1850     timelineActions->addAction(QStringLiteral("master_effects"), masterEffectStack);
1851 
1852     QAction *switchTrackTarget = new QAction(QIcon(), i18n("Switch Track Target Audio Stream"), this);
1853     connect(switchTrackTarget, &QAction::triggered, this, &MainWindow::slotSwitchTrackAudioStream);
1854     timelineActions->addAction(QStringLiteral("switch_target_stream"), switchTrackTarget);
1855     actionCollection()->setDefaultShortcut(switchTrackTarget, Qt::Key_Apostrophe);
1856 
1857     QAction *deleteTrack = new QAction(QIcon(), i18n("Delete Track…"), this);
1858     connect(deleteTrack, &QAction::triggered, this, &MainWindow::slotDeleteTrack);
1859     timelineActions->addAction(QStringLiteral("delete_track"), deleteTrack);
1860     deleteTrack->setData("delete_track");
1861 
1862     QAction *showAudio = new QAction(QIcon(), i18n("Show Record Controls"), this);
1863     connect(showAudio, &QAction::triggered, this, &MainWindow::slotShowTrackRec);
1864     timelineActions->addAction(QStringLiteral("show_track_record"), showAudio);
1865     showAudio->setCheckable(true);
1866     showAudio->setData("show_track_record");
1867 
1868     QAction *selectTrack = new QAction(QIcon(), i18n("Select All in Current Track"), this);
1869     connect(selectTrack, &QAction::triggered, this, &MainWindow::slotSelectTrack);
1870     timelineActions->addAction(QStringLiteral("select_track"), selectTrack);
1871 
1872     QAction *selectAll = KStandardAction::selectAll(this, SLOT(slotSelectAllTracks()), this);
1873     selectAll->setIcon(QIcon::fromTheme(QStringLiteral("edit-select-all")));
1874     selectAll->setShortcutContext(Qt::WidgetWithChildrenShortcut);
1875     timelineActions->addAction(QStringLiteral("select_all_tracks"), selectAll);
1876 
1877     QAction *unselectAll = KStandardAction::deselect(this, SLOT(slotUnselectAllTracks()), this);
1878     unselectAll->setIcon(QIcon::fromTheme(QStringLiteral("edit-select-none")));
1879     unselectAll->setShortcutContext(Qt::WidgetWithChildrenShortcut);
1880     timelineActions->addAction(QStringLiteral("unselect_all_tracks"), unselectAll);
1881 
1882     kdenliveCategoryMap.insert(QStringLiteral("timeline"), timelineActions);
1883 
1884     // Cached data management
1885     addAction(QStringLiteral("manage_cache"), i18n("Manage Cached Data…"), this, SLOT(slotManageCache()),
1886               QIcon::fromTheme(QStringLiteral("network-server-database")));
1887 
1888     QAction *disablePreview = new QAction(i18n("Disable Timeline Preview"), this);
1889     disablePreview->setCheckable(true);
1890     addAction(QStringLiteral("disable_preview"), disablePreview);
1891 
1892     addAction(QStringLiteral("add_guide"), i18n("Add/Remove Guide"), this, SLOT(slotAddGuide()), QIcon::fromTheme(QStringLiteral("list-add")), Qt::Key_G);
1893     addAction(QStringLiteral("delete_guide"), i18n("Delete Guide"), this, SLOT(slotDeleteGuide()), QIcon::fromTheme(QStringLiteral("edit-delete")));
1894     addAction(QStringLiteral("edit_guide"), i18n("Edit Guide…"), this, SLOT(slotEditGuide()), QIcon::fromTheme(QStringLiteral("document-properties")));
1895     addAction(QStringLiteral("search_guide"), i18n("Search Guide…"), this, SLOT(slotSearchGuide()), QIcon::fromTheme(QStringLiteral("edit-find")));
1896     addAction(QStringLiteral("export_guides"), i18n("Export Guides…"), this, SLOT(slotExportGuides()), QIcon::fromTheme(QStringLiteral("document-export")));
1897 
1898     QAction *lockGuides =
1899         addAction(QStringLiteral("lock_guides"), i18n("Guides Locked"), this, SLOT(slotLockGuides(bool)), QIcon::fromTheme(QStringLiteral("lock")));
1900     lockGuides->setCheckable(true);
1901     lockGuides->setChecked(KdenliveSettings::lockedGuides());
1902     lockGuides->setToolTip(i18n("Lock guides"));
1903     lockGuides->setWhatsThis(
1904         xi18nc("@info:whatsthis", "Lock guides. When locked, the guides won't move when using the spacer tool or inserting/removing blank in tracks."));
1905 
1906     addAction(QStringLiteral("delete_all_guides"), i18n("Delete All Guides"), this, SLOT(slotDeleteAllGuides()),
1907               QIcon::fromTheme(QStringLiteral("edit-delete")));
1908     addAction(QStringLiteral("add_subtitle"), i18n("Add Subtitle"), this, SLOT(slotAddSubtitle()), QIcon::fromTheme(QStringLiteral("list-add")),
1909               Qt::SHIFT | Qt::Key_S);
1910     addAction(QStringLiteral("disable_subtitle"), i18n("Disable Subtitle"), this, SLOT(slotDisableSubtitle()), QIcon::fromTheme(QStringLiteral("view-hidden")));
1911     addAction(QStringLiteral("lock_subtitle"), i18n("Lock Subtitle"), this, SLOT(slotLockSubtitle()), QIcon::fromTheme(QStringLiteral("lock")));
1912 
1913     addAction(QStringLiteral("manage_subtitle"), i18n("Manage Subtitles"), this, SLOT(slotManageSubtitle()),
1914               QIcon::fromTheme(QStringLiteral("settings-configure")));
1915     addAction(QStringLiteral("import_subtitle"), i18n("Import Subtitle File…"), this, SLOT(slotImportSubtitle()),
1916               QIcon::fromTheme(QStringLiteral("document-import")));
1917     addAction(QStringLiteral("export_subtitle"), i18n("Export Subtitle File…"), this, SLOT(slotExportSubtitle()),
1918               QIcon::fromTheme(QStringLiteral("document-export")));
1919     addAction(QStringLiteral("delete_subtitle_clip"), i18n("Delete Subtitle"), this, SLOT(slotDeleteItem()), QIcon::fromTheme(QStringLiteral("edit-delete")));
1920     addAction(QStringLiteral("audio_recognition"), i18n("Speech Recognition…"), this, SLOT(slotSpeechRecognition()),
1921               QIcon::fromTheme(QStringLiteral("autocorrection")));
1922 
1923     m_saveAction = KStandardAction::save(pCore->projectManager(), SLOT(saveFile()), actionCollection());
1924     m_saveAction->setIcon(QIcon::fromTheme(QStringLiteral("document-save")));
1925 
1926     QAction *const showMenuBarAction = KStandardAction::showMenubar(this, &MainWindow::showMenuBar, actionCollection());
1927     showMenuBarAction->setWhatsThis(xi18nc("@info:whatsthis", "This switches between having a <emphasis>Menubar</emphasis> "
1928                                                               "and having a <interface>Hamburger Menu</interface> button in the main Toolbar."));
1929 
1930     KStandardAction::quit(this, &MainWindow::close, actionCollection());
1931     KStandardAction::keyBindings(this, &MainWindow::slotEditKeys, actionCollection());
1932     KStandardAction::preferences(this, &MainWindow::slotPreferences, actionCollection());
1933     KStandardAction::configureNotifications(this, &MainWindow::configureNotifications, actionCollection());
1934     KStandardAction::fullScreen(this, &MainWindow::slotFullScreen, this, actionCollection());
1935 
1936     QAction *undo = KStandardAction::undo(m_commandStack, SLOT(undo()), actionCollection());
1937     undo->setEnabled(false);
1938     connect(m_commandStack, &QUndoGroup::canUndoChanged, undo, &QAction::setEnabled);
1939     connect(this, &MainWindow::enableUndo, this, [this, undo](bool enable) {
1940         if (enable) {
1941             enable = m_commandStack->activeStack()->canUndo();
1942         }
1943         undo->setEnabled(enable);
1944     });
1945 
1946     QAction *redo = KStandardAction::redo(m_commandStack, SLOT(redo()), actionCollection());
1947     redo->setEnabled(false);
1948     connect(m_commandStack, &QUndoGroup::canRedoChanged, redo, &QAction::setEnabled);
1949     connect(this, &MainWindow::enableUndo, this, [this, redo](bool enable) {
1950         if (enable) {
1951             enable = m_commandStack->activeStack()->canRedo();
1952         }
1953         redo->setEnabled(enable);
1954     });
1955 
1956     addAction(QStringLiteral("copy_debuginfo"), i18n("Copy Debug Information"), this, SLOT(slotCopyDebugInfo()), QIcon::fromTheme(QStringLiteral("edit-copy")));
1957 
1958     QAction *disableEffects = addAction(QStringLiteral("disable_timeline_effects"), i18n("Disable Timeline Effects"), pCore->projectManager(),
1959                                         SLOT(slotDisableTimelineEffects(bool)), QIcon::fromTheme(QStringLiteral("favorite")));
1960     disableEffects->setData("disable_timeline_effects");
1961     disableEffects->setCheckable(true);
1962     disableEffects->setChecked(false);
1963 
1964     addAction(QStringLiteral("switch_track_disabled"), i18n("Toggle Track Disabled"), pCore->projectManager(), SLOT(slotSwitchTrackDisabled()), QIcon(),
1965               Qt::SHIFT | Qt::Key_H, timelineActions);
1966     addAction(QStringLiteral("switch_track_lock"), i18n("Toggle Track Lock"), pCore->projectManager(), SLOT(slotSwitchTrackLock()), QIcon(),
1967               Qt::SHIFT | Qt::Key_L, timelineActions);
1968     addAction(QStringLiteral("switch_all_track_lock"), i18n("Toggle All Track Lock"), pCore->projectManager(), SLOT(slotSwitchAllTrackLock()), QIcon(),
1969               Qt::CTRL | Qt::SHIFT | Qt::Key_L, timelineActions);
1970     addAction(QStringLiteral("switch_track_target"), i18n("Toggle Track Target"), pCore->projectManager(), SLOT(slotSwitchTrackTarget()), QIcon(),
1971               Qt::SHIFT | Qt::Key_T, timelineActions);
1972     addAction(QStringLiteral("switch_active_target"), i18n("Toggle Track Active"), pCore->projectManager(), SLOT(slotSwitchTrackActive()), QIcon(), Qt::Key_A,
1973               timelineActions);
1974     addAction(QStringLiteral("switch_all_targets"), i18n("Toggle All Tracks Active"), pCore->projectManager(), SLOT(slotSwitchAllTrackActive()), QIcon(),
1975               Qt::SHIFT | Qt::Key_A, timelineActions);
1976     addAction(QStringLiteral("activate_all_targets"), i18n("Switch All Tracks Active"), pCore->projectManager(), SLOT(slotMakeAllTrackActive()), QIcon(),
1977               Qt::SHIFT | Qt::ALT | Qt::Key_A, timelineActions);
1978     addAction(QStringLiteral("restore_all_sources"), i18n("Restore Current Clip Target Tracks"), pCore->projectManager(), SLOT(slotRestoreTargetTracks()), {},
1979               {}, timelineActions);
1980     addAction(QStringLiteral("add_project_note"), i18n("Add Project Note"), pCore->projectManager(), SLOT(slotAddProjectNote()),
1981               QIcon::fromTheme(QStringLiteral("bookmark-new")), {}, timelineActions);
1982 
1983     // Build activate track shortcut sequences
1984     QList<int> keysequence{Qt::Key_1, Qt::Key_2, Qt::Key_3, Qt::Key_4, Qt::Key_5, Qt::Key_6, Qt::Key_7, Qt::Key_8, Qt::Key_9};
1985     for (int i = 1; i < 10; i++) {
1986         QAction *ac = new QAction(QIcon(), i18n("Select Audio Track %1", i), this);
1987         ac->setData(i - 1);
1988         connect(ac, &QAction::triggered, this, &MainWindow::slotActivateAudioTrackSequence);
1989         addAction(QString("activate_audio_%1").arg(i), ac, QKeySequence(Qt::ALT | keysequence[i - 1]), timelineActions);
1990         QAction *ac2 = new QAction(QIcon(), i18n("Select Video Track %1", i), this);
1991         ac2->setData(i - 1);
1992         connect(ac2, &QAction::triggered, this, &MainWindow::slotActivateVideoTrackSequence);
1993         addAction(QString("activate_video_%1").arg(i), ac2, QKeySequence(keysequence[i - 1]), timelineActions);
1994         QAction *ac3 = new QAction(QIcon(), i18n("Select Target %1", i), this);
1995         ac3->setData(i - 1);
1996         connect(ac3, &QAction::triggered, this, &MainWindow::slotActivateTarget);
1997         addAction(QString("activate_target_%1").arg(i), ac3, QKeySequence(Qt::CTRL | keysequence[i - 1]), timelineActions);
1998     }
1999 
2000     // Setup effects and transitions actions.
2001     KActionCategory *transitionActions = new KActionCategory(i18n("Transitions"), actionCollection());
2002     // m_transitions = new QAction*[transitions.count()];
2003     auto allTransitions = TransitionsRepository::get()->getNames();
2004     for (const auto &transition : qAsConst(allTransitions)) {
2005         auto *transAction = new QAction(transition.first, this);
2006         transAction->setData(transition.second);
2007         transAction->setIconVisibleInMenu(false);
2008         transitionActions->addAction("transition_" + transition.second, transAction);
2009     }
2010 
2011     // monitor actions
2012     addAction(QStringLiteral("extract_frame"), i18n("Extract Frame…"), pCore->monitorManager(), SLOT(slotExtractCurrentFrame()),
2013               QIcon::fromTheme(QStringLiteral("insert-image")));
2014 
2015     addAction(QStringLiteral("extract_frame_to_project"), i18n("Extract Frame to Project…"), pCore->monitorManager(), SLOT(slotExtractCurrentFrameToProject()),
2016               QIcon::fromTheme(QStringLiteral("insert-image")));
2017 }
2018 
2019 void MainWindow::saveOptions()
2020 {
2021     KdenliveSettings::self()->save();
2022 }
2023 
2024 bool MainWindow::readOptions()
2025 {
2026     KSharedConfigPtr config = KSharedConfig::openConfig();
2027     pCore->projectManager()->recentFilesAction()->loadEntries(KConfigGroup(config, "Recent Files"));
2028 
2029     if (KdenliveSettings::defaultprojectfolder().isEmpty()) {
2030         QDir dir(QStandardPaths::writableLocation(QStandardPaths::MoviesLocation));
2031         dir.mkpath(QStringLiteral("."));
2032         KdenliveSettings::setDefaultprojectfolder(dir.absolutePath());
2033     }
2034     QFont ft = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont);
2035     // Default unit for timeline.qml objects size
2036     int baseUnit = qMax(28, qRound(QFontInfo(ft).pixelSize() * 1.8));
2037     if (KdenliveSettings::trackheight() == 0) {
2038         int trackHeight = qMax(50, int(2.2 * baseUnit + 6));
2039         KdenliveSettings::setTrackheight(trackHeight);
2040     }
2041     bool firstRun = false;
2042     KConfigGroup initialGroup(config, "version");
2043     if (!initialGroup.exists() || KdenliveSettings::sdlAudioBackend().isEmpty() || KdenliveSettings::kdenliverendererpath().isEmpty()) {
2044         // First run, check if user is on a KDE Desktop
2045         firstRun = true;
2046         // Define default video location for first run
2047         KRecentDirs::add(QStringLiteral(":KdenliveClipFolder"), QStandardPaths::writableLocation(QStandardPaths::MoviesLocation));
2048 
2049         // this is our first run, show Wizard
2050         QPointer<Wizard> w = new Wizard(true);
2051         if (w->exec() == QDialog::Accepted && w->isOk()) {
2052             w->adjustSettings();
2053             delete w;
2054         } else {
2055             delete w;
2056             ::exit(1);
2057         }
2058     } else if (!KdenliveSettings::ffmpegpath().isEmpty() && !QFile::exists(KdenliveSettings::ffmpegpath())) {
2059         // Invalid entry for FFmpeg, check system
2060         QPointer<Wizard> w = new Wizard(true);
2061         if (w->exec() == QDialog::Accepted && w->isOk()) {
2062             w->adjustSettings();
2063         }
2064         delete w;
2065     }
2066     if (firstRun) {
2067         if (TransitionsRepository::get()->exists(QStringLiteral("qtblend")) && TransitionsRepository::get()->getVersion(QStringLiteral("qtblend")) > 200) {
2068             KdenliveSettings::setPreferredcomposite(QStringLiteral("qtblend"));
2069         }
2070     }
2071     initialGroup.writeEntry("version", version);
2072     if (KdenliveSettings::guidesCategories().isEmpty()) {
2073         KdenliveSettings::setGuidesCategories(KdenliveDoc::getDefaultGuideCategories());
2074     }
2075     return firstRun;
2076 }
2077 
2078 void MainWindow::slotRunWizard()
2079 {
2080     QPointer<Wizard> w = new Wizard(false, this);
2081     if (w->exec() == QDialog::Accepted && w->isOk()) {
2082         w->adjustSettings();
2083     }
2084     delete w;
2085 }
2086 
2087 void MainWindow::slotRefreshProfiles()
2088 {
2089     KdenliveSettingsDialog *d = static_cast<KdenliveSettingsDialog *>(KConfigDialog::exists(QStringLiteral("settings")));
2090     if (d) {
2091         d->checkProfile();
2092     }
2093 }
2094 
2095 void MainWindow::slotEditProjectSettings(int ix)
2096 {
2097     KdenliveDoc *project = pCore->currentDoc();
2098     QPair<int, int> p = getCurrentTimeline()->getAvTracksCount();
2099     int channels = project->getDocumentProperty(QStringLiteral("audioChannels"), QStringLiteral("2")).toInt();
2100     ProjectSettings *w = new ProjectSettings(project, project->metadata(), getCurrentTimeline()->controller()->extractCompositionLumas(), p.second, p.first,
2101                                              channels, project->projectTempFolder(), true, !project->isModified(), this);
2102     if (ix > 0) {
2103         w->tabWidget->setCurrentIndex(ix);
2104     }
2105     connect(w, &ProjectSettings::disableProxies, this, &MainWindow::slotDisableProxies);
2106     // connect(w, SIGNAL(disablePreview()), pCore->projectManager()->currentTimeline(), SLOT(invalidateRange()));
2107     connect(w, &ProjectSettings::refreshProfiles, this, &MainWindow::slotRefreshProfiles);
2108 
2109     if (w->exec() == QDialog::Accepted) {
2110         QString profile = w->selectedProfile();
2111         bool modified = false;
2112         if (m_renderWidget) {
2113             m_renderWidget->updateDocumentPath();
2114         }
2115         const QStringList guidesCat = w->guidesCategories();
2116         if (guidesCat != project->guidesCategories()) {
2117             project->updateGuideCategories(guidesCat, w->remapGuidesCategories());
2118         }
2119         if (KdenliveSettings::videothumbnails() != w->enableVideoThumbs()) {
2120             slotSwitchVideoThumbs();
2121         }
2122         if (KdenliveSettings::audiothumbnails() != w->enableAudioThumbs()) {
2123             slotSwitchAudioThumbs();
2124         }
2125         if (project->getDocumentProperty(QStringLiteral("previewparameters")) != w->previewParams() ||
2126             project->getDocumentProperty(QStringLiteral("previewextension")) != w->previewExtension()) {
2127             modified = true;
2128             project->setDocumentProperty(QStringLiteral("previewparameters"), w->previewParams());
2129             project->setDocumentProperty(QStringLiteral("previewextension"), w->previewExtension());
2130             slotClearPreviewRender(false);
2131         }
2132 
2133         bool proxiesChanged = false;
2134         if (project->getDocumentProperty(QStringLiteral("proxyparams")) != w->proxyParams() ||
2135             project->getDocumentProperty(QStringLiteral("proxyextension")) != w->proxyExtension()) {
2136             modified = true;
2137             proxiesChanged = true;
2138             project->setDocumentProperty(QStringLiteral("proxyparams"), w->proxyParams());
2139             project->setDocumentProperty(QStringLiteral("proxyextension"), w->proxyExtension());
2140         }
2141         if (project->getDocumentProperty(QStringLiteral("externalproxyparams")) != w->externalProxyParams()) {
2142             modified = true;
2143             proxiesChanged = true;
2144             project->setDocumentProperty(QStringLiteral("externalproxyparams"), w->externalProxyParams());
2145         }
2146         if (proxiesChanged && pCore->projectItemModel()->hasProxies() &&
2147             KMessageBox::questionTwoActions(this, i18n("You have changed the proxy parameters. Do you want to recreate all proxy clips for this project?"), {},
2148                                             KGuiItem(i18nc("@action:button", "Recreate")),
2149                                             KGuiItem(i18nc("@action:button", "Continue without"))) == KMessageBox::PrimaryAction) {
2150             pCore->bin()->rebuildProxies();
2151         }
2152 
2153         if (project->getDocumentProperty(QStringLiteral("generateproxy")) != QString::number(int(w->generateProxy()))) {
2154             modified = true;
2155             project->setDocumentProperty(QStringLiteral("generateproxy"), QString::number(int(w->generateProxy())));
2156         }
2157         if (project->getDocumentProperty(QStringLiteral("proxyminsize")) != QString::number(w->proxyMinSize())) {
2158             modified = true;
2159             project->setDocumentProperty(QStringLiteral("proxyminsize"), QString::number(w->proxyMinSize()));
2160         }
2161         if (project->getDocumentProperty(QStringLiteral("generateimageproxy")) != QString::number(int(w->generateImageProxy()))) {
2162             modified = true;
2163             project->setDocumentProperty(QStringLiteral("generateimageproxy"), QString::number(int(w->generateImageProxy())));
2164         }
2165         if (project->getDocumentProperty(QStringLiteral("proxyimageminsize")) != QString::number(w->proxyImageMinSize())) {
2166             modified = true;
2167             project->setDocumentProperty(QStringLiteral("proxyimageminsize"), QString::number(w->proxyImageMinSize()));
2168         }
2169         if (project->getDocumentProperty(QStringLiteral("proxyimagesize")) != QString::number(w->proxyImageSize())) {
2170             modified = true;
2171             project->setDocumentProperty(QStringLiteral("proxyimagesize"), QString::number(w->proxyImageSize()));
2172         }
2173         if (project->getDocumentProperty(QStringLiteral("proxyresize")) != QString::number(w->proxyResize())) {
2174             modified = true;
2175             project->setDocumentProperty(QStringLiteral("proxyresize"), QString::number(w->proxyResize()));
2176         }
2177         if (QString::number(int(w->useProxy())) != project->getDocumentProperty(QStringLiteral("enableproxy"))) {
2178             project->setDocumentProperty(QStringLiteral("enableproxy"), QString::number(int(w->useProxy())));
2179             modified = true;
2180             slotUpdateProxySettings();
2181         }
2182         if (QString::number(int(w->useExternalProxy())) != project->getDocumentProperty(QStringLiteral("enableexternalproxy"))) {
2183             project->setDocumentProperty(QStringLiteral("enableexternalproxy"), QString::number(int(w->useExternalProxy())));
2184             modified = true;
2185         }
2186         if (w->metadata() != project->metadata()) {
2187             project->setMetadata(w->metadata());
2188             if (m_renderWidget) {
2189                 m_renderWidget->updateMetadataToolTip();
2190             }
2191         }
2192         QString newProjectFolder = w->storageFolder();
2193 
2194         if (w->docFolderAsStorageFolder()) {
2195             newProjectFolder = QFileInfo(project->url().toLocalFile()).absolutePath() + QStringLiteral("/cachefiles");
2196         }
2197         if (newProjectFolder.isEmpty()) {
2198             newProjectFolder = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
2199         }
2200         if (newProjectFolder != project->projectTempFolder()) {
2201             KMessageBox::ButtonCode answer;
2202             // Project folder changed:
2203             if (project->isModified()) {
2204                 answer = KMessageBox::warningContinueCancel(
2205                     this, i18n("The current project has not been saved.<br/>This will first save the project, then move "
2206                                "all temporary files from <br/><b>%1</b> to <b>%2</b>,<br>and the project file will be reloaded",
2207                                project->projectTempFolder(), newProjectFolder));
2208                 if (answer == KMessageBox::Continue) {
2209                     pCore->projectManager()->saveFile();
2210                 }
2211             } else {
2212                 answer = KMessageBox::warningContinueCancel(
2213                     this, i18n("This will move all temporary files from<br/><b>%1</b> to <b>%2</b>,<br/>the project file will then be reloaded",
2214                                project->projectTempFolder(), newProjectFolder));
2215             }
2216             if (answer == KMessageBox::Continue) {
2217                 // Proceed with move
2218                 QString documentId = QDir::cleanPath(project->getDocumentProperty(QStringLiteral("documentid")));
2219                 bool ok;
2220                 documentId.toLongLong(&ok, 10);
2221                 if (!ok || documentId.isEmpty()) {
2222                     KMessageBox::error(this, i18n("Cannot perform operation, invalid document id: %1", documentId));
2223                 } else {
2224                     QDir newDir(newProjectFolder);
2225                     QDir oldDir(project->projectTempFolder());
2226                     if (newDir.exists(documentId)) {
2227                         KMessageBox::error(this, i18n("Cannot perform operation, target directory already exists: %1", newDir.absoluteFilePath(documentId)));
2228                     } else {
2229                         // Proceed with the move
2230                         pCore->projectManager()->moveProjectData(oldDir.absoluteFilePath(documentId), newDir.absolutePath());
2231                     }
2232                 }
2233             }
2234         }
2235         if (pCore->getCurrentProfile()->path() != profile || project->profileChanged(profile)) {
2236             if (!qFuzzyCompare(pCore->getCurrentProfile()->fps() - ProfileRepository::get()->getProfile(profile)->fps(), 0.)) {
2237                 // Fps was changed, we save the project to an xml file with updated profile and reload project
2238                 // Check if blank project
2239                 if (project->url().fileName().isEmpty() && !project->isModified()) {
2240                     // Trying to switch project profile from an empty project
2241                     pCore->setCurrentProfile(profile);
2242                     pCore->projectManager()->newFile(profile, false);
2243                     return;
2244                 }
2245                 pCore->projectManager()->saveWithUpdatedProfile(profile);
2246             } else {
2247                 bool darChanged = !qFuzzyCompare(pCore->getCurrentProfile()->dar(), ProfileRepository::get()->getProfile(profile)->dar());
2248                 pCore->setCurrentProfile(profile);
2249                 pCore->projectManager()->slotResetProfiles(darChanged);
2250                 slotUpdateDocumentState(true);
2251             }
2252         } else if (modified) {
2253             project->setModified();
2254         }
2255     }
2256     delete w;
2257 }
2258 
2259 void MainWindow::slotDisableProxies()
2260 {
2261     pCore->currentDoc()->setDocumentProperty(QStringLiteral("enableproxy"), QString::number(false));
2262     pCore->currentDoc()->setModified();
2263     slotUpdateProxySettings();
2264 }
2265 
2266 void MainWindow::slotStopRenderProject()
2267 {
2268     if (m_renderWidget) {
2269         m_renderWidget->slotAbortCurrentJob();
2270     }
2271 }
2272 
2273 void MainWindow::updateProjectPath(const QString &path)
2274 {
2275     if (m_renderWidget) {
2276         m_renderWidget->resetRenderPath(path);
2277     } else {
2278         // Clear render name as project url changed
2279         QMap<QString, QString> renderProps;
2280         renderProps.insert(QStringLiteral("renderurl"), QString());
2281         slotSetDocumentRenderProfile(renderProps);
2282     }
2283 }
2284 
2285 void MainWindow::slotRenderProject()
2286 {
2287     KdenliveDoc *project = pCore->currentDoc();
2288 
2289     if (!m_renderWidget && project) {
2290         m_renderWidget = new RenderWidget(project->useProxy(), this);
2291         connect(m_renderWidget, &RenderWidget::shutdown, this, &MainWindow::slotShutdown);
2292         connect(m_renderWidget, &RenderWidget::selectedRenderProfile, this, &MainWindow::slotSetDocumentRenderProfile);
2293         connect(m_renderWidget, &RenderWidget::abortProcess, this, &MainWindow::abortRenderJob);
2294         connect(pCore.get(), &Core::gotMissingClipsCount, m_renderWidget, &RenderWidget::updateMissingClipsCount);
2295         connect(this, &MainWindow::updateRenderWidgetProfile, m_renderWidget, &RenderWidget::adjustViewToProfile);
2296         m_renderWidget->setGuides(project->getGuideModel(getCurrentTimeline()->getUuid()));
2297         m_renderWidget->updateDocumentPath();
2298         m_renderWidget->setRenderProfile(project->getRenderProperties());
2299     }
2300 
2301     slotCheckRenderStatus();
2302     if (m_renderWidget) {
2303         m_renderWidget->showNormal();
2304     }
2305 
2306     // What are the following lines supposed to do?
2307     // m_renderWidget->enableAudio(false);
2308     // m_renderWidget->export_audio;
2309 }
2310 
2311 void MainWindow::slotCheckRenderStatus()
2312 {
2313     // Make sure there are no missing clips
2314     // TODO
2315     /*if (m_renderWidget)
2316         m_renderWidget->missingClips(pCore->bin()->hasMissingClips());*/
2317 }
2318 
2319 void MainWindow::setRenderingProgress(const QString &url, int progress, int frame)
2320 {
2321     Q_EMIT setRenderProgress(progress);
2322     if (m_renderWidget) {
2323         m_renderWidget->setRenderProgress(url, progress, frame);
2324     }
2325 }
2326 
2327 void MainWindow::setRenderingFinished(const QString &url, int status, const QString &error)
2328 {
2329     Q_EMIT setRenderProgress(100);
2330     if (m_renderWidget) {
2331         m_renderWidget->setRenderStatus(url, status, error);
2332     }
2333 }
2334 
2335 void MainWindow::addProjectClip(const QString &url, const QString &folder)
2336 {
2337     if (pCore->currentDoc()) {
2338         QStringList ids = pCore->projectItemModel()->getClipByUrl(QFileInfo(url));
2339         if (!ids.isEmpty()) {
2340             // Clip is already in project bin, abort
2341             return;
2342         }
2343         ClipCreator::createClipFromFile(url, folder, pCore->projectItemModel());
2344     }
2345 }
2346 
2347 void MainWindow::addTimelineClip(const QString &url)
2348 {
2349     if (pCore->currentDoc()) {
2350         QStringList ids = pCore->projectItemModel()->getClipByUrl(QFileInfo(url));
2351         if (!ids.isEmpty()) {
2352             pCore->selectBinClip(ids.constFirst());
2353             slotInsertClipInsert();
2354         }
2355     }
2356 }
2357 
2358 void MainWindow::scriptRender(const QString &url)
2359 {
2360     Q_UNUSED(url)
2361     slotRenderProject();
2362     m_renderWidget->slotPrepareExport(true);
2363 }
2364 
2365 #ifndef NODBUS
2366 void MainWindow::exitApp()
2367 {
2368     QApplication::exit(0);
2369 }
2370 #endif
2371 
2372 void MainWindow::slotCleanProject()
2373 {
2374     if (KMessageBox::warningContinueCancel(this, i18n("This will remove all unused clips from your project."), i18n("Clean up project")) ==
2375         KMessageBox::Cancel) {
2376         return;
2377     }
2378     pCore->bin()->cleanupUnused();
2379 }
2380 
2381 void MainWindow::slotUpdateMousePosition(int pos, int duration)
2382 {
2383     if (pCore->currentDoc()) {
2384         if (duration < 0) {
2385             duration = getCurrentTimeline()->controller()->duration();
2386         }
2387         if (pos >= 0) {
2388             m_mousePosition = pos;
2389         }
2390         switch (m_timeFormatButton->currentItem()) {
2391         case 0:
2392             m_timeFormatButton->setText(pCore->currentDoc()->timecode().getTimecodeFromFrames(m_mousePosition) + QStringLiteral(" / ") +
2393                                         pCore->currentDoc()->timecode().getTimecodeFromFrames(duration));
2394             break;
2395         default:
2396             m_timeFormatButton->setText(QStringLiteral("%1 / %2").arg(m_mousePosition, 6, 10, QLatin1Char('0')).arg(duration, 6, 10, QLatin1Char('0')));
2397         }
2398     }
2399 }
2400 
2401 void MainWindow::slotUpdateProjectDuration(int duration)
2402 {
2403     if (pCore->currentDoc()) {
2404         slotUpdateMousePosition(-1, duration);
2405     }
2406     if (m_renderWidget) {
2407         m_renderWidget->projectDurationChanged(duration);
2408     }
2409 }
2410 
2411 void MainWindow::slotUpdateZoneDuration(int duration)
2412 {
2413     if (m_renderWidget) {
2414         m_renderWidget->zoneDurationChanged(duration);
2415     }
2416 }
2417 
2418 void MainWindow::slotUpdateDocumentState(bool modified)
2419 {
2420     m_timelineTabs->updateWindowTitle();
2421     setWindowModified(modified);
2422     m_saveAction->setEnabled(modified);
2423 }
2424 
2425 void MainWindow::connectDocument()
2426 {
2427     KdenliveDoc *project = pCore->currentDoc();
2428     connect(project, &KdenliveDoc::startAutoSave, pCore->projectManager(), &ProjectManager::slotStartAutoSave);
2429     connect(project, &KdenliveDoc::reloadEffects, this, &MainWindow::slotReloadEffects);
2430     KdenliveSettings::setProject_fps(pCore->getCurrentFps());
2431     slotSwitchTimelineZone(project->getDocumentProperty(QStringLiteral("enableTimelineZone")).toInt() == 1);
2432     // update track compositing
2433     bool compositing = project->getDocumentProperty(QStringLiteral("compositing"), QStringLiteral("1")).toInt() > 0;
2434     Q_EMIT project->updateCompositionMode(compositing);
2435     getCurrentTimeline()->controller()->switchCompositing(compositing);
2436     slotUpdateProjectDuration(getCurrentTimeline()->model()->duration() - 1);
2437     const QUuid uuid = getCurrentTimeline()->getUuid();
2438 
2439     int activeTrackPosition = project->getSequenceProperty(uuid, QStringLiteral("activeTrack"), QString::number(-1)).toInt();
2440     if (activeTrackPosition == -2) {
2441         // Subtitle model track always has ID == -2
2442         getCurrentTimeline()->controller()->setActiveTrack(-2);
2443     } else if (activeTrackPosition > -1 && activeTrackPosition < getCurrentTimeline()->model()->getTracksCount()) {
2444         // otherwise, convert the position to a track ID
2445         getCurrentTimeline()->controller()->setActiveTrack(getCurrentTimeline()->model()->getTrackIndexFromPosition(activeTrackPosition));
2446     } else {
2447         qWarning() << "[BUG] \"activeTrack\" property is" << activeTrackPosition << "but track count is only"
2448                    << getCurrentTimeline()->model()->getTracksCount();
2449         // set it to some valid track instead
2450         getCurrentTimeline()->controller()->setActiveTrack(getCurrentTimeline()->model()->getTrackIndexFromPosition(0));
2451     }
2452 
2453     m_clipMonitor->updateDocumentUuid();
2454     connect(m_projectMonitor, &Monitor::multitrackView, getCurrentTimeline()->controller(), &TimelineController::slotMultitrackView, Qt::UniqueConnection);
2455     connect(m_projectMonitor, &Monitor::activateTrack, getCurrentTimeline()->controller(), &TimelineController::activateTrackAndSelect, Qt::UniqueConnection);
2456     connect(getCurrentTimeline()->controller(), &TimelineController::timelineClipSelected, this, [&](bool selected) {
2457         m_loopClip->setEnabled(selected);
2458         Q_EMIT pCore->library()->enableAddSelection(selected);
2459     });
2460     connect(pCore->library(), &LibraryWidget::saveTimelineSelection, getCurrentTimeline()->controller(), &TimelineController::saveTimelineSelection,
2461             Qt::UniqueConnection);
2462     connect(pCore->mixer(), &MixerManager::purgeCache, m_projectMonitor, &Monitor::purgeCache);
2463     connect(m_projectMonitor, &Monitor::zoneUpdated, project, [project](const QPoint &) { project->setModified(); });
2464     connect(m_clipMonitor, &Monitor::zoneUpdated, project, [project](const QPoint &) { project->setModified(); });
2465     connect(project, &KdenliveDoc::docModified, this, &MainWindow::slotUpdateDocumentState);
2466 
2467     if (m_renderWidget) {
2468         slotCheckRenderStatus();
2469         m_renderWidget->setGuides(pCore->currentDoc()->getGuideModel(uuid));
2470         m_renderWidget->updateDocumentPath();
2471         m_renderWidget->setRenderProfile(project->getRenderProperties());
2472         m_renderWidget->updateMetadataToolTip();
2473     }
2474 
2475     m_commandStack->setActiveStack(project->commandStack().get());
2476     m_timelineTabs->updateWindowTitle();
2477     setWindowModified(project->isModified());
2478     m_saveAction->setEnabled(project->isModified());
2479     m_normalEditTool->setChecked(true);
2480     connect(m_projectMonitor, &Monitor::durationChanged, this, &MainWindow::slotUpdateProjectDuration);
2481     connect(m_projectMonitor, &Monitor::zoneDurationChanged, this, &MainWindow::slotUpdateZoneDuration);
2482     connect(m_effectList2, &EffectListWidget::reloadFavorites, getCurrentTimeline(), &TimelineWidget::updateEffectFavorites);
2483     connect(m_compositionList, &TransitionListWidget::reloadFavorites, getCurrentTimeline(), &TimelineWidget::updateTransitionFavorites);
2484     connect(pCore->bin(), &Bin::processDragEnd, getCurrentTimeline(), &TimelineWidget::endDrag);
2485 
2486     // Load master effect zones
2487     getCurrentTimeline()->controller()->updateMasterZones(getCurrentTimeline()->model()->getMasterEffectZones());
2488 
2489     m_buttonSelectTool->setChecked(true);
2490     connect(m_projectMonitorDock, &QDockWidget::visibilityChanged, m_projectMonitor, &Monitor::slotRefreshMonitor, Qt::UniqueConnection);
2491     connect(m_clipMonitorDock, &QDockWidget::visibilityChanged, m_clipMonitor, &Monitor::slotRefreshMonitor, Qt::UniqueConnection);
2492     pCore->guidesList()->reset();
2493     getCurrentTimeline()->focusTimeline();
2494 }
2495 
2496 void MainWindow::slotEditKeys()
2497 {
2498     KShortcutsDialog dialog(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this);
2499 
2500 #if KXMLGUI_VERSION >= QT_VERSION_CHECK(5, 98, 0)
2501     KNSWidgets::Action *downloadKeybordSchemes =
2502         new KNSWidgets::Action(i18n("Download New Keyboard Schemes…"), QStringLiteral(":data/kdenlive_keyboardschemes.knsrc"), this);
2503     connect(downloadKeybordSchemes, &KNSWidgets::Action::dialogFinished, this, [&](const QList<KNSCore::Entry> &changedEntries) {
2504         if (changedEntries.count() > 0) {
2505             dialog.refreshSchemes();
2506         }
2507     });
2508     dialog.addActionToSchemesMoreButton(downloadKeybordSchemes);
2509 #else
2510     // Find the combobox inside KShortcutsDialog for choosing keyboard scheme
2511     QComboBox *schemesList = nullptr;
2512     for (QLabel *label : dialog.findChildren<QLabel *>()) {
2513         if (label->text() == i18n("Current scheme:")) {
2514             schemesList = qobject_cast<QComboBox *>(label->buddy());
2515             break;
2516         }
2517     }
2518     // If scheme choosing combobox was found, find the "More Actions" button in the same
2519     // dialog that provides a dropdown menu with additional actions, and add
2520     // "Download New Keyboard Schemes…" button into that menu
2521     if (schemesList) {
2522         for (QPushButton *button : dialog.findChildren<QPushButton *>()) {
2523             if (button->text() == i18n("More Actions")) {
2524                 QMenu *moreActionsMenu = button->menu();
2525                 if (moreActionsMenu) {
2526                     moreActionsMenu->addAction(i18n("Download New Keyboard Schemes…"), this, [this, schemesList] { slotGetNewKeyboardStuff(schemesList); });
2527                 }
2528                 break;
2529             }
2530         }
2531     } else {
2532         qWarning() << "Could not get list of schemes. Downloading new schemes is not available.";
2533     }
2534 #endif
2535     dialog.addCollection(actionCollection(), i18nc("general keyboard shortcuts", "General"));
2536     // Update the shortcut conflicts list bewtween mainwindow and media browser
2537     connect(&dialog, &KShortcutsDialog::saved, pCore->mediaBrowser(), &MediaBrowser::detectShortcutConflicts);
2538     dialog.configure();
2539 }
2540 
2541 void MainWindow::slotPreferences()
2542 {
2543     slotShowPreferencePage(Kdenlive::NoPage);
2544 }
2545 
2546 void MainWindow::slotShowPreferencePage(Kdenlive::ConfigPage page, int option)
2547 {
2548     /*
2549      * An instance of your dialog could be already created and could be
2550      * cached, in which case you want to display the cached dialog
2551      * instead of creating another one
2552      */
2553     if (KConfigDialog::showDialog(QStringLiteral("settings"))) {
2554         KdenliveSettingsDialog *d = static_cast<KdenliveSettingsDialog *>(KConfigDialog::exists(QStringLiteral("settings")));
2555         if (page != Kdenlive::NoPage) {
2556             d->showPage(page, option);
2557         }
2558         return;
2559     }
2560 
2561     // KConfigDialog didn't find an instance of this dialog, so lets
2562     // create it :
2563 
2564     // Get the mappable actions in localized form
2565     QMap<QString, QString> actions;
2566     KActionCollection *collection = actionCollection();
2567     for (const QString &action_name : qAsConst(m_actionNames)) {
2568         const QString action_text = KLocalizedString::removeAcceleratorMarker(collection->action(action_name)->text());
2569         actions[action_text] = action_name;
2570     }
2571 
2572     auto *dialog = new KdenliveSettingsDialog(actions, m_gpuAllowed, this);
2573     connect(dialog, &KConfigDialog::settingsChanged, this, &MainWindow::updateConfiguration);
2574     connect(dialog, &KConfigDialog::settingsChanged, this, &MainWindow::configurationChanged);
2575     connect(dialog, &KdenliveSettingsDialog::doResetConsumer, this, [this](bool fullReset) {
2576         m_scaleGroup->setEnabled(!KdenliveSettings::external_display());
2577         pCore->projectManager()->slotResetConsumers(fullReset);
2578     });
2579     connect(dialog, &KdenliveSettingsDialog::checkTabPosition, this, &MainWindow::slotCheckTabPosition);
2580     connect(dialog, &KdenliveSettingsDialog::restartKdenlive, this, &MainWindow::slotRestart);
2581     connect(dialog, &KdenliveSettingsDialog::updateLibraryFolder, pCore.get(), &Core::updateLibraryPath);
2582     connect(dialog, &KdenliveSettingsDialog::audioThumbFormatChanged, m_timelineTabs, &TimelineTabs::audioThumbFormatChanged);
2583     connect(dialog, &KdenliveSettingsDialog::resetView, this, &MainWindow::resetTimelineTracks);
2584     connect(dialog, &KdenliveSettingsDialog::updateMonitorBg, [&]() { pCore->monitorManager()->updateBgColor(); });
2585     connect(dialog, &KdenliveSettingsDialog::resetAudioMonitoring, pCore.get(), &Core::resetAudioMonitoring);
2586 
2587     dialog->show();
2588     if (page != Kdenlive::NoPage) {
2589         dialog->showPage(page, option);
2590     }
2591 }
2592 
2593 void MainWindow::slotCheckTabPosition()
2594 {
2595     int pos = tabPosition(Qt::LeftDockWidgetArea);
2596     if (KdenliveSettings::tabposition() != pos) {
2597         setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::TabPosition(KdenliveSettings::tabposition()));
2598     }
2599 }
2600 
2601 void MainWindow::slotRestart(bool clean)
2602 {
2603     if (clean) {
2604         if (KMessageBox::warningContinueCancel(this,
2605                                                i18n("This will delete Kdenlive's configuration file and restart the application. Do you want to proceed?"),
2606                                                i18nc("@title:window", "Reset Configuration")) != KMessageBox::Continue) {
2607             return;
2608         }
2609     }
2610     cleanRestart(clean);
2611 }
2612 
2613 void MainWindow::cleanRestart(bool clean)
2614 {
2615     m_exitCode = clean ? EXIT_CLEAN_RESTART : EXIT_RESTART;
2616     QApplication::closeAllWindows();
2617 }
2618 
2619 void MainWindow::closeEvent(QCloseEvent *event)
2620 {
2621     KXmlGuiWindow::closeEvent(event);
2622     if (event->isAccepted()) {
2623         QApplication::exit(m_exitCode);
2624         return;
2625     }
2626 }
2627 
2628 void MainWindow::updateConfiguration()
2629 {
2630     // TODO: we should apply settings to all projects, not only the current one
2631     m_buttonAudioThumbs->setChecked(KdenliveSettings::audiothumbnails());
2632     m_buttonVideoThumbs->setChecked(KdenliveSettings::videothumbnails());
2633     m_buttonShowMarkers->setChecked(KdenliveSettings::showmarkers());
2634 
2635     // Update list of transcoding profiles
2636     buildDynamicActions();
2637     loadClipActions();
2638 }
2639 
2640 void MainWindow::slotSwitchVideoThumbs()
2641 {
2642     KdenliveSettings::setVideothumbnails(!KdenliveSettings::videothumbnails());
2643     Q_EMIT m_timelineTabs->showThumbnailsChanged();
2644     m_buttonVideoThumbs->setChecked(KdenliveSettings::videothumbnails());
2645 }
2646 
2647 void MainWindow::slotSwitchAudioThumbs()
2648 {
2649     KdenliveSettings::setAudiothumbnails(!KdenliveSettings::audiothumbnails());
2650     pCore->bin()->checkAudioThumbs();
2651     Q_EMIT m_timelineTabs->showAudioThumbnailsChanged();
2652     m_buttonAudioThumbs->setChecked(KdenliveSettings::audiothumbnails());
2653 }
2654 
2655 void MainWindow::slotSwitchMarkersComments()
2656 {
2657     KdenliveSettings::setShowmarkers(!KdenliveSettings::showmarkers());
2658     Q_EMIT getCurrentTimeline()->controller()->showMarkersChanged();
2659     m_buttonShowMarkers->setChecked(KdenliveSettings::showmarkers());
2660 }
2661 
2662 void MainWindow::slotSwitchSnap()
2663 {
2664     KdenliveSettings::setSnaptopoints(!KdenliveSettings::snaptopoints());
2665     m_buttonSnap->setChecked(KdenliveSettings::snaptopoints());
2666     Q_EMIT getCurrentTimeline()->controller()->snapChanged();
2667 }
2668 
2669 void MainWindow::slotShowTimelineTags()
2670 {
2671     KdenliveSettings::setTagsintimeline(!KdenliveSettings::tagsintimeline());
2672     m_buttonTimelineTags->setChecked(KdenliveSettings::tagsintimeline());
2673     // Reset view to update timeline colors
2674     getCurrentTimeline()->model()->_resetView();
2675 }
2676 
2677 void MainWindow::slotDeleteItem()
2678 {
2679     if (QApplication::focusWidget() != nullptr) {
2680         for (auto &bin : m_binWidgets) {
2681             if (bin->isAncestorOf(QApplication::focusWidget())) {
2682                 bin->slotDeleteClip();
2683                 return;
2684             }
2685         }
2686     }
2687     if (QApplication::focusWidget() != nullptr && pCore->textEditWidget()->isAncestorOf(QApplication::focusWidget())) {
2688         pCore->textEditWidget()->deleteItem();
2689     } else {
2690         QWidget *widget = QApplication::focusWidget();
2691         while ((widget != nullptr) && widget != this) {
2692             if (widget == m_effectStackDock) {
2693                 m_assetPanel->deleteCurrentEffect();
2694                 return;
2695             }
2696             if (widget == pCore->guidesList()) {
2697                 pCore->guidesList()->removeGuide();
2698                 return;
2699             }
2700             widget = widget->parentWidget();
2701         }
2702 
2703         // effect stack has no focus
2704         getCurrentTimeline()->controller()->deleteSelectedClips();
2705     }
2706 }
2707 
2708 void MainWindow::slotAddClipMarker()
2709 {
2710     std::shared_ptr<ProjectClip> clip(nullptr);
2711     GenTime pos;
2712     if (m_projectMonitor->isActive()) {
2713         getCurrentTimeline()->controller()->addMarker();
2714         return;
2715     } else {
2716         clip = m_clipMonitor->currentController();
2717         pos = GenTime(m_clipMonitor->position(), pCore->getCurrentFps());
2718     }
2719     if (!clip) {
2720         m_messageLabel->setMessage(i18n("Cannot find clip to add marker"), ErrorMessage);
2721         return;
2722     }
2723     clip->getMarkerModel()->editMarkerGui(pos, this, true, clip.get());
2724 }
2725 
2726 void MainWindow::slotDeleteClipMarker(bool allowGuideDeletion)
2727 {
2728     std::shared_ptr<ProjectClip> clip(nullptr);
2729     GenTime pos;
2730     if (m_projectMonitor->isActive()) {
2731         getCurrentTimeline()->controller()->deleteMarker();
2732         return;
2733     } else {
2734         clip = m_clipMonitor->currentController();
2735         pos = GenTime(m_clipMonitor->position(), pCore->getCurrentFps());
2736     }
2737     if (!clip) {
2738         m_messageLabel->setMessage(i18n("Cannot find clip to remove marker"), ErrorMessage);
2739         return;
2740     }
2741 
2742     bool markerFound = false;
2743     clip->getMarkerModel()->getMarker(pos, &markerFound);
2744     if (!markerFound) {
2745         if (allowGuideDeletion && m_projectMonitor->isActive()) {
2746             slotDeleteGuide();
2747         } else {
2748             m_messageLabel->setMessage(i18n("No marker found at cursor time"), ErrorMessage);
2749         }
2750         return;
2751     }
2752     clip->getMarkerModel()->removeMarker(pos);
2753 }
2754 
2755 void MainWindow::slotDeleteAllClipMarkers()
2756 {
2757     std::shared_ptr<ProjectClip> clip(nullptr);
2758     if (m_projectMonitor->isActive()) {
2759         getCurrentTimeline()->controller()->deleteAllMarkers();
2760         return;
2761     } else {
2762         clip = m_clipMonitor->currentController();
2763     }
2764     if (!clip) {
2765         m_messageLabel->setMessage(i18n("Cannot find clip to remove marker"), ErrorMessage);
2766         return;
2767     }
2768     bool ok = clip->getMarkerModel()->removeAllMarkers();
2769     if (!ok) {
2770         m_messageLabel->setMessage(i18n("An error occurred while deleting markers"), ErrorMessage);
2771         return;
2772     }
2773 }
2774 
2775 void MainWindow::slotEditClipMarker()
2776 {
2777     std::shared_ptr<ProjectClip> clip(nullptr);
2778     GenTime pos;
2779     if (m_projectMonitor->isActive()) {
2780         getCurrentTimeline()->controller()->editMarker();
2781         return;
2782     } else {
2783         clip = m_clipMonitor->currentController();
2784         pos = GenTime(m_clipMonitor->position(), pCore->getCurrentFps());
2785     }
2786     if (!clip) {
2787         m_messageLabel->setMessage(i18n("Cannot find clip to edit marker"), ErrorMessage);
2788         return;
2789     }
2790 
2791     bool markerFound = false;
2792     clip->getMarkerModel()->getMarker(pos, &markerFound);
2793     if (!markerFound) {
2794         m_messageLabel->setMessage(i18n("No marker found at cursor time"), ErrorMessage);
2795         return;
2796     }
2797 
2798     clip->getMarkerModel()->editMarkerGui(pos, this, false, clip.get());
2799     // Focus back clip monitor
2800     m_clipMonitor->setFocus();
2801 }
2802 
2803 void MainWindow::slotAddMarkerGuideQuickly()
2804 {
2805     if (!getCurrentTimeline() || !pCore->currentDoc()) {
2806         return;
2807     }
2808 
2809     if (m_clipMonitor->isActive()) {
2810         pCore->bin()->addClipMarker(m_clipMonitor->activeClipId(), {m_clipMonitor->position()});
2811     } else {
2812         int selectedClip = getCurrentTimeline()->controller()->getMainSelectedItem();
2813         if (selectedClip == -1) {
2814             // Add timeline guide
2815             getCurrentTimeline()->controller()->switchGuide();
2816         } else {
2817             // Add marker to main clip
2818             getCurrentTimeline()->controller()->addQuickMarker(selectedClip);
2819         }
2820     }
2821 }
2822 
2823 void MainWindow::slotAddGuide()
2824 {
2825     getCurrentTimeline()->controller()->switchGuide(-1, false, true);
2826 }
2827 
2828 void MainWindow::slotInsertSpace()
2829 {
2830     getCurrentTimeline()->controller()->insertSpace();
2831 }
2832 
2833 void MainWindow::slotRemoveSpace()
2834 {
2835     getCurrentTimeline()->controller()->removeSpace(-1, -1, false);
2836 }
2837 
2838 void MainWindow::slotRemoveSpaceInAllTracks()
2839 {
2840     getCurrentTimeline()->controller()->removeSpace(-1, -1, true);
2841 }
2842 
2843 void MainWindow::slotRemoveAllSpacesInTrack()
2844 {
2845     getCurrentTimeline()->controller()->removeTrackSpaces(-1, -1);
2846 }
2847 
2848 void MainWindow::slotRemoveAllClipsInTrack()
2849 {
2850     getCurrentTimeline()->controller()->removeTrackClips(-1, -1);
2851 }
2852 
2853 void MainWindow::slotSeparateAudioChannel()
2854 {
2855     KdenliveSettings::setDisplayallchannels(!KdenliveSettings::displayallchannels());
2856     Q_EMIT getCurrentTimeline()->controller()->audioThumbFormatChanged();
2857     if (m_clipMonitor) {
2858         m_clipMonitor->refreshAudioThumbs();
2859     }
2860 }
2861 
2862 void MainWindow::slotAutoTrackHeight(bool enable)
2863 {
2864     KdenliveSettings::setAutotrackheight(enable);
2865     Q_EMIT pCore->autoTrackHeight(enable);
2866 }
2867 
2868 void MainWindow::slotNormalizeAudioChannel()
2869 {
2870     KdenliveSettings::setNormalizechannels(!KdenliveSettings::normalizechannels());
2871     Q_EMIT getCurrentTimeline()->controller()->audioThumbNormalizeChanged();
2872     if (m_clipMonitor) {
2873         m_clipMonitor->normalizeAudioThumbs();
2874     }
2875 }
2876 
2877 void MainWindow::slotInsertTrack()
2878 {
2879     pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor);
2880     getCurrentTimeline()->controller()->beginAddTrack(-1);
2881 }
2882 
2883 void MainWindow::slotDeleteTrack()
2884 {
2885     pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor);
2886     getCurrentTimeline()->controller()->deleteMultipleTracks(-1);
2887 }
2888 
2889 void MainWindow::slotSwitchTrackAudioStream()
2890 {
2891     getCurrentTimeline()->showTargetMenu();
2892 }
2893 
2894 void MainWindow::slotShowTrackRec(bool checked)
2895 {
2896     if (checked) {
2897         pCore->mixer()->monitorAudio(getCurrentTimeline()->controller()->activeTrack(), checked);
2898     } else {
2899         pCore->mixer()->monitorAudio(pCore->mixer()->recordTrack(), false);
2900     }
2901 }
2902 
2903 void MainWindow::slotSelectTrack()
2904 {
2905     getCurrentTimeline()->controller()->selectCurrentTrack();
2906 }
2907 
2908 void MainWindow::slotSelectAllTracks()
2909 {
2910     if (QApplication::focusWidget() != nullptr) {
2911         if (QApplication::focusWidget()->parentWidget() != nullptr) {
2912             for (auto &bin : m_binWidgets) {
2913                 if (bin->isAncestorOf(QApplication::focusWidget())) {
2914                     bin->selectAll();
2915                     return;
2916                 }
2917             }
2918         }
2919         if (QApplication::focusWidget()->objectName() == QLatin1String("guides_list")) {
2920             pCore->guidesList()->selectAll();
2921             return;
2922         }
2923     }
2924     getCurrentTimeline()->controller()->selectAll();
2925 }
2926 
2927 void MainWindow::slotUnselectAllTracks()
2928 {
2929     getCurrentTimeline()->model()->requestClearSelection();
2930 }
2931 
2932 void MainWindow::slotEditGuide()
2933 {
2934     getCurrentTimeline()->controller()->editGuide();
2935 }
2936 
2937 void MainWindow::slotSearchGuide()
2938 {
2939     pCore->guidesList()->filter_line->setFocus();
2940 }
2941 
2942 void MainWindow::slotExportGuides()
2943 {
2944     pCore->currentDoc()
2945         ->getGuideModel(getCurrentTimeline()->getUuid())
2946         ->exportGuidesGui(this, GenTime(getCurrentTimeline()->controller()->duration() - 1, pCore->getCurrentFps()));
2947 }
2948 
2949 void MainWindow::slotLockGuides(bool lock)
2950 {
2951     KdenliveSettings::setLockedGuides(lock);
2952     Q_EMIT getCurrentTimeline()->controller()->guidesLockedChanged();
2953 }
2954 
2955 void MainWindow::slotDeleteGuide()
2956 {
2957     getCurrentTimeline()->controller()->switchGuide(-1, true);
2958 }
2959 
2960 void MainWindow::slotDeleteAllGuides()
2961 {
2962     pCore->currentDoc()->getGuideModel(getCurrentTimeline()->getUuid())->removeAllMarkers();
2963 }
2964 
2965 void MainWindow::slotCutTimelineClip()
2966 {
2967     getCurrentTimeline()->controller()->cutClipUnderCursor();
2968 }
2969 
2970 void MainWindow::slotCutTimelineAllClips()
2971 {
2972     getCurrentTimeline()->controller()->cutAllClipsUnderCursor();
2973 }
2974 
2975 void MainWindow::slotInsertClipOverwrite()
2976 {
2977     const QString &binId = m_clipMonitor->activeClipId();
2978     if (binId.isEmpty()) {
2979         // No clip in monitor
2980         return;
2981     }
2982     std::function<bool(void)> undo = []() { return true; };
2983     std::function<bool(void)> redo = []() { return true; };
2984     bool res = getCurrentTimeline()->controller()->insertZone(binId, m_clipMonitor->getZoneInfo(), true, undo, redo);
2985     if (res) {
2986         pCore->pushUndo(undo, redo, i18n("Overwrite zone"));
2987     } else {
2988         pCore->displayMessage(i18n("Could not insert zone"), ErrorMessage);
2989         undo();
2990     }
2991 }
2992 
2993 void MainWindow::slotInsertClipInsert()
2994 {
2995     const QString &binId = m_clipMonitor->activeClipId();
2996     if (binId.isEmpty()) {
2997         // No clip in monitor
2998         pCore->displayMessage(i18n("No clip selected in project bin"), ErrorMessage);
2999         return;
3000     }
3001     std::function<bool(void)> undo = []() { return true; };
3002     std::function<bool(void)> redo = []() { return true; };
3003     bool res = getCurrentTimeline()->controller()->insertZone(binId, m_clipMonitor->getZoneInfo(), false, undo, redo);
3004     if (res) {
3005         pCore->pushUndo(undo, redo, i18n("Insert zone"));
3006     } else {
3007         pCore->displayMessage(i18n("Could not insert zone"), ErrorMessage);
3008         undo();
3009     }
3010 }
3011 
3012 void MainWindow::slotExtractZone()
3013 {
3014     getCurrentTimeline()->controller()->extractZone(m_clipMonitor->getZoneInfo());
3015 }
3016 
3017 void MainWindow::slotExtractClip()
3018 {
3019     getCurrentTimeline()->controller()->extract();
3020 }
3021 
3022 void MainWindow::slotSaveZoneToBin()
3023 {
3024     getCurrentTimeline()->controller()->saveZone();
3025 }
3026 
3027 void MainWindow::slotLiftZone()
3028 {
3029     getCurrentTimeline()->controller()->extractZone(m_clipMonitor->getZoneInfo(), true);
3030 }
3031 
3032 void MainWindow::slotPreviewRender()
3033 {
3034     if (pCore->currentDoc()) {
3035         getCurrentTimeline()->controller()->startPreviewRender();
3036     }
3037 }
3038 
3039 void MainWindow::slotStopPreviewRender()
3040 {
3041     if (pCore->currentDoc()) {
3042         getCurrentTimeline()->controller()->stopPreviewRender();
3043     }
3044 }
3045 
3046 void MainWindow::slotDefinePreviewRender()
3047 {
3048     if (pCore->currentDoc()) {
3049         getCurrentTimeline()->controller()->addPreviewRange(true);
3050     }
3051 }
3052 
3053 void MainWindow::slotRemovePreviewRender()
3054 {
3055     if (pCore->currentDoc()) {
3056         getCurrentTimeline()->controller()->addPreviewRange(false);
3057     }
3058 }
3059 
3060 void MainWindow::slotClearPreviewRender(bool resetZones)
3061 {
3062     if (pCore->currentDoc()) {
3063         getCurrentTimeline()->controller()->clearPreviewRange(resetZones);
3064     }
3065 }
3066 
3067 void MainWindow::slotSelectTimelineClip()
3068 {
3069     getCurrentTimeline()->controller()->selectCurrentItem(KdenliveObjectType::TimelineClip, true);
3070 }
3071 
3072 void MainWindow::slotSelectTimelineZone()
3073 {
3074     getCurrentTimeline()->controller()->setZoneToSelection();
3075 }
3076 void MainWindow::slotSelectTimelineTransition()
3077 {
3078     bool res = getCurrentTimeline()->controller()->selectCurrentItem(KdenliveObjectType::TimelineComposition, true, false, false);
3079     if (!res) {
3080         getCurrentTimeline()->controller()->selectCurrentItem(KdenliveObjectType::TimelineMix, true);
3081     }
3082 }
3083 
3084 void MainWindow::slotDeselectTimelineClip()
3085 {
3086     getCurrentTimeline()->controller()->selectCurrentItem(KdenliveObjectType::TimelineClip, false);
3087 }
3088 
3089 void MainWindow::slotDeselectTimelineTransition()
3090 {
3091     bool res = getCurrentTimeline()->controller()->selectCurrentItem(KdenliveObjectType::TimelineComposition, false, false, false);
3092     if (!res) {
3093         getCurrentTimeline()->controller()->selectCurrentItem(KdenliveObjectType::TimelineMix, false);
3094     }
3095 }
3096 
3097 void MainWindow::slotSelectAddTimelineClip()
3098 {
3099     getCurrentTimeline()->controller()->selectCurrentItem(KdenliveObjectType::TimelineClip, true, true);
3100 }
3101 
3102 void MainWindow::slotSelectAddTimelineTransition()
3103 {
3104     getCurrentTimeline()->controller()->selectCurrentItem(KdenliveObjectType::TimelineComposition, true, true);
3105 }
3106 
3107 void MainWindow::slotGroupClips()
3108 {
3109     getCurrentTimeline()->controller()->groupSelection();
3110 }
3111 
3112 void MainWindow::slotUnGroupClips()
3113 {
3114     getCurrentTimeline()->controller()->unGroupSelection();
3115 }
3116 
3117 void MainWindow::slotEditItemDuration()
3118 {
3119     getCurrentTimeline()->controller()->editItemDuration();
3120 }
3121 
3122 void MainWindow::slotAddProjectClip(const QUrl &url, const QString &folderInfo)
3123 {
3124     pCore->bin()->droppedUrls(QList<QUrl>() << url, folderInfo);
3125 }
3126 
3127 void MainWindow::slotAddTextNote(const QString &text)
3128 {
3129     pCore->projectManager()->slotAddTextNote(text);
3130 }
3131 
3132 void MainWindow::slotAddProjectClipList(const QList<QUrl> &urls)
3133 {
3134     pCore->bin()->droppedUrls(urls);
3135 }
3136 
3137 void MainWindow::slotAddTransition(QAction *result)
3138 {
3139     if (!result) {
3140         return;
3141     }
3142     // TODO refac
3143     /*
3144     QStringList info = result->data().toStringList();
3145     if (info.isEmpty() || info.count() < 2) {
3146         return;
3147     }
3148     QDomElement transition = transitions.getEffectByTag(info.at(0), info.at(1));
3149     if (pCore->projectManager()->currentTimeline() && !transition.isNull()) {
3150         pCore->projectManager()->currentTimeline()->projectView()->slotAddTransitionToSelectedClips(transition.cloneNode().toElement());
3151     }
3152     */
3153 }
3154 
3155 void MainWindow::slotAddEffect(QAction *result)
3156 {
3157     if (!result) {
3158         return;
3159     }
3160     QString effectId = result->data().toString();
3161     addEffect(effectId);
3162 }
3163 
3164 void MainWindow::addEffect(const QString &effectId)
3165 {
3166     if (m_assetPanel->effectStackOwner().type == KdenliveObjectType::BinClip) {
3167         // Pass the command to bin
3168         pCore->bin()->slotAddEffect({}, {effectId});
3169     } else if (m_assetPanel->effectStackOwner().type == KdenliveObjectType::TimelineTrack ||
3170                m_assetPanel->effectStackOwner().type == KdenliveObjectType::Master) {
3171         if (!m_assetPanel->addEffect(effectId)) {
3172             pCore->displayMessage(i18n("Cannot add effect to active item"), ErrorMessage);
3173         }
3174     } else {
3175         // Add effect to the current timeline selection
3176         QVariantMap effectData;
3177         effectData.insert(QStringLiteral("kdenlive/effect"), effectId);
3178         getCurrentTimeline()->controller()->addAsset(effectData);
3179     }
3180 }
3181 
3182 void MainWindow::slotZoomIn(bool zoomOnMouse)
3183 {
3184     slotSetZoom(m_zoomSlider->value() - 1, zoomOnMouse);
3185     slotShowZoomSliderToolTip();
3186 }
3187 
3188 void MainWindow::slotZoomOut(bool zoomOnMouse)
3189 {
3190     slotSetZoom(m_zoomSlider->value() + 1, zoomOnMouse);
3191     slotShowZoomSliderToolTip();
3192 }
3193 
3194 void MainWindow::slotFitZoom()
3195 {
3196     Q_EMIT m_timelineTabs->fitZoom();
3197 }
3198 
3199 void MainWindow::slotSetZoom(int value, bool zoomOnMouse)
3200 {
3201     value = qBound(m_zoomSlider->minimum(), value, m_zoomSlider->maximum());
3202     Q_EMIT m_timelineTabs->changeZoom(value, zoomOnMouse);
3203     updateZoomSlider(value);
3204 }
3205 
3206 void MainWindow::updateZoomSlider(int value)
3207 {
3208     slotUpdateZoomSliderToolTip(value);
3209     KdenliveDoc *project = pCore->currentDoc();
3210     if (project) {
3211         project->setZoom(pCore->currentTimelineId(), value);
3212     }
3213     m_zoomOut->setEnabled(value < m_zoomSlider->maximum());
3214     m_zoomIn->setEnabled(value > m_zoomSlider->minimum());
3215     QSignalBlocker blocker(m_zoomSlider);
3216     m_zoomSlider->setValue(value);
3217 }
3218 
3219 void MainWindow::slotShowZoomSliderToolTip(int zoomlevel)
3220 {
3221     if (zoomlevel != -1) {
3222         slotUpdateZoomSliderToolTip(zoomlevel);
3223     }
3224 
3225     QPoint global = m_zoomSlider->rect().topLeft();
3226     global.ry() += m_zoomSlider->height() / 2;
3227     QHelpEvent toolTipEvent(QEvent::ToolTip, QPoint(0, 0), m_zoomSlider->mapToGlobal(global));
3228     QApplication::sendEvent(m_zoomSlider, &toolTipEvent);
3229 }
3230 
3231 void MainWindow::slotUpdateZoomSliderToolTip(int zoomlevel)
3232 {
3233     int max = m_zoomSlider->maximum() + 1;
3234     m_zoomSlider->setToolTip(i18n("Zoom Level: %1/%2", max - zoomlevel, max));
3235 }
3236 
3237 void MainWindow::customEvent(QEvent *e)
3238 {
3239     if (e->type() == QEvent::User) {
3240         m_messageLabel->setMessage(static_cast<MltErrorEvent *>(e)->message(), MltError);
3241     }
3242 }
3243 
3244 void MainWindow::slotSnapRewind()
3245 {
3246     if (m_projectMonitor->isActive()) {
3247         getCurrentTimeline()->controller()->gotoPreviousSnap();
3248     } else {
3249         m_clipMonitor->slotSeekToPreviousSnap();
3250     }
3251 }
3252 
3253 void MainWindow::slotSnapForward()
3254 {
3255     if (m_projectMonitor->isActive()) {
3256         getCurrentTimeline()->controller()->gotoNextSnap();
3257     } else {
3258         m_clipMonitor->slotSeekToNextSnap();
3259     }
3260 }
3261 
3262 void MainWindow::slotGuideRewind()
3263 {
3264     if (m_projectMonitor->isActive()) {
3265         getCurrentTimeline()->controller()->gotoPreviousGuide();
3266     } else {
3267         m_clipMonitor->slotSeekToPreviousSnap();
3268     }
3269 }
3270 
3271 void MainWindow::slotGuideForward()
3272 {
3273     if (m_projectMonitor->isActive()) {
3274         getCurrentTimeline()->controller()->gotoNextGuide();
3275     } else {
3276         m_clipMonitor->slotSeekToNextSnap();
3277     }
3278 }
3279 
3280 void MainWindow::slotClipStart()
3281 {
3282     if (m_projectMonitor->isActive()) {
3283         getCurrentTimeline()->controller()->seekCurrentClip(false);
3284     } else {
3285         m_clipMonitor->slotStart();
3286     }
3287 }
3288 
3289 void MainWindow::slotClipEnd()
3290 {
3291     if (m_projectMonitor->isActive()) {
3292         getCurrentTimeline()->controller()->seekCurrentClip(true);
3293     } else {
3294         m_clipMonitor->slotEnd();
3295     }
3296 }
3297 
3298 void MainWindow::slotChangeTool(QAction *action)
3299 {
3300     ToolType::ProjectTool activeTool = ToolType::SelectTool;
3301 
3302     // if(action == m_buttonSelectTool) covered by default value
3303     if (action == m_buttonRazorTool) {
3304         activeTool = ToolType::RazorTool;
3305     } else if (action == m_buttonSpacerTool) {
3306         activeTool = ToolType::SpacerTool;
3307     }
3308     if (action == m_buttonRippleTool) {
3309         activeTool = ToolType::RippleTool;
3310     }
3311     if (action == m_buttonRollTool) {
3312         activeTool = ToolType::RollTool;
3313     }
3314     if (action == m_buttonSlipTool) {
3315         activeTool = ToolType::SlipTool;
3316     }
3317     if (action == m_buttonSlideTool) {
3318         activeTool = ToolType::SlideTool;
3319     }
3320     if (action == m_buttonMulticamTool) {
3321         activeTool = ToolType::MulticamTool;
3322     };
3323     slotSetTool(activeTool);
3324 }
3325 
3326 void MainWindow::slotChangeEdit(QAction *action)
3327 {
3328     TimelineMode::EditMode mode = TimelineMode::NormalEdit;
3329     if (action == m_overwriteEditTool) {
3330         mode = TimelineMode::OverwriteEdit;
3331     } else if (action == m_insertEditTool) {
3332         mode = TimelineMode::InsertEdit;
3333     }
3334     getCurrentTimeline()->model()->setEditMode(mode);
3335     showToolMessage();
3336     if (mode == TimelineMode::InsertEdit) {
3337         // Disable spacer tool in insert mode
3338         if (m_buttonSpacerTool->isChecked()) {
3339             m_buttonSelectTool->setChecked(true);
3340             slotSetTool(ToolType::SelectTool);
3341         }
3342         m_buttonSpacerTool->setEnabled(false);
3343     } else {
3344         m_buttonSpacerTool->setEnabled(true);
3345     }
3346 }
3347 
3348 void MainWindow::disableMulticam()
3349 {
3350     if (m_activeTool == ToolType::MulticamTool) {
3351         m_buttonSelectTool->setChecked(true);
3352         slotSetTool(ToolType::SelectTool);
3353     }
3354 }
3355 
3356 void MainWindow::slotSetTool(ToolType::ProjectTool tool)
3357 {
3358     if (m_activeTool == ToolType::MulticamTool) {
3359         // End multicam operation
3360         pCore->monitorManager()->switchMultiTrackView(false);
3361         pCore->monitorManager()->slotStopMultiTrackMode();
3362     }
3363     m_activeTool = tool;
3364     if (pCore->currentDoc()) {
3365         showToolMessage();
3366         getCurrentTimeline()->setTool(tool);
3367         getCurrentTimeline()->controller()->updateTrimmingMode();
3368     }
3369     if (m_activeTool == ToolType::MulticamTool) {
3370         // Start multicam operation
3371         pCore->monitorManager()->switchMultiTrackView(true);
3372         pCore->monitorManager()->slotStartMultiTrackMode();
3373     }
3374 }
3375 
3376 void MainWindow::showToolMessage()
3377 {
3378     QString message;
3379     QString toolLabel;
3380     if (m_buttonSelectTool->isChecked()) {
3381 #ifdef Q_OS_WIN
3382         message = xi18nc("@info:whatsthis",
3383                          "<shortcut>Shift drag</shortcut> for rubber-band selection, <shortcut>Shift click</shortcut> for multiple "
3384                          "selection, <shortcut>Meta drag</shortcut> to move a grouped clip to another track, <shortcut>Ctrl drag</shortcut> to pan");
3385 #else
3386         message = xi18nc("@info:whatsthis",
3387                          "<shortcut>Shift drag</shortcut> for rubber-band selection, <shortcut>Shift click</shortcut> for multiple "
3388                          "selection, <shortcut>Meta + Alt drag</shortcut> to move a grouped clip to another track, <shortcut>Ctrl drag</shortcut> to pan");
3389 #endif
3390         toolLabel = i18n("Select");
3391     } else if (m_buttonRazorTool->isChecked()) {
3392         message = xi18nc("@info:whatsthis", "<shortcut>Shift</shortcut> to preview cut frame");
3393         toolLabel = i18n("Razor");
3394     } else if (m_buttonSpacerTool->isChecked()) {
3395         message =
3396             xi18nc("@info:whatsthis",
3397                    "<shortcut>Ctrl</shortcut> to apply on current track only, <shortcut>Shift</shortcut> to also move guides. You can combine both modifiers.");
3398         toolLabel = i18n("Spacer");
3399     } else if (m_buttonSlipTool->isChecked()) {
3400         message = xi18nc("@info:whatsthis", "<shortcut>Click</shortcut> on an item to slip, <shortcut>Shift click</shortcut> for multiple selection");
3401         toolLabel = i18nc("Timeline Tool", "Slip");
3402     } /*else if (m_buttonSlideTool->isChecked()) { // TODO implement Slide
3403         toolLabel = i18nc("Timeline Tool", "Slide");
3404     }*/
3405     else if (m_buttonRippleTool->isChecked()) {
3406         message = xi18nc("@info:whatsthis", "<shortcut>Shift drag</shortcut> for rubber-band selection, <shortcut>Shift click</shortcut> for multiple "
3407                                             "selection, <shortcut>Alt click</shortcut> to select an item in a group, <shortcut>Ctrl drag</shortcut> to pan");
3408         toolLabel = i18nc("Timeline Tool", "Ripple");
3409     } /*else if (m_buttonRollTool->isChecked()) { // TODO implement Slide
3410         toolLabel = i18nc("Timeline Tool", "Roll");
3411     }*/
3412     else if (m_buttonMulticamTool->isChecked()) {
3413         message =
3414             xi18nc("@info:whatsthis", "<shortcut>Click</shortcut> on a track view in the project monitor to perform a lift of all tracks except active one");
3415         toolLabel = i18n("Multicam");
3416     }
3417     TimelineMode::EditMode mode = TimelineMode::NormalEdit;
3418     if (getCurrentTimeline() && getCurrentTimeline()->model()) {
3419         mode = getCurrentTimeline()->model()->editMode();
3420     }
3421     if (mode != TimelineMode::NormalEdit) {
3422         if (!toolLabel.isEmpty()) {
3423             toolLabel.append(QStringLiteral(" | "));
3424         }
3425         if (mode == TimelineMode::InsertEdit) {
3426             toolLabel.append(i18n("Insert"));
3427             m_trimLabel->setStyleSheet(QStringLiteral("QLabel { padding-left: 2; padding-right: 2; background-color :red; }"));
3428         } else if (mode == TimelineMode::OverwriteEdit) {
3429             toolLabel.append(i18n("Overwrite"));
3430             m_trimLabel->setStyleSheet(QStringLiteral("QLabel { padding-left: 2; padding-right: 2; background-color :darkGreen; }"));
3431         }
3432     } else {
3433         m_trimLabel->setStyleSheet(
3434             QStringLiteral("QLabel { padding-left: 2; padding-right: 2; background-color :%1; }").arg(palette().window().color().name()));
3435     }
3436     m_trimLabel->setText(toolLabel);
3437     m_messageLabel->setKeyMap(message);
3438 }
3439 
3440 void MainWindow::setWidgetKeyBinding(const QString &mess)
3441 {
3442     m_messageLabel->setKeyMap(mess);
3443 }
3444 
3445 void MainWindow::showKeyBinding(const QString &text)
3446 {
3447     m_messageLabel->setTmpKeyMap(text);
3448 }
3449 
3450 void MainWindow::slotCopy()
3451 {
3452     QWidget *widget = QApplication::focusWidget();
3453     while ((widget != nullptr) && widget != this) {
3454         if (widget == m_effectStackDock) {
3455             m_assetPanel->sendStandardCommand(KStandardAction::Copy);
3456             return;
3457         }
3458         widget = widget->parentWidget();
3459     }
3460     getCurrentTimeline()->controller()->copyItem();
3461 }
3462 
3463 void MainWindow::slotPaste()
3464 {
3465     QWidget *widget = QApplication::focusWidget();
3466     while ((widget != nullptr) && widget != this) {
3467         if (widget == m_effectStackDock) {
3468             m_assetPanel->sendStandardCommand(KStandardAction::Paste);
3469             return;
3470         }
3471         widget = widget->parentWidget();
3472     }
3473     getCurrentTimeline()->controller()->pasteItem();
3474 }
3475 
3476 void MainWindow::slotPasteEffects()
3477 {
3478     getCurrentTimeline()->controller()->pasteEffects();
3479 }
3480 
3481 void MainWindow::slotClipInTimeline(const QString &clipId, const QList<int> &ids)
3482 {
3483     Q_UNUSED(clipId)
3484     QMenu *inTimelineMenu = static_cast<QMenu *>(factory()->container(QStringLiteral("clip_in_timeline"), this));
3485     QList<QAction *> actionList;
3486     for (int i = 0; i < ids.count(); ++i) {
3487         ObjectId oid(KdenliveObjectType::TimelineClip, ids.at(i), pCore->currentTimelineId());
3488         QString track = getCurrentTimeline()->controller()->getTrackNameFromIndex(pCore->getItemTrack(oid));
3489         QString start = pCore->currentDoc()->timecode().getTimecodeFromFrames(pCore->getItemPosition(oid));
3490         int j = 0;
3491         QAction *a = new QAction(track + QStringLiteral(": ") + start, inTimelineMenu);
3492         a->setData(ids.at(i));
3493         connect(a, &QAction::triggered, this, &MainWindow::slotSelectClipInTimeline);
3494         while (j < actionList.count()) {
3495             if (actionList.at(j)->text() > a->text()) {
3496                 break;
3497             }
3498             j++;
3499         }
3500         actionList.insert(j, a);
3501     }
3502     QList<QAction *> list = inTimelineMenu->actions();
3503     unplugActionList(QStringLiteral("timeline_occurences"));
3504     qDeleteAll(list);
3505     plugActionList(QStringLiteral("timeline_occurences"), actionList);
3506 
3507     if (actionList.isEmpty()) {
3508         inTimelineMenu->setEnabled(false);
3509     } else {
3510         inTimelineMenu->setEnabled(true);
3511     }
3512 }
3513 
3514 void MainWindow::raiseBin()
3515 {
3516     Bin *bin = activeBin();
3517     if (bin) {
3518         bin->parentWidget()->setVisible(true);
3519         bin->parentWidget()->raise();
3520     }
3521 }
3522 
3523 void MainWindow::slotClipInProjectTree()
3524 {
3525     QList<int> ids = getCurrentTimeline()->controller()->selection();
3526     if (!ids.isEmpty()) {
3527         const QString binId = getCurrentTimeline()->controller()->getClipBinId(ids.constFirst());
3528         // If we have multiple bins, check first if a visible bin contains it
3529         bool binFound = false;
3530         if (binCount() > 1) {
3531             for (auto &bin : m_binWidgets) {
3532                 if (bin->isVisible() && !bin->visibleRegion().isEmpty()) {
3533                     // Check if clip is a child of this bin
3534                     if (bin->containsId(binId)) {
3535                         binFound = true;
3536                         bin->setFocus();
3537                         raiseBin();
3538                     }
3539                 }
3540             }
3541         }
3542         if (!binFound) {
3543             raiseBin();
3544         }
3545         ObjectId id(KdenliveObjectType::TimelineClip, ids.constFirst(), pCore->currentTimelineId());
3546         int start = pCore->getItemIn(id);
3547         int duration = pCore->getItemDuration(id);
3548         int pos = m_projectMonitor->position();
3549         int itemPos = pCore->getItemPosition(id);
3550         bool containsPos = (pos >= itemPos && pos < itemPos + duration);
3551         double speed = pCore->getClipSpeed(id.itemId);
3552         if (containsPos) {
3553             pos -= itemPos - start;
3554         }
3555         if (!qFuzzyCompare(speed, 1.)) {
3556             if (speed > 0.) {
3557                 // clip has a speed effect, adjust zone
3558                 start = qRound(start * speed);
3559                 duration = qRound(duration * speed);
3560                 if (containsPos) {
3561                     pos = qRound(pos * speed);
3562                 }
3563             } else if (speed < 0.) {
3564                 int max = getCurrentTimeline()->controller()->clipMaxDuration(id.itemId);
3565                 if (max > 0) {
3566                     int invertedPos = itemPos + duration - m_projectMonitor->position();
3567                     start = qRound((max - (start + duration)) * -speed);
3568                     duration = qRound(duration * -speed);
3569                     if (containsPos) {
3570                         pos = start + qRound(invertedPos * -speed);
3571                     }
3572                 }
3573             }
3574         }
3575         QPoint zone(start, start + duration - 1);
3576         if (!containsPos) {
3577             pos = start;
3578         }
3579         activeBin()->selectClipById(binId, pos, zone, true);
3580     }
3581 }
3582 
3583 void MainWindow::slotSelectClipInTimeline()
3584 {
3585     pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor);
3586     auto *action = qobject_cast<QAction *>(sender());
3587     int clipId = action->data().toInt();
3588     getCurrentTimeline()->controller()->focusItem(clipId);
3589 }
3590 
3591 /** Gets called when the window gets hidden */
3592 void MainWindow::hideEvent(QHideEvent * /*event*/)
3593 {
3594     if (isMinimized() && pCore->monitorManager()) {
3595         pCore->monitorManager()->pauseActiveMonitor();
3596     }
3597 }
3598 
3599 void MainWindow::slotResizeItemStart()
3600 {
3601     getCurrentTimeline()->controller()->setInPoint(m_activeTool == ToolType::RippleTool);
3602 }
3603 
3604 void MainWindow::slotResizeItemEnd()
3605 {
3606     getCurrentTimeline()->controller()->setOutPoint(m_activeTool == ToolType::RippleTool);
3607 }
3608 
3609 #if KXMLGUI_VERSION < QT_VERSION_CHECK(5, 98, 0)
3610 int MainWindow::getNewStuff(const QString &configFile)
3611 {
3612 #if KNEWSTUFF_VERSION > QT_VERSION_CHECK(5, 240, 0)
3613     KNSWidgets::Dialog dialog(configFile);
3614 #else
3615     KNS3::QtQuickDialogWrapper dialog(configFile);
3616 #endif
3617     const QList<KNSCore::EntryInternal> entries = dialog.exec();
3618     for (const auto &entry : qAsConst(entries)) {
3619         if (entry.status() == KNS3::Entry::Installed) {
3620             qCDebug(KDENLIVE_LOG) << "// Installed files: " << entry.installedFiles();
3621         }
3622     }
3623     return entries.size();
3624 }
3625 #endif
3626 
3627 #if KXMLGUI_VERSION < QT_VERSION_CHECK(5, 98, 0)
3628 void MainWindow::slotGetNewKeyboardStuff(QComboBox *schemesList)
3629 {
3630     if (getNewStuff(QStringLiteral(":data/kdenlive_keyboardschemes.knsrc")) > 0) {
3631         // Refresh keyboard schemes list (schemes list creation code copied from KShortcutSchemesEditor)
3632         QStringList schemes;
3633         schemes << QStringLiteral("Default");
3634         // List files in the shortcuts subdir, each one is a scheme. See KShortcutSchemesHelper::{shortcutSchemeFileName,exportActionCollection}
3635         const QStringList shortcutsDirs = QStandardPaths::locateAll(
3636             QStandardPaths::GenericDataLocation, QCoreApplication::applicationName() + QStringLiteral("/shortcuts"), QStandardPaths::LocateDirectory);
3637         qCDebug(KDENLIVE_LOG) << "shortcut scheme dirs:" << shortcutsDirs;
3638         for (const QString &dir : shortcutsDirs) {
3639             for (const QString &file : QDir(dir).entryList(QDir::Files | QDir::NoDotAndDotDot)) {
3640                 qCDebug(KDENLIVE_LOG) << "shortcut scheme file:" << file;
3641                 schemes << file;
3642             }
3643         }
3644         schemesList->clear();
3645         schemesList->addItems(schemes);
3646     }
3647 }
3648 #endif
3649 
3650 void MainWindow::slotAutoTransition()
3651 {
3652     // TODO refac
3653     /*
3654     if (pCore->projectManager()->currentTimeline()) {
3655         pCore->projectManager()->currentTimeline()->projectView()->autoTransition();
3656     }
3657     */
3658 }
3659 
3660 void MainWindow::slotSplitAV()
3661 {
3662     getCurrentTimeline()->controller()->splitAV();
3663 }
3664 
3665 void MainWindow::slotSwitchClip()
3666 {
3667     getCurrentTimeline()->controller()->switchEnableState();
3668 }
3669 
3670 void MainWindow::slotSetAudioAlignReference()
3671 {
3672     getCurrentTimeline()->controller()->setAudioRef();
3673 }
3674 
3675 void MainWindow::slotAlignAudio()
3676 {
3677     getCurrentTimeline()->controller()->alignAudio();
3678 }
3679 
3680 void MainWindow::slotUpdateTimelineView(QAction *action)
3681 {
3682     int viewMode = action->data().toInt();
3683     KdenliveSettings::setAudiotracksbelow(viewMode);
3684     getCurrentTimeline()->model()->_resetView();
3685 }
3686 
3687 void MainWindow::slotShowTimeline(bool show)
3688 {
3689     if (!show) {
3690         m_timelineState = saveState();
3691         centralWidget()->setHidden(true);
3692     } else {
3693         centralWidget()->setHidden(false);
3694         restoreState(m_timelineState);
3695     }
3696 }
3697 
3698 void MainWindow::loadClipActions()
3699 {
3700     unplugActionList(QStringLiteral("add_effect"));
3701     plugActionList(QStringLiteral("add_effect"), m_effectsMenu->actions());
3702 
3703     QList<QAction *> clipJobActions = getExtraActions(QStringLiteral("clipjobs"));
3704     unplugActionList(QStringLiteral("clip_jobs"));
3705     plugActionList(QStringLiteral("clip_jobs"), clipJobActions);
3706 
3707     QList<QAction *> atcActions = getExtraActions(QStringLiteral("audiotranscoderslist"));
3708     unplugActionList(QStringLiteral("audio_transcoders_list"));
3709     plugActionList(QStringLiteral("audio_transcoders_list"), atcActions);
3710 
3711     QList<QAction *> tcActions = getExtraActions(QStringLiteral("transcoderslist"));
3712     unplugActionList(QStringLiteral("transcoders_list"));
3713     plugActionList(QStringLiteral("transcoders_list"), tcActions);
3714 }
3715 
3716 void MainWindow::loadDockActions()
3717 {
3718     QList<QAction *> list = kdenliveCategoryMap.value(QStringLiteral("interface"))->actions();
3719     // Sort actions
3720     QMap<QString, QAction *> sorted;
3721     QStringList sortedList;
3722     for (QAction *a : qAsConst(list)) {
3723         if (a->objectName().startsWith(QStringLiteral("raise_"))) {
3724             continue;
3725         }
3726         sorted.insert(a->text(), a);
3727         sortedList << a->text();
3728     }
3729     QList<QAction *> orderedList;
3730     sortedList.sort(Qt::CaseInsensitive);
3731     for (const QString &text : qAsConst(sortedList)) {
3732         orderedList << sorted.value(text);
3733     }
3734     unplugActionList(QStringLiteral("dock_actions"));
3735     plugActionList(QStringLiteral("dock_actions"), orderedList);
3736 }
3737 
3738 void MainWindow::buildDynamicActions()
3739 {
3740     KActionCategory *ts = nullptr;
3741     if (kdenliveCategoryMap.contains(QStringLiteral("clipjobs"))) {
3742         ts = kdenliveCategoryMap.take(QStringLiteral("clipjobs"));
3743         delete ts;
3744     }
3745     ts = new KActionCategory(i18n("Clip Jobs"), m_extraFactory->actionCollection());
3746 
3747     QAction *action;
3748     QMap<QString, QString> jobValues = ClipJobManager::getClipJobNames();
3749     QMapIterator<QString, QString> k(jobValues);
3750     while (k.hasNext()) {
3751         k.next();
3752         action = new QAction(k.value(), m_extraFactory->actionCollection());
3753         action->setData(k.key());
3754         if (k.key() == QLatin1String("stabilize;v")) {
3755             connect(action, &QAction::triggered, this, [this]() { StabilizeTask::start(this); });
3756         } else if (k.key() == QLatin1String("scenesplit;v")) {
3757             connect(action, &QAction::triggered, this, [&]() { SceneSplitTask::start(this); });
3758         } else if (k.key() == QLatin1String("timewarp;av")) {
3759             connect(action, &QAction::triggered, this, [&]() { SpeedTask::start(this); });
3760         } else {
3761             connect(action, &QAction::triggered, this, [&, jobId = k.key().section(QLatin1Char(';'), 0, 0)]() { CustomJobTask::start(this, jobId); });
3762         }
3763         ts->addAction(action->text(), action);
3764     }
3765 
3766     action = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure Clip Jobs…"), m_extraFactory->actionCollection());
3767     ts->addAction(action->text(), action);
3768     connect(action, &QAction::triggered, this, [this]() { manageClipJobs(); });
3769 
3770     kdenliveCategoryMap.insert(QStringLiteral("clipjobs"), ts);
3771 
3772     if (kdenliveCategoryMap.contains(QStringLiteral("transcoderslist"))) {
3773         ts = kdenliveCategoryMap.take(QStringLiteral("transcoderslist"));
3774         delete ts;
3775     }
3776     if (kdenliveCategoryMap.contains(QStringLiteral("audiotranscoderslist"))) {
3777         ts = kdenliveCategoryMap.take(QStringLiteral("audiotranscoderslist"));
3778         delete ts;
3779     }
3780     // transcoders
3781     ts = new KActionCategory(i18n("Transcoders"), m_extraFactory->actionCollection());
3782     KActionCategory *ats = new KActionCategory(i18n("Extract Audio"), m_extraFactory->actionCollection());
3783     KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("kdenlivetranscodingrc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
3784     KConfigGroup transConfig(config, "Transcoding");
3785     // read the entries
3786     QMap<QString, QString> profiles = transConfig.entryMap();
3787     QMapIterator<QString, QString> i(profiles);
3788     while (i.hasNext()) {
3789         i.next();
3790         QStringList transList;
3791         transList << i.value().split(QLatin1Char(';'));
3792         auto *a = new QAction(i.key(), m_extraFactory->actionCollection());
3793         a->setData(transList);
3794         if (transList.count() > 1) {
3795             a->setToolTip(transList.at(1));
3796         }
3797         connect(a, &QAction::triggered, [&, a]() {
3798             QStringList transcodeData = a->data().toStringList();
3799             std::vector<QString> ids = pCore->bin()->selectedClipsIds(true);
3800             QMap<QString, QVector<int>> clipStreamSelection;
3801             QString clipId;
3802             if (transcodeData.count() > 2 && transcodeData.at(2) == QLatin1String("audio")) {
3803                 // Audio extract, check if we have multi stream clips
3804                 QMap<QString, int> clipStreamCount;
3805                 for (const QString &id : ids) {
3806                     if (id.contains(QLatin1Char('/'))) {
3807                         clipId = id.section(QLatin1Char('/'), 0, 0);
3808                     } else {
3809                         clipId = id;
3810                     }
3811                     std::shared_ptr<ProjectClip> clip = pCore->projectItemModel()->getClipByBinID(clipId);
3812                     if (clip->audioStreamsCount() > 1) {
3813                         clipStreamSelection.insert(clipId, clip->activeFfmpegStreams());
3814                     }
3815                 }
3816             }
3817             const QString tData = transcodeData.first();
3818             int clipIn = -1;
3819             int clipOut = -1;
3820             for (const QString &id : ids) {
3821                 if (id.contains(QLatin1Char('/'))) {
3822                     clipId = id.section(QLatin1Char('/'), 0, 0);
3823                     clipIn = id.section(QLatin1Char('/'), 1, 1).toInt();
3824                     clipOut = id.section(QLatin1Char('/'), 2, 2).toInt();
3825                 } else {
3826                     clipId = id;
3827                     clipIn = -1;
3828                     clipOut = -1;
3829                 }
3830                 std::shared_ptr<ProjectClip> clip = pCore->projectItemModel()->getClipByBinID(clipId);
3831                 if (clipStreamSelection.contains(clipId)) {
3832                     // Extract selected audio streams only, create one task per stream
3833                     QVector<int> selectedStreams = clipStreamSelection.value(clipId);
3834                     for (auto &ix : selectedStreams) {
3835                         QString args;
3836                         QString suffix;
3837                         if (ix == -1) {
3838                             // Merge all audio streams
3839                             args = QStringLiteral("-filter_complex amerge=inputs=%1 ").arg(clip->audioStreamsCount());
3840                             args.append(QStringLiteral("-ac %1 ").arg(clip->audioChannels()));
3841                             suffix = i18n("-merged");
3842                         } else {
3843                             args = QStringLiteral("-map 0:a:%1 ").arg(ix);
3844                             suffix = i18n("-stream-%1", ix);
3845                         }
3846                         args.append(tData);
3847                         TranscodeTask::start(ObjectId(KdenliveObjectType::BinClip, clipId.toInt(), QUuid()), suffix, QString(), args, clipIn, clipOut, false,
3848                                              clip.get());
3849                     }
3850                 } else {
3851                     TranscodeTask::start(ObjectId(KdenliveObjectType::BinClip, clipId.toInt(), QUuid()), QString(), QString(), tData, clipIn, clipOut, false,
3852                                          clip.get());
3853                 }
3854             }
3855         });
3856         if (transList.count() > 2 && transList.at(2) == QLatin1String("audio")) {
3857             // This is an audio transcoding action
3858             ats->addAction(i.key(), a);
3859         } else {
3860             ts->addAction(i.key(), a);
3861         }
3862     }
3863     kdenliveCategoryMap.insert(QStringLiteral("transcoderslist"), ts);
3864     kdenliveCategoryMap.insert(QStringLiteral("audiotranscoderslist"), ats);
3865 
3866     updateDockMenu();
3867 }
3868 
3869 void MainWindow::updateDockMenu()
3870 {
3871     // Populate View menu with show / hide actions for dock widgets
3872     KActionCategory *guiActions = nullptr;
3873     if (kdenliveCategoryMap.contains(QStringLiteral("interface"))) {
3874         guiActions = kdenliveCategoryMap.take(QStringLiteral("interface"));
3875         delete guiActions;
3876     }
3877     guiActions = new KActionCategory(i18n("Interface"), actionCollection());
3878     QAction *showTimeline = new QAction(i18n("Timeline"), this);
3879     showTimeline->setCheckable(true);
3880     showTimeline->setChecked(true);
3881     connect(showTimeline, &QAction::triggered, this, &MainWindow::slotShowTimeline);
3882     guiActions->addAction(showTimeline->text(), showTimeline);
3883     actionCollection()->addAction(showTimeline->text(), showTimeline);
3884 
3885     QList<QDockWidget *> docks = findChildren<QDockWidget *>();
3886     for (auto dock : qAsConst(docks)) {
3887         QAction *dockInformations = dock->toggleViewAction();
3888         if (!dockInformations) {
3889             continue;
3890         }
3891         dockInformations->setChecked(!dock->isHidden());
3892         guiActions->addAction(dockInformations->text(), dockInformations);
3893         QAction *action = new QAction(i18n("Raise %1", dockInformations->text()), this);
3894         connect(action, &QAction::triggered, this, [dock]() {
3895             dock->raise();
3896             dock->setFocus();
3897         });
3898         addAction("raise_" + dock->objectName(), action, {}, guiActions);
3899     }
3900     kdenliveCategoryMap.insert(QStringLiteral("interface"), guiActions);
3901 }
3902 
3903 QList<QAction *> MainWindow::getExtraActions(const QString &name)
3904 {
3905     if (!kdenliveCategoryMap.contains(name)) {
3906         return QList<QAction *>();
3907     }
3908     return kdenliveCategoryMap.value(name)->actions();
3909 }
3910 
3911 void MainWindow::slotTranscode(const QStringList &urls)
3912 {
3913     Q_ASSERT(!urls.isEmpty());
3914     QString params;
3915     QString desc;
3916     ClipTranscode *d = new ClipTranscode(urls, params, QStringList(), desc, pCore->bin()->getCurrentFolder());
3917     connect(d, &ClipTranscode::addClip, this, &MainWindow::slotAddProjectClip);
3918     d->show();
3919 }
3920 
3921 void MainWindow::slotFriendlyTranscode(const QString &binId, bool checkProfile)
3922 {
3923     QString params;
3924     QString desc;
3925     std::shared_ptr<ProjectClip> clip = pCore->projectItemModel()->getClipByBinID(binId);
3926     if (clip == nullptr) {
3927         qDebug() << "// NO CLIP FOUND FOR BIN ID: " << binId;
3928         return;
3929     }
3930     QStringList urls = {clip->url()};
3931     // Prepare clip properties
3932     QMap<QString, QString> sourceProps;
3933     sourceProps.insert(QStringLiteral("resource"), clip->url());
3934     sourceProps.insert(QStringLiteral("kdenlive:originalurl"), clip->url());
3935     sourceProps.insert(QStringLiteral("kdenlive:clipname"), clip->clipName());
3936     sourceProps.insert(QStringLiteral("kdenlive:proxy"), clip->getProducerProperty(QStringLiteral("kdenlive:proxy")));
3937     sourceProps.insert(QStringLiteral("_fullreload"), QStringLiteral("1"));
3938     ClipTranscode *d = new ClipTranscode(urls, params, QStringList(), desc, pCore->bin()->getCurrentFolder());
3939     connect(d, &ClipTranscode::addClip, [&, binId, sourceProps](const QUrl &url, const QString & /*folderInfo*/) {
3940         QMap<QString, QString> newProps;
3941         newProps.insert(QStringLiteral("resource"), url.toLocalFile());
3942         newProps.insert(QStringLiteral("kdenlive:originalurl"), url.toLocalFile());
3943         newProps.insert(QStringLiteral("kdenlive:clipname"), url.fileName());
3944         newProps.insert(QStringLiteral("kdenlive:proxy"), QStringLiteral("-"));
3945         newProps.insert(QStringLiteral("_fullreload"), QStringLiteral("1"));
3946         QMetaObject::invokeMethod(pCore->bin(), "slotEditClipCommand", Qt::QueuedConnection, Q_ARG(QString, binId), Q_ARG(stringMap, sourceProps),
3947                                   Q_ARG(stringMap, newProps));
3948     });
3949     d->exec();
3950     if (checkProfile) {
3951         pCore->bin()->slotCheckProfile(binId);
3952     }
3953 }
3954 
3955 void MainWindow::slotTranscodeClip()
3956 {
3957     const QString dialogFilter = ClipCreationDialog::getExtensionsFilter(QStringList() << i18n("All Files") + QStringLiteral(" (*)"));
3958     QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder"));
3959     QStringList urls = QFileDialog::getOpenFileNames(this, i18nc("@title:window", "Files to Transcode"), clipFolder, dialogFilter);
3960     if (urls.isEmpty()) {
3961         return;
3962     }
3963     slotTranscode(urls);
3964 }
3965 
3966 void MainWindow::slotSetDocumentRenderProfile(const QMap<QString, QString> &props)
3967 {
3968     KdenliveDoc *project = pCore->currentDoc();
3969     bool modified = false;
3970     QMapIterator<QString, QString> i(props);
3971     while (i.hasNext()) {
3972         i.next();
3973         if (project->getDocumentProperty(i.key()) == i.value()) {
3974             continue;
3975         }
3976         project->setDocumentProperty(i.key(), i.value());
3977         modified = true;
3978     }
3979     if (modified) {
3980         project->setModified();
3981     }
3982 }
3983 
3984 void MainWindow::slotUpdateTimecodeFormat(int ix)
3985 {
3986     KdenliveSettings::setFrametimecode(ix == 1);
3987     Q_EMIT pCore->updateProjectTimecode();
3988     m_clipMonitor->updateTimecodeFormat();
3989     m_projectMonitor->updateTimecodeFormat();
3990     Q_EMIT getCurrentTimeline()->controller()->frameFormatChanged();
3991     m_timeFormatButton->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
3992 }
3993 
3994 void MainWindow::slotRemoveFocus()
3995 {
3996     getCurrentTimeline()->setFocus();
3997 }
3998 
3999 void MainWindow::slotShutdown()
4000 {
4001     pCore->currentDoc()->setModified(false);
4002     // Call shutdown
4003 #ifndef NODBUS
4004     QDBusConnectionInterface *interface = QDBusConnection::sessionBus().interface();
4005     // org.kde.Shutdown is DBus activatable, so we can't query for it running
4006     if (qgetenv("XDG_CURRENT_DESKTOP") == QLatin1String("KDE")) {
4007         QDBusInterface kdeShutdown(QStringLiteral("org.kde.Shutdown"), QStringLiteral("/Shutdown"), QStringLiteral("org.kde.Shutdown"));
4008         kdeShutdown.call(QStringLiteral("logoutAndShutdown"));
4009     } else if ((interface != nullptr) && interface->isServiceRegistered(QStringLiteral("org.gnome.SessionManager"))) {
4010         QDBusInterface smserver(QStringLiteral("org.gnome.SessionManager"), QStringLiteral("/org/gnome/SessionManager"),
4011                                 QStringLiteral("org.gnome.SessionManager"));
4012         smserver.call(QStringLiteral("Shutdown"));
4013     }
4014 #endif
4015 }
4016 
4017 void MainWindow::slotSwitchMonitors()
4018 {
4019     pCore->monitorManager()->slotSwitchMonitors(!m_clipMonitor->isActive());
4020     if (m_projectMonitor->isActive()) {
4021         getCurrentTimeline()->setFocus();
4022     } else {
4023         pCore->bin()->focusBinView();
4024     }
4025 }
4026 
4027 void MainWindow::slotFocusTimecode()
4028 {
4029     if (m_clipMonitor->isActive()) {
4030         m_clipMonitor->focusTimecode();
4031     } else if (m_projectMonitor) {
4032         m_projectMonitor->focusTimecode();
4033     }
4034 }
4035 
4036 void MainWindow::slotSwitchMonitorOverlay(QAction *action)
4037 {
4038     if (pCore->monitorManager()->isActive(Kdenlive::ClipMonitor)) {
4039         m_clipMonitor->switchMonitorInfo(action->data().toInt());
4040     } else {
4041         m_projectMonitor->switchMonitorInfo(action->data().toInt());
4042     }
4043 }
4044 
4045 void MainWindow::slotSwitchDropFrames(bool drop)
4046 {
4047     KdenliveSettings::setMonitor_dropframes(drop);
4048     m_clipMonitor->restart();
4049     m_projectMonitor->restart();
4050 }
4051 
4052 void MainWindow::slotSetMonitorGamma(int gamma)
4053 {
4054     KdenliveSettings::setMonitor_gamma(gamma);
4055     m_clipMonitor->restart();
4056     m_projectMonitor->restart();
4057 }
4058 
4059 void MainWindow::slotInsertZoneToTree()
4060 {
4061     if (!m_clipMonitor->isActive() || m_clipMonitor->currentController() == nullptr) {
4062         return;
4063     }
4064     QPoint info = m_clipMonitor->getZoneInfo();
4065     QString id;
4066     // clip monitor counts the frame after the out point as the zone out, so we
4067     // need to subtract 1 to get the actual last frame
4068     pCore->projectItemModel()->requestAddBinSubClip(id, info.x(), info.y()-1, {}, m_clipMonitor->activeClipId());
4069 }
4070 
4071 void MainWindow::slotMonitorRequestRenderFrame(bool request)
4072 {
4073     if (request) {
4074         m_projectMonitor->sendFrameForAnalysis(true);
4075         return;
4076     }
4077     for (int i = 0; i < m_gfxScopesList.count(); ++i) {
4078         if (m_gfxScopesList.at(i)->isVisible() && tabifiedDockWidgets(m_gfxScopesList.at(i)).isEmpty() &&
4079             static_cast<AbstractGfxScopeWidget *>(m_gfxScopesList.at(i)->widget())->autoRefreshEnabled()) {
4080             request = true;
4081             break;
4082         }
4083     }
4084 
4085 #ifdef DEBUG_MAINW
4086     qCDebug(KDENLIVE_LOG) << "Any scope accepting new frames? " << request;
4087 #endif
4088     if (!request) {
4089         m_projectMonitor->sendFrameForAnalysis(false);
4090     }
4091 }
4092 
4093 void MainWindow::slotUpdateProxySettings()
4094 {
4095     KdenliveDoc *project = pCore->currentDoc();
4096     if (m_renderWidget) {
4097         m_renderWidget->updateProxyConfig(project->useProxy());
4098     }
4099     pCore->bin()->refreshProxySettings();
4100 }
4101 
4102 void MainWindow::slotArchiveProject()
4103 {
4104     KdenliveDoc *doc = pCore->currentDoc();
4105     pCore->projectManager()->prepareSave();
4106     QString sceneData = pCore->projectManager()->projectSceneList(doc->url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile());
4107     if (sceneData.isEmpty()) {
4108         KMessageBox::error(this, i18n("Project file could not be saved for archiving."));
4109         return;
4110     }
4111     QPointer<ArchiveWidget> d(new ArchiveWidget(doc->url().fileName(), sceneData, getCurrentTimeline()->controller()->extractCompositionLumas(),
4112                                                 getCurrentTimeline()->controller()->extractExternalEffectFiles(), this));
4113     if (d->exec() != 0) {
4114         m_messageLabel->setMessage(i18n("Archiving project"), OperationCompletedMessage);
4115     }
4116 }
4117 
4118 void MainWindow::slotDownloadResources()
4119 {
4120     QString currentFolder;
4121     if (pCore->currentDoc()) {
4122         currentFolder = pCore->currentDoc()->projectDataFolder();
4123     } else {
4124         currentFolder = KdenliveSettings::defaultprojectfolder();
4125     }
4126     m_onlineResourcesDock->show();
4127     m_onlineResourcesDock->raise();
4128     ;
4129 }
4130 
4131 void MainWindow::slotProcessImportKeyframes(GraphicsRectItem type, const QString &tag, const QString &keyframes)
4132 {
4133     Q_UNUSED(keyframes)
4134     Q_UNUSED(tag)
4135     if (type == AVWidget) {
4136         // This data should be sent to the effect stack
4137         // TODO REFAC reimplement
4138         // m_effectStack->setKeyframes(tag, data);
4139     } else if (type == TransitionWidget) {
4140         // This data should be sent to the transition stack
4141         // TODO REFAC reimplement
4142         // m_effectStack->transitionConfig()->setKeyframes(tag, data);
4143     } else {
4144         // Error
4145     }
4146 }
4147 
4148 void MainWindow::slotAlignPlayheadToMousePos()
4149 {
4150     pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor);
4151     getCurrentTimeline()->controller()->seekToMouse();
4152 }
4153 
4154 void MainWindow::triggerKey(QKeyEvent *ev)
4155 {
4156     // Hack: The QQuickWindow that displays fullscreen monitor does not integrate with QActions.
4157     // So on keypress events we parse keys and check for shortcuts in all existing actions
4158     QKeySequence seq;
4159     // Remove the Num modifier or some shortcuts like "*" will not work
4160     if (ev->modifiers() != Qt::KeypadModifier) {
4161         seq = QKeySequence(ev->key() + static_cast<int>(ev->modifiers()));
4162     } else {
4163         seq = QKeySequence(ev->key());
4164     }
4165     QList<KActionCollection *> collections = KActionCollection::allCollections();
4166     for (int i = 0; i < collections.count(); ++i) {
4167         KActionCollection *coll = collections.at(i);
4168         for (QAction *tempAction : coll->actions()) {
4169             if (tempAction->shortcuts().contains(seq)) {
4170                 // Trigger action
4171                 tempAction->trigger();
4172                 ev->accept();
4173                 return;
4174             }
4175         }
4176     }
4177     QWidget::keyPressEvent(ev);
4178 }
4179 
4180 QDockWidget *MainWindow::addDock(const QString &title, const QString &objectName, QWidget *widget, Qt::DockWidgetArea area)
4181 {
4182     QDockWidget *dockWidget = new QDockWidget(title, this);
4183     dockWidget->setObjectName(objectName);
4184     dockWidget->setWidget(widget);
4185     addDockWidget(area, dockWidget);
4186 
4187     // Add action to raise and focus the Dock (e.g. with a shortcut)
4188     /*QAction *action = new QAction(i18n("Raise %1", title), this);
4189     connect(action, &QAction::triggered, this, [dockWidget](){
4190         dockWidget->raise();
4191         dockWidget->setFocus();
4192     });
4193     addAction("raise_" + objectName, action, {});*/
4194     return dockWidget;
4195 }
4196 
4197 bool MainWindow::isMixedTabbed() const
4198 {
4199     return !tabifiedDockWidgets(m_mixerDock).isEmpty();
4200 }
4201 
4202 void MainWindow::slotUpdateMonitorOverlays(int id, int code)
4203 {
4204     QMenu *monitorOverlay = static_cast<QMenu *>(factory()->container(QStringLiteral("monitor_config_overlay"), this));
4205     if (!monitorOverlay) {
4206         return;
4207     }
4208     QList<QAction *> actions = monitorOverlay->actions();
4209     for (QAction *ac : qAsConst(actions)) {
4210         int mid = ac->data().toInt();
4211         if (mid == 0x010 || mid == 0x040) {
4212             ac->setVisible(id == Kdenlive::ClipMonitor);
4213         }
4214         ac->setChecked(code & mid);
4215     }
4216 }
4217 
4218 void MainWindow::slotChangeStyle(QAction *a)
4219 {
4220     QString style = a->data().toString();
4221     KdenliveSettings::setWidgetstyle(style);
4222     doChangeStyle();
4223     // Monitor refresh is necessary
4224     raiseMonitor(pCore->monitorManager()->isActive(Kdenlive::ClipMonitor));
4225 }
4226 
4227 void MainWindow::raiseMonitor(bool clipMonitor)
4228 {
4229     if (clipMonitor) {
4230         m_clipMonitorDock->show();
4231         m_clipMonitorDock->raise();
4232     } else {
4233         m_projectMonitorDock->show();
4234         m_projectMonitorDock->raise();
4235     }
4236 }
4237 
4238 void MainWindow::doChangeStyle()
4239 {
4240     QString newStyle = KdenliveSettings::widgetstyle();
4241     if (newStyle.isEmpty() || newStyle == QStringLiteral("Default")) {
4242         newStyle = defaultStyle("Breeze");
4243     }
4244     QApplication::setStyle(QStyleFactory::create(newStyle));
4245 }
4246 
4247 bool MainWindow::isTabbedWith(QDockWidget *widget, const QString &otherWidget)
4248 {
4249     QList<QDockWidget *> tabbed = tabifiedDockWidgets(widget);
4250     for (auto tab : qAsConst(tabbed)) {
4251         if (tab->objectName() == otherWidget) {
4252             return true;
4253         }
4254     }
4255     return false;
4256 }
4257 
4258 void MainWindow::slotToggleAutoPreview(bool enable)
4259 {
4260     KdenliveSettings::setAutopreview(enable);
4261     if (enable && getCurrentTimeline()) {
4262         getCurrentTimeline()->controller()->startPreviewRender();
4263     }
4264 }
4265 
4266 void MainWindow::showTimelineToolbarMenu(const QPoint &pos)
4267 {
4268     QMenu menu;
4269     menu.addAction(actionCollection()->action(KStandardAction::name(KStandardAction::ConfigureToolbars)));
4270     QMenu *contextSize = new QMenu(i18n("Icon Size"));
4271     menu.addMenu(contextSize);
4272     auto *sizeGroup = new QActionGroup(contextSize);
4273     int currentSize = m_timelineToolBar->iconSize().width();
4274     QAction *a = new QAction(i18nc("@item:inmenu Icon size", "Default"), contextSize);
4275     a->setData(m_timelineToolBar->iconSizeDefault());
4276     a->setCheckable(true);
4277     if (m_timelineToolBar->iconSizeDefault() == currentSize) {
4278         a->setChecked(true);
4279     }
4280     a->setActionGroup(sizeGroup);
4281     contextSize->addAction(a);
4282     KIconTheme *theme = KIconLoader::global()->theme();
4283     QList<int> avSizes;
4284     if (theme) {
4285         avSizes = theme->querySizes(KIconLoader::Toolbar);
4286     }
4287 
4288     std::sort(avSizes.begin(), avSizes.end());
4289 
4290     if (avSizes.count() < 10) {
4291         // Fixed or threshold type icons
4292         for (int it : avSizes) {
4293             QString text;
4294             if (it < 19) {
4295                 text = i18n("Small (%1x%2)", it, it);
4296             } else if (it < 25) {
4297                 text = i18n("Medium (%1x%2)", it, it);
4298             } else if (it < 35) {
4299                 text = i18n("Large (%1x%2)", it, it);
4300             } else {
4301                 text = i18n("Huge (%1x%2)", it, it);
4302             }
4303 
4304             // save the size in the contextIconSizes map
4305             auto *sizeAction = new QAction(text, contextSize);
4306             sizeAction->setData(it);
4307             sizeAction->setCheckable(true);
4308             sizeAction->setActionGroup(sizeGroup);
4309             if (it == currentSize) {
4310                 sizeAction->setChecked(true);
4311             }
4312             contextSize->addAction(sizeAction);
4313         }
4314     } else {
4315         // Scalable icons.
4316         const int progression[] = {16, 22, 32, 48, 64, 96, 128, 192, 256};
4317 
4318         for (int i : progression) {
4319             for (int it : avSizes) {
4320                 if (it >= i) {
4321                     QString text;
4322                     if (it < 19) {
4323                         text = i18n("Small (%1x%2)", it, it);
4324                     } else if (it < 25) {
4325                         text = i18n("Medium (%1x%2)", it, it);
4326                     } else if (it < 35) {
4327                         text = i18n("Large (%1x%2)", it, it);
4328                     } else {
4329                         text = i18n("Huge (%1x%2)", it, it);
4330                     }
4331 
4332                     // save the size in the contextIconSizes map
4333                     auto *sizeAction = new QAction(text, contextSize);
4334                     sizeAction->setData(it);
4335                     sizeAction->setCheckable(true);
4336                     sizeAction->setActionGroup(sizeGroup);
4337                     if (it == currentSize) {
4338                         sizeAction->setChecked(true);
4339                     }
4340                     contextSize->addAction(sizeAction);
4341                     break;
4342                 }
4343             }
4344         }
4345     }
4346     KEditToolBar::setGlobalDefaultToolBar("timelineToolBar");
4347     connect(contextSize, &QMenu::triggered, this, &MainWindow::setTimelineToolbarIconSize);
4348     menu.exec(m_timelineToolBar->mapToGlobal(pos));
4349     contextSize->deleteLater();
4350 }
4351 
4352 void MainWindow::setTimelineToolbarIconSize(QAction *a)
4353 {
4354     if (!a) {
4355         return;
4356     }
4357     int size = a->data().toInt();
4358     m_timelineToolBar->setIconDimensions(size);
4359     KSharedConfigPtr config = KSharedConfig::openConfig();
4360     KConfigGroup mainConfig(config, QStringLiteral("MainWindow"));
4361     KConfigGroup tbGroup(&mainConfig, QStringLiteral("Toolbar timelineToolBar"));
4362     m_timelineToolBar->saveSettings(tbGroup);
4363 }
4364 
4365 void MainWindow::slotManageCache()
4366 {
4367     QPointer<TemporaryData> d(new TemporaryData(pCore->currentDoc(), false, this));
4368     connect(d, &TemporaryData::disableProxies, this, &MainWindow::slotDisableProxies);
4369     d->exec();
4370 }
4371 
4372 void MainWindow::slotUpdateCompositing(bool checked)
4373 {
4374     getCurrentTimeline()->controller()->switchCompositing(checked);
4375     pCore->currentDoc()->setModified();
4376 }
4377 
4378 void MainWindow::slotUpdateCompositeAction(bool enable)
4379 {
4380     m_compositeAction->setChecked(enable);
4381 }
4382 
4383 void MainWindow::showMenuBar(bool show)
4384 {
4385     if (!show && toolBar()->isHidden()) {
4386         KMessageBox::information(this, i18n("This will hide the menu bar completely. You can show it again by typing Ctrl+M."), i18n("Hide menu bar"),
4387                                  QStringLiteral("show-menubar-warning"));
4388     }
4389     menuBar()->setVisible(show);
4390 }
4391 
4392 void MainWindow::forceIconSet(bool force)
4393 {
4394     KdenliveSettings::setForce_breeze(force);
4395     if (force) {
4396         // Check current color theme
4397         QColor background = qApp->palette().window().color();
4398         bool useDarkIcons = background.value() < 100;
4399         KdenliveSettings::setUse_dark_breeze(useDarkIcons);
4400     }
4401     if (KMessageBox::warningContinueCancel(this, i18n("Kdenlive needs to be restarted to apply the icon theme change. Restart now?")) ==
4402         KMessageBox::Continue) {
4403         slotRestart();
4404     }
4405 }
4406 
4407 TimelineWidget *MainWindow::getCurrentTimeline() const
4408 {
4409     return m_timelineTabs->getCurrentTimeline();
4410 }
4411 
4412 TimelineWidget *MainWindow::getTimeline(const QUuid uuid) const
4413 {
4414     return m_timelineTabs->getTimeline(uuid);
4415 }
4416 
4417 bool MainWindow::hasTimeline() const
4418 {
4419     return m_timelineTabs != nullptr;
4420 }
4421 
4422 void MainWindow::closeTimelineTab(const QUuid uuid)
4423 {
4424     m_timelineTabs->closeTimelineTab(uuid);
4425 }
4426 
4427 const QStringList MainWindow::openedSequences() const
4428 {
4429     if (m_timelineTabs) {
4430         return m_timelineTabs->openedSequences();
4431     }
4432     return QStringList();
4433 }
4434 
4435 void MainWindow::resetTimelineTracks()
4436 {
4437     TimelineWidget *current = getCurrentTimeline();
4438     if (current) {
4439         current->controller()->resetTrackHeight();
4440     }
4441 }
4442 
4443 void MainWindow::slotRemapItemTime()
4444 {
4445     TimelineWidget *current = getCurrentTimeline();
4446     if (current) {
4447         current->controller()->remapItemTime(-1);
4448     }
4449 }
4450 
4451 void MainWindow::slotEditItemSpeed()
4452 {
4453     TimelineWidget *current = getCurrentTimeline();
4454     if (current) {
4455         current->controller()->changeItemSpeed(-1, -1);
4456     }
4457 }
4458 
4459 void MainWindow::slotSwitchTimelineZone(bool active)
4460 {
4461     pCore->currentDoc()->setDocumentProperty(QStringLiteral("enableTimelineZone"), active ? QStringLiteral("1") : QStringLiteral("0"));
4462     Q_EMIT getCurrentTimeline()->controller()->useRulerChanged();
4463     QSignalBlocker blocker(m_useTimelineZone);
4464     m_useTimelineZone->setActive(active);
4465 }
4466 
4467 void MainWindow::slotGrabItem()
4468 {
4469     getCurrentTimeline()->controller()->grabCurrent();
4470 }
4471 
4472 void MainWindow::slotCollapse()
4473 {
4474     if ((QApplication::focusWidget() != nullptr) && (QApplication::focusWidget()->parentWidget() != nullptr) &&
4475         QApplication::focusWidget()->parentWidget() == pCore->bin()) {
4476         // Bin expand/collapse?
4477 
4478     } else {
4479         QWidget *widget = QApplication::focusWidget();
4480         while ((widget != nullptr) && widget != this) {
4481             if (widget == m_effectStackDock) {
4482                 m_assetPanel->collapseCurrentEffect();
4483                 return;
4484             }
4485             widget = widget->parentWidget();
4486         }
4487 
4488         // Collapse / expand track
4489         getCurrentTimeline()->controller()->collapseActiveTrack();
4490     }
4491 }
4492 
4493 void MainWindow::slotExpandClip()
4494 {
4495     getCurrentTimeline()->controller()->expandActiveClip();
4496 }
4497 
4498 bool MainWindow::timelineVisible() const
4499 {
4500     return !centralWidget()->isHidden();
4501 }
4502 
4503 void MainWindow::slotActivateAudioTrackSequence()
4504 {
4505     auto *action = qobject_cast<QAction *>(sender());
4506     const QList<int> trackIds = getCurrentTimeline()->model()->getTracksIds(true);
4507     int trackPos = qBound(0, action->data().toInt(), trackIds.count() - 1);
4508     int tid = trackIds.at(trackPos);
4509     getCurrentTimeline()->controller()->setActiveTrack(tid);
4510 }
4511 
4512 void MainWindow::slotActivateVideoTrackSequence()
4513 {
4514     auto *action = qobject_cast<QAction *>(sender());
4515     const QList<int> trackIds = getCurrentTimeline()->model()->getTracksIds(false);
4516     int trackPos = qBound(0, action->data().toInt(), trackIds.count() - 1);
4517     int tid = trackIds.at(trackIds.count() - 1 - trackPos);
4518     getCurrentTimeline()->controller()->setActiveTrack(tid);
4519     if (m_activeTool == ToolType::MulticamTool) {
4520         pCore->monitorManager()->slotPerformMultiTrackMode();
4521     }
4522 }
4523 
4524 void MainWindow::slotActivateTarget()
4525 {
4526     auto *action = qobject_cast<QAction *>(sender());
4527     if (action) {
4528         int ix = action->data().toInt();
4529         getCurrentTimeline()->controller()->assignCurrentTarget(ix);
4530     }
4531 }
4532 
4533 void MainWindow::resetSubtitles(const QUuid &uuid)
4534 {
4535     // Hide subtitle track
4536     m_buttonSubtitleEditTool->setChecked(false);
4537     KdenliveSettings::setShowSubtitles(false);
4538     pCore->subtitleWidget()->setModel(nullptr);
4539     if (pCore->currentDoc()) {
4540         std::shared_ptr<TimelineItemModel> timeline = pCore->currentDoc()->getTimeline(uuid);
4541         if (timeline && timeline->hasSubtitleModel()) {
4542             auto subModel = timeline->getSubtitleModel();
4543             QMap<std::pair<int, QString>, QString> currentSubs = subModel->getSubtitlesList();
4544             QMapIterator<std::pair<int, QString>, QString> i(currentSubs);
4545             while (i.hasNext()) {
4546                 i.next();
4547                 const QString workPath = pCore->currentDoc()->subTitlePath(uuid, i.key().first, false);
4548                 QFile workFile(workPath);
4549                 if (workFile.exists()) {
4550                     workFile.remove();
4551                 }
4552             }
4553         }
4554     }
4555 }
4556 
4557 void MainWindow::slotShowSubtitles(bool show)
4558 {
4559     const QUuid uuid = getCurrentTimeline()->model()->uuid();
4560     KdenliveSettings::setShowSubtitles(show);
4561     if (getCurrentTimeline()->model()->hasSubtitleModel()) {
4562         getCurrentTimeline()->connectSubtitleModel(false);
4563     } else {
4564         QMap<QString, QString> props = QMap<QString, QString>();
4565         slotEditSubtitle(props);
4566     }
4567     pCore->currentDoc()->setSequenceProperty(uuid, QStringLiteral("hidesubtitle"), show ? 0 : 1);
4568 }
4569 
4570 void MainWindow::slotInitSubtitle(const QMap<QString, QString> &subProperties, const QUuid &uuid)
4571 {
4572     std::shared_ptr<TimelineItemModel> timeline = pCore->currentDoc()->getTimeline(uuid);
4573     Q_ASSERT(!timeline->hasSubtitleModel());
4574     std::shared_ptr<SubtitleModel> subtitleModel = timeline->createSubtitleModel();
4575     // Starting a new subtitle for this project
4576     pCore->subtitleWidget()->setModel(subtitleModel);
4577     subtitleModel->loadProperties(subProperties);
4578     if (uuid == pCore->currentTimelineId() && pCore->currentDoc()->getSequenceProperty(uuid, QStringLiteral("hidesubtitle")).toInt() == 0) {
4579         KdenliveSettings::setShowSubtitles(true);
4580         m_buttonSubtitleEditTool->setChecked(true);
4581         getCurrentTimeline()->connectSubtitleModel(true);
4582     }
4583 }
4584 
4585 void MainWindow::slotEditSubtitle(const QMap<QString, QString> &subProperties)
4586 {
4587     bool hasSubtitleModel = getCurrentTimeline()->hasSubtitles();
4588     if (!hasSubtitleModel) {
4589         std::shared_ptr<SubtitleModel> subtitleModel = getCurrentTimeline()->model()->createSubtitleModel();
4590         // Starting a new subtitle for this project
4591         pCore->subtitleWidget()->setModel(subtitleModel);
4592         m_buttonSubtitleEditTool->setChecked(true);
4593         KdenliveSettings::setShowSubtitles(true);
4594         if (!subProperties.isEmpty()) {
4595             subtitleModel->loadProperties(subProperties);
4596             // Load the disabled / locked state of the subtitle
4597             Q_EMIT getCurrentTimeline()->controller()->subtitlesLockedChanged();
4598             Q_EMIT getCurrentTimeline()->controller()->subtitlesDisabledChanged();
4599         }
4600         getCurrentTimeline()->connectSubtitleModel(true);
4601     } else {
4602         KdenliveSettings::setShowSubtitles(m_buttonSubtitleEditTool->isChecked());
4603         getCurrentTimeline()->connectSubtitleModel(false);
4604     }
4605 }
4606 
4607 void MainWindow::slotAddSubtitle(const QString &text)
4608 {
4609     showSubtitleTrack();
4610     getCurrentTimeline()->model()->getSubtitleModel()->addSubtitle(-1, text);
4611 }
4612 
4613 void MainWindow::slotDisableSubtitle()
4614 {
4615     getCurrentTimeline()->controller()->switchSubtitleDisable();
4616 }
4617 
4618 void MainWindow::slotLockSubtitle()
4619 {
4620     getCurrentTimeline()->controller()->switchSubtitleLock();
4621 }
4622 
4623 void MainWindow::showSubtitleTrack()
4624 {
4625     if (!getCurrentTimeline()->hasSubtitles() || !m_buttonSubtitleEditTool->isChecked()) {
4626         m_buttonSubtitleEditTool->setChecked(true);
4627         slotEditSubtitle();
4628     }
4629 }
4630 
4631 void MainWindow::slotImportSubtitle()
4632 {
4633     showSubtitleTrack();
4634     getCurrentTimeline()->controller()->importSubtitle();
4635 }
4636 
4637 void MainWindow::slotManageSubtitle()
4638 {
4639     showSubtitleTrack();
4640     getCurrentTimeline()->controller()->subtitlesMenuActivated(-1);
4641 }
4642 
4643 void MainWindow::slotExportSubtitle()
4644 {
4645     if (!getCurrentTimeline()->hasSubtitles()) {
4646         pCore->displayMessage(i18n("No subtitles in current project"), ErrorMessage);
4647         return;
4648     }
4649     getCurrentTimeline()->controller()->exportSubtitle();
4650 }
4651 
4652 void MainWindow::slotSpeechRecognition()
4653 {
4654     if (!getCurrentTimeline()->hasSubtitles()) {
4655         slotEditSubtitle();
4656     }
4657     getCurrentTimeline()->controller()->subtitleSpeechRecognition();
4658 }
4659 
4660 void MainWindow::slotCopyDebugInfo()
4661 {
4662     QString debuginfo = QStringLiteral("Kdenlive: %1\n").arg(KAboutData::applicationData().version());
4663     QString packageType = pCore->packageType();
4664     debuginfo.append(QStringLiteral("Package Type: %1\n").arg(packageType.isEmpty() ? QStringLiteral("Unknown/Default") : packageType));
4665     debuginfo.append(QStringLiteral("MLT: %1\n").arg(mlt_version_get_string()));
4666     debuginfo.append(QStringLiteral("Qt: %1 (built against %2 %3)\n").arg(QString::fromLocal8Bit(qVersion()), QT_VERSION_STR, QSysInfo::buildAbi()));
4667     debuginfo.append(QStringLiteral("Frameworks: %2\n").arg(KCoreAddons::versionString()));
4668     debuginfo.append(QStringLiteral("System: %1\n").arg(QSysInfo::prettyProductName()));
4669     debuginfo.append(QStringLiteral("Kernel: %1 %2\n").arg(QSysInfo::kernelType(), QSysInfo::kernelVersion()));
4670     debuginfo.append(QStringLiteral("CPU: %1\n").arg(QSysInfo::currentCpuArchitecture()));
4671     debuginfo.append(QStringLiteral("Windowing System: %1\n").arg(QGuiApplication::platformName()));
4672     if (m_clipMonitor) {
4673         debuginfo.append(QStringLiteral("GPU: %1\n").arg(m_clipMonitor->getGPUInfo().join(QLatin1Char('/'))));
4674     }
4675     debuginfo.append(QStringLiteral("Movit (GPU): %1\n").arg(KdenliveSettings::gpu_accel() ? QStringLiteral("enabled") : QStringLiteral("disabled")));
4676     debuginfo.append(QStringLiteral("Track Compositing: %1\n").arg(TransitionsRepository::get()->getCompositingTransition()));
4677     QClipboard *clipboard = QApplication::clipboard();
4678     clipboard->setText(debuginfo);
4679 }
4680 
4681 bool MainWindow::eventFilter(QObject *object, QEvent *event)
4682 {
4683     switch (event->type()) {
4684     case QEvent::ShortcutOverride:
4685         if (static_cast<QKeyEvent *>(event)->key() == Qt::Key_Escape) {
4686             if (pCore->isMediaMonitoring()) {
4687                 slotShowTrackRec(false);
4688                 return true;
4689             }
4690             if (pCore->isMediaCapturing()) {
4691                 pCore->switchCapture();
4692                 return true;
4693             }
4694             if (m_activeTool != ToolType::SelectTool && m_commandStack->activeStack()->canUndo()) {
4695                 m_buttonSelectTool->trigger();
4696                 return true;
4697             } else {
4698                 if (m_commandStack->activeStack()->canUndo()) {
4699                     // Don't call selection clear if a drag operation is in progress
4700                     getCurrentTimeline()->model()->requestClearSelection();
4701                 }
4702                 return true;
4703             }
4704         }
4705         break;
4706     default:
4707         break;
4708     }
4709     return QObject::eventFilter(object, event);
4710 }
4711 
4712 void MainWindow::slotRemoveBinDock(const QString &name)
4713 {
4714     QWidget *toDelete = nullptr;
4715     int ix = 0;
4716     for (auto &b : m_binWidgets) {
4717         if (b->parentWidget()->objectName() == name) {
4718             toDelete = b->parentWidget();
4719             m_binWidgets.takeAt(ix);
4720             break;
4721         }
4722         ix++;
4723     }
4724     if (toDelete) {
4725         toDelete->deleteLater();
4726     }
4727     updateDockMenu();
4728     loadDockActions();
4729 }
4730 
4731 void MainWindow::addBin(Bin *bin, const QString &binName)
4732 {
4733     connect(bin, &Bin::findInTimeline, this, &MainWindow::slotClipInTimeline, Qt::DirectConnection);
4734     connect(bin, &Bin::setupTargets, this, [&](bool hasVideo, QMap<int, QString> audioStreams) {
4735         if (getCurrentTimeline() && getCurrentTimeline()->controller()) {
4736             getCurrentTimeline()->controller()->setTargetTracks(hasVideo, audioStreams);
4737         }
4738     });
4739     if (!m_binWidgets.isEmpty()) {
4740         // This is a secondary bin widget
4741         int ix = binCount() + 1;
4742         QDockWidget *binDock = addDock(binName.isEmpty() ? i18n("Project Bin %1", ix) : binName, QString("project_bin_%1").arg(ix), bin);
4743         bin->setupGeneratorMenu();
4744         connect(bin, &Bin::requestShowEffectStack, m_assetPanel, &AssetPanel::showEffectStack);
4745         connect(bin, &Bin::requestShowClipProperties, getBin(), &Bin::showClipProperties);
4746         connect(bin, &Bin::requestBinClose, this, [this, binDock]() { Q_EMIT removeBinDock(binDock->objectName()); });
4747         tabifyDockWidget(m_projectBinDock, binDock);
4748         // Disable title bar since it is tabbed
4749         binDock->setTitleBarWidget(new QWidget);
4750         // Update dock list
4751         updateDockMenu();
4752         loadDockActions();
4753         binDock->show();
4754         binDock->raise();
4755     }
4756     m_binWidgets << bin;
4757 }
4758 
4759 void MainWindow::tabifyBins()
4760 {
4761     QList<QDockWidget *> docks = findChildren<QDockWidget *>();
4762     for (auto dock : qAsConst(docks)) {
4763         if (dock->objectName().startsWith(QLatin1String("project_bin_"))) {
4764             tabifyDockWidget(m_projectBinDock, dock);
4765         }
4766     }
4767 }
4768 
4769 Bin *MainWindow::getBin()
4770 {
4771     if (m_binWidgets.isEmpty()) {
4772         return nullptr;
4773     }
4774     return m_binWidgets.first();
4775 }
4776 
4777 Bin *MainWindow::activeBin()
4778 {
4779     QWidget *wid = QApplication::focusWidget();
4780     if (wid) {
4781         for (auto &bin : m_binWidgets) {
4782             if (bin == wid || bin->isAncestorOf(wid)) {
4783                 return bin;
4784             }
4785         }
4786     }
4787     return m_binWidgets.first();
4788 }
4789 
4790 int MainWindow::binCount() const
4791 {
4792     if (m_binWidgets.isEmpty()) {
4793         return 0;
4794     }
4795     return m_binWidgets.count();
4796 }
4797 
4798 void MainWindow::processRestoreState(const QByteArray &state)
4799 {
4800     // On Wayland, restoreState crashes when quickly hiding/showing/hiding a monitor in restoreState, so hide before restoring
4801     m_projectMonitorDock->close();
4802     m_clipMonitorDock->close();
4803     restoreState(state);
4804 }
4805 
4806 void MainWindow::checkMaxCacheSize()
4807 {
4808     if (KdenliveSettings::lastCacheCheck().daysTo(QDateTime::currentDateTime()) < 14) {
4809         return;
4810     }
4811     if (KdenliveSettings::checkForUpdate()) {
4812         // Check if the Kdenlive version is very old
4813         const QStringList kdenliveVersion = KAboutData::applicationData().version().split(QLatin1Char('.'));
4814         if (kdenliveVersion.size() > 2) {
4815             bool ok;
4816             int kdenliveYear = kdenliveVersion.at(0).toInt(&ok);
4817             if (ok) {
4818                 int kdenliveMonth = kdenliveVersion.at(1).toInt(&ok);
4819                 if (ok) {
4820                     if (kdenliveYear < 100) {
4821                         kdenliveYear += 2000;
4822                     }
4823                     QDate releaseDate = QDate(kdenliveYear, kdenliveMonth, 1);
4824                     if (releaseDate.isValid()) {
4825                         int days = releaseDate.daysTo(QDate::currentDate());
4826                         if (days > 180) {
4827                             // Propose update
4828                             QAction *updateAction = new QAction(i18n("Go to download page"), this);
4829                             connect(updateAction, &QAction::triggered, this,
4830                                     []() { QDesktopServices::openUrl(QUrl(QStringLiteral("https://kdenlive.org/download"))); });
4831                             QAction *abortAction = new QAction(i18n("Never check again"), this);
4832                             connect(abortAction, &QAction::triggered, this, []() { KdenliveSettings::setCheckForUpdate(false); });
4833                             if (days > 360) {
4834                                 pCore->displayBinMessage(i18n("Your Kdenlive version is older than 1 year, we strongly encourage you to upgrade"),
4835                                                          KMessageWidget::Warning, {updateAction, abortAction}, true, BinMessage::BinCategory::UpdateMessage);
4836                             } else {
4837                                 pCore->displayBinMessage(i18n("Your Kdenlive version is older than 6 months, we encourage you to upgrade"),
4838                                                          KMessageWidget::Information, {updateAction, abortAction}, true,
4839                                                          BinMessage::BinCategory::UpdateMessage);
4840                             }
4841                         }
4842                     }
4843                 }
4844             }
4845         }
4846     }
4847 
4848     KdenliveSettings::setLastCacheCheck(QDateTime::currentDateTime());
4849     // Check cached data size
4850     if (KdenliveSettings::maxcachesize() <= 0) {
4851         return;
4852     }
4853     bool ok;
4854     KIO::filesize_t total = 0;
4855     QDir cacheDir = pCore->currentDoc()->getCacheDir(SystemCacheRoot, &ok);
4856     if (!ok) {
4857         return;
4858     }
4859     QDir backupFolder(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/.backup"));
4860     QList<QDir> toAdd;
4861     QList<QDir> toRemove;
4862     if (cacheDir.exists()) {
4863         toAdd << cacheDir;
4864     }
4865     if (backupFolder.exists()) {
4866         toAdd << cacheDir;
4867     }
4868     if (cacheDir.cd(QStringLiteral("knewstuff"))) {
4869         toRemove << cacheDir;
4870         cacheDir.cdUp();
4871     }
4872     if (cacheDir.cd(QStringLiteral("attica"))) {
4873         toRemove << cacheDir;
4874         cacheDir.cdUp();
4875     }
4876     if (cacheDir.cd(QStringLiteral("proxy"))) {
4877         toRemove << cacheDir;
4878         cacheDir.cdUp();
4879     }
4880     pCore->displayMessage(i18n("Checking cached data size"), InformationMessage);
4881     while (!toAdd.isEmpty()) {
4882         QDir dir = toAdd.takeFirst();
4883         KIO::DirectorySizeJob *job = KIO::directorySize(QUrl::fromLocalFile(dir.absolutePath()));
4884         job->exec();
4885         total += job->totalSize();
4886     }
4887     while (!toRemove.isEmpty()) {
4888         QDir dir = toRemove.takeFirst();
4889         KIO::DirectorySizeJob *job = KIO::directorySize(QUrl::fromLocalFile(dir.absolutePath()));
4890         job->exec();
4891         total -= job->totalSize();
4892     }
4893     if (total > KIO::filesize_t(1048576) * KdenliveSettings::maxcachesize()) {
4894         slotManageCache();
4895     }
4896 }
4897 
4898 void MainWindow::manageClipJobs(AbstractTask::JOBTYPE type, QWidget *parentWidget)
4899 {
4900     QScopedPointer<ClipJobManager> dialog(new ClipJobManager(type, parentWidget ? parentWidget : this));
4901     dialog->exec();
4902     // Rebuild list of clip jobs
4903     buildDynamicActions();
4904     loadClipActions();
4905 }
4906 
4907 TimelineWidget *MainWindow::openTimeline(const QUuid &uuid, const QString &tabName, std::shared_ptr<TimelineItemModel> timelineModel)
4908 {
4909     // Create a new timeline tab
4910     KdenliveDoc *project = pCore->currentDoc();
4911     TimelineWidget *timeline = m_timelineTabs->addTimeline(uuid, tabName, timelineModel, pCore->monitorManager()->projectMonitor()->getControllerProxy());
4912     slotSetZoom(project->zoom(uuid).x(), false);
4913     getCurrentTimeline()->controller()->setZone(project->zone(uuid), false);
4914     getCurrentTimeline()->controller()->setScrollPos(project->getSequenceProperty(uuid, QStringLiteral("scrollPos")).toInt());
4915     m_projectMonitor->slotLoadClipZone(project->zone(uuid));
4916     return timeline;
4917 }
4918 
4919 bool MainWindow::raiseTimeline(const QUuid &uuid)
4920 {
4921     return m_timelineTabs->raiseTimeline(uuid);
4922 }
4923 
4924 void MainWindow::connectTimeline()
4925 {
4926     qDebug() << "::::::::::: connecting timeline: " << getCurrentTimeline()->getUuid() << ", DUR: " << getCurrentTimeline()->controller()->duration();
4927     if (!getCurrentTimeline()->model()) {
4928         qDebug() << "::::::::::: TIMELINE HAS NO MODEL";
4929     } else {
4930         getCurrentTimeline()->model()->rebuildMixer();
4931     }
4932     // We just switched timeline, ensure a monitor effects view does not remain from previous
4933     m_projectMonitor->resetScene();
4934     const QUuid uuid = getCurrentTimeline()->getUuid();
4935     pCore->projectManager()->setActiveTimeline(uuid);
4936     connect(m_projectMonitor, &Monitor::multitrackView, getCurrentTimeline()->controller(), &TimelineController::slotMultitrackView, Qt::UniqueConnection);
4937     connect(m_projectMonitor, &Monitor::activateTrack, getCurrentTimeline()->controller(), &TimelineController::activateTrackAndSelect, Qt::UniqueConnection);
4938     connect(getCurrentTimeline()->controller(), &TimelineController::timelineClipSelected, this, [&](bool selected) {
4939         m_loopClip->setEnabled(selected);
4940         Q_EMIT pCore->library()->enableAddSelection(selected);
4941     });
4942     connect(pCore->library(), &LibraryWidget::saveTimelineSelection, getCurrentTimeline()->controller(), &TimelineController::saveTimelineSelection,
4943             Qt::UniqueConnection);
4944     getCurrentTimeline()->controller()->clipActions = kdenliveCategoryMap.value(QStringLiteral("timelineselection"))->actions();
4945     connect(getCurrentTimeline()->controller(), &TimelineController::durationChanged, pCore->projectManager(), &ProjectManager::adjustProjectDuration);
4946     connect(pCore->bin(), &Bin::processDragEnd, getCurrentTimeline(), &TimelineWidget::endDrag);
4947     pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor);
4948 
4949     KdenliveDoc *project = pCore->currentDoc();
4950     QSignalBlocker blocker(m_zoomSlider);
4951     m_zoomSlider->setValue(project->zoom(uuid).x());
4952     int position = project->getSequenceProperty(uuid, QStringLiteral("position"), QString::number(0)).toInt();
4953     pCore->monitorManager()->projectMonitor()->adjustRulerSize(getCurrentTimeline()->model()->duration() - 1, project->getFilteredGuideModel(uuid));
4954     pCore->monitorManager()->projectMonitor()->loadZone(getCurrentTimeline()->controller()->zoneIn(), getCurrentTimeline()->controller()->zoneOut());
4955     pCore->monitorManager()->projectMonitor()->setProducer(getCurrentTimeline()->model()->producer(), position);
4956     connect(project, &KdenliveDoc::docModified, this, &MainWindow::slotUpdateDocumentState);
4957     slotUpdateDocumentState(project->isModified());
4958 
4959     // Timeline preview
4960     QAction *previewRender = actionCollection()->action(QStringLiteral("prerender_timeline_zone"));
4961     if (previewRender) {
4962         previewRender->setEnabled(true);
4963     }
4964     QAction *disablePreview = actionCollection()->action(QStringLiteral("disable_preview"));
4965     if (getCurrentTimeline()->model()->hasTimelinePreview()) {
4966         disablePreview->setEnabled(true);
4967     } else {
4968         disablePreview->setEnabled(false);
4969     }
4970     disablePreview->blockSignals(true);
4971     disablePreview->setChecked(false);
4972     disablePreview->blockSignals(false);
4973 
4974     // Ensure the active timeline has an opaque black background for compositing
4975     getCurrentTimeline()->model()->makeTransparentBg(false);
4976 
4977     // Audio record actions
4978     connect(pCore.get(), &Core::finalizeRecording, getCurrentTimeline()->controller(), &TimelineController::finishRecording);
4979     connect(pCore.get(), &Core::recordAudio, getCurrentTimeline()->controller(), &TimelineController::switchRecording);
4980 
4981     // switch to active subtitle model
4982     pCore->subtitleWidget()->setModel(getCurrentTimeline()->model()->getSubtitleModel());
4983     bool hasSubtitleModel = getCurrentTimeline()->hasSubtitles();
4984     Q_EMIT getCurrentTimeline()->controller()->subtitlesLockedChanged();
4985     Q_EMIT getCurrentTimeline()->controller()->subtitlesDisabledChanged();
4986     bool showSubs = project->getSequenceProperty(uuid, QStringLiteral("hidesubtitle")).toInt() == 0;
4987     KdenliveSettings::setShowSubtitles(showSubs && hasSubtitleModel);
4988     getCurrentTimeline()->connectSubtitleModel(hasSubtitleModel);
4989     m_buttonSubtitleEditTool->setChecked(showSubs && hasSubtitleModel);
4990     if (hasSubtitleModel) {
4991         // Restore style
4992         getCurrentTimeline()->model()->getSubtitleModel()->loadProperties({});
4993         slotShowSubtitles(showSubs);
4994     }
4995     // Display timeline guides in the guides list
4996     pCore->guidesList()->setModel(project->getGuideModel(uuid), project->getFilteredGuideModel(uuid));
4997 
4998     if (m_renderWidget) {
4999         slotCheckRenderStatus();
5000         m_renderWidget->setGuides(project->getGuideModel(uuid));
5001         m_renderWidget->updateDocumentPath();
5002         m_renderWidget->showRenderDuration();
5003     }
5004 }
5005 
5006 void MainWindow::disconnectTimeline(TimelineWidget *timeline)
5007 {
5008     // Save current tab timeline position
5009     if (pCore->currentDoc()) {
5010         // pCore->currentDoc()->position = pCore->getTimelinePosition();
5011         //  disconnect(pCore->currentDoc(), &KdenliveDoc::docModified, this, &MainWindow::slotUpdateDocumentState);
5012         // qDebug()<<"=== SETTING POSITION  FOR DOC: "<<pCore->currentDoc()->position<<" / "<<pCore->currentDoc()->uuid;
5013     }
5014     // Ensure the active timeline has an transparent black background for embedded compositing
5015     timeline->model()->makeTransparentBg(true);
5016     disconnect(timeline->controller(), &TimelineController::durationChanged, pCore->projectManager(), &ProjectManager::adjustProjectDuration);
5017     disconnect(m_projectMonitor, &Monitor::multitrackView, timeline->controller(), &TimelineController::slotMultitrackView);
5018     disconnect(m_projectMonitor, &Monitor::activateTrack, timeline->controller(), &TimelineController::activateTrackAndSelect);
5019     disconnect(pCore->library(), &LibraryWidget::saveTimelineSelection, timeline->controller(), &TimelineController::saveTimelineSelection);
5020     timeline->controller()->clipActions = QList<QAction *>();
5021     disconnect(pCore->bin(), &Bin::processDragEnd, timeline, &TimelineWidget::endDrag);
5022     pCore->monitorManager()->projectMonitor()->setProducer(nullptr, -2);
5023     // Audio record actions
5024     disconnect(pCore.get(), &Core::finalizeRecording, timeline->controller(), &TimelineController::finishRecording);
5025     disconnect(pCore.get(), &Core::recordAudio, timeline->controller(), &TimelineController::switchRecording);
5026 }
5027 
5028 // Static
5029 QProcessEnvironment MainWindow::getCleanEnvironement()
5030 {
5031     QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
5032     qDebug() << "::: GOT ENV: " << env.value("LD_LIBRARY_PATH") << ", PATH: " << env.value("PATH") << "\n\nXDG:\n" << env.value("XDG_DATA_DIRS");
5033     QStringList libPath = env.value(QStringLiteral("LD_LIBRARY_PATH")).split(QLatin1Char(':'), Qt::SkipEmptyParts);
5034     QStringList updatedLDPath;
5035     for (auto &s : libPath) {
5036         if (!s.startsWith(QStringLiteral("/tmp/.mount_"))) {
5037             updatedLDPath << s;
5038         }
5039     }
5040     if (updatedLDPath.isEmpty()) {
5041         env.remove(QStringLiteral("LD_LIBRARY_PATH"));
5042