monitor.cpp 85.2 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/***************************************************************************
 *   Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA          *
 ***************************************************************************/

Vincent Pinon's avatar
Vincent Pinon committed
20
#include "monitor.h"
Nicolas Carion's avatar
Nicolas Carion committed
21
#include "bin/bin.h"
22
#include "bin/projectclip.h"
Nicolas Carion's avatar
Nicolas Carion committed
23
24
25
26
#include "core.h"
#include "dialogs/profilesdialog.h"
#include "doc/kdenlivedoc.h"
#include "doc/kthumb.h"
27
#include "glwidget.h"
Nicolas Carion's avatar
Nicolas Carion committed
28
29
30
31
#include "kdenlivesettings.h"
#include "lib/audio/audioStreamInfo.h"
#include "mainwindow.h"
#include "mltcontroller/clipcontroller.h"
32
#include "monitorproxy.h"
33
#include "profiles/profilemodel.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
34
#include "project/projectmanager.h"
35
#include "qmlmanager.h"
36
#include "recmanager.h"
37
38
#include "jobs/jobmanager.h"
#include "jobs/cutclipjob.h"
39
#include "scopes/monitoraudiolevel.h"
40
#include "timeline2/model/snapmodel.hpp"
41
#include "transitions/transitionsrepository.hpp"
42
#include "utils/thumbnailcache.hpp"
43

44
#include "klocalizedstring.h"
45
#include <KDualAction>
Nicolas Carion's avatar
Nicolas Carion committed
46
#include <KFileWidget>
Nicolas Carion's avatar
Nicolas Carion committed
47
48
49
#include <KMessageWidget>
#include <KRecentDirs>
#include <KSelectAction>
50
#include <KWindowConfig>
51
#include <KColorScheme>
Vincent Pinon's avatar
Vincent Pinon committed
52
#include <kio_version.h>
53

Laurent Montel's avatar
Laurent Montel committed
54
#include "kdenlive_debug.h"
Vincent Pinon's avatar
Vincent Pinon committed
55
#include <QScreen>
56
#include <QDrag>
Nicolas Carion's avatar
Nicolas Carion committed
57
#include <QMenu>
58
#include <QMimeData>
Nicolas Carion's avatar
Nicolas Carion committed
59
#include <QMouseEvent>
60
#include <QQuickItem>
61
#include <QScrollBar>
Nicolas Carion's avatar
Nicolas Carion committed
62
63
64
65
#include <QSlider>
#include <QToolBar>
#include <QToolButton>
#include <QVBoxLayout>
66
#include <QWidgetAction>
Nicolas Carion's avatar
Nicolas Carion committed
67
#include <utility>
68
69
#define SEEK_INACTIVE (-1)

70
71
QuickEventEater::QuickEventEater(QObject *parent)
    : QObject(parent)
72
73
74
75
76
{
}

bool QuickEventEater::eventFilter(QObject *obj, QEvent *event)
{
77
    switch (event->type()) {
Laurent Montel's avatar
Laurent Montel committed
78
    case QEvent::DragEnter: {
Nicolas Carion's avatar
Nicolas Carion committed
79
        auto *ev = reinterpret_cast<QDragEnterEvent *>(event);
80
        if (ev->mimeData()->hasFormat(QStringLiteral("kdenlive/effect"))) {
Laurent Montel's avatar
Laurent Montel committed
81
82
            ev->acceptProposedAction();
            return true;
83
        }
Laurent Montel's avatar
Laurent Montel committed
84
85
86
        break;
    }
    case QEvent::DragMove: {
Nicolas Carion's avatar
Nicolas Carion committed
87
        auto *ev = reinterpret_cast<QDragEnterEvent *>(event);
88
        if (ev->mimeData()->hasFormat(QStringLiteral("kdenlive/effect"))) {
Laurent Montel's avatar
Laurent Montel committed
89
90
            ev->acceptProposedAction();
            return true;
91
        }
Laurent Montel's avatar
Laurent Montel committed
92
93
94
        break;
    }
    case QEvent::Drop: {
Nicolas Carion's avatar
Nicolas Carion committed
95
        auto *ev = static_cast<QDropEvent *>(event);
Laurent Montel's avatar
Laurent Montel committed
96
        if (ev) {
97
98
99
100
101
            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
102
103
            ev->accept();
            return true;
104
        }
Laurent Montel's avatar
Laurent Montel committed
105
106
107
108
        break;
    }
    default:
        break;
109
110
111
112
    }
    return QObject::eventFilter(obj, event);
}

