timelinecontroller.cpp 147 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/model/subtitlemodel.hpp"
29
#include "bin/projectclip.h"
30
#include "bin/projectfolder.h"
Nicolas Carion's avatar
Nicolas Carion committed
31
#include "bin/projectitemmodel.h"
Nicolas Carion's avatar
linting  
Nicolas Carion committed
32
#include "core.h"
Nicolas Carion's avatar
Nicolas Carion committed
33
#include "dialogs/spacerdialog.h"
34
#include "dialogs/speeddialog.h"
35
#include "doc/kdenlivedoc.h"
36
#include "effects/effectsrepository.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
37
#include "effects/effectstack/model/effectstackmodel.hpp"
Nicolas Carion's avatar
linting  
Nicolas Carion committed
38
#include "kdenlivesettings.h"
39
#include "lib/audio/audioEnvelope.h"
40 41
#include "mainwindow.h"
#include "monitor/monitormanager.h"
Nicolas Carion's avatar
Nicolas Carion committed
42
#include "previewmanager.h"
Nicolas Carion's avatar
linting  
Nicolas Carion committed
43
#include "project/projectmanager.h"
44
#include "timeline2/model/clipmodel.hpp"
45
#include "timeline2/model/compositionmodel.hpp"
46
#include "timeline2/model/groupsmodel.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
47 48
#include "timeline2/model/timelineitemmodel.hpp"
#include "timeline2/model/trackmodel.hpp"
49
#include "timeline2/view/dialogs/clipdurationdialog.h"
Nicolas Carion's avatar
Nicolas Carion committed
50
#include "timeline2/view/dialogs/trackdialog.h"
Nicolas Carion's avatar
Nicolas Carion committed
51
#include "transitions/transitionsrepository.hpp"
52
#include "audiomixer/mixermanager.hpp"
53
#include "ui_import_subtitle_ui.h"
54

55
#include <KColorScheme>
56
#include <KMessageBox>
57
#include <KUrlRequesterDialog>
58
#include <KRecentDirs>
59
#include <QApplication>
Nicolas Carion's avatar
Nicolas Carion committed
60
#include <QClipboard>
Nicolas Carion's avatar
Nicolas Carion committed
61
#include <QQuickItem>
Nicolas Carion's avatar
Nicolas Carion committed
62
#include <memory>
63
#include <unistd.h>
64 65 66

int TimelineController::m_duration = 0;

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

90 91
TimelineController::~TimelineController()
{
92 93 94 95 96
    prepareClose();
}

void TimelineController::prepareClose()
{
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
97
    // Clear roor so we don't call its methods anymore
98
    QObject::disconnect( m_deleteConnection );
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
99 100
    m_ready = false;
    m_root = nullptr;
101
    // Delete timeline preview before resetting model so that removing clips from timeline doesn't invalidate
102 103 104 105
    delete m_timelinePreview;
    m_timelinePreview = nullptr;
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
106
void TimelineController::setModel(std::shared_ptr<TimelineItemModel> model)
107
{
108
    delete m_timelinePreview;
109
    m_zone = QPoint(-1, -1);
110
    m_timelinePreview = nullptr;
111
    m_model = std::move(model);
112
    m_activeSnaps.clear();
113
    connect(m_model.get(), &TimelineItemModel::requestClearAssetView, pCore.get(), &Core::clearAssetPanel);
Vincent Pinon's avatar
Vincent Pinon committed
114
    m_deleteConnection = connect(m_model.get(), &TimelineItemModel::checkItemDeletion, this, [this] (int id) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
115
        if (m_ready) {
116 117 118
            QMetaObject::invokeMethod(m_root, "checkDeletion", Qt::QueuedConnection, Q_ARG(QVariant, id));
        }
    });
Julius Künzel's avatar
Julius Künzel committed
119 120 121 122 123 124 125
    connect(m_model.get(), &TimelineItemModel::showTrackEffectStack, this, [&](int tid) {
        if (tid > -1) {
            showTrackAsset(tid);
        } else {
            showMasterEffects();
        }
    });
Nicolas Carion's avatar
Nicolas Carion committed
126
    connect(m_model.get(), &TimelineItemModel::requestMonitorRefresh, [&]() { pCore->requestMonitorRefresh(); });
127
    connect(m_model.get(), &TimelineModel::invalidateZone, this, &TimelineController::invalidateZone, Qt::DirectConnection);
128
    connect(m_model.get(), &TimelineModel::durationUpdated, this, &TimelineController::checkDuration);
129
    connect(m_model.get(), &TimelineModel::selectionChanged, this, &TimelineController::selectionChanged);
130 131 132 133
    connect(m_model.get(), &TimelineModel::selectedMixChanged, [this] (int cid, const std::shared_ptr<AssetParameterModel> &asset) {
        emit showMixModel(cid, asset);
        emit selectedMixChanged();
    });
134
    connect(m_model.get(), &TimelineModel::checkTrackDeletion, this, &TimelineController::checkTrackDeletion, Qt::DirectConnection);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
135 136
}

