timelinecontroller.cpp 90 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
    m_disablePreview->setEnabled(false);
80
    connect(pCore.get(), &Core::finalizeRecording, this, &TimelineController::finishRecording);
81 82
}

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

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

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

108 109
void TimelineController::setTargetTracks(QPair<int, int> targets)
{
110 111
    setVideoTarget(targets.first >= 0 && targets.first < m_model->getTracksCount() ? m_model->getTrackIndexFromPosition(targets.first) : -1);
    setAudioTarget(targets.second >= 0 && targets.second < m_model->getTracksCount() ? m_model->getTrackIndexFromPosition(targets.second) : -1);
112 113
}

114 115 116 117 118
std::shared_ptr<TimelineItemModel> TimelineController::getModel() const
{
    return m_model;
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
119 120
void TimelineController::setRoot(QQuickItem *root)
{
121
    m_root = root;
122 123 124 125 126 127 128
}

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

129 130
int TimelineController::getCurrentItem()
{
Nicolas Carion's avatar
Nicolas Carion committed
131
    // TODO: if selection is empty, return topmost clip under timeline cursor
132 133
    auto selection = m_model->getCurrentSelection();
    if (selection.empty()) {
134 135
        return -1;
    }
Nicolas Carion's avatar
Nicolas Carion committed
136
    // TODO: if selection contains more than 1 clip, return topmost clip under timeline cursor in selection
137
    return *(selection.begin());
138 139
}

140 141 142 143 144
double TimelineController::scaleFactor() const
{
    return m_scale;
}

145
const QString TimelineController::getTrackNameFromMltIndex(int trackPos)
146
{
147
    if (trackPos == -1) {
148 149
        return i18n("unknown");
    }
150
    if (trackPos == 0) {
151 152
        return i18n("Black");
    }
153
    return m_model->getTrackTagById(m_model->getTrackIndexFromPosition(trackPos - 1));
154 155
}

156 157
const QString TimelineController::getTrackNameFromIndex(int trackIndex)
{
158
    QString trackName = m_model->getTrackFullName(trackIndex);
159
    return trackName.isEmpty() ? m_model->getTrackTagById(trackIndex) : trackName;
160 161
}

162 163 164 165
QMap<int, QString> TimelineController::getTrackNames(bool videoOnly)
{
    QMap<int, QString> names;
    for (const auto &track : m_model->m_iteratorTable) {
166
        if (videoOnly && m_model->getTrackById_const(track.first)->isAudioTrack()) {
167 168
            continue;
        }
169
        QString trackName = m_model->getTrackFullName(track.first);
170
        names[m_model->getTrackMltIndex(track.first)] = trackName;
171 172
    }
    return names;
173 174
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
175 176
void TimelineController::setScaleFactorOnMouse(double scale, bool zoomOnMouse)
{
177 178 179 180 181 182 183
    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
184 185
}

186 187 188
void TimelineController::setScaleFactor(double scale)
{
    m_scale = scale;
189 190 191
    // Update mainwindow's zoom slider
    emit updateZoom(scale);
    // inform qml
192 193 194 195 196 197 198 199
    emit scaleFactorChanged();
}

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

200 201 202 203 204
int TimelineController::fullDuration() const
{
    return m_duration + TimelineModel::seekDuration;
}

205 206 207 208 209 210 211 212 213
void TimelineController::checkDuration()
{
    int currentLength = m_model->duration();
    if (currentLength != m_duration) {
        m_duration = currentLength;
        emit durationChanged();
    }
}

214
int TimelineController::selectedTrack() const
215
{
216 217 218 219 220 221
    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});
222
    }
223 224 225
    // 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;
226 227
}

