monitor.cpp 72.5 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>
Vincent Pinon's avatar
Vincent Pinon committed
51
#include <kio_version.h>
52

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

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

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

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

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

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

170
171
172
173
    m_qmlManager = new QmlManager(m_glMonitor);
    connect(m_qmlManager, &QmlManager::effectChanged, this, &Monitor::effectChanged);
    connect(m_qmlManager, &QmlManager::effectPointsChanged, this, &Monitor::effectPointsChanged);

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

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

197
198
    m_glWidget->setMinimumSize(QSize(320, 180));
    layout->addWidget(m_glWidget, 10);
199
200
201
    layout->addStretch();

    // Tool bar buttons
202
    m_toolbar = new QToolBar(this);
203
204
205
    int size = style()->pixelMetric(QStyle::PM_SmallIconSize);
    QSize iconSize(size, size);
    m_toolbar->setIconSize(iconSize);
206
207
208
    QWidget *sp1 = new QWidget(this);
    sp1->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
    m_toolbar->addWidget(sp1);
209
210
    if (id == Kdenlive::ClipMonitor) {
        // Add options for recording
211
        m_recManager = new RecManager(this);
Vincent Pinon's avatar
Vincent Pinon committed
212
        connect(m_recManager, &RecManager::warningMessage, this, &Monitor::warningMessage);
213
        connect(m_recManager, &RecManager::addClipToProject, this, &Monitor::addClipToProject);
214

215
        m_toolbar->addAction(manager->getAction(QStringLiteral("insert_project_tree")));
216
        m_toolbar->setToolTip(i18n("Insert Zone to Project Bin"));
217
        m_toolbar->addSeparator();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
218
    } else if (id == Kdenlive::ProjectMonitor) {
219
        connect(m_glMonitor, &GLWidget::paused, m_monitorManager, &MonitorManager::cleanMixer);
220
221
    }

222
    if (id != Kdenlive::DvdMonitor) {
223
224
225
226
227
228
229
230
231
232
233
234
        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, [&, manager]() {
            m_monitorManager->activateMonitor(m_id);
            manager->getAction(QStringLiteral("mark_in"))->trigger();
        });
        connect(markOut, &QAction::triggered, [&, manager]() {
            m_monitorManager->activateMonitor(m_id);
            manager->getAction(QStringLiteral("mark_out"))->trigger();
        });
235
    }
236
    m_toolbar->addAction(manager->getAction(QStringLiteral("monitor_seek_backward")));
237

Nicolas Carion's avatar
Nicolas Carion committed
238
    auto *playButton = new QToolButton(m_toolbar);
239
    m_playMenu = new QMenu(i18n("Play..."), this);
Laurent Montel's avatar
Laurent Montel committed
240
    QAction *originalPlayAction = static_cast<KDualAction *>(manager->getAction(QStringLiteral("monitor_play")));
241
    m_playAction = new KDualAction(i18n("Play"), i18n("Pause"), this);
242
243
    m_playAction->setInactiveIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
    m_playAction->setActiveIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause")));
244

Laurent Montel's avatar
Laurent Montel committed
245
    QString strippedTooltip = m_playAction->toolTip().remove(QRegExp(QStringLiteral("\\s\\(.*\\)")));
246
247
    // append shortcut if it exists for action
    if (originalPlayAction->shortcut() == QKeySequence(0)) {
Laurent Montel's avatar
Laurent Montel committed
248
249
        m_playAction->setToolTip(strippedTooltip);
    } else {
250
        m_playAction->setToolTip(strippedTooltip + QStringLiteral(" (") + originalPlayAction->shortcut().toString() + QLatin1Char(')'));
251
    }
252
    m_playMenu->addAction(m_playAction);
253
    connect(m_playAction, &QAction::triggered, this, &Monitor::slotSwitchPlay);
254
255
256

    playButton->setMenu(m_playMenu);
    playButton->setPopupMode(QToolButton::MenuButtonPopup);
257
    m_toolbar->addWidget(playButton);
258
    m_toolbar->addAction(manager->getAction(QStringLiteral("monitor_seek_forward")));
259
260

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