113
114
QuickMonitorEventEater::QuickMonitorEventEater(QWidget *parent)
    : QObject(parent)
115
116
117
118
119
120
{
}

bool QuickMonitorEventEater::eventFilter(QObject *obj, QEvent *event)
{
    if (event->type() == QEvent::KeyPress) {
Nicolas Carion's avatar
Nicolas Carion committed
121
        auto *ev = static_cast<QKeyEvent *>(event);
122
123
124
125
126
127
128
        if (ev) {
            emit doKeyPressEvent(ev);
            return true;
        }
    }
    return QObject::eventFilter(obj, event);
}
129

Nicolas Carion's avatar
Nicolas Carion committed
130
Monitor::Monitor(Kdenlive::MonitorId id, MonitorManager *manager, QWidget *parent)
131
132
133
    : AbstractMonitor(id, manager, parent)
    , m_controller(nullptr)
    , m_glMonitor(nullptr)
134
    , m_snaps(new SnapModel())
135
136
137
138
139
140
141
    , m_splitEffect(nullptr)
    , m_splitProducer(nullptr)
    , m_dragStarted(false)
    , m_recManager(nullptr)
    , m_loopClipAction(nullptr)
    , m_sceneVisibilityAction(nullptr)
    , m_contextMenu(nullptr)
142
    , m_markerMenu(nullptr)
143
    , m_audioChannels(nullptr)
144
145
146
    , m_loopClipTransition(true)
    , m_editMarker(nullptr)
    , m_forceSizeFactor(0)
147
    , m_offset(id == Kdenlive::ProjectMonitor ? TimelineModel::seekDuration : 0)
148
    , m_lastMonitorSceneType(MonitorSceneDefault)
