timelinecontroller.cpp 94.7 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

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

int TimelineController::m_duration = 0;

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

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

void TimelineController::prepareClose()
{
    // Delete timeline preview before resetting model so that removing clips from timeline doesn't invalidate
93
94
95
96
    delete m_timelinePreview;
    m_timelinePreview = nullptr;
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
97
void TimelineController::setModel(std::shared_ptr<TimelineItemModel> model)
98
{
99
    delete m_timelinePreview;
100
    m_zone = QPoint(-1, -1);
101
    m_timelinePreview = nullptr;
102
    m_model = std::move(model);
Nicolas Carion's avatar
Nicolas Carion committed
103
104
    connect(m_model.get(), &TimelineItemModel::requestClearAssetView, [&](int id) { pCore->clearAssetPanel(id); });
    connect(m_model.get(), &TimelineItemModel::requestMonitorRefresh, [&]() { pCore->requestMonitorRefresh(); });
105
    connect(m_model.get(), &TimelineModel::invalidateZone, this, &TimelineController::invalidateZone, Qt::DirectConnection);
106
    connect(m_model.get(), &TimelineModel::durationUpdated, this, &TimelineController::checkDuration);
107
    connect(m_model.get(), &TimelineModel::selectionChanged, this, &TimelineController::selectionChanged);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
108
109
}

110
111
void TimelineController::setTargetTracks(QPair<int, int> targets)
{
112
113
    m_hasVideoTarget = targets.first >= 0;
    m_hasAudioTarget = targets.second >= 0;
114
115
116
117
118
119
120
121
    emit hasAudioTargetChanged();
    emit hasVideoTargetChanged();
    if (m_videoTargetActive) {
        setVideoTarget(m_hasVideoTarget && (m_lastVideoTarget > -1) ? m_lastVideoTarget : targets.first);
    }
    if (m_audioTargetActive) {
        setAudioTarget(m_hasAudioTarget && (m_lastAudioTarget > -1) ? m_lastAudioTarget : targets.second);
    }
122
123
}

124
125
126
127
128
std::shared_ptr<TimelineItemModel> TimelineController::getModel() const
{
    return m_model;
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
129
130
void TimelineController::setRoot(QQuickItem *root)
{
131
    m_root = root;
132
133
134
135
136
137
138
}

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

139
140
int TimelineController::getCurrentItem()
{
Nicolas Carion's avatar
Nicolas Carion committed
141
    // TODO: if selection is empty, return topmost clip under timeline cursor
142
143
    auto selection = m_model->getCurrentSelection();
    if (selection.empty()) {
144
145
        return -1;
    }
Nicolas Carion's avatar
Nicolas Carion committed
146
    // TODO: if selection contains more than 1 clip, return topmost clip under timeline cursor in selection
147
    return *(selection.begin());
148
149
}

150
151
152
153
154
double TimelineController::scaleFactor() const
{
    return m_scale;
}

155
const QString TimelineController::getTrackNameFromMltIndex(int trackPos)
156
{
157
    if (trackPos == -1) {
158
159
        return i18n("unknown");
    }
160
    if (trackPos == 0) {
161
162
        return i18n("Black");
    }
163
    return m_model->getTrackTagById(m_model->getTrackIndexFromPosition(trackPos - 1));
164
165
}

166
167
const QString TimelineController::getTrackNameFromIndex(int trackIndex)
{
168
    QString trackName = m_model->getTrackFullName(trackIndex);
169
    return trackName.isEmpty() ? m_model->getTrackTagById(trackIndex) : trackName;
170
171
}

172
173
174
175
QMap<int, QString> TimelineController::getTrackNames(bool videoOnly)
{
    QMap<int, QString> names;
    for (const auto &track : m_model->m_iteratorTable) {
176
        if (videoOnly && m_model->getTrackById_const(track.first)->isAudioTrack()) {
177
178
            continue;
        }
179
        QString trackName = m_model->getTrackFullName(track.first);
180
        names[m_model->getTrackMltIndex(track.first)] = trackName;
181
182
    }
    return names;
183
184
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
185
186
void TimelineController::setScaleFactorOnMouse(double scale, bool zoomOnMouse)
{
187
188
189
190
191
192
193
    if (m_root) {
        m_root->setProperty("zoomOnMouse", zoomOnMouse ? qMin(getMousePos(), duration()) : -1);
        m_scale = scale;
        emit scaleFactorChanged();
    } else {
        qWarning("Timeline root not created, impossible to zoom in");
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
194
195
}

196
197
198
void TimelineController::setScaleFactor(double scale)
{
    m_scale = scale;
199
200
201
    // Update mainwindow's zoom slider
    emit updateZoom(scale);
    // inform qml
202
203
204
205
206
207
208
209
    emit scaleFactorChanged();
}

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

210
211
212
213
214
int TimelineController::fullDuration() const
{
    return m_duration + TimelineModel::seekDuration;
}

215
216
217
218
219
220
221
222
223
void TimelineController::checkDuration()
{
    int currentLength = m_model->duration();
    if (currentLength != m_duration) {
        m_duration = currentLength;
        emit durationChanged();
    }
}

224
int TimelineController::selectedTrack() const
225
{
226
227
228
229
230
231
    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});
232
    }
233
234
235
    // 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;
236
237
}

238
239
240
void TimelineController::selectCurrentItem(ObjectType type, bool select, bool addToCurrent)
{
    QList<int> toSelect;
Nicolas Carion's avatar
Nicolas Carion committed
241
242
    int currentClip = type == ObjectType::TimelineClip ? m_model->getClipByPosition(m_activeTrack, timelinePosition())
                                                       : m_model->getCompositionByPosition(m_activeTrack, timelinePosition());
243
244
245
246
    if (currentClip == -1) {
        pCore->displayMessage(i18n("No item under timeline cursor in active track"), InformationMessage, 500);
        return;
    }
247
248
249
250
    if (!select) {
        m_model->requestRemoveFromSelection(currentClip);
    } else {
        m_model->requestAddToSelection(currentClip, !addToCurrent);
251
252
253
254
255
    }
}

QList<int> TimelineController::selection() const
{
256
    if (!m_root) return QList<int>();
257
    std::unordered_set<int> sel = m_model->getCurrentSelection();
258
    QList<int> items;
259
    for (int id : sel) {
260
261
262
        items << id;
    }
    return items;
263
264
}

265
266
267
268
269
270
void TimelineController::selectItems(const QList<int> &ids)
{
    std::unordered_set<int> ids_s(ids.begin(), ids.end());
    m_model->requestSetSelection(ids_s);
}

271
272
273
274
275
276
277
void TimelineController::setScrollPos(int pos)
{
    if (pos > 0 && m_root) {
        QMetaObject::invokeMethod(m_root, "setScrollPos", Qt::QueuedConnection, Q_ARG(QVariant, pos));
    }
}

278
279
280
281
282
283
void TimelineController::resetView()
{
    m_model->_resetView();
    if (m_root) {
        QMetaObject::invokeMethod(m_root, "updatePalette");
    }
284
    emit colorsChanged();
285
286
}

287
288
bool TimelineController::snap()
{
289
290
291
    return KdenliveSettings::snaptopoints();
}

292
293
294
295
296
297
298
299
300
301
bool TimelineController::ripple()
{
    return false;
}

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

302
int TimelineController::insertClip(int tid, int position, const QString &data_str, bool logUndo, bool refreshView, bool useTargets)
303
304
{
    int id;
305
    if (tid == -1) {
306
        tid = m_activeTrack;
307
308
    }
    if (position == -1) {
309
        position = timelinePosition();
310
    }
311
    if (!m_model->requestClipInsertion(data_str, tid, position, id, logUndo, refreshView, useTargets)) {
312
313
314
315
316
        id = -1;
    }
    return id;
}

317
318
319
320
321
322
323
324
325
326
327
328
329
330
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) {
        position = timelinePosition();
    }
    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;
}

