timelinecontroller.cpp 156 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 "dialogs/speechdialog.h"
36
#include "doc/kdenlivedoc.h"
37
#include "effects/effectsrepository.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
38
#include "effects/effectstack/model/effectstackmodel.hpp"
Nicolas Carion's avatar
linting  
Nicolas Carion committed
39
#include "kdenlivesettings.h"
40
#include "lib/audio/audioEnvelope.h"
41 42
#include "mainwindow.h"
#include "monitor/monitormanager.h"
Nicolas Carion's avatar
Nicolas Carion committed
43
#include "previewmanager.h"
Nicolas Carion's avatar
linting  
Nicolas Carion committed
44
#include "project/projectmanager.h"
45
#include "timeline2/model/clipmodel.hpp"
46
#include "timeline2/model/compositionmodel.hpp"
47
#include "timeline2/model/groupsmodel.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
48 49
#include "timeline2/model/timelineitemmodel.hpp"
#include "timeline2/model/trackmodel.hpp"
50
#include "timeline2/view/dialogs/clipdurationdialog.h"
Nicolas Carion's avatar
Nicolas Carion committed
51
#include "timeline2/view/dialogs/trackdialog.h"
Nicolas Carion's avatar
Nicolas Carion committed
52
#include "transitions/transitionsrepository.hpp"
53
#include "audiomixer/mixermanager.hpp"
54
#include "ui_import_subtitle_ui.h"
55

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

int TimelineController::m_duration = 0;

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

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

void TimelineController::prepareClose()
{
96
    // Clear root so we don't call its methods anymore
97
    QObject::disconnect( m_deleteConnection );
98 99 100
    disconnect(this, &TimelineController::selectionChanged, this, &TimelineController::updateClipActions);
    disconnect(this, &TimelineController::videoTargetChanged, this, &TimelineController::updateVideoTarget);
    disconnect(this, &TimelineController::audioTargetChanged, this, &TimelineController::updateAudioTarget);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
101 102
    m_ready = false;
    m_root = nullptr;
103
    // Delete timeline preview before resetting model so that removing clips from timeline doesn't invalidate
104 105
    delete m_timelinePreview;
    m_timelinePreview = nullptr;
106
    m_model.reset();
107 108
}

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

143 144 145 146 147
void TimelineController::restoreTargetTracks()
{
    setTargetTracks(m_hasVideoTarget, m_model->m_binAudioTargets);
}

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

185 186 187 188 189
std::shared_ptr<TimelineItemModel> TimelineController::getModel() const
{
    return m_model;
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
190 191
void TimelineController::setRoot(QQuickItem *root)
{
192
    m_root = root;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
193
    m_ready = true;
194 195 196 197 198 199 200
}

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

201 202 203 204 205
Mlt::Producer TimelineController::trackProducer(int tid)
{
    return *(m_model->getTrackById(tid).get());
}

206 207 208 209 210
double TimelineController::scaleFactor() const
{
    return m_scale;
}

211
const QString TimelineController::getTrackNameFromMltIndex(int trackPos)
212
{
213
    if (trackPos == -1) {
214 215
        return i18n("unknown");
    }
216
    if (trackPos == 0) {
217 218
        return i18n("Black");
    }
219
    return m_model->getTrackTagById(m_model->getTrackIndexFromPosition(trackPos - 1));
220 221
}

222 223
const QString TimelineController::getTrackNameFromIndex(int trackIndex)
{
224
    QString trackName = m_model->getTrackFullName(trackIndex);
225
    return trackName.isEmpty() ? m_model->getTrackTagById(trackIndex) : trackName;
226 227
}

228 229 230 231
QMap<int, QString> TimelineController::getTrackNames(bool videoOnly)
{
    QMap<int, QString> names;
    for (const auto &track : m_model->m_iteratorTable) {
232
        if (videoOnly && m_model->getTrackById_const(track.first)->isAudioTrack()) {
233 234
            continue;
        }
235
        QString trackName = m_model->getTrackFullName(track.first);
236
        names[m_model->getTrackMltIndex(track.first)] = trackName;
237 238
    }
    return names;
239 240
}

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

252 253 254
void TimelineController::setScaleFactor(double scale)
{
    m_scale = scale;
255 256 257
    // Update mainwindow's zoom slider
    emit updateZoom(scale);
    // inform qml
258 259 260 261 262 263 264 265
    emit scaleFactorChanged();
}

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

266 267 268 269 270
int TimelineController::fullDuration() const
{
    return m_duration + TimelineModel::seekDuration;
}

271 272 273 274 275 276 277 278 279
void TimelineController::checkDuration()
{
    int currentLength = m_model->duration();
    if (currentLength != m_duration) {
        m_duration = currentLength;
        emit durationChanged();
    }
}

280
int TimelineController::selectedTrack() const
281
{
282 283 284 285 286
    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);
287
        selected_tracks.emplace_back(m_model->getTrackPosition(tid), tid);
288
    }
