monitor.cpp 98.6 KB
Newer Older
1
/*
Alexander Lohnau's avatar
Alexander Lohnau committed
2
    SPDX-FileCopyrightText: 2007 Jean-Baptiste Mardelle <jb@kdenlive.org>
3

Camille Moulin's avatar
Camille Moulin committed
4
SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5
*/
6

Vincent PINON's avatar
Vincent PINON committed
7
#include "monitor.h"
Nicolas Carion's avatar
Nicolas Carion committed
8
#include "bin/bin.h"
9
#include "bin/projectclip.h"
10
#include "lib/localeHandling.h"
Nicolas Carion's avatar
Nicolas Carion committed
11
12
13
14
#include "core.h"
#include "dialogs/profilesdialog.h"
#include "doc/kdenlivedoc.h"
#include "doc/kthumb.h"
15
#include "glwidget.h"
Nicolas Carion's avatar
Nicolas Carion committed
16
17
18
19
#include "kdenlivesettings.h"
#include "lib/audio/audioStreamInfo.h"
#include "mainwindow.h"
#include "mltcontroller/clipcontroller.h"
20
#include "monitorproxy.h"
Julius Künzel's avatar
Julius Künzel committed
21
#include "monitormanager.h"
22
#include "profiles/profilemodel.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
23
#include "project/projectmanager.h"
24
#include "qmlmanager.h"
25
#include "recmanager.h"
26
#include "jobs/cuttask.h"
27
#include "scopes/monitoraudiolevel.h"
28
#include "timeline2/model/snapmodel.hpp"
29
30
#include "timeline2/view/timelinewidget.h"
#include "timeline2/view/timelinecontroller.h"
31
#include "transitions/transitionsrepository.hpp"
32
#include "utils/thumbnailcache.hpp"
33

34
#include "klocalizedstring.h"
35
#include <KDualAction>
Nicolas Carion's avatar
Nicolas Carion committed
36
#include <KFileWidget>
Nicolas Carion's avatar
Nicolas Carion committed
37
#include <KMessageWidget>
38
#include <KMessageBox>
Nicolas Carion's avatar
Nicolas Carion committed
39
40
#include <KRecentDirs>
#include <KSelectAction>
41
#include <KWindowConfig>
Vincent Pinon's avatar
Vincent Pinon committed
42
#include <kio_version.h>
43

Laurent Montel's avatar
Laurent Montel committed
44
#include "kdenlive_debug.h"
Vincent Pinon's avatar
Vincent Pinon committed
45
#include <QScreen>
46
#include <QDrag>
Nicolas Carion's avatar
Nicolas Carion committed
47
#include <QMenu>
48
#include <QMimeData>
Nicolas Carion's avatar
Nicolas Carion committed
49
#include <QMouseEvent>
50
#include <QQuickItem>
51
#include <QScrollBar>
Nicolas Carion's avatar
Nicolas Carion committed
52
53
54
55
#include <QSlider>
#include <QToolBar>
#include <QToolButton>
#include <QVBoxLayout>
56
#include <QWidgetAction>
Nicolas Carion's avatar
Nicolas Carion committed
57
#include <utility>
58
59
#define SEEK_INACTIVE (-1)

60
61
QuickEventEater::QuickEventEater(QObject *parent)
    : QObject(parent)
62
63
64
65
66
{
}

bool QuickEventEater::eventFilter(QObject *obj, QEvent *event)
{
67
    switch (event->type()) {
Laurent Montel's avatar
Laurent Montel committed
68
    case QEvent::DragEnter: {
Nicolas Carion's avatar
Nicolas Carion committed
69
        auto *ev = reinterpret_cast<QDragEnterEvent *>(event);
70
        if (ev->mimeData()->hasFormat(QStringLiteral("kdenlive/effect"))) {
Laurent Montel's avatar
Laurent Montel committed
71
72
            ev->acceptProposedAction();
            return true;
73
        }
Laurent Montel's avatar
Laurent Montel committed
74
75
76
        break;
    }
    case QEvent::DragMove: {
Nicolas Carion's avatar
Nicolas Carion committed
77
        auto *ev = reinterpret_cast<QDragEnterEvent *>(event);
78
        if (ev->mimeData()->hasFormat(QStringLiteral("kdenlive/effect"))) {
Laurent Montel's avatar
Laurent Montel committed
79
80
            ev->acceptProposedAction();
            return true;
81
        }
Laurent Montel's avatar
Laurent Montel committed
82
83
84
        break;
    }
    case QEvent::Drop: {
Nicolas Carion's avatar
Nicolas Carion committed
85
        auto *ev = static_cast<QDropEvent *>(event);
Laurent Montel's avatar
Laurent Montel committed
86
        if (ev) {
87
88
89
90
91
            QStringList effectData;
            effectData << QString::fromUtf8(ev->mimeData()->data(QStringLiteral("kdenlive/effect")));
            QStringList source = QString::fromUtf8(ev->mimeData()->data(QStringLiteral("kdenlive/effectsource"))).split(QLatin1Char('-'));
            effectData << source;
            emit addEffect(effectData);
Laurent Montel's avatar
Laurent Montel committed
92
93
            ev->accept();
            return true;
94
        }
Laurent Montel's avatar
Laurent Montel committed
95
96
        break;
    }
97
    if (event->type() == QEvent::KeyPress) {
Nicolas Carion's avatar
Nicolas Carion committed
98
        auto *ev = static_cast<QKeyEvent *>(event);
99
100
101
102
103
        if (ev) {
            emit doKeyPressEvent(ev);
            return true;
        }
    }
104
105
106
    default:
        break;
    }
107
108
    return QObject::eventFilter(obj, event);
}
109