331
int TimelineController::insertNewComposition(int tid, int position, const QString &transitionId, bool logUndo)
332
333
334
335
336
337
338
339
340
341
{
    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)
342
343
{
    int id;
344
345
346
347
348
    int minimum = m_model->getClipPosition(clipId);
    int clip_duration = m_model->getClipPlaytime(clipId);
    int position = minimum;
    if (offset > clip_duration / 2) {
        position += offset;
349
350
351
352
353
354
    } else {
        // Check if we have a composition at beginning
        std::unordered_set<int> existing = m_model->getTrackById_const(tid)->getCompositionsInRange(minimum, minimum + offset);
        if (existing.size() > 0) {
            position += offset;
        }
355
    }
356
    position = qMin(minimum + clip_duration - 1, position);
357
    int duration = qMin(pCore->currentDoc()->getFramePos(KdenliveSettings::transition_duration()), m_model->getTrackById_const(tid)->suggestCompositionLength(position));
358
    int lowerVideoTrackId = m_model->getPreviousVideoTrackIndex(tid);
359
    bool revert = offset > clip_duration / 2;
360
    if (lowerVideoTrackId > 0) {
361
362
        int bottomId = m_model->getTrackById_const(lowerVideoTrackId)->getClipByPosition(position);
        if (bottomId > 0) {
363
            QPair<int, int> bottom(m_model->m_allClips[bottomId]->getPosition(), m_model->m_allClips[bottomId]->getPlaytime());
364
365
366
367
368
            if (bottom.first > minimum && position > bottom.first) {
                int test_duration = m_model->getTrackById_const(tid)->suggestCompositionLength(bottom.first);
                if (test_duration > 0) {
                    position = bottom.first;
                    duration = test_duration;
369
                    revert = position > minimum;
370
371
372
                }
            }
        }
373
374
375
376
377
        int duration2 = m_model->getTrackById_const(lowerVideoTrackId)->suggestCompositionLength(position);
        if (duration2 > 0) {
            duration = (duration > 0) ? qMin(duration, duration2) : duration2;
        }
    }
378
379
    if (duration < 0) {
        duration = pCore->currentDoc()->getFramePos(KdenliveSettings::transition_duration());
380
    } else if (duration <= 1) {
381
382
        // if suggested composition duration is lower than 4 frames, use default
        duration = pCore->currentDoc()->getFramePos(KdenliveSettings::transition_duration());
383
384
        if (minimum + clip_duration - position < 3) {
            position = minimum + clip_duration - duration;
385
        }
386
    }
387
    std::unique_ptr<Mlt::Properties> props(nullptr);
388
    if (revert) {
Nicolas Carion's avatar
Nicolas Carion committed
389
        props = std::make_unique<Mlt::Properties>();
390
391
392
393
394
395
396
397
        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");
        }
    }
