File indexing completed on 2022-10-04 15:37:24

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