228 229 230
void TimelineController::selectCurrentItem(ObjectType type, bool select, bool addToCurrent)
{
    QList<int> toSelect;
Nicolas Carion's avatar
Nicolas Carion committed
231 232
    int currentClip = type == ObjectType::TimelineClip ? m_model->getClipByPosition(m_activeTrack, timelinePosition())
                                                       : m_model->getCompositionByPosition(m_activeTrack, timelinePosition());
233 234 235 236
    if (currentClip == -1) {
        pCore->displayMessage(i18n("No item under timeline cursor in active track"), InformationMessage, 500);
        return;
    }
237 238 239 240
    if (!select) {
        m_model->requestRemoveFromSelection(currentClip);
    } else {
        m_model->requestAddToSelection(currentClip, !addToCurrent);
241 242 243 244 245
    }
}

QList<int> TimelineController::selection() const
{
246
    if (!m_root) return QList<int>();
247 248 249 250 251
    QList<int> items;
    for (int id : m_model->getCurrentSelection()) {
        items << id;
    }
    return items;
252 253
}

254 255 256 257 258 259
void TimelineController::selectItems(const QList<int> &ids)
{
    std::unordered_set<int> ids_s(ids.begin(), ids.end());
    m_model->requestSetSelection(ids_s);
}

260 261 262 263 264 265 266
void TimelineController::setScrollPos(int pos)
{
    if (pos > 0 && m_root) {
        QMetaObject::invokeMethod(m_root, "setScrollPos", Qt::QueuedConnection, Q_ARG(QVariant, pos));
    }
}

267 268 269 270 271 272
void TimelineController::resetView()
{
    m_model->_resetView();
    if (m_root) {
        QMetaObject::invokeMethod(m_root, "updatePalette");
    }
273
    emit colorsChanged();
274 275
}

276 277
bool TimelineController::snap()
{
278 279 280
    return KdenliveSettings::snaptopoints();
}

281 282 283 284 285 286 287 288 289 290
bool TimelineController::ripple()
{
    return false;
}

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

291
int TimelineController::insertClip(int tid, int position, const QString &data_str, bool logUndo, bool refreshView, bool useTargets)
292 293
{
    int id;
294
    if (tid == -1) {
295
        tid = m_activeTrack;
296 297
    }
    if (position == -1) {
298
        position = timelinePosition();
299
    }
300
    if (!m_model->requestClipInsertion(data_str, tid, position, id, logUndo, refreshView, useTargets)) {
301 302 303 304 305
        id = -1;
    }
    return id;
}

306 307 308 309 310 311 312 313 314 315 316 317 318 319
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;
}

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

397 398 399
int TimelineController::insertComposition(int tid, int position, const QString &transitionId, bool logUndo)
{
    int id;
400 401
    int duration = pCore->currentDoc()->getFramePos(KdenliveSettings::transition_duration());
    if (!m_model->requestCompositionInsertion(transitionId, tid, position, duration, nullptr, id, logUndo)) {
402 403 404 405 406 407 408
        id = -1;
    }
    return id;
}

void TimelineController::deleteSelectedClips()
{
409 410
    auto sel = m_model->getCurrentSelection();
    if (sel.empty()) {
411 412
        return;
    }
413 414
    // only need to delete the first item, the others will be deleted in cascade
    m_model->requestItemDeletion(*sel.begin());
415 416
}

417
int TimelineController::getMainSelectedItem(bool restrictToCurrentPos, bool allowComposition)
418 419 420 421 422 423 424 425 426 427 428 429
{
    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;
        }
    }
430 431 432 433 434
    if (!restrictToCurrentPos) {
        if (m_model->isClip(itemId) || (allowComposition && m_model->isComposition(itemId))) {
            return itemId;
        }
    }
435 436 437 438 439 440 441 442 443 444 445
    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;
}

446 447
void TimelineController::copyItem()
{
448 449
    std::unordered_set<int> selectedIds = m_model->getCurrentSelection();
    if (selectedIds.empty()) {
450 451
        return;
    }
452 453 454 455
    int clipId = *(selectedIds.begin());
    QString copyString = TimelineFunctions::copyClips(m_model, selectedIds);
    QClipboard *clipboard = QApplication::clipboard();
    clipboard->setText(copyString);
456
    m_root->setProperty("copiedClip", clipId);
457
    m_model->requestSetSelection(selectedIds);
458 459
}

