timelinecontroller.cpp 117 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/***************************************************************************
 *   Copyright (C) 2017 by Jean-Baptiste Mardelle                                  *
 *   This file is part of Kdenlive. See www.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) version 3 or any later version accepted by the       *
 *   membership of KDE e.V. (or its successor approved  by the membership  *
 *   of KDE e.V.), which shall act as a proxy defined in Section 14 of     *
 *   version 3 of the license.                                             *
 *                                                                         *
 *   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, see <http://www.gnu.org/licenses/>. *
 ***************************************************************************/

#include "timelinecontroller.h"
Nicolas Carion's avatar
Nicolas Carion committed
23
#include "../model/timelinefunctions.hpp"
24
#include "assets/keyframes/model/keyframemodellist.hpp"
25
#include "bin/bin.h"
26
#include "bin/clipcreator.hpp"
Nicolas Carion's avatar
linting    
Nicolas Carion committed
27
#include "bin/model/markerlistmodel.hpp"
28
#include "bin/projectclip.h"
29
#include "bin/projectfolder.h"
Nicolas Carion's avatar
Nicolas Carion committed
30
#include "bin/projectitemmodel.h"
Nicolas Carion's avatar
linting    
Nicolas Carion committed
31
#include "core.h"
Nicolas Carion's avatar
Nicolas Carion committed
32
#include "dialogs/spacerdialog.h"
33
#include "dialogs/speeddialog.h"
34
#include "doc/kdenlivedoc.h"
35
#include "effects/effectsrepository.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
36
#include "effects/effectstack/model/effectstackmodel.hpp"
Nicolas Carion's avatar
linting    
Nicolas Carion committed
37
#include "kdenlivesettings.h"
38
#include "lib/audio/audioEnvelope.h"
39
40
#include "mainwindow.h"
#include "monitor/monitormanager.h"
Nicolas Carion's avatar
Nicolas Carion committed
41
#include "previewmanager.h"
Nicolas Carion's avatar
linting    
Nicolas Carion committed
42
#include "project/projectmanager.h"
43
#include "timeline2/model/clipmodel.hpp"
44
#include "timeline2/model/compositionmodel.hpp"
45
#include "timeline2/model/groupsmodel.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
46
47
#include "timeline2/model/timelineitemmodel.hpp"
#include "timeline2/model/trackmodel.hpp"
48
#include "timeline2/view/dialogs/clipdurationdialog.h"
Nicolas Carion's avatar
Nicolas Carion committed
49
#include "timeline2/view/dialogs/trackdialog.h"
Nicolas Carion's avatar
Nicolas Carion committed
50
#include "transitions/transitionsrepository.hpp"
51
#include "audiomixer/mixermanager.hpp"
52

53
#include <KColorScheme>
54
#include <QApplication>
Nicolas Carion's avatar
Nicolas Carion committed
55
#include <QClipboard>
Nicolas Carion's avatar
Nicolas Carion committed
56
#include <QQuickItem>
Nicolas Carion's avatar
Nicolas Carion committed
57
#include <memory>
58
#include <unistd.h>
59
60
61

int TimelineController::m_duration = 0;

62
TimelineController::TimelineController(QObject *parent)
Nicolas Carion's avatar
linting    
Nicolas Carion committed
63
    : QObject(parent)
64
    , m_root(nullptr)
65
    , m_usePreview(false)
66
    , m_activeTrack(0)
67
    , m_audioRef(-1)
Vincent Pinon's avatar
Vincent Pinon committed
68
    , m_zone(-1, -1)
69
    , m_scale(QFontMetrics(QApplication::font()).maxWidth() / 250)
70
    , m_timelinePreview(nullptr)
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
71
    , m_ready(false)
72
    , m_snapStackIndex(-1)
73
{
74
75
    m_disablePreview = pCore->currentDoc()->getAction(QStringLiteral("disable_preview"));
    connect(m_disablePreview, &QAction::triggered, this, &TimelineController::disablePreview);
76
    connect(this, &TimelineController::selectionChanged, this, &TimelineController::updateClipActions);
77
78
    connect(this, &TimelineController::videoTargetChanged, this, &TimelineController::updateVideoTarget);
    connect(this, &TimelineController::audioTargetChanged, this, &TimelineController::updateAudioTarget);
79
    m_disablePreview->setEnabled(false);
80
    connect(pCore.get(), &Core::finalizeRecording, this, &TimelineController::finishRecording);
81
    connect(pCore.get(), &Core::autoScrollChanged, this, &TimelineController::autoScrollChanged);
82
    connect(pCore->mixer(), &MixerManager::recordAudio, this, &TimelineController::switchRecording);
83
84
}

85
86
TimelineController::~TimelineController()
{
87
88
89
90
91
    prepareClose();
}