398
    if (!m_model->requestCompositionInsertion(transitionId, tid, position, duration, std::move(props), id, logUndo)) {
399
        id = -1;
400
        pCore->displayMessage(i18n("Could not add composition at selected position"), InformationMessage, 500);
401
402
403
404
    }
    return id;
}

405
406
407
int TimelineController::insertComposition(int tid, int position, const QString &transitionId, bool logUndo)
{
    int id;
408
409
    int duration = pCore->currentDoc()->getFramePos(KdenliveSettings::transition_duration());
    if (!m_model->requestCompositionInsertion(transitionId, tid, position, duration, nullptr, id, logUndo)) {
410
411
412
413
414
415
416
        id = -1;
    }
    return id;
}

void TimelineController::deleteSelectedClips()
{
417
418
    auto sel = m_model->getCurrentSelection();
    if (sel.empty()) {
419
420
        return;
    }
421
422
    // only need to delete the first item, the others will be deleted in cascade
    m_model->requestItemDeletion(*sel.begin());
423
424
}

425
int TimelineController::getMainSelectedItem(bool restrictToCurrentPos, bool allowComposition)
426
427
428
429
430
431
432
433
434
435
436
437
{
    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;
        }
    }
438
439
440
441
442
    if (!restrictToCurrentPos) {
        if (m_model->isClip(itemId) || (allowComposition && m_model->isComposition(itemId))) {
            return itemId;
        }
    }
443
444
445
446
447
448
449
450
451
452
453
    if (m_model->isClip(itemId)) {
        int position = timelinePosition();
        int start = m_model->getClipPosition(itemId);
        int end = start + m_model->getClipPlaytime(itemId);
        if (position >= start && position <= end) {
            return itemId;
        }
    }
    return -1;
}

454
455
void TimelineController::copyItem()
{
456
457
    std::unordered_set<int> selectedIds = m_model->getCurrentSelection();
    if (selectedIds.empty()) {
458
459
        return;
    }
460
461
462
463
    int clipId = *(selectedIds.begin());
    QString copyString = TimelineFunctions::copyClips(m_model, selectedIds);
    QClipboard *clipboard = QApplication::clipboard();
    clipboard->setText(copyString);
464
    m_root->setProperty("copiedClip", clipId);
465
    m_model->requestSetSelection(selectedIds);
466
467
}