460
bool TimelineController::pasteItem(int position, int tid)
461
{
462 463
    QClipboard *clipboard = QApplication::clipboard();
    QString txt = clipboard->text();
464 465 466 467 468 469
    if (tid == -1) {
        tid = getMouseTrack();
    }
    if (position == -1) {
        position = getMousePos();
    }
470
    if (tid == -1) {
471
        tid = m_activeTrack;
472 473
    }
    if (position == -1) {
474
        position = timelinePosition();
475
    }
476
    return TimelineFunctions::pasteClips(m_model, txt, tid, position);
477 478
}

479 480
void TimelineController::triggerAction(const QString &name)
{
481
    pCore->triggerAction(name);
482 483 484 485 486 487
}

QString TimelineController::timecode(int frames)
{
    return KdenliveSettings::frametimecode() ? QString::number(frames) : m_model->tractor()->frames_to_time(frames, mlt_time_smpte_df);
}
488

489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
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)
{
516 517 518
    if (tid == -1) {
        tid = m_activeTrack;
    }
519
    QPointer<TrackDialog> d = new TrackDialog(m_model, tid, qApp->activeWindow());
520
    if (d->exec() == QDialog::Accepted) {
521
        int newTid;
522 523
        bool audioRecTrack = d->addRecTrack();
        bool addAVTrack = d->addAVTrack();
524
        m_model->requestTrackInsertion(d->selectedTrackPosition(), newTid, d->trackName(), d->addAudioTrack());
525 526 527 528 529 530 531 532 533
        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);
        }
534
        m_model->buildTrackCompositing(true);
535 536 537
        if (audioRecTrack) {
            m_model->setTrackProperty(newTid, "kdenlive:audio_rec", QStringLiteral("1"));
        }
538
    }
539 540 541 542
}

void TimelineController::deleteTrack(int tid)
{
543 544 545
    if (tid == -1) {
        tid = m_activeTrack;
    }
546
    QPointer<TrackDialog> d = new TrackDialog(m_model, tid, qApp->activeWindow(), true);
547
    if (d->exec() == QDialog::Accepted) {
548
        int selectedTrackIx = d->selectedTrackId();
549 550 551 552 553 554 555 556 557 558
        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);
        }
559
        m_model->requestTrackDeletion(selectedTrackIx);
560
        m_model->buildTrackCompositing(true);
561
        if (m_activeTrack == -1) {
562 563
            setActiveTrack(m_model->getTrackIndexFromPosition(m_model->getTracksCount() - 1));
        }
564
    }
565 566
}

567 568 569 570 571
void TimelineController::showConfig(int page, int tab)
{
    pCore->showConfigDialog(page, tab);
}

572 573
void TimelineController::gotoNextSnap()
{
574 575 576 577
    int nextSnap = m_model->getNextSnapPos(timelinePosition());
    if (nextSnap > timelinePosition()) {
        setPosition(nextSnap);
    }
578 579 580 581
}

void TimelineController::gotoPreviousSnap()
{
582 583 584
    if (timelinePosition() > 0) {
        setPosition(m_model->getPreviousSnapPos(timelinePosition()));
    }
585 586 587 588
}

void TimelineController::groupSelection()
{
589 590
    const auto selection = m_model->getCurrentSelection();
    if (selection.size() < 2) {
591 592 593
        pCore->displayMessage(i18n("Select at least 2 items to group"), InformationMessage, 500);
        return;
    }
594 595 596
    m_model->requestClearSelection();
    m_model->requestClipsGroup(selection);
    m_model->requestSetSelection(selection);
597 598 599 600
}

void TimelineController::unGroupSelection(int cid)
{
601
    auto ids = m_model->getCurrentSelection();
602
    // ask to unselect if needed
603 604
    m_model->requestClearSelection();
    if (cid > -1) {
605 606 607 608
        ids.insert(cid);
    }
    if (!ids.empty()) {
        m_model->requestClipsUngroup(ids);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
609
    }
610 611
}