137 138 139 140 141
void TimelineController::restoreTargetTracks()
{
    setTargetTracks(m_hasVideoTarget, m_model->m_binAudioTargets);
}

142
void TimelineController::setTargetTracks(bool hasVideo, QMap <int, QString> audioTargets)
143 144
{
    int videoTrack = -1;
145
    m_model->m_binAudioTargets = audioTargets;
146
    QMap<int, int> audioTracks;
147
    m_hasVideoTarget = hasVideo;
148
    m_hasAudioTarget = audioTargets.size();
149 150 151
    if (m_hasVideoTarget) {
        videoTrack = m_model->getFirstVideoTrackIndex();
    }
152 153
    if (m_hasAudioTarget > 0) {
        QVector <int> tracks;
154 155 156 157 158 159 160
        auto it = m_model->m_allTracks.cbegin();
        while (it != m_model->m_allTracks.cend()) {
            if ((*it)->isAudioTrack()) {
                tracks << (*it)->getId();
            }
            ++it;
        }
161
        if (KdenliveSettings::multistream_checktrack() && audioTargets.count() > tracks.count()) {
162
            pCore->bin()->checkProjectAudioTracks(QString(), audioTargets.count());
163
        }
164 165 166 167 168 169 170
        QMapIterator <int, QString>st(audioTargets);
        while (st.hasNext()) {
            st.next();
            if (tracks.isEmpty()) {
                break;
            }
            audioTracks.insert(tracks.takeLast(), st.key());
171 172
        }
    }
173 174
    emit hasAudioTargetChanged();
    emit hasVideoTargetChanged();
175 176
    setVideoTarget(m_hasVideoTarget && (m_lastVideoTarget > -1) ? m_lastVideoTarget : videoTrack);
    setAudioTarget(audioTracks);
177 178
}

179 180 181 182 183
std::shared_ptr<TimelineItemModel> TimelineController::getModel() const
{
    return m_model;
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
184 185
void TimelineController::setRoot(QQuickItem *root)
{
186
    m_root = root;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
187
    m_ready = true;
188 189 190 191 192 193 194
}

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

195 196 197 198 199
Mlt::Producer TimelineController::trackProducer(int tid)
{
    return *(m_model->getTrackById(tid).get());
}

200 201 202 203 204
double TimelineController::scaleFactor() const
{
    return m_scale;
}

205
const QString TimelineController::getTrackNameFromMltIndex(int trackPos)
206
{
207
    if (trackPos == -1) {
208 209
        return i18n("unknown");
    }
210
    if (trackPos == 0) {
211 212
        return i18n("Black");
    }
213
    return m_model->getTrackTagById(m_model->getTrackIndexFromPosition(trackPos - 1));
214 215
}

216 217
const QString TimelineController::getTrackNameFromIndex(int trackIndex)
{
218
    QString trackName = m_model->getTrackFullName(trackIndex);
219
    return trackName.isEmpty() ? m_model->getTrackTagById(trackIndex) : trackName;
220 221
}

222 223 224 225
QMap<int, QString> TimelineController::getTrackNames(bool videoOnly)
{
    QMap<int, QString> names;
    for (const auto &track : m_model->m_iteratorTable) {
226
        if (videoOnly && m_model->getTrackById_const(track.first)->isAudioTrack()) {
227 228
            continue;
        }
229
        QString trackName = m_model->getTrackFullName(track.first);
230
        names[m_model->getTrackMltIndex(track.first)] = trackName;
231 232
    }
    return names;
233 234
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
235 236
void TimelineController::setScaleFactorOnMouse(double scale, bool zoomOnMouse)
{
237
    if (m_root) {
238
        m_root->setProperty("zoomOnMouse", zoomOnMouse ? qBound(0, getMousePos(), duration()) : -1);
239 240 241 242 243
        m_scale = scale;
        emit scaleFactorChanged();
    } else {
        qWarning("Timeline root not created, impossible to zoom in");
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
244 245
}

246 247 248
void TimelineController::setScaleFactor(double scale)
{
    m_scale = scale;
249 250 251
    // Update mainwindow's zoom slider
    emit updateZoom(scale);
    // inform qml
252 253 254 255 256 257 258 259
    emit scaleFactorChanged();
}

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

260 261 262 263 264
int TimelineController::fullDuration() const
{
    return m_duration + TimelineModel::seekDuration;
}

265 266 267 268 269 270 271 272 273
void TimelineController::checkDuration()
{
    int currentLength = m_model->duration();
    if (currentLength != m_duration) {
        m_duration = currentLength;
        emit durationChanged();
    }
}

274
int TimelineController::selectedTrack() const
275
{
276 277 278 279 280 281
    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});
282
    }
283 284 285
    // 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;
286 287
}