263
264
    if (id != Kdenlive::DvdMonitor) {
        if (id == Kdenlive::ClipMonitor) {
265
            m_markerMenu = new QMenu(i18n("Go to marker..."), this);
266
267
        } else {
            m_markerMenu = new QMenu(i18n("Go to guide..."), this);
268
        }
269
270
        m_markerMenu->setEnabled(false);
        m_configMenu->addMenu(m_markerMenu);
Laurent Montel's avatar
Laurent Montel committed
271
        connect(m_markerMenu, &QMenu::triggered, this, &Monitor::slotGoToMarker);
272
        m_forceSize = new KSelectAction(QIcon::fromTheme(QStringLiteral("transform-scale")), i18n("Force Monitor Size"), this);
273
274
275
276
277
278
279
280
        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
281
        connect(m_forceSize, static_cast<void (KSelectAction::*)(QAction *)>(&KSelectAction::triggered), this, &Monitor::slotForceSize);
282
    }
283

284
285
286
    // Create Volume slider popup
    m_audioSlider = new QSlider(Qt::Vertical);
    m_audioSlider->setRange(0, 100);
Akhil's avatar
Akhil committed
287
    m_audioSlider->setValue(KdenliveSettings::volume());
288
    connect(m_audioSlider, &QSlider::valueChanged, this, &Monitor::slotSetVolume);
Nicolas Carion's avatar
Nicolas Carion committed
289
    auto *widgetslider = new QWidgetAction(this);
290
291
    widgetslider->setText(i18n("Audio volume"));
    widgetslider->setDefaultWidget(m_audioSlider);
292
293
    auto *menu = new QMenu(i18n("Volume"), this);
    menu->setIcon(QIcon::fromTheme(QStringLiteral("audio-volume-medium")));
294
    menu->addAction(widgetslider);
295
    m_configMenu->addMenu(menu);
296

297
    /*QIcon icon;
Laurent Montel's avatar
Laurent Montel committed
298
    if (KdenliveSettings::volume() == 0) {
299
        icon = QIcon::fromTheme(QStringLiteral("audio-volume-muted"));
Laurent Montel's avatar
Laurent Montel committed
300
    } else {
301
        icon = QIcon::fromTheme(QStringLiteral("audio-volume-medium"));
Laurent Montel's avatar
Laurent Montel committed
302
    }
303
    m_audioButton->setIcon(icon);*/
304

305
    setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
306
307
    setLayout(layout);
    setMinimumHeight(200);
308

309
310
    connect(this, &Monitor::scopesClear, m_glMonitor, &GLWidget::releaseAnalyse, Qt::DirectConnection);
    connect(m_glMonitor, &GLWidget::analyseFrame, this, &Monitor::frameUpdated);
311

312
    if (id != Kdenlive::ClipMonitor) {
Nicolas Carion's avatar
linting    
Nicolas Carion committed
313
314
        // TODO: reimplement
        // connect(render, &Render::durationChanged, this, &Monitor::durationChanged);
315
        connect(m_glMonitor->getControllerProxy(), &MonitorProxy::saveZone, this, &Monitor::updateTimelineClipZone);
316
    } else {
317
        connect(m_glMonitor->getControllerProxy(), &MonitorProxy::saveZone, this, &Monitor::updateClipZone);
318
    }
319
    connect(m_glMonitor->getControllerProxy(), &MonitorProxy::triggerAction, pCore.get(), &Core::triggerAction);
320
321
322
    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);
323
    connect(m_glMonitor->getControllerProxy(), &MonitorProxy::seekToKeyframe, this, &Monitor::slotSeekToKeyFrame);
324

325
    m_sceneVisibilityAction = new QAction(QIcon::fromTheme(QStringLiteral("transform-crop")), i18n("Show/Hide edit mode"), this);
326
327
    m_sceneVisibilityAction->setCheckable(true);
    m_sceneVisibilityAction->setChecked(KdenliveSettings::showOnMonitorScene());
328
    connect(m_sceneVisibilityAction, &QAction::triggered, this, &Monitor::slotEnableEffectScene);
329
    m_toolbar->addAction(m_sceneVisibilityAction);