468
bool TimelineController::pasteItem(int position, int tid)
469
{
470
471
    QClipboard *clipboard = QApplication::clipboard();
    QString txt = clipboard->text();
472
473
474
475
476
477
    if (tid == -1) {
        tid = getMouseTrack();
    }
    if (position == -1) {
        position = getMousePos();
    }
478
    if (tid == -1) {
479
        tid = m_activeTrack;
480
481
    }
    if (position == -1) {
482
        position = timelinePosition();
483
    }
484
    return TimelineFunctions::pasteClips(m_model, txt, tid, position);
485
486
}

487
488
void TimelineController::triggerAction(const QString &name)
{
489
    pCore->triggerAction(name);
490
491
}

492
QString TimelineController::timecode(int frames) const
493
494
495
{
    return KdenliveSettings::frametimecode() ? QString::number(frames) : m_model->tractor()->frames_to_time(frames, mlt_time_smpte_df);
}
496

497
498
499
500
501
502
QString TimelineController::framesToClock(int frames) const
{
    return m_model->tractor()->frames_to_time(frames, mlt_time_clock);
}

QString TimelineController::simplifiedTC(int frames) const
503
504
505
506
507
508
509
510
{
    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;
}

511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
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)
{
538
539
540
    if (tid == -1) {
        tid = m_activeTrack;
    }
541
    QPointer<TrackDialog> d = new TrackDialog(m_model, tid, qApp->activeWindow());
542
    if (d->exec() == QDialog::Accepted) {
543
        int newTid;
544
545
        bool audioRecTrack = d->addRecTrack();
        bool addAVTrack = d->addAVTrack();
546
        m_model->requestTrackInsertion(d->selectedTrackPosition(), newTid, d->trackName(), d->addAudioTrack());
547
548
549
550
551
552
553
554
555
        if (addAVTrack) {
            int newTid2;
            int mirrorPos = 0;
            int mirrorId = m_model->getMirrorAudioTrackId(newTid);
            if (mirrorId > -1) {
                mirrorPos = m_model->getTrackMltIndex(mirrorId);
            }
            m_model->requestTrackInsertion(mirrorPos, newTid2, d->trackName(), true);
        }
556
        m_model->buildTrackCompositing(true);
557
558
559
        if (audioRecTrack) {
            m_model->setTrackProperty(newTid, "kdenlive:audio_rec", QStringLiteral("1"));
        }
560
    }
561
562
563
564
}

void TimelineController::deleteTrack(int tid)
{
565
566
567
    if (tid == -1) {
        tid = m_activeTrack;
    }
568
    QPointer<TrackDialog> d = new TrackDialog(m_model, tid, qApp->activeWindow(), true);
569
    if (d->exec() == QDialog::Accepted) {
570
        int selectedTrackIx = d->selectedTrackId();
571
572
573
574
575
576
577
578
579
580
        if (m_activeTrack == selectedTrackIx) {
            // Make sure we don't keep an index on a deleted track
            m_activeTrack = -1;
        }
        if (m_model->m_audioTarget == selectedTrackIx) {
            setAudioTarget(-1);
        }
        if (m_model->m_videoTarget == selectedTrackIx) {
            setVideoTarget(-1);
        }
581
582
583
584
585
586
587
588
        if (m_lastAudioTarget == selectedTrackIx) {
            m_lastAudioTarget = -1;
            emit lastAudioTargetChanged();
        }
        if (m_lastVideoTarget == selectedTrackIx) {
            m_lastVideoTarget = -1;
            emit lastVideoTargetChanged();
        }
589
        m_model->requestTrackDeletion(selectedTrackIx);
590
        m_model->buildTrackCompositing(true);
591
        if (m_activeTrack == -1) {
592
593
            setActiveTrack(m_model->getTrackIndexFromPosition(m_model->getTracksCount() - 1));
        }
594
    }
595
596
}

597
598
599
600
601
void TimelineController::showConfig(int page, int tab)
{
    pCore->showConfigDialog(page, tab);
}

602
603
void TimelineController::gotoNextSnap()
{
604
605
606
607
    int nextSnap = m_model->getNextSnapPos(timelinePosition());
    if (nextSnap > timelinePosition()) {
        setPosition(nextSnap);
    }
608
609
610
611
}

void TimelineController::gotoPreviousSnap()
{
612
613
614
    if (timelinePosition() > 0) {
        setPosition(m_model->getPreviousSnapPos(timelinePosition()));
    }
615
616
617
618
}