612 613 614 615 616 617 618
bool TimelineController::dragOperationRunning()
{
    QVariant returnedValue;
    QMetaObject::invokeMethod(m_root, "isDragging", Q_RETURN_ARG(QVariant, returnedValue));
    return returnedValue.toBool();
}

619 620
void TimelineController::setInPoint()
{
621 622
    if (dragOperationRunning()) {
        // Don't allow timeline operation while drag in progress
623
        qDebug() << "Cannot operate while dragging";
624 625 626
        return;
    }

627
    int cursorPos = timelinePosition();
628 629 630
    const auto selection = m_model->getCurrentSelection();
    if (!selection.empty()) {
        for (int id : selection) {
631 632 633 634 635 636
            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);
637 638 639 640
        }
    }
}

641 642 643 644 645
int TimelineController::timelinePosition() const
{
    return m_seekPosition >= 0 ? m_seekPosition : m_position;
}

646 647
void TimelineController::setOutPoint()
{
648 649
    if (dragOperationRunning()) {
        // Don't allow timeline operation while drag in progress
650
        qDebug() << "Cannot operate while dragging";
651 652
        return;
    }
653
    int cursorPos = timelinePosition();
654 655 656
    const auto selection = m_model->getCurrentSelection();
    if (!selection.empty()) {
        for (int id : selection) {
657 658 659 660 661 662
            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);
663 664 665 666
        }
    }
}

667
void TimelineController::editMarker(int cid, int position)
668
{
669
    Q_ASSERT(m_model->isClip(cid));
670
    double speed = m_model->getClipSpeed(cid);
671
    if (position == -1) {
672 673 674
        // Calculate marker position relative to timeline cursor
        position = timelinePosition() - m_model->getClipPosition(cid) + m_model->getClipIn(cid);
        position = position * speed;
675
    }
676
    if (position < (m_model->getClipIn(cid) * speed) || position > (m_model->getClipIn(cid) * speed + m_model->getClipPlaytime(cid))) {
677 678 679
        pCore->displayMessage(i18n("Cannot find clip to edit marker"), InformationMessage, 500);
        return;
    }
680
    std::shared_ptr<ProjectClip> clip = pCore->bin()->getBinClip(getClipBinId(cid));
681 682 683 684 685 686
    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);
    }
687 688
}

689 690 691
void TimelineController::addMarker(int cid, int position)
{
    Q_ASSERT(m_model->isClip(cid));
692
    double speed = m_model->getClipSpeed(cid);
693
    if (position == -1) {
694 695 696
        // Calculate marker position relative to timeline cursor
        position = timelinePosition() - m_model->getClipPosition(cid) + m_model->getClipIn(cid);
        position = position * speed;
697
    }
698 699
    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);
700 701
        return;
    }
702
    std::shared_ptr<ProjectClip> clip = pCore->bin()->getBinClip(getClipBinId(cid));
703
    GenTime pos(position, pCore->getCurrentFps());
704 705 706 707 708 709
    clip->getMarkerModel()->editMarkerGui(pos, qApp->activeWindow(), true, clip.get());
}

void TimelineController::addQuickMarker(int cid, int position)
{
    Q_ASSERT(m_model->isClip(cid));
710
    double speed = m_model->getClipSpeed(cid);
711
    if (position == -1) {
712 713 714
        // Calculate marker position relative to timeline cursor
        position = timelinePosition() - m_model->getClipPosition(cid) + m_model->getClipIn(cid);
        position = position * speed;
715
    }
716 717
    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);
718 719
        return;
    }
720
    std::shared_ptr<ProjectClip> clip = pCore->bin()->getBinClip(getClipBinId(cid));
721
    GenTime pos(position, pCore->getCurrentFps());
722 723 724 725 726 727 728
    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));