330

331
332
333
334
    m_toolbar->addSeparator();
    m_timePos = new TimecodeDisplay(m_monitorManager->timecode(), this);
    m_toolbar->addWidget(m_timePos);

Nicolas Carion's avatar
Nicolas Carion committed
335
    auto *configButton = new QToolButton(m_toolbar);
336
    configButton->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-menu")));
337
    configButton->setToolTip(i18n("Options"));
338
    configButton->setMenu(m_configMenu);
339
    configButton->setPopupMode(QToolButton::InstantPopup);
340
    m_toolbar->addWidget(configButton);
341
    /*QWidget *spacer = new QWidget(this);
342
    spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
343
344
    m_toolbar->addWidget(spacer);*/
    m_toolbar->addSeparator();
345
346
    int tm = 0;
    int bm = 0;
Laurent Montel's avatar
Laurent Montel committed
347
    m_toolbar->getContentsMargins(nullptr, &tm, nullptr, &bm);
348
    m_audioMeterWidget = new MonitorAudioLevel(m_toolbar->height() - tm - bm, this);
349
    m_toolbar->addWidget(m_audioMeterWidget);
350
    if (!m_audioMeterWidget->isValid) {
351
352
353
354
        KdenliveSettings::setMonitoraudio(0x01);
        m_audioMeterWidget->setVisibility(false);
    } else {
        m_audioMeterWidget->setVisibility((KdenliveSettings::monitoraudio() & m_id) != 0);
355
    }
356

357
    connect(m_timePos, SIGNAL(timeCodeEditingFinished()), this, SLOT(slotSeek()));
358
    layout->addWidget(m_toolbar);
Laurent Montel's avatar
Laurent Montel committed
359
360
361
    if (m_recManager) {
        layout->addWidget(m_recManager->toolbar());
    }
362
363

    // Load monitor overlay qml
364
    loadQmlScene(MonitorSceneDefault);
365

366
367
368
369
    // Info message widget
    m_infoMessage = new KMessageWidget(this);
    layout->addWidget(m_infoMessage);
    m_infoMessage->hide();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
370
371
}

372
373
Monitor::~Monitor()
{
374
    delete m_audioMeterWidget;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
375
    delete m_glMonitor;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
376
377
    delete m_videoWidget;
    delete m_glWidget;
378
379
380
    delete m_timePos;
}

381
382
383
384
385
386
387
388
389
390
void Monitor::setOffsetX(int x)
{
    m_glMonitor->setOffsetX(x, m_horizontalScroll->maximum());
}

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

391
void Monitor::slotGetCurrentImage(bool request)
392
{
393
    m_glMonitor->sendFrameForAnalysis = request;
394
    m_monitorManager->activateMonitor(m_id);
395
    refreshMonitorIfActive();
396
397
    if (request) {
        // Update analysis state
Laurent Montel's avatar
Laurent Montel committed
398
        QTimer::singleShot(500, m_monitorManager, &MonitorManager::checkScopes);
399
400
401
    } else {
        m_glMonitor->releaseAnalyse();
    }
402
403
}

404
void Monitor::slotAddEffect(const QStringList &effect)
405
406
{
    if (m_id == Kdenlive::ClipMonitor) {
Laurent Montel's avatar
Laurent Montel committed
407
        if (m_controller) {
408
            emit addMasterEffect(m_controller->AbstractProjectItem::clipId(), effect);
Laurent Montel's avatar
Laurent Montel committed
409
410
411
        }
    } else {
        emit addEffect(effect);
412
413
414
    }
}

415
416
417
418
419
420
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
421
422
423
        if (ic.isNull() || ic.name().isEmpty()) {
            continue;
        }
424
        QIcon newIcon = QIcon::fromTheme(ic.name());
425
426
427
428
429
430
        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
431
432
433
        if (ic.isNull() || ic.name().isEmpty()) {
            continue;
        }
434
        QIcon newIcon = QIcon::fromTheme(ic.name());
435
436
        m->setActiveIcon(newIcon);
        ic = m->inactiveIcon();