289 290 291
    // 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;
292 293
}

294 295
void TimelineController::selectCurrentItem(ObjectType type, bool select, bool addToCurrent)
{
296 297 298 299 300 301 302
    int currentClip = -1;
    if (type == ObjectType::TimelineClip) {
        currentClip = m_activeTrack == -2 ? m_model->getSubtitleByPosition(pCore->getTimelinePosition()) : m_model->getClipByPosition(m_activeTrack, pCore->getTimelinePosition());
    } else {
        currentClip =  m_model->getCompositionByPosition(m_activeTrack, pCore->getTimelinePosition());
    }

303
    if (currentClip == -1) {
304
        pCore->displayMessage(i18n("No item under timeline cursor in active track"), ErrorMessage, 500);
305 306
        return;
    }
307 308 309 310
    if (!select) {
        m_model->requestRemoveFromSelection(currentClip);
    } else {
        m_model->requestAddToSelection(currentClip, !addToCurrent);
311 312 313 314 315
    }
}

QList<int> TimelineController::selection() const
{
316
    if (!m_root) return QList<int>();
317
    std::unordered_set<int> sel = m_model->getCurrentSelection();
318
    QList<int> items;
319
    for (int id : sel) {
320 321 322
        items << id;
    }
    return items;
323 324
}

325 326 327 328 329
int TimelineController::selectedMix() const
{
    return m_model->m_selectedMix;
}

330 331 332 333 334 335
void TimelineController::selectItems(const QList<int> &ids)
{
    std::unordered_set<int> ids_s(ids.begin(), ids.end());
    m_model->requestSetSelection(ids_s);
}

336 337 338 339 340 341 342
void TimelineController::setScrollPos(int pos)
{
    if (pos > 0 && m_root) {
        QMetaObject::invokeMethod(m_root, "setScrollPos", Qt::QueuedConnection, Q_ARG(QVariant, pos));
    }
}

343 344 345 346 347 348
void TimelineController::resetView()
{
    m_model->_resetView();
    if (m_root) {
        QMetaObject::invokeMethod(m_root, "updatePalette");
    }
349
    emit colorsChanged();
350 351
}

352 353
bool TimelineController::snap()
{
354 355 356
    return KdenliveSettings::snaptopoints();
}

357 358 359 360 361 362 363 364 365 366
bool TimelineController::ripple()
{
    return false;
}

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

367
int TimelineController::insertClip(int tid, int position, const QString &data_str, bool logUndo, bool refreshView, bool useTargets)
368 369
{
    int id;
370
    if (tid == -1) {
371
        tid = m_activeTrack;
372 373
    }
    if (position == -1) {
374
        position = pCore->getTimelinePosition();
375
    }
376
    if (!m_model->requestClipInsertion(data_str, tid, position, id, logUndo, refreshView, useTargets)) {
377 378 379 380 381
        id = -1;
    }
    return id;
}

382 383 384 385 386 387 388
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) {
389
        position = pCore->getTimelinePosition();
390 391 392 393 394 395
    }
    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;
}

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

468
    std::unique_ptr<Mlt::Properties> props(nullptr);
469
    if (revert) {
Nicolas Carion's avatar
Nicolas Carion committed
470
        props = std::make_unique<Mlt::Properties>();
471 472 473 474 475 476 477 478
        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");
        }
    }
479
    if (!m_model->requestCompositionInsertion(transitionId, tid, position, duration, std::move(props), id, logUndo)) {
480
        id = -1;
481
        pCore->displayMessage(i18n("Could not add composition at selected position"), ErrorMessage, 500);
482 483 484 485
    }
    return id;
}

486 487 488
int TimelineController::insertComposition(int tid, int position, const QString &transitionId, bool logUndo)
{
    int id;
489
    int duration = pCore->getDurationFromString(KdenliveSettings::transition_duration());
490
    if (!m_model->requestCompositionInsertion(transitionId, tid, position, duration, nullptr, id, logUndo)) {
491 492 493 494 495 496 497
        id = -1;
    }
    return id;
}

void TimelineController::deleteSelectedClips()
{
498 499
    auto sel = m_model->getCurrentSelection();
    if (sel.empty()) {
500 501 502
        // 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);
503 504
            m_model->clearAssetView(m_model->m_selectedMix);
            m_model->requestClearSelection(true);
505
        }
506
        return;
507
    }
508
    // only need to delete the first item, the others will be deleted in cascade
509 510 511 512 513 514 515
    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());
    }
516 517
}

518
int TimelineController::getMainSelectedItem(bool restrictToCurrentPos, bool allowComposition)
519 520 521 522 523 524 525 526 527 528 529 530
{
    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;
        }
    }