void TimelineController::groupSelection()
{
619
620
    const auto selection = m_model->getCurrentSelection();
    if (selection.size() < 2) {
621
622
623
        pCore->displayMessage(i18n("Select at least 2 items to group"), InformationMessage, 500);
        return;
    }
624
625
626
    m_model->requestClearSelection();
    m_model->requestClipsGroup(selection);
    m_model->requestSetSelection(selection);
627
628
629
630
}

void TimelineController::unGroupSelection(int cid)
{
631
    auto ids = m_model->getCurrentSelection();
632
    // ask to unselect if needed
633
634
    m_model->requestClearSelection();
    if (cid > -1) {
635
636
637
638
        ids.insert(cid);
    }
    if (!ids.empty()) {
        m_model->requestClipsUngroup(ids);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
639
    }
640
641
}

642
643
644
645
646
647
648
bool TimelineController::dragOperationRunning()
{
    QVariant returnedValue;
    QMetaObject::invokeMethod(m_root, "isDragging", Q_RETURN_ARG(QVariant, returnedValue));
    return returnedValue.toBool();
}

649
650
void TimelineController::setInPoint()
{
651
652
    if (dragOperationRunning()) {
        // Don't allow timeline operation while drag in progress
653
        qDebug() << "Cannot operate while dragging";
654
655
656
        return;
    }

657
    int cursorPos = timelinePosition();
658
    const auto selection = m_model->getCurrentSelection();
659
    bool selectionFound = false;
660
661
    if (!selection.empty()) {
        for (int id : selection) {
662
663
664
665
666
667
            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);
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
            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);
                }
            }
688
689
690
691
        }
    }
}

692
693
694
695
696
int TimelineController::timelinePosition() const
{
    return m_seekPosition >= 0 ? m_seekPosition : m_position;
}

697
698
void TimelineController::setOutPoint()
{
699
700
    if (dragOperationRunning()) {
        // Don't allow timeline operation while drag in progress
701
        qDebug() << "Cannot operate while dragging";
702
703
        return;
    }
704
    int cursorPos = timelinePosition();
705
    const auto selection = m_model->getCurrentSelection();
706
    bool selectionFound = false;
707
708
    if (!selection.empty()) {
        for (int id : selection) {
709
710
711
712
713
714
            int start = m_model->getItemPosition(id);
            if (start + m_model->getItemPlaytime(id) == cursorPos) {
                continue;
            }
            int size = cursorPos - start;
            m_model->requestItemResize(id, size, true, true, 0, false);
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
            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) {
                    int size = cursorPos - start;
                    m_model->requestItemResize(cid, size, true, true, 0, false);
                }
            }
733
734
735
736
        }
    }
}

737
void TimelineController::editMarker(int cid, int position)
738
{
739
    Q_ASSERT(m_model->isClip(cid));
740
    double speed = m_model->getClipSpeed(cid);
741
    if (position == -1) {
742
743
744
        // Calculate marker position relative to timeline cursor
        position = timelinePosition() - m_model->getClipPosition(cid) + m_model->getClipIn(cid);
        position = position * speed;
745
    }
746
    if (position < (m_model->getClipIn(cid) * speed) || position > (m_model->getClipIn(cid) * speed + m_model->getClipPlaytime(cid))) {
747
748
749
        pCore->displayMessage(i18n("Cannot find clip to edit marker"), InformationMessage, 500);
        return;
    }
750
    std::shared_ptr<ProjectClip> clip = pCore->bin()->getBinClip(getClipBinId(cid));
751
752
753
754
755
756
    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);
    }
757
758
}

759
760
761
void TimelineController::addMarker(int cid, int position)
{
    Q_ASSERT(m_model->isClip(cid));
762
    double speed = m_model->getClipSpeed(cid);
763
    if (position == -1) {
764
765
766
        // Calculate marker position relative to timeline cursor
        position = timelinePosition() - m_model->getClipPosition(cid) + m_model->getClipIn(cid);
        position = position * speed;
767
    }
768
769
    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);
770
771
        return;
    }
772
    std::shared_ptr<ProjectClip> clip = pCore->bin()->getBinClip(getClipBinId(cid));
773
    GenTime pos(position, pCore->getCurrentFps());
774
775
776
777
778
779
    clip->getMarkerModel()->editMarkerGui(pos, qApp->activeWindow(), true, clip.get());
}