Nicolas Carion's avatar
Nicolas Carion committed
110
Monitor::Monitor(Kdenlive::MonitorId id, MonitorManager *manager, QWidget *parent)
111
112
113
    : AbstractMonitor(id, manager, parent)
    , m_controller(nullptr)
    , m_glMonitor(nullptr)
114
    , m_snaps(new SnapModel())
115
116
117
118
119
120
    , m_splitEffect(nullptr)
    , m_splitProducer(nullptr)
    , m_dragStarted(false)
    , m_recManager(nullptr)
    , m_loopClipAction(nullptr)
    , m_contextMenu(nullptr)
121
    , m_markerMenu(nullptr)
122
    , m_audioChannels(nullptr)
123
124
125
    , m_loopClipTransition(true)
    , m_editMarker(nullptr)
    , m_forceSizeFactor(0)
126
    , m_offset(id == Kdenlive::ProjectMonitor ? TimelineModel::seekDuration : 0)
127
    , m_lastMonitorSceneType(MonitorSceneDefault)
Nicolas Carion's avatar
Nicolas Carion committed
128
{
Nicolas Carion's avatar
Nicolas Carion committed
129
    auto *layout = new QVBoxLayout;
130
    layout->setContentsMargins(0, 0, 0, 0);
131
    layout->setSpacing(0);
132
133
    // Create container widget
    m_glWidget = new QWidget;
134
    m_glWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
Nicolas Carion's avatar
Nicolas Carion committed
135
    auto *glayout = new QGridLayout(m_glWidget);
136
137
138
    glayout->setSpacing(0);
    glayout->setContentsMargins(0, 0, 0, 0);
    // Create QML OpenGL widget
Vincent Pinon's avatar
Vincent Pinon committed
139
    m_glMonitor = new GLWidget(id);
Laurent Montel's avatar
Laurent Montel committed
140
    connect(m_glMonitor, &GLWidget::passKeyEvent, this, &Monitor::doKeyPressEvent);
141
    connect(m_glMonitor, &GLWidget::panView, this, &Monitor::panView);
142
    connect(m_glMonitor->getControllerProxy(), &MonitorProxy::requestSeek, this, &Monitor::processSeek, Qt::DirectConnection);
143
    connect(m_glMonitor->getControllerProxy(), &MonitorProxy::positionChanged, this, &Monitor::slotSeekPosition);
144

Laurent Montel's avatar
Laurent Montel committed
145
    m_videoWidget = QWidget::createWindowContainer(qobject_cast<QWindow *>(m_glMonitor));
146
    m_videoWidget->setAcceptDrops(true);
Nicolas Carion's avatar
Nicolas Carion committed
147
    auto *leventEater = new QuickEventEater(this);
148
    m_videoWidget->installEventFilter(leventEater);
149
    connect(leventEater, &QuickEventEater::addEffect, this, &Monitor::slotAddEffect);
150
    connect(leventEater, &QuickEventEater::doKeyPressEvent, this, &Monitor::doKeyPressEvent);
151

152
153
154
    m_qmlManager = new QmlManager(m_glMonitor);
    connect(m_qmlManager, &QmlManager::effectChanged, this, &Monitor::effectChanged);
    connect(m_qmlManager, &QmlManager::effectPointsChanged, this, &Monitor::effectPointsChanged);
155
    connect(m_qmlManager, &QmlManager::activateTrack, this, [&](int ix) {
Vincent Pinon's avatar
Vincent Pinon committed
156
        emit activateTrack(ix, false);
157
    });
158

159
    glayout->addWidget(m_videoWidget, 0, 0);
160
161
162
163
164
165
    m_verticalScroll = new QScrollBar(Qt::Vertical);
    glayout->addWidget(m_verticalScroll, 0, 1);
    m_verticalScroll->hide();
    m_horizontalScroll = new QScrollBar(Qt::Horizontal);
    glayout->addWidget(m_horizontalScroll, 1, 0);
    m_horizontalScroll->hide();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
166
167
    connect(m_horizontalScroll, &QAbstractSlider::valueChanged, this, &Monitor::setOffsetX);
    connect(m_verticalScroll, &QAbstractSlider::valueChanged, this, &Monitor::setOffsetY);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
168
    connect(m_glMonitor, &GLWidget::frameDisplayed, this, &Monitor::onFrameDisplayed, Qt::DirectConnection);
169
    connect(m_glMonitor, &GLWidget::mouseSeek, this, &Monitor::slotMouseSeek);
Vincent Pinon's avatar
Vincent Pinon committed
170
    connect(m_glMonitor, &GLWidget::monitorPlay, this, &Monitor::slotPlay);
Laurent Montel's avatar
Laurent Montel committed
171
    connect(m_glMonitor, &GLWidget::startDrag, this, &Monitor::slotStartDrag);
Vincent Pinon's avatar
Vincent Pinon committed
172
    connect(m_glMonitor, &GLWidget::switchFullScreen, this, &Monitor::slotSwitchFullScreen);
Laurent Montel's avatar
Laurent Montel committed
173
    connect(m_glMonitor, &GLWidget::zoomChanged, this, &Monitor::setZoom);
174
    connect(m_glMonitor, SIGNAL(lockMonitor(bool)), this, SLOT(slotLockMonitor(bool)), Qt::DirectConnection);
Laurent Montel's avatar
Laurent Montel committed
175
    connect(m_glMonitor, &GLWidget::showContextMenu, this, &Monitor::slotShowMenu);
176
    connect(m_glMonitor, &GLWidget::gpuNotSupported, this, &Monitor::gpuError);
177

178
179
    m_glWidget->setMinimumSize(QSize(320, 180));
    layout->addWidget(m_glWidget, 10);
180
181
182
    layout->addStretch();

    // Tool bar buttons
183
    m_toolbar = new QToolBar(this);
184
185
186
    int size = style()->pixelMetric(QStyle::PM_SmallIconSize);
    QSize iconSize(size, size);
    m_toolbar->setIconSize(iconSize);
187

188
    auto *scalingAction = new QComboBox(this);
189
190
191
    scalingAction->setToolTip(i18n("Preview resolution - lower resolution means faster preview"));
    // Combobox padding is bad, so manually add a space before text
    scalingAction->addItems({QStringLiteral(" ") + i18n("1:1"),QStringLiteral(" ") + i18n("720p"),QStringLiteral(" ") + i18n("540p"),QStringLiteral(" ") + i18n("360p"),QStringLiteral(" ") + i18n("270p")});
Vincent Pinon's avatar
Vincent Pinon committed
192
    connect(scalingAction, QOverload<int>::of(&QComboBox::activated), this, [this] (int index) {
193
194
195
196
        switch (index) {
            case 1:
                KdenliveSettings::setPreviewScaling(2);
                break;
197
198
199
            case 2:
                KdenliveSettings::setPreviewScaling(4);
                break;
200
            case 3:
201
202
                KdenliveSettings::setPreviewScaling(8);
                break;
203
            case 4:
204
205
206
                KdenliveSettings::setPreviewScaling(16);
                break;
            default:
207
                KdenliveSettings::setPreviewScaling(0);
208
        }
Vincent Pinon's avatar
Vincent Pinon committed
209
210
        emit m_monitorManager->scalingChanged();
        emit m_monitorManager->updatePreviewScaling();
211
        m_monitorManager->refreshMonitors();
212
    });
213

Vincent Pinon's avatar
Vincent Pinon committed
214
    connect(manager, &MonitorManager::updatePreviewScaling, this, [this, scalingAction]() {
215
        m_glMonitor->updateScaling();
216
217
        switch (KdenliveSettings::previewScaling()) {
            case 2:
218
                scalingAction->setCurrentIndex(1);
219
220
                break;
            case 4:
221
                scalingAction->setCurrentIndex(2);
222
223
                break;
            case 8:
224
                scalingAction->setCurrentIndex(3);
225
226
                break;
            case 16:
227
                scalingAction->setCurrentIndex(4);
228
229
                break;
            default:
230
                scalingAction->setCurrentIndex(0);
231
232
233
                break;
        }
    });
234
235
236
237
    scalingAction->setFrame(false);
    scalingAction->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
    m_toolbar->addWidget(scalingAction);
    m_toolbar->addSeparator();
238

239
240
    if (id == Kdenlive::ClipMonitor) {
        // Add options for recording
241
        m_recManager = new RecManager(this);
Vincent Pinon's avatar
Vincent Pinon committed
242
        connect(m_recManager, &RecManager::warningMessage, this, &Monitor::warningMessage);
243
        connect(m_recManager, &RecManager::addClipToProject, this, &Monitor::addClipToProject);
244
245
246
247
248
249
        // Show timeline clip usage
        connect(pCore.get(), &Core::clipInstanceResized, this, [this](const QString &binId) {
            if (m_controller && activeClipId() == binId) {
                m_controller->checkClipBounds();
            }
        });
250

251
        m_toolbar->addAction(manager->getAction(QStringLiteral("insert_project_tree")));
252
        m_toolbar->setToolTip(i18n("Insert Zone to Project Bin"));
253
        m_toolbar->addSeparator();
254
255
256
257
        m_streamsButton = new QToolButton(this);
        m_streamsButton->setPopupMode(QToolButton::InstantPopup);
        m_streamsButton->setIcon(QIcon::fromTheme(QStringLiteral("speaker")));
        m_streamAction = m_toolbar->addWidget(m_streamsButton);
258
        m_audioChannels = new QMenu(this);
259
260
        m_streamsButton->setMenu(m_audioChannels);
        m_streamAction->setVisible(false);
Vincent Pinon's avatar
Vincent Pinon committed
261
        connect(m_audioChannels, &QMenu::triggered, this, [this] (QAction *ac) {
262
            //m_audioChannels->show();
263
264
            QList <QAction*> actions = m_audioChannels->actions();
            QMap <int, QString> enabledStreams;
265
266
267
268
269
270
            if (ac->data().toInt() == INT_MAX) {
                // Merge stream selected, clear all others
                enabledStreams.clear();
                enabledStreams.insert(INT_MAX, i18n("Merged streams"));
                // Disable all other streams
                QSignalBlocker bk(m_audioChannels);
Vincent Pinon's avatar
Vincent Pinon committed
271
                for (auto act : qAsConst(actions)) {
272
273
274
275
276
                    if (act->isChecked() && act != ac) {
                        act->setChecked(false);
                    }
                }
            } else {
Vincent Pinon's avatar
Vincent Pinon committed
277
                for (auto act : qAsConst(actions)) {
278
279
280
281
282
283
284
285
286
                    if (act->isChecked()) {
                        // Audio stream is selected
                        if (act->data().toInt() == INT_MAX) {
                            QSignalBlocker bk(m_audioChannels);
                            act->setChecked(false);
                        } else {
                            enabledStreams.insert(act->data().toInt(), act->text().remove(QLatin1Char('&')));
                        }
                    }
287
288
289
290
291
292
293
294
                }
            }
            if (!enabledStreams.isEmpty()) {
                // Only 1 stream wanted, easy
                QMap <QString, QString> props;
                props.insert(QStringLiteral("audio_index"), QString::number(enabledStreams.firstKey()));
                QList <int> streams = enabledStreams.keys();
                QStringList astreams;
Vincent Pinon's avatar
Vincent Pinon committed
295
                for (const int st : qAsConst(streams)) {
296
297
298
299
300
301
302
303
304
305
306
307
                    astreams << QString::number(st);
                }
                props.insert(QStringLiteral("kdenlive:active_streams"), astreams.join(QLatin1Char(';')));
                m_controller->setProperties(props, true);
            } else {
                // No active stream
                QMap <QString, QString> props;
                props.insert(QStringLiteral("audio_index"), QStringLiteral("-1"));
                props.insert(QStringLiteral("kdenlive:active_streams"), QStringLiteral("-1"));
                m_controller->setProperties(props, true);
            }
        });
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
308
    } else if (id == Kdenlive::ProjectMonitor) {
309
        connect(m_glMonitor, &GLWidget::paused, m_monitorManager, &MonitorManager::cleanMixer);
310
311
    }

Julius Künzel's avatar
Julius Künzel committed
312
313
314
315
316
317
318
319
320
321
322
323
324

    QAction *markIn = new QAction(QIcon::fromTheme(QStringLiteral("zone-in")), i18n("Set Zone In"), this);
    QAction *markOut = new QAction(QIcon::fromTheme(QStringLiteral("zone-out")), i18n("Set Zone Out"), this);
    m_toolbar->addAction(markIn);
    m_toolbar->addAction(markOut);
    connect(markIn, &QAction::triggered, this, [&, manager]() {
        m_monitorManager->activateMonitor(m_id);
        manager->getAction(QStringLiteral("mark_in"))->trigger();
    });
    connect(markOut, &QAction::triggered, this, [&, manager]() {
        m_monitorManager->activateMonitor(m_id);
        manager->getAction(QStringLiteral("mark_out"))->trigger();
    });
325
326
327
328
    // Per monitor rewind action
    QAction *rewind = new QAction(QIcon::fromTheme(QStringLiteral("media-seek-backward")), i18n("Rewind"), this);
    m_toolbar->addAction(rewind);
    connect(rewind, &QAction::triggered, this, &Monitor::slotRewind);
329

Nicolas Carion's avatar
Nicolas Carion committed
330
    auto *playButton = new QToolButton(m_toolbar);
331
    m_playMenu = new QMenu(i18n("Play..."), this);
332
    connect(m_playMenu, &QMenu::aboutToShow, this, &Monitor::slotActivateMonitor);
Laurent Montel's avatar
Laurent Montel committed
333
    QAction *originalPlayAction = static_cast<KDualAction *>(manager->getAction(QStringLiteral("monitor_play")));
334
    m_playAction = new KDualAction(i18n("Play"), i18n("Pause"), this);
335
336
    m_playAction->setInactiveIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
    m_playAction->setActiveIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause")));
337

338
    QString strippedTooltip = m_playAction->toolTip().remove(QRegularExpression(QStringLiteral("\\s\\(.*\\)")));
339
340
    // append shortcut if it exists for action
    if (originalPlayAction->shortcut() == QKeySequence(0)) {
Laurent Montel's avatar
Laurent Montel committed
341
342
        m_playAction->setToolTip(strippedTooltip);
    } else {
343
        m_playAction->setToolTip(strippedTooltip + QStringLiteral(" (") + originalPlayAction->shortcut().toString() + QLatin1Char(')'));
344
    }
345
    m_playMenu->addAction(m_playAction);
346
    connect(m_playAction, &QAction::triggered, this, &Monitor::slotSwitchPlay);
347
348
349

    playButton->setMenu(m_playMenu);
    playButton->setPopupMode(QToolButton::MenuButtonPopup);
350
    m_toolbar->addWidget(playButton);
351
352
353
354

    // Per monitor forward action
    QAction *forward = new QAction(QIcon::fromTheme(QStringLiteral("media-seek-forward")), i18n("Forward"), this);
    m_toolbar->addAction(forward);
Vincent Pinon's avatar
Vincent Pinon committed
355
    connect(forward, &QAction::triggered, this, [this]() {
356
357
        Monitor::slotForward();
    });
358
359

    playButton->setDefaultAction(m_playAction);
360
    m_configMenu = new QMenu(i18n("Misc..."), this);
361

Julius Künzel's avatar
Julius Künzel committed
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380

    if (id == Kdenlive::ClipMonitor) {
        m_markerMenu = new QMenu(i18n("Go to marker..."), this);
    } else {
        m_markerMenu = new QMenu(i18n("Go to guide..."), this);
    }
    m_markerMenu->setEnabled(false);
    m_configMenu->addMenu(m_markerMenu);
    connect(m_markerMenu, &QMenu::triggered, this, &Monitor::slotGoToMarker);
    m_forceSize = new KSelectAction(QIcon::fromTheme(QStringLiteral("transform-scale")), i18n("Force Monitor Size"), this);
    QAction *fullAction = m_forceSize->addAction(QIcon(), i18n("Force 100%"));
    fullAction->setData(100);
    QAction *halfAction = m_forceSize->addAction(QIcon(), i18n("Force 50%"));
    halfAction->setData(50);
    QAction *freeAction = m_forceSize->addAction(QIcon(), i18n("Free Resize"));
    freeAction->setData(0);
    m_configMenu->addAction(m_forceSize);
    m_forceSize->setCurrentAction(freeAction);
    connect(m_forceSize, static_cast<void (KSelectAction::*)(QAction *)>(&KSelectAction::triggered), this, &Monitor::slotForceSize);
381

382
383
384
    // Create Volume slider popup
    m_audioSlider = new QSlider(Qt::Vertical);
    m_audioSlider->setRange(0, 100);
385
    m_audioSlider->setValue(KdenliveSettings::volume());
386
    connect(m_audioSlider, &QSlider::valueChanged, this, &Monitor::slotSetVolume);
Nicolas Carion's avatar
Nicolas Carion committed
387
    auto *widgetslider = new QWidgetAction(this);
388
389
    widgetslider->setText(i18n("Audio volume"));
    widgetslider->setDefaultWidget(m_audioSlider);
390
391
    auto *menu = new QMenu(i18n("Volume"), this);
    menu->setIcon(QIcon::fromTheme(QStringLiteral("audio-volume-medium")));
392
    menu->addAction(widgetslider);
393
    m_configMenu->addMenu(menu);
394

395
396
397
398
399
400
401
402
403
    if (m_id == Kdenlive::ClipMonitor) {
        m_background = new KSelectAction(QIcon::fromTheme(QStringLiteral("paper-color")), i18n("Background Color"), this);
        QAction *blackAction = m_background->addAction(QIcon(), i18n("Black"));
        blackAction->setData("black");
        QAction *whiteAction = m_background->addAction(QIcon(), i18n("White"));
        whiteAction->setData("white");
        QAction *pinkAction = m_background->addAction(QIcon(), i18n("Pink"));
        pinkAction->setData("#ff00ff");
        m_configMenu->addAction(m_background);
404
405
406
407
408
409
410
        if (KdenliveSettings::monitor_background() == whiteAction->data().toString()) {
            m_background->setCurrentAction(whiteAction);
        } else if (KdenliveSettings::monitor_background() == pinkAction->data().toString()) {
            m_background->setCurrentAction(pinkAction);
        } else {
            m_background->setCurrentAction(blackAction);
        }
411
412
413
414
415
416
        connect(m_background, static_cast<void (KSelectAction::*)(QAction *)>(&KSelectAction::triggered), this, [this](QAction *a){
            KdenliveSettings::setMonitor_background(a->data().toString());
            buildBackgroundedProducer(position());
        });
    }

417
    /*QIcon icon;
Laurent Montel's avatar
Laurent Montel committed
418
    if (KdenliveSettings::volume() == 0) {
419
        icon = QIcon::fromTheme(QStringLiteral("audio-volume-muted"));
Laurent Montel's avatar
Laurent Montel committed
420
    } else {
421
        icon = QIcon::fromTheme(QStringLiteral("audio-volume-medium"));
Laurent Montel's avatar
Laurent Montel committed
422
    }
423
    m_audioButton->setIcon(icon);*/
424

425
    setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
426
427
    setLayout(layout);
    setMinimumHeight(200);
428

429
430
    connect(this, &Monitor::scopesClear, m_glMonitor, &GLWidget::releaseAnalyse, Qt::DirectConnection);
    connect(m_glMonitor, &GLWidget::analyseFrame, this, &Monitor::frameUpdated);
431
    m_timePos = new TimecodeDisplay(pCore->timecode(), this);
432

433
    if (id == Kdenlive::ProjectMonitor) {
Nicolas Carion's avatar
linting    
Nicolas Carion committed
434
435
        // TODO: reimplement
        // connect(render, &Render::durationChanged, this, &Monitor::durationChanged);
436
        connect(m_glMonitor->getControllerProxy(), &MonitorProxy::saveZone, this, &Monitor::zoneUpdated);
437
        connect(m_glMonitor->getControllerProxy(), &MonitorProxy::saveZoneWithUndo, this, &Monitor::zoneUpdatedWithUndo);
438
    } else if (id == Kdenlive::ClipMonitor) {
439
        connect(m_glMonitor->getControllerProxy(), &MonitorProxy::saveZone, this, &Monitor::updateClipZone);
440
    }
441
    m_glMonitor->getControllerProxy()->setTimeCode(m_timePos);
442
    connect(m_glMonitor->getControllerProxy(), &MonitorProxy::triggerAction, pCore.get(), &Core::triggerAction);
443
444
445
    connect(m_glMonitor->getControllerProxy(), &MonitorProxy::seekNextKeyframe, this, &Monitor::seekToNextKeyframe);
    connect(m_glMonitor->getControllerProxy(), &MonitorProxy::seekPreviousKeyframe, this, &Monitor::seekToPreviousKeyframe);
    connect(m_glMonitor->getControllerProxy(), &MonitorProxy::addRemoveKeyframe, this, &Monitor::addRemoveKeyframe);
446
    connect(m_glMonitor->getControllerProxy(), &MonitorProxy::seekToKeyframe, this, &Monitor::slotSeekToKeyFrame);
447

448
    m_toolbar->addAction(manager->getAction(QStringLiteral("monitor_editmode")));
449

450
451
452
    m_toolbar->addSeparator();
    m_toolbar->addWidget(m_timePos);

Nicolas Carion's avatar
Nicolas Carion committed
453
    auto *configButton = new QToolButton(m_toolbar);
454
    configButton->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-menu")));
455
    configButton->setToolTip(i18n("Options"));
456
    configButton->setMenu(m_configMenu);
457
    configButton->setPopupMode(QToolButton::InstantPopup);
458
    m_toolbar->addWidget(configButton);
459
    m_toolbar->addSeparator();
460
461
    QMargins mrg = m_toolbar->contentsMargins();
    m_audioMeterWidget = new MonitorAudioLevel(m_toolbar->height() - mrg.top() - mrg.bottom(), this);
462
    m_toolbar->addWidget(m_audioMeterWidget);
463
    if (!m_audioMeterWidget->isValid) {
464
465
466
467
        KdenliveSettings::setMonitoraudio(0x01);
        m_audioMeterWidget->setVisibility(false);
    } else {
        m_audioMeterWidget->setVisibility((KdenliveSettings::monitoraudio() & m_id) != 0);
468
    }
469

470
471
472
473
474
475
476
    // Trimming tool bar buttons
    m_trimmingbar = new QToolBar(this);
    m_trimmingbar->setIconSize(iconSize);

    m_trimmingOffset = new QLabel();
    m_trimmingbar->addWidget(m_trimmingOffset);

477
478
479
    m_fiveLess = new QAction(i18n("-5"), this);
    m_trimmingbar->addAction(m_fiveLess);
    connect(m_fiveLess, &QAction::triggered, this, [&](){
480
481
482
        slotTrimmingPos(-5);
        pCore->window()->getCurrentTimeline()->model()->requestSlipSelection(-5, true);
    });
483
484
485
    m_oneLess = new QAction(i18n("-1"), this);
    m_trimmingbar->addAction(m_oneLess);
    connect(m_oneLess, &QAction::triggered, this, [&](){
486
487
488
        slotTrimmingPos(-1);
        pCore->window()->getCurrentTimeline()->model()->requestSlipSelection(-1, true);
    });
489
490
491
    m_oneMore = new QAction(i18n("+1"), this);
    m_trimmingbar->addAction(m_oneMore);
    connect(m_oneMore, &QAction::triggered, this, [&](){
492
493
494
        slotTrimmingPos(1);
        pCore->window()->getCurrentTimeline()->model()->requestSlipSelection(1, true);
    });
495
496
497
    m_fiveMore = new QAction(i18n("+5"), this);
    m_trimmingbar->addAction(m_fiveMore);
    connect(m_fiveMore, &QAction::triggered, this, [&](){
498
499
500
501
        slotTrimmingPos(5);
        pCore->window()->getCurrentTimeline()->model()->requestSlipSelection(5, true);
    });

502
    connect(m_timePos, SIGNAL(timeCodeEditingFinished()), this, SLOT(slotSeek()));
503
    layout->addWidget(m_toolbar);
Laurent Montel's avatar
Laurent Montel committed
504
505
506
    if (m_recManager) {
        layout->addWidget(m_recManager->toolbar());
    }
507
508
    layout->addWidget(m_trimmingbar);
    m_trimmingbar->setVisible(false);
509
510

    // Load monitor overlay qml
511
    loadQmlScene(MonitorSceneDefault);
512

513
514
515
516
517
    // Monitor dropped fps timer
    m_droppedTimer.setInterval(1000);
    m_droppedTimer.setSingleShot(false);
    connect(&m_droppedTimer, &QTimer::timeout, this, &Monitor::checkDrops);

518
519
520
521
    // Info message widget
    m_infoMessage = new KMessageWidget(this);
    layout->addWidget(m_infoMessage);
    m_infoMessage->hide();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
522
523
}