Nicolas Carion's avatar
Nicolas Carion committed
149
{
Nicolas Carion's avatar
Nicolas Carion committed
150
    auto *layout = new QVBoxLayout;
151
    layout->setContentsMargins(0, 0, 0, 0);
152
    layout->setSpacing(0);
153
154
    // Create container widget
    m_glWidget = new QWidget;
155
    m_glWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
Nicolas Carion's avatar
Nicolas Carion committed
156
    auto *glayout = new QGridLayout(m_glWidget);
157
158
159
    glayout->setSpacing(0);
    glayout->setContentsMargins(0, 0, 0, 0);
    // Create QML OpenGL widget
Nicolas Carion's avatar
Nicolas Carion committed
160
    m_glMonitor = new GLWidget((int)id);
Laurent Montel's avatar
Laurent Montel committed
161
    connect(m_glMonitor, &GLWidget::passKeyEvent, this, &Monitor::doKeyPressEvent);
162
    connect(m_glMonitor, &GLWidget::panView, this, &Monitor::panView);
163
    connect(m_glMonitor->getControllerProxy(), &MonitorProxy::requestSeek, this, &Monitor::processSeek, Qt::DirectConnection);
164
    connect(m_glMonitor->getControllerProxy(), &MonitorProxy::positionChanged, this, &Monitor::slotSeekPosition);
165

Laurent Montel's avatar
Laurent Montel committed
166
    m_videoWidget = QWidget::createWindowContainer(qobject_cast<QWindow *>(m_glMonitor));
167
    m_videoWidget->setAcceptDrops(true);
Nicolas Carion's avatar
Nicolas Carion committed
168
    auto *leventEater = new QuickEventEater(this);
169
    m_videoWidget->installEventFilter(leventEater);
170
171
    connect(leventEater, &QuickEventEater::addEffect, this, &Monitor::slotAddEffect);

172
173
174
    m_qmlManager = new QmlManager(m_glMonitor);
    connect(m_qmlManager, &QmlManager::effectChanged, this, &Monitor::effectChanged);
    connect(m_qmlManager, &QmlManager::effectPointsChanged, this, &Monitor::effectPointsChanged);
175
    connect(m_qmlManager, &QmlManager::activateTrack, this, &Monitor::activateTrack);
176

Nicolas Carion's avatar
Nicolas Carion committed
177
    auto *monitorEventEater = new QuickMonitorEventEater(this);
178
    m_videoWidget->installEventFilter(monitorEventEater);
Laurent Montel's avatar
Laurent Montel committed
179
    connect(monitorEventEater, &QuickMonitorEventEater::doKeyPressEvent, this, &Monitor::doKeyPressEvent);
180

181
    glayout->addWidget(m_videoWidget, 0, 0);
182
183
184
185
186
187
    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
188
189
    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
190
    connect(m_glMonitor, &GLWidget::frameDisplayed, this, &Monitor::onFrameDisplayed, Qt::DirectConnection);
191
    connect(m_glMonitor, &GLWidget::mouseSeek, this, &Monitor::slotMouseSeek);
Vincent Pinon's avatar
Vincent Pinon committed
192
    connect(m_glMonitor, &GLWidget::monitorPlay, this, &Monitor::slotPlay);
Laurent Montel's avatar
Laurent Montel committed
193
    connect(m_glMonitor, &GLWidget::startDrag, this, &Monitor::slotStartDrag);
Vincent Pinon's avatar
Vincent Pinon committed
194
    connect(m_glMonitor, &GLWidget::switchFullScreen, this, &Monitor::slotSwitchFullScreen);
Laurent Montel's avatar
Laurent Montel committed
195
    connect(m_glMonitor, &GLWidget::zoomChanged, this, &Monitor::setZoom);
196
    connect(m_glMonitor, SIGNAL(lockMonitor(bool)), this, SLOT(slotLockMonitor(bool)), Qt::DirectConnection);
Laurent Montel's avatar
Laurent Montel committed
197
    connect(m_glMonitor, &GLWidget::showContextMenu, this, &Monitor::slotShowMenu);
198
    connect(m_glMonitor, &GLWidget::gpuNotSupported, this, &Monitor::gpuError);
199

200
201
    m_glWidget->setMinimumSize(QSize(320, 180));
    layout->addWidget(m_glWidget, 10);
202
203
204
    layout->addStretch();

    // Tool bar buttons
205
    m_toolbar = new QToolBar(this);
206
207
208
    int size = style()->pixelMetric(QStyle::PM_SmallIconSize);
    QSize iconSize(size, size);
    m_toolbar->setIconSize(iconSize);
209
210
211
212
213

    QComboBox *scalingAction = new QComboBox(this);
    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
214
    connect(scalingAction, QOverload<int>::of(&QComboBox::activated), this, [this] (int index) {
215
216
217
218
        switch (index) {
            case 1:
                KdenliveSettings::setPreviewScaling(2);
                break;
219
220
221
            case 2:
                KdenliveSettings::setPreviewScaling(4);
                break;
222
            case 3:
223
224
                KdenliveSettings::setPreviewScaling(8);
                break;
225
            case 4:
226
227
228
                KdenliveSettings::setPreviewScaling(16);
                break;
            default:
229
                KdenliveSettings::setPreviewScaling(0);
230
        }
Vincent Pinon's avatar
Vincent Pinon committed
231
232
        emit m_monitorManager->scalingChanged();
        emit m_monitorManager->updatePreviewScaling();
233
    });
234

Vincent Pinon's avatar
Vincent Pinon committed
235
    connect(manager, &MonitorManager::updatePreviewScaling, this, [this, scalingAction]() {
236
237
238
        m_glMonitor->updateScaling();
        switch (KdenliveSettings::previewScaling()) {
            case 2:
239
                scalingAction->setCurrentIndex(1);
240
241
                break;
            case 4:
242
                scalingAction->setCurrentIndex(2);
243
244
                break;
            case 8:
245
                scalingAction->setCurrentIndex(3);
246
247
                break;
            case 16:
248
                scalingAction->setCurrentIndex(4);
249
250
                break;
            default:
251
                scalingAction->setCurrentIndex(0);
252
253
254
255
                break;
        }
        refreshMonitorIfActive();
    });
256
257
258
259
    scalingAction->setFrame(false);
    scalingAction->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
    m_toolbar->addWidget(scalingAction);
    m_toolbar->addSeparator();
260

261
262
    m_speedLabel = new QLabel(this);
    m_speedLabel->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
263
    KColorScheme scheme(palette().currentColorGroup(), KColorScheme::Button);
264
    QColor bg = scheme.background(KColorScheme::PositiveBackground).color();
265
266
267
    m_speedLabel->setStyleSheet(QString("padding-left: %4; padding-right: %4;background-color: rgb(%1,%2,%3);").arg(bg.red()).arg(bg.green()).arg(bg.blue()).arg(m_speedLabel->sizeHint().height()/4));
    m_toolbar->addWidget(m_speedLabel);
    m_speedLabel->setFixedWidth(0);
268
269
    if (id == Kdenlive::ClipMonitor) {
        // Add options for recording
270
        m_recManager = new RecManager(this);
Vincent Pinon's avatar
Vincent Pinon committed
271
        connect(m_recManager, &RecManager::warningMessage, this, &Monitor::warningMessage);
272
        connect(m_recManager, &RecManager::addClipToProject, this, &Monitor::addClipToProject);
273

274
        m_toolbar->addAction(manager->getAction(QStringLiteral("insert_project_tree")));
275
        m_toolbar->setToolTip(i18n("Insert Zone to Project Bin"));
276
        m_toolbar->addSeparator();
277
278
279
280
        m_streamsButton = new QToolButton(this);
        m_streamsButton->setPopupMode(QToolButton::InstantPopup);
        m_streamsButton->setIcon(QIcon::fromTheme(QStringLiteral("speaker")));
        m_streamAction = m_toolbar->addWidget(m_streamsButton);
281
        m_audioChannels = new QMenu(this);
282
283
        m_streamsButton->setMenu(m_audioChannels);
        m_streamAction->setVisible(false);
Vincent Pinon's avatar
Vincent Pinon committed
284
        connect(m_audioChannels, &QMenu::triggered, this, [this] (QAction *ac) {
285
            //m_audioChannels->show();
286
287
            QList <QAction*> actions = m_audioChannels->actions();
            QMap <int, QString> enabledStreams;
288
289
290
291
292
293
            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
294
                for (auto act : qAsConst(actions)) {
295
296
297
298
299
                    if (act->isChecked() && act != ac) {
                        act->setChecked(false);
                    }
                }
            } else {
Vincent Pinon's avatar
Vincent Pinon committed
300
                for (auto act : qAsConst(actions)) {
301
302
303
304
305
306
307
308
309
                    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('&')));
                        }
                    }
310
311
312
313
314
315
316
317
                }
            }
            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