void TimelineController::addQuickMarker(int cid, int position)
{
    Q_ASSERT(m_model->isClip(cid));
780
    double speed = m_model->getClipSpeed(cid);
781
    if (position == -1) {
782
783
784
        // Calculate marker position relative to timeline cursor
        position = timelinePosition() - m_model->getClipPosition(cid) + m_model->getClipIn(cid);
        position = position * speed;
785
    }
786
787
    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);
788
789
        return;
    }
790
    std::shared_ptr<ProjectClip> clip = pCore->bin()->getBinClip(getClipBinId(cid));
791
    GenTime pos(position, pCore->getCurrentFps());
792
793
794
795
796
797
798
    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)
{
    Q_ASSERT(m_model->isClip(cid));
799
    double speed = m_model->getClipSpeed(cid);
800
    if (position == -1) {
801
802
803
        // Calculate marker position relative to timeline cursor
        position = timelinePosition() - m_model->getClipPosition(cid) + m_model->getClipIn(cid);
        position = position * speed;
804
    }
805
806
    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);
807
808
        return;
    }
809
    std::shared_ptr<ProjectClip> clip = pCore->bin()->getBinClip(getClipBinId(cid));
810
    GenTime pos(position, pCore->getCurrentFps());
811
812
813
814
815
816
817
818
819
820
    clip->getMarkerModel()->removeMarker(pos);
}

void TimelineController::deleteAllMarkers(int cid)
{
    Q_ASSERT(m_model->isClip(cid));
    std::shared_ptr<ProjectClip> clip = pCore->bin()->getBinClip(getClipBinId(cid));
    clip->getMarkerModel()->removeAllMarkers();
}

821
822
void TimelineController::editGuide(int frame)
{
823
    if (frame == -1) {
824
        frame = timelinePosition();
825
    }
826
827
828
    auto guideModel = pCore->projectManager()->current()->getGuideModel();
    GenTime pos(frame, pCore->getCurrentFps());
    guideModel->editMarkerGui(pos, qApp->activeWindow(), false);
829
830
}

831
832
833
834
835
836
837
838
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);
}

839
void TimelineController::switchGuide(int frame, bool deleteOnly)
840
{
841
842
    bool markerFound = false;
    if (frame == -1) {
843
        frame = timelinePosition();
844
    }
845
    CommentedTime marker = pCore->projectManager()->current()->getGuideModel()->getMarker(GenTime(frame, pCore->getCurrentFps()), &markerFound);
846
    if (!markerFound) {
847
848
849
850
        if (deleteOnly) {
            pCore->displayMessage(i18n("No guide found at current position"), InformationMessage, 500);
            return;
        }
851
852
        GenTime pos(frame, pCore->getCurrentFps());
        pCore->projectManager()->current()->getGuideModel()->addMarker(pos, i18n("guide"));
853
    } else {
854
        pCore->projectManager()->current()->getGuideModel()->removeMarker(marker.time());
855
856
857
    }
}

Nicolas Carion's avatar
Nicolas Carion committed
858
void TimelineController::addAsset(const QVariantMap &data)
859
860
{
    QString effect = data.value(QStringLiteral("kdenlive/effect")).toString();
861
862
    const auto selection = m_model->getCurrentSelection();
    if (!selection.empty()) {
863
        QList<int> effectSelection;
864
        for (int id : selection) {
865
            if (m_model->isClip(id)) {
866
                effectSelection << id;
867
            }
868
        }
869
        bool foundMatch = false;
870
        for (int id : effectSelection) {
871
872
873
874
875
876
877
            if (m_model->addClipEffect(id, effect, false)) {
                foundMatch = true;
            }
        }
        if (!foundMatch) {
            QString effectName = EffectsRepository::get()->getName(effect);
            pCore->displayMessage(i18n("Cannot add effect %1 to selected clip", effectName), InformationMessage, 500);
878
        }
879
880
    } else {
        pCore->displayMessage(i18n("Select a clip to apply an effect"), InformationMessage, 500);
881
882
883
884
885
886
887
888
889
890
891
    }
}

void TimelineController::requestRefresh()
{
    pCore->requestMonitorRefresh();
}