729
    double speed = m_model->getClipSpeed(cid);
730
    if (position == -1) {
731 732 733
        // Calculate marker position relative to timeline cursor
        position = timelinePosition() - m_model->getClipPosition(cid) + m_model->getClipIn(cid);
        position = position * speed;
734
    }
735 736
    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);
737 738
        return;
    }
739
    std::shared_ptr<ProjectClip> clip = pCore->bin()->getBinClip(getClipBinId(cid));
740
    GenTime pos(position, pCore->getCurrentFps());
741 742 743 744 745 746 747 748 749 750
    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();
}

751 752
void TimelineController::editGuide(int frame)
{
753
    if (frame == -1) {
754
        frame = timelinePosition();
755
    }
756 757 758
    auto guideModel = pCore->projectManager()->current()->getGuideModel();
    GenTime pos(frame, pCore->getCurrentFps());
    guideModel->editMarkerGui(pos, qApp->activeWindow(), false);
759 760
}

761 762 763 764 765 766 767 768
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);
}

769
void TimelineController::switchGuide(int frame, bool deleteOnly)
770
{
771 772
    bool markerFound = false;
    if (frame == -1) {
773
        frame = timelinePosition();
774
    }
775
    CommentedTime marker = pCore->projectManager()->current()->getGuideModel()->getMarker(GenTime(frame, pCore->getCurrentFps()), &markerFound);
776
    if (!markerFound) {
777 778 779 780
        if (deleteOnly) {
            pCore->displayMessage(i18n("No guide found at current position"), InformationMessage, 500);
            return;
        }
781 782
        GenTime pos(frame, pCore->getCurrentFps());
        pCore->projectManager()->current()->getGuideModel()->addMarker(pos, i18n("guide"));
783
    } else {
784
        pCore->projectManager()->current()->getGuideModel()->removeMarker(marker.time());
785 786 787
    }
}

Nicolas Carion's avatar
Nicolas Carion committed
788
void TimelineController::addAsset(const QVariantMap &data)
789 790
{
    QString effect = data.value(QStringLiteral("kdenlive/effect")).toString();
791 792
    const auto selection = m_model->getCurrentSelection();
    if (!selection.empty()) {
793
        QList<int> effectSelection;
794
        for (int id : selection) {
795
            if (m_model->isClip(id)) {
796
                effectSelection << id;
797
            }
798
        }
799
        bool foundMatch = false;
800
        for (int id : effectSelection) {
801 802 803 804 805 806 807
            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);
808
        }
809 810
    } else {
        pCore->displayMessage(i18n("Select a clip to apply an effect"), InformationMessage, 500);
811 812 813 814 815 816 817 818 819 820 821
    }
}

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

void TimelineController::showAsset(int id)
{
    if (m_model->isComposition(id)) {
822
        emit showTransitionModel(id, m_model->getCompositionParameterModel(id));
823
    } else if (m_model->isClip(id)) {
824 825
        QModelIndex clipIx = m_model->makeClipIndexFromID(id);
        QString clipName = m_model->data(clipIx, Qt::DisplayRole).toString();
826
        bool showKeyframes = m_model->data(clipIx, TimelineModel::ShowKeyframesRole).toInt();
Nicolas Carion's avatar
Nicolas Carion committed
827
        qDebug() << "-----\n// SHOW KEYFRAMES: " << showKeyframes;
Nicolas Carion's avatar
Nicolas Carion committed
828
        emit showItemEffectStack(clipName, m_model->getClipEffectStackModel(id), m_model->getClipFrameSize(id), showKeyframes);
829 830 831
    }
}

832 833
void TimelineController::showTrackAsset(int trackId)
{
Nicolas Carion's avatar
Nicolas Carion committed
834
    emit showItemEffectStack(getTrackNameFromIndex(trackId), m_model->getTrackEffectStackModel(trackId), pCore->getCurrentFrameSize(), false);
835 836
}

837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853
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});
}

854 855
void TimelineController::setPosition