318
                for (const int st : qAsConst(streams)) {
319
320
321
322
323
324
325
326
327
328
329
330
                    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
331
    } else if (id == Kdenlive::ProjectMonitor) {
332
        connect(m_glMonitor, &GLWidget::paused, m_monitorManager, &MonitorManager::cleanMixer);
333
334
    }

335
    if (id != Kdenlive::DvdMonitor) {
336
337
338
339
        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);
Vincent Pinon's avatar
Vincent Pinon committed
340
        connect(markIn, &QAction::triggered, this, [&, manager]() {
341
342
343
            m_monitorManager->activateMonitor(m_id);
            manager->getAction(QStringLiteral("mark_in"))->trigger();
        });
Vincent Pinon's avatar
Vincent Pinon committed
344
        connect(markOut, &QAction::triggered, this, [&, manager]() {
345
346
347
            m_monitorManager->activateMonitor(m_id);
            manager->getAction(QStringLiteral("mark_out"))->trigger();
        });
348
    }
349
350
351
352
    // 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);
353

Nicolas Carion's avatar
Nicolas Carion committed
354
    auto *playButton = new QToolButton(m_toolbar);
355
    m_playMenu = new QMenu(i18n("Play..."), this);
356
    connect(m_playMenu, &QMenu::aboutToShow, this, &Monitor::slotActivateMonitor);
Laurent Montel's avatar
Laurent Montel committed
357
    QAction *originalPlayAction = static_cast<KDualAction *>(manager->getAction(QStringLiteral("monitor_play")));
358
    m_playAction = new KDualAction(i18n("Play"), i18n("Pause"), this);
359
360
    m_playAction->setInactiveIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
    m_playAction->setActiveIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause")));
361

Laurent Montel's avatar
Laurent Montel committed
362
    QString strippedTooltip = m_playAction->toolTip().remove(QRegExp(QStringLiteral("\\s\\(.*\\)")));
363
364
    // append shortcut if it exists for action
    if (originalPlayAction->shortcut() == QKeySequence(0)) {
Laurent Montel's avatar
Laurent Montel committed
365
366
        m_playAction->setToolTip(strippedTooltip);
    } else {
367
        m_playAction->setToolTip(strippedTooltip + QStringLiteral(" (") + originalPlayAction->shortcut().toString() + QLatin1Char(')'));
368
    }
369
    m_playMenu->addAction(m_playAction);
370
    connect(m_playAction, &QAction::triggered, this, &Monitor::slotSwitchPlay);