void TimelineController::prepareClose()
{
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
92
93
94
    // Clear roor so we don't call its methods anymore
    m_ready = false;
    m_root = nullptr;
95
    // Delete timeline preview before resetting model so that removing clips from timeline doesn't invalidate
96
97
98
99
    delete m_timelinePreview;
    m_timelinePreview = nullptr;
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
100
void TimelineController::setModel(std::shared_ptr<TimelineItemModel> model)
101
{
102
    delete m_timelinePreview;
103
    m_zone = QPoint(-1, -1);
104
    m_timelinePreview = nullptr;
105
    m_model = std::move(model);
106
    m_activeSnaps.clear();
107
108
    connect(m_model.get(), &TimelineItemModel::requestClearAssetView, pCore.get(), &Core::clearAssetPanel);
    connect(m_model.get(), &TimelineItemModel::checkItemDeletion, [this] (int id) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
109
        if (m_ready) {
110
111
112
            QMetaObject::invokeMethod(m_root, "checkDeletion", Qt::QueuedConnection, Q_ARG(QVariant, id));
        }
    });
Nicolas Carion's avatar
Nicolas Carion committed
113
    connect(m_model.get(), &TimelineItemModel::requestMonitorRefresh, [&]() { pCore->requestMonitorRefresh(); });
114
    connect(m_model.get(), &TimelineModel::invalidateZone, this, &TimelineController::invalidateZone, Qt::DirectConnection);
115
    connect(m_model.get(), &TimelineModel::durationUpdated, this, &TimelineController::checkDuration);
116
    connect(m_model.get(), &TimelineModel::selectionChanged, this, &TimelineController::selectionChanged);
117
    connect(m_model.get(), &TimelineModel::checkTrackDeletion, this, &TimelineController::checkTrackDeletion, Qt::DirectConnection);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
118
119
}

120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
void TimelineController::setTargetTracks(bool hasVideo, QList <int> audioTargets)
{
    int videoTrack = -1;
    QList <int> audioTracks;
    m_hasVideoTarget = hasVideo;
    m_hasAudioTarget = !audioTargets.isEmpty();
    if (m_hasVideoTarget) {
        videoTrack = m_model->getFirstVideoTrackIndex();
    }
    if (m_hasAudioTarget) {
        QList <int> tracks;
        auto it = m_model->m_allTracks.cbegin();
        while (it != m_model->m_allTracks.cend()) {
            if ((*it)->isAudioTrack()) {
                tracks << (*it)->getId();
            }
            ++it;
        }
        int i = 0;
        while (i < audioTargets.size() && !tracks.isEmpty()) {
            audioTracks << tracks.takeLast();
            i++;
        }
    }
144
145
146
    emit hasAudioTargetChanged();
    emit hasVideoTargetChanged();
    if (m_videoTargetActive) {
147
        setVideoTarget(m_hasVideoTarget && (m_lastVideoTarget > -1) ? m_lastVideoTarget : videoTrack);
148
149
    }
    if (m_audioTargetActive) {
150
        setIntAudioTarget((m_hasAudioTarget && (m_lastAudioTarget.size() == audioTargets.size())) ? m_lastAudioTarget : audioTracks);
151
    }
152
153
}