531 532 533 534 535
    if (!restrictToCurrentPos) {
        if (m_model->isClip(itemId) || (allowComposition && m_model->isComposition(itemId))) {
            return itemId;
        }
    }
536
    if (m_model->isClip(itemId)) {
537
        int position = pCore->getTimelinePosition();
538 539 540 541 542 543 544 545 546
        int start = m_model->getClipPosition(itemId);
        int end = start + m_model->getClipPlaytime(itemId);
        if (position >= start && position <= end) {
            return itemId;
        }
    }
    return -1;
}

547 548
void TimelineController::copyItem()
{
549 550
    std::unordered_set<int> selectedIds = m_model->getCurrentSelection();
    if (selectedIds.empty()) {
551 552
        return;
    }
553 554 555 556
    int clipId = *(selectedIds.begin());
    QString copyString = TimelineFunctions::copyClips(m_model, selectedIds);
    QClipboard *clipboard = QApplication::clipboard();
    clipboard->setText(copyString);
557
    m_root->setProperty("copiedClip", clipId);
558
    m_model->requestSetSelection(selectedIds);
559 560
}

561
bool TimelineController::pasteItem(int position, int tid)
562
{
563 564
    QClipboard *clipboard = QApplication::clipboard();
    QString txt = clipboard->text();
565 566 567 568 569 570
    if (tid == -1) {
        tid = getMouseTrack();
    }
    if (position == -1) {
        position = getMousePos();
    }
571
    if (tid == -1) {
572
        tid = m_activeTrack;
573 574
    }
    if (position == -1) {
575
        position = pCore->getTimelinePosition();
576
    }
577
    return TimelineFunctions::pasteClips(m_model, txt, tid, position);
578 579
}

580 581
void TimelineController::triggerAction(const QString &name)
{
582
    pCore->triggerAction(name);
583 584
}

585 586 587 588 589
const QString TimelineController::actionText(const QString &name)
{
    return pCore->actionText(name);
}

590
QString TimelineController::timecode(int frames) const
591 592 593
{
    return KdenliveSettings::frametimecode() ? QString::number(frames) : m_model->tractor()->frames_to_time(frames, mlt_time_smpte_df);
}
594

595 596 597 598 599 600
QString TimelineController::framesToClock(int frames) const
{
    return m_model->tractor()->frames_to_time(frames, mlt_time_clock);
}

QString TimelineController::simplifiedTC(int frames) const
601 602 603 604 605 606 607 608
{
    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;
}

609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
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();
}

629 630 631 632 633
bool TimelineController::audioThumbNormalize() const
{
    return KdenliveSettings::normalizechannels();
}

634 635 636 637 638 639 640
bool TimelineController::showWaveforms() const
{
    return KdenliveSettings::audiothumbnails();
}

void TimelineController::addTrack(int tid)
{
641 642 643
    if (tid == -1) {
        tid = m_activeTrack;
    }
644
    QPointer<TrackDialog> d = new TrackDialog(m_model, tid, qApp->activeWindow());
645
    if (d->exec() == QDialog::Accepted) {
646 647
        bool audioRecTrack = d->addRecTrack();
        bool addAVTrack = d->addAVTrack();
648
        int tracksCount = d->tracksCount();
649 650
        Fun undo = []() { return true; };
        Fun redo = []() { return true; };
651 652 653 654 655 656 657 658 659 660 661 662 663
        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);
664
                }
665 666
                if (audioRecTrack) {
                    m_model->setTrackProperty(newTid, "kdenlive:audio_rec", QStringLiteral("1"));
667
                }
668 669
            } else {
                break;
670 671
            }
        }
672
        if (result) {
673
            pCore->pushUndo(undo, redo, addAVTrack || tracksCount > 1 ? i18nc("@action", "Insert Tracks") : i18nc("@action", "Insert Track"));
674
        } else {
675
            pCore->displayMessage(i18n("Could not insert track"), ErrorMessage, 500);
676
            undo();
677
        }
678
    }
679 680
}

681
void TimelineController::deleteMultipleTracks(int tid)
682
{
683 684 685
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };
    bool result = true;
686
    QPointer<TrackDialog> d = new TrackDialog(m_model, tid, qApp->activeWindow(), true,m_activeTrack);
687 688 689
    if (tid == -1) {
        tid = m_activeTrack;
    }
690
    if (d->exec() == QDialog::Accepted) {
691
        QList<int> allIds = d->toDeleteTrackIds();
692
        for (int selectedTrackIx : qAsConst(allIds)) {
693 694 695 696
            result = m_model->requestTrackDeletion(selectedTrackIx, undo, redo);
            if (!result) {
                break;
            }
697 698 699
            if (m_activeTrack == -1) {
                setActiveTrack(m_model->getTrackIndexFromPosition(m_model->getTracksCount() - 1));
            }
700
        }
701 702 703
        if (result) {
          pCore->pushUndo(undo, redo, allIds.count() > 1 ? i18n("Delete Tracks") : i18n("Delete Track"));
        }
704 705 706
    }
}