371
372
373

    playButton->setMenu(m_playMenu);
    playButton->setPopupMode(QToolButton::MenuButtonPopup);
374
    m_toolbar->addWidget(playButton);
375
376
377
378

    // 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
379
    connect(forward, &QAction::triggered, this, [this]() {
380
381
        Monitor::slotForward();
    });
382
383

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

386
387
    if (id != Kdenlive::DvdMonitor) {
        if (id == Kdenlive::ClipMonitor) {
388
            m_markerMenu = new QMenu(i18n("Go to marker..."), this);
389
390
        } else {
            m_markerMenu = new QMenu(i18n("Go to guide..."), this);
391
        }
392
393
        m_markerMenu->setEnabled(false);
        m_configMenu->addMenu(m_markerMenu);
Laurent Montel's avatar
Laurent Montel committed
394
        connect(m_markerMenu, &QMenu::triggered, this, &Monitor::slotGoToMarker);
395
        m_forceSize = new KSelectAction(QIcon::fromTheme(QStringLiteral("transform-scale")), i18n("Force Monitor Size"), this);
396
397
398
399
400
401
402
403
        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);
Nicolas Carion's avatar
Nicolas Carion committed
404
        connect(m_forceSize, static_cast<void (KSelectAction::*)(QAction *)>(&KSelectAction::triggered), this, &Monitor::slotForceSize);
405
    }
406

407
408
409
    // Create Volume slider popup
    m_audioSlider = new QSlider(Qt::Vertical);
    m_audioSlider->setRange(0, 100);
Akhil's avatar
Akhil committed
410
    m_audioSlider->setValue(KdenliveSettings::volume());
411
    connect(m_audioSlider, &QSlider::valueChanged, this, &Monitor::slotSetVolume);
Nicolas Carion's avatar
Nicolas Carion committed
412
    auto *widgetslider = new QWidgetAction(this);
413
414
    widgetslider->setText(i18n("Audio volume"));
    widgetslider->setDefaultWidget(m_audioSlider);
415
416
    auto *menu = new QMenu(i18n("Volume"), this);
    menu->setIcon(QIcon::fromTheme(QStringLiteral("audio-volume-medium")));
417
    menu->addAction(widgetslider);
418
    m_configMenu->addMenu(menu);
419

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

428
    setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
429
430
    setLayout(layout);
    setMinimumHeight(200);
431

432
433
    connect(this, &Monitor::scopesClear, m_glMonitor, &GLWidget::releaseAnalyse, Qt::DirectConnection);
    connect(m_glMonitor, &GLWidget::analyseFrame, this, &Monitor::frameUpdated);
434

435
    if (id == Kdenlive::ProjectMonitor) {
Nicolas Carion's avatar
linting    
Nicolas Carion committed
436
437
        // TODO: reimplement
        // connect(render, &Render::durationChanged, this, &Monitor::durationChanged);
438
        connect(m_glMonitor->getControllerProxy(), &MonitorProxy::saveZone, this, &Monitor::zoneUpdated);
439
        connect(m_glMonitor->getControllerProxy(), &MonitorProxy::saveZoneWithUndo, this, &Monitor::zoneUpdatedWithUndo);
440
    } else if (id == Kdenlive::ClipMonitor) {
441
        connect(m_glMonitor->getControllerProxy(), &MonitorProxy::saveZone, this, &Monitor::updateClipZone);
442
    }
443
    connect(m_glMonitor->getControllerProxy(), &MonitorProxy::triggerAction, pCore.get(), &Core::triggerAction);
444
445
446
    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);
447
    connect(m_glMonitor->getControllerProxy(), &MonitorProxy::seekToKeyframe, this, &Monitor::slotSeekToKeyFrame);
448

449
    m_sceneVisibilityAction = new QAction(QIcon::fromTheme(QStringLiteral("transform-crop")), i18n("Show/Hide edit mode"), this);
450
451
    m_sceneVisibilityAction->setCheckable(true);
    m_sceneVisibilityAction->setChecked(KdenliveSettings::showOnMonitorScene());
452
    connect(m_sceneVisibilityAction, &QAction::triggered, this, &Monitor::slotEnableEffectScene);
453
    m_toolbar->addAction(m_sceneVisibilityAction);
454

455
    m_toolbar->addSeparator();
456
    m_timePos = new TimecodeDisplay(pCore->timecode(), this);