524
525
Monitor::~Monitor()
{
526
    delete m_audioMeterWidget;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
527
    delete m_glMonitor;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
528
529
    delete m_videoWidget;
    delete m_glWidget;
530
531
532
    delete m_timePos;
}

533
534
535
536
537
538
539
540
541
542
void Monitor::setOffsetX(int x)
{
    m_glMonitor->setOffsetX(x, m_horizontalScroll->maximum());
}

void Monitor::setOffsetY(int y)
{
    m_glMonitor->setOffsetY(y, m_verticalScroll->maximum());
}

543
void Monitor::slotGetCurrentImage(bool request)
544
{
545
    m_glMonitor->sendFrameForAnalysis = request;
546
547
548
549
    if (request) {
        slotActivateMonitor();
        refreshMonitor(true);
    }
550
551
    if (request) {
        // Update analysis state
Laurent Montel's avatar
Laurent Montel committed
552
        QTimer::singleShot(500, m_monitorManager, &MonitorManager::checkScopes);
553
554
555
    } else {
        m_glMonitor->releaseAnalyse();
    }
556
557
}

558
void Monitor::slotAddEffect(const QStringList &effect)
559
560
{
    if (m_id == Kdenlive::ClipMonitor) {
Laurent Montel's avatar
Laurent Montel committed
561
        if (m_controller) {
562
            emit addMasterEffect(m_controller->AbstractProjectItem::clipId(), effect);
Laurent Montel's avatar
Laurent Montel committed
563
564
565
        }
    } else {
        emit addEffect(effect);
566
567
568
    }
}

