File indexing completed on 2024-05-12 08:54:14

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 "monitor.h"
0008 #include "bin/bin.h"
0009 #include "bin/projectclip.h"
0010 #include "capture/mediacapture.h"
0011 #include "core.h"
0012 #include "dialogs/profilesdialog.h"
0013 #include "doc/kdenlivedoc.h"
0014 #include "doc/kthumb.h"
0015 #include "jobs/cuttask.h"
0016 #include "kdenlivesettings.h"
0017 #include "lib/audio/audioStreamInfo.h"
0018 #include "lib/localeHandling.h"
0019 #include "mainwindow.h"
0020 #include "mltcontroller/clipcontroller.h"
0021 #include "project/dialogs/guideslist.h"
0022 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0023 #include "videowidget.h"
0024 #if defined(Q_OS_WIN)
0025 #include "d3dvideowidget.h"
0026 #include "openglvideowidget.h"
0027 #elif defined(Q_OS_MACOS)
0028 #include "metalvideowidget.h"
0029 #else
0030 // Linux
0031 #include "openglvideowidget.h"
0032 #endif
0033 #else // Qt6
0034 #include "glwidget.h"
0035 #endif
0036 
0037 #include "bin/model/markersortmodel.h"
0038 #include "monitormanager.h"
0039 #include "monitorproxy.h"
0040 #include "profiles/profilemodel.hpp"
0041 #include "project/projectmanager.h"
0042 #include "qmlmanager.h"
0043 #include "recmanager.h"
0044 #include "scopes/monitoraudiolevel.h"
0045 #include "timeline2/model/snapmodel.hpp"
0046 #include "timeline2/view/timelinecontroller.h"
0047 #include "timeline2/view/timelinewidget.h"
0048 #include "transitions/transitionsrepository.hpp"
0049 #include "utils/thumbnailcache.hpp"
0050 
0051 #include "KLocalizedString"
0052 #include <KActionMenu>
0053 #include <KDualAction>
0054 #include <KFileWidget>
0055 #include <KMessageBox>
0056 #include <KMessageWidget>
0057 #include <KRecentDirs>
0058 #include <KSelectAction>
0059 #include <KWindowConfig>
0060 #include <kio_version.h>
0061 #include <kwidgetsaddons_version.h>
0062 
0063 #include "kdenlive_debug.h"
0064 #include <QCheckBox>
0065 #include <QDrag>
0066 #include <QFontDatabase>
0067 #include <QMenu>
0068 #include <QMimeData>
0069 #include <QMouseEvent>
0070 #include <QQuickItem>
0071 #include <QScreen>
0072 #include <QScrollBar>
0073 #include <QSlider>
0074 #include <QToolButton>
0075 #include <QVBoxLayout>
0076 #include <QtConcurrent>
0077 #include <utility>
0078 
0079 #define SEEK_INACTIVE (-1)
0080 
0081 VolumeAction::VolumeAction(QObject *parent)
0082     : QWidgetAction(parent)
0083 {
0084 }
0085 
0086 QWidget *VolumeAction::createWidget(QWidget *parent)
0087 {
0088     auto *hlay = new QHBoxLayout(parent);
0089     auto *iconLabel = new QLabel();
0090     iconLabel->setToolTip(i18n("Audio volume"));
0091     auto *slider = new QSlider(Qt::Horizontal, parent);
0092     slider->setRange(0, 100);
0093     auto *percentLabel = new QLabel(parent);
0094     connect(slider, &QSlider::valueChanged, this, [percentLabel, iconLabel](int value) {
0095         percentLabel->setText(i18n("%1%", value));
0096         int h = 16;
0097         QString iconName(QStringLiteral("audio-volume-high"));
0098         if (value == 0) {
0099             iconName = QStringLiteral("audio-volume-muted");
0100         } else if (value < 33) {
0101             iconName = QStringLiteral("audio-volume-low");
0102         } else if (value < 66) {
0103             iconName = QStringLiteral("audio-volume-medium");
0104         }
0105         iconLabel->setPixmap(QIcon::fromTheme(iconName).pixmap(h, h));
0106     });
0107     slider->setValue(KdenliveSettings::volume());
0108     connect(slider, &QSlider::valueChanged, this, &VolumeAction::volumeChanged);
0109     hlay->addWidget(iconLabel);
0110     hlay->addWidget(slider);
0111     hlay->addWidget(percentLabel);
0112     auto w = new QWidget(parent);
0113     w->setLayout(hlay);
0114     return w;
0115 }
0116 
0117 Monitor::Monitor(Kdenlive::MonitorId id, MonitorManager *manager, QWidget *parent)
0118     : AbstractMonitor(id, manager, parent)
0119     , m_controller(nullptr)
0120     , m_glMonitor(nullptr)
0121     , m_snaps(new SnapModel())
0122     , m_splitEffect(nullptr)
0123     , m_splitProducer(nullptr)
0124     , m_dragStarted(false)
0125     , m_recManager(nullptr)
0126     , m_loopClipAction(nullptr)
0127     , m_contextMenu(nullptr)
0128     , m_markerMenu(nullptr)
0129     , m_audioChannels(nullptr)
0130     , m_loopClipTransition(true)
0131     , m_editMarker(nullptr)
0132     , m_forceSizeFactor(0)
0133     , m_lastMonitorSceneType(MonitorSceneDefault)
0134     , m_displayingCountdown(true)
0135 {
0136     auto *layout = new QVBoxLayout;
0137     layout->setContentsMargins(0, 0, 0, 0);
0138     layout->setSpacing(0);
0139     // Create container widget
0140     m_glWidget = new QWidget(this);
0141     m_glWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
0142     auto *glayout = new QGridLayout(m_glWidget);
0143     glayout->setSpacing(0);
0144     glayout->setContentsMargins(0, 0, 0, 0);
0145     // Create QML OpenGL widget
0146 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0147 #if defined(Q_OS_WIN)
0148     if (QSGRendererInterface::Direct3D11 == QQuickWindow::graphicsApi())
0149         m_glMonitor = new D3DVideoWidget(id, this);
0150     else
0151         m_glMonitor = new OpenGLVideoWidget(id, this);
0152 #elif defined(Q_OS_MACOS)
0153     m_glMonitor = new MetalVideoWidget(id, this);
0154 #else
0155     m_glMonitor = new OpenGLVideoWidget(id, this);
0156 #endif
0157     //  The m_glMonitor quickWindow() can be destroyed on undock with some graphics interface (Windows/Mac), so reconnect on destroy
0158     auto rebuildViewConnection = [this]() {
0159         connect(m_glMonitor->quickWindow(), &QQuickWindow::sceneGraphInitialized, m_glMonitor, &VideoWidget::initialize, Qt::DirectConnection);
0160         connect(m_glMonitor->quickWindow(), &QQuickWindow::beforeRendering, m_glMonitor, &VideoWidget::beforeRendering, Qt::DirectConnection);
0161         connect(m_glMonitor->quickWindow(), &QQuickWindow::beforeRenderPassRecording, m_glMonitor, &VideoWidget::renderVideo, Qt::DirectConnection);
0162         m_glMonitor->reconnectWindow();
0163     };
0164 
0165     connect(m_glMonitor, &VideoWidget::reconnectWindow, [this, rebuildViewConnection]() {
0166         connect(m_glMonitor->quickWindow(), &QQuickWindow::destroyed, [rebuildViewConnection]() { rebuildViewConnection(); });
0167     });
0168 
0169     rebuildViewConnection();
0170 #else
0171     m_glMonitor = new VideoWidget(id, this);
0172 #endif
0173     connect(m_glMonitor, &VideoWidget::passKeyEvent, this, &Monitor::doKeyPressEvent);
0174     connect(m_glMonitor, &VideoWidget::panView, this, &Monitor::panView);
0175     connect(m_glMonitor->getControllerProxy(), &MonitorProxy::requestSeek, this, &Monitor::processSeek, Qt::DirectConnection);
0176     connect(m_glMonitor->getControllerProxy(), &MonitorProxy::positionChanged, this, &Monitor::slotSeekPosition);
0177     connect(m_glMonitor->getControllerProxy(), &MonitorProxy::addTimelineEffect, this, &Monitor::addTimelineEffect);
0178 
0179     m_qmlManager = new QmlManager(m_glMonitor);
0180     connect(m_qmlManager, &QmlManager::effectChanged, this, &Monitor::effectChanged);
0181     connect(m_qmlManager, &QmlManager::effectPointsChanged, this, &Monitor::effectPointsChanged);
0182     connect(m_qmlManager, &QmlManager::activateTrack, this, [&](int ix) { Q_EMIT activateTrack(ix, false); });
0183 
0184     glayout->addWidget(m_glMonitor, 0, 0);
0185     m_verticalScroll = new QScrollBar(Qt::Vertical);
0186     glayout->addWidget(m_verticalScroll, 0, 1);
0187     m_verticalScroll->hide();
0188     m_horizontalScroll = new QScrollBar(Qt::Horizontal);
0189     glayout->addWidget(m_horizontalScroll, 1, 0);
0190     m_horizontalScroll->hide();
0191     connect(m_horizontalScroll, &QAbstractSlider::valueChanged, this, &Monitor::setOffsetX);
0192     connect(m_verticalScroll, &QAbstractSlider::valueChanged, this, &Monitor::setOffsetY);
0193     connect(m_glMonitor, &VideoWidget::frameDisplayed, this, &Monitor::onFrameDisplayed, Qt::DirectConnection);
0194     connect(m_glMonitor, &VideoWidget::mouseSeek, this, &Monitor::slotMouseSeek);
0195     connect(m_glMonitor, &VideoWidget::switchFullScreen, this, &Monitor::slotSwitchFullScreen);
0196     connect(m_glMonitor, &VideoWidget::zoomChanged, this, &Monitor::setZoom);
0197     connect(m_glMonitor, SIGNAL(lockMonitor(bool)), this, SLOT(slotLockMonitor(bool)), Qt::DirectConnection);
0198     connect(m_glMonitor, &VideoWidget::showContextMenu, this, &Monitor::slotShowMenu);
0199     connect(m_glMonitor, &VideoWidget::gpuNotSupported, this, &Monitor::gpuError);
0200 
0201     m_glWidget->setMinimumSize(QSize(320, 180));
0202     layout->addWidget(m_glWidget, 10);
0203     layout->addStretch();
0204 
0205     // Tool bar buttons
0206     m_toolbar = new QToolBar(this);
0207     int size = style()->pixelMetric(QStyle::PM_SmallIconSize);
0208     QSize iconSize(size, size);
0209     m_toolbar->setIconSize(iconSize);
0210 
0211     auto *scalingAction = new QComboBox(this);
0212     scalingAction->setToolTip(i18n("Preview resolution - lower resolution means faster preview"));
0213     scalingAction->setWhatsThis(xi18nc("@info:whatsthis", "Sets the preview resolution of the project/clip monitor. One can select between 1:1, 720p, 540p, "
0214                                                           "360p, 270p (the lower the resolution the faster the preview)."));
0215     // Combobox padding is bad, so manually add a space before text
0216     scalingAction->addItems({QStringLiteral(" ") + i18n("1:1"), QStringLiteral(" ") + i18n("720p"), QStringLiteral(" ") + i18n("540p"),
0217                              QStringLiteral(" ") + i18n("360p"), QStringLiteral(" ") + i18n("270p")});
0218     connect(scalingAction, QOverload<int>::of(&QComboBox::activated), this, [this](int index) {
0219         switch (index) {
0220         case 1:
0221             KdenliveSettings::setPreviewScaling(2);
0222             break;
0223         case 2:
0224             KdenliveSettings::setPreviewScaling(4);
0225             break;
0226         case 3:
0227             KdenliveSettings::setPreviewScaling(8);
0228             break;
0229         case 4:
0230             KdenliveSettings::setPreviewScaling(16);
0231             break;
0232         default:
0233             KdenliveSettings::setPreviewScaling(0);
0234         }
0235         Q_EMIT m_monitorManager->scalingChanged();
0236         Q_EMIT m_monitorManager->updatePreviewScaling();
0237         m_monitorManager->refreshMonitors();
0238     });
0239 
0240     connect(manager, &MonitorManager::updatePreviewScaling, this, [this, scalingAction]() {
0241         m_glMonitor->updateScaling();
0242         switch (KdenliveSettings::previewScaling()) {
0243         case 2:
0244             scalingAction->setCurrentIndex(1);
0245             break;
0246         case 4:
0247             scalingAction->setCurrentIndex(2);
0248             break;
0249         case 8:
0250             scalingAction->setCurrentIndex(3);
0251             break;
0252         case 16:
0253             scalingAction->setCurrentIndex(4);
0254             break;
0255         default:
0256             scalingAction->setCurrentIndex(0);
0257             break;
0258         }
0259     });
0260     scalingAction->setFrame(false);
0261     scalingAction->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
0262     m_toolbar->addWidget(scalingAction);
0263     m_toolbar->addSeparator();
0264 
0265     if (id == Kdenlive::ClipMonitor) {
0266         // Add options for recording
0267         m_recManager = new RecManager(this);
0268         connect(m_recManager, &RecManager::warningMessage, this, &Monitor::warningMessage);
0269         connect(m_recManager, &RecManager::addClipToProject, this, &Monitor::addClipToProject);
0270         connect(m_glMonitor, &VideoWidget::startDrag, this, &Monitor::slotStartDrag);
0271         connect(pCore.get(), &Core::binClipDeleted, m_glMonitor->getControllerProxy(), &MonitorProxy::clipDeleted);
0272         // Show timeline clip usage
0273         connect(pCore.get(), &Core::clipInstanceResized, this, [this](const QString &binId) {
0274             if (m_controller && activeClipId() == binId) {
0275                 m_controller->checkClipBounds();
0276             }
0277         });
0278 
0279         m_toolbar->addAction(manager->getAction(QStringLiteral("insert_project_tree")));
0280         m_toolbar->addSeparator();
0281         m_streamsButton = new QToolButton(this);
0282         m_streamsButton->setPopupMode(QToolButton::InstantPopup);
0283         m_streamsButton->setIcon(QIcon::fromTheme(QStringLiteral("speaker")));
0284         m_streamsButton->setToolTip(i18n("Audio streams"));
0285         m_streamAction = m_toolbar->addWidget(m_streamsButton);
0286         m_audioChannels = new QMenu(this);
0287         m_streamsButton->setMenu(m_audioChannels);
0288         m_streamAction->setVisible(false);
0289 
0290         // Connect job data
0291         connect(&pCore->taskManager, &TaskManager::detailedProgress, m_glMonitor->getControllerProxy(), &MonitorProxy::setJobsProgress);
0292 
0293         connect(m_audioChannels, &QMenu::triggered, this, [this](QAction *ac) {
0294             // m_audioChannels->show();
0295             QList<QAction *> actions = m_audioChannels->actions();
0296             QMap<int, QString> enabledStreams;
0297             QVector<int> streamsList;
0298             if (ac->data().toInt() == INT_MAX) {
0299                 // Merge stream selected, clear all others
0300                 enabledStreams.clear();
0301                 enabledStreams.insert(INT_MAX, i18n("Merged streams"));
0302                 // Disable all other streams
0303                 QSignalBlocker bk(m_audioChannels);
0304                 for (auto act : qAsConst(actions)) {
0305                     if (act->isChecked() && act != ac) {
0306                         act->setChecked(false);
0307                     }
0308                     if (act->data().toInt() != INT_MAX) {
0309                         streamsList << act->data().toInt();
0310                     }
0311                 }
0312             } else {
0313                 for (auto act : qAsConst(actions)) {
0314                     if (act->isChecked()) {
0315                         // Audio stream is selected
0316                         if (act->data().toInt() == INT_MAX) {
0317                             QSignalBlocker bk(m_audioChannels);
0318                             act->setChecked(false);
0319                         } else {
0320                             enabledStreams.insert(act->data().toInt(), act->text().remove(QLatin1Char('&')));
0321                         }
0322                     }
0323                     if (act->data().toInt() != INT_MAX) {
0324                         streamsList << act->data().toInt();
0325                     }
0326                 }
0327             }
0328             if (!enabledStreams.isEmpty()) {
0329                 // Only 1 stream wanted, easy
0330                 QMap<QString, QString> props;
0331                 props.insert(QStringLiteral("audio_index"), QString::number(enabledStreams.firstKey()));
0332                 props.insert(QStringLiteral("astream"), QString::number(streamsList.indexOf(enabledStreams.firstKey())));
0333                 QList<int> streams = enabledStreams.keys();
0334                 QStringList astreams;
0335                 for (const int st : qAsConst(streams)) {
0336                     astreams << QString::number(st);
0337                 }
0338                 props.insert(QStringLiteral("kdenlive:active_streams"), astreams.join(QLatin1Char(';')));
0339                 m_controller->setProperties(props, true);
0340             } else {
0341                 // No active stream
0342                 QMap<QString, QString> props;
0343                 props.insert(QStringLiteral("audio_index"), QStringLiteral("-1"));
0344                 props.insert(QStringLiteral("astream"), QStringLiteral("-1"));
0345                 props.insert(QStringLiteral("kdenlive:active_streams"), QStringLiteral("-1"));
0346                 m_controller->setProperties(props, true);
0347             }
0348         });
0349     } else if (id == Kdenlive::ProjectMonitor) {
0350         // JBM - This caused the track audio levels to go blank on pause, doesn't seem to have another use
0351         // connect(m_glMonitor, &VideoWidget::paused, m_monitorManager, &MonitorManager::cleanMixer);
0352     }
0353 
0354     m_markIn = new QAction(QIcon::fromTheme(QStringLiteral("zone-in")), i18n("Set Zone In"), this);
0355     m_markOut = new QAction(QIcon::fromTheme(QStringLiteral("zone-out")), i18n("Set Zone Out"), this);
0356     m_toolbar->addAction(m_markIn);
0357     m_toolbar->addAction(m_markOut);
0358     connect(m_markIn, &QAction::triggered, this, [&, manager]() {
0359         m_monitorManager->activateMonitor(m_id);
0360         manager->getAction(QStringLiteral("mark_in"))->trigger();
0361     });
0362     connect(m_markOut, &QAction::triggered, this, [&, manager]() {
0363         m_monitorManager->activateMonitor(m_id);
0364         manager->getAction(QStringLiteral("mark_out"))->trigger();
0365     });
0366     // Per monitor rewind action
0367     QAction *rewind = new QAction(QIcon::fromTheme(QStringLiteral("media-seek-backward")), i18n("Rewind"), this);
0368     m_toolbar->addAction(rewind);
0369     connect(rewind, &QAction::triggered, this, [&]() { Monitor::slotRewind(); });
0370 
0371     auto *playButton = new QToolButton(m_toolbar);
0372     m_playMenu = new QMenu(i18n("Play"), this);
0373     connect(m_playMenu, &QMenu::aboutToShow, this, &Monitor::slotActivateMonitor);
0374     QAction *originalPlayAction = static_cast<KDualAction *>(manager->getAction(QStringLiteral("monitor_play")));
0375     m_playAction = new KDualAction(i18n("Play"), i18n("Pause"), this);
0376     m_playAction->setInactiveIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
0377     m_playAction->setActiveIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause")));
0378     connect(m_glMonitor, &VideoWidget::monitorPlay, m_playAction, &QAction::trigger);
0379 
0380     QString strippedTooltip = m_playAction->toolTip().remove(QRegularExpression(QStringLiteral("\\s\\(.*\\)")));
0381     // append shortcut if it exists for action
0382     if (originalPlayAction->shortcut() == QKeySequence(0)) {
0383         m_playAction->setToolTip(strippedTooltip);
0384     } else {
0385         m_playAction->setToolTip(strippedTooltip + QStringLiteral(" (") + originalPlayAction->shortcut().toString() + QLatin1Char(')'));
0386     }
0387     m_playMenu->addAction(m_playAction);
0388     connect(m_playAction, &QAction::triggered, this, &Monitor::slotSwitchPlay);
0389 
0390     playButton->setMenu(m_playMenu);
0391     playButton->setPopupMode(QToolButton::MenuButtonPopup);
0392     m_toolbar->addWidget(playButton);
0393 
0394     // Per monitor forward action
0395     QAction *forward = new QAction(QIcon::fromTheme(QStringLiteral("media-seek-forward")), i18n("Forward"), this);
0396     m_toolbar->addAction(forward);
0397     connect(forward, &QAction::triggered, this, [this]() { Monitor::slotForward(); });
0398 
0399     m_configMenuAction = new KActionMenu(QIcon::fromTheme(QStringLiteral("application-menu")), i18n("More Options…"), m_toolbar);
0400     m_configMenuAction->setWhatsThis(xi18nc("@info:whatsthis", "Opens the list of project/clip monitor options (e.g. audio volume, monitor size)."));
0401     m_configMenuAction->setPopupMode(QToolButton::InstantPopup);
0402     connect(m_configMenuAction->menu(), &QMenu::aboutToShow, this, &Monitor::updateMarkers);
0403 
0404     playButton->setDefaultAction(m_playAction);
0405     auto *volumeAction = new VolumeAction(this);
0406     connect(volumeAction, &VolumeAction::volumeChanged, this, &Monitor::slotSetVolume);
0407     m_configMenuAction->addAction(volumeAction);
0408 
0409     m_markerMenu = new KActionMenu(id == Kdenlive::ClipMonitor ? i18n("Go to Marker…") : i18n("Go to Guide…"), this);
0410     m_markerMenu->setEnabled(false);
0411     m_configMenuAction->addAction(m_markerMenu);
0412     connect(m_markerMenu->menu(), &QMenu::triggered, this, &Monitor::slotGoToMarker);
0413     m_forceSize = new KSelectAction(QIcon::fromTheme(QStringLiteral("transform-scale")), i18n("Force Monitor Size"), this);
0414     QAction *fullAction = m_forceSize->addAction(QIcon(), i18n("Force 100%"));
0415     fullAction->setData(100);
0416     QAction *halfAction = m_forceSize->addAction(QIcon(), i18n("Force 50%"));
0417     halfAction->setData(50);
0418     QAction *freeAction = m_forceSize->addAction(QIcon(), i18n("Free Resize"));
0419     freeAction->setData(0);
0420     m_configMenuAction->addAction(m_forceSize);
0421     m_forceSize->setCurrentAction(freeAction);
0422 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 240, 0)
0423     connect(m_forceSize, &KSelectAction::actionTriggered, this, &Monitor::slotForceSize);
0424 #else
0425     connect(m_forceSize, static_cast<void (KSelectAction::*)(QAction *)>(&KSelectAction::triggered), this, &Monitor::slotForceSize);
0426 #endif
0427 
0428     if (m_id == Kdenlive::ClipMonitor) {
0429         m_background = new KSelectAction(QIcon::fromTheme(QStringLiteral("paper-color")), i18n("Background Color"), this);
0430         QAction *blackAction = m_background->addAction(QIcon(), i18n("Black"));
0431         blackAction->setData("black");
0432         QAction *whiteAction = m_background->addAction(QIcon(), i18n("White"));
0433         whiteAction->setData("white");
0434         QAction *pinkAction = m_background->addAction(QIcon(), i18n("Pink"));
0435         pinkAction->setData("#ff00ff");
0436         m_configMenuAction->addAction(m_background);
0437         if (KdenliveSettings::monitor_background() == whiteAction->data().toString()) {
0438             m_background->setCurrentAction(whiteAction);
0439         } else if (KdenliveSettings::monitor_background() == pinkAction->data().toString()) {
0440             m_background->setCurrentAction(pinkAction);
0441         } else {
0442             m_background->setCurrentAction(blackAction);
0443         }
0444 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 240, 0)
0445         connect(m_background, &KSelectAction::actionTriggered, this, [this](QAction *a) {
0446 #else
0447         connect(m_background, static_cast<void (KSelectAction::*)(QAction *)>(&KSelectAction::triggered), this, [this](QAction *a) {
0448 #endif
0449             KdenliveSettings::setMonitor_background(a->data().toString());
0450             buildBackgroundedProducer(position());
0451         });
0452     }
0453 
0454     setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
0455     setLayout(layout);
0456     setMinimumHeight(200);
0457 
0458     connect(this, &Monitor::scopesClear, m_glMonitor, &VideoWidget::releaseAnalyse, Qt::DirectConnection);
0459     connect(m_glMonitor, &VideoWidget::analyseFrame, this, &Monitor::frameUpdated);
0460     m_timePos = new TimecodeDisplay(this);
0461 
0462     if (id == Kdenlive::ProjectMonitor) {
0463         connect(m_glMonitor->getControllerProxy(), &MonitorProxy::saveZone, this, &Monitor::zoneUpdated);
0464         connect(m_glMonitor->getControllerProxy(), &MonitorProxy::saveZoneWithUndo, this, &Monitor::zoneUpdatedWithUndo);
0465     } else if (id == Kdenlive::ClipMonitor) {
0466         connect(m_glMonitor->getControllerProxy(), &MonitorProxy::saveZone, this, &Monitor::updateClipZone);
0467     }
0468     m_glMonitor->getControllerProxy()->setTimeCode(m_timePos);
0469     connect(m_glMonitor->getControllerProxy(), &MonitorProxy::triggerAction, pCore.get(), &Core::triggerAction);
0470     connect(m_glMonitor->getControllerProxy(), &MonitorProxy::seekNextKeyframe, this, &Monitor::seekToNextKeyframe);
0471     connect(m_glMonitor->getControllerProxy(), &MonitorProxy::seekPreviousKeyframe, this, &Monitor::seekToPreviousKeyframe);
0472     connect(m_glMonitor->getControllerProxy(), &MonitorProxy::addRemoveKeyframe, this, &Monitor::addRemoveKeyframe);
0473     connect(m_glMonitor->getControllerProxy(), &MonitorProxy::seekToKeyframe, this, &Monitor::slotSeekToKeyFrame);
0474 
0475     m_toolbar->addAction(manager->getAction(QStringLiteral("monitor_editmode")));
0476 
0477     m_toolbar->addSeparator();
0478     m_toolbar->addWidget(m_timePos);
0479     m_toolbar->addAction(m_configMenuAction);
0480     m_toolbar->addSeparator();
0481     QMargins mrg = m_toolbar->contentsMargins();
0482     m_audioMeterWidget = new MonitorAudioLevel(m_toolbar->height() - mrg.top() - mrg.bottom(), this);
0483     m_toolbar->addWidget(m_audioMeterWidget);
0484     if (!m_audioMeterWidget->isValid) {
0485         KdenliveSettings::setMonitoraudio(0x01);
0486         m_audioMeterWidget->setVisibility(false);
0487     } else {
0488         m_audioMeterWidget->setVisibility((KdenliveSettings::monitoraudio() & m_id) != 0);
0489         if (id == Kdenlive::ProjectMonitor) {
0490             connect(m_audioMeterWidget, &MonitorAudioLevel::audioLevelsAvailable, pCore.get(), &Core::audioLevelsAvailable);
0491         }
0492     }
0493 
0494     // Trimming tool bar buttons
0495     m_trimmingbar = new QToolBar(this);
0496     m_trimmingbar->setIconSize(iconSize);
0497 
0498     m_trimmingOffset = new QLabel();
0499     m_trimmingbar->addWidget(m_trimmingOffset);
0500 
0501     m_fiveLess = new QAction(i18n("-5"), this);
0502     m_trimmingbar->addAction(m_fiveLess);
0503     connect(m_fiveLess, &QAction::triggered, this, [&]() {
0504         slotTrimmingPos(-5);
0505         pCore->window()->getCurrentTimeline()->model()->requestSlipSelection(-5, true);
0506     });
0507     m_oneLess = new QAction(i18n("-1"), this);
0508     m_trimmingbar->addAction(m_oneLess);
0509     connect(m_oneLess, &QAction::triggered, this, [&]() {
0510         slotTrimmingPos(-1);
0511         pCore->window()->getCurrentTimeline()->model()->requestSlipSelection(-1, true);
0512     });
0513     m_oneMore = new QAction(i18n("+1"), this);
0514     m_trimmingbar->addAction(m_oneMore);
0515     connect(m_oneMore, &QAction::triggered, this, [&]() {
0516         slotTrimmingPos(1);
0517         pCore->window()->getCurrentTimeline()->model()->requestSlipSelection(1, true);
0518     });
0519     m_fiveMore = new QAction(i18n("+5"), this);
0520     m_trimmingbar->addAction(m_fiveMore);
0521     connect(m_fiveMore, &QAction::triggered, this, [&]() {
0522         slotTrimmingPos(5);
0523         pCore->window()->getCurrentTimeline()->model()->requestSlipSelection(5, true);
0524     });
0525 
0526     connect(m_timePos, SIGNAL(timeCodeEditingFinished()), this, SLOT(slotSeek()));
0527     layout->addWidget(m_toolbar);
0528     if (m_recManager) {
0529         layout->addWidget(m_recManager->toolbar());
0530     }
0531     layout->addWidget(m_trimmingbar);
0532     m_trimmingbar->setVisible(false);
0533 
0534     // Load monitor overlay qml
0535     loadQmlScene(MonitorSceneDefault);
0536 
0537     // Monitor dropped fps timer
0538     m_droppedTimer.setInterval(1000);
0539     m_droppedTimer.setSingleShot(false);
0540     connect(&m_droppedTimer, &QTimer::timeout, this, &Monitor::checkDrops);
0541 
0542     // Info message widget
0543     m_infoMessage = new KMessageWidget(this);
0544     layout->addWidget(m_infoMessage);
0545     m_infoMessage->hide();
0546 }
0547 
0548 Monitor::~Monitor()
0549 {
0550     m_markerModel.reset();
0551     delete m_audioMeterWidget;
0552     delete m_glMonitor;
0553     delete m_glWidget;
0554     delete m_timePos;
0555 }
0556 
0557 void Monitor::setOffsetX(int x)
0558 {
0559     m_glMonitor->setOffsetX(x, m_horizontalScroll->maximum());
0560 }
0561 
0562 void Monitor::setOffsetY(int y)
0563 {
0564     m_glMonitor->setOffsetY(y, m_verticalScroll->maximum());
0565 }
0566 
0567 void Monitor::slotGetCurrentImage(bool request)
0568 {
0569     m_glMonitor->sendFrameForAnalysis = request;
0570     if (request) {
0571         slotActivateMonitor();
0572         refreshMonitor(true);
0573         // Update analysis state
0574         QTimer::singleShot(500, m_monitorManager, &MonitorManager::checkScopes);
0575     } else {
0576         m_glMonitor->releaseAnalyse();
0577     }
0578 }
0579 
0580 void Monitor::refreshIcons()
0581 {
0582     QList<QAction *> allMenus = this->findChildren<QAction *>();
0583     for (int i = 0; i < allMenus.count(); i++) {
0584         QAction *m = allMenus.at(i);
0585         QIcon ic = m->icon();
0586         if (ic.isNull() || ic.name().isEmpty()) {
0587             continue;
0588         }
0589         QIcon newIcon = QIcon::fromTheme(ic.name());
0590         m->setIcon(newIcon);
0591     }
0592     QList<KDualAction *> allButtons = this->findChildren<KDualAction *>();
0593     for (int i = 0; i < allButtons.count(); i++) {
0594         KDualAction *m = allButtons.at(i);
0595         QIcon ic = m->activeIcon();
0596         if (ic.isNull() || ic.name().isEmpty()) {
0597             continue;
0598         }
0599         QIcon newIcon = QIcon::fromTheme(ic.name());
0600         m->setActiveIcon(newIcon);
0601         ic = m->inactiveIcon();
0602         if (ic.isNull() || ic.name().isEmpty()) {
0603             continue;
0604         }
0605         newIcon = QIcon::fromTheme(ic.name());
0606         m->setInactiveIcon(newIcon);
0607     }
0608 }
0609 
0610 QAction *Monitor::recAction()
0611 {
0612     if (m_recManager) {
0613         return m_recManager->recAction();
0614     }
0615     return nullptr;
0616 }
0617 
0618 void Monitor::slotLockMonitor(bool lock)
0619 {
0620     m_monitorManager->lockMonitor(m_id, lock);
0621 }
0622 
0623 void Monitor::setupMenu(QMenu *goMenu, QMenu *overlayMenu, QAction *playZone, QAction *loopZone, QMenu *markerMenu, QAction *loopClip)
0624 {
0625     delete m_contextMenu;
0626     m_contextMenu = new QMenu(this);
0627     m_contextMenu->addMenu(m_playMenu);
0628     if (goMenu) {
0629         m_contextMenu->addMenu(goMenu);
0630     }
0631 
0632     if (markerMenu) {
0633         m_contextMenu->addMenu(markerMenu);
0634         QList<QAction *> list = markerMenu->actions();
0635         for (int i = 0; i < list.count(); ++i) {
0636             if (list.at(i)->objectName() == QLatin1String("edit_marker")) {
0637                 m_editMarker = list.at(i);
0638                 break;
0639             }
0640         }
0641     }
0642 
0643     m_playMenu->addAction(playZone);
0644     m_playMenu->addAction(loopZone);
0645     if (loopClip) {
0646         m_loopClipAction = loopClip;
0647         m_playMenu->addAction(loopClip);
0648     }
0649 
0650     m_contextMenu->addAction(m_markerMenu);
0651     if (m_id == Kdenlive::ClipMonitor) {
0652         // m_contextMenu->addAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save zone"), this, SLOT(slotSaveZone()));
0653         auto *extractZone = new QAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Extract Zone"), this);
0654         connect(extractZone, &QAction::triggered, this, &Monitor::slotExtractCurrentZone);
0655         m_configMenuAction->addAction(extractZone);
0656         m_contextMenu->addAction(extractZone);
0657         m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("insert_project_tree")));
0658     }
0659     m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("extract_frame")));
0660     m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("extract_frame_to_project")));
0661     m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("add_project_note")));
0662 
0663     m_contextMenu->addAction(m_markIn);
0664     m_contextMenu->addAction(m_markOut);
0665     QAction *setThumbFrame =
0666         m_contextMenu->addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Set current image as thumbnail"), this, SLOT(slotSetThumbFrame()));
0667     m_configMenuAction->addAction(setThumbFrame);
0668     if (m_id == Kdenlive::ProjectMonitor) {
0669         m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("monitor_multitrack")));
0670     } else if (m_id == Kdenlive::ClipMonitor) {
0671         QAction *alwaysShowAudio = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-show-audiothumb")), i18n("Always show audio thumbnails"), this);
0672         alwaysShowAudio->setCheckable(true);
0673         connect(alwaysShowAudio, &QAction::triggered, this, [this](bool checked) {
0674             KdenliveSettings::setAlwaysShowMonitorAudio(checked);
0675             m_glMonitor->rootObject()->setProperty("permanentAudiothumb", checked);
0676         });
0677         alwaysShowAudio->setChecked(KdenliveSettings::alwaysShowMonitorAudio());
0678         m_contextMenu->addAction(alwaysShowAudio);
0679         m_configMenuAction->addAction(alwaysShowAudio);
0680     }
0681 
0682     if (overlayMenu) {
0683         m_contextMenu->addMenu(overlayMenu);
0684     }
0685 
0686     m_configMenuAction->addAction(m_monitorManager->getAction("mlt_scrub"));
0687 
0688     QAction *switchAudioMonitor = new QAction(i18n("Show Audio Levels"), this);
0689     connect(switchAudioMonitor, &QAction::triggered, this, &Monitor::slotSwitchAudioMonitor);
0690     m_configMenuAction->addAction(switchAudioMonitor);
0691     switchAudioMonitor->setCheckable(true);
0692     switchAudioMonitor->setChecked((KdenliveSettings::monitoraudio() & m_id) != 0);
0693 
0694     if (m_id == Kdenlive::ClipMonitor) {
0695         QAction *recordTimecode = new QAction(i18n("Show Source Timecode"), this);
0696         recordTimecode->setCheckable(true);
0697         connect(recordTimecode, &QAction::triggered, this, &Monitor::slotSwitchRecTimecode);
0698         recordTimecode->setChecked(KdenliveSettings::rectimecode());
0699         m_configMenuAction->addAction(recordTimecode);
0700     }
0701 
0702     // For some reason, the frame in QAbstracSpinBox (base class of TimeCodeDisplay) needs to be displayed once, then hidden
0703     // or it will never appear (supposed to appear on hover).
0704     m_timePos->setFrame(false);
0705 }
0706 
0707 void Monitor::slotGoToMarker(QAction *action)
0708 {
0709     int pos = action->data().toInt();
0710     slotSeek(pos);
0711 }
0712 
0713 void Monitor::slotForceSize(QAction *a)
0714 {
0715     int resizeType = a->data().toInt();
0716     int profileWidth = 320;
0717     int profileHeight = 200;
0718     if (resizeType > 0) {
0719         // calculate size
0720         QRect r = QApplication::primaryScreen()->geometry();
0721         profileHeight = m_glMonitor->profileSize().height() * resizeType / 100;
0722         profileWidth = int(pCore->getCurrentProfile()->dar() * profileHeight);
0723         if (profileWidth > r.width() * 0.8 || profileHeight > r.height() * 0.7) {
0724             // reset action to free resize
0725             const QList<QAction *> list = m_forceSize->actions();
0726             for (QAction *ac : list) {
0727                 if (ac->data().toInt() == m_forceSizeFactor) {
0728                     m_forceSize->setCurrentAction(ac);
0729                     break;
0730                 }
0731             }
0732             warningMessage(i18n("Your screen resolution is not sufficient for this action"));
0733             return;
0734         }
0735     }
0736     switch (resizeType) {
0737     case 100:
0738     case 50:
0739         // resize full size
0740         setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
0741         profileHeight += m_glMonitor->m_displayRulerHeight;
0742         m_glMonitor->setMinimumSize(profileWidth, profileHeight);
0743         m_glMonitor->setMaximumSize(profileWidth, profileHeight);
0744         setMinimumSize(QSize(profileWidth, profileHeight + m_toolbar->height()));
0745         break;
0746     default:
0747         // Free resize
0748         m_glMonitor->setMinimumSize(profileWidth, profileHeight);
0749         m_glMonitor->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
0750         setMinimumSize(QSize(profileWidth, profileHeight + m_toolbar->height() + m_glMonitor->getControllerProxy()->rulerHeight()));
0751         setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
0752         break;
0753     }
0754     m_forceSizeFactor = resizeType;
0755     updateGeometry();
0756 }
0757 
0758 void Monitor::buildBackgroundedProducer(int pos)
0759 {
0760     if (m_controller == nullptr) {
0761         return;
0762     }
0763     if (KdenliveSettings::monitor_background() != "black") {
0764         Mlt::Tractor trac(pCore->getProjectProfile());
0765         QString color = QString("color:%1").arg(KdenliveSettings::monitor_background());
0766         std::shared_ptr<Mlt::Producer> bg(new Mlt::Producer(*trac.profile(), color.toUtf8().constData()));
0767         int maxLength = m_controller->originalProducer()->get_length();
0768         bg->set("length", maxLength);
0769         bg->set("out", maxLength - 1);
0770         bg->set("mlt_image_format", "rgba");
0771         trac.set_track(*bg.get(), 0);
0772         trac.set_track(*m_controller->originalProducer().get(), 1);
0773         QString composite = TransitionsRepository::get()->getCompositingTransition();
0774         std::unique_ptr<Mlt::Transition> transition = TransitionsRepository::get()->getTransition(composite);
0775         transition->set("always_active", 1);
0776         transition->set_tracks(0, 1);
0777         trac.plant_transition(*transition.get(), 0, 1);
0778         m_glMonitor->setProducer(std::make_shared<Mlt::Producer>(trac), isActive(), pos);
0779     } else {
0780         m_glMonitor->setProducer(m_controller->originalProducer(), isActive(), pos);
0781     }
0782 }
0783 
0784 void Monitor::updateMarkers()
0785 {
0786     if (m_markerMenu) {
0787         // Fill guide menu
0788         m_markerMenu->menu()->clear();
0789         std::shared_ptr<MarkerListModel> model;
0790         if (m_id == Kdenlive::ClipMonitor && m_controller) {
0791             model = m_controller->getMarkerModel();
0792         } else if (m_id == Kdenlive::ProjectMonitor && pCore->currentDoc()) {
0793             model = pCore->currentDoc()->getGuideModel(pCore->currentTimelineId());
0794         }
0795         if (model) {
0796             QList<CommentedTime> markersList = model->getAllMarkers();
0797             for (const CommentedTime &mkr : qAsConst(markersList)) {
0798                 QString label = pCore->timecode().getTimecode(mkr.time()) + QLatin1Char(' ') + mkr.comment();
0799                 QAction *a = new QAction(label);
0800                 a->setData(mkr.time().frames(pCore->getCurrentFps()));
0801                 m_markerMenu->addAction(a);
0802             }
0803         }
0804         m_markerMenu->setEnabled(!m_markerMenu->menu()->isEmpty());
0805     }
0806 }
0807 
0808 void Monitor::updateDocumentUuid()
0809 {
0810     m_glMonitor->rootObject()->setProperty("documentId", pCore->currentDoc()->uuid());
0811 }
0812 
0813 void Monitor::slotSeekToPreviousSnap()
0814 {
0815     if (m_controller) {
0816         m_glMonitor->getControllerProxy()->setPosition(getSnapForPos(true).frames(pCore->getCurrentFps()));
0817     }
0818 }
0819 
0820 void Monitor::slotSeekToNextSnap()
0821 {
0822     if (m_controller) {
0823         m_glMonitor->getControllerProxy()->setPosition(getSnapForPos(false).frames(pCore->getCurrentFps()));
0824     }
0825 }
0826 
0827 int Monitor::position()
0828 {
0829     return m_glMonitor->getControllerProxy()->getPosition();
0830 }
0831 
0832 GenTime Monitor::getSnapForPos(bool previous)
0833 {
0834     int frame = previous ? m_snaps->getPreviousPoint(m_glMonitor->getCurrentPos()) : m_snaps->getNextPoint(m_glMonitor->getCurrentPos());
0835     return {frame, pCore->getCurrentFps()};
0836 }
0837 
0838 void Monitor::slotLoadClipZone(const QPoint &zone)
0839 {
0840     m_glMonitor->getControllerProxy()->setZone(zone.x(), zone.y(), false);
0841     Q_EMIT zoneDurationChanged(zone.y() - zone.x());
0842     checkOverlay();
0843 }
0844 
0845 void Monitor::slotSetZoneStart()
0846 {
0847     QPoint oldZone = m_glMonitor->getControllerProxy()->zone();
0848     int currentIn = m_glMonitor->getCurrentPos();
0849     int updatedZoneOut = -1;
0850     if (currentIn > oldZone.y()) {
0851         updatedZoneOut = qMin(m_glMonitor->duration() - 1, currentIn + (oldZone.y() - oldZone.x()));
0852     }
0853 
0854     Fun undo_zone = [this, oldZone, updatedZoneOut]() {
0855         m_glMonitor->getControllerProxy()->setZoneIn(oldZone.x());
0856         if (updatedZoneOut > -1) {
0857             m_glMonitor->getControllerProxy()->setZoneOut(oldZone.y());
0858         }
0859         const QPoint zone = m_glMonitor->getControllerProxy()->zone();
0860         Q_EMIT zoneDurationChanged(zone.y() - zone.x());
0861         checkOverlay();
0862         return true;
0863     };
0864     Fun redo_zone = [this, currentIn, updatedZoneOut]() {
0865         if (updatedZoneOut > -1) {
0866             m_glMonitor->getControllerProxy()->setZoneOut(updatedZoneOut);
0867         }
0868         m_glMonitor->getControllerProxy()->setZoneIn(currentIn);
0869         const QPoint zone = m_glMonitor->getControllerProxy()->zone();
0870         Q_EMIT zoneDurationChanged(zone.y() - zone.x());
0871         checkOverlay();
0872         return true;
0873     };
0874     redo_zone();
0875     pCore->pushUndo(undo_zone, redo_zone, i18n("Set Zone"));
0876 }
0877 
0878 void Monitor::slotSetZoneEnd()
0879 {
0880     QPoint oldZone = m_glMonitor->getControllerProxy()->zone();
0881     int currentOut = m_glMonitor->getCurrentPos() + 1;
0882     int updatedZoneIn = -1;
0883     if (currentOut < oldZone.x()) {
0884         updatedZoneIn = qMax(0, currentOut - (oldZone.y() - oldZone.x()));
0885     }
0886     Fun undo_zone = [this, oldZone, updatedZoneIn]() {
0887         m_glMonitor->getControllerProxy()->setZoneOut(oldZone.y());
0888         if (updatedZoneIn > -1) {
0889             m_glMonitor->getControllerProxy()->setZoneIn(oldZone.x());
0890         }
0891         const QPoint zone = m_glMonitor->getControllerProxy()->zone();
0892         Q_EMIT zoneDurationChanged(zone.y() - zone.x());
0893         checkOverlay();
0894         return true;
0895     };
0896 
0897     Fun redo_zone = [this, currentOut, updatedZoneIn]() {
0898         if (updatedZoneIn > -1) {
0899             m_glMonitor->getControllerProxy()->setZoneIn(updatedZoneIn);
0900         }
0901         m_glMonitor->getControllerProxy()->setZoneOut(currentOut);
0902         const QPoint zone = m_glMonitor->getControllerProxy()->zone();
0903         Q_EMIT zoneDurationChanged(zone.y() - zone.x());
0904         checkOverlay();
0905         return true;
0906     };
0907     redo_zone();
0908     pCore->pushUndo(undo_zone, redo_zone, i18n("Set Zone"));
0909 }
0910 
0911 // virtual
0912 void Monitor::mousePressEvent(QMouseEvent *event)
0913 {
0914     m_monitorManager->activateMonitor(m_id);
0915     if ((event->button() & Qt::RightButton) == 0u) {
0916         if (m_glWidget->geometry().contains(event->pos())) {
0917             m_DragStartPosition = event->pos();
0918             event->accept();
0919         }
0920     } else if (m_contextMenu) {
0921         slotActivateMonitor();
0922 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0923         m_contextMenu->popup(event->globalPos());
0924 #else
0925         m_contextMenu->popup(event->globalPosition().toPoint());
0926 #endif
0927         event->accept();
0928     }
0929     QWidget::mousePressEvent(event);
0930 }
0931 
0932 void Monitor::slotShowMenu(const QPoint pos)
0933 {
0934     slotActivateMonitor();
0935     if (m_contextMenu) {
0936         updateMarkers();
0937         m_contextMenu->popup(pos);
0938     }
0939 }
0940 
0941 void Monitor::resizeEvent(QResizeEvent *event)
0942 {
0943     Q_UNUSED(event)
0944     if (m_glMonitor->zoom() > 0.0f) {
0945         float horizontal = float(m_horizontalScroll->value()) / float(m_horizontalScroll->maximum());
0946         float vertical = float(m_verticalScroll->value()) / float(m_verticalScroll->maximum());
0947         adjustScrollBars(horizontal, vertical);
0948     } else {
0949         m_horizontalScroll->hide();
0950         m_verticalScroll->hide();
0951     }
0952 }
0953 
0954 void Monitor::adjustScrollBars(float horizontal, float vertical)
0955 {
0956     if (m_glMonitor->zoom() > 1.0f) {
0957         m_horizontalScroll->setPageStep(m_glWidget->width());
0958         m_horizontalScroll->setMaximum(int(m_glWidget->width() * m_glMonitor->zoom()));
0959         m_horizontalScroll->setValue(qRound(horizontal * float(m_horizontalScroll->maximum())));
0960         Q_EMIT m_horizontalScroll->valueChanged(m_horizontalScroll->value());
0961         m_horizontalScroll->show();
0962     } else {
0963         Q_EMIT m_horizontalScroll->valueChanged(int(0.5f * m_glWidget->width() * m_glMonitor->zoom()));
0964         m_horizontalScroll->hide();
0965     }
0966 
0967     if (m_glMonitor->zoom() > 1.0f) {
0968         m_verticalScroll->setPageStep(m_glWidget->height());
0969         m_verticalScroll->setMaximum(int(m_glWidget->height() * m_glMonitor->zoom()));
0970         m_verticalScroll->setValue(int(m_verticalScroll->maximum() * vertical));
0971         Q_EMIT m_verticalScroll->valueChanged(m_verticalScroll->value());
0972         m_verticalScroll->show();
0973     } else {
0974         Q_EMIT m_verticalScroll->valueChanged(int(0.5f * m_glWidget->height() * m_glMonitor->zoom()));
0975         m_verticalScroll->hide();
0976     }
0977 }
0978 
0979 void Monitor::setZoom(float zoomRatio)
0980 {
0981     if (qFuzzyCompare(m_glMonitor->zoom(), 1.0f)) {
0982         adjustScrollBars(1., 1.);
0983     } else if (qFuzzyCompare(m_glMonitor->zoom() / zoomRatio, 1.0f)) {
0984         adjustScrollBars(0.5f, 0.5f);
0985     } else {
0986         float horizontal = float(m_horizontalScroll->value()) / float(m_horizontalScroll->maximum());
0987         float vertical = float(m_verticalScroll->value()) / float(m_verticalScroll->maximum());
0988         adjustScrollBars(horizontal, vertical);
0989     }
0990 }
0991 
0992 bool Monitor::monitorIsFullScreen() const
0993 {
0994     return m_glWidget->isFullScreen();
0995 }
0996 
0997 void Monitor::slotSwitchFullScreen(bool minimizeOnly)
0998 {
0999     // TODO: disable screensaver?
1000     m_glMonitor->refreshZoom = true;
1001     if (!m_glWidget->isFullScreen() && !minimizeOnly) {
1002         // Move monitor widget to the second screen (one screen for Kdenlive, the other one for the Monitor widget)
1003         if (qApp->screens().count() > 1) {
1004             bool screenFound = false;
1005             int ix = -1;
1006             if (!KdenliveSettings::fullscreen_monitor().isEmpty()) {
1007                 // If the platform does now provide screen serial number, use indexes
1008                 for (const QScreen *screen : qApp->screens()) {
1009                     ix++;
1010                     bool match = KdenliveSettings::fullscreen_monitor() == QString("%1:%2").arg(QString::number(ix), screen->serialNumber());
1011                     // Check if monitor's index changed
1012                     if (!match && !screen->serialNumber().isEmpty()) {
1013                         match = KdenliveSettings::fullscreen_monitor().section(QLatin1Char(':'), 1) == screen->serialNumber();
1014                     }
1015                     if (match) {
1016                         // Match
1017                         m_glWidget->setParent(nullptr);
1018                         m_glWidget->move(screen->geometry().topLeft());
1019                         m_glWidget->resize(screen->geometry().size());
1020                         screenFound = true;
1021                         break;
1022                     }
1023                 }
1024             }
1025             if (!screenFound) {
1026                 for (const QScreen *screen : qApp->screens()) {
1027                     // Autodetect second monitor
1028                     QRect screenRect = screen->geometry();
1029                     if (!screenRect.contains(pCore->window()->geometry().center())) {
1030                         m_glWidget->setParent(nullptr);
1031                         m_glWidget->move(screenRect.topLeft());
1032                         m_glWidget->resize(screenRect.size());
1033                         screenFound = true;
1034                         break;
1035                     }
1036                 }
1037             }
1038             if (!screenFound) {
1039                 m_glWidget->setParent(nullptr);
1040             }
1041         } else {
1042             m_glWidget->setParent(nullptr);
1043         }
1044         m_glWidget->showFullScreen();
1045         setFocus();
1046     } else {
1047         m_glWidget->showNormal();
1048         auto *lay = static_cast<QVBoxLayout *>(layout());
1049         lay->insertWidget(0, m_glWidget, 10);
1050         // With some Qt versions, focus was lost after switching back from fullscreen,
1051         // QApplication::setActiveWindow restores focus to the correct window
1052         QApplication::setActiveWindow(this);
1053         setFocus();
1054     }
1055 }
1056 
1057 void Monitor::fixFocus()
1058 {
1059     setFocus();
1060 }
1061 
1062 // virtual
1063 void Monitor::mouseReleaseEvent(QMouseEvent *event)
1064 {
1065     if (m_dragStarted) {
1066         event->ignore();
1067         QWidget::mouseReleaseEvent(event);
1068         return;
1069     }
1070     if (event->button() != Qt::RightButton) {
1071         if (m_glMonitor->geometry().contains(event->pos())) {
1072             if (isActive()) {
1073                 slotPlay();
1074             } else {
1075                 slotActivateMonitor();
1076             }
1077         } // else event->ignore(); //QWidget::mouseReleaseEvent(event);
1078     }
1079     m_dragStarted = false;
1080     event->accept();
1081     QWidget::mouseReleaseEvent(event);
1082 }
1083 
1084 void Monitor::slotStartDrag()
1085 {
1086     if (m_id == Kdenlive::ProjectMonitor || m_controller == nullptr) {
1087         // dragging is only allowed for clip monitor
1088         return;
1089     }
1090     auto *drag = new QDrag(this);
1091     auto *mimeData = new QMimeData;
1092     QByteArray prodData;
1093     QPoint p = m_glMonitor->getControllerProxy()->zone();
1094     if (p.x() == -1 || p.y() == -1) {
1095         prodData = m_controller->AbstractProjectItem::clipId().toUtf8();
1096     } else {
1097         QStringList list;
1098         list.append(m_controller->AbstractProjectItem::clipId());
1099         list.append(QString::number(p.x()));
1100         list.append(QString::number(p.y() - 1));
1101         prodData.append(list.join(QLatin1Char('/')).toUtf8());
1102     }
1103     mimeData->setData(QStringLiteral("text/producerslist"), prodData);
1104     mimeData->setData(QStringLiteral("text/dragid"), QUuid::createUuid().toByteArray());
1105     drag->setMimeData(mimeData);
1106     drag->exec(Qt::CopyAction);
1107     Q_EMIT pCore->bin()->processDragEnd();
1108 }
1109 
1110 // virtual
1111 void Monitor::wheelEvent(QWheelEvent *event)
1112 {
1113     slotMouseSeek(event->angleDelta().y(), event->modifiers());
1114     event->accept();
1115 }
1116 
1117 void Monitor::mouseDoubleClickEvent(QMouseEvent *event)
1118 {
1119     event->accept();
1120     slotSwitchFullScreen();
1121 }
1122 
1123 void Monitor::keyPressEvent(QKeyEvent *event)
1124 {
1125     if (event->key() == Qt::Key_Escape) {
1126         slotSwitchFullScreen();
1127         event->accept();
1128         return;
1129     }
1130     if (m_glWidget->isFullScreen()) {
1131         event->ignore();
1132         Q_EMIT passKeyPress(event);
1133         return;
1134     }
1135     QWidget::keyPressEvent(event);
1136 }
1137 
1138 void Monitor::slotMouseSeek(int eventDelta, uint modifiers)
1139 {
1140     if ((modifiers & Qt::ControlModifier) != 0u) {
1141         // Ctrl wheel zooms monitor
1142         m_glMonitor->slotZoom(eventDelta > 0);
1143         return;
1144     } else if ((modifiers & Qt::ShiftModifier) != 0u) {
1145         // Shift wheel seeks one second
1146         int delta = qRound(pCore->getCurrentFps());
1147         if (eventDelta > 0) {
1148             delta = -delta;
1149         }
1150         delta = qBound(0, m_glMonitor->getCurrentPos() + delta, m_glMonitor->duration() - 1);
1151         m_glMonitor->getControllerProxy()->setPosition(delta);
1152     } else if ((modifiers & Qt::AltModifier) != 0u) {
1153         if (eventDelta >= 0) {
1154             Q_EMIT seekToPreviousSnap();
1155         } else {
1156             Q_EMIT seekToNextSnap();
1157         }
1158     } else {
1159         if (eventDelta >= 0) {
1160             slotRewindOneFrame();
1161         } else {
1162             slotForwardOneFrame();
1163         }
1164     }
1165 }
1166 
1167 void Monitor::slotSetThumbFrame()
1168 {
1169     pCore->setDocumentModified();
1170     if (m_controller == nullptr || m_controller->clipType() == ClipType::Timeline) {
1171         // This is a sequence thumbnail
1172         pCore->bin()->setSequenceThumbnail(pCore->currentTimelineId(), m_glMonitor->getCurrentPos());
1173         return;
1174     }
1175     m_controller->setProducerProperty(QStringLiteral("kdenlive:thumbnailFrame"), m_glMonitor->getCurrentPos());
1176     Q_EMIT refreshClipThumbnail(m_controller->AbstractProjectItem::clipId());
1177 }
1178 
1179 void Monitor::slotExtractCurrentZone()
1180 {
1181     if (m_controller == nullptr) {
1182         return;
1183     }
1184     CutTask::start(ObjectId(KdenliveObjectType::BinClip, m_controller->clipId().toInt(), QUuid()), getZoneStart(), getZoneEnd(), this);
1185 }
1186 
1187 std::shared_ptr<ProjectClip> Monitor::currentController() const
1188 {
1189     return m_controller;
1190 }
1191 
1192 void Monitor::slotExtractCurrentFrame(QString frameName, bool addToProject)
1193 {
1194     if (m_playAction->isActive()) {
1195         // Pause playing
1196         switchPlay(false);
1197     }
1198     if (QFileInfo(frameName).fileName().isEmpty()) {
1199         // convenience: when extracting an image to be added to the project,
1200         // suggest a suitable image file name. In the project monitor, this
1201         // suggestion bases on the project file name; in the clip monitor,
1202         // the suggestion bases on the clip file name currently shown.
1203         // Finally, the frame number is added to this suggestion, prefixed
1204         // with "-f", so we get something like clip-f#.png.
1205         QString suggestedImageName =
1206             QFileInfo(currentController() ? currentController()->clipName()
1207                                           : pCore->currentDoc()->url().isValid() ? pCore->currentDoc()->url().fileName() : i18n("untitled"))
1208                 .completeBaseName() +
1209             QStringLiteral("-f") + QString::number(m_glMonitor->getCurrentPos()).rightJustified(6, QLatin1Char('0')) + QStringLiteral(".png");
1210         frameName = QFileInfo(frameName, suggestedImageName).fileName();
1211     }
1212 
1213     QString framesFolder = KRecentDirs::dir(QStringLiteral(":KdenliveFramesFolder"));
1214     if (framesFolder.isEmpty()) {
1215         framesFolder = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
1216     }
1217     QScopedPointer<QDialog> dlg(new QDialog(this));
1218     QScopedPointer<KFileWidget> fileWidget(new KFileWidget(QUrl::fromLocalFile(framesFolder), dlg.data()));
1219     dlg->setWindowTitle(addToProject ? i18nc("@title:window", "Save Image to Project") : i18nc("@title:window", "Save Image"));
1220     auto *layout = new QVBoxLayout;
1221     layout->addWidget(fileWidget.data());
1222     QCheckBox *b = nullptr;
1223     if (m_id == Kdenlive::ClipMonitor && m_controller && m_controller->clipType() != ClipType::Text) {
1224         QSize fSize = m_controller->getFrameSize();
1225         if (fSize != pCore->getCurrentFrameSize()) {
1226             b = new QCheckBox(i18n("Export image using source resolution"), dlg.data());
1227             b->setChecked(KdenliveSettings::exportframe_usingsourceres());
1228             fileWidget->setCustomWidget(b);
1229         }
1230     }
1231     fileWidget->setConfirmOverwrite(true);
1232     fileWidget->okButton()->show();
1233     fileWidget->cancelButton()->show();
1234     QObject::connect(fileWidget->okButton(), &QPushButton::clicked, fileWidget.data(), &KFileWidget::slotOk);
1235     QObject::connect(fileWidget.data(), &KFileWidget::accepted, fileWidget.data(), &KFileWidget::accept);
1236     QObject::connect(fileWidget.data(), &KFileWidget::accepted, dlg.data(), &QDialog::accept);
1237     QObject::connect(fileWidget->cancelButton(), &QPushButton::clicked, dlg.data(), &QDialog::reject);
1238     dlg->setLayout(layout);
1239 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1240     fileWidget->setMimeFilter(QStringList() << QStringLiteral("image/png"));
1241 #else
1242     fileWidget->setFilters({KFileFilter::fromMimeType(QStringLiteral("image/png"))});
1243 #endif
1244     fileWidget->setMode(KFile::File | KFile::LocalOnly);
1245     fileWidget->setOperationMode(KFileWidget::Saving);
1246     QUrl relativeUrl;
1247     relativeUrl.setPath(frameName);
1248     fileWidget->setSelectedUrl(relativeUrl);
1249     KSharedConfig::Ptr conf = KSharedConfig::openConfig();
1250     QWindow *handle = dlg->windowHandle();
1251     if ((handle != nullptr) && conf->hasGroup("FileDialogSize")) {
1252         KWindowConfig::restoreWindowSize(handle, conf->group("FileDialogSize"));
1253         dlg->resize(handle->size());
1254     }
1255     if (dlg->exec() == QDialog::Accepted) {
1256         QString selectedFile = fileWidget->selectedFile();
1257         bool useSourceResolution = b != nullptr && b->isChecked();
1258         if (!selectedFile.isEmpty()) {
1259             if (b != nullptr) {
1260                 KdenliveSettings::setExportframe_usingsourceres(useSourceResolution);
1261             }
1262             KRecentDirs::add(QStringLiteral(":KdenliveFramesFolder"), QUrl::fromLocalFile(selectedFile).adjusted(QUrl::RemoveFilename).toLocalFile());
1263             // check if we are using a proxy
1264             if ((m_controller != nullptr) && !m_controller->getProducerProperty(QStringLiteral("kdenlive:proxy")).isEmpty() &&
1265                 m_controller->getProducerProperty(QStringLiteral("kdenlive:proxy")) != QLatin1String("-")) {
1266                 // Clip monitor, using proxy. Use original clip url to get frame
1267                 QTemporaryFile src(QDir::temp().absoluteFilePath(QString("XXXXXX.mlt")));
1268                 if (src.open()) {
1269                     src.setAutoRemove(false);
1270                     m_controller->cloneProducerToFile(src.fileName());
1271                     const QStringList pathInfo = {src.fileName(), selectedFile, pCore->bin()->getCurrentFolder()};
1272 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1273                     QtConcurrent::run(m_glMonitor->getControllerProxy(), &MonitorProxy::extractFrameToFile, m_glMonitor->getCurrentPos(), pathInfo,
1274                                       addToProject, useSourceResolution);
1275 #else
1276                     QtConcurrent::run(&MonitorProxy::extractFrameToFile, m_glMonitor->getControllerProxy(), m_glMonitor->getCurrentPos(), pathInfo,
1277                                       addToProject, useSourceResolution);
1278 #endif
1279                 } else {
1280                     // TODO: warn user, cannot open tmp file
1281                     qDebug() << "Could not create temporary file";
1282                 }
1283                 return;
1284             } else {
1285                 if (m_id == Kdenlive::ProjectMonitor) {
1286                     // Create QImage with frame
1287                     QImage frame;
1288                     // Disable monitor preview scaling if any
1289                     int previewScale = KdenliveSettings::previewScaling();
1290                     if (previewScale > 0) {
1291                         KdenliveSettings::setPreviewScaling(0);
1292                         m_glMonitor->updateScaling();
1293                     }
1294                     // Check if we have proxied clips at position
1295                     QStringList proxiedClips = pCore->window()->getCurrentTimeline()->model()->getProxiesAt(m_glMonitor->getCurrentPos());
1296                     // Temporarily disable proxy on those clips
1297                     QMap<QString, QString> existingProxies;
1298                     if (!proxiedClips.isEmpty()) {
1299                         existingProxies = pCore->currentDoc()->proxyClipsById(proxiedClips, false);
1300                     }
1301                     disconnect(m_glMonitor, &VideoWidget::analyseFrame, this, &Monitor::frameUpdated);
1302                     bool analysisStatus = m_glMonitor->sendFrameForAnalysis;
1303                     m_glMonitor->sendFrameForAnalysis = true;
1304                     if (m_captureConnection) {
1305                         QObject::disconnect(m_captureConnection);
1306                     }
1307                     m_captureConnection =
1308                         connect(m_glMonitor, &VideoWidget::analyseFrame, this,
1309                                 [this, proxiedClips, selectedFile, existingProxies, addToProject, analysisStatus, previewScale](const QImage &img) {
1310                                     m_glMonitor->sendFrameForAnalysis = analysisStatus;
1311                                     m_glMonitor->releaseAnalyse();
1312                                     if (pCore->getCurrentSar() != 1.) {
1313                                         QImage scaled = img.scaled(pCore->getCurrentFrameDisplaySize());
1314                                         scaled.save(selectedFile);
1315                                     } else {
1316                                         img.save(selectedFile);
1317                                     }
1318                                     if (previewScale > 0) {
1319                                         KdenliveSettings::setPreviewScaling(previewScale);
1320                                         m_glMonitor->updateScaling();
1321                                     }
1322                                     // Re-enable proxy on those clips
1323                                     if (!proxiedClips.isEmpty()) {
1324                                         pCore->currentDoc()->proxyClipsById(proxiedClips, true, existingProxies);
1325                                     }
1326                                     QObject::disconnect(m_captureConnection);
1327                                     connect(m_glMonitor, &VideoWidget::analyseFrame, this, &Monitor::frameUpdated);
1328                                     KRecentDirs::add(QStringLiteral(":KdenliveFramesFolder"),
1329                                                      QUrl::fromLocalFile(selectedFile).adjusted(QUrl::RemoveFilename).toLocalFile());
1330                                     if (addToProject) {
1331                                         QString folderInfo = pCore->bin()->getCurrentFolder();
1332                                         QMetaObject::invokeMethod(pCore->bin(), "droppedUrls", Qt::QueuedConnection,
1333                                                                   Q_ARG(QList<QUrl>, {QUrl::fromLocalFile(selectedFile)}), Q_ARG(QString, folderInfo));
1334                                     }
1335                                 });
1336                     if (proxiedClips.isEmpty()) {
1337                         // If there is a proxy, replacing it in timeline will trigger the monitor once replaced
1338                         refreshMonitor();
1339                     }
1340                     return;
1341                 } else {
1342                     QStringList pathInfo;
1343                     if (useSourceResolution) {
1344                         // Create a producer with the original clip
1345                         QTemporaryFile src(QDir::temp().absoluteFilePath(QString("XXXXXX.mlt")));
1346                         if (src.open()) {
1347                             src.setAutoRemove(false);
1348                             m_controller->cloneProducerToFile(src.fileName());
1349                             pathInfo = QStringList({src.fileName(), selectedFile, pCore->bin()->getCurrentFolder()});
1350                         }
1351                     } else {
1352                         pathInfo = QStringList({QString(), selectedFile, pCore->bin()->getCurrentFolder()});
1353                     }
1354 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1355                     QtConcurrent::run(m_glMonitor->getControllerProxy(), &MonitorProxy::extractFrameToFile, m_glMonitor->getCurrentPos(), pathInfo,
1356                                       addToProject, useSourceResolution);
1357 #else
1358                     QtConcurrent::run(&MonitorProxy::extractFrameToFile, m_glMonitor->getControllerProxy(), m_glMonitor->getCurrentPos(), pathInfo,
1359                                       addToProject, useSourceResolution);
1360 #endif
1361                 }
1362             }
1363         }
1364     }
1365 }
1366 
1367 void Monitor::setTimePos(const QString &pos)
1368 {
1369     m_timePos->setValue(pos);
1370     slotSeek();
1371 }
1372 
1373 void Monitor::slotSeek()
1374 {
1375     slotSeek(m_timePos->getValue());
1376 }
1377 
1378 void Monitor::slotSeek(int pos)
1379 {
1380     if (!slotActivateMonitor()) {
1381         return;
1382     }
1383     m_glMonitor->getControllerProxy()->setPosition(pos);
1384     Q_EMIT m_monitorManager->cleanMixer();
1385 }
1386 
1387 void Monitor::refreshAudioThumbs()
1388 {
1389     Q_EMIT m_glMonitor->getControllerProxy()->audioThumbFormatChanged();
1390     Q_EMIT m_glMonitor->getControllerProxy()->colorsChanged();
1391 }
1392 
1393 void Monitor::normalizeAudioThumbs()
1394 {
1395     Q_EMIT m_glMonitor->getControllerProxy()->audioThumbNormalizeChanged();
1396 }
1397 
1398 void Monitor::checkOverlay(int pos)
1399 {
1400     if (m_qmlManager->sceneType() != MonitorSceneDefault) {
1401         // we are not in main view, ignore
1402         return;
1403     }
1404     QString overlayText;
1405     QColor color;
1406     if (pos == -1) {
1407         pos = m_timePos->getValue();
1408     }
1409 
1410     if (m_markerModel) {
1411         int mid = m_markerModel->markerIdAtFrame(pos);
1412         if (mid > -1) {
1413             CommentedTime marker = m_markerModel->markerById(mid);
1414             overlayText = marker.comment();
1415             color = pCore->markerTypes.value(marker.markerType()).color;
1416         }
1417     }
1418     m_glMonitor->getControllerProxy()->setMarker(overlayText, color);
1419 }
1420 
1421 int Monitor::getZoneStart()
1422 {
1423     return m_glMonitor->getControllerProxy()->zoneIn();
1424 }
1425 
1426 int Monitor::getZoneEnd()
1427 {
1428     return m_glMonitor->getControllerProxy()->zoneOut();
1429 }
1430 
1431 void Monitor::slotZoneStart()
1432 {
1433     if (!slotActivateMonitor()) {
1434         return;
1435     }
1436     m_glMonitor->getControllerProxy()->setPosition(m_glMonitor->getControllerProxy()->zoneIn());
1437 }
1438 
1439 void Monitor::slotZoneEnd()
1440 {
1441     if (!slotActivateMonitor()) {
1442         return;
1443     }
1444     m_glMonitor->getControllerProxy()->setPosition(m_glMonitor->getControllerProxy()->zoneOut());
1445 }
1446 
1447 void Monitor::slotRewind(double speed)
1448 {
1449     if (!slotActivateMonitor() || m_trimmingbar->isVisible()) {
1450         return;
1451     }
1452     if (qFuzzyIsNull(speed)) {
1453         double currentspeed = m_glMonitor->playSpeed();
1454         if (currentspeed > -1) {
1455             m_glMonitor->purgeCache();
1456             speed = -1;
1457             m_speedIndex = 0;
1458         } else {
1459             m_speedIndex++;
1460             if (m_speedIndex > 5) {
1461                 m_speedIndex = 0;
1462             }
1463             speed = -MonitorManager::speedArray[m_speedIndex];
1464         }
1465     }
1466     updatePlayAction(true);
1467     m_glMonitor->switchPlay(true, speed);
1468 }
1469 
1470 void Monitor::slotForward(double speed, bool allowNormalPlay)
1471 {
1472     if (!slotActivateMonitor() || m_trimmingbar->isVisible()) {
1473         return;
1474     }
1475     if (qFuzzyIsNull(speed)) {
1476         double currentspeed = m_glMonitor->playSpeed();
1477         if (currentspeed < 1) {
1478             m_speedIndex = 0;
1479             if (allowNormalPlay) {
1480                 m_glMonitor->purgeCache();
1481                 updatePlayAction(true);
1482                 m_glMonitor->switchPlay(true);
1483                 return;
1484             }
1485         } else {
1486             m_speedIndex++;
1487         }
1488         if (m_speedIndex > 5) {
1489             m_speedIndex = 0;
1490         }
1491         speed = MonitorManager::speedArray[m_speedIndex];
1492     }
1493     updatePlayAction(true);
1494     m_glMonitor->switchPlay(true, speed);
1495 }
1496 
1497 void Monitor::slotRewindOneFrame(int diff)
1498 {
1499     if (!slotActivateMonitor()) {
1500         return;
1501     }
1502     m_glMonitor->getControllerProxy()->setPosition(qMax(0, m_glMonitor->getCurrentPos() - diff));
1503 }
1504 
1505 void Monitor::slotForwardOneFrame(int diff)
1506 {
1507     if (!slotActivateMonitor()) {
1508         return;
1509     }
1510     if (m_id == Kdenlive::ClipMonitor) {
1511         m_glMonitor->getControllerProxy()->setPosition(qMin(m_glMonitor->duration() - 1, m_glMonitor->getCurrentPos() + diff));
1512     } else {
1513         m_glMonitor->getControllerProxy()->setPosition(m_glMonitor->getCurrentPos() + diff);
1514     }
1515 }
1516 
1517 void Monitor::adjustRulerSize(int length, const std::shared_ptr<MarkerSortModel> &markerModel)
1518 {
1519     if (m_controller != nullptr) {
1520         m_glMonitor->setRulerInfo(length);
1521     } else {
1522         m_glMonitor->setRulerInfo(length, markerModel);
1523     }
1524     m_timePos->setRange(0, length);
1525 
1526     if (markerModel) {
1527         QAbstractItemModel *sourceModel = markerModel->sourceModel();
1528         connect(sourceModel, SIGNAL(dataChanged(QModelIndex, QModelIndex, QVector<int>)), this, SLOT(checkOverlay()));
1529         connect(sourceModel, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(checkOverlay()));
1530         connect(sourceModel, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(checkOverlay()));
1531     } else {
1532         // Project simply changed length, update display
1533         Q_EMIT durationChanged(length);
1534     }
1535 }
1536 
1537 void Monitor::stop()
1538 {
1539     updatePlayAction(false);
1540     m_glMonitor->stop();
1541 }
1542 
1543 void Monitor::mute(bool mute)
1544 {
1545     // TODO: we should set the "audio_off" property to 1 to mute the consumer instead of changing volume
1546     m_glMonitor->setVolume(mute ? 0 : KdenliveSettings::volume() / 100.0);
1547 }
1548 
1549 void Monitor::start()
1550 {
1551     if (!isVisible() || !isActive()) {
1552         return;
1553     }
1554     m_glMonitor->startConsumer();
1555 }
1556 
1557 void Monitor::slotRefreshMonitor(bool visible)
1558 {
1559     if (visible && monitorVisible()) {
1560         if (slotActivateMonitor()) {
1561             start();
1562         }
1563     }
1564 }
1565 
1566 void Monitor::forceMonitorRefresh()
1567 {
1568     if (!slotActivateMonitor()) {
1569         return;
1570     }
1571     m_glMonitor->refresh();
1572 }
1573 
1574 void Monitor::refreshMonitor(bool directUpdate)
1575 {
1576     if (!m_glMonitor->isReady() || isPlaying()) {
1577         return;
1578     }
1579     if (isActive()) {
1580         if (directUpdate) {
1581             m_glMonitor->refresh();
1582         } else {
1583             m_glMonitor->requestRefresh();
1584         }
1585     } else if (monitorVisible()) {
1586         // Monitor was not active. Check if the other one is visible to re-activate it afterwards
1587         bool otherMonitorVisible = m_id == Kdenlive::ClipMonitor ? m_monitorManager->projectMonitorVisible() : m_monitorManager->clipMonitorVisible();
1588         slotActivateMonitor();
1589         if (isActive()) {
1590             m_glMonitor->refresh();
1591             // Monitor was not active, so we activate it, refresh and activate the other monitor once done
1592             QObject::disconnect(m_switchConnection);
1593             m_switchConnection = connect(m_glMonitor, &VideoWidget::frameDisplayed, this, [=]() {
1594                 m_monitorManager->activateMonitor(m_id == Kdenlive::ClipMonitor ? Kdenlive::ProjectMonitor : Kdenlive::ClipMonitor, otherMonitorVisible);
1595                 QObject::disconnect(m_switchConnection);
1596             });
1597         }
1598     }
1599 }
1600 
1601 bool Monitor::monitorVisible() const
1602 {
1603     return m_glWidget->isFullScreen() || !m_glWidget->visibleRegion().isEmpty();
1604 }
1605 
1606 void Monitor::refreshMonitorIfActive(bool directUpdate)
1607 {
1608     if (!m_glMonitor->isReady() || !isActive()) {
1609         return;
1610     }
1611     if (directUpdate) {
1612         m_glMonitor->refresh();
1613     } else {
1614         m_glMonitor->requestRefresh();
1615     }
1616 }
1617 
1618 void Monitor::pause()
1619 {
1620     if (!m_playAction->isActive() || !slotActivateMonitor()) {
1621         return;
1622     }
1623     switchPlay(false);
1624 }
1625 
1626 void Monitor::switchPlay(bool play)
1627 {
1628     if (m_trimmingbar->isVisible()) {
1629         return;
1630     }
1631     m_speedIndex = 0;
1632     if (!play) {
1633         m_droppedTimer.stop();
1634     }
1635     if (!KdenliveSettings::autoscroll()) {
1636         Q_EMIT pCore->autoScrollChanged();
1637     }
1638     if (!m_glMonitor->switchPlay(play)) {
1639         play = false;
1640     }
1641     m_playAction->setActive(play);
1642 }
1643 
1644 void Monitor::updatePlayAction(bool play)
1645 {
1646     m_playAction->setActive(play);
1647     if (!play) {
1648         m_droppedTimer.stop();
1649     }
1650     if (!KdenliveSettings::autoscroll()) {
1651         Q_EMIT pCore->autoScrollChanged();
1652     }
1653 }
1654 
1655 void Monitor::slotSwitchPlay()
1656 {
1657     if (!slotActivateMonitor() || m_trimmingbar->isVisible()) {
1658         return;
1659     }
1660     if (!KdenliveSettings::autoscroll()) {
1661         Q_EMIT pCore->autoScrollChanged();
1662     }
1663     m_speedIndex = 0;
1664     bool play = m_playAction->isActive();
1665     if (pCore->getAudioDevice()->isRecording()) {
1666         int recState = pCore->getAudioDevice()->recordState();
1667         if (recState == QMediaRecorder::RecordingState) {
1668             if (!play) {
1669                 pCore->getAudioDevice()->pauseRecording();
1670             }
1671         } else if (recState == QMediaRecorder::PausedState && play) {
1672             pCore->getAudioDevice()->resumeRecording();
1673         }
1674         m_displayingCountdown = true;
1675     } else if (pCore->getAudioDevice()->isMonitoring()) {
1676         if (m_displayingCountdown || KdenliveSettings::disablereccountdown()) {
1677             m_displayingCountdown = false;
1678             m_playAction->setActive(false);
1679             pCore->recordAudio(-1, true);
1680             return;
1681         }
1682         pCore->recordAudio(-1, true);
1683     }
1684     if (!m_glMonitor->switchPlay(play)) {
1685         play = false;
1686         m_playAction->setActive(false);
1687     }
1688     bool showDropped = false;
1689     if (m_id == Kdenlive::ClipMonitor) {
1690         showDropped = KdenliveSettings::displayClipMonitorInfo() & 0x20;
1691     } else if (m_id == Kdenlive::ProjectMonitor) {
1692         showDropped = KdenliveSettings::displayProjectMonitorInfo() & 0x20;
1693     }
1694     if (showDropped) {
1695         m_glMonitor->resetDrops();
1696         if (play) {
1697             m_droppedTimer.start();
1698         } else {
1699             m_droppedTimer.stop();
1700         }
1701     } else {
1702         m_droppedTimer.stop();
1703     }
1704 }
1705 
1706 void Monitor::slotPlay()
1707 {
1708     m_playAction->trigger();
1709 }
1710 
1711 bool Monitor::isPlaying() const
1712 {
1713     return m_playAction->isActive();
1714 }
1715 
1716 void Monitor::resetPlayOrLoopZone(const QString &binId)
1717 {
1718     if (activeClipId() == binId) {
1719         m_glMonitor->resetZoneMode();
1720     }
1721 }
1722 
1723 void Monitor::slotPlayZone()
1724 {
1725     if (!slotActivateMonitor()) {
1726         return;
1727     }
1728     bool ok = m_glMonitor->playZone();
1729     if (ok) {
1730         updatePlayAction(true);
1731     }
1732 }
1733 
1734 void Monitor::slotLoopZone()
1735 {
1736     if (!slotActivateMonitor()) {
1737         return;
1738     }
1739     bool ok = m_glMonitor->playZone(true);
1740     if (ok) {
1741         updatePlayAction(true);
1742     }
1743 }
1744 
1745 void Monitor::slotLoopClip(QPoint inOut)
1746 {
1747     if (!slotActivateMonitor()) {
1748         return;
1749     }
1750     bool ok = m_glMonitor->loopClip(inOut);
1751     if (ok) {
1752         updatePlayAction(true);
1753     }
1754 }
1755 
1756 void Monitor::updateClipProducer(const std::shared_ptr<Mlt::Producer> &prod)
1757 {
1758     if (m_glMonitor->setProducer(prod, isActive(), -1)) {
1759         prod->set_speed(1.0);
1760     }
1761 }
1762 
1763 void Monitor::updateClipProducer(const QString &playlist)
1764 {
1765     Q_UNUSED(playlist)
1766     // TODO
1767     // Mlt::Producer *prod = new Mlt::Producer(*m_glMonitor->profile(), playlist.toUtf8().constData());
1768     // m_glMonitor->setProducer(prod, isActive(), render->seekFramePosition());
1769     m_glMonitor->switchPlay(true);
1770 }
1771 
1772 void Monitor::slotOpenClip(const std::shared_ptr<ProjectClip> &controller, int in, int out)
1773 {
1774     if (m_controller) {
1775         m_glMonitor->resetZoneMode();
1776         // store last audiothumb zoom / position
1777         double zoomFactor = m_glMonitor->rootObject()->property("zoomFactor").toDouble();
1778         if (zoomFactor != 1.) {
1779             double zoomStart = m_glMonitor->rootObject()->property("zoomStart").toDouble();
1780             m_controller->setProducerProperty(QStringLiteral("kdenlive:thumbZoomFactor"), zoomFactor);
1781             m_controller->setProducerProperty(QStringLiteral("kdenlive:thumbZoomStart"), zoomStart);
1782         } else {
1783             m_controller->resetProducerProperty(QStringLiteral("kdenlive:thumbZoomFactor"));
1784             m_controller->resetProducerProperty(QStringLiteral("kdenlive:thumbZoomStart"));
1785         }
1786         m_controller->setProducerProperty(QStringLiteral("kdenlive:monitorPosition"), position());
1787         disconnect(m_controller.get(), &ProjectClip::audioThumbReady, this, &Monitor::prepareAudioThumb);
1788         disconnect(m_controller->getMarkerModel().get(), SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &, const QVector<int> &)), this,
1789                    SLOT(checkOverlay()));
1790         disconnect(m_controller->getMarkerModel().get(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(checkOverlay()));
1791         disconnect(m_controller->getMarkerModel().get(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SLOT(checkOverlay()));
1792         if (m_controller->hasLimitedDuration()) {
1793             disconnect(m_controller.get(), &ProjectClip::boundsChanged, m_glMonitor->getControllerProxy(), &MonitorProxy::updateClipBounds);
1794             disconnect(m_controller.get(), &ProjectClip::registeredClipChanged, m_controller.get(), &ProjectClip::checkClipBounds);
1795         }
1796     } else if (controller == nullptr) {
1797         // Nothing to do
1798         pCore->taskManager.displayedClip = -1;
1799         return;
1800     }
1801     disconnect(this, &Monitor::seekPosition, this, &Monitor::seekRemap);
1802     m_controller = controller;
1803     pCore->taskManager.displayedClip = m_controller ? m_controller->clipId().toInt() : -1;
1804     m_glMonitor->getControllerProxy()->setAudioStream(QString());
1805     m_snaps.reset(new SnapModel());
1806     m_glMonitor->getControllerProxy()->resetZone();
1807     if (controller) {
1808         m_markerModel = m_controller->getMarkerModel();
1809         if (pCore->currentRemap(controller->clipId())) {
1810             connect(this, &Monitor::seekPosition, this, &Monitor::seekRemap, Qt::UniqueConnection);
1811         }
1812         ClipType::ProducerType type = controller->clipType();
1813         if (type == ClipType::AV || type == ClipType::Video || type == ClipType::SlideShow) {
1814             m_glMonitor->rootObject()->setProperty("baseThumbPath",
1815                                                    QString("image://thumbnail/%1/%2/#").arg(controller->clipId(), pCore->currentDoc()->uuid().toString()));
1816         } else {
1817             m_glMonitor->rootObject()->setProperty("baseThumbPath", QString());
1818         }
1819         m_audioChannels->clear();
1820         if (m_controller->audioInfo()) {
1821             QMap<int, QString> audioStreamsInfo = m_controller->audioStreams();
1822             if (audioStreamsInfo.size() > 1) {
1823                 // Multi stream clip
1824                 QMapIterator<int, QString> i(audioStreamsInfo);
1825                 QMap<int, QString> activeStreams = m_controller->activeStreams();
1826                 if (activeStreams.size() > 1) {
1827                     m_glMonitor->getControllerProxy()->setAudioStream(i18np("%1 audio stream", "%1 audio streams", activeStreams.size()));
1828                     // TODO: Mix audio channels
1829                 } else if (!activeStreams.isEmpty()) {
1830                     m_glMonitor->getControllerProxy()->setAudioStream(activeStreams.first());
1831                 }
1832                 QAction *ac;
1833                 while (i.hasNext()) {
1834                     i.next();
1835                     ac = m_audioChannels->addAction(i.value());
1836                     ac->setData(i.key());
1837                     ac->setCheckable(true);
1838                     if (activeStreams.contains(i.key())) {
1839                         ac->setChecked(true);
1840                     }
1841                 }
1842                 ac = m_audioChannels->addAction(i18n("Merge all streams"));
1843                 ac->setData(INT_MAX);
1844                 ac->setCheckable(true);
1845                 if (activeStreams.contains(INT_MAX)) {
1846                     ac->setChecked(true);
1847                 }
1848                 m_streamAction->setVisible(true);
1849             } else {
1850                 m_streamAction->setVisible(false);
1851             }
1852         } else {
1853             m_streamAction->setVisible(false);
1854             // m_audioChannels->menuAction()->setVisible(false);
1855         }
1856         connect(m_controller.get(), &ProjectClip::audioThumbReady, this, &Monitor::prepareAudioThumb);
1857         if (m_controller->hasLimitedDuration()) {
1858             connect(m_controller.get(), &ProjectClip::boundsChanged, m_glMonitor->getControllerProxy(), &MonitorProxy::updateClipBounds);
1859             connect(m_controller.get(), &ProjectClip::registeredClipChanged, m_controller.get(), &ProjectClip::checkClipBounds);
1860         }
1861         connect(m_controller->getMarkerModel().get(), SIGNAL(dataChanged(QModelIndex, QModelIndex, QVector<int>)), this, SLOT(checkOverlay()));
1862         connect(m_controller->getMarkerModel().get(), SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(checkOverlay()));
1863         connect(m_controller->getMarkerModel().get(), SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(checkOverlay()));
1864 
1865         if (m_recManager->toolbar()->isVisible()) {
1866             // we are in record mode, don't display clip
1867             return;
1868         }
1869         if (KdenliveSettings::rectimecode()) {
1870             m_timePos->setOffset(m_controller->getRecordTime());
1871         }
1872         if (m_controller->statusReady()) {
1873             m_timePos->setRange(0, int(m_controller->frameDuration() - 1));
1874             m_glMonitor->setRulerInfo(int(m_controller->frameDuration() - 1), controller->getFilteredMarkerModel());
1875             double audioScale = m_controller->getProducerDoubleProperty(QStringLiteral("kdenlive:thumbZoomFactor"));
1876             if (in == out && in == -1) {
1877                 // Only apply on bin clip, not sub clips
1878                 int lastPosition = m_controller->getProducerIntProperty(QStringLiteral("kdenlive:monitorPosition"));
1879                 if (lastPosition > 0 && lastPosition != m_controller->originalProducer()->position()) {
1880                     m_controller->originalProducer()->seek(lastPosition);
1881                 }
1882                 if (audioScale > 0. && audioScale != 1.) {
1883                     double audioStart = m_controller->getProducerDoubleProperty(QStringLiteral("kdenlive:thumbZoomStart"));
1884                     m_glMonitor->rootObject()->setProperty("zoomFactor", audioScale);
1885                     m_glMonitor->rootObject()->setProperty("zoomStart", audioStart);
1886                     m_glMonitor->rootObject()->setProperty("showZoomBar", true);
1887                 } else {
1888                     m_glMonitor->rootObject()->setProperty("zoomFactor", 1);
1889                     m_glMonitor->rootObject()->setProperty("zoomStart", 0);
1890                     m_glMonitor->rootObject()->setProperty("showZoomBar", false);
1891                 }
1892             } else {
1893                 m_glMonitor->rootObject()->setProperty("zoomFactor", 1);
1894                 m_glMonitor->rootObject()->setProperty("zoomStart", 0);
1895                 m_glMonitor->rootObject()->setProperty("showZoomBar", false);
1896             }
1897             pCore->guidesList()->setClipMarkerModel(m_controller);
1898             loadQmlScene(MonitorSceneDefault);
1899             updateMarkers();
1900             connect(m_glMonitor->getControllerProxy(), &MonitorProxy::addSnap, this, &Monitor::addSnapPoint, Qt::DirectConnection);
1901             connect(m_glMonitor->getControllerProxy(), &MonitorProxy::removeSnap, this, &Monitor::removeSnapPoint, Qt::DirectConnection);
1902             if (out == -1) {
1903                 m_glMonitor->getControllerProxy()->setZone(m_controller->zone(), false);
1904             } else {
1905                 m_glMonitor->getControllerProxy()->setZone(in, out, false);
1906             }
1907             m_snaps->addPoint(int(m_controller->frameDuration() - 1));
1908             // Loading new clip / zone, stop if playing
1909             if (m_playAction->isActive()) {
1910                 updatePlayAction(false);
1911             }
1912             m_audioMeterWidget->audioChannels = controller->audioInfo() ? controller->audioInfo()->channels() : 0;
1913             m_controller->getMarkerModel()->registerSnapModel(m_snaps);
1914             m_glMonitor->getControllerProxy()->setClipProperties(controller->clipId().toInt(), controller->clipType(), controller->hasAudioAndVideo(),
1915                                                                  controller->clipName());
1916             if (!m_controller->hasVideo() || KdenliveSettings::displayClipMonitorInfo() & 0x10) {
1917                 if (m_audioMeterWidget->audioChannels == 0 || !m_controller->hasAudio()) {
1918                     qDebug() << "=======\n\nSETTING AUDIO DATA IN MONITOR EMPTY!!!";
1919                     m_glMonitor->getControllerProxy()->setAudioThumb();
1920                 } else {
1921                     QList<int> streamIndexes = m_controller->activeStreams().keys();
1922                     qDebug() << "=======\n\nSETTING AUDIO DATA IN MONITOR NOT EMPTY!!!";
1923                     if (streamIndexes.count() == 1 && streamIndexes.at(0) == INT_MAX) {
1924                         // Display all streams
1925                         streamIndexes = m_controller->audioStreams().keys();
1926                     }
1927                     m_glMonitor->getControllerProxy()->setAudioThumb(streamIndexes, m_controller->activeStreamChannels());
1928                 }
1929             }
1930             if (monitorVisible() && !m_monitorManager->projectMonitor()->isPlaying()) {
1931                 slotActivateMonitor();
1932             }
1933             buildBackgroundedProducer(in);
1934         } else {
1935             qDebug() << "*************** CONTROLLER NOT READY";
1936         }
1937         // hasEffects =  controller->hasEffects();
1938     } else {
1939         m_markerModel = nullptr;
1940         loadQmlScene(MonitorSceneDefault);
1941         m_glMonitor->setProducer(nullptr, isActive(), -1);
1942         m_glMonitor->getControllerProxy()->setAudioThumb();
1943         m_glMonitor->rootObject()->setProperty("zoomFactor", 1);
1944         m_glMonitor->rootObject()->setProperty("zoomStart", 0);
1945         m_glMonitor->rootObject()->setProperty("showZoomBar", false);
1946         m_audioMeterWidget->audioChannels = 0;
1947         m_glMonitor->getControllerProxy()->setClipProperties(-1, ClipType::Unknown, false, QString());
1948         pCore->guidesList()->setClipMarkerModel(nullptr);
1949         // m_audioChannels->menuAction()->setVisible(false);
1950         m_streamAction->setVisible(false);
1951         if (monitorVisible()) {
1952             slotActivateMonitor();
1953         }
1954     }
1955     if (isActive()) {
1956         start();
1957     }
1958     checkOverlay();
1959 }
1960 
1961 void Monitor::loadZone(int in, int out)
1962 {
1963     m_glMonitor->getControllerProxy()->setZone({in, out}, false);
1964 }
1965 
1966 void Monitor::reloadActiveStream()
1967 {
1968     if (m_controller) {
1969         QList<QAction *> acts = m_audioChannels->actions();
1970         QSignalBlocker bk(m_audioChannels);
1971         QList<int> activeStreams = m_controller->activeStreams().keys();
1972         QMap<int, QString> streams = m_controller->audioStreams();
1973         qDebug() << "==== REFRESHING MONITOR STREAMS: " << activeStreams;
1974         if (activeStreams.size() > 1) {
1975             m_glMonitor->getControllerProxy()->setAudioStream(i18np("%1 audio stream", "%1 audio streams", activeStreams.size()));
1976             // TODO: Mix audio channels
1977         } else if (!activeStreams.isEmpty()) {
1978             m_glMonitor->getControllerProxy()->setAudioStream(m_controller->activeStreams().first());
1979         } else {
1980             m_glMonitor->getControllerProxy()->setAudioStream(QString());
1981         }
1982         prepareAudioThumb();
1983         for (auto ac : qAsConst(acts)) {
1984             int val = ac->data().toInt();
1985             if (streams.contains(val)) {
1986                 // Update stream name in case of renaming
1987                 ac->setText(streams.value(val));
1988             }
1989             if (activeStreams.contains(val)) {
1990                 ac->setChecked(true);
1991             } else {
1992                 ac->setChecked(false);
1993             }
1994         }
1995     }
1996 }
1997 
1998 const QString Monitor::activeClipId()
1999 {
2000     if (m_controller) {
2001         return m_controller->clipId();
2002     }
2003     return QString();
2004 }
2005 
2006 void Monitor::slotPreviewResource(const QString &path, const QString &title)
2007 {
2008     if (isPlaying()) {
2009         stop();
2010     }
2011     QApplication::processEvents();
2012     slotOpenClip(nullptr);
2013     m_streamAction->setVisible(false);
2014     // TODO: direct loading of the producer blocks UI, we should use a task to load the producer
2015     m_markerModel = nullptr;
2016     m_glMonitor->setProducer(path);
2017     m_timePos->setRange(0, m_glMonitor->producer()->get_length() - 1);
2018     m_glMonitor->getControllerProxy()->setClipProperties(-1, ClipType::Unknown, false, title);
2019     m_glMonitor->setRulerInfo(m_glMonitor->producer()->get_length() - 1);
2020     loadQmlScene(MonitorSceneDefault);
2021     checkOverlay();
2022     slotStart();
2023     switchPlay(true);
2024 }
2025 
2026 void Monitor::resetProfile()
2027 {
2028     m_glMonitor->reloadProfile();
2029     m_glMonitor->rootObject()->setProperty("framesize", QRect(0, 0, m_glMonitor->profileSize().width(), m_glMonitor->profileSize().height()));
2030     // Update drop frame info
2031     m_qmlManager->setProperty(QStringLiteral("dropped"), false);
2032     m_qmlManager->setProperty(QStringLiteral("fps"), QString::number(pCore->getCurrentFps(), 'f', 2));
2033 }
2034 
2035 void Monitor::resetConsumer(bool fullReset)
2036 {
2037     m_glMonitor->resetConsumer(fullReset);
2038 }
2039 
2040 void Monitor::updateClipZone(const QPoint zone)
2041 {
2042     if (m_controller == nullptr) {
2043         return;
2044     }
2045     m_controller->setZone(zone);
2046 }
2047 
2048 void Monitor::restart()
2049 {
2050     m_glMonitor->restart();
2051 }
2052 
2053 void Monitor::switchMonitorInfo(int code)
2054 {
2055     int currentOverlay;
2056     if (m_id == Kdenlive::ClipMonitor) {
2057         currentOverlay = KdenliveSettings::displayClipMonitorInfo();
2058         currentOverlay ^= code;
2059         KdenliveSettings::setDisplayClipMonitorInfo(currentOverlay);
2060     } else {
2061         currentOverlay = KdenliveSettings::displayProjectMonitorInfo();
2062         currentOverlay ^= code;
2063         KdenliveSettings::setDisplayProjectMonitorInfo(currentOverlay);
2064     }
2065     updateQmlDisplay(currentOverlay);
2066     if (code == 0x01) {
2067         // Hide/show ruler
2068         m_glMonitor->switchRuler(currentOverlay & 0x01);
2069     }
2070 }
2071 
2072 void Monitor::slotEditMarker()
2073 {
2074     if (m_editMarker) {
2075         m_editMarker->trigger();
2076     }
2077 }
2078 
2079 void Monitor::updateTimecodeFormat()
2080 {
2081     m_glMonitor->rootObject()->setProperty("timecode", m_timePos->displayText());
2082 }
2083 
2084 QPoint Monitor::getZoneInfo() const
2085 {
2086     return m_glMonitor->getControllerProxy()->zone();
2087 }
2088 
2089 void Monitor::enableEffectScene(bool enable)
2090 {
2091     KdenliveSettings::setShowOnMonitorScene(enable);
2092     MonitorSceneType sceneType = enable ? m_lastMonitorSceneType : MonitorSceneDefault;
2093     slotShowEffectScene(sceneType, true);
2094     if (enable) {
2095         Q_EMIT updateScene();
2096     }
2097 }
2098 
2099 void Monitor::slotShowEffectScene(MonitorSceneType sceneType, bool temporary, const QVariant &sceneData)
2100 {
2101     if (m_trimmingbar->isVisible()) {
2102         return;
2103     }
2104     if (sceneType == MonitorSceneNone) {
2105         // We just want to revert to normal scene
2106         if (m_qmlManager->sceneType() == MonitorSceneSplit || m_qmlManager->sceneType() == MonitorSceneDefault) {
2107             // Ok, nothing to do
2108             return;
2109         }
2110         sceneType = MonitorSceneDefault;
2111     } else if (m_qmlManager->sceneType() == MonitorSplitTrack) {
2112         // Don't show another scene type if multitrack mode is active
2113         loadQmlScene(MonitorSplitTrack, sceneData);
2114         return;
2115     }
2116     if (!temporary) {
2117         m_lastMonitorSceneType = sceneType;
2118     }
2119     loadQmlScene(sceneType, sceneData);
2120 }
2121 
2122 void Monitor::slotSeekToKeyFrame()
2123 {
2124     if (m_qmlManager->sceneType() == MonitorSceneGeometry) {
2125         // Adjust splitter pos
2126         int kfr = m_glMonitor->rootObject()->property("requestedKeyFrame").toInt();
2127         Q_EMIT seekToKeyframe(kfr);
2128     }
2129 }
2130 
2131 void Monitor::setUpEffectGeometry(const QRect &r, const QVariantList &list, const QVariantList &types)
2132 {
2133     QQuickItem *root = m_glMonitor->rootObject();
2134     if (!root) {
2135         return;
2136     }
2137     if (!list.isEmpty() || m_qmlManager->sceneType() == MonitorSceneRoto) {
2138         root->setProperty("centerPointsTypes", types);
2139         root->setProperty("centerPoints", list);
2140     }
2141     if (!r.isEmpty()) {
2142         root->setProperty("framesize", r);
2143     }
2144 }
2145 
2146 void Monitor::setEffectSceneProperty(const QString &name, const QVariant &value)
2147 {
2148     QQuickItem *root = m_glMonitor->rootObject();
2149     if (!root) {
2150         return;
2151     }
2152     root->setProperty(name.toUtf8().constData(), value);
2153 }
2154 
2155 QRect Monitor::effectRect() const
2156 {
2157     QQuickItem *root = m_glMonitor->rootObject();
2158     if (!root) {
2159         return {};
2160     }
2161     return root->property("framesize").toRect();
2162 }
2163 
2164 QVariantList Monitor::effectPolygon() const
2165 {
2166     QQuickItem *root = m_glMonitor->rootObject();
2167     if (!root) {
2168         return QVariantList();
2169     }
2170     return root->property("centerPoints").toList();
2171 }
2172 
2173 QVariantList Monitor::effectRoto() const
2174 {
2175     QQuickItem *root = m_glMonitor->rootObject();
2176     if (!root) {
2177         return QVariantList();
2178     }
2179     QVariantList points = root->property("centerPoints").toList();
2180     QVariantList controlPoints = root->property("centerPointsTypes").toList();
2181     // rotoscoping effect needs a list of
2182     QVariantList mix;
2183     mix.reserve(points.count() * 3);
2184     for (int i = 0; i < points.count(); i++) {
2185         mix << controlPoints.at(2 * i);
2186         mix << points.at(i);
2187         mix << controlPoints.at(2 * i + 1);
2188     }
2189     return mix;
2190 }
2191 
2192 void Monitor::setEffectKeyframe(bool enable)
2193 {
2194     QQuickItem *root = m_glMonitor->rootObject();
2195     if (root) {
2196         root->setProperty("iskeyframe", enable);
2197     }
2198 }
2199 
2200 bool Monitor::effectSceneDisplayed(MonitorSceneType effectType)
2201 {
2202     return m_qmlManager->sceneType() == effectType;
2203 }
2204 
2205 void Monitor::slotSetVolume(int volume)
2206 {
2207     KdenliveSettings::setVolume(volume);
2208     double renderVolume = m_glMonitor->volume();
2209     m_glMonitor->setVolume(volume / 100.0);
2210     if (renderVolume > 0 && volume > 0) {
2211         return;
2212     }
2213     /*QIcon icon;
2214     if (volume == 0) {
2215         icon = QIcon::fromTheme(QStringLiteral("audio-volume-muted"));
2216     } else {
2217         icon = QIcon::fromTheme(QStringLiteral("audio-volume-medium"));
2218     }*/
2219 }
2220 
2221 void Monitor::sendFrameForAnalysis(bool analyse)
2222 {
2223     m_glMonitor->sendFrameForAnalysis = analyse;
2224 }
2225 
2226 void Monitor::updateAudioForAnalysis()
2227 {
2228     m_glMonitor->updateAudioForAnalysis();
2229 }
2230 
2231 void Monitor::onFrameDisplayed(const SharedFrame &frame)
2232 {
2233     Q_EMIT m_monitorManager->frameDisplayed(frame);
2234     if (m_id == Kdenlive::ProjectMonitor) {
2235         Q_EMIT pCore->updateMixerLevels(frame.get_position());
2236     }
2237     if (!m_glMonitor->checkFrameNumber(frame.get_position(), m_playAction->isActive())) {
2238         updatePlayAction(false);
2239     }
2240 }
2241 
2242 void Monitor::checkDrops()
2243 {
2244     int dropped = m_glMonitor->droppedFrames();
2245     if (dropped == 0) {
2246         // No dropped frames since last check
2247         m_qmlManager->setProperty(QStringLiteral("dropped"), false);
2248         m_qmlManager->setProperty(QStringLiteral("fps"), QString::number(pCore->getCurrentFps(), 'f', 2));
2249     } else {
2250         m_glMonitor->resetDrops();
2251         dropped = int(pCore->getCurrentFps() - dropped);
2252         m_qmlManager->setProperty(QStringLiteral("dropped"), true);
2253         m_qmlManager->setProperty(QStringLiteral("fps"), QString::number(dropped, 'f', 2));
2254     }
2255 }
2256 
2257 void Monitor::reloadProducer(const QString &id)
2258 {
2259     if (!m_controller) {
2260         return;
2261     }
2262     if (m_controller->AbstractProjectItem::clipId() == id) {
2263         slotOpenClip(m_controller);
2264     }
2265 }
2266 
2267 QString Monitor::getMarkerThumb(GenTime pos)
2268 {
2269     if (!m_controller) {
2270         return QString();
2271     }
2272     if (!m_controller->getClipHash().isEmpty()) {
2273         bool ok = false;
2274         QDir dir = pCore->currentDoc()->getCacheDir(CacheThumbs, &ok);
2275         if (ok) {
2276             QString url = dir.absoluteFilePath(m_controller->getClipHash() + QLatin1Char('#') + QString::number(pos.frames(pCore->getCurrentFps())) +
2277                                                QStringLiteral(".png"));
2278             if (QFile::exists(url)) {
2279                 return url;
2280             }
2281         }
2282     }
2283     return QString();
2284 }
2285 
2286 void Monitor::setPalette(const QPalette &p)
2287 {
2288     QWidget::setPalette(p);
2289     QList<QToolButton *> allButtons = this->findChildren<QToolButton *>();
2290     for (int i = 0; i < allButtons.count(); i++) {
2291         QToolButton *m = allButtons.at(i);
2292         QIcon ic = m->icon();
2293         if (ic.isNull() || ic.name().isEmpty()) {
2294             continue;
2295         }
2296         QIcon newIcon = QIcon::fromTheme(ic.name());
2297         m->setIcon(newIcon);
2298     }
2299     QQuickItem *root = m_glMonitor->rootObject();
2300     if (root) {
2301         QMetaObject::invokeMethod(root, "updatePalette");
2302     }
2303     m_audioMeterWidget->refreshPixmap();
2304 }
2305 
2306 void Monitor::gpuError()
2307 {
2308     qCWarning(KDENLIVE_LOG) << " + + + + Error initializing Movit GLSL manager";
2309     warningMessage(i18n("Cannot initialize Movit's GLSL manager, please disable Movit"), -1);
2310 }
2311 
2312 void Monitor::warningMessage(const QString &text, int timeout, const QList<QAction *> &actions)
2313 {
2314     m_infoMessage->setMessageType(KMessageWidget::Warning);
2315     m_infoMessage->setText(text);
2316     for (QAction *action : actions) {
2317         m_infoMessage->addAction(action);
2318     }
2319     m_infoMessage->setCloseButtonVisible(true);
2320     m_infoMessage->animatedShow();
2321     if (timeout > 0) {
2322         QTimer::singleShot(timeout, m_infoMessage, &KMessageWidget::animatedHide);
2323     }
2324 }
2325 
2326 void Monitor::activateSplit()
2327 {
2328     loadQmlScene(MonitorSceneSplit);
2329     if (isActive()) {
2330         m_glMonitor->requestRefresh();
2331     } else if (slotActivateMonitor()) {
2332         start();
2333     }
2334 }
2335 
2336 void Monitor::slotSwitchCompare(bool enable)
2337 {
2338     if (m_id == Kdenlive::ProjectMonitor) {
2339         if (enable) {
2340             if (m_qmlManager->sceneType() == MonitorSceneSplit) {
2341                 // Split scene is already active
2342                 return;
2343             }
2344             m_splitEffect.reset(new Mlt::Filter(pCore->getProjectProfile(), "frei0r.alphagrad"));
2345             if ((m_splitEffect != nullptr) && m_splitEffect->is_valid()) {
2346                 m_splitEffect->set("0", 0.5);    // 0 is the Clip left parameter
2347                 m_splitEffect->set("1", 0);      // 1 is gradient width
2348                 m_splitEffect->set("2", -0.747); // 2 is tilt
2349             } else {
2350                 // frei0r.scal0tilt is not available
2351                 warningMessage(i18n("The alphagrad filter is required for that feature, please install frei0r and restart Kdenlive"));
2352                 return;
2353             }
2354             Q_EMIT createSplitOverlay(m_splitEffect);
2355             return;
2356         }
2357         // Delete temp track
2358         Q_EMIT removeSplitOverlay();
2359         m_splitEffect.reset();
2360         loadQmlScene(MonitorSceneDefault);
2361         if (isActive()) {
2362             m_glMonitor->requestRefresh();
2363         } else if (slotActivateMonitor()) {
2364             start();
2365         }
2366         return;
2367     }
2368     if (m_controller == nullptr || !m_controller->hasEffects()) {
2369         // disable split effect
2370         if (m_controller) {
2371             pCore->displayMessage(i18n("Clip has no effects"), InformationMessage);
2372         } else {
2373             pCore->displayMessage(i18n("Select a clip in project bin to compare effect"), InformationMessage);
2374         }
2375         return;
2376     }
2377     if (enable) {
2378         if (m_qmlManager->sceneType() == MonitorSceneSplit) {
2379             // Split scene is already active
2380             qDebug() << " . . . .. ALREADY ACTIVE";
2381             return;
2382         }
2383         buildSplitEffect(m_controller->masterProducer());
2384     } else if (m_splitEffect) {
2385         // TODO
2386         m_glMonitor->setProducer(m_controller->originalProducer(), isActive(), position());
2387         m_splitEffect.reset();
2388         m_splitProducer.reset();
2389         loadQmlScene(MonitorSceneDefault);
2390     }
2391     slotActivateMonitor();
2392 }
2393 
2394 void Monitor::resetScene()
2395 {
2396     loadQmlScene(MonitorSceneDefault);
2397 }
2398 
2399 void Monitor::buildSplitEffect(Mlt::Producer *original)
2400 {
2401     m_splitEffect.reset(new Mlt::Filter(pCore->getProjectProfile(), "frei0r.alphagrad"));
2402     if ((m_splitEffect != nullptr) && m_splitEffect->is_valid()) {
2403         m_splitEffect->set("0", 0.5);    // 0 is the Clip left parameter
2404         m_splitEffect->set("1", 0);      // 1 is gradient width
2405         m_splitEffect->set("2", -0.747); // 2 is tilt
2406     } else {
2407         // frei0r.scal0tilt is not available
2408         pCore->displayMessage(i18n("The alphagrad filter is required for that feature, please install frei0r and restart Kdenlive"), ErrorMessage);
2409         return;
2410     }
2411     QString splitTransition = TransitionsRepository::get()->getCompositingTransition();
2412     Mlt::Transition t(pCore->getProjectProfile(), splitTransition.toUtf8().constData());
2413     if (!t.is_valid()) {
2414         m_splitEffect.reset();
2415         pCore->displayMessage(i18n("The cairoblend transition is required for that feature, please install frei0r and restart Kdenlive"), ErrorMessage);
2416         return;
2417     }
2418     Mlt::Tractor trac(pCore->getProjectProfile());
2419     std::shared_ptr<Mlt::Producer> clone = ProjectClip::cloneProducer(std::make_shared<Mlt::Producer>(original));
2420     // Delete all effects
2421     int ct = 0;
2422     Mlt::Filter *filter = clone->filter(ct);
2423     while (filter != nullptr) {
2424         QString ix = QString::fromLatin1(filter->get("kdenlive_id"));
2425         if (!ix.isEmpty()) {
2426             if (clone->detach(*filter) == 0) {
2427             } else {
2428                 ct++;
2429             }
2430         } else {
2431             ct++;
2432         }
2433         delete filter;
2434         filter = clone->filter(ct);
2435     }
2436     trac.set_track(*original, 0);
2437     trac.set_track(*clone.get(), 1);
2438     clone.get()->attach(*m_splitEffect.get());
2439     t.set("always_active", 1);
2440     trac.plant_transition(t, 0, 1);
2441     delete original;
2442     m_splitProducer = std::make_shared<Mlt::Producer>(trac.get_producer());
2443     m_glMonitor->setProducer(m_splitProducer, isActive(), position());
2444     m_glMonitor->setRulerInfo(int(m_controller->frameDuration()), m_controller->getFilteredMarkerModel());
2445     loadQmlScene(MonitorSceneSplit);
2446 }
2447 
2448 QSize Monitor::profileSize() const
2449 {
2450     return m_glMonitor->profileSize();
2451 }
2452 
2453 void Monitor::loadQmlScene(MonitorSceneType type, const QVariant &sceneData)
2454 {
2455     if (type == m_qmlManager->sceneType() && sceneData.isNull()) {
2456         return;
2457     }
2458     bool sceneWithEdit = type == MonitorSceneGeometry || type == MonitorSceneCorners || type == MonitorSceneRoto;
2459     if (!m_monitorManager->getAction(QStringLiteral("monitor_editmode"))->isChecked() && sceneWithEdit) {
2460         // User doesn't want effect scenes
2461         pCore->displayMessage(i18n("Enable edit mode in monitor to edit effect"), InformationMessage, 500);
2462         type = MonitorSceneDefault;
2463     }
2464     m_qmlManager->setScene(m_id, type, pCore->getCurrentFrameSize(), pCore->getCurrentDar(), m_glMonitor->displayRect(), double(m_glMonitor->zoom()),
2465                            m_timePos->maximum());
2466     if (m_glMonitor->zoom() != 1.) {
2467         m_glMonitor->setZoom(m_glMonitor->zoom(), true);
2468     }
2469     QQuickItem *root = m_glMonitor->rootObject();
2470     switch (type) {
2471     case MonitorSceneSplit:
2472         QObject::connect(root, SIGNAL(qmlMoveSplit()), this, SLOT(slotAdjustEffectCompare()), Qt::UniqueConnection);
2473         break;
2474     case MonitorSceneTrimming:
2475     case MonitorSceneGeometry:
2476     case MonitorSceneCorners:
2477     case MonitorSceneRoto:
2478         break;
2479     case MonitorSceneDefault:
2480         QObject::connect(root, SIGNAL(editCurrentMarker()), this, SLOT(slotEditInlineMarker()), Qt::UniqueConnection);
2481         m_qmlManager->setProperty(QStringLiteral("timecode"), m_timePos->displayText());
2482         if (m_id == Kdenlive::ClipMonitor) {
2483             QObject::connect(root, SIGNAL(endDrag()), pCore->bin(), SIGNAL(processDragEnd()), Qt::UniqueConnection);
2484             updateQmlDisplay(KdenliveSettings::displayClipMonitorInfo());
2485         } else if (m_id == Kdenlive::ProjectMonitor) {
2486             updateQmlDisplay(KdenliveSettings::displayProjectMonitorInfo());
2487             QObject::connect(root, SIGNAL(startRecording()), pCore.get(), SLOT(startRecording()), Qt::UniqueConnection);
2488         }
2489         break;
2490     case MonitorSplitTrack:
2491         m_qmlManager->setProperty(QStringLiteral("tracks"), sceneData);
2492         break;
2493     default:
2494         break;
2495     }
2496     m_qmlManager->setProperty(QStringLiteral("fps"), QString::number(pCore->getCurrentFps(), 'f', 2));
2497 }
2498 
2499 void Monitor::setQmlProperty(const QString &name, const QVariant &value)
2500 {
2501     m_qmlManager->setProperty(name, value);
2502 }
2503 
2504 void Monitor::slotAdjustEffectCompare()
2505 {
2506     double percent = 0.5;
2507     if (m_qmlManager->sceneType() == MonitorSceneSplit) {
2508         // Adjust splitter pos
2509         QQuickItem *root = m_glMonitor->rootObject();
2510         percent = root->property("percentage").toDouble();
2511         // Store real frame percentage for resize events
2512         root->setProperty("realpercent", percent);
2513     }
2514     if (m_splitEffect) {
2515         m_splitEffect->set("0", 0.5 - (percent - 0.5) * .666);
2516     }
2517     m_glMonitor->refresh();
2518 }
2519 
2520 void Monitor::slotSwitchRec(bool enable)
2521 {
2522     if (!m_recManager) {
2523         return;
2524     }
2525     if (enable) {
2526         m_toolbar->setVisible(false);
2527         m_recManager->toolbar()->setVisible(true);
2528     } else if (m_recManager->toolbar()->isVisible()) {
2529         m_recManager->stop();
2530         m_toolbar->setVisible(true);
2531         Q_EMIT refreshCurrentClip();
2532     }
2533 }
2534 
2535 void Monitor::slotSwitchTrimming(bool enable)
2536 {
2537     if (!m_trimmingbar) {
2538         return;
2539     }
2540     if (enable) {
2541         loadQmlScene(MonitorSceneTrimming);
2542         m_toolbar->setVisible(false);
2543         m_trimmingbar->setVisible(true);
2544         if (pCore->activeTool() == ToolType::RippleTool) {
2545             m_oneLess->setVisible(false);
2546             m_oneMore->setVisible(false);
2547             m_fiveLess->setVisible(false);
2548             m_fiveMore->setVisible(false);
2549         } else {
2550             m_oneLess->setVisible(true);
2551             m_oneMore->setVisible(true);
2552             m_fiveLess->setVisible(true);
2553             m_fiveMore->setVisible(true);
2554         }
2555         m_glMonitor->switchRuler(false);
2556     } else if (m_trimmingbar->isVisible()) {
2557         loadQmlScene(MonitorSceneDefault);
2558         m_trimmingbar->setVisible(false);
2559         m_toolbar->setVisible(true);
2560         m_glMonitor->switchRuler(KdenliveSettings::displayClipMonitorInfo() & 0x01);
2561     }
2562 }
2563 
2564 void Monitor::doKeyPressEvent(QKeyEvent *ev)
2565 {
2566     keyPressEvent(ev);
2567 }
2568 
2569 void Monitor::slotEditInlineMarker()
2570 {
2571     QQuickItem *root = m_glMonitor->rootObject();
2572     if (root) {
2573         std::shared_ptr<MarkerListModel> model;
2574         if (m_controller) {
2575             // We are editing a clip marker
2576             model = m_controller->getMarkerModel();
2577         } else {
2578             model = pCore->currentDoc()->getGuideModel(pCore->currentTimelineId());
2579         }
2580         QString newComment = root->property("markerText").toString();
2581         bool found = false;
2582         CommentedTime oldMarker = model->getMarker(m_timePos->getValue(), &found);
2583         if (!found || newComment == oldMarker.comment()) {
2584             // No change
2585             return;
2586         }
2587         oldMarker.setComment(newComment);
2588         model->addMarker(oldMarker.time(), oldMarker.comment(), oldMarker.markerType());
2589     }
2590 }
2591 
2592 void Monitor::prepareAudioThumb()
2593 {
2594     if (m_controller) {
2595         m_glMonitor->getControllerProxy()->setAudioThumb();
2596         if (!m_controller->audioStreams().isEmpty() && m_controller->hasAudio()) {
2597             QList<int> streamIndexes = m_controller->activeStreams().keys();
2598             if (streamIndexes.count() == 1 && streamIndexes.at(0) == INT_MAX) {
2599                 // Display all streams
2600                 streamIndexes = m_controller->audioStreams().keys();
2601             }
2602             m_glMonitor->getControllerProxy()->setAudioThumb(streamIndexes, m_controller->activeStreamChannels());
2603         }
2604     }
2605 }
2606 
2607 void Monitor::slotSwitchAudioMonitor()
2608 {
2609     if (!m_audioMeterWidget->isValid) {
2610         KdenliveSettings::setMonitoraudio(0x01);
2611         m_audioMeterWidget->setVisibility(false);
2612         return;
2613     }
2614     int currentOverlay = KdenliveSettings::monitoraudio();
2615     currentOverlay ^= m_id;
2616     KdenliveSettings::setMonitoraudio(currentOverlay);
2617     if ((KdenliveSettings::monitoraudio() & m_id) != 0) {
2618         // We want to enable this audio monitor, so make monitor active
2619         slotActivateMonitor();
2620     }
2621     displayAudioMonitor(isActive());
2622 }
2623 
2624 void Monitor::updateGuidesList()
2625 {
2626     if (m_id == Kdenlive::ProjectMonitor) {
2627         if (pCore->currentDoc()) {
2628             const QUuid uuid = pCore->currentDoc()->activeUuid;
2629             if (!uuid.isNull()) {
2630                 pCore->guidesList()->setModel(pCore->currentDoc()->getGuideModel(uuid), pCore->currentDoc()->getFilteredGuideModel(uuid));
2631             }
2632         }
2633     } else if (m_id == Kdenlive::ClipMonitor) {
2634         pCore->guidesList()->setClipMarkerModel(m_controller);
2635     }
2636 }
2637 
2638 void Monitor::displayAudioMonitor(bool isActive)
2639 {
2640     bool enable = isActive && ((KdenliveSettings::monitoraudio() & m_id) != 0 || (m_id == Kdenlive::ProjectMonitor && pCore->audioMixerVisible));
2641     if (enable) {
2642         connect(m_monitorManager, &MonitorManager::frameDisplayed, m_audioMeterWidget, &ScopeWidget::onNewFrame, Qt::UniqueConnection);
2643     } else {
2644         disconnect(m_monitorManager, &MonitorManager::frameDisplayed, m_audioMeterWidget, &ScopeWidget::onNewFrame);
2645     }
2646     m_audioMeterWidget->setVisibility((KdenliveSettings::monitoraudio() & m_id) != 0);
2647     if (isActive && m_glWidget->isFullScreen()) {
2648         // If both monitors are fullscreen, this is necessary to do the switch
2649         m_glWidget->showFullScreen();
2650         pCore->window()->activateWindow();
2651         pCore->window()->setFocus();
2652     }
2653 }
2654 
2655 void Monitor::updateQmlDisplay(int currentOverlay)
2656 {
2657     m_glMonitor->rootObject()->setVisible((currentOverlay & 0x01) != 0);
2658     m_glMonitor->rootObject()->setProperty("showMarkers", currentOverlay & 0x04);
2659     bool showDropped = currentOverlay & 0x20;
2660     m_glMonitor->rootObject()->setProperty("showFps", showDropped);
2661     m_glMonitor->rootObject()->setProperty("showTimecode", currentOverlay & 0x02);
2662     if (m_id == Kdenlive::ClipMonitor) {
2663         m_glMonitor->rootObject()->setProperty("showAudiothumb", currentOverlay & 0x10);
2664         m_glMonitor->rootObject()->setProperty("showClipJobs", currentOverlay & 0x40);
2665     }
2666     if (showDropped) {
2667         if (!m_droppedTimer.isActive() && m_playAction->isActive()) {
2668             m_glMonitor->resetDrops();
2669             m_droppedTimer.start();
2670         }
2671     } else {
2672         m_droppedTimer.stop();
2673     }
2674 }
2675 
2676 void Monitor::clearDisplay()
2677 {
2678     m_glMonitor->clear();
2679 }
2680 
2681 void Monitor::panView(QPoint diff)
2682 {
2683     // Only pan if scrollbars are visible
2684     if (m_horizontalScroll->isVisible()) {
2685         m_horizontalScroll->setValue(m_horizontalScroll->value() + diff.x());
2686     }
2687     if (m_verticalScroll->isVisible()) {
2688         m_verticalScroll->setValue(m_verticalScroll->value() + diff.y());
2689     }
2690 }
2691 
2692 void Monitor::processSeek(int pos, bool noAudioScrub)
2693 {
2694     if (!slotActivateMonitor()) {
2695         return;
2696     }
2697     if (KdenliveSettings::pauseonseek()) {
2698         if (m_playAction->isActive()) {
2699             pause();
2700         } else {
2701             m_glMonitor->setVolume(KdenliveSettings::volume() / 100.);
2702         }
2703     }
2704     m_glMonitor->requestSeek(pos, noAudioScrub);
2705     Q_EMIT m_monitorManager->cleanMixer();
2706 }
2707 
2708 void Monitor::requestSeek(int pos)
2709 {
2710     m_glMonitor->getControllerProxy()->setPosition(pos);
2711 }
2712 
2713 void Monitor::requestSeekIfVisible(int pos)
2714 {
2715     if (monitorVisible()) {
2716         requestSeek(pos);
2717     }
2718 }
2719 
2720 void Monitor::setProducer(std::shared_ptr<Mlt::Producer> producer, int pos)
2721 {
2722     if (locked) {
2723         return;
2724     }
2725     m_audioMeterWidget->audioChannels = pCore->audioChannels();
2726     if (producer) {
2727         m_markerModel = pCore->currentDoc()->getGuideModel(pCore->currentTimelineId());
2728     } else {
2729         m_markerModel.reset();
2730     }
2731     m_glMonitor->setProducer(std::move(producer), isActive(), pos);
2732 }
2733 
2734 void Monitor::reconfigure()
2735 {
2736     m_glMonitor->reconfigure();
2737 }
2738 
2739 void Monitor::slotSeekPosition(int pos)
2740 {
2741     Q_EMIT seekPosition(pos);
2742     m_timePos->setValue(pos);
2743     checkOverlay();
2744 }
2745 
2746 void Monitor::slotStart()
2747 {
2748     if (!slotActivateMonitor()) {
2749         return;
2750     }
2751     m_glMonitor->switchPlay(false);
2752     m_glMonitor->getControllerProxy()->setPosition(0);
2753 }
2754 
2755 void Monitor::slotTrimmingPos(int pos, int offset, int frames1, int frames2)
2756 {
2757     if (m_glMonitor->producer() != pCore->window()->getCurrentTimeline()->model()->producer().get()) {
2758         processSeek(pos);
2759     }
2760     QString tc(pCore->timecode().getDisplayTimecodeFromFrames(offset, KdenliveSettings::frametimecode()));
2761     m_trimmingOffset->setText(tc);
2762     m_glMonitor->getControllerProxy()->setTrimmingTC1(frames1);
2763     m_glMonitor->getControllerProxy()->setTrimmingTC2(frames2);
2764 }
2765 
2766 void Monitor::slotTrimmingPos(int offset)
2767 {
2768     offset = pCore->window()->getCurrentTimeline()->controller()->trimmingBoundOffset(offset);
2769     if (m_glMonitor->producer() != pCore->window()->getCurrentTimeline()->model()->producer().get()) {
2770         processSeek(m_glMonitor->producer()->position() + offset);
2771     }
2772     QString tc(pCore->timecode().getDisplayTimecodeFromFrames(offset, KdenliveSettings::frametimecode()));
2773     m_trimmingOffset->setText(tc);
2774 
2775     m_glMonitor->getControllerProxy()->setTrimmingTC1(offset, true);
2776     m_glMonitor->getControllerProxy()->setTrimmingTC2(offset, true);
2777 }
2778 
2779 void Monitor::slotEnd()
2780 {
2781     if (!slotActivateMonitor()) {
2782         return;
2783     }
2784     m_glMonitor->switchPlay(false);
2785     if (m_id == Kdenlive::ClipMonitor) {
2786         m_glMonitor->getControllerProxy()->setPosition(m_glMonitor->duration() - 1);
2787     } else {
2788         m_glMonitor->getControllerProxy()->setPosition(pCore->projectDuration() - 1);
2789     }
2790 }
2791 
2792 void Monitor::addSnapPoint(int pos)
2793 {
2794     m_snaps->addPoint(pos);
2795 }
2796 
2797 void Monitor::removeSnapPoint(int pos)
2798 {
2799     m_snaps->removePoint(pos);
2800 }
2801 
2802 void Monitor::slotSetScreen(int screenIndex)
2803 {
2804     Q_EMIT screenChanged(screenIndex);
2805 }
2806 
2807 void Monitor::slotZoomIn()
2808 {
2809     m_glMonitor->slotZoom(true);
2810 }
2811 
2812 void Monitor::slotZoomOut()
2813 {
2814     m_glMonitor->slotZoom(false);
2815 }
2816 
2817 void Monitor::setConsumerProperty(const QString &name, const QString &value)
2818 {
2819     m_glMonitor->setConsumerProperty(name, value);
2820 }
2821 
2822 void Monitor::purgeCache()
2823 {
2824     m_glMonitor->purgeCache();
2825 }
2826 
2827 void Monitor::updateBgColor()
2828 {
2829 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
2830     m_glMonitor->m_bgColor = KdenliveSettings::window_background();
2831 #else
2832     m_glMonitor->setClearColor(KdenliveSettings::window_background());
2833 #endif
2834 }
2835 
2836 MonitorProxy *Monitor::getControllerProxy()
2837 {
2838     return m_glMonitor->getControllerProxy();
2839 }
2840 
2841 void Monitor::updateMultiTrackView(int tid)
2842 {
2843     QQuickItem *root = m_glMonitor->rootObject();
2844     if (root) {
2845         root->setProperty("activeTrack", tid);
2846     }
2847 }
2848 
2849 void Monitor::slotSwitchRecTimecode(bool enable)
2850 {
2851     qDebug() << "=== SLOT SWITCH REC: " << enable;
2852     KdenliveSettings::setRectimecode(enable);
2853     if (!enable) {
2854         m_timePos->setOffset(0);
2855         return;
2856     }
2857     if (m_controller) {
2858         qDebug() << "=== GOT TIMECODE OFFSET: " << m_controller->getRecordTime();
2859         m_timePos->setOffset(m_controller->getRecordTime());
2860     }
2861 }
2862 
2863 void Monitor::focusTimecode()
2864 {
2865     m_timePos->setFocus();
2866     m_timePos->selectAll();
2867 }
2868 
2869 void Monitor::startCountDown()
2870 {
2871     QQuickItem *root = m_glMonitor->rootObject();
2872     if (root) {
2873         QMetaObject::invokeMethod(root, "startCountdown");
2874     }
2875 }
2876 
2877 void Monitor::stopCountDown()
2878 {
2879     QQuickItem *root = m_glMonitor->rootObject();
2880     if (root) {
2881         QMetaObject::invokeMethod(root, "stopCountdown");
2882     }
2883 }
2884 
2885 void Monitor::extractFrame(const QString &path)
2886 {
2887     QStringList pathInfo = {QString(), path, QString()};
2888     m_glMonitor->getControllerProxy()->extractFrameToFile(m_glMonitor->getCurrentPos(), pathInfo, false, true);
2889 }
2890 
2891 const QStringList Monitor::getGPUInfo()
2892 {
2893     return m_glMonitor->getGPUInfo();
2894 }