288 289
void TimelineController::selectCurrentItem(ObjectType type, bool select, bool addToCurrent)
{
290 291
    int currentClip = type == ObjectType::TimelineClip ? m_model->getClipByPosition(m_activeTrack, pCore->getTimelinePosition())
                                                       : m_model->getCompositionByPosition(m_activeTrack, pCore->getTimelinePosition());
292 293 294 295
    if (currentClip == -1) {
        pCore->displayMessage(i18n("No item under timeline cursor in active track"), InformationMessage, 500);
        return;
    }
296 297 298 299
    if (!select) {
        m_model->requestRemoveFromSelection(currentClip);
    } else {
        m_model->requestAddToSelection(currentClip, !addToCurrent);
300 301 302 303 304
    }
}

QList<int> TimelineController::selection() const
{
305
    if (!m_root) return QList<int>();
306
    std::unordered_set<int> sel = m_model->getCurrentSelection();
307
    QList<int> items;
308
    for (int id : sel) {
309 310 311
        items << id;
    }
    return items;
312 313
}

314 315 316 317 318
int TimelineController::selectedMix() const
{
    return m_model->m_selectedMix;
}

319 320 321 322 323 324
void TimelineController::selectItems(const QList<int> &ids)
{
    std::unordered_set<int> ids_s(ids.begin(), ids.end());
    m_model->requestSetSelection(ids_s);
}

325 326 327 328 329 330 331
void TimelineController::setScrollPos(int pos)
{
    if (pos > 0 && m_root) {
        QMetaObject::invokeMethod(m_root, "setScrollPos", Qt::QueuedConnection, Q_ARG(QVariant, pos));
    }
}

332 333 334 335 336 337
void TimelineController::resetView()
{
    m_model->_resetView();
    if (m_root) {
        QMetaObject::invokeMethod(m_root, "updatePalette");
    }
338
    emit colorsChanged();
339 340
}

341 342
bool TimelineController::snap()
{
343 344 345
    return KdenliveSettings::snaptopoints();
}

346 347 348 349 350 351 352 353 354 355
bool TimelineController::ripple()
{
    return false;
}

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

356
int TimelineController::insertClip(int tid, int position, const QString &data_str, bool logUndo, bool refreshView, bool useTargets)
357 358
{
    int id;
359
    if (tid == -1) {
360
        tid = m_activeTrack;
361 362
    }
    if (position == -1) {
363
        position = pCore->getTimelinePosition();
364
    }
365
    if (!m_model->requestClipInsertion(data_str, tid, position, id, logUndo, refreshView, useTargets)) {
366 367 368 369 370
        id = -1;
    }
    return id;
}

371 372 373 374 375 376 377
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) {
378
        position = pCore->getTimelinePosition();
379 380 381 382 383 384
    }
    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;
}

385
int TimelineController::insertNewComposition(int tid, int position, const QString &transitionId, bool logUndo)
386 387 388 389 390 391 392 393 394 395
{
    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)
396 397
{
    int id;
398
    int minimumPos = clipId > -1 ? m_model->getClipPosition(clipId) : offset;
399
    int clip_duration = clipId > -1 ? m_model->getClipPlaytime(clipId) : pCore->getDurationFromString(KdenliveSettings::transition_duration());
400 401
    int endPos = minimumPos + clip_duration;
    int position = minimumPos;
402
    int duration = qMin(clip_duration, pCore->getDurationFromString(KdenliveSettings::transition_duration()));
403
    int lowerVideoTrackId = m_model->getPreviousVideoTrackIndex(tid);
404
    bool revert = offset > clip_duration / 2;
405
    int bottomId = 0;
406
    if (lowerVideoTrackId > 0) {
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
        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);
427
                if (test_duration > 0) {
428 429 430 431
                    offset -= (bottom.first - position);
                    position = bottom.first;
                    duration = test_duration;
                    revert = position > minimumPos;
432 433
                }
            }