569
570
571
572
573
574
void Monitor::refreshIcons()
{
    QList<QAction *> allMenus = this->findChildren<QAction *>();
    for (int i = 0; i < allMenus.count(); i++) {
        QAction *m = allMenus.at(i);
        QIcon ic = m->icon();
Laurent Montel's avatar
Laurent Montel committed
575
576
577
        if (ic.isNull() || ic.name().isEmpty()) {
            continue;
        }
578
        QIcon newIcon = QIcon::fromTheme(ic.name());
579
580
581
582
583
584
        m->setIcon(newIcon);
    }
    QList<KDualAction *> allButtons = this->findChildren<KDualAction *>();
    for (int i = 0; i < allButtons.count(); i++) {
        KDualAction *m = allButtons.at(i);
        QIcon ic = m->activeIcon();
Laurent Montel's avatar
Laurent Montel committed
585
586
587
        if (ic.isNull() || ic.name().isEmpty()) {
            continue;
        }
588
        QIcon newIcon = QIcon::fromTheme(ic.name());
589
590
        m->setActiveIcon(newIcon);
        ic = m->inactiveIcon();
Laurent Montel's avatar
Laurent Montel committed
591
592
593
        if (ic.isNull() || ic.name().isEmpty()) {
            continue;
        }
594
        newIcon = QIcon::fromTheme(ic.name());
595
596
597
598
        m->setInactiveIcon(newIcon);
    }
}