Laurent Montel's avatar
Laurent Montel committed
437
438
439
        if (ic.isNull() || ic.name().isEmpty()) {
            continue;
        }
440
        newIcon = QIcon::fromTheme(ic.name());
441
442
443
444
        m->setInactiveIcon(newIcon);
    }
}

445
QAction *Monitor::recAction()
446
{
Laurent Montel's avatar
Laurent Montel committed
447
    if (m_recManager) {
448
        return m_recManager->recAction();
Laurent Montel's avatar
Laurent Montel committed
449
    }
Laurent Montel's avatar
Laurent Montel committed
450
    return nullptr;
451
452
}

453
454
455
456
457
void Monitor::slotLockMonitor(bool lock)
{
    m_monitorManager->lockMonitor(m_id, lock);
}

458
void Monitor::setupMenu(QMenu *goMenu, QMenu *overlayMenu, QAction *playZone, QAction *loopZone, QMenu *markerMenu, QAction *loopClip)
459
{
460
    delete m_contextMenu;
461
    m_contextMenu = new QMenu(this);
462
    m_contextMenu->addMenu(m_playMenu);
Laurent Montel's avatar
Laurent Montel committed
463
    if (goMenu) {
464
        m_contextMenu->addMenu(goMenu);
Laurent Montel's avatar
Laurent Montel committed
465
    }
466

467
    if (markerMenu) {
468
        m_contextMenu->addMenu(markerMenu);
Nicolas Carion's avatar
Nicolas Carion committed
469
        QList<QAction *> list = markerMenu->actions();
470
        for (int i = 0; i < list.count(); ++i) {
471
            if (list.at(i)->data().toString() == QLatin1String("edit_marker")) {
472
473
474
475
476
                m_editMarker = list.at(i);
                break;
            }
        }
    }
477

478
479
    m_playMenu->addAction(playZone);
    m_playMenu->addAction(loopZone);
480
481
482
483
    if (loopClip) {
        m_loopClipAction = loopClip;
        m_playMenu->addAction(loopClip);
    }
484

Nicolas Carion's avatar
Nicolas Carion committed
485
    // TODO: add save zone to timeline monitor when fixed
486
    m_contextMenu->addMenu(m_markerMenu);
487
    if (m_id == Kdenlive::ClipMonitor) {
488
        m_contextMenu->addAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save zone"), this, SLOT(slotSaveZone()));
Nicolas Carion's avatar
Nicolas Carion committed
489
        QAction *extractZone =
490
            m_configMenu->addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Extract Zone"), this, SLOT(slotExtractCurrentZone()));
491
        m_contextMenu->addAction(extractZone);
492
    }
493
494
    m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("extract_frame")));
    m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("extract_frame_to_project")));
495

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
496
    if (m_id == Kdenlive::ProjectMonitor) {
497
        m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("monitor_multitrack")));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
498
    } else if (m_id == Kdenlive::ClipMonitor) {
499
500
        QAction *setThumbFrame =
            m_contextMenu->addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Set current image as thumbnail"), this, SLOT(slotSetThumbFrame()));
501
502
503
        m_configMenu->addAction(setThumbFrame);
    }

Laurent Montel's avatar
Laurent Montel committed
504
    if (overlayMenu) {
505
        m_contextMenu->addMenu(overlayMenu);
Laurent Montel's avatar
Laurent Montel committed
506
    }
507

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

512
513
514
    // 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);
515
516
}

517
518
519
520
521
522
void Monitor::slotGoToMarker(QAction *action)
{
    int pos = action->data().toInt();
    slotSeek(pos);
}