434 435 436 437 438 439
        } 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);
            }
440 441
        }
    }
442
    int defaultLength = pCore->getDurationFromString(KdenliveSettings::transition_duration());
443
    bool isShortComposition = TransitionsRepository::get()->getType(transitionId) == AssetListType::AssetType::VideoShortComposition;
444 445
    if (duration < 0 || (isShortComposition && duration > 1.5 * defaultLength)) {
        duration = defaultLength;
446
    } else if (duration <= 1) {
447
        // if suggested composition duration is lower than 4 frames, use default
448
        duration = pCore->getDurationFromString(KdenliveSettings::transition_duration());
449 450
        if (minimumPos + clip_duration - position < 3) {
            position = minimumPos + clip_duration - duration;
451
        }
452
    }
453 454 455 456
    QPair<int, int> finalPos = m_model->getTrackById_const(tid)->validateCompositionLength(position, offset, duration, endPos);
    position = finalPos.first;
    duration = finalPos.second;

457
    std::unique_ptr<Mlt::Properties> props(nullptr);
458
    if (revert) {
Nicolas Carion's avatar
Nicolas Carion committed
459
        props = std::make_unique<Mlt::Properties>();
460 461 462 463 464 465 466 467
        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");
        }
    }
468
    if (!m_model->requestCompositionInsertion(transitionId, tid, position, duration, std::move(props), id, logUndo)) {
469
        id = -1;
470
        pCore->displayMessage(i18n("Could not add composition at selected position"), InformationMessage, 500);
471 472 473 474
    }
    return id;
}

475 476 477
int TimelineController::insertComposition(int tid, int position, const QString &transitionId, bool logUndo)
{
    int id;
478
    int duration = pCore->getDurationFromString(KdenliveSettings::transition_duration());
479
    if (!m_model->requestCompositionInsertion(transitionId, tid, position, duration, nullptr, id, logUndo)) {
480 481 482 483 484 485 486
        id = -1;
    }
    return id;
}

void TimelineController::deleteSelectedClips()
{
487 488
    auto sel = m_model->getCurrentSelection();
    if (sel.empty()) {
489 490 491
        // Check if a mix is selected
        if (m_model->m_selectedMix > -1 && m_model->isClip(m_model->m_selectedMix)) {
            m_model->removeMix(m_model->m_selectedMix);
492 493
            m_model->clearAssetView(m_model->m_selectedMix);
            m_model->requestClearSelection(true);
494
        }
495
        return;
496
    }
497
    // only need to delete the first item, the others will be deleted in cascade
498 499 500 501 502 503 504
    if (m_model->m_editMode == TimelineMode::InsertEdit) {
        // In insert mode, perform an extract operation (don't leave gaps)
        extract(*sel.begin());
    }
    else {
        m_model->requestItemDeletion(*sel.begin());
    }
505 506
}

507
int TimelineController::getMainSelectedItem(bool restrictToCurrentPos, bool allowComposition)
508 509 510 511 512 513 514 515 516 517 518 519
{
    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;
        }
    }
520 521 522 523 524
    if (!restrictToCurrentPos) {
        if (m_model->isClip(itemId) || (allowComposition && m_model->isComposition(itemId))) {
            return itemId;
        }
    }
525
    if (m_model->isClip(itemId)) {
526
        int position = pCore->getTimelinePosition();
527 528 529 530 531 532 533 534 535
        int start = m_model->getClipPosition(itemId);
        int end = start + m_model->getClipPlaytime(itemId);
        if (position >= start && position <= end) {
            return itemId;
        }
    }
    return -1;
}

536 537
void TimelineController::copyItem()
{
538 539
    std::unordered_set<int> selectedIds = m_model->getCurrentSelection();
    if (selectedIds.empty()) {
540 541
        return;
    }
542 543 544 545
    int clipId = *(selectedIds.begin());
    QString copyString = TimelineFunctions::copyClips(m_model, selectedIds);
    QClipboard *clipboard = QApplication::clipboard();
    clipboard->setText(copyString);
546
    m_root->setProperty("copiedClip", clipId);
547
    m_model->requestSetSelection(selectedIds);
548 549
}