457
458
    m_toolbar->addWidget(m_timePos);

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

476
    connect(m_timePos, SIGNAL(timeCodeEditingFinished()), this, SLOT(slotSeek()));
477
    layout->addWidget(m_toolbar);
Laurent Montel's avatar
Laurent Montel committed
478
479
480
    if (m_recManager) {
        layout->addWidget(m_recManager->toolbar());
    }
481
482

    // Load monitor overlay qml
483
    loadQmlScene(MonitorSceneDefault);
484

485
486
487
488
489
    // Monitor dropped fps timer
    m_droppedTimer.setInterval(1000);
    m_droppedTimer.setSingleShot(false);
    connect(&m_droppedTimer, &QTimer::timeout, this, &Monitor::checkDrops);

490
491
492
493
    // Info message widget
    m_infoMessage = new KMessageWidget(this);
    layout->addWidget(m_infoMessage);
    m_infoMessage->hide();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
494
495
}

496
497
Monitor::~Monitor()
{
498
    delete m_audioMeterWidget;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
499
    delete m_glMonitor;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
500
501
    delete m_videoWidget;
    delete m_glWidget;
502
503
504
    delete m_timePos;
}

505
506
507
508
509
510
511
512
513
514
void Monitor::setOffsetX(int x)
{
    m_glMonitor->setOffsetX(x, m_horizontalScroll->maximum());
}

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

515
void Monitor::slotGetCurrentImage(bool request)
516
{
517
    m_glMonitor->sendFrameForAnalysis = request;
518
    Kdenlive::MonitorId id = m_monitorManager->activeMonitor()->id();
519
    m_monitorManager->activateMonitor(m_id);
520
    refreshMonitorIfActive(true);
521
522
    if (request) {
        // Update analysis state
Laurent Montel's avatar
Laurent Montel committed
523
        QTimer::singleShot(500, m_monitorManager, &MonitorManager::checkScopes);
524
525
526
    } else {
        m_glMonitor->releaseAnalyse();
    }
527
    m_monitorManager->activateMonitor(id);
528
529
}

530
void Monitor::slotAddEffect(const QStringList &effect)
531
532
{
    if (m_id == Kdenlive::ClipMonitor) {
Laurent Montel's avatar
Laurent Montel committed
533
        if (m_controller) {
534
            emit addMasterEffect(m_controller->AbstractProjectItem::clipId(), effect);
Laurent Montel's avatar
Laurent Montel committed
535
536
537
        }
    } else {
        emit addEffect(effect);
538
539
540
    }
}

541
542
543
544
545
546
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
547
548
549
        if (ic.isNull() || ic.name().isEmpty()) {
            continue;
        }
550
        QIcon newIcon = QIcon::fromTheme(ic.name());
551
552
553
554
555
556
        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
557
558
559
        if (ic.isNull() || ic.name().isEmpty()) {
            continue;
        }
560
        QIcon newIcon = QIcon::fromTheme(ic.name());
561
562
        m->setActiveIcon(newIcon);
        ic = m->inactiveIcon();
Laurent Montel's avatar
Laurent Montel committed
563
564
565
        if (ic.isNull() || ic.name().isEmpty()) {
            continue;
        }
566
        newIcon = QIcon::fromTheme(ic.name());
567
568
569
570
        m->setInactiveIcon(newIcon);
    }
}

571
QAction *Monitor::recAction()
572
{
Laurent Montel's avatar
Laurent Montel committed
573
    if (m_recManager) {
574
        return m_recManager->recAction();
Laurent Montel's avatar
Laurent Montel committed
575
    }
Laurent Montel's avatar
Laurent Montel committed
576
    return nullptr;
577
578
}

579
580
581
582
583
void Monitor::slotLockMonitor(bool lock)
{
    m_monitorManager->lockMonitor(m_id, lock);
}