523
void Monitor::slotForceSize(QAction *a)
524
{
525
526
527
528
529
    int resizeType = a->data().toInt();
    int profileWidth = 320;
    int profileHeight = 200;
    if (resizeType > 0) {
        // calculate size
Vincent Pinon's avatar
Vincent Pinon committed
530
        QRect r = QApplication::primaryScreen()->geometry();
531
        profileHeight = m_glMonitor->profileSize().height() * resizeType / 100;
532
        profileWidth = pCore->getCurrentProfile()->dar() * profileHeight;
533
534
        if (profileWidth > r.width() * 0.8 || profileHeight > r.height() * 0.7) {
            // reset action to free resize
Nicolas Carion's avatar
format    
Nicolas Carion committed
535
            const QList<QAction *> list = m_forceSize->actions();
536
            for (QAction *ac : list) {
537
                if (ac->data().toInt() == m_forceSizeFactor) {
538
539
540
541
                    m_forceSize->setCurrentAction(ac);
                    break;
                }
            }
542
            warningMessage(i18n("Your screen resolution is not sufficient for this action"));
543
544
            return;
        }
545
    }
546
    switch (resizeType) {
Laurent Montel's avatar
Laurent Montel committed
547
548
549
550
551
552
    case 100:
    case 50:
        // resize full size
        setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
        m_videoWidget->setMinimumSize(profileWidth, profileHeight);
        m_videoWidget->setMaximumSize(profileWidth, profileHeight);
553
        setMinimumSize(QSize(profileWidth, profileHeight + m_toolbar->height() + m_glMonitor->getControllerProxy()->rulerHeight()));
Laurent Montel's avatar
Laurent Montel committed
554
555
        break;
    default:
556
557
        // Free resize
        m_videoWidget->setMinimumSize(profileWidth, profileHeight);
558
        m_videoWidget->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
559
        setMinimumSize(QSize(profileWidth, profileHeight + m_toolbar->height() + m_glMonitor->getControllerProxy()->rulerHeight()));
560
        setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
561
        break;
562
    }
563
    m_forceSizeFactor = resizeType;
564
565
566
    updateGeometry();
}

567
568
569
570
571
572
573
574
575
576
QString Monitor::getTimecodeFromFrames(int pos)
{
    return m_monitorManager->timecode().getTimecodeFromFrames(pos);
}

double Monitor::fps() const
{
    return m_monitorManager->timecode().fps();
}

577
578
579
580
581
Timecode Monitor::timecode() const
{
    return m_monitorManager->timecode();
}

582
void Monitor::updateMarkers()
583
{
584
    if (m_controller) {
585
        m_markerMenu->clear();
586
        QList<CommentedTime> markers = m_controller->getMarkerModel()->getAllMarkers();
587
        if (!markers.isEmpty()) {
588
            for (int i = 0; i < markers.count(); ++i) {
Nicolas Carion's avatar
Nicolas Carion committed
589
                int pos = (int)markers.at(i).time().frames(m_monitorManager->timecode().fps());
Laurent Montel's avatar
Laurent Montel committed
590
                QString position = m_monitorManager->timecode().getTimecode(markers.at(i).time()) + QLatin1Char(' ') + markers.at(i).comment();
591
592
593
                QAction *go = m_markerMenu->addAction(position);
                go->setData(pos);
            }
594
        }
595
596
597
598
        m_markerMenu->setEnabled(!m_markerMenu->isEmpty());
    }
}

Laurent Montel's avatar
Laurent Montel committed
599
void Monitor::setGuides(const QMap<double, QString> &guides)
600
{
Nicolas Carion's avatar
linting    
Nicolas Carion committed
601
    // TODO: load guides model
602
603
    m_markerMenu->clear();
    QMapIterator<double, QString> i(guides);
Laurent Montel's avatar
Laurent Montel committed
604
    QList<CommentedTime> guidesList;
605
606
607
608
    while (i.hasNext()) {
        i.next();
        CommentedTime timeGuide(GenTime(i.key()), i.value());
        guidesList << timeGuide;
Nicolas Carion's avatar
Nicolas Carion committed
609
        int pos = (int)timeGuide.time().frames(m_monitorManager->timecode().fps());
Laurent Montel's avatar
Laurent Montel committed
610
        QString position = m_monitorManager->timecode().getTimecode(timeGuide.time()) + QLatin1Char(' ') + timeGuide.comment();
611
612
613
        QAction *go = m_markerMenu->addAction(position);
        go->setData(pos);
    }
Nicolas Carion's avatar
linting    
Nicolas Carion committed
614
    // m_ruler->setMarkers(guidesList);
615
616
617
618
    m_markerMenu->setEnabled(!m_markerMenu->isEmpty());
    checkOverlay();
}