707 708 709 710 711 712
void TimelineController::switchTrackRecord(int tid)
{
    if (tid == -1) {
        tid = m_activeTrack;
    }
    if (!m_model->getTrackById_const(tid)->isAudioTrack()) {
713
        pCore->displayMessage(i18n("Select an audio track to display record controls"), ErrorMessage, 500);
714 715 716 717 718 719 720 721 722 723 724
    }
    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
725
        emit m_model->dataChanged(ix, ix, {TimelineModel::AudioRecordRole});
726 727 728
    }
}

729 730 731
void TimelineController::checkTrackDeletion(int selectedTrackIx)
{
    if (m_activeTrack == selectedTrackIx) {
732 733
            // Make sure we don't keep an index on a deleted track
            m_activeTrack = -1;
734
            emit activeTrackChanged();
735
        }
736 737 738 739
        if (m_model->m_audioTarget.contains(selectedTrackIx)) {
            QMap<int, int> selection = m_model->m_audioTarget;
            selection.remove(selectedTrackIx);
            setAudioTarget(selection);
740 741 742 743
        }
        if (m_model->m_videoTarget == selectedTrackIx) {
            setVideoTarget(-1);
        }
744
        if (m_lastAudioTarget.contains(selectedTrackIx)) {
745
            m_lastAudioTarget.remove(selectedTrackIx);
746 747 748 749 750 751
            emit lastAudioTargetChanged();
        }
        if (m_lastVideoTarget == selectedTrackIx) {
            m_lastVideoTarget = -1;
            emit lastVideoTargetChanged();
        }
752 753
}

754 755
void TimelineController::showConfig(int page, int tab)
{
Vincent Pinon's avatar
Vincent Pinon committed
756
    emit pCore->showConfigDialog(page, tab);
757 758
}

759 760
void TimelineController::gotoNextSnap()
{
761 762 763 764
    if (m_activeSnaps.empty() || pCore->undoIndex() != m_snapStackIndex) {
        m_snapStackIndex = pCore->undoIndex();
        m_activeSnaps.clear();
        m_activeSnaps = pCore->projectManager()->current()->getGuideModel()->getSnapPoints();
765 766
        m_activeSnaps.push_back(m_zone.x());
        m_activeSnaps.push_back(m_zone.y() - 1);
767 768
    }
    int nextSnap = m_model->getNextSnapPos(pCore->getTimelinePosition(), m_activeSnaps);
769
    if (nextSnap > pCore->getTimelinePosition()) {
770 771
        setPosition(nextSnap);
    }
772 773 774 775
}

void TimelineController::gotoPreviousSnap()
{
776
    if (pCore->getTimelinePosition() > 0) {
777 778 779 780
        if (m_activeSnaps.empty() || pCore->undoIndex() != m_snapStackIndex) {
            m_snapStackIndex = pCore->undoIndex();
            m_activeSnaps.clear();
            m_activeSnaps = pCore->projectManager()->current()->getGuideModel()->getSnapPoints();
781 782
            m_activeSnaps.push_back(m_zone.x());
            m_activeSnaps.push_back(m_zone.y() - 1);
783 784
        }
        setPosition(m_model->getPreviousSnapPos(pCore->getTimelinePosition(), m_activeSnaps));
785
    }
786 787
}

788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819
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);
    }
}

820 821
void TimelineController::groupSelection()
{
822 823 824 825 826
    if (dragOperationRunning()) {
        // Don't allow timeline operation while drag in progress
        pCore->displayMessage(i18n("Cannot perform operation while dragging in timeline"), ErrorMessage);
        return;
    }
827 828
    const auto selection = m_model->getCurrentSelection();
    if (selection.size() < 2) {
829
        pCore->displayMessage(i18n("Select at least 2 items to group"), ErrorMessage, 500);
830 831
        return;
    }
832 833 834
    m_model->requestClearSelection();
    m_model->requestClipsGroup(selection);
    m_model->requestSetSelection(selection);
835 836 837 838
}

void TimelineController::unGroupSelection(int cid)
{
839 840 841 842 843
    if (dragOperationRunning()) {
        // Don't allow timeline operation while drag in progress
        pCore->displayMessage(i18n("Cannot perform operation while dragging in timeline"), ErrorMessage);
        return;
    }
844
    auto ids = m_model->getCurrentSelection();
845
    // ask to unselect if needed
846 847
    m_model->requestClearSelection();
    if (cid > -1) {
848 849 850 851
        ids.insert(cid);
    }
    if (!ids.empty()) {
        m_model->requestClipsUngroup(ids);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
852
    }
853 854
}

855 856 857 858 859 860