584
void Monitor::setupMenu(QMenu *goMenu, QMenu *overlayMenu, QAction *playZone, QAction *loopZone, QMenu *markerMenu, QAction *loopClip)
585
{
586
    delete m_contextMenu;
587
    m_contextMenu = new QMenu(this);
588
    m_contextMenu->addMenu(m_playMenu);
Laurent Montel's avatar
Laurent Montel committed
589
    if (goMenu) {
590
        m_contextMenu->addMenu(goMenu);
Laurent Montel's avatar
Laurent Montel committed
591
    }
592

593
    if (markerMenu) {
594
        m_contextMenu->addMenu(markerMenu);
Nicolas Carion's avatar
Nicolas Carion committed
595
        QList<QAction *> list = markerMenu->actions();
596
        for (int i = 0; i < list.count(); ++i) {
597
            if (list.at(i)->objectName() == QLatin1String("edit_marker")) {
598
599
600
601
602
                m_editMarker = list.at(i);
                break;
            }
        }
    }
603

604
605
    m_playMenu->addAction(playZone);
    m_playMenu->addAction(loopZone);
606
607
608
609
    if (loopClip) {
        m_loopClipAction = loopClip;
        m_playMenu->addAction(loopClip);
    }
610

611
    m_contextMenu->addMenu(m_markerMenu);
612
    if (m_id == Kdenlive::ClipMonitor) {
613
        //m_contextMenu->addAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save zone"), this, SLOT(slotSaveZone()));
Nicolas Carion's avatar
Nicolas Carion committed
614
        QAction *extractZone =
615
            m_configMenu->addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Extract Zone"), this, SLOT(slotExtractCurrentZone()));
616
        m_contextMenu->addAction(extractZone);
617
        m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("insert_project_tree")));
618
    }
619
620
    m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("extract_frame")));
    m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("extract_frame_to_project")));
621
    m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("add_project_note")));
622

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
623
    if (m_id == Kdenlive::ProjectMonitor) {
624
        m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("monitor_multitrack")));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
625
    } else if (m_id == Kdenlive::ClipMonitor) {
626
627
        QAction *setThumbFrame =
            m_contextMenu->addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Set current image as thumbnail"), this, SLOT(slotSetThumbFrame()));
628
        m_configMenu->addAction(setThumbFrame);
629
630
631
        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
632
        connect(alwaysShowAudio, &QAction::triggered, this, [this](bool checked) {
633
634
635
636
637
638
            KdenliveSettings::setAlwaysShowMonitorAudio(checked);
            m_glMonitor->rootObject()->setProperty("permanentAudiothumb", checked);
        });
        alwaysShowAudio->setChecked(KdenliveSettings::alwaysShowMonitorAudio());
        m_contextMenu->addAction(alwaysShowAudio);
        m_configMenu->addAction(alwaysShowAudio);
639
640
    }

Laurent Montel's avatar
Laurent Montel committed
641
    if (overlayMenu) {
642
        m_contextMenu->addMenu(overlayMenu);
Laurent Montel's avatar
Laurent Montel committed
643
    }
644

645
646
    QAction *switchAudioMonitor = m_configMenu->addAction(i18n("Show Audio Levels"), this, SLOT(slotSwitchAudioMonitor()));
    switchAudioMonitor->setCheckable(true);
Nicolas Carion's avatar
Nicolas Carion committed
647
    switchAudioMonitor->setChecked((KdenliveSettings::monitoraudio() & m_id) != 0);
648

649
650
651
    // 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);
652
653
}

654
655
656
657
658
659
void Monitor::slotGoToMarker(QAction *action)
{
    int pos = action->data().toInt();
    slotSeek(pos);
}

660
void Monitor::slotForceSize(QAction *a)
661
{
662
663
664
665
666
    int resizeType = a->data().toInt();
    int profileWidth = 320;
    int profileHeight = 200;
    if (resizeType > 0) {
        // calculate size
Vincent Pinon's avatar
Vincent Pinon committed
667
        QRect r = QApplication::primaryScreen()->geometry();
668
        profileHeight = m_glMonitor->profileSize().height() * resizeType / 100;
669
        profileWidth = pCore->getCurrentProfile()->dar() * profileHeight;
670
671
        if (profileWidth > r.width() * 0.8 || profileHeight > r.height() * 0.7) {
            // reset action to free resize
Nicolas Carion's avatar
format    
Nicolas Carion committed
672
            const QList<QAction *> list = m_forceSize->actions();
673
            for (QAction *ac : list) {
674
                if (ac->data().toInt() == m_forceSizeFactor) {
675
676
677
678
                    m_forceSize->setCurrentAction(ac);
                    break;
                }
            }
679
            warningMessage(i18n("Your screen resolution is not sufficient for this action"));
680
681
            return;
        }
682
    }
683
    switch (resizeType) {
Laurent Montel's avatar
Laurent Montel committed
684
685
686
687
    case 100:
    case 50:
        // resize full size
        setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
688
        profileHeight += m_glMonitor->m_displayRulerHeight;
Laurent Montel's avatar
Laurent Montel committed
689
690
        m_videoWidget->setMinimumSize(profileWidth, profileHeight);
        m_videoWidget->setMaximumSize(profileWidth, profileHeight);
691
        setMinimumSize(QSize(profileWidth, profileHeight + m_toolbar->height()));
Laurent Montel's avatar
Laurent Montel committed
692
693
        break;
    default:
694
695
        // Free resize
        m_videoWidget->setMinimumSize(profileWidth, profileHeight);
696
        m_videoWidget->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
697
        setMinimumSize(QSize(profileWidth, profileHeight + m_toolbar->height() + m_glMonitor->getControllerProxy()->rulerHeight()));
698
        setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
699
        break;
700
    }
701
    m_forceSizeFactor = resizeType;
702
703
704
    updateGeometry();
}