void TimelineController::showAsset(int id)
{
    if (m_model->isComposition(id)) {
892
        emit showTransitionModel(id, m_model->getCompositionParameterModel(id));
893
    } else if (m_model->isClip(id)) {
894
895
        QModelIndex clipIx = m_model->makeClipIndexFromID(id);
        QString clipName = m_model->data(clipIx, Qt::DisplayRole).toString();
896
        bool showKeyframes = m_model->data(clipIx, TimelineModel::ShowKeyframesRole).toInt();
Nicolas Carion's avatar
Nicolas Carion committed
897
        qDebug() << "-----\n// SHOW KEYFRAMES: " << showKeyframes;
Nicolas Carion's avatar
Nicolas Carion committed
898
        emit showItemEffectStack(clipName, m_model->getClipEffectStackModel(id), m_model->getClipFrameSize(id), showKeyframes);
899
900
901
    }
}

902
903
void TimelineController::showTrackAsset(int trackId)
{
Nicolas Carion's avatar
Nicolas Carion committed
904
    emit showItemEffectStack(getTrackNameFromIndex(trackId), m_model->getTrackEffectStackModel(trackId), pCore->getCurrentFrameSize(), false);
905
906
}

907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
void TimelineController::adjustAllTrackHeight(int trackId, int height)
{
    bool isAudio = m_model->getTrackById_const(trackId)->isAudioTrack();
    auto it = m_model->m_allTracks.cbegin();
    while (it != m_model->m_allTracks.cend()) {
        int target_track = (*it)->getId();
        if (target_track != trackId && m_model->getTrackById_const(target_track)->isAudioTrack() == isAudio) {
            m_model->getTrackById(target_track)->setProperty(QStringLiteral("kdenlive:trackheight"), QString::number(height));
        }
        ++it;
    }
    int tracksCount = m_model->getTracksCount();
    QModelIndex modelStart = m_model->makeTrackIndexFromID(m_model->getTrackIndexFromPosition(0));
    QModelIndex modelEnd = m_model->makeTrackIndexFromID(m_model->getTrackIndexFromPosition(tracksCount - 1));
    m_model->dataChanged(modelStart, modelEnd, {TimelineModel::HeightRole});
}

924
925
void TimelineController::setPosition(int position)
{
926
    setSeekPosition(position);
927
928
929
    emit seeked(position);
}

930
931
void TimelineController::setAudioTarget(int track)
{
932
    if (track > -1 && !m_model->isTrack(track) || !m_hasAudioTarget) {
933
934
        return;
    }
935
    m_model->m_audioTarget = track;
936
937
938
939
940
    emit audioTargetChanged();
}

void TimelineController::setVideoTarget(int track)
{
941
    if (track > -1 && !m_model->isTrack(track) || !m_hasVideoTarget) {
942
943
        return;
    }
944
    m_model->m_videoTarget = track;
945
946
947
    emit videoTargetChanged();
}

948
949
void TimelineController::setActiveTrack(int track)
{
950
951
952
    if (track > -1 && !m_model->isTrack(track)) {
        return;
    }
953
954
955
956
    m_activeTrack = track;
    emit activeTrackChanged();
}

957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
void TimelineController::setSeekPosition(int position)
{
    m_seekPosition = position;
    emit seekPositionChanged();
}

void TimelineController::onSeeked(int position)
{
    m_position = position;
    emit positionChanged();
    if (m_seekPosition > -1 && position == m_seekPosition) {
        m_seekPosition = -1;
        emit seekPositionChanged();
    }
}
972
973
974

void TimelineController::setZone(const QPoint &zone)
{
975
976
977
978
    if (m_zone.x() > 0) {
        m_model->removeSnap(m_zone.x());
    }
    if (m_zone.y() > 0) {
979
        m_model->removeSnap(m_zone.y() - 1);
980
981
982
983
984
    }
    if (zone.x() > 0) {
        m_model->addSnap(zone.x());
    }
    if (zone.y() > 0) {
985
        m_model->addSnap(zone.y() - 1);
986
    }
987
988
989
990
991
992
    m_zone = zone;
    emit zoneChanged();
}

void TimelineController::setZoneIn(int inPoint)
{
993
994
995
996
997
998
    if (m_zone.x() > 0) {
        m_model->removeSnap(m_zone.x());
    }
    if (inPoint > 0) {
        m_model->addSnap(inPoint);
    }
999
1000
1001
1002
1003
1004
    m_zone.setX(inPoint);
    emit zoneMoved(m_zone);
}

void TimelineController::setZoneOut(int outPoint)
{
1005
    if (m_zone.y() > 0) {
1006
        m_model->removeSnap(m_zone.y() - 1);