599
QAction *Monitor::recAction()
600
{
Laurent Montel's avatar
Laurent Montel committed
601
    if (m_recManager) {
602
        return m_recManager->recAction();
Laurent Montel's avatar
Laurent Montel committed
603
    }
Laurent Montel's avatar
Laurent Montel committed
604
    return nullptr;
605
606
}

607
608
609
610
611
void Monitor::slotLockMonitor(bool lock)
{
    m_monitorManager->lockMonitor(m_id, lock);
}

612
void Monitor::setupMenu(QMenu *goMenu, QMenu *overlayMenu, QAction *playZone, QAction *loopZone, QMenu *markerMenu, QAction *loopClip)
613
{
614
    delete m_contextMenu;
615
    m_contextMenu = new QMenu(this);
616
    m_contextMenu->addMenu(m_playMenu);
Laurent Montel's avatar
Laurent Montel committed
617
    if (goMenu) {
618
        m_contextMenu->addMenu(goMenu);
Laurent Montel's avatar
Laurent Montel committed
619
    }
620

621
    if (markerMenu) {
622
        m_contextMenu->addMenu(markerMenu);
Nicolas Carion's avatar
Nicolas Carion committed
623
        QList<QAction *> list = markerMenu->actions();
624
        for (int i = 0; i < list.count(); ++i) {
625
            if (list.at(i)->objectName() == QLatin1String("edit_marker")) {
626
627
628
629
630
                m_editMarker = list.at(i);
                break;
            }
        }
    }
631

632
633
    m_playMenu->addAction(playZone);
    m_playMenu->addAction(loopZone);
634
635
636
637
    if (loopClip) {
        m_loopClipAction = loopClip;
        m_playMenu->addAction(loopClip);
    }
638

639
    m_contextMenu->addMenu(m_markerMenu);
640
    if (m_id == Kdenlive::ClipMonitor) {
641
        //m_contextMenu->addAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save zone"), this, SLOT(slotSaveZone()));
Nicolas Carion's avatar
Nicolas Carion committed
642
        QAction *extractZone =
643
            m_configMenu->addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Extract Zone"), this, SLOT(slotExtractCurrentZone()));
644
        m_contextMenu->addAction(extractZone);
645
        m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("insert_project_tree")));