619
620
void Monitor::slotSeekToPreviousSnap()
{
Laurent Montel's avatar
Laurent Montel committed
621
    if (m_controller) {
622
        m_glMonitor->getControllerProxy()->setPosition(getSnapForPos(true).frames(m_monitorManager->timecode().fps()));
Laurent Montel's avatar
Laurent Montel committed
623
    }
624
625
}

626
627
void Monitor::slotSeekToNextSnap()
{
Laurent Montel's avatar
Laurent Montel committed
628
    if (m_controller) {
629
        m_glMonitor->getControllerProxy()->setPosition(getSnapForPos(false).frames(m_monitorManager->timecode().fps()));
Laurent Montel's avatar
Laurent Montel committed
630
    }
631
632
}

633
int Monitor::position()
634
{
635
    return m_glMonitor->getControllerProxy()->getPosition();
636
637
}

638
639
GenTime Monitor::getSnapForPos(bool previous)
{
640
    int frame = previous ? m_snaps->getPreviousPoint(m_glMonitor->getCurrentPos()) : m_snaps->getNextPoint(m_glMonitor->getCurrentPos());
Nicolas Carion's avatar
Nicolas Carion committed
641
    return {frame, pCore->getCurrentFps()};
642
643
}

644
void Monitor::slotLoadClipZone(const QPoint &zone)
645
{
646
    m_glMonitor->getControllerProxy()->setZone(zone.x(), zone.y());
647
    checkOverlay();
648
649
}

650
651
void Monitor::slotSetZoneStart()
{
652
    m_glMonitor->getControllerProxy()->setZoneIn(m_glMonitor->getCurrentPos());
653
654
    if (m_controller) {
        m_controller->setZone(m_glMonitor->getControllerProxy()->zone());
655
656
657
    } else {
        // timeline
        emit timelineZoneChanged();
658
    }
659
    checkOverlay();
660
661
}

662
void Monitor::slotSetZoneEnd(bool discardLastFrame)
663
{
664
    Q_UNUSED(discardLastFrame);
665
    int pos = m_glMonitor->getCurrentPos();
666
    if (m_controller == nullptr) {
667
        pos++;
668
    }
669
    m_glMonitor->getControllerProxy()->setZoneOut(pos);
670
671
672
    if (m_controller) {
        m_controller->setZone(m_glMonitor->getControllerProxy()->zone());
    }
673
    checkOverlay();
674
675
}

676
// virtual
Laurent Montel's avatar
Laurent Montel committed
677
void Monitor::mousePressEvent(QMouseEvent *event)
678
{
679
    m_monitorManager->activateMonitor(m_id);
Nicolas Carion's avatar
Nicolas Carion committed
680
    if ((event->button() & Qt::RightButton) == 0u) {
681
        if (m_glWidget->geometry().contains(event->pos())) {
682
            m_DragStartPosition = event->pos();
683
            event->accept();
684
        }
685
    } else if (m_contextMenu) {
686
        slotActivateMonitor();
687
        m_contextMenu->popup(event->globalPos());
688
        event->accept();
689
    }
690
    QWidget::mousePressEvent(event);
691
692
}

693
694
void Monitor::slotShowMenu(const QPoint pos)
{
695
    slotActivateMonitor();
Laurent Montel's avatar
Laurent Montel committed
696
    if (m_contextMenu) {
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
        if (m_markerMenu) {
            // Fill guide menu
            m_markerMenu->clear();
            std::shared_ptr<MarkerListModel> model;
            if (m_id == Kdenlive::ClipMonitor && m_controller) {
                model = m_controller->getMarkerModel();
            } else if (m_id == Kdenlive::ProjectMonitor && pCore->currentDoc()) {
                model = pCore->currentDoc()->getGuideModel();
            }
            if (model) {
                QList<CommentedTime> markersList = model->getAllMarkers();
                for (CommentedTime mkr : markersList) {
                    QAction *a = m_markerMenu->addAction(mkr.comment());
                    a->setData(mkr.time().frames(pCore->getCurrentFps()));
                }
            }
            m_markerMenu->setEnabled(!m_markerMenu->isEmpty());
        }
Laurent Montel's avatar
Laurent Montel committed
715
716
        m_contextMenu->popup(pos);
    }
717
718
}