154
155
156
157
158
std::shared_ptr<TimelineItemModel> TimelineController::getModel() const
{
    return m_model;
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
159
160
void TimelineController::setRoot(QQuickItem *root)
{
161
    m_root = root;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
162
    m_ready = true;
163
164
165
166
167
168
169
}

Mlt::Tractor *TimelineController::tractor()
{
    return m_model->tractor();
}

170
171
172
173
174
Mlt::Producer TimelineController::trackProducer(int tid)
{
    return *(m_model->getTrackById(tid).get());
}

175
176
177
178
179
double TimelineController::scaleFactor() const
{
    return m_scale;
}

180
const QString TimelineController::getTrackNameFromMltIndex(int trackPos)
181
{
182
    if (trackPos == -1) {
183
184
        return i18n("unknown");
    }
185
    if (trackPos == 0) {
186
187
        return i18n("Black");
    }
188
    return m_model->getTrackTagById(m_model->getTrackIndexFromPosition(trackPos - 1));
189
190
}

191
192
const QString TimelineController::getTrackNameFromIndex(int trackIndex)
{
193
    QString trackName = m_model->getTrackFullName(trackIndex);
194
    return trackName.isEmpty() ? m_model->getTrackTagById(trackIndex) : trackName;
195
196
}

197
198
199
200
QMap<int, QString> TimelineController::getTrackNames(bool videoOnly)
{
    QMap<int, QString> names;
    for (const auto &track : m_model->m_iteratorTable) {
201
        if (videoOnly && m_model->getTrackById_const(track.first)->isAudioTrack()) {
202
203
            continue;
        }
204
        QString trackName = m_model->getTrackFullName(track.first);
205
        names[m_model->getTrackMltIndex(track.first)] = trackName;
206
207
    }
    return names;
208
209
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
210
211
void TimelineController::setScaleFactorOnMouse(double scale, bool zoomOnMouse)
{
212
    if (m_root) {
213
        m_root->setProperty("zoomOnMouse", zoomOnMouse ? qBound(0, getMousePos(), duration()) : -1);
214
215
216
217
218
        m_scale = scale;
        emit scaleFactorChanged();
    } else {
        qWarning("Timeline root not created, impossible to zoom in");
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
219
220
}

221
222
223
void TimelineController::setScaleFactor(double scale)
{
    m_scale = scale;
224
225
226
    // Update mainwindow's zoom slider
    emit updateZoom(scale);
    // inform qml
227
228
229
230
231
232
233
234
    emit scaleFactorChanged();
}

int TimelineController::duration() const
{
    return m_duration;
}

235
236
237
238
239
int TimelineController::fullDuration() const
{
    return m_duration + TimelineModel::seekDuration;
}

240
241
242
243
244
245
246
247
248
void TimelineController::checkDuration()
{
    int currentLength = m_model->duration();
    if (currentLength != m_duration) {
        m_duration = currentLength;
        emit durationChanged();
    }
}

249
int TimelineController::selectedTrack() const
250
{
251
252
253
254
255
256
    std::unordered_set<int> sel = m_model->getCurrentSelection();
    if (sel.empty()) return -1;
    std::vector<std::pair<int, int>> selected_tracks; // contains pairs of (track position, track id) for each selected item
    for (int s : sel) {
        int tid = m_model->getItemTrackId(s);
        selected_tracks.push_back({m_model->getTrackPosition(tid), tid});
257
    }
258
259
260
    // sort by track position
    std::sort(selected_tracks.begin(), selected_tracks.begin(), [](const auto &a, const auto &b) { return a.first < b.first; });
    return selected_tracks.front().second;
261
262
}

263
264
265
void TimelineController::selectCurrentItem(ObjectType type, bool select, bool addToCurrent)
{
    QList<int> toSelect;
266
267
    int currentClip = type == ObjectType::TimelineClip ? m_model->getClipByPosition(m_activeTrack, pCore->getTimelinePosition())
                                                       : m_model->getCompositionByPosition(m_activeTrack, pCore->getTimelinePosition());
268
269
270
271
    if (currentClip == -1) {
        pCore->displayMessage(i18n("No item under timeline cursor in active track"), InformationMessage, 500);
        return;
    }
272
273
274
275
    if (!select) {
        m_model->requestRemoveFromSelection(currentClip);
    } else {
        m_model->requestAddToSelection(currentClip, !addToCurrent);
276
277
278
279
280
    }
}

QList<int> TimelineController::selection() const
{
281
    if (!m_root) return QList<int>();
282
    std::unordered_set<int> sel = m_model->getCurrentSelection();
283
    QList<int> items;
284
    for (int id : sel) {
285
286
287
        items << id;
    }
    return items;
288
289
}

290
291
292
293
294
295
void TimelineController::selectItems(const QList<int> &ids)
{
    std::unordered_set<int> ids_s(ids.begin(), ids.end());
    m_model->requestSetSelection(ids_s);
}

296
297
298
299
300
301
302
void TimelineController::setScrollPos(int pos)
{
    if (pos > 0 && m_root) {
        QMetaObject::invokeMethod(m_root, "setScrollPos", Qt::QueuedConnection, Q_ARG(QVariant, pos));
    }
}

303
304
305
306
307
308
void TimelineController::resetView()
{
    m_model->_resetView();
    if (m_root) {
        QMetaObject::invokeMethod(m_root, "updatePalette");
    }
309
    emit colorsChanged();
310
311
}

312
313
bool TimelineController::snap()
{
314
315
316
    return KdenliveSettings::snaptopoints();
}

317
318
319
320
321
322
323
324
325
326
bool TimelineController::ripple()
{
    return false;
}

bool TimelineController::scrub()
{
    return false;
}

327
int TimelineController::insertClip(int tid, int position, const QString &data_str, bool logUndo, bool refreshView, bool useTargets)
328
329
{
    int id;
330
    if (tid == -1) {
331
        tid = m_activeTrack;
332
333
    }
    if (position == -1) {
334
        position = pCore->getTimelinePosition();
335
    }
336
    if (!m_model->requestClipInsertion(data_str, tid, position, id, logUndo, refreshView, useTargets)) {
337
338
339
340
341
        id = -1;
    }
    return id;
}

342
343
344
345
346
347
348
QList<int> TimelineController::insertClips(int tid, int position, const QStringList &binIds, bool logUndo, bool refreshView)
{
    QList<int> clipIds;
    if (tid == -1) {
        tid = m_activeTrack;
    }
    if (position == -1) {
349
        position = pCore->getTimelinePosition();
350
351
352
353
354
355
    }
    TimelineFunctions::requestMultipleClipsInsertion(m_model, binIds, tid, position, clipIds, logUndo, refreshView);
    // we don't need to check the return value of the above function, in case of failure it will return an empty list of ids.
    return clipIds;
}

356
int TimelineController::insertNewComposition(int tid, int position, const QString &transitionId, bool logUndo)
357
358
359
360
361
362
363
364
365
366
{
    int clipId = m_model->getTrackById_const(tid)->getClipByPosition(position);
    if (clipId > 0) {
        int minimum = m_model->getClipPosition(clipId);
        return insertNewComposition(tid, clipId, position - minimum, transitionId, logUndo);
    }
    return insertComposition(tid, position, transitionId, logUndo);
}

int TimelineController::insertNewComposition(int tid, int clipId, int offset, const QString &transitionId, bool logUndo)
367
368
{
    int id;
369
370
    int minimumPos = clipId > -1 ? m_model->getClipPosition(clipId) : offset;
    int clip_duration = clipId > -1 ? m_model->getClipPlaytime(clipId) : pCore->currentDoc()->getFramePos(KdenliveSettings::transition_duration());
371
372
    int endPos = minimumPos + clip_duration;
    int position = minimumPos;
373
    int duration = qMin(clip_duration, pCore->currentDoc()->getFramePos(KdenliveSettings::transition_duration()));
374
    int lowerVideoTrackId = m_model->getPreviousVideoTrackIndex(tid);
375
    bool revert = offset > clip_duration / 2;
376
    int bottomId = 0;
377
    if (lowerVideoTrackId > 0) {
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
        bottomId = m_model->getTrackById_const(lowerVideoTrackId)->getClipByPosition(position + offset);
    }
    if (bottomId <= 0) {
        // No video track underneath
        if (offset < duration && duration < 2 * clip_duration) {
            // Composition dropped close to start, keep default composition duration
        } else if (clip_duration - offset < duration * 1.2  && duration < 2 * clip_duration) {
            // Composition dropped close to end, keep default composition duration
            position = endPos - duration;
        } else {
            // Use full clip length for duration
            duration = m_model->getTrackById_const(tid)->suggestCompositionLength(position);
        }
    } else {
        duration = qMin(duration, m_model->getTrackById_const(tid)->suggestCompositionLength(position));
        QPair<int, int> bottom(m_model->m_allClips[bottomId]->getPosition(), m_model->m_allClips[bottomId]->getPlaytime());
        if (bottom.first > minimumPos) {
            // Lower clip is after top clip
            if (position + offset > bottom.first) {
                int test_duration = m_model->getTrackById_const(tid)->suggestCompositionLength(bottom.first);
398
                if (test_duration > 0) {
399
400
401
402
                    offset -= (bottom.first - position);
                    position = bottom.first;
                    duration = test_duration;
                    revert = position > minimumPos;
403
404
                }
            }
405
406
407
408
409
410
        } else if (position >= bottom.first) {
            // Lower clip is before or at same pos as top clip
            int test_duration = m_model->getTrackById_const(lowerVideoTrackId)->suggestCompositionLength(position);
            if (test_duration > 0) {
                duration = qMin(test_duration, clip_duration);
            }
411
412
        }
    }
413
    int defaultLength = pCore->currentDoc()->getFramePos(KdenliveSettings::transition_duration());
414
    bool isShortComposition = TransitionsRepository::get()->getType(transitionId) == AssetListType::AssetType::VideoShortComposition;
415
416
    if (duration < 0 || (isShortComposition && duration > 1.5 * defaultLength)) {
        duration = defaultLength;
417
    } else if (duration <= 1) {
418
419
        // if suggested composition duration is lower than 4 frames, use default
        duration = pCore->currentDoc()->getFramePos(KdenliveSettings::transition_duration());
420
421
        if (minimumPos + clip_duration - position < 3) {
            position = minimumPos + clip_duration - duration;
422
        }
423
    }
424
425
426
427
    QPair<int, int> finalPos = m_model->getTrackById_const(tid)->validateCompositionLength(position, offset, duration, endPos);
    position = finalPos.first;
    duration = finalPos.second;

428
    std::unique_ptr<Mlt::Properties> props(nullptr);
429
    if (revert) {
Nicolas Carion's avatar
Nicolas Carion committed
430
        props = std::make_unique<Mlt::Properties>();
431
432
433
434
435
436
437
438
        if (transitionId == QLatin1String("dissolve")) {
            props->set("reverse", 1);
        } else if (transitionId == QLatin1String("composite") || transitionId == QLatin1String("slide")) {
            props->set("invert", 1);
        } else if (transitionId == QLatin1String("wipe")) {
            props->set("geometry", "0%/0%:100%x100%:100;-1=0%/0%:100%x100%:0");
        }
    }
439
    if (!m_model->requestCompositionInsertion(transitionId, tid, position, duration, std::move(props), id, logUndo)) {
440
        id = -1;
441
        pCore->displayMessage(i18n("Could not add composition at selected position"), InformationMessage, 500);
442
443
444
445
    }
    return id;
}

446
447
448
int TimelineController::insertComposition(int tid, int position, const QString &transitionId, bool logUndo)
{
    int id;
449
450
    int duration = pCore->currentDoc()->getFramePos(KdenliveSettings::transition_duration());
    if (!m_model->requestCompositionInsertion(transitionId, tid, position, duration, nullptr, id, logUndo)) {
451
452
453
454
455
456
457
        id = -1;
    }
    return id;
}

void TimelineController::deleteSelectedClips()
{
458
459
    auto sel = m_model->getCurrentSelection();
    if (sel.empty()) {
460
461
        return;
    }
462
463
    // only need to delete the first item, the others will be deleted in cascade
    m_model->requestItemDeletion(*sel.begin());
464
465
}

466
int TimelineController::getMainSelectedItem(bool restrictToCurrentPos, bool allowComposition)
467
468
469
470
471
472
473
474
475
476
477
478
{
    auto sel = m_model->getCurrentSelection();
    if (sel.empty() || sel.size() > 2) {
        return -1;
    }
    int itemId = *(sel.begin());
    if (sel.size() == 2) {
        int parentGroup = m_model->m_groups->getRootId(itemId);
        if (parentGroup == -1 || m_model->m_groups->getType(parentGroup) != GroupType::AVSplit) {
            return -1;
        }
    }
479
480
481
482
483
    if (!restrictToCurrentPos) {
        if (m_model->isClip(itemId) || (allowComposition && m_model->isComposition(itemId))) {
            return itemId;
        }
    }
484
    if (m_model->isClip(itemId)) {
485
        int position = pCore->getTimelinePosition();
486
487
488
489
490
491
492
493
494
        int start = m_model->getClipPosition(itemId);
        int end = start + m_model->getClipPlaytime(itemId);
        if (position >= start && position <= end) {
            return itemId;
        }
    }
    return -1;
}

495
496
void TimelineController::copyItem()
{
497
498
    std::unordered_set<int> selectedIds = m_model->getCurrentSelection();
    if (selectedIds.empty()) {
499
500
        return;
    }
501
502
503
504
    int clipId = *(selectedIds.begin());
    QString copyString = TimelineFunctions::copyClips(m_model, selectedIds);
    QClipboard *clipboard = QApplication::clipboard();
    clipboard->setText(copyString);
505
    m_root->setProperty("copiedClip", clipId);
506
    m_model->requestSetSelection(selectedIds);
507
508
}

509
bool TimelineController::pasteItem(int position, int tid)
510
{
511
512
    QClipboard *clipboard = QApplication::clipboard();
    QString txt = clipboard->text();
513
514
515
516
517
518
    if (tid == -1) {
        tid = getMouseTrack();
    }
    if (position == -1) {
        position = getMousePos();
    }
519
    if (tid == -1) {
520
        tid = m_activeTrack;
521
522
    }
    if (position == -1) {
523
        position = pCore->getTimelinePosition();
524
    }
525
    return TimelineFunctions::pasteClips(m_model, txt, tid, position);
526
527
}

528
529
void TimelineController::triggerAction(const QString &name)
{
530
    pCore->triggerAction(name);
531
532
}

533
QString TimelineController::timecode(int frames) const
534
535
536
{
    return KdenliveSettings::frametimecode() ? QString::number(frames) : m_model->tractor()->frames_to_time(frames, mlt_time_smpte_df);
}
537

538
539
540
541
542
543
QString TimelineController::framesToClock(int frames) const
{
    return m_model->tractor()->frames_to_time(frames, mlt_time_clock);
}

QString TimelineController::simplifiedTC(int frames) const
544
545
546
547
548
549
550
551
{
    if (KdenliveSettings::frametimecode()) {
        return QString::number(frames);
    }
    QString s = m_model->tractor()->frames_to_time(frames, mlt_time_smpte_df);
    return s.startsWith(QLatin1String("00:")) ? s.remove(0, 3) : s;
}

552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
bool TimelineController::showThumbnails() const
{
    return KdenliveSettings::videothumbnails();
}

bool TimelineController::showAudioThumbnails() const
{
    return KdenliveSettings::audiothumbnails();
}

bool TimelineController::showMarkers() const
{
    return KdenliveSettings::showmarkers();
}

bool TimelineController::audioThumbFormat() const
{
    return KdenliveSettings::displayallchannels();
}

bool TimelineController::showWaveforms() const
{
    return KdenliveSettings::audiothumbnails();
}

void TimelineController::addTrack(int tid)
{
579
580
581
    if (tid == -1) {
        tid = m_activeTrack;
    }
582
    QPointer<TrackDialog> d = new TrackDialog(m_model, tid, qApp->activeWindow());
583
    if (d->exec() == QDialog::Accepted) {
584
        int newTid;
585
586
        bool audioRecTrack = d->addRecTrack();
        bool addAVTrack = d->addAVTrack();
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
        Fun undo = []() { return true; };
        Fun redo = []() { return true; };
        bool result = m_model->requestTrackInsertion(d->selectedTrackPosition(), newTid, d->trackName(), d->addAudioTrack(), undo, redo);
        if (result) {
            m_model->setTrackProperty(newTid, "kdenlive:timeline_active", QStringLiteral("1"));
            if (addAVTrack) {
                int newTid2;
                int mirrorPos = 0;
                int mirrorId = m_model->getMirrorAudioTrackId(newTid);
                if (mirrorId > -1) {
                    mirrorPos = m_model->getTrackMltIndex(mirrorId);
                }
                result = m_model->requestTrackInsertion(mirrorPos, newTid2, d->trackName(), true, undo, redo);
                if (result) {
                    m_model->setTrackProperty(newTid2, "kdenlive:timeline_active", QStringLiteral("1"));
                }
            }
            if (audioRecTrack) {
                m_model->setTrackProperty(newTid, "kdenlive:audio_rec", QStringLiteral("1"));
606
607
            }
        }
608
609
610
611
612
        if (result) {
            pCore->pushUndo(undo, redo, addAVTrack ? i18n("Insert Tracks") : i18n("Insert Track"));
        } else {
            pCore->displayMessage(i18n("Could not insert track"), InformationMessage, 500);
            undo();
613
        }
614
    }
615
616
617
618
}

void TimelineController::deleteTrack(int tid)
{
619
620
621
    if (tid == -1) {
        tid = m_activeTrack;
    }
622
    QPointer<TrackDialog> d = new TrackDialog(m_model, tid, qApp->activeWindow(), true);
623
    if (d->exec() == QDialog::Accepted) {
624
        int selectedTrackIx = d->selectedTrackId();
625
626
627
628
629
630
631
        m_model->requestTrackDeletion(selectedTrackIx);
        if (m_activeTrack == -1) {
            setActiveTrack(m_model->getTrackIndexFromPosition(m_model->getTracksCount() - 1));
        }
    }
}

632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654

void TimelineController::switchTrackRecord(int tid)
{
    if (tid == -1) {
        tid = m_activeTrack;
    }
    if (!m_model->getTrackById_const(tid)->isAudioTrack()) {
        pCore->displayMessage(i18n("Select an audio track to display record controls"), InformationMessage, 500);
    }
    int recDisplayed = m_model->getTrackProperty(tid, QStringLiteral("kdenlive:audio_rec")).toInt();
    if (recDisplayed == 1) {
        // Disable rec controls
        m_model->setTrackProperty(tid, QStringLiteral("kdenlive:audio_rec"), QStringLiteral("0"));
    } else {
        // Enable rec controls
        m_model->setTrackProperty(tid, QStringLiteral("kdenlive:audio_rec"), QStringLiteral("1"));
    }
    QModelIndex ix = m_model->makeTrackIndexFromID(tid);
    if (ix.isValid()) {
        m_model->dataChanged(ix, ix, {TimelineModel::AudioRecordRole});
    }
}

655
656
657
void TimelineController::checkTrackDeletion(int selectedTrackIx)
{
    if (m_activeTrack == selectedTrackIx) {
658
659
            // Make sure we don't keep an index on a deleted track
            m_activeTrack = -1;
660
            emit activeTrackChanged();
661
662
663
664
665
666
667
        }
        if (m_model->m_audioTarget == selectedTrackIx) {
            setAudioTarget(-1);
        }
        if (m_model->m_videoTarget == selectedTrackIx) {
            setVideoTarget(-1);
        }
668
669
        if (m_lastAudioTarget.contains(selectedTrackIx)) {
            m_lastAudioTarget.removeAll(selectedTrackIx);
670
671
672
673
674
675
            emit lastAudioTargetChanged();
        }
        if (m_lastVideoTarget == selectedTrackIx) {
            m_lastVideoTarget = -1;
            emit lastVideoTargetChanged();
        }
676
677
}

678
679
680
681
682
void TimelineController::showConfig(int page, int tab)
{
    pCore->showConfigDialog(page, tab);
}

683
684
void TimelineController::gotoNextSnap()
{
685
686
687
688
    if (m_activeSnaps.empty() || pCore->undoIndex() != m_snapStackIndex) {
        m_snapStackIndex = pCore->undoIndex();
        m_activeSnaps.clear();
        m_activeSnaps = pCore->projectManager()->current()->getGuideModel()->getSnapPoints();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
689
690
        m_activeSnaps.push_back((size_t)m_zone.x());
        m_activeSnaps.push_back((size_t)(m_zone.y() - 1));
691
692
    }
    int nextSnap = m_model->getNextSnapPos(pCore->getTimelinePosition(), m_activeSnaps);
693
    if (nextSnap > pCore->getTimelinePosition()) {
694
695
        setPosition(nextSnap);
    }
696
697
698
699
}

void TimelineController::gotoPreviousSnap()
{
700
    if (pCore->getTimelinePosition() > 0) {
701
702
703
704
        if (m_activeSnaps.empty() || pCore->undoIndex() != m_snapStackIndex) {
            m_snapStackIndex = pCore->undoIndex();
            m_activeSnaps.clear();
            m_activeSnaps = pCore->projectManager()->current()->getGuideModel()->getSnapPoints();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
705
706
            m_activeSnaps.push_back((size_t)m_zone.x());
            m_activeSnaps.push_back((size_t)(m_zone.y() - 1));
707
708
        }
        setPosition(m_model->getPreviousSnapPos(pCore->getTimelinePosition(), m_activeSnaps));
709
    }
710
711
}

712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
void TimelineController::gotoNextGuide()
{
    QList<CommentedTime> guides = pCore->projectManager()->current()->getGuideModel()->getAllMarkers();
    int pos = pCore->getTimelinePosition();
    double fps = pCore->getCurrentFps();
    for (auto &guide : guides) {
        if (guide.time().frames(fps) > pos) {
            setPosition(guide.time().frames(fps));
            return;
        }
    }
    setPosition(m_duration - 1);
}

void TimelineController::gotoPreviousGuide()
{
    if (pCore->getTimelinePosition() > 0) {
        QList<CommentedTime> guides = pCore->projectManager()->current()->getGuideModel()->getAllMarkers();
        int pos = pCore->getTimelinePosition();
        double fps = pCore->getCurrentFps();
        int lastGuidePos = 0;
        for (auto &guide : guides) {
            if (guide.time().frames(fps) >= pos) {
                setPosition(lastGuidePos);
                return;
            }
            lastGuidePos = guide.time().frames(fps);
        }
        setPosition(lastGuidePos);
    }
}

744
745
void TimelineController::groupSelection()
{
746
747
    const auto selection = m_model->getCurrentSelection();
    if (selection.size() < 2) {
748
749
750
        pCore->displayMessage(i18n("Select at least 2 items to group"), InformationMessage, 500);
        return;
    }
751
752
753
    m_model->requestClearSelection();
    m_model->requestClipsGroup(selection);
    m_model->requestSetSelection(selection);
754
755
756
757
}

void TimelineController::unGroupSelection(int cid)
{
758
    auto ids = m_model->getCurrentSelection();
759
    // ask to unselect if needed
760
761
    m_model->requestClearSelection();
    if (cid > -1) {
762
763
764
765
        ids.insert(cid);
    }
    if (!ids.empty()) {
        m_model->requestClipsUngroup(ids);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
766
    }
767
768
}

769
770
771
772
773
774
775
bool TimelineController::dragOperationRunning()
{
    QVariant returnedValue;
    QMetaObject::invokeMethod(m_root, "isDragging", Q_RETURN_ARG(QVariant, returnedValue));
    return returnedValue.toBool();
}

776
777
void TimelineController::setInPoint()
{
778
779
    if (dragOperationRunning()) {
        // Don't allow timeline operation while drag in progress
780
        qDebug() << "Cannot operate while dragging";
781
782
783
        return;
    }

784
    int cursorPos = pCore->getTimelinePosition();
785
    const auto selection = m_model->getCurrentSelection();
786
    bool selectionFound = false;
787
788
    if (!selection.empty()) {
        for (int id : selection) {
789
790
791
792
793
794
            int start = m_model->getItemPosition(id);
            if (start == cursorPos) {
                continue;
            }
            int size = start + m_model->getItemPlaytime(id) - cursorPos;
            m_model->requestItemResize(id, size, false, true, 0, false);
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
            selectionFound = true;
        }
    }
    if (!selectionFound) {
        if (m_activeTrack >= 0) {
            int cid = m_model->getClipByPosition(m_activeTrack, cursorPos);
            if (cid < 0) {
                // Check first item after timeline position
                int maximumSpace = m_model->getTrackById_const(m_activeTrack)->getBlankEnd(cursorPos);
                if (maximumSpace < INT_MAX) {
                    cid = m_model->getClipByPosition(m_activeTrack, maximumSpace + 1);
                }
            }
            if (cid >= 0) {
                int start = m_model->getItemPosition(cid);
                if (start != cursorPos) {
                    int size = start + m_model->getItemPlaytime(cid) - cursorPos;
                    m_model->requestItemResize(cid, size, false, true, 0, false);
                }
            }
815
816
817
818
819
820
        }
    }
}

void TimelineController::setOutPoint()
{
821
822
    if (dragOperationRunning()) {
        // Don't allow timeline operation while drag in progress
823
        qDebug() << "Cannot operate while dragging";
824
825
        return;
    }
826
    int cursorPos = pCore->getTimelinePosition();
827
    const auto selection = m_model->getCurrentSelection();
828
    bool selectionFound = false;
829
830
    if (!selection.empty()) {
        for (int id : selection) {
831
832
833
834
            int start = m_model->getItemPosition(id);
            if (start + m_model->getItemPlaytime(id) == cursorPos) {
                continue;
            }
835
            int size = cursorPos - start;
836
            m_model->requestItemResize(id, size, true, true, 0, false);
837
838
839
840
841
842
843
844
845
846
847
848
849
850
            selectionFound = true;
        }
    }
    if (!selectionFound) {
        if (m_activeTrack >= 0) {
            int cid = m_model->getClipByPosition(m_activeTrack, cursorPos);
            if (cid < 0) {
                // Check first item after timeline position
                int minimumSpace = m_model->getTrackById_const(m_activeTrack)->getBlankStart(cursorPos);
                cid = m_model->getClipByPosition(m_activeTrack, qMax(0, minimumSpace - 1));
            }
            if (cid >= 0) {
                int start = m_model->getItemPosition(cid);
                if (start + m_model->getItemPlaytime(cid) != cursorPos) {
851
                    int size = cursorPos - start;
852
853
854
                    m_model->requestItemResize(cid, size, true, true, 0, false);
                }
            }
855
856
857
858
        }
    }
}

859
void TimelineController::editMarker(int cid, int position)
860
{
861
862
863
    if (cid == -1) {
        cid = m_root->property("mainItemId").toInt();
    }
864
    Q_ASSERT(m_model->isClip(cid));
865
    double speed = m_model->getClipSpeed(cid);
866
    if (position == -1) {
867
        // Calculate marker position relative to timeline cursor
868
        position = pCore->getTimelinePosition() - m_model->getClipPosition(cid) + m_model->getClipIn(cid);
869
        position = position * speed;
870
    }
871
    if (position < (m_model->getClipIn(cid) * speed) || position > (m_model->getClipIn(cid) * speed + m_model->getClipPlaytime(cid))) {
872
873
874
        pCore->displayMessage(i18n("Cannot find clip to edit marker"), InformationMessage, 500);
        return;
    }
875
    std::shared_ptr<ProjectClip> clip = pCore->bin()->getBinClip(getClipBinId(cid));
876
877
878
879
880
881
    if (clip->getMarkerModel()->hasMarker(position)) {
        GenTime pos(position, pCore->getCurrentFps());
        clip->getMarkerModel()->editMarkerGui(pos, qApp->activeWindow(), false, clip.get());
    } else {
        pCore->displayMessage(i18n("Cannot find clip to edit marker"), InformationMessage, 500);
    }
882
883
}

884
885
void TimelineController::addMarker(int cid, int position)
{
886
887
888
    if (cid == -1) {
        cid = m_root->property("mainItemId").toInt();
    }
889
    Q_ASSERT(m_model->isClip(cid));
890
    double speed = m_model->getClipSpeed(cid);
891
    if (position == -1) {
892
        // Calculate marker position relative to timeline cursor
893
        position = pCore->getTimelinePosition() - m_model->getClipPosition(cid) + m_model->getClipIn(cid);
894
        position = position * speed;
895
    }
896
897
    if (position < (m_model->getClipIn(cid) * speed) || position > (m_model->getClipIn(cid) * speed + m_model->getClipPlaytime(cid))) {
        pCore->displayMessage(i18n("Cannot find clip to edit marker"), InformationMessage, 500);
898
899
        return;
    }
900
    std::shared_ptr<ProjectClip> clip = pCore->bin()->getBinClip(getClipBinId(cid));
901
    GenTime pos(position, pCore->getCurrentFps());
902
903
904
905
906
    clip->getMarkerModel()->editMarkerGui(pos, qApp->activeWindow(), true, clip.get());
}

void TimelineController::addQuickMarker(int cid, int position)
{
907
908
909
    if (cid == -1) {
        cid = m_root->property("mainItemId").toInt();
    }
910
    Q_ASSERT(m_model->isClip(cid));
911
    double speed = m_model->getClipSpeed(cid);
912
    if (position == -1) {
913
        // Calculate marker position relative to timeline cursor
914
        position = pCore->getTimelinePosition() - m_model->getClipPosition(cid);
915
        position = position * speed;
916
    }
917
    if (position < (m_model->getClipIn(cid) * speed) || position > ((m_model->getClipIn(cid) + m_model->getClipPlaytime(cid) * speed))) {
918
        pCore->displayMessage(i18n("Cannot find clip to edit marker"), InformationMessage, 500);
919
920
        return;
    }
921
    std::shared_ptr<ProjectClip> clip = pCore->bin()->getBinClip(getClipBinId(cid));
922
    GenTime pos(position, pCore->getCurrentFps());
923
924
925
926
927
928
    CommentedTime marker(pos, pCore->currentDoc()->timecode().getDisplayTimecode(pos, false), KdenliveSettings::default_marker_type());
    clip->getMarkerModel()->addMarker(marker.time(), marker.comment(), marker.markerType());
}

void TimelineController::deleteMarker(int cid, int position)
{
929
930
931
    if (cid == -1) {
        cid = m_root->property("mainItemId").toInt();
    }
932
    Q_ASSERT(m_model->isClip(cid));
933
    double speed = m_model->getClipSpeed(cid);
934
    if (position == -1) {
935
        // Calculate marker position relative to timeline cursor
936
        position = pCore->getTimelinePosition() - m_model->getClipPosition(cid) + m_model->getClipIn(cid);
937
        position = position * speed;
938
    }
939
940
    if (position < (m_model->getClipIn(cid) * speed) || position > (m_model->getClipIn(cid) * speed + m_model->getClipPlaytime(cid))) {
        pCore->displayMessage(i18n("Cannot find clip to edit marker"), InformationMessage, 500);
941
942
        return;
    }
943
    std::shared_ptr<ProjectClip> clip = pCore->bin()->getBinClip(getClipBinId(cid));
944
    GenTime pos(position, pCore->getCurrentFps());
945
946
947
948
949
    clip->getMarkerModel()->removeMarker(pos);
}

void TimelineController::deleteAllMarkers(int cid)
{
950
951
952
    if (cid == -1) {
        cid = m_root->property("mainItemId").toInt();
    }
953
954
955
956
957
    Q_ASSERT(m_model->isClip(cid));
    std::shared_ptr<ProjectClip> clip = pCore->bin()->getBinClip(getClipBinId(cid));
    clip->getMarkerModel()->removeAllMarkers();
}

958
959
void TimelineController::editGuide(int frame)
{
960
    if (frame == -1) {
961
        frame = pCore->getTimelinePosition();
962
    }
963
964
965
    auto guideModel = pCore->projectManager()->current()->getGuideModel();
    GenTime pos(frame, pCore->getCurrentFps());
    guideModel->editMarkerGui(pos, qApp->activeWindow(), false);
966
967
}

968
969
970
971
972
973
974
975
void TimelineController::moveGuide(int frame, int newFrame)
{
    auto guideModel = pCore->projectManager()->current()->getGuideModel();
    GenTime pos(frame, pCore->getCurrentFps());
    GenTime newPos(newFrame, pCore->getCurrentFps());
    guideModel->editMarker(pos, newPos);
}

976
void TimelineController::switchGuide(int frame, bool deleteOnly)
977
{
978
979
    bool markerFound = false;
    if (frame == -1) {
980
        frame = pCore->getTimelinePosition();
981
    }
982
    CommentedTime marker = pCore->projectManager()->current()->getGuideModel()->getMarker(GenTime(frame, pCore->getCurrentFps()), &markerFound);
983
    if (!markerFound) {
984
985
986
987
        if (deleteOnly) {
            pCore->displayMessage(i18n("No guide found at current position"), InformationMessage, 500);
            return;
        }
988
989
        GenTime pos(frame, pCore->getCurrentFps());
        pCore->projectManager()->current()->getGuideModel()->addMarker(pos, i18n("guide"));
990
    } else {
991
        pCore->projectManager()->current()->getGuideModel()->removeMarker(marker.time());
992
993
994
    }
}

Nicolas Carion's avatar
Nicolas Carion committed
995
void TimelineController::addAsset(const QVariantMap &data)
996
997
{
    QString effect = data.value(QStringLiteral("kdenlive/effect")).toString();
998
999
    const auto selection = m_model->getCurrentSelection();
    if (!selection.empty()) {
1000
        QList<int> effect