646
    }
647
648
    m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("extract_frame")));
    m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("extract_frame_to_project")));
649
    m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("add_project_note")));
650

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
651
    if (m_id == Kdenlive::ProjectMonitor) {
652
        m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("monitor_multitrack")));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
653
    } else if (m_id == Kdenlive::ClipMonitor) {
654
655
        QAction *setThumbFrame =
            m_contextMenu->addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Set current image as thumbnail"), this, SLOT(slotSetThumbFrame()));
656
        m_configMenu->addAction(setThumbFrame);
657
658
659
        QAction *alwaysShowAudio =
            new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-show-audiothumb")), i18n("Always show audio thumbnails"), this);
        alwaysShowAudio->setCheckable(true);
Vincent Pinon's avatar
Vincent Pinon committed
660
        connect(alwaysShowAudio, &QAction::triggered, this, [this](bool checked) {
661
662
663
664
665
666
            KdenliveSettings::setAlwaysShowMonitorAudio(checked);
            m_glMonitor->rootObject()->setProperty("permanentAudiothumb", checked);
        });
        alwaysShowAudio->setChecked(KdenliveSettings::alwaysShowMonitorAudio());
        m_contextMenu->addAction(alwaysShowAudio);
        m_configMenu->addAction(alwaysShowAudio);
667
668
    }