719
720
void Monitor::resizeEvent(QResizeEvent *event)
{
Vincent Pinon's avatar
Vincent Pinon committed
721
    Q_UNUSED(event)
722
    if (m_glMonitor->zoom() > 0.0f) {
Vincent Pinon's avatar
Vincent Pinon committed
723
724
        float horizontal = float(m_horizontalScroll->value()) / float(m_horizontalScroll->maximum());
        float vertical = float(m_verticalScroll->value()) / float(m_verticalScroll->maximum());
725
726
727
728
729
730
731
732
733
734
735
        adjustScrollBars(horizontal, vertical);
    } else {
        m_horizontalScroll->hide();
        m_verticalScroll->hide();
    }
}

void Monitor::adjustScrollBars(float horizontal, float vertical)
{
    if (m_glMonitor->zoom() > 1.0f) {
        m_horizontalScroll->setPageStep(m_glWidget->width());
Vincent Pinon's avatar
Vincent Pinon committed
736
        m_horizontalScroll->setMaximum((int)((float)m_glMonitor->profileSize().width() * m_glMonitor->zoom()) - m_horizontalScroll->pageStep());
Vincent Pinon's avatar
Vincent Pinon committed
737
        m_horizontalScroll->setValue(qRound(horizontal * float(m_horizontalScroll->maximum())));
738
739
740
        emit m_horizontalScroll->valueChanged(m_horizontalScroll->value());
        m_horizontalScroll->show();
    } else {
Vincent Pinon's avatar
Vincent Pinon committed
741
        int max = (int)((float)m_glMonitor->profileSize().width() * m_glMonitor->zoom()) - m_glWidget->width();
742
743
744
745
746
747
        emit m_horizontalScroll->valueChanged(qRound(0.5 * max));
        m_horizontalScroll->hide();
    }

    if (m_glMonitor->zoom() > 1.0f) {
        m_verticalScroll->setPageStep(m_glWidget->height());
Vincent Pinon's avatar
Vincent Pinon committed
748
        m_verticalScroll->setMaximum((int)((float)m_glMonitor->profileSize().height() * m_glMonitor->zoom()) - m_verticalScroll->pageStep());
Nicolas Carion's avatar
Nicolas Carion committed
749
        m_verticalScroll->setValue((int)((float)m_verticalScroll->maximum() * vertical));
750
751
752
        emit m_verticalScroll->valueChanged(m_verticalScroll->value());
        m_verticalScroll->show();
    } else {
Vincent Pinon's avatar
Vincent Pinon committed
753
        int max = (int)((float)m_glMonitor->profileSize().height() * m_glMonitor->zoom()) - m_glWidget->height();
754
755
756
757
758
759
760
        emit m_verticalScroll->valueChanged(qRound(0.5 * max));
        m_verticalScroll->hide();
    }
}

void Monitor::setZoom()
{
761
    if (qFuzzyCompare(m_glMonitor->zoom(), 1.0f)) {
762
763
        m_horizontalScroll->hide();
        m_verticalScroll->hide();
764
765
        m_glMonitor->setOffsetX(m_horizontalScroll->value(), m_horizontalScroll->maximum());
        m_glMonitor->setOffsetY(m_verticalScroll->value(), m_verticalScroll->maximum());
766
767
768
    } else {
        adjustScrollBars(0.5f, 0.5f);
    }
769
770
}

771
void Monitor::slotSwitchFullScreen(bool minimizeOnly)
772
{
773
    // TODO: disable screensaver?
774
    pause();
775
    if (!m_glWidget->isFullScreen() && !minimizeOnly) {
Vincent Pinon's avatar
Vincent Pinon committed
776
777
778
        // Move monitor widget to the second screen (one screen for Kdenlive, the other one for the Monitor widget)
        if (qApp->screens().count() > 1) {
            for (auto screen : qApp->screens()) {
779
780
                QRect screenRect = screen->