550
bool TimelineController::pasteItem(int position, int tid)
551
{
552 553
    QClipboard *clipboard = QApplication::clipboard();
    QString txt = clipboard->text();
554 555 556 557 558 559
    if (tid == -1) {
        tid = getMouseTrack();
    }
    if (position == -1) {
        position = getMousePos();
    }
560
    if (tid == -1) {
561
        tid = m_activeTrack;
562 563
    }
    if (position == -1) {
564
        position = pCore->getTimelinePosition();
565
    }
566
    return TimelineFunctions::pasteClips(m_model, txt, tid, position);
567 568
}

569 570
void TimelineController::triggerAction(const QString &name)
{
571
    pCore->triggerAction(name);
572 573
}

574 575 576 577 578
const QString TimelineController::actionText(const QString &name)
{
    return pCore->actionText(name);
}

579
QString TimelineController::timecode(int frames) const
580 581 582
{
    return KdenliveSettings::frametimecode() ? QString::number(frames) : m_model->tractor()->frames_to_time(frames, mlt_time_smpte_df);
}
583

584 585 586 587 588 589
QString TimelineController::framesToClock(int frames) const
{
    return m_model->tractor()->frames_to_time(frames, mlt_time_clock);
}

QString TimelineController::simplifiedTC(int frames) const
590 591 592 593 594 595 596 597
{
    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;
}

598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617
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();
}

618 619 620 621 622
bool TimelineController::audioThumbNormalize() const
{
    return KdenliveSettings::normalizechannels();
}

623 624 625 626 627 628 629
bool TimelineController::showWaveforms() const
{
    return KdenliveSettings::audiothumbnails();
}

void TimelineController::addTrack(int tid)
{
630 631 632
    if (tid == -1) {
        tid = m_activeTrack;
    }
633
    QPointer<TrackDialog> d = new TrackDialog(m_model, tid, qApp->activeWindow());
634
    if (d->exec() == QDialog::Accepted) {
635 636
        bool audioRecTrack = d->addRecTrack();
        bool addAVTrack = d->addAVTrack();
637
        int tracksCount = d->tracksCount();
638 639
        Fun undo = []() { return true; };
        Fun redo = []() { return true; };
640 641 642 643 644 645 646 647 648 649 650 651 652
        bool result = true;
        for (int ix = 0; ix < tracksCount; ++ix) {
            int newTid;
            result = m_model->requestTrackInsertion(d->selectedTrackPosition(), newTid, d->trackName(), d->addAudioTrack(), undo, redo);
            if (result) {
                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);
653
                }
654 655
                if (audioRecTrack) {
                    m_model->setTrackProperty(newTid, "kdenlive:audio_rec", QStringLiteral("1"));
656
                }
657 658
            } else {
                break;
659 660
            }
        }
661
        if (result) {
662
            pCore->pushUndo(undo, redo, addAVTrack || tracksCount > 1 ? i18n("Insert Tracks") : i18n("Insert Track"));
663 664 665
        } else {
            pCore->displayMessage(i18n("Could not insert track"), InformationMessage, 500);
            undo();
666
        }
667
    }
668 669
}

670
void TimelineController::deleteMultipleTracks(int tid)
671
{
672 673 674
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };
    bool result = true;
675
    QPointer<TrackDialog> d = new TrackDialog(m_model, tid, qApp->activeWindow(), true,m_activeTrack);
676 677 678
    if (tid == -1) {
        tid = m_activeTrack;
    }
679
    if (d->exec() == QDialog::Accepted) {
680
        QList<int> allIds = d->toDeleteTrackIds();
Pushkar Kukde's avatar
Pushkar Kukde committed
681
        for (int selectedTrackIx : allIds) {
682 683 684 685
            result = m_model->requestTrackDeletion(selectedTrackIx, undo, redo);
            if (!result) {
                break;
            }
686 687 688
            if (m_activeTrack == -1) {
                setActiveTrack(m_model->getTrackIndexFromPosition(m_model->getTracksCount() - 1));
            }
689
        }
690 691 692
        if (result) {
          pCore->pushUndo(undo, redo, allIds.count() > 1 ? i18n("Delete Tracks") : i18n("Delete Track"));
        }
693 694 695
    }
}

696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713
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()) {
Vincent Pinon's avatar
Vincent Pinon committed
714
        emit m_model->dataChanged(ix, ix, {TimelineModel::AudioRecordRole});
715 716 717
    }
}