Laurent Montel's avatar
Laurent Montel committed
669
    if (overlayMenu) {
670
        m_contextMenu->addMenu(overlayMenu);
Laurent Montel's avatar
Laurent Montel committed
671
    }
672

673
674
    QAction *switchAudioMonitor = m_configMenu->addAction(i18n("Show Audio Levels"), this, SLOT(slotSwitchAudioMonitor()));
    switchAudioMonitor->setCheckable(true);
Nicolas Carion's avatar
Nicolas Carion committed
675
    switchAudioMonitor->setChecked((KdenliveSettings::monitoraudio() & m_id) != 0);
676
677
    
    if (m_id == Kdenlive::ClipMonitor) {
678
        QAction *recordTimecode = new QAction(i18n("Show Source Timecode"), this);
679
680
681
682
683
        recordTimecode->setCheckable(true);
        connect(recordTimecode, &QAction::triggered, this, &Monitor::slotSwitchRecTimecode);
        recordTimecode->setChecked(KdenliveSettings::rectimecode());
        m_configMenu->addAction(recordTimecode);
    }
684

685
686
687
    // For some reason, the frame in QAbstracSpinBox (base class of TimeCodeDisplay) needs to be displayed once, then hidden
    // or it will never appear (supposed to appear on hover).
    m_timePos->setFrame(false);