705

706
void Monitor::updateMarkers()
707
{
708
    if (m_controller) {
709
        m_markerMenu->clear();
710
        QList<CommentedTime> markers = m_controller->getMarkerModel()->getAllMarkers();
711
        if (!markers.isEmpty()) {
712
            for (int i = 0; i < markers.count(); ++i) {
713
714
                int pos = (int)markers.at(i).time().frames(pCore->getCurrentFps());
                QString position = pCore->timecode().getTimecode(markers.at(i).time()) + QLatin1Char(' ') + markers.at(i).comment();
715
716
717
                QAction *go = m_markerMenu->addAction(position);
                go->setData(pos);
            }
718
        }
719
720
721
722
        m_markerMenu->setEnabled(!m_markerMenu->isEmpty());
    }
}

Laurent Montel's avatar
Laurent Montel committed
723
void Monitor::setGuides(const QMap<double, QString> &guides)
724
{
Nicolas Carion's avatar
linting    
Nicolas Carion committed
725
    // TODO: load guides model
726
727
    m_markerMenu->clear();
    QMapIterator<double, QString> i(guides);
Laurent Montel's avatar
Laurent Montel committed
728
    QList<CommentedTime> guidesList;
729
730
731
732
    while (i.hasNext()) {
        i.next();
        CommentedTime timeGuide(GenTime(i.key()), i.value());
        guidesList << timeGuide;
733
734
        int pos = (int)timeGuide.time().frames(pCore->getCurrentFps());
        QString position = pCore->timecode().getTimecode(timeGuide.time()) + QLatin1Char(' ') + timeGuide.comment();
735
736
737
        QAction *go = m_markerMenu->addAction(position);
        go->setData(pos);
    }
Nicolas Carion's avatar
linting    
Nicolas Carion committed
738
    // m_ruler->setMarkers(guidesList);
739
740
741
742
    m_markerMenu->setEnabled(!m_markerMenu->isEmpty());
    checkOverlay();
}

743
744
void Monitor::slotSeekToPreviousSnap()
{
Laurent Montel's avatar
Laurent Montel committed
745
    if (m_controller) {
746
        m_glMonitor->getControllerProxy()->setPosition(getSnapForPos(true).frames(pCore->getCurrentFps()));
Laurent Montel's avatar
Laurent Montel committed
747
    }
748
749
}

750
751
void Monitor::slotSeekToNextSnap()
{
Laurent Montel's avatar
Laurent Montel committed
752
    if (m_controller) {
753
        m_glMonitor->getControllerProxy()->setPosition(getSnapForPos(false).frames(pCore->getCurrentFps()));
Laurent Montel's avatar
Laurent Montel committed
754
    }
755
756
}

757
int Monitor::position()
758
{
759
    return m_glMonitor->getControllerProxy()->getPosition();
760
761
}

762
763
GenTime Monitor::getSnapForPos(bool previous)
{
764
    int frame = previous ? m_snaps->getPreviousPoint(m_glMonitor->getCurrentPos()) : m_snaps->getNextPoint(m_glMonitor->getCurrentPos());
Nicolas Carion's avatar
Nicolas Carion committed
765
    return {frame, pCore->getCurrentFps()};
766
767
}

768
void Monitor::slotLoadClipZone(const QPoint &zone)
769
{
770
    m_glMonitor->getControllerProxy()->setZone(zone.x(), zone.y(), false);
771
    checkOverlay();
772
773
}

774
775
void Monitor::slotSetZoneStart()
{
776
    m_glMonitor->getControllerProxy()->setZoneIn(m_glMonitor->getCurrentPos());