718 719 720
void TimelineController::checkTrackDeletion(int selectedTrackIx)
{
    if (m_activeTrack == selectedTrackIx) {
721 722
            // Make sure we don't keep an index on a deleted track
            m_activeTrack = -1;
723
            emit activeTrackChanged();
724
        }
725 726 727 728
        if (m_model->m_audioTarget.contains(selectedTrackIx)) {
            QMap<int, int> selection = m_model->m_audioTarget;
            selection.remove(selectedTrackIx);
            setAudioTarget(selection);
729 730 731 732
        }
        if (m_model->m_videoTarget == selectedTrackIx) {
            setVideoTarget(-1);
        }
733
        if (m_lastAudioTarget.contains(selectedTrackIx)) {
734
            m_lastAudioTarget.remove(selectedTrackIx);
735 736 737 738 739 740
            emit lastAudioTargetChanged();
        }
        if (m_lastVideoTarget == selectedTrackIx) {
            m_lastVideoTarget = -1;
            emit lastVideoTargetChanged();
        }
741 742
}

743 744
void TimelineController::showConfig(int page, int tab)
{
Vincent Pinon's avatar
Vincent Pinon committed
745
    emit pCore->showConfigDialog(page, tab);
746 747
}

748 749
void TimelineController::gotoNextSnap()
{
750 751 752 753
    if (m_activeSnaps.empty() || pCore->undoIndex() != m_snapStackIndex) {
        m_snapStackIndex = pCore->undoIndex();
        m_activeSnaps.clear();
        m_activeSnaps = pCore->projectManager()->current()->getGuideModel()->getSnapPoints();
754 755
        m_activeSnaps.push_back(m_zone.x());
        m_activeSnaps.push_back(m_zone.y() - 1);
756 757
    }
    int nextSnap = m_model->getNextSnapPos(pCore->getTimelinePosition(), m_activeSnaps);
758
    if (nextSnap > pCore->getTimelinePosition()) {
759 760
        setPosition(nextSnap);
    }
761 762 763 764
}

void TimelineController::gotoPreviousSnap()
{
765
    if (pCore->getTimelinePosition() > 0) {
766 767 768 769
        if (m_activeSnaps.empty() || pCore->undoIndex() != m_snapStackIndex) {
            m_snapStackIndex = pCore->undoIndex();
            m_activeSnaps.clear();
            m_activeSnaps = pCore->projectManager()->current()->getGuideModel()->getSnapPoints();
770 771
            m_activeSnaps.push_back(m_zone.x());
            m_activeSnaps.push_back(m_zone.y() - 1);
772 773
        }
        setPosition(m_model->getPreviousSnapPos(pCore->getTimelinePosition(), m_activeSnaps));
774
    }
775 776
}

777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808
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);
    }
}

809 810
void TimelineController::groupSelection()
{
811 812
    const auto selection = m_model->getCurrentSelection();
    if (selection.size() < 2) {
813 814 815
        pCore->displayMessage(i18n("Select at least 2 items to group"), InformationMessage, 500);
        return;
    }
816 817 818
    m_model->requestClearSelection();
    m_model->requestClipsGroup(selection);
    m_model->requestSetSelection(selection);
819 820 821 822
}

void TimelineController::unGroupSelection(int cid)
{
823
    auto ids = m_model->getCurrentSelection();
824
    // ask to unselect if needed
825 826
    m_model->requestClearSelection();
    if (cid > -1) {
827 828 829 830
        ids.insert(cid);
    }
    if (!ids.empty()) {
        m_model->requestClipsUngroup(ids);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
831
    }
832 833
}

834 835 836 837 838 839 840
bool TimelineController::dragOperationRunning()
{
    QVariant returnedValue;
    QMetaObject::invokeMethod(m_root, "isDragging", Q_RETURN_ARG(QVariant, returnedValue));
    return returnedValue.toBool();
}

841 842
void TimelineController::setInPoint()
{
843 844
    if (dragOperationRunning()) {
        // Don't allow timeline operation while drag in progress
845
        qDebug() << "Cannot operate while dragging";
846 847 848
        return;
    }

849
    int cursorPos = pCore->getTimelinePosition();
850
    const auto selection = m_model->getCurrentSelection();
851
    bool selectionFound = false;
852 853
    if (!selection.empty()) {
        for (int id : selection) {
854 855 856 857 858 859
            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);
860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879
            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);
                }
            }
880 881 882 883 884 885
        }
    }
}

void TimelineController::setOutPoint()
{
886 887
    if (dragOperationRunning()) {
        // Don't allow timeline operation while drag in progress
888
        qDebug() << "Cannot operate while dragging";