688
689
}

690
691
692
693
694
695
void Monitor::slotGoToMarker(QAction *action)
{
    int pos = action->data().toInt();
    slotSeek(pos);
}

696
void Monitor::slotForceSize(QAction *a)
697
{
698
699
700
701
702
    int resizeType = a->data().toInt();
    int profileWidth = 320;
    int profileHeight = 200;
    if (resizeType > 0) {
        // calculate size
Vincent Pinon's avatar
Vincent Pinon committed
703
        QRect r = QApplication::primaryScreen()->geometry();
704
        profileHeight = m_glMonitor->profileSize().height() * resizeType / 100;
Vincent Pinon's avatar
Vincent Pinon committed
705
        profileWidth = int(pCore->getCurrentProfile()->dar() * profileHeight);
706
707
        if (profileWidth > r.width() * 0.8 || profileHeight > r.height() * 0.7) {
            // reset action to free resize
Nicolas Carion's avatar
format    
Nicolas Carion committed
708
            const QList<QAction *> list = m_forceSize->actions();
709
            for (QAction *ac : list) {
710
                if (ac->data().toInt() == m_forceSizeFactor) {
711
712
713
714
                    m_forceSize->setCurrentAction(ac);
                    break;
                }
            }
715
            warningMessage(i18n("Your screen resolution is not sufficient for this action"));
716
717
            return;
        }
718
    }
719
    switch (resizeType) {
Laurent Montel's avatar
Laurent Montel committed
720
721
722
723
    case 100:
    case 50:
        // resize full size
        setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
724
        profileHeight += m_glMonitor->m_displayRulerHeight;
Laurent Montel's avatar
Laurent Montel committed
725
726
        m_videoWidget->setMinimumSize(profileWidth, profileHeight);
        m_videoWidget->setMaximumSize(profileWidth, profileHeight);
727
        setMinimumSize(QSize(profileWidth, profileHeight + m_toolbar->height()));
Laurent Montel's avatar
Laurent Montel committed
728
729
        break;
    default:
730
731
        // Free resize
        m_videoWidget->setMinimumSize(profileWidth, profileHeight);
732
        m_videoWidget->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
733
        setMinimumSize(QSize(profileWidth, profileHeight + m_toolbar->height() + m_glMonitor->getControllerProxy()->rulerHeight()));
734
        setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
735
        break;
736
    }
737
    m_forceSizeFactor = resizeType;
738
739
740
    updateGeometry();
}

741
742
743
744
745
void Monitor::buildBackgroundedProducer(int pos) {
    if (KdenliveSettings::monitor_background() != "black") {
        Mlt::Tractor trac(pCore->getCurrentProfile()->profile());
        QString color = QString("color:%1").arg(KdenliveSettings::monitor_background());
        std::shared_ptr<Mlt::Producer> bg(new Mlt::Producer(*trac.profile(), color.toUtf8().constData()));
746
747
748
        int maxLength = m_controller->originalProducer()->get_length();
        bg->set("length", maxLength);
        bg->set("out", maxLength - 1);
749
750
751
752
753
754
755
756
757
758
759
760
761
        trac.set_track(*bg.get(), 0);
        trac.set_track(*m_controller->originalProducer().get(), 1);
        QString composite = TransitionsRepository::get()->getCompositingTransition();
        std::unique_ptr<Mlt::Transition> transition = TransitionsRepository::get()->getTransition(composite);
        transition->set("always_active", 1);
        transition->set_tracks(0, 1);
        trac.plant_transition(*transition.get(), 0, 1);
        m_glMonitor->setProducer(std::make_shared<Mlt::Producer>(trac), isActive(), pos);
    } else {
       m_glMonitor->setProducer(m_controller->originalProducer(), isActive(), pos);
    }
}

762

763
void Monitor::updateMarkers()
764
{
765
    if (m_controller) {
766
        m_markerMenu->clear();
767
        QList<CommentedTime> markers = m_controller->getMarkerModel()->getAllMarkers();
768
        if (!markers.isEmpty()) {
769
            for (int i = 0; i < markers.count(); ++i) {
Vincent Pinon's avatar
Vincent Pinon committed
770
                int pos = markers.at(i).time().frames(pCore->getCurrentFps());
771
                QString position = pCore->timecode().getTimecode(markers.at(i).time()) + QLatin1Char(' ') + markers.at(i).comment();
772
773
774
                QAction *go = m_markerMenu->addAction(position);
                go->setData(pos);
            }
775
        }
776
777
778
779
        m_markerMenu->setEnabled(!m_markerMenu->isEmpty());
    }
}

780
781
782
783

void Monitor::updateDocumentUuid()
{
    m_glMonitor->rootObject()->setProperty("documentId", pCore->currentDoc()->uuid);
784
785
}

786
787
void Monitor::slotSeekToPreviousSnap()
{
Laurent Montel's avatar
Laurent Montel committed
788
    if (m_controller) {
789
        m_glMonitor->getControllerProxy()->setPosition(getSnapForPos(true).frames(pCore->getCurrentFps()));
Laurent Montel's avatar
Laurent Montel committed
790
    }
791
792
}

793
794
void Monitor::slotSeekToNextSnap()
{
Laurent Montel's avatar
Laurent Montel committed
795
    if (m_controller) {
796
        m_glMonitor->getControllerProxy()->setPosition(getSnapForPos(false).frames(pCore->getCurrentFps()));
Laurent Montel's avatar
Laurent Montel committed
797
    }
798
799
}

800
int Monitor::position()
801
{
802
    return m_glMonitor->getControllerProxy()->getPosition();